首页
社区
课程
招聘
[分享]IAT钩取 之 让计算器显示中文数字
发表于: 2021-8-18 16:26 6833

[分享]IAT钩取 之 让计算器显示中文数字

2021-8-18 16:26
6833

IAT钩取 之 让计算器显示中文数字

目录

 

API 钩取技术中有一种是通过注入 DLL 文件来钩取某个 API 的,DLL文件注入目标进程后,修改 IAT 来更改进程中调用的特定 API 的功能。

简单分析

“通过 DLL 注入实现 IAT 钩取的技术”,这项技术的优点是工作原理与具体实现都比较简单(只需要先将勾取的 API 在用户的 DLL 重定义,然后再注入目标进程即可);缺点是,如果想钩取的 API 不在目标进程的 IAT 中,那么就无法使用该技术进行钩取操作。换而言之,如果要钩取的 API 是由程序代码动态加载 DLL 文件而得以使用的,那么我们将无法使用这项技术钩取它。

让计算器显示中文数字

使用 DLL 注入实现 IAT 钩取技术,使得计算器显示中文数字,而不是阿拉伯数字。

选定目标API

使用 PEview 工具查看 calc.exe 的导入表。确定是哪个 API 提供了要钩取的哪个功能。如下所示

 

1.jpg

 

上图中有两个 API 比较引人注目,分别是 SetWindowTextW()、SetDlgItemTextW(),经过查询,SetDlgItemTextW() 内部又调用了 SetWindowTextW() ,因此假设主要钩取 SetWindowTextW() 这一个 API 。

 

SetWindowTextW() 定义

1
2
3
4
5
6
7
8
BOOL SetWindowTextW(
    HWND        hWnd,
    LPCTSTR     lpString
);
 
param
第一个参数(hWnd)为窗口句柄。
第二个参数(lpString)为字符串指针。

 

API 名称最后的“W”表示该 API 是宽字符(“Wide character”)版本。
API 名称最后的“A”表示该 API 是ASCII(“ASCII character”)版本。

验证假设

使用 Ollydbg 打开 calc.exe ,查找计算器代码调试中 SetWindowTextW() API 部分并设置断点,运行计算器,调式器在设置断点处暂停,如下

 

2.jpg

 

继续运行程序,如下图所示,计算器显示“0.”,为了调试在计算器中随便输入数字,比如 7 。

 

3.jpg

 

4.jpg

 

将缓冲区的数据改成中文的数字 “七”("37 00"-->"03 4e"),“七”的 Unioncode 码为 “4e03”,由于是小段标记法,所以覆写时要逆序(034e)。修改后继续运行,在计算器中可以看见显示了中文数字“七”。如下所示

 

5.jpg

 

到这里,我们对 SetWindowTextW() API 的验证就结束了。验证结果表明我们的假设是对的。经过上述验证知道了 SetWindowTextW() API 的位置(01002628) ,并且确定了一个事实:只要修改字符串中的内容,就能修改计算器中显示的格式。

源代码分析

InjectDll.exe 源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
#include "stdio.h"
#include "windows.h"
#include "tlhelp32.h"
#include "winbase.h"
#include "tchar.h"
 
 
void usage()
{
    printf("- 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 = 0;
    HANDLE hToken;
    if (OpenProcessToken(GetCurrentProcess(),
        TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &hToken))
    {
        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;
            pTP->Privileges[0].Attributes = dwState;
 
            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>
    if (!_tcsicmp(argv[1], L"i"))
        InjectDll((DWORD)_tstoi(argv[2]), argv[3]);
    else if (!_tcsicmp(argv[1], L"e"))
        EjectDll((DWORD)_tstoi(argv[2]), argv[3]);
 
    return 0;
}

InjectDll.exe 源码比较简单这里就不加分析了

hookiat.dll 源码

hookiat.dll 源码

1
 

DllMain()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
BOOL WINAPI DllMain(HINSTANCE hModule, DWORD ul_reason_for_call, LPVOID lpvReserved)
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
        // g_pOrgFunc 保存原始 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;
}

SetWindwoTextW()

1
2
case DLL_PROCESS_ATTACH:
     g_pOrgFunc = GetProcAddress(GetModuleHandle(L"user32.dll"),

在 DLL_PROCESS_ATTACH 事件中先获取 user32.SetWindowTextW() API 的地址,然后将其保存到全局变量(g_pOrgFunc),后面“脱钩”时会用到这个地址。

IAT 钩取

1
hook_iat("user32.dll", g_pOrgGunc, (PROC)MySetWindowTextW);

上面这条语句用来调用 hookiat() 函数,钩取 IAT (即将 user32.SetWindowTextW()的地址更改为 hookiat.MySetWindowTextW()的地址更改为user32.SetWindowTextW()的地址)

hook_iait()

hook_iait()函数源码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
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;
 
    // 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]);   //e_lfanew
 
    // dwRVA = RVA to IMAGE_IMPORT_DESCRIPTOR Table
    dwRVA = *((DWORD*)&pAddr[0x80]);    //可选头->IMAGE_IMPORT_DESCRIPTOR
 
    // pImportDesc = VA to IMAGE_IMPORT_DESCRIPTOR Table
    pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA);
 
    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);
 
            // 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;
}

该函数时具体执行 IAT 钩取的函数。函数代码虽然不长脑,但是其中有很多注释,使函数自身看上去比较长。

 

hook_iat() 函数的前半部分用来读取 PE 文件头信息,并查找 IAT 的位置

