天天看點

滴水逆向-代碼節空白區添加代碼(程式寫入)

滴水逆向-代碼節空白區添加代碼(程式寫入)

相關實作代碼

檔案頭:globlepdd.h

// globlepdd.h: interface for the globlepdd class.
//
//////////////////////////////////////////////////////////////////////

#if !defined(AFX_GLOBLEPDD_H__DDA6AB97_A94D_41F9_B3B9_8426B6CB7934__INCLUDED_)
#define AFX_GLOBLEPDD_H__DDA6AB97_A94D_41F9_B3B9_8426B6CB7934__INCLUDED_

#if _MSC_VER > 1000
#pragma once
#endif // _MSC_VER > 1000

#include <windows.h>
#include <stdio.h>

//#define FILEPATH_IN         "C:\\WINDOWS\\system32\\kernel32.dll"
//#define FilePath_In         "C:\\cntflx\\notepad.exe"
#define FilePath_In         "C:\\cntflx\\ipmsg.exe"
//#define FilePath_Out        "C:\\cntflx\\notepadnewpes.exe"
#define FilePath_Out        "C:\\cntflx\\ipmsgnewpeaddcodes.exe"
#define MESSAGEBOXADDR      0x77D5050B
#define SHELLCODELENGTH     0x12 //16進制的,轉換為十進制就是18

extern BYTE ShellCode[];

DWORD ReadPEFile(IN LPSTR lpszFile,OUT LPVOID* pFileBuffer);

DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer,OUT LPVOID* pImageBuffer);

DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer,OUT LPVOID* pNewBuffer);

BOOL MemeryTOFile(IN LPVOID pMemBuffer,IN size_t size,OUT LPSTR lpszFile);

//DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva);

VOID AddCodeInCodeSec();

#endif // !defined(AFX_GLOBLEPDD_H__DDA6AB97_A94D_41F9_B3B9_8426B6CB7934__INCLUDED_)      

核心代碼globlepdd.cpp

// globlepdd.cpp: implementation of the globlepdd class.
//
//////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "globlepdd.h"
#include <string.h>
#include <windows.h>
#include <stdlib.h>

//////////////////////////////////////////////////////////////////////
// Construction/Destruction
//////////////////////////////////////////////////////////////////////

//定義一個全局變量
BYTE ShellCode[] =
{
    0x6A,00,0x6A,00,0x6A,00,0x6A,00, //MessageBox push 0的寫死
    0xE8,00,00,00,00,  // call彙編指令E8和後面待填充的寫死
    0xE9,00,00,00,00   // jmp彙編指令E9和後面待填充的寫死
};

//ExeFile->FileBuffer  傳回值為計算所得檔案大小

