盒子
盒子
文章目录
  1. Windows进程注入
    1. Dll注入
      1. 获取目标进程的所有dll基址
    2. 代码注入
  2. 参考链接

Windows进程注入

Windows进程注入

原理很简单,用Windows API函数CreateRemoteThread即可。不过进程注入一般分为两种情况,第一种是最常见的情况,为dll注入。就是将dll注入到进程当中实现自己所想要的操作,dll里的功能就是自己编写的代码。

Dll注入

  1. 先调用OpenProcess获取目标进程的句柄
  2. 再调用VirtualAllocEx在目标进程中分配一块内存,用来存储自己的内容,例如dll文件的路径名
  3. 再调用WriteProcessMemory填充分配的内存
  4. 再调用CreateRemoteThread在目标进程中注入线程函数(具体看代码)
  5. 再调用WaitForSingleObject等待线程函数执行结束
  6. 最终结束处理,调用VirtualFreeExCloseHandle做资源释放

具体怎么完成的看代码,先看注入代码:

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
#include "stdafx.h"
#include <Windows.h>
#define path _T("C:\\test.dll")
bool Inject(DWORD dwId,WCHAR* szPath)
{
//1 在目标进程中申请一个空间
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwId);
LPVOID pRemoteAddress = VirtualAllocEx(
hProcess,
NULL,
1,
MEM_COMMIT,
PAGE_READWRITE
);
//2 把dll的路径写入到对方的进程空间中
DWORD dwWriteSize = 0;
//写一段数据到指定进程所开辟的内存空间
WriteProcessMemory(hProcess, pRemoteAddress, szPath, wcslen(szPath) * 2 + 2, &dwWriteSize);

//3 创建一个远程线程,让目标进程调用LoadLibrary
//这里的LoadLibrary也可以从kernel32.dll中获取地址(因为注入目标进程也调用一样的kernel32.dll,地址一样),但是可以直接用,先获取地址再用有点多此一举了。该注入线程根本含义就是在目标进程中执行LoadLibrary(pRemoteAddress)也就达到了注入dll的目的,功能函数就在dll文件里去编写完善了
HANDLE hThread = CreateRemoteThread(
hProcess,
NULL,
0,
(LPTHREAD_START_ROUTINE)LoadLibrary,
pRemoteAddress,
NULL,
NULL
);
WaitForSingleObject(hThread, -1);
//4 释放申请的虚拟内存空间
VirtualFreeEx(hProcess, pRemoteAddress, 1, MEM_DECOMMIT);
CloseHandle(hThread);
CloseHandle(hProcess);
return 0;
}


int _tmain(int argc, _TCHAR* argv[])
{
DWORD dwPid = 0;

scanf_s("%d", &dwPid);
Inject(dwPid, path);
return 0;
}

dll怎么写,举个最简单的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#include "stdafx.h"

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
{
MessageBox(NULL, L"test", L"AAAAAA", NULL);

}
break;
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}

卸载dll也同样的原理,注入卸载dll的FreeLibrary线程即可:

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
void UnInjectDll(DWORD dwPid, char *szDllName)
{
// 使目标进程调用GetModuleHandle,获得DLL在目标进程中的句柄
DWORD dwHandle;
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, dwPid);
LPVOID pFunc = GetModuleHandleA;
char lpBuf[MAXBYTE];
HANDLE hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc, lpBuf, 0, &dwPid );

// 等待GetModuleHandle运行完毕
WaitForSingleObject( hThread, INFINITE );

// 获得GetModuleHandle的返回值
GetExitCodeThread( hThread, &dwHandle );

// 释放目标进程中申请的空间
int dwSize = strlen(szDllName) + sizeof(char);
VirtualFreeEx( hProcess, lpBuf, dwSize, MEM_DECOMMIT );
CloseHandle( hThread );

// 使目标进程调用FreeLibrary,卸载DLL
pFunc = FreeLibrary;
hThread = CreateRemoteThread( hProcess, NULL, 0,
(LPTHREAD_START_ROUTINE)pFunc, (LPVOID)dwHandle, 0, &dwPid );

// 等待FreeLibrary卸载完毕
WaitForSingleObject( hThread, INFINITE );
CloseHandle( hThread );
CloseHandle( hProcess );
}

不过上面这种卸载方法也一点小问题,我尝试过之后是不能二次注入的(也有可能是我的问题),也就是卸载是没有办法完全卸载的,从而不能第二次注入进去。

