> ## 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.

# 逆向工程到底在逆什麼？—— 寫給會寫程式、但沒碰過組語的你
- URL: https://taiwanding.com/ni-xiang-gong-cheng-dao-di-zai-ni-shi-mo-xie-gei-hui-xie-cheng-shi-dan-mei-peng-guo-zu-yu-de-ni/
- Published: 2026-08-07T11:15:13.000Z
- Updated: 2026-08-10T11:10:00.000Z
- Author: Kevin Chen
- Tags: reverse, #逆向, #教學, #基礎

> 這是逆向系列的**觀念篇**，只講觀念，幾乎不出現組語，下一篇是方法篇，讀者設定：你會寫程式（懂變數、函式、迴圈就夠），但從來沒碰過反組譯、Ghidra、組合語言，讀完你會知道「逆向在幹嘛、為什麼需要那一堆工具」，之後看方法篇才不會被組語嚇跑。

---

## 先回答那個沒好好解釋的問題

你可能看過很多逆向的文章，裡面都是一堆十六進位、暫存器、`mov` `ldr` `cmp`，看起來像天書，然後你心裡有個問題一直沒被回答：

**這些人到底在幹嘛？他們在「逆」什麼？**

我用一句話先給你答案，這一篇剩下的部分都是在把這句話講清楚：

> **你寫的原始碼，在編譯的時候被「壓扁」了，壓扁的過程丟掉了很多東西，逆向，就是拿著那個被壓扁的成品，把丟掉的東西盡量還原回來。**

---

## 等一下，這技能到底能幹嘛？

在講原理之前，先給你讀下去的理由，逆向不是只有駭客電影裡才用得到，它解決的是同一類問題：**你手上有一個程式，但沒有它的原始碼，而你需要知道它在幹嘛。**

實際的場景：

- **資安研究／漏洞挖掘** —— 廠商不會給你原始碼，但你要找出它哪裡寫錯了
- **惡意程式分析** —— 抓到一個可疑檔案，它到底在偷什麼、連去哪裡？
- **軟體相容與維護** —— 公司有個十五年前的系統，寫的人跑了、文件沒了、原始碼不見了，但它還在跑
- **確認你買的東西在幹嘛** —— 那個 App 是不是在背景偷傳你的資料？
- **CTF 與資安競賽** —— 逆向是固定出現的大題型
- **純粹的好奇** —— 這個功能是怎麼做到的？

**共通點都是「黑箱」，逆向就是把黑箱打開的技術。**

---

## 一個你每天都在做、但沒意識到的事

你寫這樣一段程式：

```c
int check_password(char *user_input) {
    char *correct = "hunter2";
    if (strcmp(user_input, correct) == 0) {
        return 1;
    }
    return 0;
}

```

（用 C 舉例是有原因的：**C 會被編譯成 CPU 直接執行的機器碼**，最能示範這篇要講的事，Python、JavaScript 這類語言的運作方式不太一樣，它們有自己的一套，我們先不管。）

你按下編譯，電腦給你一個執行檔。

**問你一個問題：那個執行檔裡，還找得到 `check_password` 這個名字嗎？找得到 `correct` 這個變數名嗎？**

答案是：**不一定，而且這兩個名字的命運還不一樣。**

`correct` 這種函式內部的變數名，在**不含除錯資訊的一般 release 版本**裡通常**找不到**，`check_password` 則**可能還在**（留在符號表裡），也可能在後續處理中被移除。

但真正重要的不是「在不在」，而是這件事：**CPU 執行的時候，完全不靠這些名字**，它們對程式的運作是可有可無的 —— 這才是為什麼它們「可以」被丟掉。

**它們從來就不是給電腦看的，它們是給你看的。**

---

## 電腦眼中的世界，沒有「名字」

這是整個逆向的地基，我慢慢講。

你寫程式的時候用名字：`user_input`、`check_password`、`correct`，這些名字讓**你**能理解程式在幹嘛。

但 CPU 不需要名字，CPU 只認得三種東西：

1. **位置** —— 「去第 5000 號格子拿東西」
2. **暫存器** —— CPU 手邊的幾個小抽屜，用來暫放正在處理的數字
3. **動作** —— 「把這兩個數字相加」「如果相等就跳到第 3000 號格子」

