Satoooonの物置

雑多に色々と何かをしている

Seccamp全国大会2021 Cトラック(脅威解析)応募課題 Writeup

この記事は茨城高専アドベントカレンダー2021 16日目の記事です。

adventar.org

セキュリティ・キャンプには次年度の人のために応募課題の回答を晒すような文化があるらしいですね。AdCの季節になりましたし、いい機会なのでここで晒そうと思います。

ただし、応募理由などを全部晒すのは色々と恥ずかしいので技術的な問いについてだけ晒していこうと思います。ちなみに応募理由をぼかしてかくとCTFだけでは得られない知識に興味があるからというものでした。

以下の回答は当時のもので間違った/誤解している記述も含んでいると思うのであくまで一つの回答として見てください。

また、応募課題には以下の様に明記されています。

なお、正解がある設問については、"正解していること"よりも"正解にたどり着くまでのプロセスや熱意"を重要視しています。答えにたどり着くまでの試行錯誤や自分なりの工夫等を書いて、精一杯アピールしてください。

問4

ここ数年に発表された、以下のキーワードに関連するニュースや記事や学術論文から1つ選び、それに関して調べた内容を記述してください。内容には、1.選んだ理由、2.技術的詳細、3.被害規模または影響範囲、4.対策、の4点を必ず含めてください。なお、対策は今ある技術のみに捕われず、将来的な技術や法律など、自由な発想で書いてください。

キーワード:

テーマ: Cross-site leaks

  1. 理由

    Web24というイベントでセキュリティの話を聞いていたらSecFetchという知らない単語が出てきたのでそれについて以前少し調べたが、あまりよく理解できていないと思ったから。

  2. 技術的詳細

    参考: https://xsleaks.dev/ , https://portswigger.net/daily-swig/cross-site-search-attack-applied-to-snoop-on-googles-bug-tracker

    クロスサイト、つまりあるサイト(victim.comとする)の情報を攻撃者のサイト(evil.comとする)から読み取る攻撃手法。

    例えばvictim.comがメールサービスだとして、メールを検索して表示するGETエンドポイント/search?query=があるとする。

    このとき、evil.comから直接/searchを叩くことはCORSで許可されていない限り不可能だ。しかし、iframe要素などを利用することで他の方法で間接的に呼び出すことはできる。

    この時にiframe.onloadなどを利用すれば/search応答時間を調べることができる。

    ある人が人に知られたくないサービスを利用していて、そのメールサーバーであるhoge@example.comとvictim.comでやり取りしているとしよう。ここで/search?from=hoge@example.comと検索すると、サービスを利用していない人は表示するメールが無いため応答は早いだろう。しかし、サービスを利用している人は応答時間が長くなる。つまりevil.comから/search?from=hoge@example.com応答時間を計測できれば、訪れた人がそのサービスを利用しているかどうかが判別できてしまう。

    このような応答時間などのレスポンスの違いを利用して、クロスサイトから情報を窃取する攻撃手法がCross-site leaksである。

  3. 被害規模または影響範囲

    ユーザーの機密情報によって異なる応答を返すエンドポイントを持っていて、Cross-site leaksの対策をしていない全てのサイト。被害規模は限定的な機密情報の漏洩。

  4. 対策

    実際に行われている対策:

    • CookieのSameSite属性をStrictにする

      GETリクエストでもCookieが送られなければユーザーによって応答が異なることはない

    • Fetch Metadataを利用する

      例えば先程の検索エンドポイントは本来ならiframeでは読み込まれないはずだ。つまりiframeなどでの読み込みを排除し、本来の読み込まれ方のみ制限できるようになればよい。

      読み取まれ方を制御できるようにするには読み込み方をサーバー側にブラウザが伝えればよくて、そのためのヘッダがSec-Fetch-*だ。

      例えばiframeでvictim.com/searchを読み込むときにはHTTPヘッダでSec-Fetch-Dest: iframevictim.com/searchに送られる。この値を見てユーザーの情報を読み込む前に制御すればよいわけだ。

      しかし、この機能はまだ実装していないブラウザも多い。

      https://caniuse.com/mdn-http_headers_sec-fetch-dest

    • X-Frame-Optionsを設定する

      このヘッダをvictim.comのレスポンスヘッダで設定すると、iframeなどで読み込んでいいかどうか、というのを制御できる。これによりevil.comからのページの読み込みを防ぐことができる。

    私が考えた対策:

    • iframeの内部情報を取得できないようにする

      そもそもiframeの情報が分離されていないのが悪い

      (こうしても読み取んでからの処理の重さとかからリークできそうで怖い)

    • ブラウザでCross-site Leakを検知する

      何回もiframeが呼ばれてるのは異常なので、頑張れば検知できそう。

      ただしブラックリスト的な検知なのでバイパスも見つけやすく、根本的な解決にはならなそう。

問6

配布した6_decode.binは PE形式のプログラムであり、実行すると内部に保持したエンコードされたデータをデコードして表示します。6_decode.binを解析し、挙動などわかったことをまとめて解析レポートを作成してください。また、エンコードアルゴリズムを特定して、デコードスクリプトを作成し、次のデータを復号して回答してください。

"\x8d\x93\x13\x8a\x43\xb6\x59\x4d\x41\x80\x1b\x53\x02\x86\xf2\xed\x55\x55\x78\x59\x8b\x77\x35\x17\x56"

解析が最後までできたとしても、できなかったとしても、解析の手順、解析にあたり学習したこと、解析の過程で判明したことを文字数制限の範囲で自由に記述してください。

❯ file ./6_decode.bin
./6_decode.bin: PE32+ executable (GUI) x86-64, for MS Windows

x86_64アーキテクチャで動く32bitのwindows実行ファイル。本来は信用できないバイナリはVMで解析するべきだろうが、説明を信用して実行してみる。すると、Decode!!というタイトルのThis_is_decoded_textと表示されたポップアップが出て終わった。

ここからはGhidraで解析していく。特徴的な名前の関数はない。恐らくユーザーが作った関数は名前を消されてる関数の中にあるだろうと読んで一つずつ調べていく。

すると0x140001150にある関数の中にポップアップを表示する処理を発見した。関数をデコンパイルした後に解読していくとこのようになる。

undefined8 FUN_140001150(void) {
  DWORD size;
  HRSRC hResInfo;
  HGLOBAL hResData;
  CHAR *key_ptr;
  LPCSTR buffer;
  ulonglong i;
  LPCSTR buf_ptr;
  
  hResInfo = FindResourceW((HMODULE)0x0,(LPCWSTR)0x81,L"DATA");
  size = SizeofResource((HMODULE)0x0,hResInfo);
  hResData = LoadResource((HMODULE)0x0,hResInfo);
  key_ptr = (CHAR *)LockResource(hResData);
  buffer = (LPCSTR)VirtualAlloc((LPVOID)0x0,(ulonglong)size,0x1000,4);
  i = (ulonglong)size;
  buf_ptr = buffer;
  while (i != 0) {
    i = i - 1;
    *buf_ptr = *key_ptr;
    key_ptr = key_ptr + 1;
    buf_ptr = buf_ptr + 1;
  }
  FUN_140001000((longlong)buffer,size,0x89192712,"SECCAMP2021",0xb);
  MessageBoxA((HWND)0x0,buffer,"Decode!!",0);
  return 0;
}

FindResourceWとは何だろうか。Microsoftのドキュメントを見てみるとResourceとはWindowsベースのアプリケーションの実行可能ファイルに追加することができるバイナリデータを指すようだ。FindResourceWrapWという似たような関数があったので見てみると、指定したリソースの場所を返す関数で第二引数にはリソースに割り当てられたID、第三引数にはリソースのタイプを指定する文字列が入ることがわかる。

IDとリソースを対応させるテーブルがどこかにあるのだろうが、そこまでは追いたくないのでリソースタイプのDATAを元に調べてみる。すると、GhidraのSymbol Tree > LabelsにRsrc_DATA_81_411というデータを見つけた。恐らくこれがFindResourceWで指定しているリソースだろう。

                             ********************************************************
*  Rsrc_DATA_81_411 Size of resource: 0x14 bytes      ********************************************************
                             Rsrc_DATA_81_411                                XREF[1]:     FUN_140001150:140001178 (*)   
       14000513c 9d  cb  1e       db[20]
                 a7  65  ed 
                 5f  4d  01 
          14000513c [0]            9Dh,  CBh,  1Eh,  A7h
          140005140 [4]            65h,  EDh,  5Fh,  4Dh
          140005144 [8]             1h,  D6h,  49h,  4Ah
          140005148 [12]           55h,  BDh,  D7h,  83h
          14000514c [16]           52h,   7h,  30h,  40h

(見やすいように整形してある。)

LoadResource, LockResourceも調べてみると、それぞれリソースデータへのハンドラと生の参照を返す関数であることがわかる。VisualAllocは関数名から推測するにメモリ確保をする関数だろう。つまり、Rsrc_DATA_81_411bufferに読み込みいくつかのデータと共にFUN_140001000に渡し、返ってきた結果をポップアップで表示するという処理であることがわかる。

