交响诗篇 发表于 2007-3-17 10:17:12

Riijj crackme 10 anniversary 算法简析

作者:hawking
转贴自:一蓑烟雨

【文章标题】: Riijj crackme 10 anniversary 算法简析
【文章作者】: hawking
【作者邮箱】: rich_hawking@hotmail.com
【软件名称】: riijjcm10f2.exe
【软件大小】: 376k
【下载地址】: http://bbs.pediy.com/showthread.php?s=&threadid=36800
【保护方式】: 启动检测 key file
【编写语言】: VC++6
【使用工具】: OD PEiD
【操作平台】: win2k sp4
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
这是一个纯粹娱乐的 crackme,是riijj兄台作为圣诞礼物奉献给看雪论坛的全体同仁的。当初水平不够,看到浮点指令头大,连爆破都没能做到。想想心有不甘,根据warshon兄弟提供的一些线索,现对其注册过程作简单分析。

一、寻找关键代码

用PEiD检查,程序没有加壳,是Microsoft Visual C++ 6.0程序。用IDA打开分析,然后创建MAP文件,再用OD载入,导入MAP文件。
根据riijj的介绍,我们知道这个Crackme是通过启动时检测 key file来注册的。下断点 bp CreateFileA 然后F9运行。断下来之后我们看一下堆栈:

0012FD28   00420957/CALL 到 CreateFileA 来自 riijjcm1.00420951
0012FD2C   0042A0B0|FileName = "dinner.bin"
0012FD30   80000000|Access = GENERIC_READ
0012FD34   00000003|ShareMode = FILE_SHARE_READ|FILE_SHARE_WRITE
0012FD38   0012FD54|pSecurity = 0012FD54
0012FD3C   00000003|Mode = OPEN_EXISTING
0012FD40   00000080|Attributes = NORMAL
0012FD44   00000000\hTemplateFile = NULL
0012FD48   00424552riijjcm1.00424552

从这里我们知道了key file文件名是dinner.bin。我们新建一个空白文本文件随便输入一些文字并命名为dinner.bin之后复制到Crackme所在的文件夹。
Ctrl+F2重新运行程序,下断点 bp ReadFile 然后F9运行。断下来之后的堆栈:

0012FD20   0041F9C5/CALL 到 ReadFile 来自 riijjcm1.0041F9BF
0012FD24   00000080|hFile = 00000080 (window)
0012FD28   008D33C0|Buffer = 008D33C0
0012FD2C   00001000|BytesToRead = 1000 (4096.)
0012FD30   0012FD44|pBytesRead = 0012FD44
0012FD34   00000000\pOverlapped = NULL

从这里可以看出程序从key file中读取数据,并将读取到的数据保存在内存某一块地址当中。不断地Ctrl+F9返回之后我们来到了这里。

004011C1 >|> \56            push    esi
004011C2|.55            push    ebp
004011C3|.8D4C24 40   lea   ecx, dword ptr
004011C7|.E8 E4020000   call    <sub_4014B0>                     ;我们就是从这里返回的
004011CC|.8D4C24 40   lea   ecx, dword ptr
004011D0|.E8 8B1A0000   call    <sub_402C60>                     ;closefile
004011D5|.85C0          test    eax, eax
004011D7|.75 13         jnz   short <loc_4011EC>
004011D9|.50            push    eax
004011DA|.8B4424 3C   mov   eax, dword ptr
004011DE|.6A 02         push    2
004011E0|.8B48 04       mov   ecx, dword ptr
004011E3|.8D4C0C 40   lea   ecx, dword ptr
004011E7|.E8 54020000   call    <sub_401440>
004011EC >|>B9 0A000000   mov   ecx, 0A
004011F1|.8BF5          mov   esi, ebp                         ;这里的ebp指向了刚刚我们从key file中读取到的数据
004011F3|.8DBC24 C80000>lea   edi, dword ptr           ;buffer
004011FA|.F3:A5         rep   movs dword ptr es:, dword p>;复制40个读取到的字节到buffer

