happytown第17个crackme算法分析
【文章标题】: happytown第17个crackme算法分析【文章作者】: dewar
【作者邮箱】: [email protected]
【下载地址】: 附件
【加壳方式】: 无
【保护方式】: 无
【编写语言】: VC
【使用工具】: OD
【操作平台】: WINXP
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
本人菜鸟一只,这是我的笔记,内容很菜,高手略过.
1.PEID查无壳,VC编写.
2.OD截入,停在OEP处,按Ctrl+N,在名称一栏中找到USER32.GetDlgItemTextA,选中后右击====>在每个参考上设置断点.打开断点窗口可看到已设置好两个断点.(一个用户名的,一个注册码的,正好^_^)
3.F9运行,输入注册信息
用户名:dewarj
注册码:abcdefghijklmn
为什么这么输往下看就知道了.
4.点Check按钮,程序立即中断在如下的地方:
......
004011B6|.68 C9000000 PUSH 0C9 ; /Count = C9 (201.)
004011BB|.52 PUSH EDX ; |Buffer
004011BC|.68 E8030000 PUSH 3E8 ; |ControlID = 3E8 (1000.)
004011C1|.50 PUSH EAX ; |hWnd
004011C2|.FF15 AC504000 CALL DWORD PTR DS:[<&USER32.GetDlg>; \<====中断在这里.获取用户名及长度,用户名放入EDX所指的缓存中,长度在EAX中
004011C8|.8BF0 MOV ESI,EAX ;长度=>ESI
004011CA|.83FE 06 CMP ESI,6
004011CD|.0F8C 3B020000 JL CrackMe_.0040140E ;长度不能小于6
004011D3|.83FE 0F CMP ESI,0F
004011D6|.0F8F 32020000 JG CrackMe_.0040140E ;长度不能大于15
004011DC|.8D4C24 4C LEA ECX,DWORD PTR SS: ;取用户名到ECX
004011E0|.56 PUSH ESI
004011E1|.51 PUSH ECX
004011E2|.E8 39020000 CALL CrackMe_.00401420 ;用户名小写转化为大写
004011E7|.83C4 08 ADD ESP,8
004011EA|.85C0 TEST EAX,EAX ;用户名中有无字母外的字符
004011EC|.0F84 1C020000 JE CrackMe_.0040140E ;有就跳向失败
004011F2|.33ED XOR EBP,EBP
004011F4|.33C9 XOR ECX,ECX
004011F6|.3BF5 CMP ESI,EBP
004011F8|.7E 2F JLE SHORT CrackMe_.00401229
004011FA|>33FF /XOR EDI,EDI ;EDI清零
004011FC|.3BCD |CMP ECX,EBP ;循环变量ECX与EBP比较
004011FE|.7E 24 |JLE SHORT CrackMe_.00401224 ;小于等于就跳
00401200|>8A543C 4C |/MOV DL,BYTE PTR SS:;取用户名的第EDI个字符
00401204|.8A440C 4C ||MOV AL,BYTE PTR SS:;取用户名的第ECX个字符
00401208|.3AD0 ||CMP DL,AL ;两位进行比较
0040120A|.75 13 ||JNZ SHORT CrackMe_.0040121F ;不等就跳
0040120C|.3BCE ||CMP ECX,ESI ;相等就看是不是串尾
0040120E|.8BC1 ||MOV EAX,ECX ;循环变量EAX=ECX
00401210|.7D 0D ||JGE SHORT CrackMe_.0040121F ;是就跳
00401212|>8A5404 4D ||/MOV DL,BYTE PTR SS: ;\不是就
00401216|.885404 4C |||MOV BYTE PTR SS:,DL ;|将用户名第EAX(ECX)位的后一字符前移一位
0040121A|.40 |||INC EAX ;|循环变量EAX+1(下一位)
0040121B|.3BC6 |||CMP EAX,ESI ;|是否到串尾
0040121D|.^ 7C F3 ||\JL SHORT CrackMe_.00401212 ;/没有就循环
0040121F|>47 ||INC EDI ;循环变量EDI+1(下一位)
00401220|.3BF9 ||CMP EDI,ECX ;EDI与ECX比较
00401222|.^ 7C DC |\JL SHORT CrackMe_.00401200 ;小于就循环
00401224|>41 |INC ECX ;循环变量ECX+1(下一位)
00401225|.3BCE |CMP ECX,ESI ;是否到串尾
00401227|.^ 7C D1 \JL SHORT CrackMe_.004011FA ;没有就循环
00401229|>8D4424 4C LEA EAX,DWORD PTR SS: ;以上是去重,但不完全:ECX位ECX+1位与前面有重复,则ECX+1位保留.
0040122D|.50 PUSH EAX ; /String
0040122E|.FF15 04504000 CALL DWORD PTR DS:[<&KERNEL32.lstr>; \取处理后的用户名长度
00401234|.8BF0 MOV ESI,EAX
00401236|.83FE 06 CMP ESI,6 ;不能小于6位
00401239|.0F8C CF010000 JL CrackMe_.0040140E ;小于跳向失败
5.到这里我们知道,我们输入的用户名只能是6~15位,且只能是英文字母.程序得到用户名后要对其进行处理,处理完后的用户名长度不能小于6位.处理过程是这样的:
(1)取用户名的第i位字符(i的范围从1到用户名长度);
(2)用户名第i位前的各位依次与第i位进行比较,如果相同就删除第i位并将其后的各字符前移一位,然后继续比较(这时用户名是变化的);
(3)i加1,如果小于用户名长度(不是串尾)就回到(1)循环
去重不完全的原因是在第(2)中,当第i+1位前移到第i位后它就躲过了比较.可见当相邻N(N≥2)位相同时,若其前相同字符数≤(N-1),就会保留下一个来.如EAEEBEEECEEED经过处理的变化经过是:
EAEEBEEECEEED==>EAEBEEECEEED==>EAEBECEEED==>EAEBECD
最后的'EEE'因前面会留下3个'E'所以全部去掉了.
这样,我输的dewarj就变成了DEWARJ
0040123F|.8D4C24 4C LEA ECX,DWORD PTR SS:
00401243|.8D5424 20 LEA EDX,DWORD PTR SS:
00401247|.51 PUSH ECX ; /String2
00401248|.52 PUSH EDX ; |String1
00401249|.FF15 00504000 CALL DWORD PTR DS:[<&KERNEL32.lstr>; \lstrcpyA
0040124F|.B3 41 MOV BL,41 ;A
00401251|.896C24 10 MOV DWORD PTR SS:,EBP
00401255|.8D7434 20 LEA ESI,DWORD PTR SS:
00401259|>80FB 4A /CMP BL,4A ;J
0040125C|.75 0A |JNZ SHORT CrackMe_.00401268
0040125E|.B3 49 |MOV BL,49 ;I
00401260|.C74424 10 010>|MOV DWORD PTR SS:,1
00401268|>0FBEC3 |MOVSX EAX,BL
0040126B|.8D4C24 20 |LEA ECX,DWORD PTR SS:
0040126F|.50 |PUSH EAX
00401270|.51 |PUSH ECX
00401271|.E8 2A020000 |CALL CrackMe_.004014A0
00401276|.83C4 08 |ADD ESP,8
00401279|.85C0 |TEST EAX,EAX
0040127B|.75 03 |JNZ SHORT CrackMe_.00401280
0040127D|.881E |MOV BYTE PTR DS:,BL
0040127F|.46 |INC ESI
00401280|>8A5424 10 |MOV DL,BYTE PTR SS:
00401284|.896C24 10 |MOV DWORD PTR SS:,EBP
00401288|.FEC2 |INC DL
0040128A|.02DA |ADD BL,DL
0040128C|.80FB 5B |CMP BL,5B ;ASCII(Z)=5A
0040128F|.^ 75 C8 \JNZ SHORT CrackMe_.00401259 ;从A到Z按顺序补上用户名中没有的字符(J除外)
6.接着对用户名进行处理:从A到Z按顺序补上用户名中没有的字符(J除外),这样就得到了真正参与运算的用户名.我输入的dewarj得到的是:DEWARJBCFGHIKLMNOPQSTUVXYZ
00401291|.8B8C24 E00100>MOV ECX,DWORD PTR SS:
00401298|.8D8424 140100>LEA EAX,DWORD PTR SS:
0040129F|.68 C9000000 PUSH 0C9 ; /Count = C9 (201.)
004012A4|.50 PUSH EAX ; |Buffer
004012A5|.68 E9030000 PUSH 3E9 ; |ControlID = 3E9 (1001.)
004012AA|.51 PUSH ECX ; |hWnd
004012AB|.FF15 AC504000 CALL DWORD PTR DS:[<&USER32.GetDlg>; \取注册码
004012B1|.83F8 0E CMP EAX,0E ;必须是14位
004012B4|.0F85 54010000 JNZ CrackMe_.0040140E
004012BA|.8D9424 140100>LEA EDX,DWORD PTR SS:
004012C1|.50 PUSH EAX
004012C2|.52 PUSH EDX
004012C3|.E8 58010000 CALL CrackMe_.00401420 ;转为大写
004012C8|.83C4 08 ADD ESP,8
004012CB|.85C0 TEST EAX,EAX ;不能是除字母以外的其它字符
004012CD|.0F84 3B010000 JE CrackMe_.0040140E
004012D3|.8B5C24 10 MOV EBX,DWORD PTR SS:
004012D7|.896C24 1C MOV DWORD PTR SS:,EBP
004012DB|.8B6C24 1C MOV EBP,DWORD PTR SS:
004012DF|.8B7C24 1C MOV EDI,DWORD PTR SS:
004012E3|>8B4424 1C /MOV EAX,DWORD PTR SS: ;取注册码的第EAX组(一组2位)
004012E7|.8A9404 140100>|MOV DL,BYTE PTR SS:;第1位=>DL
004012EE|.8A8C04 150100>|MOV CL,BYTE PTR SS:;第2位=>CL
004012F5|.8DB404 150100>|LEA ESI,DWORD PTR SS:[ESP+EAX+115>;ESI=注册码第EAX组第2位的地址
004012FC|.80FA 4A |CMP DL,4A ;第1位与'J'比较
004012FF|.884C24 17 |MOV BYTE PTR SS:,CL ;CL=>
00401303|.75 02 |JNZ SHORT CrackMe_.00401307 ;不等就跳(不处理)
00401305|.B2 49 |MOV DL,49 ;相等,J变I
00401307|>33C9 |XOR ECX,ECX
00401309|.8D7424 20 |LEA ESI,DWORD PTR SS: ;ESI=处理后用户名的首址
0040130D|>33C0 |/XOR EAX,EAX ;循环变量清零EAX=0
0040130F|>381406 ||/CMP BYTE PTR DS:,DL ;DL与用户名的第EAX位比较
00401312|.75 0A |||JNZ SHORT CrackMe_.0040131E ;不等就跳(下一位)
00401314|.8B5C24 10 |||MOV EBX,DWORD PTR SS: ;相等就
00401318|.8BE9 |||MOV EBP,ECX ;记下组数=>EBP
0040131A|.894424 18 |||MOV DWORD PTR SS:,EAX ;记下位数=>
0040131E|>40 |||INC EAX ;下一位
0040131F|.83F8 05 |||CMP EAX,5 ;循环变量EAX与5比较
00401322|.^ 7C EB ||\JL SHORT CrackMe_.0040130F ;少于5就循环
00401324|.41 ||INC ECX ;下一组
00401325|.83C6 05 ||ADD ESI,5 ;指针ESI后移5位
00401328|.83F9 05 ||CMP ECX,5 ;5组都完了没?
0040132B|.^ 7C E0 |\JL SHORT CrackMe_.0040130D ;没有就循环
0040132D|.33F6 |XOR ESI,ESI
0040132F|.8D4C24 20 |LEA ECX,DWORD PTR SS:
00401333|>33C0 |/XOR EAX,EAX ;循环变量清零EAX=0
00401335|>8A5424 17 ||/MOV DL,BYTE PTR SS: ;=CL(注册码第EAX组第2位)=>DL
00401339|.3811 |||CMP BYTE PTR DS:,DL ;与用户名的第EAX位比较
0040133B|.75 04 |||JNZ SHORT CrackMe_.00401341 ;不等就跳(不处理)
0040133D|.8BFE |||MOV EDI,ESI ;相等就:组数存EDI
0040133F|.8BD8 |||MOV EBX,EAX ;位数存EBX
00401341|>40 |||INC EAX
00401342|.41 |||INC ECX ;下一位
00401343|.83F8 05 |||CMP EAX,5 ;位数与5比较
00401346|.^ 7C ED ||\JL SHORT CrackMe_.00401335 ;小于就循环
00401348|.46 ||INC ESI ;下一组
00401349|.83FE 05 ||CMP ESI,5 ;组数与5比较
0040134C|.^ 7C E5 |\JL SHORT CrackMe_.00401333 ;小于就循环
0040134E|.3BEF |CMP EBP,EDI ;注册码第EAX组中的两位在用户名中是否同组
00401350|.895C24 10 |MOV DWORD PTR SS:,EBX ;注册码第2位在用户名某组中的位数=>
00401354|.75 3A |JNZ SHORT CrackMe_.00401390 ;不同组就跳
00401356|.8B4424 18 |MOV EAX,DWORD PTR SS: ;同组就:
0040135A|.85C0 |TEST EAX,EAX ;注册码第1位在用户名某组中的位数是不是第0位
0040135C|.75 0A |JNZ SHORT CrackMe_.00401368 ;不是就跳(不处理)
0040135E|.C74424 18 040>|MOV DWORD PTR SS:,4 ;是就0变4
00401366|.EB 04 |JMP SHORT CrackMe_.0040136C
00401368|>FF4C24 18 |DEC DWORD PTR SS: ;注册码第1位在用户名某组中的位数减1
0040136C|>85DB |TEST EBX,EBX ;注册码第2位在用户名某组中的位数是不是第0位
0040136E|.75 07 |JNZ SHORT CrackMe_.00401377 ;不是就跳(不处理)
00401370|.BB 04000000 |MOV EBX,4 ;是就0变4
00401375|.EB 01 |JMP SHORT CrackMe_.00401378
00401377|>4B |DEC EBX ;注册码第2位在用户名某组中的位数减1
00401378|>8B4424 18 |MOV EAX,DWORD PTR SS:
0040137C|.8BD5 |MOV EDX,EBP
0040137E|.895C24 10 |MOV DWORD PTR SS:,EBX
00401382|.8D0CA8 |LEA ECX,DWORD PTR DS:
00401385|.03D1 |ADD EDX,ECX ;EDX=第1位的组数*5+第1位的位数
00401387|.8D0CBB |LEA ECX,DWORD PTR DS:;CL=用户名(注册码第2位所在组数,注册码第2位所在位数)
0040138A|.8A4414 20 |MOV AL,BYTE PTR SS: ;AL=用户名(注册码第1位所在组数,注册码第1位所在位数)
0040138E|.EB 46 |JMP SHORT CrackMe_.004013D6
00401390|>8B4C24 18 |MOV ECX,DWORD PTR SS: ;不同组就:
00401394|.3BCB |CMP ECX,EBX ;注册码第EAX组中的两位在用户名中是否同位(组不同)
00401396|.75 30 |JNZ SHORT CrackMe_.004013C8 ;不同就跳
00401398|.85ED |TEST EBP,EBP ;相同就:第1位所在的组数是否为0
0040139A|.75 07 |JNZ SHORT CrackMe_.004013A3
0040139C|.BD 04000000 |MOV EBP,4 ;为0就变4
004013A1|.EB 01 |JMP SHORT CrackMe_.004013A4
004013A3|>4D |DEC EBP ;第1位组数减1
004013A4|>85FF |TEST EDI,EDI ;第2位所在组数是否为0
004013A6|.75 07 |JNZ SHORT CrackMe_.004013AF
004013A8|.BF 04000000 |MOV EDI,4 ;是0就变4
004013AD|.EB 01 |JMP SHORT CrackMe_.004013B0
004013AF|>4F |DEC EDI ;第2位组数减1
004013B0|>8D04A9 |LEA EAX,DWORD PTR DS:
004013B3|.8BCD |MOV ECX,EBP
004013B5|.03C8 |ADD ECX,EAX
004013B7|.8D14BB |LEA EDX,DWORD PTR DS:
004013BA|.8A440C 20 |MOV AL,BYTE PTR SS: ;AL=用户名(注册码第1位所在组数,注册码第1位所在位数)
004013BE|.8BCF |MOV ECX,EDI
004013C0|.03CA |ADD ECX,EDX
004013C2|.8A4C0C 20 |MOV CL,BYTE PTR SS: ;CL=用户名(注册码第2位所在组数,注册码第2位所在位数)
004013C6|.EB 16 |JMP SHORT CrackMe_.004013DE
004013C8|>8D14AB |LEA EDX,DWORD PTR DS:
004013CB|.8BC5 |MOV EAX,EBP
004013CD|.03C2 |ADD EAX,EDX ;EAX=第1位组数*5+第2位的位数
004013CF|.8D0CB9 |LEA ECX,DWORD PTR DS:
004013D2|.8A4404 20 |MOV AL,BYTE PTR SS: ;AL=用户名(注册码第1位所在组数,注册码第2位所在位数)
004013D6|>8BD7 |MOV EDX,EDI
004013D8|.03D1 |ADD EDX,ECX
004013DA|.8A4C14 20 |MOV CL,BYTE PTR SS: ;CL=用户名(注册码第2位所在组数,注册码第1位所在位数)
004013DE|>8B5424 1C |MOV EDX,DWORD PTR SS:
004013E2|.3A4414 3C |CMP AL,BYTE PTR SS: ;AL与'CRACKINGFORFUN'中的相应位进行比较
004013E6|.75 26 |JNZ SHORT CrackMe_.0040140E ;相等才行
004013E8|.3A4C14 3D |CMP CL,BYTE PTR SS: ;AL与'CRACKINGFORFUN'中的相应位进行比较
004013EC|.75 20 |JNZ SHORT CrackMe_.0040140E ;相等才行
004013EE|.83C2 02 |ADD EDX,2
004013F1|.83FA 0E |CMP EDX,0E
004013F4|.895424 1C |MOV DWORD PTR SS:,EDX
004013F8|.^ 0F8C E5FEFFFF \JL CrackMe_.004012E3
004013FE|.5F POP EDI
004013FF|.5E POP ESI
00401400|.5D POP EBP
00401401|.B8 01000000 MOV EAX,1 ;返回1,就成功
00401406|.5B POP EBX
00401407|.81C4 CC010000 ADD ESP,1CC
0040140D|.C3 RETN
0040140E|>5F POP EDI
0040140F|.5E POP ESI
00401410|.5D POP EBP
00401411|.33C0 XOR EAX,EAX ;返回0,就死
00401413|.5B POP EBX
00401414|.81C4 CC010000 ADD ESP,1CC
0040141A\.C3 RETN
7.将上面的算法整理一下:
首先,将一个14位的固定的字符串'CRACKINGFORFUN'2位一组,共分为7位,将我们输入的用户名处理后的字串5个一组,可分得5组
第0组:DEWAR
第1组:JBCFG
第2组:HIKLM
第3组:NOPQS
第4组:TUVXY
多出一个Z来,多出来的原因是程序本意是去除J的,但我们的用户名中有意加入了J,所以会多一位.加上程序去重不完全,所以如果你用户名输入的是'EAEEBEEECEEEEDJ'将会多出4位来.程序只取前25位来计算注册码,多出来的几位在正确的注册码中不会出现,并无太大的影响.
(1)将输入的注册码(共14位),2位一组,分为7组.
(2)检查每组第0位是不是J,是就用I代替,
(3)得到每组第0位和第1位在用户名中对应的组数和位数.记为第0位=(组数0,位数0),第1位=(组数1,位数1)
(4)如组数0=组数1,就得到两位新的注册码:第0位=(组数0,(位数0-1)),第1位=(组数1,(位数1-1))(如果位数0(或位数1)原来为0就变4)
(5)如位数0=位数1,就得到两位新的注册码:第0位=((组数0-1),位数0),第1位=((组数1-1),位数1)(如果组数0(或组数1)原来为0就变4)
(6)如果组数和位数都不相同,就得到两位新的注册码:第0位=(组数0,位数1),第1位=(组数1,位数0)
(7)将得到的新的注册码与固定字符串'CRACKINGFORFUN'的相应组进行逐位比较,一旦不同就GAME OVER;
(8)7组都算完了没有,没有就重复第(2)直到算完.全部符合就OK.
8.正确的注册码可由字符串'CRACKINGFORFUN'倒推回去得到:
(1)分组情况同7.
(2)得到字符串'CRACKINGFORFUN'每组中第0位和第1位在用户名中对应的组数和位数.记为第0位=(组数0,位数0),第1位=(组数1,位数1);
(3)如组数0=组数1,则两位注册码:第0位=(组数0,(位数0+1)),第1位=(组数1,(位数1+1))(如果位数0(或位数1)原来为4就变0)
(4)如位数0=位数1,则两位注册码:第0位=((组数0+1),位数0),第1位=((组数1+1),位数1)(如果组数0(或组数1)原来为4就变0)
(5)如果组数和位数都不相同,则两位注册码:第0位=(组数0,位数1),第1位=(组数1,位数0)
(6)7组都算完了没有,没有就重复第(2)直到算完.
(7)将计算得出的7组注册码按顺序连接起来就是正确的注册码了.
本例中:
固定字符串C R A C K I N G F O R F U N
组数1 0 0 1 2 2 3 1 1 3 0 1 4 3
位数2 4 3 2 2 1 0 4 3 1 4 3 1 0
注册码组数1 0 0 1 2 2 3 1 1 3 0 1 4 3
注册码位数4 2 2 3 3 2 4 0 1 3 3 4 0 1
注册码G W W F L K S J B Q A G T O
--------------------------------------------------------------------------------
【经验总结】
1.由于注册码的计算和输入的用户名有关系,所以当输入的用户名中无J时,计算出的注册码是没有问题的.
2.当输入的注册名中有J出现时,如果所计算出的注册码中的J出现在奇数位,那么它一定处于某组的第0位,会用I来代替,所以
最后比较时一定不会满足要求.此时就没有正确的注册码,必须更换注册名.
3.当输入的注册名中有J出现时,如果所计算出的注册码中的J出现在偶数位,那么它一定处于某组的第1位,计算时当正常的字
符处理,不会有任何的影响,此是所计算出的注册码就是有效的.
--------------------------------------------------------------------------------
【版权声明】: 转载请注明作者并保持文章的完整, 谢谢!
2007年01月20日 20:41:22
[ 本帖最后由 dewar 于 2007-1-22 08:11 编辑 ] 排得很好看的格式,怎么成这样了:L
有时间再写个注册机上来:P 文章分析的特清晰!严重支持 :handshake 写的很详细~!
想学习算法对我比较有用~ :lol: 很详细,我要慢慢消化。/find 太厉害了,老凶那天做成个动画发上来就更好了!:lol: 分析的相当成功,有条不紊!!! 下来看一下。 精彩,就是偶差点水平... 很好的算法文章,谢谢楼主的辛苦!..
我发现这个程序从取用户名/注册码到计算都是通过子程序实现的.所以通过字符串来查找注册信息,
在成功信息附近跟入
00401081 .E8 AA000000 CALL CrackMe_.00401130
就可以实现对算法的跟踪..
支持了!
[ 本帖最后由 blitz 于 2007-12-16 17:03 编辑 ]
页:
[1]
2