セキュリティ・キャンプ 2019 脆弱性・マルウェア解析トラックの応募課題

※問題があれば非公開にします

※問6に追記しました(2019/07/18)

目次

 

はじめに


セキュリティ・キャンプ全国大会2019 A[脆弱性・マルウェア解析トラック]の選考を通過しました。

セキュリティ・キャンプについては昨年知り、応募課題を見て実力不足を感じたため応募を諦めました。(応募課題だけでも取り組むべきだったと今は思っています)

私がReversing/Forensicsを学び始めたのは最近のことであり、以下に晒す応募課題は間違っている可能性が非常に高いです。(間違っている場所について教えていただけると助かります)

セキュリティ・キャンプ2019の応募問題は以下のURLから見ることができます。

https://www.ipa.go.jp/jinzai/camp/2019/zenkoku2019_vote.html#senkoushahappyou

 

問1


あなたが今まで作ってきたソフトウェアにはどのようなものがありますか? また、それらはどんな言語やライブラリを使って作ったのか、どこにこだわって作ったのか、たくさん自慢してください。

高校の時に作成したAndroidアプリを2つ、高校の課題研究での成果物、大学で開催した学内CTFの各問題について概要と意図をまとめました。

 

問2


今までに解析したことのあるソフトウェアやハードウェアにはどのようなものがありますか?また、その解析目的や解析方法、工夫した点があればそれらも教えてください。

大和セキュリティ勉強会:マルウェア解析入門での話と、その時紹介して頂いたSECCON2016のAnti-debugging問題について書きました。以下はAnti-debuggingについて課題で記述した内容です。

 

CTFの問題で印象に残っている問題はSECCON2016のAnti-debuggingの問題である。この問題はアンチデバッギングの例題として”大和セキュリティ勉強会”で紹介してもらった。

解析までの手順

  • fileコマンドでファイル形式の確認(PE32だったので拡張子をexeに変更した)
  • 実際にファイルを動かしてソフトウェアの挙動を確認(passwordの入力を求められるので適当な文字を入れてみる)
  • Ghidraでpassword入力周りの挙動を確認(passwordが”I have a pen.”であることがわかった)
  • 解析したパスワードを入力して実行してみる(“Your password is correct.”の文字列だけが表示された)
  • Ollydbgを利用してパスワード入力部分にブレークポイントを設定してステップ実行してみる
  • kernel32.DLL IsDebuggerPresent関数を呼びデバッガにハンドリングされている場合は“But detected debugger!” を返す実装になっていることがわかった
  • アンチデバッキングを回避するためにIsDebuggerPresentを呼んだ直後のCMP命令+JNZ命令に注目した(IsDebuggerPresentの仕様を見るとデバッガにハンドリングされている場合は0以外を返し、そうでない場合は0を返す仕様になっていることがわかった)
  • Ollydbgの機能を利用してJNZをJMPに書き換えることでIsDebuggerPersentを無効化できると考えた
  • 実際にJMPに書き換えて実行すると”But detected debugger”のメッセージを回避することができたが新たに”But detected NtGlobalFlag”というメッセージが出力された
  • NtGlobalFlagが何を指しているかわからなかったため調べてみると、プロセスがデバッグされているかどうかのフラグ情報であるとわかった
  • IsDebuggerPresentの回避と同じ手法でJNZをJMPに書き換えて実行すると”But detected remotedebug”というメッセージが新たに表示された
  • アセンブラを見るとその先も何箇所かアンチデバッギングしている箇所があるのですべてJMPでCMPを無効化した
  • 各種デバッガの検知中のCALL命令でIN(check I/O Port)があり、そのまま実行するとIN命令で処理が止まるためCALL命令をNOPで埋めた
  • これまでの解析結果を反映して実行するとIDIV命令を実行しているところで終了するようになった
  • IDIVのオペランドを見るとIDIV命令の直前でMOVした値(0)が利用されていることがわかる(0除算が原因でプログラムが正常に動作しない)
  • IDIV命令をNOPで埋めIDIV以降のデバッガ検知処理をJMPで回避することでFlagが表示された

 

問3


ブログやGitHubなど技術情報を公開しているURLがあれば教えてください。またその内容についてアピールすべきポイントがあれば記載してください。

[プロフィール]

https://profile.narupi.net

[ブログ]

https://www.narupi.net

[GitHub]

https://github.com/narupi

[CTFtime]

