1. 程式人生 > >簡單加密殼專案筆記

簡單加密殼專案筆記

專案分為兩部分組成,一個是加殼程式,主要用於加密目標檔案後生成一個新的可執行程式;另一個是殼本身DLL程式,主要用於在程式執行之前先解密被加密的部分。

一、第一部分——加殼程式

第一步,開啟加殼的目標程式,讀取資料。初步解析一下是不是PE檔案。

CPE obj;
//開啟並獲取檔案PE資料
obj.GetFileBuf(PATH);
bool CPE::GetFileBuf(char * szFilePath)
{
       DWORD dwRealSize = 0;
       //1 開啟檔案
       HANDLE hFile = CreateFileA( szFilePath, FILE_ALL_ACCESS,NULL
,NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL,NULL ); if (hFile == INVALID_HANDLE_VALUE) { goto error; } //2 獲取目標檔案大小並申請記憶體後初始化 m_dwFileSize = GetFileSize(hFile, NULL); m_pFileBuf = new CHAR[m_dwFileSize]; ZeroMemory(m_pFileBuf, m_dwFileSize); //3 讀入
ReadFile(hFile, m_pFileBuf, m_dwFileSize, &dwRealSize, NULL); //4 初步解析一下 m_pFileDosHeader = (PIMAGE_DOS_HEADER)m_pFileBuf; if (m_pFileDosHeader->e_magic != IMAGE_DOS_SIGNATURE) { goto error; } m_pFileNtHeader = (PIMAGE_NT_HEADERS)(m_pFileDosHeader->e_lfanew + m_pFileBuf); if
(m_pFileNtHeader->Signature != IMAGE_NT_SIGNATURE) { goto error; } m_pFileSection = IMAGE_FIRST_SECTION(m_pFileNtHeader); return true; error: CloseHandle(hFile); if (m_pFileBuf != NULL) { delete[]m_pFileBuf; m_pFileBuf = NULL; } MessageBoxA(0, "失敗", 0, 0); return false; }

第二步,用LoadLibrary把Stub.dll載入進來。因為DLL載入進來後被修改了,造成它退出的時候會報錯。所以需要再拷貝一份後在這裡做修改。

 //1 把Stub.dll載入進來
       //獲取模組控制代碼 通過路徑
       HMODULE hModule = LoadLibrary(L"..\\Release\\Stub.dll");
       //拷貝一份(Middle中間),修改這一份。因為DLL載入進來後被修改了,造成它退出的時候會報錯。
       //模組大小長度
       DWORD MiddleSize = obj.GetModuleLen(hModule);
       //申請空間存放模組
       PCHAR pStubBuf = (PCHAR)VirtualAlloc(NULL, MiddleSize, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
       //正式拷貝
       memcpy(pStubBuf, hModule, MiddleSize);
       //獲取模組PE資料
       PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pStubBuf;
       PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pStubBuf);
       PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
DWORD CPE::GetModuleLen(HMODULE hModule)
{
       //模組基址
       PBYTE pImage = (PBYTE)hModule;
       //DOS頭
       PIMAGE_DOS_HEADER pImageDosHeader = (PIMAGE_DOS_HEADER)pImage;
       //NT頭
       PIMAGE_NT_HEADERS pImageNtHeader = (PIMAGE_NT_HEADERS)(pImageDosHeader->e_lfanew + pImage);
       //檔案頭中->擴充套件頭->檔案在記憶體中的大小
       return pImageNtHeader->OptionalHeader.SizeOfImage;
}

第三步,設定殼程式碼執行完之後jmp跳轉的位置(未加殼程式的OEP)

//2 找到全域性結構體
pKeInfo g_KeInfo = (pKeInfo)GetProcAddress(hModule, "g_KeInfo");
//設定殼程式碼執行後的入口點
g_KeInfo->dwOep = obj.GetOep();
//修改DLL中的OEP結構體
DWORD dwOffset_Pack = (DWORD)g_KeInfo - (DWORD)hModule;
memcpy((PCHAR)(pStubBuf + dwOffset_Pack), (PCHAR)g_KeInfo, sizeof(KeInfo));
//DLL檔案OEP相對基址的偏移 dwNewOep在DLL檔案中設定
DWORD dwOffset = g_KeInfo->dwNewOep - (DWORD)hModule - pSection->VirtualAddress;
//獲取原目標檔案OEP的VA
DWORD CPE::GetOep()
{
       return m_pFileNtHeader->OptionalHeader.AddressOfEntryPoint + m_pFileNtHeader->OptionalHeader.ImageBase;
}

