做公司简介网站,做婚恋网站怎么样,电商运营基本知识,大连专业企业建站找哪家在正常情况下#xff0c;要想使用GetProcAddress函数#xff0c;需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址#xff0c;接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址#xff0c;但在有时这个函…在正常情况下要想使用GetProcAddress函数需要首先调用LoadLibraryA函数获取到kernel32.dll动态链接库的内存地址接着在调用GetProcAddress函数时传入模块基址以及模块中函数名即可动态获取到特定函数的内存地址但在有时这个函数会被保护起来导致我们无法直接调用该函数获取到特定函数的内存地址此时就需要自己编写实现LoadLibrary以及GetProcAddress函数该功能的实现需要依赖于PEB线程环境块通过线程环境块可遍历出kernel32.dll模块的入口地址接着就可以在该模块中寻找GetProcAddress函数入口地址当找到该入口地址后即可直接调用实现动态定位功能。
首先通过PEB/TEB找到自身进程的所有载入模块数据获取TEB也就是线程环境块。在编程的时候TEB始终保存在寄存器 FS 中。
0:000 !teb
TEB at 00680000ExceptionList: 008ff904StackBase: 00900000StackLimit: 008fc000RpcHandle: 00000000Tls Storage: 0068002cPEB Address: 0067d0000:000 dt _teb 00680000
ntdll!_TEB0x000 NtTib : _NT_TIB0x01c EnvironmentPointer : (null) 0x020 ClientId : _CLIENT_ID0x028 ActiveRpcHandle : (null) 0x02c ThreadLocalStoragePointer : 0x0068002c Void0x030 ProcessEnvironmentBlock : 0x0067d000 _PEB // 偏移为30,PEB从该命令的输出可以看出PEB 结构体的地址位于 TEB 结构体偏移0x30 的位置该位置保存的地址是 0x0067d000。也就是说PEB 的地址是 0x0067d000通过该地址来解析 PEB并获得 LDR结构。
0:000 dt nt!_peb 0x0067d000
ntdll!_PEB0x000 InheritedAddressSpace : 0 0x001 ReadImageFileExecOptions : 0 0x002 BeingDebugged : 0x1 0x003 BitField : 0x4 0x003 ImageUsesLargePages : 0y00x003 IsProtectedProcess : 0y00x003 IsImageDynamicallyRelocated : 0y10x003 SkipPatchingUser32Forwarders : 0y00x003 IsPackagedProcess : 0y00x003 IsAppContainer : 0y00x003 IsProtectedProcessLight : 0y00x003 IsLongPathAwareProcess : 0y00x004 Mutant : 0xffffffff Void0x008 ImageBaseAddress : 0x00f30000 Void0x00c Ldr : 0x774c0c40 _PEB_LDR_DATA // LDR从如上输出结果可以看出LDR 在 PEB 结构体偏移的 0x0C 处该地址保存的地址是 0x774c0c40 通过该地址来解析 LDR 结构体。WinDBG 输出如下内容
0:000 dt _peb_ldr_data 0x774c0c40
ntdll!_PEB_LDR_DATA0x000 Length : 0x300x004 Initialized : 0x1 0x008 SsHandle : (null) 0x00c InLoadOrderModuleList : _LIST_ENTRY [ 0x9e3208 - 0x9e5678 ]0x014 InMemoryOrderModuleList : _LIST_ENTRY [ 0x9e3210 - 0x9e5680 ]0x01c InInitializationOrderModuleList : _LIST_ENTRY [ 0x9e3110 - 0x9e35f8 ]0x024 EntryInProgress : (null) 0x028 ShutdownInProgress : 0 0x02c ShutdownThreadId : (null) 0:000 dt _LIST_ENTRY
ntdll!_LIST_ENTRY0x000 Flink : Ptr32 _LIST_ENTRY0x004 Blink : Ptr32 _LIST_ENTRY现在来手动遍历第一条链表输入命令0x9e3208在链表偏移 0x18 的位置是模块的映射地址即 ImageBase在链表 偏移 0x28 的位置是模块的路径及名称的地址在链表偏移 0x30 的位置是模块名称的地址。
0:000 dd 0x9e3208
009e3208 009e3100 774c0c4c 009e3108 774c0c54
009e3218 00000000 00000000 00f30000 00f315bb
009e3228 00007000 00180016 009e1fd4 00120010
009e3238 009e1fda 000022cc 0000ffff 774c0b080:000 du 009e1fd4
009e1fd4 C:\main.exe
0:000 du 009e1fda
009e1fda main.exe读者可自行验证如下所示的确是模块的名称。既然是链表就来下一条链表的信息,009e3100保存着下一个链表结构。依次遍历就是了。
0:000 dd 009e3100
009e3100 009e35e8 009e3208 009e35f0 009e3210
009e3110 009e39b8 774c0c5c 773a0000 00000000
009e3120 0019c000 003c003a 009e2fe0 001400120:000 du 009e2fe0
009e2fe0 C:\Windows\SYSTEM32\ntdll.dll上述地址009e3100介绍的结构是微软保留结构只能从网上找到一个结构定义然后自行看着解析就好了。
typedef struct _LDR_DATA_TABLE_ENTRY
{PVOID Reserved1[2];LIST_ENTRY InMemoryOrderLinks;PVOID Reserved2[2];PVOID DllBase;PVOID EntryPoint;PVOID Reserved3;UNICODE_STRING FullDllName;BYTE Reserved4[8];PVOID Reserved5[3];union {ULONG CheckSum;PVOID Reserved6;};ULONG TimeDateStamp;
} LDR_DATA_TABLE_ENTRY, *PLDR_DATA_TABLE_ENTRY; 根据如上流程想要得到kernel32.dll模块的入口地址我们可以进行这几步首先得到TEB地址并在该地址中寻找PEB线程环境块并在该环境块内得到LDR结构在该结构中获取第二条链表地址输出该链表中的0x10以及0x20即可得到当前模块的基地址以及完整的模块路径信息该功能的实现分为32位于64位如下代码则是实现代码。
#include iostream
#include Windows.h// 将不同的节压缩为单一的节
#pragma comment(linker, /merge:.data.text)
#pragma comment(linker, /merge:.rdata.text)
#pragma comment(linker, /section:.text,RWE)// 得到32位模式下kernel32.dll地址
DWORD GetModuleKernel32()
{DWORD *PEB NULL, *Ldr NULL, *Flink NULL, *p NULL;DWORD *BaseAddress NULL, *FullDllName NULL;__asm{mov eax, fs:[0x30] // FS保存着TEBmov PEB, eax // 30定位到PEB}// 得到LDRLdr *((DWORD **)((unsigned char *)PEB 0x0c));// 在LDR基础上找到第二条链表Flink *((DWORD **)((unsigned char *)Ldr 0x14));p Flink;p *((DWORD **)p);// 计数器int count 0;while (Flink ! p){BaseAddress *((DWORD **)((unsigned char *)p 0x10));FullDllName *((DWORD **)((unsigned char *)p 0x20));if (BaseAddress 0)break;// printf(镜像基址 %08x \r\n 模块路径 %S \r\n, BaseAddress, (unsigned char *)FullDllName);// 第二个模块是kernel32.dllif (count 1){// printf(address %x \n, BaseAddress);return reinterpret_castDWORD(BaseAddress);}p *((DWORD **)p);count count 1;}// 未找到Kernel32模块return 0;
}// 获取64位模式下的kernel32.dll基址
ULONGLONG GetModuleKernel64()
{ULONGLONG dwKernel32Addr 0;// 获取TEB的地址_TEB* pTeb NtCurrentTeb();// 获取PEB的地址PULONGLONG pPeb (PULONGLONG)*(PULONGLONG)((ULONGLONG)pTeb 0x60);// 获取PEB_LDR_DATA结构的地址PULONGLONG pLdr (PULONGLONG)*(PULONGLONG)((ULONGLONG)pPeb 0x18);// 模块初始化链表的头指针InInitializationOrderModuleListPULONGLONG pInLoadOrderModuleList (PULONGLONG)((ULONGLONG)pLdr 0x10);// 获取链表中第一个模块信息exe模块PULONGLONG pModuleExe (PULONGLONG)*pInLoadOrderModuleList;//printf(EXE Base %X \n, pModuleExe[6]);// 获取链表中第二个模块信息ntdll模块PULONGLONG pModuleNtdll (PULONGLONG)*pModuleExe;//printf(Ntdll Base %X \n, pModuleNtdll[6]);// 获取链表中第三个模块信息Kernel32模块PULONGLONG pModuleKernel32 (PULONGLONG)*pModuleNtdll;//printf(Kernel32 Base %X \n, pModuleKernel32[6]);// 获取kernel32基址dwKernel32Addr pModuleKernel32[6];return dwKernel32Addr;
}int main(int argc, char *argv[])
{// 输出32位kernel32DWORD kernel32BaseAddress GetModuleKernel32();std::cout kernel32 std::hex kernel32BaseAddress std::endl;// 输出64位kernel32ULONGLONG kernel64BaseAddress GetModuleKernel64();std::cout kernel64 std::hex kernel32BaseAddress std::endl;system(pause);return 0;
}如上代码中分别实现了32位于64位两种获取内存模块基址GetModuleKernel32用于获取32位模式GetModuleKernel64则用于获取64位内存基址读者可自行调用两种模式输出如下图所示 我们通过调用GetModuleKernel32()函数读入kernel32.dll模块入口地址后则下一步就可以通过循环遍历该模块的导出表并寻找到GetProcAddress导出函数地址找到该导出函数内存地址后则可以通过kernel32模块基址加上dwFunAddrOffset相对偏移获取到该函数的内存地址此时通过函数指针就可以将该函数地址读入到内存指针内。
// 封装基地址获取功能
ULONGLONG MyGetProcAddress()
{// 获取32位基址ULONGLONG dwBase GetModuleKernel32();// 获取64位基址// ULONGLONG dwBase GetModuleKernel64();// 获取DOS头PIMAGE_DOS_HEADER pDos (PIMAGE_DOS_HEADER)dwBase;// 获取32位NT头PIMAGE_NT_HEADERS pNt (PIMAGE_NT_HEADERS)(dwBase pDos-e_lfanew);// 获取64位NT头// PIMAGE_NT_HEADERS64 pNt (PIMAGE_NT_HEADERS64)(dwBase pDos-e_lfanew);// 获取数据目录表PIMAGE_DATA_DIRECTORY pExportDir pNt-OptionalHeader.DataDirectory;pExportDir (pExportDir[IMAGE_DIRECTORY_ENTRY_EXPORT]);DWORD dwOffset pExportDir-VirtualAddress;// 获取导出表信息结构PIMAGE_EXPORT_DIRECTORY pExport (PIMAGE_EXPORT_DIRECTORY)(dwBase dwOffset);DWORD dwFunCount pExport-NumberOfFunctions;DWORD dwFunNameCount pExport-NumberOfNames;DWORD dwModOffset pExport-Name;// 获取导出地址表PDWORD pEAT (PDWORD)(dwBase pExport-AddressOfFunctions);// 获取导出名称表PDWORD pENT (PDWORD)(dwBase pExport-AddressOfNames);// 获取导出序号表PWORD pEIT (PWORD)(dwBase pExport-AddressOfNameOrdinals);for (DWORD dwOrdinal 0; dwOrdinal dwFunCount; dwOrdinal){if (!pEAT[dwOrdinal]){continue;}// 获取序号DWORD dwID pExport-Base dwOrdinal;// 获取导出函数地址ULONGLONG dwFunAddrOffset pEAT[dwOrdinal];for (DWORD dwIndex 0; dwIndex dwFunNameCount; dwIndex){// 在序号表中查找函数的序号if (pEIT[dwIndex] dwOrdinal){// 根据序号索引到函数名称表中的名字ULONGLONG dwNameOffset pENT[dwIndex];char* pFunName (char*)((ULONGLONG)dwBase dwNameOffset);if (!strcmp(pFunName, GetProcAddress)){// 根据函数名称返回函数地址return dwBase dwFunAddrOffset;}}}}return 0;
}// 定义名称指针
typedef ULONGLONG(WINAPI *fnGetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI *fnLoadLibraryA)(_In_ LPCSTR lpLibFileName);int main(int argc, char *argv[])
{DWORD kernel32BaseAddress GetModuleKernel32();if (kernel32BaseAddress 0){return 0;}// 获取kernel32基址/获取GetProcAddress的基址fnGetProcAddress pfnGetProcAddress (fnGetProcAddress)MyGetProcAddress();std::cout pfnGetProcAddress std::endl;// 获取Kernel32核心API地址fnLoadLibraryA pfnLoadLibraryA (fnLoadLibraryA)pfnGetProcAddress((HMODULE)kernel32BaseAddress, LoadLibraryA);printf(自定义读入LoadLibrary %x \n, pfnLoadLibraryA);system(pause);return 0;
}输出效果如下图所示我们即可读入fnLoadLibraryA函数的内存地址 上述代码的使用也很简单当我们能够得到GetProcAddress的内存地址后就可以使用该内存地址动态定位到任意一个函数地址我们通过得到LoadLibrary函数地址与GetModuleHandleA函数地址通过两个函数就可以定位到Windows系统内任意一个函数我们以调用MessageBox弹窗为例动态输出一个弹窗该调用方式如下所示。
// 定义名称指针
typedef ULONGLONG(WINAPI *fnGetProcAddress)(_In_ HMODULE hModule, _In_ LPCSTR lpProcName);
typedef HMODULE(WINAPI *fnLoadLibraryA)(_In_ LPCSTR lpLibFileName);
typedef int(WINAPI *fnMessageBox)(HWND hWnd, LPSTR lpText, LPSTR lpCaption, UINT uType);
typedef HMODULE(WINAPI *fnGetModuleHandleA)(_In_opt_ LPCSTR lpModuleName);
typedef BOOL(WINAPI *fnVirtualProtect)(_In_ LPVOID lpAddress, _In_ SIZE_T dwSize, _In_ DWORD flNewProtect, _Out_ PDWORD lpflOldProtect);
typedef void(WINAPI *fnExitProcess)(_In_ UINT uExitCode);int main(int argc, char * argv[])
{// 获取kernel32基址 / 获取GetProcAddress的基址fnGetProcAddress pfnGetProcAddress (fnGetProcAddress)MyGetProcAddress();ULONGLONG dwBase GetModuleKernel32();printf(fnGetProcAddress %x \n, pfnGetProcAddress);printf(GetKernel32Addr %x \n, dwBase);// 获取Kernel32核心API地址fnLoadLibraryA pfnLoadLibraryA (fnLoadLibraryA)pfnGetProcAddress((HMODULE)dwBase, LoadLibraryA);printf(pfnLoadLibraryA %x \n, pfnLoadLibraryA);fnGetModuleHandleA pfnGetModuleHandleA (fnGetModuleHandleA)pfnGetProcAddress((HMODULE)dwBase, GetModuleHandleA);printf(pfnGetModuleHandleA %x \n, pfnGetModuleHandleA);fnVirtualProtect pfnVirtualProtect (fnVirtualProtect)pfnGetProcAddress((HMODULE)dwBase, VirtualProtect);printf(pfnVirtualProtect %x \n, pfnVirtualProtect);// 有了核心API之后,即可获取到User32.dll的基地址pfnLoadLibraryA(User32.dll);HMODULE hUser32 (HMODULE)pfnGetModuleHandleA(User32.dll);fnMessageBox pfnMessageBoxA (fnMessageBox)pfnGetProcAddress(hUser32, MessageBoxA);printf(User32 %x \t MessageBox %x \n, hUser32, pfnMessageBoxA);HMODULE hKernel32 (HMODULE)pfnGetModuleHandleA(kernel32.dll);fnExitProcess pfnExitProcess (fnExitProcess)pfnGetProcAddress(hKernel32, ExitProcess);printf(Kernel32 %x \t ExitProcess %x \n, hKernel32, pfnExitProcess);// 弹出信息框int nRet pfnMessageBoxA(NULL, hello lyshark, MsgBox, MB_YESNO);if (nRet IDYES){printf(你点击了YES \n);}system(pause);pfnExitProcess(0);return 0;
}运行上述代码通过动态调用的方式获取到MessageBox函数内存地址并将该内存放入到pfnMessageBoxA指针内最后直接调用该指针即可输出如下图所示的效果图