通过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结构体中的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:
        }
    }
}

原文地址:https://www.cnblogs.com/zUotTe0/p/11421173.html

时间: 2024-10-05 03:09:22

通过PEB寻找函数地址的相关文章

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

其实分在逆向篇不太合适,因为并没有逆向什么程序. 在http://www.exploit-db.com/exploits/28996/上看到这么一段最简单的ShellCode,其中的技术也是比较常见的,0day那本书上也提到过,大神都用烂了.不过想来很久没有碰汇编了,就心血来潮,权当温习一下. /* User32-free Messagebox Shellcode for any Windows version ==========================================

hash算法搜索获得api函数地址的实现

我们一般要获得一个函数的地址,通常采用的是明文,例如定义一个api函数字符串"MessageBoxA",然后在GetProcAddress函数中一个字节一个字节进行比较.这样弊端很多,例如如果我们定义一个杀毒软件比较敏感的api函数字符串,那么可能就会增加杀毒软件对我们的程序的判定值,而且定义这些字符串还有一个弊端是占用的字节数较大.我们想想如何我们的api函数字符串通过算法将它定义成一个4字节的值,然后在GetProcAddress中把AddressOfNames表中的每个地址指向的

C++ 获取类成员函数地址方法 浅析

C语言中可以用函数地址直接调用函数: void print () { printf ("function print"); } typdef void (*fun)(); fun f = print; f(); C++中类非静态成员函数必须通过实例去调用,C++中类成员函数调用: class test { public: void print () { printf ("function print"); } }; 我们同样可以通过定义函数指针来调用如下: type

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

C++ 虚函数表解析 陈皓 http://blog.csdn.net/haoel 前言 C++中的虚函数的作用主要是实现了多态的机制.关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数.这种技术可以让父类的指针有“多种形态”,这是一种泛型技术.所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法.比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议. 关于虚函数的使用方法,我在这里不做过多的阐述.大家可以

X64下 FF 25 + 00 00 00 00 + 导出表函数地址小测试

在X64的情况下,JMP反汇编出来的 FF 25 后面加的是 00 00 00 00 和导出表函数地址 测试代码如下: void JmpFunctionAddressOfExportTableInX64Using00() { DWORD OldProtect; ULONG_PTR v1 = (ULONG_PTR)GetProcAddress(LoadLibrary(L"user32.dll"), "MessageBoxA"); ULONG_PTR v2 = 0; p

X86下,FF 25+ 导入表函数地址小测试

在X86下,JMP反汇编出来的FF 25加的是导入表的地址 测试代码如下: void JmpFunctionAddressOfImportTableInWinXP_X86() { DWORD dwOld = 0; ULONG_PTR v2 = 0; void* v1 = NULL; GetFunctionByImport_X86(GetModuleHandle(NULL), "MessageBoxA"); v1 = lpAddress; printf("%p\r\n"

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

在C++中,成员函数的指针是个比较特殊的东西.对普通的函数指针来说,可以视为一个地址,在需要的时候可以任意转换并直接调用.但对成员函数来说,常规类型转换是通不过编译的,调用的时候也必须采用特殊的语法.C++专门为成员指针准备了三个运算符: "::*"用于指针的声明,而"->*"和".*"用来调用指针指向的函数. // Thunk.cpp : Defines the entry point for the console applicatio

[转载]Windows系统调用架构分析—也谈KiFastCallEntry函数地址的获取

原文地址:点击打开链接 为什么要写这篇文章 1.      因为最近在学习<软件调试>这本书,看到书中的某个调试历程中讲了Windows的系统调用的实现机制,其中讲到了从Ring3跳转到Ring0之后直接进入了KiFastCallEntry这个函数. 2.      碰巧前天又在网上看到了一篇老文章介绍xxx安全卫士对Windows系统调用的Hook,主要就是Hook到这个函数 3.      刚刚做完毕业设计,对使用中断来实现系统调用的方式记忆犹新. 以上原因导致我最近眼前总是出现系统调用这

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

Delphi中使用@取函数地址的问题 例如以下代码:unit Unit1;interfaceuses  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,Dialogs;type  TForm1 = class(TForm)    procedure one();    function two(x,y:integer):integer;  private    { Private declarat