ではFUN_140001000はどのような処理をしているのだろうか。デコンパイルして解読するとこうなる。

void FUN_140001000(char *buffer,int buf_size,longlong a,char *str,int strsize) {
  longlong xor_byte;
  longlong add_byte;
  int i;
  
  i = 0;
  add_byte = a;
  while (i < buf_size) {
    xor_byte = add_byte * 5 + 0x2365f703;
    if ((0xa0 < (byte)(buffer[i] - 1U)) && (buffer[i] != '\0')) {
      buffer[i] = buffer[i] + -1;
    }
    buffer[i] = buffer[i] ^ (byte)xor_byte;
    add_byte = (xor_byte >> 2) + -0x1ca9;
    buffer[i] = buffer[i] ^ str[i % strsize] + str[i % strsize] + (char)add_byte;
    i = i + 1;
  }
  return;
}

bufferの各文字に対して色々している。具体的にはNULLでなくbuffer[i]が0xa1より大きいときはbuffer[i]を1足し、ループごとに値が変わるxor_bytesをbuffer[i]とXORし、これまたループごとに値の変わるadd_byteとstrsize内でぐるぐる回るようなstr[i % strsize]*2を足してbuffer[i]とXORしている。

実際にC言語のプログラムに移してみる。

#include <stdio.h>
#include <stdint.h>
#include <string.h>

void FUN_140001000(char *buffer, int buf_size, long long int a, char *str, int strsize) {
  long long int xor_byte;
  long long int add_byte;
  int i;
  
  i = 0;
  add_byte = a;
  while (i < buf_size) {
    xor_byte = add_byte * 5 + 0x2365f703;
    if ((0xa0 < (uint8_t)(buffer[i] - 1U)) && (buffer[i] != '\0')) {
      buffer[i] = buffer[i] + -1;
    }
    buffer[i] = buffer[i] ^ (char)xor_byte;
    add_byte = (xor_byte >> 2) + -0x1ca9;
    buffer[i] = buffer[i] ^ str[i % strsize] + str[i % strsize] + (char)add_byte;
    i = i + 1;
  }
  return;
}

int main(void) {
    char buf[] = "\x9d\xcb\x1e\xa7\x65\xed\x5f\x4d\x01\xd6\x49\x4a\x55\xbd\xd7\x83\x52\x07\x30\x40";
    FUN_140001000(buf, strlen(buf), 0x89192712, "SECCAMP2021", 0xb);
    printf("Decoded text: %s\n", buf);
}
❯ gcc decode.c && ./a.out
Decoded text: This_is_decoded_text

期待通りに復号できた。bufの初期値を問題で提示されているバイト列にして再度試してみる。

❯ gcc decode.c && ./a.out
Decoded text: D1d_y0u_$01v3_C0s70m_X0r?

こちらも問題なく復号できた。

問7

配布ファイル内の7_sc_spreadsheets.001は、NTFSボリュームのddイメージファイルです。イメージファイル内には複数のExcel形式のファイルが存在します。以下の問題について、必要であれば解析結果から得られる見解も含めて回答してください。

問7-1

これらはあるファイルが起点となり、変更が加えられたファイルです。起点となったファイルの入手元を回答してください。

回答

回答: https://www.kazamiya.net/files/ccba209a4d0c139e1437781932409ccf/Book10.xls

まずflsファイルシステムの中身を覗く。

❯ fls -r 7_sc_spreadsheets.001
r/r 4-128-1:    $AttrDef
r/r 8-128-2:    $BadClus
r/r 8-128-1:    $BadClus:$Bad
r/r 6-128-1:    $Bitmap
r/r 7-128-1:    $Boot
d/d 11-144-4:   $Extend
+ d/d 29-144-2: $Deleted
+ r/r 25-144-2: $ObjId:$O
+ r/r 24-144-3: $Quota:$O
+ r/r 24-144-2: $Quota:$Q
+ r/r 26-144-2: $Reparse:$R
+ d/d 27-144-2: $RmMetadata
++ r/r 28-128-4:        $Repair
++ r/r 28-128-2:        $Repair:$Config
++ d/d 31-144-2:        $Txf
++ d/d 30-144-2:        $TxfLog
+++ r/r 32-128-2:       $Tops
+++ r/r 32-128-4:       $Tops:$T
+++ r/r 33-128-1:       $TxfLog.blf
+++ r/r 34-128-1:       $TxfLogContainer00000000000000000001
+++ r/r 35-128-1:       $TxfLogContainer00000000000000000002
r/r 2-128-1:    $LogFile
r/r 0-128-6:    $MFT
r/r 1-128-1:    $MFTMirr
r/r 9-128-8:    $Secure:$SDS
r/r 9-144-11:   $Secure:$SDH
r/r 9-144-14:   $Secure:$SII
r/r 10-128-1:   $UpCase
r/r 10-128-4:   $UpCase:$Info
r/r 3-128-3:    $Volume
r/r 38-128-3:   Book10.xls
r/r 38-128-6:   Book10.xls:Zone.Identifier
r/r 40-128-3:   Book10p.xls
d/d 36-144-1:   System Volume Information
+ r/r 37-128-1: WPSettings.dat
-/r * 39-128-3: Book10m.xls
-/r * 39-128-4: Book10m.xls:Zone.Identifier
V/V 256:        $OrphanFiles

