Upgrade to Pro — share decks privately, control downloads, hide ads and more …

逆向工程實務 極入門篇

LJP-TW
February 20, 2021

逆向工程實務 極入門篇

2021/2/20 AIS3 Club 東部資安社群新春推廣 東華大學

LJP-TW

February 20, 2021
Tweet

More Decks by LJP-TW

Other Decks in Technology

Transcript

  1. Outline 1 基礎知識篇 • 簡介逆向工程 • 程式如何變成程序 • 簡介 CPU

    暫存器 • Runtime Architecture • 簡介 x86_64 • 逆向工程方式 • 靜態分析 • 動態分析 • Endian • ASLR 簡易實務篇 • x86_64 組合語言 • 函式庫 • 加速逆向工程 • 阻止逆向工程
  2. x64dbg • Snapshot_2021-02-09_17-28.zip • 解壓縮 • 執行 release/x96dbg.exe • 一開始初始化選項

    全部選確定 • 桌面就會出現 x32dbg 和 x64dbg 的捷徑 6 https://sourceforge.net/projects/x64dbg/
  3. 順向工程 9 程式碼 程式 程序 運行 組譯 編譯 C、C++、RustC x86

    asm .exe、 Linux ELF Java Java Bytecode .class Python Python Bytecode .pyc Smali Dalvik Bytecode .dex
  4. 逆向工程 10 程式碼 程式 程序 組譯 編譯 運行 反組譯 反編譯

    C、C++、RustC x86 asm .exe、 Linux ELF Java Java Bytecode .class Python Python Bytecode .pyc Smali Dalvik Bytecode .dex
  5. 程式 to 程序 • 程式檔案的頭部會存放資訊 告訴 OS 怎麼把它放到記憶體裡 程式進入點在哪 •

    頭部結構 • Windows: PE (Portable Executable) Header • Linux: ELF (Executable and Linkable Format) • 接著以大家熟悉的 exe 舉例 15
  6. 程式 to 程序 • 實際拆一隻 exe • DOS Header •

    Magic number • MZ 開頭 • 用來快速簡易辨識格式 • File address of new exe header • PE Header 所在的 offset 16
  7. 程式 to 程序 • PE Header • Magic number •

    PE 開頭 • 又細分成幾個 Header • File Header • Optional Header 17
  8. 程式 to 程序 • Optional Header • Entry Point •

    程式進入點 • Image Base • 整個程式的基址 • 第一道指令位址 • Image Base + Entry Point • 0x4014E0 18
  9. 程式 to 程序 • 第一道指令位址 • Image Base + Entry

    Point • 0x4014E0 • 偷瞄一下 0x4014E0 • 被工具自動標示成 entry 19
  10. 程式 to 程序 • Section Header • Raw Address •

    在檔案中的 offset • Virtual Address • 放到記憶體中的位址 (要再加 Image Base) • 有許多 section • .text • 可執行的機械碼 主程式通常放在這 • .data • 放資料的區域 20
  11. 程式 to 程序 22 程式檔案 記憶體 Header .text section .data

    section … Header 0x400000 (Image Base) .text section .data section … 0x401000 0x403000 0x0 0x400 0x1c00
  12. 程式 to 程序 23 記憶體 Header 0x400000 (Image Base) .text

    section .data section … 0x401000 0x403000
  13. 程式 to 程序 Linux 的 ELF 格式雖然和 PE 不同 但目的也是一樣,

    要讓 OS 知道怎麼 load 他 所以 ELF 很多欄位都可以在 PE 中找到類似功能的對應欄位 24
  14. CPU 暫存器 • CPU 實現了不同指令集架構 (Instruction Set Architecture) • WIKI

    支援 • 不同指令集架構有不同的暫存器、指令格式 • 暫存器 (Register) • x86_64: rax rbx rcx rdx rdi rsi rbp rsp rip … • ARM: SP LR PC R0 R1 R2 R3 R4 … • MIPS: r0 r1 r2 r3 ... • 接下來都以 x86_64 / x86 來講 27
  15. x86_64 暫存器 28 al ah rax (8 Bytes) eax (4

    Bytes) ax (2 Bytes) bl bh rbx (8 Bytes) ebx (4 Bytes) bx (2 Bytes) cl ch rcx (8 Bytes) ecx (4 Bytes) cx (2 Bytes) dl dh rdx (8 Bytes) edx (4 Bytes) dx (2 Bytes) si (2 Bytes) rsi (8 Bytes) esi (4 Bytes) di (2 Bytes) rdi (8 Bytes) edi (4 Bytes) bp (2 Bytes) rbp (8 Bytes) ebp (4 Bytes) sp (2 Bytes) rsp (8 Bytes) esp (4 Bytes)
  16. x86 暫存器 29 al ah eax (4 Bytes) ax (2

    Bytes) bl bh ebx (4 Bytes) bx (2 Bytes) cl ch ecx (4 Bytes) cx (2 Bytes) dl dh edx (4 Bytes) dx (2 Bytes) si (2 Bytes) esi (4 Bytes) di (2 Bytes) edi (4 Bytes) bp (2 Bytes) ebp (4 Bytes) sp (2 Bytes) esp (4 Bytes)
  17. x86_64 暫存器 • 還有更多暫存器, 例如 r8 ~ r15、rip、segment 暫存器 …

    • 有幾個重要的暫存器 • rsp: stack 頂部位址 (Stack Pointer) • rbp: stack 底部位址 (Base Pointer) • rip: 下一條要執行的指令位址 (Instruction Pointer) 30
  18. 程式 to 程序 32 記憶體 Header 0x400000 (Image Base) .text

    section .data section … 0x401000 0x403000
  19. 程序 33 記憶體 .text section 0x00401000 CPU EAX EBX ECX

    EDX EDI ESI EBP 0x0128FF94 ESP 0x0128FF8C EIP 0x004014e0 0x4014E0 stack 0x0128FF8C 0x0128FF94 EIP -> <-EBP <-ESP
  20. 程序 34 記憶體 .text section 0x00401000 CPU EAX EBX ECX

    EDX EDI ESI EBP 0x0128FF94 ESP 0x0128FF80 EIP 0x004014e3 0x4014E0 stack 0x0128FF80 0x0128FF94 EIP -> <-EBP <-ESP
  21. x86_64 指令 • 一種機械碼對應一種組合語言指令 • 像是 83 ec 0c 對應

    sub esp, 0xc • 加 add • 減 sub • 乘 mul • 除 div • 呼叫 call • 返回 ret • 跳 jmp • 遇到沒看過的就查 36
  22. x86_64 指令 37 mov rax, 1 add rax, 5 mov

    rbx, 7 sub rbx, rax inc rax ASM rax = 1 rax = rax + 5 rbx = 7 rbx = rbx – rax rax++ C ✕ ✕
  23. 38

  24. 程式分析 • 實務上為兩者交叉使用,看個人喜好方式 • 靜態分析工具 (反組譯, 反編譯, 分析函數) • IDA

    • Ghidra • 動態分析工具 (設定中斷點, 觀察 Registers、 Memory) • x64dbg • windbg • gdb 42
  25. 靜態分析: Ghidra 44 創好新的 Project 後 介面類似這樣 File > Import

    File 或按 I 或直接把程式拖進來 對程式按兩下即可開始分析
  26. 靜態分析: Ghidra • 在進入 main 前會執行一些初始化工作 • 找長得像是 main(int argc,

    char *argv[], char *envp[]) 的 function call 49 Ref: https://hackmd.io/@sysprog/c-runtime?type=view
  27. 靜態分析: Ghidra • 把函數改名, 對著函數名稱按 L • 修改參數名稱, 對著參數按 L,

    改成 argc argv … • 修改參數型別, 對著參數按 ctrl+L, 改成 int、 char **… • 修改這些的意義是幫助你理解程式 51
  28. 動態分析: x64dbg • 把程式拖進去 • 按一下組語指令的視窗 • Ctrl + g

    移動到指定記憶體位址 • 設定中斷點: 按一下要設中斷點的指令後按 F2 • 繼續執行: 按 F9 讓程序繼續執行,一直按 F9 直到剛剛設定的中斷 點為止 55
  29. 動態分析: x64dbg • F8 單步步過一條指令 • 步過步過… • 即將執行 mov

    qword ptr ss:[rsp+90], rax • 看一下 rax 和 rsp 現在是多少 • rax 為 0x0000F95351A401E4 • 看一下位址為 rsp + 90 的記憶體內容現在是什麼 56
  30. 動態分析: x64dbg • 點一下資料視窗, 按 Ctrl + g 移動到 rsp

    + 90 • 內容是 0 • 步過這行指令後, 將 rax 寫入到此 • rax 為 0x0000F95351A401E4, 但寫入記憶體順序卻反了 • WHY? 57
  31. Endian 59 Memory 0x34 0x56 0x78 0x12 a+1 a+2 a+3

    a Memory 0x56 0x34 0x12 0x78 a+1 a+2 a+3 a Big-Endian Little-Endian 0x12345678 32-bits Integer
  32. Endian • Little-Endian 好處: • 型別轉換 (e.g. int to short

    int) 時不用換記憶體位址 60 Memory 0x56 0x34 0x12 0x78 a+1 a+2 a+3 a Little-Endian 0x12345678 32-bits Integer 0x5678 16-bits short Integer 0x78 8-bits char
  33. 動態分析: x64dbg • 點一下資料視窗, 按 Ctrl + g 移動到 rsp

    + 90 • 內容是 0 • 步過這行指令後, 將 rax 寫入到此 • rax 為 0x0000F95351A401E4, 但寫入記憶體順序卻反了 • WHY? Answer: Little-Endian 61
  34. ASLR • 在 PE Header 中以 DLL can move bit

    是否為 1 來決定要不要使 用 ASLR • IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE • 利用 PE-Bear 修改 binary, 把 ASLR 關閉 64
  35. Lab: observe_c.exe 65 任務目標 • 在 Ghidra 中找到 main 函數

    • 關閉執行檔的 ASLR • 以 x64dbg 動態觀察 main 函數 • 熟悉工具
  36. 條件 jmp • 配合 cmp / test • 迴圈會往回跳 /

    IF 只會往前跳 78 Ref: http://www.unixwiz.net/techtips/x86-jumps.html je / jz 相同 / 為 0 jne / jnz 不同 / 不為 0 jb / jl 無符號 / 有符號 小於 ja / jg 無符號 / 有符號 大於 jnb / jnl 無符號 / 有符號 不小於 Jna / jng 無符號 / 有符號 不大於
  37. struct 83 10 Stack RSP -> int user[4] int room

    info_struct local_info RSP+0x48 -> …
  38. struct 85 10 Stack int local_info.id RSP+0x48 -> char local_info.name[10]

    RSP+0x4C -> char *local_info.data RSP+0x58 -> Q: 為何不是 RSP + 0x56 呢 2 Bytes
  39. x86 Calling Convention • 規定了呼叫函數時如何傳遞參數 • Windows • Function(rcx, rdx,

    r8, r9) • Linux • Function(rdi, rsi, rdx, rcx, r8, r9) • 超過的參數會放到 stack 上 88
  40. Stack Frame • 函數都是以 RSP 來定位區域變數 • Q: 怎麼區別不同函數的區域變數? •

    呼叫函數後, RIP 就從 A 函數跑到 B 函數了 • Q: 要怎麼 return 回 A 函數? 92
  41. Stack Frame 102 Stack RSP Stack of main RBP Stack[-0x8]

    00000000 004015de Stack[0] Stack[-0x8]
  42. Stack Frame 103 Stack RSP Stack of main RBP Stack[-0x48]

    00000000 004015de Stack[0] Stack[-0x8]
  43. Stack Frame 104 Stack RSP Stack of main RBP Stack[-0x48]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B
  44. Stack Frame 105 Stack RSP Stack of main RBP Stack[-0x48]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B 快轉一下
  45. Stack Frame 106 Stack RSP Stack of main RBP Stack[-0x48]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B
  46. Stack Frame 107 Stack RSP Stack of main RBP Stack[-0x48]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B
  47. Stack Frame 108 Stack RSP Stack of main RBP Stack[-0x8]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B
  48. Stack Frame 109 Stack RSP Stack of main RBP Stack[-0x8]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B
  49. Stack Frame 110 Stack RSP Stack of main RBP Stack[-0x8]

    00000000 004015de Stack[0] Stack[-0x8] Stack of function_B
  50. Stack Frame 111 Stack Stack of main Stack[0] RSP 比較一下

    call 之前與 call 之後 RBP Stack[-0x8]
  51. Stack Frame • 函數都是以 RSP 來定位區域變數 • Q: 怎麼區別不同函數的區域變數? •

    呼叫函數後, RIP 就從 A 函數跑到 B 函數了 • Q: 要怎麼 return 回 A 函數? 112
  52. Stack Frame • 函數都是以 RSP 來定位區域變數 • Q: 怎麼區別不同函數的區域變數? •A:

    想辦法讓不同函數的 stack 區域不同 • 呼叫函數後, RIP 就從 A 函數跑到 B 函數了 • Q: 要怎麼 return 回 A 函數? •A: 在呼叫 B 函數前把下一條指令 push 進 stack B 函數以 ret 把 A 函數下一條指令從 stack pop 回 rip 進而回到 A 函數 113
  53. Stack Frame 114 Stack RSP Stack of old function RBP

    Return Address … Old RBP Stack of current function Old RBP
  54. 函式庫 Library • 把一堆函式打包在一起變成函式庫 • 程式可以用兩種方式連結函式庫 • Static Link 將程式用到的函式庫函數一起包到程式中

    • Dynamic Link 將程式哪邊有用到函式庫函數記錄下來, 並且在執行階段時才把外部函式 庫加載進記憶體, 並進行連結 119
  55. 函式庫 Library • Dynamic Link 將程式哪邊有用到函式庫函數記錄下來, 並且在執行階段時才把外 部函式庫加載進記憶體, 並進行連結 •

    不同 OS 大同小異 • Windows: .dll (Dynamic-Link Library) kernel32.dll ntdll.dll msvcrt.dll • Linux: .so (Shared Object) libc-2.31.so libcrypto.so libssl.so 122
  56. 靜態分析 DLL • DLL 沒有 main, 但是有 DllMain (可有可無) •

    看 export 哪些 functions • 看 exe 用到什麼 functions 123
  57. 靜態分析 DLL • 那就在把 HIDLL.DLL 拖進來分析 • 看 Exports 了哪些函數,

    追蹤想追的函數 • DLL DLL, HIDLL.DLL OTHERDLL.DLL 125
  58. 動態分析 DLL • 把用到 DLL 的 exe 拉進工具裡 • 1.

    斷點下在 exe 呼叫到 DLL 函數的位址 126
  59. 加快逆向工程 • 更快的找到關鍵程式碼 = 加快逆向工程, but HOW? • 1. 動態分析

    call 函數, 觀察輸入輸出直接猜函數在做什麼 • 2. 若呼叫到特定函數, 則猜測為關鍵程式碼 • 3. 搜尋特定字串在哪邊有使用到 130
  60. Lab: DuRaRaRa 133 Lab 敘述: 此題為今年 AIS3 EOF 初賽的 Reverse

    題目 PE 中還有一個 PE Lab 目標: 通過追蹤特定函數, 加快逆向工程, 最終把 PE 裡面的 PE 抓出來
  61. Lab: DuRaRaRa 138 在 0x403020 開始, 長度 0x5a492, 是另一個 PE

    原程式用 VirtualAlloc + memcpy 把此 PE 複製到新位址 CreateThread 把新位址的 PE 給跑起來
  62. Lab: DuRaRaRa 把從 0x403020 開始, 長度 0x5a492 的 Bytes 取出來

    先 goto 0x403020, 用上面選單 Select > Bytes 選 Select Forward 139
  63. Anti-Reverse Engineering • 加殼 • https://en.wikipedia.org/wiki/Executable_compression#DOS_executable • 加密 / 編碼

    • Anti-Debug • https://github.com/LordNoteworthy/al-khaser • Self-modifying • https://en.wikipedia.org/wiki/Self-modifying_code • Manual Symbol Resolution • https://blog.christophetd.fr/hiding-windows-api-imports-with-a-customer-loader/ • https://www.ired.team/miscellaneous-reversing-forensics/windows-kernel-internals/exploring- process-environment-block • … 142
  64. 加殼 Pack • 把原本程式的機械碼壓縮, 並在 Entry Point 寫上解壓縮程式 • 原程式在執行時期才會被解壓縮並且運行

    • 讓靜態分析變困難 • 著名的加殼器 (Packer): UPX • 需要脫殼 143 UPX 加殼後 Ghidra 認不出是 PE UPX 加殼前
  65. Manual Symbol Resolution • Windows 有一個酷酷的結構: PEB (Process Environment Block)

    • 通過 PEB, 能找出所有已加載的 DLL 位址 • 通過爬 DLL 的 export table, 能找到此 DLL 支援的函數的位址 • 達到了 不在程式原始碼中呼叫 API 卻能呼叫 API 的目標 • 靜態分析工具自然解析不出呼叫了什麼 API 146
  66. 149

  67. _LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks +0x030 DllBase +0x058 BaseDllName _PEB_LDR_DATA Manual Symbol

    Resolution 152 +0x010 InLoadOrderModuleList +0x020 InLoadOrderModuleList _LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks …… +0x030 DllBase +0x058 BaseDllName
  68. Manual Symbol Resolution 154 _LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks +0x030 DllBase +0x058

    BaseDllName DOS Header PE Header +0x03c e_lfanew DllBase + e_lfanew +0x088 Export Directory RVA Export Directory DllBase + Export Directory RVA +0x00c NAME +0x014 NumberOfFunctions +0x01c AddressOfFunctions +0x020 AddressOfNames