火绒提供的样本,我们可以学到什么?

某一天我像往常一样在工位上躺平,就在我享受这惬意的躺平生活时,我的Boss直聘突然收到了火绒招聘人事的消息,简单跟他聊了几句之后,互相加了QQ,对方直接给了样本,让写一个分析报告,要求是这样的。

作为一名运维人员,还真没写报告的习惯,所以我不打算写啥报告,直接逆向分析,争取把这个程序的源代码全部搞出来。

这个小程序里面还真的有我们可以借鉴的功能呢,等我把这些小功能逆出来分享在这里吧。

样本下载:https://share.weiyun.com/6Sh9kBYU

在逆向还原代码时,应该从主函数开始,逐步递进,层层恢复,借助IDA+OD等工具,恢复代码,我们的目的只有一个,那就是让恢复的代码能够顺利通过编译,并实现同样的运行效果即可。


还原 sub_40CAB0 函数

逆向还原子过程 sub_40CAB0(): 先还原功能性模块,第一个需要还原的位置是sub_40CAB0此处代码比较简单,还原没有任何难度,但需要注意有个内嵌子过程需要后期恢复。

#include <Windows.h>
#include <iostream>

int sub_40CAB0()
{
	HMODULE LibraryA;
	FARPROC ProcAddress;

	int result;
	char proc_name[8];
	char kernel_base[16];
	char tasklist_[32];
	char rundll32[16];

	strcpy(kernel_base, "KERNEL32.dll");
	strcpy(proc_name, "WinExec");

	LibraryA = LoadLibraryA(kernel_base);
	ProcAddress = GetProcAddress(LibraryA, proc_name);
	std::cout << "得到WinExec地址: " << ProcAddress << std::endl;

	strcpy(rundll32, "rundll32.exe");

	result = 1;    // 此处函数需要继续逆向分析,暂时使用1代替
	if (result)
	{
		strcpy(tasklist_, "taskkill /f /im rundll32.exe");
		return ((int(__stdcall *)(char *, DWORD))ProcAddress)(tasklist_, 0);
	}
	return result;
}

int main(int argc, char *argv)
{
	sub_40CAB0();
	getchar();
	return 0;
}

上方有个地方我们需要继续跟进,所以先用result = 1;代替,后面的过程我们需要跟进,上方代码我们确保可以便宜通过,并成功执行,如下。


逆向还原子过程 sub_40EC00(): 我们继续还原子过程sub_40EC00()该过程稍微复杂一点,还原代码如下。

#include <Windows.h>
#include <iostream>

// 取进程数,并判断是否是所需进程
int __cdecl sub_40EC00(int a1)
{
	HMODULE KERNEL32Base;
	FARPROC CreateToolhelp32SnapshotBase;
	DWORD *v3;

	FARPROC v11;
	FARPROC v9;
	FARPROC lstrcmpiABase;
	FARPROC Process32NextBase;
	int v10;
	FARPROC Process32FirstBase;
	char lstrcmpiAAscii[12];
	CHAR LibFileName[16];
	char Process32NextAscii[16];
	char Process32FirstAscii[16];
	CHAR CreateToolhelp32SnapshotAscii[28];

	// 取kernel32基地址
	strcpy(LibFileName, "KERNEL32.dll");
	KERNEL32Base = LoadLibraryA(LibFileName);

	// CreateToolhelp32Snapshot
	strcpy(CreateToolhelp32SnapshotAscii, "CreateToolhelp32Snapshot");
	CreateToolhelp32SnapshotBase = GetProcAddress(KERNEL32Base, CreateToolhelp32SnapshotAscii);
	std::cout << "CreateToolhelp32Snapshot 基地址 = >" << CreateToolhelp32SnapshotBase << std::endl;

	// Process32Next
	strcpy(Process32NextAscii, "Process32Next");
	Process32NextBase = GetProcAddress(KERNEL32Base, Process32NextAscii);
	std::cout << "Process32Next 基地址 => " << Process32NextBase << std::endl;

	// Process32First
	strcpy(Process32FirstAscii, "Process32First");
	Process32FirstBase = GetProcAddress(KERNEL32Base, Process32FirstAscii);
	std::cout << "Process32First 基地址 => " << Process32FirstBase << std::endl;

	// lstrcmpiA
	strcpy(lstrcmpiAAscii, "lstrcmpiA");
	lstrcmpiABase = GetProcAddress(KERNEL32Base, lstrcmpiAAscii);
	std::cout << "lstrcmpiA 基地址 => " << lstrcmpiABase << std::endl;

	// 调用函数,获取进程类型
	// dwFlags:指定了获取系统进程快照的类型
	// th32ProcessID:指向要获取进程快照的ID,获取系统内所有进程快照时是0
	v10 = ((int(__stdcall *)(int, DWORD))CreateToolhelp32SnapshotBase)(2, 0);
	std::cout << "获取进程快照: " << v10 << std::endl;

	// 申请临时空间
	v3 = (DWORD *)operator new(296u);
	*v3 = 296;

	// 调用获取进程信息快照
	if (!((int(__stdcall *)(int, DWORD *))Process32FirstBase)(v10, v3))
		return 0;

	return 0;
}

