My submisson for the pre-examination of Global Cybersecurity Camp v2.0 Tokyo (GCC Tokyo)

なんと強者であふれるGlobal Cybersecurity Camp v2.0 Tokyo (GCC Tokyo)に参加できることになったので,僕の応募課題をここに載せておこうと思います.僕の友人の方々はご存知かと思いますが,セキュキャン全国2018に行ってから僕はほとんど何もこれといって誇れる活動をしていません.それがコンプレックスであり,かつたいした実力もない状態でこの強者あふれるイベントに応募しても現実として通過できるとは思っていませんでした.しかしこのままだったら本当に高度人材への道が絶たれてしまい,人生に残された希望が完全に失われることになる.それはどうしても嫌だということでチャレンジしました.

・・・・・・

まず問題文を載せて,その後に回答の要約,回答本文,備考的な感じで感想という順番で書いていこうと思います.回答は提出したものそのままです.英文の誤りを気にしてはいけません…特に複数形と単数形.
問1と問2はここに晒すのは恥ずかしいので気が変わらない限り回答の公開を控えさせていただきます.

問1

【問題文】

What is your cyber security experience?

【回答要約】
これまでの数少ない経歴を書きました.
【回答】
略.
【感想】
特に昨年末~今年上半期にかけて何も書くことがなくて本当につらかったです...

問2

【問題文】

What is your English proficiency?
Please write in English.

【回答要約】
TOEICは対策してないという理由で2017年12月以来受けてこなかったので客観的なことはほぼ書けず.読解なら大丈夫だろう,焦ると話すのはきつくなる,YouTubeやUdemyで英語を聴く機会はあるけど全部聴きとるのは無理,というようなことを書きました.
【回答】
略.
【感想】
2番目に心配していた点.ここに応募してくる人はどうせ堪能だろうから,応募課題の問題部分ができていたとしてもここで落とされると思っていました.許していただけたのが本当にありがたいです...

問3

【問題文】

セキュリティ・キャンプに参加したことがある人は、参加した大会名と、クラス/トラック/受講講義等を教えてください。
(ない場合は「なし」と記載。)

【回答要約】
特に意識することなく普通に書きました.

【回答】

参加大会名: セキュリティ・キャンプ全国大会2018
参加トラック: A - 脆弱性・マルウェア解析トラック
受講講義:
  * A1~A3 - インシデントレスポンスで攻撃者を追いかけろ
  * A4 - In-depth static malware analysis
  * E5 - Linuxカーネル脆弱性入門
  * D6 - 組込みリアルタイムOSとIoTシステム演習 ~守って!攻めて!ロホ゛ット制御ハ゛トルて゛体験する組込みセキュリティ~
  * E7 - シリアル通信から学ぶBadUSB自作演習

【感想】
特になし.

問4

【問題文】

公開してる技術系の活動や資料(ブログや、Twitter、GitHub、Slideshare、Speaker Deckなど)がありましたらURL等を記載してください。
(学校の課題で作ったものか、自発的に作ったものかがわかるように列挙してください。)
専攻でやっていることについては、概要と、その分野での客観的にわかる成果などを記載してください。

【回答要約】
自分の活動の無さが露呈するのを覚悟で,自分が持ってる色んなWebサイトのアカウントを乗っけました.

【回答】

全て私の自発的な活動です.

[1] My portfolio website (全てのリンクはここから参照できます)
https://ciruela.netlify.com/

[2] Speaker Deck
https://speakerdeck.com/xcyba17her

[3] Hack The Box
https://www.hackthebox.eu/profile/206153

[4] Hatena Blog
https://verliezer93764.hatenablog.jp/

[5] GitHub
https://github.com/Ciruela-Xcyba17her

