《逆向工程核心原理》学习笔记:API钩取

Posted 思源湖的鱼

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了《逆向工程核心原理》学习笔记:API钩取相关的知识,希望对你有一定的参考价值。

目录

前言

继续学习《逆向工程核心原理》,本篇笔记是第四部分:API钩取,主要介绍了调试钩取、DLL注入实现IAT钩取、API代码修改钩取和全局API钩取等内容

一、API钩取简介

1、基础概念

钩取(Hook):截取信息、更改程序执行流向、添加新功能的技术

  • 使用反汇编/调试器把握程序结构与原理
  • 开发Hook代码,以修改bug、改善程序功能
  • 灵活操作可执行文件和进程内存,设置Hook代码

API(Application Programming Interface):可以认为是调用资源的路径,notepad.exe为例如下图所示


其API调用如下:

API钩取:对Win32 API的钩取,一个例子如下图所示

2、技术图表


(下划线的是常用且好用的方法)

二、调试钩取技术

通过调试钩取技术,来钩取kernel32!WriteFile() API

1、调试器

调试器(Debbuger):能逐一执行被调试者的指令,拥有对寄存器和内存的所有访问权限,工作原理如下图所示


调试事件共9种,见微软官方调试事件
其中,与调试相关的是EXCEPTION_DEBUG_EVENT,与其相关的异常列表如下:


其中,调试器必须处理的是EXCEPTION_BREAKPOINT(断点),对应汇编指令是INT3,IA-32指令是0xCC
要设置断点时,只需将代码在内存中的起始地址的1个字节设置为0xCC即可,想继续调试就把它恢复

2、调试流程

基本思路:被调试者的API起始部份修改为0xCC,控制权转移到调试器后执行指定操作,最后使被调试者重新进入运行状态

  • 对目标进程进行附加操作,使之成为被调试者
  • Hook:API起始地址的第1个字节修改为0xCC
  • 调用相应API,控制权转移到调试器
  • 执行操作:操作参数、返回值等
  • 脱钩:0xCC恢复原值
  • 运行相应API(正常状态)
  • Hook:再次修改为0xCC(继续钩取)
  • 控制权返还被调试者

3、示例:记事本 WriteFile() API钩取

目标是将notepad.exe中所有小写字母都变成大写字母

WriteFile() 定义如下:

BOOL WriteFile(
	HANDLE  hFile, //文件句柄
	LPCVOID lpBuffer, //数据缓存区指针
	DWORD   nNumberOfBytesToWrite, //要写的字节数
	LPDWORD lpNumberOfBytesWritten, //用于保存实际写入字节数的存储区域的指针
	LPOVERLAPPED lpOverlapped //OVERLAPPED结构体指针
);

(1)hookdbg.exe

源码如下:

// hookdbg.exe

#include "windows.h"
#include "stdio.h"

LPVOID g_pfWriteFile = NULL;
CREATE_PROCESS_DEBUG_INFO g_cpdi;
BYTE g_chINT3 = 0xCC, g_chOrgByte = 0;

BOOL OnCreateProcessDebugEvent(LPDEBUG_EVENT pde)

    // 获取 WriteFile() API 地址(注意是调试进程的内存地址,不是被调试进程)
    g_pfWriteFile = GetProcAddress(GetModuleHandleA("kernel32.dll"), "WriteFile");

    // API Hook - WriteFile()
    //   更改第一个字节为 0xCC (INT 3) 
    //   (orginal byte 是g_chOrgByte备份)
    memcpy(&g_cpdi, &pde->u.CreateProcessInfo, sizeof(CREATE_PROCESS_DEBUG_INFO));
    ReadProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                      &g_chOrgByte, sizeof(BYTE), NULL);
    WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                       &g_chINT3, sizeof(BYTE), NULL);

    return TRUE;