**注意這裡面沒有「名字」**，對 CPU 來說，`check_password` 不是一個名字，是「從第 4384 號格子開始的那一段指令」。

至於 `correct` 這個變數，它的處境更有趣一點：`correct` 本身是一個**指路標**（它記著「那串文字放在哪」），而 `"hunter2"` 這串**文字本身**被放在另一個地方，編譯後，指路標可能存在 CPU 的小抽屜裡、可能存在某個暫存區、甚至可能被編譯器發現「根本不需要這個指路標」而直接省掉；**而那串文字，通常會被放進一塊專門存放固定資料的區域**。

這個區別現在看起來很細，但它很重要 —— **後面我們講「為什麼用工具撈得到字串」，撈到的就是那塊區域裡的東西。**

所以編譯做的事，本質上是**翻譯**：

```
你寫的（給人看）              編譯後（給 CPU 看）
─────────────────────────────────────────
check_password          →    位置 0x1120 的那段指令
user_input              →    暫存器裡的一個值
correct（指路標本身）    →    暫存器或暫存區裡的一個值，
                             也可能被編譯器發現不需要而省掉
"hunter2"（文字本身）    →    通常放進一塊唯讀資料區
if a == b               →    「比較，然後跳」

```

翻譯完之後，**那些名字就沒用了**，CPU 靠位置和暫存器就能跑，名字可以整包丟掉。

**這裡要澄清一個常見的誤會：編譯和「移除名字」不是同一件事。**

編譯之後，CPU 已經不需要那些名字了 —— 但名字**未必立刻消失**，很多建置流程會把符號留著（給連結器用、給除錯器用），所以你拿到的執行檔可能還帶著一部分名字。

**`strip` 是移除這些資訊的常見方式之一**（剝除的意思）—— 它會清除執行時不需要的符號與除錯資訊，讓檔案更小。但不是只有它會讓名字消失：**編譯器和連結器也可能依照建置設定，一開始就不保留某些名稱。**

所以一個名字的命運有好幾種：**可能從一開始就沒被寫進檔案**、**可能保留在符號表或除錯資訊裡**、**也可能在連結或 `strip` 的時候被移除**。

「有沒有除錯資訊」「有沒有 strip」「是 release 還是 debug」——**這是相關但不同的幾件事**，不要混成一件。你真正該養成的習慣是：拿到檔案先看它現在還剩下什麼，而不是假設它應該剩下什麼。

---

## 用一個比喻把這件事鎖死

想像一本食譜。

編譯＝把「標籤」撕掉，只留「精確的位置」 同一份食譜，人看的版本有語意，機器跑的版本只有座標 原始碼（給人看） 取「主要食材」 （雞胸肉）200 克 ← 標籤告訴你「那是什麼」 加入「醃料 A」 （醬油、糖、米酒） ← 括號裡是給人的說明 靜置 30 分鐘 看得懂在幹嘛 編譯 撕標籤 執行檔（給 CPU 看） 取 3 號櫃第 2 格 的 200 克 位置精確，但不知道是什麼 加入 5 號罐 （？？？） 罐裡調了什麼，看不到 靜置 30 分鐘 照做得出來，卻不懂在幹嘛 關鍵：撕掉標籤後，資訊不是變模糊，是「變精確但沒有意義」 逆向＝拿著右邊這本，靠動作和順序，把每個「那是什麼」反推回去 

圖解 1：編譯就像把食譜的標籤全部撕掉 —— 位置還在，意義沒了。

**原文（給人看的版本）**和**撕掉標籤的版本**差在哪？我們一步步看。

**原始碼版本的食譜**長這樣：

> 「取『主要食材』（雞胸肉）200 克，加入『醃料 A』（醬油、糖、米酒），靜置 30 分鐘。」

括號裡的東西、還有「主要食材」「醃料 A」這種**標籤**，是給讀的人理解用的。

**編譯後的食譜**，被人把所有標籤撕掉了，變成：

> 「取 3 號櫃第 2 格的 200 克，加入 5 號罐，靜置 30 分鐘。」

**你還是能照著做**（位置、數量、順序都精確地在），但你不知道 3 號櫃第 2 格裝的是什麼、5 號罐裡調了什麼。