【感想】
自分の足跡は他人と比べてものすごく少なくてそれがコンプレックスだったので,せめてボリュームを増やせればと問6と問7を終わらせた後に急遽ProgateでECMAScript6を学び,Reactを学び,CSSに苦戦しつつ,Gatsby.js + Netlifyでポートフォリオウェブサイトを突貫工事で作りました.その期間およそ1週間と少し.本当はトップにWebGLなどによるアニメーションでさらにアピールを加えるつもりでしたが無理でした.まあWebデザインの勉強をしていないので期待してはいけません.ちなみにこれに時間をかけたせいで問5で痛い目を見ます.
それにしても内容がひどい.SpeakerDeckとHatenaBlogには簡単な問題のWriteupしか載ってない,HTBでは肝心のUser OwnもRoot Ownもできていない,GitHubにはカスみたいな制作物しかない,Twitterはお察し.まあポートフォリオサイトができただけでもまあ自分が今やれるだけのことはできたと思います.

問5

【問題文】

# Automate Vulnerability Finding and Binary Exploitation
This challenge is intended to reveal your thinking and working process, how you tackled it.  
Please describe your process as much detail as possible.  
If you can't fill all answers, it's fine.  

Your goal is finding all inputs that crash the toy program (referred as "crash inputs") by automated manner.  
All challenges use simple_linter.c as a target program, located in same directory.

1. Find as many crash inputs as possible of simple_linter.c by using AFL and laf-intel. Describe the difference between them based on your observation.
2. Write your angr script which can find all crash inputs of simple_linter.c.
3. Does symbolic execution always work better than fuzzing in the context of vulnerability finding? Please explain the advantage/disadvantages between AFL, angr and Driller.

【回答要約】
意図的にバグコードが仕組まれたsimple_linter.cというプログラムを題材にして,AFL,laf-intelをパッチしたAFL,angr(symbolic execution),Drillerの特徴を問う問題です.
Q1は実際にAFLと,laf-intelをパッチしたAFLとでsimple_linter.cをファジングし,挙動の違いを説明する問題です.strcmp(input, "MAGICHDR")の結果次第で発動するクラッシュ(id:1)について,AFL単体では検知できない(しにくい)のですが,laf-intelを利用すると検知しやすくなります.AFLは遺伝的アルゴリズムにより入力を変化させ,どんどんプログラムにおける"未踏の分岐先"を探していくことで最終的にクラッシュする入力を見つけ出しますが,クラッシュ(id:1)については入力の最初が正しく"MAGICHDR\x00"でない限り到達できず,未踏の分岐先が発見できないことになります.何のヒントもなしにこのような入力になりAFLがその分岐先を発見できる可能性は低く,結果としてAFL単体ではクラッシュ(id:1)を見つけられなかったと考えました.一方でlaf-intelに含まれるファジング用のバイナリを生成するためのコンパイラafl-clang-fastはstrcmpやそれに相当する2バイト以上の比較命令を複数の1バイトずつの比較に変換することができます.それによりAFLは"未踏の分岐先"に到達しやすくなり,結果としてファジングでクラッシュ(id:1)を発見できたと考えました.
Q2は全てのクラッシュ(id:0からid:2まで)を発見するangrスクリプトを書く問題です.感想で述べるようにangrについてはwriteupを主に参考に勉強しました.
Q3は,AFL,angr,Drillerの比較を考察する問題です.angrはシンボリック変数を用いた制約条件を組み立てることで特定のパスへと移動するための入力を求めることができます.AFLが苦手なstrcmpでの比較も制約条件の1つに過ぎないのでクラッシュ(id:1)も発見でき,AFLに比べて優位といえます.しかし再帰的なプログラムやサイズが大きいプログラムでは分岐の経路数が爆発的に多くなり,網羅は難しくなります.AFLがこの状況に対し優位であるという説明については納得できませんでしたが,AFLもangrも互いに異なる利点と欠点をもっていると考えました.DrillerはAFLとangrのいいとこどりをしたようなもので,path explosionが起きそうなところやstrcmpではない経路の網羅にはAFLのようなファジングのプロセスを使い,特定の値が必要な経路を探索するにはシンボリック実行のようなプロセスを使うというように2つの手法を組み合わせて経路を探索していく,と資料から読み取りました.しかし,経路探索を利用してハッシュ関数の第一原像計算困難性を破るという芸当をするのはさすがに難しいようです.

