4

用手机打 CTF 是什么样的体验

 3 years ago
source link: https://evilpan.com/2020/11/22/ish-fun/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

尝试不用电脑,只拿一台iPhone去参加看雪CTF2020 (娱乐向)

视频地址: https://www.bilibili.com/video/BV1Da4y1p7F3

背景

最近 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
 -- :seat:
[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 0x1400011a4

var_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               ; 48
     0x14000113e      80e930         sub cl, 0x30               ; 48
     0x140001141      44885530       mov byte [var_bp_30h], r10b
     0x140001145      4102ca         add cl, r10b
     0x140001148      4180e930       sub r9b, 0x30              ; 48
     0x14000114c      02d1           add dl, cl
     0x14000114e      884d31         mov byte [var_bp_31h], cl
     0x140001151      0fb64c2438     movzx ecx, byte [var_38h]
     0x140001156      80e930         sub cl, 0x30               ; 48
     0x140001159      885532         mov byte [var_bp_32h], dl
     0x14000115c      02ca           add cl, dl
     0x14000115e      488d5530       lea rdx, [var_bp_30h]
     0x140001162      4402c9         add r9b, cl
     0x140001165      884d33         mov byte [var_bp_33h], cl
     0x140001168      0fb64c243a     movzx ecx, byte [var_3ah]
     0x14000116d      80e930         sub cl, 0x30               ; 48
     0x140001170      44884d34       mov byte [var_bp_34h], r9b
     0x140001174      4102c9         add cl, r9b
     0x140001177      884d35         mov byte [var_bp_35h], cl
     0x14000117a      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 之后,我的手机时间分配变成了这样:

ZvYZbuM.png!mobile time

手机上的 Terminal 可以用来做什么呢?这里可以列举一些:

  1. 用来 ssh 登录到服务器进行管理
  2. 用来随时查看 man page
  3. 用来编写和运行自动化脚本
  4. 进行一些简单的命令行操作

其中 ssh 对我来说是个刚需,因为在此之前苹果上一直没有一个好用且免费的 ssh client,只能用 Termius 勉强度日。不过可惜的是由于苹果的策略,iSH 暂时还不能在后台运行,不然直接 ssh -R 还能当做隧道用,岂不美哉?

当然,iSH 还是有很多局限性的,比如只能模拟 x86 的指令集,而且由于是软件模拟,因此运行效率比较低。如果要执行复杂的任务,比如 gcc 编译大型项目,还是最好在 PC 上运行了。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK