###### tags: `CTF Write Up` # 2019 AIS3 EOF quals ## Reverse1: H0W 這題作者用 編譯了 一個 c share library 並用 python 這題解題上分成幾個步驟 1. 了解 在 python 上使用 c 的shared library 方式 2. trace shared library 每個呼叫到的函式作用(gdb+ida) 3. 配合 python 腳本分析整體流程 4. 逆推流程與撰寫腳本 Debug 是我花最久時間的,因為以前完全沒遇過過要debug python 的狀況 ### python shared library 因為C 的速度比python快很多,因此 python share library 常用在一些要加速的功能上,編譯的方式可以看下方ref https://stavshamir.github.io/python/making-your-c-library-callable-from-python-by-wrapping-it-with-cython/ ### trace shared library 接著就直奔主題吧,針對debug python 的部分,官方就有一些文件了,[請看這](https://wiki.python.org/moin/DebuggingWithGdb) 這題的sharedlibrary 我不確定是不是故意的但作者好像故意把會 call 到的 function symbol 給刪除了,導致沒辦法直接用靜態分析就對應 function 名,因此我用 GDB 下斷點加上修改 python script去讓他每 call 完一個 lib function 後停住,這樣會比較方便抓出call的是哪個function 抓出每個function後,就可以開始靜態加動態分析去了解整個流程,下面是我理解出的每個 function 功能 nini3: 開檔 output.txt nini1: 抓時間 nini4: srand(time) nini5: 做混淆(會用到rand去生成隨機數) nini2: 將時間寫入output(包含年月星期時分秒)<-沒有日期所以有4種可能 nini6: 寫檔 output.txt 下面這些會再nini4 被 call 到 ichinokata: 混淆函數1 ninokata: 混淆函數2 sannokata: 混淆函數3 yonnokata: 混淆函數4 ### 程式整體流程 1. 抓現在 timer 暫存器的值: X 2. 在同個資料夾下開啟output.txt,準備寫入 3. 將剛剛抓得到的 X 丟進 srand 4. 將一個文檔分成4byte 一組 for 迴圈執行 5,6 5. 用 c rand function 得出一個隨機數後 mod 3 這個值會被拿來決定混淆的方式 6. 將混淆過後的結果寫入 output.txt 7. 最後將時間用 gmtime 的形式寫進 output.txt ### 逆推流程與撰寫腳本 ![](https://i.imgur.com/kNWUUjw.png) 這題的關鍵就在它有在最後附上他的時間點,因此我們可以推算出傳進 srand 的值進而拿到同樣的rand值,由於他的寫法是 Year Month Week Hour Min Sec,可以發現作者這邊故意設計不把day印出所以總共可能的時間有四個 我們的攻擊腳本要有以下功能 - time 轉成 timestamp - 利用 c 的 rand 得出所有的隨機值 (這邊記得不要用 python 的 random 因為實作有點不同) - 反推每個混淆過程,做成function 順利的話就可以跑出四個 binary,稍微看一下就可以發現是 png 圖檔,其中一張是正常的,其他是都壞的 ref https://www.tutorialspoint.com/c_standard_library/c_function_gmtime.htm https://www.geeksforgeeks.org/rotate-bits-of-an-integer/ ## Reverse2: pokemonGO 這題是Go的逆向題,整體追code還蠻算蠻容易的,但畢竟是沒學過的語言稍微學了一下語法 解題步驟如下 1. 找出 log 的規則 2. 關鍵程式 log 3. 整理成公式 & Get FLAG ### 找出規則 這份 trace log 有很多方法可以追蹤,那我的作法是先利用scanf (因為有source code)去稍微讀懂 source code 和 trace log 的關係。我這邊舉出兩個我覺得比較重要的 #### for 迴圈格式 從0開始到t3 index 放在 t12 ```go t12 = phi [0: 0:int, 5: t14, 17: t32] #i t13 = t12 <= t3 if t13 goto 2 else 3 .2: t6 = slice format[t12:] t7 = (*ss).advance(s, t6) Entering (*fmt.ss).advance at /usr/lib/go-1.10/src/fmt/scan.go:1067:14. .0: jump 3 .3: t5 = phi [0: 0:int, 14: t10, 41: t65, 30: t10, 33: t10] #i t6 = len(format) t7 = t5 < t6 if t7 goto 1 else 2 ``` #### func回傳值 ``` //t2,t3 := func... t2 = extract t1 #0 t3 = extract t1 #1 ``` ### 關鍵程式 log 分析程式很重要的一點是挑重點看,log在呼叫到function後會去往下 CALL go 原生的source 那種我們就可以不用看了。 第一段 call scanf ,這邊用 scanf 是在輸入密碼 ```go Entering main.main at /home/terrynini38514/Desktop/PokemonV2.go:38:6. .0: t0 = new string (input) t1 = new [1]interface{} (varargs) t2 = &t1[0:int] t3 = make interface{} <- *string (t0) *t2 = t3 t4 = slice t1[:] t5 = fmt.Scanf("%s":string, t4...) ``` 第二段 呼叫 關鍵副程式 PikaCheck ```go Leaving fmt.Scanf, resuming main.main at /home/terrynini38514/Desktop/PokemonV2.go:40:14. t6 = *t0 t7 = PikaCheck(t6) ``` 第三段 進入PikaCheck(input) 建立一個 int a[20] 並 for loop 20次,每次都執行 a[i] = ascii(input[i]) + ascii(input[(i+1)%20]) 還原程式碼 ``` for (i=0;i<len(input);i++) a[i] = ascii(input[i]) + ascii(input[(i+1)%20]) ``` 附上原log ```go Entering main.PikaCheck at /home/terrynini38514/Desktop/PokemonV2.go:6:6. .0: t0 = local [20]int (a) jump 3 .3: t92 = phi [0: 0:int, 1: t10] #i t93 = len(input) t94 = t92 < t93 if t94 goto 1 else 2 .1: t1 = &t0[t92] t2 = input[t92] t3 = convert int <- uint8 (t2) t4 = t92 + 1:int t5 = len(input) t6 = t4 % t5 t7 = input[t6] t8 = convert int <- uint8 (t7) t9 = t3 + t8 *t1 = t9 t10 = t92 + 1:int jump 3 .3: t92 = phi [0: 0:int, 1: t10] #i t93 = len(input) t94 = t92 < t93 if t94 goto 1 else 2 .1: t1 = &t0[t92] t2 = input[t92] t3 = convert int <- uint8 (t2) t4 = t92 + 1:int t5 = len(input) t6 = t4 % t5 t7 = input[t6] t8 = convert int <- uint8 (t7) t9 = t3 + t8 *t1 = t9 t10 = t92 + 1:int jump 3 ... ... ... .3: t92 = phi [0: 0:int, 1: t10] #i t93 = len(input) t94 = t92 < t93 if t94 goto 1 else 2 .1: t1 = &t0[t92] t2 = input[t92] t3 = convert int <- uint8 (t2) t4 = t92 + 1:int t5 = len(input) t6 = t4 % t5 t7 = input[t6] t8 = convert int <- uint8 (t7) t9 = t3 + t8 *t1 = t9 t10 = t92 + 1:int jump 3 .3: t92 = phi [0: 0:int, 1: t10] #i t93 = len(input) t94 = t92 < t93 if t94 goto 1 else 2 ``` 再來就做了加減法的動作後做compare ```go if (a[0]-185+a1[1]-212+a[2]-172+a[3]-145+a[4]-185+a[5]-212+a[6]-172+a[7]-177+a[8]-217+a[9]-212+a[10]-204+a[0]-177+a[11]-185+a[12]-212+a[13]-204+a[14]-209+a[15]-161+a[6]-124+a[17]-172+a[18]-177==0){ return true else return false } ``` ``` go .2: t11 = &t0[0:int] t12 = *t11 t13 = t12 - 185:int t14 = 0:int + t13 t15 = &t0[1:int] t16 = *t15 t17 = t16 - 212:int t18 = t14 + t17 t19 = &t0[2:int] t20 = *t19 t21 = t20 - 172:int t22 = t18 + t21 t23 = &t0[3:int] t24 = *t23 t25 = t24 - 145:int t26 = t22 + t25 t27 = &t0[4:int] t28 = *t27 t29 = t28 - 185:int t30 = t26 + t29 t31 = &t0[5:int] t32 = *t31 t33 = t32 - 212:int t34 = t30 + t33 t35 = &t0[6:int] t36 = *t35 t37 = t36 - 172:int t38 = t34 + t37 t39 = &t0[7:int] t40 = *t39 t41 = t40 - 177:int t42 = t38 + t41 t43 = &t0[8:int] t44 = *t43 t45 = t44 - 217:int t46 = t42 + t45 t47 = &t0[9:int] t48 = *t47 t49 = t48 - 212:int t50 = t46 + t49 t51 = &t0[10:int] t52 = *t51 t53 = t52 - 204:int t54 = t50 + t53 t55 = &t0[11:int] t56 = *t55 t57 = t56 - 177:int t58 = t54 + t57 t59 = &t0[12:int] t60 = *t59 t61 = t60 - 185:int t62 = t58 + t61 t63 = &t0[13:int] t64 = *t63 t65 = t64 - 212:int t66 = t62 + t65 t67 = &t0[14:int] t68 = *t67 t69 = t68 - 204:int t70 = t66 + t69 t71 = &t0[15:int] t72 = *t71 t73 = t72 - 209:int t74 = t70 + t73 t75 = &t0[16:int] t76 = *t75 t77 = t76 - 161:int t78 = t74 + t77 t79 = &t0[17:int] t80 = *t79 t81 = t80 - 124:int t82 = t78 + t81 t83 = &t0[18:int] t84 = *t83 t85 = t84 - 172:int t86 = t82 + t85 t87 = &t0[19:int] t88 = *t87 t89 = t88 - 177:int t90 = t86 + t89 t91 = t90 == 0:int if t91 goto 4 else 5 ``` 回傳是錯誤 ```go .5: return false:bool Leaving main.PikaCheck, resuming main.main at /home/terrynini38514/Desktop/PokemonV2.go:41:17. if t7 goto 1 else 3 .3: t23 = new [1]interface{} (varargs) t24 = &t23[0:int] t25 = make interface{} <- string ("Nothing here my d...":string) *t24 = t25 t26 = slice t23[:] t27 = fmt.Println(t26...) Entering fmt.Println at /usr/lib/go-1.10/src/fmt/print.go:263:6. .0: ``` ### 整理成公式 & Get FLAG 解法就是 a[0] = input[0]+input[1] = 185 依此類推,就可以找出幾組成功的解,從中找到有意義的那個就是答案了 ```go var a[20]int for (i=0;i<len(input);i++) a[i] = ascii(input[i]) + ascii(input[(i+1)%20]) if (a[0]-185+a1[1]-212+a[2]-172+a[3]-145+a[4]-185+a[5]-212+a[6]-172+a[7]-177+a[8]-217+a[9]-212+a[10]-204+a[0]-177+a[11]-185+a[12]-212+a[13]-204+a[14]-209+a[15]-161+a[6]-124+a[17]-172+a[18]-177==0){ return true else return false } ``` ## Reverse3: YugiMuto 這題的難度應該算是 debugging 工具麻煩加上不熟悉的處理器架構,我這邊是過蠻多工具的,後來是使用 bgb 和 radare2 當我的調試工具 解題步驟: 1. 找到合適的中斷點以及關鍵 function 2. document 嗑下去 3. 找到密碼 ### 找到合適的中斷點以及關鍵 function 進入輸入密碼的環節後就會發現程式一直loop在 0x10d6 ~ 0x128a 因此大膽推測這邊就是在等待使用者操作,所以直接先breakpoint在下一行,測試一下就發現抓對了 ![](https://i.imgur.com/NcFJP8b.png) ### document 嗑下去 抓到密碼認證關鍵程式碼後,就必須開始trace code 了但是這個架構以前完全沒看過,因此在trace的過程最好搭配 [官方 document](http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf ) 主要程式我就不細講,下面這個是用radare2畫出來的graph,藍色線路徑是一個loop,loop次數是0x14 不難猜到應該就是每個字會進行一次回圈判斷那個字是否正確,因此框起來的部分就是我們要看注意的, ![](https://i.imgur.com/hA7mPw6.png) 分析到最後會發現他會把密碼放在stack上,因此抓下來就是答案了 ref https://github.com/VoidHack/write-ups/tree/master/Square%20CTF%202017/reverse/gameboy https://github.com/devdri/awake http://marc.rawer.de/Gameboy/Docs/GBCPUman.pdf ## Reverse5: DuRarara 這題好玩了一點,簡單來說會有一隻程式開一隻新程式,但這支子程式並沒有辦法attach 上去,因此要想辦法繞過, 解題步驟: 1. 主程式 trace code 2. 動態提取 memory 成執行檔 4. 子程式 trace code 5. 找到關鍵運算程式 & get password ### 1. 主程式 trace code 追進去看code 就會發現有一個子程式是由父程式動態被噴進記憶體的,知道這個之後我們的目標不在是看這些實際上複製哪些程式碼,而是去將子程式抓出來調試 ![](https://i.imgur.com/6iTQQRs.png) 我的方法是在 resumethread 前停住,然後開啟另一個x64 dbg attach 新開的子程式後,在 qDbgUiRemoteBreakin 這個函式時下一個斷點,因為個函式會檢查 dbgsymbol,因此要手動去 PEB 表那裡將 DEBUGGING FLAG 設為 0,這樣就可以進入 entrypoint,進入entrypoint 會發現 address 跟原本的 0x951xxx 是不一樣的,子程式會被放在0x401a59 ![](https://i.imgur.com/3IorLIl.png) 接著我們就可以用dbg的插件將這段記憶體 dump 成一個PE 檔,利用x64dbg的套件把整個memory dump下來成為一個 PE file ![](https://i.imgur.com/oKc8Ryj.png) 接下來就是過驗證,這邊應該沒幾下就可以看到關鍵function了,作者在這邊做了一些複雜的計算,所以要稍微仔細看一下,最後就可以找出密碼了 `FLAG{D-Day:2020/01/14}` ![](https://i.imgur.com/m7dp2kN.png) **補充資訊** qDbgUiRemoteBreakin 是用來讓一些debug參數用的 ZwContinue 傳進去的兩個參數 其中一個是entrypoint 進入點的指針結構塊 ZwContinue 呼叫前會看到存放PIE指針的結構塊 https://docs.microsoft.com/zh-tw/windows/win32/api/winnt/ns-winnt-arm64_nt_context?redirectedfrom=MSDN https://aaaddr.wordpress.com/2015/01/08/%E4%BB%A5debugger%E5%8E%9F%E7%90%86%E6%8B%86%E8%A7%A3line%E7%9A%84anti-debug-attach/ https://www.unknowncheats.me/forum/assembly/316066-dbguiremotebreakin-bypass.html