https://ctftime.org/user/41462

 

問4


あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)またそれらを受講したい理由を教えてください。

A1~3, A4, C5, B5, A6について各5行程度でまとめました。各講義について思っていることを素直に書きました。

 

問5


自分が最も技術的に興味を持った脆弱性をひとつ挙げ、技術的詳細(脆弱性の原因、攻略方法、対策方法など)について分かったことや思ったこと、調査の過程で工夫したこと等を報告してください。その際、書籍やウェブサイトを調べて分かったことはその情報源を明記し、自分が独自に気付いたことや思ったことはそれと分かる形で報告してください。また脆弱性の攻略方法を試す際は、他者に迷惑を掛けないように万全の措置をとってください。

 

調査した脆弱性 : HeartBleed

脆弱性の原因:

HeartBleedはOpenSSL 1.0.1~1.0.1fの拡張機能Heartbeatに含まれる脆弱性によって発生する。Heartbeatとは一定時間ごとにクライアントがHeartbeatRequestを送信し、サーバはクライアントから送られてきたHeartbeatRequestのデータをコピーし返信する仕組みである。[1] 脆弱性が含まれるバージョンのOpenSSLのソースコードを見ると、クライアントから送られてきたpayload(サイズ長)をmemcpy関数でそのまま利用している。[2][3] つまり、実際に送信するデータサイズと異なるpayloadに書き換えたHeartbeatRequestを送信することで差分のバイト数分ホスト側のメモリの内容を読み取ることができる。多くのサイトでHeartbeatRequest一回につき取得できる最大のデータサイズは64KBとなっていた。しかし、何故最大64KBなのかについては書かれていなかったため、これについて実装をもとに考察した。

  • HeartbeatRequestで指定できるpayloadはソースコード内でunsigned int型で宣言されている
  • c言語でのunsigned intは2または4バイト、取りうる値は0~65535または0~ 4294967295
  • payloadが渡されているmemcpy関数の第三引数はコピーバイト数を表す
  • payloadが最大値の場合65535バイトを指定することとなる
  • 65535バイトは64Kバイトなので実際に送信するデータサイズを0バイトとしてpayloadに65535を渡すと、コピーできる最大バイト数は64Kバイトとなる

環境構築:

はじめにHeartbleedの脆弱性があるOpenSSLを利用したwebサーバの作成を行った。VirtualBoxを利用しOpenSSL 1.0.1 14がプリインストールされているUbuntu12.04 LTSの仮想マシンを建てた。行った設定は以下の通りである。

  • 仮想マシンにnginxをインストールし、OpenSSLで証明書を作成した
  • nginxのconfigにhttpsの設定を追記
  • 仮想マシンのネットワーク設定はNATとホストオンリーアダプタの2つを設定

攻略方法:

対象のサーバは10.0.0.103であるとする。

まず対象のwebサーバが脆弱なOpenSSLを利用しているか確かめるためにnmapの調査スクリプトを利用した。[5]

nmap -p 443 –script ssl-heartbleed 10.0.0.103

結果は

State: VULNERABLE

Risk factor: High

であり、Heartbleedの脆弱性が利用できることがわかる。

次にmetasploitフレームワークでheartbleedのペイロードを検索し利用した。結果としてHeartbeat responseとしてサーバのメモリ内容を読み取ることができた。exploit中のパケットをwiresharkでキャプチャーしてみるとHeartbeat RequestのPayload Lengthが65535でありPayloadは0byteであることが確認できた。Heartbeat ResponseのPayloadは16381byteとなっていた。

対策方法:

この脆弱性は2011年12月31 日の時点で発生し2014年4月までパッチが当てられなかった。[6] そのため、この期間に既に脆弱性が悪用されていた可能性がある。この脆弱性を悪用することでサーバ上メモリにロードしたIDやPasswordなどの個人情報、サーバの証明書の情報などを取得できる。主な対策方法としてクライアント側はIDやPasswordの変更、サーバ側は証明書の再発行、Heartbeatの無効化、OpenSSLのバージョンアップなどが考えられる。

その他の対策方法として私が考えたのは、iptablesなどのパケットフィルタリングを利用して攻撃を検知する方法である。IPヘッダのパケットサイズは固定であるため、先頭からのバイト数を指定してHeartbeatRequestをブラックリストに登録できるのではないかと考えた。実際にこの方法は考えられたのか調べてみるといくつか記事が見つかった。[7][8]