int main(int argc, char *argv)
{
	sub_40EC00(11);
	getchar();
	return 0;
}

尝试恢复非判断流程,恢复后,我们编译并调用看看效果,是否满足条件了。

接着继续恢复判断表达式,此处的sub_407070是子过程,我们暂时使用if (!1)代替。

#include <Windows.h>
#include <iostream>

// 取进程数,并判断是否是所需进程
int __cdecl sub_40EC00(int a1)
{
	HMODULE KERNEL32Base;
	FARPROC CreateToolhelp32SnapshotBase;
	DWORD *v3;

	FARPROC lstrcmpiABase;
	FARPROC Process32NextBase;
	int v10;
	FARPROC Process32FirstBase;
	char lstrcmpiAAscii[12];
	CHAR LibFileName[16];
	char Process32NextAscii[16];
	char Process32FirstAscii[16];
	CHAR CreateToolhelp32SnapshotAscii[28];

	// 取kernel32基地址
	strcpy(LibFileName, "KERNEL32.dll");
	KERNEL32Base = LoadLibraryA(LibFileName);

	// CreateToolhelp32Snapshot
	strcpy(CreateToolhelp32SnapshotAscii, "CreateToolhelp32Snapshot");
	CreateToolhelp32SnapshotBase = GetProcAddress(KERNEL32Base, CreateToolhelp32SnapshotAscii);
	std::cout << "CreateToolhelp32Snapshot 基地址 = >" << CreateToolhelp32SnapshotBase << std::endl;

	// Process32Next
	strcpy(Process32NextAscii, "Process32Next");
	Process32NextBase = GetProcAddress(KERNEL32Base, Process32NextAscii);
	std::cout << "Process32Next 基地址 => " << Process32NextBase << std::endl;

	// Process32First
	strcpy(Process32FirstAscii, "Process32First");
	Process32FirstBase = GetProcAddress(KERNEL32Base, Process32FirstAscii);
	std::cout << "Process32First 基地址 => " << Process32FirstBase << std::endl;

	// lstrcmpiA
	strcpy(lstrcmpiAAscii, "lstrcmpiA");
	lstrcmpiABase = GetProcAddress(KERNEL32Base, lstrcmpiAAscii);
	std::cout << "lstrcmpiA 基地址 => " << lstrcmpiABase << std::endl;

	// 调用函数,获取进程类型
	// dwFlags:指定了获取系统进程快照的类型
	// th32ProcessID:指向要获取进程快照的ID,获取系统内所有进程快照时是0
	v10 = ((int(__stdcall *)(int, DWORD))CreateToolhelp32SnapshotBase)(2, 0);
	std::cout << "获取进程快照: " << v10 << std::endl;

	// 申请临时空间
	v3 = (DWORD *)operator new(296);
	*v3 = 296;

	// 调用获取进程信息快照
	if (!((int(__stdcall *)(int, DWORD *))Process32FirstBase)(v10, v3))
		return 0;

	// 此处我们先把子过程!sub_407070(v3 + 9, a1)用1代替,后期需要继续调试
	if (!1)
		return v3[2];

	// 调用获取进程列表,此处就是调用获取第一个进程列表
	if (!((int(__stdcall *)(int, DWORD *))Process32NextBase)(v10, v3))
		return 0;
	
	while (1)
	{
		// 这是一个对比函数,主要用来对比传入的ID是否是需要的进程,如果是则跳出循环
		if (!((int(__stdcall *)(DWORD *, int))lstrcmpiABase)(v3 + 9, a1))
			break;

		// 获取下一个进程信息
		if (!((int(__stdcall *)(int, DWORD *))Process32NextBase)(v10, v3))
			return 0;
	}
	Sleep(1);
	return v3[2];
}