这里往下出现了不少浮点指令,关键代码段就是下面了。这里我们可以看出,程序真正用到的也只是key file中的前40个字节。

二、算法简析

004011EC >|>B9 0A000000   mov   ecx, 0A
004011F1|.8BF5          mov   esi, ebp                         ;这里的ebp指向了刚刚我们从key file中读取到的数据
004011F3|.8DBC24 C80000>lea   edi, dword ptr           ;buffer
004011FA|.F3:A5         rep   movs dword ptr es:, dword p>;复制40个读取到的字节到buffer
004011FC|.DD05 D8414200 fld   qword ptr [<dbl_4241D8>]         ;0.0
00401202|.DD5424 18   fst   qword ptr                ;n2 = 0
00401206|.DD05 D8414200 fld   qword ptr [<dbl_4241D8>]         ;0.0
0040120C|.DD5424 10   fst   qword ptr                ;n1 = 0
00401210|.0FBF9424 C800>movsx   edx, word ptr          ;取buffer前2个字节(len)
00401218|.8BCA          mov   ecx, edx
0040121A|.8DB424 CA0000>lea   esi, dword ptr
00401221|.8BC1          mov   eax, ecx
00401223|.8D7C24 20   lea   edi, dword ptr
00401227|.C1E9 02       shr   ecx, 2
0040122A|.F3:A5         rep   movs dword ptr es:, dword p>
0040122C|.8BC8          mov   ecx, eax
0040122E|.33C0          xor   eax, eax                         ;i=0
00401230|.83E1 03       and   ecx, 3
00401233|.85D2          test    edx, edx
00401235|.F3:A4         rep   movs byte ptr es:, byte ptr>
00401237|.0F8E 2B010000 jle   <loc_401368>

这一段代码先初始化两个浮点数n1和n2,使其全部为0,然后再取buffer当中的前两位(len),其实buffer中的前两位代表长度,也就是用户名的长度。再将buffer当中从第3位起,长度为len的字节复制到内存当中的另一个地方待用。
其实可以这么看,buffer前2位代表用户名长度len,从第3位开始长len的数据代表的就是用户名name。

0040123D >|> /0FBE4C04 20   /movsx   ecx, byte ptr       ;name(i)
00401242|. |894C24 0C   |mov   dword ptr , ecx
00401246|. |40            |inc   eax                           ;i++
00401247|. |DB4424 0C   |fild    dword ptr
0040124B|. |3BC2          |cmp   eax, edx
0040124D|. |D9C0          |fld   st
0040124F|. |DEC3          |faddp   st(3), st
00401251|. |D9CA          |fxch    st(2)
00401253|. |DC0D D0414200 |fmul    qword ptr [<dbl_4241D0>]      ;1.2
00401259|. |D9CA          |fxch    st(2)
0040125B|. |DEC1          |faddp   st(1), st
0040125D|. |DC0D C8414200 |fmul    qword ptr [<dbl_4241C8>]      ;1.3
00401263|.^\7C D8         \jl      short <loc_40123D>
00401265|.DD5C24 10   fstp    qword ptr                ;n1
00401269|.DD5C24 18   fstp    qword ptr                ;n2

这里对用户名作运算,反复取用户名的每一位,分别乘以1.3和1.2并累加至n1和n2 。

0040126D >|> /55            push    ebp                              
0040126E|. |E8 5C810000   call    <sub_4093CF>                     ;heapfree
00401273|. |DD4424 14   fld   qword ptr                ;n1
00401277|. |DC0D C0414200 fmul    qword ptr [<dbl_4241C0>]         ;5.0
0040127D|. |DD4424 1C   fld   qword ptr                ;n2
00401281|. |DC0D B8414200 fmul    qword ptr [<dbl_4241B8>]         ;9.0
00401287|. |83C4 04       add   esp, 4
0040128A|. |DEC1          faddp   st(1), st
0040128C|. |DD8424 E80000>fld   qword ptr                ;f2 key file 中33至40字节所代表的浮点数
00401293|. |DC0D B0414200 fmul    qword ptr [<dbl_4241B0>]         ;7.0
00401299|. |DEC1          faddp   st(1), st
0040129B|. |DD8424 E00000>fld   qword ptr                ;f1 key file 中25至32字节所代表的浮点数
004012A2|. |DCC0          fadd    st, st
004012A4|. |DEC1          faddp   st(1), st
004012A6|. |DC25 A8414200 fsub    qword ptr [<dbl_4241A8>]         ;50.0
004012AC|. |E8 C7460100   call    <__ftol>                           ;取整