DWORD ReadPEFile(IN LPSTR lpszFile, OUT LPVOID* pFileBuffer)
{
    //下面有個IN和OUT,大緻意思就是參數的類型傳入進來之後不進行宏擴充;
    //啥也不幹,即使了解成幹,也是擴充成空白,這個是C++文法中允許的;
    //LPSTR  ---->  typedef CHAR *LPSTR, *PSTR; 意思就是char* 指針;在WINNT.H頭檔案裡面
    FILE* pFile = NULL;
    //定義一個FILE結構體指針,在标準的Stdio.h檔案頭裡面
  
    DWORD fileSize = 0;
    // typedef unsigned long       DWORD;  DWORD是無符号4個位元組的整型
    LPVOID pTempFileBuffer = NULL;
    //LPVOID ---->  typedef void far *LPVOID;在WINDEF.H頭檔案裡面;别名的void指針類型

    //打開檔案
    pFile = fopen(lpszFile,"rb"); //lpszFile是當作參數傳遞進來
    if (!pFile)
    {
        printf("打開檔案失敗!\r\n");
        return 0;
    }
    /*
    關于在指針類型中進行判斷的操作,下面代碼出現的情況和此一樣,這裡解釋下:
    1.因為指針判斷都要跟NULL比較,相當于0,假值,其餘都是真值
    2.if(!pFile)和if(pFile == NULL), ----> 為空,就執行語句;這裡是兩個等于号不是一個等于号
    3.if(pFile)就是if(pFile != NULL), 不為空,就執行語句;
    */

    //讀取檔案内容後,擷取檔案的大小
    fseek(pFile,0,SEEK_END);
    fileSize = ftell(pFile);
    fseek(pFile,0,SEEK_SET);

    /*
    fseek 通過使用二進制的方式打開檔案,移動檔案讀寫指針的位置,在stdio.h頭檔案裡

    int fseek(FILE * stream, long offset, int fromwhere);

    上面是fseek的函數原型
    第一個參數stream 為檔案指針
    第二個參數offset 為偏移量,整數表示正向偏移,負數表示負向偏移
    第三個參數fromwhere 為指針的起始位置,設定從檔案的哪裡開始偏移,可能取值為:SEEK_CUR,SEEK_END,SEEK_SET
    SEEK_SET 0 檔案開頭
    SEEK_CUR 1 目前讀寫的位置
    SEEK_END 2 檔案尾部

    下面是相關用法和例子:
  fseek(fp,100L,0);把fp指針移動到離檔案開頭100位元組處;
  fseek(fp,100L,1);把fp指針移動到離檔案目前位置100位元組處;
    fseek(fp,100L,2);把fp指針退回到離檔案結尾100位元組處。
    fseek(fp,0,SEEK_SET);将讀寫位置移動到檔案開頭;
    fseek(fp,0,SEEK_END);将讀寫位置移動到檔案尾時;
    fseek(fp,100L,SEEK_SET);将讀寫位置移動到離檔案開頭100位元組處;
    fseek(fp,100L,SEEK_CUR);将讀寫位置移動到離檔案目前位置100位元組處;
    fseek(fp,-100L,SEEK_END);将讀寫指針退回到離檔案結尾100位元組處;
    fseek(fp,1234L,SEEK_CUR);把讀寫位置從目前位置向後移動1234位元組;
    fseek(fp,0L,2);把讀寫位置移動到檔案尾;
    其中 --->  L字尾表示長整數

    ftell()用于傳回檔案目前指針指向的位置,與fseek配合可以算出檔案元素資料總數。
    參考:http://c.biancheng.net/cpp/html/2519.html

    ftell()函數用來擷取檔案讀寫指針的目前位置,其原型為:long ftell(FILE * stream); 同樣在stdio.h頭檔案裡
    參數:stream 為已打開的檔案指針。
    */

    //動态申請記憶體空間
    pTempFileBuffer = malloc(fileSize);

    /*
    參考:http://c.biancheng.net/cpp/html/137.html
    原型:void* malloc (size_t size);
    size_t ---> typedef unsigned int size_t; 無符号整型别名是size_t
    void*  ---> 函數的傳回值類型是 void* ;void并不是說沒有傳回值或者傳回空指針,而是傳回的指針類型未知;
    是以在使用 malloc() 時通常需要進行強制類型轉換,将 void 指針轉換成我們希望的類型;
    例如:char *ptr = (char *)malloc(10);  //配置設定10個位元組的記憶體空間,用來存放字元
    參數說明 ---> size 為需要配置設定的記憶體空間的大小,以位元組(Byte)計。
    函數說明 ---> malloc()在堆區配置設定一塊指定大小的記憶體空間,用來存放資料。這塊記憶體空間在函數執行完成後不會被初始化;
    它們的值是未知的,是以配置設定完成記憶體之後需要初始化;
    傳回值:配置設定成功傳回指向該記憶體的位址,失敗則傳回 NULL。
    */

    if (!pTempFileBuffer)
    {
        printf("記憶體配置設定失敗!\r\n");
        fclose(pFile);
        return 0;
    }

    //根據申請到的記憶體空間,讀取資料

    size_t n = fread(pTempFileBuffer,fileSize,1,pFile);
    if (!n)
    {
        printf("讀取資料失敗!\r\n");
        free(pTempFileBuffer);   // 釋放記憶體空間
        fclose(pFile);           // 關閉檔案流
        return 0;
    }

    //資料讀取成功,關閉檔案
    *pFileBuffer = pTempFileBuffer;  // 将讀取成功的資料所在的記憶體空間的首位址放入指針類型pFileBuffer
    pTempFileBuffer = NULL;  // 初始化清空臨時申請的記憶體空間
    fclose(pFile);           // 關閉檔案
    return fileSize;         // 傳回擷取檔案的大小
}

//CopyFileBuffer --> ImageBuffer