BOOL OnExceptionDebugEvent(LPDEBUG_EVENT pde)

    CONTEXT ctx;
    PBYTE lpBuffer = NULL;
    DWORD dwNumOfBytesToWrite, dwAddrOfBuffer, i;
    PEXCEPTION_RECORD per = &pde->u.Exception.ExceptionRecord;

    // 断点异常 (INT 3) 
    if( EXCEPTION_BREAKPOINT == per->ExceptionCode )
    
        // 断点地址为 WriteFile() API 地址
        if( g_pfWriteFile == per->ExceptionAddress )
        
            // #1. Unhook
            //   0xCC 恢复为 original byte
            WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                               &g_chOrgByte, sizeof(BYTE), NULL);

            // #2. 获取线程上下文
            ctx.ContextFlags = CONTEXT_CONTROL;
            GetThreadContext(g_cpdi.hThread, &ctx);

            // #3. 获取 WriteFile() 的 param 2, 3 值
            //   函数参数存在于相应进程的栈
            //   param 2 : ESP + 0x8 缓冲区地址
            //   param 3 : ESP + 0xC 缓冲区大小
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0x8), 
                              &dwAddrOfBuffer, sizeof(DWORD), NULL);
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)(ctx.Esp + 0xC), 
                              &dwNumOfBytesToWrite, sizeof(DWORD), NULL);

            // #4. 分配临时缓冲区
            lpBuffer = (PBYTE)malloc(dwNumOfBytesToWrite+1);
            memset(lpBuffer, 0, dwNumOfBytesToWrite+1);

            // #5. 复制 WriteFile() 缓冲区到临时缓冲区
            ReadProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                              lpBuffer, dwNumOfBytesToWrite, NULL);
            printf("\\n### original string ###\\n%s\\n", lpBuffer);

            // #6. 小写字母 -> 大写字母
            for( i = 0; i < dwNumOfBytesToWrite; i++ )
            
                if( 0x61 <= lpBuffer[i] && lpBuffer[i] <= 0x7A )
                    lpBuffer[i] -= 0x20;
            

            printf("\\n### converted string ###\\n%s\\n", lpBuffer);

            // #7. 变换后的缓冲区复制到 WriteFile() 缓冲区
            WriteProcessMemory(g_cpdi.hProcess, (LPVOID)dwAddrOfBuffer, 
                               lpBuffer, dwNumOfBytesToWrite, NULL);
            
            // #8. 释放临时缓冲区
            free(lpBuffer);

            // #9. 线程上下文的 EIP 改为 WriteFile() 首地址
            //   (当前为 WriteFile() + 1 位置,INT3命令后)
            ctx.Eip = (DWORD)g_pfWriteFile;
            SetThreadContext(g_cpdi.hThread, &ctx);

            // #10. 运行被调试者
            ContinueDebugEvent(pde->dwProcessId, pde->dwThreadId, DBG_CONTINUE);
            Sleep(0);

            // #11. API Hook
            WriteProcessMemory(g_cpdi.hProcess, g_pfWriteFile, 
                               &g_chINT3, sizeof(BYTE), NULL);

            return TRUE;
        
    

    return FALSE;