*.xlsファイルと*.xls:Zone.Identifierが気になるのでそれぞれ下記のような操作を行い取り出してみる。

❯ icat 7_sc_spreadsheets.001 38-128-6 > "Book10.xls:Zone.Identifier"

起点となったファイルを知りたいのでまずタイムスタンプを見てみる。

❯ fls -l 7_sc_spreadsheets.001 | grep xls
r/r 38-128-3:   Book10.xls      2021-03-27 01:02:55 (JST)       2021-03-27 01:04:17 (JST)       2021-03-27 01:02:55 (JST)       2021-03-27 01:02:54 (JST)       22528   0       0
r/r 38-128-6:   Book10.xls:Zone.Identifier      2021-03-27 01:02:55 (JST)       2021-03-27 01:04:17 (JST)       2021-03-27 01:02:55 (JST)       2021-03-27 01:02:54 (JST)       188     0       0
r/r 40-128-3:   Book10p.xls     2021-03-27 01:02:55 (JST)       2021-03-27 01:04:17 (JST)       2021-03-27 01:02:55 (JST)       2021-03-27 01:02:54 (JST)       24064   0       0
-/r * 39-128-3: Book10m.xls     2021-03-27 01:06:17 (JST)       2021-03-27 01:07:36 (JST)       2021-03-27 01:07:36 (JST)       2021-03-27 01:04:17 (JST)       24576   0       0
-/r * 39-128-4: Book10m.xls:Zone.Identifier     2021-03-27 01:06:17 (JST)       2021-03-27 01:07:36 (JST)       2021-03-27 01:07:36 (JST)       2021-03-27 01:04:17 (JST)       188     0       0

(flsの説明より1番目が変更日時、4番目の時刻がファイル作成日時)

並べ替えると、

  • Book10.xls (03-27 01:02:54作成, 03-27 01:02:55最終変更)
  • Book10p.xls (03-27 01:02:54作成, 03-27 01:02:55最終変更)
  • Book10m.xls (03-27 01:04:17作成, 03-27 01:06:17最終変更)

の順番である。名前からもBook10.xlsが起点のファイルであるとわかる。

*.xls:Zone.Identifierは何だろうか?とりあえず中身を見てみる。

❯ cat "Book10.xls:Zone.Identifier"
[ZoneTransfer]
ZoneId=3
ReferrerUrl=https://www.kazamiya.net/files/ccba209a4d0c139e1437781932409ccf/
HostUrl=https://www.kazamiya.net/files/ccba209a4d0c139e1437781932409ccf/Book10.xls

URLが出てきた。Zone Identifierを検索に掛けてみると、Windows 10で、ダウンロードしたファイルのブロック設定を解除するが見つかる。

ゾーン識別子とは、実際にはNTFS代替データストリーム(ストリーム名はZone.Identifier)に格納された、ファイルのダウンロード元(インターネットゾーンか、それとも制限付きゾーンか)を示すテキスト情報である。

とのことなので、ダウンロードしてファイルを入手したのだろう。

つまりファイルの入手元はhttps://www.kazamiya.net/files/ccba209a4d0c139e1437781932409ccf/Book10.xlsであることがわかる。

念のため確認したが、取り出したBook10.xlsと一致した。

❯ wget https://www.kazamiya.net/files/ccba209a4d0c139e1437781932409ccf/Book10.xls
...

Book10.xls.1                  100%[=================================================>]  22.00K  --.-KB/s    in 0.006s

2021-05-13 16:40:45 (3.48 MB/s) - ‘Book10.xls.1’ saved [22528/22528]
❯ diff Book10.xls Book10.xls.1
❯ # 一致していると表示が出ない