**注意這個比喻的重點**：撕掉標籤之後，剩下的資訊不是變模糊了，而是**變得精確但沒有意義**——機器需要的是「去哪裡拿」，人需要的是「那是什麼」。編譯丟掉的正是後者。

**這就是編譯後的程式**，動作全在、跑得起來，但所有幫助理解的標籤都被撕掉了。

而**逆向工程，就是拿著這本被撕掉標籤的食譜，靠著「動作和順序」反推出每個『那個』到底是什麼。**

- 看到「靜置 30 分鐘」→ 猜這步是醃製
- 看到「加入 5 號罐之後顏色變褐」→ 猜 5 號罐是醬油
- 一步一步，把撕掉的標籤重新貼回去

**這就是逆向的全部**，剩下的都是細節。

---

## 編譯到底丟掉了哪些「標籤」？

不只是名字，編譯這個「壓扁」的動作，丟掉的東西可以分成四層，由淺到深：

編譯丟掉的四層資訊 越往下越難重建，最底層無法唯一還原 ① 名字 userPassword → 堆疊上某一格 checkPassword → 某個位置 自己重新命名 ② 型別 同一串 bytes 是整數？文字？位置？機器碼裡沒有這個資訊 看用法反推 ③ 結構 if / while / for / switch → 大多變成「比較 + 條件跳轉」 從跳轉關係重建 ④ 意圖 註解、命名品味、「為什麼這樣設計」的理由 無法唯一還原 重建難度 逆向＝重建 ①②③，然後靠它們去推測 ④ 同一份機器碼可以對應好幾種原始寫法與動機，光看產物分不出來 

圖解 2：編譯丟掉的四層資訊。工具能幫你的主要在 ①②③，④ 永遠是人的工作。

下面我用你寫程式的經驗，一層一層對照：

### 第一層：名字（最容易理解的）

`check_password` → 沒了，變成一個位置，`user_input` → 沒了，變成暫存器裡的一個值。

**逆向時你要做的**：幫每一段沒名字的東西，自己取一個名字。你會在工具裡把「位置 0x1120 那段」重新命名成 `check_password`，因為你看懂了它在幹嘛。

### 第二層：型別（這層最反直覺）

你寫 `int age = 25;`，你知道它是整數。你寫 `float price = 25.0;`，你知道它是浮點數。**編譯器知道，因為你告訴它了。但編譯完之後，這個資訊沒有被寫進執行檔裡。**

為什麼？因為 CPU 不需要它，CPU 只需要知道「對這 4 個 bytes 做整數加法」還是「做浮點數加法」—— 而**這個資訊藏在「指令」裡，不在「資料」裡**。

具體一點。假設你在執行檔裡看到 4 個 bytes：

```
68 65 20 66

```

**這是什麼？** 可能性至少有四種：

- 一個整數
- 一個小數
- 一個「指向別處的位置編號」
- **四個文字**（`h`、`e`、空格、`f`）

同一串 bytes，四種可能的身分 機器碼裡沒有型別資訊，只能從「它被怎麼用」去推 68 65 20 66 4 個 bytes，就這樣 一個整數 若拿去做加法運算 → 支持這個假設 一個小數 若拿去做浮點運算 → 支持這個假設 一個位置編號 若拿去「到那裡取東西」 → 支持這個假設 四個文字 h e ␣ f 最容易被人眼認出 為什麼「找字串」常常有效？ 因為文字是這四種裡最容易一眼認出來的 一段連續的可列印 bytes，人掃過去就看得出來 —— 這正是 strings 的原理 型別不是「讀出來」的，是「推出來」的 —— 而且是「支持」不是「證明」 

圖解 3：同一串 bytes 的多重身分。型別是編譯時丟掉的資訊，只能從使用方式反推。

**光看這 4 個 bytes，你分不出來**，它們就只是四個數字。

**那怎麼辦？看它被怎麼用。**

如果程式拿這 4 個 bytes 去做「加法運算」→ **支持**「它是數字」這個假設。  
如果程式拿它去「一個字元一個字元跟另一段比較」→ **支持**「它是文字」。  
如果程式拿它去「當作位置，去那裡取東西」→ **支持**「它是一個位置編號」。

注意用詞是「支持」不是「證明」。同一塊資料被用在多個地方、或被當成好幾種東西看待，都是真實會發生的事。**逆向裡的判斷幾乎都是「目前最合理的解釋」，不是鐵證。**

