seccamp2019事前課題晒し
seccamp2019事前課題晒し
あまり解けていなかったので恥ずかしいがフィードバックがもらえるかもしれないのと、ある程度の目安になればいいなと思ったので事前課題を晒したいと思います。
以下、回答
1
その1
Markdownで記述したToday I Learning(TIL)の見返しを行うことができるコマンドラインツール。
使用言語:Rust
工夫:用途を考え、htmlの形式やコマンドライン上でのハイライト、見たいTILのみを一覧にリストにするなど、TILの閲覧時の負担の軽減に力を入れた。
その2
映画、小説、漫画、アニメなど様々な作品に登場する実在する場所が舞台となった地への観光を目的としたWebアプリケーション。
ソフトウェアと書いていたが、Webの脆弱性を勉強するには書籍ややられサイトを攻撃することも大切だが、自分でWebアプリケーションを開発することで得られるものがあるのではと考え、開発したものを載せることにした。
使用言語:Ruby
FrameWork:Rails
工夫:年々聖地巡礼を行う人が多くなっているところに目をつけ、マネタイズの面で想像しやすいようなサービスを開発しようと考えた。例えばある観光地を訪れるユーザの数が多くなった場合、その近くにある店などとクーポン券などを発行することで連携を行い、地域活性化につなげようと考えた。
2
主にCTFで配布された実行ファイルを解析しました。目的はFlagの入手です。
解析の流れとしては、まず渡されたプログラムを実行し動作の確認をします。そこからフラグの入手で使えそうな関数が使われていないか判断します(メッセージボックスを使用しているならMessageBox
関数を使用している)
動作確認が終了した後はデバッガを使用し、処理を追っていき、Flagに関わる処理を探し、条件の書き換えやFlagを入手するための紙や疑似言語で置き換えることで処理を解析しPythonでFlagを出力するような処理を書き求めています。
解析の中でupxなどのパッカーを使用しているときは圧縮された実行ファイルを処理の初めに解凍している部分があり、loopが多く見つかるということを学び、ある程度の難読化されたソフトウェアの解析にも取り組んできました。
工夫した点は渡されたファイルを実行し、使用している関数や文字列から辿っていくことで解析にかかる時間の短縮を行っているところです。
上記のことを学ぶのには主に書籍を利用しました。
以下が学習に使用した書籍になります。
Security
- セキュリティウォリア―敵を知り己を知れば百戦危うからず
- ハッカー・プログラミング大全 攻撃編
- サイバーセキュリティプログラミング ―Pythonで学ぶハッカーの思考
- サイバーセキュリティテスト完全ガイド Kali Linuxによるペネトレーションテスト
- リバースエンジニアリングバイブル コード再創造の美学
- 絶対わかる情報処理安全確保支援士 2017年春版
- 解析魔法少女 美咲ちゃん マジカル・オープン!
- 体系的に学ぶ 安全なWebアプリケーションの作り方 第2版 脆弱性が生まれる原理と対策の実践
- Hacking: 美しき策謀 第2版 ―脆弱性攻撃の理論と実際
Deep_learning
- ゼロから作るDeep Learning
3
ソフトウェアやWeb開発でのソースコード管理。簡単なWriteupを書いています。
https://github.com/various3211workd
ReversingのWriteupを主に書いています。
https://todoaaaaaa.hatenablog.com/
4
Reverse Engineering Malware - Know Your Enemies
ソフトウェアの流れをデバッガを用いて動的に追うということをCTFを通じて学んだ経験を活かしたい、又複雑なコードを読み解くための知識を習得し、マルウェアの解析についての理解を深めたいと思ったため選択したいと考えています。
書籍を読み学習したのですが、今回のSCの課題である問6の問題でも不明な点が多く、Windows環境でばかりデバッグをしてきたため、GDBを用いたLinux環境でのデバッグをすらすら行えるようになりたいと思ったためです。事例と法律
セキュリティエンジニアとして法律に関する理解は避けては通れないと考えているため選択しました。
研究目的で行う行為と犯罪との区別は個人で調べても理解できることが少なく、そのため学習が進まないこともあったため、基本的なところだけでも理解しようと思います。
又、無限アラートなどの自分では大したことがないだろうと考えているものでもよくない行為などをしないために、開発者としてもどのような行動が危険で、行ってはいけないのかの線引きをしっかりしたいと考え、身を守るすべを知りたいと思いました。Pythonによるマルウェア検出の自動化
私自身が機械学習の基礎の基礎というところを勉強した経験があるのですが、それを活かすことのできる場面に遭遇したことがなく、もったいないと感じていました。
そこでその知識を生かすため、又、機械学習の分野で使用されているPythonを使用してマルウェアの検出を自動化することは、足りないといわれているセキュリティ人材を助けるための手段としても注目されるのではと考えたため選択したいと考えています。
5
ArpSpoofingについて
おさらい
ARPはイーサネットにおいてIPアドレスからMACアドレスを取得するためのプロトコル。逆はRARP。
IPアドレス宛にパケットを送信したい場合に、IPアドレスと機器を紐づけるためにARP要求をブロードキャストで発信し、対応する機器がユニキャストで答えることでIPアドレスと機器が結び付く。
イーサネット:ここではローカルエリアネットワーク(LAN)
ARP Spoofingとは
中間者攻撃で用いられる方法の一種。同一ネットワーク上で攻撃対象のマシンとルータの間に割り込む方法。
対象のARPテーブルを書き換えるために、攻撃者がルータのIPアドレスと自身のMACアドレスを使用したパケットを対象に送信することで、対象は攻撃者のマシンをルータだと誤認する。
MACアドレスを書き換える攻撃なので同一ネットワーク上で成立する。
問題点
影響
- 通信の盗聴
- 通信の妨害
- 通信の改ざん
コーディング
簡単なパケットを作成して送信するのにすごく便利なpythonで書くことにする。
from scapy.all import * import sys target_ip = sys.argv[1] # 送信先IPアドレス target_mac = sys.argv[2] # 送信先MACアドレス source_ip = sys.argv[3] # 送信元IPアドレス # ARP要求パケット作成 frame = Ether(dst=target_mac)/ARP() frame.op = 2 # 1 ARP要求 / 2 ARPリクエスト frame.psrc = source_ip # 送信元 frame.pdst = target_ip # 送信先 # 送信 sendp(frame)
実環境で使用する場合は継続的にパケットを送信しないと正しい組み合わせに戻ってしまうので注意が必要
対策
- ARPテーブルの上書きを監視する
arpテーブルが書き換わることは攻撃されたマシンで判断できるためARPテーブルが書き換わるのを監視していればよい。
ARPテーブルの上書きを検知するツールとしてarpwatchというものがある。
ftp://ftp.ee.lbl.gov/arpwatch.tar.gz からダウンロードし、makeすることで使用できる。
- DAI機能のあるスイッチを利用する
DAI(Dynamic ARP Inspection)の略。
パケットを検査するセキュリティ機能で、無効なIPアドレスと> MACアドレスで組み合わされたARPパケットを破棄する機能がある。
パケットの判断基準は以下のようになっている
* DHCP Snoopingが有効DHCP Snoopingによって構築されたデータベースに基づいてパケットの有効性を判断する。
DHCP SnoopingはDHCPサーバがLAN上のクライアントとのやり取りをするDHCPメッセージをのぞき見し、不正なトラフィックを防止するものだ。
DHCPトランザクションが正常に終了した場合はスイッチ内のデータベースに蓄積される。このデータベースをもとにパケットの有効性を判断するようになっている。
* DHCP snoopingが無効
ARP ACLに基づいてパケットの有効性を判断する。
これはarp access-list
というコマンドを使用して許可するホストのIPアドレスとMACアドレスを登録するものだ。
上記のどちらの対策でもスイッチによって対策しているので機器を新しく購入することが必要になる。
ルータやPC側で接続するMACアドレスに書き換えできない値を設定できるようになれば一番良いと思うのだが...
参考にしたサイト
- https://ja.wikipedia.org/wiki/ARP%E3%82%B9%E3%83%97%E3%83%BC%E3%83%95%E3%82%A3%E3%83%B3%E3%82%B0
- https://scapy.readthedocs.io/en/latest/
- https://ja.wikipedia.org/wiki/DHCP%E3%82%B9%E3%83%8C%E3%83%BC%E3%83%94%E3%83%B3%E3%82%B0
6
まずx64のアセンブリの命令セットと比較するためにコード表を参考にしながら読み解いていくことにした。
メモ
オプションでIntel構文で表示している。
つまり左辺の値に右辺との演算閣下が入る形になる。
r* : レジスタ
rdx = 8b4のアドレス。引数の先頭アドレスを渡している
DWORD PTR [rsp-0xc] = 0x0
r10 = 0xedd5a792ef95fa9e
r9d = 0xffffffcc // マスクをとるためのビット列...?
ざっと処理を眺めてみると、eaxの値(戻り値)を0
にして終了している処理が見える
xor eax,eax ret
おそらくここにジャンプすればいいと予想を立ててみる。
それでは順番に処理を見てみる。
mov eax,DWORD PTR [rsp-0xc] cmp eax,0xd ja 57c <main+0x4c>
eaxにDWORD PTR [rsp-0xc]の値(今回は0)を入れている。
eaxと0xdを比較し、eaxが大きければ57cにジャンプする。
今回はジャンプしないが、ジャンプ先を見てみると
mov eax,0x1 ret
57c番地ではeaxに0x1を入れて終了していることがわかる。
上記の処理をする箇所には何度もジャンプしているので、これはeaxがカウンタの役割をしていて、文字列の長さを比べているのではないかと考える。
0xdは10進数で表すと13なのでここでは特定の文字列は13文字以下である。
movsxd rax,DWORD PTR [rdx+rax*4] add rax,rdx jmp rax
raxにrdxとrax*4の足し算のアドレスを入れ、rdxと足し算し、そのアドレスにジャンプしている。
ところが1行目の
movsxd rax,DWORD PTR [rdx+rax*4]
の[rdx+rax*4]
の部分でrax
の値を使用しているがその中身がわからずに躓く。
x86_64ではraxには関数の戻り値を入れることが規約になっていたはず。
rax*4ということは左に4シフト?した値だろうか。
そもそもraxの値がわからない
rdiには引数の先頭アドレスが入っているので引数に対して処理を施し、結果の値へジャンプしていることはわかるのだが。
この処理についてわからないので後回しにし、movsxd rax,DWORD PTR [rdx+rax*4]
の処理を行っている559番地の近くにジャンプしている処理から見てみることにする。
すると
mov eax,DWORD PTR [rsp-0xc] add ecx,0x1 cmp eax,0xd jbe 559 <main+0x29> mov eax,0x1 ret
という処理が見つかる。
ここでもeaxに0x1
を入れて終了している処理がある。
1を返却して終了するのは間違った文字列を引数に渡したときの処理なので
mov eax,0x1 ret
に飛ばないように処理を追っていこうと考える。
そのために渡されたコードから上記のような処理にジャンプする部分を省いて考える。
さらに下のあたりを見ると550番地にジャンプしている処理が多く見える。
ここで先程のjmp rax
という命令で多数に分岐し何らかの処理をし元の550番地に戻るというループの処理をしていると仮説を立ててみる。
疑似言語で置き換えると
void func() { for () { if () { } else if() { } ... else { } } }
のようになると思う。
逆から見ていってみることにする。
6ee番地から
xor eax,eax ret
という処理が見える。
戻り値であるeaxをxorで演算し終了しているので、0が戻る文字列を入力した場合の最後の処理がここだとわかる。
6ee番地へジャンプする処理を探すと、
cmp QWORD PTR [rsp-0x8],r10 je 6ee <main+0x1be>
6c5番地で6ee
へジャンプする処理がある。
ここではQWORD PTR [rsp-0x8]
とr10
の内容を比較し、同値なら終了することになっている。
r10
の内容は途中で何かしらの処理を挟んで変更されていない限り0xedd5a792ef95fa9e
のはず。
r10
に対して処理を施している箇所を検索したが、みつからないため最初にr10
へ入れた値から変更されていないことがわかった。
この処理を行っている番地をジャンプ先に指定している処理は見つからなかったため、どこにでもジャンプできる可能性のあるjmp rax
から飛んでくるはずだ。
なぞのレジスタraxにはecxの値を入れていることが多い。
そしてecxは0x1を足したり、0x7と比較するという処理が多くみられる。
ここからecxはカウンタのような役割を持っており、raxにはそのカウンタの値を入れているのではないか?????
考えているだけではわからないので実際にコマンドライン引数を取るプログラムを書いて中身を見ることにした。
sansforensics@siftworkstation -> ~/work $ file a a: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=347e4ea52c05f928ce099615da99ff430ba128c4, not stripped sansforensics@siftworkstation -> ~/work $ cat a.c #include <stdio.h> int main(int argc, char *argv[]) { printf("%s", argv[1]); return 0; }
第一引数の値をコマンドライン上に出力するだけの簡単なプログラムだ。
このプログラムの逆アセンブル結果は以下の通りになった。
0000000000400526 <main>: 400526: 55 push rbp 400527: 48 89 e5 mov rbp,rsp 40052a: 48 83 ec 10 sub rsp,0x10 40052e: 89 7d fc mov DWORD PTR [rbp-0x4],edi 400531: 48 89 75 f0 mov QWORD PTR [rbp-0x10],rsi 400535: 48 8b 45 f0 mov rax,QWORD PTR [rbp-0x10] 400539: 48 83 c0 08 add rax,0x8 40053d: 48 8b 00 mov rax,QWORD PTR [rax] 400540: 48 89 c6 mov rsi,rax 400543: bf e4 05 40 00 mov edi,0x4005e4 400548: b8 00 00 00 00 mov eax,0x0 40054d: e8 ae fe ff ff call 400400 <printf@plt> 400552: b8 00 00 00 00 mov eax,0x0 400557: c9 leave 400558: c3 ret 400559: 0f 1f 80 00 00 00 00 nop DWORD PTR [rax+0x0]
こちらではrax
というレジスタを使用する前に値を入れているので課題とは違う形だ。
と、ここで「ん、もしかしてrax
に入れているのはコマンドライン引数のrdx+4byte
ではないか」と気が付いた。
つまりこのプログラムではコマンドライン引数から4バイトずつ値を取得し、その値を用いて処理しているのだ。
試しに.rodataの始めに読み込まれると思われるecfd
で試してみる。
リトルエンディアンで表示するのでecfd -> fdec
となる。
これを当てはめると
PS>[convert]::tostring((0xfdec + 0x8b4), 16) 106a0
となり、溢れた値を切り捨てると06a0
になる。
ジャンプしたいアドレスは6ee
なので計算してみると
PS>[convert]::tostring((0x8b4 - 0x6ee), 16) 1c6
となるので特定の文字列は1c6
を含んだもの...
使用したツール
抱いた感想
movsxd rax,DWORD PTR [rdx+rax*4]
ここですごく躓いた。
答えとしてはおそらく間違っていると思うが、自分なりの答えが出せた点はよかったと思う。
7
まずは環境の構築としてMacのファイルシステムであるapfsをマウントするためにsift workstationの中に落としてくる。
https://github.com/sgan81/apfs-fuse
の通りに実行すればよい
$ sudo apt update $ sudo apt install fuse libfuse-dev bzip2 libbz2-dev $ cmake gcc-c++ git libattr1-dev $ git clone https://github.com/sgan81/apfs-fuse.git $ cd apfs-fuse $ git submodule init $ git submodule update $ mkdir build $ cd build $ cmake .. $ ccmake . $ make
するとcmakeの段階でエラーが発生する。
公式を見ると
After compilation, the binaries are located in bin. Note that the driver uses FUSE 3.0 by default (required on 32-bit systems). If you want do compile using FUSE 2.6, use ccmake . to change the option USE_FUSE3 to OFF.
と書いてある。
64bitのOSではUSE_FUSE3
をOFF
にすればよいのでccmake
で切り替える。
ついでに作成したコマンドを呼び出せるようにしておく
$ cp apfs-fuse /usr/local/bin/
これでapfs-fuse
のインストールが完了した。
さっそく叩いてみる。
$ apfs-fuse sample.raw /mnt/apfs
sample.rawをマウントすると中にあるpdfファイルが閲覧できた。
7-1
challenge.rawをマウントできるように修正する問題だ。
まずはコマンドをたたいてみる。
$ apfs-fuse challenge.raw /mnt/apfs-fuse This doesn't seem to be an apfs volume (invalid superblock). oid 402 xid 3e NOT FOUND!!! Unable to get volume!
するとエラーが表示された。
これはapfsのボリュームではないというエラーで、superblockが無効なようだ。
又、oidの402
とxidの3e
という値が見つからないとのことだ。
バイナリエディタでchallenge.rawを見て一番初めに出てくる3e
の値を書き換えると表示されるエラーのうちxidの値が書き換わるのでxidはこの番地の値を参照していることがわかる。
challenge.rawの中を見ていくとNXSBという文字列が見える。これはsample.rawでは000020
番地から始まっている文字列だ。
- oidはオブジェクトの参照に使用される8byteのid
- xidはオブジェクトのバージョン。オブジェクトが変更されると増加
上記のことがわかっており、そこでエラーが発生している。
又、次の2byteである0x0001
はオブジェクトの種類を表しており、ここで表示されている値だと、Container Superblockになる。
Container Superblockでは始めの4byteの値がmagic'NXSB'
になる。
ここでchallenge.rawのデータを初期状態に戻し、000020番地
からの値をNXSB
に書き換える。
次にblockのサイズを変更する。
000024番地
から4byteの値を4096byteになるように変更する。
するとマウントすることができるようになった。
1. 修正箇所のオフセットと修正内容。
000020番地からの4byteをNXSB
というmagicNumberに変更
000024番地からの4byteを00100000
に変更
2. そのように修正するとマウントできるようになる理由。
Block Headerのtypeを示している2byteの値に0x0001
と書いてあるのでこのdump結果ではContainer Superblockにあたる。その場合、Container Superblockの始めの4byteはmagicでNXSB
を記入することになっているがXXXX
になっていたことが原因だ。
さらにこの状態でもマウントできなかったため、次の4byteであるblock_sizeを決めている値を見てみると、0
になっていたのでsample.rawを参考にし、値が4096byteになるように修正することでblockのサイズがわかりマウントすることができた。
参考にしたサイト
https://static.ernw.de/whitepaper/ERNW_Whitepaper65_APFS-forensics_signed.pdf
7-2
マウントしたファイルの中からpdfを確認するとiir_vol41.pdfが閲覧できないことがわかった。
ファイルの内容はsample.rawに含まれているものと同じだろうと考え、二つを見比べることにした。
すると7-1でマウントしたほうのiir_vol41.pdf
にはまったくデータが含まれていないことがわかった。
つまりこの問題はpdfを修正する問題ではなく、マウント段階でおかしなことになっているchallenge.rawを修正する問題だ。
ここまで考えてから問題文を読むと当該ファイルを開けるようにchallenge.rawのデータを修正し
と書いてあったのであまり意味がなかった。
この問題を解くにあたり、まずpdfファイルは保存されているが中にデータは入っておらず、すべて0になっているところに注目した。
つまり、マウント時にiir_vol41.pdf
を作成する段階で、データのあるポイントを指していないので、その部分をデータのあるポイントを指すように書き換えればいいのではと考えた。
まずはオブジェクトを指している物理アドレスを探すことにした。
オブジェクトの物理アドレスはomap Entry
に格納されている。
- omap Entry Key Structure
pos | size | type | id |
---|---|---|---|
0 | 8 | u8le | kind & obj_id |
8 | 8 | u8le | xid |
- omap Entry Value Structure
pos | size | type | id |
---|---|---|---|
0 | 4 | u4le | paddr |
4 | 4 | u4le | size |
8 | 8 | u8le | obj_id |
物理アドレスはpaddr
の値なのでここを参照すればよい...?
1. 修正箇所のオフセットと修正内容。
2. そのように修正するとファイルを開くことができるようになる理由。
3. 回答に至る調査の過程(簡潔に)。
開くことができないpdfファイルをバイナリエディタで見てみるとまったくデータが含まれていないことがわかったのでchallenge.rawを修正することにした。