iT邦幫忙

2026 iThome 鐵人賽

DAY 15
0
Security

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

Day15|Reverse 實戰 & 結論

  • 分享至 

  • xImage
  •  

Day11 的第一個 Crackme 中,Password 最後其實還是會直接出現在程式裡。

例如:

strcmp(input, "r3v_1s_fun")

這種題目只要找到 strcmp,通常很快就可以得到答案。

但真實的 Reverse Challenge 當然不一定會這麼友善。

所以今天來做一個稍微進階一點的 Crackme:

Stripped Binary
+
Password 不會直接出現在 strings
+
XOR 驗證

這次的重點不是學新的工具。

而是把前面學到的東西真的串起來。


0x00 拿到 Binary

假設今天拿到:

chall

先直接執行:

./chall

輸入:

Password: test
Wrong!

目前能知道的資訊只有:

程式會要求 Password

輸入錯誤:
Wrong!

輸入正確:
應該會出現 Correct!

我們的目標就是:

找出正確 Password

0x01 基本資訊蒐集

拿到 Binary 還是一樣,先從最便宜的方法開始。

file chall

可能看到:

chall: ELF 64-bit LSB pie executable, x86-64, dynamically linked, stripped

其中有一個很重要的資訊:

stripped

代表 Symbol 已經被移除了。

如果使用:

nm chall

可能會看到:

nm: chall: no symbols

所以這次不會直接看到:

main
check_password
verify

這些漂亮的 Function Name。


0x02 strings 還有用嗎?

接著:

strings chall

可能找到:

Password:
Correct!
Wrong!

但是卻找不到:

r3v_x0r!

也就是說:

Password 並沒有直接以 Plaintext String 的形式存在 Binary 裡。

所以這次沒有辦法像之前一樣:

strings
↓
看到 Password
↓
結束

但 strings 還是非常有用。

因為:

Correct!
Wrong!

本身就是很好用的線索。


0x03 從 String Xref 開始找

把 Binary 丟進 IDA 或 Ghidra。

因為 Binary 被 Strip 過,所以可能會看到很多:

sub_1169
sub_11A2
sub_1201

這種完全看不懂的 Function Name。

這時候不要從第一個 Function 開始慢慢看。

直接找:

Correct!

接著查看:

Xref

也就是:

誰使用了這個 String?

找到 Reference 後,可能會跳到某個 Function:

sub_1201

Decompiler 大概會看到:

if (sub_1169(input))
    puts("Correct!");
else
    puts("Wrong!");

雖然我們不知道:

sub_1169

叫什麼。

但從行為就可以猜到:

sub_1169(input)

很可能就是:

Password Validation

所以可以先 Rename:

sub_1169
↓
check_password

這就是前面 Stripped Binary 提過的概念:

Function Name 消失,不代表 Program Logic 消失。


0x04 進入 check_password

接著進入:

check_password

可能看到類似:

int check_password(char *input)
{
    int i;
    unsigned char enc[8] = {
        0x51,
        0x10,
        0x55,
        0x7c,
        0x5b,
        0x13,
        0x51,
        0x02
    };

    if (strlen(input) != 8)
        return 0;

    for (i = 0; i < 8; i++)
    {
        if ((input[i] ^ 0x23) != enc[i])
            return 0;
    }

    return 1;
}

這次沒有:

strcmp(input, password)

而是逐個 Character 驗證。

核心是這一行:

(input[i] ^ 0x23) != enc[i]

換句話說:

User Input
   ↓
XOR 0x23
   ↓
跟 enc[i] 比較

0x05 XOR 可以反推嗎?

這裡有一個很重要的特性。

假設:

A XOR B = C

那麼:

C XOR B = A

因為:

X XOR K XOR K = X

同一個值 XOR 兩次會回到原本的值。

所以現在我們知道:

input[i] XOR 0x23 = enc[i]

那就可以直接反過來:

input[i] = enc[i] XOR 0x23

因此只需要把:

51 10 55 7c 5b 13 51 02

全部 XOR:

0x23

就可以還原 Password。


0x06 用 Python 還原

這種時候其實不需要手算。

