• 标 题:NewsReactor 1.0 Build 5009的注册机制分析 (16千字)
  • 作 者:6767[BCG]
  • 时 间:2001-8-7 20:22:34
  • 链 接:http://bbs.pediy.com

NewsReactor 1.0 Build 5009的注册机制分析
by Fpc[CCG]&6767[BCG]    @2001/08

tools: SI, wdasm, BcB 5.0

Homepage: http://www.daansystems.com
Features: NewsReactor is a tool to download binaries from usenet newsgroups.
    NewsReactor scans, combines and downloads files from a selected newsgroup.
    This program is shareware. You can register NewsReactor to get the full version.


这个软件的注册很有意思,分析加调试用了两天,巨爽,不是说软件,而是这个过程。本来要写crackme,现在不用了,嘿嘿。

1、它的未注册版在启动时会有一个几秒钟的Nag。单击按钮“Enter RegCode”,在打开的对话框中输入名字:LazyUser和注册码:123654;

2、^D切换到SoftIce,下命令:Bpx hMemcpy,按下F5返回到Windows;

3、单击“OK”,被拦下,因为有两个输入框,所以要按一次F5,还会被拦下;

4、按n次F12到程序领空,你会到下面(用wdasm的反汇编结果来表示):



:0040C84C 8D4C2434                lea ecx, dword ptr [esp+34]

* Reference To: MFC42.Ordinal:09D2, Ord:09D2h
                                  |
:0040C850 E893D50000              Call 00419DE8
:0040C855 83F801                  cmp eax, 00000001        <- 返回处
:0040C858 0F85F7010000            jne 0040CA55            <- eax=1表示有输入,所以不会跳
:0040C85E 8D8C2498000000          lea ecx, dword ptr [esp+00000098]
:0040C865 51                      push ecx
:0040C866 8BCF                    mov ecx, edi

* Reference To: MFC42.Ordinal:035A, Ord:035Ah
                                  |
:0040C868 E813D70000              Call 00419F80            <- 这几个call是按编号来的,不理
:0040C86D 8D942494000000          lea edx, dword ptr [esp+00000094]
:0040C874 8BCE                    mov ecx, esi
:0040C876 52                      push edx

* Reference To: MFC42.Ordinal:035A, Ord:035Ah
                                  |
:0040C877 E804D70000              Call 00419F80
:0040C87C 6A0A                    push 0000000A
:0040C87E 8BCE                    mov ecx, esi

* Reference To: MFC42.Ordinal:1ADA, Ord:1ADAh
                                  |
:0040C880 E899DB0000              Call 0041A41E
:0040C885 6A0D                    push 0000000D
:0040C887 8BCE                    mov ecx, esi

* Reference To: MFC42.Ordinal:1ADA, Ord:1ADAh
                                  |
:0040C889 E890DB0000              Call 0041A41E


:0040C88E 8B36                    mov esi, dword ptr [esi]    <- esi取得Irc(Inputed Reg Code)的地址
:0040C890 395EF8                  cmp dword ptr [esi-08], ebx    <- 判断Irc长度是否为0
                                <- 这种情况常见:[esi]处为某字串,那么[esi-8]处有可能就是该字串的长度
:0040C893 0F8471010000            je 0040CA0A            <- 跳走就完蛋



:0040C899 A0106D4200              mov al, byte ptr [00426D10]    <- 下面这一小段是对缓冲区清0
:0040C89E B93F000000              mov ecx, 0000003F
:0040C8A3 8884249C000000          mov byte ptr [esp+0000009C], al
:0040C8AA 33C0                    xor eax, eax
:0040C8AC 8DBC249D000000          lea edi, dword ptr [esp+0000009D]
:0040C8B3 895C2410                mov dword ptr [esp+10], ebx
:0040C8B7 F3                      repz
:0040C8B8 AB                      stosd
:0040C8B9 66AB                    stosw
:0040C8BB AA                      stosb


:0040C8BC 8BFE                    mov edi, esi            <- 这一小段是经典的取字串长度代码
:0040C8BE 83C9FF                  or ecx, FFFFFFFF
:0040C8C1 33C0                    xor eax, eax
:0040C8C3 895C2414                mov dword ptr [esp+14], ebx
:0040C8C7 F2                      repnz
:0040C8C8 AE                      scasb
:0040C8C9 F7D1                    not ecx            <- ecx=Len+1


