逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

时间:2022-03-06 06:56:46

我们通过一个示例来练习向计算机进程插入用户的DLL文件,钩取负责向计算器显示文本的SetWindowTextW,使得计算器中显示中文数字而不是原来的阿拉伯数字。钩取前后的原理图如下所示。

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

下面我们先测试一下代码。
运行calc.exe并查看PID。

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字
在命令行窗口中输入命令与参数。
逆向工程核心原理读书笔记-API钩取之计算器显示中文数字
在计算器中任意输入一些数字,发现变成了中文数字。
逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

我们来分析一下源代码,看看是怎么实现的。
InjectDll.cpp源代码与DLL注入的代码基本结构类似。

[cpp]  view plain  copy
  1. #include "stdio.h"  
  2. #include "windows.h"  
  3. #include "tlhelp32.h"  
  4. #include "winbase.h"  
  5. #include "tchar.h"  
  6.   
  7.   
  8. void usage()  
  9. {  
  10.     printf("\nInjectDll.exe by ReverseCore\n"  
  11.            "- blog  : http://www.reversecore.com\n"  
  12.            "- email : reversecore@gmail.com\n\n"  
  13.            "- USAGE : InjectDll.exe <i|e> <PID> <dll_path>\n\n");  
  14. }  
  15.   
  16.   
  17. BOOL InjectDll(DWORD dwPID, LPCTSTR szDllName)  
  18. {  
  19.     HANDLE hProcess, hThread;  
  20.     LPVOID pRemoteBuf;  
  21.     DWORD dwBufSize = (DWORD)(_tcslen(szDllName) + 1) * sizeof(TCHAR);  
  22.     LPTHREAD_START_ROUTINE pThreadProc;  
  23.   
  24.     if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )  
  25.     {  
  26.         DWORD dwErr = GetLastError();  
  27.         return FALSE;  
  28.     }  
  29.   
  30.     pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);  
  31.   
  32.     WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllName, dwBufSize, NULL);  
  33.   
  34.     pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "LoadLibraryW");  
  35.     hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);  
  36.     WaitForSingleObject(hThread, INFINITE);   
  37.   
  38.     CloseHandle(hThread);  
  39.     CloseHandle(hProcess);  
  40.   
  41.     return TRUE;  
  42. }   
  43.   
  44.   
  45. BOOL EjectDll(DWORD dwPID, LPCTSTR szDllName)  
  46. {  
  47.     BOOL bMore = FALSE, bFound = FALSE;  
  48.     HANDLE hSnapshot, hProcess, hThread;  
  49.     MODULEENTRY32 me = { sizeof(me) };  
  50.     LPTHREAD_START_ROUTINE pThreadProc;  
  51.   
  52.     if( INVALID_HANDLE_VALUE == (hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwPID)) )  
  53.         return FALSE;  
  54.   
  55.     bMore = Module32First(hSnapshot, &me);  
  56.     for( ;bMore ;bMore = Module32Next(hSnapshot, &me) )  
  57.     {  
  58.         if( !_tcsicmp(me.szModule, szDllName) || !_tcsicmp(me.szExePath, szDllName) )  
  59.         {  
  60.             bFound = TRUE;  
  61.             break;  
  62.         }  
  63.     }  
  64.   
  65.     if( !bFound )  
  66.     {  
  67.         CloseHandle(hSnapshot);  
  68.         return FALSE;  
  69.     }  
  70.   
  71.     if( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )  
  72.     {  
  73.         CloseHandle(hSnapshot);  
  74.         return FALSE;  
  75.     }  
  76.   
  77.     pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(L"kernel32.dll"), "FreeLibrary");  
  78.     hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, me.modBaseAddr, 0, NULL);  
  79.     WaitForSingleObject(hThread, INFINITE);   
  80.   
  81.     CloseHandle(hThread);  
  82.     CloseHandle(hProcess);  
  83.     CloseHandle(hSnapshot);  
  84.   
  85.     return TRUE;  
  86. }  
  87.   
  88.   
  89. DWORD _EnableNTPrivilege(LPCTSTR szPrivilege, DWORD dwState)  
  90. {  
  91.     DWORD dwRtn = 0;  
  92.     HANDLE hToken;  
  93.     if (OpenProcessToken(GetCurrentProcess(),  
  94.         TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))  
  95.     {  
  96.         LUID luid;  
  97.         if (LookupPrivilegeValue(NULL, szPrivilege, &luid))  
  98.         {  
  99.             BYTE t1[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];  
  100.             BYTE t2[sizeof(TOKEN_PRIVILEGES) + sizeof(LUID_AND_ATTRIBUTES)];  
  101.             DWORD cbTP = sizeof(TOKEN_PRIVILEGES) + sizeof (LUID_AND_ATTRIBUTES);  
  102.   
  103.             PTOKEN_PRIVILEGES pTP = (PTOKEN_PRIVILEGES)t1;  
  104.             PTOKEN_PRIVILEGES pPrevTP = (PTOKEN_PRIVILEGES)t2;  
  105.   
  106.             pTP->PrivilegeCount = 1;  
  107.             pTP->Privileges[0].Luid = luid;  
  108.             pTP->Privileges[0].Attributes = dwState;  
  109.   
  110.             if (AdjustTokenPrivileges(hToken, FALSE, pTP, cbTP, pPrevTP, &cbTP))  
  111.                 dwRtn = pPrevTP->Privileges[0].Attributes;  
  112.         }  
  113.   
  114.         CloseHandle(hToken);  
  115.     }  
  116.   
  117.     return dwRtn;  
  118. }  
  119.   
  120.   
  121. int _tmain(int argc, TCHAR* argv[])  
  122. {  
  123.     if( argc != 4 )  
  124.     {  
  125.         usage();  
  126.         return 1;  
  127.     }  
  128.   
  129.     // adjust privilege  
  130.     _EnableNTPrivilege(SE_DEBUG_NAME, SE_PRIVILEGE_ENABLED);  
  131.   
  132.     // InjectDll.exe <i|e> <PID> <dll_path>  
  133.     if( !_tcsicmp(argv[1], L"i") )  
  134.         InjectDll((DWORD)_tstoi(argv[2]), argv[3]);  
  135.     else if(!_tcsicmp(argv[1], L"e") )  
  136.         EjectDll((DWORD)_tstoi(argv[2]), argv[3]);  
  137.   
  138.     return 0;  
  139. }  