DWORD CopyFileBufferToImageBuffer(IN LPVOID pFileBuffer,OUT LPVOID* pImageBuffer)
{
    //LPVOID ---->  typedef void far *LPVOID;在WINDEF.H頭檔案裡面;别名的void指針類型
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    LPVOID pTempImageBuffer = NULL;
    /*
    上面都是PE裡面的相關結構體類型,使用其類型進行自定義變量,并初始化值為NULL
    PIMAGE_DOS_HEADER ---> 指向結構體,别名為這兩個 IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER
    PIMAGE_NT_HEADERS ---> 指向結構體,typedef PIMAGE_NT_HEADERS32    PIMAGE_NT_HEADERS;
    PIMAGE_FILE_HEADER ---> 指向結構體,别名為這兩個 IMAGE_FILE_HEADER, *PIMAGE_FILE_HEADER;
    PIMAGE_OPTIONAL_HEADER32 ---> 指向結構體,别名為這兩個 IMAGE_OPTIONAL_HEADER32, *PIMAGE_OPTIONAL_HEADER32;
    PIMAGE_SECTION_HEADER ---> 指向結構體,别名為這兩個 IMAGE_SECTION_HEADER, *PIMAGE_SECTION_HEADER;
    */

    if (pFileBuffer == NULL)
    {
        printf("FileBuffer 擷取失敗!\r\n");
        return 0;
    }

    //判斷是否是有效的MZ标志
    if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("無效的MZ辨別\r\n");
        return 0;
    }
    /*
    IMAGE_DOS_SIGNATURE 這個在頭檔案WINNT.H裡面,對應是個無參數宏;
    #define IMAGE_DOS_SIGNATURE                 0x5A4D      // MZ
    在宏擴充的時候就會替換為0x5A4D ,然後根據架構的不同進行排序存儲,分大端和小端模式;
    使用上面方式進行比對是否是有效的MZ頭是非常有效;
    而且IMAGE_DOS_SIGNATURE存儲的值是兩個位元組,剛好就是PWORD ---> typedef WORD near *PWORD;
    是以在進行比較的時候需要強制類型轉換為相同的類型進行比較
    */

    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    //這裡的定義,就相當于已經确定了,其頭肯定是MZ了,然後強制轉換類型為PIMAGE_DOS_HEADER,就是Dos頭

    //判斷是否是有效的PE标志
    if (*((PDWORD)((DWORD)pFileBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("無效的PE标記\r\n");
        return 0;
    }
    /*
    IMAGE_NT_SIGNATURE  ---> #define IMAGE_NT_SIGNATURE   0x00004550  // PE00
    上述同樣是個宏擴充,在頭檔案WINNT.H裡面;
    在進行比對的時候因為在Dos頭裡面有個值是 e_lfanew 對應的時候DWORD類型,是以在進行指針相加的時候
    需要先進行強制類型轉換,然後相加,即移動指針位置;然後最終需要比對的結果是0x4550站兩個位元組
    是以又要強制轉換類型為PWORD;
    */
    //定位NT頭
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
    //上面偏移完成之後pFileBuffer的指針偏移到了NT頭---> pNTHeader
    //****************************************************************************************
    //定位PE檔案頭
    pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader)+4);
    //根據PE頭的結構體内容,PE檔案頭位置在NT頭首位址偏移4個位元組即可得到pPEHeader
    //****************************************************************************************
    //定位可選PE頭
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
    /*
    要得到可選PE的首位址位置,就根據上面得到的PE檔案頭位置裡面的IMAGE_SIZEOF_FILE_HEADER來定位;
    IMAGE_SIZEOF_FILE_HEADER也是個宏擴充,裡面位元組描述了PE檔案頭的大小是20個位元組;
    #define IMAGE_SIZEOF_FILE_HEADER  20,是以隻要在PE檔案頭的首位址偏移20個位元組即可移動到可選PE頭;
    指針相加的時候,此處的類型依然是DWORD
    */
    //****************************************************************************************
    //第一個節表指針
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
    /*
    這裡要移動到第一個節表指針的首位址,就需要根據上面标準PE檔案頭中的SizeOfOptionalHeader擷取具體可選PE
    頭的大小,然後根據這個大小進行偏移即可;
    */
    //****************************************************************************************

    /*
    到了節表的首位址位置之後,因為需要将FileBuffer複制到ImageBuffer,這個過程中,節表之前的Dos頭,NT頭
    PE檔案頭,可選PE頭,她們的大小都是不變的,是以定位出來之後,到後面的操作中直接複制即可,而節表不一樣
    她在FileBuffer狀态和ImageBuffer狀态是不相同的,她們節表之間複制轉換到ImageBuffer是需要拉長節表,是以
    在操作的時候是需要确定FileBuffer到ImageBuffer之後ImageBuffer的大小是多少,而這個大小,已經在可選PE頭
    裡面的某一個值中已經給出來了 ---> SizeOfImage ;
    注意:FileBuffer和ImageBuffer都是在記憶體中的展示,隻不過FileBuffer是使用winhex等類似的形式打開檢視其
    二進制的形式,而ImageBuffer則是輕按兩下打開應用程式,将其加載至記憶體中顯示的二進制的形式;
    */
    //****************************************************************************************

    //根據SizeOfImage申請新的記憶體空間
    pTempImageBuffer = malloc(pOptionHeader->SizeOfImage);

    if (!pTempImageBuffer)
    {
        printf("再次在堆中申請一塊記憶體空間失敗\r\n");
        return 0;
    }

    //因為下面要開始對記憶體空間進行複制操作,是以需要初始化操作,将其置為0,避免垃圾資料,或者其他異常
    //初始化新的緩沖區
    memset(pTempImageBuffer,0,pOptionHeader->SizeOfImage);
    /*
    參考:http://c.biancheng.net/cpp/html/157.html

    在頭檔案string.h裡面

    void* memset( void* ptr,int value,size_t num );
    memset()函數用來将指定記憶體的前n個位元組設定為特定的值;

    參數說明:
    ptr     為要操作的記憶體的指針;
    value   為要設定的值;既可以向value傳遞int類型的值,也可以傳遞char類型的值,int和char可以根據ASCII碼互相轉換;
    num     為ptr的前num個位元組,size_t就是unsigned int。
    函數說明:memset()會将ptr所指的記憶體區域的前num個位元組的值都設定為value,然後傳回指向ptr的指針;
    */
    //****************************************************************************************

    //根據SizeOfHeaders大小的确定,先複制Dos頭
    memcpy(pTempImageBuffer,pDosHeader,pOptionHeader->SizeOfHeaders);
    /*
    參考:http://c.biancheng.net/cpp/html/155.html

    在頭檔案string.h裡面

    void* memcpy (void* dest,const void* src,size_t num);
    memcpy()函數功能用來複制記憶體的;她會複制src所指向内容的首位址,作為起始位置,然後偏移num個位元組到dest所指的記憶體位址
    的位置;此函數有個特征就是,她并不關心被複制的資料類型,隻是逐位元組地進行複制,這給函數的使用帶來了很大的靈活性,
    可以面向任何資料類型進行複制;

    需要注意的是:
    dest 指針要配置設定足夠的空間,也就是要大于等于num位元組的空間,如果沒有配置設定足夠的空間會出現錯誤;
    dest和src所指的記憶體空間不能重疊(如果發生了重疊,使用 memmove() 會更加安全)。

    是以上面的代碼的含義如下:
    (1)pDosHeader ---> 是指向pFileBuffer的首位址,也就是記憶體複制的時候從這裡開始;
    (2)pTempImageBuffer  ---> 這裡是表示上面要複制的目的,要把内容複制到這塊記憶體來;
    (3)pOptionHeader->SizeOfHeaders  ---> 這裡表示複制多大的内容到pTempImageBuffer裡面去;
    (4)從上面看來我們就知道複制到目标pOptionHeader->SizeOfHeaders所在的記憶體空間一定要比pTempImageBuffer大;
    */
    //****************************************************************************************

    //上面把已經确定的頭都複制好了,那麼下面就可以開始複制節的裡面的内容,因為節不僅僅是一個,是以需要用到for循環進行操作
    //根據節表循環copy節的内容
    PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
    //定義一個臨時節表的指針
    for (int i=0;i<pPEHeader->NumberOfSections;i++,pTempSectionHeader++)
    {
        memcpy((void*)((DWORD)pTempImageBuffer + pTempSectionHeader->VirtualAddress),
            (void*)((DWORD)pFileBuffer + pTempSectionHeader->PointerToRawData),pTempSectionHeader->SizeOfRawData);
    }
    /*
    上面的大概操作就是根據标準PE檔案頭裡面的值 NumberOfSections确定有幾個節,然後不斷的計算并增加指針偏移位置,不停的複制

    PointerToRawData   ---> 節在檔案中的偏移位址;
    VirtualAddress     ---> 節在記憶體中的偏移位址;
    SizeOfRawData      ---> 節在檔案中對齊後的尺寸;

    (void*)((DWORD)pTempImageBuffer + pTempSectionHeader->VirtualAddress)   ---> Dest(目的地)
    上面我們已經知道了函數memcpy是怎麼複制操作的,是以這裡我們依依解釋下:
    首先我們知道,上面展示的是目的地,而且我們的目的是要從FileBuffer節内容複制到ImageBuffer節的内容,
    那麼要使用到的是檔案被輕按兩下打開之後在記憶體中的偏移位址,這個位址就是VirtualAddress;這裡舉個例子:
    正常打開notepad.exe,然後使用winhex加載這個notepad.exe的記憶體資料,同時使用PE解析工具得到兩個值的資訊如下:
    可選PE頭 ---> ImageBase   ---> 0x01000000
    第一個節表顯示的VirtualAddress  ---> 00001000
    上面兩個值相加就得到了檔案被打開在記憶體中第一個節的真實資料的起始位置 ---> 0x01001000
    檢視winhex對應的位址,确認是對的;

    (void*)((DWORD)pFileBuffer + pTempSectionHeader->PointerToRawData)      ---> Src(源複制的起始記憶體位址)
    同樣是上面的例子:
    PointerToRawData是節在檔案中的偏移位址,而我們知道,在檔案中和在記憶體中是不一樣的,因為在記憶體中有ImageBase的說法,
    但在檔案中沒有,是以她的起始位置就是檔案存儲在硬碟的時候使用winhex打開的開頭位置,為這裡同樣使用winhex以二進制的形式
    打開notepad.exe(非輕按兩下打開),發現檔案的起始位置是0x00000000,同時使用PE解析工具确認出了PointerToRawData的值
    PointerToRawData  ---> 0x00000400 ; 起始位置為0x00000000 ,她們相加就得到第一個節表的起始位置為0x00000400
    檢視winhex對應的位址,确認是對的;
    是以這裡總結下來的Src,就是記憶體複制的時候,從這個偏移位址開始拿資料開始複制;

    pTempSectionHeader->SizeOfRawData
    這裡就是告訴我們上面複制要複制多大的内容到 (void*)((DWORD)pTempImageBuffer + pTempSectionHeader->VirtualAddress)
    SizeOfRawData ---> 節在檔案中對齊後的尺寸;
    例子還是以上面的為例:
    通過PE解析工具确認SizeOfRawData的大小為:0x00007800

    總結:
    memcpy((void*)((DWORD)pTempImageBuffer + pTempSectionHeader->VirtualAddress),
    (void*)((DWORD)pFileBuffer + pTempSectionHeader->PointerToRawData),
    pTempSectionHeader->SizeOfRawData);

    上面代碼就是在檔案中的形式找到要複制的位置0x00000400的起始位置開始複制,要複制0x00007800個位元組大小,也就是從
    0x00000400這個位址開始向後偏移7800個位元組,将這些資料複制到檔案輕按兩下被打開時候的記憶體位址0x01001000為起點向後覆寫複制
    完成即可,為這裡測試算了下;0x00000400+0x00007800=0x00007C00 ; 0x00007C00這個位址剛好是第二個節的PointerToRawData
    這樣就可以很好的了解for循環對第二個節的複制;
    */

    //****************************************************************************************
    //傳回資料
    *pImageBuffer = pTempImageBuffer;
    //将複制好後節的首位址儲存到指針pImageBuffer中
    pTempImageBuffer = NULL;
    //初始化清空臨時使用的pTempImageBuffer

    return pOptionHeader->SizeOfImage;
}

