1.样本概况
1.1 应用程序信息
MD5值:e0 35 f8 4e 71 f2 17 5e 5c 28 b6 f5 0e 0c 7e 41
简单功能介绍:文件分析工具
1.2 分析环境及工具
系统环境:win7 32位
分析工具:OD,VS2013,010Editor
应用程序:010Editor v8.01
1.3 分析目标
实现对010Editor的注册算法的分析,实现注册机生成序列号
2.具体分析过程
2.1 寻找关键函数
2.1.1 查找字符串
注册失败时弹出对话框,含有字符串。尝试搜索失败提示的字符串
找到字符串代码处,观察上下文,看到注册成功的字符串的标识
从下往上分析校验流程,寻找EDI的来源,一步步找到关键函数
进入函数内部寻找EDI == 0xDB的条件
通过函数的参数,发现该函数对用户名和密码进行了操作,该函数应该为关键校验函数,进一步分析
继续往下分析
分析用户名加密函数,还原代码,为注册机做准备
2.2 还原逻辑代码(如有代码验证贴出关键代码)
通过以上分析,用户名与密码得出以下校验条件:
1.(((P1^P7)^0x18)+0x3D)^A7>9
2.((((P2^P8)*0x100+P3^P6)^0x7892)+0x4D30)^0x3421)/B!=0
3.(WORD)((((P2^P8)*0x100+P3^P6)^0x7892)+0x4D30)^0x3421)/B<=0x3EB
4.((N*0xB^0x3421-0x4D30)^0x7892)&0xffff0000 = 0
5.P4=0x9c
6.P5=CodeName[0]
7.P6=CodeName[1]
8.P7=CodeName[2]
9.P8=CodeName[3]
先通过以上条件进行假定:
对于1中,暂假定(((P1^P7)^0x18)+0x3D)^A7=0x10,则(P1^P7)=0x62
结合3,4,通过以下代码枚举出一个值得出 P2^P8 = 0x88,P3^P6 = 0x01
for (DWORD i = 0; i < 0xffff;++i)
{
if (
(((((i ^ 0x7892) + 0x4d30) ^ 0x3421) & 0xffff) % 0xb == 0) &&
(((((i ^ 0x7892) + 0x4d30) ^ 0x3421) & 0xffff) / 0xb< 0x400)
)
{
printf("%04x",i);
break;
}
}
综合以上条件:
- P1=CodeName[2]^0x62
- P2=CodeName[3]^0x88
- P3=CodeName[1]^0x01
- P4=0x9C
- P5=CodeName[0]
- P6=CodeName[1]
- P7=CodeName[2]
- P8=CodeName[3]
CodeName代码还原如下:
DWORD dataBase = 0x2e64148;//此为010软件中加密用户名用到的地址
DWORD CodeName(char *pName, int n, DWORD arg3, DWORD arg4)
{
char *temp = pName;
int nameLen = strlen(pName);
if (nameLen <= 0)
{
return 0;
}
DWORD a1 = 0;
DWORD a2 = 0;
DWORD a3 = 0;
DWORD a4 = 0;
DWORD arg4Temp = arg4;
arg4Temp = (arg4Temp << 4) - arg4;
DWORD arg3Temp = arg3;
arg3Temp = (arg3Temp << 4) + arg3;
a2 = arg3Temp;
DWORD result = 0;
for (int i = 0; i < nameLen; ++i)
{
BYTE cTemp = toupper(pName[i]);
DWORD resultTemp = 0;
resultTemp = *(DWORD*)(dataBase + cTemp * 4);
resultTemp += a1;
DWORD nTemp = cTemp + 0xd;
nTemp &= 0xff;
resultTemp = resultTemp ^ (*(DWORD*)(nTemp * 4 + dataBase));
nTemp = cTemp + 0x2f;
nTemp &= 0xff;
resultTemp=(DWORD)(resultTemp*(*(DWORD*)(nTemp*4 dataBase)));
resultTemp += *(DWORD*)((a2 & 0xff) * 4 + dataBase);
resultTemp += *(DWORD*)((arg4Temp & 0xff) * 4 + dataBase);
resultTemp += *(DWORD*)((a3 & 0xff) * 4 + dataBase);
result = resultTemp;
a1 = result;
a2 += 0x13;
arg4Temp += 0x0d;
a2 += 0x9;
a4 += 7;
}
}
2.3 对于网络验证给文件打补丁
对于网络验证,软件会判断当前网络环境,如果当前已经联网会进行网络校验
由于用户名密码校验算法中的不唯一性,造成我们算出的注册码可能与服务器算出的并不一致,所以对于网络验证我们只能采用打补丁的方式直接nop掉即可过掉。
3.总结
对于010Edittor的逆向,关键是对算法进行逆向,对于一般的破解,首先可以搜索关键字符串,该方法非常简单方便,但是当出现了多处字符串引用时可以对API下断通过栈回溯的方式找到关键点,然后根据判断条件逐层分析成功的条件,查找关键跳转的条件来源,追溯关键函数,对关键函数进行仔细分析。010Editor的校验有多重,包括网络校验,因为可能涉及到数据库查询,我们并不清楚服务器的匹配方法,所以想要完美的过掉网络验证不是很容易,所以只能通过文件补丁的方式跳过网络校验,而仅执行本地校验的方式。其次010Editor的key和name的对应并不唯一,我们的注册机的实现思路仅仅是其中的一种假设,所以这也说明网络验证时我们生成的key并不一定可用。
4.注册机关键代码
由于010在加密用户名时用到了当前进程空间中的数据,所以在不清楚数据来源的情况下采用注入的方式获取数据或者通过远程进程内存读取 ReadProcessMemory 的方式获取该数据。
// 查找010Editor的进程窗口
void Ccrack_010_noInjectDlg::findwindow()
{
HWND hWnd = ::FindWindow(L"Qt5QWindowIcon", L"Register");
if (hWnd == NULL)
{
hWnd = ::FindWindow(L"Qt5QWindowIcon", L"010 Editor");
}
if (NULL == hWnd)
{
MessageBox(L"请先运行程序");
exit(0);
}
MessageBox(L"找到窗口");
DWORD processId = 0;
GetWindowThreadProcessId(hWnd, &processId);
h010Process = OpenProcess(PROCESS_VM_READ, FALSE, processId);
if (h010Process !=NULL)
{
MessageBox(L"获得句柄");
}
}
// 根据用户名计算注册码 相关代码片段
// 加密用户名代码
DWORD Ccrack_010_noInjectDlg::CodeName(char *pName, int n, DWORD arg3, DWORD arg4)
{
char *temp = pName;
int nameLen = strlen(pName);
if (nameLen <= 0)
{
return 0;
}
DWORD a1 = 0;
DWORD a2 = 0;
DWORD a3 = 0;
DWORD a4 = 0;
DWORD arg4Temp = arg4;
arg4Temp = (arg4Temp << 4) - arg4;
DWORD arg3Temp = arg3;
arg3Temp = (arg3Temp << 4) + arg3;
a2 = arg3Temp;
DWORD result = 0;
for (int i = 0; i < nameLen; ++i)
{
BYTE cTemp = toupper(pName[i]);
DWORD resultTemp = 0;
DWORD remoteRead = 0;
// resultTemp = *(DWORD*)(dataBase + cTemp * 4);
ReadProcessMemory(h010Process, (LPVOID)(dataBase + cTemp * 4), &remoteRead, 4, NULL);
resultTemp = remoteRead;
resultTemp += a1;
if (n == 1)
{
DWORD nTemp = cTemp + 0xd;
nTemp &= 0xff;
//resultTemp = resultTemp ^ (*(DWORD*)(nTemp * 4 + dataBase));
ReadProcessMemory(h010Process, (LPVOID)(dataBase + nTemp * 4), &remoteRead, 4, NULL);
resultTemp = resultTemp^remoteRead;
nTemp = cTemp + 0x2f;
nTemp &= 0xff;
//resultTemp = (DWORD)(resultTemp*(*(DWORD*)(nTemp * 4 + dataBase)));
ReadProcessMemory(h010Process, (LPVOID)(dataBase + nTemp * 4), &remoteRead, 4, NULL);
resultTemp = resultTemp*remoteRead;
//resultTemp += *(DWORD*)((a2 & 0xff) * 4 + dataBase);
ReadProcessMemory(h010Process, (LPVOID)((a2 & 0xff) * 4 + dataBase), &remoteRead, 4, NULL);
resultTemp += remoteRead;
//resultTemp += *(DWORD*)((arg4Temp & 0xff) * 4 + dataBase);
ReadProcessMemory(h010Process, (LPVOID)((arg4Temp & 0xff) * 4 + dataBase), &remoteRead, 4, NULL);
resultTemp += remoteRead;
//resultTemp += *(DWORD*)((a3 & 0xff) * 4 + dataBase);
ReadProcessMemory(h010Process, (LPVOID)((a3 & 0xff) * 4 + dataBase), &remoteRead, 4, NULL);
resultTemp += remoteRead;
result = resultTemp;
a1 = result;
a3 += 0x13;
arg4Temp += 0x0d;
a2 += 0x9;
a4 += 7;
}
}
return result;
}
// 将数字转换为字母,用于输出到GUI界面
void Ccrack_010_noInjectDlg::num2Str(BYTE *str, BYTE num)
{
if ((num & 0xf) < 0xA)
{
*(str + 1) = (num & 0xf) + '0';
}
else{
*(str + 1) = (num & 0xf) + 'a' - 0x0A;
}
if (((num >> 4) & 0xf) < 0xA)
{
*str = ((num >> 4) & 0xf) + '0';
}
else{
*str = ((num >> 4) & 0xf) + 'a' - 0x0A;
}
}
// 计算注册码并输出
void Ccrack_010_noInjectDlg::OnBnClickedButton1()
{
UpdateData(TRUE);
_asm{
mov eax, eax
mov eax, eax
}
DWORD dwName = CodeName(CStringA(m_user).GetBuffer(), 1, 0, 0xE6);
BYTE pwd[20] = { 0 };
pwd[6] = '9';
pwd[7] = 'c';
for (int i = 5; i < 9; ++i)
{
num2Str(&pwd[(i - 1) * 2], (BYTE)((dwName >> ((i - 5) * 8)) & 0xff));
}
BYTE p5 = dwName & 0xff;
BYTE p6 = (dwName >> 8) & 0xff;
BYTE p7 = (dwName >> 0x10) & 0xff;
BYTE p8 = (dwName >> 0x18) & 0xff;
num2Str(pwd, p7 ^ 0x62);
num2Str(pwd + 2, p8 ^ 0x88);
num2Str(pwd + 4, p6 ^ 0x01);
m_key = pwd;
UpdateData(FALSE);
}