> **這就是逆向最核心的動作之一：型別不是「讀出來」的，是「推出來」的。**

**先講一個好消息**：文字（字串）是這裡面**最容易認出來**的一種，因為一段連續的、剛好都落在「可列印文字」範圍的 bytes，看起來就是一句話 —— 人眼一掃就認得出 `Please enter your password` 不是隨機數字。

**這就是為什麼有工具專門幫你把這種片段找出來**（後面會講到的 `strings`），也是為什麼「找字串」常常是逆向的第一招。

順帶說清楚這個工具在做什麼，免得你把它想得太神：**它就是掃過檔案，把「連續好幾個 byte 剛好都落在可列印範圍」的片段印出來**。它不理解檔案格式、也不管那段 bytes 原本是不是字串 —— 所以它會漏掉被拆開或編碼過的文字，也會撈出一堆剛好長得像文字的雜訊。**它是一個很粗但很便宜的篩子。**

**難的是其他型別**：一個 4 bytes 到底是整數、小數、還是位置編號？一堆 bytes 排在一起是一個陣列、還是一個包含好幾個欄位的結構？**這些沒有工具能百分百告訴你，只能靠「它被怎麼用」去推。**

### 第三層：結構（你的 if 和 while 都不見了）

你寫的 `if`、`while`、`for`、`switch`，編譯後**全部變成同一種東西**：「比較，然後跳」。

```
你寫的                    編譯後
──────────────────────────────────
if (x > 5) {...}    →    比較 x 和 5，如果不大於就跳過下面
while (x < 10) {...} →   比較 x 和 10，如果小於就跳回上面

```

**看到沒有？在常見、未經大幅最佳化的形態裡，`if` 和 `while` 經常都變成「比較 + 條件跳轉」**，差別在跳的方向。

**但跳轉方向本身不是可靠的判定規則** —— 編譯器會做迴圈旋轉、把判斷搬到底部、把區塊重新排序，跳的方向未必對應你直覺中的結構。它是線索，不是定則。

**但這也只是最常見的形態，不是唯一的。** `switch` 如果分支很多，編譯器可能改用一張「跳轉表」（查表決定跳去哪），長得完全不像一串比較；很短的 `if` 也可能被換成「不跳，直接兩個值選一個」的指令。最佳化開得越強，變形越多。

**逆向時你要做的**：從實際的跳轉關係，重建出原本的控制流長什麼樣。

### 第四層：意圖（這層永遠回不來）

你寫的註解、你取的好名字、你腦子裡「為什麼這樣設計」的理由 —— 這些編譯後**無法唯一還原**，同一份機器碼可以對應到好幾種不同的原始寫法和動機，光看產物分不出來。

**這一層是逆向真正的難處，也是機器幫不了你的地方**，前三層工具可以幫你猜個七八成，但「這段程式為什麼要這樣寫、作者在想什麼」，只能靠你自己的經驗和推理。

---

## 所以，逆向工程的定義是這樣

把上面整合起來：

> **編譯是一個「有損壓縮」的過程 —— 它把你寫的、充滿名字和結構的原始碼，壓成一堆只有位置和動作的機器碼，過程中丟掉了名字、型別、結構、意圖。**
> 
> **逆向工程，就是在缺少原始碼的情況下，靠著「還留著的東西」，把「被丟掉的東西」盡量重建出來。**

「有損壓縮」這個詞很精準。就像 JPEG 把照片壓小、丟掉一些細節，你放大回去永遠回不到原本的畫質 —— 編譯也是。

**你逆向出來的東西，永遠不會跟原始碼一模一樣**（名字是你自己取的、結構是你自己重建的），而且更誠實的說法是：**你建立的是一個「足以解釋你所觀察到的行為」的模型**，不是原始碼的複製品，這個模型可能在你還沒觀察到的情況下就失準 —— 所以逆向的結論永遠要留一句「就目前看到的而言」。

---

## 那為什麼有「留下來」的東西可以利用？

你可能會問：如果編譯把名字都丟了，那逆向不是無從下手嗎？

**關鍵來了 —— 不是所有東西都能丟。**

有一條鐵律：

> **凡是「程式跑起來時，得由外面提供」的東西，相關的線索就不容易被清乾淨** —— 因為清掉了，就沒人找得到該去哪裡拿。