DWORD CopyImageBufferToNewBuffer(IN LPVOID pImageBuffer,OUT LPVOID* pNewBuffer)
{
    //下面大部分操作都是跟上面一樣的,這裡就不再贅述了
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    LPVOID pTempNewBuffer = NULL;
    DWORD sizeOfFile = 0;
    DWORD numberOfSection = 0;

    if (pImageBuffer == NULL)
    {
        printf("緩沖區指針無效\r\n");
    }
    //判斷是否是有效的MZ标志
    if (*((PWORD)pImageBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ頭\r\n");
        return 0;
    }
    pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    //判斷是否是有效的PE标志
    if (*((PDWORD)((DWORD)pImageBuffer+pDosHeader->e_lfanew)) != IMAGE_NT_SIGNATURE)
    {
        printf("不是有效的PE标志\r\n");
        return 0;
    }
    //NT頭位址
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pImageBuffer + pDosHeader->e_lfanew);
    //标準PE檔案頭
    pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
    //可選PE頭
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);
    //第一個節表位址
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

    //計算檔案需要的空間--最後一個節的檔案偏移+節對齊後的長度
    /*
    numberOfSection = pPEHeader->NumberOfSections;
    pSectionHeader = pSectionHeader[numberOfSection-1];
    sizeOfFile = (pSectionHeader->PointerToRawData + pSectionHeader->Misc.VirtualSize + pOptionHeader->FileAlignment);
    printf("sizeOfFile %X \r\n",sizeOfFile);

    for (DWORD i=0;i<=numberOfSection;i++)
    {
        sizeOfFile += sizeOfFile[i];
    }
    */

    sizeOfFile = pOptionHeader->SizeOfHeaders;
    //使用winhex打開notepad.exe 是0x00000400,這是第一個節之前的所有大小
    for(DWORD i = 0;i<pPEHeader->NumberOfSections;i++)
    {
        sizeOfFile += pSectionHeader[i].SizeOfRawData;  // pSectionHeader[i]另一種加法
    }

    /*
    上面的for循環大概意思就是基于幾個節的數量依次循環疊加sizeOfFile的值;因為SizeOfRawData是檔案中對齊後的大小;
    是以循環計算如下:
    sizeOfFile = 0x00000400 + 0x00007800 = 0x00007C00
    sizeOfFile = 0x00007C00 + 0x00000800 = 0x00008400
    sizeOfFile = 0x00008400 + 0x00008000 = 0x00010400

    */

    //根據SizeOfImage申請新的空間
    pTempNewBuffer = malloc(sizeOfFile);

    if (!pTempNewBuffer)
    {
        printf("申請記憶體空間失敗\r\n");
        return 0;
    }
    //初始化新的緩沖區
    memset(pTempNewBuffer,0,sizeOfFile);
    //根據SizeOfHeaders 先copy頭
    memcpy(pTempNewBuffer,pDosHeader,pOptionHeader->SizeOfHeaders);
    //根據節表循環複制節
    //PIMAGE_SECTION_HEADER pTempSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader);
    PIMAGE_SECTION_HEADER pTempSectionHeader = pSectionHeader;
    for (int j=0;j<pPEHeader->NumberOfSections;j++,pTempSectionHeader++)
    {
        /*memcpy((LPVOID)((DWORD)pTempNewBuffer + pTempSectionHeader->PointerToRawData),
        (LPVOID)((DWORD)pImageBuffer + pTempSectionHeader->VirtualAddress),
        pTempSectionHeader->SizeOfRawData);*/
        //PointerToRawData節區在檔案中的偏移,VirtualAddress節區在記憶體中的偏移位址,SizeOfRawData節在檔案中對齊後的尺寸
        memcpy((PDWORD)((DWORD)pTempNewBuffer+pTempSectionHeader->PointerToRawData),
        (PDWORD)((DWORD)pImageBuffer+pTempSectionHeader->VirtualAddress),
        pTempSectionHeader->SizeOfRawData);
        //printf("%X  --> PoniterToRadata\r\n",pTempSectionHeader->PointerToRawData);
        //printf("%X  --> VirtualAddress\r\n",pTempSectionHeader->VirtualAddress);
        //printf("%X  --> VirtualSize\r\n",pTempSectionHeader->Misc.VirtualSize);
    }

    //傳回資料
    *pNewBuffer = pTempNewBuffer;
    pTempNewBuffer = NULL;
    return sizeOfFile;
  }