これらの記事を見るとIPヘッダから52バイト目の4バイトが0x18030000:0x1803FFFFに入っているならDROPするというルールを設定することでフィルタリングできることがわかる。またiptablesのログ機能を利用すれば検知しづらいHeartbleedの攻撃を検知することが可能であることがわかった。

これらの点を踏まえると基本的にはOpenSSLのバージョンアップを行い、どうしてもアップデートできない場合はiptablesなどでパケットフィルタリングを行うことで対策できる。しかし後者の方法は意図的にパケットを改変したHeartbeatRequestは検知できないため、あくまで応急処置である。

参考文献:

[1] https://tools.ietf.org/html/rfc6520
[2]
https://git.openssl.org/gitweb/?p=openssl.git;a=blobdiff;f=ssl/t1_lib.c;h=bddffd92cc045ae920d63e6e140c78b4d96c3425;hp=b82fadace66e764b47ab2d854621ad89b804e8d2;hb=96db9023b881d7cd9f379b0c154650d6c108e9a3;hpb=0d7717fc9c83dafab8153cbd5e2180e6e04cc802
[3] https://qiita.com/maueki/items/69d358d207eb62a2d321
[4] http://www1.cts.ne.jp/~clab/hsample/Primary/Io8.html
[5] https://nmap.org/nsedoc/scripts/ssl-heartbleed.html
[6] https://en.wikipedia.org/wiki/Heartbleed
[7] https://www.securityfocus.com/archive/1/531779/30/0/threaded
[8] https://yasulib.hatenablog.jp/entry/20140413/1397353893

 

問6


※この課題の解答はおそらく間違っています

以下にDebian 9.8(amd64)上で動作するプログラムflatteningのmain関数の逆アセンブル結果(*1)とmain関数で使われているデータ領域のダンプ結果(*2)があります。 このプログラムは、コマンドライン引数としてある特定の文字列を指定されたときのみ実行結果が0となり、それ以外の場合は実行結果が1となります。 この実行結果が0となる特定の文字列を探し、その文字列を得るまでに考えたことや試したこと、使ったツール、抱いた感想等について詳細に報告してください。

(*1)”objdump -d -Mintel flattening”の出力結果のうち、main関数の箇所を抜粋しました。