下面详细讲解hookiat.dll的源代码(hookiat.cpp)。
DLLMain的代码非常简单。在DLL_PROCESS_ATTACH事件中先获取user32.SetWindowTextW的地址,然后保存到全局变量g_pOrgFunc中,后面脱钩会用到这个地址。
[cpp]  view plain  copy
  1. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)  
  2. {  
  3.     switch( fdwReason )  
  4.     {  
  5.         case DLL_PROCESS_ATTACH :   
  6.             // 保存原始API的地址  
  7.             g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),   
  8.                                         "SetWindowTextW");  
  9.   
  10.             // # hook  
  11.             //   用hookiat.MySetWindowText钩取user32.SetWindowTextW  
  12.             hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);  
  13.             break;  
  14.   
  15.         case DLL_PROCESS_DETACH :  
  16.             // # unhook  
  17.             //   将calc.exe的IAT恢复原值  
  18.             hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);  
  19.             break;  
  20.     }  
  21.   
  22.     return TRUE;  
  23. }  
下面看看MySetWindowTextW函数。lpString参数是一块缓冲区,该缓冲区用来存放要输出显示的字符串。for循环将存放在lpString的阿拉伯数字字符串转换为中文数字字符串。for循环结束后,最后再调用函数指针g_pOrgFunc。
[cpp]  view plain  copy
  1. BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)  
  2. {  
  3.     wchar_t* pNum = L"零一二三四五六七八九";  
  4.     wchar_t temp[2] = {0,};  
  5.     int i = 0, nLen = 0, nIndex = 0;  
  6.   
  7.     nLen = wcslen(lpString);  
  8.     for(i = 0; i < nLen; i++)  
  9.     {  
  10.         //   将阿拉伯数字转换为中文数字  
  11.         //   lpString是宽字符版本(2个字节)字符串  
  12.         if( L'0' <= lpString[i] && lpString[i] <= L'9' )  
  13.         {  
  14.             temp[0] = lpString[i];  
  15.             nIndex = _wtoi(temp);  
  16.             lpString[i] = pNum[nIndex];  
  17.         }  
  18.     }  
  19.   
  20.     //   调用user32.SetWindowTextW  
  21.     //   (修改lpString缓冲区中的内容)  
  22.     return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);  
  23. }  
