<pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
    <pre id="vvttv"></pre>

      <p id="vvttv"></p>

          <p id="vvttv"></p>

                <p id="vvttv"></p>

                <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                  <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                    <p id="vvttv"></p>

                    原文地址:http://drops.wooyun.org/papers/8814

                    http://int0xcc.svbtle.com/a-guide-to-malware-binary-reconstruction

                    在分析惡意軟件或對惡意軟件進行脫殼的時候,我們經常會遇到重建PE文件的需求。現在大多數自動化的PE重建工具雖然很棒,但并不能針對每一種情況,有時候需要我們自己手動重建PE文件。在這篇博客中,我們將介紹一些重建PE文件的方法。

                    0x00 重建“stolen API code”的IAT表


                    “stolen API code”技術常被惡意軟件用于阻撓逆向人員脫殼之后重建IAT表,從而達到反脫殼的效果。具體修復“stolen API code”后IAT表的方法在后面會介紹到,我們先了解一下IAT在PE(Portable Executable)文件里面的具體實現。

                    0x01 IAT基礎知識


                    IAT(Import Address Table)是PE文件里面的一種結構,它包含了Windows loader加載動態鏈接庫和導入API函數地址的信息。查看PE文件的時候你應該注意到IMAGE_OPTIONAL_HEADER結構里面的兩個指針:一個指向IMAGE_IMPORT_DESCRIPTOR,另一個指向導入函數地址的數組。

                    函數可以通過函數名稱或序號(API號)導入。

                    FirstThunk成員指向導入的API函數數組(也稱為導入地址表)。

                    上圖顯示了一個kernel32.dll的導入函數GetProcAddress()的例子。

                    加殼程序通常會破壞IAT表的原始形式,由殼代碼自己解決函數導入而不是依靠Windows loader。因此脫殼后需要重建程序的IAT表,下面我們將使用Scylla v0.9.6b這個工具來重建IAT。

                    0x02 Stolen API code


                    一些加殼程序會使用“stolen code”技術防止逆向人員重建IAT表。“stolen code”重新把跳轉到API函數的指令在某個內存區域被重新仿真。所以使用掃描器掃描這些導入函數的時候得到的是一些無效的API指針。

                    使用“stolen API code”技術的情況下,Scylla是無法自動重建IAT表的。我們需要寫一個Scylla插件方便獲得正確的偏移然后重建IAT表。

                    0x03 編寫一個Scylla插件


                    Scylla插件的基本上是以DLL文件形式注入到目標進程。為此它提供構建插件所需的API接口,還有讓Scylla插件方便嵌入到目標程序的接口。此外Scylla還提供了一個命名內存映射文件用于Scylla插件在目標程序中可以獲取一些信息。

                    Scylla提供命名的文件映射用于目標DLL指向特定的內存區域。

                    這個內存映射文件名為“ScyllaPluginExchange”。

                    通過ScyllaPluginExchange可以獲取到以下的信息:

                    UNRESOLVED_IMPORT結構體通過SCYLLA_EXCHANGE.offsetUnresolvedImportsArray成員取得。

                    編寫插件的第一步是利用Scylla提供的命名內存映射文件拿到SCYLLA_EXCHANGE結構體的基地址:

                    #!c++
                    BOOL getMappedView()
                    {
                        hMapFile = OpenFileMappingA(FILE_MAP_ALL_ACCESS, 0, FILE_MAPPING_NAME); //open named file mapping object    
                    
                        if (hMapFile == 0)
                        {
                            writeToLogFile("OpenFileMappingA failed\r\n");
                            return FALSE;
                        }    
                    
                        // lpViewOfFile就是SCYLLA_EXCHANGE結構體的基地址
                        lpViewOfFile = MapViewOfFile(hMapFile, FILE_MAP_ALL_ACCESS, 0, 0, 0); //map the view with full access
                        if (lpViewOfFile == 0)
                        {
                            CloseHandle(hMapFile); //close mapping handle
                            hMapFile = 0;
                            writeToLogFile("MapViewOfFile failed\r\n");
                            return FALSE;
                        }
                        return TRUE;
                    }
                    

                    UNRESOLVED_IMPORT包含了一個未解決的導入函數列表。

                    #!c++
                    typedef struct _UNRESOLVED_IMPORT {       // Scylla Plugin exchange format
                        DWORD_PTR ImportTableAddressPointer;  //in VA, address in IAT which points to an invalid api address
                        DWORD_PTR InvalidApiAddress;          //in VA, invalid api address that needs to be resolved
                    } UNRESOLVED_IMPORT, *PUNRESOLVED_IMPORT;
                    

                    ImportTableAddressPointer指針指向有效的API地址。InvalidApiAddress指針指向未決斷的API函數地址,在本文例子中,這是一塊動態分配的內存區域,那些被偷的代碼(stolen code)就是在這里進行仿真。

                    可以看到我們需要計算每個ImportTableAddressPointer到jmp指令有多少個字節,然后取出JMP指令所跳轉的目標地址減去這個字節數得到原來的API基地址:

                    #!c++
                        while (unresolvedImport->ImportTableAddressPointer != 0) //last element is a nulled struct
                        {
                            insDelta = 0;
                            invalidApiAddress = unresolvedImport->InvalidApiAddress;
                            sprintf(buffer, "API Address = 0x%p\t IAT Address = 0x%p\n",  invalidApiAddress, unresolvedImport->ImportTableAddressPointer);    
                    
                            writeToLogFile(buffer);    
                    
                            IATbase = unresolvedImport->InvalidApiAddress;
                            for (j = 0; j <  COUNT_INS; j++)
                            {
                                memset(&inst, 0x00, sizeof(INSTRUCTION));    
                    
                                i = get_instruction(&inst, IATbase, MODE_32);
                                memset(buffer, 0x00, sizeof(buffer));
                                get_instruction_string(&inst, FORMAT_ATT, 0, buffer, sizeof(buffer));
                                if (strstr(buffer, "jmp"))
                                {    
                    
                                    printf(" JUMP Dest = %d" , ( (unsigned int)strtol(strstr(buffer, "jmp") + 4 + 2, NULL, 16)));
                                    *(DWORD*)(unresolvedImport->ImportTableAddressPointer) =  ( (unsigned int)strtol(strstr(buffer, "jmp") + 4 + 2, NULL, 16) + IATbase ) - insDelta;
                                    unresolvedImport->InvalidApiAddress = ( (unsigned int)strtol(strstr(buffer, "jmp") + 4 + 2, NULL, 16) + IATbase ) - insDelta;
                                    break;
                                }
                                else
                                {
                                    insDelta = insDelta + i;
                                }    
                    
                                IATbase = IATbase + i;
                            }
                            unresolvedImport++; //next pointer to struct
                        }
                    

                    這段代碼將遍歷所有未決斷的導入函數,并嘗試定位到正確的API地址。

                    JMP指令的目標地址減去insDelta就可以得到最終的InvalidApiAddress地址:

                    #!c++
                    unresolvedImport->InvalidApiAddress = ((unsigned int)strtol(strstr(buffer, “jmp”) + 4 + 2, NULL, 16) + IATbase) - insDelta;
                    

                    在修復整個IAT表之后可能還會有一些無效的導入地址,這些無效的導入地址需要手動把它們刪除掉。

                    運行上面編寫的插件之后還有一些殘留的無效地址,現在手動把它們刪掉:

                    0x04 導出RunPE加殼后的程序


                    RunPE的工作原理是創建一個暫停狀態的dummy進程,然后挖空并注入惡意代碼。這種技術常用于隱藏惡意代碼。RunPE注入的代碼可以導出為一個有效的PE文件。對于PE+文件頭需要修改一下,因為64位架構的PE文件一些字段使用了QWORD類型。

                    Windows loader加載程序到內存后根據IMAGE_SECTION_HEADER.VirtualSize進行對齊。但是Section表的RawSize可能會小于VirtualSize,這個時候會操作系統需要填充這塊區域。

                    磁盤上的PE文件是根據IMAGE_OPTIONAL_HEADER64.FileAlignment進行對齊的,因此從內存中導出PE文件之后還需要根據IMAGE_OPTIONAL_HEADER64.FileAlignment對PE文件進行對齊。

                    用IDA加載導出的PE文件時提示無法找到正確的虛擬地址,因為它的PE文件不對齊。

                    這個問題很好解決,我們先取出PE+文件結構:

                    #!c++
                    IMAGE_DOS_HEADER DosHdr = {0};
                    IMAGE_FILE_HEADER FileHdr = {0};
                    IMAGE_OPTIONAL_HEADER64 OptHdr = {0};    
                    
                    // Read All Structure as per offset    
                    
                    fread(&DosHdr, sizeof(IMAGE_DOS_HEADER), 0x01, fp);    
                    
                    fseek(fp, (unsigned int)DosHdr.e_lfanew + 4,SEEK_SET);    
                    
                    fread(&FileHdr, sizeof(IMAGE_FILE_HEADER), 1, fp);
                    fread(&OptHdr, sizeof(IMAGE_OPTIONAL_HEADER64), 1, fp);
                    

                    遍歷讀取所有section header:

                    #!c++
                        while (iNumSec < FileHdr.NumberOfSections)
                        {
                            fread(&pTail[iNumSec], sizeof( IMAGE_SECTION_HEADER), 1, fp);
                            iNumSec++;
                        }
                    

                    然后讀取第一個section的PointerToRawData:

                    #!c++
                        i = ftell(fp);    
                    
                        buffer = (unsigned char*) malloc(sizeof(char) * pTail[0].PointerToRawData + 1);    
                    
                        fseek(fp, 0, SEEK_SET);    
                    
                        fread(buffer, pTail[0].PointerToRawData, 1, fp); // Read/Write Everything Till the beginning of first section    
                    
                        fwrite(buffer, pTail[0].PointerToRawData, 1, out);
                    

                    最后,將數據以一個對齊的形式重寫:

                    #!c++
                        while ( i < iNumSec)
                        {    
                    
                            buffer = (unsigned char*) malloc(sizeof(char) * pTail[i].SizeOfRawData + 1);    
                    
                            fseek(fp, pTail[i].VirtualAddress, SEEK_SET);
                            fread(buffer, pTail[i].SizeOfRawData, 1, fp);    
                    
                            fwrite(buffer, pTail[i].SizeOfRawData, 1, out);
                            i++;
                        }
                    

                    全部修復完成之后就可以得到一個正確的PE文件,下面是IDA加載那個修復好的PE文件:

                    sample_plugin.rar

                      <pre id="vvttv"><mark id="vvttv"><progress id="vvttv"></progress></mark></pre>
                      <pre id="vvttv"></pre>

                        <p id="vvttv"></p>

                            <p id="vvttv"></p>

                                  <p id="vvttv"></p>

                                  <pre id="vvttv"><cite id="vvttv"><progress id="vvttv"></progress></cite></pre>

                                    <output id="vvttv"><dfn id="vvttv"><th id="vvttv"></th></dfn></output>

                                      <p id="vvttv"></p>

                                      这里只有精品视频