0000000000000530 <main>:
 530:	48 8d 15 7d 03 00 00 	lea    rdx,[rip+0x37d]        # 8b4 <_IO_stdin_used+0x4>
 537:	c7 44 24 f4 00 00 00 	mov    DWORD PTR [rsp-0xc],0x0
 53e:	00 
 53f:	49 ba 9e fa 95 ef 92 	movabs r10,0xedd5a792ef95fa9e
 546:	a7 d5 ed 
 549:	41 b9 cc ff ff ff    	mov    r9d,0xffffffcc
 54f:	90                   	nop
 550:	8b 44 24 f4          	mov    eax,DWORD PTR [rsp-0xc]
 554:	83 f8 0d             	cmp    eax,0xd
 557:	77 23                	ja     57c <main+0x4c>
 559:	48 63 04 82          	movsxd rax,DWORD PTR [rdx+rax*4]
 55d:	48 01 d0             	add    rax,rdx
 560:	ff e0                	jmp    rax
 562:	66 0f 1f 44 00 00    	nop    WORD PTR [rax+rax*1+0x0]
 568:	c7 44 24 f4 09 00 00 	mov    DWORD PTR [rsp-0xc],0x9
 56f:	00 
 570:	8b 44 24 f4          	mov    eax,DWORD PTR [rsp-0xc]
 574:	83 c1 01             	add    ecx,0x1
 577:	83 f8 0d             	cmp    eax,0xd
 57a:	76 dd                	jbe    559 <main+0x29>
 57c:	b8 01 00 00 00       	mov    eax,0x1
 581:	c3                   	ret    
 582:	66 0f 1f 44 00 00    	nop    WORD PTR [rax+rax*1+0x0]
 588:	48 63 c1             	movsxd rax,ecx
 58b:	c7 44 24 f4 0c 00 00 	mov    DWORD PTR [rsp-0xc],0xc
 592:	00 
 593:	44 0f b6 44 04 f8    	movzx  r8d,BYTE PTR [rsp+rax*1-0x8]
 599:	eb b5                	jmp    550 <main+0x20>
 59b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]
 5a0:	48 63 c1             	movsxd rax,ecx
 5a3:	45 8d 1c 08          	lea    r11d,[r8+rcx*1]
 5a7:	c7 44 24 f4 0b 00 00 	mov    DWORD PTR [rsp-0xc],0xb
 5ae:	00 
 5af:	44 30 5c 04 f8       	xor    BYTE PTR [rsp+rax*1-0x8],r11b
 5b4:	eb 9a                	jmp    550 <main+0x20>
 5b6:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
 5bd:	00 00 00 
 5c0:	83 f9 07             	cmp    ecx,0x7
 5c3:	0f 86 18 01 00 00    	jbe    6e1 <main+0x1b1>
 5c9:	c7 44 24 f4 0d 00 00 	mov    DWORD PTR [rsp-0xc],0xd
 5d0:	00 
 5d1:	e9 7a ff ff ff       	jmp    550 <main+0x20>
 5d6:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
 5dd:	00 00 00 
 5e0:	c7 44 24 f4 09 00 00 	mov    DWORD PTR [rsp-0xc],0x9
 5e7:	00 
 5e8:	31 c9                	xor    ecx,ecx
 5ea:	e9 61 ff ff ff       	jmp    550 <main+0x20>
 5ef:	90                   	nop
 5f0:	c7 44 24 f4 08 00 00 	mov    DWORD PTR [rsp-0xc],0x8
 5f7:	00 
 5f8:	45 89 c8             	mov    r8d,r9d
 5fb:	e9 50 ff ff ff       	jmp    550 <main+0x20>
 600:	83 f9 08             	cmp    ecx,0x8
 603:	0f 85 73 ff ff ff    	jne    57c <main+0x4c>
 609:	c7 44 24 f4 07 00 00 	mov    DWORD PTR [rsp-0xc],0x7
 610:	00 
 611:	e9 3a ff ff ff       	jmp    550 <main+0x20>
 616:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
 61d:	00 00 00 
 620:	83 c1 01             	add    ecx,0x1
 623:	c7 44 24 f4 02 00 00 	mov    DWORD PTR [rsp-0xc],0x2
 62a:	00 
 62b:	e9 20 ff ff ff       	jmp    550 <main+0x20>
 630:	48 63 c1             	movsxd rax,ecx
 633:	80 7c 04 f8 00       	cmp    BYTE PTR [rsp+rax*1-0x8],0x0
 638:	0f 85 96 00 00 00    	jne    6d4 <main+0x1a4>
 63e:	c7 44 24 f4 06 00 00 	mov    DWORD PTR [rsp-0xc],0x6
 645:	00 
 646:	e9 05 ff ff ff       	jmp    550 <main+0x20>
 64b:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]
 650:	4c 8b 5e 08          	mov    r11,QWORD PTR [rsi+0x8]
 654:	48 63 c1             	movsxd rax,ecx
 657:	c7 44 24 f4 04 00 00 	mov    DWORD PTR [rsp-0xc],0x4
 65e:	00 
 65f:	45 0f b6 1c 03       	movzx  r11d,BYTE PTR [r11+rax*1]
 664:	44 88 5c 04 f8       	mov    BYTE PTR [rsp+rax*1-0x8],r11b
 669:	e9 e2 fe ff ff       	jmp    550 <main+0x20>
 66e:	66 90                	xchg   ax,ax
 670:	83 f9 07             	cmp    ecx,0x7
 673:	77 c9                	ja     63e <main+0x10e>
 675:	c7 44 24 f4 03 00 00 	mov    DWORD PTR [rsp-0xc],0x3
 67c:	00 
 67d:	e9 ce fe ff ff       	jmp    550 <main+0x20>
 682:	66 0f 1f 44 00 00    	nop    WORD PTR [rax+rax*1+0x0]
 688:	c7 44 24 f4 02 00 00 	mov    DWORD PTR [rsp-0xc],0x2
 68f:	00 
 690:	31 c9                	xor    ecx,ecx
 692:	e9 b9 fe ff ff       	jmp    550 <main+0x20>
 697:	66 0f 1f 84 00 00 00 	nop    WORD PTR [rax+rax*1+0x0]
 69e:	00 00 
 6a0:	83 ff 02             	cmp    edi,0x2
 6a3:	0f 85 d3 fe ff ff    	jne    57c <main+0x4c>
 6a9:	c7 44 24 f4 01 00 00 	mov    DWORD PTR [rsp-0xc],0x1
 6b0:	00 
 6b1:	e9 9a fe ff ff       	jmp    550 <main+0x20>
 6b6:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
 6bd:	00 00 00 
 6c0:	4c 39 54 24 f8       	cmp    QWORD PTR [rsp-0x8],r10
 6c5:	74 27                	je     6ee <main+0x1be>
 6c7:	c7 44 24 f4 0e 00 00 	mov    DWORD PTR [rsp-0xc],0xe
 6ce:	00 
 6cf:	e9 7c fe ff ff       	jmp    550 <main+0x20>
 6d4:	c7 44 24 f4 05 00 00 	mov    DWORD PTR [rsp-0xc],0x5
 6db:	00 
 6dc:	e9 6f fe ff ff       	jmp    550 <main+0x20>
 6e1:	c7 44 24 f4 0a 00 00 	mov    DWORD PTR [rsp-0xc],0xa
 6e8:	00 
 6e9:	e9 62 fe ff ff       	jmp    550 <main+0x20>
 6ee:	31 c0                	xor    eax,eax
 6f0:	c3                   	ret    
 6f1:	66 2e 0f 1f 84 00 00 	nop    WORD PTR cs:[rax+rax*1+0x0]
 6f8:	00 00 00 
 6fb:	0f 1f 44 00 00       	nop    DWORD PTR [rax+rax*1+0x0]

(*2)”objdump -s flattening”の出力結果のうち、セクション .rodata の内容を抜粋しました。

セクション .rodata の内容:
 08b0 01000200 ecfdffff d4fdffff bcfdffff  ................
 08c0 9cfdffff 7cfdffff 6cfdffff 4cfdffff  ....|...l...L...
 08d0 3cfdffff 2cfdffff 0cfdffff ecfcffff  <...,...........
 08e0 d4fcffff b4fcffff 0cfeffff           ............    

 

与えられるデータはmain関数の逆アセンブル結果とmain関数で使われているデータ領域のダンプ結果だけである。私がこれまでに行ってきたリバースエンジニアリングは、実際の実行ファイルを解析するツールで解析するものがほとんどだった。実際に実行ファイルを動かすことでデバッガによる動的解析が可能であり、ELFフォーマットとして完成しているためIDAやGhidraなどで解析に便利な機能が利用できた。しかし、今回の場合は基本的にはツール等に頼れず、自力で逆アセンブル結果を読み解く必要があり非常に勉強になった。

まずret命令に注目した。ret命令は581と6f0に存在している。581のret命令に注目すると直前の57cでmov命令によりeaxに0x1を代入していることがわかる。同様に6f0のret命令を見ると直前でxor命令を利用してeaxに0を格納していることがわかる。一般にeaxは関数の返り値として利用されることから、これらのret命令がflatteningの実行結果を表していると仮定した。

次にジャンプ命令に注目した。この逆アセンブル結果には24個ものジャンプ命令が存在する。先程のret命令の仮定が正しいとすれば、アドレス6ee(0を返すret命令の直前)にジャンプする場所を探せば良いとわかる。6eeにジャンプする命令を探すと6c5に1つだけ見つかる。je命令はゼロフラグがONのときにジャンプする命令であり、直前のcmp命令でフラグをセットしている。つまり、QWORD PTR [rsp-0x8] == r10が成り立つ場合が正しいコマンドライン引数であるとわかる。しかし、cmp命令の実行までにいくつか無条件に550へジャンプする命令がある(jmp 550)。これを回避しcmp命令を実行するためには6b6/6bd/6c0のいずれかのアドレスにジャンプしてくる必要がある。

先程までの解析を踏まえて6b6/6bd/6c0のいずれかにジャンプする命令を探した。しかし、直接アドレスを指定してジャンプしている部分は存在しなかった。他のジャンプ命令を見てみると560にjmp raxが存在していた。このraxに先程の3つのアドレスのいずれかを指定できれば0を返すretに繋げることができると考えた。

次にコード中に頻出するjmp 550について考えた。550以降ではeaxにDWORD PTR [rsp-0xc]を代入し、cmp命令でeaxと0xdを比較している。比較結果がeax > 0xdのとき57cにジャンプする(57cは最終的に1をリターンする)。つまり、eax < 0xdとなる必要があるとわかる。コード全体を眺めるとjmp 550の直前でDWORD PTR[rsp-0xc]に0x0~eのいずれかを代入していることがわかる。つまり、1をリターンさせないために0xd,0xeを代入している5c9と6c7にはジャンプしてはならない。550ブロックでは次のような処理によってraxを求めジャンプしている。

