はじめに
セキュリティキャンプ2019全国大会の脆弱性・マルウェア解析トラックの選考を通過し,参加できることになりました. 今更ですが,事前課題を晒したいと思います.課題1から5の,恥ずかしい部分(ポエムとか)はかなり割愛しました. 技術的な課題の部分で参考になる部分があれば,と思います.
セキュリティキャンプとは
IPA主催の情報セキュリティの勉強会です.全国大会と地方大会があって,全国大会は5日間かけて行われます.
課題
課題1
あなたが今まで作ってきたソフトウェアにはどのようなものがありますか? また、それらはどんな言語やライブラリを使って作ったのか、どこにこだわって作ったのか、たくさん自慢してください。
今まで作ってきたものをいい感じに自慢してください.そんなに大層なものはつくったことがなかったですが,こだわった点はしっかり書きました.(割愛)
課題2
今までに解析したことのあるソフトウェアやハードウェアにはどのようなものがありますか?また、その解析目的や解析方法、工夫した点があればそれらも教えてください。
今まで解析したことがあるものをつらつらと書きました.(割愛)
課題3
ブログやGitHubなど技術情報を公開しているURLがあれば教えてください。またその内容についてアピールすべきポイントがあれば記載してください。
GitHubのアカウントを載せました.
課題4
あなたが今年のセキュリティ・キャンプで受講したいと思っている講義は何ですか?(複数可)またそれらを受講したい理由を教えてください。
受講したい講義を素直に書き出しました.その講義を受けて今後どういったことに活かしていきたいかといったことも書きました.(割愛)
課題5
自分が最も技術的に興味を持った脆弱性をひとつ挙げ、技術的詳細(脆弱性の原因、攻略方法、対策方法など)について分かったことや思ったこと、調査の過程で工夫したこと等を報告してください。 その際、書籍やウェブサイトを調べて分かったことはその情報源を明記し、自分が独自に気付いたことや思ったことはそれと分かる形で報告してください。 また脆弱性の攻略方法を試す際は、他者に迷惑を掛けないように万全の措置をとってください。
ある脆弱性について,色々調べながら書きました.(割愛)
課題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 ............
以下が回答です. コードを一気に読むのが難しかったので,丁寧に処理を追うこととした.
537-54f leaは第2オペランドの実行アドレスを計算し,第1オペランドに代入する.ripは次の命令が入っているアドレスが入っている.rdxには0x537+0x37d = 0x8b4が入る.(右にあったコメントの通りだった) DWORD PTRは対象としている値を4バイトとして扱う.rsp-0xcが示すアドレスに0x0を入れる.これはスタックに値を積んでいる. moveabsで0xedd5a792ef95fa9eを入れる.64bitの即値を扱える命令であった. r9dに0xffffffccを入れる.
550-562 eaxにrsp-0xcのアドレスにある値を4バイトとして取り出す.cmpでeaxが0xdより大きければ57cにジャンプ. movsxdでrdx+rax*4が指す中身を4バイトとして取り出し,raxのビット数64に拡張して代入を行う. rax+rdxの結果をraxに入れ,raxの示すアドレスにジャンプする. ここで一度問題に直面した.例として,rax=0の場合を考える.エンディアンのことをすっかり忘れていた.0x8b4の示す値は0xecfdffffである.0x8b4 + 0xecfdffff = 0xecfe08b3となりアドレスがおかしいことに気づいた.そこでリトルエンディアンとして考えると0x8b4 + 0xfffffdec = 0x1000006a0となる.最上位のキャリーは桁が捨てられるので0x000006a0となり,正しい結果が得られた.
586-57a rsp-0xcの示すアドレスに0x9を入れる.eaxにrsp-0xcの示すアドレスに入っている値(9)を入れる.ecxに1を足す.Eaxが0xdでなければ,559へジャンプ.
57c-582 eaxに1を入れ,returnする.これは入力が特定の文字列ではない場合の結果となる.
588-59b raxにeacを拡張代入する.rsp-0xcの示すアドレスに0xcを入れる.r8dにrsp+rax*1-0x8のアドレスに入っている値を1バイトとして取り出す.スタックから値を取り出している. 550にジャンプ.
5a0-5bd ecxをraxに拡張代入する.rsp-0xcの示すアドレスに0xbを入れる.r11dにr8+rcx1が示すアドレスを入れる.rcxがカウンタか.rsp-0xcの示すアドレスに0x7を入れる.rsp+rax1-0x8にrsp+rax*1-0x8とr11dをxorした結果を格納する.550にジャンプ. 5c0-5dd ecxが0x7以下なら6e1にジャンプする.rsp-0xcの示すアドレスに0xdを入れて,550にジャンプ.
5e0-5ef rsp-0xcの示すアドレスに0x9を入れる.xorを用いて,ecxをゼロクリアする.550にジャンプ.ecxはおそらくカウンタ的な扱いだと思う.
5f0-5fb rsp-0xcの示すアドレスに0x8を入れる.r8dにr9d(0xffffffcc)を入れる.550にジャンプ.
600-61d ecxが0x8でなければ57cにジャンプする. rsp-0xcの示すアドレスに0x7を入れて,550にジャンプ.
630-64b ecxの値をraxに代入.rsp+rax*1-0x8を1バイトとして扱った値が0x0でなければ,6d4にジャンプする.rsp-0xcの示すアドレスに0x6を入れて,550にジャンプ.
650-66e r11にrsi+0x8の示す内容を64ビットとして代入する.rsiはコマンドライン引数のポインタが入っている.Rsi+0x0には実行ファイルのパス,Rsi+0x8には引数としてコマンドラインに打った文字列のポインタが入っていると考えられる. excの値をraxに入れ,rsp-0xcの示すアドレスに0x4を入れる. Movzx命令は第2オペランドを第1オペランドに代入し,第1オペランドで提供されないビットを0で埋める.R11dは32ビットである.R11+rax1の示すアドレスを1バイトとしてr11dにmovzx命令を用いて代入.R11dをrsp+rax1-0x8が示すアドレスに1バイトとして代入.550にジャンプ.
670-682 ecxが0x7より大きければ63eにジャンプ. rsp-0xcの示すアドレスに0x3を入れて,550にジャンプ.
688-69e rsp-0xcの示すアドレスに0x2を入れる. ecxをxorで0クリアして,550にジャンプ.
6a0-6bd ediが0x2と等しくなければ,57cにジャンプ.ediの内容が分からず調べると,引数が入る.1から6の引数はedi,esi,edxの順に入る.argc(引数の数),argvの順であるため,ediはコマンドライン引数の数である.実行ファイルと文字列以外に引数があった場合には終了するということである. rsp-0xcの示すアドレスに0x1を入れ,550にジャンプ.
6d4-6dc rsp-0xcの示すアドレスに0x5を入れ,550にジャンプ.
6e1-6e9 rsp-0xcの示すアドレスに0xaを入れ,550にジャンプ.
6ee-6f0 eaxをxorでゼロクリアして,return 0となり.これは特定の文字列が入力された場合の挙動である.
コードの内容を理解した上で,次は実際にその流れを追った.その結果,処理が2つのブロックに分かれていることに気づいた.それらについて説明する.
・入力された文字列をスタックに積む rsp-0xcの値は0x0,rdxの値は0x8dx. (a) rsp-0xcの値をeaxに代入.eaxの値が0xdより大きければ57c(return 0)に遷移.raxにrdx+rax*4のアドレスにある値を代入し,rax += rdxを行う.raxの指すアドレスに遷移.これは以下に従って遷移する.
rep-0xc → 遷移先アドレス → 説明のための表記
0x0 → 6a0 → (b)
0x1 → 688 → (c)
0x2 → 670 → (d)
0x3 → 650 → (e)
0x4 → 630 → (f)
0x5 → 620 → (g)
0x6 → 600 → (h)
(b) ediと2が等しくなければ,57cに遷移し,return 0.
(c) ecxをxorでゼロクリア.rsp-0xcに0x2を入れ,(a)に戻る
(d) ecxが0x7より大きければ63eに移る.ここで文字列のカウントを行い,8文字を超えると次の処理(2)へ移行する(ループ終了).rsp-0xcに0x3を入れ,(a)に戻る.
(e) コマンドライン引数の第2引数の文字列のアドレスにecx(文字数カウンタ)を足して,そのアドレスから値を取り出す.値をスタックに積み上げる.rsp-0xcに0x4を入れ,(a)に戻る.
(f) 直前の(オ)でスタックに積んだ値が0x0でなければ,6d4に遷移し.rsp-0xcに0x5を入れ,(a)に戻る.0x0の場合,rsp-0xcに0x6を入れ,(a)に戻る.
(g) カウンタであるecxを1進めてrsp-0xcに0x2を入れ,(a)に戻る.
(h) カウントが8文字でなければreturn 0となる.8文字の場合,rsp-0xcに0x7を入れ,(a)に戻る.処理(スタックに積まれた文字列を検査)へ移行.
この処理で,コマンドライン引数として指定された文字列の8文字目までを1文字目からスタックに積む. ここで,9文字以上入れた場合,9文字目以降は文字を処理するためのスタックには積まれない.つまり,答えの文字列を先頭8文字に置けば,あとの後ろの文字列は何でもいいのではないかと考えた.
・スタックに積まれた文字列を検査する 処理(入力された文字列をスタックに積む)が終わったあと,スタックは入力された文字列の先頭8文字が下から積まれた状態である. rsp-0xcの値は0x7,rdxの値は0x8dx. (a) rsp-0xcの値をeaxに代入.eaxの値が0xdより大きければ57cに遷移(return 0).raxにrdx+rax*4のアドレスにある値を代入し,rax += rdxを行う.raxの指すアドレスに遷移.これは以下に従って遷移する.
0x7 → 5f0 → (b)
0x8 → 5e0 → (c)
0x9 → 5c0 → (d)
0xa → 5a0 → (e)
0xb → 588 → (f)
0xc → 568 → (g)
0xd → 6c0 → (h)
(b) rsp-0xcに0x8を入れる.r8dにr9d(0xffffffcc)を入れて(a)に戻る.
(c) rsp-0xcに0x9を入れる.ecxをxorで0クリア.(a)に戻る.
(d) ecxが7以下なら6e1に遷移し, rsp-0xcに0xaを入れ,(a)に戻る. rsp-0xcに0xdを入れ,(a)に戻る.
(e) raxにecxを入れ,r11dにr8+rcx1という値を入れる.rsp+rax1-0x8とr11dのxor(1バイト)をとり,それをrsp+rax*1-0x8に入れる. rsp-0xcに0xbを入れ,(a)に戻る.
(f) raxにecxを入れ,r8dにrsp+rax*1-0x8の値を入れる.rsp-0xcに0xcを入れ,(a)に戻る.
(g) rsp-0xcに0x9を入れ,eaxにrsp-0xcにある値を入れる(0x9).ecxを1進め,eaxと0xdを比べ,等しくなければ(a)の559へ戻る.
(h) ここで,スタックに積んだ文字列とr10(0xedd5a792ef95fa9e)と比較し,等しければreturn 1,等しくなければreturn 0.
これらの2つの処理を踏まえ,1文字ずつ考えることとした.
1文字目と0xffffffcc+0のxorがr10の1バイト目(0x9e)と,等しくなるのだから1文字目は0xcc xor 0x9e = 0x52.Asciiで”R”を示す.
2文字目と0x9e+1のxorがr10の2バイト目(0xfa)と,等しくなるのだから2文字目は0x9f xor 0xfa = 0x65.Asciiで”e”を示す.
3文字目と0xfa+2のxorがr10の3バイト目(0x95)と,等しくなるのだから3文字目は0xfc xor 0x95 = 0x69.Asciiで”i”を示す.
4文字目と0x95+3のxorがr10の4バイト目(0xef)と,等しくなるのだから4文字目は0x98 xor 0xef = 0x77.Asciiで”w”を示す.
5文字目と0xef+4のxorがr10の5バイト目(0x92)と,等しくなるのだから5文字目は0xf3 xor 0x92 = 0x61.Asciiで”a”を示す.
6文字目と0x92+5のxorがr10の6バイト目(0xa7)と,等しくなるのだから6文字目は0x97 xor 0xa7 = 0x30.Asciiで”0”を示す.
7文字目と0xa7+6のxorがr10の7バイト目(0xd5)と,等しくなるのだから7文字目は0xad xor 0xd5 = 0x78.Asciiで”x”を示す.
8文字目と0xd5+7のxorがr10の8バイト目(0xed)と,等しくなるのだから8文字目は0xdc xor 0xed = 0x31.Asciiで”1”を示す.
これらの文字を繋げると”Reiwa0x1”となった.
課題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 <image file> <mount dir> # fusermount -u <mount dir>
問7-1
challenge.rawをfuse-apfsでマウントできるようにするために修正し、以下の内容を答えてください。
- 修正箇所のオフセットと修正内容。
修正箇所:0x20 - 0x23,0x24 - 0x27 修正内容:4E 58 53 42,00 10 00 00
- そのように修正するとマウントできるようになる理由。
challenge.rawをfuse-apsでマウントしようとすると,“This doesn’t seem to be an apfs volume (invalid superblock). oid 402 xid 3e NOT FOUND!!! Unable to get volume!”というエラーがでた.そこで,エラーが出ているsuperblockから調べることとした.文献[1]を調べると,Container Superblockはファイルシステムのエントリポイントであり,ブロックサイズやブロックの数,spacemanegerのポインタなどを持つことがわかった.Container Superblockの構成としては,文献[2]の6.1節に詳しく書かれており,これを確認するためsample.rawをhexeditというエディタを用いて開いた.まずblock headerから始まり0x0-0x20まで続く.0x0からのOffset 32(0x20)の位置,0x20-0x23に”NXSB”という文字列があり,Offset 36(0x24)の位置,0x24-0x27にブロックサイズ(4096 bytes)が存在し,その後も文献[2]の6.1節の通りだったため,blockheader,superblockの構成を確認できた.おそらく,challenge.rawはsuperblockの内容が書き換えられているのだろうと思い,superblockを確認することとした.0x20-0x23を見てみると,”NXSB”と書かれてあるはずが”XXXX”,0x24-0x27のブロックサイズがあるべきはずのところが0となっていた.これらの理由でsuperblockとして認識されず,ブロックサイズがないということでファイルがないと判断されてしまったのではないかと考えた.以下に修正内容についての理由の詳細を述べる. ・0x20-0x23 この部分には,文献[2]によると,”NXSB”が入る.つまり,4E 58 53 42である.この記述でsuperblockということを示している. ・0x24-0x27 この部分には,ブロックサイズが入る.文献[1]によると標準的なブロックサイズは4096bytesだと書かれている.sample.rawと同じファイルがchallenge.rawに入っているとするならば,それらのブロックサイズは同じと考え,4096bytesにした.
これらを書き換えると,マウントすることができた.root/IIRの中に,iir_vol40.pdf,iir_vol41.pdf,iir_vol42.pdfがあることを確認した.またiir_vol41.pdfが開けないことも分かった.
課題を進めていくうちに判明したことだが,ApfsContainer.cppにおいて,“This doesn’t seem to be an apfs volume (invalid superblock).”というエラーメッセージの検索を行うと,m_nx.nx_magic != NX_MAGICの式が成り立つ際にこのエラーが出ることが分かった.m_nx.nx_magicはContainer Superblockのオフセット32からのSignatureであり,NX_MAGICは,ApfsLib/Diskstruct.hにおいて0x4253584Eと値が定義されていたので,この問7-1の回答があっているだろうと感じた.このことから,エラーメッセージがどこで吐かれているかを正しく見つけるともう少しスマートに課題が解けたのかなとも感じた.
問7-2
challenge.rawに含まれるボリュームには、sample.rawに含まれるものと同じPDFファイルが保存されています。しかし、問1の問題を解決してマウントしても、そのままでは開くことができないファイルが1つあります。当該ファイルを開けるようにchallenge.rawのデータを修正し、以下の内容を答えてください。
- 修正箇所のオフセットと修正内容。
修正箇所:0x12C000-0x12DFFF 修正内容:0x12C000-0x12DFFFのデータを,0x124000-0x125FFFのデータで上書きする.
- そのように修正するとファイルを開くことができるようになる理由。
0x12D000から始まる,oid:409,xid:3Dのファイルシステムツリーにおいて,iir_vol41.pdfには25というfile idが振られている. ファイルシステムツリーには,ファイルのデータを構成するための開始アドレスとそのサイズを示す箇所がある.該当箇所は以下である. FileExt 25 0 => 2F4000 0 0 FileExt 25 2F4000 => 2F2000 0 0 FileExt 25 5E6000 => 3DB000 0 0 FileExt 25 800000 => 100000 0 0 開始アドレスに関する情報が0になっており,ファイルに正しくデータを写すことができない.開始アドレスを適切な値にすれば,ファイルが開けると考えた.適切な値をoid:409,xid:39,3A,3B,3Cの中で見つけた.iir_vol41.pdfの適切な開始アドレス,サイズに関する情報は以下であった. FileExt 25 0 => 2F4000 4FAC 0 FileExt 25 2F4000 => 2F2000 558B 0 FileExt 25 5E6000 => 3DB000 4471 0 FileExt 25 800000 => 100000 49C3 0 この開始アドレスは,どのxidについても同じであり,正しいと考えられる.そこで,ファイルシステムツリーのxidが1つ古い3Cを用い,それに合わせてオブジェクトマップも変更する. オブジェクトマップについて,oid:409,xid:3Dを指す0x12C000-0x12CFFFを,oid:409,xid:3Cを指す0x124000-0x124FFFに置き換え,ファイルシステムツリーについて,開始アドレスがつぶされている0x12D000-0x12DFFFを,正しい開始アドレスが入る0x125000-0x125FFFに置き換える.
- 回答に至る調査の過程(簡潔に)。
まず,iir_vol41.pdfをhexeditで開いてみると,sampleに入っているiir_vol41.pdfと同サイズではあるが,全てのデータが0であった.サイズは同じなのになぜデータが移されていないのか疑問に思った.
apfs101.pdfを参考にして,メタデータの参照を実際に追うこととした.その際,apfs-dumpを用いて,ダンプ結果を見た. まず,paddr:00...0000のcontainer super blockを見て,そのnx_omap_oidが132.paddr: 00...0132のオブジェクトマップを見て,そのtree_oidが133.paddr:00...0133のオブジェクトマップを見て,そのpaddrが131.paddr:00...0131のapfs super blockを見て,そのomap_oidが12B.paddr:00...012Bを見て,tree_oidが00...012C.paddr:00...012Cを見ると,キーが3つあり,それぞれを参照してみると,paddr:00...012D,oid:409,xid:3Dに今回のpdfファイルを構成している情報があると気づいた.oid:409,xid:3Dのオブジェクトでは,iir_vol41.pdf => 25,iir_vol40.pdf => 29,iir_vol42.pdf => 24というfile idがそれぞれ振られていた.また,ファイルのデータの開始アドレス,サイズを示しているを探すと,FileExtという箇所を見つけた.例として,以下にiir_vol42.pdfの情報を示す. FileExt 24 0 => 3DB000 5B7F 0 FileExt 24 3DB000 => 5F000 4412 0 これの意味としては,アドレス5B7F000からサイズ3DB000分だけファイルにデータを移し,アドレス4412000からサイズ5F000分だけファイルにデータを移すということである.これは実際にchallenge.rawの該当アドレスから,そのサイズのデータが実際にiir_vol42.pdfに移されていることを確かめた. ここで,iir_vol41.pdfの該当箇所を見ると以下のようになっていた. FileExt 25 0 => 2F4000 0 0 FileExt 25 2F4000 => 2F2000 0 0 FileExt 25 5E6000 => 3DB000 0 0 FileExt 25 800000 => 100000 0 0 開始アドレスに関する情報が0になっている.これを適切な値にすれば,ファイルが開けると考えた.
以下に,iir_vol41.pdfのファイルを開くために行ったことを示す. まず,古いxidを持つオブジェクトの利用について述べる. apfs101.pdfにチェックサムの検証に失敗した場合は,同じoidで古いxidを持つオブジェクトが利用されるとあった.そこで,oid:409,xid:3Dのチェックサムを壊せばxidが1つ古い,oid:409,xid;3Cのオブジェクトが利用されるのではと考えた.しかし,oid:409,xid:3Dのチェックサムを壊してマウントすると,”oid 402 xid 3e NOT FOUND”というエラーが出た.変更を加えた箇所以外のところで,エラーが出たため,なぜこのエラーが出たのかが分からない.
次に,オブジェクトマップの変更について述べる. paddr:00..012Cにoid:409,xid:3Dが,paddr:00..12Dにあることを示す情報がある.oid:409,xid:3Dのファイルシステムツリーが壊れているので,oid:409,xid:3Dへのマップ先を変更してoid:409,xid:3Cがあるpaddr:00...125を示すように変更させようとした.しかし,これも失敗した.このマップ先の変更に合わせ,チェックサムが正しい値に修正されていないことが原因だと考えられる.チェックサムが正しくなければ,オブジェクトは認識されない.チェックサムを正しくせずにマウントすると,”transport end point is not connected”というエラーが出力されて,マウント先のディレクトリに入ることができなかった.これはファイルシステムが壊れている際に出力されるエラーらしい.チェックサムを計算しようとしたが,oid:409,xid:3Dのチェックサム"58D71C947F226F9A”の値がまず,再現できなかった.文献[5]にあるFletcher64を用いて,文献[2]にある式を使ったのだが,何度やってもできなかった.
最後に,オブジェクトマップとファイルシステムツリーの入れ替えについて述べる. データを変更するにあたってチェックサムを修正しなければならないことが分かった.しかし,何度計算しても,チェックサムの計算が合わない.そこで,oid:409,xid:3Dにおいてiir_vol41.pdfにデータを入れるための開始アドレスが間違っているならば,xidが1つ古くて開始アドレスがあっているoid:409,xid:3Cをそのまま用いればいいと考えた.それに対応して,オブジェクトマップも書き換える必要があったため,oid:409,xid:3Cを指している部分を活用しようと考えた.オブジェクトマップについて,oid:409,xid:3Dを指す0x12C000-0x12CFFFを,oid:409,xid:3Cを指す0x124000-0x124FFFに置き換え,ファイルシステムツリーについて,開始アドレスがつぶされている0x12D000-0x12DFFFを,正しい開始アドレスが入る0x125000-0x125FFFに置き換えることで,iir_vol41.pdfを含む3つのpdfファイルを開くことができた.
おわりに
来年度以降に応募される方にとって参考になる部分があれば幸いです.