第四步,迴圈遍歷區段,找到.text程式碼段。

//3 找到程式碼段
for (int i=0;i<pNt->FileHeader.NumberOfSections;++i)
{
       if ((strcmp((char*)pSection->Name,".text") == 0))
       {
              break;
       }
       pSection++;
}

第五步,因為在殼程式碼中會使用全域性變數,訪問全域性變數在會變層面都是通過VA得到的,但是拷貝到目標程式中VA就發生變化了,這些地方都是在重定位表中的,所以我們要根據資料的相對虛擬地址和目標程式新區段的起始位置作為基址修改重定位表。

DWORD CPE::CalcNewSectionPoint()
{
       //新區段頭的RVA
       DWORD AddNewSectionRva = 0;
       //新區段個數
       DWORD dwNumberOfSection = 0;
       //最後一個區段的指標
       PIMAGE_SECTION_HEADER pLastSection = NULL;
       //最後一個區段大小
       DWORD dwLastSectionSize = 0;
       //最後一個區段是第幾個(區段個數)
       dwNumberOfSection = m_pFileNtHeader->FileHeader.NumberOfSections;
       //最後一個區段的指標
       pLastSection = (m_pFileSection + dwNumberOfSection - 1);
       //最後一個區段的大小
       dwLastSectionSize = pLastSection->Misc.VirtualSize;
       //新區段的RVA=最後區段RVA+對齊後的大小
       AddNewSectionRva =pLastSection->VirtualAddress + CalcAligment(dwLastSectionSize, 0x1000);
       return AddNewSectionRva;
}
//修改重定位資訊 (固定重定位)
void CPE::FixReloc(PCHAR pDllBuf, PCHAR pOldBuf)
{
       /*基本思路
       1 DLL中程式碼段訪問全域性變數的地方,全部都儲存在重定位表中
       2 我們把程式碼段拷貝到了目標程式,訪問全域性變數的地方,VA肯定都是錯的
       3 所以說要修復VA,需要到重定位表中找到這些位置,去計算新的VA
       */
       typedef struct  _TYPEOFFSET
       {
              WORD Offset : 12;     // (1) 大小為12Bit的重定位偏移
              WORD Type : 4;        // (2) 大小為4Bit的重定位資訊型別值
       }TYPEOFFSET, *PTYPEOFFSET;
       //1 找到重定位表
       PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pDllBuf;
       PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pDllBuf);
       //重定位表在資料目錄中的結構體
       PIMAGE_DATA_DIRECTORY pRelocDir = (pNt->OptionalHeader.DataDirectory + 5);
       //重定位表
       /*typedef struct _IMAGE_BASE_RELOCATION
        {
              DWORD   VirtualAddress;
              DWORD   SizeOfBlock;
              //  WORD    TypeOffset[1];
        } IMAGE_BASE_RELOCATION;*/
       typedef IMAGE_BASE_RELOCATION UNALIGNED * PIMAGE_BASE_RELOCATION;
       //重定位(中間那層)結構體陣列的VA
       PIMAGE_BASE_RELOCATION pReloc = (PIMAGE_BASE_RELOCATION)
              (pRelocDir->VirtualAddress + pDllBuf);
       //遍歷每一個TYPEOFFSET結構體  typeoffset
       while (pReloc->SizeOfBlock != 0)
       {
              //本0x1000位元組內需要重定位的個數
              DWORD dwCount = (pReloc->SizeOfBlock - 8) / 2;
              //本0x1000位元組的起始位置
              DWORD dwBaseRva = pReloc->VirtualAddress;
              //下一個WORD Block 最裡面的結構體
              PTYPEOFFSET pOffset = (PTYPEOFFSET)(pReloc + 1);
              //新區段基址的RVA    Target目標
              DWORD dwTargetLastSectionRva = CalcNewSectionPoint();
              //判斷高4位的屬性 0x3則需要重定位
              for (int i = 0; i < dwCount; i++)
              {
                     if (pOffset->Type == 0x3)
                     {
                           //需要重定位的點VA
                           PDWORD pRelocPoint = (PDWORD)(pOffset[i].Offset + dwBaseRva + pDllBuf);
                           //下面這種是偷懶的做法,我們應該判斷一下當前重定位點在哪一個區段中,就減哪一個區段的RVA
                           //我們這裡預設,肯定在程式碼段中,預設程式碼段的起始RVA為0x1000
                           //重定位點距離本區段的偏移 chazhi差值
                           DWORD dwChazhi = *pRelocPoint - (DWORD)pOldBuf - 0x1000;
                           //修改重定位資訊(地址)
                           *pRelocPoint = dwChazhi + dwTargetLastSectionRva + 0x00400000;
                     }
              }
              //下一個外層結構體 IMAGE_BASE_RELOCATION
              pReloc = (PIMAGE_BASE_RELOCATION)((PCHAR)pReloc + pReloc->SizeOfBlock);
       }
}

