0x01 主函数分析
将可执行文件拖入ida,寻找winmain函数,f5,结果发现里面啥也没。主函数调用空函数…这怎么可能。
int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
{
sub_401280();
return 0;
}
void sub_401280()
{
;
}
0x02 分析隐藏的逻辑
1.tls回调函数
关于tls函数的作用,前面有过简单分析,程序入口点相关,ctrl+E,寻找程序入口点。
tls中代码逻辑如下
HANDLE __stdcall TlsCallback_0(int a1, int a2, int a3)
{
HANDLE result; // eax
int v4; // [esp+4h] [ebp-10h]
int v5; // [esp+8h] [ebp-Ch]
int Parameter; // [esp+Ch] [ebp-8h]
int v7; // [esp+10h] [ebp-4h]
result = (HANDLE)0xCCCCCCCC;
v4 = 0xCCCCCCCC;
v5 = 0xCCCCCCCC;
Parameter = 0xCCCCCCCC;
v7 = 0xCCCCCCCC;
if ( a2 == 1 )
{
sub_401D50();
InitializeCriticalSection(&CriticalSection);//解决同步互斥的一个函数
sub_401C10();
result = CreateThread(0, 0, StartAddress, &Parameter, 0, 0);
}
return result;
}
我们要关注的主要是函数调用,也就是sub_401D50()和sub_401C10()。
1.1 sub_401D50()函数
函数逻辑
int sub_401D50()
{
signed int i; // [esp+0h] [ebp-18h]
int v2; // [esp+Ch] [ebp-Ch]
int v3; // [esp+10h] [ebp-8h]
int v4; // [esp+14h] [ebp-4h]
v3 = 0xCCCCCCCC;
v4 = 0xCCCCCCCC;
v2 = 233;
*(int *)((char *)&v2 + 1) = (char *)off_414014 - (char *)((_BYTE *)off_414018 + 4) - 5;
sub_4019B0(off_414018);
for ( i = 0; i < 5; ++i )
*((_BYTE *)off_414018 + i + 4) = *((_BYTE *)&v2 + i);
return sub_4019E0(off_414018);
}
长跳指令偏移基本算法:long_jmp_E9_offset = tgt_addr - (long_jmp_E9_addr+5) //length of long_jmp_E9 = 5
结果很明显,*(int *)((char *)&v2 + 1) = (char *)off_414014 - (char *)((_BYTE *)off_414018 + 4) - 5;就是跳转,off_414014和off_414018的内容如下
.data:00414014 off_414014 dd offset sub_401220 ;
.data:00414018 ; LPVOID off_414018
.data:00414018 off_414018 dd offset sub_401280 ;
就是获取两个sub_401220和sub_401280的偏移,最后保存到off_414018,而且还有一个长跳转,这是一个HOOK操作。通过IDA HEX View直接将401284位置的数据改为E9 97 FF FF FF(jmp指令)看一下HOOK的目的是什么
修改后的流程
来看看sub_401220()函数的伪代码
void __noreturn sub_401220()
{
HINSTANCE hInstance; // ST18_4
hInstance = GetModuleHandleW(0);
DialogBoxParamW(hInstance, (LPCWSTR)0x65, 0, DialogFunc, 0);
exit(0);
}
拿到句柄,创建了DialogBox,看看DialogFunc
BOOL __stdcall DialogFunc(HWND hWnd, UINT a2, WPARAM a3, LPARAM a4)
{
if ( a2 == 16 )
{
DestroyWindow(hWnd);
}
else if ( a2 == 273 && a3 == 1002 )
{
sub_401040(hWnd);
}
return 0;
}
明确一点,一个软件,肯定是要完成功能,因此一定要调用函数,而且是能完成功能的函数,像什么DestroyWindow(hWnd);销毁窗口,这不用看就知道没用,所以关键在有功能的函数。 一步一步往下找,再看看sub_401040
int __cdecl sub_401040(HWND hDlg)
{
void *v1; // ST08_4
signed int i; // [esp+Ch] [ebp-24h]
signed int v4; // [esp+10h] [ebp-20h]
CHAR String; // [esp+18h] [ebp-18h]
int v6; // [esp+19h] [ebp-17h]
int v7; // [esp+1Dh] [ebp-13h]
int v8; // [esp+21h] [ebp-Fh]
int v9; // [esp+25h] [ebp-Bh]
__int16 v10; // [esp+29h] [ebp-7h]
char v11; // [esp+2Bh] [ebp-5h]
strcpy(Text, "try again!");
strcpy(Caption, "fail");
String = 0;
v6 = 0;
v7 = 0;
v8 = 0;
v9 = 0;
v10 = 0;
v11 = 0;
GetDlgItemTextA(hDlg, 1001, &String, 20);//获取输入元素个数
v4 = 0;
for ( i = 0; i < 20; ++i )
v4 += *(&String + i);
if ( v4 > 0 && v4 < 0x1024 )
{
v1 = malloc(v4);
sub_401020();
j___free_base(v1);
}
return MessageBoxA(0, Text, Caption, 0);
}
整体的逻辑,判断校验和在不在0-0x1024之间,不再的话弹窗“失败”,所以重点肯定在成功的里面,进入sub_401020()
void sub_401020()
{
;
}
额,完蛋,又是空的,不可能。再看看
1.2 sub_401C10()函数
然后就回退,回退但最初的tls回调函数,里面有两个函数调用,刚才分析了一个,还有一个,看看这个。基本逻辑如下
int sub_401C10()
{
void *v0; // edx
signed int j; // [esp+0h] [ebp-Ch]
signed int i; // [esp+8h] [ebp-4h]
sub_4018D0(0xCCCCCCCC, 0xCCCCCCCC, 0xCCCCCCCC);
lpAddress = v0;
sub_4019B0(v0);
for ( i = 0; i < 5; ++i )
byte_4147DC[i] = *((_BYTE *)lpAddress + i + 32);
*(_DWORD *)&byte_414028[1] = (char *)sub_401A10 - (char *)((_BYTE *)lpAddress + 32) - 5;
for ( j = 0; j < 5; ++j )
*((_BYTE *)lpAddress + j + 32) = byte_414028[j];
return sub_4019E0(lpAddress);
}
这个跟上一个的逻辑很像啊,相同的粗看,不同的重点看。最明显的上面三行差别很大,多了两个函数,而且v0也就是edx,这个参数传进了两个for循环,一个一个看吧。 首先sub_4018D0,伪代码如下
int sub_4018D0()
{
int v0; // edx
sub_4018F5(0x3BD696F4);
return sub_401928(v0, 0x925DF53F);
}
查看sub_4018F5代码
int __stdcall sub_4018F5(int a1)
{
int v1; // ecx
_DWORD *v2; // esi
int v3; // ST08_4
int v4; // ST04_4
int v5; // eax
int result; // eax
int v7; // edx
v1 = a1;
v2 = *(_DWORD **)(*(_DWORD *)(__readfsdword(0x30u) + 0xC) + 0x1C);
do
{
v2 = (_DWORD *)*v2;
v3 = v1;
v4 = v2[6];// 获取dll名称;
v5 = sub_401810(v2[6]);//计算名称的长度
result = sub_4017B0(v4, 2 * v5);//计算一个校验和
v1 = v3;
}
while ( result != v3 );//校验和与传入参数不等的时候,一直不停的校验
v7 = v2[2];//相等获取dll基址
return result;
}
看看v2是啥 readfsdword的微软官方解释为:
Microsoft Specific
Read memory from a location specified by an offset relative to the beginning of the FS segment.
也就是从相对于FS段开头的偏移量指定的位置读取内存。FS段寄存器指向当前的TEB结构,在TEB编译0x30处是PEB指针,通过这个指针即可获得PED的地址。 所以,根据指定一个值获得动态库基址。将DLL基址和目标函数值传入sub_401928()函数
下面看sub_401928
int __stdcall sub_401928(int a1, int a2)
{
_DWORD *v2; // esi
int i; // eax
int v4; // esi
int v5; // eax
int v7; // [esp-8h] [ebp-14h]
int v8; // [esp+0h] [ebp-Ch]
int v9; // [esp+4h] [ebp-8h]
int v10; // [esp+8h] [ebp-4h]
v2 = (_DWORD *)(a1 + *(_DWORD *)(a1 + *(_DWORD *)(a1 + 60) + 120));
v10 = v2[7];
v9 = v2[8];
v8 = v2[9];
for ( i = 0; ; i = v7 + 1 )
{
v4 = a1 + *(_DWORD *)(a1 + v9 + 4 * i);
v7 = i;
v5 = sub_401870(a1 + *(_DWORD *)(a1 + v9 + 4 * i));
if ( sub_4017B0(v4, v5) == a2 )
break;
}
return a1 + *(_DWORD *)(a1 + v10 + 4 * *(unsigned __int16 *)(a1 + v8 + 2 * v7));
}
该函数返回目标函数地址,目标函数为:GetDlgItemTextA()也就是即sub_00401A10
TLS函数分析完成, 函数 401A10 为真正的check函数,其调用 sub_401290函数,如果 sub_401290函数返回1,则起再次计算输入sn的hash值,并判断此hash值是否等于0x5634D252
2.判定函数
2.1 sub_401A10()函数
伪代码:
int __usercall sub_401A10@<eax>(int a1@<ecx>, int a2@<ebp>, int a3@<esi>)
{
unsigned int v3; // et0
int v4; // eax
_BYTE *v5; // ecx
unsigned int v7; // [esp-24h] [ebp-24h]
int v8; // [esp-18h] [ebp-18h]
v8 = a1;
v3 = __readeflags();
v7 = v3;
dword_414800 = a3;
dword_4147EC = *(_DWORD *)(a2 + 20);
dword_4147F0 = malloc(dword_4147EC);
dword_4147F4 = a2 + 16;
memmove(dword_4147F0, *(const void **)(a2 + 16), dword_4147EC);//获取输入内容
*(_DWORD *)(a2 - 16) = dword_4147F0;
*(_DWORD *)(a2 - 20) = *(_DWORD *)(a2 - 16) + 1;
do
*(_BYTE *)(a2 - 21) = *(_BYTE *)(*(_DWORD *)(a2 - 16))++;
while ( *(_BYTE *)(a2 - 21) );
*(_DWORD *)(a2 - 28) = *(_DWORD *)(a2 - 16) - *(_DWORD *)(a2 - 20);
if ( sub_401290((int)dword_4147F0, *(_DWORD *)(a2 - 28)) )//第一层检验
{
v4 = sub_401870(dword_4147F0);
if ( sub_4017B0((int)dword_4147F0, v4) == 1446302290 )//第二层检验
{
v5 = off_414030;
*(_WORD *)off_414030 = 0;
v5[2] = 0;
for ( *(_WORD *)(a2 - 4) = 0; *(signed __int16 *)(a2 - 4) < 8; ++*(_WORD *)(a2 - 4) )
*((char *)off_414030 - *(signed __int16 *)(a2 - 4)) = (39 - *(unsigned __int16 *)(a2 - 4)) ^ byte_41401C[7 - *(signed __int16 *)(a2 - 4)];
*(_WORD *)off_414034 = 0;
for ( *(_WORD *)(a2 - 8) = 0; *(signed __int16 *)(a2 - 8) < 3; ++*(_WORD *)(a2 - 8) )
*((char *)off_414034 - *(signed __int16 *)(a2 - 8)) = (34 - *(unsigned __int16 *)(a2 - 8)) ^ byte_414024[2 - *(signed __int16 *)(a2 - 8)];
}
}
j___free_base(dword_4147F0);
dword_4147F8 = (int (__thiscall *)(_DWORD))((char *)lpAddress + 32);
sub_4019B0(lpAddress);
for ( *(_DWORD *)(a2 - 12) = 0; *(_DWORD *)(a2 - 12) < 5; ++*(_DWORD *)(a2 - 12) )
*((_BYTE *)lpAddress + *(_DWORD *)(a2 - 12) + 32) = byte_4147DC[*(_DWORD *)(a2 - 12)];
sub_4019E0(lpAddress);
dword_4147FC = 1;
__writeeflags(v7);
return dword_4147F8(v8);
}
2.2 sub_401290()函数
第一层检验
bool __cdecl sub_401290(int a1, int a2)
{
byte_4147D0[0] = 4;
byte_4147D0[1] = 1;
byte_4147D0[2] = 3;
byte_4147D0[3] = 7;
byte_4147D0[4] = 2;
byte_4147D0[5] = 5;
byte_4147D0[6] = 8;
byte_4147D0[7] = 6;
byte_4147D0[8] = 0;
return sub_4015B0(a1, a2);
}
有个数组,是413725860
bool __cdecl sub_4015B0(int a1, int a2)
{
int i; // [esp+0h] [ebp-Ch]
int v4; // [esp+8h] [ebp-4h]
v4 = 0xCCCCCCCC;
if ( a2 % 2 )
return 0;
for ( i = 0; i < a2; i += 2 )
{
if ( *(_BYTE *)(i + a1) == 'w' )
v4 = 0;
if ( *(_BYTE *)(i + a1) == 'd' )
v4 = 1;
if ( *(_BYTE *)(i + a1) == 's' )
v4 = 2;
if ( *(_BYTE *)(i + a1) == 'a' )
v4 = 3;
if ( !sub_401380(v4, *(char *)(i + a1 + 1) - 48) )
return 0;
}
return byte_4147D0[0] == 1
&& byte_4147D0[1] == 2
&& byte_4147D0[2] == 3
&& byte_4147D0[3] == 4
&& byte_4147D0[4] == 5
&& byte_4147D0[5] == 6
&& byte_4147D0[6] == 7
&& byte_4147D0[7] == 8
&& !byte_4147D0[8];
}
对传入的序列号进行了判断输入w->0,d->1,s->2 a->3,传入的值经过一个变换,会变为123456780
char __cdecl sub_401380(int a1, int a2)
{
char result; // al
signed int i; // [esp+8h] [ebp-8h]
signed int v4; // [esp+Ch] [ebp-4h]
if ( !a2 )
return 0;
v4 = 0;
LABEL_4:
if ( v4 >= 3 )
return 0;
for ( i = 0; ; ++i )
{
if ( i >= 3 )
{
++v4;
goto LABEL_4;
}
if ( byte_4147D0[3 * v4 + i] == a2 )
break;
LABEL_6:
;
}
switch ( a1 )
{
case 0:
if ( v4 )
{
if ( byte_4147D0[3 * (v4 - 1) + i] )
{
result = 0;
}
else
{
byte_4147D0[3 * (v4 - 1) + i] = byte_4147D0[3 * v4 + i];
byte_4147D0[3 * v4 + i] = 0;
result = 1;
}
}
else
{
result = 0;
}
break;
case 1:
if ( i == 2 )
{
result = 0;
}
else if ( byte_4147D1[3 * v4 + i] )
{
result = 0;
}
else
{
byte_4147D1[3 * v4 + i] = byte_4147D0[3 * v4 + i];
byte_4147D0[3 * v4 + i] = 0;
result = 1;
}
break;
case 2:
if ( v4 == 2 )
{
result = 0;
}
else if ( byte_4147D0[3 * (v4 + 1) + i] )
{
result = 0;
}
else
{
byte_4147D0[3 * (v4 + 1) + i] = byte_4147D0[3 * v4 + i];
byte_4147D0[3 * v4 + i] = 0;
result = 1;
}
break;
case 3:
if ( i )
{
if ( byte_4147CF[3 * v4 + i] )
{
result = 0;
}
else
{
byte_4147CF[3 * v4 + i] = byte_4147D0[3 * v4 + i];
byte_4147D0[3 * v4 + i] = 0;
result = 1;
}
}
else
{
result = 0;
}
break;
default:
goto LABEL_6;
}
return result;
}
对传入的参数在0 1 2 3,进行了判断,有w a s d 我们就就可以初步的推断是对表中的元素进行了移动。就可以初步的推断是对表中的元素进行了移动。大佬提示,是九宫格。
3.求解
总结起来,就是
4 1 3
7 2 5 d6
8 6 0
↓
4 1 3
7 2 5 d8
8 0 6
↓
4 1 3
7 2 5 s7
0 8 6
↓
4 1 3
0 2 5 s4
7 8 6
↓
0 1 3
4 2 5 a1
7 8 6
↓
1 0 3
4 2 5 w2
7 8 6
↓
1 2 3
4 0 5 a5
7 8 6
↓
1 2 3
4 5 0 w6
7 8 6
↓
1 2 3
4 5 6
7 8 0
序列号为d6d8s7s4a1w2a5w6,结果如图
完结