安全编程技术-API hook实验

API Hook实验

API Hook技术是对API函数进行Hook(挂钩)的技术。API Hook的基本方法就是通过hook“接触”到需要修改的API函数入口点,改变它的地址指向新的自定义的函数。

API Hook和消息Hook的区别是,它们Hook的对象不同:消息Hook Windows消息,API Hook 特定函数

API Hook方法多种:IAT Hook、代码Hook、EAT Hook

EAT: export address table,导出地址表

本次实验是一个IAT Hook示例。


实验环境:Win xp sp3, VC 6.0


利用API Hook技术,模拟对lstrcpy函数进行Hook,捕获输入输出。

步骤:

[1] 编写自定义函数:实现检测等需要的功能;

[2] Hook实现:根据PE文件结构寻找IAT,并将IAT中的目标函数的地址更换为自定义的函数地址;

[3] Dll注入:将包含IAT Hook代码及自定义的Hook函数的Dll注入到目标文件中。


  1. strcpy_exe应用程序的代码(注入的目标文件):
	#include <stdio.h>
	#include <string.h>
	#include <windows.h>
	
	int main(){
	    char dest[50] = {0};
	    char src[50] = {0};
	    char flag = '0';
	    
	    while(true)
	    {
	        printf("push q to quit, c to continue!\n");
	        flag=getchar();
	        getchar();
	        if(flag == 'q') break;
	        printf("Input Src String!\n");
	        printf("Src:  ");
	        gets(src);
	        lstrcpyW((LPWSTR)dest, (LPWSTR)src);
	        printf("Dest:%s\n\n",dest);
	    }
	    return 0;
	}
  1. strcpy_hookiat.dll编写(自定义函数功能)

    #include <stdio.h>
    #include <stdlib.h>
    #include <wchar.h>
    #include <windows.h>
    #include <time.h>
    
    typedef LPTSTR(WINAPI* PFLSTRCPYW)(LPWSTR lpString1, LPWSTR lpString2);
    FARPROC g_pOrgFunc = NULL;
    
    char* wideCharToMultiByte(wchar_t* pwcStrKey)
    {
        // 第一次调用确认转换后单字节字符串的长度,用于开辟空间
        int pSize = WideCharToMultiByte(CP_OEMCP, 0, pwcStrKey, wcslen(pwcStrKey), NULL, 0, NULL, NULL);
        char* pcStrKey = new char[pSize + 1];
        
        // 第二次调用将双字节字符串转换成单字节字符串
        WideCharToMultiByte(CP_OEMCP, 0, pwcStrKey, wcslen(pwcStrKey), pcStrKey, pSize, NULL, NULL);
        pcStrKey[pSize] = '\0';
        
        return pcStrKey;
        //如果想要转换成String,直接赋值即可
        //String pKey = pcStrKey;
    }
    
    void writelog(const char *log)
    {
        time_t tDate;
        struct tm *eventTime;
        time(&tDate);
        eventTime = localtime(&tDate);
        
        int iYear = eventTime->tm_year + 1900;
        int iMon = eventTime->tm_mon + 1;
        int iDay = eventTime->tm_mday;
        int iHour = eventTime->tm_hour;
        int iMin = eventTime->tm_min;
        int iSec = eventTime->tm_sec;
        
        char sDate[16];
        sprintf(sDate, "%04d-%02d-%02d", iYear, iMon, iDay);
        
        char sTime[16];
        sprintf(sTime, "%02d:%02d:%02d", iHour, iMin, iSec);
        
        char s[1024];
        sprintf(s, "%s %s %s\n", sDate, sTime, log);
        
        FILE *fd = fopen("my.log", "a+");
        if (fd != NULL) {
            fputs(s, fd);
            fclose(fd);
        }
    }
    
    // MylstrcpyW
    LPTSTR WINAPI MylstrcpyW(LPWSTR lpString1, LPWSTR lpString2)
    {
        char log[100];
        //char* dst = wideCharToMultiByte(lpString1);
        //char* src = wideCharToMultiByte(lpString2);
        
        sprintf(log, "lstrcpyW被调用:\tDST参数为%s\tSRC参数为%s", (char*)(lpString1), (char*)(lpString2));
        writelog(log);
        
        // 执行kernel32!lstrcpyW() API
        LPTSTR Result = ((PFLSTRCPYW)g_pOrgFunc)(lpString1, lpString2);
        
        if (Result != NULL) {
            sprintf(log, "本次Cpy成功:\tDST参数为%s\tSRC参数为%s", (char*)(lpString1), (char*)(lpString2));
            writelog(log);
        } else {
            sprintf(log, "本次cpy失败");
            writelog(log);
        }
        
        //delete[] dst;
        //delete[] src;
        
        return Result;
    }
    
    // IAT HOOK函数
    BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)
    {
        // szDllName指目标API所在系统Dll名称,即"kernel32.dll"
        // pfnOrg指原始API地址,即lstrcpyW()的地址
        // pfnNew指用于替换lstrcpyW()的自定义函数的地址,即MylstrcpyW()的地址
    
        HMODULE hMod;
        LPCSTR szLibName;
        PIMAGE_IMPORT_DESCRIPTOR pImportDesc;
        PIMAGE_THUNK_DATA pThunk;
        DWORD dwOldProtect, dwRVA;
        PBYTE pAddr;
    
        // hMod, pAddr = 可执行文件的ImageBase
        //             = VA of MZ signature (IMAGE_DOS_HEADER)
        hMod = GetModuleHandle(NULL);
        pAddr = (PBYTE)hMod;
    
        // pAddr = VA of PE signature (IMAGE_NT_HEADERS)
        pAddr += *((DWORD*)&pAddr[0x3C]);
    
       // dwRVA = RVA of IMAGE_IMPORT_DESCRIPTOR Table
       dwRVA = *((DWORD*)&pAddr[0x80]);
    
        // pImportDesc = VA of IMAGE_IMPORT_DESCRIPTOR Table
        pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA);
        
        for (; pImportDesc->Name; pImportDesc++) {
            // szLibName = VA of 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);
                
                // pThunk->u1.Function = VA of 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((LPCTSTR)"kernel32.dll"), "lstrcpyW");
                
                // 用hookiat!MysetWindowText()钩取kernel32!SetWindowTextW()
                hook_iat("kernel32.dll", g_pOrgFunc, (PROC)MylstrcpyW);
                break;
                
            case DLL_PROCESS_DETACH:
                // 将..exe的IAT恢复原值
                hook_iat("kernel32.dll", (PROC)MylstrcpyW, g_pOrgFunc);
                break;
        }
        
        return TRUE;
    }
    
    1. InjectDll.exe编写(通过PID注入dll)
    #include <stdio.h>
    #include <windows.h>
    #include <tlhelp32.h>
    #include <winbase.h>
    #include <tchar.h>
    
    // 定义缺少的常量
    #ifndef SE_PRIVILEGE_ENABLED
    #define SE_PRIVILEGE_ENABLED 0x00000002
    #endif
    
    #ifndef SE_DEBUG_NAME
    #define SE_DEBUG_NAME TEXT("SeDebugPrivilege")
    #endif
    
    void usage()
    {
        printf("\nInjectDll.exe\n"
               "- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\n\n");
    }
    
    // dwPID指待注入目标进程的PID值
    // szDllName指带注入DLL的path
    BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)
    {
        HANDLE hProcess, hThread;
        LPVOID pRemoteBuf;
        DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);
        LPTHREAD_START_ROUTINE pThreadProc;
    
        // #1.使用dwPID获取目标进程(notepad.exe)句柄
        if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
        {
            DWORD dwErr = GetLastError();
            _tprintf(_T("OpenProcess failed: %lu\n"), dwErr);
            return FALSE;
        }
    
        // #2.在目标进程(notepad.exe)中分配szDllName大小的内存
        pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);
        if (pRemoteBuf == NULL)
        {
            _tprintf(_T("VirtualAllocEx failed\n"));
            CloseHandle(hProcess);
            return FALSE;
        }
    
        // #3.将szDll路径写入分配的内存
        if (!WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL))
        {
            _tprintf(_T("WriteProcessMemory failed\n"));
            VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return FALSE;
        }
    
        // #4.获取LoadLibraryA() API的地址
        pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "LoadLibraryA");
        if (pThreadProc == NULL)
        {
            _tprintf(_T("GetProcAddress failed\n"));
            VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return FALSE;
        }
    
        // #5.在exe进程中运行线程
        hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
        if (hThread == NULL)
        {
            _tprintf(_T("CreateRemoteThread failed\n"));
            VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
            CloseHandle(hProcess);
            return FALSE;
        }
    
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
        VirtualFreeEx(hProcess, pRemoteBuf, 0, MEM_RELEASE);
        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;
    
        //使用TH32CS_SNAPMODULE参数,获取eXe加载的DLL名称
        if (INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)))
        {
            _tprintf(_T("CreateToolhelp32Snapshot failed\n"));
            return FALSE;
        }
    
        //从DLL中查找注入的DLL
        bMore = Module32First(hSnapshot, &me);
        for (; bMore; bMore = Module32Next(hSnapshot, &me))
        {
            if (!_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName))
            {
                bFound = TRUE;
                break;
            }
        }
    
        if (!bFound)
        {
            _tprintf(_T("DLL not found in target process\n"));
            CloseHandle(hSnapshot);
            return FALSE;
        }
    
        //OpenProcess函数用来打开一个己存在的进程对象,并返回进程的句柄
        if (!(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)))
        {
            _tprintf(_T("OpenProcess failed\n"));
            CloseHandle(hSnapshot);
            return FALSE;
        }
    
        //获取FreeLibrary() API的地址
        pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandleA("kernel32.dll"), "FreeLibrary");
        if (pThreadProc == NULL)
        {
            _tprintf(_T("GetProcAddress failed\n"));
            CloseHandle(hProcess);
            CloseHandle(hSnapshot);
            return FALSE;
        }
    
        //在exe进程中运行线程
        hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);
        if (hThread == NULL)
        {
            _tprintf(_T("CreateRemoteThread failed\n"));
            CloseHandle(hProcess);
            CloseHandle(hSnapshot);
            return FALSE;
        }
    
        WaitForSingleObject(hThread, INFINITE);
        CloseHandle(hThread);
        CloseHandle(hProcess);
        CloseHandle(hSnapshot);
        return TRUE;
    }
    
    DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)
    {
        DWORD dwRtn = 0;
        HANDLE hToken;
        //获得进程访问令牌的句柄
        if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
        {
            LUID luid;
            //查看系统权限的特权值,返向信息到一个LUID结构体里
            if (LookupPrivilegeValue(NULL, szPrivilege, &luid))
            {
                BYTE t1[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
                BYTE t2[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];
                DWORD cbTP = sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES);
    
                PTOKEN_PRIVILEGES pTP = (PTOKEN_PRIVILEGES)t1;
                PTOKEN_PRIVILEGES pPrevTP = (PTOKEN_PRIVILEGES)t2;
    
                pTP->PrivilegeCount = 1;
                pTP->Privileges[0].Luid = luid;   //SE_DEBUG_NAME特权
                pTP->Privileges[0].Attributes = dwState; //SE_PRIVILEGE_ENABLED表示属性为特权启动
    
                //启用或禁止指定访问令牌的特权
                if (AdjustTokenPrivileges(hToken, FALSE, pTP, cbTP, pPrevTP, &cbTP))
                {
                    dwRtn = pPrevTP->Privileges[0].Attributes;
                }
            }
            CloseHandle(hToken);
        }
        return dwRtn;
    }
    
    int _tmain(int argc, TCHAR *argv[])
    {
        if (argc != 4)
        {
            usage();
            return 1;
        }
    
        //adjust privilege
        _EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED);
    
        //InjectDll.exe <i|e> <PID> <dll_path>
        BOOL result = FALSE;
        if (!_tcsicmp(argv[1], _T("i")))
        {
            result = InjectDll((DWORD)_ttoi(argv[2]), argv[3]);
        }
        else if (!_tcsicmp(argv[1], _T("e")))
        {
            result = EjectDll((DWORD)_ttoi(argv[2]), argv[3]);
        }
        else
        {
            _tprintf(_T("Invalid option: %s\n"), argv[1]);
            usage();
            return 1;
        }
    
        _tprintf(_T("Operation %s\n"), result ? _T("succeeded") : _T("failed"));
        return result ? 0 : 1;
    }
    
    1. 结果演示

      • 将文件放置到同一路径下
        image-20250510090903590

      • 运行strcpy_exe,在资源管理器中查看PID 这里是1844
        image-20250510091313751

      • 将InjectDll.exe拖入cmd命令窗口,按下回车可以看见用法
        image-20250510091513269

      • 将InjectDll.exe拖入cmd后,输入 i 1844 ,再把dll也拖入cmd
        image-20250510091635932

      • 按下回车,提示Operation succeeded
        image-20250510091807130

      • 再使用strcpy_exe进行复制字符串,会在目录下生成一个my.txt,里面记录了复制的日志
        image-20250510092234728

        image-20250510092119520

    至于这里为什么要双写前两个字符,据说这里固定丢失前两个字符,具体原因不是本次实验的重点。

posted @ 2025-05-10 09:44  踏雪xun槑  阅读(19)  评论(0)    收藏  举报