第六步,向新程式中新增一個新區段,用來存放DLL的程式碼。分兩步新增,一個是新增區段頭表的資訊,另外是新增區段資料。

//新增區段
obj.AddSection("ChiLeMa", pSection->VirtualAddress + pStubBuf, pSection->SizeOfRawData, pSection->Characteristics);
//申請一段新空間m_pNewBuf,將目標程式的資料放入其中,然後在最後面再加一個區段用來寫入殼程式碼。
void CPE::AddSection
(
       //區段名
       PCHAR szName,
       //新區段由來的地方
       PCHAR pSectionBuf,
       //區段大小
       DWORD dwSize,
       //區段屬性
       DWORD dwCharacteristics
)
{
       //1 計算一個新增完區段之後是多大,,
       m_dwNewSize = m_dwFileSize + CalcAligment(dwSize, 0x200);
       //然後申請空間 並初始化
       m_pNewBuf = new CHAR[m_dwNewSize];
       ZeroMemory(m_pNewBuf, m_dwNewSize);
       //把目標PE檔案內容拷貝到新空間中
       memcpy(m_pNewBuf, m_pFileBuf, m_dwFileSize);
       //獲取新的DOS頭
       m_pNewDosHeader = (PIMAGE_DOS_HEADER)m_pNewBuf;
       //獲取新的NT頭
       m_pNewNtHeader = (PIMAGE_NT_HEADERS)(m_pNewDosHeader->e_lfanew + m_pNewBuf);
       //獲取新的區段頭
       m_pNewSection = IMAGE_FIRST_SECTION(m_pNewNtHeader);
       //檔案對齊數
       DWORD dwFileAligment = m_pNewNtHeader->OptionalHeader.FileAlignment;
       //記憶體中 塊對齊數
       DWORD dwSectionAlignment = m_pNewNtHeader->OptionalHeader.SectionAlignment;
        // 2 新增區段分為兩個部分:
       //2.1 在區段表中新增資訊(因為區段頭和區段之間是有空隙的,所以暫不用考慮資料重疊的問題)
       PIMAGE_SECTION_HEADER pLastSection =
              m_pNewSection + m_pNewNtHeader->FileHeader.NumberOfSections - 1;
       //新區段應該新增的位置
       PIMAGE_SECTION_HEADER pAddNewSection = pLastSection + 1;
       //2.1.1 區段名
       strcpy_s((char*)(pAddNewSection->Name), 8, szName);
       //2.1.2 新新增區段頭的起始RVA=最後一個區段的RVA+它在記憶體中的大小
       pAddNewSection->VirtualAddress =
              pLastSection->VirtualAddress +
              CalcAligment(pLastSection->Misc.VirtualSize, dwSectionAlignment);
       //2.1.3 記憶體中大小
       pAddNewSection->Misc.VirtualSize = dwSize;
       //2.1.4 起始Offset 區段的檔案偏移
       pAddNewSection->PointerToRawData = pLastSection->PointerToRawData + pLastSection->SizeOfRawData;
       //2.1.5 檔案中大小
       pAddNewSection->SizeOfRawData = CalcAligment(dwSize, dwFileAligment);
       //2.1.6 區段屬性
       pAddNewSection->Characteristics = dwCharacteristics;
       //2.2 拷貝新區段
       memcpy(m_pNewBuf + m_dwFileSize, pSectionBuf, dwSize);
       //3 修改頭部資訊
       m_pNewNtHeader->FileHeader.NumberOfSections += 1;
       m_pNewNtHeader->OptionalHeader.SizeOfImage += CalcAligment(dwSize, dwSectionAlignment);
}

第七步,取消隨機基址。

//取消隨機重定位基址
obj.CancelDynamicBase();
void CPE::CancelDynamicBase()
{
       m_pNewNtHeader->OptionalHeader.DllCharacteristics &= ~IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE;
}

第八步,設定新OEP到新建立的程式中。

