【文章标题】: 'RSACrackMe512分析过程及汇编注册机源码
【文章作者】: coolstar14
【使用工具】: IDA, BigInterCalc, RDLP
【软件名称】: RSACrackMe512
【软件大小】: 40KB
【下载地址】: http://bbs.pediy.com/attachment.php?attachmentid=7447&d=1187671766
【软件介绍】: 用RSA512作为校验算法的CrackMe。
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
详细过程:
IDA反汇编, 字符串参考, 直接定位到关键代码:
.text:00402F18
.text:00402F18 loc_402F18: ; CODE XREF: DialogFunc+140j
.text:00402F18 lea eax, [ebp+dst_buffer]
.text:00402F1E lea ecx, [ebp+rsa_n]
.text:00402F24 push eax ; dst_buffer
.text:00402F25 push ecx ; rsa_n
.text:00402F26 lea edx, [ebp+input_sn]
.text:00402F2C push offset s_10001 ; "10001"
.text:00402F31 push edx ; input_sn
.text:00402F32 mov [ebp+var_4], 0
.text:00402F39 call sub_402BC0 ;关键函数, 跟进
.text:00402F39
.text:00402F3E test eax, eax
.text:00402F40 jnz short loc_402F68 ; eax不等于0, 跳转到后续判断, 否则就提示失败
.text:00402F40
.text:00402F42 push 40h ; uType
.text:00402F44 push offset Caption ; "注册提示"
.text:00402F44
.text:00402F49
.text:00402F49 loc_402F49: ; CODE XREF: DialogFunc+1D8j ;注册提示
.text:00402F49 push offset s_VSIAGm ; "注册码错误,继续加油!"
....
.text:00402F64 mov esp, ebp
.text:00402F66 pop ebp
.text:00402F67 retn
.text:00402F67
.text:00402F68 ; ---------------------------------------------------------------------------
.text:00402F68
.text:00402F68 loc_402F68: ; CODE XREF: DialogFunc+190j
.text:00402F68 lea eax, [ebp+dst_buffer]
.text:00402F6E lea ecx, [ebp+String2]
.text:00402F74 push eax ; lpString1
.text:00402F75 push ecx ; lpString2
.text:00402F76 call sub_402CE0 ;比较函数, 可以通过内存查看, eax, ecx分别为解密sn后获得的缓冲区和我们输入的用户名
.text:00402F76
.text:00402F7B add esp, 8
.text:00402F7E cmp eax, 1
.text:00402F81 push 40h ; uType
.text:00402F83 push offset Caption ; "注册提示"
.text:00402F88 jnz short loc_402F49 ;前面比较函数返回值 eax不等于1跳转到失败, 否则注册成功.
.text:00402F88
.text:00402F8A push offset s_ZUgmVSI ; "恭喜你,注册码正确!"
.text:00402F8F push esi ; hWnd
.text:00402F90 call ds:MessageBoxA
.text:00402F96 xor eax, eax
函数 402F39, 参数4个, 通过查看内存可以确定, 有一个为我们输入的序列号, 一个为10001的字符串, 它是RSA公钥对中最常用的e , 还有一个长度为128的字符串, 这个很容易就可以怀疑它是RSA公钥对中的n. 另外一个会在下面的比较函数中用它, 它就是结果输出缓冲区了.
函数:sub_402BC0
.text:00402BC0 ; Attributes: bp-based frame
.text:00402BC0
.text:00402BC0 ; int __stdcall sub_402BC0(int input_sn,int rsa_e_10001,int rsa_n,LPSTR dst_buffer)
.text:00402BC0 sub_402BC0 proc near ; CODE XREF: DialogFunc+189p
.text:00402BC0
.text:00402BC0 String2 = byte ptr -2088h
.text:00402BC0 var_88 = dword ptr -88h
.text:00402BC0 var_big_e = dword ptr -64h
.text:00402BC0 var_big_n = dword ptr -44h
.text:00402BC0 var_big_sn = dword ptr -20h
.text:00402BC0 var_10 = dword ptr -10h
.text:00402BC0 var_C = dword ptr -0Ch
.text:00402BC0 var_4 = dword ptr -4
.text:00402BC0 input_sn = dword ptr 8
.text:00402BC0 rsa_e_10001 = dword ptr 0Ch
.text:00402BC0 rsa_n = dword ptr 10h
.text:00402BC0 dst_buffer = dword ptr 14h
.text:00402BC0
.text:00402BC0 push ebp
.text:00402BC1 mov ebp, esp
.text:00402BC3 push 0FFFFFFFFh
.text:00402BC5 push offset loc_406973
.text:00402BCA mov eax, large fs:0
.text:00402BD0 push eax
.text:00402BD1 mov large fs:0, esp
.text:00402BD8 push ecx
.text:00402BD9 mov eax, 2078h
.text:00402BDE call sub_4034A0
.text:00402BDE
.text:00402BE3 push ebx
.text:00402BE4 push esi
.text:00402BE5 push edi
.text:00402BE6 lea ecx, [ebp+var_88]
.text:00402BEC mov [ebp+var_10], esp
.text:00402BEF call sub_402900
.text:00402BEF
.text:00402BF4 xor ebx, ebx
.text:00402BF6 lea ecx, [ebp+var_big_sn]
.text:00402BF9 mov [ebp+var_4], ebx
.text:00402BFC call sub_401060
.text:00402BFC
.text:00402C01 mov eax, [ebp+rsa_e_10001]
.text:00402C04 lea ecx, [ebp+var_big_e]
.text:00402C07 push eax
.text:00402C08 mov byte ptr [ebp+var_4], 2
.text:00402C0C call sub_4028F0 ; 大数转换, 只有一个参数为大数的字符串表示. 之前有几个函数调用, 像是初始化之类的东西, 具体没弄明白.
.text:00402C0C
.text:00402C11 mov ecx, [ebp+rsa_n]
.text:00402C14 push ecx
.text:00402C15 lea ecx, [ebp+var_big_n]
.text:00402C18 call sub_4028F0
.text:00402C18
.text:00402C1D mov edx, [ebp+input_sn]
.text:00402C20 lea ecx, [ebp+var_big_sn]
.text:00402C23 push edx
.text:00402C24 call sub_4028F0
.text:00402C24
.text:00402C29 mov ecx, 800h
.text:00402C2E xor eax, eax
.text:00402C30 lea edi, [ebp+String2]
.text:00402C36 rep stosd
.text:00402C38 lea eax, [ebp+var_big_sn]
.text:00402C3B lea ecx, [ebp+String2]
.text:00402C41 push eax
.text:00402C42 push 2000h
.text:00402C47 push ecx
.text:00402C48 lea ecx, [ebp+var_88]
.text:00402C4E call sub_402A40 ;大数转换完, 下一步自然是计算, 这个函数很麻烦, 需要跟进, 验证序列号有一部分在这里面.
.text:00402C4E
.text:00402C53 mov eax, [ebp+dst_buffer]
.text:00402C56 lea edx, [ebp+String2]
.text:00402C5C push edx ; lpString2
.text:00402C5D push eax ; lpString1
.text:00402C5E call ds:lstrcpyA ;将结果复制到目的缓冲区, 可以由这儿中断, 然后直接修改内存使eax指向内在为我们输入的用户名, 继续后可以看到, 会提示注册成功, 所以后面调用的函数我们就可以不用关心了.
.text:00402C64 lea ecx, [ebp+var_big_sn]
...............
.text:00402C96
.text:00402C96 sub_402BC0 endp
IDA有个很实用的功能, 它可以让你修改参数名字, 它会在整个函数体里统一替换, 在自己确定了某个参数和变量的含义后可以修改它为好记的名字, 方便之后的阅读和理解. 上面的函数的参数为我跟踪替换后的样子,比最初版本肯定是要好些的.
回到分析上, 在大数的处理上, 不清楚这个CM用的什么大数库或者为自己写的吧. 上面瓢的大数的参数, 比如说var_big_e, 是个内存地址, 其+4后的地址指向malloc出来存放转换出来大数的地址.
函数:sub_402A40 其中
.text:00402A78
.text:00402A7D lea edi, [esi+44h]
.text:00402A80 add esi, 24h
.text:00402A83 push edi ; big_n
.text:00402A84 push esi ; big_e_10001
.text:00402A85 lea ecx, [esp+38h+var_AfterDeBuff]
.text:00402A89 push ebx ; big_sn
.text:00402A8A push ecx ; out is powmod=sn^10001 (mod n)
.text:00402A8B call sub_402670
通过查看 402670 调用前后各参数地址的变化, 发现只有ecx指向的缓冲区有变化, 使用 BigIntCalc代入各参数计算, 发现ecx的结果恰巧是powmod操作. 由此可以确定第一步, 这个cm是把序列号转为大数, 用公钥解密了的. 那么没有私钥好像是不能继续下去了, 分解512 好像很花时间, 我们采用替换公钥对n的方法继续下面的分析, IDA 查看字符串参数, 找到公钥串所在偏移 4080e0, 相对文件偏移80e0(ida可以在状态栏看到的), Ultraedit打开exe, 直接定位并使用我们自己产生的公钥对中的n替换它. 这样我们就可得到一个知道私钥的修改版本.
重新截入修改后的版本, 在402a8b下断, 使用私钥加密我们输入的用户名, 加密后的结果做为序列号输入, 点击注册, 断下, 单步运行一下, 可以看到ecx指向的大数, 加密前的用户名的逆序. ecx为指针, 它指向的大数实际地址为其地址+4, 然后再做为地址, 如:
0012d508 14 71 40 00 70 05 8f 00 则其指向的大数实际为8f0570, 查看这个地址, 可以看到调用402670前为我们输入的序列号, 调用后变为了我们输入的用户名的逆序. 本来以为这样应该算是注册了, 结果运行仍然弹出注册失败. 看来并不是简单的用户名私钥加密形成序列号. 继续..
mov eax, [esp+30h+arg_2000]
lea edx, [esp+30h+var_AfterDeBuff]
push 2
push edx ; addr after powmod
push eax
push ecx ; addr String2
call sub_402B10
402b10, 它有个参数为我们期望的外层比较函数将要使用的地址, 跟进它.
402b10 调用 4024a0, 这个函数对解密后的数据做了进一步的判断, 如果不符合要求, 就返回一个 负数, 解密出来的内容并不会复制到我们期望的String2的地址中, 这个函数我没分析出来, 后来在密码学CM主帖中看到一个RSA1024有效的注册码, RSA1024那个CM与512这个反汇编后除了公钥n长度不一致外其它倒是一样. 所以看到powmod后的地址的结果, 从而猜测出解密后数据应该的内容继而得出4024a0函数各参数的意义和作用.
4024a0, 它判断了解密后数据的长度, 然后符合长度限制的它复制了指定偏移指定位的数据到目的地址. 其4个参数分别为:
源地址, 目的地址, 偏移位置, 复制字节数.
402b10 3次调用4024a0, 分别做了如下操作, 1复制并检查powmod解密后数据倒数第二个字节为2, 2复制获得powmod解密后数据倒数3,4字节做为有效数据长度xlen, 3以第二步取到长度复制偏移0长度xlen的数据到目的缓冲区.
最后, 可以看到sn产生的办法, 取这样一个串, user 256-4-ulen的任意字符 两字节ulen 02 任意结尾. 最后将这个串逆序 得到 加密前原文, 该串以私钥加密即得到序列号. 因为填充字符的存在, 所以相同用户名是可以有很多匹配的序列号的.
结束:
验证用工具使用了 readyu 大侠提供的, RDLP, BigInterCalc.
下载位置:
http://bbs1.pediy.com/showthread.php?t=47934&highlight=RDLP
http://bbs1.pediy.com/showthread.php?t=49005&highlight=RDLP
附一组修改公钥n后验证成功的注册码
替换的公钥n:
C1E8FDD98735103BCCEC2032A319FB8ADB1E79D5663486116CF54EE96CE3CB8988974330DEBB3639DC41C0FC0680F0549D94E90410D1B01A58E9D0F265008FD9
加密用私钥d:
41A1C0B9EDBF921D0B81286CBB33C225FF8053305D858D933C53D33FC2B15F6437E7D80333B01EE6DF23D3BF378F6FAAF366BC5398CF9E74B4E6836174894A59
加密前源串:
01020a00FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFeeFFFFFFFFFFFFFFFFFF00636F6F6C737461723134
用户名:coolstar14
序列号:5BC6E01B6C7D34455FDB584A6B5352187CF9ECCB29FD28E4FFFF3FB33D4A6B27191285FFB23451FA9DDA95EA44909D2B9D54BBC77841F668CD9F6F3BC266131F
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!