Slide 1

Slide 1 text

CCCamp CTF (ALLES CTF) 2019 Writeup Ciruela (@__Xcyba17her_)

Slide 2

Slide 2 text

目次  FlagConverter (Forensics)  Part 1 (234 Solved, 43 pts.)  Part 2 (18 solved, 215 pts.) 2

Slide 3

Slide 3 text

FlagConverter Part 1 3

Slide 4

Slide 4 text

どんな問題?  PCのメモリダンプからフラグを探す.Part1 ~ Part3を通じてメ モリダンプファイルflagconverter.dmpを使う  Part1のフラグは,メモリダンプに暗号化されていない状態のフ ラグがあり,それをさがす問題 4

Slide 5

Slide 5 text

平文のままのフラグを探す  フラグはALLES{で始まる  strings flagconverter | grep ALLES{  ALLES{f0r3n51k_15_50m3t1m35_t00_345y} 5

Slide 6

Slide 6 text

FlagConverter Part 2 6

Slide 7

Slide 7 text

どんな問題?  Part2のフラグは,メモリダンプに暗号化されている状態のフラ グがあり,それをさがす問題  フラグの暗号化を行ったプロセスが存在することが問題文から読 み取れるため,メモリダンプの分析を行う  Volatilityを使う  今回使用するのはvolatility_2.6_win64_standalone  実際に分析する際にコマンドプロンプト経由で使用したのは volatility_2.6_win64_standalone.exeであるが,省略して volatilityと表記することにする 7

Slide 8

Slide 8 text

OSの特定  volatility -f flagconverter.dmp imageinfo  今後volatilityのコマンドのOS種別部分を”Win7SP1x64”とする 8

Slide 9

Slide 9 text

プロセスの一覧を眺める  volatility -f flagconverter.dmp --profile=Win7SP1x64 pstree  怪しいのは,名前になじみがなく問題名に関連する,最下部の2 つのconverter.exe 9

Slide 10

Slide 10 text

プロセスの本体を抽出する  flagは暗号化されておりメモリダンプから暗号文を見つけだすこ とさえ困難であると予想できる  プロセスの実行ファイルを抜き出して静的解析・動的解析するこ とにする  volatility -f flagconverter.dmp --profile=Win7SP1x64 procdump -D [output_dir] –p [PID] 10

Slide 11

Slide 11 text

実行してみる  PIDが2308の方は基数変換やbase64エンコードなどができるGUI ツール.ただハッシュ値を使ってVirusTotal等からネット上に あるか探してみたが見つからなかった  PIDが2316の方は実行さえできない  勘がこいつのほうを調べた方がいいと言っている 11

Slide 12

Slide 12 text

dnSpyで開く  どちらもdnSpyによりデコンパイルできる  PIDが2316の方を調べる  今後これを単に2316と呼ぶことにする 12

Slide 13

Slide 13 text

2316の動作を調べる  InitializeComponent()関数を見る  恐らく”text”という名前の入力フォームと,”Encrypt…”と書いてあ るボタンが存在する 13 ボタンの定義 テキストボックス の定義

Slide 14

Slide 14 text

2316の動作を調べる  Click_Button()関数を見る  入力フォームの内容だと予想できるthis.text.textを crypto.function02(),crypto.function03(), ToBase64String()関数を用いていじっており,いかにも入力文字列 を暗号化していそう  しかし,crypto.function02()とcrypto.function03()の中身を見 ることができない  Cryptoクラスを持つdllファイルを参照している可能性がある 14

Slide 15

Slide 15 text

dllファイルを捜索する  メモリダンプにおいてdllファイルは同じあるいは近いディレクト リに存在している可能性がある  まず,2316のいるパスを求める  volatility -f flagconverter.dmp --profile=Win7SP1x64 filescan | grep converter.exe  次は,¥Device¥¥HarddiskVolume2¥Users¥ALLES¥Desktop¥配下 を探す 15

Slide 16

Slide 16 text

dllファイルを捜索する  volatility -f flagconverter.dmp --profile=Win7SP1x64 filescan | grep ¥¥Device¥¥HarddiskVolume2¥¥Users¥¥ALLES¥¥Desktop  Crypto.dllが見つかる  一番上に表示されているCrypto.dllを抽出することを試みる 16

Slide 17

Slide 17 text

dllファイルを抽出する  volatility -f flagconverter.dmp --profile=Win7SP1x64 dumpfiles --dump-dir [output_dir] -Q 0x000000003e6495f0  抽出したものをTrIDNETで調べると,dllファイルの可能性が高い  Crypto.dllと名前を変えておく 17

Slide 18

Slide 18 text

Crypto.dllをdnSpyで調べる  これもdnSpyで調べられる  function01()関数,function02()関数,function03()関数の 定義がある  2316のClick_Button関数ではfunction03(),function02()の 順で呼ばれていたので,その順で調べていく 18

Slide 19

Slide 19 text

Crypto.function03関数の機能  合計16bytesの配列this.byte_1と合計32bytesの配列 this.byte_0を初期化する機能を持つ  WindowsIdentity.GetCurrent().User.GetBinaryForm関数 は,”ユーザーアカウントのSIDのバイナリ表現を引数の配列にコ ピーしていく”関数である  この”SIDのバイナリ表現”を単にSIDをASCIIコード列に変換してい るものと思っていたため期間中に解くことができなかった(無念) 19

Slide 20

Slide 20 text

ユーザのSIDを調べる  volatility -f flagconverter.dmp --profile=Win7SP1x64 getsids  ただしgetsidsはプラグインらしいので別途入手が必要な可能性あり  恐らくユーザ”ALLES”のSIDである”S-1-5-21-2947568794- 2193893069-2968809547-1000”が使われる 20

Slide 21

Slide 21 text

WindowsIdentity.GetCurrent ().User.GetBinaryForm関数  2316を実行できないので, WindowsIdentity.GetCurrent().User.GetBinaryForm関数によりSID のバイナリ表現がどうなのかをC#プログラムを書いてみて調べた  もちろんSIDのバイナリ表現には決まりがあり,それを読んでやってみてもよ かったが,初めてC#を書いてみたかった  C#のコンパイラは,僕の環境では次の場所にあった: C:¥Windows¥Microsoft.NET¥Framework¥v4.0.30319¥csc.exe  参考は,各C#入門サイトおよびと,WindowsIdentityクラスについて書 いてあるhttps://docs.microsoft.com/ja- jp/dotnet/api/system.security.principal.windowsidentity?vie w=netframework-4.8など 21

Slide 22

Slide 22 text

SIDのバイナリ表現  書いたC#ソースコード 22 using System.Security.Principal; using System; public static class SidBinTest{ public static void Main(){ byte[] array = new byte[28]; SecurityIdentifier user = new SecurityIdentifier("S-1-5-21-2947568794-2193893069- 2968809547-1000"); user.GetBinaryForm(array, 0); for(int i = 0; i < 28; i++){ Console.Write(String.Format("{0:x2}", array[i])); } } }

Slide 23

Slide 23 text

SIDのバイナリ表現  実行結果  求めるバイナリ表現は, 0105000000000005150000009a54b0afcd26c4824b70f4b0e8030 000  実はこれで28bytesの長さなので,function03で定義した28bytes の配列arrayにぴったり収まる  SIDを単にASCII変換すると優に28bytesを超えているし,その28と いう数字がどこから来たものなのか競技中はずっと不思議に思って いた.もう少し掘り下げていれば解けていたのかもしれない…  これを用いてthis.byte_1とthis.byte_0は初期化されるがそこ はソルバに処理を書くことにする 23

Slide 24

Slide 24 text

Crypto.function02関数の機能  長いが,ここが暗号化処理の主要な部分に見える.雰囲気で読む  まずfunction01を呼び出したりして暗号化方法を決め,Unicode文 字列引数string_0についてパディング処理を行い,暗号化したもの を配列resultに入れ,返り値にしている. 24

Slide 25

Slide 25 text

Crypto.function01関数の機能  Rijndael暗号のivとkeyとブロック長を指定している  Rijndael暗号はAESのもととなった暗号化方式で,Rijndaelの 鍵長およびブロック長を制限してAESとして規格化された?  https://www.atmarkit.co.jp/ait/articles/1506/18/news019. html  今回のPython3によるソルバでは,AESのライブラリを用いて復 号した.IVとKeyは既知であるため,復号できる  Rijndaelのライブラリもあるが,今回はうまく使えなかった  https://github.com/meyt/py3rijndael 25

Slide 26

Slide 26 text

メモリ上の暗号文  Click_Button関数を見てみると,最後にToBase64String()関数 でbase64エンコードされた文字列がthis.Text.textに格納され ている  つまり,暗号文はbase64エンコードされた状態でメモリ上に存在 する可能性が高い  方針が立つ: 1. メモリダンプからbase64エンコードをされていそうな文字列を抽出 する 2. 1で抽出されたそれぞれについて,dase64デコード,Rijndael復号 を試みる 26

Slide 27

Slide 27 text

base64デコード可能な文字列の 抽出  strings flagconverter.dmp | grep -E "^([A-Za-z0- 9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za- z0-9+/]{2}==)$" > base64_all.txt  これだけでは26万行ほど抽出されるので,重複の削除と,短すぎ る結果と長すぎる結果の削除を行う 27 f = open('base64_all.txt’, ‘r’) g = open(‘base64_all_filtered.txt', 'w') base64_list = [] while True: line = f.readline() if line == ‘’: break if base64_list.count(line) == 0 and 16 <= len(line) <= 160: base64_list.append(line) for line in base64_list: g.write(line) f.close() g.close()

Slide 28

Slide 28 text

全候補について復号を試行 28 import base64 from Crypto.Cipher import AES def decrypt(SID, ciphertext): IV = SID[:16] KEY = SID[len(SID)-16:]*2 return AES.new(KEY, AES.MODE_CBC, IV).decrypt(ciphertext) if __name__=='__main__': sid = 0x0105000000000005150000009a54b0afcd26c4824b70f4b0e8030000 sid = sid.to_bytes(28, 'big') iv = sid[:16] key = sid[28 - 16 :28] * 2 aes = AES.new(key, AES.MODE_CBC, iv) f = open('base64_all_filtered.txt', 'r') g = open(‘decrypt.txt’, 'wb') while True: cipher_base64 = f.readline()[:-1] # remove '¥n' if cipher_base64 == ‘’: break try: cipher = base64.b64decode(cipher_base64) plaintext = aes.decrypt(cipher) g.write(plaintext) except: continue f.close() g.close() input('[end]')

Slide 29

Slide 29 text

全候補について復号を試行  復号を行えたものの結果を示すdecrypt.txtを見ると,明らかに フラグの一部でありそうな部分が見つかる  先頭部分がうまく復号されていない(?)ので,対応する暗号文の みを復号するようにしてみる  復号結果の書き込みついでにその暗号文の書き込みを行うと,フラ グに対する暗号文は” ZuwJUgfmKzIMbo4F8agPy1MPLq+r7cAlDLowY+RT2wgp1uifc2TXeNH4 bvbb2VqfK6r77SPHFrrMYR+GMGv8JGS87Tiybyi4LNNHQWnTR8LlGlSe HWWA9pydAXuJjSk8FzUFbqHOKqHc+bCtJ/4K2Q==”であることがわか る 29

Slide 30

Slide 30 text

怪しい暗号文を復号する 30 import base64 from Crypto.Cipher import AES sid = 0x0105000000000005150000009a54b0afcd26c4824b70f4b0e8030000 sid = sid.to_bytes(28, 'big') iv = sid[:16] key = sid[28 - 16 :28] * 2 aes = AES.new(key, AES.MODE_CBC, iv) cipher_base64 = 'ZuwJUgfmKzIMbo4F8agPy1MPLq+r7cAlDLowY+RT2wgp1uifc2TXeNH4bvbb2VqfK6r77SPHF rrMYR+GMGv8JGS87Tiybyi4LNNHQWnTR8LlGlSeHWWA9pydAXuJjSk8FzUFbqHOKqHc+bCtJ/4 K2Q==‘ cipher = base64.b64decode(cipher_base64) plaintext = aes.decrypt(cipher) print(plaintext) with open('flag.txt', 'wb') as f: f.write(plaintext)

Slide 31

Slide 31 text

怪しい暗号文を復号する  実行結果(コマンドプロンプト上)  実行結果(flag.txt)  フラグは ALLES{50m3_f0r3n51k_50m3_r3v3r51ng_4nd_50m3_c2ypt0_fun}  ‘¥x00’が多いのは,Unicodeに変換してから暗号化されたから?  パディングが2つなので最後が’¥x02’になってる 31

Slide 32

Slide 32 text

以上 Part3はまたあとでやります… 32