接下来分析hook_iat函数,它负责具体钩取API。
[cpp]  view plain  copy
  1. BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)  
  2. {  
  3.     HMODULE hMod;  
  4.     LPCSTR szLibName;  
  5.     PIMAGE_IMPORT_DESCRIPTOR pImportDesc;   
  6.     PIMAGE_THUNK_DATA pThunk;  
  7.     DWORD dwOldProtect, dwRVA;  
  8.     PBYTE pAddr;  
  9.   
  10.     // hMod, pAddr = ImageBase of calc.exe  
  11.     //             = VA to MZ signature (IMAGE_DOS_HEADER)  
  12.     hMod = GetModuleHandle(NULL);  
  13.     pAddr = (PBYTE)hMod;  
  14.   
  15.     // pAddr = VA to PE signature (IMAGE_NT_HEADERS)  
  16.     pAddr += *((DWORD*)&pAddr[0x3C]);  
  17.   
  18.     // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table  
  19.     dwRVA = *((DWORD*)&pAddr[0x80]);  
  20.   
  21.     // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table  
  22.     pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);  
  23.   
  24.     for( ; pImportDesc->Name; pImportDesc++ )  
  25.     {  
  26.         // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name  
  27.         szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);  
  28.         if( !_stricmp(szLibName, szDllName) )  
  29.         {  
  30.             // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk  
  31.             //        = VA to IAT(Import Address Table)  
  32.             pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +   
  33.                                          pImportDesc->FirstThunk);  
  34.   
  35.             // pThunk->u1.Function = VA to API  
  36.             for( ; pThunk->u1.Function; pThunk++ )  
  37.             {  
  38.                 if( pThunk->u1.Function == (DWORD)pfnOrg )  
  39.                 {  
  40.                   
  41.                     VirtualProtect((LPVOID)&pThunk->u1.Function,   
  42.                                    4,   
  43.                                    PAGE_EXECUTE_READWRITE,   
  44.                                    &dwOldProtect);  
  45.   
  46.                     pThunk->u1.Function = (DWORD)pfnNew;  
  47.                   
  48.                     VirtualProtect((LPVOID)&pThunk->u1.Function,   
  49.                                    4,   
  50.                                    dwOldProtect,   
  51.                                    &dwOldProtect);                        
  52.   
  53.                     return TRUE;  
  54.                 }  
  55.             }  
  56.         }  
  57.     }  
  58.   
  59.     return FALSE;  
  60. }  
首先从ImageBase开始经由PE签名找到IDT。

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

pImportDesc变量中存储着IMAGE_IMPORT_DESCRIPTOR结构体的起始地址,后者是calc.exe进程IDT的第一个结构体。IDT是由IMAGE_IMPORT_DESCRIPTOR结构体组成的数组。若想查找到IAT,先要查找到这个位置。使用PEView查看该地址(00012B80+01000000=01012B80),如图所示。

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

在for循环中通过比较查找到user32.dll的IMAGE_IMPORT_DESCRIPTOR结构体地址,从上图可以看出最终pImportDesc的值为01012BF4。接下来进入user32的IAT,pImportDesc->FirstThunk成员所指的就是IAT。使用PEView查看该地址(000010A4+01000000=010010A4),如图所示。

逆向工程核心原理读书笔记-API钩取之计算器显示中文数字

接下来又在for循环中查找SetWindowTextW的IAT地址(01001110),然后修改它的值。因为计算器进程的IAT内存区域是只读的,所以需要使用VirtualProtect在钩取之前将相应的区域改为可读写的,钩取之后再改回来。
完整的代码如下。