//設定OEP到新程式中(dwOffset:DLL檔案OEP相對基址的偏移)
obj.SetOep(dwOffset);
void CPE::SetOep(DWORD dwOffset)
{
       //目標程式未加殼之前 最後一個區段首地址
       PIMAGE_SECTION_HEADER pLastSection = m_pNewSection + (m_pNewNtHeader->FileHeader.NumberOfSections) - 1;
       //最後一個區段的最末尾  也就是新區段的開始  也就是我們要的新OEP (RVA)
       //OEP= 區段起始RVA + DLL檔案OEP相對基址的偏移
       m_pNewNtHeader->OptionalHeader.AddressOfEntryPoint = pLastSection->VirtualAddress + dwOffset;
}

第九步,加密。

//加密
obj.Encode();
void CPE::Encode()
{
       //區段基址+區段的檔案偏移PointerToRawData
       PCHAR Begin = m_pNewBuf + m_pNewSection->PointerToRawData;
       //迴圈加密
       for (int i = 0; i < m_pNewSection->SizeOfRawData; i++)
       {
              Begin[i] ^= 0x15;
       }
}

第十步,建立加殼後的新檔案。

//建立加殼後的新檔案
obj.CreateNewFile("G:\\Temp\\Test_Ke.exe");
//建立一個新檔案,將修改好的m_pNewBuf讀入進來。
bool CPE::CreateNewFile(char* szFilePath)
{
       DWORD dwRealSize = 0;
       //1 建立檔案
       HANDLE hFile = CreateFileA(
              szFilePath,
              FILE_ALL_ACCESS,
              NULL,
              NULL,
              OPEN_ALWAYS,
              FILE_ATTRIBUTE_NORMAL,
              NULL
       );
       //2 讀入 m_pNewBuf
       WriteFile(hFile, m_pNewBuf, m_dwNewSize, &dwRealSize, NULL);
       if (GetLastError() == 0)
       {
              MessageBox(0, L"建立成功", 0, MB_OK);
       }
       //3 關閉控制代碼
       CloseHandle(hFile);
       return 0;
}

二、 第二部分 DLL殼程式

第一步,設定新程式的OEP,使加殼後的程式先執行殼程式碼,然後再JMP跳轉到原程式的OEP。

//宣告入口點函式
void Start();
//匯出變數  並給dwNewOep設定為Start函式地址
extern "C" _declspec(dllexport) KeInfo g_KeInfo = { (DWORD)Start };

在標頭檔案定義的結構體。

typedef struct _KeInfo
{
       //新的程式入口點
       DWORD dwNewOep;
       DWORD dwOep;
}KeInfo,*pKeInfo;

這一步要和第一部分中第八步結合,會把新檔案中的 OptionalHeader. AddressOfEntryPoint(OEP)設定為這裡。

第二步,獲取Kenrnel32模組基址,然後動態獲取API函式的地址

//獲取Kenrnel32模組基址
int  GetKernel32Base()
{
       int nAddress = 0;
       _asm
       {
              push eax;
              mov eax, fs:[0x30];
              mov eax, [eax + 0xC];
              mov eax, [eax + 0xC];
              mov eax, [eax];
              mov eax, [eax];
              mov eax, dword ptr ds : [eax + 0x18];
              mov nAddress, eax;
              pop eax;
       }
       return nAddress;
}

先獲取LoadLibrary和GetProcAddress函式

bool MyGetProcAddress()
{
       //到kernel32.dll的匯出表中,根據函式名得到函式地址

       //1 得到kernel32.dll的基址並獲取基本的PE資訊
       PCHAR pBuf = (PCHAR)GetKernel32Base();
       PIMAGE_DOS_HEADER pDos = (PIMAGE_DOS_HEADER)pBuf;
       PIMAGE_NT_HEADERS pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pBuf);
       PIMAGE_SECTION_HEADER pSection = IMAGE_FIRST_SECTION(pNt);
       // 2 得到匯出表
       //擴充套件頭中 資料目錄表+0=匯入表結構體
       PIMAGE_DATA_DIRECTORY pExportDir = pNt->OptionalHeader.DataDirectory + 0;
       //匯出表VA
       PIMAGE_EXPORT_DIRECTORY pExport = (PIMAGE_EXPORT_DIRECTORY)(pExportDir->VirtualAddress + pBuf);
       //函式名稱表VA
       PDWORD pNameTable = PDWORD(pExport->AddressOfNames + pBuf);
       //函式地址表VA
       PDWORD pAddressTable = PDWORD(pExport->AddressOfFunctions + pBuf);
       //函式序號表VA
       PWORD  pOrder = PWORD(pExport->AddressOfNameOrdinals + pBuf);
       //迴圈遍歷函式名 通過序號表的連線找到函式地址表 ->函式地址
       for (int i = 0; i < pExport->NumberOfNames; i++)
       {
              char * pszFunName = pNameTable[i] + pBuf;
              if (strcmp(pszFunName, "GetProcAddress") == 0)
              {
                     int nOrder = pOrder[i];
                     g_MyGetProcAddress = (GETPROCADDRESS)(pAddressTable[nOrder] + pBuf);
              }
              if (strcmp(pszFunName, "LoadLibraryW") == 0)
              {
                     int nOrder = pOrder[i];
                     g_MyLoadLibrary = (LOADLIBRARYW)(pAddressTable[nOrder] + pBuf);
              }
       }
       return true;
}