int main(int argc, char *argv)
{
	sub_40EC00(1258);
	getchar();
	return 0;
}

由于缺失代码,所以此处不强制要求程序能跑起来,只需要能够通过编译即说明完成工作。


逆向还原子过程 sub_407070(): 子过程 sub_40EC00 中嵌套了另一个子过程,我们继续递进,将sub_407070子过程逆出来,这个过程主要实现机制转换,比对工作。

主要功能:判断是否是大写字母,是则转为小写,并将传入的两个值进行比对。

#include <Windows.h>
#include <iostream>

// 进制转换
int sub_407070(unsigned char *x, unsigned char *y)
{
	int item_a, item_b;

	do
	{
		item_a = *x++;
		if (item_a >= 'A' && item_a <= 'Z')
			item_a += 32;

		item_b = *y++;
		if (item_b >= 'A' && item_b <= 'Z')
			item_b += 32;
	} while (item_a && item_a == item_b);

	return item_a - item_b;
}

int main(int argc, char *argv)
{
	unsigned char a[] = "ABCD";
	unsigned char b[] = "QWERTYU";

	int ref = sub_407070(a, b);
	std::cout << "转换与比对: " << ref << std::endl;

	int ref1 = sub_407070(b, a);
	std::cout << "转换与比对: " << ref1 << std::endl;

	getchar();
	return 0;
}

至此,我们通过IDA跳回到主函数,此时我们已经完全恢复好主函数中的sub_40CAB0()子过程了,该子过程可以跳过了,源代码总结如下.

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 进制转换
int sub_407070(unsigned char *x, unsigned char *y)
{
	int item_a, item_b;

	do
	{
		item_a = *x++;
		if (item_a >= 'A' && item_a <= 'Z')
			item_a += 32;

		item_b = *y++;
		if (item_b >= 'A' && item_b <= 'Z')
			item_b += 32;
	} while (item_a && item_a == item_b);

	return item_a - item_b;
}

