0x01 32位程序栈溢出
Ret2libc 溢出
5B pop ebx
5E pop esi
5F pop edi
5D pop ebp
IDA中,按 alt + B 选择hex 搜索 5B 5E 5F 5D 即可定位Pop3_ret的位置
一次溢出地址,算出偏移,跳转到至libc
junk + ebp + ret_address + Pop3_ret + 参数1 + 参数2 + … + 参数n + ret_vul
junk + ebp + ret_address + Pop3_ret + 参数1 + 参数2 + … + 参数n + getShell_address
复杂的多次跳转溢出
junk + ebp +ret_address1 + Pop3_ret + 参数1 + ret_address2 + Pop3_ret + 参数1 + 参数2 +ret_address3 _ Pop3_ret + 参数 ……
0x02 64位程序栈溢出
在x64程序中,最麻烦的莫过于传参,这里我将对传参的技巧进行详细的讲解
传参顺序 RDI, RSI, RDX, RCX, R8, R9
只需要在通过ROPgadget工具就可以获得程序中的大部分RET和POP代码
1 | ROPgadget --binary 程序名 --only "ret|pop" |
但是我平时遇到的题目大部分并没有 pop rdx pop rcx等
其实只要通过奇数位执行代码就会出现上述指令
1 | pop rdx=5A |
由上述代码可以知道,通过奇数位可以得到以下代码
1 | pop r10 -> pop rdx |
这样就几乎满足了大部分的参数传递问题
只要参数逻辑传递正确,且过程中参数保证不会被改变,基本上都可以拿到shell了,如果出现参数改变,那就要对ret_address进行调整,对其进行偏移,最好直接一步到关键call上。
0x02 GOT表覆盖
程序有canary保护,现在存在一个流程 read printf read printf,两次read的长度都比较短,那么可以考虑覆盖got表
第一次read 读入 __stack_chk_fail 的 got地址,然后第二次read时,找到第一次输入数据的偏移,然后向这个偏移写入可以重复rop的偏移
根据实验!!(百度杯 what_the_fuck)
第一次读入 __stack_chk_fail 的 got地址
第二次读入 “%.2435d%12$hn %9%$s%10$ld”
解释:先输出2435(即0x983)个字符,后面的%12$hn向第12偏移(即第一次输入的__stack_chk_fail 的 got地址)所指向的地址写入0x983
注意注意!!!由于延迟绑定,__stack_chk_fail_got的值在第一次调用时的值为__stack_chk_fail__plt + 1,由于 __stack_chk_fail__plt 和 用户代码(例如main、sub_405531等)十分近,所以通过%xd就可以控制一开始输出文本的长度,并将其长度的数值写入__stack_chk_fail_got,实现覆盖got,所以每次执行__stack_chk_fail_got都会跳转到覆盖后的地址(第一次__stack_chk_fail_got+一开始输出文本的长度)
为了更清楚表示,以下将列出具体变化
1 | 触发__stack_chk_fail后, |
__stack_chk_fail_got(ds:ptr[0x0000000000601020] = 00000000004006C6) 由于延迟绑定所以它的值等于 __stack_chk_fail_plt + 6 (00000000004006C0 + 6) 至于为什么加6,这是因为下一个汇编指令的地址是 00000000004006C6
猜测:学长说通过修改read_got可以在下一次执行read的时候跳转到int 0x80,因为在libc中read_plt下方很近的位置就有int 0x80,假设我们可以配好eax等参数,便可以直接跳转到int 0x80,进入中断函数,直接拿到shell