然後通過上面這兩個函式,獲取出其他要使用到的API函式

//得到API函式
void InitApi()
{
       //呼叫函式獲取函式地址
       MyGetProcAddress();
       //獲取GetModuleHandleW
       g_MyGetModuleHandlew = (GETMODULEHANDLEW)g_MyGetProcAddress
       (g_MyLoadLibrary(L"Kernel32.dll"), "GetModuleHandleW");
       //獲取VirtualProtect
       g_MyVirtualProtect = (VIRTUALPROTECT)g_MyGetProcAddress
       (g_MyLoadLibrary(L"Kernel32.dll"), "VirtualProtect");
       //獲取其他各種函式
       MyGetModuleHandleW = (GetModuleHandleW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"Kernel32.dll"), "GetModuleHandleW");
       MyCreateWindowExW = (CreateWindowExW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "CreateWindowExW");
       MySendMessageW = (SendMessageW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "SendMessageW");
       MyDefWindowProcW = (DefWindowProcW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "DefWindowProcW");
       MyPostQuitMessage = (PostQuitMessage1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "PostQuitMessage");
       MyShowWindow = (ShowWindow1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "ShowWindow");
       MyTranslateMessage = (TranslateMessage1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "TranslateMessage");
       MyGetMessageW = (GetMessageW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "GetMessageW");
       MyDispatchMessageW = (DispatchMessageW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "DispatchMessageW");
       MyDestroyWindow = (DestroyWindow1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "DestroyWindow");
       MyRegisterClassW = (RegisterClassW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "RegisterClassW");
       MyExitProcess = (ExitProcess1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"Kernel32.dll"), "ExitProcess");
       MyGetWindowTextW = (GetWindowTextW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "GetWindowTextW");
       Mywcscmp = (wcscmp1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"ucrtbased.dll"), "wcscmp");
       MyMessageBoxW = (MessageBoxW1)g_MyGetProcAddress
       (g_MyLoadLibrary(L"User32.dll"), "MessageBoxW");
}

當然每個函式都需要在這上面寫以下定義:
舉例:

typedef HMODULE(WINAPI *LOADLIBRARYW)(_In_ LPCWSTR lpLibFileName);
typedef FARPROC(WINAPI *GETPROCADDRESS)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI *GETMODULEHANDLEW)(_In_opt_ LPCWSTR lpModuleName);
typedef VOID (WINAPI *PostQuitMessage1)(
       _In_ int nExitCode);
typedef BOOL (WINAPI *ShowWindow1)(
       _In_ HWND hWnd,
       _In_ int nCmdShow);
typedef BOOL (WINAPI *TranslateMessage1)(
       _In_ CONST MSG *lpMsg);
typedef LRESULT (WINAPI *DispatchMessageW1)(
       _In_ CONST MSG *lpMsg);
LOADLIBRARYW      g_MyLoadLibrary;
GETPROCADDRESS    g_MyGetProcAddress;
GETMODULEHANDLEW  g_MyGetModuleHandlew;
PostQuitMessage1  MyPostQuitMessage;
ShowWindow1            MyShowWindow;
TranslateMessage1 MyTranslateMessage;
DispatchMessageW1 MyDispatchMessageW;

第三步,寫SDK彈框程式。在密碼輸入正確並且點選按鈕之後開始解密程式碼。

