a

よろしくのキワミ

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

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)

実環境で使用する場合は継続的にパケットを送信しないと正しい組み合わせに戻ってしまうので注意が必要

対策
  1. ARPテーブルの上書きを監視する

arpテーブルが書き換わることは攻撃されたマシンで判断できるためARPテーブルが書き換わるのを監視していればよい。
ARPテーブルの上書きを検知するツールとしてarpwatchというものがある。
ftp://ftp.ee.lbl.gov/arpwatch.tar.gz からダウンロードし、makeすることで使用できる。

  1. 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アドレスに書き換えできない値を設定できるようになれば一番良いと思うのだが...

参考にしたサイト

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を含んだもの...

使用したツール
  • firefoxブラウザ (インターネットで調べもの, 課題の中での文字列検索)
  • objdump
  • gcc
  • vi
抱いた感想
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_FUSE3OFFにすればよいので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を修正することにした。

プライバシーポリシー お問い合わせ