这一节我们将接触运用windowsAPI编写的两个恶意程序
同时会展示该如何调用windowsAPI去恢复已经被污染的exe程序
Lab07-02
首先我们使用strings工具对该程序进行初步的分析

可以看到一些用于调用windows接口的函数,因此我们要使用一个新的工具Dependency walker去查询该程序的依赖项。
从而具体分析它调用了哪些函数

得到了这样的依赖结构(我们在分析的时候会发现OLEAUT32.DLL中是通过序号引入的,此时便可以在下表中查找序号对应的函数)
我们尝试一下运行该程序

发现其效果是试图打开一个网页
接下来使用IDA进行高级静态分析

可以发现一开始的操作是要调用一个windows接口,我们深入其中分析一下调用的接口函数


我们对该iid进行查询,发现其对应的接口为IWebBrowser2

接下来再通过clsid在本地注册表 HKEY_LOCAL_MACHINE\SOFTWARE\Classes\CLSID\{0002DF01-0000-0000-C000-000000000046}中查询对应的程序

继续我们的IDA分析,分析接下来对接口的调用部分

可以发现通过ecx将需要的URL赋给了pvarg这个变量,最后又调用了我们的接口,关于其具体的值,可以通过为其设置标准结构体查看


Navigate函数如其名,为我们重定向到一个URL,正是之前给定的ecx中的网址
到此,该恶意程序的功能全部分析完毕
Lab07-03
首先使用strings进行exe分析,发现出现kernel32.dll和kerne132.dll,初步估计该程序将使用伪造的dll文件代替原有dll文件

再分析Lab07-03.dll,看到其内部调用了这几个DLL库,猜测该dll文件中存在后门操作,通过WS2_32中的函数创建远程连接

接下来使用IDA对两个文件进行具体分析,先来分析Lab07-03.exe

首先判断程序第二个参数是否为“WARNING_THIS_WILL_DESTROY_YOUR_MACHINE”,如果不是便退出


在内存中加载这两个库文件,并将Lab07-03.dll拷贝到”C:\Windows\System32\Kernel32.dll”路径下,伪装成系统文件
接下来进入sub_4011E0,查看具体发生了什么

经过初步分析可以得知,此处递归搜索了C盘中的每一个exe程序,并且对其进行了sub_4010A0操作,我们继续深入到该函数中进行分析


可以看到,这段代码通过内存映射文件的方式修改一个文件的内容,具体来说是将文件的导入表中 kernel32.dll 替换为 kerne132.dll。通过对PE文件结构的解析,精准找到导入表以及对应的kernel32.dll字符串的地址再通过方框中的操作进行替换。
接下来,我们来分析另一个文件Lab07-03.dll,,由于其内部的具体流程太过复杂,我们截取其中最为关键的函数调用来分析

通过dllmain函数的调用,我们可以得到程序的大体流程:
创建互斥量保证只有单一进程运行;远程连接到指定服务器”127.26.152.13”,并获取指令

可能接收到3种指令,”sleep”便休眠程序1min,”q”便清理资源退出程序,”exec”便执行该字段后的指令。此时你的终端便已经被远程主机获取。
我们尝试修复该程序造成的破坏:首先,运行带有参数的恶意程序,对本地系统进行感染破坏

查看之前分析的本地特征,确实发现存在kerne132.dll,并且C盘中的exe程序的依赖项已经被更改为kerne132.dll