1
2
3
4
5
hMod    = GetModuleHandle(NULL);    //hMod  = ImageBase
pAddr   = (PBYTE)hMod;              //pAddr = ImageBase
pAddr  += *((DWORD*)&Addr[0x3C]);   //pAddr = "PE" signature
dwRVA   = *((DWORD*)&pAddr[0x80]);  //dwRVA = RVA of IMAGE_IMPORT_DESCRIPTOR
pImportDesc = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD)hMod + dwRVA);

上面这几行代码首先从 ImageBase 开始,经由 PE 签名找到 IDT 。pImportDesc 变量中存储者 IMAGE_IMPORT_DESCRIPTOR 结构体的起始地址,后者时 calc.exe 进程 IDT 的第一个结构体。 IDT 是由 IMAGE_IMPORT_DESCRIPTOR 结构体组成的数组, 若想查找到 IAT ,先要查找到这个位置。

 

接下来使用 for 循环遍历该 IDT 。

1
2
3
4
5
6
7
8
9
for( ; pImportDesc-> Name ; pImportDesc++)
{
    //szLibName = VA to IMAGE_IMPORT_DESCRIPTRO.Name
    szLibName = (LPCSTR)((DWORD)hMod + pImportDesc->Name);
    if(!stricmp(szLibName.szDllName))
    {
        ...  
    }
}

在上面的 for 循环中比较 pImportDesc -> Name 与 szDllName("user32.dll"),通过比较查找到 user32.dll 的 IMAGE_IMPORT_DESCRIPTOR 结构体地址。 pImportDesc -> FirstThunk 成员所指的就是 IAT。

1
2
3
//pThunk = IMAGE_IMPORT_DESCRIPTOR.FirstThunk
//       = VA to IAT( import address table)
pThunk   = (PIMAGE_THUNK_DATA)((DWORD)hMod + pImportDesc->FisrtThunk);
1
2
3
4
5
6
7
8
//pThunk -> u1.Function = VA to API
for( ; pThunk -> u1.Function; pThunk++)
{
    if( pThunk -> u1.Function == (DWORD)pfnOrg)
    {
        ...
    }
}

在上面的 for 循环中比较 pThunk->u1.Function 与 pfnOrg(SetWindowTextW 的起始地址),准确查找到 SetWindowTextW 的IAT 地址。

 

上面代码就是计算器进程的 ImageBase 开始查找 user32.SetWindowTextW 的 IAT 地址的整个过程。

 

查找到 IAT 地址之后,接下来就要修改(hooking)它的值。

1
2
//修改 IAT 值
pThunk -> u1.Function = (DWORD)pfnNew;

pThunk->u1.Function中,原来的值为 SetWindowTextW 的地址,上面的语句将其修改为 pfnNew 值。这样,计算器代码调用 user32.SetWindowTextW() API 时,实际会先调用 hookiat.MySetWindowTextW()函数。

 

提示

 

从上述hook_iat()函数代码中可以看到,钩取前先调用了 VirtualProtect()函数,将相应的 IAT 的内存区域更改为“可读写模式”,钩取之后重新返回原模式的代码是存在的。该语句用来改变内存属性,由于计算器进程的 IAT 内存区域是只读的,所以需要使用该语句将其更改为“可读写”模式。

MySetWindowTextW()

MySetWindowTextW() 函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// MySetWindowTextW 函数
BOOL WINAPI MySetWindowTextW(HWND hWnd, LPWSTR lpString)
{
    wchar_t* pNum = (wchar_t*)L"零一二三四五六七八九";
    wchar_t temp[2] = { 0, };
    int i = 0, nLen = 0, nIndex = 0;
 
    nLen = wcslen(lpString);
    for (i = 0; i < nLen; i++)
    {
        // 将阿拉伯数字转换成中文数字
        //   lpString 是宽字节版本(2个字节)字符串
        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);
}

计算器进程(calc.exe)的 IAT 被钩取后,每当代码中调用user32.SetWindowTextW() 函数时,都会首先调用 hookiat.MySetWindowTextW()函数。

字符转换

1
2
3
4
5
6
7
8
9
10
11
for (i = 0; i < nLen; i++)
    {
        // 将阿拉伯数字转换成中文数字
        //   lpString 是宽字节版本(2个字节)字符串
        if (L'0' <= lpString[i] && lpString[i] <= L'9')
        {
            temp[0] = lpString[i];
            nIndex = _wtoi(temp);
            lpString[i] = pNum[nIndex];
        }
    }

上述代码将存放在lpString的阿拉伯数字字符串转换为中文数字字符串。

return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);

1
return ((PFSETWINDOWTEXTW)g_pOrgFunc)(hWnd, lpString);

返回时调用函数指针g_pOrgFunc ,它指向 user32.SetWindowTextW() API 的起始位置(该地址在 DllMain()中已经保存了下来)。也就是说,,调用 原来的SetWindowTextW() 函数,将变换后的中文数字消失在计算器的显示框中。

实验效果

钩取之前的截图

 

6.jpg

 

钩取之后的截图

 

7.jpg

 

“脱钩”之后的截图

 

8.jpg


[招生]科锐逆向工程师培训(2024年11月15日实地,远程教学同时开班, 第51期)

收藏
免费 1
支持
分享
最新回复 (2)
雪    币: 86
活跃值: (2026)
能力值: ( LV3,RANK:20 )
在线值:
发帖
回帖
粉丝
2
求一下课件~也在看这本书 
2021-8-18 19:48
1
雪    币: 1099
活跃值: (1206)
能力值: ( LV4,RANK:40 )
在线值:
发帖
回帖
粉丝
3
课件地址:https://reversecore.com/
2021-8-18 20:19
0
游客
登录 | 注册 方可回帖
返回
//