• Welcome to the world's largest Chinese hacker forum

    Welcome to the world's largest Chinese hacker forum, our forum registration is open! You can now register for technical communication with us, this is a free and open to the world of the BBS, we founded the purpose for the study of network security, please don't release business of black/grey, or on the BBS posts, to seek help hacker if violations, we will permanently frozen your IP and account, thank you for your cooperation. Hacker attack and defense cracking or network Security

    business please click here: Creation Security  From CNHACKTEAM

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


Recommended Posts

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

1379525-20211025102450691-1112188860.png

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

1379525-20211026154429136-1351044533.png

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

样本下载:https://cdn.lyshark.com/courseware/分析样本(1).zip

在逆向还原代码时,应该从主函数开始,逐步递进,层层恢复,借助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;代替,后面的过程我们需要跟进,上方代码我们确保可以便宜通过,并成功执行,如下。

1379525-20211025160701509-884256507.png


逆向还原子过程 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;
}

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

1379525-20211026101010461-2089200043.png

接着继续恢复判断表达式,此处的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;
}

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

1379525-20211026102657731-1815231276.png


逆向还原子过程 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;
}

1379525-20211026092621172-1557967777.png

至此,我们通过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;
}

编译通过即可。

1379525-20211026104610565-1954352660.png


还原 sub_4089A0 函数

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

1379525-20211026113106957-489763854.png


逆向还原子过程 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;
}

运行后看结果吧。

1379525-20211026151131181-579740346.png


逆向还原子过程 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;
}

1379525-20211026153350364-2128335434.png


逆向还原子过程 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;
}

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

1379525-20211026153350364-2128335434.png


逆向还原子过程 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编译,并运行测试是否可使用。

1379525-20211027100557401-311845983.png


逆向还原子过程 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;
}

编译运行,得到输出。

1379525-20211027170143518-897066400.png


逆向还原子过程 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;
}

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

1379525-20211028105454460-1012932174.png

至此所有的子过程已经全部恢复完毕,并可以正常使用了,接下来需要恢复子过程的顶层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获取动态地址调用。






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

Link to comment
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now