rax = [rdx+rax*4]

rax += rdx

jmp rax

大まかに部分ごとの処理がわかったので全体の処理を1行ずつ追うことにした。530のlea命令では[rip+0x37d]が示すアドレスをrdxにロードしている。ここでコメントされている#8b4という数字が何を指しているのか考えた。恐らくrip+0x37d=8b4が成り立っているのだろうと仮定し、それを検証するべく以下のコードを作成した。

//arg.c

int main(int argc, char *argv[]){

if(argv1 == “Z”)return 0;

else return 1;

}

このコードをobjdumpでダンプした結果、今回の課題と同じようにlea命令によるアドレスのロードが確認できた。lea命令からripを計算するとどちらもlea命令の次のアドレスを指していることが確認できた(ripはプログラムカウンタとして利用されるはずなのでこの結果は正しいと言える)。また、gdbを用いてlea命令後にブレークポイントを設定しlea命令で読み込まれたアドレスが指す内容を表示して確認した。結果、.rodataセクションの内容(上記のコード中の”Z”)のアスキーコードが格納されていることが確認できた。つまり、課題で与えられたコードでも同じことが言えると仮定し、lea命令でrdxに読み込まれるデータは0xecであるとわかった。

順に処理を見ていくと次に重要になるのは、559~560行の処理である。559では、最初に読み込んだrdxの値を利用してデータをraxに代入する処理を行っている。初回の実行で代入される値はDWORD PTR[8b4+0*4]である。つまり0xfdecである(リトルエンディアン)。55dでは、0xfdec + 0x8b4をraxに代入している。よってraxの値は0x6a0となり560ではjmp 6a0となる。

660からの処理を見てみるとrdiが0x2出ない場合は1をリターンするようになっている。0x2という数値から引数の個数をチェックしていると仮定し、gdbでarg.cに対していくつか引数を渡して仮定が正しいか確認した。結果、引数の数に合わせてrdiの値が変化し0x2は引数が1つのときの値であることがわかった。つまりプログラムに与えるコマンドライン引数の数は1つであることがわかった(実行ファイル名を含めず)。その後は[esp-0x3c]のアドレスから4バイトまでのデータに0x1を代入し550へジャンプしている。

以後550に飛びアドレスを求めてジャンプする処理を繰り返す。以下計算したアドレスと処理の関係である(簡略化のためにDWORD PTR[rdx+rax*4]をSRCと表記する)。

0: SRC = 8b4 → 6a0 : eax = 0x1;

1: SRC = 8b8 → 688 : eax = 0x2; ecx = 0;

2: SRC = 8bc → 670 : ecx > 0x7のとき63eへジャンプ ; eax = 0x3;

3: SRC = 8c0 → 650 : eax = 0x4; rax = 0; r11に引数を代入

4: SRC = 8c4 → 630 : r11b != 0 ならeax = 0x5; r11b == 0なら eax = 0x6

5: SRC = 8c8 → 620 : ecx += 0x1; eax = 0x2;

6: SRC = 8cc → 600 : ecx != 0x8なら1をリターン; eax = 0x7;

7: SRC = 8d0 → 5f0 : eax = 0x8; r8d = 0xffffffcc(r9d);

8: SRC = 8d4 → 5e0 : eax = 0x9; ecx = 0;

9: SRC = 8d8 → 5c0 : ecx <= 0x7なら6e1へジャンプ;(eax = 0xa);

eax = 0xd;

a: SRC = 8dc → 5a0 : rax = ecx; r11d = [r8+rcx*1]; eax = 0xb;

BYTE PTR[rsp+rax*1-0x8] xor r11b;

b: SRC = 8e0 → 588 : rax = ecx; eax = 0xc; r8d = BYTE PTR[rsp=rax*1-0x8];

c: SRC = 8e4 → 568 : eax = 0x9; ecx += 1; eax <= 0xdなら559へジャンプ;

eax > 0xdなら1をリターン;

d: SRC = 8e8 → 6c0 : QWORD PTR[rsp-0x8] == r10なら0をリターンする

2~5はループになっている。コマンドライン引数を1文字ずつメモリに格納している。r11bは今見ている一文字でこれが0の場合(文字列の終端の場合)はループを抜ける。

6で文字数による判定を行っている。文字数が8文字でない場合は1をリターンする。

このプログラムで重要な処理はa~cである。2~5で入力したデータに対してr11bでxorを取っている。つまりr11bと最終的な比較で利用されるr10がわかっていれば入力する値を求めることができる。以下はxorを利用し複号した手順である。

0x9e ^ 0xcc = 0x52

0xfa ^ 0x9e = 0x64

0x95 ^ 0xfa = 0x6f

0xef ^ 0x95 = 0x7a

0x92 ^ 0xef = 0x7d

0xa7 ^ 0x92 = 0x35

0xd5 ^ 0xa7 = 0x72

0xed ^ 0xd5 = 0x38

これらをASCiiコードとして文字に起こすと”Rdoz}sr8”となった。

解析に間違いがなければ”Rdoz}sr8”が0を返すコマンドライン引数である。

 

※追記

正しい解答は”Reiwa0x1″となるみたいです。

最終的な比較の際にoffsetの更新をしていなかったため一文字目以降がずれていました。

問7


### 導入 本課題はSIFT Workstationでfuse-apfsを利用して実施することを想定しています。次のリンクからOVA形式の仮想マシンイメージをダウンロードして調査環境を構築してください。 – https://digital-forensics.sans.org/community/downloads

### 課題 ファイルsample.rawおよびchallenge.rawは暗号化されていないAPFSパーティションをDDでダンプしたイメージデータです。 それぞれ1つのAPFSボリュームを含んでおり、3種類の異なるPDFファイルが双方のボリュームに一つずつ保存されています。 ただし、challenge.raw は一部のデータが改ざんされており、このままではイメージに含まれるボリュームをマウントしたり、全てのファイルを正しく取り出したりすることができません。 資料apfs101.pdfを参考に、以下の問いに答えてください。極力簡潔に記述してください。

参考までに、APFSパーティションイメージをapfs-fuseでマウント/アンマウントする場合は、以下のオプションを利用します。 “` # apfs-fuse -s 0 # fusermount -u “`

#### 問7-1 challenge.rawをfuse-apfsでマウントできるようにするために修正し、以下の内容を答えてください。

  1. 修正箇所のオフセットと修正内容。
  2. そのように修正するとマウントできるようになる理由。

#### 問7-2 challenge.rawに含まれるボリュームには、sample.rawに含まれるものと同じPDFファイルが保存されています。しかし、問1の問題を解決してマウントしても、そのままでは開くことができないファイルが1つあります。当該ファイルを開けるようにchallenge.rawのデータを修正し、以下の内容を答えてください。

  1. 修正箇所のオフセットと修正内容。
  2. そのように修正するとファイルを開くことができるようになる理由。
  3. 回答に至る調査の過程(簡潔に)。  

問7については提出期限までに答えが求まりませんでした。

そのため課題解決までに考えたこと・試したことをまとめて提出しました。

・apfs-fuse のbuild
・apfs-fuse を利用してsample.raw でマウントをテスト
・apfs-fuse を利用してchallenge.raw のマウントをテスト
・invalid superblock/oid 402 xid 3e NOT FOUND のエラーを確認した
・oid がblock アドレスと一致していないことが原因と考えた
・strling.exe を利用しsample.raw とchallenge.raw のバイナリ構造の比較をした
・どちらのバイナリにも共通で”NXSB”というASCii が見つかった
・NXSB について調べてみるとapfs のContainer Superblock を表す識別子であるとわかった[1]
・ここでblock アドレスをoid と一致させるための方針として、oid,xid を書き換える方法とcontainer superblock からのblock offset の間にデータを挿入しoid,xid と一致させる手法を考えた

[1]https://blog.cugu.eu/post/apfs/

 

まとめ


学内CTFを行ったり勉強会に参加したりしていて、課題に割ける時間がなく問7に関しては提出期限前日に取り組む事になりました。(解決できませんでしたが)

問5~7の技術的課題は取り組むだけでも非常に成長できます。昨年も合否に関係なく事前課題だけでも取り組むべきだったと感じました。

結果論になりますが、熱意だけで私のようなnewbieでも拾ってもらえたので自信がなくても応募してみるべきだと思います。

この記事を誰が見てるかわかりませんが、2019年以降のセキュリティ・キャンプに応募しようと考えてる方の参考になれば良いと思います。