pwnable.kr [Toddler's Bottle II]
random
連到機器後來看一下檔案內容:
1 | random@ubuntu:~$ ls |
沒有權限,來看一下 random.c:
1 |
|
這題的問題主要在於 rand() 的使用,在使用之前應該設定 srand() 來讓亂數隨機產生,否則每次程式執行產生的亂數都會一樣。既然亂數相同,我們就可以直接利用 XOR 可逆的特性求出 key 的值。
直接使用 C 實作:
1 |
|
獲得 key 之後送出到檔案就可以拿到 flag。
1 | random@ubuntu:~$ ./random |
input2
連到機器一樣看一下檔案內容:
1 | input2@ubuntu:~$ ls |
沒有權限,來看一下 input2.c:
1 |
|
很明顯地,這是一個闖關題,而我們需要符合所有的條件才能獲得 flag,以下分段來用 Python 解決:
- Stage 1
1 | // argv |
很直接,就是設定好參數即可,C 語言的鍵值會被轉換為 ascii 編碼,如下所示:
1 | from pwn import * |
- Stage 2
1 | // stdio |
這次要設定 I/O, fd=0 的時候很直觀,就是一般的讀取輸入,但 fd=2 是 stderr,一般情況下並不能直接讀取,因此需要使用 pipe 來指定資料流的走向。如下所示:
1 | import os |
- Stage 3
1 | // env |
蠻簡單的,設定好 env 的參數即可:
1 | p = process('./input2', env={'\xde\xad\xbe\xef' :'\xca\xfe\xba\xbe'}) |
- Stage 4
1 | // file |
這次必須讀到指定檔案的指定內容,因此我們按照要求建立一個檔案即可:
1 | with open("\x0a", 'w') as f: |
- Stage 5
1 | // network |
這段 code 看起來有點嚇人,但其實他只做兩件事:
- 從攻擊者給的
argv['C']拿到 port 的數值,並把該 port 開啟監聽 - 確認該 port 拿到的數值是否與要求相符
所以可以直接輕鬆用 pwntools 解決:
1 | argv[ord('C')] = 1234 |
將五個關卡的 code 整合起來作為 exploit:
1 | from pwn import * |
有點麻煩的是,我們預設在 /home/input2 是沒有寫入權限的,因此我們需要在 /tmp 建立一個資料夾來執行這段 code,但我們在 /tmp 沒有 flag 的檔案,因此需要使用 ln -s 來進行文件鏈結:
1 | input2@ubuntu:/tmp$ mkdir m3t30r |
接著把 exploit 也送上去執行即可:
1 | input2@ubuntu:/tmp/m3t30r$ python3 exploit.py |
leg
連到機器時輸出了一堆看起來是開機的 log,來看一下他給的檔案內容:
1 | ls |
給了一個 flag 跟 leg 檔案,接下來看題目的原始碼部分:
leg.c
1 |
|
leg.asm
1 | (gdb) disass main |
看起來很明確,我們要找出 key1()+key2()+key3() 的值。一個一個來看:
key1
1 | int key1(){ |
1 | (gdb) disass key1 |
前兩行是函數的 prologue,代表函數開始的地方,而後三行是函數的 epilogue,代表函數結束的地方,因此不用理他們,從第三行開始:
mov r3, pc:把pc暫存器(Program Counter,程式計數器)的值複製到r3暫存器mov r0, r3:把r3暫存器的值複製到r0暫存器,而 calling convention 會把r0的值作為 return 的值
所以回傳值就是 pc 的值,但 pc 的值是多少呢?
在 CPU 的執行順序中,會採用 Fetch 擷取 -> Decode 解碼 -> Execute 執行 的架構,因此代表當某一行指令執行時,下一行指令正在解碼,而下下行指令正在擷取。而 pc 則永遠指向正在擷取的位址,因此在這個情況下,就是 mov r3, pc 執行時下下行的位址:0x00008ce4,這就是 key1。
key2
1 | int key2(){ |
1 | (gdb) disass key2 |
一樣從第三行開始看,他 push 了一個 r6 到 stack 上,但後面看起來跟 r3 一點關係都沒有,所以只是個幌子,不理他,直接看 r3 的地方:
mov r3, pc:把pc暫存器(Program Counter,程式計數器)的值複製到r3暫存器adds r3, #4:把r3暫存器的值加4
後面是一些 stack 的處理,一樣不用理他,而他最後一樣 mov r0, r3,所以最終的回傳值就是 r3 的值。執行 pc 時的值指向下下行的 0x00008d08,因此最後的回傳值就是 0x00008d08 + 4 = 0x00008d0c。
key3
1 | int key3(){ |
1 | (gdb) disass key3 |
前兩行一樣不看,從第三行開始:
mov r3, lr:把lr暫存器(Link Register,連結暫存器)的值複製到r3暫存器- 把
r3暫存器的值複製到r0暫存器,而 calling convention 會把r0的值作為 return 的值
所以我們的目標就是找到 lr 暫存器的值,但 lr 暫存器的值應該是多少呢?
lr 暫存器是用來存放函數的 return address,因此就是跳回 main 時應該跳回的位址,來看一下 main 的組合語言:
1 | 0x00008d7c <+64>: bl 0x8d20 <key3> |
上述組合語言中,0x00008d7c 呼叫了 key3 函式,因此應該跳回的位址就是下一行的 0x00008d80,也就是 lr 的存放值。因此最終的回傳值就是 0x00008d80。
總和以上三個 key,就可以算出我們應該給的值:
送出到 leg 檔案即可獲得 flag:
1 | / $ ./leg |
mistake
連到機器後來看一下檔案內容:
1 | mistake@ubuntu:~$ ls |
有一個 password 和 flag 檔案,來看一下 mistake.c:
1 |
|
乍看之下看起來沒什麼漏洞,但仔細觀察後可以發現,他在 if 使用的地方出現了一些問題:
1 | if(fd=open("/home/mistake/password",O_RDONLY,0400) < 0){ |
正常情況下,這個條件式想要判斷的是讀取檔案有沒有成功,如果讀取失敗就會讓 open("/home/mistake/password",O_RDONLY,0400) 的值為 -1,讓判斷式成立。但在上述情況下,使用方法應該如下:
1 | if((fd=open("/home/mistake/password",O_RDONLY,0400)) < 0){ |
如果使用題目的寫法,就會牽涉到一個運算子優先順序(Operator Precedence)的問題:在 C 語言中,比較運算子(< 或 >)的優先序預設是高於賦值運算子(=),因此會先進行比較再把值存到 fd,如此就會與原本的目的不同。
在這個情況下,因為 password 檔案存在因此讀取會成功,open("/home/mistake/password",O_RDONLY,0400) 的回傳值不會 < 0,因此判斷式的結果為 0,不執行 if 條件式中的內容,並且讓 fd = 0。
接著看下面的 code:
1 | printf("do not bruteforce...\n"); |
在中斷 0~20 秒不等之後,又執行了一段 if 條件式,詳情如下:
1 | if(!(len=read(fd,pw_buf,PW_LEN) > 0)){ |
首先,這段 code 原本是為了讀取剛剛打開的 password 檔案,所以使用了剛剛讀取使用的 fd 變數。但因為運算子的使用錯誤,讓 fd = 0,變成一般的 stdin 讀取,如此就不是讀取檔案了,是讀取使用者的輸入,讓使用者可以控制 pw_buf 的內容。
接著是與剛剛一樣的問題,原本使用者是想確認有讀到 password 檔案,因此使用 len 來確定長度不小於 0,但正確的使用方式為:
1 | if(!((len=read(fd,pw_buf,PW_LEN)) > 0)){ |
在題目的情況下,> 的優先序大於 =,而 read() 函式回傳的是讀取到的字串大小,因此只要我們有輸入,回傳值就會 > 0,加上前面的 ! 讓整個判斷式為 0,也不會執行 if 判斷式內的內容。
最後,他會比對我們輸入的 pw_buf2 XOR 指定的 XORKEY=1 之後是否相符,因此我們只要輸入 XOR 後會符合的兩組密碼即可。來用 Python 試試看:
1 | >>> chr(ord("A")^1) |
直接輸入兩組密碼即可過關(注意 PW_LEN=10):
1 | mistake@ubuntu:~$ ./mistake |
- Title: pwnable.kr [Toddler's Bottle II]
- Author: Albert Huang
- Created at : 2026-09-07 10:42:30
- Updated at : 2026-09-09 21:35:05
- Link: https://blog.albert-web.tw/2026/09/07/pwnable-kr-writeup-2/
- License: This work is licensed under CC BY-NC-SA 4.0.