1.程序分析
1.1 start
start 函数
public start
start proc near
push 0 ; lpModuleName
call GetModuleHandleA
mov hInstance, eax
push 0
push 0
push 0
call sub_401039
call InitCommonControls
push 0 ; dwInitParam
push offset DialogFunc ; lpDialogFunc
push 0 ; hWndParent
push 65h ; lpTemplateName
push hInstance ; hInstance
call DialogBoxParamA
push 0 ; uExitCode
call ExitProcess
start endp
, 分析sub_401039的原因,其他的call,都是系统函数。看看它的内容
1.2 sub_401039
void (__stdcall __noreturn *__stdcall sub_401039(int a1, int a2, int a3))(int, int, int)
{
int v3; // ecx
int v4; // edx
void (__stdcall __noreturn *result)(int, int, int); // eax
sub_401080();
v3 = 0;
v4 = 0;
for ( result = start;
(unsigned int)sub_401A02 >= (unsigned int)result;
result = (void (__stdcall __noreturn *)(int, int, int))((char *)result + 1) )
{
LOBYTE(v4) = *(_BYTE *)result;
v3 += v4;
}
return result;
调用了sub_401080();
1.3 sub_401080
_DWORD *sub_401080()
{
v0 = GetModuleHandleA(ModuleName);//返回函数模块句柄(基址)
v1 = (int)v0;
v2 = (_DWORD *)sub_401377((int)v0);
sub_40139E(v1, v2, (int)&unk_403AED);
sub_40139E(v1, v3, (int)&unk_403AF8);
sub_40139E(v1, v4, (int)&unk_403B03);
sub_40139E(v1, v5, (int)&unk_403B0E);
sub_40139E(v1, v6, (int)&unk_403B19);
sub_40139E(v1, v7, (int)&unk_403B24);
sub_40139E(v1, v8, (int)&unk_403B2F);
v9 = GetModuleHandleA(aUser32Dll_0);
v10 = (int)v9;
v11 = (_DWORD *)sub_401377((int)v9);
sub_40139E(v10, v11, (int)&unk_403AED);
sub_40139E(v10, v12, (int)&unk_403AF8);
sub_40139E(v10, v13, (int)&unk_403B03);
sub_40139E(v10, v14, (int)&unk_403B0E);
sub_40139E(v10, v15, (int)&unk_403B19);
sub_40139E(v10, v16, (int)&unk_403B24);
sub_40139E(v10, v17, (int)&unk_403B2F);
sub_40139E(v10, v18, (int)&unk_403B3A);
v19 = GetModuleHandleA(aKernel32Dll_0);
v20 = (int)v19;
v21 = (_DWORD *)sub_401377((int)v19);
sub_40139E(v20, v21, (int)&unk_403B45);
sub_40139E(v20, v22, (int)&unk_403B50);
sub_40139E(v20, v23, (int)&unk_403B5B);
sub_40139E(v20, v24, (int)&unk_403B66);
sub_40139E(v20, v25, (int)&unk_403B71);
sub_40139E(v20, v26, (int)&unk_403B7C);
sub_40139E(v20, v27, (int)&unk_403B87);
sub_40139E(v20, v28, (int)&unk_403B92);
sub_40139E(v20, v29, (int)&unk_403B9D);
sub_40139E(v20, v30, (int)&unk_403BA8);
sub_40139E(v20, v31, (int)&unk_403BB3);
sub_40139E(v20, v32, (int)&unk_403BBE);
sub_40139E(v20, v33, (int)&unk_403BC9);
sub_40139E(v20, v34, (int)&unk_403BD4);
sub_40139E(v20, v35, (int)&unk_403BDF);
sub_40139E(v20, v36, (int)&unk_403BEA);
sub_40139E(v20, v37, (int)&unk_403BF5);
sub_40139E(v20, v38, (int)&unk_403C00);
sub_40139E(v20, v39, (int)&unk_403C0B);
return sub_40139E(v20, v40, (int)&unk_403C16);
}
调用了sub_401377,分析sub_401377
1.4 sub_401377
int __stdcall __spoils<ecx> sub_401377(int a1)
{
int result; // eax
result = a1;
if ( a1 )
result = a1 + *(_DWORD *)(a1 + *(_DWORD *)(a1 + 0x3C) + 0x78);// a1 是基址,对于pe的操作,遍历导出表
return result;
}
具体内容如下 伪代码的意思,用基址a1加上3C,然后取出这个地址里面的值,把这个值加上78,然后再取里面的值。与基址相加,这个其实看到的时候稍微有点印象,防病毒里面讲过,这是一波对于pe的操作,最终结果是找到了导出表的位置。随便找一个dll(32位下的),拖进010editor,验证如下。
1.5 sub_40139E
_DWORD *__stdcall __spoils<ecx> sub_40139E(int base, _DWORD *export, int a3)
{
_DWORD *result; // eax
unsigned int i; // ebx
int v5; // edx
int v6; // esi
unsigned __int8 *v7; // ebx
int v8; // eax
unsigned int v9; // eax
unsigned int v10; // [esp-14h] [ebp-124h]
_DWORD *v11; // [esp+8h] [ebp-108h]
result = export;
v11 = (_DWORD *)(export[8] + base); // 导出表结构体的第8个字段,NumberOfNames;以名称方式导出的函数的总数根据导出表结构推出来的
if ( !*(_BYTE *)a3 )
{
for ( i = 0; i < export[6]; i = v10 + 1 ) // 导出表结构体的第6个字段,NumberOfFunctions;导出函数的总数,该循环,直接遍历
{
v10 = i;
v5 = 0;
v6 = 0;
v7 = (unsigned __int8 *)(base + *v11);
v8 = *v7;
while ( (_BYTE)v8 )
{
if ( v5 == 3 ) // 比较第三个字符
{
if ( (_BYTE)v8 != *(_BYTE *)(a3 + 5) )
break;
}
else if ( v5 == 5 && (_BYTE)v8 != *(_BYTE *)(a3 + 6) )// 比较第五个字符
{
break;
}
v6 += v8;
LOBYTE(v8) = *++v7;
++v5;
}
if ( v6 == *(_DWORD *)(a3 + 1) ) // 校验和
{
v9 = base + *(_DWORD *)(export[7] + base + 4 * *(unsigned __int16 *)(export[9] + base + 2 * v10));
*(_BYTE *)a3 = 1;
*(_WORD *)(a3 + 9) = v9;
result = (_DWORD *)(v9 >> 16);
*(_WORD *)(a3 + 7) = (_WORD)result;
return result;
}
++v11;
}
result = 0;
}
return result;
}
导出表结构如下
typedef struct _IMAGE_EXPORT_DIRECTORY {
DWORD Characteristics;//未使用,总是定义为0
DWORD TimeDateStamp;//文件生成时间
WORD MajorVersion;//未使用,总是定义为0
WORD MinorVersion;//未使用,总是定义为0
DWORD Name; //模块的真实名称的RVA
DWORD Base; //基数,加上序数就是函数地址数组的索引值
DWORD NumberOfFunctions;//导出函数的总数
DWORD NumberOfNames; //以名称方式导出的函数的总数
DWORD AddressOfFunctions; // RVA from base of image指向输出函数地址的RVA
DWORD AddressOfNames; // RVA from base of image指向输出函数名字的RVA
DWORD AddressOfNameOrdinals; // RVA from base of image向输出函数序号的RVA
} IMAGE_EXPORT_DIRECTORY, *PIMAGE_EXPORT_DIRECTORY;
综上,1.3 sub_401080函数,就是遍历导出表,获取函数名称 回退到1.2 sub_401039。
2.反调试
指向非法地址
如图所示
逻辑:eax+ebx,两个函数偏移相加,进行比较,然后将两个偏移累加求和,得到的和,减取0下10,再将ecx与loc_401072比较,不相等就跳转至0x83648888,此地址为内核地址,ring0级别,用户不能访问,立刻报错,起到了反调试的作用。
解决方法,edit——patch program——assmeble,将jz改为jmp,不让它执行跳转到ring0的语句。 参考大佬的wp,反调试的地方还有
401505 jz short loc_40150E >> jmp
401847 jz short loc_401850 >> jmp
3. 俄罗斯方块
跟随流程分析,来到DialogFunc
3.1 DialogFunc
INT_PTR __userpurge DialogFunc@<eax>(int a1@<ebx>, HWND hDlg, UINT a3, WPARAM a4, LPARAM a5)
{
int (__stdcall *v5)(_DWORD, _DWORD, int (__stdcall *)(int), _DWORD, _DWORD, _DWORD); // ebx
switch ( a3 )
{
case 0x110u:
hWnd = hDlg;
if ( unk_403BA8 )
{
LOWORD(a1) = *(_WORD *)((char *)&unk_403BA8 + 7);
v5 = (int (__stdcall *)(_DWORD, _DWORD, int (__stdcall *)(int), _DWORD, _DWORD, _DWORD))(a1 << 16);
LOWORD(v5) = *(_WORD *)((char *)&unk_403BA8 + 9) + (_WORD)v5;
if ( !v5(0, 0, sub_40149C, 0, 0, 0) )//创建线程
ExitProcess(0);
}
break;
case 0x111u:
if ( (_WORD)a4 == 105 )
sub_4012E7();
break;
case 0x10u:
EndDialog(hDlg, 0);
break;
default:
return 0;
}
return 1;
}
可以看到一个模块如下
mov bx, [eax+7]
shl ebx, 10h
add bx, [eax+9]
push 0
push 0
push 0
push offset sub_40149C
push 0
push 0
call ebx
or eax, eax
jnz short loc_4012AB
根据大佬wp,在输入字符串之后,这是创建线程,来对字符串进行操作。点进去可以看到 sub_40149C这个函数,创建了五个类似的线程
mov bx, [eax+7]
shl ebx, 10h
add bx, [eax+9]
push 0
push 0
push 0
push offset loc_4016F0
push 0
push 0
call ebx
or eax, eax
jnz short loc_40157F
3.2 sub_4012E7()
在另一个case调用了sub_4012E7(),具体分析见注释。
char *sub_4012E7()
{
int v0; // edx
signed int v1; // ebx
CHAR *v2; // eax
int v3; // ecx
char *result; // eax
CHAR String; // [esp+0h] [ebp-100h]
RtlZeroMemory(&String, 256);
GetDlgItemTextA(hWnd, 1001, &String, 256); // sub_4012E7,check,获取输入,存入string
if ( !dword_403037 )
sub_401BA1();
v0 = 0;
v1 = 8;
v2 = &String;
v3 = (unsigned __int8)String;
while ( v1 ) // v1=8,v1在循环中做减法,共计循环8次
{
v0 += v3;
LOBYTE(v3) = *++v2;
--v1;
} // v0是一个和,把v3,也就是输入字符串的每个值相加
if ( (_BYTE)v3 ) // v3不是0,弹出errorbox
result = (char *)MessageBoxA(hWnd, Caption, Caption, 0);
else
result = sub_401B0A(v0); // v3是假,进入这个函数
return result;
}
3.3 sub_401BA1()
int sub_401BA1()
{
unsigned int v0; // eax
int result; // eax
unsigned int j; // [esp+0h] [ebp-8h]
unsigned int i; // [esp+4h] [ebp-4h]
memset(dword_40303B, 0, 0x9Au);
for ( i = 0; i < 0xE; ++i )
{
for ( j = 0; j < 0xB; ++j )
{
if ( !j || j == 10 || i == 13 )
*(_DWORD *)&dword_40303B[11 * i + j] = 1;
}
}
v0 = time(0);
srand(v0);
result = sub_401C84();
dword_403037 = 1;
return result;
}
调用了sub_401C84()
3.4 sub_401C84()
int sub_401C84()
{
unsigned int v0; // eax
v0 = rand();
dword_4030E1 = v0 % 0x1C;
dword_4030DD = (int)&asc_4030ED[16 * (v0 % 0x1C)];
dword_4030D5 = 3;
dword_4030D9 = 0;
return boundary(3, 0);
}
看一下asc_4030ED内容
这是俄罗斯方块,16个字节为一组
“* * * * ” 代表‘——’
“* * * * ” 代表‘|’,其他以此类推
所以至此3.3sub_401BA1()分析完毕,初始化俄罗斯方块
3.5 sub_401B0A
分析 3.2 sub_4012E7()中的另一个函数sub_401B0A,分析见注释。ida有点傻了,没有完全逆出来逻辑,结合ida-view和程序设计思想分析
char *__stdcall sub_401B0A(int a1)
{
if ( !dword_403037 ) // 判断游戏是否完成
return (char *)MessageBoxA(0, Caption, Caption, 0);
++byte_403C99;
switch ( a1 ) // a1是输入字符串的和。共计五个分支,根据累加和,挑选一个分支执行
{
case 0x566:
return (char *)sub_401DA0();
case 0x79A:
return (char *)sub_401DC1();
case 0x86B:
return sub_401E16();
case 0x5D5:
return (char *)sub_401DE2();
case 0x325: // 必须选这个,因为ASCII码,最大的z为7A,乘8之后,为0x3D0,最大为0x3D0
return (char *)sub_401DE2();
}
byte_403C99 = 0;
return (char *)MessageBoxA(0, Caption, Caption, 0);
}
传入的参数a1,是校验和,就是前面提到的,把8个字符的ascii相加,可以判断出在所有的可选的字符中,8个ascii最大值为0x3D0,根本不可能出现上面的case,只能选case 0x325
3.6 sub_401DA0()
int sub_401DA0()
{
int result; // eax
result = boundary(dword_4030D5 - 1, dword_4030D9);
if ( !result ) // 通过boundary函数进行计算,判断有没有触碰边界,如果碰到了,直接返回。没有触碰到,返回为假,把俄罗斯方块移动一步
--dword_4030D5; // 移动方块
return result;
}
这个是边界判断函数,判断方块是否能移动。
4.动态调试
2333333,不会,先缺着,没看懂。 得出条件 int (x7 x6 x5 x4 ^ 0x66) +int( x3 x2 x1 x0 ^ 0x66) = 0x32113442 push 004032b5 //“good”地址
5.破解
最终思路
- sn字节求和为325
- sn需要异或 66,记v
- dword,v0 + v1 = 32113442
- v作为key解码,y = (x + v1) ^ v0,解码的明文中含有 Good那个地址。
附一篇大佬破解程序
#include "stdafx.h"
#include <string.h>
const char *cch = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
int test2(char buf[62*62], unsigned char vv)
{
int k = 0;
char sn[4] = { 0 };
for (int i = 0; i < 62; i++)
{
sn[0] = (unsigned char)cch[i];
for (int j = 0; j < 62; j++)
{
sn[1] = (unsigned char)cch[j];
if (((sn[0] ^ 0x66) + (sn[1] ^ 0x66)) == vv)
{
buf[k++] = sn[0];
buf[k++] = sn[1];
}
}
}
return k;
}
unsigned char cc[] = {
0x69, 0x92, 0x14, 0x09, 0x58, 0x1A, 0x65, 0x79, 0x60, 0x13, 0xCC, 0xD1, 0x28, 0x34, 0x0A, 0x2C,
0x0F, 0x40, 0x0B, 0x5C, 0x83, 0x08, 0x4B, 0xF4, 0x30, 0x85, 0x39, 0x34, 0x18, 0x40, 0x0B, 0xCD,
0xE9, 0x6F, 0xCA, 0x8C, 0x1F, 0x0F, 0x4B, 0xF4,
};
int test3(char *sn)
{
char buf[16] = { 0 };
for (int i = 0; i < 8; i++)
{
buf[i] = sn[i] ^ 0x66;
}
unsigned long *vv = (unsigned long *)buf;
int s = vv[0] + vv[1];
if (s != 0x32113442)
return 0;
unsigned long mm[0x0a];
unsigned long *c = (unsigned long*)cc;
for (int i = 0; i < 0x0a; i++)
{
int x = c[i];
x += vv[1];
x ^= vv[0];
mm[i] = x;
}
int f = 0;
for (int i = 0; i < 0x28; i++)
{
if (*(((unsigned char*)mm)+ i) == 0xb5)
{
if (*(((unsigned char*)mm) + i+1) == 0x32)
{
if (*(((unsigned char*)mm) + i + 2) == 0x40)
{
if (*(((unsigned char*)mm) + i + 3) == 0x00)
{
f = 1;
}
}
}
}
}
//004032B5
if (f == 1)
{
printf("sn:%s\n", sn);
}
return 0;
}
int _tmain(int argc, _TCHAR* argv[])
{
char buf[16] = { 0 };
unsigned char a0[62 * 62] = { 0 };
int k0 = test2((char*)a0, 0x42);
unsigned char a1[62 * 62] = { 0 };
int k1 = test2((char*)a1, 0x34);
unsigned char a2[62 * 62] = { 0 };
int k2 = test2((char*)a2, 0x11);
unsigned char a3[62 * 62] = { 0 };
int k3 = test2((char*)a3, 0x32);
for (int i = 0; i < k0; i+=2)
{
int x0 = a0[i] + a0[i + 1];
for (int j = 0; j < k1; j += 2)
{
int x1 = a1[j] + a1[j + 1];
for (int m = 0; m < k2; m += 2)
{
int x2 = a2[m] + a2[m + 1];
for (int n = 0; n < k3; n += 2)
{
int x3 = a3[n] + a3[n + 1];
if ((x0 + x1 + x2 + x3) == 0x325)
{
buf[0] = a0[i]; buf[4] = a0[i+1];
buf[1] = a1[j]; buf[5] = a1[j+1];
buf[2] = a2[m]; buf[6] = a2[m+1];
buf[3] = a3[n]; buf[7] = a3[n+1];
test3(buf);
}
}
}
}
}
return 0;
}
6.总结
能力不足,只能分析一小部分,强行结尾了。。。