这里对n1 n2 f1 f2作运算( 5 * n1 + 9 * n2 + 7 * f2 + f1 + f1 - 50.0)并将结果取整(result1)。

004012B1|.99            cdq
004012B2|.33C2          xor   eax, edx
004012B4|.5E            pop   esi
004012B5|.2BC2          sub   eax, edx
004012B7|.5D            pop   ebp
004012B8|.894424 04   mov   dword ptr , eax         ;取result1绝对值
004012BC|.DB4424 04   fild    dword ptr
004012C0|.DC1D A0414200 fcomp   qword ptr [<dbl_4241A0>]         ;0.01
004012C6|.DFE0          fstsw   ax                               ;将结果与0.01比较
004012C8|.F6C4 01       test    ah, 1                            ;
004012CB|.0F84 A0000000 je      <loc_401371>                     ;大于则跳 Game Over

这里要求result1的绝对值要小于0.01,由于result1是整数,所以result1只能为0 ,也就是说( 5*n1 + 9*n2 + 7*f2 + f1 + f1 - 50.0 )只能大于-1且小于1 。

004012D1|.DD4424 08   fld   qword ptr                 ;n1
004012D5|.DC8424 E00000>fadd    qword ptr                ;f2
004012DC|.DC0D 98414200 fmul    qword ptr [<dbl_424198>]         ;4.0
004012E2|.DD4424 10   fld   qword ptr                ;n2
004012E6|.DC0D B0414200 fmul    qword ptr [<dbl_4241B0>]         ;7.0
004012EC|.DEC1          faddp   st(1), st
004012EE|.DD8424 D80000>fld   qword ptr                ;f1
004012F5|.DC0D 90414200 fmul    qword ptr [<dbl_424190>]         ;3.0
004012FB|.DEC1          faddp   st(1), st
004012FD|.DC25 88414200 fsub    qword ptr [<dbl_424188>]         ;40.0
00401303|.E8 70460100   call    <__ftol>
00401308|.99            cdq
00401309|.33C2          xor   eax, edx
0040130B|.2BC2          sub   eax, edx
0040130D|.894424 04   mov   dword ptr , eax
00401311|.DB4424 04   fild    dword ptr
00401315|.DC1D A0414200 fcomp   qword ptr [<dbl_4241A0>]         ;0.01
0040131B|.DFE0          fstsw   ax
0040131D|.F6C4 01       test    ah, 1
00401320|.74 4F         je      short <loc_401371>

同上,要求 -1 < ( n1 + f2 ) * 4 + 7 * n2 + 3 * f1 - 40.0 < 1 不等式成立。