void DebugLoop()

    DEBUG_EVENT de;
    DWORD dwContinueStatus;

    // 等待被调试者发生事件
    while( WaitForDebugEvent(&de, INFINITE) )
    
        dwContinueStatus = DBG_CONTINUE; //dwContinueStatus值为DBG_CONTINUE(处理正常)或DBG_EXCEPTION_NOT_HANDLED(无法处理或在SEH中处理)

        // 被调试者生成或附加事件
        if( CREATE_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        
            OnCreateProcessDebugEvent(&de); //OnCreateProcessDebugEvent是CREATE_PROCESS_DEBUG_EVENT事件句柄
        
        // 异常事件
        else if( EXCEPTION_DEBUG_EVENT == de.dwDebugEventCode )
        
            if( OnExceptionDebugEvent(&de) ) //OnExceptionDebugEvent是EXCEPTION_DEBUG_EVENT事件句柄,处理被调试者的INT3指令
                continue;
        
        // 被调试者终止事件
        else if( EXIT_PROCESS_DEBUG_EVENT == de.dwDebugEventCode )
        
            // 被调试者终止 -> debugger 终止
            break;
        

        // 再次运行被调试者
        ContinueDebugEvent(de.dwProcessId, de.dwThreadId, dwContinueStatus);
    


int main(int argc, char* argv[])

    DWORD dwPID;

    if( argc != 2 )
    
        printf("\\nUSAGE : hookdbg.exe <pid>\\n");
        return 1;
    

    // Attach Process
    dwPID = atoi(argv[1]); //以程序运行参数的形式接收进程PID
    if( !DebugActiveProcess(dwPID) ) //将调试器附加到进程上
    
        printf("DebugActiveProcess(%d) failed!!!\\n"
               "Error Code = %d\\n", dwPID, GetLastError());
        return 1;
    

    // 调试器循环
    DebugLoop();

    return 0;


(2)试验

先运行notepad.exe,并获取notepad的PID是32220,然后运行hookdbg.exe,如下

输入一串字符串,保存
再次打开的时候会发现都变成大写字母了

三、DLL注入实现IAT钩取技术

本节向计算器calc.exe插入用户的DLL文件,钩取IAT的user32.SetWindowTextW() API地址,使得计算器显示中文数字

1、选定目标API

PEView打开calc.exe,在IAT中寻找API,如下两个负责显示文本

其中,SetDigitemTextW()又调用了SetWindowTextW()
SetWindowTextW() API定义如下:

BOOL SetWindowText(
	HWND hwnd, //窗口句柄
	LPCTSTR lpString //字符串指针(钩取的目标)
);

在OD里验证下
右键-search for-all intermodular calls,在SetWindowTextW()设置断点


运行,然后可以看到第一个断点处lpString的值是0,就是计算器显示的初始值


在计算器中输入7,继续运行,发现lpString的值变为7(注意此时地址不同)


尝试修改为中文“七”,Unicode码4e03,记住是小端序故要逆序
然后就会在计算器上显示“七”
验证完毕

2、IAT钩取工作原理

关于IAT,可以见《逆向工程核心原理》学习笔记(二):PE文件

原理如下图所示:

  • 首先注入hookiat.dll文件,文件中提供了 MySetWindowTextW() 函数
  • 然后修改IAT中的CALL的值为 MySetWindowTextW() 函数起始地址
  • 一系列处理后,再CALL到user32.SetWindowTextW()函数起始地址
  • user32.SetWindowTextW()执行完后返回到hookiat.dll执行下一条指令
  • 最后返回到01002628

3、示例:计算器显示中文数字

(1)hookiat.dll

// hookiat.dll

#include "stdio.h"
#include "wchar.h"
#include "windows.h"


// typedef
typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);


// globals
FARPROC g_pOrgFunc = NULL;


BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)

    wchar_t* pNum = L"零一二三四五六七八九";
    wchar_t temp[2] = 0,;
    int i = 0, nLen = 0, nIndex = 0;

    nLen = wcslen(lpString);
    for(i = 0; i < nLen; i++)
    
        // 阿拉伯数字转换为中文数字
        //   lpString 是 wide-character (2 byte) 字符串
        if( L'0' <= lpString[i] && lpString[i] <= L'9' )
        
            temp[0] = lpString[i];
            nIndex = _wtoi(temp);
            lpString[i] = pNum[nIndex];
        
    

    // 调用 user32!SetWindowTextW() API 
    //   (修改 lpString 缓冲区中内容)
    return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);



