通过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. CTF-sql-宽字节注入

    本文章主要涉及sql宽字节注入注入的原理讲解,如有错误,望指出.(附有目录,如需查看请点右下角) 一.首先介绍一下本篇文章所用到的知识点: 常用到的url编码: 空格:%20 单引号:%27 在sql ...

  2. Java集合-ArrayList源码分析

    目录 1.结构特性 2.构造函数 3.成员变量 4.常用的成员方法 5.底层数组扩容原理 6.序列化原理 7.集合元素排序 8.迭代器的实现 9.总结 1.结构特性 Java ArrayList类使用 ...

  3. Solon Web 开发,十二、统一的渲染控制

    Solon Web 开发 一.开始 二.开发知识准备 三.打包与运行 四.请求上下文 五.数据访问.事务与缓存应用 六.过滤器.处理.拦截器 七.视图模板与Mvc注解 八.校验.及定制与扩展 九.跨域 ...

  4. 【记录一个问题】opencv中 cv::dft()与cv::ocl_dft()计算的结果相差较大

    以一个跟踪算法来测试: 使用cv::dft(), 矩阵未按照2次幂对齐,最终跟踪平均准确率 84.3% 使用cv::dft(),矩阵使用cv::copyMakeBorder对齐,最终跟踪平均准确率 8 ...

  5. 搭建服务器之DNS

    DNS服务器,实用软件为bind,服务守护进程为named,一下记录一下自己的搭建过程: 1.yum install bind*  其中包括bind本身软件,测试dns的一些工具dig,nslooku ...

  6. 「DP 浅析」斜率优化

    #0.0 屑在前面 将结合经典例题 「HNOI2008」玩具装箱 以及 「NOI2007」货币兑换 进行讲解. #1.0 简述 #1.1 适用情况 斜率优化一般适用于状态转移方程如下的 DP \[f_ ...

  7. C++11之future(二)

    如果有两个线程,其中一个线程想要获取另一个线程的返回值,该怎么办? 于是接下来要谈的package_task就是为了解决这个问题而诞生的. // ConsoleApplication5.cpp : 定 ...

  8. Casbin + Gin + Gorm 学习探索

    Casbin 是一个强大的,开源的访问控制框架,权限管理机制支持多种访问控制模型: 并且支持多种编程语言: 文档地址:https://casbin.org/docs/zh-CN/overview Gi ...

  9. golang中goroutine池的使用

    1. 概念本质上是生产者.消费者模型可以有效的控制goroutine数量,防止暴涨案例:生成一个随机数,计算该随机数每一个数字相加的和,例如:123:1+2+3=6主协程负责生产数据发送到待处理通道中 ...

  10. java抽象类概述特点

    1 package face_09; 2 /* 3 * 抽象类: 4 * 抽象:笼统,模糊,看不懂!不具体. 5 * 6 * *特点: 7 * 1,方法只有声明没有实现时,该方法就是抽象方法,需要被a ...