00401322|.8D8C24 8C0000>lea   ecx, dword ptr           ;成功的话走这里
00401329|.C78424 080100>mov   dword ptr , -1
00401334|.E8 47020000   call    <sub_401580>
00401339|.8D8C24 8C0000>lea   ecx, dword ptr
00401340|.C78424 8C0000>mov   dword ptr , offset <off_>
0040134B|.E8 887E0000   call    <std::ios_base::~ios_base(void)>
00401350|.B0 01         mov   al, 1                            ;设标志flag = 1
00401352|.5F            pop   edi
00401353|.8B8C24 FC0000>mov   ecx, dword ptr
0040135A|.64:890D 00000>mov   dword ptr fs:, ecx
00401361|.81C4 08010000 add   esp, 108
00401367|.C3            retn
00401368 >|>DDD8          fstp    st                              
0040136A|.DDD8          fstp    st
0040136C|.^ E9 FCFEFFFF   jmp   <loc_40126D>
00401371 >|>8D8C24 8C0000>lea   ecx, dword ptr           ;失败的话走这里
00401378|.C78424 080100>mov   dword ptr , -1
00401383|.E8 F8010000   call    <sub_401580>
00401388|.8D8C24 8C0000>lea   ecx, dword ptr
0040138F|.C78424 8C0000>mov   dword ptr , offset <off_>
0040139A|.E8 397E0000   call    <std::ios_base::~ios_base(void)>
0040139F|.EB 23         jmp   short <loc_4013C4>
004013A1 >|>8D8C24 8C0000>lea   ecx, dword ptr       
004013A8|.C78424 080100>mov   dword ptr , -1
004013B3|.E8 C8010000   call    <sub_401580>
004013B8|.8D8C24 8C0000>lea   ecx, dword ptr
004013BF|.E8 6C000000   call    <sub_401430>
004013C4 >|>8B8C24 000100>mov   ecx, dword ptr          
004013CB|.32C0          xor   al, al                           ;设标志flag = 0
004013CD|.5F            pop   edi
004013CE|.64:890D 00000>mov   dword ptr fs:, ecx
004013D5|.81C4 08010000 add   esp, 108
004013DB\.C3            retn

最终返回到这里:

00405F2F >|> \8B4C24 08   mov   ecx, dword ptr          ;loc_405F2F
00405F33|.51            push    ecx                              ; /ShowState
00405F34|.50            push    eax                              ; |hWnd
00405F35|.FF15 64414200 call    dword ptr [<&USER32.ShowWindow>] ; \ShowWindow
00405F3B|.8B15 C4E34200 mov   edx, dword ptr [<hWnd>]
00405F41|.52            push    edx                              ; /hWnd => 001B04AA ('Riijj crackme 10 - anniversary',class='riijj')
00405F42|.FF15 5C414200 call    dword ptr [<&USER32.UpdateWindow>; \UpdateWindow
00405F48|.E8 33B1FFFF   call    <sub_401080>                     ;关键call
00405F4D|.A2 F8E24200   mov   byte ptr [<byte_42E2F8>], al   ;处保存的是标志位,程序后面就是根据这个标志来显示不同的画面
00405F52|.FF15 BC404200 call    dword ptr [<&KERNEL32.GetTickCou>; [GetTickCount
00405F58|.50            push    eax
00405F59|.E8 9D030100   call    <sub_4162FB>
00405F5E|.83C4 04       add   esp, 4
00405F61|.E8 7A000000   call    <sub_405FE0>
00405F66|.B8 01000000   mov   eax, 1
00405F6B\.C3            retn

三、注册机(C#)

算法其实不难,主要是从key file中读取到的用户名生成两个浮点数n1 n2 ,并且将key file文件中特定的两个位置的数据看作两个浮点数f1 f2
要求下面两个不等式成立,则注册成功。

-1 <5 * n1 + 9 * n2 + 7 * f2 + f1 + f1 - 50.0 < 1
-1 < ( n1 + f2 ) * 4 + 7 * n2 + 3 * f1 - 40.0 < 1

最后我们再来看看key file中包含信息的数据结构

07 00 68 61 77 6B 69 6E 67 00 00 00 00 00 00 00.hawking.......
00 00 00 00 00 00 00 00 F1 12 28 3F 00 6C A8 C0........?(?.lɡ
37 BE E8 12 6E F9 A6 C0                        7捐n?

上面的数据结构中,前2位代表用户名长度,后面22位代表用户名(也就是说用户名最大有效长度为22位),再往后就是2个浮点数了。

struct RegisteInfo
{
          ushort Length ;
          char UserName ;
          double Fnum1 ;
          double Fnum2 ;
};

--------------------------------------------------------------------------------
【版权声明】: 感谢看雪论坛、一蓑烟雨, 转载请注明作者并保持文章的完整, 谢谢!

zsl01 发表于 2008-9-23 08:24:00

好东东,支持一下。

980691659 发表于 2020-3-25 15:08:37

好东东,感谢分享了
页: [1]
查看完整版本: Riijj crackme 10 anniversary 算法简析