問7-2

復元可能なファイルを含めて、全てのExcelファイルのSHA-256ハッシュ値を回答してください。

回答

回答:

  • 15702c89d64e3f7da51d2bd7376b05bf5ecc8e477a2d14221fde6da67c714140 (Book10.xls)
  • 921e84d44ac6ac57f49c9a25b9e6ad60acf6698643e7167a29970b290f809427 (Book10p.xls)
  • 12094ea2678d9750e482cd9b3bd2fa924647c4101f7b280a3f9ac0e9a38d7b47 (Book10m.xls)
  • 020b9e009135573ef32433135457292f5c9c706cca22b3785ac73a20707af473 (unknown.xls)

Book10.xls Book10p.xls Book10m.xlsについては問7-1で抽出したものである。

他にも.xlsが隠れていないかツールを使って調べた。binwalkxlsファイルに対応していないのか成果が無かったが、foremostを使うと新たに1個のxlsファイルを発見できた。

❯ foremost 7_sc_spreadsheets.001
Processing: 7_sc_spreadsheets.001
|**|
❯ ls output/ole/
00011328.ole  00011376.ole  00011424.ole  00011480.ole

(.oleという拡張子だが.xlsファイルとして解釈できる)

fileコマンドで前に出てきたファイルと比較すると、00011424.oleが新しく出てきたファイルであることがわかる。

foremostだとファイルの終端に余分なデータがついてきてしまうようなので、ファイルを適切に整えなければいけない。

radare2で既存の.xlsファイルの終端を互いに比較して調べてみる。

❯ r2 Book10.xls
 -- Don't look at the code. Don't look.
[0x00000000]> iI
baddr    0x0
binsz    22528
...
[0x00000000]> px 0x50 @ 22528-0x50
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F ...
0x000057b0  6900 6f00 6e00 0000 0000 0000 0000 0000 ...
0x000057c0  3800 0201 ffff ffff ffff ffff ffff ffff ...
0x000057d0  0000 0000 0000 0000 0000 0000 0000 0000 ...
0x000057e0  0000 0000 0000 0000 0000 0000 0000 0000 ...
0x000057f0  0000 0000 2100 0000 0010 0000 0000 0000 ...
...
❯ r2 Book10m.xls
 -- r2 is a great OS, but a terrible hex editor
[0x00000000]> iI
baddr    0x0
binsz    24576
...
[0x00000000]> px 0x50 @ 24576-0x50
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F 
0x00005fb0  6900 6f00 6e00 0000 0000 0000 0000 0000 
0x00005fc0  3800 0201 ffff ffff ffff ffff ffff ffff 
0x00005fd0  0000 0000 0000 0000 0000 0000 0000 0000 
0x00005fe0  0000 0000 0000 0000 0000 0000 0000 0000 
0x00005ff0  0000 0000 2500 0000 0010 0000 0000 0000 

終端から0xbバイト前までは各ファイルで一致するようなので、i\x00o\x00n\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x008\x00\x02\x01\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00unknown.txtを検索してみる。

❯ r2 ./output/ole/00011424.ole
 -- Polish reversers blame git
[0x00000000]> / i\x00o\x00n\x00\x00\x00\x00...
Searching 68 bytes in [0x0-0x6800]
hits: 1
0x00005fb0 hit0_0 .Informati\u0000o\u0000n\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u00008\u0000\u0002\u0001\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u00ff\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000\u0000%INDX.
[0x00000000]> px @ 0x00005fb0
- offset -   0 1  2 3  4 5  6 7  8 9  A B  C D  E F 
0x00005fb0  6900 6f00 6e00 0000 0000 0000 0000 0000 
0x00005fc0  3800 0201 ffff ffff ffff ffff ffff ffff 
0x00005fd0  0000 0000 0000 0000 0000 0000 0000 0000 
0x00005fe0  0000 0000 0000 0000 0000 0000 0000 0000 
0x00005ff0  0000 0000 2500 0000 0010 0000 0000 0000 
0x00006000  494e 4458 2800 0900 6c47 1000 0000 0000 
0x00006010  0000 0000 0000 0000 2800 0000 c801 0000 
0x00006020  e80f 0000 0000 0000 0200 0000 0000 0000 

ファイルの終端は0x6000であると推測できたので、ddコマンドで切り出しせば正しいファイルが得られる。後は各ファイルのハッシュ値を取ればよい。