// 取进程数,并判断是否是所需进程
int __cdecl sub_40EC00(int a1)
{
	HMODULE KERNEL32Base;
	FARPROC CreateToolhelp32SnapshotBase;
	DWORD *v3;

	FARPROC lstrcmpiABase;
	FARPROC Process32NextBase;
	int v10;
	FARPROC Process32FirstBase;
	char lstrcmpiAAscii[12];
	CHAR LibFileName[16];
	char Process32NextAscii[16];
	char Process32FirstAscii[16];
	CHAR CreateToolhelp32SnapshotAscii[28];

	// 取kernel32基地址
	strcpy(LibFileName, "KERNEL32.dll");
	KERNEL32Base = LoadLibraryA(LibFileName);

	// CreateToolhelp32Snapshot
	strcpy(CreateToolhelp32SnapshotAscii, "CreateToolhelp32Snapshot");
	CreateToolhelp32SnapshotBase = GetProcAddress(KERNEL32Base, CreateToolhelp32SnapshotAscii);
	std::cout << "CreateToolhelp32Snapshot 基地址 = >" << CreateToolhelp32SnapshotBase << std::endl;

	// Process32Next
	strcpy(Process32NextAscii, "Process32Next");
	Process32NextBase = GetProcAddress(KERNEL32Base, Process32NextAscii);
	std::cout << "Process32Next 基地址 => " << Process32NextBase << std::endl;

	// Process32First
	strcpy(Process32FirstAscii, "Process32First");
	Process32FirstBase = GetProcAddress(KERNEL32Base, Process32FirstAscii);
	std::cout << "Process32First 基地址 => " << Process32FirstBase << std::endl;

	// lstrcmpiA
	strcpy(lstrcmpiAAscii, "lstrcmpiA");
	lstrcmpiABase = GetProcAddress(KERNEL32Base, lstrcmpiAAscii);
	std::cout << "lstrcmpiA 基地址 => " << lstrcmpiABase << std::endl;

	// 调用函数,获取进程类型
	// dwFlags:指定了获取系统进程快照的类型
	// th32ProcessID:指向要获取进程快照的ID,获取系统内所有进程快照时是0
	v10 = ((int(__stdcall *)(int, DWORD))CreateToolhelp32SnapshotBase)(2, 0);
	std::cout << "获取进程快照: " << v10 << std::endl;

	// 申请临时空间
	v3 = (DWORD *)operator new(296);
	*v3 = 296;

	// 调用获取进程信息快照
	if (!((int(__stdcall *)(int, DWORD *))Process32FirstBase)(v10, v3))
		return 0;

	// 调用子过程
	if (!sub_407070((unsigned char *)v3 + 9, (unsigned char *)a1))
		return v3[2];

	// 调用获取进程列表,此处就是调用获取第一个进程列表
	if (!((int(__stdcall *)(int, DWORD *))Process32NextBase)(v10, v3))
		return 0;

	while (1)
	{
		// 这是一个对比函数,主要用来对比传入的ID是否是需要的进程,如果是则跳出循环
		if (!((int(__stdcall *)(DWORD *, int))lstrcmpiABase)(v3 + 9, a1))
			break;

		// 获取下一个进程信息
		if (!((int(__stdcall *)(int, DWORD *))Process32NextBase)(v10, v3))
			return 0;
	}
	Sleep(1);
	return v3[2];
}

// 主函数
int sub_40CAB0()
{
	HMODULE LibraryA;
	FARPROC ProcAddress;

	int result;
	char proc_name[8];
	char kernel_base[16];
	char tasklist_[32];
	char rundll32[16];

	strcpy(kernel_base, "KERNEL32.dll");
	strcpy(proc_name, "WinExec");

	LibraryA = LoadLibraryA(kernel_base);
	ProcAddress = GetProcAddress(LibraryA, proc_name);
	std::cout << "得到WinExec地址: " << ProcAddress << std::endl;

	strcpy(rundll32, "rundll32.exe");

	result = sub_40EC00((int)rundll32);
	if (result)
	{
		strcpy(tasklist_, "taskkill /f /im rundll32.exe");
		return ((int(__stdcall *)(char *, DWORD))ProcAddress)(tasklist_, 0);
	}
	return result;
}

int main(int argc, char *argv)
{
	sub_40CAB0();
	getchar();
	return 0;
}

编译通过即可。


还原 sub_4089A0 函数

这个主函数就有趣多了,层层嵌套,复杂度已经上来了,我给大家描述一下我们需要还原的子过程,以及每个过程所在层级,这样我们可以看图,层层递进依次恢复代码。


逆向还原子过程 sub_4070E0(): 此子过程是最内侧的,实现的是大写转小写,并比较长度,返回差值,其还原后代码如下。

这里告诉大家一个规范,当IDA中逆向出unsigned __int8 *x其实可以使用unsigned char *x代替。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 先逆最内侧的函数
int __stdcall sub_4070E0(unsigned char *x, unsigned char *y, int count)
{
	int StringPtr_A;
	int StringPtr_B;

	do
	{
		StringPtr_A = *x++;
		if (StringPtr_A >= 'A' && StringPtr_A <= 'Z')// 判断英文是否是大写
			StringPtr_A += 32;                        // 大写转小写
		StringPtr_B = *y++;
		if (StringPtr_B >= 'A' && StringPtr_B <= 'Z')
			StringPtr_B += 32;
		--count;
	}                                             // 
	// 比较所有变量是否为空
	// 此处需要注意优先级,双等于号优先级最高,其次才是与运算
	while (count && StringPtr_A && StringPtr_A == StringPtr_B);
	return StringPtr_A - StringPtr_B;
}

