ROP小结
这次RCTF,对于本以为掌握了的ROP,学到了新的姿势,在这里总结下。
本文不进行实例调试,用脑子DEBUG……详细文件可以去我的github上找…
RCalc
首先是计算器这题,作者自己实现了一个canary,首先在每个函数开头通过sub_400AAB
函数生成了一个随机数,存放到堆中,和栈上面,然后在函数结尾使用sub_400B92
函数检查这个栈上的随机数和堆中的随机数是否一样。
然后可以去看看sub_400A06
函数,在存放canary的堆上面有个一个0x100的堆,用于存放需要保存的计算结果,这个结果保存的函数没设定边界值,所以可以覆盖到canary的堆,从而覆盖到canary。
绕过canary后,在sub_400FA2
函数中,scanf函数存在栈溢出,正常情况下想,之后就是通过ROP很容易就能getshell了。
但是,这里有一个坑点,scanf函数的%s
不能出现\x09
, \x0a
, \x0b
, \x0c
, \x0d
, \x20
经过测试,如果输入中出现这几个字符,会被转成\x00
,或者之后的数据就不会被读入变量中。
这对我来说非常致命,因为got表的地址中都含有\x20
,还有一些ROP被这些字符限制着,当时还想到了一个别的思路,比如利用read
函数或者sub_400c4e
函数,但是没找到控制rdx的ROP所以没法用read
函数,另外,就算能调用,也没有思路下一步该怎么做,该读到什么位置?然后该怎么通过read
继续溢出?
而关于sub_400c4e
函数,虽然函数中含有\x0c
字符,但是我找到了一个
1 | add eax, 0x48002018 ; test eax, eax ; je 0x400803 ; call rax |
通过这个ROP就能调用sub_400c4e
了,而rdx
为最后一次choice输入的值,因为处理这个输入值得时候有个cdqe,虽然如果我输入0x100000005在判断中也是5,通过这个思路,可以让sub_400c4e
函数中进行溢出,rdi和rsi也都是可控的。
但是在测试中发现,首先我不知道栈地址,所以没法控制rsi,而在sub_400FA2
函数的ret指令时,rsi正好就是一个栈地址,在当前栈地址的很上面,这种情况下,栈溢出会覆盖掉sub_400c4e
的局部变量,导致没法成功进行栈溢出。
在比赛结束之后,看来国外的一篇wp后,学到了两个知识点: http://hama.hatenadiary.jp/entry/2017/05/22/092142
首先是他们找的ROP:
1 | mov rdx, r13 ; mov rsi, r14 ; mov edi, r15d ; call qword [r12+rbx*8]; |
通过这个ROP基本可以调用任意函数了,我找ROP一般是使用默认的命令:
1 | $ ROPgadget --binary RCalc |
但是却没有这个ROP,需要用
1 | $ ROPgadget --binary RCalc --depth 11 |
才有第二句ROP,而第一句还是没有。。所以我发现,我在找ROP上还是太菜了….
UPDATE 20170528
经过大佬教育,原来这是64位程序中存在的一个万能ROP,这两句ROP在同一个函数里,而这个函数是gcc编译进程序中去的
其实,当时我已经基本可以做到调用任意函数了,但是关键点还是第二点。
使用:
1 | leave ; ret ; |
来修改栈地址,这样一来思路就很清晰了。
找一段可以写的地址,比如bss区域,写入ROP,然后再把rsp修改成该地址,就可以getshell了,同read函数向bss区域写值,然后使用leave
修改rsp
我还是太菜,从来没想过修改栈地址……
Recho
本来也是一道简单的栈溢出,但循环的判断:
1 | while(read(0, buf, 0x10)>0) |
要栈溢出首先得先停了这个循环,在shell中可以使用ctrl+d表示EOF,但是脚本咋写?
发现pwntools可以用下面的命令发送EOF
1 | s = remote(xxxx,xx) |
但是这样我们没法继续输入了,所以我们需要发送一次payload就getflag,我们只能getflag而不能getshell,因为服务器已经关闭了接收我们数据的连接。
这题没有libc,所以写ROP又是一个技术活….
使用下面这个PoC:
1 | #!/usr/bin/env python |
经过远程和本地测试对比,发现远程的libc应该和我本地的一样
然后使用本地的libc写payload就好了。。
思路是改写got表中随便一个函数的地址改成system就好了,比如我修改的是atoi函数,然后找binary中的ROP,找到下面三个:
1 | pop rdi; ret; |
通过这三个,我们就能修改偏移了,比如:
1 | payload += p64(0x4008a3)+p64(0x601040) # pop rdi; ret; rdi = 0x601040; atoi |
这相当于got表中atoi函数的地址加上0xe510
同样再使用上面的ROP往bss中写入cat flag
,最后输出的指令是system('cat flag')
总结
本以为栈溢出都会了,其实还是太菜…..