❯ dd if=./output/ole/00011424.ole of=./output/ole/unknown.xls bs=16 count=1536
1536+0 records in
1536+0 records out
24576 bytes (25 kB, 24 KiB) copied, 2.47799 s, 9.9 kB/s
❯ openssl sha256 ./output/ole/unknown.xls Book10.xls Book10m.xls Book10p.xls
SHA256(./output/ole/unknown.xls)= 020b9e009135573ef32433135457292f5c9c706cca22b3785ac73a20707af473
SHA256(Book10.xls)= 15702c89d64e3f7da51d2bd7376b05bf5ecc8e477a2d14221fde6da67c714140
SHA256(Book10m.xls)= 12094ea2678d9750e482cd9b3bd2fa924647c4101f7b280a3f9ac0e9a38d7b47
SHA256(Book10p.xls)= 921e84d44ac6ac57f49c9a25b9e6ad60acf6698643e7167a29970b290f809427

問7-3

これらのファイルはどのような順番で、どのような変更が加えられたかを、できるだけ具体的に示してください。

回答

回答:

  • Book10.xlsを作成。Sheet1This is sample file.を記述。

  • unknown.xlsでシートのMacro1のセルA1~3に下記のXLM/VelvetSweatshopをダイアログで表示するマクロを追加。

    IF(GET.WORKSPACE(19),,CLOSE(TRUE))
    IF(ALERT("XLM/V"&"elvetS"&"weats"&"hop",1),,CLOSE(TRUE))
    HALT()
    
  • Book10m.xlsMacro1を非表示に。

  • Book10p.xlsでパスワードをVelvetSweatshopとしてMicrosoft Enhanced Cryptographic Provider v1.0で暗号化した。

どのような変更が加えられたかというのはdiffを見るのが早いが、.xlsは読めるような形式にはなっていないためどうにかして読める形に変換する必要がある。

今回は自分のExcel.xlsを開き、.xlsmとして保存するようにした。(有害なマクロが潜んでいる可能性があり、本来はVMのような隔離された環境で開くのが正しいが、今回は流石に配布物は有害ではないだろうと信じて開く。)

xlsmはzip形式で格納されているのでunzipで解凍する。また、diffが見やすくなるようにIntellij Ideaでファイルを整形しておく。

Book10.xlsが起点であることは推測できているので、各ファイルそれぞれdiffコマンドでどれくらい似ているかを比較した。すると、unknown.xlsが派生したファイルであることがわかる。

❯ diff --color -ru book10 unknown
...
Only in unknown/xl: macrosheets
...
--- book10/xl/workbook.xml   2021-05-13 14:31:47.555037100 +0900
+++ unknown/xl/workbook.xml  2021-05-13 14:41:05.571779800 +0900
@@ -14,14 +14,18 @@
...
     <sheets>
-        <sheet name="Sheet1" sheetId="1" r:id="rId1"/>
+        <sheet name="Macro1" sheetId="2" r:id="rId1"/>
+        <sheet name="Sheet1" sheetId="1" r:id="rId2"/>
     </sheets>
+    <definedNames>
+        <definedName name="_xlnm.Auto_Open">Macro1!$A$1</definedName>
+    </definedNames>
     <calcPr calcId="144525"/>
     <extLst>
         <ext uri="{B58B0392-4F1F-4190-BB64-5DF3571DCE5F}"

フォルダを見ると、x1/macrosheets/sheet1.xmlが追加されている。diffからも推測するにMacro1というシートが新しく作られたのであろう。

❯ cat unknown/xl/macrosheets/sheet1.xml
...
    <sheetData>
        <row r="1" spans="1:1" x14ac:dyDescent="0.2">
            <c r="A1">
                <f>IF(GET.WORKSPACE(19),,CLOSE(TRUE))</f>
                <v>0</v>
            </c>
        </row>
        <row r="2" spans="1:1" x14ac:dyDescent="0.2">
            <c r="A2" t="b">
                <f>IF(ALERT("XLM/V"&amp;"elvetS"&amp;"weats"&amp;"hop",1),,CLOSE(TRUE))</f>
                <v>0</v>
            </c>
        </row>
        <row r="3" spans="1:1" x14ac:dyDescent="0.2">
            <c r="A3" t="b">
                <f>HALT()</f>
                <v>0</v>
            </c>
        </row>
    </sheetData>
...

