XXXX DVD Ripper 算法分析及成品注册机
本帖最后由 姐又寡闻了 于 2019-6-6 22:38 编辑写一篇自己分析算法的一个过程,不对欢迎指正。
今天的程序是 Ahead DVD Ripper 年代好像有点久远,然而就从简单的算法来分析走起
PEID查壳一个VC++ 6.0,直接拖入OD 随意输入用户名与注册码 点击OK
是个MFC,到MFC42下按钮事件,来到关键算法CALL
00414156 .C74424 14 000>mov dword ptr ss:,0x0
0041415E .E8 47D70500 call <jmp.&MFC42.#6334>
00414163 .E8 6AD60500 call <jmp.&MFC42.#1168>
00414168 .8B48 04 mov ecx,dword ptr ds: ;Ahead_DV.00413FB0
0041416B .E8 0ADA0500 call <jmp.&MFC42.#1669>
00414170 .8B46 64 mov eax,dword ptr ds:
00414173 .8B4E 60 mov ecx,dword ptr ds:
00414176 .50 push eax ;EAX 序列号
00414177 .51 push ecx ;ECX 用户名
00414178 .C64424 18 01mov byte ptr ss:,0x1
0041417D .E8 EEFCFFFF call Ahead_DV.00413E70 ;关键CALL
00414182 .83C4 08 add esp,0x8
00414185 .85C0 test eax,eax ;Ahead_DV.00478518
00414187 .75 15 jnz short Ahead_DV.0041419E ;关键跳。
00414189 .6A 40 push 0x40
0041418B .68 2C2D4A00 push Ahead_DV.004A2D2C ;ASCII "Sorry"
00414190 .68 002D4A00 push Ahead_DV.004A2D00 ;ASCII "Invalid username or registration code "
00414195 .8BCE mov ecx,esi ;mfc42.#4234
00414197 .E8 9CD60500 call <jmp.&MFC42.#4224>
0041419C .EB 54 jmp short Ahead_DV.004141F2
0041419E >8B46 60 mov eax,dword ptr ds:
004141A1 .8D4C24 04 lea ecx,dword ptr ss:
004141A5 .50 push eax ;Ahead_DV.00478518
004141A6 .68 E42C4A00 push Ahead_DV.004A2CE4 ;ASCII "License To:%s "
004141AB .51 push ecx
004141AC .E8 57D60500 call <jmp.&MFC42.#2818>
004141B1 .8B5424 10 mov edx,dword ptr ss: ;mfc42.73D322FD
004141B5 .83C4 0C add esp,0xC
004141B8 .8BCE mov ecx,esi ;mfc42.#4234
004141BA .6A 40 push 0x40
004141BC .68 D82C4A00 push Ahead_DV.004A2CD8 ;ASCII "Thank you"
004141C1 .52 push edx
F7跟进关键CALL来到了这里:
00413E70/$53 push ebx
00413E71|.55 push ebp
00413E72|.8B6C24 0C mov ebp,dword ptr ss: ;用户名
00413E76|.56 push esi ;mfc42.#4234
00413E77|.57 push edi
00413E78|.BE 2C2F4C00 mov esi,Ahead_DV.004C2F2C
00413E7D|.8BC5 mov eax,ebp
00413E7F|>8A10 /mov dl,byte ptr ds: ;取用户名单字符
00413E81|.8A1E |mov bl,byte ptr ds:
00413E83|.8ACA |mov cl,dl ;单字符给了CL
00413E85|.3AD3 |cmp dl,bl
00413E87|.75 1E |jnz short Ahead_DV.00413EA7 ;不为空则跳出
00413E89|.84C9 |test cl,cl
00413E8B|.74 16 |je short Ahead_DV.00413EA3
00413E8D|.8A50 01 |mov dl,byte ptr ds:
00413E90|.8A5E 01 |mov bl,byte ptr ds:
00413E93|.8ACA |mov cl,dl
00413E95|.3AD3 |cmp dl,bl
00413E97|.75 0E |jnz short Ahead_DV.00413EA7
00413E99|.83C0 02 |add eax,0x2
00413E9C|.83C6 02 |add esi,0x2
00413E9F|.84C9 |test cl,cl
00413EA1|.^ 75 DC \jnz short Ahead_DV.00413E7F
00413EA3|>33C0 xor eax,eax ;Ahead_DV.00478518
00413EA5|.EB 05 jmp short Ahead_DV.00413EAC
00413EA7|>1BC0 sbb eax,eax ;Ahead_DV.00478518
00413EA9|.83D8 FF sbb eax,-0x1
00413EAC|>85C0 test eax,eax ;Ahead_DV.00478518
00413EAE|.74 51 je short Ahead_DV.00413F01
最后的00413EAE 跳到了下面这段代码, 从未进关键CALL之前,可以看到下面EAX为关键标志,如果EAX为0则注册失败,通过对上面汇编代码分析得出,这代码主要判断用户名是否为空
00413F01|> \5F pop edi ;这里EAX为0,注册失败
00413F02|.5E pop esi ;mfc42.73D323EB
00413F03|.5D pop ebp ;mfc42.73D323EB
00413F04|.33C0 xor eax,eax ;Ahead_DV.00478518
00413F06|.5B pop ebx ;mfc42.73D323EB
00413F07\.C3 retn
以此类推,下面是注册码是否为空的判断代码:
00413EB0|.8B7C24 18 mov edi,dword ptr ss: ;取序列号
00413EB4|.BE 2C2F4C00 mov esi,Ahead_DV.004C2F2C
00413EB9|.8BC7 mov eax,edi
00413EBB|>8A10 /mov dl,byte ptr ds: ;与上面循环一样
00413EBD|.8A1E |mov bl,byte ptr ds:
00413EBF|.8ACA |mov cl,dl
00413EC1|.3AD3 |cmp dl,bl
00413EC3|.75 1E |jnz short Ahead_DV.00413EE3 ;不为空则跳出
00413EC5|.84C9 |test cl,cl
00413EC7|.74 16 |je short Ahead_DV.00413EDF
00413EC9|.8A50 01 |mov dl,byte ptr ds:
00413ECC|.8A5E 01 |mov bl,byte ptr ds:
00413ECF|.8ACA |mov cl,dl
00413ED1|.3AD3 |cmp dl,bl
00413ED3|.75 0E |jnz short Ahead_DV.00413EE3
00413ED5|.83C0 02 |add eax,0x2
00413ED8|.83C6 02 |add esi,0x2
00413EDB|.84C9 |test cl,cl
00413EDD|.^ 75 DC \jnz short Ahead_DV.00413EBB
00413EDF|>33C0 xor eax,eax ;Ahead_DV.00478518
00413EE1|.EB 05 jmp short Ahead_DV.00413EE8
00413EE3|>1BC0 sbb eax,eax ;Ahead_DV.00478518
00413EE5|.83D8 FF sbb eax,-0x1
00413EE8|>85C0 test eax,eax ;Ahead_DV.00478518
00413EEA|.74 15 je short Ahead_DV.00413F01
注意这里,用户名和注册码都不为空时,这里又进入下一层CALL,此CALL也必须跟进,因为关键算法就在里面了。
00413C40/$6A FF push -0x1
00413C42|.68 203B4700 push Ahead_DV.00473B20 ;SE 处理程序安装
00413C47|.64:A1 0000000>mov eax,dword ptr fs:
00413C4D|.50 push eax ;Ahead_DV.00478518
00413C4E|.64:8925 00000>mov dword ptr fs:,esp
00413C55|.83EC 14 sub esp,0x14
00413C58|.8B4424 24 mov eax,dword ptr ss: ;用户名
00413C5C|.53 push ebx
00413C5D|.55 push ebp
00413C5E|.56 push esi ;mfc42.#4234
00413C5F|.57 push edi
00413C60|.50 push eax ;Ahead_DV.00478518
00413C61|.8D4C24 18 lea ecx,dword ptr ss:
00413C65|.E8 2EDC0500 call <jmp.&MFC42.#537> ;EAX存着用户名指针
00413C6A|.8D4C24 14 lea ecx,dword ptr ss:
00413C6E|.C74424 2C 000>mov dword ptr ss:,0x0
00413C76|.E8 F3DE0500 call <jmp.&MFC42.#6282>
00413C7B|.8D4C24 14 lea ecx,dword ptr ss:
00413C7F|.E8 E4DE0500 call <jmp.&MFC42.#6283>
00413C84|.6A 20 push 0x20
00413C86|.8D4C24 18 lea ecx,dword ptr ss:
00413C8A|.E8 B5DB0500 call <jmp.&MFC42.#2915>
00413C8F|.8B4C24 38 mov ecx,dword ptr ss:
00413C93|.8BD8 mov ebx,eax ;Ahead_DV.00478518
00413C95|.51 push ecx
00413C96|.8D4C24 14 lea ecx,dword ptr ss:
00413C9A|.E8 F9DB0500 call <jmp.&MFC42.#537>
00413C9F|.8D4C24 10 lea ecx,dword ptr ss: ;EAX存着序列号指针
00413CA3|.C64424 2C 01mov byte ptr ss:,0x1
00413CA8|.E8 C1DE0500 call <jmp.&MFC42.#6282>
00413CAD|.8D4C24 10 lea ecx,dword ptr ss:
00413CB1|.E8 B2DE0500 call <jmp.&MFC42.#6283>
00413CB6|.6A 20 push 0x20
00413CB8|.8D4C24 14 lea ecx,dword ptr ss:
00413CBC|.E8 83DB0500 call <jmp.&MFC42.#2915>
00413CC1|.8BD0 mov edx,eax ;EDX序列号
00413CC3|.83CE FF or esi,-0x1
00413CC6|.8BFA mov edi,edx
00413CC8|.8BCE mov ecx,esi ;mfc42.#4234
00413CCA|.33C0 xor eax,eax ;Ahead_DV.00478518
00413CCC|.895424 20 mov dword ptr ss:,edx
00413CD0|.F2:AE repne scas byte ptr es:
00413CD2|.F7D1 not ecx
00413CD4|.49 dec ecx
00413CD5|.8BFB mov edi,ebx
00413CD7|.8BE9 mov ebp,ecx
00413CD9|.8BCE mov ecx,esi ;mfc42.#4234
00413CDB|.F2:AE repne scas byte ptr es:
00413CDD|.F7D1 not ecx
00413CDF|.49 dec ecx
00413CE0|.3BCD cmp ecx,ebp ;比较用户名与序列号的长度
00413CE2|.0F87 54010000 ja Ahead_DV.00413E3C ;跳到注册失败
进入这个CALL后,通过上面的分析,可以看到,取了用户名与注册码的长度进行比较,若用户名长度大于注册码的长度则跳到00413E3C
如果跳到 00413E3C 这个位置明显是注册失败,从上图可以看到EAX又被赋值为0,而且可以看到这里有3个跳转来自.所以1处就是刚分析得到的,用户长度不能大于注册码长度,否则注册失败。
接着继续分析下面代码,这里可以看到,用户名的长度与注册码的长度分别做了比较,如果为空则跳到 00413E3C,那么刚才的2处错误跳转就来自于这2个比较。
00413CE8|.8BFB mov edi,ebx
00413CEA|.8BCE mov ecx,esi ;mfc42.#4234
00413CEC|.F2:AE repne scas byte ptr es:
00413CEE|.F7D1 not ecx
00413CF0|.49 dec ecx
00413CF1|.0F84 45010000 je Ahead_DV.00413E3C ;用户名是否为空,跳到注册失败
00413CF7|.8BFA mov edi,edx
00413CF9|.8BCE mov ecx,esi ;mfc42.#4234
00413CFB|.F2:AE repne scas byte ptr es:
00413CFD|.F7D1 not ecx
00413CFF|.49 dec ecx
00413D00|.0F84 36010000 je Ahead_DV.00413E3C ;序列号是否为空,跳到注册失败
接着就是关键的一些算法,我们一点一点来看。
00413D06|.894424 38 mov dword ptr ss:,eax ;Ahead_DV.00478518
00413D0A|>8B5424 38 /mov edx,dword ptr ss:
00413D0E|.8D4C24 34 |lea ecx,dword ptr ss: ;ECX存放用户名指针
00413D12|.8A82 782C4A00 |mov al,byte ptr ds: ;EDX计数,常量ZQY
00413D18|.884424 18 |mov byte ptr ss:,al
00413D1C|.E8 93DA0500 |call <jmp.&MFC42.#540>
00413D21|.8BFB |mov edi,ebx
00413D23|.83C9 FF |or ecx,-0x1
00413D26|.33C0 |xor eax,eax ;Ahead_DV.00478518
00413D28|.33ED |xor ebp,ebp
00413D2A|.F2:AE |repne scas byte ptr es:
00413D2C|.F7D1 |not ecx
00413D2E|.49 |dec ecx
00413D2F|.C64424 2C 02|mov byte ptr ss:,0x2
00413D34|.74 4B |je short Ahead_DV.00413D81 ;用户名长度为8,没有跳,暂时不管
00413D36|>8A042B |/mov al,byte ptr ds: ;取用户名1字节
00413D39|.33F6 ||xor esi,esi ;mfc42.#4234
00413D3B|>3A0475 102C4A>||/cmp al,byte ptr ds: ;查表,比较
00413D42|.74 08 |||je short Ahead_DV.00413D4C
00413D44|.46 |||inc esi ;mfc42.#4234
00413D45|.83FE 34 |||cmp esi,0x34 ;比较大小字母 总的52位
00413D48|.^ 7C F1 ||\jl short Ahead_DV.00413D3B
00413D4A|.EB 11 ||jmp short Ahead_DV.00413D5D
00413D4C|>8A0C75 112C4A>||mov cl,byte ptr ds: ;对应的码表赋值cl
00413D53|.51 ||push ecx
00413D54|.8D4C24 38 ||lea ecx,dword ptr ss:
00413D58|.E8 05DE0500 ||call <jmp.&MFC42.#940> ;EAX存放着算出来的字符串指针
00413D5D|>83FE 34 ||cmp esi,0x34
00413D60|.75 0E ||jnz short Ahead_DV.00413D70
00413D62|.8B5424 18 ||mov edx,dword ptr ss:
00413D66|.8D4C24 34 ||lea ecx,dword ptr ss:
00413D6A|.52 ||push edx
00413D6B|.E8 F2DD0500 ||call <jmp.&MFC42.#940>
00413D70|>8BFB ||mov edi,ebx
00413D72|.83C9 FF ||or ecx,-0x1
00413D75|.33C0 ||xor eax,eax ;Ahead_DV.00478518
00413D77|.45 ||inc ebp
00413D78|.F2:AE ||repne scas byte ptr es:
00413D7A|.F7D1 ||not ecx
00413D7C|.49 ||dec ecx
00413D7D|.3BE9 ||cmp ebp,ecx ;ebp为计数
00413D7F|.^ 72 B5 |\jb short Ahead_DV.00413D36
00413D81|>8B4424 34 |mov eax,dword ptr ss: ;用户名计算得出的字符串
00413D85|.8B48 F8 |mov ecx,dword ptr ds: ;Ahead_DV.00411CF0
00413D88|.83F9 10 |cmp ecx,0x10 ;比较是否大于等于 10h
00413D8B|.7D 3A |jge short Ahead_DV.00413DC7
00413D8D|.8BC1 |mov eax,ecx
00413D8F|.B9 10000000 |mov ecx,0x10
00413D94|.2BC8 |sub ecx,eax ;Ahead_DV.00478518
00413D96|.8D5424 1C |lea edx,dword ptr ss:
00413D9A|.51 |push ecx
00413D9B|.52 |push edx
00413D9C|.B9 1C044E00 |mov ecx,Ahead_DV.004E041C ;UNICODE "匀?"
00413DA1|.E8 B6DD0500 |call <jmp.&MFC42.#4129> ;截取10h-用户名长度字符串 "HLWsCbMErpAQOXWK"
00413DA6|.50 |push eax ;Ahead_DV.00478518
00413DA7|.8D4C24 38 |lea ecx,dword ptr ss:
00413DAB|.C64424 30 03|mov byte ptr ss:,0x3
00413DB0|.E8 CBDA0500 |call <jmp.&MFC42.#939>
00413DB5|.8D4C24 1C |lea ecx,dword ptr ss: ;计算的字符串+截取字符
00413DB9|.C64424 2C 02|mov byte ptr ss:,0x2 ;ASCII "ZZCKAZBZHLWsCbME"
00413DBE|.E8 EFD80500 |call <jmp.&MFC42.#800>
00413DC3|.8B4424 34 |mov eax,dword ptr ss:
00413DC7|>8B4C24 20 |mov ecx,dword ptr ss: ;mfc42.73D491F4
00413DCB|.51 |push ecx ; /s2 = "匞"
00413DCC|.50 |push eax ; |s1 = "?G"
00413DCD|.FF15 6C574700 |call dword ptr ds:[<&MSVCRT._mbscmp>] ; \_mbscmp
00413DD3|.83C4 08 |add esp,0x8 ;真假比较
00413DD6|.8D4C24 34 |lea ecx,dword ptr ss:
00413DDA|.85C0 |test eax,eax ;Ahead_DV.00478518
00413DDC|.C64424 2C 01|mov byte ptr ss:,0x1
00413DE1|.74 1B |je short Ahead_DV.00413DFE
00413DE3|.33F6 |xor esi,esi ;mfc42.#4234
00413DE5|.E8 C8D80500 |call <jmp.&MFC42.#800>
00413DEA|.8B4424 38 |mov eax,dword ptr ss:
00413DEE|.40 |inc eax ;Ahead_DV.00478518
00413DEF|.83F8 03 |cmp eax,0x3
00413DF2|.894424 38 |mov dword ptr ss:,eax ;Ahead_DV.00478518
00413DF6|.^ 0F8C 0EFFFFFF \jl Ahead_DV.00413D0A
00413DFC|.EB 0A jmp short Ahead_DV.00413E08
00413DFE|>BE 01000000 mov esi,0x1 ;标志位赋值
00413E03|.E8 AAD80500 call <jmp.&MFC42.#800>
00413E08|>8D4C24 10 lea ecx,dword ptr ss:
00413E0C|.C64424 2C 00mov byte ptr ss:,0x0
00413E11|.E8 9CD80500 call <jmp.&MFC42.#800>
00413E16|.8D4C24 14 lea ecx,dword ptr ss:
00413E1A|.C74424 2C FFF>mov dword ptr ss:,-0x1
00413E22|.E8 8BD80500 call <jmp.&MFC42.#800>
00413E27|.8BC6 mov eax,esi ;mfc42.#4234
00413E29|.5F pop edi ;mfc42.73D323EB
00413E2A|.5E pop esi ;mfc42.73D323EB
00413E2B|.5D pop ebp ;mfc42.73D323EB
00413E2C|.5B pop ebx ;mfc42.73D323EB
00413E2D|.8B4C24 14 mov ecx,dword ptr ss:
00413E31|.64:890D 00000>mov dword ptr fs:,ecx
00413E38|.83C4 20 add esp,0x20
00413E3B|.C3 retn
这里有个可疑的常量,我们先记录一下,一会还要用到这个
这里AL为存放用户名的逐个取字符,EBP为计数,与esi*2+0x4A2C10 内存地址进行比较,我们数据跟随,可以看到一张表,34h == 52,这里进行循环比较,从这个内存地址我们可以看出,他就是取这张表来进行比较,总共比的是a-z与A-Z刚好是52个大小写字母
比较完若找到字符,则把esi*2+0x4A2C11 赋值给CL,没有找到就跳过赋值,通过跟踪分析得出,这里如果用户名的字符是字母则把表里的紧跟的字符赋值给了cl,如果不是字母,则利用之前的那个常量,还记得吗,ZQY的一个字符Z来代替,然后依次循环N*NAME长度,这样就获取到了由用户名通过码表计算出来的一串字符串。
00413D81|> \8B4424 34 |mov eax,dword ptr ss: ;用户名计算得出的字符串
00413D85|.8B48 F8 |mov ecx,dword ptr ds:
00413D88|.83F9 10 |cmp ecx,0x10 ;比较是否大于等于 10h
00413D8B|.7D 3A |jge short Ahead_DV.00413DC7
00413D8D|.8BC1 |mov eax,ecx
00413D8F|.B9 10000000 |mov ecx,0x10
00413D94|.2BC8 |sub ecx,eax
00413D96|.8D5424 1C |lea edx,dword ptr ss:
00413D9A|.51 |push ecx
00413D9B|.52 |push edx
00413D9C|.B9 1C044E00 |mov ecx,Ahead_DV.004E041C ;UNICODE "匀?"
00413DA1|.E8 B6DD0500 |call <jmp.&MFC42.#4129> ;截取10h-用户名长度字符串 "HLWsCbMErpAQOXWK"
00413DA6|.50 |push eax
00413DA7|.8D4C24 38 |lea ecx,dword ptr ss:
00413DAB|.C64424 30 03|mov byte ptr ss:,0x3
00413DB0|.E8 CBDA0500 |call <jmp.&MFC42.#939>
00413DB5|.8D4C24 1C |lea ecx,dword ptr ss: ;计算的字符串+截取字符
取得计算出来的字符串长度判断是否够16位,若不够,则补到16位,而补的字符串取值来自0x4E041C ASCII ”HLWsCbMErpAQOXWK“,最后组合成新的注册码与我们之前的假码进行比较。
这里看到,当比较结果给了EAX,如果EAX为0则ESI就为1,最后通过ESI给EAX后,注册即可成功。所以大概的流程就是这样,现在我们来大概梳理一下,还有那个常量的问题,再次来分析一下。
从这边看到这里大循环3次,而且和这个常量有关,我们再走一遍分析一下,得出这一次计算出来的结果则是如果名字不是字母则用Q来代替,之前是用Z来代替,那么我们就可以总结出,如果NAME含有不是字母的其他字符,那么他就有3个KEY。
到这里已经很清晰了,通过上面的分析我们就可以来自己写KEYGEN了,首先名字不能为空,其次名字长度不能大于注册码的长度,然后通过码表依次计算得出新的字符串,不到16位则用第2个表的字符串进行补位,最后生成的就是正确的KEY。
用易语言写的KEYGEN,-.-# 凑合看看就好啦~ 附上成品注册机。
ZMZwise 发表于 2016-7-23 17:49
虽然看不懂,但是还是支持原创作品
可以尝试看看,写得都挺通俗易懂的。哪里不明白也欢迎讨论~ yiboy 发表于 2016-7-24 11:55
支持楼主,鼓励原创,膜拜大神
跟大家一起起步学习,有什么问题欢迎一起讨论。 唔EAX为0注册失败怎么看出来的 二十七秒 发表于 2016-7-23 17:35
唔EAX为0注册失败怎么看出来的
虽然看不懂,但是还是支持原创作品 谢谢分享!!!虽然我不懂 我是小白,看不懂{:1_909:} 支持楼主,鼓励原创,膜拜大神 很详细,加精鼓励,期待更多分享!