【文章标题】: ReHPer KeyGenMe v1.0分析
【文章作者】: 隐者无疆[BCG]
【作者邮箱】: xxx@yahoo.com
【作者主页】: hxxp://www.xxxx.com
【软件名称】: ReHPer KeyGenMe v1.0
【软件大小】: 372K
【下载地址】: http://www.crackmes.de
【加壳方式】: 无壳
【保护方式】: Name+SN
【编写语言】: Borland Delphi
【使用工具】: OD,IDA,DeDe
【操作平台】: Windows
【软件介绍】: 2/10 - Needs a little brain (or luck)
【作者声明】: 我是一只小菜鸟,偶有所得,愿与大家分享。
--------------------------------------------------------------------------------
【详细过程】
偶然间,在crackmes.de上看到了这个CM,发现作者的思路有独到之处。现公布我的分析,不当之处欢迎指正。
1.Peid侦壳,显示“Borland Delphi 6.0 - 7.0”。
2.用Dede分析,找到check按钮对应的代码;IDA分析该段代码。
3.贴出关键代码如下:
.text:00454D0A mov [ebp+NameOpRst], 1 ; initialize
.text:00454D11 lea edx, [ebp+szName]
.text:00454D14 mov eax, [ebp+hForm1]
.text:00454D17 mov eax, [eax+36Ch]
.text:00454D1D call Controls::TControl::GetText(void)
.text:00454D1D
.text:00454D22 mov eax, [ebp+szName]
.text:00454D25 lea edx, [ebp+szName_Lower]
.text:00454D28 call Sysutils::LowerCase(System::AnsiString)
.text:00454D28
.text:00454D2D mov eax, [ebp+szName_Lower]
.text:00454D30 test eax, eax
.text:00454D32 jz short NullName
.text:00454D32
.text:00454D34 sub eax, 4
.text:00454D37 mov eax, [eax] ; eax<-- NameLen
.text:00454D37
.text:00454D39
.text:00454D39 NullName: ; CODE XREF: Button1Click+56j
.text:00454D39 add al, 2 ;注意这个地方,用户名的长度加2,这里很关键
.text:00454D3B test al, al
.text:00454D3D jbe short _text_454D60
.text:00454D3D
.text:00454D3F mov dl, 1
.text:00454D3F
.text:00454D41
.text:00454D41 GoOn: ; CODE XREF: Button1Click+82j
.text:00454D41 movzx ecx, dl ;\ 这一部分代码用于处理输入的Name 以及
.text:00454D44 mov ebx, [ebp+szName_Lower] ;| 内存中Name字符串后面的两个字节
.text:00454D47 movzx ecx, byte ptr [ebx+ecx-1] ;| 作者在这里给我们等着我们
.text:00454D4C add [ebp+NameOpRst], ecx ;| 关于这一部分,下面有具体分析
.text:00454D4F mov ecx, [ebp+NameOpRst] ;|
.text:00454D52 ror ecx, 6 ;| 通过这个循环的计算,得到一个整数值
.text:00454D55 add ecx, 1 ;| 记作 val
.text:00454D58 mov [ebp+NameOpRst], ecx ;|
.text:00454D5B inc edx ;|
.text:00454D5C dec al ;|
.text:00454D5E jnz short GoOn ;/
.text:00454D5E
.text:00454D60
.text:00454D60 _text_454D60: ; CODE XREF: Button1Click+61j
.text:00454D60 not [ebp+NameOpRst]
.text:00454D63 xor eax, eax
.text:00454D65 pop edx
.text:00454D66 pop ecx
.text:00454D67 pop ecx
.text:00454D68 mov fs:[eax], edx
.text:00454D6B jmp short GetText2
。。。
.text:00454DA1 GetText2: ; CODE XREF: Button1Click+8Fj
.text:00454DA1 lea edx, [ebp+szText2]
.text:00454DA4 mov eax, [ebp+hForm1]
.text:00454DA7 mov eax, [eax+370h]
.text:00454DAD call Controls::TControl::GetText(void)
.text:00454DAD
.text:00454DB2 mov eax, [ebp+szText2]
.text:00454DB5 push eax
.text:00454DB6 mov eax, [ebp+NameOpRst] ;前面计算得到的val
.text:00454DB9 xor edx, edx
.text:00454DBB push edx
.text:00454DBC push eax
.text:00454DBD lea edx, [ebp+HexVal] ;val十六进制值对应的字符串,记作sn1
.text:00454DC0 mov eax, 8
.text:00454DC5 call Sysutils::IntToHex(__int64,int)
.text:00454DC5
.text:00454DCA lea eax, [ebp+HexVal]
.text:00454DCD push eax
.text:00454DCE lea eax, [ebp+szComputerName] ;计算机名,记作 sn2
.text:00454DD1 call _text_454C9C ; GetComputerName
.text:00454DD1
.text:00454DD6 mov edx, [ebp+szComputerName]
.text:00454DD9 pop eax
.text:00454DDA call System::__linkproc__ LStrCat(void) ; sn1+sn2
.text:00454DDA
.text:00454DDF mov edx, [ebp+HexVal] ; sn1+sn2
.text:00454DE2 pop eax ; 输入的序列号
.text:00454DE3 call System::__linkproc__ LStrCmp(void)
.text:00454DE3
.text:00454DE8 jnz short Failed
.text:00454DE8
.text:00454DEA push 40h ; uType
.text:00454DEC mov eax, ds:_bss_45C5A4
.text:00454DF1 call System::__linkproc__ LStrToPChar(System::AnsiString)
.text:00454DF1
.text:00454DF6 push eax ; lpCaption
.text:00454DF7 push offset s_GoodWorkWrite ; "Good work! Write keygen! :)"
.text:00454DFC mov eax, [ebp+hForm1]
.text:00454DFF call _text_43E414
.text:00454DFF
.text:00454E04 push eax ; hWnd
.text:00454E05 call MessageBoxA_0
.text:00454E05
.text:00454E0A jmp short End
4.总结一下,注册验证的过程:通过一个循环处理输入的 用户名 和 在内存中紧跟其后的两个字节(!!),
得到一个整数值,这个整数十六进制数值转成字符串即为序列号的第一部分s1;取计算机名为序列号的第二部分sn2
sn1和sn2直接相连,即得正确的序列号。
5. 问题是 在内存中紧跟用户名后的两个字节是什么?
首先,内存中紧跟用户名后的第一个字节为 “\0”,即字符串以0结尾。
那么,内存中紧跟用户名后的第二个字节呢? 很不幸,这个字节的值基本上是随机的。不过万幸的是我们可以想
一个办法让它不得不取“定值”。
内存中的内容会按四字节对齐,因此输入用户名的存储地址肯定是4的倍数。读入的name被存放在一段闲置的内存
空间中,同时原来的数据被覆盖。在OD中观察转存区发现,被覆盖的部分要么原来就是"\0",要么是长度为三个字节
的内存地址。所以,如果我们限定用户名的长度为 4*n+2(n=0,1,2,3,4,5),则内存中紧跟用户名后的第二个字节
肯定为"\0"。
6.对输入的用户名作上述限制后,注册机编写就比较简单了。
作者说:Needs a little brain (or luck),我想其中的luck就是输入的用户名长度恰好满足上述条件,呵呵
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!
2006年12月25日 12:28:37
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!