このようなマクロが追加されている。

  • IF(GET.WORKSPACE(19),,CLOSE(TRUE))

    https://malware.news/t/excel-4-macros-get-workspace-reference/38892 を参照すると、マウスが無効のときにExcelを閉じる処理と推測できる。

    CLOSE関数については公式のリファレンスは見つからなかったが、他に出てきたExcel 4.0 Macro Functions Referenceで調べたところExcelを閉じる関数であるとわかる。

  • IF(ALERT("XLM/V"&"elvetS"&"weats"&"hop",1),,CLOSE(TRUE))

    &は文字列結合演算子で、ALERT(msg, 1)OK/キャンセルのダイアログを出してOKが押されたらTRUE、でなければFALSEを返す関数。

    つまりXLM/VelvetSweatshopを出し、キャンセルを押したらExcelを閉じる処理であるとわかる。

  • HALT()

    先程のリファレンスを読むと、マクロを停止する関数であることがわかる。

実際に挙動を確認するためExcelunknown.xlsを開いてみると、推測した通りダイアログが表示された。

次にunknownから派生したファイルを調べると、Book10m.xlsであることがわかる。

❯ diff --color -ru unknown book10m
...
--- unknown/xl/workbook.xml  2021-05-13 14:41:05.571779800 +0900
+++ book10m/xl/workbook.xml  2021-05-13 14:33:36.126945800 +0900
@@ -14,13 +14,14 @@
...
     <sheets>
-        <sheet name="Macro1" sheetId="2" r:id="rId1"/>
+        <sheet name="Macro1" sheetId="2" state="hidden" r:id="rId1"/>
         <sheet name="Sheet1" sheetId="1" r:id="rId2"/>
     </sheets>
     <definedNames>

state="hidden"というのが怪しい。調べるとシートを非表示/再表示する方法があるようなので開いて再表示できるか確かめると、予想した通りMacro1が非表示にされていた。

残りはBook10p.xlsだが、diffを見ても特筆すべきものは見当たらない。

試しにcatで中身を見てみると、気になる文字列を見つけることができる。

h��Microsoft Enhanced Cryptographic Provider v1.0�L�^�m

どうやら暗号化されているようだ。しかし何故暗号化されているのに開けるのか?という疑問がある。

ここで先程から気になっているポップアップの文字列XLM/VelvetSweatshopを調べてみる。すると、Excelの奇妙なパスワードとマクロウイルスという記事が見つかる。

ソースコードを読むと、どうやらVelvetSweatshopという特別なパスワードがあるようです。暗号化されたファイルを処理する場合、まずこのパスワードで復号してみて駄目だったら通常のパスワードを求める処理に移っていました。そこで私のツールでこのパスワードをつけて先程のファイルに対して復号処理をしたらちゃんと復号できたのです。

つまりBook10p.xlsVelvetSweatshopをパスワードにして暗号化しているのだろうか。記事の著者のツールを使おうとしてみたが、.xlsには対応していなかった。

自分で暗号方式の仕様を探すのはとても面倒くさいので、John the ripperを使ってみる。

❯ ~/package/john-1.9.0-Jumbo-1/run/office2john.py Book10p.xls > Book10p.hash
❯ echo "VelvetSweatshop" > password
❯ ~/package/john-1.9.0-Jumbo-1/run/john --wordlist=./password Book10p.hash
...
❯ ~/package/john-1.9.0-Jumbo-1/run/john --show Book10p.hash
Book10p.xls:VelvetSweatshop

1 password hash cracked, 0 left

パスワードがVelvetSweatshopであることが確認できた。

問8

ファジングは大量の入力を生成してプログラムを実行、結果を観測しプログラムがクラッシュしたり正常終了しなくなるような入力を発見する手法です。

しかしプログラムが異常な動作をする度に全ての入力を保存しているとキリがありません。例えば「開き括弧"("に対応する閉じ括弧")"が無い場合にクラッシュする」ようなバグの場合、 ファザーは"(", "((", "(()".... 等の膨大な数の入力をクラッシュ入力として保存してしまいます。発見された入力が数万個もあるのに蓋を開ければ全て同じ単一のバグを引いていたようでは解析も徒労に終わってしまいます。

そこでAmerican Fuzzy Lop (AFL)というファザーは独自のアルゴリズムを用いて、同一のバグに対するクラッシュ入力は1つしか保存しないようにしています。

https://github.com/google/AFL/blob/fab1ca5ed7e3552833a18fc2116d33a9241699bc/afl-fuzz.c#L3302

AFLのソースコードを読解し、当該アルゴリズムの概要を「afl-gcc」「エッジカバレッジ」「ビットマップ」という用語を用いて説明してください。

当初はafl-fuzz.cのみから理解しようとしたが、とても無理なように思えたのでAmerican Fuzzy Lop (AFL)の構造docs/technical_details.txt 、安全客のsakuraのAFL源码全注释(二)などを参考にした。

