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