舉幾個例子：

**如果程式是用一般的「動態連結」方式打包的，那它向外部函式庫要的功能名稱，經常會留下來。**

舉例：你的程式要「比較兩個字串」，用了一個叫 `strcmp` 的現成函式，這個函式的程式碼不在你的執行檔裡，而是在系統的共用函式庫裡 —— 所以**程式跑起來的時候，得有人幫它找到 `strcmp` 在哪**，負責這件事的元件（載入器）需要名字才能找，因此這個名字通常會被保留下來。

**這讓「程式向外要了哪些功能」成為一份很好用的清單**，它會洩漏大量資訊：

- 要了 `strcmp` / `strncmp`（字串比較）→ **優先懷疑**這程式裡有「比對」，可能在驗證什麼
- 要了跟「加密」有關的功能 → **優先懷疑**有加密，秘密可能被藏起來了
- 要了跟「網路」有關的功能 → **優先懷疑**它會連網，去看它連去哪
- 要了跟「反調試」有關的功能 → **優先懷疑**它在防著別人分析

**光看這份清單，你還沒讀任何一行程式，就已經能猜出這個程式大概在幹嘛了**，這是逆向裡投報率最高的一招。

**但要注意它的邊界** —— 這份清單只涵蓋「執行時才去跟外面要」的功能，有些東西不會出現在上面：

- 程式**把功能直接打包進自己身體裡**（叫做靜態連結）
- 編譯器把小功能**直接展開嵌進程式碼裡**
- 程式**自己重寫一份**，不跟系統借
- 程式**執行時才臨時去借**

所以正確的說法是：**這份清單是「建立假設的起點」，不是「能力的完整清冊」。** 看到 `strcmp` 就大膽假設有字串比對；但**沒看到加密相關的功能，不能就此斷定它沒有加密**。這個分寸很重要，後面的文章會反覆提到。

---

## 「找字串」為什麼常常有用？

如果你玩過一點逆向，或看過別人玩，你會發現一個常見的第一步：**跑一個叫 `strings` 的工具，在掃描範圍內找出連續的可列印 bytes。**

為什麼這招常常有效？

因為**很多秘密就是明文字串**，而字串（跟名字不一樣）**經常會被留在程式裡**，因為程式執行時要用到它們（顯示訊息、比對密碼、連線網址）。

所以「跑 strings 找字串」不是偷懶，**它是投報率最高的第一步**，你該先試。

但它會在三種情況失效，而理解這三種失效，正好幫你看清「逆向的難度階梯」：

strings 是一個很粗、但很便宜的篩子 它只找「連續的可列印 bytes」，不理解檔案格式 檔案裡的東西 明文字串 被切碎的字串 加密過的資料 純數值 / 指令 strings 連續可列印 bytes？ 不理解格式 不管原本是啥 撈得到 ✓ 明文字串、看起來像文字的雜訊 漏掉 ✗ 被切碎的（只給你半句話） 被加密／編碼的（一堆亂碼） 秘密根本不是字串的情況 每一種「漏掉」，都在告訴你該換一個視角看了： 半句話 → 去看指令，把尾巴撿回來 一堆亂碼 → 去讀它怎麼解密 什麼都沒有 → 秘密可能是演算法本身，得完整重建邏輯 

圖解 4：strings 的能與不能。它是逆向的第一招，也是難度階梯的第一階。

**① 字串被拆碎藏起來** —— 有時候編譯器或作者會把一個字串切成好幾段，`strings` 只撈得到其中一段。你會看到半句話。  
**這時你被迫往下一層走**：去看程式的實際指令，把碎片拼回來。

**② 字串被加密了** —— 秘密不是明文放著，是加密後藏起來，程式執行時才解密。`strings` 什麼都撈不到，只有一堆亂碼，**這時你要做的更多**：看懂它怎麼解密，然後自己重跑一次解密。

**③ 秘密根本不是字串** —— 有時候秘密是一個「演算法」，比如「怎麼判斷一組序號是否合法」，這沒有任何字串可撈，秘密藏在計算邏輯裡。  
**這時你要完整重建那個演算法。** 這是最難的一階，也是真正的逆向功力所在。

> **所以「我只會跑 strings 找字串」不是能力不足，是你還沒遇到需要往上爬的題目。** 而知道「上面那幾階是什麼」，就是這篇文章想給你的地圖。