接下来我们要完成一个程序,其功能是遍历C盘中的所有exe程序,并修复其导入表为真正的系统库kernel32.dll,示例代码如下(由于笔者学习时尚未熟悉内存映射方式修改文件,该段代码使用的是文件读写的方式)
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#define MAX_EXE_COUNT 10240
#define MAX_PATH_LEN 512
#define LOG_BUFFER_LEN 1024
// -------------------------- 全局变量 --------------------------
TCHAR* g_exeList[MAX_EXE_COUNT];
int g_exeCount = 0;
// -------------------------- 函数原型声明 --------------------------
void FreeEXEList(void);
void SafeLog(const TCHAR* format, ...);
void ScanEXE(const TCHAR* path);
BOOL RepairPE(const TCHAR* exePath);
void Pause(void);
// 打印
void SafeLog(const TCHAR* format, ...) {
TCHAR buffer[LOG_BUFFER_LEN];
va_list args;
va_start(args, format);
_vstprintf_s(buffer, LOG_BUFFER_LEN, format, args);
va_end(args);
_tprintf(_T("%s"), buffer);
fflush(stdout);
}
//扫描
void ScanEXE(const TCHAR* path) {
TCHAR searchPath[MAX_PATH_LEN];
_stprintf_s(searchPath, MAX_PATH_LEN, _T("%s\\*.*"), path)
WIN32_FIND_DATA findData;
HANDLE hFind = FindFirstFile(searchPath, &findData);
do {
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
if (_tcscmp(findData.cFileName, _T(".")) != 0 &&
_tcscmp(findData.cFileName, _T("..")) != 0) {
TCHAR subPath[MAX_PATH_LEN];
_stprintf_s(subPath, MAX_PATH_LEN, _T("%s\\%s"), path, findData.cFileName);
ScanEXE(subPath);
}
} else {
TCHAR* ext = _tcsrchr(findData.cFileName, _T('.'));
if (ext != NULL && _tcsicmp(ext, _T(".exe")) == 0) {
TCHAR* fullPath = (TCHAR*)malloc(MAX_PATH_LEN * sizeof(TCHAR));
_stprintf_s(fullPath, MAX_PATH_LEN, _T("%s\\%s"), path, findData.cFileName)
g_exeList[g_exeCount++] = fullPath;
}
}
} while (FindNextFile(hFind, &findData));
FindClose(hFind);
}
// 修复
BOOL RepairPE(const TCHAR* exePath) {
BOOL result = FALSE;
HANDLE hFile = INVALID_HANDLE_VALUE; // 初始化文件句柄为无效值
// 1. 打开文件
hFile = CreateFile(
exePath,
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL
);
if (hFile == INVALID_HANDLE_VALUE) {
DWORD err = GetLastError();
SafeLog(_T("打开文件失败:%s(错误码:%d)\n"), exePath, err);
goto cleanup; // 直接跳转到 cleanup 标签
}
// 2. 读取 DOS 头
IMAGE_DOS_HEADER dosHeader;
DWORD bytesRead;
if (!ReadFile(hFile, &dosHeader, sizeof(dosHeader), &bytesRead, NULL) ||
bytesRead != sizeof(dosHeader) ||
dosHeader.e_magic != IMAGE_DOS_SIGNATURE) {
SafeLog(_T("无效的 PE 文件:%s(不是 DOS 可执行文件)\n"), exePath);
goto cleanup; // 读取失败,跳转到 cleanup
}
// 3. 读取 NT 头
IMAGE_NT_HEADERS32 ntHeader;
if (SetFilePointer(hFile, dosHeader.e_lfanew, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ||
!ReadFile(hFile, &ntHeader, sizeof(ntHeader), &bytesRead, NULL) ||
bytesRead != sizeof(ntHeader) ||
ntHeader.Signature != IMAGE_NT_SIGNATURE) {
SafeLog(_T("无效的 PE 文件:%s(NT 头损坏)\n"), exePath);
goto cleanup; // 读取失败,跳转到 cleanup
}
// 4. 定位导入表
IMAGE_DATA_DIRECTORY importDir = ntHeader.OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
if (importDir.VirtualAddress == 0 || importDir.Size == 0) {
SafeLog(_T("跳过无导入表的文件:%s\n"), exePath);
goto cleanup; // 无导入表,跳转到 cleanup
}
// 5. 将 RVA 转换为文件偏移
DWORD importFileOffset = 0;
IMAGE_SECTION_HEADER sectionHeader;
DWORD sectionTableOffset = dosHeader.e_lfanew + sizeof(IMAGE_NT_HEADERS32);
BOOL foundSection = FALSE;
for (WORD i = 0; i < ntHeader.FileHeader.NumberOfSections; i++) {
if (SetFilePointer(hFile, sectionTableOffset + i * sizeof(IMAGE_SECTION_HEADER), NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ||
!ReadFile(hFile, §ionHeader, sizeof(sectionHeader), &bytesRead, NULL) ||
bytesRead != sizeof(sectionHeader)) {
SafeLog(_T("读取节表失败:%s(节索引:%d)\n"), exePath, i);
goto cleanup; // 读取失败,跳转到 cleanup
}
if (importDir.VirtualAddress >= sectionHeader.VirtualAddress &&
importDir.VirtualAddress < sectionHeader.VirtualAddress + sectionHeader.SizeOfRawData) {
importFileOffset = importDir.VirtualAddress - sectionHeader.VirtualAddress + sectionHeader.PointerToRawData;
foundSection = TRUE;
break;
}
}
if (!foundSection) {
SafeLog(_T("未找到导入表所在节:%s\n"), exePath);
goto cleanup; // 未找到,跳转到 cleanup
}
// 6. 遍历导入表并修复恶意 DLL
IMAGE_IMPORT_DESCRIPTOR importDesc;
BOOL repaired = FALSE;
DWORD currentOffset = importFileOffset;
while (TRUE) {
if (SetFilePointer(hFile, currentOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ||
!ReadFile(hFile, &importDesc, sizeof(importDesc), &bytesRead, NULL) ||
bytesRead != sizeof(importDesc)) {
SafeLog(_T("读取导入描述符失败:%s(偏移:%d)\n"), exePath, currentOffset);
break; // 这里 break 出 while 循环,之后会走到 cleanup
}
if (importDesc.Name == 0 && importDesc.FirstThunk == 0) {
break; // 遍历结束
}
DWORD dllNameRVA = importDesc.Name;
DWORD dllNameFileOffset = dllNameRVA - sectionHeader.VirtualAddress + sectionHeader.PointerToRawData;
char dllName[256];
if (SetFilePointer(hFile, dllNameFileOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ||
!ReadFile(hFile, dllName, sizeof(dllName), &bytesRead, NULL) ||
bytesRead == 0) {
SafeLog(_T("读取 DLL 名称失败:%s(偏移:%d)\n"), exePath, dllNameFileOffset);
currentOffset += sizeof(IMAGE_IMPORT_DESCRIPTOR);
continue;
}
dllName[sizeof(dllName) - 1] = '\0';
if (_stricmp(dllName, "kerne132.dll") == 0) {
TCHAR backupPath[MAX_PATH_LEN];
const char* correctDllName = "kernel32.dll";
DWORD correctNameLen = strlen(correctDllName) + 1;
if (SetFilePointer(hFile, dllNameFileOffset, NULL, FILE_BEGIN) == INVALID_SET_FILE_POINTER ||
!WriteFile(hFile, correctDllName, correctNameLen, &bytesRead, NULL) ||
bytesRead != correctNameLen) {
SafeLog(_T("修复 DLL 名称失败:%s(错误码:%d)\n"), exePath, GetLastError());
} else {
repaired = TRUE;
}
}
currentOffset += sizeof(IMAGE_IMPORT_DESCRIPTOR);
}
result = repaired;
// 7.回收资源
cleanup:
if (hFile != INVALID_HANDLE_VALUE) {
CloseHandle(hFile);
}
return result;
}
//释放内存
void FreeEXEList() {
for (int i = 0; i < g_exeCount; i++) {
if (g_exeList[i] != NULL) {
free(g_exeList[i]);
g_exeList[i] = NULL;
}
}
g_exeCount = 0;
}
void Pause() {
SafeLog(_T("\n按回车键退出..."));
while (getchar() != '\n');
getchar();
}
int _tmain() {
SafeLog(_T("============= PE 文件修复工具(WinXP 32位专用)=============\n"));
SafeLog(_T("功能:扫描 C 盘所有 EXE 文件,修复导入表中恶意的 kerne132.dll\n"));
SafeLog(_T("===========================================================\n\n"));
SafeLog(_T("开始扫描 C 盘 EXE 文件...\n"));
ScanEXE(_T("C:\\"));
SafeLog(_T("\n扫描完成!共找到 %d 个 EXE 文件,开始修复...\n\n"), g_exeCount);
for (int i = 0; i < g_exeCount; i++) {
if (g_exeList[i] != NULL) {
RepairPE(g_exeList[i]);
}
}
SafeLog(_T("\n修复任务完成!\n"));
FreeEXEList();
Pause();
return 0;
}
鉴于笔者在高版本主机中编译的该段程序,为了与winXP兼容,编译命令如下
gcc -o repair.exe repair.c -m32 -DWINVER=0x0501 -D_WIN32_WINNT=0x0501 -static-libgcc -static-libstdc++ -luser32 -lkernel32 -ladvapi32
其中的 -DWINVER=0x0501 -D_WIN32_WINNT=0x0501用于指定windows平台为XP。接下来在XP虚拟机中执行修复程序

修复后再次查看C盘中的exe文件依赖项,发现已经被修复为了系统库Kernel32.dll

至此,本次实验圆满结束