# (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)
かわいい