---

## 為什麼需要那一堆工具？（一句話版本）

`strings`、`objdump`、`Ghidra`、`radare2`、`gdb`…… 為什麼這麼多？剛入門看到會頭暈。

**因為它們在回答不同的問題**，一旦你用「這個工具回答什麼問題」來分類，混亂就消失了，我先給你最粗的分法，細節留到方法篇：

**「這個程式對外宣告／索取了什麼？」** —— 用 `nm`、`readelf` 這類看符號與 metadata 的工具，**不用讀程式碼就有大量情報**，該最先做。

**「檔案裡有沒有看起來像文字的東西？」** —— 用 `strings`，注意它跟上面那類不同：**它不理解檔案格式，只是掃過去找連續的可列印 bytes**，粗、但極便宜。

**「這段程式的實際指令是什麼？」** —— 用「反組譯」工具（像 `objdump`），它把那堆 0 和 1 翻譯成 CPU 指令，**這是最接近原始證據的一層**（真正一〇〇% 不帶解讀的其實是那堆 bytes 本身，但人類讀不動），讀起來最硬，但也最不容易被誤導。

**「這段邏輯用人話講是什麼？」** —— 用「反編譯」工具（像 `Ghidra`），它會把那些硬邦邦的指令，**盡量還原成類似 C 語言的樣子**，好讀很多，但注意它是「盡量還原」，是猜的，**有時候會猜錯**。

**「這程式跑起來實際會怎樣？」** —— 用「動態分析」工具（像 `gdb`、Frida）。前面三種都是「不執行程式、光看」，這種是「讓程式真的跑，中途攔截觀察」。

一個很重要的取捨原則，先記著：

> **越原始的工具越可靠（但難讀），越高階的工具越好讀（但可能騙你）。**
> 
> 所以老手的做法是：用高階工具（Ghidra）快速看懂大方向，遇到讀起來怪怪的地方，**回去用低階工具（objdump）驗證**，因為越低階的工具，加進去的猜測越少。

---

## 一張圖總結：逆向在幹嘛

```
你寫原始碼                    有名字、有型別、有結構、有意圖
    │
    │  編譯（有損壓縮，丟掉標籤）
    ▼
執行檔                        只剩位置、動作、和「仍留下的線索」
    │
    │  逆向（靠仍留下的線索，反推被丟掉的）
    ▼
你重建的理解                  自己取的名字、推出的型別、重建的結構
                             （足以解釋已觀察的行為，
                               但不一定與原始碼完整等價）

```

**逆向不是魔法，是考古**，你面對的是一個被撕掉標籤的成品，靠著還留著的線索（動作、順序、跟系統借用的功能、沒被藏起來的字串），一片一片把原本的意義拼回來。

---

## 你現在該有的心態

如果你是完全沒碰過逆向的人，讀完這篇，你該帶走的**不是任何一個工具指令**，而是這三個觀念：

1. **編譯會丟東西，逆向就是把丟掉的撿回來**，名字、型別、結構被丟了，但功能還在。
2. **有些東西不容易丟掉，那些就是你的線索**，跟系統借用的功能清單、明文字串、程式的動作順序 —— 但記得它們是「起點」不是「全貌」。
3. **工具沒有高下，只有分工。** 看清單、看指令、看人話版、看執行時 —— 各自回答不同問題。

有了這三個觀念，你再去看任何一篇「滿是十六進位和 `mov` 的逆向文章」，都不會再覺得是天書 —— 你會知道那個人**只是在把撕掉的標籤貼回去**而已。

---

## 下一篇：方法篇

有了這篇的觀念地基，方法篇，會帶你真的碰一次組語，你會親眼看到：

- 一個「變數」在底層到底長什麼樣（全域變數、參數、區域變數、常數，四種完全不同）
- 為什麼有時候 `strings` 只能撈到「半句話」（真實案例）
- `objdump`、`radare2`、`Ghidra` 到底怎麼選、什麼時候用哪個
- 一套「拿到陌生程式時的分析起手式」

到時候你會發現，因為有這篇打底，那些組語看起來一點都不可怕了。

---

*本文為逆向工程入門筆記，寫給想入門但被組語勸退的人，有錯歡迎指正。*