2016 ALICTF xxFileSystem write-up
0x00 介紹
這是關於檔案系統的一個題,給了程式和資料,比賽時是分成了兩部分,第一部分是需要我們恢復刪除的檔案,第二部分是需要解密加了密的檔案。比賽完時有挺多隊把第一部分解出,但第二部分只有PPP解出,我賽後在隊友wxy前期的分析和出題人的幫助下才解了出來,這裡僅做一個分享,各位大大忽噴。
0x01 恢復檔案
第一部分的題是恢復刪除的檔案,刪除檔案的函式為0x4018FC
#!cpp
__int64 __fastcall rm_func(MemStruct *system, char *arg)
{
char dir_flag; // [sp+13h] [bp-1Dh]@1
signed int mask; // [sp+14h] [bp-1Ch]@9
char *filename; // [sp+18h] [bp-18h]@4
inodeSt *filenode; // [sp+20h] [bp-10h]@9
__int64 v7; // [sp+28h] [bp-8h]@1
v7 = *MK_FP(__FS__, 40LL);
dir_flag = 0;
if ( *(arg + 1) && !strcmp(**(arg + 1), "-r") )
dir_flag = 1;
filename = *arg;
if ( arg_filter(filename, off_6082C0) ^ 1 )
{
puts(" error");
}
else if ( !strcmp(filename, ".") || !strcmp(filename, "..") )
{
puts(" error");
}
else
{
filenode = find_filenode(system, *&system->pwd_node, filename, &mask);
if ( filenode )
{
if ( filenode->is_dir && dir_flag != 1 )
{
puts(" error (add -r please)");
}
else if ( rm_filenode(system, filenode) )
{
*(*&system->pwd_node + 0x60LL) &= ~(1 << mask);
--*(*&system->pwd_node + 0x64LL);
}
}
else
{
puts(" error");
}
}
return *MK_FP(__FS__, 40LL) ^ v7;
}
這個函式主要的操作是根據檔名找到對應的檔案節點,然後呼叫真正的刪除函式0x40168F
#!cpp
signed __int64 __fastcall rm_filenode(MemStruct *system, inodeSt *filenode)
{
signed int i; // [sp+10h] [bp-20h]@4
signed int dir_mask; // [sp+14h] [bp-1Ch]@4
signed int j; // [sp+18h] [bp-18h]@12
signed int file_mask; // [sp+1Ch] [bp-14h]@12
if ( check_password(system, filenode) ^ 1 )
return 0LL;
if ( filenode->is_dir )
{
dir_mask = *&filenode->mask >> 2;
for ( i = 2; i <= 23; ++i )
{
if ( dir_mask & 1 )
{
if ( rm_filenode(system, (system->disk + (*(&filenode->id + i) << 9))) ^ 1 )
return 0LL;
*&filenode->mask &= ~(1 << i);
--*&filenode->size;
}
dir_mask >>= 1;
}
used_table[*&filenode->id] = 0;
adjust_used_table(system, *&filenode->id);
}
else
{
file_mask = *&filenode->mask >> 2;
for ( j = 2; j <= 23; ++j )
{
if ( file_mask & 1 )
{
used_table[*(&filenode->id + j)] = 0;
*&filenode->mask &= ~(1 << j);
*&filenode->size -= 0x200LL;
used_table[*(&filenode->id + j)] = 0;
adjust_used_table(system, *(&filenode->id + j));
if ( *&filenode->size < 0 )
*&filenode->size = 0LL;
if ( !*&filenode->size )
break;
}
file_mask >>= 1;
}
if ( *&filenode->size > 0LL && rm_filenode(system, (system->disk + (*&filenode->parent_filenode_id << 9))) ^ 1 )
return 0LL;
used_table[*&filenode->id] = 0;
adjust_used_table(system, *&filenode->id);
}
return 1LL;
}
這個函式首先會判斷這個檔案節點是否為目錄,如果是目錄就迴圈刪除目錄中的檔案,如果是檔案則迴圈刪除檔案塊,透過分析可以知道檔案頭的格式
偏移 | 目錄 | 檔案 |
---|---|---|
0x0-0x3 | 當前節點的id | 同目錄 |
0x4-0x7 | 上級目錄的id | 0xFFFFFFFF |
0x8-0x5F | 包含檔案的id(每4個位元組) | 包含檔案塊的id(每4個位元組) |
0x60-0x63(mask) | 之前每4個位元組的id是否有效(0x8開始) | 同目錄 |
0x64-0x67(size) | 包含檔案的個數 | 檔案大小(每個塊512位元組) |
0x68-0x6B | 00000000 | 同目錄 |
0x6C-~ | 目錄名 | 檔名 |
0x16C | 0x01 | 0x00 |
0x16D-0x170(type) | 第0位置1表示隱藏,第3位置1表示加密 | 同目錄 |
0x171-0x172 | password的md5值的前2個位元組 | 同目錄 |
從刪除檔案可以看出,它只是把used_table中id對應的值置0,並把size和mask中的有效位清0,透過這些分析可以得出被刪除檔案的一些特徵:
- 當前節點的id在used_table中的值為0
- mask為0x03
- size為0
由於檔案的0x4-0x7為0xFFFFFFFF,再加上之前的一些特徵就可以搜尋出被刪除檔案的檔名,程式碼如下
#!python
from pwn import *
file = open('xxdisk.bin', 'rb')
data = file.read()
used_table = data[0:0x4e20]
str_data = ''.join(data[0x5024:])
index = 0
filenames = []
while True:
if index != -1:
index = str_data.find('\xff\xff\xff\xff', index + 1)
used_table_index = u32(str_data[index-4:index])
if used_table_index < 0x4e20 and used_table[used_table_index] == '\x00':
filename = ''
filename_index = index - 4 + 0x6c
while str_data[filename_index] != '\x00':
filename += str_data[filename_index]
filename_index += 1
filenames.append(filename)
else:
break
print filenames
搜尋出的結果如下圖
發現了一個奇怪的名為555的檔案,根據它的檔案塊id提取出這個檔案
#!python
file = open('xxdisk.bin', 'rb')
data = file.read()
out = open('extract', 'wb')
ids = [0x333, 0x32c, 0x32d, 0x324, 0x326, 0x325]
for file_id in ids:
out.write(data[0x5024+(file_id<<9):0x5024+(file_id<<9)+512])
out.close()
提取出來是一個tar包,解壓後得到一個名為555的檔案,cat一下在最後得到flag
0x02 解密檔案
第二部分的題是解密加了密的檔案,首先搜尋xxdisk.bin可以發現有一個flag.file的加密檔案,透過檢視type可以知道是一個加密的檔案,因此需要分析加密函式0x402B04
#!cpp
__int64 __fastcall crypt_func(MemStruct *system, const char **filename_arg)
{
unsigned int pass_len; // [email protected]
const char *filename; // [sp+10h] [bp-130h]@1
inodeSt *filenode; // [sp+18h] [bp-128h]@4
char input_password_md5; // [sp+20h] [bp-120h]@10
char password; // [sp+30h] [bp-110h]@8
__int64 v8; // [sp+138h] [bp-8h]@1
v8 = *MK_FP(__FS__, 40LL);
filename = *filename_arg;
if ( !strcmp(*filename_arg, ".") || !strcmp(filename, "..") )
{
puts(" error");
}
else
{
filenode = find_file(system, *&system->pwd_node, filename);
if ( filenode )
{
if ( *&filenode->filetype & 8 )
{
puts(" error");
}
else
{
printf("password:", filename_arg);
fflush(0LL);
readln(&password, 256, '\n');
if ( strlen(&password) > 5 )
{
pass_len = strlen(&password);
md5(&password, pass_len, &input_password_md5);
*&filenode->pass_md5 = *&input_password_md5;// 前兩個位元組
*&filenode->filetype |= 8u;
do_crypt(system, filenode, &password, *(*&system->pwd_node + 4LL));
}
else
{
puts(" error");
}
}
}
else
{
puts(" error");
}
}
return *MK_FP(__FS__, 40LL) ^ v8;
}
它會計算password的md5值,然後把前兩個位元組儲存到0x171-0x172,在把0x16D-0x170的值或8,然後進行加密操作0x4027AF
#!cpp
__int64 __fastcall do_crypt(MemStruct *system, inodeSt *filenode, char *pass_arg, unsigned __int16 parent_filenode_id_arg)
{
char *password; // [email protected]
unsigned __int16 parent_filenode_id; // [email protected]
char key; // [sp+20h] [bp-20h]@1
__int64 v8; // [sp+38h] [bp-8h]@1
password = pass_arg;
parent_filenode_id = parent_filenode_id_arg;
v8 = *MK_FP(__FS__, 40LL);
memset(&key, 0, 10uLL);
sprintf(
&key,
"%c%c%c%c%04x",
*password,
password[1],
password[2],
password[3],
*&filenode->pass_md5 + parent_filenode_id);
des_key_init(&key);
if ( filenode->is_dir )
crypt_head(filenode);
else
crypt_all(system, filenode);
return *MK_FP(__FS__, 40LL) ^ v8;
}
這個函式先是初始化key,key=password[0:4]+(int(md5(password)[0:2])+parent_node_id)
,然後如果是資料夾就僅僅加密檔案頭0x00-0x68,如果是檔案就加密檔案內容和頭部。
完整的加密步驟是:
- filenode[0x171-0x172] = md5(password)[0:2]
- filenode[0x16D-0x170] |= 8
- key = password[0:4] + (int(md5(password)[0:2]) + parent_node_id)
- des_key_init(key)
- 如果是資料夾則加密filenode[0x00-0x68],如果是檔案則加密檔案塊內容和filenode[0x00-0x68]
- 每組8位元組進行des加密
注意:這裡的DES是非標準的
因此我們要做的就是爆破password[0:4]和parent_node_id,我先嚐試解開了flag-door,然後發現裡面是三個加了密的資料夾,又解密了其中一個資料夾,發現裡面又是三個加了密的資料夾,於是猜測flag.file是位於這些加了密的資料夾裡的,所以parent_node_id就是這些加了密的資料夾的id,後來又發現有些加了密的資料夾是空的(很容易判斷。。。),可以剔除掉這些,下面進行搜尋
#!python
file = open('xxdisk.bin', 'rb')
data = file.read()
indexes = []
for i in xrange(len(data)):
if data[i] == '\x01' and data[i+1] == '\x08':
start = i - 0x16c
offset1 = start + 8
offset2 = offset1 + 0x10
offset7 = offset2 + 0x50
if data[offset1] != data[offset2] and data[offset7] == '\x00':
indexes.append((start - 0x5024) / 512)
print indexes
搜尋結果如下圖
接下來就是爆破出加密flag.file的key,由於不是標準的DES,我抽取的程式碼也有問題,所以我直接patch程式,把main函式改為了下面這樣
print_char_table是數字+字母+下劃線,indexes是上面搜尋出的id,這裡可以把第一個for迴圈分塊處理,這樣可以比較快地跑出key
#!python
./xxFileSystem_patch_1 `echo -n '\x82\xA7\x1D\xDE\x4D\xB6\x74\xB6'` `echo -n '\xd4\x02\x00\x00\xff\xff\xff\xff'`
Wh4tfab0
Wh4vfab0
Wh4Tfab0
Wh4Vfab0
這樣就知道了flag.file的parent_node_id為0x210,接下來就是解密這個檔案,有4個key,隨便取一個就行,解密時可以把flag.file掛載到根目錄,就是把根目錄的parent_node_id改為0x210,把0x2d4(flag.file的id)加到目錄的檔案id中,並把mask的有效位和size都設定正確,然後動態除錯下斷點解密,你們懂的,解密出來之後cat,在最後得到下一部分的檔名
檢視xx_0.0_xx的type發現它是一個隱藏了的加密檔案,用之前的方法爆破出key為B4d_a8bc
,然後把type改為0x8,掛載到根目錄,相同的方法解密出xx_0.0_xx是一個tar包,解壓得到一個aes.cc的檔案,cat一下得到flag
0x03 總結
收穫挺多的,增強了我patch的能力,但是還有很長的路要走。。。
相關文章
- Alictf2014 Writeup2020-08-19TF2
- CVE-2016-9838: Joomla 2016 最新漏洞!2016-12-16OOM
- [HEOI2016/TJOI2016]排序-題解2019-01-05排序
- Lesnoe Ozero 2016. BSUIR Open 2016 Finals2018-04-11UI
- P2824 [HEOI2016/TJOI2016] 排序2024-04-05排序
- NOIP20162019-05-12
- NAIPC-20162016-12-04AI
- 2016總結2016-11-23
- JOI Open 20162024-09-13
- 2016筆記——SDWebImage2018-07-27筆記Web
- 2016年終總結2016-08-31
- 2016hctf writeup2017-03-04
- Project Server 2016 部署2016-11-30ProjectServer
- 2016半年總結2017-01-22
- bzoj4554: [Tjoi2016&Heoi2016]遊戲(二分圖匹配)2017-12-09遊戲
- office2016 mac版2020-06-20Mac
- 2016年終工作總結2019-02-16
- Bloomberg Businessweek - July 4, 20162018-12-25OOM
- 2016-個人總結2016-12-31
- 2016讀書總結2016-12-30
- 2016年個人總結2017-01-11
- 2016年年終總結2017-02-04
- Pycharm 2016註冊碼2017-11-22PyCharm
- 2016年工作計劃2016-02-15
- 大量最新office2016啟用金鑰_office2016各版本啟用碼(附office2016啟用方法)2018-10-23
- bzoj4552: [Tjoi2016&Heoi2016]排序(二分+線段樹)2018-03-14排序
- New Scientist - July 2, 2016 UK2018-12-25
- SQL Server 2016 函式:CAST2018-06-03SQLServer函式AST
- 2016年前端年末撕逼2016-12-15前端
- SQL Server 2016 Alwayson新增功能2017-09-28SQLServer
- 2016年看書總結2016-12-30
- 2016知識青年報告2017-01-22
- 2016年轉行做java2017-01-14Java
- 2016騰訊校招面試分享2016-04-15面試
- 2016學習&生活規劃2016-04-05
- 工作總結 2016 3 152017-12-14
- 2016-2-圖解HTTP2016-01-18圖解HTTP
- ctsc&apio2016 總結2016-05-11API