[cpp]  view plain  copy
  1. // include  
  2. #include "stdio.h"  
  3. #include "wchar.h"  
  4. #include "windows.h"  
  5.   
  6.   
  7. // typedef  
  8. typedef BOOL (WINAPI *PFSETWINDOWTEXTW)(HWND hWnd, LPWSTR lpString);  
  9.   
  10.   
  11. // globals  
  12. FARPROC g_pOrgFunc = NULL;  
  13.   
  14.   
  15. BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)  
  16. {  
  17.     wchar_t* pNum = L"零一二三四五六七八九";  
  18.     wchar_t temp[2] = {0,};  
  19.     int i = 0, nLen = 0, nIndex = 0;  
  20.   
  21.     nLen = wcslen(lpString);  
  22.     for(i = 0; i < nLen; i++)  
  23.     {  
  24.         //   将阿拉伯数字转换为中文数字  
  25.         //   lpString是宽字符版本(2个字节)字符串  
  26.         if( L'0' <= lpString[i] && lpString[i] <= L'9' )  
  27.         {  
  28.             temp[0] = lpString[i];  
  29.             nIndex = _wtoi(temp);  
  30.             lpString[i] = pNum[nIndex];  
  31.         }  
  32.     }  
  33.   
  34.     //   调用user32.SetWindowTextW  
  35.     //   (修改lpString缓冲区中的内容)  
  36.     return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);  
  37. }  
  38.   
  39.   
  40. BOOL hook_iat(LPCSTR szDllName, PROC pfnOrg, PROC pfnNew)  
  41. {  
  42.     HMODULE hMod;  
  43.     LPCSTR szLibName;  
  44.     PIMAGE_IMPORT_DESCRIPTOR pImportDesc;   
  45.     PIMAGE_THUNK_DATA pThunk;  
  46.     DWORD dwOldProtect, dwRVA;  
  47.     PBYTE pAddr;  
  48.   
  49.     // hMod, pAddr = ImageBase of calc.exe  
  50.     //             = VA to MZ signature (IMAGE_DOS_HEADER)  
  51.     hMod = GetModuleHandle(NULL);  
  52.     pAddr = (PBYTE)hMod;  
  53.   
  54.     // pAddr = VA to PE signature (IMAGE_NT_HEADERS)  
  55.     pAddr += *((DWORD*)&pAddr[0x3C]);  
  56.   
  57.     // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table  
  58.     dwRVA = *((DWORD*)&pAddr[0x80]);  
  59.   
  60.     // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table  
  61.     pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod+dwRVA);  
  62.   
  63.     for( ; pImportDesc->Name; pImportDesc++ )  
  64.     {  
  65.         // szLibName = VA to IMAGE_IMPORT_DESCRIPTOR.Name  
  66.         szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);  
  67.         if( !_stricmp(szLibName, szDllName) )  
  68.         {  
  69.             // pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk  
  70.             //        = VA to IAT(Import Address Table)  
  71.             pThunk = (PIMAGE_THUNK_DATA)((DWORD)hMod +   
  72.                                          pImportDesc->FirstThunk);  
  73.   
  74.             // pThunk->u1.Function = VA to API  
  75.             for( ; pThunk->u1.Function; pThunk++ )  
  76.             {  
  77.                 if( pThunk->u1.Function == (DWORD)pfnOrg )  
  78.                 {  
  79.                     // 更改为可读写模式  
  80.                     VirtualProtect((LPVOID)&pThunk->u1.Function,   
  81.                                    4,   
  82.                                    PAGE_EXECUTE_READWRITE,   
  83.                                    &dwOldProtect);  
  84.   
  85.                     // 修改IAT的值  
  86.                     pThunk->u1.Function = (DWORD)pfnNew;  
  87.                   
  88.                     VirtualProtect((LPVOID)&pThunk->u1.Function,   
  89.                                    4,   
  90.                                    dwOldProtect,   
  91.                                    &dwOldProtect);                        
  92.   
  93.                     return TRUE;  
  94.                 }  
  95.             }  
  96.         }  
  97.     }  
  98.   
  99.     return FALSE;  
  100. }  
  101.   
  102.   
  103.   
  104. BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)  
  105. {  
  106.     switch( fdwReason )  
  107.     {  
  108.         case DLL_PROCESS_ATTACH :   
  109.             // 保存原始API的地址  
  110.             g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),   
  111.                                         "SetWindowTextW");  
  112.   
  113.             // # hook  
  114.             //   用hookiat.MySetWindowText钩取user32.SetWindowTextW  
  115.             hook_iat("user32.dll", g_pOrgFunc, (PROC)MySetWindowTextW);  
  116.             break;  
  117.   
  118.         case DLL_PROCESS_DETACH :  
  119.             // # unhook  
  120.             //   将calc.exe的IAT恢复原值  
  121.             hook_iat("user32.dll", (PROC)MySetWindowTextW, g_pOrgFunc);  
  122.             break;  
  123.     }  
  124.   
  125.     return TRUE;  
  126. }