我在 Linux 上常用的 hexeditor: sudo apt install ncurses-hexedit
執行後長這樣,隨便輸入會跳 Incorrect Password
先用 exeinfope,看起來沒有殼
用 ida32 打開後反編譯 WinMain
只有呼叫一個函式 DialogBoxParamA,其中第四個參數 DialogFunc
就是指向 dialogbox 的 procedure 指標。
因此點入 DialogFunc
,這是一個 stdcall
再點進 sub_401080
基本上就是用 GetDlgItemTextA
取出我們的輸入放到 變數 String
,接著做一些判斷後再決定要跳出 Incorrect password
還是 Congratulations
。
照 stack 的順序,在 String
是 E
,v3
是 a
,v4
是 a5y
,v5
是 R3versing
所以整串 flag 就是 Ea5yR3versing
執行後長這樣,要我們輸入 Name 和 Serial,根據 Readme,要找到 Serial 為 5B134977135E7D13
對應的 Name
沒加殼
打開 ida 看 main
,有兩個 scanf,分別是 Name
和 Serial
。
重點是迴圈中的部分,它對我們的 Name
做了一些事。sprintf 第一個參數是目標字串位址,第二個是 format,之後的就是 format 中的代入值。
所以 Name
的每一個字元都會變成 hex 的形式,最後合成一個字串跟 Serial
做比對,寫個 script 跑出 flag K3yg3nm3
執行後一片空白,這題 ReadMe 說要 Find the OEP,也就是說很可能有殼
看來是個不知名的殼,可能是出題者自己做的,有關殼的科普看這裡
由於脫殼後通常會有一個很大的 jmp,所以就直接用 ida 解掉,得知 OEP 為 401150
另一個方法是用 debugger,慢慢執行到有很大的跳轉的地方為止
執行起來長這樣,丟給它一個 mp3 檔案就會開始播放,只播一分鐘就會停掉並且跳出一個訊息框(msgbox)。Readme 說要讓它播放超過一分鐘
沒殼
這題給了一個 exe 和 dll,先用 ida 看 exe,似乎看不出什麼特別的
再用 ida 看 dll 的部分,太大了,要先想辦法找地方下手
首先先想辦法找到它跳出訊息框的函式,再來看它是根據什麼來決定是否顯示訊息框的。
用 x32dbg 動態分析,一直 f9 往下跑,直到音樂跑了一分鐘跳出訊息框為止。這時查看呼叫堆疊(call stack),看看這個訊息框是哪個函式
得知訊息框的函式位址為 72A1D132
,而 call 它的位址在 4045DE
,於是點兩下後會跳到 4045DE
的 CPU 畫面
最後就是要看它判斷是否呼叫訊息框的地方,往上滑可以看到
其中 0xEA60 = 60000,也就是一分鐘,把 jl
改成 jmp
,結果跳出錯誤
這次把斷點下在 4045FE
也就是之前改的 jl
的目的地位址。在 59 秒時下斷點後會停在這,接著按 F8 一步步執行,看在那裡會出錯。
在到 60 秒之前,這個 004046AB | jge music_player.4046BF |
都會跳轉,只有在 60 秒到達時才會繼續往下走後引發 exception。所以把這邊的 jge
也改成 jmp
試試看。執行完就拿到 flag L1stenCare
沒殼,32-bit 的 C++ program
執行後隨便輸入後就關掉了
開 ida 分析後發現在 DialogFunc()
有一行紅字,也許是程式中寫入的,sub_40066F 也沒有辦法分析
只好動態分析,開 x32dbg,搜尋字串引用後發現 Correct
跟進去下斷點,在 40466F
也下一個斷點
執行後按 F7 跟進 40466F
,基本上這段做的就是將我們的輸入加 2,再加 0x601605C7,最後再 +1 +1。之後跳去 0x406090,其中會把剛剛算出來的數字的當成位址,並把那位址和那位址+1 填成 0x90(nop)
看到這邊目標就很明確了,由於 jmp replace.404690
的後面跟著 jmp replace.401084
,所以只要把 jmp replace.401084
填成兩個 nop,就可以輸出 Correct
算個數學就可以拿到 flag 2687109798
沒殼,C++ 寫的
執行之後跳出一個視窗,中間空白可以畫畫,下面有個 Check 按鈕,隨便亂畫按下按鈕會出現 Wrong
開 ida 靜態分析,先看 WinMain,其中程式註冊了一個 class,其中 lpfnWndProc 是 sub_401130
點進 sub_401130
觀察一下後發現有一行 call 了 MessageBoxA,其中 Text 是 Wrong
於是目前目標就是讓程式不要執行到這一行。在這一行之前有個 while 迴圈,只要成功執行 90000 次,就會直接 return。
而 while 的比較條件為 *bitmap_index == bitmap_index[v14]
,這部分可以用動態分析去看實際的內容是什麼。
用 x32dbg 打開後,在 while 迴圈前的位址下斷點(找到位址的方法可以用 ida,把游標點在 while 迴圈的前一行後按下 tab,查看底下顯示的位址)。執行後先什麼都不用畫,直接按 Check,應該會停在剛設的斷點
下面有個 cmp dl, bl
就是 while 迴圈的比較條件,而 dl 來自 [ecx],bl 來自 [ecx + eax],也就是說剛剛 ida 看到的 bitmap_index
應該就是這兩個其中一個
先看看 [ecx],在下面資料視窗中按下 ctrl+g
,輸入 ecx 現在的值 42F0048
,可以看到連續 90000 個 FF
,可以猜到這應該是我們的輸入,因為我們剛剛什麼都沒畫就直接交出去
再來看看 [ecx + eax],先算出
也是一堆 FF
,不過有好幾個是 00
,看來這就是我們要的,所以現在把這一段 dump 下來,在下面命令中輸入 savedata dump.mem, 47E060 , 15F90
,savedata
是用來 dump 記憶體的指令,第一個參數是檔名,第二個參數是開始的位址,第三個參數是大小,參數分別以逗點隔開
dump 完後,由於這是 bitmap(一種圖片格式),不過少了文件頭,需要自己填上
我是用 HxD 打開 dump.mem,可以在前面插入文件頭,先參考 bitmap 文件頭格式。先到網路上隨便下載一個 bmp 檔案並用 HxD 打開,因為文件頭有 54 bytes,所以複製前 54 bytes 到 dump.mem 的開頭
接著要修改幾個欄位,首先是 filesize(2~6 bytes),這是整個檔案大小,也就是原本的 90000 再加上 54 bytes 文件頭
90000+54 = 0x15fc6
。再來是寬度(0x12~0x16),從 ida 的 CreateCompatibleBitmap
可以看到是 200 = 0xc8
。最後是高度(0x16~0x1A),150 = 0x96
。其他不用動,改完後大概長這樣
最後把副檔名改成 bmp,就可以打開拿到 flag GOT
沒殼,C++ 寫的
ReadMe 說要找到對應到 76876-77776
的 name,這個 name 共四個字,最後一個字是 p
執行起來長這樣,看起來目標很明確,就是要讓中間的字不是 Wrong
用 ida 靜態分析,打開 wWinMain 會發現不知道下一步不知道去哪了,只有一個 AfxWinMain()
,可以看這個了解,基本上就是會呼叫一個 virtual function,這部分 ida 不知道在哪。但是打開 sub_401CD0
可以看到關鍵判斷式
其中呼叫了 sub_401740
,點進去後看到很長一串程式,裡面也能看到兩個 GetWindowTextW()
分別取出我們輸入的 name 和 serial
基本上後面的部分就是在對我們的 name 做一些操作後跟 serial 做比較,這部分可以用 z3 寫一個 script,就可以拿到 flag bump
沒殼,C++ 寫的
執行後長得跟小時候玩的 CSO 很像,基本操作是控制人物的移動和發射子彈,每隻怪物要打好幾槍才死掉
用 ida 打開後看 WinMain,裡面初始化很多遊戲的設定,一開始最讓我感興趣的是有一行 MessageBoxA(hWnd, "Game Over! You are dead", "ReversingKr - FPS Game", 0x40u);
,只要更改 407020
位址的值,就可以更改血量,不過讓自己變成無敵也不能怎樣
第二個讓我感興趣的是 sub_4039C0
,點進去可以看到 MessageBoxA(hWnd, byte_407028, "Game Clear!", 0x40u);
,裡面用 while 迴圈持續判斷 result
位址的值是否為 1
,如果是的話就加 132 * 4
(因為是 int),直到 result >= 0x40F8B4
所以目標是讓所有的 result + 132 * 4 * n
位址的值都變成 1。打開 x32dbg 跟著,會發現每打死一隻怪物,就會有其中一個 result
變成 0。
於是用 x32dbg,在那個函數下一個斷點,把每個怪物的記憶體的那個 byte 都改為 0,會跳出 Game Clear!,但是沒有 flag,只有一串密文,很可能是在殺死怪物的過程中有做類似解密的動作
其實從剛剛在 sub_4039C0
的 MessageBoxA(hWnd, byte_407028, "Game Clear!", 0x40u);
就知道了,那串 byte_407028
就是一串密文,游標指著它按下 x 做查詢,可以看到另一個函數有引用它
裡面有做 xor 解密,基本上就是每殺死一隻怪物,就會把相對應的字元做解密。所以只要把這個密文跟 byte_409184[v1 * 4] 做 xor。
動態分析可以查看 byte_409184 位址的值,就是 0, 4, 8, 12, … 等差數列。ida 和 x32dbg 的位址只有 offset 一樣,所以要自己看一下實際位址為何
最後逆回來就可以拿到 flag Thr3EDPr0m
有 upx 殼,可以下載脫殼工具解
ReadMe.txt 說 Decrypt File (EXE)
,可能代表它給的 file 是一個 exe,只是被加密了
執行後先輸出一段亂碼,再讓我們輸入 key,之後又噴出一些亂碼
脫殼完用 ida 打開看 _main,要反編譯時卻說檔案太大 4135E0: too big function
開 x32dbg 直接一步一步跟,直到要進入 _main(0x4135E0)
進去 0x4135E0,看到很多垃圾,先 push 再 pop,結果跟原本一樣的那種。原來檔案太大就是因為這些垃圾,所以可以把它們全部填成 nop(不能刪掉不然會出錯)
下面這幾行可以幫我們 patch 成 nop
不過在垃圾前面的 function prologue(55 8B EC 83 EC 24 53 56 57
) 需要搬到一堆 nop 的尾端當作 function 的開頭,不然一樣會因為 function 太大而反編譯不了。
修補完後就可以用 ida 反編譯分析,在做的事情就是把 file 的每一個字元跟我們輸入的 key 做 xor 後再 not,即 file[i] = ~(file[i] ^ key[i % len(key)])
所以要拿到 key 的方法就是把 file 取 not 後跟正常的 exe 做 xor 就可以拿到 key letsplaychess
不過這還不是 flag,因為還沒將 file 進行解密
用差不多的方法將 file 解密後會拿到一個 exe,執行後就拿到 flag Colle System
有私有殼,ReadMe 說要用 x86 來跑
執行後可以輸入,但是輸入後就直接卡在那了
IDA 打開後基本上沒有頭緒,也看不到大的跳轉,不過很明顯就是有殼
拿去 x32dbg 動態分析,會發現如果直接按 F9 繼續執行,程式會有 Exception,可能中間有 antidebugger,所以單步執行來看發生什麼事。
首先,讓程式產生 Exception 的是 40107D
的 call,最終程式是死在 402052
,所以就按 F7 跟進去
之後會有一個迴圈(407063~40706D
)在修改 .text 段的值,這部分就是在殼在做事了,這部分就直接 F4 到迴圈結束的下一行就好
繼續執行,不管遇到什麼都繼續執行,現在主要是要找關鍵程式碼。執行到 4070EB
跟進去就直接 call 402050
Exception 了。推測是在4070EB
的 call 之前要滿足什麼條件才不會跑到這行,所以這邊就先改 ZF 後跳過去
接著又是很多個 self-modifying 步驟,直到後面有個迴圈(407139~40715B
),它檢查某一段 memory 的值是否為 ABABABAB
或 EEFEEEFE
,如果是的話就會跟上一個一樣跳到 402050
產生 Exception,所以這邊在 407155
把 ecx 改成 1F4
後跳過去就好
然後又接著各種 self-modifying 步驟,self-modifying 程式好玩的地方就在於,很多時候以為接下來要執行的程式看起來怪怪的要死掉時,在最後一刻就又會被變成可以執行的程式。
在一個大 jmp 後,來到 40157C
,明顯就是 OEP,這時終於可以用 Scylla 來 dump 出脫殼後的程式。打開 Scylla 後,在 OEP 輸入 4015C7
,按下 IAT Autosearch,最後按 Dump,就可以拿到一個脫殼後的執行檔
拿到 ida 看,可以看到函數已經被正常解析了,雖然 main 不能 decompile,不過也不長,就直接看就好。
基本上就是先印出 Reversing.kr CrackMe
和 Input
後,接著 call 401240
,最後判斷要輸出 Correct
還是 Wrong
其中 401240
應該就是把輸入做一些操作,這個函數可以 decompile
可以看到它最後 JUMPOUT 到 40720D
,而這個位址在 ida 上靜態看是不能跑的,這部分是因為 Scylla 沒有把 40720D
的值正確 dump 下來
所以一種方法是直接再動態分析原本的 exe,只是每次都要再重過一次前面的關;另一種方法是直接把那部分的程式 patch 好。因為我也不太確定要 patch 的範圍,所以就用第一種方法。
所以拿原始檔案跑到 OEP 後繼續執行,直到 40162B
call 401270
,也就是 main,按 F7 跟進去
跑到 40129B
會發現程式死掉了,這是因為它存取到了不可存取的 address。這邊就直接把它 patch 成 nop,或是調整 EIP 跳過去就好。
然後就是剛剛 ida 看到的那三個步驟,輸入完後就可以繼續執行(建議輸入 abcdefghij
)。跑到 4012C6
後就 call 了 401240
,這邊也是 ida 剛看到的,按 F7 跟進去
到目前為止都是 ida 看得到的,直到 40720D
後就要純看組語了。到這裡可以看到我們的輸入被放在 40B970
執行到 40726B
時,會檢查某兩 Byte 是不是 B8
和 BA
,如果不是的話就無窮迴圈,所以這邊就把那兩個 byte 改好繼續執行
接著繼續執行到 407294
的地方,跟進去又會發現程式死掉了,這邊直接 patch 成 ret,我猜是出題者故意放的陷阱
再來又執行到 4072C9
,這邊要是執行下去的話程式又要死了。而在這之前 4072C4
有做 cmp
判斷是否要執行,所以這邊把 eax
改成 C0000353
就好
到了 407437
,call 了 407305
會死掉,所以看是要改 al
還是 ZF
都可以。407463
同理
407405
把第一個字元和第七個字元放到 40B990
和 40B991
407488
把放在 40B991
的值給了 bl,也就是我們輸入的第七個字元,然後 xor 0x36
接下來做了很多不需要理會的操作後,407600
把剛剛的第七個字元 xor 0x36 的值拿出來跟 0x36 比必須相等,否則程式會死掉,也就是說第七個字元是 \x00
在 40760F
取出放在 40B990
的輸入的第一個字元後, 407614
要跟進去,會先把它 ror al, 6
存到 40B000
,再 rol 4 存到 40B001
,最後 xor 34 存到 40B002
407700
把 40B000
的字元取出後存到 cl,在 4076D0
終於做了 cmp cl, 49
,就是說第一個字元 = rol(0x49, 6)
繼續執行到 4076E9
,可以看到它把我們的輸入一個一個拿出來放到暫存器,在 40774F
又存到 memory
第三個字元在 407783
取出,xor 77 後,在 4077A3
做 cmp 要等於 0x35
第二個字元在 407800
取出,在 4077AC
做 xor,最後在 4077C5
做 cmp 要等於 0x69
接著又是第一個字元,但是 407817
這裡不同的是,如果相等的話反而程式會結束掉,可能是出題者在防範我們這些解題者XD
第四個字元在 40790E
取出,xor 21 後要等於 0x64
第五和第六個字元以此類推,最後可以寫一個 python script,算出 flag RIBENA
這次是給個 ELF,先拿去 ida 靜態分析
其中檢查我們的輸入,如果不正確就會 return 0
所以就是把輸入照著它要求的算出來就可以拿到 flag L1NUX
沒殼
這題要在 x86 執行,並且要給權限
用 ida 打開後,最關鍵的部分在 401110
,其中有按下 Enable 或 Check 按鈕後執行的操作
這個判斷式的重點在它 call 401280
來判斷結果為何。於是看看 401280
做了什麼。其中它 call 了 DeviceIoControl
,將第二個參數 dwIoControlCode
傳入
這部分就是在跟 driver 互動,因此現在得分析另一個檔案 WinKer.sys
打開後首先先看看 DriverEntry
,其中 call 了兩個函數,一個是用來 debug 的 14005
,另一個才是我們關心的程式操作。除非對 Windows 的資料結構很了解,不然直接看這部分會有點辛苦。主要是第 37 行有設了 DRIVER_OBJECT 的 Dispatch routine;還有第 44 行的 KeInitializeDpc
,簡單來說就是會開一個 thread 去跑目標函數
先看 11288
,裡面處理個剛剛我們看到的 dwIoControlCode
0x1000 與 0x2000,我們主要要關注的是 0x2000 的部分,因為這才是判斷輸出 Correct!
還是 Wrong
的地方,其中指是把 13030
設為
0 和設定 Irp
再來是 11266
,裡面很簡單,就是監聽在 0x60 port,也就是 keyboard,然後 call 111DC
,參數為監聽到的值
111DC
中就是在判斷輸入的值是什麼,而依照鍵盤的 scan code 對照表,就可以得出應該要輸入的值,例如 case 1
就是 -91
,換成 unsigned char 是 0x92,就是對應到鍵盤的 K
可能會遇到的坑
11156
和 110D0
裡面都有 xor,11156
要 xor 0x12,而 110D0
要 xor 0x12 和 0x5,因為每次按下鍵盤都會從 111DC
最後就可以拿到 flag keybdinthook
PS. 之後查了一下 Driver 要怎麼做動態分析,沒想到步驟挺繁雜的,之後要找時間研究,附個 MicroSoft 的 Manual
有 UPX 殼
執行後可以輸入東西,按下 OK 後視窗就關閉了
但是脫殼完後執行卻會出錯,看起來不像是解壞了,因為如果是解壞了,應該會像是 Ransomware 那題解壞一樣,沒有辦法執行
用 ida 靜態分析,發現有一大堆函數,先看看 WinMain()
也不知道在做什麼
所以先按 shift + F12 查查字串引用,找到 EXE corrupted
,有兩個地方引用它,稍微逆向一下這部分
發現前面有個 if 判斷式,其中呼叫了 sub_4508C7
,程式裡面對檔案本身做了一些檢查,要看懂這段程式得要對 fread、fseek 有一定的認識
最後這個 z 會被丟入 sub_450ABA
做一堆運算,但是結果沒拿來做什麼,有點可疑。
所以用 x32dbg 下去跟,繞過前面檢查的方法就是只要把GetModuleFileNameA()
的檔名改掉,變成原本的檔案就好
跟進去後發現剛剛 ida 中那個 sub_450ABA
的第二個參數就是 0x20,也就是 MD5 的輸出長度。所以直接跟到這個 function 結束前,看看運算結果儲存的地方的值(a1)就好。可以發現有一串 MD5 在記憶體中 220226394582d7117410e3c021748c2a
,再拿去線上工具解得到半個 flag isolated
另一半的 flag 可以猜到就是跟我們的輸入有關
一樣得先找到關鍵程式碼,點選 ida 中的 import,可以看到引用的函式庫,把用到 GetWindowText 或 GetMessageA 的位址都下斷點,看看按下 OK 後會停在哪,最後鎖定了 DialogFunc
動態分析找 GetWindowTextA()
後輸入所在的位址,向上拉可以發現一串 MD5 54593f6b9413fc4ff2b4dec2da337806
,解出來是 pawn
其實如果繼續跟也是可以,在輸入所在的位址下硬體讀取的斷點,教學在這。在 0x457A00 這個位址中,程式比較了我們的輸入是否等於另一個字串,而那個字串就是剛剛我們往上翻找到的那一串 MD5
兩半就可以組成 flag isolated pawn
執行後長這樣,空白視窗
看來是 C# 寫的,雖然看題目名稱也知道
用 dnspy 丟進去可以 decompile,它直接把 W54RE6MIPSP6S
寫在程式中了,不過順序可能不對
所以根據變數名稱,並對應著按鈕位置,可以找到 flag P4W6RP6SES
不過後來發現較快的兩個解法
拿到兩個 exe,分別為 Original.exe 和 Packed.exe。
Original.exe 沒有殼,執行後直接跳出視窗說 Congratulation! Password is ??????????
Packed.exe 有不知名的殼,執行後可以輸入東西,但是隨意輸入後沒有反應
既然 Orginal.exe 沒有殼,就先逆它看看。用 ida 看一看,發現就只是把兩個字串做 xor,最後輸出剛好都是 ?
。目前不知道這個 exe 是做什麼用的,但是可以想到 Packed.exe 脫殼後應該就會做跟 Original.exe 差不多的事
接著是 Packed.exe,先用 ida 看看有沒有可疑的東西,結果看了一看沒什麼想法。用 x32dbg 動態分析,按 F9 後停在了 EntryPoint,這邊有一個 call,如果直接 F8,整個程式就這樣跳出輸入視窗了,所以跟進去看看裡面做了什麼
查找 String Reference 沒發現可疑的東西。瘋狂的 F8 後,會發現最後有個地方執行完之後終於跳出輸入視窗
跟進去看看發現原來這是 GetMessageA
,而下面兩個 call 分別是 TranslateMessage
和 DispatchMessage
。中間有兩個 je
,第一個是檢查我們輸入的東西是不是 WM_QUIT
,所以這不用理它。第二個則是檢查 [ebp+402A3E]
是不是 0,所以有可能 GetMessageA
的回調函數中會做一些判斷改這個值。
這邊可以用 ida 的交叉引用功能,先在那個位址游標指著 byte_402A3E[ebx]
後按下 x
,可以看哪裡有用到它,這邊能看到除了 4090A8
外,還有 4091A8
也使用了它
所以用 x32dbg 看看 4091A8
的 asm,可以看到的確這個地方幫 byte_402A3E[ebx]
做 inc
而判斷要不要 inc 的是上面的 cmp eax, 0xE98F842A
,而這邊的 eax 又是上面 cmp eax, 0xE98F842A
的回傳值,於是我們在這邊下一個斷點後,在輸入的視窗輸入東西就會停在這邊
在這個 call 按 F7
跟進去,在這裡對我們的輸入做了很多操作。不難看懂這段在做什麼,就是對我們輸入的每一個字元做 xor, add, ror, xor 重複 0x10000 次。但是這邊很難爆出來,因為假設正確的輸入有 n 個,並且都是英文數字([A-Za-z0-9]),要爆的數量也有 74^n,基本上 n 如果大於 4,就有點困難了,何況我們不知道 n 等於多少。所以這先放在備案,先繼續逆
在 4090A8
,把 byte_402A3E[ebp]
這個值改成 1,之後就可以跟下去
往下跟幾步後在 4090CB
有個 call,看了一下會發現它後面會對 0x401000 做一些操作,可能這裡就是解密的部分。而前面有兩個 call 4091DA
這邊又是拿我們的輸入去做跟剛剛很難爆不出來的那邊一樣的事情
前面兩個 call 完的結果會分別存在 eax 和 ebx,接著就是針對 0x401000 去解密,而解密的結果應該就會跟 Original.exe 的 0x401000 一樣。所以要怎麼知道 eax 和 ebx 分別是多少? 首先 eax 可以知道,因為那一句 xor dword ptr ds:[edi], eax
,可以知道 eax 就是 Original.exe 和 Packed.exe 的 0x401000(一次 4 byte) 做 xor
那 ebx 呢? 因為 ebx 是 32 bit,也就是最多只要試 0xFFFFFFFF 次就可以爆完。而且我們也知道下一輪的 eax 會是 Original.exe 和 Packed.exe 的 0x401004 做 xor
因此只要把正確的 ebx 爆出來理論上就可以讓程式解密正確
在 x32dbg 塞回去就可以讓程式正常解密並執行,最後爆出 ebx 是 0xb2f098e8
,但是這個 ebx 是已經執行過 rol ebx, cl
的 ebx,所以 ebx 要在執行過 409226
之後才塞回去。而 eax 是最一開始的 eax,所以 eax 要在 40921F
執行前塞回去
塞回去後繼續執行就噴出 flag From_GHL2_!!
這次給的不是 exe,先用 file 指令看看
丟入 IDA 靜態分析,在 sub_2224 發現接收輸入的程式碼
簡單來說就是將我們的輸入拿去做一些處理,最後跟 byte_3004 去做對比,所以就是要找到對英的輸入,所以將那段逆回來就可以拿到 flag Do_u_like_ARM_instructi0n?:)
這題是個 Linux 題,用 file SimpleVM
得到 SimpleVM: ELF 32-bit LSB executable, Intel 80386, version 1 (GNU/Linux), statically linked, stripped
用 ida 查看發現有噴錯 Illegal program entry point (C023DC)
,不過這不影響執行。如果要修正的話,就用 hexeditor 查看一下 SimpleVM 這個檔案,會發現它總共的長度是到 13EA
。然而若輸入 readelf -a SimpleVM
,fileSiz, MemSiz 卻都是 13c7
所以要把這兩項用 hexeditor 改掉,改成 13EA
,注意是 Little Endian
然而拖入 ida 後什麼都沒有,所以直接執行看看。執行後,出現 Input :
,隨便輸入後噴出 Wrong
用 gdb 打開它,run
之後 ctrl + C,接著打 vmmap
看它的記憶體配置,可以看到 0x8048000 ~ 0x804b000
這一段,用 dump memory mem 0x8048000 0x804b000
把這段記憶體 dump 出來。這段應該就是主要被執行的程式碼,那為什麼是這段呢? 純粹用猜的,一開始我把每一段都 dump 出來看過了
把 dump 出來的 mem 丟到 ida 後可以正常反組譯,接著查詢字串可以發現 Input :
在 8048556
中,於是現在要做的事情就是分析它。
首先,輸入 Input
那行看起來就是使用 write
,那其他的呢? 可以透過位址的 offset 來判斷其他 libcall 是誰。在 gdb 中輸入 vmmap
後,會出現 libc 的基底位址,拿 write
的位址減去這個基底位址就是 offset
但是那個 8048460
並不是 write
在 libc 的位址,而是會先跳到 8048460
後再去 call libc 的 write
。所以在 gdb 中輸入 x/i 0x8048460
會出現 0x8048460: jmp DWORD PTR ds:0x804b018
,就是說位址會跳到 0x804b018
中存的值。那再看看 0x804b018
中存的值是什麼,輸入 x/wx
可以得到 0xf7eb96f0
,拿這個位址再去剪掉剛剛說的 libc 的基底位址就可以算出 offset。同理,所以若要知道其他 libcall 是什麼,就只要做相同的步驟後,拿得到的位址減去基底位址就可以算出 offset。
之後,用 objdump -M intel -d /lib32/libc-2.27.so
,就可以利用剛剛算出來的 offset 得到對應的 libcall。整理之後會發現各位址對應的 libcall 如下:
到這裡就能大概看懂程式在做什麼了,首先會先 fork,造出一個 child process,接著這個 process 會先讀我們輸入的 flag,並檢查這個 flag <= 8 (含換行)
。然後把 0x804B0A0
中的 200 bytes 每個 byte 做一些操作,這部分不逆沒關係,因為檢查 flag 正不正確的地方在 parent process。最後把 flag 和 0x804B0A0
的 200 bytes 傳給 parent process
再來是 parent process 的部分,首先它在接收到 child process 送來的 flag 和 0x804B0A0
後,會把 0x804B0A0
的每個 byte xor 0x20,接著把 flag 塞在 0x804B0A0
後的前八個 byte,最後再把 0x804B0A0
的每個 byte xor 0x10
做完上述操作後,透過 804BC6D
這個 function 檢查 flag 的正確性,正確就輸入 correct,否則 wrong
關於 8048C6D
這一段,裡面有一個 while 和一個 switch,基本上就是判斷 dword_804B190
中的值做判斷,如果是 0xB 就離開迴圈,0xA 也會離開迴圈,但是是失敗的
再來是裡面這些 function,有看懂它們在做什麼,不過想不出在不知道 flag 的情況下逆回去的方法。所以我的策略就是,假設 flag 越接近正確答案就會在迴圈內執行越多次,那只要每次只換 flag 的其中一個字元,其他字元不變,選擇執行次數最高的當正確的。雖然這做法可能會有例外,不過值得一試,最後也成功了。
不過現在還差一樣東西,就是 0x804B0A0
那 200 bytes 是什麼。這可能就必須動態分析去拿了,先執行 SimpleVM,然後在另一個 terminal 輸入 ps auwxx | grep SimpleVM
會出現兩行
選擇第一個 attach 上去 gdb attach [PID]
,因為第二個是 child process,之後輸入 b *0x8048C6D
,把斷點斷在 0x8048C6D
,最後輸入 c
。在剛剛執行 SimpleVM 的那個 terminal 應該目前是需要我們輸入,於是隨便輸入 aaaaaaa
,這時 gdb 應該就會斷在 0x8048C6D
。這是輸入 x/25gx 0x804b0a0
就可以把 0x804b0a0
的 200 bytes dump 下來 dump memory 804b0a0.mem 0x804b0a0 0x804b0a0+200
現在有了 0x804b0a0 的 200 bytes 和前面剛說的那個假設,就可以嘗試寫 script 去爆破,程式碼看起來很長,但是其實都只是把 ida 上的每個 function 改成 python 而已。最後成功得到 flag id3*ndh
32-bit exe,有 UPX 殼
看起來是 C++ 寫的 x64 執行檔
拿去 ida64 分析,先看 wmain。基本上就是要輸入六個數字,拿去跟亂數產生的六個數字做比對
解到這裡有兩個想法,第一個是直接靜態分析在跳出迴圈後它做了什麼事情;第二個是動態分析,查看那六個亂數後輸入,以符合條件。兩種方法應該都可以,但是我選擇第二種。
打開 x64dbg,一步一步跟著,直到跟到要輸入數字的地方,先隨便輸入 6 個 1
接著要找到 rand() 產生的 6 個亂數,所以直接執行到跑完產生亂數的迴圈後,查看 stack,比對 ida64 的位址(esp + 0x54),就可以找到我們的輸入和產生的亂數
之後把這六個亂數都改成 1,繼續執行就會拿到 flag Password is from_GHL2_-_!
又是一題 C#
執行後會要求輸入,隨意輸入後按 Check 會出現 Wrong
一樣丟到 dnspy 反編譯,在 InitializeComponent()
中發現 Check
button 按下後的 function btnCheck_Click()
,其中又發現它 call 了 MetMetMet
寫了很長一段,其實就是把 [1, 2] + base64(input)
當作參數丟入 form1.bb
這個函數,而 form1.bb 則是在 Form1()
定義的
而 Form1()
中對 form1.bb
就是把 MetMett()
修改了幾個字元,難怪原本的 MetMett()
在反編譯時會出錯
所以這邊可以用動態分析,一步一步跟到修改後的函數內,在 invoke
時 F11 跟入
如果跟入之後的程式看不懂,可以去查那是什麼再決定要不要跟入,不然就都跟入最保險。總之最後會到達 MetM()
其實就是比對輸入的每個字元是否等於某個字元而已,所以逆回來後 base64 decode 就拿到 flag dYnaaMic
給了一個 swf 檔,所以去下載一個 Adobe Flash Player 來執行,再下載 JPEXS Free Flash Decompiler 做分析。載入 player 後,畫面如下
丟入 Decompiler 後,可以看到很多元件,例如 buttons, texts, frames, … 在 frames 中可以看到這個 flash 總共有七個畫面,展開後可以看到每個畫面下所包含的元件,這些元件可以用後面的括弧數字來辨別
最重要的部分在 scripts 中,裡面包含著點選每個 button 後所要做的動作,但是看起來有做過混淆,因此可以在 Settings 將 Automatic deobfuscation 選項勾選
勾選完畢後就可以清楚看到每個 button 要做的動作,例如點選 DefineButton2(4) 可以看到 if(spw == 1456)
,那 spw
是什麼?點選剛剛 frames 中的 frame1,可以看到對應的 PlaceObject2(4) 和 PlaceObject2(6),再點選 texts 的 DefineButton2(6),可以看到 variablename
是 spw
也就是說在第一格輸入 1456
,就可以通過判斷到第三個畫面(因為 gotoPlay(3)
),而第三個畫面也是有一個 if
,只是這次變數名稱變為 spwd
,用剛剛同樣的方式可以找到對應的 texts,而通過判斷後會有做一些操作並到下一個畫面,以次類推
所以根據程式流程,最後發現順序為 1, 3, 4, 2, 6, 5,分別對應到的數字為 1456, 25, 44, 8, 88, 20546
使用 Player 跑一次,分別輸入上述數字,最後可以拿到 flag 16876
PS. 不確定有沒有人跟我一樣發現自己算的結果卻跟實際執行算出來的不同,這部分我的想法是根據這篇文章,因為代碼有混淆過,所以 JPEXS 解錯了,或是漏了一些東西,不過沒有實際驗證過
樸實無華的 32-bit exe
用 ida 打開看看,程式邏輯很簡單。輸入長度 8 的字串,然後把這八個字元分別塞進一個長度為 256 的 aHelloWelcomeTo 字串中。接著跑 256 次迴圈遍歷剛剛改的字串,做一些跟 sub_401000
產生的值做運算後,把結果比對判斷對錯。
這題就是一題數學題,基本上運算過程不可逆,因此得用爆破的方式。但是 8 個字元複雜度太高,所以得用前後夾擊的方式算。也就是說,從前面算爆破 4 個 bytes 到中間,另一方面又從後面爆破 4 個 bytes 到中間,然後比對兩者的值,如果一樣就代表正確。
從前面爆破到後面很簡單,就只要照著 ida 的程式照寫就好,但是從後面爆破回來就會面臨到不知道 v11 是多少的困境。
一開始只用 ida 逆向會覺得第 63 行的 v12 = v9 >> 8
只會影響後面那行的 LODWORD(v9) = v12 ^ dword_4085E8[2 * v11];
,但是其實後面那行的 HIDWORD(v9)
也要右移 8 bits 後才做 xor。這部分我是用 x32dbg 去追才知道的。
知道了這點之後,可以確定的是 v9 最高的那 byte 在 xor 前一定都是 0x00。xor 之後就會變成 dword_4085EC
的最高位,因此我們就能透過比對 dword_4085EC
的 index 知道當次迴圈的 v11。
舉第一個例子來說,最後比對的 v13 為 1735352167
也就是 0x676f5f67
。要從後面逆回去時,找 dword_4085EC
中誰的最高位是 0x67,就可以得出 index。
那有沒有可能會從 dword_4085EC
對應到兩個 index? 答案是沒有,原因應該是可以從 sub_401000
知道,但是我是直接用行動證明了,確定 dword_4085EC
的每個最高位不重複
最後寫了 python script,跑了十幾分鐘終於找到 flag CrCA1g@!
拿到一個 jar 檔案,直接拿去線上 decompiler(要選 CFR),反編譯後會有一個 java 檔案,其中只有一個 class
很明顯就是要輸入一個 64-bit 的 singed int l,讓 l * 26729 = -1536092243306511225
。然而 -1536092243306511225
無法整除 26729
,可能是 overflow 了。所以就跑一個迴圈,初始值為 -1536092243306511225
,每次加 2^64
,直到這個數可以整除 26729
跑出來的結果是 9468659231510783855
,交出去發現不對。因為要轉成 singed int,所以直接拿這個數減 2^64
就是 flag -8978084842198767761