免杀专题
21、特征码定位原理
1. 课程目标
学习如何定位安全软件检测的特征码位置,这是免杀技术的基础技能。
1.1 学习目标
- 理解特征码定位的原理
- 掌握二分法定位技术
- 学会使用定位工具
- 实践特征码修改
2. 名词解释
| 名词 | 英文 | 解释 |
|---|---|---|
| 特征码 | Signature | 杀软用于识别恶意软件的字节序列 |
| 二分法 | Binary Search | 将文件分半定位的方法 |
| CCL | Characteristic Code Locator | 特征码定位工具 |
| MyCCL | - | 常用的中文特征码定位工具 |
| 分块定位 | Block Locating | 将文件分成多块逐一检测 |
3. 定位原理
3.1 二分法定位流程
原始文件 (被杀):
┌────────────────────────────────────────────┐
│ A │ B │ C │ D │ E │ F │ G │ H │
└────────────────────────────────────────────┘
第1步:分成两半
┌─────────────────────┐ ┌─────────────────────┐
│ A │ B │ C │ D │ │ E │ F │ G │ H │
└─────────────────────┘ └─────────────────────┘
用0填充右半边,扫描 → 被杀 → 特征码在左半边
第2步:继续二分左半边
┌──────────┐ ┌──────────┐
│ A │ B │ │ C │ D │
└──────────┘ └──────────┘
用0填充右边,扫描 → 不被杀 → 特征码在右边(C或D)
第3步:继续二分
...最终定位到具体字节
3.2 定位策略
| 策略 | 说明 | 适用场景 |
|---|---|---|
| 全局二分 | 对整个文件二分 | 单一特征码 |
| 分块扫描 | 分成固定大小块 | 多个特征码 |
| 混合方法 | 先分块再二分 | 复杂情况 |
4. 手动定位实现
4.1 文件分块工具
#include <windows.h>
#include <stdio.h>
// 将文件分成多块,每块用0填充测试
BOOL SplitFileForTest(LPCWSTR szInputPath, LPCWSTR szOutputDir, DWORD dwBlockCount) {
// 1. 读取原始文件
HANDLE hFile = CreateFileW(szInputPath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) return FALSE;
DWORD dwFileSize = GetFileSize(hFile, NULL);
LPBYTE pBuffer = (LPBYTE)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
DWORD dwRead;
ReadFile(hFile, pBuffer, dwFileSize, &dwRead, NULL);
CloseHandle(hFile);
// 2. 计算每块大小
DWORD dwBlockSize = dwFileSize / dwBlockCount;
printf("[*] 文件大小: %d bytes\n", dwFileSize);
printf("[*] 块数量: %d\n", dwBlockCount);
printf("[*] 块大小: %d bytes\n", dwBlockSize);
// 3. 生成测试文件
for (DWORD i = 0; i < dwBlockCount; i++) {
// 复制原始数据
LPBYTE pTestBuffer = (LPBYTE)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
memcpy(pTestBuffer, pBuffer, dwFileSize);
// 将当前块填充为0
DWORD dwOffset = i * dwBlockSize;
DWORD dwFillSize = (i == dwBlockCount - 1) ?
(dwFileSize - dwOffset) : dwBlockSize;
memset(pTestBuffer + dwOffset, 0, dwFillSize);
// 保存测试文件
WCHAR szOutputPath[MAX_PATH];
wsprintfW(szOutputPath, L"%s\\block_%02d.exe", szOutputDir, i);
HANDLE hOut = CreateFileW(szOutputPath, GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, 0, NULL);
DWORD dwWritten;
WriteFile(hOut, pTestBuffer, dwFileSize, &dwWritten, NULL);
CloseHandle(hOut);
printf("[+] 生成: block_%02d.exe (offset: 0x%X, size: 0x%X)\n",
i, dwOffset, dwFillSize);
VirtualFree(pTestBuffer, 0, MEM_RELEASE);
}
VirtualFree(pBuffer, 0, MEM_RELEASE);
return TRUE;
}
int main(int argc, char* argv[]) {
printf("========================================\n");
printf(" 特征码定位 - 分块工具\n");
printf("========================================\n\n");
if (argc < 4) {
printf("用法: %s <输入文件> <输出目录> <块数量>\n", argv[0]);
return 1;
}
WCHAR szInput[MAX_PATH], szOutput[MAX_PATH];
MultiByteToWideChar(CP_ACP, 0, argv[1], -1, szInput, MAX_PATH);
MultiByteToWideChar(CP_ACP, 0, argv[2], -1, szOutput, MAX_PATH);
int blockCount = atoi(argv[3]);
SplitFileForTest(szInput, szOutput, blockCount);
return 0;
}
4.2 二分法定位工具
#include <windows.h>
#include <stdio.h>
typedef struct _SIG_LOCATION {
DWORD dwOffset;
DWORD dwSize;
} SIG_LOCATION;
// 自动扫描函数(需要实现)
typedef BOOL(*SCAN_FUNC)(LPCWSTR szFilePath);
// 二分法定位特征码
BOOL BinarySearchSignature(LPCWSTR szFilePath, DWORD dwStart, DWORD dwEnd,
SCAN_FUNC pfnScan, SIG_LOCATION* pLocation) {
// 读取文件
HANDLE hFile = CreateFileW(szFilePath, GENERIC_READ, FILE_SHARE_READ,
NULL, OPEN_EXISTING, 0, NULL);
DWORD dwFileSize = GetFileSize(hFile, NULL);
LPBYTE pBuffer = (LPBYTE)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
DWORD dwRead;
ReadFile(hFile, pBuffer, dwFileSize, &dwRead, NULL);
CloseHandle(hFile);
DWORD dwLeft = dwStart;
DWORD dwRight = dwEnd;
while (dwRight - dwLeft > 16) { // 精确到16字节
DWORD dwMid = (dwLeft + dwRight) / 2;
// 创建测试文件(填充右半部分为0)
LPBYTE pTest = (LPBYTE)VirtualAlloc(NULL, dwFileSize, MEM_COMMIT, PAGE_READWRITE);
memcpy(pTest, pBuffer, dwFileSize);
memset(pTest + dwMid, 0, dwRight - dwMid);
// 保存并扫描
HANDLE hTemp = CreateFileW(L"test_temp.exe", GENERIC_WRITE, 0,
NULL, CREATE_ALWAYS, 0, NULL);
DWORD dwWritten;
WriteFile(hTemp, pTest, dwFileSize, &dwWritten, NULL);
CloseHandle(hTemp);
VirtualFree(pTest, 0, MEM_RELEASE);
BOOL bDetected = pfnScan(L"test_temp.exe");
DeleteFileW(L"test_temp.exe");
if (bDetected) {
// 特征码在左半部分
dwRight = dwMid;
printf("[*] 范围缩小: 0x%X - 0x%X (特征码在左)\n", dwLeft, dwRight);
} else {
// 特征码在右半部分
dwLeft = dwMid;
printf("[*] 范围缩小: 0x%X - 0x%X (特征码在右)\n", dwLeft, dwRight);
}
}
pLocation->dwOffset = dwLeft;
pLocation->dwSize = dwRight - dwLeft;
printf("[+] 特征码位置: 0x%X - 0x%X (%d bytes)\n",
dwLeft, dwRight, pLocation->dwSize);
VirtualFree(pBuffer, 0, MEM_RELEASE);
return TRUE;
}
5. 使用现有工具
5.1 MyCCL使用步骤
1. 打开MyCCL工具
2. 加载要分析的PE文件
3. 选择杀毒软件(手动扫描或自动)
4. 设置分块数量(如10块)
5. 开始分块扫描
6. 找到被杀的块后,对该块继续细分
7. 重复直到定位到精确位置
8. 查看定位结果,记录特征码偏移
5.2 VirTest使用
1. 加载目标文件
2. 选择扫描引擎
3. 自动生成测试文件
4. 批量扫描
5. 分析结果
6. 特征码分析
6.1 分析定位到的特征码
#include <windows.h>
#include <stdio.h>
void AnalyzeSignature(LPBYTE pData, DWORD dwOffset, DWORD dwSize) {
printf("[*] 特征码分析\n");
printf(" 偏移: 0x%X\n", dwOffset);
printf(" 大小: %d bytes\n\n", dwSize);
// 打印十六进制
printf(" HEX: ");
for (DWORD i = 0; i < dwSize && i < 32; i++) {
printf("%02X ", pData[dwOffset + i]);
}
printf("\n\n");
// 打印ASCII
printf(" ASCII: ");
for (DWORD i = 0; i < dwSize && i < 32; i++) {
BYTE b = pData[dwOffset + i];
printf("%c", (b >= 0x20 && b < 0x7F) ? b : '.');
}
printf("\n\n");
// 尝试识别特征码类型
printf(" 类型分析:\n");
// 检查是否是字符串
BOOL bIsString = TRUE;
for (DWORD i = 0; i < dwSize; i++) {
BYTE b = pData[dwOffset + i];
if (b != 0 && (b < 0x20 || b >= 0x7F)) {
bIsString = FALSE;
break;
}
}
if (bIsString) printf(" - 可能是字符串\n");
// 检查是否是代码
// 简单判断:是否包含常见指令
if (pData[dwOffset] == 0x55 && pData[dwOffset+1] == 0x8B) {
printf(" - 可能是函数开头 (push ebp; mov ebp, esp)\n");
}
}
6.2 特征码类型
| 类型 | 说明 | 修改方法 |
|---|---|---|
| 字符串 | 明文字符串 | 加密、混淆 |
| API名 | 导入的API名称 | 动态获取API |
| 代码序列 | 特定指令序列 | 等价替换、花指令 |
| 资源数据 | 图标、版本信息 | 修改或删除 |
| 常量 | 硬编码常量 | 运行时计算 |
7. 多特征码定位
7.1 复合特征定位
// 当文件包含多个特征码时
void LocateMultipleSignatures(LPCWSTR szFilePath) {
// 1. 先用较大的块定位出所有可疑区域
// 2. 对每个区域单独进行二分法
// 3. 记录所有特征码位置
SIG_LOCATION signatures[10];
int sigCount = 0;
// 分10块扫描
for (int i = 0; i < 10; i++) {
// 只保留当前块,其他填0
// 如果被杀,说明当前块包含特征码
// 对该块继续二分
}
}
8. 课后作业
8.1、作业1:手动定位(必做)
- 选择一个被杀毒软件查杀的样本
- 使用二分法手动定位特征码
- 记录特征码的偏移和内容
8.2、作业2:工具使用(必做)
- 使用MyCCL或类似工具定位特征码
- 对比手动定位的结果
8.3、作业3:特征码分析(进阶)
- 分析定位到的特征码是什么类型
- 思考可能的修改方法
9. 下一课预告
下一课我们将学习ShellCode加密原理,了解如何通过加密来隐藏恶意代码。