【回答】

#############################
### Answer for Question 1 ###
#############################

First, I tried fuzzing using both engines. They need a corpus file as the seed of genetic algorithm, so I prepared three corpus inputs: "A", "BBBBBBBB" and "CCCCCCCCABCDABCD", to check if there are differences in fuzzing results.

[1] Fussing with AFL
I used "Linux kali 4.19.0-kali5-amd64 #1 SMP Debian 4.19.37-6kali1 (2019-07-22) x86_64 GNU/Linux".
I fuzzed for 7 hours to find all unique crashes in AFL's ability. The result of found crash inputs are listed below.

(1) Using corpus "A"
  1. "\xde\xad\xad\xad\xad\xad\xa0\xad\xad\xb0\xad\x9a\xd0\x20"
  2. (571 bytes of data almost consists of \x00)
(2) Using corpus "BBBBBBBB"
  1. "\xde\xad"
  2. (1241 bytes of data almost consists of \x00)
(3) Using corpus "CCCCCCCCABCDABCD"
  1. "\xde\xad\x00\x00"
  2. (1082 bytes of data almost consists of \x00)

[2] Fuzzing with laf-intel-patched AFL
I tried to install clang-3.8 on my kali-linux by some ways and packages, but I couldn't install it. So I used Ubuntu 14.04 LTS and installed clang-3.8 by following instructions on [https://gitlab.com/laf-intel/laf-llvm-pass/issues/1]. Also, set environment variable LAF_TRANSFORM_COMPARES=1 to transform strcmp() into some one byte cmp instructions.

The result of found crash inputs are listed below.
(1) Using corpus "A"
  1. "\xde\xad\x1f\xde"
  2. (571 bytes of data)
  3. (256 bytes of "\x00")
  4. (65536 bytes of data almost consists of \x00)
(2) Using corpus "BBBBBBBB"
  1. "\xde\xad\xfa\x00"
  2. (1241 bytes of data almost consists of \x00)
  3. (234 bytes of data almost consists of \x00)
  4. (78861 bytes of data almost consists of \x00 and \x02)
  5. "\x4d\x41\x47\x49\x43\x48\x44\x52" ("MAGICHDR")
(3) Using corpus "CCCCCCCCABCDABCD"
  1. "\xde\xad\x43"
  2. (23259 bytes of data almost consists of \x00)
  3. (226 bytes of data almost consists of \x00)
  4. (66666 bytes of data almost consists of \x00)
  5. (41 bytes of data beginning with "MAGICHDR")

Then I did static analysis of "simple_linter.c". Apparently there are 3 kinds of crash inputs: inputs which start from "\xde\xad" (id:0), inputs which satisfy strcmp(input, "MAGICHDR") == 0 (id:1), and inputs which satisfy checksum validations in the program (id:2). Long crash inputs in the results seems to be related to crash id:2. Seeing from the results, I noticed fuzzing with only AFL couldn't find the crash input of strcmp validation(id:1), but AFL with laf-intel could find it. This is the major advantage of laf-intel.
Then I'll explain the major difference between AFL and laf-intel by each tools' functions. If you want to fuzz from a binary's source code with AFL, you must use afl-gcc or other compiler in AFL package. While compiling, it inserts "__afl_maybe_log" functions after each jump instructions to obtain code coverage information. It is used to notify AFL that which branch has been taken. If a new branch is taken, AFL detects it and runs further mutation in the new branch, then tries to find next new branch. I couldn't understand the detailed mechanism of how AFL detects new program path, but I thought the most important thing in fuzzing with AFL is to detect and explore new branch. Let's think about crash id:1, validation of strcmp(input, "MAGICHDR"). Using only AFL, the crash path is not taken unless the generated input is not started with "MAGICHDR\x00" correctly. Hence, AFL has difficulty in finding crash inputs of id:1. On the other hand, afl-clang-fast compiler of laf-intel splits the strcmp() or large-size comparation instructions into one-byte comparation instructions. Hence, AFL with laf-intel can discover new paths and crash inputs of id:1.
By the way, I wondered why both AFL and laf-intel could find crash inputs of id:2. simple-linter.c's checksum validation uses 4-bytes comparation, so I thought AFL is not able to find crash inputs of id:2. I couldn't understand it, but I noticed that all long crash inputs of the results above is started with "\x00"s, and this case satisfies the checksum validation. So I thought AFL's algorithm generated an input of "\x00"s as a rule (or extreme case as its genetic algorithm), and detected new branch, then produced crash inputs.


