一些基础啦
C语言的常规方法
使用LoadLibrary与GetProcAddress这两个API
1
| PDWORD _addr = (PDWORD)GetProcAddress(LoadLibrary(<DllName>), <FunctionName>)
|
隐藏 GetProcAddress
使用pe头,mz头->pe头->导出表->遍历->比较Name->获得NameOrdinals->取Functions,得到函数在内存中基地址的偏移量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| PDWORD _addr = nullptr; _base_addrODULE _base_addr = LoadLibrary(<DllName>); PIMAGE_DOS_HEADER pdh = (PIMAGE_DOS_HEADER)_base_addr; PIMAGE_NT_HEADERS pnt = (PIMAGE_NT_HEADERS)(_base_addr + pdh->e_lfanew); PIMAGE_EXPORT_DIRECTORY pexports = (PIMAGE_EXPORT_DIRECTORY)(_base_addr + pnt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress); PDWORD _Functions = (PDWORD)(_base_addr + pexports->AddressOfFunctions); PDWORD _Name = (PDWORD)(_base_addr + pexports->AddressOfNames); PDWORD _NameOrdinals = (PWORD)(_base_addr + pexports->AddressOfNameOrdinals);
for (DWORD i = 0; i < pexports->NumberOfNames; i++) { if (!strcmp(_base_addr + _Name[i], <FunctionName>)) { _addr = _base_addr + _Function[_NameOrdinals[i]]; break; } }
|
隐藏 LoadLibrary
这里只讨论获取已加载dll的基地址
通过fs获取
使用PEB头,TEB->PEB(fs:[30])->PEB_LDR_DATA(_LDR_DATA_TABLE_ENTRY-0x10)->遍历->BaseDllName/DllBase,取得基地址
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| ntdll!_PEB_LDR_DATA -0x00c Length : Uint4B -0x008 Initialized : UChar -0x004 SsHandle : Ptr32 Void ntdll!_LDR_DATA_TABLE_ENTRY +0x000 InLoadOrderLinks : _LIST_ENTRY //第三个为kernel32 +0x008 InMemoryOrderLinks : _LIST_ENTRY //EnumProcessModules(hProcess,*lphModule,cb,lpcbNeeded) +0x010 InInitializationOrderLinks : _LIST_ENTRY //CreateToolhelp32Snapshot(dwFlags,th32ProcessID) +0x018 DllBase : Ptr32 Void //基地址 +0x01c EntryPoint : Ptr32 Void +0x020 SizeOfImage : Uint4B +0x024 FullDllName : _UNICODE_STRING +0x02c BaseDllName : _UNICODE_STRING //dll名 ... +0x0a4 SigningLevel : UChar
|
当然,前提是dll需要被加载
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| PDWORD _base_addr LPWSTR _dll_name = nullptr PLIST_ENTRY pList __asm { mov eax, fs:[0x30] //dt nt!_peb mov eax, [eax + 0x0c] //dt nt!_PEB_LDR_DATA add eax, 0x00c //dt nt!_LDR_DATA_TABLE_ENTRY mov pList, eax } PDWORD pFlag = (PDWORD)(pList->Blink) do { pList = pList->Flink _base_addr = (PDWORD)((DWORD)pList + 0x018) _dll_name = (LPWSTR)(*(PDWORD)((DWORD)pList + 0x2c + 0x4)) if(!wcscmp(_dll_name,<DllName>))//hash? { break }
} while (pFlag != (PDWORD)pList)
|
暴力搜索PE头
如果DLL被断链,据我了解,只能使用此种方法.
因为DLL被加载时,pe装载器会把DLL装载进内存的,所以我们只需要暴力搜索pe的文件头MZ,即可获取基地址.(当然,文件头不能被抹除)
又因为pe装载器内存是1k对齐的(SectionAlignment),所以我们只需要以1k为单位搜索即可.如果已知DLL内函数的地址,那就更容易搜索出来了.例如最外层SEH即指向kernel32.dll的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| PDWORD addr = GetProcAddress; addr = addr & 0xfffff000); WORD _pe_flag = 0x0; while { __try { _pe_flag = *addr; } __except { _pe_flag = 0x0; } if { break; } addr = addr - 0x1000); }
|
ShellCode
随便找个分析下就有了
杂项
- 有说可以使用SEH搜索kernel32.dll,尝试后发现SEH调用的是ntdll的函数,搜索出来的是ntdll.dll的基地址
- 有说可以用poi(poi(poi(fs:0x18)+4)-0x1c)向上搜索,然而测试后发现所指的值为0x0.且fs:0x18所指处即为fs寄存器的值,多此一举.根本不知道这个操作是什么道理.
- 以上测试均在Win10 17763.253下进行
参考资料
InLoadOrderLinks第三个为kernel32
没读过不过差不多所有参考资料都有说的0day