# (forensics, reversing) safe_rm ###### tags: `InterKosenCTF2019` `forensics` `reversing` `disk.img`というファイルが渡される。なるほどforensicsを感じる。 ## forensicsパート まずは`file`コマンドで確認してみる。 ``` $ file disk.img disk.img: DOS/MBR boot sector; partition 1 : ID=0x83, start-CHS (0x0,0,2), end-CHS (0x1,70,5), startsector 1, 20479 sectors ``` ディスクダンプっぽい。The Sleuth Kit をつかって中身を覗いてく。基本的に使えそうなツールを総当りすれば良くて、私は`fls`, `icat`, `fsstat`, `mmstat`, `mmls`あたりを雑に試すことが多い。 `mmls`の結果によると、次のようにパーティションが一つだけあるディスクボリュームっぽい(実はこれは`file`の結果にもそう書いてある)。 ``` $ mmls disk.img DOS Partition Table Offset Sector: 0 Units are in 512-byte sectors Slot Start End Length Description 000: Meta 0000000000 0000000000 0000000001 Primary Table (#0) 001: ------- 0000000000 0000000000 0000000001 Unallocated 002: 000:000 0000000001 0000020479 0000020479 Linux (0x83) ``` ので、この部分を取り出す。`python`でやってもいいが、`dd`を使うのが楽。 ``` $ dd if=disk.img of=hoge bs=512 skip=1 ``` このファイルシステムのダンプに対しても`file`コマンドを仕掛ける。 ``` $ file hoge hoge: DOS/MBR boot sector, code offset 0x52+2, OEM-ID "NTFS ", sectors/cluster 8, Media descriptor 0xf8, sectors/track 0, dos < 4.0 BootSector (0x80), FAT (1Y bit by descriptor); NTFS, sectors 20478, $MFT start cluster 4, $MFTMirror start cluster 1279, bytes/RecordSegment 2^(-1*246), clusters/index block 1, serial number 052cd791725efd80d ``` どうやらNTFSらしい。`fls`でどんなファイルが存在しているのかも見る。 ``` $ fls hoge 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-2: $Extend r/r 2-128-1: $LogFile r/r 0-128-1: $MFT r/r 1-128-1: $MFTMirr r/r 9-128-2: $Secure:$SDS r/r 9-144-3: $Secure:$SDH r/r 9-144-4: $Secure:$SII r/r 10-128-1: $UpCase r/r 10-128-2: $UpCase:$Info r/r 3-128-3: $Volume r/r 65-128-2: saferm -/r * 64-128-2: document.zip V/V 66: $OrphanFiles ``` NTFSのシステムファイル以外には、`saferm`というファイルと、削除されている`document.zip`しかない。`icat`で抽出する。 ``` $ icat hoge 65-128-2 >saferm $ file saferm saferm: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=56290bd1c9e89a65dad224953667d84270822d9a, not stripped $ icat hoge 64-128-2 >document.zip $ file document.zip document.zip: data ``` document.zipは名前に反してzip形式ではないらしい。問題名などからみてsafermを用いて削除された結果だろう。NTFSということだしバックアップなどがどこかに残っていないかと思ったがそれを探るのはちょっと面倒そうなので一旦safermの解析をやる。 ## reversingパート IDAで見てもghidraで見てもいいけど、とにかく適当なデコンパイラやディスアセンブラでsafermを解析していく。素直で小さいバイナリだしstripもされていないのですぐに読める。大体こんな感じ(`fread`と`fwrite`の引数の順番は自身がないけど、どっちでも問題ないところなので気にしないことにする)。 ```clike= #include<stdio.h> #include<unistd.h> long keygen() { long buf; FILE *fp = fopen("/dev/urandom", "rb"); fread(&buf, 8, 1, fp); fclose(fp); return buf; } void saferm(char *filename) { FILE *fp = fopen(filename, "rb+"); char *key = (void*)keygen(); char buf[8]; while (fread(buf, 1, 8, fp) == 8) { fseek(fp, -8, SEEK_CUR); for (int i = 0; i < 8; i++) { buf[i] = buf[i] ^ key[i]; } fwrite(buf, 8, 1, fp); } fclose(fp); unlink(filename); } ``` `/dev/urandom`から読み込んだ8バイトを鍵としてXORをとっている。これでファイルの中身を復元できないようにした後、`unlink`で削除しているということらしい。 ## 復元 documents.zipというファイル名を信じるなら削除されたファイルはzipなので、フォーマットで定められている値が8バイトわかれば鍵を復元できる。 zipの先頭が`504B0304`であることは明らかなので、とりあえず4バイトはわかる。残りの4バイトはどうやって探してもいいけど、今回は`zip`コマンドで適当なzipファイルを作り、そのファイルの値とdocuments.zipの値が一緒であることを祈った。 これを用いてファイルを復元するコードはこんな感じになる。 ```clike= #include <stdio.h> void recover(char *filename) { FILE *fp = fopen(filename, "rb+"); FILE *fp2 = fopen("recovered.zip", "wb+"); char key[] = {0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00}; char buf[8]; fread(buf, 1, 8, fp); for (int i = 0; i < 8; i++) { key[i] = buf[i] ^ key[i]; } fseek(fp, 0, SEEK_SET); while (fread(buf, 1, 8, fp) == 8) { for (int i = 0; i < 8; i++) { buf[i] = buf[i] ^ key[i]; } fwrite(buf, 1, 8, fp2); } fclose(fp); fclose(fp2); } int main(int argc, char *argv[]) { recover(argv[1]); } ``` 復元してみる。 ``` $ make recover cc recover.c -o recover $ ./recover document.zip $ file recovered.zip recovered.zip: Zip archive data, at least v2.0 to extract $ unzip recovered.zip Archive: recovered.zip End-of-central-directory signature not found. Either this file is not a zipfile, or it constitutes one disk of a multi-part archive. In the latter case the central directory and zipfile comment will be found on the last disk(s) of this archive. unzip: cannot find zipfile directory in one of recovered.zip or recovered.zip.zip, and cannot find recovered.zip.ZIP, period. ``` どうやらzipではあるが、末尾がおかしいらしい。原因はdocument.zipが8の倍数の大きさを持っていないために、末尾数バイトが復元されなかったことのようだ。 ``` $ ls -l *zip -rw-rw-r-- 1 user user 15572 7月 11 21:20 document.zip -rw-rw-r-- 1 user user 15568 7月 11 21:29 recovered.zip ``` この数バイトは当然XORによる暗号化もされていないのでそのままdocument.zipの値を引っ張ってくればよいはず。これを反映してソースコードを修正する。 ```clike= #include <stdio.h> void recover(char *filename) { FILE *fp = fopen(filename, "rb+"); FILE *fp2 = fopen("recovered.zip", "wb+"); char key[] = {0x50, 0x4B, 0x03, 0x04, 0x14, 0x00, 0x00, 0x00, 0x00}; char buf[8]; fread(buf, 1, 8, fp); for (int i = 0; i < 8; i++) { key[i] = buf[i] ^ key[i]; } fseek(fp, 0, SEEK_SET); while (fread(buf, 1, 8, fp) == 8) { for (int i = 0; i < 8; i++) { buf[i] = buf[i] ^ key[i]; } fwrite(buf, 1, 8, fp2); } fread(buf, 1, 4, fp); fwrite(buf, 1, 4, fp2); fclose(fp); fclose(fp2); } int main(int argc, char *argv[]) { recover(argv[1]); } ``` 今度はunzipに成功した。中身は`document.pdf`なのでこれを開いてみる。 ![](https://i.imgur.com/pRLf61t.png) かわいい