クラッシュの重複を特定するには単純にクラッシュした場所が同じかどうかを確かめれば良さそうに見えるが、例えば異なる二つのバグによりクラッシュしたときにクラッシュした場所がライブラリ関数の中の同じ場所であったりすると本当はクラッシュした原因は異なるのに重複扱いされてしまう。

では何をクラッシュの重複判定のパラメータにすればいいのか。一つの方法として経路情報を用いて判定する方法がある。プログラムが始まってからどのような経路を辿ったかを見て、同じ経路を辿ってクラッシュしたなら同じバグによるクラッシュだろうと推測できる。このような方法をエッジカバレッジという。

しかし全て計測していくのはコストがかかるので、AFLでは全ての経路情報ではなく「どの場所からどの場所へ移動したか」という情報のみを保存する。この情報をAFLはタプルと呼んでいる。

例えばA->B->C->Aという経路を移動したとき、AFLは(A, B), (B, C), (C, A)というタプルを保存する。これは移動の順番は保存していないため、B->C->A->Bと経路を移動しても同じ情報が保存される。

ではプログラム内の経路の移動をどのように検知すればいいだろうか。

AFLではまずafl-gccというgccをラップしたプログラムでファジングしたいプログラムをコンパイルする。この時にjから始まり2文字目がmでない、つまりjmp以外のジャンプ系命令の直後に__afl_maybe_logという関数を呼び出すコードを挿入する。この関数は、現在通った経路に現在位置を追加し、経路の情報を共有メモリ上に保存する処理をする。

経路の情報は以下のようなビットマップで表され、保存される。

cur_location = <COMPILE_TIME_RANDOM>;
shared_mem[cur_location ^ prev_location]++;
prev_location = cur_location >> 1;

cur_locationはコンパイル時に経路に割り当てられたランダムな整数だ。それと前に通った経路の整数であるprev_locationをXORした値が、タプルを表す値となる。それをshared_memで何回同じ経路移動をしたか、つまり各タプルが何個あるかをカウントしていく。

shared_memは64KiBの空間であるためタプルを表す値が衝突する可能性もあるが、ランダムな値をXORしているため衝突は散発的に起こるので一部のクラッシュ重複を絶対に誤検知してしまうといった致命的な問題にはならない。また20000の分岐があるプログラムでも衝突する確率は14%ほどという計測結果が出ている。

さて、では本題のクラッシュの重複を検知するアルゴリズムはこのようなものだ。

  • クラッシュしたときに以前のどのクラッシュにも見られないタプルが含まれている (=新規の経路移動をしてクラッシュした)
  • 以前のクラッシュに常に存在していたタプルが現在の状態に含まれていない (=以前常にしていた経路移動をせずにクラッシュした)

この2つのいずれかを満たしたときに新しいクラッシュであると判断し、どちらも満たさない場合は過去にあったクラッシュと同一と判断される。

ではどのように実装されているかを見ていく。

まずファジングをする前に共有メモリをtrace_bitsとして定義しておく。また、virgin_crashというまだクラッシュの時に観測していないタプルを管理する配列を定義して0xffで初期化しておく。

その後ファジングを行いクラッシュした際にhas_new_bits関数でvirgin_crashを更新している。更新の式はvirgin_crash[i] &= ~trace_bits[i];で、virgin_crash[i]が0xffであれば未観測、そうでなければ観測済みと表現している。

この関数は更新と同時に状態を返す。trace_bits[i] && virgin_crash[i] == 0xffを満たす、つまり新たなタプルが現れたときには2, 新たなタプルは現れないけど既存のタプルの数が更新されるときには1, 既存のタプルも更新されない、つまり以前と同一の経路移動をしていた場合は0を返す。この返り値が0のときにこのクラッシュは以前と重複している、と判定している。

この既存のタプルの数が更新される判定はtrace_bits[i] & virgin_crash[i]でしているのだが、ここで疑問がある。

例えば最初にあるタプルを7回観測したとするとそのタプルに対するvirgin_crashは0b11111000=248になる。その後に同じタプルを1回観測したとすると、タプルの数が変動しているにもかかわらず先程の式が満たされない。増加だけを対象としているのか、それとも高速化のために厳密にしていないのか。それは調べてもわからなかった。

終わりに

文は冗長気味ですが、普段からWriteupを書いているとこういうのを書くときに役に立ちますね。

seccampに関する諸々は明日のseccamp参加記の方で書こうと思います。