JoyChou 发表于 2012-8-16 18:14

一个简单CM的简单算法分析

【文章标题】: 一个简单CM的简单算法分析
【文章作者】: JoyChou
【软件名称】: Game
【软件大小】: 176KB
【下载地址】: 见附件
【加壳方式】: 无壳
【保护方式】: 注册码保护
【编写语言】: Microsoft Visual C++ 5.0
【使用工具】: PEiD,OD,IDA
【操作平台】: XP SP3
【软件介绍】: 一个简单的猜数字游戏
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
首先查壳无壳,VC++的程序,运行后,ALT+E查看模块没有MFC42模块,不是MFC的程序。
OD载入后,ctrl+n,查看输入表
发现MessageBox,GetDlgItemText暴露无遗,然后搜索字符串也OK,
所以这个程序是拿给大家玩的,并无什么难度。

——————————————————————————————————
菜鸟试着从程序入口点分析这个CM,不直接着手关键点
在编写Win32应用程序的适合,都必须在源码里实现一个Winmain函数,
但Windows程序并不是从WinMain函数开始的,首先被执行的是启动函数相关代码,这段
代码由编译器自动生成,然后才是WinMain函数,
所以程序的OEP并不是WinMain函数,我们OD载入向下看,一直到这,00401682|.52            push edx
00401683|.6A 00         push 0
00401685|.6A 00         push 0                                  ; /pModule = NULL
00401687|.FF15 B8A14200 call dword ptr ds:[<&KERNEL32.GetModule>; \GetModuleHandleA
0040168D|.50            push eax
0040168E|.E8 81F9FFFF   call Game.00401014 //程序WinMain函数的入口点
00401693|.8945 A0       mov dword ptr ss:,eax
00401696|.8B45 A0       mov eax,dword ptr ss:
00401699|.50            push eax
0040169A|.E8 91080000   call Game.00401F30 // 退出程序

此时ctrl+G 输入401014来到程序入口点
00401014   $ /E9 17000000   jmp Game.00401030

来到00401030
00401030   > \55            push ebp
00401031   .8BEC          mov ebp,esp
00401033   .83EC 40       sub esp,40
00401036   .53            push ebx
00401037   .56            push esi
00401038   .57            push edi
00401039   .8D7D C0       lea edi,dword ptr ss:
0040103C   .B9 10000000   mov ecx,10
00401041   .B8 CCCCCCCC   mov eax,CCCCCCCC
00401046   .F3:AB         rep stos dword ptr es:
00401048   .8B45 08       mov eax,dword ptr ss:
0040104B   .A3 507C4200   mov dword ptr ds:,eax
00401050   .8BF4          mov esi,esp
00401052   .6A 00         push 0                                           ; /lParam = NULL
00401054   .68 0A104000   push Game.0040100A                               ; |DlgProc = Game.0040100A
00401059   .6A 00         push 0                                           ; |hOwner = NULL
0040105B   .6A 65         push 65                                          ; |pTemplate = 65
0040105D   .8B4D 08       mov ecx,dword ptr ss:                     ; |
00401060   .51            push ecx                                       ; |hInst
00401061   .FF15 D0A24200 call dword ptr ds:[<&USER32.DialogBoxParamA>]    ; \DialogBoxParamA// 弹出对话框
00401067   .3BF4          cmp esi,esp
00401069   .E8 52040000   call Game.004014C0 // 无用call
0040106E   .33C0          xor eax,eax
00401070   .5F            pop edi
00401071   .5E            pop esi
00401072   .5B            pop ebx
00401073   .83C4 40       add esp,40
00401076   .3BEC          cmp ebp,esp
00401078   .E8 43040000   call Game.004014C0 // 无用call
0040107D   .8BE5          mov esp,ebp
0040107F   .5D            pop ebp
00401080   .C2 1000       retn 10


到这后,我们再向下看,就看到了程序的主要部分

004010B8|.8B45 0C       mov eax,dword ptr ss:
004010BB|.8945 E8       mov dword ptr ss:,eax
004010BE|.837D E8 10    cmp dword ptr ss:,10
004010C2|.0F84 B9000000 je Game.00401181
004010C8|.817D E8 10010>cmp dword ptr ss:,110
004010CF|.74 0E         je short Game.004010DF
004010D1|.817D E8 11010>cmp dword ptr ss:,111
004010D8|.74 26         je short Game.00401100
004010DA|.E9 B9000000   jmp Game.00401198
004010DF|>8BF4          mov esi,esp
004010E1|.6A 00         push 0                                           ; /lParam = 0
004010E3|.6A 0E         push 0E                                          ; |wParam = E
004010E5|.68 C5000000   push 0C5                                       ; |Message = EM_LIMITTEXT
004010EA|.8B4D 08       mov ecx,dword ptr ss:                     ; |
004010ED|.51            push ecx                                       ; |hWnd
004010EE|.FF15 C0A24200 call dword ptr ds:[<&USER32.SendMessageA>]       ; \SendMessageA // 使对话框的输入字符最大长度为0E,不过程序没有执行到这



00401113|.6A 10         push 10                                          ; /Count = 10 (16.)
00401115|.8D45 EC       lea eax,dword ptr ss:                  ; |
00401118|.50            push eax                                       ; |Buffer
00401119|.68 E9030000   push 3E9                                       ; |ControlID = 3E9 (1001.)
0040111E|.8B4D 08       mov ecx,dword ptr ss:                     ; |
00401121|.51            push ecx                                       ; |hWnd
00401122|.FF15 C4A24200 call dword ptr ds:[<&USER32.GetDlgItemTextA>]    ; \GetDlgItemTextA //获取对话框控件的字符串,在这下断就好了
00401128|.3BF4          cmp esi,esp
0040112A|.E8 91030000   call Game.004014C0
0040112F|.8D55 EC       lea edx,dword ptr ss:
00401132|.52            push edx //获得的字符串压栈
00401133|.E8 D7FEFFFF   call Game.0040100F // 处理获取的字符串


F7进入00401133 这个关键call
00401320 /> \55 push ebp
00401321 |. 8BEC mov ebp,esp
00401323 |. 83EC 58 sub esp,58
00401326 |. 53 push ebx
00401327 |. 56 push esi
00401328 |. 57 push edi
00401329 |. 8D7D A8 lea edi,dword ptr ss: ; HDg
0040132C |. B9 16000000 mov ecx,16
00401331 |. B8 CCCCCCCC mov eax,CCCCCCCC
00401336 |. F3:AB rep stos dword ptr es:
00401338 |. C645 EC 00 mov byte ptr ss:,0
0040133C |. 33C0 xor eax,eax
0040133E |. 8945 ED mov dword ptr ss:,eax
00401341 |. 8945 F1 mov dword ptr ss:,eax
00401344 |. 8945 F5 mov dword ptr ss:,eax
00401347 |. 66:8945 F9 mov word ptr ss:,ax
0040134B |. 8845 FB mov byte ptr ss:,al
0040134E |. 8B4D 08 mov ecx,dword ptr ss: ; 假码
00401351 |. 51 push ecx ; 假码入栈
00401352 |. E8 A9010000 call Game.00401500 ; 计算假码长度返回给eax,这里就不进去了
00401357 |. 83C4 04 add esp,4
0040135A |. 8945 FC mov dword ptr ss:,eax ; ebp-4为假码长度
0040135D |. C745 E8 01000>mov dword ptr ss:,1 ; i=i+2,i初始化为1
00401364 |. EB 09 jmp short Game.0040136F
00401366 |> 8B55 E8 /mov edx,dword ptr ss:
00401369 |. 83C2 02 |add edx,2
0040136C |. 8955 E8 |mov dword ptr ss:,edx
0040136F |> 8B45 E8 mov eax,dword ptr ss:
00401372 |. 3B45 FC |cmp eax,dword ptr ss:
00401375 |. 7D 17 |jge short Game.0040138E ; eax大于等于假码长度就跳出循环
00401377 |. 8B4D 08 |mov ecx,dword ptr ss: ; ebp+8指向假码
0040137A |. 034D E8 |add ecx,dword ptr ss: ; 取偶数位,也就是s,s,s数组表示
0040137D |. 0FBE11 |movsx edx,byte ptr ds: ; 依次给edx
00401380 |. 83FA 32 |cmp edx,32 ; 和2比较
00401383 |. 74 07 |je short Game.0040138C ; 相等就跳
00401385 |. 33C0 |xor eax,eax
00401387 |. E9 B4000000 |jmp Game.00401440 ; 跳向错误
0040138C |>^ EB D8 \jmp short Game.00401366

所以程序先处理偶数位,要求就是每位偶数位都是2,下面就是处理奇数位,但是处理奇数位的时候用了一个反汇编代码比较多的表示方法

a % 2 == 0,当然0,2,4,6等余数都是0,所以表示的是奇数。

if(a % 2 == 0)
{
//do something
}
对应的汇编指令比较多些

Assembly code

00A31395 mov eax,dword ptr
00A31398 and eax,80000001h
00A3139D jns main+34h (0A313A4h)
00A3139F dec eax
00A313A0 or eax,0FFFFFFFEh
00A313A3 inc eax
00A313A4 text eax,eax
00A313A7 jne main+40h (0A313B0h)

如果这样处理
if(a & 0x1 == 1)
{
//do something
}
对应的汇编指令好少啊~

Assembly code   

002D1395 mov eax,dword ptr
002D1398 and eax,1
002D139B je main+34h (2D13A4h)
相比之下,我想出题者肯定是想增加下难度而已。菜鸟再次飘过~~下面继续分析奇数位~~

接下来的代码就是将奇数位放在ebp-14,这里菜鸟就不仔细分析了,有时候可以直接步过看结果,猜测算法,大大减少了分析代码的时间
0040138E |> \C745 E8 00000>mov dword ptr ss:,0 ; i=0
00401395 |. EB 09 jmp short Game.004013A0
00401397 |> 8B45 E8 /mov eax,dword ptr ss:
0040139A |. 83C0 01 |add eax,1 ; i++
0040139D |. 8945 E8 |mov dword ptr ss:,eax
004013A0 |> 8B4D E8 mov ecx,dword ptr ss:
004013A3 |. 3B4D FC |cmp ecx,dword ptr ss: ; i和假码长度比较
004013A6 |. 7D 2A |jge short Game.004013D2 ; 大于等于就跳
004013A8 |. 8B55 E8 |mov edx,dword ptr ss:
004013AB |. 81E2 01000080 |and edx,80000001 ; edx=i^80000001
004013B1 |. 79 05 |jns short Game.004013B8 ; SF=0就跳
004013B3 |. 4A |dec edx
004013B4 |. 83CA FE |or edx,FFFFFFFE
004013B7 |. 42 |inc edx
004013B8 |> 85D2 |test edx,edx
004013BA |. 75 14 |jnz short Game.004013D0
004013BC |. 8B45 E8 |mov eax,dword ptr ss:
004013BF |. 99 |cdq
004013C0 |. 2BC2 |sub eax,edx
004013C2 |. D1F8 |sar eax,1
004013C4 |. 8B4D 08 |mov ecx,dword ptr ss:
004013C7 |. 034D E8 |add ecx,dword ptr ss:
004013CA |. 8A11 |mov dl,byte ptr ds:
004013CC |. 885405 EC |mov byte ptr ss:,dl
004013D0 |>^ EB C5 \jmp short Game.00401397
004013D2 |> 8D45 EC lea eax,dword ptr ss:

下面就是处理奇数位~~
004013D2 |> \8D45 EC lea eax,dword ptr ss:
004013D5 |. 50 push eax ; 奇数位入栈处理
004013D6 |. E8 2AFCFFFF call Game.00401005 ; 这个call很重要,进去看看
004013DB |. 83C4 04 add esp,4
004013DE |. 85C0 test eax,eax ; eax返回应该不为0
004013E0 |. 74 2D je short Game.0040140F ; 跳向失败,不让它跳
004013E2 |. 8D4D EC lea ecx,dword ptr ss:
004013E5 |. 51 push ecx
004013E6 |. E8 1AFCFFFF call Game.00401005 ; 和上面call一样,也就是处理两次
004013EB |. 83C4 04 add esp,4
004013EE |. 85C0 test eax,eax ; eax返回应该不为0
004013F0 |. 74 17 je short Game.00401409 ; 跳向失败,不让它跳
004013F2 |. 8D55 EC lea edx,dword ptr ss:
004013F5 |. 52 push edx
004013F6 |. E8 05010000 call Game.00401500 ; 计算ebp-14指向字符串的长度,再IDA里可以直接看到strlen这个函数名字
004013FB |. 83C4 04 add esp,4
004013FE |. 83F8 04 cmp eax,4 ; 和4比较
00401401 |. 74 04 je short Game.00401407 ; 不相等就跳向失败
00401403 |. 33C0 xor eax,eax
00401405 |. EB 39 jmp short Game.00401440 ; 跳向失败
00401407 |> EB 04 jmp short Game.0040140D ; 长度等于4就跳向这
00401409 |> 33C0 xor eax,eax
0040140B |. EB 33 jmp short Game.00401440
0040140D |> EB 04 jmp short Game.00401413 ; 再跳到这
0040140F |> 33C0 xor eax,eax
00401411 |. EB 2D jmp short Game.00401440
00401413 |> 0FBE45 EC movsx eax,byte ptr ss: ; 再到这,取奇数第一位
00401417 |. 0FBE4D ED movsx ecx,byte ptr ss: ; 奇数第二位
0040141B |. 3BC1 cmp eax,ecx
0040141D |. 7E 1F jle short Game.0040143E ; 跳向失败,故第一位应该大于第二位
0040141F |. 0FBE55 ED movsx edx,byte ptr ss:
00401423 |. 0FBE45 EE movsx eax,byte ptr ss: ; 奇数第三位
00401427 |. 3BD0 cmp edx,eax
00401429 |. 7D 13 jge short Game.0040143E ; 跳向失败,故第二位应该小于第三位
0040142B |. 0FBE4D EE movsx ecx,byte ptr ss:
0040142F |. 0FBE55 EF movsx edx,byte ptr ss: ; 奇数位第四位
00401433 |. 3BCA cmp ecx,edx
00401435 |. 75 07 jnz short Game.0040143E ; 跳向失败,故第三位应该等于第四位
00401437 |. B8 01000000 mov eax,1
0040143C |. EB 02 jmp short Game.00401440
0040143E |> 33C0 xor eax,eax ; 目的就是跳过这代码,使eax不等于0
00401440 |> 5F pop edi
00401441 |. 5E pop esi
00401442 |. 5B pop ebx
00401443 |. 83C4 58 add esp,58
00401446 |. 3BEC cmp ebp,esp
00401448 |. E8 73000000 call Game.004014C0 ; 没用call
0040144D |. 8BE5 mov esp,ebp
0040144F |. 5D pop ebp
00401450 \. C3 retn

这里F7进入004013D6 这个call,很关键的call,
也就是输入的要求:首先输入的数字必须满足
假码的第三位=第三位-第二位,结果加上0x30,最后的结果必须和第一位相等,然后依次循环,即a-a=a
等价于第三位=第一位+第二位
运行一次,减少一位
要运行2次,最后和4比较,故奇数位是6位
比如:
1235 call一次 变成112 ,就是后者减去前者
如果第二次就是 01

00401200 /> \55 push ebp
00401201 |. 8BEC mov ebp,esp
00401203 |. 83EC 44 sub esp,44
00401206 |. 53 push ebx
00401207 |. 56 push esi
00401208 |. 57 push edi
00401209 |. 8D7D BC lea edi,dword ptr ss:
0040120C |. B9 11000000 mov ecx,11
00401211 |. B8 CCCCCCCC mov eax,CCCCCCCC
00401216 |. F3:AB rep stos dword ptr es:
00401218 |. C745 FC 00000>mov dword ptr ss:,0
0040121F |. EB 09 jmp short Game.0040122A
00401221 |> 8B45 FC /mov eax,dword ptr ss:
00401224 |. 83C0 01 |add eax,1
00401227 |. 8945 FC |mov dword ptr ss:,eax
0040122A |> 8B4D 08 mov ecx,dword ptr ss: ; 指向奇数位假码
0040122D |. 034D FC |add ecx,dword ptr ss: ; 循环变量
00401230 |. 0FBE51 02 |movsx edx,byte ptr ds:
00401234 |. 85D2 |test edx,edx ; 读取前4个
00401236 |. 74 2C |je short Game.00401264
00401238 |. 8B45 08 |mov eax,dword ptr ss:
0040123B |. 0345 FC |add eax,dword ptr ss:
0040123E |. 0FBE08 |movsx ecx,byte ptr ds: ; 取s
00401241 |. 8B55 08 |mov edx,dword ptr ss:
00401244 |. 0355 FC |add edx,dword ptr ss:
00401247 |. 0FBE42 02 |movsx eax,byte ptr ds: ; 取s
0040124B |. 8B55 08 |mov edx,dword ptr ss:
0040124E |. 0355 FC |add edx,dword ptr ss:
00401251 |. 0FBE52 01 |movsx edx,byte ptr ds: ; 取s
00401255 |. 2BC2 |sub eax,edx ; s = s-s
00401257 |. 83C0 30 |add eax,30 ; s+0x30
0040125A |. 3BC8 |cmp ecx,eax ; ecx必须等于eax,以后依次循环
0040125C |. 74 04 |je short Game.00401262
0040125E |. 33C0 |xor eax,eax
00401260 |. EB 7E |jmp short Game.004012E0
00401262 |>^ EB BD \jmp short Game.00401221
00401264 |> 8B45 08 mov eax,dword ptr ss: ; 假码
00401267 |. 0345 FC add eax,dword ptr ss:
0040126A |. 0FBE48 01 movsx ecx,byte ptr ds: ; s
0040126E |. 8B55 08 mov edx,dword ptr ss:
00401271 |. 0355 FC add edx,dword ptr ss:
00401274 |. 0FBE02 movsx eax,byte ptr ds: ; s
00401277 |. 2BC8 sub ecx,eax
00401279 |. 83C1 30 add ecx,30
0040127C |. 8B55 08 mov edx,dword ptr ss:
0040127F |. 0355 FC add edx,dword ptr ss:
00401282 |. 0FBE42 FF movsx eax,byte ptr ds:
00401286 |. 2BC8 sub ecx,eax ; 使得ecx=0
00401288 |. 85C9 test ecx,ecx
0040128A |. 74 04 je short Game.00401290 ; 不跳就错误了
0040128C |. 33C0 xor eax,eax
0040128E |. EB 50 jmp short Game.004012E0
00401290 |> C745 FC 00000>mov dword ptr ss:,0
00401297 |. EB 09 jmp short Game.004012A2
00401299 |> 8B4D FC /mov ecx,dword ptr ss:
0040129C |. 83C1 01 |add ecx,1
0040129F |. 894D FC |mov dword ptr ss:,ecx
004012A2 |> 8B55 08 mov edx,dword ptr ss:
004012A5 |. 0355 FC |add edx,dword ptr ss:
004012A8 |. 0FBE42 01 |movsx eax,byte ptr ds:
004012AC |. 85C0 |test eax,eax
004012AE |. 74 22 |je short Game.004012D2
004012B0 |. 8B4D 08 |mov ecx,dword ptr ss:
004012B3 |. 034D FC |add ecx,dword ptr ss:
004012B6 |. 0FBE51 01 |movsx edx,byte ptr ds: ; s
004012BA |. 8B45 08 |mov eax,dword ptr ss:
004012BD |. 0345 FC |add eax,dword ptr ss:
004012C0 |. 0FBE08 |movsx ecx,byte ptr ds: ; s
004012C3 |. 2BD1 |sub edx,ecx ; s-s
004012C5 |. 83C2 30 |add edx,30 ; s+0x30
004012C8 |. 8B45 08 |mov eax,dword ptr ss:
004012CB |. 0345 FC |add eax,dword ptr ss:
004012CE |. 8810 |mov byte ptr ds:,dl ; 将计算结果覆盖之前的奇数
004012D0 |.^ EB C7 \jmp short Game.00401299
004012D2 |> 8B4D 08 mov ecx,dword ptr ss:
004012D5 |. 034D FC add ecx,dword ptr ss:
004012D8 |. C601 00 mov byte ptr ds:,0 ; 最后一位置为0,即少一位
004012DB |. B8 01000000 mov eax,1
004012E0 |> 5F pop edi
004012E1 |. 5E pop esi
004012E2 |. 5B pop ebx
004012E3 |. 8BE5 mov esp,ebp
004012E5 |. 5D pop ebp
004012E6 \. C3 retn
最后就是算法总结:
首先输入的数字必须满足
假码的第三位=第三位-第二位,结果加上0x30,最后的结果必须和第一位相等,然后依次循环,即a-a=a

即第三位-第二位=第一位,依次循环 等价于第三位=第一位+第二位
call sub_401005看懂这个函数很关键啊,运行一次,减少一位
要运行2次,最后和4比较,故奇数位6位
1235 call一次 变成112 ,就是后者减去前者
如果第二次就是 01

最后覆盖后的结果(奇数是4位)必须满足:
第一位应该大于第二位
第二位应该小于第三位
第三位应该等于第四位

看懂所有算法后,我们就可以试着猜了
第一次肯定从0开始,011235 一次10112 再一次就不对了
继续猜~~从1开始,112358 依次01123 再一次 就是1011,刚好符合题意。运行下ok。搞定~~
然后注册码就是——121222325282

--------------------------------------------------------------------------------
【经验总结】
菜鸟第一次写破文,写得很乱,自己也不怎么会,只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
经验就是需要耐心吧,OD结合到IDA用效果会更好,对有些简单算法的反汇编代码应该熟悉。

--------------------------------------------------------------------------------
【版权声明】: 本文原创于JoyChou 转载请注明作者并保持文章的完整, 谢谢!




JoyChou 发表于 2012-8-16 18:17

发重了。额

Victory.ms 发表于 2012-8-16 19:33

挽尊楼主 嘻嘻{:301_992:}
页: [1]
查看完整版本: 一个简单CM的简单算法分析