用手机打 CTF 是什么样的体验
嘗試不用電腦,只拿一臺iPhone去參加看雪CTF2020 (娛樂向)
用手機打 CTF (iSH + radare2)
背景
最近 iSH 在 Apple Store 上架了,之前一直搶不到 testflight 的配額,難得強管控的蘋果會讓這種 Terminal 類的應用發布,所以第一時間下載來玩玩。測試之后發現可以當做是一個簡單的 Linux 虛擬機,至少常用的命令都沒什么問題,正好看到看雪論壇有個 CTF,于是就試一下,能不能用手機來打一局 CTF 😃
環境
iSH 是一個開源的 Terminal Emulator,在 iOS 中運行,使用用戶態的 x86 指令模擬以及 syscall 翻譯實現。iSH 中使用的是 Alphine Linux 鏡像,用過 Docker 的應該都不會陌生。Alphine 是個非常輕量級的 Linux 發行版,在官網上可以直接下載各個平臺預編譯的鏡像,平時有測試內核需求又不滿足于 ramfs 的可以使用下載的鏡像進行方便測試。
在 iSH 中已經有了一個簡單的 Alphine 環境,可能是是因為蘋果商店審核的原因,在其中并沒有預置包管理工具。我們可以下載一個完整的 iso 重新掛載,但是有更簡單的方法,直接從 Alphine Packages 中下載靜態編譯版本的 apk-tools-static:
wget http://dl-cdn.alpinelinux.org/alpine/latest-stable/main/x86/apk-tools-static-2.10.5-r1.apk tar -zxvf apk-tools-static-2.10.5-r1.apk ./sbin/apk.static -X https://mirrors.tuna.tsinghua.edu.cn --initdb add apk-tools apk update rm -rf ./sbin我們用下載的 apk-tools-static 來安裝 apk-tools,然后包管理工具 apk 就可以正常使用了。如果初始化的時候沒有指定鏡像源,可以使用下面的命令修改為清華大學的鏡像源,加快國內的訪問速度:
sed -i 's/dl-cdn.alpinelinux.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apk/repositories之后需要的工具就可以自行安裝了,我這里只需要 radare2 和 Python:
apk add radare2 apk add python3題目是看雪 CTF2020 的簽到題: https://ctf.pediy.com/game-season_fight-158.htm
Write Up
解題過程在文章開頭的視頻中已經可以看到了,這里還是用文字介紹下解題思路。
首先 rabin2 查看目標程序的信息:
$ rabin2 -I kctf2020.exe arch x86 baddr 0x140000000 binsz 12288 bintype pe bits 64 canary false retguard false class PE32+ cmp.csum 0x0000bcb6 compiled Thu Nov 12 03:49:32 2020 crypto false dbg_file D:\Users\admin\Documents\Visual Studio 2015\Projects\ctf2020\ConsoleApplication1\x64\Release\ConsoleApplication1.pdb endian little havecode true hdr.csum 0x00000000 guid 4EF50FB8F5E74CC481D01589FC111B1A1 laddr 0x0 lang c linenum false lsyms false machine AMD 64 maxopsz 16 minopsz 1 nx true os windows overlay false cc ms pcalign 0 pic true relocs false signed false sanitiz false static false stripped false subsys Windows CUI va true可以看到是 PE64 的的程序,鑒于是簽到題,所以就直接逆向分析了:
$ r2 kctf2020.exe-- 💺 [0x14000154c]> s main [0x140001000]> af [0x140001000]> pd 20;-- section..text: ┌ 459: int main (int argc, char **argv, char **envp); │ bp: 7 (vars 7, args 0) │ sp: 14 (vars 14, args 0) │ rg: 0 (vars 0, args 0) │ 0x140001000 4055 push rbp ; [00] -r-x section size 8192 named .text │ 0x140001002 488dac24c0fe. lea rbp, [rsp - 0x140] │ 0x14000100a 4881ec400200. sub rsp, 0x240 │ 0x140001011 488b05e82f00. mov rax, qword [section..data] ; [0x140004000:8]=0x2b992ddfa232 ; "2\xa2\xdf-\x99+" │ 0x140001018 4833c4 xor rax, rsp │ 0x14000101b 488985300100. mov qword [var_130h], rax │ 0x140001022 33d2 xor edx, edx │ 0x140001024 488d4c2430 lea rcx, [var_30h] │ 0x140001029 41b800010000 mov r8d, 0x100 ; 256 │ 0x14000102f e8ce0e0000 call 0x140001f02 │ 0x140001034 33d2 xor edx, edx │ 0x140001036 488d4d30 lea rcx, [var_bp_30h] │ 0x14000103a 41b800010000 mov r8d, 0x100 ; 256 │ 0x140001040 e8bd0e0000 call 0x140001f02 │ 0x140001045 488d0dd42100. lea rcx, str.KCTF_2020 ; 0x140003220 ; "KCTF 2020!\n" │ 0x14000104c e8ff010000 call 0x140001250 │ 0x140001051 488d0dd82100. lea rcx, str.http:__bbs.pediy.com ; 0x140003230 ; "http://bbs.pediy.com\n" │ 0x140001058 e8f3010000 call 0x140001250 │ 0x14000105d 488d0de42100. lea rcx, str.Please_input_your_flag: ; 0x140003248 ; "Please input your flag: " │ 0x140001064 e8e7010000 call 0x140001250在 0x140001250 地址處經常有調用,根據參數可以猜測是打印函數,驗證一下:
[0x140001000]> s 0x140001250 [0x140001250]> af [0x140001250]> pdf; CALL XREFS from main @ 0x14000104c, 0x140001058, 0x140001064, 0x14000119b, 0x1400011ab ┌ 85: int printf (const char *format); │ ; var int64_t var_20h_2 @ rsp+0x20 │ ; var int64_t var_8h @ rsp+0x50 │ ; var int64_t var_10h @ rsp+0x58 │ ; var int64_t var_18h @ rsp+0x60 │ ; var int64_t var_20h @ rsp+0x68 │ ; arg int64_t arg1 @ rcx │ ; arg int64_t arg2 @ rdx │ ; arg int64_t arg3 @ r8 │ ; arg int64_t arg4 @ r9 │ 0x140001250 48894c2408 mov qword [var_8h], rcx ; arg1 │ 0x140001255 4889542410 mov qword [var_10h], rdx ; arg2 │ 0x14000125a 4c89442418 mov qword [var_18h], r8 ; arg3 │ 0x14000125f 4c894c2420 mov qword [var_20h], r9 ; arg4 │ 0x140001264 53 push rbx │ 0x140001265 56 push rsi │ 0x140001266 57 push rdi │ 0x140001267 4883ec30 sub rsp, 0x30 │ 0x14000126b 488bf9 mov rdi, rcx │ 0x14000126e 488d742458 lea rsi, [var_10h] │ 0x140001273 b901000000 mov ecx, 1 │ 0x140001278 ff150a1f0000 call qword [sym.imp.api_ms_win_crt_stdio_l1_1_0.dll___acrt_iob_func] ; [0x140003188:8]=0x3a98 reloc.api_ms_win_crt_stdio_l1_1_0.dll___acrt_iob_func │ 0x14000127e 488bd8 mov rbx, rax │ 0x140001281 e8baffffff call fcn.140001240 │ 0x140001286 4533c9 xor r9d, r9d │ 0x140001289 4889742420 mov qword [var_20h_2], rsi │ 0x14000128e 4c8bc7 mov r8, rdi │ 0x140001291 488bd3 mov rdx, rbx │ 0x140001294 488b08 mov rcx, qword [rax] │ 0x140001297 ff15e31e0000 call qword [sym.imp.api_ms_win_crt_stdio_l1_1_0.dll___stdio_common_vfprintf] ; [0x140003180:8]=0x3aaa reloc.api_ms_win_crt_stdio_l1_1_0.dll___stdio_common_vfprintf │ 0x14000129d 4883c430 add rsp, 0x30 │ 0x1400012a1 5f pop rdi │ 0x1400012a2 5e pop rsi │ 0x1400012a3 5b pop rbx └ 0x1400012a4 c3 ret果然是打印函數,直接重命名一下,清爽一些:
[0x140001250]> af printf [0x140001250]> s- [0x140001000]> pd 20;-- section..text: ┌ 459: int main (int argc, char **argv, char **envp); │ bp: 7 (vars 7, args 0) │ sp: 14 (vars 14, args 0) │ rg: 0 (vars 0, args 0) │ 0x140001000 4055 push rbp ; [00] -r-x section size 8192 named .text │ 0x140001002 488dac24c0fe. lea rbp, [rsp - 0x140] │ 0x14000100a 4881ec400200. sub rsp, 0x240 │ 0x140001011 488b05e82f00. mov rax, qword [section..data] ; [0x140004000:8]=0x2b992ddfa232 ; "2\xa2\xdf-\x99+" │ 0x140001018 4833c4 xor rax, rsp │ 0x14000101b 488985300100. mov qword [var_130h], rax │ 0x140001022 33d2 xor edx, edx │ 0x140001024 488d4c2430 lea rcx, [var_30h] │ 0x140001029 41b800010000 mov r8d, 0x100 ; 256 │ 0x14000102f e8ce0e0000 call 0x140001f02 │ 0x140001034 33d2 xor edx, edx │ 0x140001036 488d4d30 lea rcx, [var_bp_30h] │ 0x14000103a 41b800010000 mov r8d, 0x100 ; 256 │ 0x140001040 e8bd0e0000 call 0x140001f02 │ 0x140001045 488d0dd42100. lea rcx, str.KCTF_2020 ; 0x140003220 ; "KCTF 2020!\n" │ 0x14000104c e8ff010000 call printf ; int printf(const char *format) │ 0x140001051 488d0dd82100. lea rcx, str.http:__bbs.pediy.com ; 0x140003230 ; "http://bbs.pediy.com\n" │ 0x140001058 e8f3010000 call printf ; int printf(const char *format) │ 0x14000105d 488d0de42100. lea rcx, str.Please_input_your_flag: ; 0x140003248 ; "Please input your flag: " │ 0x140001064 e8e7010000 call printf ; int printf(const char *format)同理,把 scanf 也重命名上,先看程序開頭的邏輯:
│ 0x140001069 41b800010000 mov r8d, 0x100 ; 256 │ 0x14000106f 488d542430 lea rdx, [var_30h] │ 0x140001074 488d0de92100. lea rcx, [0x140003264] ; "%s" │ 0x14000107b e860010000 call scanf ; int scanf(const char *format) │ 0x140001080 488d542430 lea rdx, [var_30h] │ 0x140001085 4883c9ff or rcx, 0xffffffffffffffff │ 0x140001089 0f1f80000000. nop dword [rax] │ ┌─> 0x140001090 48ffc1 inc rcx │ ? 0x140001093 803c0a00 cmp byte [rdx + rcx], 0 │ └─< 0x140001097 75f7 jne 0x140001090 │ 0x140001099 83f90c cmp ecx, 0xc ; 12 │ ┌─< 0x14000109c 0f8502010000 jne 0x1400011a4 │ │ 0x1400010a2 807c243066 cmp byte [var_30h], 0x66 │ ┌──< 0x1400010a7 0f85f7000000 jne 0x1400011a4 │ ││ 0x1400010ad 807c24316c cmp byte [var_31h], 0x6c │ ┌───< 0x1400010b2 0f85ec000000 jne 0x1400011a4 │ │││ 0x1400010b8 807c243261 cmp byte [var_32h], 0x61 │ ┌────< 0x1400010bd 0f85e1000000 jne 0x1400011a4 │ ││││ 0x1400010c3 807c243367 cmp byte [var_33h], 0x67 │ ┌─────< 0x1400010c8 0f85d6000000 jne 0x1400011a4 │ │││││ 0x1400010ce 807c24347b cmp byte [var_34h], 0x7b │ ┌──────< 0x1400010d3 0f85cb000000 jne 0x1400011a4 │ ││││││ 0x1400010d9 807c243b7d cmp byte [var_3bh], 0x7d │ ┌───────< 0x1400010de 0f85c0000000 jne 0x1400011a4var_30h 是輸入的字符串,分別進行字節和長度比對,要求輸入字符串長度為 12,并且格式為 flag{xxxxxx},這里有個技巧是 radare2 中 VV 模式下的跳轉:
- tab/shift+tab: 跳轉到下一個/前一個 basic block
- t/f: 跳轉到 true/false 分支
- u/U: undo/redo 跳轉
然后是 flag 中間字符的判斷:
0x1400010e4 440fb74c2439 movzx r9d, word [var_39h] 0x1400010ea 4c8d442420 lea r8, [var_20h] 0x1400010ef 448b542435 mov r10d, dword [var_35h] 0x1400010f4 33c0 xor eax, eax 0x1400010f6 6644894c2424 mov word [var_24h], r9w 0x1400010fc 8bd0 mov edx, eax 0x1400010fe 4489542420 mov dword [var_20h], r10d 0x140001103 0f1f4000 nop dword [rax] 0x140001107 660f1f840000. nop word [rax + rax]這里使用 mov dword 來進行拷貝,將 var_35h 拷貝 到 var_20h 中,同時 r8 寄存器指向 var_20h。接著先循環逐個判斷字符c - 0x30是否小于 9,如果有大于 9 的就報錯:
┌─> 0x140001110 410fb608 movzx ecx, byte [r8]? 0x140001114 80e930 sub cl, 0x30 ; 48? 0x140001117 80f909 cmp cl, 9 ; 9 ┌──< 0x14000111a 0f8784000000 ja 0x1400011a4 │? 0x140001120 ffc2 inc edx │? 0x140001122 49ffc0 inc r8 │? 0x140001125 83fa06 cmp edx, 6 ; 6 │└─< 0x140001128 72e6 jb 0x140001110 │ 0x14000112a ...接著經過下面的判斷,記得 var_30h 是我們的輸入,長度為 12,格式為 flag{xxxxxx}:
0x14000112a 0fb6542437 movzx edx, byte [var_37h]0x14000112f 4c8d053e2100. lea r8, [0x140003274] ; "2;=EFI"0x140001136 0fb64c2436 movzx ecx, byte [var_36h]0x14000113b 80ea30 sub dl, 0x30 ; 480x14000113e 80e930 sub cl, 0x30 ; 480x140001141 44885530 mov byte [var_bp_30h], r10b0x140001145 4102ca add cl, r10b0x140001148 4180e930 sub r9b, 0x30 ; 480x14000114c 02d1 add dl, cl0x14000114e 884d31 mov byte [var_bp_31h], cl0x140001151 0fb64c2438 movzx ecx, byte [var_38h]0x140001156 80e930 sub cl, 0x30 ; 480x140001159 885532 mov byte [var_bp_32h], dl0x14000115c 02ca add cl, dl0x14000115e 488d5530 lea rdx, [var_bp_30h]0x140001162 4402c9 add r9b, cl0x140001165 884d33 mov byte [var_bp_33h], cl0x140001168 0fb64c243a movzx ecx, byte [var_3ah]0x14000116d 80e930 sub cl, 0x30 ; 480x140001170 44884d34 mov byte [var_bp_34h], r9b0x140001174 4102c9 add cl, r9b0x140001177 884d35 mov byte [var_bp_35h], cl0x14000117a 660f1f440000 nop word [rax + rax]┌─> 0x140001180 0fb60c02 movzx ecx, byte [rdx + rax]? 0x140001184 48ffc0 inc rax? 0x140001187 413a4c00ff cmp cl, byte [r8 + rax - 1] ┌──< 0x14000118c 7516 jne 0x1400011a4 │? 0x14000118e 4883f807 cmp rax, 7 ; 7 │└─< 0x140001192 75ec jne 0x140001180 │ 0x140001194 488d0de52000. lea rcx, [0x140003280] ; "You are winner!\n" │ 0x14000119b e8b0000000 call printf ; int printf(const char *format)設flag{}中間的內容為flag,這里的邏輯可以寫成偽代碼:
buf[0] = flag[0] buf[1] = buf[0] + flag[1] - 48 buf[2] = buf[1] + flag[2] - 48 buf[3] = buf[2] + flag[3] - 48 buf[4] = buf[3] + flag[4] - 48 buf[5] = buf[4] + flag[5] - 48 if buf == "2;=EFI":printf("You are winner!\n")反向計算,可得:
flag[0] = buf[0] flag[1] = buf[1] - buf[0] + 48 flag[2] = buf[2] - buf[1] + 48 flag[3] = buf[3] - buf[2] + 48 flag[4] = buf[4] - buf[3] + 48 flag[5] = buf[5] - buf[4] + 48所以計算的 flag 為:
$ ./solve.py flag: b'292813'最終答案應該是 flag{292813},如果有 windows 環境的話可以進行動態驗證,在 Linux/MacOS 環境中可以用 wine 來進行驗證,如下:
$ wine64 kctf2020.exe 0025:err:plugplay:runloop_thread Couldn't open IOHIDManager. 0009:fixme:vcruntime:__telemetry_main_invoke_trigger (0x0) KCTF 2020! http://bbs.pediy.com Please input your flag: 0009:fixme:msvcrt:MSVCRT__stdio_common_vfscanf options 3 not handled flag{292813} You are winner! 0009:fixme:vcruntime:__telemetry_main_return_trigger (0x0)后記
安裝了 iSH 之后,我的手機時間分配變成了這樣:
手機上的 Terminal 可以用來做什么呢?這里可以列舉一些:
其中 ssh 對我來說是個剛需,因為在此之前蘋果上一直沒有一個好用且免費的 ssh client,只能用 Termius 勉強度日。不過可惜的是由于蘋果的策略,iSH 暫時還不能在后臺運行,不然直接 ssh -R 還能當做隧道用,豈不美哉?
當然,iSH 還是有很多局限性的,比如只能模擬 x86 的指令集,而且由于是軟件模擬,因此運行效率比較低。如果要執行復雜的任務,比如 gcc 編譯大型項目,還是最好在 PC 上運行了。
總結
以上是生活随笔為你收集整理的用手机打 CTF 是什么样的体验的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 启动3dmax 9,出现脚本错误警告的解
- 下一篇: cocos2dx tolua 再见了p