int main(int argc, char *argv)
{
	unsigned char sz[32] = "hello lyshark";
	unsigned char sz2[32] = "hello world";

	// 传入两个字符串,以及字符串长度
	int ref = sub_4070E0(sz, sz2, 10);
	std::cout << "两者差值: " << ref << std::endl;

	ref = sub_4070E0(sz2, sz, 10);
	std::cout << "两者差值: " << ref << std::endl;

	getchar();
	return 0;
}

运行后看结果吧。


逆向还原子过程 sub_407130(): 该过程比较简单,内部嵌套了上方子过程,我们将其恢复一下。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 先逆最内侧的函数
int __stdcall sub_4070E0(unsigned __int8 *x, unsigned __int8 *y, int count)
{
	int StringPtr_A;
	int StringPtr_B;

	do
	{
		StringPtr_A = *x++;
		if (StringPtr_A >= 'A' && StringPtr_A <= 'Z')// 判断英文是否是大写
			StringPtr_A += 32;                        // 大写转小写
		StringPtr_B = *y++;
		if (StringPtr_B >= 'A' && StringPtr_B <= 'Z')
			StringPtr_B += 32;
		--count;
	}                                             // 
	// 比较所有变量是否为空
	// 此处需要注意优先级,双等于号优先级最高,其次才是与运算
	while (count && StringPtr_A && StringPtr_A == StringPtr_B);
	return StringPtr_A - StringPtr_B;
}

// 定义全局变量
unsigned __int8 byte_4180D0[8] = { 32 ,0 };

// 逆中层
int __stdcall sub_407130(int array_ptr)
{
	int index;
	unsigned __int8 *i;

	index = 0;

	// 此处获取数组指针,然后与byte_4180D0比较,比较第一位
	for (i = (unsigned __int8 *)array_ptr; !sub_4070E0(i, byte_4180D0, 1); ++i)
		++index;
	
	// 返回比较后的数组索引
	return index + array_ptr;
}

int main(int argc, char *argv)
{
	int ref_count = sub_407130(5);
	std::cout << &ref_count << std::endl;

	getchar();
	return 0;
}


逆向还原子过程 sub_4070B0(): 这个过程,主要实现了在指定字节数组中判断某个字符是否存在

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 在指定字节数组中判断某个字符是否存在
BYTE *__stdcall sub_4070B0(BYTE *byte_array, unsigned char value)
{
	BYTE *byte_array_ptr;
	char i;

	byte_array_ptr = byte_array;
	for (i = *byte_array; i; i = *++byte_array_ptr)         // 每次取出后一个字符
	{
		if (i == value)                                    // 判断字符是否与value相等
			break;                                         // 如果存在指定字符,则直接终止循环
	}
	return *byte_array_ptr != value ? 0 : byte_array_ptr;   // 判断v2,不等于value则直接返回0,否则返回v2
}

int main(int argc, char *argv)
{
	getchar();
	return 0;
}

不出意外,可以顺利通过编译检查。


逆向还原子过程 sub_4074C0(): 此子过程相对于上方过程稍微复杂一点,但其实现的目的只有一个,就是从原始位置拷贝字符串放入目标位置,并在结尾处以0填充。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 对字符串的拷贝处理
BYTE *__stdcall sub_4074C0(BYTE *string_dst, char *string_src, int count)
{
	BYTE *result;
	BYTE *dst_end;
	int v6;
	char is_null;

	result = string_dst;
	dst_end = string_dst;
	if (*string_dst)
	{
		while (*++dst_end)                        // 将目标字符串指针移动到最后面
			;
	}
	v6 = count - 1;                               // 最后一个元素需要填充,所以索引要减去1
	if (count)                                    // 不为0执行
	{
		do
		{
			is_null = *string_src;
			*dst_end++ = *string_src++;               // 取出原字符串 ,并将字符串放入到需要返回的空间中
			if (!is_null)                             // 不为空则继续
				break;
		} while (v6--);
	}
	*dst_end = '0';
	return result;                                    // 最后返回指针
}