还有一种卸载的方法我没有尝试,也贴一下:

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
bool UnInjectDll(const TCHAR* ptszDllFile, DWORD dwProcessId)  
{
// 参数无效
if (NULL == ptszDllFile || 0 == ::_tcslen(ptszDllFile))
{
return false;
}
HANDLE hModuleSnap = INVALID_HANDLE_VALUE;
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
// 获取模块快照
hModuleSnap = CreateToolhelp32Snapshot(TH32CS_SNAPMODULE, dwProcessId);
if (INVALID_HANDLE_VALUE == hModuleSnap)
{
return false;
}
MODULEENTRY32 me32;
memset(&me32, 0, sizeof(MODULEENTRY32));
me32.dwSize = sizeof(MODULEENTRY32);
// 开始遍历
if(FALSE == Module32First(hModuleSnap, &me32))
{
CloseHandle(hModuleSnap);
return false;
}
// 遍历查找指定模块
bool isFound = false;
do
{
isFound = (0 == _tcsicmp(me32.szModule, ptszDllFile) || 0 == _tcsicmp(me32.szExePath, ptszDllFile));
if (isFound) // 找到指定模块
{
break;
}
} while (TRUE == ::Module32Next(hModuleSnap, &me32));
CloseHandle(hModuleSnap);
if (false == isFound)
{
return false;
}
// 获取目标进程句柄
hProcess = OpenProcess(PROCESS_CREATE_THREAD | PROCESS_VM_OPERATION, FALSE, dwProcessId);
if (NULL == hProcess)
{
return false;
}
// 从 Kernel32.dll 中获取 FreeLibrary 函数地址
LPTHREAD_START_ROUTINE lpThreadFun = (PTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle(_T("Kernel32")), "FreeLibrary");
if (NULL == lpThreadFun)
{
::CloseHandle(hProcess);
return false;
}
// 创建远程线程调用 FreeLibrary
hThread = CreateRemoteThread(hProcess, NULL, 0, lpThreadFun, me32.modBaseAddr /* 模块地址 */, 0, NULL);
if (NULL == hThread)
{
CloseHandle(hProcess);
return false;
}
// 等待远程线程结束
WaitForSingleObject(hThread, INFINITE);
// 清理
CloseHandle(hThread);
CloseHandle(hProcess);
return true;
}

可能这种方式比较有效,可以尝试一下。

dll注入一般情况是需要对目标进程做一些代码改动的,注入了dll之后最重要的是需要拿到目标进程中的各个dll的基地址,这样才方便后续做动作。

获取目标进程的所有dll基址

一般流程:

  1. 拿到TEB结构体地址
  2. 通过TEB拿到PEB地址
  3. 通过PEB拿到PEB_LDR_DATA指针(里面存放进程中装载的所有dll信息)
  4. 通过PEB_LDR_DATA拿到LIST_ENTRY链表头指针(dll信息的链表指针)
  5. 遍历_LIST_ENTRY,找到DLL基址

看代码:

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
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>

typedef struct _LIST_ENTRY
{
_LIST_ENTRY* Flink;
_LIST_ENTRY* Blink;
}_LIST_ENTRY;

typedef struct _UNICODE_STRING
{
unsigned short Length;
unsigned short MaximumLength;
wchar_t* Buffer;
};

int main()
{
int* pPEB = NULL;

__asm
{
push eax
mov eax,fs:[0x30]
mov pPEB,eax
pop eax
}

int* pIDR = (int*)(*(pPEB + 0x03)); //0x03 * 4 = 0x0c

_LIST_ENTRY* pInLoadOrderModuleList = (_LIST_ENTRY*)(pIDR + 0x03); //0x03 * 4 = 0x0c

_LIST_ENTRY* pHead, *p;
p = pHead = pInLoadOrderModuleList->Flink;

int count = 0;
do
{
printf("%d: ", count++);
_UNICODE_STRING* pBaseName = (_UNICODE_STRING*)(((int)p) + 0x2c);
if (pBaseName->Buffer)
wprintf(L"%s ", pBaseName->Buffer);
printf("0x%x\n", *((int*)(((int)p) + 0x18)));
//
p = p->Flink;
} while (p != pHead);

return 0;
}

代码注入

代码注入就是不把dll注入到目标进程中去,而是自己编写所要注入的代码,在代码里面完成自己想要的操作。但是这个其实是有难度的,自己编写的注入进程(A)和被注入目标进程(B)不在同一个进程空间,所以B无法调用A中的函数,也无法访问A中的数据。(这就是为什么大家更乐意用dll注入的方式,因为注入的dll和目标进程属于同一进程空间)

因此,在A中编写注入到B中的代码时,需要注意不论是函数、变量、还是结构体等其他东西,都需要在B中构造好后再让B去执行。

直接看代码,前面的注入流程都一样,区别就在于创建的线程函数ThreadProc

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
144
145
//该结构体用于接收API和4个字符串
typedef struct _THREAD_PARAM {
FARPROC pFunc[2];
char szBuf[4][128];
} THREAD_PARAM, *PTHREAD_PARAM;

typedef HMODULE (WINAPI *PFLOADLIBRARYA)
(
LPCSTR lpLibFileName
);

typedef FARPROC (WINAPI *PFGETPROCADDRESS)
(
HMODULE hModule,
LPCSTR lpProcName
);

