通过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
无返回结果,
输入:
x kernelbase!LoadLibraryA
查看kernelbase中LoadLibraryA的地址,输出为:
和代码输出一致
在x64dbg中观察到的现象为:
kernel32.dll中的LoadLibrary地址和代码获取的一致,反汇编此代码所在位置:
可以看到直接跳转到了kernelBase的LoadLibraryA的地址。
在此出现的LoadLibraryA的地址也和代码返回一致
可以得出结论:
kernel32.dll
中的LoadLibraryA
是转发存根(Forwarder Stub),实际代码在kernelbase.dll
中。