BOOL MemeryTOFile(IN LPVOID pMemBuffer,IN size_t size,OUT LPSTR lpszFile)
{
    FILE* fp = NULL;
    fp = fopen(lpszFile, "wb+");
    if (!fp)  //  這裡我剛開始寫漏了一個等于号,變成複制NULL了,導緻錯誤
//  if(fp == NULL)  可以這麼寫,沒問題
    {
        return FALSE;
    }
    fwrite(pMemBuffer,size,1,fp);
    fclose(fp);
    fp = NULL;
    return TRUE;
}
/*
DWORD RvaToFileOffset(IN LPVOID pFileBuffer,IN DWORD dwRva)
{
    DWORD dwFOAValue = 0;
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_NT_HEADERS pNTHeader = NULL;
    PIMAGE_FILE_HEADER pPEHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);

    //判斷指針是否有效
    if (!pFileBuffer)
    {
        printf("FileBuffer 指針無效\r\n");
        return dwFOAValue;
    }
    //判斷是否是有效的MZ标志
    if (*((PWORD)pFileBuffer) != IMAGE_DOS_SIGNATURE)
    {
        printf("不是有效的MZ标志\r\n");
        return dwFOAValue;
    }
    //為需要用到的指針指派
    pDosHeader = (PIMAGE_DOS_HEADER)pFileBuffer;
    pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pFileBuffer+pDosHeader->e_lfanew);
    pPEHeader = (PIMAGE_FILE_HEADER)(((DWORD)pNTHeader) + 4);
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader+IMAGE_SIZEOF_FILE_HEADER);

    //判斷dwRva所處的節

    //計算與節開始位置的差

    //該節檔案中的偏移+差 == 該值在檔案中的偏移
    return 0;
}
*/

