通过PEB和PE导出表获取函数

peb结构获取dll地址

源码地址: https://github.com/harry-hard/blog-dev_code/tree/main/PEB

PBYTE getDllAddress(wchar_t* dllName) {
	//通过PEB结构获取dll地址
	PPEB  pPeb = __readgsqword(0x60);
	PPEB_LDR_DATA ldr = pPeb->Ldr;
	PLIST_ENTRY head = &ldr->InMemoryOrderModuleList;
	PLIST_ENTRY flink = head->Flink;
	PBYTE kernel32dllAddr = NULL;
	while (flink != head) {
		PLDR_DATA_TABLE_ENTRY entry = (ULONG_PTR)flink - LDR_OFFSET;
		//AI写的
		PWSTR filename = wcsrchr(entry->FullDllName.Buffer, L'\\');
		filename = filename ? filename + 1 : entry->FullDllName.Buffer;
		//AI结束
		if (_wcsicmp(filename, dllName) == 0) {
			kernel32dllAddr = entry->DllBase;
			break;
		}
		else
			flink = flink->Flink;
	}
	if (!kernel32dllAddr) {
		printf("Failed to find kernel32.dll\n");
		return (PVOID)0;
	}
	return kernel32dllAddr;
}
graph TD
    A[开始] --> B[获取PEB地址]
    B --> C[访问PEB_LDR_DATA]
    C --> D[定位模块链表头部]
    D --> E[遍历链表节点]
    E --> F{是否链表头?}
    F -->|是| Z[结束遍历]
    F -->|否| G[计算LDR_DATA_TABLE_ENTRY地址]
    G --> H[提取DLL文件名]
    H --> I{文件名匹配?}
    I -->|是| J[记录DLL基地址]
    J --> K[跳出循环]
    I -->|否| L[移动到下一个节点]
    K --> Z
    L --> E
    Z --> M{找到基地址?}
    M -->|是| N[返回基地址]
    M -->|否| O[输出错误信息]
    O --> P[返回空指针]

    classDef startEnd fill:#90EE90,stroke:#4CAF50;
    classDef process fill:#E3F2FD,stroke:#2196F3;
    classDef decision fill:#FFF3E0,stroke:#FF9800;
    classDef error fill:#FFEBEE,stroke:#F44336;
    
    class A,Z,N,P startEnd;
    class B,C,D,E,G,H,J,L process;
    class F,I,M decision;
    class O error;

    style A stroke-width:2px
    style N stroke-width:2px
    style P stroke-width:2px

遍历dll导出表获取函数地址:

PBYTE getFuncAddress(const char* funcName,PBYTE kernel32dllAddr) {
	//根据获取到的dll寻找函数导出表
	PIMAGE_DOS_HEADER imgPe = (PIMAGE_DOS_HEADER)kernel32dllAddr;
	PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)(kernel32dllAddr + imgPe->e_lfanew);
	//PIMAGE_SECTION_HEADER sec = (PIMAGE_SECTION_HEADER)IMAGE_FIRST_SECTION(nt);
	/*IMAGE_OPTIONAL_HEADER opt = nt->OptionalHeader;*/
	PIMAGE_DATA_DIRECTORY dataDir = &nt->OptionalHeader.DataDirectory[0];
	PIMAGE_EXPORT_DIRECTORY exp = kernel32dllAddr + dataDir->VirtualAddress;
	PDWORD nameFunc = kernel32dllAddr + exp->AddressOfNames;
	PDWORD addrFunc = kernel32dllAddr + exp->AddressOfFunctions;
	PWORD ordinals = kernel32dllAddr + exp->AddressOfNameOrdinals;
	for (int i = 0; i < exp->NumberOfNames; i++) {
		DWORD name_rva = nameFunc[i];
		if (name_rva == 0 || name_rva >= nt->OptionalHeader.SizeOfImage) {
			printf("Invalid RVA: 0x%08X\n", name_rva);
			continue;
		}
		if (strcmp(funcName, kernel32dllAddr + (DWORD)nameFunc[i]) == 0) {
			return kernel32dllAddr + (DWORD)addrFunc[ordinals[i]];
		}
	}
}
graph TD
    subgraph 模块遍历流程
        Start([开始]) --> A[获取PEB地址]
        A --> B[访问PEB->Ldr]
        B --> C[定位链表头部]
        C --> D[初始化遍历指针]
        D --> E{当前节点≠头部?}
        E -->|是| F[计算LDR_DATA入口]
        F --> G[提取DLL文件名]
        G --> H{名称匹配?}
        H -->|是| I[记录基地址]
        H -->|否| J[移动下一节点]
        I --> K[终止循环]
        J --> E
        E -->|否| L[结束遍历]
        L --> M{基地址有效?}
        M -->|是| N([返回基地址])
        M -->|否| O[输出错误信息]
        O --> P([返回空指针])
    end

    classDef startEnd fill:#C8E6C9,stroke:#4CAF50,color:#2E7D32;
    classDef process fill:#E3F2FD,stroke:#2196F3,color:#0D47A1;
    classDef decision fill:#FFE0B2,stroke:#FF9800,color:#BF360C;
    classDef error fill:#FFCDD2,stroke:#F44336,color:#B71C1C;
    
    class Start,N,P startEnd;
    class A,B,C,D,F,G process;
    class E,H,M decision;
    class O error;

    style Start stroke-width:2px
    style N stroke-width:2px
    style P stroke-width:2px

KernelBase.dll与Kernel32.dll中函数的异同:

其中要获取LoadLibraryA()函数地址,在win11 x64操作系统中,已无法通过kernel32.dll获取其函数地址,而是要通过KernelBase.dll,以下是windbg的验证:

输入:

x kernel32!LoadLibraryA

image.png image.png

无返回结果,

输入:

x kernelbase!LoadLibraryA

查看kernelbase中LoadLibraryA的地址,输出为:

image.png image.png

和代码输出一致

在x64dbg中观察到的现象为:

image.png image.png

kernel32.dll中的LoadLibrary地址和代码获取的一致,反汇编此代码所在位置:

image.png image.png

image.png image.png

可以看到直接跳转到了kernelBase的LoadLibraryA的地址。

在此出现的LoadLibraryA的地址也和代码返回一致

可以得出结论:

  • kernel32.dll 中的 LoadLibraryA 是转发存根(Forwarder Stub),实际代码在 kernelbase.dll 中。