setodaNote CTF Writeup (Rev)
この記事は、setodaNote CTF RevジャンルのWriteupです。
- Helloworld (50pts, 264solves)
- ELF(80pts, 187solves)
- Passcode (120pts, 218solves)
- Passcode2 (150pts, 109solves)
- to_analyze (200pts, 82solves)
Helloworld (50pts, 264solves)
問題ファイルはexeファイル。パスワードが infected
なのは中々物騒。。
念のため仮想環境でcmd.exeを開いて、実行してみる。
> helloworld.exe Nice try, please set some word when you run me.
実行する時に何かセットして実行しろと言われるので、引数に何かセットしてみる。
>helloworld.exe 1 Good job, but please set 'flag' when you run me.
flagをセットしろと言われるので、flagを引数にセットして実行してみる。
>helloworld.exe flag flag{free_fair_and_secure_cyberspace}
flagが表示された。
flag{free_fair_and_secure_cyberspace}
ELF(80pts, 187solves)
問題ファイルはバイナリファイル。
問題名的にELFのバイナリぽいが、とりあえずfileコマンドしてみる。
$ file elf elf: data
何のファイルとしても認識されない。
バイナリの先頭のデータを16進数表示で見てみる。
$ xxd elf | head 00000000: 5858 5858 0201 0100 0000 0000 0000 0000 XXXX............ 00000010: 0300 3e00 0100 0000 5010 0000 0000 0000 ..>.....P....... 00000020: 4000 0000 0000 0000 5031 0000 0000 0000 @.......P1...... 00000030: 0000 0000 4000 3800 0b00 4000 1c00 1b00 ....@.8...@..... 00000040: 0600 0000 0400 0000 4000 0000 0000 0000 ........@....... 00000050: 4000 0000 0000 0000 4000 0000 0000 0000 @.......@....... 00000060: 6802 0000 0000 0000 6802 0000 0000 0000 h.......h....... 00000070: 0800 0000 0000 0000 0300 0000 0400 0000 ................ 00000080: a802 0000 0000 0000 a802 0000 0000 0000 ................ 00000090: a802 0000 0000 0000 1c00 0000 0000 0000 ................
先頭が XXXX
となっており、ファイルのmagic numberが潰されているぽい。
問題名的にELFファイルだと考えて、ELFのnagic numberをバイナリエディタで書き換えてみる。ELFのmagic numberは"ELF magic number" などでググると出てくるが、 7F 45 4C 46
である。
書き換え後fileコマンドで確認するとELFファイルとして認識されるので、実行権限つけて実行するとFLAG
$ file elf elf: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4f0f6e7df2d02645bb6387a08a099ddecb22b6f1, for GNU/Linux 3.2.0, stripped $ chmod +x elf $ ./elf flag{run_makiba}
FLAG
flag{run_makiba}
Passcode (120pts, 218solves)
問題ファイルはバイナリファイル。拡張子がないので、fileコマンドで何のファイルか確認する。
$ file passcode passcode: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=8be572b7a0563868ee29af143b2df0c7d6b1636d, for GNU/Linux 3.2.0, stripped
ELFファイルなので、Linuxで実行権限をつけて実行してみる。
$ chmod +x ./passcode $ ./passcode Enter the passcode: aaaaaa Invalid passcode. Too short.
passcodeを聞かれるがパスワードがわからない。Ghidraで開いて処理を見てみる。
今回のバイナリはstrippedでシンボル情報が剥がされてるのでmainの処理がぱっと見どこかわからない。
Defined Stringsから "Enter the passcode" や "Invalid passcode." などの文字列を探し、それらを参照している関数を調べる。FUN_00101185
から参照されているらしいので、その関数のデコンパイル結果を見てみる。
以下の部分でパスワードの確認をしており、strcmp で "20150109" と入力した文字列を比較しているので、"20150109" がpasscodeぽい。
iVar1 = strcmp((char *)&local_108,"20150109"); if (iVar1 == 0) { puts("The passcode has been verified.\n"); printf("Flag is : flag{%s}",&local_108); } else { printf("Invalid passcode. Nice try."); }
正解のパスコードがFLAG
flag{20150109}
Passcode2 (150pts, 109solves)
問題ファイルはバイナリファイル。passcodeと同様にfileコマンドでファイルの種類を調べる。
$ file passcode2 passcode2: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=a396332a87a60f8e353e93a001a1a9521673f19d, for GNU/Linux 3.2.0, stripped
ELFなのでLinuxで実行してみる。
$ chmod +x ./passcode $ ./passcode2 Enter the passcode: aaaaaaaa Invalid passcode. Too short.
passcodeと同じエラーが出る。
passcodeと同様にGhidraで開いて、Defined Stringsからpasscodeをチェックしている関数を特定。FUN_00101175
でチェックしてる。
今回、passcodeを比較しているのは以下の部分。
sVar3 = strlen((char *)&local_118); if (sVar3 == 0xb) { local_10 = 0; while ((sVar3 = strlen((char *)local_124), local_10 < sVar3 && (*(byte *)((long)&local_118 + local_10) == (local_124[local_10] ^ 0x2a)))) { local_10 = local_10 + 1; } sVar3 = strlen((char *)local_124); if (local_10 == sVar3) { puts("The passcode has been verified.\n"); printf("Flag is : flag{%s}",&local_118); } else { printf("Invalid passcode. Nice try."); } } else { printf("Invalid passcode."); }
local_124
の配列に格納されている文字列をXORで0x2aしたものと比較している。
Ghidraがデコンパイルした local_124
の配列は以下のとおり。
local_124[0] = 0x18; local_124[1] = 0x1f; local_124[2] = 4; local_124[3] = 0x79;
しかし、直前で入力したpasscodeの文字列の長さが0xb(11)文字かを確認している(if(sVar3 == 0xb)
のところ)ので、これは短すぎる。
これはGhidraが間違ってデコンパイルしており、実際には以下のデータを0x2aでXORしている。(デコンパイラに頼りすぎるのもダメですね。。
local_124[0] = 0x18; local_124[1] = 0x1f; local_124[2] = 4; local_124[3] = 0x79; local_120 = 0x4f; local_11f = 0x5a; local_11e = 4; local_11d = 0x18; local_11c = 0x1a; local_11b = 0x1b; local_11a = 0x1e;
あとはCyberChefなりなんなりで、これらの値を0x2aでXORすると 25.Sep.2014
となる。
これをpasscodeとして入力すると、正しい答えであることを確認できた。
$ ./passcode2 Enter the passcode: 25.Sep.2014 The passcode has been verified. Flag is : flag{25.Sep.2014}
FLAG
flag{25.Sep.2014}
to_analyze (200pts, 82solves)
問題ファイルはexeファイル。またパスワードが infected
なので、念のため仮想環境のWIndowsで実行してみる。
> to_analyze.exe nice try!
何もわからんので、ファイルを解析する。
まず、PeStudio でどのような特性のファイルか見てみる。signatureを見ると、どうやら Microsoft Visual C# v7.0 / Basic .NET
で作られたファイルらしい。
.NETで作られた実行ファイルであればデコンパイルできる。 .NET decompilerであるILSpy を使ってコードをデコンパイルする。
ILSpyで開くとデコンパイルされたコードが出てくる。DotfuscatorAttribute
といういかにも難読化してそうなクラス名があるので、ググるとDotfuscatorという難読化ツールがあるらしい。
de4dot という難読化解除ツールがあるらしいので試してみる。
C:\Users\tanaka\Desktop>de4dot -f to_analyze.exe -o to_analyze_de4dot.exe de4dot v3.1.41592.3405 Copyright (C) 2011-2015 de4dot@gmail.com Latest version and source code: https://github.com/0xd4d/de4dot Detected Dotfuscator (C:\Users\tanaka\Desktop\to_analyze.exe) Cleaning C:\Users\tanaka\Desktop\to_analyze.exe Renaming all obfuscated symbols Saving C:\Users\tanaka\Desktop\to_analyze_de4dot.exe
すべての難読化されたシンボルをrenameしてくれた模様。出力されたファイルを改めてILSpyで開いてみると、メソッド名やフィールド名が一部renameされていて読みやすくなっている。
実行したときに "Nice Try" と表示した場所を見てみる。smethod_2
の引数で渡されたディレクトリ名の存在有無で処理を分けており、特定のディレクトリが存在した場合に答えが表示されるようだ。
private static void smethod_2(string string_0, byte[] byte_0) { if (Directory.Exists(string_0)) { Console.WriteLine("Yes, that's the right answer."); ...<snip>... } else { Console.WriteLine("nice try!"); } }
ifの処理を追えばFLAGがわかりそうではあったが、今回はマルウェア解析ぽくifで正解に分岐する条件を調べてみる。
smethod_2
は Main
から呼ばれおり、Main
では array
に格納されたデータを文字列として渡している。
using System.Text; private static void Main(string[] args) { byte[] array = new byte[15] { 65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177 }; for (int i = 0; i < array.Length; i++) { array[i] ^= 35; if (smethod_1(array[i], 119)) { array[i] += 3; } array[i] ^= 21; array[i] -= 32; array[i] = smethod_0(array[i], 39); } smethod_2(Encoding.ASCII.GetString(array), array); }
array
は 65, 127, 89, 80, 182, 160, 183, 182, 89, 118, 119, 116, 177, 189, 177
で初期化されているが、後の処理でこれらのデータに対して様々な計算を行っているので、エンコードされていると考えられる。
途中呼ばれている smethod_1
および smethod_0
の処理は以下のとおり。
private static bool smethod_1(byte byte_0, int int_0) { if (int_0 == 119) { if (byte_0 == 107 || byte_0 == 117 || byte_0 == 108 || byte_0 == 102 || byte_0 == 98) { return true; } } else if (byte_0 == 110 || byte_0 == 119 || byte_0 == 99 || byte_0 == 111 || byte_0 == 97 || byte_0 == 101 || byte_0 == 112 || byte_0 == 103 || byte_0 == 108 || byte_0 == 107 || byte_0 == 112 || byte_0 == 113) { return true; } return false; } private static byte smethod_0(byte byte_0, int int_0) { switch (int_0) { case 114: byte_0 = (byte)(byte_0 ^ 0x28u); break; case 39: byte_0 = (byte)(byte_0 ^ 0x13u); break; } return byte_0; }
これらを踏まえて、smethod_2
に渡されるディレクトリ名をデコードするスクリプトをPythonで書いた。
#! /usr/bin/env python nums = [65, 127, 89, 80, 182, 160, 183, 182, 89, 118,119, 116, 177, 189, 177] directory_name = '' for n in nums: n ^= 35 if ( n == 107 or n == 117 or n == 108 or n == 102 or n == 98): n += 3 n ^= 21 n -= 32 n ^= 0x13 directory_name += chr(n) print(directory_name)
py -3 decode.py C:\Users\321txt
渡されるディレクトリ名が C:\Users\321txt
であることがわかるので、このディレクトリを作って、バイナリを実行してみる。
FLAG
flag{Do_y0u_Kn0w_Ursnif?}
マルウェア Ursnifが使ってるテクニックの一つなのかな?
ちなみに今回は解析にFLARE VMを使ったが、今回必要な解析ツールが一通り揃っていたので楽だった。なお、ユーザ名とかもバッチリ写ってるので念の為言っておくと、仕事で使ってる環境じゃなくて個人で使ってる環境ですw