从汇编层看64位程序运行——ROP攻击以控制程序执行流程

一般我们运行有调用关系的代码,就像套娃一样,一个套着一个。比如main函数调用foo7函数,foo7调用foo函数,则main函数要先进入foo7,然后进入foo。等foo执行完,会回到foo7;等foo7执行完,再回到main。
在这里插入图片描述
那么我们有办法脱离这种套娃模式,然后foo函数执行返回到main函数吗?
在这里插入图片描述
当然是有的。
这就需要使用ROP攻击。
ROP攻击全称是:Return Oriented Programming (ROP) attacks。它是指我们通过修改栈中保存的Next RIP地址,来达成控制程序运行流程的方法。
《从汇编层看64位程序运行——函数的调用和栈平衡》中,我们介绍了call指令会将本函数中下一个要执行的汇编指令地址push到栈中。这个地址就是我们需要攻击的地址。
在这里插入图片描述
如果我们将上述地址改成foo函数的入口地址,并将foo的Next RIP(后面也称ret rip)地址改成foo7本来应该返回到main函数中的地址,则就可以达成一次完整的攻击。
在这里插入图片描述
我们看下具体步骤。
一般情况下,我们找到ret rip有两种方法:

  • 无栈传递参数时,是ebp + 0x08。
  • 有栈传递参数时,即可以使用ebp + 0x08,又可以用最后一个栈传递参数地址减去0x08。

这次我们需要改变两个栈帧中的ret rip的值,于是上述方法我们各采用一次。

代码讲解

void foo7(unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e, unsigned int f, void* g) {
    void* ptr = (void*)&g;
    void* next_rip = (void*)((unsigned long long)ptr - 8);
    unsigned long long rip = *((unsigned long long*)next_rip);
    rip = rip * 1;
    memcpy(next_rip, &g, 8);
    __asm__("mov -0x8(%rbp), %rdi\n\t");
}

foo7一共有7个参数,我们在《从汇编层看64位程序运行——参数传递的底层实现》中分析过这个案例。它的前6个参数会通过rdi、rsi、rdx、rcx、r8d和r9d这几个寄存器来传递到foo中,而第7个参数g则是通过栈传递。

int main() {
    void* g = (void*)&foo;
    foo7(1, 2, 3, 4, 5, 6, g);
    return 0;
}

我们让g这个参数等于foo函数地址,然后直接调用了foo7。这样call foo7指令压入栈的ret rip就会紧挨着g这个参数在栈中的位置。因为栈的增长方向是向地址小的方向发展,所以这个g在栈上的地址减去0x08,就得到foo7执行完要返回到main函数中的地址。

void* next_rip = (void*)((unsigned long long)ptr - 8);

对于原始要返回的地址,我们需要记录下,以让foo执行完返回到main中。

unsigned long long rip = *((unsigned long long*)next_rip);
rip = rip * 1;

然后我们就可以着手将foo7栈帧中的ret rip改成foo的入口地址

memcpy(next_rip, &g, 8);

这次我们按照传递参数的规律,将foo7最后一个局部变量rip的值保存到rdi寄存器中。后面foo函数会直接从rdi中获取这个地址。这么做的原因是,我们没有通过编译器调用foo,所以它不会帮我们构建参数传递的过程。这个过程就需要我们手工进行。

__asm__("mov -0x8(%rbp), %rdi\n\t");

进入foo函数,我们就将变量取出,直接覆盖foo的ret rip。

void foo(void* return_rip) {
    __asm__("mov %rdi, 0x08(%rbp)\n\t");
    printf("Hello from foo!\n");
}

这样,下面代码的执行结果是输出“Hello from foo!”。即foo函数被执行一次。

#include <stdio.h>
#include <string.h>

void foo(void* return_rip) {
    __asm__("mov %rdi, 0x08(%rbp)\n\t");
    printf("Hello from foo!\n");
}

void foo7(unsigned int a, unsigned int b, unsigned int c, unsigned int d, unsigned int e, unsigned int f, void* g) {
    void* ptr = (void*)&g;
    void* next_rip = (void*)((unsigned long long)ptr - 8);
    unsigned long long rip = *((unsigned long long*)next_rip);
    rip = rip * 1;
    memcpy(next_rip, &g, 8);
    __asm__("mov -0x8(%rbp), %rdi\n\t");
}

int main() {
    void* g = (void*)&foo;
    foo7(1, 2, 3, 4, 5, 6, g);
    return 0;
}

在这里插入图片描述

调试讲解

分析完代码后,我们通过调试来验证这个过程。
在这里插入图片描述
+12到+23,汇编代码将foo的地址压入栈中。我们在+23处下断点,看看没压栈时栈的状态。
在这里插入图片描述
然后单步一次,看看压栈完的状态。可以看到foo的地址被压栈,rsp的值减少了0x08。
在这里插入图片描述
一直到+58处call指令,中间的指令都没有压栈和出栈行为。我们直接跳到call指令,然后单步进去,然后查看栈的状态。
在这里插入图片描述
我们看到call指令压入的值0x00005555555551fb就是理论上foo7要返回到main函数的地址。
在这里插入图片描述

void* ptr = (void*)&g;
void* next_rip = (void*)((unsigned long long)ptr - 8);
unsigned long long rip = *((unsigned long long*)next_rip);

上面这块代码对应于
在这里插入图片描述

-0x18(%rbp)保存的是ptr;-0x10(%rbp)保存的是next_rip ;-0x8(%rbp)保存的是rip。

memcpy(next_rip, &g, 8);

上面代码对应如下面图
在这里插入图片描述
我们让程序执行到+67处,观察它和运行到+70处的栈的变化。
在这里插入图片描述
可以看到foo7的返回地址变成了foo函数的入口地址。
foo7在退出前,会把上一栈帧的rbp还原,我们再看还原后的栈。
在这里插入图片描述
此时栈顶就是foo的地址了,这样foo7的ret执行完,会跳转到foo函数
在这里插入图片描述
我们在foo函数中插入的汇编被执行后,foo函数栈帧的ret ip就会被修改成main函数的地址了。
在这里插入图片描述
这样等foo执行ret后,程序会自动回到main函数
在这里插入图片描述
后面代码会恢复栈平衡,于是整个程序可以正常运行。

0x00005555555551fb <+63>:    add    $0x8,%rsp

最近更新

  1. docker php8.1+nginx base 镜像 dockerfile 配置

    2024-07-17 07:30:01       67 阅读
  2. Could not load dynamic library ‘cudart64_100.dll‘

    2024-07-17 07:30:01       72 阅读
  3. 在Django里面运行非项目文件

    2024-07-17 07:30:01       58 阅读
  4. Python语言-面向对象

    2024-07-17 07:30:01       69 阅读

热门阅读

  1. OpenResty使用Lua笔记

    2024-07-17 07:30:01       25 阅读
  2. Springboot定义阿里云oss工具类

    2024-07-17 07:30:01       24 阅读
  3. 入门 git

    2024-07-17 07:30:01       21 阅读
  4. IPython 的 %history -p 命令:探索命令行历史的秘籍

    2024-07-17 07:30:01       30 阅读
  5. [NOIP2006 提高组] 作业调度方案(含代码)

    2024-07-17 07:30:01       19 阅读
  6. OpenSearch 第三方IoT设备日志分析

    2024-07-17 07:30:01       29 阅读
  7. Photoshop

    Photoshop

    2024-07-17 07:30:01      20 阅读
  8. Github07-16 Python开源项目日报 Top10

    2024-07-17 07:30:01       23 阅读
  9. 用于图像增强的学习型可控ISP

    2024-07-17 07:30:01       28 阅读