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

大纲

《从汇编层看64位程序运行------ROP攻击以控制程序执行流程》一文中,我们介绍了如何使用ROP攻击来修改程序执行流程。但是这个方案存在一个问题,就是《从汇编层看64位程序运行------有惊无险的栈溢出》中所说的,会导致foo回到main函数后,RSP寄存器的值会比正确的大0x08(即栈缩小了0x08)。这是因为整个方案call了一次,ret了两次,即压栈一次,退栈两次。虽然不会导致程序执行出现问题,但是终究是不完美的。

代码

本文我们将介绍一种更简单、更安全的方案。

先看代码,请问程序输出的是0还是1999?

c 复制代码
#include <stdio.h>
#include <string.h>

int foo() {
    return 1999;
}

int 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)ptr;
    __asm__("pop %rbp\n\t");
    __asm__("push -0x8(%rbp)\n\t");
    __asm__("push %rbp\n\t");
    return 0;
}

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

main函数中只调用了foo7,它返回的是0。foo7中也没调用foo,更没显式的返回foo的返回值。那么这个程序打印的是0吗?答案是"不是,它输出的是1999"。

这是因为foo7被ROP攻击了,导致其return后进入foo函数。而foo7和foo函数的返回值都是通过rax寄存器传递,于是main函数直接使用了从foo函数退出后的rax的值。这样,程序打印的是1999。

在这个方案中,我们没有对foo函数做任何汇编修改。但是会保证执行流程回到main函数后,rsp、rbp寄存器都是正确的。这是因为我们在foo7函数中,把栈的结构做了修改,模拟了一次push栈的操作。这样一次call、一次push、2次ret,最终栈是平衡的。

分析

我们在调用foo7之前下断点,查看此时rbp、rsp的值。它们分别为:0x7fffffffdf10和0x7fffffffdef8。

然后在call foo7之后下断点,可以看到rbp、rsp的值回到了call之前的状态。所以栈是平衡的。

我们看下foo7的汇编代码

+36行会将rbp寄存器的值从栈中pop出来,然后将foo的地址push到栈中,这样我们就构造了foo7要返回到foo函数中的基本设置。后面又见rbp寄存器的值push到栈中,是为了后续系统自动生成的pop %rbp做准备。

我们没有在foo函数中做修改的原因是:进入foo函数后,它就会拿到rbp寄存器,而此时rbp寄存器是main函数的rbp。这样如果foo中有任何栈上数据的修改,都会影响到main函数的栈帧数据正确性,即溢出了。所以我们将修改逻辑的代码放在foo7函数中。

相关推荐
大P哥阿豪19 分钟前
Go defer(二):从汇编的角度理解延迟调用的实现
开发语言·汇编·后端·golang
花小璇学linux22 分钟前
imx6ull-裸机学习实验1——汇编LED灯实验
linux·汇编·imx6ull·arm裸机开发
看到我,请让我去学习2 小时前
OpenCV编程- (图像基础处理:噪声、滤波、直方图与边缘检测)
c语言·c++·人工智能·opencv·计算机视觉
倔强的小石头_4 小时前
【C语言指南】函数指针深度解析
java·c语言·算法
jz_ddk10 小时前
[学习] C语言数学库函数背后的故事:`double erf(double x)`
c语言·开发语言·学习
无小道11 小时前
c++-引用(包括完美转发,移动构造,万能引用)
c语言·开发语言·汇编·c++
FirstFrost --sy12 小时前
数据结构之二叉树
c语言·数据结构·c++·算法·链表·深度优先·广度优先
森焱森13 小时前
垂起固定翼无人机介绍
c语言·单片机·算法·架构·无人机
秋说15 小时前
【PTA数据结构 | C语言版】两枚硬币
c语言·数据结构·算法
☆璇16 小时前
【数据结构】栈和队列
c语言·数据结构