shellcode編寫探究
前言
shellcode是不依賴環境,放到任何地方都可以執行的機器碼。shellcode的應用場景很多,本文不研究shellcode的具體應用,而只是研究編寫一個shellcode需要掌握哪些知識。
ShellCode編寫原則
1、不能有全局變量
因為我們編寫shellcode時,使用的全局變量是自己的進程里面的全局變量,注入到別的進程里,這個地址就沒用了。
2、不能使用常量字符串
和第一點原因一樣,字符串常量值也是全局變量,注入到別的進程里,根本沒有這個字符串。
要使用字符串,需要使用字符數組。
char s[] = {'1','2',0};
3、不能直接調用系統函數
調用系統函數的方式是間接調用(FF15),需要從IAT表里獲取API地址,每個進程的IAT表位置不同,且對方的進程可能沒有導入你需要調用的函數的DLL,那么你是不能調用這個系統函數的。
所以我們需要用到 LoadLibrary 和 GetProcAddress 這兩個函數,來動態獲取系統API的函數指針。
但是 LoadLibrary,GetProcAddress 本身就是系統函數,它們本身就依賴IAT表,咋辦呢?
解決方案是這樣的:通過FS:[0x30] 找到PEB,然后通過PEB里的LDR鏈表 [PEB+0x0C]找到 kernel32.dll 的地址,然后我們遍歷它的 IAT表,找到 LoadLibrary 和 GetProcAddress 函數。
4、不能嵌套調用其他函數
和前兩點道理是一樣的,本進程里的函數地址,拿到別的進程的虛擬地址空間是無效的。
TEB/PEB
每個線程都有一個TEB結構來存儲線程的一些屬性結構,TEB的地址用fs:[0]來獲取

在0x30這個地址有一個指針指向PEB結構,PEB就是進程用來記錄自己信息的一個結構

完整結構如下

在PEB的0x00c偏移有一個 Ldr _PEB_LDR_DATA結構跟進去

可以得到3個結構如下所示
InLoadOrderModuleList:模塊加載的順序InMemoryOrderModuleList:模塊在內存的順序InInitializationOrderModuleList:模塊初始化的順序
思路
我們一般使用api會直接使用LoadLibrary和GetProcessAddress,但是這里肯定會依賴IAT表,所以這里我們就需要自己實現api所完成的功能
TEB -> PEB -> PEB + 0x0C -> Ldr _PEB_LDR_DATA -> InLoadOrderModuleList -> kernel32.dll -> 導出表定位GetProcessAddress -> 通過找到的GetProcessAddress實現LoadLibrary
實現過程
首先我們自己定義幾個結構體,因為我們不依賴系統自己實現
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
DWORD Length;
bool Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);
然后定義shellcode,這里因為kernel32.dll是unicode字符串所以用兩字節存儲
char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};
找到InLoadOrderModuleList存入寄存器
__asm
{
mov eax,fs:[0x30] // PEB
mov eax,[eax+0x0C] // PEB->LDR
add eax,0x0C // LDR->InLoadOrderModuleList
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}
找到kernel32.dll,通過遍歷的方式來尋找,通過LDR指向DllBase獲取基址

