注意事项
在Windows中,ASLR_(Address Space Layout Randomization)(内存随机化保护)的存在使得内存地址随机化成为常态。PE文件通过其重定向表(.reloc节)能够适应这种随机化,使得程序在每次启动时都能正确处理地址引用。
shellcode作为一段纯机器码,不存在重定向表。在编写时,不能依赖固定的内存地址,由于ASLR的作用,这些地址在重启后会发生改变,导致shellcode失效。
也就是说,在开启了ASLR的系统上,在Shellcode中不能直接通过名称调用Windows API,也不能通过DLL base + 偏移的方式调用API。如果没有启用 ASLR,Windows 加载器会尝试将 DLL 加载到其 PE 头中 ImageBase 字段指定的地址。并且由于系统 DLL 是共享的,在不同进程中它们会被映射到相同的虚拟地址。
由于Shellcode没有 rdata段,不能使用全局变量,所有的变量只能在函数的内部栈上分配,使用相对寻址而非绝对寻址。
工作可以分为三步:
- 在内存中查找需要调用的 DLL 文件。
- 解析 DLL 的导出表。
- 通过导出表名称对比定位到函数指针。
- 为了简化第3步,可以先获取两个关键API(GetProcessAddress、LoadLibrary)
第一步:获取 Kernel32.DLL 地址
Shellcode 中通常需要调用操作系统API,为了使该代码可以在不同的机器上运行,避免写死地址时由于地址随机化导致找不到的问题,需要动态定位Windows API地址。
参考:Finding Kernel32 Base and Function Addresses in Shellcode - ired.team
建议动手通过 windbg 跟一遍获取流程,会加深对偏移的理解。
PEB (Process Environment Block) 是 Windows 操作系统中每个进程都有的一个数据结构,每个进程都有一个唯一的PEB。PEB中存储了进程的各种关键信息:进程的基本参数如进程的镜像基址、命令行参数、环境变量等。PEB中还存储了进程加载的模块列表(LoaderData),这个列表记录了所有被加载到进程空间的DLL信息。
同理,TEB (Thread Environment Block) 是保存线程相关的结构体,一个进程只有一个PEB,但可以有多个TEB 对应多个线程,每个TEB都持有一个指向其所属进程PEB的指针,这样线程就能访问进程级的信息。
通过获取PEB结构,遍历其中的模块列表,可以找出所需要的DLL模块。
开始之前,先细化工作:
- 获取 PEB 结构体的地址
https://en.wikipedia.org/wiki/Win32_Thread_Information_Block
当Windows创建一个新线程时,系统会在GDT(全局描述符表)中创建一个段描述符,这个段描述符指向该线程的TEB。然后系统会将FS寄存器加载这个段描述符(Segment Descriptor) 的选择子(Selector) 。这样,FS段寄存器就会指向当前线程的TEB。
在x86架构中,可以通过 FS:[0x18]
来访问TEB结构,x64中,通过 GS:[0x30]
访问 TEB 结构。然后通过偏移 0x60
找到进程的 PEB 结构地址。
1: kd> dt _TEB
ntdll!_TEB
+0x000 NtTib : _NT_TIB
+0x038 EnvironmentPointer : Ptr64 Void
+0x040 ClientId : _CLIENT_ID
+0x050 ActiveRpcHandle : Ptr64 Void
+0x058 ThreadLocalStoragePointer : Ptr64 Void
+0x060 ProcessEnvironmentBlock : Ptr64 _PEB // <- PEB 结构
+0x068 LastErrorValue : Uint4B
- 获取 Ldr
PEB 结构的 0x18
偏移地址,指向 Ldr 结构,该结构的类型是 PEB_LDR_DATA
1: kd> dt _PEB
ntdll!_PEB
+0x000 InheritedAddressSpace : UChar
+0x001 ReadImageFileExecOptions : UChar
+0x002 BeingDebugged : UChar
+0x003 BitField : UChar
+0x003 ImageUsesLargePages : Pos 0, 1 Bit
+0x003 IsProtectedProcess : Pos 1, 1 Bit
+0x003 IsImageDynamicallyRelocated : Pos 2, 1 Bit
+0x003 SkipPatchingUser32Forwarders : Pos 3, 1 Bit
+0x003 IsPackagedProcess : Pos 4, 1 Bit
+0x003 IsAppContainer : Pos 5, 1 Bit
+0x003 IsProtectedProcessLight : Pos 6, 1 Bit
+0x003 IsLongPathAwareProcess : Pos 7, 1 Bit
+0x004 Padding0 : [4] UChar
+0x008 Mutant : Ptr64 Void
+0x010 ImageBaseAddress : Ptr64 Void
+0x018 Ldr : Ptr64 _PEB_LDR_DATA // <- Ldr 链表
+0x020 ProcessParameters : Ptr64 _RTL_USER_PROCESS_PARAMETERS
+0x028 SubSystemData : Ptr64 Void
Ldr 结构体 PEB_LDR_DATA 结构如下,遍历 InMemoryOrderModuleList 链表可获取所有DLL信息
1: kd> dt _PEB_LDR_DATA
ntdll!_PEB_LDR_DATA
+0x000 Length : Uint4B
+0x004 Initialized : UChar
+0x008 SsHandle : Ptr64 Void
+0x010 InLoadOrderModuleList : _LIST_ENTRY // 模块加载顺序
+0x020 InMemoryOrderModuleList : _LIST_ENTRY // 模块在内存中的排序
+0x030 InInitializationOrderModuleList : _LIST_ENTRY // 模块初始化装载顺序(TLS)
+0x040 EntryInProgress : Ptr64 Void
+0x048 ShutdownInProgress : UChar
+0x050 ShutdownThreadId : Ptr64 Void
- 遍历链表,对比 DLL Name
还需要得知 InMemoryOrderModuleList 字段的结构有哪些成员。
LDR_DATA_TABLE_ENTRY 主要成员如下:
1: kd> dt _LDR_DATA_TABLE_ENTRY
ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x010 InMemoryOrderLinks : _LIST_ENTRY
+0x020 InInitializationOrderLinks : _LIST_ENTRY
+0x030 DllBase : Ptr64 Void
+0x038 EntryPoint : Ptr64 Void
+0x040 SizeOfImage : Uint4B
+0x048 FullDllName : _UNICODE_STRING
+0x058 BaseDllName : _UNICODE_STRING
汇编实现,由于大多数情况下 InMemoryOrderModuleList 中的模块列表中的第一个是原始进程,第二个通常是 ntdll,第三个通常是 kernel32,所以实现中未使用字符串匹配。
; Get TEB
mov rax, gs:[30h] ; TEB address in GS
; Get PEB from TEB
mov rax, [rax+60h] ; PEB offset is 0x60 in TEB
; Get LDR from PEB
mov rax, [rax+18h] ; LDR offset is 0x18 in PEB
; Get InMemoryOrderModuleList from LDR
mov rsi, [rax+20h] ; InMemoryOrderModuleList offset is 0x20
mov rsi, [rsi] ; Get second entry (ntdll)
mov rsi, [rsi] ; Get thirdly entry (kernel32)
当前shellcode无法调用系统API,所以如需对比字符串匹配名称,需要自己实现简易的字符串对比,或是实现基于名称hash值的对比。使用hash还可以避免明文字符串特征。
翻译成 C 语言实现:
// Hash calculation function
DWORD CalculateNameHash(PWCHAR pName, USHORT nameLength) {
DWORD hashValue = 0;
while (nameLength--) {
hashValue = _rotr(hashValue, 13);
WCHAR currentChar = *pName++;
// Convert to uppercase if lowercase
if (currentChar >= L'a') {
currentChar -= 0x20;
}
hashValue += currentChar;
}
return hashValue;
}
// Get Kernel32 base address through PEB
HMODULE GetKernel32Base() {
const DWORD KERNEL32_HASH = 0x6E2BCA17;
PPEB pPeb = NULL;
HMODULE hKernel32 = NULL;
// Get PEB address from GS register
pPeb = (PPEB)__readgsqword(0x60);
if (!pPeb || !pPeb->Ldr) {
return NULL;
}
// Get first entry of InMemoryOrderModuleList
PLIST_ENTRY pCurrentEntry = pPeb->Ldr->InMemoryOrderModuleList.Flink;
PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
// Iterate through loaded modules
while (pCurrentEntry != &pPeb->Ldr->InMemoryOrderModuleList) {
pCurrentModule = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (pCurrentModule->BaseDllName.Buffer && pCurrentModule->BaseDllName.Length) {
#ifdef _DEBUG
wprintf(L"Checking module: %s, ", pCurrentModule->BaseDllName.Buffer);
#endif
// Calculate hash of current module name
DWORD currentHash = CalculateNameHash(
pCurrentModule->BaseDllName.Buffer,
pCurrentModule->BaseDllName.Length / sizeof(WCHAR)
);
#ifdef _DEBUG
wprintf(L"Hash: 0x%08X\n", currentHash);
#endif
// Check if this is Kernel32.dll
if (currentHash == KERNEL32_HASH) {
hKernel32 = (HMODULE)pCurrentModule->DllBase;
break;
}
}
pCurrentEntry = pCurrentEntry->Flink;
}
return hKernel32;
}
output
Checking module: Test.exe, Hash: 0x0E92CF49
Checking module: ntdll.dll, Hash: 0xAD74DBF2
Checking module: KERNEL32.DLL, Hash: 0x6E2BCA17
Kernel32.dll base address: 0x00007FFB07500000
示例代码中会缺少部分结构体定义,这并不主要,在结尾我会给出完整的代码,你也可以去搜索别人定义好的结构体。
第二步:查找需要的API地址
导出表(Export Table)是Windows可执行文件中的一个结构,记录了可执行文件中某些函数或变量的名称和地址,这些名称和地址可以供其他程序调用或使用。当PE文件执行时Windows装载器将文件装入内存并将导入表中登记的DLL文件一并装入,再根据DLL文件中函数的导出信息对可执行文件的导入表(IAT)进行修正。
导出表中包含了三种信息:
- 函数名称:记录了可执行文件中导出函数的名称,在其他程序中调用时需要用到这个名称。
- 函数地址:记录了可执行文件中导出函数的地址,使用时需要调用该函数的地址。
- 函数序号:记录了每个导出函数的序号,可以通过序号直接调用函数。
该步骤需要找到 DLL 文件的导入表位置,解析导入表,遍历到对应的API,这里需要一点PE结构的背景知识。
首先需要知道如何才能正确定位到导出表,导出表存在于PE结构的:
DosHeaders -> NtHeaders -> OptionalHeader -> DataDirectory
数据目录(DataDirectory)的首个结构就是 IMAGE_DIRECTORY_ENTRY_EXPORT 导出表的地址
对应的结构是
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics; // 未使用,总为0
DWORD TimeDateStamp; // 文件创建时间戳
WORD MajorVersion; // 未使用,总为0
WORD MinorVersion; // 未使用,总为0
DWORD Name; // 指向一个代表此 DLL名字的 ASCII字符串的 RVA
DWORD Base; // 函数的起始序号
DWORD NumberOfFunctions; // 导出函数的总数
DWORD NumberOfNames; // 以名称方式导出的函数的总数
DWORD AddressOfFunctions; // 指向输出函数地址的RVA
DWORD AddressOfNames; // 指向输出函数名字的RVA
DWORD AddressOfNameOrdinals; // 指向输出函数序号的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
AddressOfFunctions
函数地址表:存储所有导出函数的实际地址- 所指向内容是以 4 字节为一个单位的数组元素,每个元素代表函数入口。
AddressOfNames
函数名称表:存储函数名称字符串的地址- 所指向内容是以 4 字节为一个单位的数组元素,每个元素代表一个指向字符串的 RVA。
AddressOfNamesOrdinals
序号表:建立名称表和地址表之间的映射关系- 所指向内容是以 2 字节为一个单位的数组元素,每个元素代表对应名字在 AddressOfFunctions 中的序号数。
AddressOfNames
与AddressOfNamesOrdinals
的数值相同,名称和需要数一定是对应的。
首先先从PE结构导出表缀中解析出这三个关键数组:
- 从 DLL Base 开始定位 PE Header:
// DLL Base + 0x3C 可以找到 PE 签名(PE\0\0)的偏移
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dllBase;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(dllBase + pDosHeader->e_lfanew);
- 通过 NT Header 找到导出表目录:
// OptionalHeader 中的 DataDirectory[0] 就是导出表信息
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(
dllBase +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
);
- 从导出表中获取关键数组:
// 获取函数名称数组、序号数组和函数地址数组
PDWORD pFunctionTable = (PDWORD)((BYTE*)dllBase + pExportDir->AddressOfFunctions);
PDWORD pNameTable = (PDWORD)((BYTE*)dllBase + pExportDir->AddressOfNames);
PWORD pOrdinalTable = (PWORD)((BYTE*)dllBase + pExportDir->AddressOfNameOrdinals);
通过函数名匹配来查找入口地址
- 从导出表的 NumberOfNames 字段得到已命名函数的总数
- 遍历名称列表,判断名称是否匹配,记录匹配的索引
- 直接用索引下标访问函数地址表,得到实际的函数地址
// 1. 遍历函数名称表查找匹配
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
// 2. 获取当前函数名称
LPCSTR pCurrentName = (LPCSTR)(dllBase + pNameTable[i]);
// 3. 比较函数名称
if (strcmp(pCurrentName, lpProcName) == 0) {
// 找到匹配的函数名,通过序号表获取函数地址表中的索引
WORD index = pOrdinalTable[i];
// 4. 返回函数地址
return (FARPROC)(dllBase + pFunctionTable[index]);
}
}
由于在shellcode 中无法使用 strcmp,所以需要自己实现一个简易的字符串对比,
// 1. 遍历函数名称表查找匹配
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
// 2. 获取当前函数名称
LPCSTR pCurrentName = (LPCSTR)(dllBase + pNameTable[i]);
SIZE_T ret = 0;
LPSTR tempName = pCurrentName;
LPSTR matchPtr = lpProcName;
// 3. 比较函数名称
while (*tempName && !(ret = *tempName - *matchPtr))
{
tempName++;
matchPtr++;
}
if (ret == 0)
{
// 找到匹配的函数名,通过序号表获取函数地址表中的索引
WORD index = pOrdinalTable[i];
// 4. 返回函数地址
return (FARPROC)(dllBase + pFunctionTable[index]);
}
}
进阶,通过hash查找函数地址
由于在查找函数时无法调用系统API,也不能调用libc中的函数,所以需要一种简单的方法实现判断是否正确找到需要的函数名称以及避免在shellcode中出现明文的函数名字符串。
// 1. 遍历函数名称表查找匹配
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
// 2. 获取当前函数名称
LPCSTR pCurrentName = (LPCSTR)((BYTE*)dllBase + pNameTable[i]);
// 3. 比较函数名称
if (CalculateNameHash(pCurrentName, 0x12345678) == 0) {
// 找到匹配的函数名,通过序号表获取函数地址表中的索引
WORD index = pOrdinalTable[i];
// 4. 返回函数地址
return (FARPROC)((BYTE*)dllBase + pFunctionTable[index]);
}
}
进阶,通过特征码定位
暂无必要,后续待定
第三步:使用 GetProcAddress 定位API
有了 GetProcAddress 和 LoadLibraryA 以后,就可以自由的查找函数了
typedef HMODULE(WINAPI* GetProcAddress_t)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI* LoadLibraryA_t)(_In_ LPCSTR lpLibFileName);
typedef int (WINAPI* MessageBoxA_t)(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType);
GetProcAddress_t fnGetProcAddress = NULL;
LoadLibraryA_t fnLoadLibraryA = NULL;
// GetProcAddress hash: 0x1ACAEE7A
fnGetProcAddress = ParserExportByHash(hKernel32, 0x1ACAEE7A);
// LoadLibraryA hash: 0x8A8B4676
fnLoadLibraryA = ParserExportByHash(hKernel32, 0x8A8B4676);
char str1[] = { 'U','S','E','R','3','2','.','d','l','l','\0' };
char str2[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
char str3[] = { 'h','e','l','l','o',' ','w','o','r','l','d','\0' };
typedef int (WINAPI* MessageBoxA_t)(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType);
MessageBoxA_t pMessageBoxA = (MessageBoxA_t)fnGetProcAddress(fnLoadLibraryA(str1), str2);
if (fnGetProcAddress == NULL || fnLoadLibraryA == NULL) {
__debugbreak();
}
pMessageBoxA(NULL, str3, NULL, NULL);
第四步:提取 ShellCode
有很多种方法提取shellcode,比如将shellcode 编译到一个单独的节中,然后解析PE格式,从节表中扣出对应的shellcode。
我比较喜欢直接在运行时提取,比较方便。
设置为 release 模式,x64架构,关闭工程中的 Security Check(/GS-),关闭编译优化(/Od)
编译优化可能会重排指令顺序和调整代码结构,可能会破坏 shellcode 中的相对跳转地址计算。而Security Check 会插入运行时的栈检查代码,导致无法工作。
将主代码放到 ShellcodeStart函数中,在函数后创建 ShellcodeEnd 函数
这样从 Start -> 到 End 的空间就是整个 shellcode 的内容,
VOID ShellcodeStart() { ... }
VOID ShellcodeEnd() { ... }
在main函数中插入部分代码提取出 shellcode
int main()
{
const UINT_PTR startAdddress = (UINT_PTR)ShellcodeStart;
const UINT_PTR shellcodeSize = (UINT_PTR)ShellcodeEnd - startAdddress;
for (SIZE_T i = 0; i < shellcodeSize; ++i)
{
BYTE singleCode = ((PBYTE)startAdddress)[i];
wprintf(L",0x%02X", singleCode);
}
wprintf(L"\n\nDone!");
return 0;
}
最后解决一些编译警告,出锅!
完整代码
#include <Windows.h>
#include <wchar.h>
typedef HMODULE(WINAPI* GetProcAddress_t)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI* LoadLibraryA_t)(_In_ LPCSTR lpLibFileName);
//0x10 bytes (sizeof)
struct _UNICODE_STRING
{
USHORT Length; //0x0
USHORT MaximumLength; //0x2
WCHAR* Buffer; //0x8
};
//0x58 bytes (sizeof)
struct _PEB_LDR_DATA
{
ULONG Length; //0x0
UCHAR Initialized; //0x4
VOID* SsHandle; //0x8
struct _LIST_ENTRY InLoadOrderModuleList; //0x10
struct _LIST_ENTRY InMemoryOrderModuleList; //0x20
struct _LIST_ENTRY InInitializationOrderModuleList; //0x30
VOID* EntryInProgress; //0x40
UCHAR ShutdownInProgress; //0x48
VOID* ShutdownThreadId; //0x50
};
//0x7c8 bytes (sizeof)
typedef struct _PEB
{
UCHAR InheritedAddressSpace; //0x0
UCHAR ReadImageFileExecOptions; //0x1
UCHAR BeingDebugged; //0x2
union
{
UCHAR BitField; //0x3
struct
{
UCHAR ImageUsesLargePages : 1; //0x3
UCHAR IsProtectedProcess : 1; //0x3
UCHAR IsImageDynamicallyRelocated : 1; //0x3
UCHAR SkipPatchingUser32Forwarders : 1; //0x3
UCHAR IsPackagedProcess : 1; //0x3
UCHAR IsAppContainer : 1; //0x3
UCHAR IsProtectedProcessLight : 1; //0x3
UCHAR IsLongPathAwareProcess : 1; //0x3
};
};
UCHAR Padding0[4]; //0x4
VOID* Mutant; //0x8
VOID* ImageBaseAddress; //0x10
struct _PEB_LDR_DATA* Ldr; //0x18
struct _RTL_USER_PROCESS_PARAMETERS* ProcessParameters; //0x20
VOID* SubSystemData; //0x28
VOID* ProcessHeap; //0x30
struct _RTL_CRITICAL_SECTION* FastPebLock; //0x38
union _SLIST_HEADER* volatile AtlThunkSListPtr; //0x40
VOID* IFEOKey; //0x48
union
{
ULONG CrossProcessFlags; //0x50
struct
{
ULONG ProcessInJob : 1; //0x50
ULONG ProcessInitializing : 1; //0x50
ULONG ProcessUsingVEH : 1; //0x50
ULONG ProcessUsingVCH : 1; //0x50
ULONG ProcessUsingFTH : 1; //0x50
ULONG ProcessPreviouslyThrottled : 1; //0x50
ULONG ProcessCurrentlyThrottled : 1; //0x50
ULONG ProcessImagesHotPatched : 1; //0x50
ULONG ReservedBits0 : 24; //0x50
};
};
UCHAR Padding1[4]; //0x54
union
{
VOID* KernelCallbackTable; //0x58
VOID* UserSharedInfoPtr; //0x58
};
ULONG SystemReserved; //0x60
ULONG AtlThunkSListPtr32; //0x64
VOID* ApiSetMap; //0x68
ULONG TlsExpansionCounter; //0x70
UCHAR Padding2[4]; //0x74
VOID* TlsBitmap; //0x78
ULONG TlsBitmapBits[2]; //0x80
VOID* ReadOnlySharedMemoryBase; //0x88
VOID* SharedData; //0x90
VOID** ReadOnlyStaticServerData; //0x98
VOID* AnsiCodePageData; //0xa0
VOID* OemCodePageData; //0xa8
VOID* UnicodeCaseTableData; //0xb0
ULONG NumberOfProcessors; //0xb8
ULONG NtGlobalFlag; //0xbc
union _LARGE_INTEGER CriticalSectionTimeout; //0xc0
ULONGLONG HeapSegmentReserve; //0xc8
ULONGLONG HeapSegmentCommit; //0xd0
ULONGLONG HeapDeCommitTotalFreeThreshold; //0xd8
ULONGLONG HeapDeCommitFreeBlockThreshold; //0xe0
ULONG NumberOfHeaps; //0xe8
ULONG MaximumNumberOfHeaps; //0xec
VOID** ProcessHeaps; //0xf0
VOID* GdiSharedHandleTable; //0xf8
VOID* ProcessStarterHelper; //0x100
ULONG GdiDCAttributeList; //0x108
UCHAR Padding3[4]; //0x10c
struct _RTL_CRITICAL_SECTION* LoaderLock; //0x110
ULONG OSMajorVersion; //0x118
ULONG OSMinorVersion; //0x11c
USHORT OSBuildNumber; //0x120
USHORT OSCSDVersion; //0x122
ULONG OSPlatformId; //0x124
ULONG ImageSubsystem; //0x128
ULONG ImageSubsystemMajorVersion; //0x12c
ULONG ImageSubsystemMinorVersion; //0x130
UCHAR Padding4[4]; //0x134
ULONGLONG ActiveProcessAffinityMask; //0x138
ULONG GdiHandleBuffer[60]; //0x140
VOID(*PostProcessInitRoutine)(); //0x230
VOID* TlsExpansionBitmap; //0x238
ULONG TlsExpansionBitmapBits[32]; //0x240
ULONG SessionId; //0x2c0
UCHAR Padding5[4]; //0x2c4
union _ULARGE_INTEGER AppCompatFlags; //0x2c8
union _ULARGE_INTEGER AppCompatFlagsUser; //0x2d0
VOID* pShimData; //0x2d8
VOID* AppCompatInfo; //0x2e0
struct _UNICODE_STRING CSDVersion; //0x2e8
struct _ACTIVATION_CONTEXT_DATA* ActivationContextData; //0x2f8
struct _ASSEMBLY_STORAGE_MAP* ProcessAssemblyStorageMap; //0x300
struct _ACTIVATION_CONTEXT_DATA* SystemDefaultActivationContextData; //0x308
struct _ASSEMBLY_STORAGE_MAP* SystemAssemblyStorageMap; //0x310
ULONGLONG MinimumStackCommit; //0x318
VOID* SparePointers[4]; //0x320
ULONG SpareUlongs[5]; //0x340
VOID* WerRegistrationData; //0x358
VOID* WerShipAssertPtr; //0x360
VOID* pUnused; //0x368
VOID* pImageHeaderHash; //0x370
union
{
ULONG TracingFlags; //0x378
struct
{
ULONG HeapTracingEnabled : 1; //0x378
ULONG CritSecTracingEnabled : 1; //0x378
ULONG LibLoaderTracingEnabled : 1; //0x378
ULONG SpareTracingBits : 29; //0x378
};
};
UCHAR Padding6[4]; //0x37c
ULONGLONG CsrServerReadOnlySharedMemoryBase; //0x380
ULONGLONG TppWorkerpListLock; //0x388
struct _LIST_ENTRY TppWorkerpList; //0x390
VOID* WaitOnAddressHashTable[128]; //0x3a0
VOID* TelemetryCoverageHeader; //0x7a0
ULONG CloudFileFlags; //0x7a8
ULONG CloudFileDiagFlags; //0x7ac
CHAR PlaceholderCompatibilityMode; //0x7b0
CHAR PlaceholderCompatibilityModeReserved[7]; //0x7b1
struct _LEAP_SECOND_DATA* LeapSecondData; //0x7b8
union
{
ULONG LeapSecondFlags; //0x7c0
struct
{
ULONG SixtySecondEnabled : 1; //0x7c0
ULONG Reserved : 31; //0x7c0
};
};
ULONG NtGlobalFlag2; //0x7c4
} PEB, * PPEB;
//0x120 bytes (sizeof)
typedef struct _LDR_DATA_TABLE_ENTRY
{
struct _LIST_ENTRY InLoadOrderLinks; //0x0
struct _LIST_ENTRY InMemoryOrderLinks; //0x10
struct _LIST_ENTRY InInitializationOrderLinks; //0x20
VOID* DllBase; //0x30
VOID* EntryPoint; //0x38
ULONG SizeOfImage; //0x40
struct _UNICODE_STRING FullDllName; //0x48
struct _UNICODE_STRING BaseDllName; //0x58
union
{
UCHAR FlagGroup[4]; //0x68
ULONG Flags; //0x68
struct
{
ULONG PackagedBinary : 1; //0x68
ULONG MarkedForRemoval : 1; //0x68
ULONG ImageDll : 1; //0x68
ULONG LoadNotificationsSent : 1; //0x68
ULONG TelemetryEntryProcessed : 1; //0x68
ULONG ProcessStaticImport : 1; //0x68
ULONG InLegacyLists : 1; //0x68
ULONG InIndexes : 1; //0x68
ULONG ShimDll : 1; //0x68
ULONG InExceptionTable : 1; //0x68
ULONG ReservedFlags1 : 2; //0x68
ULONG LoadInProgress : 1; //0x68
ULONG LoadConfigProcessed : 1; //0x68
ULONG EntryProcessed : 1; //0x68
ULONG ProtectDelayLoad : 1; //0x68
ULONG ReservedFlags3 : 2; //0x68
ULONG DontCallForThreads : 1; //0x68
ULONG ProcessAttachCalled : 1; //0x68
ULONG ProcessAttachFailed : 1; //0x68
ULONG CorDeferredValidate : 1; //0x68
ULONG CorImage : 1; //0x68
ULONG DontRelocate : 1; //0x68
ULONG CorILOnly : 1; //0x68
ULONG ChpeImage : 1; //0x68
ULONG ReservedFlags5 : 2; //0x68
ULONG Redirected : 1; //0x68
ULONG ReservedFlags6 : 2; //0x68
ULONG CompatDatabaseProcessed : 1; //0x68
};
};
USHORT ObsoleteLoadCount; //0x6c
USHORT TlsIndex; //0x6e
struct _LIST_ENTRY HashLinks; //0x70
ULONG TimeDateStamp; //0x80
// ...
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;
DWORD CalculateNameHashW(LPWSTR pName, USHORT nameLength);
DWORD CalculateNameHashA(LPCSTR pName);
FARPROC ParserExportByHash(UINT_PTR dllBase, UINT hash);
HMODULE GetKernel32Base();
VOID ShellcodeStart()
{
HMODULE hKernel32 = GetKernel32Base();
if (!hKernel32) {
#ifdef _DEBUG
wprintf(L"Failed to locate Kernel32.dll\n");
#endif
return;
}
#ifdef _DEBUG
wprintf(L"Kernel32.dll base address: 0x%p\n", hKernel32);
#endif
GetProcAddress_t fnGetProcAddress = NULL;
LoadLibraryA_t fnLoadLibraryA = NULL;
// GetProcAddress hash: 0x1ACAEE7A
fnGetProcAddress = (GetProcAddress_t)ParserExportByHash((UINT_PTR)hKernel32, 0x1ACAEE7A);
// LoadLibraryA hash: 0x8A8B4676
fnLoadLibraryA = (LoadLibraryA_t)ParserExportByHash((UINT_PTR)hKernel32, 0x8A8B4676);
char str1[] = { 'U','S','E','R','3','2','.','d','l','l','\0' };
char str2[] = { 'M','e','s','s','a','g','e','B','o','x','A','\0' };
char str3[] = { 'h','e','l','l','o',' ','w','o','r','l','d','\0' };
typedef int (WINAPI* MessageBoxA_t)(_In_opt_ HWND hWnd, _In_opt_ LPCSTR lpText, _In_opt_ LPCSTR lpCaption, _In_ UINT uType);
MessageBoxA_t pMessageBoxA = (MessageBoxA_t)fnGetProcAddress(fnLoadLibraryA(str1), str2);
if (fnGetProcAddress == NULL || fnLoadLibraryA == NULL) {
__debugbreak();
}
pMessageBoxA(NULL, str3, NULL, 0);
}
// Hash calculation function
DWORD CalculateNameHashW(LPWSTR pName, USHORT nameLength)
{
DWORD hashValue = 0;
while (nameLength--) {
hashValue = _rotr(hashValue, 13);
WCHAR currentChar = *pName++;
// Convert to uppercase if lowercase
if (currentChar >= L'a') {
currentChar -= 0x20;
}
hashValue += currentChar;
}
return hashValue;
}
// Hash calculation function for ANSI characters
DWORD CalculateNameHashA(LPCSTR pName)
{
DWORD hashValue = 0;
char currentChar;
while (currentChar = *pName++) {
hashValue = _rotr(hashValue, 13);
// Convert to uppercase if lowercase
if (currentChar >= 'a') {
currentChar -= 0x20;
}
hashValue += currentChar;
}
return hashValue;
}
// Get Kernel32 base address through PEB
HMODULE GetKernel32Base() {
const DWORD KERNEL32_HASH = 0x6E2BCA17;
PPEB pPeb = NULL;
HMODULE hKernel32 = NULL;
// Get PEB address from FS register
pPeb = (PPEB)__readgsqword(0x60);
if (!pPeb || !pPeb->Ldr) {
return NULL;
}
// Get first entry of InMemoryOrderModuleList
PLIST_ENTRY pCurrentEntry = pPeb->Ldr->InMemoryOrderModuleList.Flink;
PLDR_DATA_TABLE_ENTRY pCurrentModule = NULL;
// Iterate through loaded modules
while (pCurrentEntry != &pPeb->Ldr->InMemoryOrderModuleList) {
pCurrentModule = CONTAINING_RECORD(pCurrentEntry, LDR_DATA_TABLE_ENTRY, InMemoryOrderLinks);
if (pCurrentModule->BaseDllName.Buffer && pCurrentModule->BaseDllName.Length) {
#ifdef _DEBUG
wprintf(L"Checking module: %s, ", pCurrentModule->BaseDllName.Buffer);
#endif
// Calculate hash of current module name
DWORD currentHash = CalculateNameHashW(
pCurrentModule->BaseDllName.Buffer,
pCurrentModule->BaseDllName.Length / sizeof(WCHAR)
);
#ifdef _DEBUG
wprintf(L"Hash: 0x%08X\n", currentHash);
#endif
// Check if this is Kernel32.dll
if (currentHash == KERNEL32_HASH) {
hKernel32 = (HMODULE)pCurrentModule->DllBase;
break;
}
}
pCurrentEntry = pCurrentEntry->Flink;
}
return hKernel32;
}
FARPROC ParserExportByName(UINT_PTR dllBase, LPCSTR lpProcName)
{
// DLL Base + 0x3C 可以找到 PE 签名(PE\0\0)的偏移
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dllBase;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(dllBase + pDosHeader->e_lfanew);
// OptionalHeader 中的 DataDirectory[0] 就是导出表信息
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(
dllBase +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
);
// 获取函数名称数组、序号数组和函数地址数组
PDWORD pFunctionTable = (PDWORD)(dllBase + pExportDir->AddressOfFunctions);
PDWORD pNameTable = (PDWORD)(dllBase + pExportDir->AddressOfNames);
PWORD pOrdinalTable = (PWORD)(dllBase + pExportDir->AddressOfNameOrdinals);
// 1. 遍历函数名称表查找匹配
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
// 2. 获取当前函数名称
LPCSTR pCurrentName = (LPCSTR)(dllBase + pNameTable[i]);
SIZE_T ret = 0;
LPCSTR tempName = pCurrentName;
LPCSTR matchPtr = lpProcName;
// 3. 比较函数名称
while (*tempName && !(ret = *tempName - *matchPtr))
{
tempName++;
matchPtr++;
}
if (ret == 0)
{
// 找到匹配的函数名,通过序号表获取函数地址表中的索引
WORD index = pOrdinalTable[i];
// 4. 返回函数地址
return (FARPROC)(dllBase + pFunctionTable[index]);
}
}
return NULL;
}
FARPROC ParserExportByHash(UINT_PTR dllBase, UINT hash)
{
// DLL Base + 0x3C 可以找到 PE 签名(PE\0\0)的偏移
PIMAGE_DOS_HEADER pDosHeader = (PIMAGE_DOS_HEADER)dllBase;
PIMAGE_NT_HEADERS pNtHeaders = (PIMAGE_NT_HEADERS)(dllBase + pDosHeader->e_lfanew);
// OptionalHeader 中的 DataDirectory[0] 就是导出表信息
PIMAGE_EXPORT_DIRECTORY pExportDir = (PIMAGE_EXPORT_DIRECTORY)(
dllBase +
pNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress
);
// 获取函数名称数组、序号数组和函数地址数组
PDWORD pFunctionTable = (PDWORD)(dllBase + pExportDir->AddressOfFunctions);
PDWORD pNameTable = (PDWORD)(dllBase + pExportDir->AddressOfNames);
PWORD pOrdinalTable = (PWORD)(dllBase + pExportDir->AddressOfNameOrdinals);
// 1. 遍历函数名称表查找匹配
for (DWORD i = 0; i < pExportDir->NumberOfNames; i++) {
// 2. 获取当前函数名称
LPCSTR pCurrentName = (LPCSTR)(dllBase + pNameTable[i]);
if (CalculateNameHashA(pCurrentName) == hash)
{
// 找到匹配的函数名,通过序号表获取函数地址表中的索引
WORD index = pOrdinalTable[i];
// 4. 返回函数地址
return (FARPROC)(dllBase + pFunctionTable[index]);
}
}
return NULL;
}
VOID ShellcodeEnd()
{
__debugbreak();
}
int main()
{
const UINT_PTR startAdddress = (UINT_PTR)ShellcodeStart;
const UINT_PTR shellcodeSize = (UINT_PTR)ShellcodeEnd - startAdddress;
for (SIZE_T i = 0; i < shellcodeSize; ++i)
{
BYTE singleCode = ((PBYTE)startAdddress)[i];
wprintf(L",0x%02X", singleCode);
}
wprintf(L"\n\nDone!");
return 0;
}
参考: