火絨提供的樣本,我們可以學到什么?


某一天我像往常一樣在工位上躺平,就在我享受這愜意的躺平生活時,我的Boss直聘突然收到了火絨招聘人事的消息,簡單跟他聊了幾句之后,互相加了QQ,對方直接給了樣本,讓寫一個分析報告,要求是這樣的。

作為一名運維人員,還真沒寫報告的習慣,所以我不打算寫啥報告,直接逆向分析,爭取把這個程序的源代碼全部搞出來。

這個小程序里面還真的有我們可以借鑒的功能呢,等我把這些小功能逆出來分享在這里吧。

樣本下載: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;代替,后面的過程我們需要跟進,上方代碼我們確保可以便宜通過,並成功執行,如下。


逆向還原子過程 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獲取動態地址調用。






筆者正在抽時間分析,恢復代碼,(最近很忙,只能慢慢來了),兩周后繼續分析。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM