# セキュリティキャンプ応募課題
## ※間違っている可能性・わからなかった場所等あります
### 5.
#### 5.1.
プログラム自体はentryから始まるが(TLS callbackがある場合はそっちが先)、Visual studioでコンパイルした場合、プログラミングした内容はWinMainから始まるのでWinMainからの処理を追う。
WInMainが始まると、1バイトずつ何かしらのデータをスタックに書き込んでいる。これらの値がすべてasciiコードで文字にできる値っぽかったので、decompile画面で、代入されている一つ目の変数の型を「char[30]」に変更した。すると、値がすべてascii文字に変わり、”C:\Users\Public\KB59E7269.log”という文字列になった。これは何かしらのパスだと思われるので変数名を「path」に変更した。
次の関数呼び出しではpathとuint型変数のアドレスを引数として渡している。この関数の処理を追う。まずCreateFileAで「path」をパスとして、ファイルを開いている。Microsoft docsを参考にしながらその他の引数を確認すると、「dwCreationDisposition」に3がセットされており、ファイルが存在するときのみファイルを開くようになっていることがわかった。他もざっと確認はしたが、おそらくのちに必要なアクセス権などを要求するフラグなどなので、とりあえず気にしないことにした。次にGetFiPOweerleSizeを先ほど取得したハンドルを渡して呼び、その戻り値を第二引数の参照先に格納していた。つまり、第二引数は、「path」の表すファイルのサイズとなった。その後はmallocで確保した領域に、ReadFileでファイルから読み取ったデータを書き込み、ハンドルをクローズしたのちに確保した領域のポインタをreturnしている。
先ほど返ってきたポインタがNULLであればプログラムは終了する。 NULLでなければ次の処理に進む。次は先ほどのポインタとサイズを引数とした関数である。内部を少し見たところ、これが復号関数であると考えられるので問5.2で考える。復号が終わるとWinExecを呼ぶ。Microsoft docsを読むと、WinExecはコマンドライン文字列を第一引数に受け取り実行する。第二引数はウィンドウの表示に関わる値だった。
このプログラムの動作をまとめると、
・”C:\Users\Public\KB59E7269.log”を開き、データを読み取る
・読み取った値を復号する
・復号したデータをコマンド文字列として実行する
となる。
#### 5.2.
読み取った暗号データのある領域へのポインタをももつ変数を「data」と名付け、そのサイズを持つ変数「size」と名付けた。復号関数の引数は、(data, 0x10, data+0x10, size-0x10)である。また、その後のWinExecの引数に、data+0x10をコマンドライン文字列として渡していることを考えると、復号されたデータはdata+0x10以降に入っていると考えられる。
復号関数の処理を追っていく。
1. 「tmp」に0~255までの値を一バイトずつインデックス順に格納する。
2. x=0、i=0、j=0で初期化する
3. (x+tmp[i]+data[j])&0xffを新たなxとする(オーバーフローしたら0になる)
4. tmp[i]とtmp[x]をスワップする
5. jをインクリメントし、引数2の値(0x10)になった場合、0にする
6. iをインクリメントし、2.に戻る。i <= 255まで繰り返す
7. x=0、i=0、j=0
8. j=(j+1)&0xff(jをインクリメントし、8bitからオーバーフローしたら0にする)
9. (tmp[j]+x)を新たなxとする(オーバーフローしたら0になる)
10. tmp[j]とtmp[x]をスワップする
11. tmp[tmp[(tmp[j] + tmp[x])&0xff]]とdata[0x10+i]をxorする
以上が復号処理である。xorは同じ値を2回演算すると、元の値になるという性質がある。また、data[0~0xf]は前半部分でしか使われておらず、書き換えはdata[0x10]以降のみである。これらのことから、この復号関数は暗号関数でもあり、引数は(暗号キーのポインタ、暗号キーの長さ、暗号化・復号対象のポインタ、暗号化・復号対象の長さ)であるとわかった。仕様としては、暗号化に使ったのと同じキーを復号時に与えれば、第三引数の値は復号されるというものである。
data.datを”C:\Users\Public\KB59E7269.log”であると仮定し、この関数と同一の処理を行い、復号結果を出力するスクリプトをpythonで作成した(問5.3)。これを実行すると、
PowerShell.exe -enc VwByAGkAdABlAC0ASABvAHMAdAAgACIAQwBvAG4AZwByAGEAdAB1AGwAYQB0AGkAbwBuAHMAIQAgAEIAZQAgAEMAYQByAGUAZgB1AGwALAAgAGkAZgAgAHkAbwB1ACAAcgB1AG4AIABtAGEAbABpAGMAaQBvAHUAcwAgAHMAYwByAGkAcAB0AHMALAAgAHQAaABlACAAcwBoAGUAbABsACAAdwBhAHMAIAB0AGEAawBlAG4ALgAiACAALQBGAG8AcgBlAGcAcgBvAHUAbgBkAEMAbwBsAG8AcgAgAHIAZQBkADsAIABTAHQAYQByAHQALQBTAGwAZQBlAHAAIAAtAHMAIAA1AA==
という文字列が出力された。これを(念のため)Windows11VM上のPowershellで実行してみると、赤い文字で「Congratulations! Be Careful, if you run malicious scripts, the shell was taken.」と出力された。
調べてみたところ、Powershellは-encを指定するとBase64でエンコードされたコマンドを実行できるということがわかった。試しに-enc以降の部分をCyberChefでBase64を逆エンコードしてみたところ、
「W.r.i.t.e.-.H.o.s.t. .".C.o.n.g.r.a.t.u.l.a.t.i.o.n.s.!. .B.e. .C.a.r.e.f.u.l.,. .i.f. .y.o.u. .r.u.n. .m.a.l.i.c.i.o.u.s. .s.c.r.i.p.t.s.,. .t.h.e. .s.h.e.l.l. .w.a.s. .t.a.k.e.n...". .-.F.o.r.e.g.r.o.u.n.d.C.o.l.o.r. .r.e.d.;. .S.t.a.r.t.-.S.l.e.e.p. .-.s. .5.」
となった。「.」が入っているのが気になるが、取り払ってこのコマンドを実行すれば出力と合致する。
#### 5.3
```
data = []
with open("data.dat", "rb") as f:
while True:
buf = f.read(1)
if not buf:
break
data.append(int.from_bytes(buf, "little"))
tmp = [i for i in range(0, 0x100)]
x = 0
j = 0
for i in range(0, 0x100):
x = data[j] + tmp[i] + x
x = x&0xff
y = tmp[i]
tmp[i] = tmp[x]
tmp[x] = y
j = (j+1)%0x10
j = 0
x = 0
for i in range(0, len(data) - 0x10):
j = (j+1)&0xff
x = tmp[j] + x
x = x&0xff
y = tmp[j]
tmp[j] = tmp[x]
tmp[x] = y
data[i+0x10] ^= tmp[tmp[tmp[j] + tmp[x] & 0xff]]
ans = ""
for b in data[0x10:]:
ans += chr(b)
print(ans)
```
### 6.
#### 6.1
わからなかったです。
#### 6.2
オンラインストレージサービスにアクセスするにはブラウザを使うのが一般的だと考えられたため、Firefoxのログ等を取得する方法を調べた。そこで以下のサイト(https://support.mozilla.org/en-US/kb/profiles-where-firefox-stores-user-data )を参考にして調査を行ったところ、「C\Users\sc2022\AppData\Roaming\Mozilla\Firefox\Profiles\hc93rfhn.default-release\places.sqlite」というデータベースファイルにダウンロードしたファイルや、閲覧したサイトに関する情報があることがわかった。このファイルをDB Browserで開き、「moz_places」テーブルを見てみると、「容量無制限の無料オンラインストレージ firestorage」がタイトルになっているURLが二つ(https://fire.st/ZM7Nt1uとhttps://firestorage.jp/me/space/fndixhju57z27j5x )あった。試しに前者のURLにブラウザでアクセスしてみたところ、「firestorage」というサービスのサイトに行った。これは短縮URLであるとのことだったので、本サイトに移動したところ、「https://firestorage.jp/me/space/a83o61gr1qkryu54 」というURLに飛んだ。
これを確認した後で、「https://firestorage.jp/me/space/fndixhju57z27j5x 」にアクセスすると、「space/」以降の部分はワンタイムで使われているとわかる。また、正しいURLの形は「https://fire.st/[7桁の英数字] 」であることもわかった。ここで「https://fire.st/ZM7Nt1u 」は上記の情報より正しいURLの形であるので、このURLからアクセスした場合は実際に使われていた環境にアクセスしていると考えられる。
再びDB Browserに戻ってテーブルを確認すると、「last_visit_date」というカラムがあるのに気が付いた。これはおそらくURLにアクセスした時間を表している。「https://fire.st/ZM7Nt1u 」のレコードの「last_visit_date」を確認すると、「1647956125091000」という値が書いてある。タイムスタンプはエポック秒で記録されていると考えられる。ただ、エポック秒にしては大きかった。これをpythonのdatetimeモジュールを使っていろいろ試したところ、どうやらマイクロ秒で記録されていたらしく、時間は「2022年3月22日22時35分25.091秒(日本時間)」であるとわかった。
#### 6.3
正しいURLの形は「https://fire.st/[7桁の英数字] 」であるので、「https://fire.st/ZM7Nt1u 」は正しいURLの形である。よってこのURLからアクセスした場合は実際に使われていた環境にアクセスしていると考えられる。ここにアップロードされているファイルは何もないので、アップロードされたファイルはないと考えられる。
### 7.
#### 7.1
まず、Buffer Overflowとは、あるデータを保存しておくために確保された固定長領域「Buffer」に対して、その領域を超えるような書き込みをしてしまうことである。Heapとは動的に確保、読み書きができる領域で、具体的にはmalloc関数やnew演算子でオブジェクト生成をしたときなどに確保される領域である。
Heapは「チャンク」という単位で確保される。一回のmallocやnewでは一つのチャンクが確保されるが、そのチャンク内を分割してメンバに割り当てている。このようにHeap上に確保された領域のうち、分割された範囲を超過して書き込みを行うことをHeap Buffer Overflowという。攻撃者はこれを利用してデータを書き換えることや、ポインタを書き換えて別の攻撃につなげることができる。
例えば
struct A{char[8] buf; struct A *next;};
で定義される構造体Aを
a = (struct A*)malloc(sizeof(struct A))
で確保する。当然a->bufとa->nextには違う領域が割り振られているが、同じチャンクに確保されている。ここで、a->bufに対して8バイトより多くの書き込みを行った場合、想定される範囲を超過して書き込みを行っているので、Heap Buffer Overflowが成立する。
バッファへの書き込みは、より上位のアドレスへと行われていく。ゆえにa->nextがa->bufより上位のアドレスに確保されていて、かつa->bufがOverflowできるのであれば、攻撃者はa->nextに任意の値を書き込むことができる。a->nextは構造体Aのポインタ型なので、a->nextを参照して別の構造体Aにアクセスするような処理があることが想定される。
以下のようなコードを想定する。
コード例.txt
上記のコードでは任意のアドレスに、任意のデータを書き込むことができる。
二度目の入力で、8バイト+(書き込みたいアドレスのリトルエンディアン)を入力すると、三度目の入力は、先ほど書き込んだアドレスに書き込まれる。よって、任意のアドレスに任意のデータを書き込むことができる(これを1・2度目の入力でやった場合、二度目の入力の9~16バイト目は書き込み可能なアドレスになっていないとエラーが出るため不便になる)。上記のコードにはないが、この手法で関数ポインタなどを書き換えることができれば任意のコードの実行も可能である。
#### 7.2
Heap領域は無限にあるわけではないので、確保したチャンクを使わなくなったら開放(free)する。Use-after-Freeはこのようにして解放された後のチャンクに対して読み取り、もしくは書き込みを行うことである。
解放されたチャンクは再利用できるように、単方向リストなどの制御構造(glibcでいうtcacheなど)につながれる(場合によっては再利用されない)。つまり、Use-after-Freeを使って解放済みのチャンクのデータを読み取ると、次のチャンクのアドレスがわかったり、ライブラリ関数上の特定のアドレスがわかったりする。特定のアドレスがわかれば、オフセットからライブラリ関数がメモリ上のどこに配置されているか特定できる可能性がある。
Use-after-Freeによって書き込みができる場合、任意のアドレスへの書き込み・任意のコードの実行ができる可能性がある。解放済みのチャンクは再利用のための制御構造に入れられるが、これは単方向リストや双方向リストなどで管理される。この時、それぞれのチャンク内にリンク先のアドレスが書き込まれている。これらはサイズごとに別々のリストとして管理されている(サイズが大きい場合はこの限りではない)。よって特定のサイズxのチャンクBにUse-after-Freeで書き込みが行えるとすると、チャンクBに任意のアドレスを書き込み、サイズxのチャンクを何度か確保すると、先ほど書き込んだ任意のアドレスがチャンクとして確保される。なぜなら制御構造は、確保したチャンクのリンク先を、次に確保するからである。このようにして任意アドレスの確保ができ、書き込みもできた場合、関数ポインタなどを書き換えれば任意の処理を実行させることができる。
#### 7.3
(1) A(1, 2); B(3);
0x401027, 0x1, 0x4010f2, 0x2, 0x0, 0x401aa0, 0x401027, 0x3, 0x401bb0
(2) A(B(0));
0x401027, 0x0, 0x401bb0, 0x40103a, 0x0, 0x40132a, 0x401aa0
(3) A(x);
実現できない:
非負整数型の引数はポインタではなく値を渡さなければならない。つまり「メモリアドレス0x602080から値を読みだして、rdiに格納する」という処理が必要となる。これを実現するにはメモリから値を読み出してレジスタにコピーできる命令が必要となるが、メモリから読み出せる命令は「rep movsq」のみである。これは、メモリからメモリに値をコピーする命令であるため要件を満たさない。よってこの問題は実現できない。
(4) x = 0xdeadbeefcafebabe;
実現できない:
これを実現するには、「レジスタに0xdeadbeefcafebabeを格納し、レジスタからメモリアドレスに0x602080にコピーする」という処理が必要となる。しかし、(3)でも述べたように、メモリからメモリにコピーする命令しかないため、実現できないといえる。一応0xdeadbeefcafebabeという値が、読み取り可能な領域にあるのであれば、実現可能となる。0xdeadbeefcafebabeが0x6020a0に書いてある前提であれば、以下のようにROP chainが組める。
0x401027, 0x602080, 0x4011f2, 0x6020a0, 0, 0x40103a, 1, 0x40132d
(「mov rdi, rax」は3バイトの命令なので、0x40132aから3バイトスキップした0x40132dは「rep movsq; ret;」というROP gadgetになる)
### 8.
#### 8.1
eBPFはLinuxカーネル内で、プログラムを軽量Linux仮想マシンのようなサンドボックス環境で実行する機能。eBPFプログラムを実行すると、渡されたプログラムがJITコンパイラによってコンパイルされ、コンパイルされたバイトコードは検証エンジンによってカーネルを破壊することがないか検証される。これによってカーネルのソースコードの変更やカーネルモジュールの追加などをすることなく、安全性と効率性を保ったままOSの機能を拡張することができる。eBPFの前身はBPFというパケットフィルタリングの機能だが、eBPFではそれに限らずOS用途一般で使える。
#### 8.2
・tcpdump
パケットのキャプチャに用いるアプリケーションで、パケットのフィルタリングにBPFを使っている。
・seccomp
プロセスのシステムコールを制限する機能で、システムコールのフィルタリングにBPFをつかっている。seccompを使っているコードを見てみると、seccomp関数の引数としてフィルタリングするBPFコードを渡している。(おそらくフィルタするシステムコールだった場合は終了、そうでなければ続行という風に分岐するようなコードを書いていると思われる)