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

Re:0 從零開始的逆向工程

LJP-TW
October 10, 2021

Re:0 從零開始的逆向工程

2021/10/10 高中職生資安研習營 - 基礎逆向工程

LJP-TW

October 10, 2021
Tweet

More Decks by LJP-TW

Other Decks in Technology

Transcript

  1. 工具安裝 • 虛擬機準備這邊請自行研究 • Windows (無限制版本, 建議 win7 以上) •

    Linux (無限制發行版, 推薦 Ubuntu / Kali) • 本篇簡報範例沒有病毒, 可以直接運行在本機環境 • 但外面撿來的樣本, 還是請關在虛擬機運行 6
  2. JDK • 安裝 Ghidra 前要先安裝 JDK • jdk-17_windows-x64_bin.exe • 一直按下一步就對了

    • 設置環境變數 8 https://www.oracle.com/java/technologies/downloads/#jdk17-windows
  3. PE-Bear • PE-bear_0.5.4_x64_win_vs17.zip • 解壓縮 • 執行 PE-bear.exe 即可 11

    https://github.com/hasherezade/pe-bear-releases/releases/
  4. x64dbg • snapshot_2021-07-01_23-17 • 解壓縮 • 執行 release/x96dbg.exe • 一開始初始化選項

    全部選確定 • 桌面就會出現 x32dbg 和 x64dbg 的捷徑 12 https://sourceforge.net/projects/x64dbg/
  5. gdb • 開啟終端機執行以下指令 • sudo apt-get install gdb • 建議額外安裝

    gdb-gef 套件 • 參考 https://github.com/hugsy/gef 文中敘述的安裝方式 13
  6. 逆向工程簡介 • 當你想… • 破解程式 • 修改程式 • 分析惡意程式 •

    挖掘漏洞 • 卻又沒有原始碼 • 你就需要逆向工程! 20
  7. 組譯/反組譯 • 每一句組合語言都只和一組機械碼互相對應 • 比如說 mov rax, rbx 翻成機械碼就是 0x48

    0x89 0xd8 • 0x48 0x89 0xd8 翻成組合語言也只有 mov rax, rbx 這種可能 • 所以反組譯器相對來說比較好做 32
  8. 程式產生過程 • 溫馨提醒: 不同語言的機制可能不太一樣 • C / C++ • C#

    • Java • Python • … • 本篇簡報只針對 C / C++ 34
  9. 程式產生過程 • 知道了程式的產生過程後, 你多少應該能知道逆向工程的原理 • 通過反組譯, 將人類看不懂的 0101 變成看得懂的組合語言, 就能

    知道程式在執行什麼 • 有些工具提供反編譯功能, 能更有效的看懂程式在幹嘛 • 當然, 如果有 source code 最好 35
  10. 程式運作過程 • 你點兩下程式, 告訴作業系統 (OS) 你想執行他 • 所以問題更具體地問是, OS 怎麼將程式跑起來的?

    • OS 會從程式檔案的頭部讀取資訊 • 這些資訊包含怎麼把它放到記憶體裡、 程式進入點在哪… 37
  11. 程式運作過程 • 不同 OS 是如何載入程式的方式大同小異 • 頭部結構 • Windows: PE

    (Portable Executable) Header • Linux: ELF (Executable and Linkable Format) • 載入後, 就從程式進入點開始執行 • 先帶大家看看熟悉的 exe 內部結構 38
  12. PE format 43 • 再來是 NT Hdr (或稱 PE Hdr)

    • 其包含了 • COFF Hdr (或稱 File Hdr)
  13. PE format 44 • 再來是 NT Hdr (或稱 PE Hdr)

    • 其包含了 • COFF Hdr (或稱 File Hdr) • Optional Hdr
  14. PE format 46 PE Hdr 以 “PE” 開頭 紀錄有幾個 sections

    紀錄 Optional Hdr 多大 紀錄有哪些特殊設定
  15. PE format 50 • 解釋一下 RVA (Relative Virtual Address) •

    首先先直接展示一個程式在跑的時候, 記憶體位址的樣子
  16. VA RVA 53 • 解釋 RVA (Relative Virtual Address) 之前

    • 先解釋什麼是 VA (Virtual Address) • 做一下小實驗, 如果執行兩個 hello.exe, 記憶體位址分布長怎樣
  17. VA RVA 54 • 兩個 process 的記憶體位址有重疊耶?! • 如果改掉 A

    process 記憶體內容 (地址重疊的部分), B process 的 內容也會被改嗎? • 實驗一下, 答案是不會的 • 所以那個記憶體位址到底是啥
  18. VA RVA 56 • 那為什麼要這麼複雜? • 如果程式都能直接碰到實體記憶體位址 PA (Physical Address)

    • 你要怎麼知道這個 PA 有沒有被其他程式占用? • 這個問題很難, 但現代 OS 幫你搞定了這個問題 • OS 只給你 VA, 實際上存取時, OS 有他的方式, 能夠 VA <-> PA
  19. VA RVA 57 • 正常狀況下 A process 的 0x55665566 VA

    • 跟 B process 的 0x55665566 VA • 不是對應到同一個 PA • 解釋了剛剛的實驗結果
  20. VA RVA 58 • 搞懂 VA 了, 可以講 RVA 了

    • 只是一個方便 PE 結構不用寫這麼多字的東西 • VA = ImageBase + RVA • 第一條指令位址 VA = ImageBase + Entry point RVA
  21. ASLR 60 • 可是實驗中, 我們的第一條指令位址顯然不是剛算的 • 原因是 ASLR (Address Space

    Layout Randomization) • 記憶體位址每次執行時都是固定的話, 會有安全問題 (請看隔壁棚 Pwn 的課) • 啟用了 ASLR, OS 就會隨機產生 ImageBase, 原本提供的 ImageBase 就被忽略了 QQ
  22. ASLR 61 • 第一條指令位址 VA = ASLR 隨機產生的 ImageBase +

    進入點 RVA • 那麼要怎麼知道 ASLR 有沒有開啟呢?
  23. PE format 66 • Section Hdr • 在右邊的圖是對應 Section Table

    • 其實會有多個 Section Hdr • 用 PE-Bear 來展示一下
  24. PE format 69 • 解釋一下 Section Hdr • 程式檔案的 Raw

    Addr 開始的 Raw size 個 Bytes 會映射到 Virtual Addr 開始的 Virtual Size 個 Bytes • Virtual Addr 是 RVA • Characteristics 設定了該區記憶體位址的權限
  25. PE format 73 • 但看了一下 .rdata, 好像不是這麼回事? • 實際上是因為另一個機制, 改掉了這邊的資料

    • 關鍵字: IMAGE_IMPORT_DESCRIPTOR、 IAT (Import Address Table) • 關於這個機制, 我們以後會專門做一期視頻給大家講解
  26. 逆向工程簡介 74 • 看完這個章節之後, 你應該… • 知道為何要逆向工程 • 因為沒有 source

    code QQ • 略懂逆向工程原理 • 因為你知道了程式產生過程 • 所以你知道了反組譯、 反編譯這些技術為何可行 • 略懂從哪開始進行逆向 • 因為你知道了程式怎麼運作起來的
  27. 暫存器 • 通用暫存器 (General-Purpose Registers) 80 AH AL 7 8

    0 15 16 31 32 63 16-bit 32-bit 64-bit AX EAX RAX BH BL CH CL DH DL BP SP SI DI BX EBX RBX CX ECX RCX DX EDX RDX BP EBP RBP SP ESP RSP SI ESI RSI DI EDI RDI Base Pointer Stack Pointer
  28. 暫存器 • 指令暫存器 • Instruction Pointer Register • 或稱 Program

    Counter • 存放下一條指令的位址 81 IP 0 15 16 31 32 63 16-bit 32-bit 64-bit IP EIP RIP
  29. x86 組合語言 • 除了 mov 搬移指令以外, 還有很多很多很多的指令 • 遇到沒看過的就菇狗關鍵字 “x86

    <指令>” • 加 add • 減 sub • 乘 mul • 除 div • 呼叫 call • 返回 ret • 跳 jmp 86
  30. Ghidra • 在進入 main 前會執行一些初始化工作 • 在反編譯視窗中找長得像是 main(int argc, char

    *argv[], char *envp[]) 的 function call • 點進去該 function (FUN_1400010e0) 98
  31. 條件 jmp • 配合 cmp / test • 迴圈會往回跳 /

    If 只會往前跳 106 je / jz 相同 / 為 0 jne / jnz 不同 / 不為 0 jb / jl 無符號 / 有符號 小於 ja / jg 無符號 / 有符號 大於 jnb / jnl 無符號 / 有符號 不小於 Jna / jng 無符號 / 有符號 不大於
  32. Struct 112 name data name id 可以觀察到 compiler 將 RSP

    + 0x70 的位址當 id RSP + 0x74 的位址當 name 起始位址 將 RSP + 0x80 當 data 0 3 4 7 8 b c f 0x70 0x80 name d e
  33. x86 Calling Convention • 規定了呼叫函數時如何傳遞參數 • Windows • Function(rcx, rdx,

    r8, r9) • Linux • Function(rdi, rsi, rdx, rcx, r8, r9) • 多的參數會放到 stack 上 116
  34. Endian • Byte 的順序 • 一個整數 0x12345678, 兩種儲存方式 120 0x78

    0x56 0x34 0x12 0x12 0x34 0x56 0x78 3 2 1 0 3 2 1 0 Little Endian Big Endian
  35. Endian • 常見是用 Little Endian • 將 int 0x12345678 轉成

    short 0x5678, 起始位址不用改變 121 0x78 0x56 0x34 0x12 3 2 1 0 Little Endian int short byte
  36. Stack Frame • Q1: 函數都是以 RSP 或 RBP 來定位區域變數, 那怎麼區別不同函

    數的區域變數? • Q2: 呼叫函數後, RIP 就從 A 函數跑到 B 函數了, 要怎麼 return 回 A 函數? • 如果不知道答案, 那你就需要看一下這章 123
  37. Stack Frame • 不同區域會有不同的 Stack Frame • 裡面存放著區域變數 • 在

    Function 的頭部和尾部, 有一些用來處理 Stack Frame 的指令 • 頭部: Prologue • 尾部: Epilogue 124 push rbp mov rbp, rsp … leave ret main
  38. Stack Frame 125 RSP 0x00007fffffffe5c8 push rbp mov rbp, rsp

    sub rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 Stack
  39. Stack Frame 126 RSP 0x00007fffffffe5c8 push rbp mov rbp, rsp

    sub rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 Stack
  40. Stack Frame 127 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RSP RBP 原本的值 0x00007fffffffe5c0 Stack
  41. Stack Frame 128 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RSP RBP 原本的值 0x00007fffffffe5c0 Stack RBP
  42. Stack Frame 129 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 Main Stack Frame
  43. Stack Frame 130 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 0x401234 Main Stack Frame
  44. Stack Frame 131 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598
  45. Stack Frame 132 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590
  46. Stack Frame 133 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590
  47. Stack Frame 134 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590 0x00007fffffffe560 Function1 Stack Frame
  48. Stack Frame 135 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack RBP RSP 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590 0x00007fffffffe560 Function1 Stack Frame leave = mov rsp, rbp pop rbp
  49. Stack Frame 136 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590 0x00007fffffffe560 Function1 Stack Frame leave = mov rsp, rbp pop rbp RSP RBP
  50. Stack Frame 137 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590 0x00007fffffffe560 Function1 Stack Frame leave = mov rsp, rbp pop rbp RBP RSP
  51. Stack Frame 138 0x00007fffffffe5c8 push rbp mov rbp, rsp sub

    rsp, 20h … call function1 leave ret main push rbp mov rbp, rsp sub rsp, 30h … leave ret function1 RBP 原本的值 0x00007fffffffe5c0 Stack 0x00007fffffffe5a0 0x401234 0x401234 Main Stack Frame 0x00007fffffffe598 0x00007fffffffe5c0 0x00007fffffffe590 0x00007fffffffe560 Function1 Stack Frame leave = mov rsp, rbp pop rbp RSP
  52. Stack Frame • 統整一下 139 Stack RSP 上層函數的 Stack Frame

    RBP Return Address … Old RBP 目前函數的 Stack Frame Old RBP
  53. Stack Frame • Q1: 函數都是以 RSP 或 RBP 來定位區域變數, 那怎麼區別不同函

    數的區域變數? • A1: 想辦法讓不同函數的 stack 區域不同 • Q2: 呼叫函數後, RIP 就從 A 函數跑到 B 函數了, 要怎麼 return 回 A 函數? • A2: 在呼叫 B 函數前把下一條指令 push 進 stack B 函數執行 ret 把 A 函數下一條指令從 stack pop 回 rip 進而回到 A 函數 140
  54. x86 組合語言 • 看完這個章節之後, 你應該… • 略懂暫存器 • 略懂 x86

    組合語言 • 略懂 endian • 略懂 stack frame • 會用 Ghidra 找到 main 函數 141
  55. 分析方法 • 實務上為兩者交叉使用,看個人喜好方式 • 靜態分析工具 (反組譯, 反編譯, 分析函數, 重新命名函數/變數) •

    IDA • Ghidra • 動態分析工具 (設定中斷點, 觀察暫存器、 記憶體內容, 觀察位址空間) • x64dbg • windbg • gdb 146
  56. 分析方法 • 接下來示範一下動靜態交叉分析 • 動態分析工具選用 x64dbg • 靜態分析工具選用 Ghidra •

    前面的章節我們已經用 Ghidra 找到 hello.exe 的 main 了 • 接續著介紹其他功能 147
  57. Ghidra • 函數改名 • 對我們剛找到的 main 函數按 L 改名 •

    已經點進去了想退回來? 按 Alt + ← 149
  58. Ghidra • 在 0x140001131 呼叫了 FUN_140001020 • 用 PE-Bear 看

    ImageBase 多少: 0x140000000 • RVA = VA – ImageBase = 0x140001131 – 0x140000000 = 0x1131 154
  59. x64dbg • 將 hello.exe 拖進去 x64dbg • 先找到這次 ASLR 隨機產生的

    ImageBase: 0x7ff676150000 • VA = ImageBase + RVA = 0x7ff676150000 + 0x1131 = 0x7ff676151131 155
  60. x64dbg • 這時候觀察一下該 function call 之前設定了哪些參數 • 只有設定了 rcx, 看一下

    rcx 內容 • 點一下資料視窗中間, 然後按 ctrl+g, 輸入 rcx 159
  61. x64dbg • 觀察完參數後, 按下 f8 步過此函數 • f7 是步入, 會走進函數裡面

    • f8 是步過, 會走完這個函數 • 可以發現視窗輸出了剛剛第一個參數指向的字串 • 所以我們姑且先判斷此函數是 puts 或 printf 之類的函數 161
  62. Ghidra • 在 0x140001131 呼叫了 FUN_140001020 • 回來 Ghidra, 點進去

    FUN_140001020 裡面看 • 發現他實際上還會呼叫 __stdio_common_vfprintf 162
  63. Ghidra • 前面問你叫什麼名字 • 接下來呼叫 FUN_140001080, 帶著 “%32s” 跟 local_258

    呼叫 • 接下來跟你說 Hello • 實驗一下就知道 FUN_140001080 是 scanf • 而 local_258 也能順著程式的含意, 將他命名為 names 167
  64. ELF 逆向工程 • 前面有提到不同 OS 是如何載入程式的方式大同小異 • 頭部結構 • Windows:

    PE (Portable Executable) Header • Linux: ELF (Executable and Linkable Format) • 載入後, 就從程式進入點開始執行 • 基於篇幅的原因, 這裡就只講 ELF 的程式進入點之類的怎麼看 172
  65. 關閉 PIE / ASLR ? • Q: Windows 的 PE

    format 可以將 ASLR 關閉, 那 Linux 的 ELF format 能不能關掉 ASLR 跟剛剛說的 PIE? • ASLR 不是 ELF format 決定要不要開, 是 kernel 的設定, 要關 ASLR 要去設定 kernel • PIE 理論上是可以關閉, 但要做的修改不像改 PE 的 ASLR 去掉某 個 bit 這麼簡單, 實務上至少我沒有找到工具可以關 PIE, 但其實不 關也沒關係 175
  66. gdb • 秀出記憶體內容 • x/<幾個><格式><尺寸> <記憶體位址> • e.g. • x/16xg

    0x1000 • 秀出從 0x1000 開始的 16 個以十六進制 (x) 格式表示的 8 Bytes(g) 188