:0040C8CB 2BF9                    sub edi, ecx            <- 这一段是经典的字串拷贝程序
:0040C8CD 8D94249C010000          lea edx, dword ptr [esp+0000019C]
:0040C8D4 8BC1                    mov eax, ecx
:0040C8D6 8BF7                    mov esi, edi
:0040C8D8 8BFA                    mov edi, edx            <- 目标地址
:0040C8DA 55                      push ebp
:0040C8DB C1E902                  shr ecx, 02
:0040C8DE F3                      repz
:0040C8DF A5                      movsd
:0040C8E0 8BC8                    mov ecx, eax
:0040C8E2 8D542414                lea edx, dword ptr [esp+14]
:0040C8E6 83E103                  and ecx, 00000003
:0040C8E9 8D8424A0010000          lea eax, dword ptr [esp+000001A0]
:0040C8F0 F3                      repz
:0040C8F1 A4                      movsb


:0040C8F2 8D4C2418                lea ecx, dword ptr [esp+18]
:0040C8F6 8DAC24A0010000          lea ebp, dword ptr [esp+000001A0]
:0040C8FD 51                      push ecx            <- 目标地址二
:0040C8FE 52                      push edx            <- 目标地址一

* Possible StringData Ref from Data Obj ->"%lu-%lu"
                                  |
:0040C8FF 68C05E4200              push 00425EC0            <- 处理的方式
:0040C904 50                      push eax            <- 待处理字串的偏移址

* Reference To: MSVCRT.sscanf, Ord:02B5h
                                  |
:0040C905 FF15FCD74100            Call dword ptr [0041D7FC]    <- 对字串格式化处理

:0040C90B 83C410                  add esp, 00000010
:0040C90E 85C0                    test eax, eax            <- eax为对几个部分进行了格式化,为0表失败或完成
:0040C910 0F8492000000            je 0040C9A8

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C9A2(C)
|
:0040C916 8D4C242C                lea ecx, dword ptr [esp+2C]    <- 入口的push其实就是上面的格式化处理结果
:0040C91A 8D542414                lea edx, dword ptr [esp+14]    <-
:0040C91E 51                      push ecx
:0040C91F 8B4C2428                mov ecx, dword ptr [esp+28]    <- 注册码将生成到这里
:0040C923 52                      push edx
:0040C924 895C2434                mov dword ptr [esp+34], ebx
:0040C928 895C2438                mov dword ptr [esp+38], ebx
:0040C92C 895C243C                mov dword ptr [esp+3C], ebx
:0040C930 E84B170000              call 0040E080            <- !!计算注册码的核心,下面有介绍


:0040C935 8D7C242C                lea edi, dword ptr [esp+2C]
:0040C939 83C9FF                  or ecx, FFFFFFFF
:0040C93C 33C0                    xor eax, eax
:0040C93E 8D9424A0000000          lea edx, dword ptr [esp+000000A0]
:0040C945 F2                      repnz
:0040C946 AE                      scasb
:0040C947 F7D1                    not ecx
:0040C949 2BF9                    sub edi, ecx
:0040C94B 6A2D                    push 0000002D            <-
:0040C94D 8BF7                    mov esi, edi
:0040C94F 8BFA                    mov edi, edx
:0040C951 8BD1                    mov edx, ecx
:0040C953 83C9FF                  or ecx, FFFFFFFF
:0040C956 F2                      repnz
:0040C957 AE                      scasb
:0040C958 8BCA                    mov ecx, edx
:0040C95A 4F                      dec edi
:0040C95B C1E902                  shr ecx, 02
:0040C95E F3                      repz
:0040C95F A5                      movsd
:0040C960 8BCA                    mov ecx, edx
:0040C962 55                      push ebp
:0040C963 83E103                  and ecx, 00000003
:0040C966 F3                      repz
:0040C967 A4                      movsb

* Reference To: MSVCRT.strchr, Ord:02B7h
                                  |
:0040C968 8B35F4D74100            mov esi, dword ptr [0041D7F4]
:0040C96E FFD6                    call esi            <- 检查Irc中是否含有‘-’,不是必须的
:0040C970 83C408                  add esp, 00000008
:0040C973 3BC3                    cmp eax, ebx
:0040C975 7431                    je 0040C9A8            <- 如果不含,则跳下去,准备比较了
                                <- 当只在Irc中输入一个数,比如:34553
                                <- 就会直接跳下去
:0040C977 40                      inc eax            <- 当Irc中含‘-’时,这一段被执行到
:0040C978 6A2D                    push 0000002D
:0040C97A 50                      push eax
:0040C97B FFD6                    call esi
:0040C97D 8BE8                    mov ebp, eax            <-
:0040C97F 83C408                  add esp, 00000008
:0040C982 3BEB                    cmp ebp, ebx            <- 经测试: ebp=ebx=0
:0040C984 7422                    je 0040C9A8            <- 所以跳走, 进行比较

:0040C986 8D442418                lea eax, dword ptr [esp+18]
:0040C98A 8D4C2414                lea ecx, dword ptr [esp+14]
:0040C98E 50                      push eax
:0040C98F 45                      inc ebp
:0040C990 51                      push ecx