直接寫一小段 Python:

enc = [
    0x51,
    0x10,
    0x55,
    0x7c,
    0x5b,
    0x13,
    0x51,
    0x02
]

password = ''.join(chr(x ^ 0x23) for x in enc)

print(password)

執行:

python3 solve.py

得到:

r3v_x0r!

所以我們目前推測:

Password = r3v_x0r!

但跟 Day11 一樣。

先不要因為 Script 跑出答案就直接宣布結束。

我們還可以:

驗證自己的分析

0x07 用 GDB 驗證

開啟:

gdb ./chall

因為這次是 Stripped Binary,Function Name 可能不好找。

所以可以先從 IDA 找到:

check_password

對應的 Address。

假設是:

0x1169

如果 Binary 是 PIE,實際 Address 會因為 ASLR 改變,因此可以先:

start

再根據 Binary Base Address 計算實際位置。

接著在 XOR 附近下 Breakpoint。

執行後觀察 Register 或 Memory。

可能會看到類似:

movzx eax, byte ptr [rbp+rax-0x20]
xor   eax, 0x23
cmp   al, byte ptr [rbp+rdx-0x18]
jne   wrong

把它翻回比較容易理解的形式:

tmp = input[i];

tmp ^= 0x23;

if (tmp != enc[i])
    goto wrong;

所以我們在 Decompiler 裡看到的邏輯確實沒有錯。


0x08 實際測試

最後直接執行:

./chall

輸入:

Password: r3v_x0r!

得到:

Correct!

成功。


0x09 整個分析流程

今天這題如果整理成完整流程,大概就是:

chall
  ↓
file
  ↓
發現 Stripped Binary
  ↓
strings
  ↓
找到 Correct! / Wrong!
  ↓
IDA / Ghidra
  ↓
String Xref
  ↓
找到驗證附近的 Function
  ↓
追蹤 Caller / Callee
  ↓
找到 Password Validation
  ↓
分析 Control Flow
  ↓
發現 XOR 0x23
  ↓
取得 Encoded Bytes
  ↓
Python 還原
  ↓
GDB 驗證
  ↓
取得 Password

這其實已經非常接近平常解 Reverse Challenge 時會使用的思路了。


0x0A Reverse 不只是看 Assembly

做到 Day15,可以稍微整理一下 Reverse Engineering 的核心。

Reverse 並不是:

看到 Binary
↓
把所有 Assembly 從頭看到尾

更多時候其實是:

Information Gathering
        ↓
找到有價值的線索
        ↓
提出 Hypothesis
        ↓
追蹤 Data / Control Flow
        ↓
理解 Validation Logic
        ↓
寫 Script 還原資料
        ↓
Dynamic Analysis 驗證

例如今天:

Correct!

只是一個 String。

但透過:

String
↓
Xref
↓
Function
↓
Validation Logic
↓
Encoded Data

最後就可以一路追到真正的 Password。

這也是為什麼前面一直在介紹:

Strings
Xref
Control Flow
Decompiler
GDB

因為這些東西並不是獨立存在的工具或知識。

真正解題時,它們通常會全部串在一起。


0x0B Reverse 篇結束

到今天為止,前半段的 Reverse Engineering 基礎差不多告一段落。

我們從:

C Code

一路看到:

Binary
ELF
Assembly
Register
Stack
Function Call
Static Analysis
IDA / Ghidra
GDB
Control Flow
Xref
Stripped Binary
Crackme

當然 Reverse Engineering 還有非常多東西可以繼續深入,例如:

Anti-Debug
Packing
Obfuscation
VM
Malware Analysis
C++ Reverse
Firmware Reverse

但如果目標是開始解 Binary CTF,現在已經有足夠的基礎可以繼續往下走了。

而接下來,系列會開始進入另一個 Binary CTF 的大主題:

Pwn

前面 Reverse 的目標通常是:

理解程式在做什麼

接下來我們要開始問的是:

程式哪裡做錯了?

以及:

我們能不能利用這個錯誤控制程式?

下一篇就正式開始進入 Pwn 的世界


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

尚未有邦友留言

立即登入留言