// hook_iat
//   负责钩取 IAT 
BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)

	HMODULE hMod;
	LPCSTR szLibName;
	PIMAGE_IMPORT_DESCRIPTOR pImportDesc; 
	PIMAGE_THUNK_DATA pThunk;
	DWORD dwOldProtect, dwRVA;
	PBYTE pAddr;

    //查找 IAT 位置
    // hMod, pAddr = ImageBase of calc.exe
    //             = VA to MZ signature (IMAGE_DOS_HEADER)
	hMod = GetModuleHandle(NULL);
	pAddr = (PBYTE)hMod;

    // pAddr = VA to PE signature (IMAGE_NT_HEADERS)
	pAddr += *((DWORD*)&pAddr[0x3C]);

    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
	dwRVA = *((DWORD*)&pAddr[0x80]);

    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
	pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);

    // for循环找到user32.dll
	for( ; pImportDesc->Name; pImportDesc++ )
	
        // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name
		szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
		if( !_stricmp(szLibName, szDllName) )
		
            // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
            //        = VA to IAT(Import Address Table)
			pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod + 
                                         pImportDesc->FirstThunk);
            // for循环找到SetWindowTextW的IAT地址
            // pThunk->u1.Function = VA to API
			for( ; pThunk->u1.Function; pThunk++ )
			
				if( pThunk->u1.Function == (DWORD)pfnOrg )
				
                    // 更改内存属性为 E/R/W
					VirtualProtect((LPVOID)&pThunk->u1.Function, 
                                   4, 
                                   PAGE_EXECUTE_READWRITE, 
                                   &dwOldProtect);

                    // 修改 IAT 值(钩取)
                    pThunk->u1.Function = (DWORD)pfnNew;
					
                    // 恢复内存属性
                    VirtualProtect((LPVOID)&pThunk->u1.Function, 
                                   4, 
                                   dwOldProtect, 
                                   &dwOldProtect);						

					return TRUE;
				
			
		
	

	return FALSE;




BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)

	switch( fdwReason )
	
		case DLL_PROCESS_ATTACH : 
            // 保存原始 API 地址
           	g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"), 
                                        "SetWindowTextW");

            // # hook
            //   用 hookiat.MySetWindowText() 钩取 user32.SetWindowTextW() 
			hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);
			break;

		case DLL_PROCESS_DETACH :
            // # unhook
            //   将 calc.exe 的 IAT 恢复原值
            hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);
			break;
	

	return TRUE;

(2)InjectDll.exe

类似于《逆向工程核心原理》学习笔记(三):DLL注入

// InjectDll.exe

#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "winbase.h"
#include "tchar.h"


void usage()

	printf("\\nInjectDll.exe by ReverseCore\\n"
           "- blog  : http://www.reversecore.com\\n"
           "- email : reversecore@gmail.com\\n\\n"
           "- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\\n\\n");



BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)

	HANDLE hProcess, hThread;
	LPVOID pRemoteBuf;
    DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
	LPTHREAD_START_ROUTINE pThreadProc;

	if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
    
        DWORD dwErr = GetLastError();
		return FALSE;
    

	pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

	WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);

	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");
	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);	

	CloseHandle(hThread);
	CloseHandle(hProcess);

	return TRUE;
 


BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)

	BOOL bMore = FALSE, bFound = FALSE;
	HANDLE hSnapshot, hProcess, hThread;
	MODULEENTRY32 me =  sizeof(me) ;
	LPTHREAD_START_ROUTINE pThreadProc;

	if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) )
		return FALSE;

	bMore = Module32First(hSnapshot, &me);
	for( ;bMore ;bMore = Module32Next(hSnapshot, &me) )
	
		if( !_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName) )
		
			bFound = TRUE;
			break;
		
	

	if( !bFound )
	
		CloseHandle(hSnapshot);
		return FALSE;
	

	if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
	
		CloseHandle(hSnapshot);
		return FALSE;
	

	pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");
	hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
	WaitForSingleObject(hThread, INFINITE);	

	CloseHandle(hThread);
	CloseHandle(hProcess);
	CloseHandle(hSnapshot);

	return TRUE;



DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)

	DWORD dwRtn = 《逆向工程核心原理》学习笔记:API钩取

《逆向工程核心原理》学习笔记:API钩取

《逆向工程核心原理》学习笔记:DLL注入

《逆向工程核心原理》学习笔记:DLL注入

《逆向工程核心原理》学习笔记:DLL注入

x64 下记事本WriteFile() API钩取