12/31~1/3に開催されたContrailCTFに作問者として参加しました。
私が担当した問題は
[misc]Lets_Connctと[rev]DownloaderLog
です。サクッと気持ちよく解いてもらえるような問題を目指していた(他のメンバが難しい問題を作ることは軽く予想できていた)ので、結果的に両問共にsolve数が多い問題となって良かったと思っています。
Lets_Connecct
問題名の通りアクセスするだけなので気軽に取り組める問題です。
ncで指定のアドレスに接続するとdocker上のbashに接続できます。bashに接続した後lsコマンドを実行するとflagファイルが見えるのですが、catコマンドでflagの中身を確認しようとすると、catコマンドがないと言われてしまいます。そして何の外部コマンドを使用できるか知るために/bin/の中身を表示させてみるとlsしかないことがわかります。 bashで使えるコマンドは2種類あり、一つは/bin/や/usr/bin/、/usr/local/bin/などにインストールされている外部コマンドであり、二つ目はbashが持っている機能である組み込みコマンドです。この問題環境においては外部コマンドはlsしかないので、このあとは組み込みコマンドで問題を解いていくことになります。
0:0:~ pr0xy$ nc 114.177.250.4 2999 bash: cannot set terminal process group (-1): Inappropriate ioctl for device bash: no job control in this shell bash-4.4$ pwd pwd / bash-4.4$ ls -al ls -al total 1132 drwxr-x--- 1 0 1000 4096 Dec 30 08:53 . drwxr-x--- 1 0 1000 4096 Dec 30 08:53 .. -rwxr-x--- 1 0 1000 220 Apr 4 2018 .bash_logout -rwxr-x--- 1 0 1000 3771 Apr 4 2018 .bashrc -rwxr-x--- 1 0 1000 807 Apr 4 2018 .profile -rwxr-x--- 1 0 1000 1113504 Dec 30 04:53 bash drwxr-x--- 1 0 1000 4096 Dec 30 08:53 bin drwxr-x--- 1 0 1000 4096 Dec 30 04:54 dev -rwxr----- 1 0 1000 44 Dec 30 05:14 flag drwxr-x--- 1 0 1000 4096 Dec 30 04:54 lib drwxr-x--- 1 0 1000 4096 Dec 30 04:54 lib32 drwxr-x--- 1 0 1000 4096 Dec 30 04:54 lib64 bash-4.4$ cat flag cat flag bash: cat: command not found bash-4.4$ ls -al /bin/ ls -al /bin/ total 140 drwxr-x--- 1 0 1000 4096 Dec 30 08:53 . drwxr-x--- 1 0 1000 4096 Dec 30 08:53 .. -rwxr-x--- 1 0 1000 133792 Dec 30 08:53 ls
bashにおいてhelp組み込みコマンドで組み込みコマンド一覧を確認できます。
bash-4.4$ help help GNU bash, version 4.4.20(1)-release (x86_64-pc-linux-gnu) These shell commands are defined internally. Type `help' to see this list. Type `help name' to find out more about the function `name'. Use `info bash' to find out more about the shell in general. Use `man -k' or `info' to find out more about commands not in this list. A star (*) next to a name means that the command is disabled. job_spec [&] history [-c] [-d offset] [n] or hist> (( expression )) if COMMANDS; then COMMANDS; [ elif C> . filename [arguments] jobs [-lnprs] [jobspec ...] or jobs > : kill [-s sigspec | -n signum | -sigs> [ arg... ] let arg [arg ...] [[ expression ]] local [option] name[=value] ... alias [-p] [name[=value] ... ] logout [n] bg [job_spec ...] mapfile [-d delim] [-n count] [-O or> bind [-lpsvPSVX] [-m keymap] [-f file> popd [-n] [+N | -N] break [n] printf [-v var] format [arguments] builtin [shell-builtin [arg ...]] pushd [-n] [+N | -N | dir] caller [expr] pwd [-LP] case WORD in [PATTERN [| PATTERN]...)> read [-ers] [-a array] [-d delim] [-> cd [-L|[-P [-e]] [-@]] [dir] readarray [-n count] [-O origin] [-s> command [-pVv] command [arg ...] readonly [-aAf] [name[=value] ...] o> compgen [-abcdefgjksuv] [-o option] [> return [n] complete [-abcdefgjksuv] [-pr] [-DE] > select NAME [in WORDS ... ;] do COMM> compopt [-o|+o option] [-DE] [name ..> set [-abefhkmnptuvxBCHP] [-o option-> continue [n] shift [n] coproc [NAME] command [redirections] shopt [-pqsu] [-o] [optname ...] declare [-aAfFgilnrtux] [-p] [name[=v> source filename [arguments] dirs [-clpv] [+N] [-N] suspend [-f] disown [-h] [-ar] [jobspec ... | pid > test [expr] echo [-neE] [arg ...] time [-p] pipeline enable [-a] [-dnps] [-f filename] [na> times eval [arg ...] trap [-lp] [[arg] signal_spec ...] exec [-cl] [-a name] [command [argume> true exit [n] type [-afptP] name [name ...] export [-fn] [name[=value] ...] or ex> typeset [-aAfFgilnrtux] [-p] name[=v> false ulimit [-SHabcdefiklmnpqrstuvxPT] [l> fc [-e ename] [-lnr] [first] [last] o> umask [-p] [-S] [mode] fg [job_spec] unalias [-a] name [name ...] for NAME [in WORDS ... ] ; do COMMAND> unset [-f] [-v] [-n] [name ...] for (( exp1; exp2; exp3 )); do COMMAN> until COMMANDS; do COMMANDS; done function name { COMMANDS ; } or name > variables - Names and meanings of so> getopts optstring name [arg] wait [-n] [id ...] hash [-lr] [-p pathname] [-dt] [name > while COMMANDS; do COMMANDS; done help [-dms] [pattern ...] { COMMANDS ; }
この中から使えそうなものを探すと、readコマンドが使えそうです。 readコマンドを使うと以下のような感じでflagを表示できます。 flagには172.17.0.5の3000portに本当のflagが移動したと書いてあります。
bash-4.4$ while read line; do echo $line; done < flag while read line; do echo $line; done < flag Flag has moved to 3000 port on 172.17.0.5.
しかし172.17.0.5はローカルipアドレスの範囲内なので外部からnc 172.17.0.5 3000とすることはできないですし、114.177.250.4 2999のbashにはncコマンドがありません。よって、ここでもbashの機能を使って接続します。 bashには/dev/tcp/[ip]/[port]でtcp通信が行える機能があります。 以下はこの機能を使ってファイルディスクリプタ7番でコネクションを張って、readコマンドでファイルディスクリプタ7番の内容を読み取っています。
bash-4.4$ exec 7<> /dev/tcp/172.17.0.5/3000 exec 7<> /dev/tcp/172.17.0.5/3000 bash-4.4$ while read line; do echo $line; done <&7 while read line; do echo $line; done <&7 ctrctf{b4sh_1s_a_mul7ifuncti0n_sh3ll} bash-4.4$
標準入力(ファイルディスクリプタ0番)を172.17.0.5:3000にするという方法もあります。
bash-4.4$ exec 0<> /dev/tcp/172.17.0.5/3000 exec 0<> /dev/tcp/172.17.0.5/3000 bash-4.4$ ctrctf{b4sh_1s_a_mul7ifuncti0n_sh3ll} bash: ctrctf{b4sh_1s_a_mul7ifuncti0n_sh3ll}: command not found bash-4.4$ exit
CTF開催中にサーバが変更されたことが/flagに反映されていなくて申し訳なかったです。🙇♂️ この記事を作成中に反映されていないことに気づきました。
DownloaderLog
この問題は、malware感染後にmalware(downloader)がインターネットから何やら怪しげなファイルをダウンロードしていた様子がログに残っていたという問題設定です。 log.pcapファイル(https://www.dropbox.com/s/losftwhw7w0718d/log.pcap?dl=0)がログに該当します。
log.pcapファイルをwireshark等で開くとhttp通信でファイルを二つダウンロードしてきていることがわかります。 http通信の内容を詳しくみると、まずhttp://file.io/k7zg2B(アップロード時のファイル名はsuspicious)からELFファイルのようなファイルをダウンロードし、次にhttp://file.io/7GNqHT(アップロード時のファイル名はonetime_secret.key)からunixタイムのようなものが書かれているテキストファイルをダウンロードしてきていることがわかります。
wiresharkの[File]->[Exports Objects]->[HTTP]でk7zg2Bを抽出します。
ここからk7zg2B(認証+flag表示プログラム)というファイルの解析を行なっていきます(後ろに認証プログラムのソースコードを貼っていますので参考にしてください)。
ghidraでk7zg2Bを開くと0x00400716からmain関数があります。
ghidraで処理を見ると分かると思いますが、main関数では引数に与えられたファイルの中身の数字が(現在時刻-6000)~(現在時刻+6000)の場合、0x6010bdから始まる関数を呼び出します。この関数はcheck_func.S 内に書いてある_check_func関数です。
(gdbでステップ実行をしていると0x6010bdに入った瞬間デバッグが終了してしまうかもしれません。その際は少し先の部分にbreakpointを張って実行してみてください。 detect_debuggerが始まる0x601081にbreakpointを張るとうまくいくかもしれません。)
_check_func関数では、0x601081から始まるdetect_debuggerを呼んでます。これはデバッガを検知する関数です。gdb等のデバッガを使ってプログラムを走らせるとdetect_debugger内のsyscall後、raxレジスタに-1 が入ってしまうのでsyscall後、set $rax=0 とすることでデバッガ検知を回避することができます。 _check_funcに戻った後、0x601060から始まるdecode_xorを呼んでます。これはdataセクションのあるpack_start~ pack_endの部分をdecodeして実行できるようにしている関数です。
detect_debugger終了後の_check_func関数ではファイルの数字が(現在時刻-60)~(現在時刻) かつ 53の倍数の時のみflagを出力するという処理になっています。
余計な命令が入らないように最小限のアセンブリでプログラムを作成したので、アセンブリを追いかけることのできるレベルかなと思っています。
現実的にはmalwareがhttp通信を行なっていることはあまりなく、httpsやdnsのレコード取得を使ってファイルや司令のやりとりをすることかと思います。 ctf仕様の問題になりましたが、file.ioという一見怪しくないようなサービスを利用している点など雰囲気を感じ取ってもられば良いなと思って作問しました。
最後にバイナリの作成手順を書いておきます。
$ gcc main.c check_func.S -no-pie $ strip a.out
そしてa.outのdataセクションに入っているpack_startラベルからpack_endラベルに該当する部分をxor 25 でエンコードしました。
// main.c #include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<time.h> extern void _check_func(int key_time,int now_time); char *filename; void cleanup(){ char *cmd[] = {"/bin/rm",filename,NULL}; execv("/bin/rm",cmd); } int main(int argc,char **argv){ filename = argv[0]; if(argc != 2){ puts("key required :("); cleanup(); return 1; } FILE *fp; if((fp = fopen(argv[1],"r")) == NULL){ return 2; } char t[11] = {0}; fread(t,sizeof(char),10,fp); int key_time = atoi(t); int now_time = time(NULL); if(key_time - 6000 < now_time && now_time < key_time + 6000){ _check_func(key_time,now_time); }else{ puts("invalid :|"); cleanup(); return 3; } return 0; }
check_func.Sの中でxorでエンコードする処理を入れたいと思ったのでw(書き込み)権限のあるdataセクションに配置しています。(textセクションをmprotectしたり、新しい領域をrwxで確保するという方法もあります)
check_func.Sに書いてあるflag文字列が散らばっているのは、xor25でデコード後dataセクションの部分をメモリダンプされるとflagが丸見えになることを避けたかったからです。 かといって複雑で面倒な処理を加えると解く側が嫌になってしまうと思ったので、単純にjmp命令を多用するスパゲティコードに仕上げました。 ctfdのflag提出状況を見ていると散らばっている文字列を色々つなぎ合わせているflag(ctrctf{3inary_a4na1yst}など)があったので少しは効果があったのかなと思っています。
# check_func.S .data .global _check_func decode_xor: mov $pack_start,%eax xor %ecx,%ecx mov $pack_end,%ecx sub $pack_start,%ecx dec %ecx next_byte: xor $25,(%ecx,%eax,1) loop next_byte xor $25,(%ecx,%eax,1) ret detect_debugger: mov $101,%rax mov $0,%rdi mov $0,%rsi mov $1,%rdx mov $0,%r10 syscall cmp $0xffffffffffffffff,%rax jne ok mov $60,%rax mov $255,%rdi syscall ok: ret _check_func: push %rbp mov %rsp,%rbp sub $0x10,%rsp mov %edi,-0x4(%rbp) mov %esi,-0x8(%rbp) call detect_debugger call decode_xor pack_start: mov -0x4(%rbp),%eax cmp %eax,-0x8(%rbp) jle bye mov -0x4(%rbp),%eax add $0x3c,%eax cmp %eax,-0x8(%rbp) jge bye jmp L5end L5: .ascii "4na1yst" L5end: mov -0x4(%rbp),%eax mov $0,%rdx mov $53,%rbx div %rbx cmp $0,%rdx jne bye mov $1,%rax mov $1,%rdi mov $flag_msg,%rsi mov $21,%rdx syscall jmp wL1s flag_msg: .ascii "Nice :) Flag is here\n" L4: .ascii "3inary_" wL6s: mov $1,%rax mov $L6,%rsi mov $2,%rdx syscall jmp succes_end wL6e: L3: .ascii "are_" wL3s: mov $1,%rax mov $L3,%rsi mov $4,%rdx syscall jmp wL4s wL3e: L6: .ascii "}\n" wL4s: mov $1,%rax mov $L4,%rsi mov $7,%rdx syscall jmp wL5s wL4e: L2: .ascii "{u_" wL2s: mov $1,%rax mov $L2,%rsi mov $3,%rdx syscall jmp wL3s wL2e: L1: .ascii "ctrctf" succes_end: mov $60,%rax mov $0,%rdi syscall wL5s: mov $1,%rax mov $L5,%rsi mov $7,%rdx syscall jmp wL6s wL5e: bye: mov $1,%rax mov $2,%rdi mov $err_msg,%rsi mov $20,%rdx syscall mov $60,%rax mov $41,%rdi syscall wL1s: mov $1,%rdi mov $1,%rax mov $L1,%rsi mov $6,%rdx syscall jmp wL2s wL1e: err_msg: .ascii "invalid time_key :}\n" pack_end:
最後に
ContrailCTFに参加していただきありがとうございました!!