#############################
### Answer for Question 2 ###
#############################

I used angr for the first time. I couldn't understand all of its functions by document [https://docs.angr.io/], so I saw many ctf writeups [ especially, https://gist.github.com/Bono-iPad] which use angr and instructions on YouTube [especially, https://www.youtube.com/watch?v=a4tKDX4F5Ng] to learn its concepts and how to use.

begin angr_script.py ------------------------------------

# used python 3.7.5

import angr
import claripy
import subprocess
import os

# compiled by "gcc (Debian 8.3.0-19) 8.3.0" without any options other than -o.
run_file_name = './simple_linter'
sim_file_name = './input.bin'
addr_crash_offset = [0x13a5, 0x13c6, 0x1590]
addr_safe_offset = [0x13af, 0x13d0, 0x15a6]

p = angr.Project(run_file_name, load_options={'auto_load_libs': False})
# binary base address : generally 0x400000
bin_base_addr = p.loader.main_object.min_addr

# use more than sizeof(simple_format) bytes length BitVector as simfile's content
# referenced https://github.com/angr/angr/issues/968
data = claripy.BVS('data', 200 * 8)
sim_file = angr.SimFile(sim_file_name, content=data)

# "i" indicates crash id
for i in range(3):

	# Set the state ready to execute main entry point with arguments and simfile
	# referenced https://github.com/angr/angr/issues/968
	state = p.factory.entry_state(
				args = ['./simple_linter', sim_file_name],
				fs={sim_file_name: sim_file})
				
	# explore path
	sm = p.factory.simulation_manager(state)
	sm.explore(find=bin_base_addr + addr_crash_offset[i], avoid=bin_base_addr + addr_safe_offset[i])

	# print inputs that cause crash
	# referenced https://qiita.com/RKX1209/items/a514e5727119796e9fd5
	for found_state in sm.found:
		crash_input = found_state.posix.dump_file_by_path(sim_file_name)
		print('[ + ] input(s) that causes crash id:%d found!' % (i + 1))
		print(crash_input)

	# check if crash_input cause crash
	# solving id:2 takes about 1 min.
	print('[ + ] test execution using crash input')
	test_input_file_name = 'tmp.dat'
	f = open('tmp.dat', 'wb')
	f.write(crash_input)
	f.close()
	subprocess.run([run_file_name, test_input_file_name])
	os.remove(test_input_file_name)

end of begin angr_script.py -----------------------------


#############################
### Answer for Question 3 ###
#############################

