通过PEB寻找函数地址
通过PEB的Ldr参数(结构体定义为_PEB_LDR_DATA),遍历当前进程加载的模块信息链表,找到目标模块。
摘自PEB LDR DATA:
typedef struct _PEB_LDR_DATA
{
0x00 ULONG Length; /* Size of structure, used by ntdll.dll as structure version ID */
0x04 BOOLEAN Initialized; /* If set, loader data section for current process is initialized */
0x08 PVOID SsHandle;
0x0c LIST_ENTRY InLoadOrderModuleList; /* Pointer to LDR_DATA_TABLE_ENTRY structure. Previous and next module in load order */
0x14 LIST_ENTRY InMemoryOrderModuleList; /* Pointer to LDR_DATA_TABLE_ENTRY structure. Previous and next module in memory placement order */
0x1c LIST_ENTRY InInitializationOrderModuleList; /* Pointer to LDR_DATA_TABLE_ENTRY structure. Previous and next module in initialization order */
} PEB_LDR_DATA, *PPEB_LDR_DATA; // +0x24
_PEB_LDR_DATA结构体中的InLoadOrderModuleList、InMemoryOrderModuleList、InInitializationOrderModuleList指向一个当前进程加载模块的链表,链表的每个结点都被定义为_LIST_ENTRY类型的结构体,三条链表以不同方式串连,加载顺序、内存分布顺序、初始化顺序。
_LIST_ENTRY:
0:000> dt ntdll!_LIST_ENTRY
+0x000 Flink : Ptr32 _LIST_ENTRY
+0x004 Blink : Ptr32 _LIST_ENTRY
其中Flink指向下一结点,尾部结点的Flink则指向头部;Blink指向前一结点,首部节点指向尾部结点;所以该链表结构就是一个双向循环链表。
除头结点外,_LIST_ENTRY结构体中的两个指针都指向一个_LDR_DATA_TABLE_ENTRY结构体,看这情况也就是说_LDR_DATA_TABLE_ENTRY头部为_LIST_ENTRY咯?该结构体含有当前结点对应的模块的许多信息,根据成员BaseDllName匹配需要的已加载模块,再由DllBase得到句柄。
在通过InLoadOrderLinks进行模块查找时,Flink或者Blink可直接作为_LDR_DATA_TABLE_ENTRY地址;如果通过InMemoryOrderLinks 或InInitializationOrderLinks 进行匹配时,需要将F(B)link地址偏移-0x08或-0x10作为地址,与两者在_LDR_DATA_TABLE_ENTRY结构体中的偏移相对应。
0:000> dt ntdll!_LDR_DATA_TABLE_ENTRY
+0x000 InLoadOrderLinks : _LIST_ENTRY
+0x008 InMemoryOrderLinks : _LIST_ENTRY
+0x010 InInitializationOrderLinks : _LIST_ENTRY
+0x018 DllBase : Ptr32 Void
+0x01c EntryPoint : Ptr32 Void
+0x020 SizeOfImage : Uint4B
+0x024 FullDllName : _UNICODE_STRING
+0x02c BaseDllName : _UNICODE_STRING
+0x034 FlagGroup : [4] UChar
+0x034 Flags : Uint4B
+0x034 PackagedBinary : Pos 0, 1 Bit
+0x034 MarkedForRemoval : Pos 1, 1 Bit
+0x034 ImageDll : Pos 2, 1 Bit
+0x034 LoadNotificationsSent : Pos 3, 1 Bit
+0x034 TelemetryEntryProcessed : Pos 4, 1 Bit
+0x034 ProcessStaticImport : Pos 5, 1 Bit
+0x034 InLegacyLists : Pos 6, 1 Bit
+0x034 InIndexes : Pos 7, 1 Bit
+0x034 ShimDll : Pos 8, 1 Bit
+0x034 InExceptionTable : Pos 9, 1 Bit
+0x034 ReservedFlags1 : Pos 10, 2 Bits
+0x034 LoadInProgress : Pos 12, 1 Bit
+0x034 LoadConfigProcessed : Pos 13, 1 Bit
+0x034 EntryProcessed : Pos 14, 1 Bit
+0x034 ProtectDelayLoad : Pos 15, 1 Bit
+0x034 ReservedFlags3 : Pos 16, 2 Bits
+0x034 DontCallForThreads : Pos 18, 1 Bit
+0x034 ProcessAttachCalled : Pos 19, 1 Bit
+0x034 ProcessAttachFailed : Pos 20, 1 Bit
+0x034 CorDeferredValidate : Pos 21, 1 Bit
+0x034 CorImage : Pos 22, 1 Bit
+0x034 DontRelocate : Pos 23, 1 Bit
+0x034 CorILOnly : Pos 24, 1 Bit
+0x034 ChpeImage : Pos 25, 1 Bit
+0x034 ReservedFlags5 : Pos 26, 2 Bits
+0x034 Redirected : Pos 28, 1 Bit
+0x034 ReservedFlags6 : Pos 29, 2 Bits
+0x034 CompatDatabaseProcessed : Pos 31, 1 Bit
+0x038 ObsoleteLoadCount : Uint2B
+0x03a TlsIndex : Uint2B
+0x03c HashLinks : _LIST_ENTRY
+0x044 TimeDateStamp : Uint4B
+0x048 EntryPointActivationContext : Ptr32 _ACTIVATION_CONTEXT
+0x04c Lock : Ptr32 Void
+0x050 DdagNode : Ptr32 _LDR_DDAG_NODE
+0x054 NodeModuleLink : _LIST_ENTRY
+0x05c LoadContext : Ptr32 _LDRP_LOAD_CONTEXT
+0x060 ParentDllBase : Ptr32 Void
+0x064 SwitchBackContext : Ptr32 Void
+0x068 BaseAddressIndexNode : _RTL_BALANCED_NODE
+0x074 MappingInfoIndexNode : _RTL_BALANCED_NODE
+0x080 OriginalBase : Uint4B
+0x088 LoadTime : _LARGE_INTEGER
+0x090 BaseNameHashValue : Uint4B
+0x094 LoadReason : _LDR_DLL_LOAD_REASON
+0x098 ImplicitPathOptions : Uint4B
+0x09c ReferenceCount : Uint4B
+0x0a0 DependentLoadFlags : Uint4B
+0x0a4 SigningLevel : UChar
测试不调用系统API,利用PEB寻找模块,并通过模块寻找目标函数;这种情况大多是在Shellcode中用到,比方说恶意程序、病毒等;在许多情况下shellcode通常作为独立代码执行,不被加载器基址重定位,也无法直接调用API,所以通过PEB查找目标模块,进而查找目标函数,通常首先都会获取LoadLibraryA和GetProcAddress地址,便于之后直接加载指定模块,获取导出函数并调用。
写的时候我发现从函数序数表得到的函数序号减去序号基数base会得到不正确结果,不减则正确,代码调试时得到base值为1。
导出表结构:
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;
DWORD TimeDateStamp;
WORD MajorVersion;
WORD MinorVersion;
DWORD Name;
DWORD Base;
DWORD NumberOfFunctions;
DWORD NumberOfNames;
DWORD AddressOfFunctions; // RVA from base of image
DWORD AddressOfNames; // RVA from base of image
DWORD AddressOfNameOrdinals; // RVA from base of image
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
测试代码,写的时候在通过模块获取函数地址的时候没用汇编,要提二进制码还得重写这个部分;不过顺便温习一下导出表结构。
#include "windows.h"
#include "stdio.h"
//typedef void(*func)();
VOID WINAPI Lower(WCHAR* s) {
WCHAR* pos = s;
for (; *pos; pos++) {
if (*pos <= 'Z' && *pos >= 'A')
*pos |= 0x20;
}
//printf("\t==lower string : %ws\n", s);
}
BOOL WINAPI __strcmpW(WCHAR* a, WCHAR *b) {
//printf("\tcompared dll name: %ws\n\n", b);
int i = 0;
for (i = 0; a[i] || b[i]; i++)
if (a[i] != b[i])
return FALSE;
return TRUE;
}
HMODULE WINAPI FindModuleByPeb(WCHAR* targetModule) {
WCHAR dllName[50] = { 0 };
BOOL foundModule = FALSE;
DWORD dllBase = NULL;
printf("[#] start get module handle\n");
/*
通过PEB结构中的Ldr寻找到InLoadOrderModuleList,遍历寻找已加载的模块,通过模块名进行寻找
*/
__asm {
push targetModule
call Lower
mov eax, fs:[30h] // eax <- peb
mov eax, [eax + 0ch] // eax <- Ldr _PEB_LDR_DATA
mov eax, [eax + 0ch] // eax <- first Flink address, InLoadOrderModuleList [Type: _LIST_ENTRY]
_LOOP :
push eax
mov eax, [eax + 2ch + 4] // dll name string address
cmp eax, 0
jz _END // 字符串为NULL,说明寻找完毕,退出
lea ebx, dllName
push ebx // for calling compare
push ebx // for calling lower string
_COPYNAME :
mov dl, byte ptr[eax]
mov byte ptr[ebx], dl // copy name
add ebx, 2
add eax, 2
cmp[eax], 0
jnz _COPYNAME
mov[ebx], 0
call Lower // lower dll name string
push targetModule
call __strcmpW // compare dll name
cmp al, 1
jz _FOUND
pop eax
mov eax, [eax] // next Flink
jmp _LOOP // if not found, go to next flink and loop again
_FOUND :
pop eax
push DWORD ptr[eax + 18h] // save dllBase
pop dllBase
mov foundModule, 1 // found target dll
_END :
}
if (foundModule) {
printf("\t[ok] Have found target module :)\n");
printf("\t\tDllBase : %#x\n\t\tDll Name: %ws\n\n", dllBase, targetModule);
}
else
printf("\t[no] Not found :(\n\n");
return (HMODULE)dllBase;
}
func WINAPI GetProcByhMod(HMODULE hMod, WCHAR* procName) {
PIMAGE_DOS_HEADER pIDH = NULL; //DOS 头
PIMAGE_NT_HEADERS pINH = NULL; // NT头
PIMAGE_DATA_DIRECTORY pIDD = NULL; // 数据目录表
PIMAGE_EXPORT_DIRECTORY pIED = NULL; // 导出表
INT i = 0, length = 0;
WORD ordinal = -1;
DWORD funcAddr = NULL;
WCHAR funcName[60] = { 0 }; // 函数名字
CHAR *name = NULL;
pIDH = (PIMAGE_DOS_HEADER)hMod;
printf("[#]start Get Library By found module handle\n");
if ((WORD)pIDH->e_magic == 0x5a4d) // magic值 MZ
printf("\tMatch \"MZ\" magic :)\n");
else
printf("\tNot Match \"MZ\" magic :(\n");
pINH = (PIMAGE_NT_HEADERS)(pIDH->e_lfanew+(DWORD)hMod);
/*
printf("offset : %#x\n", pIDH->e_lfanew);
printf("Image Base : %#x\n", hMod);
printf("PIMAGE_NT_HEADERS value : %#x\n", pINH);
*/
if ((WORD)pINH->Signature == 0x4550) // 签名 PE
printf("\tMatch \"PE\" signature :)\n");
else
printf("\tNot Match \"PE\" signature :(\n");
pIDD = (PIMAGE_DATA_DIRECTORY)((pINH->OptionalHeader).DataDirectory); // 数据目录表
pIED = (PIMAGE_EXPORT_DIRECTORY)(pIDD[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress + (DWORD)hMod);
printf("\texport table VA : %#x\n\tfunction names array address : %#x\n", (DWORD)pIED, pIED->AddressOfNames + (DWORD)hMod);
Lower(procName); //
for (i = 0; i < pIED->NumberOfNames; i++) {
name = (CHAR*)(*((DWORD*)(pIED->AddressOfNames + (DWORD)hMod) + i) + (DWORD)hMod);
for (length = 0; name[length]; length++); // 函数名长度
/*printf("==> %s\n", name);
通过functionames数组获取下标,根据该下标(输出函数名表和输出序号表一一对应)在输出序号表
获取函数地址表中的序号,将序号减去基数作为下标寻找到函数地址RVA。
*/
MultiByteToWideChar(CP_ACP, NULL, name, ++length, funcName, length);
//printf("\tcompared function name : %ws\n", funcName);
Lower(funcName);
if (__strcmpW(procName, funcName)) {
printf("\t[ok] succeedfound function name :)\n");
ordinal = *((WORD*)(pIED->AddressOfNameOrdinals + (DWORD)hMod) + i); // WORD
printf("\t\tindex of target function : %#x\n\t\tordinal number : %#x\n\t\torinal base : %#x\n", i, ordinal, pIED->Base);
funcAddr = *((DWORD*)(pIED->AddressOfFunctions + (DWORD)hMod) + (ordinal/* - pIED->Base加上之后不对*/)) + (DWORD)hMod;
printf("\tGet function address : %#x\n", funcAddr);
break;
}
}
if (!funcAddr)
printf("\t[no] not Found target function :(");
return (func)funcAddr;
}
INT main(INT argc, CHAR* argv[]) {
WCHAR searchMod[] = { L"Kernel32.dll" };
WCHAR procLoadlib[] = { L"LoadLibraryA" };
WCHAR procGetProc[] = { L"GetProcAddress" };
//func procAddr = NULL;
//
CHAR tarMod[] = { "User32.dll" };
CHAR targFunc[] = { "MessageBoxA" }; // 测试弹窗
CHAR test[] = { "test" };/////
/*HMODULE hMod = LoadLibraryA(tarMod);
typedef int (*msgBoxProc)(HWND, LPCTSTR, LPCTSTR, UINT);
msgBoxProc f = (msgBoxProc)GetProcAddress(hMod, targFunc);
f(NULL, (LPCTSTR)"test", (LPCTSTR)"test", MB_OK);*/
HMODULE hMod = FindModuleByPeb(searchMod);
if (hMod) {
__asm {
lea eax, procLoadlib
push eax //LoadLibraryA
push hMod
call GetProcByhMod
cmp eax, 0
jz _END2
mov ebx,eax
lea eax, tarMod // target mod; user32.dll
push eax
call ebx // call LoadLibraryA
cmp eax,0
jz _END2
push eax // save hInstance value
lea eax,procGetProc // string GetProcAddress
push eax
push hMod
call GetProcByhMod
cmp eax, 0
jz _END2
mov ebx, eax
lea eax, targFunc
pop edx
push eax // messageboxa
push edx // target hMod
call ebx // call getprocaddress
cmp eax, 0
jz _END2
mov ebx, eax
push MB_OK
lea eax, test
push eax
push eax
push 0 // param for messagebox
call ebx // call got api - messageboxA
_END2:
}
}
}
通过PEB寻找函数地址的更多相关文章
- 【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取
其实分在逆向篇不太合适,因为并没有逆向什么程序. 在http://www.exploit-db.com/exploits/28996/上看到这么一段最简单的ShellCode,其中的技术也是比较常见的 ...
- hash算法搜索获得api函数地址的实现,"kernel32.dll", "CreateThread"
我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较.这样弊端很 ...
- Delphi中使用@取函数地址的问题(转)
Delphi中使用@取函数地址的问题 例如以下代码:unit Unit1;interfaceuses Windows, Messages, SysUtils, Variants, Classes ...
- 告别硬编码-发个获取未导出函数地址的Dll及源码
还在为找内核未导出函数地址而苦恼嘛? 还在为硬编码通用性差而不爽吗? 还在为暴搜内核老蓝屏而痛苦吗? 请看这里: 最近老要用到内核未导出的函数及一些结构,不想再找特征码了,准备到网上找点符号文件解析的 ...
- gdb查看虚函数表、函数地址
1. 查看函数地址 看函数在代码的哪一行,使用info line就可以看到类似下面这中输出 点击(此处)折叠或打开 (gdb) info line a.cpp:10 Line 10 of &q ...
- 直接调用类成员函数地址(用汇编取类成员函数的地址,各VS版本还有所不同)
在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用.但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法. ...
- C++ 虚函数表解析(比较清楚,还可打印虚函数地址)
C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父 ...
- 总结C++中取成员函数地址的几种方法
这里, 我整理了4种C++中取成员函数地址的方法, 第1,2,4种整理于网上的方法, 第3种cdecl_cast是我自己想到的. 其中, 第4种(汇编)的方法不能在VC6上编译通过. 推荐使用第1,2 ...
- C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址
C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址 讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当 ...
随机推荐
- [未完] Linux 4.4 USB —— spiflash模拟usb大容量存储设备 调试记录 Gadget Mass Stroage
linux 4.4 USB Gadget Mass Stroage 硬件平台: licheepi nano衍生 调试记录 驱动信息 │ This driver is a replacement for ...
- 无聊的周末用Java写个扫雷小游戏
周末无聊,用Java写了一个扫雷程序,说起来,这个应该是在学校的时候,写会比较好玩,毕竟自己实现一个小游戏,还是比较好玩的.说实话,扫雷程序里面核心的东西,只有点击的时候,去触发更新数据这一步. Sw ...
- SSRF漏洞用到的其他协议(dict协议,file协议)
0x00 引言 当SSRF打内网reids时,若gopher协议用不了,我们也可以用其他协议 0x01 dict协议一.定义:词典网络协议,在RFC 2009中进行描述.它的目标是超越Webster ...
- pycharm创建脚本头文件模板
代码头文件信息可以包括:python 解析器的位置.字符集.作者信息.创建脚本时间等,pycharm工具创建头部信息模板操作步骤如下: 设置头文件:文件-->设置-->编辑器-->文 ...
- 【刷题-LeetCode】123 Best Time to Buy and Sell Stock III
Best Time to Buy and Sell Stock III Say you have an array for which the ith element is the price of ...
- Collection类集
1.Collection接口 Collection是最基本的集合接口,一个Collection代表一组Object,即Collection的元素(Elements).一些Collection允许相同的 ...
- T-SQL的游标和fetch
很多时候我们sql操作会得到一个结果集合,当我们需要依次查看集合内的内容时,我们便需要游标特性了. 所以,sql的游标类似c++里容器的迭代器,下面举个例子来辅助理解: DECLARE vend_cu ...
- 淘系工程师讲解的使用Spring特性优雅书写业务代码
使用Spring特性优雅书写业务代码 大家在日常业务开发工作中相信多多少少遇到过下面这样的几个场景: 当某一个特定事件或动作发生以后,需要执行很多联动动作,如果串行去执行的话太耗时,如果引入消息中 ...
- linux中sort命令全面解析
目录 一:sort命令作用 二:sort格式 1.sort参数 2.参数解析 sort命令简介 Linux sort 命令用于将文本文件内容加以排序. sort 可针对文本文件的内容,以行为单位来排序 ...
- 学习MyBatis必知必会(2)~MyBatis基本介绍和MyBatis基本使用
一.MyBatis框架基本介绍: 1.认识 MyBatis: MyBatis 是支持普通 SQL 查询,存储过程和高级映射的持久层框架,严格上说应该是一个 SQL 映射框架. 其前身是 iBatis, ...