//開始添加ShellCode代碼

VOID AddCodeInCodeSec()
{
    LPVOID pFileBuffer = NULL;
    LPVOID pImageBuffer = NULL;
    LPVOID pNewBuffer = NULL;
    PIMAGE_DOS_HEADER pDosHeader = NULL;
    PIMAGE_OPTIONAL_HEADER32 pOptionHeader = NULL;
    PIMAGE_SECTION_HEADER pSectionHeader = NULL;
    PBYTE codeBegin = NULL;
    BOOL isOK = FALSE;
    DWORD size = 0;

    //File-->FileBuffer
    ReadPEFile(FilePath_In,&pFileBuffer);
    if (!pFileBuffer)
    {
        printf("檔案-->緩沖區失敗\r\n");
        return ;
    }

    //FileBuffer-->ImageBuffer
    CopyFileBufferToImageBuffer(pFileBuffer,&pImageBuffer);
    if (!pImageBuffer)
    {
        printf("FileBuffer-->ImageBuffer失敗\r\n");
        free(pFileBuffer);
        return ;
    }

    //判斷代碼段空閑區域是否能夠足夠存儲ShellCode代碼
    pDosHeader = (PIMAGE_DOS_HEADER)pImageBuffer;
    pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)(((DWORD)pImageBuffer + pDosHeader->e_lfanew) + 4 + IMAGE_SIZEOF_FILE_HEADER);
    pSectionHeader = (PIMAGE_SECTION_HEADER)(((DWORD)pImageBuffer + pDosHeader->e_lfanew) + 4 + IMAGE_SIZEOF_FILE_HEADER + IMAGE_SIZEOF_NT_OPTIONAL32_HEADER);
    if (((pSectionHeader->SizeOfRawData) - (pSectionHeader->Misc.VirtualSize)) < SHELLCODELENGTH)
    {
        printf("代碼區域空閑空間不夠\r\n");
        free(pFileBuffer);
        free(pImageBuffer);
    }

    //将代碼複制到空閑區域
    codeBegin = (PBYTE)((DWORD)pImageBuffer + pSectionHeader->VirtualAddress + pSectionHeader->Misc.VirtualSize);
    printf("pSectionHeader->VirtualAddress: %#010X\r\n", pSectionHeader->VirtualAddress);
    printf("pSectionHeader->Misc.VirtualSize: %#010X\r\n", pSectionHeader->Misc.VirtualSize);
    printf("codeBegin: %#010X\r\n", codeBegin);

    memcpy(codeBegin,ShellCode,SHELLCODELENGTH);

    //修正E8-->call後面的代碼區域
    DWORD callAddr = (MESSAGEBOXADDR - (pOptionHeader->ImageBase + ((DWORD)(codeBegin + 0xD) - (DWORD)pImageBuffer)));
    printf("callAddr ---> %#010X \r\n",callAddr);
    *(PDWORD)(codeBegin + 0x09) = callAddr;
    printf("*(PWORD)(codeBegin + 0x09) ---> %#010X \r\n",*(PDWORD)(codeBegin + 0x09));
    /*
    關于修正E8的了解,公式:X = 要跳轉的位址 - (E8目前的位址 + 5);
    要跳轉的位址,這裡是毋庸置疑的,就是我們要加入代碼MessageBox的位址;
    然後要減去E8目前的位址+5的位置,這裡不是太好了解;
    我們的目的是要将E8後面的4個位元組計算出來,然後寫入到E8後面,也就是公式中X;
    上面公式E8目前位址+5 ,而在此情況要定位到這個位置就要從代碼的Dos開始通過指針相加;
    進行位置偏移到E8目前位址+5的位置;
    是以定位codeBegin的位置是:pImageBuffer指針最開始的位置(Dos頭位置)通過記憶體中偏移的寬度移動到第一個節表的位置;
    也就是上面的pSectionHeader->VirtualAddress 操作形式;
    然後再偏移第一個節表在記憶體中對齊前實際的寬度(尺寸)pSectionHeader->Misc.VirtualSize;
    上述一番操作之後就到了第一個節表沒有對齊前的位置,這個位置就是我們可以添加ShellCode代碼的起始位置;
    到了添加ShellCode代碼的起始位置之後,就要想辦法添加E8位置後面的4個位元組,此時根據ShellCode代碼的寬度;
    進行計算,确認0x6A 00 0x6A 00 0x6A 00 0x6A 00 E8 00 00 00 00 剛好向後面數13個位置,按照十六進制看;
    就是0xD,是以在codeBegin偏移0xD個位置即可到達E9的位置,這也就是我們說的(E8目前的位址 + 5);
    到了上面的位置之後,由于我們最終是需要在程式運作之後在記憶體中添加ShellCode代碼;是以這裡一定要計算出;
    其準确的偏移位址,這樣不管怎麼拉伸到哪個位置,都能準确找到位置;
    注意:這裡需要注意一點了解,上面說的pImageBuffer這個是我們加載程式到我們申請的記憶體中,絕不是程式在;
    運作中的那個記憶體,這裡一定要了解清楚,她們是不一樣的,了解了這個就能了解上面代碼為什麼要減去Dos頭的;
    首位址,(DWORD)(codeBegin + 0xD) - (DWORD)pImageBuffer)
    */

    //修正E9-->jmp後面的代碼區域
    DWORD jmpAddr = ((pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint) - (pOptionHeader->ImageBase + ((DWORD)(codeBegin + SHELLCODELENGTH) - (DWORD)pImageBuffer)));
    printf("jmpAddr ---> %#010X \r\n",jmpAddr);
    *(PDWORD)(codeBegin + 0x0E) = jmpAddr;
    printf("*(PWORD)(codeBegin + 0x0E) ---> %#010X \r\n",*(PDWORD)(codeBegin + 0x0E));
    /*
    公式:X = 要跳轉的位址 - (E9目前的位址 + 5)
    這裡同樣是要計算出E9後面4個位元組的位址,我們的目的是在這裡添加OEP的位址,讓其執行完成MessageBox之後跳轉;
    OEP的位址,那麼這裡就要先計算出OEP位址,就是pOptionHeader->ImageBase + pOptionHeader->AddressOfEntryPoint;
    再減去(E9目前的位址 + 5) 0x6A 00 0x6A 00 0x6A 00 0x6A 00 E8 00 00 00 00 E9 00 00 00 00;
    (DWORD)codeBegin + SHELLCODELENGTH 就是加上ShellCode總長度,偏移完成之後減去ImageBuffer首位址再加上ImageBase;
    */

    //修正OEP
    printf("pOptionHeader->AddressOfEntryPoint ---> %#010X \r\n",pOptionHeader->AddressOfEntryPoint);
    printf("(DWORD)codeBegin ---> %#010X \r\n",((DWORD)codeBegin - (DWORD)pImageBuffer));
    pOptionHeader->AddressOfEntryPoint = (DWORD)codeBegin - (DWORD)pImageBuffer;
    printf("pOptionHeader->AddressOfEntryPoint ---> %#010X \r\n",pOptionHeader->AddressOfEntryPoint);
    //修正OEP好了解,就是定位到OEP位址,然後直接通過codeBegin位址減去pImageBuffer的首位址即可;

    //ImageBuffer-->NewBuffer
    size = CopyImageBufferToNewBuffer(pImageBuffer,&pNewBuffer);
    if (size == 0 || !pNewBuffer)
    {
        printf("ImageBuffer-->NewBuffer失敗\r\n");
        free(pFileBuffer);
        free(pImageBuffer);
        return ;
    }

    //NewBuffer-->檔案
    isOK = MemeryTOFile(pNewBuffer,size,FilePath_Out);
    if (isOK)
    {
        printf("修改代碼添加SHELLCODE 存盤成功\r\n");
        return ;
    }

    //釋放記憶體
    free(pFileBuffer);
    free(pImageBuffer);
    free(pNewBuffer);
}      

程式入口代碼

// peaddcdft.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "globlepdd.h"

int main(int argc, char* argv[])
{
    //Fun();
    AddCodeInCodeSec();
    printf("Hello World!\n");
    return 0;
}      

執行效果:

滴水逆向-代碼節空白區添加代碼(程式寫入)