I referenced [https://www.ndss-symposium.org/wp-content/uploads/2017/09/07_3-ndss2016-slides.pdf]. Then I summarized characteristics of AFL, angr, and Driller like this: AFL and angr(symbolic execution) have trade-off-like relations in their advantages/disadvantages, and Driller is developed to have both of their advantages. Then I'll explain each tools' characteristics.
Angr tries to fuzz a program by symbolic execution. In symbolic execution, input and other variables are defined as symbolic variables. Also, it expresses comparation instructions as constraints using symbolic variables. Then it uses solver (e.g. z3 solver) to find specific inputs which satisfy the constraints. As refered in Question 1, AFL is not good at finding new branches if a program has long-bytes comparation instructions. However, angr can find them because long-bytes comparation instructions are just constraints to solve. So I can say angr (and symbolic execution) has an advantage for fuzzing a program which has long-bytes comparation instructions. However, let's think about fuzzing a program that has recursive functions. Recursive process is expressed as many comparation instructions and many program paths. In this situation, angr has difficulty in finding inputs which satisfy each constraint and takes a long time to iterate over program paths. But AFL simply generates inputs by its algorithm, so AFL has an advantage for fuzzing a program which has a lot of comparation instructions. From the discussions above, AFL is good at fuzzing a program that has many comparation instructions, but not good at fuzzing a program that has long-bytes comparation instructions. On the other hand, AFL is not good at fuzzing a program that has many comparation instructions, but good at fuzzing a program that has long-bytes comparation instructions. Then I'll explain about Driller. I thought its fuzzing process is like this: first it uses AFL-like fuzzing process to search many paths that are easily found. Then it uses angr-like fuzzing process to search new paths that needs specific input. Then it explores the new path and backed to AFL-like fuzzing process. So Driller can do both iterating many paths and finding new paths which need specific values. However, finding paths which need specific hash value is still difficult for Driller.

【感想】
laf-intelを使うにはclang-3.8が必要なんですが,これが本当に苦労しました.まあ結局のところ古いUbuntuを入れるだけで解決したのですが,makeが成功したときは感動しました!(後でDockerを使えばよかったと気づく)
一度laf-intelの導入に挫折し,再開したのはポートフォリオサイト制作がひと段落したのちの締め切り一週間前です.Ubuntu14.04によりlaf-intelの導入は成功しましたが,次なる問題はangrスクリプトの作成でした.angrはかつてCTFで使おうとしましたが機能が複雑で理解しきれず断念していました.とはいえ解けないと未来はないのでまず公式ドキュメントを読みました.しかしふ~んとは思うものの結局どう使うんだという感じで理解できず.次はYouTubeに情報を求めました.かろうじて回答にも挙げたhttps://www.youtube.com/watch?v=a4tKDX4F5Ngは理解しやすく助かりましたが,多くの動画は同じくふ~んと思いつつ結局どう使うんだという感じになることばかりでした.結局CTFのwriteupと先ほどの動画,公式ドキュメントを並行して読んで何とかangrスクリプトを書き上げました.


問6

【問題文】

### 導入
本課題ではapfs-fuseを使用してAPFSボリュームをマウントすることを想定しています。次のリンクからソースをダウンロードし、各自コンパイルして利用してください。
https://github.com/sgan81/apfs-fuse

apfs-fuseの依存するライブラリのインストールなどが困難な場合は、SIFT Workstation上でコンパイルすることを推奨します。資料apfs101.pdfのP.27以降に手順を記載しています。

### 課題
ファイルsample.rawおよびchallenge.rawは暗号化されていないAPFSパーティション(container)をDDでダンプしたイメージデータです。
それぞれ1つのAPFSボリュームを含んでおり、ボリュームには3つのPDFファイルを含む"IIR"という名前のフォルダが配置されています。
  /
 ├ IIR
    ├ iir_vol40.pdf
    ├ iir_vol41.pdf
    ├ iir_vol42.pdf

ただし、challenge.rawは一部のデータが改ざんされており、このままではイメージに含まれるボリュームをマウントしたり、全てのファイルを正しく取り出したりすることができません。
資料apfs101.pdfを参考に、以下の問いに答えてください。(極力簡潔に記述してください。)

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

#### 問1
challenge.rawをfuse-apfsでマウントできるようにするために修正し、以下の内容を答えてください。
1. 修正箇所のオフセットと修正内容。
2. そのように修正するとマウントできるようになる理由。

#### 問2
challenge.rawに含まれるボリュームには、sample.rawに含まれるものと同じPDFファイルが保存されています。しかし、問1を解決してマウントしても、そのままでは開くことができないファイルが1つあります。当該ファイルを開けるようにchallenge.rawのデータを修正し、以下の内容を答えてください。なお、challenge.rawに含まれるファイルに関するデータが破損している場合などは、sample.rawに含まれる同名のファイルに関するデータを参考に修正してください。ファイルが適切に復元できているかどうかは、当該ファイルのhashを計算し、sample.rawの同名のファイルのhashと比較することで確認できます。
1. 修正箇所のオフセットと修正内容。
2. そのように修正するとファイルを開くことができるようになる理由。
3. 回答に至る調査の過程(簡潔に)。
ヒント:objectの内容を更新した際は、ヘッダ冒頭のFletcher-64 Checksumの再計算が必要です。

【回答要約】
APFSのメモリダンプchallenge.rawを修復してマウントできるようにし,かつマウントができてもiir_vol40.pdfというファイルの内容が読み込めなくなっているのでメモリダンプから修復を行うという,某Aトラックの応募課題でよく見るようなタイプの問題.
すごい優しいことにAPFSの解析入門の資料"apfs101.pdf"が与えられており,その資料とAPFSの仕様書を読めば解ける内容でした.
Q1ではchallenge.rawをマウントできるようにすることが求められます.マウントツールapfs-fuseファイルシステムを読み込むための核ともいえる,メモリダンプ先頭のContainer Superblockの修正をします.実はAPFSでは各ブロック(NTFSではクラスタにあたる?)についてバックアップが存在し,o_xidという値でその新旧を確認できます.challenge.rawにはContainer Superblockのバックアップが(たしか)4つ存在し,そのうち最新のバージョンを参照してContainer Superblockを複製すれば,壊れる前のchallenge.rawに近づけられます.
これでマウントができるようになりますが,マウントしたところで/IIR/iir_vol40.pdfの内容が読めないことがわかります.これを読めるようにするのがQ2の課題です.ちなみに同一ディレクトリにiir_vol41.pdfとiir_vol42.pdfもありますが,これらは内容を確認できます.メタデータは読み込めているのでファイルの内容を参照するポインタのような役目を果たす部分がおかしいことが予想でき,はじめに書いたようにapfs101.pdfとAPFSの仕様書を見ながら解析と修正を進めていきました.

【回答】

#############################
### Answer for Question 1 ###
#############################

Until I managed to mount challenge.raw, I changed values of three addresses listed below.

(1) 4 bytes from offset 0x20(nx_magic), changed to 0x4E 0x48 0x53 0x42 ("NXSB")
The value of o_type field is 1. It means this object's object type is NXSB and nx_magic must be "NXSB".

(2) 4 bytes from offset 0x24(nx_block_size), changed to 0x1000
According to p.17 of [https://github.com/libyal/libfsapfs/blob/master/documentation], each block's size of APFS is basically 0x1000.

(3) 4 bytes from offset 0xA0(nx_omap_oid), changed to 0x132
In order to make Container Superblock same with latest version, I searched Superblock that has the largest (or equal to first Container Superblock's) o_xid in Checkpoint area. Then I found the block starts from offset 0x4000 is the latest version of Superblock. Its nx_omap_oid is 0x132, so nx_omap_oid of Container Superblock is 0x132.


#############################
### Answer for Question 2 ###
#############################

[1] What I changed until "iir_vol40.pdf" can be mounted
(1) 8 bytes from offset 0x12D7B0(j_file_extent_val_t.phys_block_num of 1st fragment of "iir_vol40.pdf"), changed to 0x4AC3
(2) 8 bytes from offset 0x12D798(j_file_extent_val_t.phys_block_num of 2nd fragment of "iir_vol40.pdf"), changed to 0x468B
(3) 8 bytes from offset 0x12D780(j_file_extent_val_t.phys_block_num of 3rd fragment of "iir_vol40.pdf"), changed to 0x52C9
(4) 8 bytes from offset 0x12D000(Fletcher64 Checksum), changed to the sequence of 0x8F 0x72 0xE6 0x91 0xDD 0x53 0xA4 0x9C

About (1)~(3). These are the pointers for each fragment data of "iir_vol40.pdf". In "challenge.raw", these values are set to 0, so the metadata of "iir_vol40.pdf" is recognized but its content can't be recognized. There are 2 FSTREEs that contain information of "iir_vol40.pdf": older one placed at offset 0x125000, and newer one placed at offset 0x12D000. According to older one, each fragment of "iir_vol40.pdf" are placed at block numbers listed above. Also, I confirmed these block numbers are correct. Hence, block numbers listed above are suitable for each fragment's j_file_extent_val_t.phys_block_num.
About (4). I changed (1)~(3), so I must recalculate Flecher64 Checksum for the block. I used checksum calculation program introduced in "apfs101.pdf". The result is 0x91e6728f9ca453dd, so 8 bytes from offset 0x12D000 must be sequence of 0x8F 0x72 0xE6 0x91 0xDD 0x53 0xA4 0x9C.

[2] How I investigated "challenge.raw"
Because "iir_vol41.pdf" and "iir_vol42.pdf" are recognized correctly, and name of "iir_vol40.pdf" is recognized, I thought the pointer for its content is needed to fix. Then I analyzed by next flow.
(1) Container Superblock.
(2) OMAP object from offset 0x132000.
(3) B-Tree structure of OMAP (btree_node_phys_t) from offset 0x133000.
(4) Volume Superblock (apfs_superblock_t) from offset 0x131000.
(5) OMAP of Volume Superblock from offset 0x12B000.
(6) B-Tree structure of Volume Superblock from offset 0x12C000, then found 3 FSTREEs and analyzed  each of them.
(7) I found 2 FSTREEs that contain information of "iir_volo40.pdf": the one from 0x125000, the other from 0x12D000.
(8) By referencing each FSTREE's o_xid, I noticed FSTREE from offset 0x125000 is older, and FSTREE from offset 0x12D000 is newer.
(9) Fixed the pointer of each fragment(j_file_extent_val_t.phys_block_num) referencing the older FSTREE.
(10) Fixed Fletcher64 checksum.
(11) sha1 hash of "iir_vol40.pdf" from "sample.raw" and "iir_vol40.pdf" from fixed "challenge.raw" has the same value of 2b4ec0cddad73b04a72784d2d9379610427dbaa6, so I judged I finished to fix "challenge.raw".

【感想】
作業感があったのでしっかり復習します.予想通りAPFSのパーサを書く人を見かけたのですが,かつて作ろうとしていたpcap解析アプリよろしく開発の収集がつかなくなりそうだったので僕は書きませんでした…

問7

【問題文】

# Advanced Binary Deobfuscation Pre-Challenge

This CTF-style challenge is intended for measuring your program analysis skills. 

1. Submit a solver which extracts the flag within the `vmgcc` program. Literally, this program employs a custom VM architecture.
2. Submit a disassembler for the `keycheck` bytecode. Analyzing `vmgcc` is required in advance.

Enjoy!

【回答要約】
プログラム内で独自のCPUアーキテクチャを定義しており,ハードコードされている機械語の実行後に仮想的なレジスタr0が0になるような入力をするとSuccessというような表示がされるので,その条件を満たす入力を求める問題です.Q1ではその入力を求め,Q2ではその疑似CPUアーキテクチャの定義のもとでkeycheckというバイナリファイルをdisassembleすることが求められます.命令は5個定義されています.ただこれも優しいことにヒントとして案内される動画を見れば問題なく解けます.実はこのプログラムにsuccessというような表示をさせる入力は無数にあります.しかし問題文にはextract the flagとあるので特定の文字列を探さないといけないのかなと思いましたが,プログラムにSuccessを表示させるのが本問の目的ととらえ,深追いはしませんでした.
追記:このようなプログラム内でVMを定義し命令を実行させていくという難読化方法を"Virtualization Obfuscation"というそうです。

【回答】

#############################
### Answer for question 1 ###
#############################

#------- solver starts from here -------
### I used Python 3.6.4

from z3 import *
from binascii import *

# k[i] represents i-th register value
k = [BitVec('k%d' % i, 32) for i in range(4)]

and_nums = [
    0x41424344,
    0x45464748,
    0x494a4b4c,
    0x4d4e4f50
]

xor_nums = [
    0x41404044,
    0x41044748,
    0x494a404c,
    0x4d424340
]

s = Solver()

# optional first 4byte limitation: couldn't find meaningful flag... 
s.add(k[0] == int(hexlify(b'flag'[::-1]).decode(), 16))

xored = 0
for i in range(len(k)):
    xored ^= xor_nums[i]

# main limitation
s.add((k[0] & and_nums[0])
    ^ (k[1] & and_nums[1])
    ^ (k[2] & and_nums[2])
    ^ (k[3] & and_nums[3])
    ^ xored == 0
    )

# only use printable characters
for i in range(4):
    for j in range(4):
        b = Extract(8 * j + 7, 8 * j, k[i])
        s.add(b >= 0x21, b <= 0x7e)

# solve
r = s.check()
if r == sat:
    m = s.model()
    for i in range(4):
        print(unhexlify(b'%08x' % m[k[i]].as_long())[::-1].decode(), end='')

#------- solver ends here -------


#############################
### Answer for question 2 ###
#############################

#------- disassembler starts from here -------
### I used Python 3.6.4

# There are five instructions.
# vand dst_reg, src_reg ... dst_reg = dst_reg & src_reg
# vxor dst_reg, src_reg ... dst_reg = dst_reg ^ src_reg
# vmov dst_reg, (value) ... dst_reg = (value)
# vmov dst_reg, src_reg ... dst_reg = src_reg
# vend ... end of program
FILENAME = 'keycheck'
d = bytearray(open(FILENAME, 'rb').read())
print(d)
i = 0

while i < len(d):
    print('%.4x:  ' % i, end='')
    if d[i] == 0x0A:
        print('vand vr%i, vr%i' % (d[i + 1], d[i + 2]))
        i += 3
        continue
    
    if d[i] == 0x0B:
        print('vxor vr%i, vr%i' % (d[i + 1], d[i + 2]))
        i += 3
        continue
    
    if d[i] == 0x1A:
        print('vmov vr%i, 0x%x' % (d[i + 1], int.from_bytes(d[i + 2 : (i + 2) + 4], 'little')))
        i += 6
        continue
    
    if d[i] == 0x1B:
        print('vmov vr%i, vr%i' % (d[i + 1], d[i + 2]))
        i += 3
        continue
    
    if d[i] == 0xFF:
        print('vend')
        break
    
#------- disassembler ends here -------

【感想】
I enjoyed solving this problem. :)
ちなみにこれに触発された問題をContrailCTFで提供しようかなと思いましたがエスパー感が出そうで思いとどまっています.

最後に

参加者が僕では絶対に手が届かない場所にいると思っている人たちばかりですし,海外の人は絶対に僕とは比較にならない技術を持っているのでめっちゃ緊張しています.でも本来ならば僕には与えられなかったであろうこの機会は有効活用せねばなりません.知り合いもたくさん作りたいです.1年前の過ちの繰り返しにならないように!!
ところで,僕は応募課題をやる前はMac OSファイルシステムなんて知らなかったですし,AFLもlaf-intelもDrillerも名前すら聞いたことなかったですし,z3を使ったソルバも初めて書きましたし,angrも全く使い方を知らなかったです(今もangrの1割も理解できてないと思いますが).セキュキャンの課題は調べれば解けなくないと思うのでぜひ挑戦してみてほしいと思います.
(ただしこれは罠で,当日理解が追い付かず死亡する可能性もある)