先说明这是我在CrackMe2007文档中碰到的,在文档不定技术\其妙的跳转下,觉得这个crackMe特别有趣,不过那篇文章没分析完全,只是最近都在弄这文档下的,都是N年前的东西了,会不会过时了啊
首先这个crackMe是asm写的,所以代码比较好看,OD随便一体调,就找到了关键代码处
00401229 . 6A 28 push 28 ; /Count = 28 (40.)
0040122B . 68 68344000 push 00403468 ; |Buffer = SV_Keyge.00403468
00401230 . 68 EB030000 push 3EB ; |ControlID = 3EB (1003.)
00401235 . FF75 08 push dword ptr [ebp+8] ; |hWnd
00401238 . E8 0B010000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
0040123D . 6A 28 push 28 ; /Count = 28 (40.)
0040123F . 68 68304000 push 00403068 ; |Buffer = SV_Keyge.00403068
00401244 . 68 EC030000 push 3EC ; |ControlID = 3EC (1004.)
00401249 . FF75 08 push dword ptr [ebp+8] ; |hWnd
0040124C . E8 F7000000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401251 . 68 68304000 push 00403068 ; /String = ""
00401256 . E8 53010000 call <jmp.&kernel32.lstrlenA> ; \lstrlenA
0040125B . 83F8 10 cmp eax, 10
0040125E . 75 64 jnz short 004012C4
00401260 . E8 D3FDFFFF call 00401038 //关键调用
可以看出程序要求注册码为16个字节,跟进最后那个调用
00401038 /$ 8D1D 68344000 lea ebx, dword ptr [403468] ; 用户名
0040103E |. 8D35 68304000 lea esi, dword ptr [403068] ; 注册码
00401044 |. 8D3D 68324000 lea edi, dword ptr [403268] ; 关键缓存
0040104A |. EB 2A jmp short 00401076
0040104C |> 6A 03 /push 3 ; /n = 3
0040104E |. 56 |push esi ; |String2
0040104F |. 68 68364000 |push 00403668 ; |String1 = SV_Keyge.00403668
00401054 |. E8 4F030000 |call <jmp.&kernel32.lstrcpynA> ; \lstrcpynA
00401059 |. 68 68364000 |push 00403668
0040105E |. E8 51030000 |call 004013B4 ;关键调用
00401063 |. 3203 |xor al, byte ptr [ebx]
00401065 |. 8807 |mov byte ptr [edi], al
00401067 |. 46 |inc esi
00401068 |. 46 |inc esi
00401069 |. 43 |inc ebx
0040106A |. 803B 00 |cmp byte ptr [ebx], 0
0040106D |. 75 06 |jnz short 00401075
0040106F |. 8D1D 68344000 |lea ebx, dword ptr [403468]
00401075 |> 47 |inc edi
00401076 |> 803E 00 cmp byte ptr [esi], 0
00401079 |.^ 75 D1 \jnz short 0040104C
0040107B \. C3 retn
看看上面这段代码的循环,可以发现它的作用是每次循环取注册码2个字节然后call 4013b4一个不知名的调用,先不管它,从后续的代码来看这个调用将返回值以字节和用户名进行字节异或,然后存入那个未知缓存中,一直循环8次,因此可以推测注册码以每2个字节为一组进行一些运算再和用户名异或得出8个字节在目标缓存中,这里同时也知道了用户名应该为8个字节。解析完毕,跟进call 4013b4
004013B4 /$ 55 push ebp
004013B5 |. 8BEC mov ebp, esp
004013B7 |. 53 push ebx
004013B8 |. 56 push esi
004013B9 |. 57 push edi
004013BA |. 8B7D 08 mov edi, dword ptr [ebp+8]
004013BD |. 8B75 08 mov esi, dword ptr [ebp+8]
004013C0 |> 8A07 /mov al, byte ptr [edi]
004013C2 |. 47 |inc edi
004013C3 |. 0AC0 |or al, al
004013C5 |.^ 75 F9 \jnz short 004013C0
004013C7 |. 2BF7 sub esi, edi ; 大概自己计算下,可以得出这条指令执行后esi==-3
004013C9 |. 33DB xor ebx, ebx
004013CB |. 03FE add edi, esi ; 这里将循环增长后的edi还原
004013CD |. 33D2 xor edx, edx
004013CF |. F7D6 not esi ; 取反后esi==2
004013D1 |. EB 23 jmp short 004013F6
004013D3 |> 8A07 /mov al, byte ptr [edi]
004013D5 |. 3C 41 |cmp al, 41
004013D7 |. 72 0C |jb short 004013E5
004013D9 |. 2C 57 |sub al, 57 ; al>='A'
004013DB |. 80D2 00 |adc dl, 0
004013DE |. C0E2 05 |shl dl, 5
004013E1 |. 02C2 |add al, dl
004013E3 |. EB 02 |jmp short 004013E7
004013E5 |> 2C 30 |sub al, 30 ; al-='0'
004013E7 |> 8D4E FF |lea ecx, dword ptr [esi-1] ; 这里第一次ecx=1,第二次ecx=0,可以直接计算出下面的移位数
004013EA |. 83E0 0F |and eax, 0F ; 保留低4位
004013ED |. C1E1 02 |shl ecx, 2
004013F0 |. D3E0 |shl eax, cl
004013F2 |. 03D8 |add ebx, eax ; 最后结果都相加保存于ebx
004013F4 |. 47 |inc edi
004013F5 |. 4E |dec esi
004013F6 |> 0BF6 or esi, esi
004013F8 |.^ 75 D9 \jnz short 004013D3
004013FA |. 8BC3 mov eax, ebx ; 计算结果给eax,返回
004013FC |. 5F pop edi
004013FD |. 5E pop esi
004013FE |. 5B pop ebx
004013FF |. C9 leave
00401400 \. C2 0400 retn 4
到这里这个关键调用分析完了,继续向下分析,然后好戏上场
00401265 . 8D3D 68324000 lea edi, dword ptr [403268]
0040126B . FFD7 call edi
0040126D . 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
0040126F . 68 3B304000 push 0040303B ; |Title = "Then ?"
00401274 . 68 1C304000 push 0040301C ; |Text = "Try again"
00401279 . 6A 00 push 0 ; |hOwner = NULL
0040127B . E8 E6000000 call <jmp.&user32.MessageBoxA> ; \MessageBoxA
发现失败弹窗在这里,并且如果还记得403268这个地址的话就会知道,这是程序刚刚根据注册码和用户名计算出来数据啊,怎么被当成代码执行了!这里肯定产生异常,直接F9的话肯定跑飞没救了,先不运行它,回溯上去,发现一段异常安装代码
004011F1 . 64:FF35 00000>push dword ptr fs:[0]
004011F8 . 8F45 EC pop dword ptr [ebp-14]
004011FB . C745 F0 00104>mov dword ptr [ebp-10], 00401000
00401202 . C745 F4 82124>mov dword ptr [ebp-C], 00401282
00401209 . 8D45 EC lea eax, dword ptr [ebp-14]
0040120C . 64:A3 0000000>mov dword ptr fs:[0], eax
00401212 . 8965 F8 mov dword ptr [ebp-8], esp
00401215 . 896D FC mov dword ptr [ebp-4], ebp
这里的安装方式不太习惯,仔细看看可以知道,异常堆栈中保存了异常处理地址外加一个别的地址,然后esp,ebp都保存在内,于是我们找到异常处理例程,断下,让程序飞
00401000 /. 55 push ebp
00401001 |. 8BEC mov ebp, esp
00401003 |. 52 push edx
00401004 |. 8B55 08 mov edx, dword ptr [ebp+8] ; lpExceptionRecode
00401007 |. FF32 push dword ptr [edx] ; 异常码
00401009 |. 8F05 90364000 pop dword ptr [403690]
0040100F |. 8B55 0C mov edx, dword ptr [ebp+C] ; lpSeh
00401012 |. 8B45 10 mov eax, dword ptr [ebp+10] ; lpContext
00401015 |. FF72 08 push dword ptr [edx+8] ; safe_place
00401018 |. 8F80 B8000000 pop dword ptr [eax+B8] ; eip
0040101E |. FF72 0C push dword ptr [edx+C] ; 还原esp
00401021 |. 8F80 C4000000 pop dword ptr [eax+C4] ; esp
00401027 |. FF72 10 push dword ptr [edx+10] ; ebp
0040102A |. 8F80 B4000000 pop dword ptr [eax+B4] ; ebp
00401030 |. B8 00000000 mov eax, 0
00401035 |. 5A pop edx
00401036 |. C9 leave
00401037 \. C3 retn
从这里看出,异常处理保存了异常码然后恢复堆栈指针,然后跑到safe_place即401282处继续执行
继续断下
00401282 . 8D3D 68324000 lea edi, dword ptr [403268] ;这里是前面计算出的8字节结果的地址
00401288 . 83C7 04 add edi, 4 ;转到后4个字节
0040128B . 8B07 mov eax, dword ptr [edi]
0040128D . 3B05 90364000 cmp eax, dword ptr [403690] ;这个地址是前面存储异常代码的内存,居然用它来和后4个字节比较,有点难以理解它的企图
00401293 75 1C jnz short 004012B1
00401295 . E8 60000000 call 004012FA //关键调用,跟进
0040129A . 68 E9030000 push 3E9 ; /ControlID = 3E9 (1001.)
0040129F . FF75 08 push dword ptr [ebp+8] ; |hWnd
004012A2 . E8 9B000000 call <jmp.&user32.GetDlgItem> ; \GetDlgItem
004012A7 . 6A 00 push 0 ; /Enable = FALSE
004012A9 . 50 push eax ; |hWnd
004012AA . E8 8D000000 call <jmp.&user32.EnableWindow> ; \EnableWindow
004012AF . EB 13 jmp short 004012C4
004012B1 > 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
004012B3 . 68 3B304000 push 0040303B ; |Title = "Then ?"
004012B8 . 68 1C304000 push 0040301C ; |Text = "Try again"
004012BD . 6A 00 push 0 ; |hOwner = NULL
004012BF . E8 A2000000 call <jmp.&user32.MessageBoxA> ; \MessageBoxA
004012C4 > FF75 EC push dword ptr [ebp-14]
004012C7 . 64:8F05 00000>pop dword ptr fs:[0]
其实这里我们又发现了一个失败弹窗,顿时凌乱了,上下翻找,所有代码里居然没有正确弹窗,OD字符串查找也没发现正确字符创(其实IDA中可以找到注册成功字符串...)
先不管了,继续跟进一个调用call 4012fa
004012FA /$ 33D2 xor edx, edx
004012FC |. 33C9 xor ecx, ecx
004012FE |> 02D0 /add dl, al
00401300 |. 41 |inc ecx
00401301 |. C1C8 08 |ror eax, 8
00401304 |. 83F9 04 |cmp ecx, 4
00401307 |.^ 75 F5 \jnz short 004012FE
00401309 |. 80FA 54 cmp dl, 54 ; 上面这些代码的目的就是将结果后四个字节逐字节相加并判断是否等于0x54
0040130C |. 75 1B jnz short 00401329
0040130E |. 8D3D 46304000 lea edi, dword ptr [403046]
00401314 |. 33DB xor ebx, ebx
00401316 |> 3107 /xor dword ptr [edi], eax
00401318 |. 83C7 04 |add edi, 4
0040131B |. 43 |inc ebx
0040131C |. 83FB 07 |cmp ebx, 7
0040131F |.^ 75 F5 \jnz short 00401316 ; 上面总计对403046开始处28个字节进行了些令人不解的运算(和异常代码异或)
00401321 |. 8D3D 46304000 lea edi, dword ptr [403046]
00401327 |. FFD7 call edi ; 不管怎么说,它又开始对数据进行调用....
00401329 \> C3 retn
然后.....就没有然后了,这个调用之后的下面就是些一般处理然后退出了
总结下已知信息,除了我们知道的一些计算过程就是:
(暂且称那8个字节计算结果缓存为buf)
执行403268产生的异常代码号应该和buf+4后4个字节相等(推测)
异常代码号逐字节相加应该等于0x54
对于前4字节没有更多的信息了,于是我们想到去windows头文件找SEH的异常代码号吧,果然发现了个相加0x54的异常
#define STATUS_INTEGER_DIVIDE_BY_ZERO ((DWORD )0xC0000094L)
#define EXCEPTION_INT_DIVIDE_BY_ZERO STATUS_INTEGER_DIVIDE_BY_ZERO
无疑是除0异常了,于是我们尝试着用这个异常去执行最后那个令人不解的数据异或,然后查看结果数据,既然是要执行它,我们就用汇编查看
00403046 6A 00 push 0
00403048 68 3B304000 push 0040303B ; ASCII "Then ?"
0040304D 68 26304000 push 00403026 ; ASCII "Yeah that's right !!"
00403052 6A 00 push 0
00403054 E8 0DE3FFFF call <jmp.&user32.MessageBoxA>
00403059 C3 retn
找到了!原来这28个字节的正确变换结果就是提示注册成功,难怪前面都找不到了
这个异常号是由buf8个字节的执行产生的后果,首先后4个字节由于上面发现已经被确定为0xc00000094,就是说还剩4个字节来让我们产生除0异常,于是自己想各种4字节内产生除0异常的汇编代码吧,我是直接xor eax,eax,div eax,一汇编刚好4个字节,OK了
就是说计算用户名注册码的结果应该为如下序列:
33 c0 f7 f0 94 00 00 c0
当然也可以识别的序列,只要前4字节产生除0异常
总的来说这个crackMe就是要求你的用户名密码经过计算后产生的计算结果前4字节产生除0异常,而后四个字节就是相关的异常号,需要你的用户名和密码计算来产生异常并且验证异常号,否则就陷入无限异常和程序终止,很有趣的CrackMe
分析完毕,附上CrackMe和注册机
crack.rar
注册机由随机生成的注册码,然后反算用户名(反过来的话就不是那么好算了),附上代码
#include <iostream>
#include <ctime>
int main()
{
char user[20] = { 0 };
char code[20]= { 0 };
char exc[] = { 0x33,0xc0,0xf7,0xf0, 0x94,0x00,0x00,0xc0 };
char k;
srand(time(0));
for (int i=0; i < 8; i++)
{
unsigned char dl;
//循环直到找到的是有效ascii输入字符而非无法输入或不可见的字符
while(1)
{
dl=0;
k=0;
code[2*i]=rand()%2?rand()%26+65:rand()%10+48;
code[2*i+1]=rand()%2?rand()%26+65:rand()%10+48;
char c=code[2*i];
if (c>='A')
{
int x=(c>='W'?0:1);
dl=(dl+x)*32;
c=c-'W'+dl;
}
else c-='0';
c&=0xf;
c<<=4;
k+=c;
c=code[2*i+1];
if (c>='A')
{
int x=(c>='W'?0:1);
dl=(dl+x)*32;
c=c-'W'+dl;
}
else c-='0';
c&=0xf;
k+=c;
user[i] = k^exc[i];
if (user[i]>0&&isalnum(user[i]) )
break;
}
}
printf("user: %s\ncode: %s",user,code);
getchar();
return 0;
}
一个有效的序列号: vknxY4Vk 45AB9988SD3456AB
[培训]内核驱动高级班,冲击BAT一流互联网大厂工作,每周日13:00-18:00直播授课