* Possible StringData Ref from Data Obj ->"%lu-%lu"
                                  |
:0040C991 68C05E4200              push 00425EC0
:0040C996 55                      push ebp

* Reference To: MSVCRT.sscanf, Ord:02B5h
                                  |
:0040C997 FF15FCD74100            Call dword ptr [0041D7FC]
:0040C99D 83C410                  add esp, 00000010
:0040C9A0 85C0                    test eax, eax
:0040C9A2 0F856EFFFFFF            jne 0040C916            <-



* Referenced by a (U)nconditional or (C)onditional Jump at Addresses:
|:0040C910(C), :0040C975(C), :0040C984(C)
|
:0040C9A8 8D9424A0000000          lea edx, dword ptr [esp+000000A0]
:0040C9AF 52                      push edx

* Possible StringData Ref from Data Obj ->"Registered by: %s
"
                                  |
:0040C9B0 68AC5E4200              push 00425EAC
:0040C9B5 E8869A0000              call 00416440
:0040C9BA 83C408                  add esp, 00000008
:0040C9BD 8D8424A0000000          lea eax, dword ptr [esp+000000A0]
:0040C9C4 8D4C2420                lea ecx, dword ptr [esp+20]
:0040C9C8 50                      push eax

* Reference To: MFC42.Ordinal:0219, Ord:0219h
                                  |
:0040C9C9 E8D0D50000              Call 00419F9E            <- 好象是字符串拷贝的调用


:0040C9CE 8B7C2428                mov edi, dword ptr [esp+28]
:0040C9D2 8B00                    mov eax, dword ptr [eax]
:0040C9D4 8B0F                    mov ecx, dword ptr [edi]
:0040C9D6 50                      push eax            <- 假码
:0040C9D7 50                      push ecx            <- 真码

* Reference To: MSVCRT._mbscmp, Ord:0159h
                                  |
:0040C9D8 FF15E8D74100            Call dword ptr [0041D7E8]    <- 比较
                                <- 下面的不用管了,因为如果两者一致,就成功;不一致则失败

... ...


* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C893(C)
|
:0040CA0A 53                      push ebx
:0040CA0B 6A30                    push 00000030

* Possible StringData Ref from Data Obj ->"Registration code invalid!"
                                  |
:0040CA0D 68EC5C4200              push 00425CEC
:0040CA12 EB3C                    jmp 0040CA50

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040C9F8(C)
|

... ...


:0040CA48 53                      push ebx
:0040CA49 6A30                    push 00000030

* Possible StringData Ref from Data Obj ->"Thank you for registering!
Please "
                                        ->"restart NewsReactor,"
                                  |
:0040CA4B 68385D4200              push 00425D38

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040CA12(U)
|

* Reference To: MFC42.Ordinal:04B0, Ord:04B0h
                                  |
:0040CA50 E8F9D60000              Call 0041A14E

... ...


:0040CAAB C20400                  ret 0004


下面我们来看它是如何计算注册码的:


* Referenced by a CALL at Addresses:
|:00404C2F  , :0040C204  , :0040C930 
|
:0040E080 83EC10                  sub esp, 00000010

:0040E083 8B442414                mov eax, dword ptr [esp+14]    <- 格式化处理后数字的位置

:0040E087 53                      push ebx            <- 保存寄存器
:0040E088 56                      push esi
:0040E089 57                      push edi

:0040E08A 8B10                    mov edx, dword ptr [eax]    <- edx取得数字,因为我输入的是:123654,所以edx=0x1E306
:0040E08C 8B4804                  mov ecx, dword ptr [eax+04]    <- 如果我们没有输入‘-’,只有一个数字,则ecx=0;
                                <- 若输入格式是"数字-数字", 则ecx=

:0040E08F C744240C36A60A02        mov [esp+0C], 020AA636    <- 一个数组的初始化
:0040E097 C7442410418D9514        mov [esp+10], 14958D41
:0040E09F C744241454D75700        mov [esp+14], 0057D754
:0040E0A7 C7442418C8BD3400        mov [esp+18], 0034BDC8

:0040E0AF B82037EFC6              mov eax, C6EF3720        <- eax初始化
:0040E0B4 BE20000000              mov esi, 00000020        <- 循环次数

* Referenced by a (U)nconditional or (C)onditional Jump at Address:
|:0040E0FC(C)                            <- 这个循环当然是关键了
|                                <- 这段代码极为精练, 肯定是直接用汇编写成的


:0040E0B9 8BFA                    mov edi, edx
:0040E0BB 8BDA                    mov ebx, edx
:0040E0BD C1EF05                  shr edi, 05
:0040E0C0 C1E304                  shl ebx, 04
:0040E0C3 33FB                    xor edi, ebx

