<span id="7ztzv"></span>
<sub id="7ztzv"></sub>

<span id="7ztzv"></span><form id="7ztzv"></form>

<span id="7ztzv"></span>

        <address id="7ztzv"></address>

            原文地址:http://drops.wooyun.org/tips/16678

            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,通過這些分析可以得出被刪除文件的一些特征:

            由于文件的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
            

            搜索出的結果如下圖

            search_result

            發現了一個奇怪的名為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

            get_flag1

            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,如果是文件就加密文件內容和頭部。

            完整的加密步驟是:

            1. filenode[0x171-0x172] = md5(password)[0:2]
            2. filenode[0x16D-0x170] |= 8
            3. key = password[0:4] + (int(md5(password)[0:2]) + parent_node_id)
            4. des_key_init(key)
            5. 如果是文件夾則加密filenode[0x00-0x68],如果是文件則加密文件塊內容和filenode[0x00-0x68]
            6. 每組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
            

            搜索結果如下圖

            search_ids

            接下來就是爆破出加密flag.file的key,由于不是標準的DES,我抽取的代碼也有問題,所以我直接patch程序,把main函數改為了下面這樣

            patched_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,在最后得到下一部分的文件名

            cat_flag.file

            查看xx_0.0_xx的type發現它是一個隱藏了的加密文件,用之前的方法爆破出key為B4d_a8bc,然后把type改為0x8,掛載到根目錄,相同的方法解密出xx_0.0_xx是一個tar包,解壓得到一個aes.cc的文件,cat一下得到flag

            get_flag2

            0x03 總結


            收獲挺多的,增強了我patch的能力,但是還有很長的路要走。。。

            <span id="7ztzv"></span>
            <sub id="7ztzv"></sub>

            <span id="7ztzv"></span><form id="7ztzv"></form>

            <span id="7ztzv"></span>

                  <address id="7ztzv"></address>

                      亚洲欧美在线