typedef int (WINAPI *PFMESSAGEBOXA)
(
HWND hWnd,
LPCSTR lpText,
LPCSTR lpCaption,
UINT uType
);

DWORD WINAPI ThreadProc(LPVOID lParam){
PTHREAD_PARAM pParam = (PTHREAD_PARAM)lParam;
HMODULE hMod = NULL;
FARPROC pFunc = NULL;

//未直接调用相关API和未直接定义使用字符串,而通过THREAD_PARAM结构体以线程参数的形式传递使用
// LoadLibrary()
hMod = ((PFLOADLIBRARYA)pParam->pFunc[0])(pParam->szBuf[0]); // "user32.dll"
if( !hMod ){
return 1;
}


// GetProcAddress()
pFunc = (FARPROC)((PFGETPROCADDRESS)pParam->pFunc[1])(hMod, pParam->szBuf[1]); // "MessageBoxA"
if( !pFunc ){
return 1;
}

// MessageBoxA()
((PFMESSAGEBOXA)pFunc)(NULL, pParam->szBuf[2], pParam->szBuf[3], MB_OK);

return 0;
}


BOOL InjectCode(DWORD dwPID){
HMODULE hMod = NULL;
THREAD_PARAM param = {0,};
HANDLE hProcess = NULL;
HANDLE hThread = NULL;
LPVOID pRemoteBuf[2] = {0,};
DWORD dwSize = 0;

hMod = GetModuleHandleA("kernel32.dll");

//设置THREAD_PARAM结构体
//加载到所有进程的kernel32.dll的地址都相同,因此从本进程获取的API与在notepad进程中获取的API地址是一样的
param.pFunc[0] = GetProcAddress(hMod, "LoadLibraryA");
param.pFunc[1] = GetProcAddress(hMod, "GetProcAddress");
strcpy_s(param.szBuf[0], "user32.dll");
strcpy_s(param.szBuf[1], "MessageBoxA");
strcpy_s(param.szBuf[2], "代码注入 By SKI12");
strcpy_s(param.szBuf[3], "blog.csdn.net/ski_12");

// Open Process
if ( !(hProcess = OpenProcess(PROCESS_ALL_ACCESS, // dwDesiredAccess
FALSE, // bInheritHandle
dwPID)) ) // dwProcessId
{
printf("OpenProcess() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for THREAD_PARAM
//注入数据部分到进程
dwSize = sizeof(THREAD_PARAM);
if( !(pRemoteBuf[0] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
//param参数值为数据
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[0], // lpBaseAddress
(LPVOID)&param, // lpBuffer
dwSize, // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

// Allocation for ThreadProc()
//注入代码部分到进程
dwSize = (DWORD)InjectCode - (DWORD)ThreadProc;
if( !(pRemoteBuf[1] = VirtualAllocEx(hProcess, // hProcess
NULL, // lpAddress
dwSize, // dwSize
MEM_COMMIT, // flAllocationType
PAGE_EXECUTE_READWRITE)) ) // flProtect
{
printf("VirtualAllocEx() fail : err_code = %d\n", GetLastError());
return FALSE;
}
//ThreadProc()函数为操作代码
if( !WriteProcessMemory(hProcess, // hProcess
pRemoteBuf[1], // lpBaseAddress
(LPVOID)ThreadProc, // lpBuffer
dwSize, // nSize
NULL) ) // [out] lpNumberOfBytesWritten
{
printf("WriteProcessMemory() fail : err_code = %d\n", GetLastError());
return FALSE;
}

//将数据与代码注入到进程内存后,调用CreateRemoteThread()执行远程线程
if( !(hThread = CreateRemoteThread(hProcess, // hProcess
NULL, // lpThreadAttributes
0, // dwStackSize
(LPTHREAD_START_ROUTINE)pRemoteBuf[1], // dwStackSize
pRemoteBuf[0], // lpParameter
0, // dwCreationFlags
NULL)) ) // lpThreadId
{
printf("CreateRemoteThread() fail : err_code = %d\n", GetLastError());
return FALSE;
}

WaitForSingleObject(hThread, INFINITE);

CloseHandle(hThread);
CloseHandle(hProcess);

return TRUE;
}

看下来其实可以发现代码注入只适合写小型的注入,一旦涉及到需要执行大量操作,那么要准备的数据和函数就会变得更多了…

参考链接

dll注入:

https://www.cnblogs.com/17bdw/p/6527998.html

https://blog.csdn.net/u012319493/article/details/50456685

https://blog.csdn.net/sky04/article/details/7027225

代码注入:

https://blog.csdn.net/SKI_12/article/details/82947635

获取DLL基址:

https://blog.csdn.net/weixin_43206704/article/details/87903325

支持一下
扫一扫,支持v1nke
  • 微信扫一扫
  • 支付宝扫一扫