int main(int argc, char *argv)
{
	char dst[257];
	char src[257] = "hello lyshark";

	// 调用拷贝前3个字符,并在末尾填充0
	memset(dst, 0, sizeof(dst));
	sub_4074C0((BYTE *)dst, src, 3);
	std::cout << "前3个字符: " << dst << std::endl;

	// 调用拷贝后三个字符
	memset(dst, 0, sizeof(dst));
	sub_4074C0((BYTE *)dst, src, 5);
	std::cout << "前5个字符: " << dst << std::endl;

	getchar();
	return 0;
}

经过逆向分析后,我们将其通过VS编译,并运行测试是否可使用。


逆向还原子过程 sub_4073E0(): 该子过程只实现了一个简单的字符串拷贝功能。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 简单实现了字符串拷贝
BYTE *__stdcall sub_4073E0(BYTE *dst, BYTE *src)
{
	BYTE *result;
	char *src_string_ptr;
	bool string_is_null;
	BYTE *dst_string_ptr;
	char src_string_is_null;

	result = dst;                                 // 此处传指针,a1同样受影响
	src_string_ptr = (char *)src + 1;
	string_is_null = *src == 0;                   // 判断a2是否为空,字符串是否结尾
	*dst = *src;
	dst_string_ptr = dst + 1;
	if (!string_is_null)                        // 此处时返回值,往上看,也就说明此处判断字符串是否为空
	{
		do
		{
			src_string_is_null = *src_string_ptr;     // 取出指针中的字符,给v6
			*dst_string_ptr++ = *src_string_ptr++;    // 字符串拷贝
		} while (src_string_is_null);               // 字符串 a2 不为空
	}
	return result;                                // 返回字符串
}

int main(int argc, char *argv)
{
	BYTE dst[257];
	BYTE src[257] = "hello lyshark";

	sub_4073E0(dst, src);

	std::cout << "拷贝结果: " << dst << std::endl;

	getchar();
	return 0;
}

编译运行,得到输出。


逆向还原子过程 sub_407320(): 该子过程实现了字符串拼接操作,没有调用原生strcat函数。

#define _CRT_SECURE_NO_WARNINGS
#include <Windows.h>
#include <iostream>

// 实现字符串连接操作
BYTE * __stdcall sub_407320(BYTE *dst, char *src)
{
	BYTE *result;
	BYTE *dst_ptr;
	BYTE *v5;
	char src_ptr;
	char *v7;
	char v8;

	result = dst;
	dst_ptr = dst;
	if (*dst)                                   // dst不为空
	{
		while (*++dst_ptr)                        // 移动到字符串末尾
			;
	}
	v5 = dst_ptr + 1;                             // 末尾的下一个位置
	src_ptr = *src;
	v7 = src + 1;                                 // 指向原src字符串
	*(v5 - 1) = *src;
	if (src_ptr)
	{
		do
		{
			v8 = *v7;                                 // 取出src中的字符,依次给v8
			*v5++ = *v7++;                            // 将V7拷贝到V5 相当于把src连接到dst后面
		} while (v8);                               // 判断src是否是字符串的结束
	}
	return result;
}

int main(int argc, char *argv)
{
	BYTE dst[257] = "lyshark ";
	BYTE src[257] = "yyds 永远的伤";

	sub_407320(dst, (CHAR *)src);

	std::cout << "拼接后: " << dst << std::endl;

	getchar();
	return 0;
}

编译运行后,看一下 拼接结果把。

至此所有的子过程已经全部恢复完毕,并可以正常使用了,接下来需要恢复子过程的顶层sub_4075C0()该过程的恢复要比上方复杂许多,我们慢慢来分析吧。

逆向还原中间层过程 sub_4075C0(): 由于该子过程过于庞大,短期内无法直接全部恢复,为防止出现错误,我们向上一层,先恢复上一层代码。

上一层子过程sub_4089A0()代码量较少,我们先来恢复这一段。

#include <Windows.h>
#include <iostream>

