恢复绕行的库函数
Posted
技术标签:
【中文标题】恢复绕行的库函数【英文标题】:Recovering Detoured Library Functions 【发布时间】:2016-06-20 15:39:46 【问题描述】:这个问题很简单,我想做的是恢复我的流程的迂回功能。
当我说绕道时,我指的是通常的 jmp
指令到一个未知的位置。
例如ntdll.dll导出NtOpenProcess()
不绕行时,函数指令的前5个字节沿mov eax, *
行。
(* 偏移量取决于操作系统版本。)
当它绕道而行时,mov eax, *
变成了jmp.
我正在尝试将它们的字节恢复到任何内存修改之前的原始状态。
我的想法是尝试从磁盘而不是内存中读取我需要的信息,但是我不知道如何做到这一点,因为我只是一个初学者。
非常欢迎任何帮助或解释,如果我没有正确解释我的问题,请告诉我!
【问题讨论】:
【参考方案1】:我终于弄明白了。
关于 NtOpenProcess 的示例。 我决定跳过它们而不是恢复字节。
首先我们要定义ntdll的基础。
/* locate ntdll */
#define NTDLL _GetModuleHandleA("ntdll.dll")
一旦我们完成了,我们就可以开始了。 GetOffsetFromRva 将根据传递给它的地址和模块头计算文件的偏移量。
DWORD GetOffsetFromRva(IMAGE_NT_HEADERS * nth, DWORD RVA)
PIMAGE_SECTION_HEADER sectionHeader = IMAGE_FIRST_SECTION(nth);
for (unsigned i = 0, sections = nth->FileHeader.NumberOfSections; i < sections; i++, sectionHeader++)
if (sectionHeader->VirtualAddress <= RVA)
if ((sectionHeader->VirtualAddress + sectionHeader->Misc.VirtualSize) > RVA)
RVA -= sectionHeader->VirtualAddress;
RVA += sectionHeader->PointerToRawData;
return RVA;
return 0;
我们调用它来获取我们需要的文件偏移量,以便找到函数的原始字节。
DWORD GetExportPhysicalAddress(HMODULE hmModule, char* szExportName)
if (!hmModule)
return 0;
DWORD dwModuleBaseAddress = (DWORD)hmModule;
IMAGE_DOS_HEADER* pHeaderDOS = (IMAGE_DOS_HEADER *)hmModule;
if (pHeaderDOS->e_magic != IMAGE_DOS_SIGNATURE)
return 0;
IMAGE_NT_HEADERS * pHeaderNT = (IMAGE_NT_HEADERS *)(dwModuleBaseAddress + pHeaderDOS->e_lfanew);
if (pHeaderNT->Signature != IMAGE_NT_SIGNATURE)
return 0;
/* get the export virtual address through a custom GetProcAddress function. */
void* pExportRVA = GetProcedureAddress(hmModule, szExportName);
if (pExportRVA)
/* convert the VA to RVA... */
DWORD dwExportRVA = (DWORD)pExportRVA - dwModuleBaseAddress;
/* get the file offset and return */
return GetOffsetFromRva(pHeaderNT, dwExportRVA);
return 0;
使用获取文件偏移量的函数,我们现在可以读取原始导出字节。
size_t ReadExportFunctionBytes(HMODULE hmModule, char* szExportName, BYTE* lpBuffer, size_t t_Count)
/* get the offset */
DWORD dwFileOffset = GetExportPhysicalAddress(hmModule, szExportName);
if (!dwFileOffset)
return 0;
/* get the path of the targetted module */
char szModuleFilePath[MAX_PATH];
GetModuleFileNameA(hmModule, szModuleFilePath, MAX_PATH);
if (strnull(szModuleFilePath))
return 0;
/* try to open the file off the disk */
FILE *fModule = fopen(szModuleFilePath, "rb");
if (!fModule)
/* we couldn't open the file */
return 0;
/* go to the offset and read it */
fseek(fModule, dwFileOffset, SEEK_SET);
size_t t_Read = 0;
if ((t_Read = fread(lpBuffer, t_Count, 1, fModule)) == 0)
/* we didn't read anything */
return 0;
/* close file and return */
fclose(fModule);
return t_Read;
我们可以从 mov 指令中检索系统调用索引,该指令最初放置在 x86 上导出的前 5 个字节中。
DWORD GetSyscallIndex(char* szFunctionName)
BYTE buffer[5];
ReadExportFunctionBytes(NTDLL, szFunctionName, buffer, 5);
if (!buffer)
return 0;
return BytesToDword(buffer + 1);
获取 NtOpenProcess 地址并在其上添加 5 到蹦床。
DWORD _ptrNtOpenProcess = (DWORD) GetProcAddress(NTDLL, "NtOpenProcess") + 5;
DWORD _oNtOpenProcess = GetSyscallIndex("NtOpenProcess");
恢复/重建的 NtOpenProcess。
__declspec(naked) NTSTATUS NTAPI _NtOpenProcess
(
_Out_ PHANDLE ProcessHandle,
_In_ ACCESS_MASK DesiredAccess,
_In_ POBJECT_ATTRIBUTES ObjectAttributes,
_In_opt_ PCLIENT_ID ClientId
)
__asm
mov eax, [_oNtOpenProcess]
jmp dword ptr ds : [_ptrNtOpenProcess]
让我们称之为吧。
int main()
printf("NtOpenProcess %x index: %x\n", _ptrNtOpenProcess, _oNtOpenProcess);
uint32_t pId = 0;
do
pId = GetProcessByName("notepad.exe");
Sleep(200);
while (pId == 0);
OBJECT_ATTRIBUTES oa;
CLIENT_ID cid;
cid.UniqueProcess = (HANDLE)pId;
cid.UniqueThread = 0;
InitializeObjectAttributes(&oa, NULL, 0, NULL, NULL);
HANDLE hProcess;
NTSTATUS ntStat;
ntStat = _NtOpenProcess(&hProcess, PROCESS_ALL_ACCESS, &oa, &cid);
if (!NT_SUCCESS(ntStat))
printf("Couldn't open the process. NTSTATUS: %d", ntStat);
return 0;
printf("Successfully opened the process.");
/* clean up. */
NtClose(hProcess);
getchar();
return 0;
【讨论】:
函数ReadExportFunctionBytes中存在资源泄漏:需要在倒数第二个return之前调用fclose(fModule)。以上是关于恢复绕行的库函数的主要内容,如果未能解决你的问题,请参考以下文章