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

火绒提供的样本,我们可以学到什么?_软件逆向破解系列教程

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

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

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

样本下载:​​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;​​代替,后面的过程我们需要跟进,上方代码我们确保可以便宜通过,并成功执行,如下。

火绒提供的样本,我们可以学到什么?_软件逆向破解系列教程_03


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

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

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

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

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

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


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

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

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

编译通过即可。

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


还原 sub_4089A0 函数

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

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


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

运行后看结果吧。

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


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

火绒提供的样本,我们可以学到什么?_字符串_10


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

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

火绒提供的样本,我们可以学到什么?_字符串_10


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

火绒提供的样本,我们可以学到什么?_软件逆向破解系列教程_12


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

编译运行,得到输出。

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


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

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

火绒提供的样本,我们可以学到什么?_软件逆向破解系列教程_14

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





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

版权声明:本博客文章与代码均为学习时整理的笔记,文章 [均为原创] 作品,转载请 [添加出处] ,您添加出处是我创作的动力!