专业屏幕截图工具算法分析
初步踩点:软件无壳,VC7.0编译,单一注册码保护,注册有错误提示,未注册所抓图片有文字水印。
假设注册码为AMASAo-77nhEB-96HD2N(这里说明一下,注册码并不是一开始就假设成这样的,我一开始粗略跟踪用的假码是playboysen963852,跟踪一遍将假码修正为playbo-ysenhu-an9638,再跟踪一遍修正假码为AMASbo-ysenhu-an9638,再跟踪一次将假码修正为AMASAo-77enhu-an9638的,第五遍跟踪才将注册码修正成这样的(当然其实这已经被“修正”为真注册码了^_^)自己跟踪时可随便输入一个假码,自由练习)
尝试MessageBoxA、MessageBoxW、GetDlgItemTextA断错误提示无果,最终发现软件使用的是GetDlgItemTextW断点,汗~~~
代码:
004455DE > \53 push ebx ; Case 1 of switch 0044559C
004455DF . 55 push ebp
004455E0 . 8BAC24 380A0000 mov ebp,dword ptr ss:[esp+A38]
004455E7 . 57 push edi
004455E8 . 6A 20 push 20 ; /Count = 20 (32.)
004455EA . 8D5424 28 lea edx,dword ptr ss:[esp+28] ; |
004455EE . 52 push edx ; |Buffer
004455EF . 68 F1030000 push 3F1 ; |ControlID = 3F1 (1009.)
004455F4 . 55 push ebp ; |hWnd
004455F5 . FF15 58144800 call dword ptr ds:[<&USER32.GetDlgItemTextW>] ; \GetDlgItemTextW
004455FB . 8D4424 24 lea eax,dword ptr ss:[esp+24] ; 注册码出现
004455FF . 50 push eax ; /Arg1
00445600 . 8D4C24 18 lea ecx,dword ptr ss:[esp+18] ; |
00445604 . E8 47DAFBFF call ashsnap.00403050 ; \关键处,F7进入
00445609 . 68 4C774E00 push ashsnap.004E774C ; /Arg1 = 004E774C
0044560E . 8D4C24 20 lea ecx,dword ptr ss:[esp+20] ; |
00445612 . C78424 380A0000>mov dword ptr ss:[esp+A38],0 ; |
0044561D . E8 2EDAFBFF call ashsnap.00403050 ; \同上,关键
跟入00445604处关键算法call复制内容到剪贴板
代码:
00403050 /$ 83EC 40 sub esp,40
00403053 |. 8B4424 44 mov eax,dword ptr ss:[esp+44]
......
00403068 |. C706 00000000 mov dword ptr ds:[esi],0
0040306E |. E8 0DF50200 call ashsnap.00432580 ; kernel32.WideCharToMultiByte
00403073 |. 8B15 98204800 mov edx,dword ptr ds:[482098]
......
004030C3 |. 52 push edx
004030C4 |. E8 573A0500 call ashsnap.00456B20 ; 关键call,F7进入
004030C9 |. 83C4 20 add esp,20
跟入004030C4关键call复制内容到剪贴板
代码:
00456B20 /$ 8B4424 14 mov eax,dword ptr ss:[esp+14]
00456B24 |. 8B4C24 10 mov ecx,dword ptr ss:[esp+10]
......
00456B3F |. 55 push ebp
00456B40 |. E8 EBFDFFFF call ashsnap.00456930 ; 关键call,F7进入
00456B45 |. 83C4 14 add esp,14
00456B48 |. 84C0 test al,al
00456B4A |. 74 20 je short ashsnap.00456B6C
这里提醒大家,分析算法的时候,在比较关键的地方遇到call应该先进入大致浏览一下,否则你很可能错过关键处,导致你接下去无从下手。
好,我们接着跟入00456B40关键call复制内容到剪贴板
代码:
00456930 /$ 83EC 44 sub esp,44
00456933 |. 83C9 FF or ecx,FFFFFFFF
00456936 |. 33C0 xor eax,eax
00456938 |. 53 push ebx
00456939 |. 55 push ebp
0045693A |. 56 push esi
0045693B |. 8B7424 54 mov esi,dword ptr ss:[esp+54]
0045693F |. 57 push edi
00456940 |. 8BFE mov edi,esi ; 注册码放入EDI
00456942 |. F2:AE repne scas byte ptr es:[edi] ; 计算注册码的长度
00456944 |. F7D1 not ecx
00456946 |. 49 dec ecx
00456947 |. 83F9 14 cmp ecx,14 ; 注册码必须20位
0045694A |. 0F85 BE010000 jnz ashsnap.00456B0E
00456950 |. 8A4E 06 mov cl,byte ptr ds:[esi+6] ; 注册码第七位必须是"-"
00456953 |. B0 2D mov al,2D
00456955 |. 3AC8 cmp cl,al
00456957 |. 0F85 B1010000 jnz ashsnap.00456B0E
0045695D |. 3846 0D cmp byte ptr ds:[esi+D],al ; 注册码第十四位必须是"-"
00456960 |. 0F85 A8010000 jnz ashsnap.00456B0E
00456966 |. 6A 04 push 4
00456968 |. 8D4424 20 lea eax,dword ptr ss:[esp+20]
0045696C |. 56 push esi
0045696D |. 50 push eax
0045696E |. E8 BD24FFFF call ashsnap.00448E30 ; 提取注册码前四位
00456973 |. 8D6E 04 lea ebp,dword ptr ds:[esi+4]
00456976 |. 6A 01 push 1
00456978 |. 8D4C24 68 lea ecx,dword ptr ss:[esp+68]
0045697C |. 55 push ebp
0045697D |. 51 push ecx
0045697E |. E8 AD24FFFF call ashsnap.00448E30 ; 提取注册码第五位
00456983 |. 8D56 05 lea edx,dword ptr ds:[esi+5]
00456986 |. 6A 01 push 1
00456988 |. 8D4424 48 lea eax,dword ptr ss:[esp+48]
0045698C |. 52 push edx
0045698D |. 50 push eax
0045698E |. E8 9D24FFFF call ashsnap.00448E30 ; 提取注册码第六位("-"被忽略)
00456993 |. 8D4E 07 lea ecx,dword ptr ds:[esi+7]
00456996 |. 6A 02 push 2
00456998 |. 8D5424 38 lea edx,dword ptr ss:[esp+38]
0045699C |. 51 push ecx
0045699D |. 52 push edx
0045699E |. E8 8D24FFFF call ashsnap.00448E30 ; 提取注册码第八九位
004569A3 |. 8D46 09 lea eax,dword ptr ds:[esi+9]
004569A6 |. 6A 02 push 2
004569A8 |. 8D4C24 61 lea ecx,dword ptr ss:[esp+61]
004569AC |. 50 push eax
004569AD |. 51 push ecx
004569AE |. E8 7D24FFFF call ashsnap.00448E30 ; 提取注册码第十、十一位
004569B3 |. 8D56 0B lea edx,dword ptr ds:[esi+B]
004569B6 |. 6A 02 push 2
004569B8 |. 8D4424 54 lea eax,dword ptr ss:[esp+54]
004569BC |. 52 push edx
004569BD |. 50 push eax
004569BE |. E8 6D24FFFF call ashsnap.00448E30 ; 分离注册码第十二、十三位
004569C3 |. 83C4 48 add esp,48
004569C6 |. 8D4E 0E lea ecx,dword ptr ds:[esi+E]
004569C9 |. 8D5424 2F lea edx,dword ptr ss:[esp+2F]
004569CD |. 6A 03 push 3
004569CF |. 51 push ecx
004569D0 |. 52 push edx
004569D1 |. E8 5A24FFFF call ashsnap.00448E30 ; 分离注册码15-17位
004569D6 |. 8D46 11 lea eax,dword ptr ds:[esi+11]
004569D9 |. 6A 02 push 2
004569DB |. 8D4C24 26 lea ecx,dword ptr ss:[esp+26]
004569DF |. 50 push eax
004569E0 |. 51 push ecx
004569E1 |. E8 4A24FFFF call ashsnap.00448E30
004569E6 |. 8D56 13 lea edx,dword ptr ds:[esi+13]
004569E9 |. 6A 01 push 1
004569EB |. 8D4424 4E lea eax,dword ptr ss:[esp+4E]
004569EF |. 52 push edx
004569F0 |. 50 push eax
004569F1 |. E8 3A24FFFF call ashsnap.00448E30 ; 分离出注册码最后一位
004569F6 |. 8D4C24 50 lea ecx,dword ptr ss:[esp+50] ; 字符串组合“onh96HN”
......
00456A54 |. E8 D723FFFF call ashsnap.00448E30
00456A59 |. 8B7C24 68 mov edi,dword ptr ss:[esp+68] ; 固定字符串"sk()44$$GFSNM099023$"
00456A5D |. 8D4424 40 lea eax,dword ptr ss:[esp+40] ; 字符串组合"onh96HNA77AMAS"
00456A61 |. 57 push edi
00456A62 |. 6A 0E push 0E ; 常数14(参数之一)
00456A64 |. 50 push eax
00456A65 |. E8 46040000 call ashsnap.00456EB0 ; 截取"sk()44$$GFSNM099023$"后6位放入EDX
00456A6A |. 57 push edi
00456A6B |. 8D4C24 50 lea ecx,dword ptr ss:[esp+50]
00456A6F |. 6A 0E push 0E ; 常数14(参数之一)
00456A71 |. 51 push ecx
00456A72 |. E8 B9030000 call ashsnap.00456E30 ; 这里直接关系到00456A8E的值,关键
00456A77 |. 50 push eax
00456A78 |. 8D5424 4C lea edx,dword ptr ss:[esp+4C]
00456A7C |. 68 B0B94900 push ashsnap.0049B9B0 ; %04x
00456A81 |. 52 push edx
00456A82 |. E8 6B0AFFFF call ashsnap.004474F2
00456A87 |. 83C4 30 add esp,30
00456A8A |. 8D7C24 14 lea edi,dword ptr ss:[esp+14] ; 从注册码中提取出的值"EBD2"
00456A8E |. 8D4424 24 lea eax,dword ptr ss:[esp+24] ; 这个值是上面的关键call计算出的
00456A92 |> 8A10 /mov dl,byte ptr ds:[eax] ; 这一段循环很显然是比较上面两个值是否相等
00456A94 |. 8ACA |mov cl,dl
......
00456AAE |. 3ACB |cmp cl,bl
00456AB0 |.^ 75 E0 \jnz short ashsnap.00456A92
00456AB2 |> 33C0 xor eax,eax
00456AB4 |. EB 05 jmp short ashsnap.00456ABB
追入00456A72关键处复制内容到剪贴板
代码:
00456E30 /$ 8B5424 0C mov edx,dword ptr ss:[esp+C]
00456E34 |. 57 push edi
00456E35 |. 8BFA mov edi,edx
00456E37 |. 83C9 FF or ecx,FFFFFFFF
00456E3A |. 33C0 xor eax,eax
00456E3C |. F2:AE repne scas byte ptr es:[edi] ; 求"sk()44$$GFSNM099023$"长度
00456E3E |. F7D1 not ecx
00456E40 |. 49 dec ecx
00456E41 |. 5F pop edi
00456E42 |. 83F9 03 cmp ecx,3
00456E45 |. 72 18 jb short ashsnap.00456E5F ; 我们设"sk()44$$GFSNM099023$"为Y
00456E47 |. 0FBE42 01 movsx eax,byte ptr ds:[edx+1] ; Y的第二位 k
00456E4B |. 0FBE0A movsx ecx,byte ptr ds:[edx] ; Y的第一位 s
00456E4E |. 0FBE52 02 movsx edx,byte ptr ds:[edx+2] ; Y的第三位 (
00456E52 |. C1E0 04 shl eax,4 ; 对这三位做运算
00456E55 |. 0BC1 or eax,ecx
00456E57 |. C1E0 10 shl eax,10
00456E5A |. 0BC2 or eax,edx
00456E5C |. C1E0 03 shl eax,3 ; 到这里算出值 EAX=37980140
00456E5F |> 8B4C24 08 mov ecx,dword ptr ss:[esp+8] ; 常数14
00456E63 |. 8B5424 04 mov edx,dword ptr ss:[esp+4]
00456E67 |. 56 push esi ; 注册码值
00456E68 |. 51 push ecx
00456E69 |. 52 push edx
00456E6A |. 50 push eax ; 上面是四个参数,EAX即可上面算出的值
00456E6B |. E8 90FEFFFF call ashsnap.00456D00 ; 得出另外一个运算结果
00456E70 |. 8BC8 mov ecx,eax ; 运算结果放入ECX保存,EAX继续运算
00456E72 |. 83C4 0C add esp,0C
00456E75 |. C1E8 09 shr eax,9 ; 又是一串运算,位移、与、或、异或等等
00456E78 |. 8BD1 mov edx,ecx ; 但是不用怕,这种平铺直叙的运算我们可懒得去逆向
00456E7A |. 25 00F87F00 and eax,7FF800 ; 直接复制出来,Delphi嵌入汇编搞定
00456E7F |. 81E2 80070000 and edx,780 ; 不过说实话,这些运算基本上在高级语言中都可以直接还原的
......
00456EA5 |. 33C2 xor eax,edx
00456EA7 |. 5E pop esi
00456EA8 |. 33C1 xor eax,ecx ; 到这里,终于算完了,幸亏运算很简单
00456EAA \. C3 retn
单步走出此函数,到这里复制内容到剪贴板
代码:
......
00456AEA |. 885E 01 mov byte ptr ds:[esi+1],bl
00456AED |> 8B7424 68 mov esi,dword ptr ss:[esp+68]
00456AF1 |. 3BF3 cmp esi,ebx
00456AF3 |. 74 0F je short ashsnap.00456B04
00456AF5 |. 8D4424 10 lea eax,dword ptr ss:[esp+10] ; 一定要注意,这里是最最最关键的地方
00456AF9 |. 50 push eax ; 这是一处关键校验,验证注册码的第八、九位
00456AFA |. E8 20F1FFFF call ashsnap.00455C1F ; 我因为刚开始忽略了这里,走了很多弯路
00456AFF |. 83C4 04 add esp,4 ; 到底关键在哪里,等会单步下去你就知道了
00456B02 |. 8906 mov dword ptr ds:[esi],eax
追入00456AFA处的call复制内容到剪贴板
代码:
......
00455BDC |> 0FB60E movzx ecx,byte ptr ds:[esi] ; 注册码第八位放入
00455BDF |. 46 inc esi
00455BE0 |. 83F9 2D cmp ecx,2D ; 比较是不是“-”
00455BE3 |. 8BD1 mov edx,ecx
00455BE5 |. 74 05 je short ashsnap.00455BEC
00455BE7 |. 83F9 2B cmp ecx,2B ; 比较是不是“+”
00455BEA |. 75 04 jnz short ashsnap.00455BF0
00455BEC |> 0FB60E movzx ecx,byte ptr ds:[esi]
00455BEF |. 46 inc esi
00455BF0 |> 33C0 xor eax,eax
00455BF2 |> 83F9 30 /cmp ecx,30 ; 比较是不是数字
00455BF5 |. 7C 0A |jl short ashsnap.00455C01
00455BF7 |. 83F9 39 |cmp ecx,39
00455BFA |. 7F 05 |jg short ashsnap.00455C01
00455BFC |. 83E9 30 |sub ecx,30
00455BFF |. EB 03 |jmp short ashsnap.00455C04
00455C01 |> 83C9 FF |or ecx,FFFFFFFF ; 如果不是数字就直接跳到这里,就错误了,不信你可以试试
00455C04 |> 83F9 FF |cmp ecx,-1
00455C07 |. 74 0C |je short ashsnap.00455C15
00455C09 |. 8D0480 |lea eax,dword ptr ds:[eax+eax*4] ; [eax+eax*4]其实就是[5*eax]
00455C0C |. 8D0441 |lea eax,dword ptr ds:[ecx+eax*2] ; [ecx+eax*2]放入EAX
00455C0F |. 0FB60E |movzx ecx,byte ptr ds:[esi] ; 这两步是关键运算,所得出的EAX值会作为最终校验的关键
00455C12 |. 46 |inc esi ; 提前透露下,根据注册码第八九位运算出的EAX值应该等于4Dh
00455C13 |.^ EB DD \jmp short ashsnap.00455BF2
00455C15 |> 83FA 2D cmp edx,2D ; 比较第八位是不是“-”
00455C18 |. 5F pop edi
00455C19 |. 5E pop esi
00455C1A |. 75 02 jnz short ashsnap.00455C1E ; 对应上面的cmp指令
00455C1C |. F7D8 neg eax
00455C1E |> C3 retn
单步走出两个call可以回到我们刚才跟踪的地方复制内容到剪贴板
代码:
004030CC |. 8845 00 mov byte ptr ss:[ebp],al ; 把返回值保存备用
004030CF |. BE 241F4800 mov esi,ashsnap.00481F24 ; 固定字符串"AMAS"
004030D4 |. 8D4424 08 lea eax,dword ptr ss:[esp+8] ; 这里是注册码的前四位
004030D8 |. 53 push ebx
004030D9 |. 8DA424 00000000 lea esp,dword ptr ss:[esp]
004030E0 |> 8A10 /mov dl,byte ptr ds:[eax] ; 这个循环在做比较
004030E2 |. 8A1E |mov bl,byte ptr ds:[esi] ; 说明注册码前四位应该是"AMAS"
004030E4 |. 8ACA |mov cl,dl
......
004030FD |. 83C6 02 |add esi,2
00403100 |. 84C9 |test cl,cl
00403102 |.^ 75 DC \jnz short ashsnap.004030E0
00403104 |> 33C0 xor eax,eax
00403106 |. EB 05 jmp short ashsnap.0040310D
00403108 |> 1BC0 sbb eax,eax
0040310A |. 83D8 FF sbb eax,-1
0040310D |> 85C0 test eax,eax
0040310F |. 5B pop ebx
00403110 |. 75 16 jnz short ashsnap.00403128
00403112 |. 57 push edi
00403113 |. BF 94204800 mov edi,ashsnap.00482094 ; 这里是一个固定字符"A"
00403118 |. 8D7424 50 lea esi,dword ptr ss:[esp+50]
0040311C |. B9 02000000 mov ecx,2
00403121 |. 33C0 xor eax,eax
00403123 |. F3:A6 repe cmps byte ptr es:[edi],byte ptr ds:[esi] ; 这里是注册码的另一处校验,注册码第五位必须是“A”
00403125 |. 5F pop edi
00403126 |. 74 04 je short ashsnap.0040312C ; 这里必须跳,否则错误
00403128 |> C645 00 00 mov byte ptr ss:[ebp],0
0040312C |> 5E pop esi
单步出来到这里复制内容到剪贴板
代码:
00445622 . 8A4424 14 mov al,byte ptr ss:[esp+14]
00445626 . 84C0 test al,al
00445628 . 8B7C24 20 mov edi,dword ptr ss:[esp+20]
0044562C . 8A5C24 1C mov bl,byte ptr ss:[esp+1C]
00445630 . C68424 340A0000>mov byte ptr ss:[esp+A34],1
00445638 . 0F84 13020000 je ashsnap.00445851 ; 关键
0044563E . 8B7424 18 mov esi,dword ptr ss:[esp+18]
00445642 . 83FE 0A cmp esi,0A
00445645 . 0F85 3C010000 jnz ashsnap.00445787 ; 跳过去,就会验证最后一处
0044564B . 84DB test bl,bl
0044564D . 74 08 je short ashsnap.00445657
......
0044566D . 8D4C24 24 lea ecx,dword ptr ss:[esp+24]
00445671 . 51 push ecx ; /String2
00445672 . 68 4C774E00 push ashsnap.004E774C ; |String1 = ashsnap.004E774C
00445677 . FF15 DC124800 call dword ptr ds:[<&KERNEL32.lstrcpyW>] ; \lstrcpyW
0044567D . 68 18944800 push ashsnap.00489418 ; 提示试用期延长,即符合条件的注册码即认为是官方试用码
......
00445787 > \83FE 4D cmp esi,4D ; 这里就是在检验00455C09处得出的值,呵呵
0044578A . 0F85 C1000000 jnz ashsnap.00445851 ; 关键跳
00445790 . 8D4C24 24 lea ecx,dword ptr ss:[esp+24]
00445794 . 51 push ecx ; /String2
00445795 . 68 4C774E00 push ashsnap.004E774C ; |String1 = ashsnap.004E774C
0044579A . FF15 DC124800 call dword ptr ds:[<&KERNEL32.lstrcpyW>] ; \lstrcpyW
004457A0 . 68 78934800 push ashsnap.00489378 ; "Your key is valid. Thank you very much for buying the software. :-)"
004457A5 . 8D9424 30020000 lea edx,dword ptr ss:[esp+230]
在00445787处一个cmp指令莫名其妙的在比较一处值,我走了好多弯路就是找不到程序到底在比较什么,最后尝试数次内存断点后终于明白了这个值是从哪来的~~
到这里,算法基本完了,剩下的工作肯定是总结算法,有精力就搞个注册机
大致总结如下:
1.注册码必须20位
2.第七位和第十四位必须是”-“(连字符)
3.程序把注册码分成九份进行验证(忽略“-”连字符),大致分割为
注册码: AMASAo-77nhEB-96HD2N
分割后: AMAS A o 77 nh EB 96H D2 N
设为: K1 K2 K3 K4 K5 K6 K7 K8 9
对应关系:K1K2是“AMASA”;K6K8处的字符是00456A72处的函数(如果懒得去逆向,可以直接复制汇编代码从而Delphi或者C++嵌入汇编)计算出的,其值取决于K3K5K7K9K4K1;K4处的两个字符经过00455C09处的计算结果应该为4Dh。
一组可用注册码AMASAo-77nhEB-96HD2N,有兴趣可以试一试~~
正确注册后,软件将注册码明码保存在以下位置(方便大家多次调试):复制内容到剪贴板
代码:
Windows Registry Editor Version 5.00
[HKEY_CURRENT_USER\Software\Ashampoo\Ashampoo Magical Snap 2]
"RegistrationKey"="AMASAo-77nhEB-96HD2N"
[HKEY_LOCAL_MACHINE\SOFTWARE\Ashampoo\Ashampoo Magical Snap 2]
"RegistrationKey"="AMASAo-77nhEB-96HD2N"