zoukankan      html  css  js  c++  java
  • 火绒提供的样本,我们可以学到什么?

    某一天我像往常一样在工位上躺平,就在我享受这惬意的躺平生活时,我的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获取动态地址调用。






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


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

    博主警告:如果您恶意转载本人文章,则您的整站文章,将会变为我的原创作品,请相互尊重!
  • 相关阅读:
    最近迷上用dvd字幕学习英语
    原始套接字
    c语言socket编程
    inet_aton和inet_network和inet_addr三者比较
    用man来查找c函数库
    ubuntu的系统日志配置文件的位置
    复制文件
    vim复制粘贴解密(转)
    vim的自动补齐功能
    两个数据结构ip和tcphdr
  • 原文地址:https://www.cnblogs.com/LyShark/p/15457309.html
Copyright © 2011-2022 走看看