【文章标题】Lesco's first assembler crackme分析与疑问
【难度级别】初入门的新手
【下载地址】见附件
【破解工具】OD,peid,Resource Hacker
【加壳方式】无壳
【保护方式】最基本Anti-debug手段
【个人声明】本人破解特菜,此CrackMe算是刚接触的,这是一个在精华2007下的一个CrackMe,因为这些CrackMe之前都有大牛发过详细的解法过程,所以我一般都不写分析的,但是今天这个比较例外,这个CrackMe比较特别,其中算法的嵌入实现想问问坛子里的朋友~这有点像16位汇编中的修改中断地址 改写中断程序..但是到了WIN32汇编我并不知道怎么实现,这个CrackMe具体的精明之处请看下文,
同理 如果你是大牛请忽略分析过程直接看中间和末尾的红色字体问题~这个CrackMe由于是在精华2007里面的,所以详细的分析过程里面肯定有,这里我就按自己的思路说一遍.
One:
首先载入PEID查看发现是MASM32 / TASM32,原来是用比较熟悉的WIN32ASM写的,那直接载入Resource Hacker
发现控件标识如下:
CONTROL "", 1001, EDIT, ....
CONTROL "", 1002, EDIT, ....
对我们有用的只有1001和1002
Two:
载入后直接右键查找所有模块的调用,这时候看到两个GetDlgItemTextA 刚好对应两个EDIT 一个Name 一个Key,果断F2下断:
0040142F |. 6A 15 push 15 ; /Count = 15 (21.)
00401431 |. 68 F8304000 push 004030F8 ; |Buffer = asm_cm01.004030F8
00401436 |. 68 E9030000 push 3E9 ; |ControlID = 3E9 (1001.)
0040143B |. FF75 08 push dword ptr [ebp+8] ; |hWnd
0040143E |. E8 4F010000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
00401443 |. 83F8 05 cmp eax, 5 ; NameLen>=5
输入用户名和密码:
pediy
123456
后按F9 程序断在0x0040142F处.往后单步F8跟踪不久遍发现一个算法:
0040146E |> /8D15 F8304000 /lea edx, dword ptr [4030F8]
00401474 |. |0FB61C11 |movzx ebx, byte ptr [ecx+edx]
00401478 |. |881D 04394000 |mov byte ptr [403904], bl
0040147E |. |41 |inc ecx
0040147F |. |51 |push ecx
00401480 |. |0FAF0D FC3840>|imul ecx, dword ptr [4038FC] ; *5
00401487 |. |83C1 17 |add ecx, 17 ; +0x17
0040148A |. |83F1 0F |xor ecx, 0F ; xor 0x0F
[COLOR="red"]0040148D |. |33D9 |xor ebx, ecx
0040148F |. |8BC1 |mov eax, ecx[/COLOR]
00401491 |. |8D0D F8324000 |lea ecx, dword ptr [4032F8]
00401497 |. |030D 00394000 |add ecx, dword ptr [403900]
0040149D |. |51 |push ecx
0040149E |. |50 |push eax ; /<%u>
0040149F |. |68 12304000 |push 00403012 ; |Format = "%u"
004014A4 |. |51 |push ecx ; |s
004014A5 |. |E8 D0000000 |call <jmp.&user32.wsprintfA> ; \wsprintfA
004014AA |. |83C4 0C |add esp, 0C
004014AD |. |59 |pop ecx
004014AE |. |51 |push ecx ; /String
004014AF |. |E8 2C010000 |call <jmp.&kernel32.lstrlenA> ; \lstrlenA
004014B4 |. |0105 00394000 |add dword ptr [403900], eax
004014BA |. |59 |pop ecx
004014BB |. |803D 04394000>|cmp byte ptr [403904], 0
004014C2 |.^\75 AA \jnz short 0040146E
这个算法很简单,直接执行到循环结束,首先说明这个算法是作者用来混淆的,但是开始我们并不知道.
这个算法算出来pediy就是:194641366358
在分析完算法之前 我们看看算法本身,红色部分是不是值得斟酌斟酌?在0x0040148D和0x0040148F两行显然是多余...思考大家会喜欢这样做么?
从这里是否可以看出这个算法带有假算法的嫌疑?
虽然对算法有疑问,但是它毕竟能算出一个'序列号'来
重新运行此程序,输入用户名和密码:
pediy
194641366358
同样中断运行到上面那算法之后往后的代码如下:
004014C4 |. 68 F4010000 push 1F4 ; /Count = 1F4 (500.)
004014C9 |. 68 F8344000 push 004034F8 ; |Buffer = asm_cm01.004034F8
004014CE |. 68 EA030000 push 3EA ; |ControlID = 3EA (1002.)
004014D3 |. FF75 08 push dword ptr [ebp+8] ; |hWnd
004014D6 |. E8 B7000000 call <jmp.&user32.GetDlgItemTextA> ; \GetDlgItemTextA
004014DB |. 66:0BC0 or ax, ax
004014DE |. 75 1E jnz short 004014FE
004014E0 |. 6A 00 push 0 ; /LanguageID = 0 (LANG_NEUTRAL)
004014E2 |. 6A 00 push 0 ; |Style = MB_OK|MB_APPLMODAL
004014E4 |. 68 76304000 push 00403076 ; |Title = "Lesco's first assembler crackme"
004014E9 |. 68 D8304000 push 004030D8 ; |Text = "Enter a serial"
004014EE |. 6A 00 push 0 ; |hOwner = NULL
004014F0 |. E8 AF000000 call <jmp.&user32.MessageBoxExA> ; \MessageBoxExA
004014F5 |. B8 01000000 mov eax, 1
004014FA |. C9 leave
004014FB |. C2 1000 retn 10
[COLOR="red"]004014FE |> 68 F8324000 push 004032F8 ; /String2 = "194641366358"
00401503 |. 68 F8344000 push 004034F8 ; |String1 = "194641366358"[/COLOR]
00401508 |. E8 CD000000 call <jmp.&kernel32.lstrcmpA> ; \lstrcmpA
一切似乎非常顺利,具备一切正确注册码的条件~
然后程序如愿的运行到U do it 的MessageBox函数中~
004014FE |> \68 F8324000 push 004032F8 ; /String2 = "194641366358"
00401503 |. 68 F8344000 push 004034F8 ; |String1 = "194641366358"
00401508 |. E8 CD000000 call <jmp.&kernel32.lstrcmpA> ; \lstrcmpA
0040150D |. 0BC0 or eax, eax
0040150F |. 75 15 jnz short 00401526
00401511 |. 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401513 |. 68 76304000 push 00403076 ; |Title = "Lesco's first assembler crackme"
00401518 |. 68 96304000 push 00403096 ; |Text = "Congrats, u did it!!!"
0040151D |. 6A 00 push 0 ; |hOwner = NULL
0040151F |. E8 7A000000 call <jmp.&user32.MessageBoxA> ; \MessageBoxA
00401524 |. EB 13 jmp short 00401539
00401526 |> 6A 00 push 0 ; /Style = MB_OK|MB_APPLMODAL
00401528 |. 68 76304000 push 00403076 ; |Title = "Lesco's first assembler crackme"
0040152D |. 68 AC304000 push 004030AC ; |Text = "This serial sucks!!!"
00401532 |. 6A 00 push 0 ; |hOwner = NULL
[COLOR="red"]00401534 |. E8 65000000 call <jmp.&user32.MessageBoxA> ; \MessageBoxA[/COLOR]
00401539 |> 6A 00 push 0 ; /ExitCode = 0
0040153B |. E8 76000000 call <jmp.&kernel32.ExitProcess> ; \ExitProcess
F8一步一步跟踪..到上面红色一行,马上就CALL MessageBox的时候 程序跳了,对 它跳了~
跳到这个地方:
0040103D /. 55 push ebp
0040103E |. 8BEC mov ebp, esp
[COLOR="red"]00401040 |. 8D1D F8304000 lea ebx, dword ptr [4030F8] ; Name[/COLOR]
00401046 |. 33C0 xor eax, eax
00401048 |. 66:A1 F8304000 mov ax, word ptr [4030F8]
0040104E |. 66:35 2FE3 xor ax, 0E32F
00401052 |. 66:0FAFC0 imul ax, ax ; ^2
00401056 |. 66:35 6CAB xor ax, 0AB6C ; 0xAB6C
0040105A |. 33C9 xor ecx, ecx
0040105C |. 890D 00394000 mov dword ptr [403900], ecx
00401062 |> 50 /push eax
00401063 |. 8D1D F8304000 |lea ebx, dword ptr [4030F8]
00401069 |. 0FB61419 |movzx edx, byte ptr [ecx+ebx]
0040106D |. 51 |push ecx
0040106E |. 66:0FB6C8 |movzx cx, al ; AL==2D
00401072 |. 66:0FAFD1 |imul dx, cx
00401076 |. 66:0FB6CC |movzx cx, ah ; AH==3C
0040107A |. 66:0FAFD1 |imul dx, cx
0040107E |. 59 |pop ecx
0040107F |. 66:81F2 EB45 |xor dx, 45EB
00401084 |. 51 |push ecx
00401085 |. C1EA 02 |shr edx, 2 ; /0x04
00401088 |. 66:03D2 |add dx, dx ; ....
0040108B |. 41 |inc ecx
0040108C |. 52 |push edx ; edx~~~输出
0040108D |. 50 |push eax
0040108E |. 53 |push ebx
0040108F |. 8BC1 |mov eax, ecx
00401091 |. BB 02000000 |mov ebx, 2
00401096 |. 99 |cdq
00401097 |. F7FB |idiv ebx ; /0x02
00401099 |. 5B |pop ebx
0040109A |. 58 |pop eax
0040109B |. 0AD2 |or dl, dl
0040109D |. 75 08 |jnz short 004010A7
0040109F |. 8D35 0C304000 |lea esi, dword ptr [40300C] ; %X
004010A5 |. EB 06 |jmp short 004010AD
004010A7 |> 8D35 0F304000 |lea esi, dword ptr [40300F] ; %x
004010AD |> 5A |pop edx
004010AE |. 8D3D F8324000 |lea edi, dword ptr [4032F8]
004010B4 |. 033D 00394000 |add edi, dword ptr [403900]
004010BA |. 52 |push edx
004010BB |. 56 |push esi ; |Format
004010BC |. 57 |push edi ; |s
004010BD |. E8 B8040000 |call <jmp.&user32.wsprintfA> ; \wsprintfA
004010C2 |. 83C4 0C |add esp, 0C
004010C5 |. 57 |push edi ; /String
004010C6 |. E8 15050000 |call <jmp.&kernel32.lstrlenA> ; \lstrlenA
004010CB |. 0105 00394000 |add dword ptr [403900], eax
004010D1 |. 59 |pop ecx
004010D2 |. 41 |inc ecx
004010D3 |. 58 |pop eax
004010D4 |. 3B0D FC384000 |cmp ecx, dword ptr [4038FC]
004010DA |.^ 7C 86 \jl short 00401062
跳到红色代码行~换言之,就是在一个毫无JMP征兆的地方出现了跳转
这就是我想问的问题之一:
疑惑一:为什么会在一个毫无征兆的地方出现JMP?是OD的错误,还是作者编码的实现?
接着分析上面的代码 显然这是一个算法~我们把它用C还原:
BYTE szKeyName[100];
int szNameLen,i=0,j=0;
BYTE szFirst=0,szSecond=0;
WORD szSum=0;
BYTE szEnd[100]={0};
BYTE szEE[10]={0};
DWORD szSS=0;
......省略一些代码~
szNameLen=GetDlgItemText(hDlg,IDC_Name,szKeyName,sizeof(BYTE)*100);
if(szNameLen<0x05)
{
SetDlgItemText(hDlg,IDC_Serial,TEXT("NameLen>=0x05h"));
break;
}
__asm mov al, szKeyName[0]
__asm mov ah, szKeyName[1]
__asm xor ax,0x0E32F
__asm imul ax,ax
__asm xor ax,0x0AB6C
__asm mov szFirst,al
__asm mov szSecond,ah
SetDlgItemText(hDlg,IDC_Serial,TEXT("[The Key Is]"));
for (i=0;i<szNameLen;i++)
{
szSum=szKeyName[i];
szSum *=szFirst;
szSum=szSum&0xFFFF;
szSum *=szSecond;
szSum=szSum&0xFFFF;
szSum =szSum^0x45EB;
szSum /=0x04;
szSum=szSum&0xFFFF;
szSum *=0x02;
szSS=szSum;
if (i%2)
{
wsprintf(szEE,"%X",szSS);
GetDlgItemText(hDlg,IDC_Serial,szEnd,sizeof(BYTE)*100);
lstrcat(szEnd,szEE);
SetDlgItemText(hDlg,IDC_Serial,szEnd);
}
else
{
wsprintf(szEE,"%x",szSum);
GetDlgItemText(hDlg,IDC_Serial,szEnd,sizeof(BYTE)*100);
lstrcat(szEnd,szEE);
SetDlgItemText(hDlg,IDC_Serial,szEnd);
}
}
/*各个变量顾名思义就知道它意思了*/
可见这里有可以算出一个序列字符:6c54366A2dacB425ce2
往后看就知道这才是正在的算法所在了~
004010F6 |. 68 F8344000 push 004034F8 ; /String = "194641366358" ;我们输入的注册码~
004010FB |. E8 E0040000 call <jmp.&kernel32.lstrlenA> ; \lstrlenA ;
00401100 |. 59 pop ecx
00401101 |. 3BC8 cmp ecx, eax
00401103 74 25 je short 0040112A
00401105 |. 33C9 xor ecx, ecx
00401107 |. 33D2 xor edx, edx
00401109 |> 8D05 F8324000 /lea eax, dword ptr [4032F8];正确的注册码
0040110F |. 8D1D F8344000 |lea ebx, dword ptr [4034F8]
00401115 |. 8A0401 |mov al, byte ptr [ecx+eax]
00401118 |. 8A1C19 |mov bl, byte ptr [ecx+ebx]
0040111B |. 38D8 |cmp al, bl
0040111D |. 75 0B |jnz short 0040112A
0040111F |. 41 |inc ecx
00401120 |. 3B0D 00394000 |cmp ecx, dword ptr [403900]
00401126 |.^ 7C E1 \jl short 00401109 ;以上一堆是相等验证~
好了到这里这个CrackMe基本分析完了,这个CrackMe在精华2007中随便找的,估计里面也有相关的分析 可能那比较正确 ,我刚接触CrackMe和反汇编言语之中会有诸多谬误,望多多谅解.
这里分析下只是对这个CrackMe做个解释,然后我想问的第二个问题是:
在内存中查看会发现有MessageBoxA的ASC 这可能和伪造正确提示MessageBox的跳转有关
疑惑二:我非常希望知道的是 作者是如何通过编码实现的?这感觉好像是16位ASM的修改中断地址 修改中断程序
或者说,OD里面的函数提示 是可以人为在编码里修改的??
附件加上了我写的注册机,注册机算法部分就是上面发的C代码
[2011.11.08]
/*经过这段时间的对API的认识,我发现了实现这种情况的方法之一*/
这涉及函数约定调用问题`下面我们看_stdcall和_declspec两种约定下的同一个函数的调用的DASM:
首先是_STDCALL
0040105A |. FF35 30304000 push dword ptr [403030]
00401060 |. 8F45 E4 pop dword ptr [ebp-1C]
00401063 |. C745 F0 06000>mov dword ptr [ebp-10], 6
0040106A |. C745 F4 00000>mov dword ptr [ebp-C], 0
00401071 |. C745 F8 00304>mov dword ptr [ebp-8], 00403000 ;
00401078 |. 68 007F0000 push 7F00 ; /RsrcName = IDI_APPLICATION
0040107D |. 6A 00 push 0 ; |hInst = NULL
0040107F |. E8 E0000000 call <jmp.&USER32.LoadIconA> ; \LoadIconA
......省略一些代码
00401146 $- FF25 30204000 jmp dword ptr [<&USER32.CreateWindow>; USER32.CreateWindowExA
0040114C $- FF25 2C204000 jmp dword ptr [<&USER32.DefWindowPro>; USER32.DefWindowProcA
00401152 $- FF25 1C204000 jmp dword ptr [<&USER32.DispatchMess>; USER32.DispatchMessageA
00401158 $- FF25 18204000 jmp dword ptr [<&USER32.GetMessageA>>; USER32.GetMessageA
0040115E $- FF25 28204000 jmp dword ptr [<&USER32.LoadCursorA>>; USER32.LoadCursorA
[COLOR="Red"]00401164 $- FF25 10204000 jmp dword ptr [<&USER32.LoadIconA>] ; USER32.LoadIconA[/COLOR]
[COLOR="red"];到0040107F跟进后先跳到这里 看地址显然还在程序领空[/COLOR]
0040116A $- FF25 14204000 jmp dword ptr [<&USER32.PostQuitMess>; USER32.PostQuitMessage
00401170 $- FF25 34204000 jmp dword ptr [<&USER32.RegisterClas>; USER32.RegisterClassExA
00401176 $- FF25 38204000 jmp dword ptr [<&USER32.ShowWindow>] ; USER32.ShowWindow
0040117C $- FF25 20204000 jmp dword ptr [<&USER32.TranslateMes>; USER32.TranslateMessage
00401182 $- FF25 24204000 jmp dword ptr [<&USER32.UpdateWindow>; USER32.UpdateWindow
00401188 .- FF25 08204000 jmp dword ptr [<&KERNEL32.ExitProces>; kernel32.ExitProcess
0040118E $- FF25 04204000 jmp dword ptr [<&KERNEL32.GetCommand>; kernel32.GetCommandLineA
00401194 $- FF25 00204000 jmp dword ptr [<&KERNEL32.GetModuleH>; kernel32.GetModuleHandleA
再看看_DECLSPEC
0042DA58 |. 6A 7C push 7C ; /RsrcName = 124.
0042DA5A |. A1 D0444900 mov eax, dword ptr [hInst] ; |
0042DA5F |. 50 push eax ; |hInst => NULL
0042DA60 |. FF15 FC744900 call dword ptr [<&USER32.LoadIconA>] ; \LoadIconA
....省略一些代码
[COLOR="red"]77D2E8F6 > 8BFF mov edi, edi[/COLOR]
[COLOR="red"];到0042DA60后跟进到这里 看地址显然已经不是程序领空了 也就是说直接进入LoadIconA函数里面了~[/COLOR]
77D2E8F8 55 push ebp
77D2E8F9 8BEC mov ebp, esp
77D2E8FB 66:F745 0E FFFF test word ptr [ebp+E], 0FFFF
77D2E901 0F85 EC590100 jnz 77D442F3
77D2E907 FF75 0C push dword ptr [ebp+C]
77D2E90A FF75 08 push dword ptr [ebp+8]
77D2E90D E8 AAFFFFFF call LoadIconW
77D2E912 5D pop ebp
77D2E913 C2 0800 retn 8
77D2E916 6A 09 push 9
77D2E918 8D7B 04 lea edi, dword ptr [ebx+4]
77D2E91B 59 pop ecx
对比 两种约定方式 如果在_stdcall方式下,对JMP这句进行修改 改为跳到自己写的算法CALL 然后运行完算法在JMP的话.这不久OK了.这估计是实现这个CM这种情况的方法之一
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!