用OllyICE打开 FunnyKeyGenMe,F9运行,随便输入Name和SN,提示“Wrong Serial!”。查找所有参考文本字串,很容易找到对Name和SN进

行判断的地方。整段语句如下:

004010A0 /$ 81EC 58020000  sub esp, 258
004010A6 |. 53       push ebx
004010A7 |. 55       push ebp
004010A8 |. 56       push esi
004010A9 |. 57       push edi
004010AA |. 6A 10      push 10
004010AC |. 68 20030000   push 320
004010B1 |. E8 EA080000   call 004019A0
004010B6 |. 8BD8      mov ebx, eax
004010B8 |. B9 31000000   mov ecx, 31
004010BD |. 33C0      xor eax, eax
004010BF |. 8DBC24 A90100> lea edi, dword ptr [esp+1A9]
004010C6 |. C68424 A80100> mov byte ptr [esp+1A8], 0
004010CE |. C64424 18 00  mov byte ptr [esp+18], 0
004010D3 |. F3:AB      rep stos dword ptr es:[edi]
004010D5 |. 66:AB      stos word ptr es:[edi]
004010D7 |. AA       stos byte ptr es:[edi]
004010D8 |. B9 31000000   mov ecx, 31
004010DD |. 33C0      xor eax, eax
004010DF |. 8D7C24 19    lea edi, dword ptr [esp+19]
004010E3 |. C68424 E00000> mov byte ptr [esp+E0], 0
004010EB |. F3:AB      rep stos dword ptr es:[edi]
004010ED |. 66:AB      stos word ptr es:[edi]
004010EF |. AA       stos byte ptr es:[edi]
004010F0 |. B9 31000000   mov ecx, 31
004010F5 |. 33C0      xor eax, eax
004010F7 |. 8DBC24 E10000> lea edi, dword ptr [esp+E1]
004010FE |. 8B35 B8B04000  mov esi, dword ptr [<&USER32.GetDlgI> ; USER32.GetDlgItemTextA
00401104 |. F3:AB      rep stos dword ptr es:[edi]
00401106 |. 66:AB      stos word ptr es:[edi]
00401108 |. 83C4 08     add esp, 8
0040110B |. AA       stos byte ptr es:[edi]
0040110C |. 8BBC24 6C0200> mov edi, dword ptr [esp+26C]
00401113 |. 8D8424 A00100> lea eax, dword ptr [esp+1A0]
0040111A |. 68 C9000000   push 0C9                ; /Count = C9 (201.)
0040111F |. 50       push eax                ; |Buffer
00401120 |. 68 E8030000   push 3E8                ; |ControlID = 3E8 (1000.)
00401125 |. 57       push edi                ; |hWnd
00401126 |. FFD6      call esi                ; \GetDlgItemTextA
00401128 |. 85C0      test eax, eax
0040112A |. 75 1E      jnz short 0040114A

上面这个 GetDlgItemTextA 是用来获取Name的,Name的长度最大为201,并对Name的长度进行判断,如果为0,则显示错误信息,如果不为

0,则跳转至 0040114A。

0040112C |. 68 E8C04000   push 0040C0E8             ; /Text = "Wrong Serial!"
00401131 |. 68 E9030000   push 3E9                ; |ControlID = 3E9 (1001.)
00401136 |. 57       push edi                ; |hWnd
00401137 |. FF15 BCB04000  call dword ptr [<&USER32.SetDlgItemTe> ; \SetDlgItemTextA
0040113D |. 5F       pop edi
0040113E |. 5E       pop esi
0040113F |. 5D       pop ebp
00401140 |. 33C0      xor eax, eax
00401142 |. 5B       pop ebx
00401143 |. 81C4 58020000  add esp, 258
00401149 |. C3       retn
0040114A |> 8D4C24 10    lea ecx, dword ptr [esp+10]
0040114E |. 68 C9000000   push 0C9                ; /Count = C9 (201.)
00401153 |. 51       push ecx                ; |Buffer
00401154 |. 68 E9030000   push 3E9                ; |
00401159 |. 57       push edi                ; |hWnd
0040115A |. FFD6      call esi                ; \GetDlgItemTextA
0040115C |. 85C0      test eax, eax
0040115E |. 75 1E      jnz short 0040117E

上面这个GetDlgItemTextA是获取SN的,最大长度也为201,与前面对Name的长度判断一样,也对其长度进行检验,如果为0,则出错,如果不

为0,则跳至 0040117E。比较奇怪的是,在OllyICE中,这个GetDlgItemTextA并没有像前面获取Name那样明显表示出来,只有简单的push和

call语句,“;”及后面的注释,是我自己添加的。

00401160 |> 68 E8C04000   push 0040C0E8             ; /Text = "Wrong Serial!"
00401165 |. 68 E9030000   push 3E9                ; |ControlID = 3E9 (1001.)
0040116A |. 57       push edi                ; |hWnd
0040116B |. FF15 BCB04000  call dword ptr [<&USER32.SetDlgItemTe> ; \SetDlgItemTextA
00401171 |. 5F       pop edi
00401172 |. 5E       pop esi
00401173 |. 5D       pop ebp
00401174 |. 33C0      xor eax, eax
00401176 |. 5B       pop ebx
00401177 |. 81C4 58020000  add esp, 258
0040117D |. C3       retn
0040117E |> 8A4424 10    mov al, byte ptr [esp+10]

注意这里,[esp+10]其实就是SN的开始,下面紧跟着的[esp+10]也是SN的起始。

00401182 |. 84C0      test al, al
00401184 |. 74 3D      je short 004011C3
00401186 |. 8D7424 10    lea esi, dword ptr [esp+10]
0040118A |> 833D 64CF4000> /cmp dword ptr [40CF64], 1
00401191 |. 7E 13      |jle short 004011A6
00401193 |. 0FBE16     |movsx edx, byte ptr [esi]
00401196 |. 68 80000000   |push 80
0040119B |. 52       |push edx
0040119C |. E8 AF590000   |call 00406B50
004011A1 |. 83C4 08     |add esp, 8
004011A4 |. EB 11      |jmp short 004011B7
004011A6 |> 0FBE06     |movsx eax, byte ptr [esi]
004011A9 |. 8B0D 58CD4000  |mov ecx, dword ptr [40CD58]      ; FunnyKey.0040CD62
004011AF |. 8A0441     |mov al, byte ptr [ecx+eax*2]
004011B2 |. 25 80000000   |and eax, 80
004011B7 |> 85C0      |test eax, eax
004011B9 |.^ 74 A5     |je short 00401160
004011BB |. 8A46 01     |mov al, byte ptr [esi+1]
004011BE |. 46       |inc esi
004011BF |. 84C0      |test al, al
004011C1 |.^ 75 C7     \jnz short 0040118A

上面这段循环,是用了相对比较复杂的方式对SN的组成进行了判断。ecx的值为 0040CD62,而eax则是SN的每个字符。以0040CD62起始,其附

近的数据如下:

0040CD62 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 . . . . . . . .
0040CD72 20 00 28 00 28 00 28 00 28 00 28 00 20 00 20 00 .(.(.(.(.(. . .
0040CD82 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 . . . . . . . .
0040CD92 20 00 20 00 20 00 20 00 20 00 20 00 20 00 20 00 . . . . . . . .
0040CDA2 48 00 10 00 10 00 10 00 10 00 10 00 10 00 10 00 H........
0040CDB2 10 00 10 00 10 00 10 00 10 00 10 00 10 00 10 00 ........
0040CDC2 84 00 84 00 84 00 84 00 84 00 84 00 84 00 84 00 ????????
0040CDD2 84 00 84 00 10 00 10 00 10 00 10 00 10 00 10 00 ??......
0040CDE2 10 00 81 00 81 00 81 00 81 00 81 00 81 00 01 00 .??????.
0040CDF2 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 ........
0040CE02 01 00 01 00 01 00 01 00 01 00 01 00 01 00 01 00 ........
0040CE12 01 00 01 00 01 00 10 00 10 00 10 00 10 00 10 00 ........
0040CE22 10 00 82 00 82 00 82 00 82 00 82 00 82 00 02 00 .??????.
0040CE32 02 00 02 00 02 00 02 00 02 00 02 00 02 00 02 00 ........
0040CE42 02 00 02 00 02 00 02 00 02 00 02 00 02 00 02 00 ........
0040CE52 02 00 02 00 02 00 10 00 10 00 10 00 10 00 20 00 ....... .
0040CE62 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040CE72 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................
0040CE82 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 ................

根据这段数据,结合上面的语句可以看出,只有当eax的值为0x30-0x39,0x41-0x46或0x61-0x66时,[ecx+eax*2] and 0x80的值才不为0,否

则会跳转至显示错误信息语句。其实,这就意味着SN的组成必须是数字0-9,及字母A-F或字母a-f。如果敏感一点儿,就应该感觉到SN是一个

16进制的数字。

004011C3 |> 6A 00      push 0
004011C5 |. C783 34020000> mov dword ptr [ebx+234], 10
004011CF |. E8 AC050000   call 00401780
004011D4 |. 6A 00      push 0
004011D6 |. 8BF0      mov esi, eax
004011D8 |. E8 A3050000   call 00401780
004011DD |. 6A 00      push 0
004011DF |. 8BE8      mov ebp, eax
004011E1 |. E8 9A050000   call 00401780
004011E6 |. 6A 00      push 0
004011E8 |. 8BF8      mov edi, eax
004011EA |. E8 91050000   call 00401780
004011EF |. 8D5424 20    lea edx, dword ptr [esp+20]
004011F3 |. 8BD8      mov ebx, eax
004011F5 |. 52       push edx
004011F6 |. 57       push edi
004011F7 |. E8 A4270000   call 004039A0

注意上面的edx,其实就是SN。

004011FC |. 68 44C04000   push 0040C044             ; ASCII

"AE5BB4F266003259CF9A6F521C3C03410176CF16DF53953476EAE3B21EDE6C3C7B03BDCA20B31C0067FFA797E4E910597873EEF113A60FECCD95DEB5B2

BF10066BE2224ACE29D532DC0B5A74D2D006F1"

00401201 |. 56       push esi
00401202 |. E8 99270000   call 004039A0
00401207 |. 68 3CC04000   push 0040C03C             ; ASCII "10001"
0040120C |. 55       push ebp
0040120D |. E8 8E270000   call 004039A0

到这里,可能应该看出一些端倪,0x10001,这是很熟悉的东西,RSA算法中常用的公钥。再考虑到的上面的几条语句把edx即SN作为参数,然

后把[0040C044]处很长的一串字符作为参数,把"10001"作为参数,调用的都是同一个函数004039A0,因此推测这里是对SN、[0040C044]对应

的长字符和"10001"进行了初始化。[0040C044]对应的长字符其实就是RSA算法中的两个大质数之积n。并且,根据SN、n及0x10001最终计算出

一个值V,V = (SN^0x10001) mod n 。

00401212 |. 56       push esi
00401213 |. 57       push edi
00401214 |. E8 67140000   call 00402680
00401219 |. 83C4 30     add esp, 30
0040121C |. 83F8 FF     cmp eax, -1
0040121F |. 0F85 A2000000  jnz 004012C7
00401225 |. 53       push ebx
00401226 |. 56       push esi
00401227 |. 55       push ebp
00401228 |. 57       push edi
00401229 |. E8 42210000   call 00403370
0040122E |. 8D8424 E80000> lea eax, dword ptr [esp+E8]
00401235 |. 6A 00      push 0
00401237 |. 50       push eax
00401238 |. 53       push ebx
00401239 |. 6A 00      push 0
0040123B |. E8 E01E0000   call 00403120
00401240 |. 56       push esi
00401241 |. E8 0A0F0000   call 00402150
00401246 |. 55       push ebp
00401247 |. E8 040F0000   call 00402150
0040124C |. 57       push edi
0040124D |. E8 FE0E0000   call 00402150
00401252 |. 53       push ebx
00401253 |. E8 F80E0000   call 00402150
00401258 |. 83C4 30     add esp, 30
0040125B |. E8 100F0000   call 00402170
00401260 |. 8D8C24 D80000> lea ecx, dword ptr [esp+D8]
00401267 |. 8D9424 A00100> lea edx, dword ptr [esp+1A0]
0040126E |. 51       push ecx                ; /String2
0040126F |. 52       push edx                ; |String1
00401270 |. FF15 00B04000  call dword ptr [<&KERNEL32.lstrcmpA>] ; \lstrcmpA

上面是最后的判断,[esp+D8]及ecx是由SN,n,和0x10001计算出来的值 V 所对应的一串字符,[esp+1A0]及edx则是Name,当两者相等时,

则判断Name和SN是正确的。或者,换一个说法,就是Name的每个字符都使用16进制ASCII值表示,并联接在一起成为一个16进制数字,该数字

与 (SN^0x10001) mod n 相等。

00401276 |. 85C0      test eax, eax
00401278 |. 74 25      je short 0040129F
0040127A |. 8B8424 6C0200> mov eax, dword ptr [esp+26C]
00401281 |. 68 E8C04000   push 0040C0E8             ; /Text = "Wrong Serial!"
00401286 |. 68 E9030000   push 3E9                ; |ControlID = 3E9 (1001.)
0040128B |. 50       push eax                ; |hWnd
0040128C |. FF15 BCB04000  call dword ptr [<&USER32.SetDlgItemTe> ; \SetDlgItemTextA
00401292 |. 5F       pop edi
00401293 |. 5E       pop esi
00401294 |. 5D       pop ebp
00401295 |. 33C0      xor eax, eax
00401297 |. 5B       pop ebx
00401298 |. 81C4 58020000  add esp, 258
0040129E |. C3       retn
0040129F |> 8B8C24 6C0200> mov ecx, dword ptr [esp+26C]
004012A6 |. 68 30C04000   push 0040C030             ; /Text = "Success!"
004012AB |. 68 E9030000   push 3E9                ; |ControlID = 3E9 (1001.)
004012B0 |. 51       push ecx                ; |hWnd
004012B1 |. FF15 BCB04000  call dword ptr [<&USER32.SetDlgItemTe> ; \SetDlgItemTextA
004012B7 |. 5F       pop edi
004012B8 |. 5E       pop esi
004012B9 |. 5D       pop ebp
004012BA |. B8 01000000   mov eax, 1
004012BF |. 5B       pop ebx
004012C0 |. 81C4 58020000  add esp, 258
004012C6 |. C3       retn
004012C7 |> 8B9424 6C0200> mov edx, dword ptr [esp+26C]
004012CE |. 68 E8C04000   push 0040C0E8             ; /Text = "Wrong Serial!"
004012D3 |. 68 E9030000   push 3E9                ; |ControlID = 3E9 (1001.)
004012D8 |. 52       push edx                ; |hWnd
004012D9 |. FF15 BCB04000  call dword ptr [<&USER32.SetDlgItemTe> ; \SetDlgItemTextA
004012DF |. 5F       pop edi
004012E0 |. 5E       pop esi
004012E1 |. 5D       pop ebp
004012E2 |. 33C0      xor eax, eax
004012E4 |. 5B       pop ebx
004012E5 |. 81C4 58020000  add esp, 258
004012EB \. C3       retn

几个问题:

1、0040118A | 833D 64CF4000> cmp dword ptr [40CF64], 1

这个语句的作用以及[40CF64]的值是如何得来的,我没有仔细分析,只是在两台电脑上实验了一下,值都是1。

2、下面这段语句:

00401214 |. E8 67140000   call 00402680
00401219 |. 83C4 30     add esp, 30
0040121C |. 83F8 FF     cmp eax, -1
0040121F |. 0F85 A2000000  jnz 004012C7

该段语句是根据00402680的返回值与-1的比较,来判断SN是否正确的,我没有仔细分析00402680这个函数,只是在两台电脑上实验发现,返

回值都是-1,不知函数00402680的作用是什么。

3、这种应用了某种密码学进行序列号判断的程序,往往调用了大量的复杂函数,而且很难分析出每个函数的具体功能。比如本例,只是采用

了RSA算法的基本运算,因此我是根据RSA的算法来推测Name和SN的判定方法的,并不是对每个函数进行了跟踪和分析。但如果是对Name或

SN的某种变形进行运算,再判断;或先运算,再变形,然后判断,则难度应该是大大增加。此时如何正确判定那些是关键函数,都分别进行

了何种运算,是基本的密码学运算还是进行了某些变形,我现在仍未理出头绪。

4、本例的Name和SN判定方法分析,我在两台电脑上进行了实验,应该是基本正确。可是,如果要写出注册机,则应该对n进行分解才行,找

到私钥,然后可以根据Name对应的16进制数字、私钥以及n来计算SN。我使用了RSA工具,可是没能分解n,不知是不是我的方法有误。

破文写得不怎么样,问题却一大堆,还请各位朋友多多指点。

谢谢。

(附:FunnyKeyGenMe

 

曹华 2007年9月4日

补充:

1、感谢“看雪学院”网友“坚持到底”的指点,上面提到的第二个问题,该段语句:

00401212 |. 56       push esi
00401213 |. 57       push edi
00401214 |. E8 67140000   call 00402680
00401219 |. 83C4 30     add esp, 30
0040121C |. 83F8 FF     cmp eax, -1
0040121F |. 0F85 A2000000  jnz 004012C7

是对SN和大数n进行判断,如果SN小于n,则可以继续。注意,esi对应的是初始化后的n,edi是初始化后的SN。

2、感谢复旦日月光华网友doublelee的指点。第一个问题中,[40CF64]的值应该是固定为1的,是在exe文件的CF64处。而关于那个大数n,在

www.rsa.com 中有介绍,竟然是个悬赏 US$ 20,000 的东东,难怪无法自己用RSA工具进行分解。相关链接如下:

http://www.rsa.com/rsalabs/node.asp?id=2093 http://www.rsa.com/rsalabs/node.asp?id=2964

那个大数n对应的质数因子p、q应分别为:

1634733645809253848443133883865090859841783670033092312181110852389333100104508151212118167511579 (十进制)

1900871281664822113126851573935413975471896789968515493666638539088027103802104498957191261465571 (十进制)

n为:

3107418240490043721350750035888567930037346022842727545720161948823206440518081504556346829671723286
782437916272838033415471073108501919548529007337724822783525742386454014691736602477652346609 (十进制)

AE5BB4F266003259CF9A6F521C3C03410176CF16DF53953476EAE3B21EDE6C3C7B03BDCA20B31C0067FFA797E4E910597873 EEF113A60FECCD95DEB5B2BF10066BE2224ACE29D532DC0B5A74D2D006F1 (十六进制)