pr0xy’s blog

文章になってないことが多いです。メモです

ContrailCTFの担当した問題について

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通信を行なっていることはあまりなく、httpsdnsのレコード取得を使ってファイルや司令のやりとりをすることかと思います。 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に参加していただきありがとうございました!!