Reverse Cheat Sheet

解題思路
- 先觀察
- File Cmd
- Detect It Easy(有無加殼)
- 靜態
- IDA
- 有可能要追一下
.init/.fini也就是主程式啟動前/結束後的process - DLL reverse
- Unpack
- 動態
- x96dbg
- gdb
Tools
| Type | Description | Link |
|---|---|---|
| App | MobSF: Must run in python 3.8ApkTool: Just follow the step in install guide |
MobSF ApkTools |
| .NET | To decompile C#(.NET) | dnSpy |
| x86/x64 Simulator | x86模擬器 x86/x64 assembler/disassembler |
|
| Python | Pyc disassemble | |
| asm→C | 一個可以把組語轉換成 c pseudo code 的線上工具 | Compiler Explorer |
| General | 一個線上的 decompiler,結合多種工具,只要上傳檔案 (小於2MB) 就可以呈現多種 decompiler tools 的結果 | Decompiler Explorer |
靜態分析
根據執⾏檔內容直接進⾏分析
| 工具 | 類型 | 特色 | 典型用途 |
|---|---|---|---|
| IDA | 靜態 + 動態 | 商業級、Hex-Rays 反編譯強 | APT、漏洞研究、大型專案 |
| Ghidra | 靜態 | NSA 開源、內建 decompiler | ARM 韌體、CTF |
| Radare2 | 靜態 + 動態 | CLI 為主、可腳本化 | 自動化分析、進階研究 |
| Cutter | 靜態 (GUI for r2) | Radare2 圖形化介面 | 喜歡 r2 但不想用 CLI |
- IDA不一定每一個assembly都有辦法被反編譯,所以要特別注意tlscallback/exception handler之類的問題
PE結構分析
| 工具 | 類型 | 特色 | 典型用途 |
|---|---|---|---|
| PE Tools | PE 結構檢視 | 修改 Import / Entry Point | 修補 PE、檢查 packer |
| PEview | PE 檢視 | 查看 PE header / section | 分析檔案結構 |
| PEViewer | PE 檢視 | 視覺化顯示 PE 結構 | 初學者友好 |
| PE-bear | PE 分析 | 現代化 GUI、顯示資源/section | CTF、malware 初步檢查 |
- PE-Bear: 用來快速檢查 PE 結構是否正常、是否被加殼、Import 是否正確的工具。另外,如果有要修exception handler RVA,或是查看TLSCallback的需求,也可以用到
動態分析
通過觀察執⾏流程與結果加以猜測程式本⾝的⾏為: 網路流量、記憶體的變化、某些 API Call
| 工具 | 平台 | 類型 | 特色 | 典型用途 |
|---|---|---|---|---|
| OllyDbg | Windows | User-mode | 老牌 32-bit x86 除錯器,插件多 | 舊 crackme、32-bit malware |
| x64dbg | Windows | User-mode | 支援 x86/x64、開源、介面友好 | CTF、patch、反驗證 |
| WinDbg | Windows | User+Kernel | 微軟官方除錯器、支援 kernel debug | Driver 分析、藍屏、Kernel exploit |
| GDB | Linux / macOS | User-mode | CLI 為主、支援多架構、可遠端除錯 | Linux 逆向、CTF、pwn、程式除錯 |
好用的解題工具
-
angr - cheatsheet:
$ pip install angr claripy直接對 binary 做 symbolic execution 幫你「走所有路徑」找出能到 win() 的 input
適合在:
- 很多巢狀 if statement
- 很多跳轉或是複雜的Control Flow Graph或是switch
- 很多驗證流程
angr基本流程 - 範例來自 Simple-Reverse-0x28(2023-Lab-Super-Angry)/
- 建立一個project:
import angr; import claripy; proj = angr.Project('./super_angry') - 建立claripy symbol - 以這個lab的例子來說就是建立我們輸入進去的程式的input string
1
sym_arg = claripy.BVS('sym_arg', 8 * 32) # 就像z3一樣要建立symbol - 建立初始的state - 以這個lab來說就是我們一開始輸入的input string
1
2state = proj.factory.entry_state(args=[proj.filename, sym_arg]) simgr = proj.factory.simulation_manager(state) - 有了proj / symbol / initial state之後就要開始讓他跑起來
1
simgr.explore(find = lambda s: b'Correct!' in s.posix.dumps(1))
-
z3:
$ pip install z3-solver適合解:
- 複雜條件判斷
- bitwise 運算
- 多個 if 組合
- 需要算出滿足條件的輸入
z3的大致步驟 - 範例來自 Simple-Reverse-0x27(2023-Lab-Scramble)/
- 建立一個solver:
from z3 import *; s = Solver() - 建立符號 - 以此lab來說就是建立43個符號對應每一個flag字元:
bvs = [BitVec(f'bt_{i}', 32) for i in range(flag_len)] - 加上constraint - 以此lab來說每一個flag字元都應該限制在空白到0x7f之間,另外還要加上每一個符號(就是flag字元),經過我們已知的scramble pattern之後應該要是最後的target:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18for bv in bvs: s.add(And(bv >= 0x20, bv <= 0x7f)) for i, patter in enumerate(patters): formula = f'bvs[{i}]' for step in patter: op = step[0] value = step[1] if op == 'add': formula = f'({formula} + {value})' elif op == 'sub': formula = f'({formula} - {value})' elif op == 'lsh': formula = f'({formula} << {value})' print(f'{formula} == {targets[i]}') s.add(eval(formula) == targets[i]) - 判斷有無解,如果有的話就把每一個符號的值取出來
1
2
3
4
5
6
7
8
9if s.check() == sat: print('Find ~~~') print(s.model()) flag = "" for bv in bvs: flag += chr(s.model()[bv].as_long()) print(flag)
IDA 常用快捷鍵
- [IDA Interface](https://blog.30cm.tw/2018/01/ida.html)
基本使用
- [Space]: 在 Text View / Graph View 切換
- 關閉Opcode: 有時候會不想要看哪麼多Opcoder就可以使用,
Options/General → Number of opcode bytes (non-graph)設定成 0 - [F5/Tab]: Decompile
- [n]: 改名
- [;/Insert]: 註解
- [x]: 秀出 Xrefs
- [\ ]: 不顯示/顯示資料型別
- [Ctrl+e]: 顯示 entry points,如果要reverse dll會方便很多,
_DLLMainCRTStartup→DllMain / DllEntryPoint / CRT_INIT
- [Numpad-]: 如果function中的宣告很多,可以右鍵選擇
Collapse declarations
改型別
- 型別
- char(1 byte)
- WORD(2 bytes)
- DWORD(4 bytes)
- PDWORD(pointer of DWORD = DWORD *)
- 若是DWORD *name,代表name這個變數是一個pointer而且指向的地方是一個DWORD
- [y]: 改型別,可以參考 Simple Reverse - 0x19(2023 Lab - WinMalware - Extract Next Stage Payload)
- [h]: 改表示方式 (dec / hex)
- [u]: 取消定義,可以框選起來做操作
- [a]: 當成字串,可以框選起來做操作
- [c]: 當成code,可以框選起來做操作,將 IDA 認不出來的部分當成 Code
- [p]: 當成function,可以框選起來做操作,通常是將紅色區域標成 Function
- [*]: 將data轉成array
- [r]: 將常數顯示為char
- [Alt+A]: 將data轉成字串
- 可以先把bytes的型別定義好(單獨的bytes變成array),變成array有兩種方法,第一種是直接用
Y定義他的型別成int dword_2008[32],前面的int就看每一個字元是來決定,後面[32]就代表有多少字元變成array;第二種方法就是直接按d改變一個字元的型態變成int,然後在edit/Array的地方可以叫出Convert to array的視窗(如果前面沒有先用d改變型態的話,他會以為所有字元都是一個byte,然後總共有128個字元這樣換算,但其實我們是總共32個字元,每一個字元是4個bytes,也就是int,這一點要特別注意)

- 接著就是在
Option/String literals視窗中設定用哪一個型態表示字串,這邊因為每一個字元都是4 bytes,也就是32 bits,所以選擇C-style
- 完整流程
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17.rodata:0000000000002008 unk_2008 db 46h ; F ; DATA XREF: main+8↑o .rodata:0000000000002009 db 0 .rodata:000000000000200A db 0 .rodata:000000000000200B db 0 .rodata:000000000000200C db 4Ch ; L .rodata:000000000000200D db 0 .rodata:000000000000200E db 0 .rodata:000000000000200F db 0 .rodata:0000000000002010 db 41h ; A .rodata:0000000000002011 db 0 .rodata:0000000000002012 db 0 .rodata:0000000000002013 db 0 .rodata:0000000000002014 db 47h ; G .rodata:0000000000002015 db 0 .rodata:0000000000002016 db 0 .rodata:0000000000002017 db 0 ...↓
1
2
3.rodata:0000000000002008 dword_2008 dd 46h, 4Ch, 41h, 47h, 7Bh, 68h, 33h, 2 dup(31h), 4Fh, 5Fh, 72h, 65h, 76h, 65h, 72h, 73h, 31h, 6Eh, 67h .rodata:0000000000002008 ; DATA XREF: main+8↑o .rodata:0000000000002008 dd 5Fh, 33h, 6Eh, 67h, 69h, 6Eh, 2 dup(65h), 72h, 35h, 7Dh, 0↓
1
.rodata:0000000000002008 text "UTF-32LE", 'FLAG{h311O_revers1ng_3ngineer5}',0
- 完整流程
- 可以先把bytes的型別定義好(單獨的bytes變成array),變成array有兩種方法,第一種是直接用
Anti-Deassembler會用到
- [Ctrl+N]: 直接patch該instructions為NOP
- [Ctrl+Alt+K]: 直接patch為任意instructions
- [Ctrl+Alt+P]: 查看目前為止Patch的地方
Edit → Patch program → Apply patches to input file: 把patch好的program另存新檔,在此之前需要先處理好IDA的python環境問題,可以參考Unexpected fatal error while initializing python runtime
比較不常用
- [t]: set sizeof(XXX);如果已經確定目前的constant就是某個變數的length,那可以直接按t讓他變成sizeof(那個變數)
舉例:如果已經確定目前的
0x238就是PROCESSENTRY32W的size,就可以直接這樣用,會變得比較清楚

- [Shift+F1]: show出Local Type視窗

- [Shift+F12]: 開啟Strings視窗

-
對某一個數值按m: ENUM這個功能就是在替換一些常見的windows API參數,讓原本的純數字可以用文字表示,這樣比較好懂API的操作,逆向會更順暢(補充說明:IDA有收錄很多MSDN上的一些API,他每一個參數表示的文字,例如這一篇底下有顯示很多Constant/value的對應,而正常情況下IDA會顯示的是value,如果要把它換成Constant文字的表達式就可以用到ENUM這個功能),又例如:
目前已經知道
CreateToolhelp32Snapshot(2, 0);中的2的意義是TH32CS_SNAPPROCESS(可以參考MSDN),此時就可以直接按m之後再選擇TH32CS_SNAPPROCESS
- [Alt+M/Ctrl+M]: 前者是註冊書籤,後者是察看並選擇標籤,可以快速跑到標示的地址
-
[Ctrl+E]: 如果是分析DLL file,可能會有很多不同的entry point,利用這個shortcut可以顯示目前有幾個entry point,很方便

- [Shift+E]: 如何快速把bytes dump出來
- 選擇要輸出的bytes

- 按[Shift+E],跳出的視窗選擇想要的格式,再直接複製即可

- 選擇要輸出的bytes
- 如果函式沒有return東西的話,可以右鍵該函示,選擇
Remove return value或是Shift+Del
Plugin
- [Alt+Shift+S](ret-sync): 可以sync各種dubbuger和IDA
- [Ctrl+Alt+F](findcrypt): 可以顯示潛在的加密演算法
x64dbg 常用快捷鍵
- [F2]: 設定中斷點
- [F4]: Run / Continue 到當前選取的 assembly
- [F7]: 步入
- [F8]: 步過
- [F9]: 繼續執行
- [Ctrl+F9]: 執行到 ret
- [Ctrl+G]: goto
- [Space]: 修改組譯
- [Alt+a]: Attach process
- 符號視窗: 看到這支程式有用到那些API Module(.dll)
- 如果遇到TLS Callback類型的program,可以在
選項/偏好設定/事件中勾選要在User TLS Callbacks中斷
如何dump memory
可以參考Simple-Reverse-0x13(Lab-Meow),就是直接用Scylla中File/Dump Memory處理
Process相關的操作與資訊 (SysinternalSuite)
有關SysinternalSuite可以直接參考Misc Cheat Sheet
- ProcDot: 結合Process Monitor和Wireshark的分析工具,並且可視化分析的結果
Anti-Revese
- Scylla Hide
Exception Handler
也是一種Anti-Debug的技巧,因為debugger通常都會catch來自main program的Exception,所以programmer可以寫一個條件,註冊exception handler,如果這個exception被catch,那就代表有人嘗試使用debugger,參考CSDN-反調試- SetUnhandledExceptionFilter
1 | |
以上的部分是programmer自己註冊一個handler串上去SEH(Structured Exception Handling),本質上是一個鏈狀結構體,會不斷 Match Exception ,如果沒有 Match 就往下一個 Block 移動繼續做 Matching
解法
看懂程式流程在哪裡被catch
- 做 Patch
- 手動繞過
- 直接改 Register
Anti-Debug
- 比較時間差,在程式的各種地方插入檢查時間與製造 Delay
- Win32 API
- IsDebuggerPresent(): x96dbg的Attach([Alt+A])功能可以bypass IsDebuggerPresent
- CheckRemoteDebuggerPresent()
- RtlQueryProcessHeapInformation()
- RtlQueryProcessDebugInformation()
- NtQuerySystemInformation()
- NtQueryInformationProcess(): 這個 API 可以 Query 很多種類的 Process Information,更詳細的就看講義
比較時間差解法
- 做 Patch 直接把 Timer, Sleep 相關的東西全都 NOP 掉
- Hook 時間函數做 Speed Hack 讓時間變快
- 可以用 CheatEngine 做 SpeedHack
- 但要注意 Debugger 不能共存這件事情
Win32 API解法
Hook 掉這些 Function 就好,讓回傳值跟沒被 Debug 的數值一樣,但隨便 Hook 也會有機會被發現
Anti-Disassembler: 讓 Disassembler 壞掉的小技巧
- 針對線性掃描: 因為指令集密度很高,如果在程式中製造 Offset…,可能會解出看起來對的程式碼,但行為可能根本不一樣
- 針對 Control-flow Based Disassembler: 因為這種 Disassembler 會根據 Control-Flow 做追蹤,如果利用假的 jmp 指令來跳躍,可以使分析頭跳到奇怪的地方,然後就解壞了
解法
- 把解析壞掉的部分 Undefine 掉,找到對的 Code 開始點再標記回去,就是 IDA 的
u,c的功能應用
Anti-Attach:
在 Windows 下,ntdll 有一個函數可以用來做到 Anti-Attach → DbgUiRemoteBreakin
Packing
- 加密殼: Themida, VMProtect, ASProtect
- 壓縮殼: UPX, ASPack
- UPX Packer
1
$ upx -d {filename}`
- UPX Packer
- VM殼: 將程式變成另外一種客製化的 Bytecode 並跑在 VM 裡面,e.g. VMProtect, Themida
脫殼方式
- 找工具
- 如果看懂殼的邏輯就手動脫殼
- 動態脫殼:等動態跑起來後,程式邏輯被解密就直接dump memory
Obfuscation
- 通過 Obfuscator 將程式碼扭成麻花,e.g. 編譯器優化
- 參雜垃圾 Code
- 在 Control-Flow Graph 上面畫畫的 REPsych, Artfuscator
- 把 Control-Flow 變成一直線的 Movfuscator
- 將 if 這種 branch 攤平 → 用一個 loop 加 switch 來做分支攤平
- 將程式碼小片段變成某種 State Machine
- 一些將程式碼片段搞消失的方法
- 通過各種加載手段來映射片段的程式碼 → VirtualAlloc, VirtualProtect, mmap, mprotect
- 把程式切成小份到處亂丟,要用的時候再解包出來 map 到記憶體上