:0040E0C5 8BD8                    mov ebx, eax
:0040E0C7 C1EB0B                  shr ebx, 0B
:0040E0CA 83E303                  and ebx, 00000003
:0040E0CD 03FA                    add edi, edx
:0040E0CF 8B5C9C0C                mov ebx, dword ptr [esp+4*ebx+0C]

:0040E0D3 03D8                    add ebx, eax
:0040E0D5 054786C861              add eax, 61C88647
:0040E0DA 33FB                    xor edi, ebx
:0040E0DC 2BCF                    sub ecx, edi            <-


:0040E0DE 8BF9                    mov edi, ecx
:0040E0E0 8BD9                    mov ebx, ecx
:0040E0E2 C1EF05                  shr edi, 05
:0040E0E5 C1E304                  shl ebx, 04
:0040E0E8 33FB                    xor edi, ebx

:0040E0EA 8BD8                    mov ebx, eax
:0040E0EC 83E303                  and ebx, 00000003
:0040E0EF 03F9                    add edi, ecx
:0040E0F1 8B5C9C0C                mov ebx, dword ptr [esp+4*ebx+0C]
:0040E0F5 03D8                    add ebx, eax
:0040E0F7 33FB                    xor edi, ebx
:0040E0F9 2BD7                    sub edx, edi            <-

:0040E0FB 4E                      dec esi
:0040E0FC 75BB                    jne 0040E0B9


:0040E0FE 8B442424                mov eax, dword ptr [esp+24]        <- 准备存放处理结果
:0040E102 5F                      pop edi
:0040E103 5E                      pop esi
:0040E104 5B                      pop ebx
:0040E105 8910                    mov dword ptr [eax], edx        <- 结果的第一部分
:0040E107 894804                  mov dword ptr [eax+04], ecx        <- 结果的第二部分
:0040E10A 83C410                  add esp, 00000010
:0040E10D C20800                  ret 0008


对于这样的算法我们应该怎样分析呢?
一、将上面这段程序略微整理一下,从0X0开始穷举,检测结果是否全部为可显示字符,如果是,该数就是一个注册码;

二、写出逆算法,根据姓名生成注册码,难度大,有挑战性。

我想写逆算法,不但要在代码上实现,还得知道必要的参数。在上面这段程序中,出口值经测试可知eax=0,edx为注册码的第一部分,ecx为注册码的第二部分,就是说假设我们想以用户名“43214321”来注册,那么edx=0x31323334,ecx=0x31323334,以此作为条件来看应该输入什么样的注册码才能成功。

根据对上面程序的分析,经过长达几个小时的调试,终于证明如下的程序段可实现这个功能(注意:其中的数值以0x开头,视编译器的需要而定):

很多参数直接搬过来就能用,注意代码的顺序和关键位置即可。

begin:
    sub esp, 00000030
    push ebx
    push esi
    push edi

    mov [esp+0x0C], 0x020AA636
    mov [esp+0x10], 0x14958D41
    mov [esp+0x14], 0x0057D754
    mov [esp+0x18], 0x0034BDC8

    ;初始化
    xor eax, eax
    mov ecx, 0x31323334
    mov edx, 0x31323334
    mov esi, 0x20

loop1:
    mov edi, ecx
    mov ebx, ecx
    shr edi, 05
    shl ebx, 04
    xor edi, ebx   
    add edi, ecx   

    mov ebx, eax
    and ebx, 0x3 
    mov ebx, dword ptr [esp+4*ebx+0x0C]
    add ebx, eax   

    xor edi, ebx   
    add edx, edi                <- 关键位置


    sub eax, 0x61C88647            <- 关键位置

    mov edi, edx
    mov ebx, edx
    shr edi, 05
    shl ebx, 04
    xor edi, ebx
    add edi, edx
    
    mov ebx, eax
    shr ebx, 0x0B
    and ebx, 0x3
    mov ebx, dword ptr [esp+4*ebx+0x0C]
    add ebx, eax

    xor edi, ebx
    add ecx, edi                <- 关键位置

    dec esi
    jnz loop1
    
exit1:
    pop edi
    pop esi
    pop ebx
    add esp, 00000030


OK,经过这个循环,edx=705987235,ecx=970065253。好了重新注册,输入名字:43214321,注册码:705987235-970065253,Coolll! "Thank you for...."。

其实被这个逆算法折磨很长时间,一开始想偷懒,只输入一个数字怎样都不能成功,原因在于edx的值可知,而ecx相当于是随机的(就是未知的一个值),后来偶然想到‘-’的用处,才成功。


关于注册码:名字可以是为少于、等于8个的任意字符,若长度大于8则会失败;保存在同一目录下的NewsReactor.ini,

[Registration