// 上层调用
int __cdecl sub_4089A0(int a1, int a2, int a3, int a4)
{
	HMODULE msvcrt_handle;
	HMODULE user32_handle;

	FARPROC memset_base;
	FARPROC wsprintf_base;

	msvcrt_handle = LoadLibraryA("MSVCRT.dll");
	user32_handle = LoadLibraryA("USER32.dll");

	memset_base = GetProcAddress(msvcrt_handle, "memset");
	wsprintf_base = GetProcAddress(user32_handle, "wsprintfA");

	char key_[40];
	char dst[1024];

	memset(dst, 0, sizeof(dst));

	((void(__cdecl *)(int, DWORD, int))memset_base)(a3, 0, a4);
	((void(__cdecl *)(char *, DWORD, int))memset_base)(dst, 0, 1024);


	strcpy(key_, "SYSTEM\CurrentControlSet\Services\%s");

	((void(__cdecl *)(char *, char *, int))wsprintf_base)(dst, key_, a1);

	std::cout << "拼接注册表: "  << dst << std::endl;

	// return sub_4075C0(2147483650, (int)dst, a2, 1, (BYTE *)a3, 0, a4, 0);
	return 0;
}

int main(int argc,char *argv)
{
	sub_4089A0(1,1,1,1);
	return 0;
}

由于该方法过长,我们无需将所有的代码全部逆出来,直接分析sub_4075C0()函数参数,将我们需要的分支结构恢复即可。

首先该函数参数return sub_4075C0(2147483650, (int)v14, a2, 1, (_BYTE *)a3, 0, a4, 0);经分析后如下所示。

#include <Windows.h>
#include <iostream>

int __cdecl sub_4075C0(int a1, int a2, int a3, int a4, BYTE *a5, int a6, int a7, int a8)
{
	return 1;
}

int main(int argc,char *argv)
{
	char v14[1021];
	int a2;
	int a3;
	int a4;

	memset(v14, 0, sizeof(v14));
	sub_4075C0(2147483650, (int)v14, a2, 1, (BYTE *)a3, 0, a4, 0);

	return 0;
}

根据F5的分析,我们先把大体的循环分支等结构写出来,因为这是最基本的框架,接着在依次恢复每个分支中的子功能。

#include <Windows.h>
#include <iostream>

int __cdecl sub_4075C0(int a1, int a2, int a3, int a4, BYTE *a5, int a6, int a7, int a8)
{

	if (1)
	{
		// 打开成功执行
	}
	else
	{
		// 第一层循环
		switch (a8)
		{

			// 分支0内部
		case 0:
			switch (a4)
			{
			case 1:
			case 2:
				if (1)
				{

				}
				break;

			case 3:
				if (1)
				{

				}
				break;

			case 4:
				if (1)
				{

				}
				break;

			case 7:
				if (1)
				{
					for (int x = 0; x < 1; x++)
					{

					}
				}

				break;

			default:
				break;
			}
			break;
		
		case 1:

			while (1)
			{
				if (1)
				{
					break;
				}
			}
			break;

		case 2:

			while (1)
			{
				if (1)
				{
					break;
				}

				switch (a4)
				{
				case 1:
				case 2:
				case 3:
				case 4:
				case 7:
					break;
				}
				break;
			}
			break;

		case 3:
			break;

		default:
			break;

		}
	}

	return 1;
}

int main(int argc,char *argv)
{

	char v14[1021];
	int a2;
	int a3;
	int a4;

	memset(v14, 0, sizeof(v14));
	sub_4075C0(2147483650, (int)v14, a2, 1, (BYTE *)a3, 0, a4, 0);
	getchar();
	return 0;
}

由于代码中大量使用了动态获取API函数地址,所以为了还原简单,我们将直接调用API实现功能,不在使用GetProcAddress获取动态地址调用。






笔者正在抽时间分析,恢复代码,(最近很忙,只能慢慢来了)


版权声明: 本博客,文章与代码均为学习时整理的笔记,博客中除去明确标注有参考文献的文章,其他文章 均为原创 作品,转载请务必 添加出处 ,您添加出处是我创作的动力!

博主警告:如果您恶意转载本人文章,则您的整站文章,将会变为我的原创作品,请相互尊重!
原文地址:https://www.cnblogs.com/LyShark/p/15457309.html