iT邦幫忙

2026 iThome 鐵人賽

DAY 9
0
Security

從零開始の Binary CTF:30 天系統化學習 Rev & Pwn系列 第 9 篇

Day09|Static Analysis - IDA / Ghidra

  • 分享至 

  • xImage
  •  

前幾天我們已經看過 Assembly、Stack,也開始接觸 Static Analysis。

像是:

file
strings
readelf
objdump
nm

這些工具都可以幫助我們觀察 Binary。

但如果今天拿到的是一個比較大的程式,全部都使用:

objdump -d ./chall

然後從幾千、幾萬行 Assembly 裡面慢慢找,基本上會看到懷疑人生。

所以在 Reverse Engineering 中,我們通常會使用專門的 Binary Analysis Tool。

常見的工具包含:

  • IDA
  • Ghidra
  • Binary Ninja
  • Cutter

今天主要先介紹最常見的 IDA 與 Ghidra。


0x00 IDA / Ghidra 是什麼?

簡單來說,它們可以幫我們把 Binary 裡面的 Machine Code:

Machine Code
    ↓
Disassembly
    ↓
Assembly

轉成比較容易閱讀的 Assembly。

除此之外,IDA 與 Ghidra 還提供一個非常重要的功能:

Decompiler

Decompiler 會嘗試把 Assembly 轉換成類似 C Code 的形式:

Machine Code
    ↓
Disassembler
    ↓
Assembly
    ↓
Decompiler
    ↓
Pseudo C

例如原本的程式:

if (strcmp(input, "shark") == 0) {
    puts("Correct!");
}

編譯之後可能變成:

call strcmp
test eax, eax
jne wrong

Decompiler 則可能重新表示成:

if (!strcmp(input, "shark"))
{
    puts("Correct!");
}

看起來是不是舒服很多。

但這邊有一個很重要的觀念:

Decompiler 顯示的程式碼不是原本的 Source Code。

它只是根據 Assembly 推測出來的 Pseudocode。

所以變數名稱、型別甚至程式結構,都有可能跟原本的程式不同。


0x01 準備一個簡單的 Binary

今天可以自己準備一個簡單的程式:

#include <stdio.h>
#include <string.h>

int main() {

    char input[32];

    printf("Password: ");
    scanf("%31s", input);

    if (strcmp(input, "shark") == 0) {
        puts("Correct!");
    } else {
        puts("Wrong!");
    }

    return 0;
}

接著進行編譯:

gcc main.c -o main

我們可以先使用前一天學過的方法觀察:

file main

例如:

ELF 64-bit LSB pie executable, x86-64

也可以看看裡面的字串:

strings main

可能會看到:

Password:
shark
Correct!
Wrong!

光看這裡,其實已經可以猜到這個程式可能有 Password Checking。

接下來把它丟進 IDA 或 Ghidra。


0x02 Functions

Binary 被載入之後,其中一個最重要的地方就是:

Functions

IDA / Ghidra 會分析 Binary,嘗試辨識裡面的 Function。

例如:

main
puts
printf
scanf
strcmp

在 Reverse Engineering 的時候,我通常第一件事情就是先找:

main

因為很多 CTF 題目的主要邏輯,都可以從 main() 開始追。

當然 Real World Binary 就不一定這麼單純。

但現階段從 main() 開始是最容易理解的。


0x03 Disassembly View

點進 main() 後,我們就可以看到 Assembly。

可能會看到類似:

push    rbp
mov     rbp, rsp
sub     rsp, 20h

前幾天學過 Stack 的話,應該已經開始有點熟悉了。

這通常是在建立 Stack Frame。

接下來可能看到:

lea     rax, [rbp-20h]
mov     rsi, rax
lea     rdi, format
call    scanf

然後進行 Password 比較:

lea     rax, [rbp-20h]
lea     rsi, password
mov     rdi, rax
call    strcmp

這邊就是:

strcmp(input, "shark")

接著:

test    eax, eax
jne     wrong

為什麼是 eax?

因為在 x86-64 Linux 常見的 calling convention 中,Function 的 Return Value 通常放在:

RAX

而 strcmp() 如果兩個字串相同:

return 0

所以:

test eax, eax

就是在檢查:

eax == 0 ?

這就是為什麼前面學 Assembly 很重要。

因為 Decompiler 雖然很好用,但真正執行的還是 Assembly。


0x04 Decompiler

接著來看 Reverse Engineering 中非常好用的功能:

Decompiler

它可能會把剛才的 Assembly 顯示成:

int main()
{
    char input[32];

    printf("Password: ");
    scanf("%31s", input);

    if (strcmp(input, "shark"))
        puts("Wrong!");
    else
        puts("Correct!");

    return 0;
}

這時候程式邏輯就非常清楚了。

我們可以直接看出:

input
 ↓
strcmp
 ↓
"shark"

如果相同:

Correct!

不同:

Wrong!

這也是很多 Reverse CTF 最基礎的分析方式。


0x05 Graph View

