通过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结构体中的InLoadOrderModuleListInMemoryOrderModuleListInInitializationOrderModuleList指向一个当前进程加载模块的链表,链表的每个结点都被定义为_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地址;如果通过InMemoryOrderLinksInInitializationOrderLinks 进行匹配时,需要将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查找目标模块,进而查找目标函数,通常首先都会获取LoadLibraryAGetProcAddress地址,便于之后直接加载指定模块,获取导出函数并调用。

  写的时候我发现从函数序数表得到的函数序号减去序号基数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寻找函数地址的更多相关文章

  1. 【逆向篇】分析一段简单的ShellCode——从TEB到函数地址获取

    其实分在逆向篇不太合适,因为并没有逆向什么程序. 在http://www.exploit-db.com/exploits/28996/上看到这么一段最简单的ShellCode,其中的技术也是比较常见的 ...

  2. hash算法搜索获得api函数地址的实现,"kernel32.dll", "CreateThread"

    我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较.这样弊端很 ...

  3. Delphi中使用@取函数地址的问题(转)

    Delphi中使用@取函数地址的问题   例如以下代码:unit Unit1;interfaceuses  Windows, Messages, SysUtils, Variants, Classes ...

  4. 告别硬编码-发个获取未导出函数地址的Dll及源码

    还在为找内核未导出函数地址而苦恼嘛? 还在为硬编码通用性差而不爽吗? 还在为暴搜内核老蓝屏而痛苦吗? 请看这里: 最近老要用到内核未导出的函数及一些结构,不想再找特征码了,准备到网上找点符号文件解析的 ...

  5. gdb查看虚函数表、函数地址

    1. 查看函数地址     看函数在代码的哪一行,使用info line就可以看到类似下面这中输出 点击(此处)折叠或打开 (gdb) info line a.cpp:10 Line 10 of &q ...

  6. 直接调用类成员函数地址(用汇编取类成员函数的地址,各VS版本还有所不同)

    在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用.但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法. ...

  7. C++ 虚函数表解析(比较清楚,还可打印虚函数地址)

    C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父 ...

  8. 总结C++中取成员函数地址的几种方法

    这里, 我整理了4种C++中取成员函数地址的方法, 第1,2,4种整理于网上的方法, 第3种cdecl_cast是我自己想到的. 其中, 第4种(汇编)的方法不能在VC6上编译通过. 推荐使用第1,2 ...

  9. C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址

    C++反汇编第三讲,反汇编中识别虚表指针,以及指向的虚函数地址 讲解之前,了解下什么是虚函数,什么是虚表指针,了解下语法,(也算复习了) 开发知识为了不码字了,找了一篇介绍比较好的,这里我扣过来了,当 ...

随机推荐

  1. Python的内存管理和垃圾回收机制

    内存管理 Python解释器由c语言开发完成,py中所有的操作最终都由底层的c语言来实现并完成,所以想要了解底层内存管理需要结合python源码来进行解释. 1. 两个重要的结构体 include/o ...

  2. 使用altium designer 21极坐标绘制异形焊盘 比如焊接螺母的 环绕焊盘

    先看一张图 在回流焊时,不能直接做一个大圆圈焊盘来焊接螺母,这样焊锡膏因为流动问题,可能会导致螺母歪斜 厂家推荐的焊盘形状右上角 所以 需要绘制异形焊盘 首先进入ad的封装库工作界面 在库中点右下角P ...

  3. C# - 逆变的具体应用场景

    前言 早期在学习泛型的协变与逆变时,网上的文章讲解.例子算是能看懂,但关于逆变的具体应用场景这方面的知识,我并没有深刻的认识. 本文将在具体的场景下,从泛型接口设计的角度出发,逐步探讨逆变的作用,以及 ...

  4. SQL安全执行

    begin tran begin -----------执行SQL开始----------------- update Audioset Name =3 -----------执行SQL结束----- ...

  5. MongDB日志分析

    Result文件数据说明: Ip:106.39.41.166,(城市) Date:10/Nov/2016:00:01:02 +0800,(日期) Day:10,(天数) Traffic: 54 ,(流 ...

  6. C# 实现NPOI的Excel导出

    技术点: 1.自定义attribute属性 2.通过反射取类及其属性的attribute属性值 3.NPOI包常用属性及方法(我也仅仅知道用到过的,陌生的要么见名知意,要么百度查) 实现功能点: Li ...

  7. 如何根据经纬度计算地面上某点在XYZ空间直角坐标系中的坐标

    如何根据经纬度计算地面上某点在XYZ空间直角坐标系中的坐标 /** * @param r: number 到地心的距离 * @param lon: number 经度 * @param lat: nu ...

  8. 推荐召回--基于用户的协同过滤UserCF

    目录 1. 前言 2. 原理 3. 数据及相似度计算 4. 根据相似度计算结果 5. 相关问题 5.1 如何提炼用户日志数据? 5.2 用户相似度计算很耗时,有什么好的方法? 5.3 有哪些改进措施? ...

  9. Servlet-IDEA菜单生成Servlet程序

    更快捷方法生成Servlet程序(IDEA菜单生成Servlet) 菜单:new ---> Servlet程序 取消注解做这个配置 可以创建Servlet程序,编写HelloServlet3类, ...

  10. 第08讲:Flink 窗口、时间和水印

    Flink系列文章 第01讲:Flink 的应用场景和架构模型 第02讲:Flink 入门程序 WordCount 和 SQL 实现 第03讲:Flink 的编程模型与其他框架比较 第04讲:Flin ...