// Find Kerner32.dll
while (pPLD != pBeg)
{
pLast = (WORD*)pPLD->BaseDllName.Buffer;
pFirst = (WORD*)szKernel32;
while (*pFirst && *pLast == *pFirst)
pFirst++,pLast++;
if (*pFirst == *pLast)
{
dwKernelBase = (DWORD)pPLD->DllBase;
break;
}
pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
}
然后通過指針定位到導出表
// 通過指針定位到導出表 PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase; PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew); PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4); PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER)); PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader); PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress); // 導出函數地址表RVA DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfFunctions); // 導出函數名稱表RVA WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNameOrdinals); // 導出函數序號表RVA DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNames);
還是通過遍歷找到GetProcessAddress,用指針指向這個地址
DWORD dwCnt = 0;
char* pFinded = NULL, *pSrc = szGetProcAddress;
for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);
while (*pFinded && *pFinded == *pSrc)
pFinded++, pSrc++;
if (*pFinded == *pSrc)
{
pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
break;
}
pSrc = szGetProcAddress;
}
然后就可以使用pGetProcessAddress實現LoadLibrary和MessageBox
// 通過pGetProcAddress進行調用 pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary); pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox); pMessageBox(NULL,szHelloShellCode,0,MB_OK);
完整代碼如下
// shellcode.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <windows.h>
#include <stdio.h>
#include <string.h>
typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, *PUNICODE_STRING;
typedef struct _PEB_LDR_DATA
{
DWORD Length;
bool Initialized;
PVOID SsHandle;
LIST_ENTRY InLoadOrderModuleList;
LIST_ENTRY InMemoryOrderModuleList;
LIST_ENTRY InInitializationOrderModuleList;
} PEB_LDR_DATA,*PPEB_LDR_DATA;
typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY;
typedef HMODULE (WINAPI * PLOADLIBRARY)(LPCSTR);
typedef DWORD (WINAPI * PGETPROCADDRESS)(HMODULE, LPCSTR);
typedef DWORD (WINAPI * PMESSAGEBOX)(HWND, LPCSTR,LPCSTR,UINT);
DWORD WINAPI ShellCode();
int main(int argc, char* argv[])
{
ShellCode();
getchar();
return 0;
}
DWORD WINAPI ShellCode()
{
PGETPROCADDRESS pGetProcAddress = NULL;
PLOADLIBRARY pLoadLibrary = NULL;
PMESSAGEBOX pMessageBox = NULL;
PLDR_DATA_TABLE_ENTRY pPLD;
PLDR_DATA_TABLE_ENTRY pBeg;
WORD *pFirst = NULL;
WORD *pLast = NULL;
DWORD ret = 0, i = 0;
DWORD dwKernelBase = 0;
char szKernel32[] = {'k',0,'e',0,'r',0,'n',0,'e',0,'l',0,'3',0,'2',0,'.',0,'d',0,'l',0,'l',0,0,0}; // Unicode
char szUser32[] = {'u','s','e','r','3','2','.','d','l','l',0};
char szGetProcAddress[] = {'G','e','t','P','r','o','c','A','d','d','r','e','s','s',0};
char szLoadLibrary[] = {'L','o','a','d','L','i','b','r','a','r','y','A',0};
char szMessageBox[] = {'M','e','s','s','a','g','e','B','o','x','A',0};
char szHelloShellCode[] = {'H','e','l','l','o','S','h','e','l','l','C','o','d','e',0};
__asm
{
mov eax,fs:[0x30] // PEB
mov eax,[eax+0x0C] // PEB->LDR
add eax,0x0C // LDR->InLoadOrderModuleList
mov pBeg,eax
mov eax,[eax]
mov pPLD,eax
}
// Find Kerner32.dll
while (pPLD != pBeg)
{
pLast = (WORD*)pPLD->BaseDllName.Buffer;
pFirst = (WORD*)szKernel32;
while (*pFirst && *pLast == *pFirst)
pFirst++,pLast++;
if (*pFirst == *pLast)
{
dwKernelBase = (DWORD)pPLD->DllBase;
break;
}
pPLD = (LDR_DATA_TABLE_ENTRY*)pPLD->InLoadOrderLinks.Flink;
}
// Kernel32.dll -> GetProcAddress
if (dwKernelBase != 0)
{
// 通過指針定位到導出表
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dwKernelBase;
PIMAGE_NT_HEADERS pNTHeader = (PIMAGE_NT_HEADERS)((DWORD)pDosHeader + pDosHeader->e_lfanew);
PIMAGE_FILE_HEADER pPEHeader = (PIMAGE_FILE_HEADER)((DWORD)pDosHeader + pDosHeader->e_lfanew + 4);
PIMAGE_OPTIONAL_HEADER32 pOptionHeader = (PIMAGE_OPTIONAL_HEADER32)((DWORD)pPEHeader + sizeof(/images/shellcode/image_FILE_HEADER));
PIMAGE_SECTION_HEADER pSectionHeader = (PIMAGE_SECTION_HEADER)((DWORD)pOptionHeader + pPEHeader->SizeOfOptionalHeader);
PIMAGE_EXPORT_DIRECTORY pExportDirectory = (PIMAGE_EXPORT_DIRECTORY)((DWORD)dwKernelBase + pOptionHeader->DataDirectory[0].VirtualAddress);
// 導出函數地址表RVA
DWORD *pAddOfFun_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfFunctions);
// 導出函數名稱表RVA
WORD *pAddOfOrd_Raw = (WORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNameOrdinals);
// 導出函數序號表RVA
DWORD *pAddOfNames_Raw = (DWORD*)((DWORD)dwKernelBase + pExportDirectory->AddressOfNames);
DWORD dwCnt = 0;
char* pFinded = NULL, *pSrc = szGetProcAddress;
for (; dwCnt < pExportDirectory->NumberOfNames;dwCnt++)
{
pFinded = (char*)((DWORD)dwKernelBase + pAddOfNames_Raw[dwCnt]);
while (*pFinded && *pFinded == *pSrc)
pFinded++, pSrc++;
if (*pFinded == *pSrc)
{
pGetProcAddress = (PGETPROCADDRESS)(pAddOfFun_Raw[pAddOfOrd_Raw[dwCnt]] + (DWORD)dwKernelBase);
break;
}
pSrc = szGetProcAddress;
}
}
// 通過pGetProcAddress進行調用
pLoadLibrary = (PLOADLIBRARY)pGetProcAddress((HMODULE)dwKernelBase, szLoadLibrary);
pMessageBox = (PMESSAGEBOX)pGetProcAddress(pLoadLibrary(szUser32),szMessageBox);
pMessageBox(NULL,szHelloShellCode,0,MB_OK);
return 0;
}
成功彈窗

這里我們進反匯編看一下,是有檢測堆棧平衡的代碼的

在物理機里面查看也是有的

這里關閉一下堆棧平衡的檢測,默認情況如下

修改為禁用安全檢查

即可生成沒有檢查堆棧平衡的代碼