HWND g_hBtn;
HINSTANCE g_hInstance;
//用於接收Edit中字串
WCHAR Buf[10] = {};
//定義PE變數
PCHAR pExeBuf;
PIMAGE_DOS_HEADER pDos = NULL;
PIMAGE_NT_HEADERS pNt = NULL;
PIMAGE_SECTION_HEADER pSection = NULL;
//第一個區段VA 開始得地方
PCHAR Begin;
//區段大小 長度
DWORD dwTextSize;
//儲存一下原區段屬性
DWORD lpflOldProtect = 0;
LRESULT CALLBACK OnWndCommand1(
       _In_  HWND hwnd,
       _In_  UINT uMsg,
       _In_  WPARAM wParam,
       _In_  LPARAM lParam
)
{
       switch (HIWORD(wParam))// 通知碼
       {
       case BN_CLICKED:
       {
              switch (LOWORD(wParam))
              {
              case 0x1001:
                     //這裡寫判斷
                     MyGetWindowTextW(g_hBtn, Buf, 10);
                     if (!Mywcscmp(Buf, L"15PB"))
                     {
                           //獲取本模組的控制代碼
                           pExeBuf = (PCHAR)g_MyGetModuleHandlew(NULL);//PE
                           pDos = (PIMAGE_DOS_HEADER)pExeBuf;
                           pNt = (PIMAGE_NT_HEADERS)(pDos->e_lfanew + pExeBuf);
                           pSection = IMAGE_FIRST_SECTION(pNt);
                           //第一個區段VA(因為只有一個.text段的原因)
                           Begin = pSection->VirtualAddress + pExeBuf;
                           //得到區段大小 長度
                           dwTextSize = pSection->SizeOfRawData;
                           //修改區段屬性 改成可讀可寫
                           g_MyVirtualProtect(Begin, dwTextSize, PAGE_EXECUTE_READWRITE, &lpflOldProtect);
                           //遍歷解密
                           for (DWORD i = 0; i < pSection->SizeOfRawData; i++)
                           {
                                  Begin[i] = Begin[i] ^ 0x15;
                           }
                           MySendMessageW(hwnd, WM_CLOSE, NULL, NULL);
                     }
                     else
                     {
                           MyMessageBoxW(hwnd, TEXT("恭喜你!專案不及格"), NULL, MB_OK);
                     }
                     return true;
              case 0x1002:
                     MyExitProcess(0);
                     return true;
              default:
                     break;
              }
       }
              break;
       default:
              break;
       }
       return MyDefWindowProcW(hwnd, uMsg, wParam, lParam);
}

中間省略視窗回撥函式

void SDK(_In_ HINSTANCE hInstance )
{
       g_hInstance = hInstance;
       // 建立視窗的過程
       // 1. 註冊視窗類
       // 2. 根據視窗類建立視窗
       // 3. 訊息迴圈
       WNDCLASS wnd = {};
       // 下面兩個是必須的
       wnd.lpszClassName = L"Super";
       // 所有通過"Super"類建立的視窗,他們的訊息回撥函式都是WindowProc
       wnd.lpfnWndProc = WindowProc11;
       // 設定視窗背景色
       wnd.hbrBackground = (HBRUSH)(COLOR_INACTIVECAPTION);
       // 註冊視窗
       if(!MyRegisterClassW(&wnd))
       {
              hInstance = 0;
       }
       // 捏人  人
       HWND hWnd = MyCreateWindowExW(0L,
              L"Super",// 視窗類名
              L"Super_Ke",// 視窗名
              WS_OVERLAPPEDWINDOW,// 視窗風格,個性話定義
              300, 100,// 視窗的起始位置
              500, 300,// 視窗的寬高
              NULL,// 父視窗
              NULL,// 選單控制代碼
              hInstance,// 例項控制代碼
              NULL);// 附加資訊
       MyShowWindow(hWnd, SW_SHOW);
       // 訊息迴圈
       MSG msg = {};
       // 接收訊息,分發給不同的視窗
       while (MyGetMessageW(&msg, NULL, NULL, NULL ))
       {
              MyTranslateMessage(&msg);
              // 把不同視窗的訊息分發給對應的回撥函式->WindowProc
              MyDispatchMessageW(&msg);
       }
}

第四步,把以上DLL殼程式程式碼用一個函式封裝起來。

void Run()
{
       //呼叫函式得到自定義API地址
       InitApi();
       //獲取目標程式控制代碼
       _In_ HINSTANCE hInstance = (_In_ HINSTANCE)MyGetModuleHandleW(NULL);
       SDK(hInstance);
}

第五步,實現最開始宣告的Start入口點函式。首先呼叫Run函式執行DLL的殼程式碼,然後跳轉到原程式的OEP。

void _declspec(naked) Start()
{
       _asm
       {
              call Run;
              jmp g_KeInfo.dwOep;
       }
}