dewar 发表于 2007-1-20 20:43:28

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 编辑 ]

dewar 发表于 2007-1-20 20:52:54

排得很好看的格式,怎么成这样了:L
有时间再写个注册机上来:P

sztxgg 发表于 2007-1-20 22:09:31

文章分析的特清晰!严重支持 :handshake

avel 发表于 2007-1-25 22:42:26

写的很详细~!
想学习算法对我比较有用~ :lol:

spider007 发表于 2007-1-30 19:56:39

很详细,我要慢慢消化。/find

myselfsky 发表于 2007-1-31 11:03:57

太厉害了,老凶那天做成个动画发上来就更好了!:lol:

xuhw 发表于 2007-1-31 15:10:05

分析的相当成功,有条不紊!!!

tswrl 发表于 2007-4-29 19:42:05

下来看一下。

Luckly 发表于 2007-5-7 00:08:12

精彩,就是偶差点水平...

blitz 发表于 2007-12-16 17:02:44

很好的算法文章,谢谢楼主的辛苦!..

我发现这个程序从取用户名/注册码到计算都是通过子程序实现的.所以通过字符串来查找注册信息,
在成功信息附近跟入
00401081   .E8 AA000000   CALL CrackMe_.00401130
就可以实现对算法的跟踪..

支持了!

[ 本帖最后由 blitz 于 2007-12-16 17:03 编辑 ]
页: [1] 2
查看完整版本: happytown第17个crackme算法分析