除了單純一行一行看 Assembly,IDA 也可以使用 Graph View。

程式的控制流程可能會被表示成:

        strcmp(input, "shark")
                  |
             test eax
              /   \
             /     \
         eax=0    eax!=0
           |         |
       Correct!    Wrong!

這其實就是程式的:

Control Flow

像 C 裡面的:

if (...) {
}
else {
}

編譯成 Assembly 後,就會變成:

cmp / test
   ↓
conditional jump

例如:

je
jne
jg
jl

Graph View 可以讓我們不用一直在 Assembly 裡面上下跳來跳去。

對分析複雜的 if、for、while 都非常有幫助。


0x06 Strings

另一個非常重要的功能就是:

Strings

很多時候拿到一個 Binary,我們甚至不用從 main() 開始。

可以先看看有哪些有趣的 String。

例如:

Password:
Correct!
Wrong!
shark

如果看到:

Correct!

這個字串很可能就是程式成功時會使用的。

這時候我們可以查看:

誰使用了這個字串?

這就會用到下一個非常重要的概念。


0x07 Xrefs

Xrefs 全名是:

Cross References

簡單來說就是:

哪些地方有使用這個東西?

例如我們找到:

Correct!

接著查看 Xrefs。

工具可能告訴我們:

main + 0x52

代表 main() 裡面某個地方會使用這個 String。

我們就可以直接跳過去。

所以 Reverse Engineering 很常見的流程是:

Strings
   ↓
找到有趣字串
   ↓
Xrefs
   ↓
找到使用位置
   ↓
分析附近 Assembly

例如 CTF 裡面看到:

Correct!
Congratulations!
Flag:
Wrong password
Access denied

都值得先查看 Xrefs。
這通常比從 Entry Point 一路慢慢 Trace 快很多。


0x08 不要完全相信 Decompiler

Decompiler 非常方便,但千萬不要變成:

只看 Pseudocode,不看 Assembly。

例如 Decompiler 可能顯示:

if (v3)

但真正的 Assembly 可能是:

test eax, eax
jne loc_401230

Decompiler 只是幫你重新整理。

真正執行的仍然是:

Machine Code

因此遇到奇怪的地方時,最好回頭看:

Assembly

尤其之後遇到:

  • Compiler Optimization
  • Obfuscation
  • Anti-Decompiler
  • Malware
  • Pwn
  • Weird Control Flow

Decompiler 的結果可能會變得非常奇怪。


0x09 一個基本的 Reverse Workflow

目前我們已經可以建立一個非常基本的 Reverse 流程:

拿到 Binary
     ↓
file
     ↓
strings
     ↓
丟進 IDA / Ghidra
     ↓
找 main()
     ↓
查看 Decompiler
     ↓
查看 Assembly
     ↓
Strings / Xrefs
     ↓
理解程式邏輯

現在先不用追求:

我要看懂整支程式

實際 Reverse 的時候,更常做的是:

我現在想知道 Password 在哪裡驗證?

或者:

哪裡會輸出 Correct?

然後只追蹤與目標有關的部分。

這也是 Reverse Engineering 很重要的一個觀念:

不一定要把整個 Binary 看完,而是找到與目標相關的程式邏輯。


0x0A IDA 還是 Ghidra?

如果只是學 Reverse:

兩個都可以。

IDA

優點:

  • 業界非常常見
  • Disassembly 很成熟
  • Graph View 很好用
  • 很多 Writeup 都使用 IDA

Ghidra

優點:

  • 免費
  • Open Source
  • Decompiler 很強
  • 功能完整

現階段不用糾結哪一個比較強。

因為真正需要學的是:

Assembly
Control Flow
Function
Calling Convention
Xrefs
Program Logic

工具只是幫助我們觀察 Binary。


0x0B 小結

今天正式開始使用 Reverse Engineering Tool。

我們認識了:

Functions
Disassembly
Decompiler
Graph View
Strings
Xrefs

其中最重要的是建立這個概念:

Source Code
    ↓
Compiler
    ↓
Machine Code
    ↓
Disassembler
    ↓
Assembly
    ↓
Decompiler
    ↓
Pseudo C

Decompiler 並不是把原始 Source Code 復原。

它只是幫助我們更容易理解 Assembly。

而真正的 Reverse Engineering,仍然是在理解:

Binary 到底做了什麼?

到目前為止,我們主要都還是在:

Static Analysis

也就是不執行程式的情況下分析 Binary。

但有些東西光看 Assembly 很難判斷。

例如:

某個 Register 現在到底是多少?
這個 Function 執行完之後回傳什麼?
某個 Memory Address 裡面到底放了什麼?

這時候最直接的方法,就是:

讓程式真的跑起來,然後停在我們想看的地方。

所以下一篇:Dynamic Analysis - GDB


上一篇
Day08|Static Analysis
下一篇
Day10| Dynamic Analysis — GDB
系列文
從零開始の Binary CTF:30 天系統化學習 Rev & Pwn 共 18 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言