0x0 初探
先用PEID查看一下CM的信息
从图中可以发现,程序使用Debug的编译方式编译且有一个TLS回调函数。
因为程序有ASLR,不方便分析,因此我用FFI去掉后再继续分析。
1x0 分析TLS回调函数
OD载入,断在TLS入口处。
一共有四个函数,其中前两个我们需要关注一下。因为这两个函数是反调试的函数。
1x1 分析函数 0041141A
CM先创建一个进程快照
然后就开始枚举进程,取出进程名,如果进程名字符合如下进程名中的其中一个,就直接报错退出。
ollyice.exe
ollydbg.exe
peid.exe
ida.exe
idaq.exe
对应的检测代码如下:
004129D1 8D85 CCFDFFFF lea eax, dword ptr [ebp-0x234]
004129D7 50 push eax
004129D8 8B8D C0FDFFFF mov ecx, dword ptr [ebp-0x240]
004129DE 51 push ecx
004129DF E8 7EE8FFFF call <kernel32!Process32FirstW>
004129E4 8985 B4FDFFFF mov dword ptr [ebp-0x24C], eax
004129EA 83BD B4FDFFFF 0>/cmp dword ptr [ebp-0x24C], 0x0
004129F1 0F84 94010000 |je 00412B8B
004129F7 8BF4 |mov esi, esp
004129F9 8D85 F0FDFFFF |lea eax, dword ptr [ebp-0x210]
004129FF 50 |push eax
00412A00 FF15 A0C14100 |call dword ptr [<&ucrtbased.wcslen>] ; ucrtbase.wcslen
00412A06 83C4 04 |add esp, 0x4
00412A09 3BF4 |cmp esi, esp
00412A0B E8 6CE7FFFF |call <CheckEsp>
00412A10 83C0 01 |add eax, 0x1
00412A13 8BF4 |mov esi, esp
00412A15 50 |push eax
00412A16 8D8D F0FDFFFF |lea ecx, dword ptr [ebp-0x210]
00412A1C 51 |push ecx
00412A1D FF15 9CC14100 |call dword ptr [<&ucrtbased._wcslwr_s>] ; ucrtbase._wcslwr_s
00412A23 83C4 08 |add esp, 0x8
00412A26 3BF4 |cmp esi, esp
00412A28 E8 4FE7FFFF |call <CheckEsp>
00412A2D 8BF4 |mov esi, esp
00412A2F 68 848B4100 |push 00418B84 ; UNICODE "ollyice.exe"
00412A34 8D85 F0FDFFFF |lea eax, dword ptr [ebp-0x210]
00412A3A 50 |push eax
00412A3B FF15 A4C14100 |call dword ptr [<&ucrtbased.wcscmp>] ; ucrtbase.wcscmp
00412A41 83C4 08 |add esp, 0x8
00412A44 3BF4 |cmp esi, esp
00412A46 E8 31E7FFFF |call <CheckEsp>
00412A4B 85C0 |test eax, eax
00412A4D 75 1E |jnz short 00412A6D
00412A4F 68 A08B4100 |push 00418BA0 ; ///////WARNING///////\n
00412A54 E8 9EE9FFFF |call <printf>
00412A59 83C4 04 |add esp, 0x4
00412A5C 8BF4 |mov esi, esp
00412A5E 6A 00 |push 0x0
00412A60 FF15 94C14100 |call dword ptr [<&ucrtbased.exit>] ; ucrtbase.exit
00412A66 3BF4 |cmp esi, esp
00412A68 E8 0FE7FFFF |call <CheckEsp>
00412A6D 8BF4 |mov esi, esp
00412A6F 68 BC8B4100 |push 00418BBC ; UNICODE "ollydbg.exe"
00412A74 8D85 F0FDFFFF |lea eax, dword ptr [ebp-0x210]
00412A7A 50 |push eax
00412A7B FF15 A4C14100 |call dword ptr [<&ucrtbased.wcscmp>] ; ucrtbase.wcscmp
00412A81 83C4 08 |add esp, 0x8
00412A84 3BF4 |cmp esi, esp
00412A86 E8 F1E6FFFF |call <CheckEsp>
00412A8B 85C0 |test eax, eax
00412A8D 75 1E |jnz short 00412AAD
00412A8F 68 D88B4100 |push 00418BD8 ; ///////\nWARNING\n///////\n
00412A94 E8 5EE9FFFF |call <printf>
00412A99 83C4 04 |add esp, 0x4
00412A9C 8BF4 |mov esi, esp
00412A9E 6A 00 |push 0x0
00412AA0 FF15 94C14100 |call dword ptr [<&ucrtbased.exit>] ; ucrtbase.exit
00412AA6 3BF4 |cmp esi, esp
00412AA8 E8 CFE6FFFF |call <CheckEsp>
00412AAD 8BF4 |mov esi, esp
00412AAF 68 F48B4100 |push 00418BF4 ; UNICODE "peid.exe"
00412AB4 8D85 F0FDFFFF |lea eax, dword ptr [ebp-0x210]
00412ABA 50 |push eax
00412ABB FF15 A4C14100 |call dword ptr [<&ucrtbased.wcscmp>] ; ucrtbase.wcscmp
00412AC1 83C4 08 |add esp, 0x8
00412AC4 3BF4 |cmp esi, esp
00412AC6 E8 B1E6FFFF |call <CheckEsp>
00412ACB 85C0 |test eax, eax
00412ACD 75 1E |jnz short 00412AED
00412ACF 68 D88B4100 |push 00418BD8 ; ///////\nWARNING\n///////\n
00412AD4 E8 1EE9FFFF |call <printf>
00412AD9 83C4 04 |add esp, 0x4
00412ADC 8BF4 |mov esi, esp
00412ADE 6A 00 |push 0x0
00412AE0 FF15 94C14100 |call dword ptr [<&ucrtbased.exit>] ; ucrtbase.exit
00412AE6 3BF4 |cmp esi, esp
00412AE8 E8 8FE6FFFF |call <CheckEsp>
00412AED 8BF4 |mov esi, esp
00412AEF 68 108C4100 |push 00418C10 ; UNICODE "ida.exe"
00412AF4 8D85 F0FDFFFF |lea eax, dword ptr [ebp-0x210]
00412AFA 50 |push eax
00412AFB FF15 A4C14100 |call dword ptr [<&ucrtbased.wcscmp>] ; ucrtbase.wcscmp
00412B01 83C4 08 |add esp, 0x8
00412B04 3BF4 |cmp esi, esp
00412B06 E8 71E6FFFF |call <CheckEsp>
00412B0B 85C0 |test eax, eax
00412B0D 75 1E |jnz short 00412B2D
00412B0F 68 D88B4100 |push 00418BD8 ; ///////\nWARNING\n///////\n
00412B14 E8 DEE8FFFF |call <printf>
00412B19 83C4 04 |add esp, 0x4
00412B1C 8BF4 |mov esi, esp
00412B1E 6A 00 |push 0x0
00412B20 FF15 94C14100 |call dword ptr [<&ucrtbased.exit>] ; ucrtbase.exit
00412B26 3BF4 |cmp esi, esp
00412B28 E8 4FE6FFFF |call <CheckEsp>
00412B2D 8BF4 |mov esi, esp
00412B2F 68 208C4100 |push 00418C20 ; UNICODE "idaq.exe"
00412B34 8D85 F0FDFFFF |lea eax, dword ptr [ebp-0x210]
00412B3A 50 |push eax
00412B3B FF15 A4C14100 |call dword ptr [<&ucrtbased.wcscmp>] ; ucrtbase.wcscmp
00412B41 83C4 08 |add esp, 0x8
00412B44 3BF4 |cmp esi, esp
00412B46 E8 31E6FFFF |call <CheckEsp>
00412B4B 85C0 |test eax, eax
00412B4D 75 1E |jnz short 00412B6D
00412B4F 68 D88B4100 |push 00418BD8 ; ///////\nWARNING\n///////\n
00412B54 E8 9EE8FFFF |call <printf>
00412B59 83C4 04 |add esp, 0x4
00412B5C 8BF4 |mov esi, esp
00412B5E 6A 00 |push 0x0
00412B60 FF15 94C14100 |call dword ptr [<&ucrtbased.exit>] ; ucrtbase.exit
00412B66 3BF4 |cmp esi, esp
00412B68 E8 0FE6FFFF |call <CheckEsp>
00412B6D 8D85 CCFDFFFF |lea eax, dword ptr [ebp-0x234]
00412B73 50 |push eax
00412B74 8B8D C0FDFFFF |mov ecx, dword ptr [ebp-0x240]
00412B7A 51 |push ecx
00412B7B E8 A1E6FFFF |call 00411221
00412B80 8985 B4FDFFFF |mov dword ptr [ebp-0x24C], eax
00412B86 ^ E9 5FFEFFFF \jmp 004129EA
1x2 分析函数 00411186
这段函数其实就是IsDebuggerPresent。检测了PEB的BeingDebugged,若BeingDebugged为0则未被调试,否则被调试。
函数代码如下:
1x3 一次性全部拆解
这两个检测函数都位于TLS回调函数内,因此删掉即可。
这样系统就不会去执行TLS回调函数了,而是直接执行入口点处的代码了。
2x0 分析第一个输入
OD来到main函数00412E20处,单步调试一阵发现第一个算法函数 00411316
因为这个函数的代码很长,所以使用IDA来分析。
IDA来到00411316处,使用F5插件查看反编译好后的C代码。
首先将注册码的顺序颠倒(123456789 -> 987654321)
然后将循环次数作为密匙,加密颠倒后的Key
最后和一串密文比较,若全部相等则通过第一个Key的验证。
v8处的数据如下:
计算注册码的办法很简单,写一个逆运算即可。
代码如下:
运行以后得到结果:M.KATSURAGI
3x0 分析第二个输入
第二个输入就有点复杂了,其中还夹杂着反调试(ntdll!NtQueryInformationProcess查询DebugPort)。x86用StrongOD插件,x64用xjun师傅的SharpOD插件都可以绕过(我是Windows7 x64)
首先程序判断输入的Key是否是0x23个字节,如果不是则失败。
构造一个key,使大小为0x23即可通过。我构造的是0123456789ABCDEFGHIJKLMNOPQRSTUVWXY
继续分析,来到第一个计算的函数(一共有四个),我先将这个计算的函数和将要出现的计算的函数分别标记,到最后一起分析。
第一个计算的函数我命名为First。
加密以后,CM做了一些操作以后(包括调试器的检测)来到第二个计算的函数,我命名为Second。
然后执行第三个计算的函数,我命名为Third
然后执行第四个计算的函数,我命名为Fourth
然后将First计算的数据的第八个字节开始用Second计算的七个字节替换,第十五个字节开始用Third计算的七个字节替换,第二十二个字节开始用Fourth的计算的七个字节替换。
最后和内置好的一串数据比较,如果相等就成功。
其中,内置好的正确数据如下:
3x1 四个计算函数之First的分析
First函数比较简单,将Key的每个字节和0x76异或。
3x2 四个计算函数之Second的分析
代码如下:
3x3 四个计算函数之Third的分析
这个和Second进行的运算差不多。代码如下:
3x4 四个计算函数之Fourth的分析
代码如下:
3x5 计算第二个注册码
这个注册码一共经历了四次变换(First Second Third Fourth)
其中,First运算可逆,Second Third Fourth都不是可逆的函数。
观察一下计算后的数据,容易发现注册码被分成了如下的五个部分:
其中有高亮的(第二个到第四个)都是被替换后的,而没有高亮的(第一个和最后一个)都是不被替换的,因此没有高亮的部分可以直接用First运算来还原。
而有高亮的部分我没有找到好的办法去还原,只有枚举了。
总体的计算思路如下:
1.先不考虑没有高亮的部分(第一个和最后一个),枚举第二个,用Second运算来枚举,尝试一个字节一个字节枚举,考虑到注册码都是可显示字符,scanf又有遇到空格截断的特性,因此从0x21开始枚举。若Second返回的的数据是正确的,则开始第二个字节的枚举。
2.枚举第三个,用Third运算来枚举,尝试一个字节一个字节枚举,若Third返回的的数据是正确的,则开始第二个字节的枚举。
3.枚举第四个,用Fourth运算来枚举,尝试一个字节一个字节枚举,若Fourth返回的的数据是正确的,则开始第二个字节的枚举。
4.这步中的数据应该是正确的Key经过First运算后的数据了,因为First运算是xor运算,故将这部分的数据再用First运算一次即可得到Key。
计算代码如下:
#include <stdio.h>
#include <stdlib.h>
void First(unsigned char *Output,unsigned char *Input,int size)//Key的第一次变换
{
for(int i = 0;i < size;i++)
Output[i] = Input[i] ^ 0x76;
}
void Second(unsigned char *Output,unsigned char *Input,int size)//Key的第二次变换
{
if(size > 7)
return;
for(int i = 0;i < size;i++)
{
int tmp;
int tmp1;
Output[i] = Input[i] ^ 0xAD;
tmp = (Output[i] & 0xAA) >> 1;
tmp1 = 2 * Output[i] & 0xAA;
Output[i] = tmp1 | tmp;
}
}
void Third(unsigned char *Output,unsigned char *Input,int size)//Key的第三次变换
{
if(size > 7)
return;
for(int i = 0;i < size;i++)
{
int tmp;
int tmp1;
Output[i] = Input[i] ^ 0xBE;
tmp = (Output[i] & 0xCC) >> 2;
tmp1 = 4 * Output[i] & 0xCC;
Output[i] = tmp1 | tmp;
}
}
void Fourth(unsigned char *Output,unsigned char *Input,int size)//Key的第四次变换
{
if(size > 7)
return;
for(int i = 0;i < size;i++)
{
int tmp;
int tmp1;
Output[i] = Input[i] ^ 0xEF;
tmp = (Output[i] & 0xF0) >> 4;
tmp1 = 16 * Output[i] & 0xF0;
Output[i] = tmp1 | tmp;
}
}
int main(void)
{
int j;
unsigned char ch;
unsigned char Data1[] = //第二个key
{
0x1E,0x15,0x02,0x10,0x0D,0x48,0x48,
0x6F,0xDD,0xDD,0x48,0x64,0x63,0xD7,
0x2E,0x2C,0xFE,0x6A,0x6D,0x2A,0xF2,
0x6F,0x9A,0x4D,0x8B,0x4B,0x9A,0xAA,
0x41,0x42,0x42,0x42,0x13,0x14,0x0B
};
/*
计算第二个Key
这部分的Key一共有35个字节,若每七个字节是一块则一共有五块.
*/
j = 0;//第二块
ch = 0x21;
while(true)
{
unsigned char a;
if(j == 7)
break;
Second(&a,&ch,1);//第二次变换
if(a != Data1[j + 7])
{
ch++;
continue;
}
Data1[j + 7] = ch;
ch = 0x21;
j++;
}
j = 0;//第三块
ch = 0x21;
while(true)
{
unsigned char a;
if(j == 7)
break;
Third(&a,&ch,1);//第三次变换
if(a != Data1[j + 14])
{
ch++;
continue;
}
Data1[j + 14] = ch;
ch = 0x21;
j++;
}
j = 0;//第四块
ch = 0x21;
while(true)
{
unsigned char a;
if(j == 7)
break;
Fourth(&a,&ch,1);//第四次变换
if(a != Data1[j + 21])
{
ch++;
continue;
}
Data1[j + 21] = ch;
ch = 0x21;
j++;
}
First(Data1,Data1,sizeof(Data1));//第一次变换
/* Output:hctf{>>D55_CH0CK3R_B0o0M!-037444eb} */
printf("%0.35s\n",Data1);
system("pause");
return 0;
}
运行以后得到结果:hctf{>>D55_CH0CK3R_B0o0M!-037444eb}
4x0 最后一个输入
这个就很简单了,问你输入Y或者N,若输入Y则成功(不区分大小写),否则失败。
4x1 完整的计算代码
将上述代码写到一起,然后再加上输出最后一个注册码的代码即可。
代码如下:
#include <stdio.h>
#include <stdlib.h>
void First(unsigned char *Output,unsigned char *Input,int size)//Key的第一次变换
{
for(int i = 0;i < size;i++)
Output[i] = Input[i] ^ 0x76;
}
void Second(unsigned char *Output,unsigned char *Input,int size)//Key的第二次变换
{
if(size > 7)
return;
for(int i = 0;i < size;i++)
{
int tmp;
int tmp1;
Output[i] = Input[i] ^ 0xAD;
tmp = (Output[i] & 0xAA) >> 1;
tmp1 = 2 * Output[i] & 0xAA;
Output[i] = tmp1 | tmp;
}
}
void Third(unsigned char *Output,unsigned char *Input,int size)//Key的第三次变换
{
if(size > 7)
return;
for(int i = 0;i < size;i++)
{
int tmp;
int tmp1;
Output[i] = Input[i] ^ 0xBE;
tmp = (Output[i] & 0xCC) >> 2;
tmp1 = 4 * Output[i] & 0xCC;
Output[i] = tmp1 | tmp;
}
}
void Fourth(unsigned char *Output,unsigned char *Input,int size)//Key的第四次变换
{
if(size > 7)
return;
for(int i = 0;i < size;i++)
{
int tmp;
int tmp1;
Output[i] = Input[i] ^ 0xEF;
tmp = (Output[i] & 0xF0) >> 4;
tmp1 = 16 * Output[i] & 0xF0;
Output[i] = tmp1 | tmp;
}
}
int main(void)
{
int j;
unsigned char ch;
unsigned char Data[] = {0xA4,0xA9,0xAA,0xBE,0xBC,0xB9,0xB3,0xA9,0xBE,0xD8,0xBE};//第一个key
unsigned char Data1[] = //第二个key
{
0x1E,0x15,0x02,0x10,0x0D,0x48,0x48,
0x6F,0xDD,0xDD,0x48,0x64,0x63,0xD7,
0x2E,0x2C,0xFE,0x6A,0x6D,0x2A,0xF2,
0x6F,0x9A,0x4D,0x8B,0x4B,0x9A,0xAA,
0x41,0x42,0x42,0x42,0x13,0x14,0x0B
};
/* 计算第一个key */
for(int i = 0;i < sizeof(Data);i++)
Data[i] = (((((i ^ 0x76) - 52) ^ 0x80) + 43) ^ Data[i]);
for(int i = 0;i < sizeof(Data) / 2;i++)
{
Data[i] ^= Data[sizeof(Data) - i - 1];
Data[sizeof(Data) - i - 1] ^= Data[i];
Data[i] ^= Data[sizeof(Data) - i - 1];
}
/* Output:M.KATSURAGI */
printf("%0.11s\n",Data);
/*
计算第二个Key
这部分的Key一共有35个字节,若每七个字节是一块则一共有五块.
*/
j = 0;//第二块
ch = 0x21;
while(true)
{
unsigned char a;
if(j == 7)
break;
Second(&a,&ch,1);//第二次变换
if(a != Data1[j + 7])
{
ch++;
continue;
}
Data1[j + 7] = ch;
ch = 0x21;
j++;
}
j = 0;//第三块
ch = 0x21;
while(true)
{
unsigned char a;
if(j == 7)
break;
Third(&a,&ch,1);//第三次变换
if(a != Data1[j + 14])
{
ch++;
continue;
}
Data1[j + 14] = ch;
ch = 0x21;
j++;
}
j = 0;//第四块
ch = 0x21;
while(true)
{
unsigned char a;
if(j == 7)
break;
Fourth(&a,&ch,1);//第四次变换
if(a != Data1[j + 21])
{
ch++;
continue;
}
Data1[j + 21] = ch;
ch = 0x21;
j++;
}
First(Data1,Data1,sizeof(Data1));//第一次变换
/* Output:hctf{>>D55_CH0CK3R_B0o0M!-037444eb} */
printf("%0.35s\n",Data1);
/*
计算第三个key
Output:y
*/
puts("y");
system("pause");
return 0;
}
运行以后得到完整的Key:
用原版CM注册,得到结果如下:
全文完。