TeCHiScy 发表于 2017-4-3 16:33

WinEdt 10.2 注册算法分析

本帖最后由 TeCHiScy 于 2017-10-7 14:45 编辑

注意:本文仅发布于吾爱破解,未经作者(TeCHiScy)同意不得转载。
2017. 10.7 补充:
根据最近两天来的实际使用体验,疑似软件中还存在几处暗桩需要进一步分析。

2017.4.7 补充:
660CC8 -> CheckCode   (660d41 -> Pushing Registration Code) 0x13
860748 -> CheckCode   (8607B1 -> Pushing Registration Code) 0x0B
7A78A0 -> CheckCode   (7A792D -> Pushing Registration Code) 0x0D
6AA424 -> CheckCode   (6aa486 -> Pushing Registration Code) 0x1F
7CA8C8 -> CheckCode   (7ca8f0 -> Pushing Registration Code) 0x25 (与之前的条件一致)

上述这些函数已确定也是注册码的判断函数,也就是说,除了点击注册按钮时候判断的那些条件以外,上述这些条件也必须满足,否则程序内置的定时器将会把之前已经判定为“正确”的注册码重新判定为无效,上述括号中的地址为将注册码压栈的指令地址。具体来说,这些附加的检查函数要求注册码 x 在满足之前文中给出的 2 个等式与 1 个不等式的前提下,还需要满足以下几个等式。其中,at_地址 表示位于某个地址的一个函数,这些函数都是对输入的 Name 进行处理的。

(3) x % 0x1F = @6AA281(Name) % 0x1F
(4) x % 0x0B = @8605A0(Name) % 0x0B
(5) x % 0x13 = @660AF4(Name) % 0x13
(6) x % 0x0D = @7A7698(Name) % 0x0D

2017.4.6 补充:
由于 WinEdt 除了本文分析的注册算法之外,还有一个 Timer 定时检测给定的注册码是否有效,因此如果给定的注册码不满足一些附加条件,那么在注册成功后依然可能出现 Internal Error 报错,或是标题栏显示试用期已过,针对于此问题的最新研究成果见上述补充内容。此外,本论坛的 whisperzzzz 也正在研究采用爆破法破解。
可以确定,此软件采用了虚假注册的方式来迷惑 Cracker,关于虚假注册,本论坛 http://www.52pojie.cn/forum.php?mod=viewthread&tid=367035 帖子有所提及。提示:破解 WinEdt 这个软件的时候需要注意,它对注册码的二次验证不是必然触发的,也即它首先利用定时器每 11 秒调用一次检查函数,但是检查函数会随机 Random 一个值,如果 Random 的结果满足要求,才会对注册码进行某一类检查(即检查它是否满足本文给出的所有等式中的一个),这是破解时候必须要注意的。

另外补充几个关键的地址,有兴趣的同学可以在此基础上进行研究:
007CB842 -> Timer 全局计时器
00881008 -> Delphi 的随机数种子存放的位置
00889B24 -> 一个全局计数器,类似于程序运行时间计数器,每秒 +1
007CB214 -> 很关键的一个函数,无论是窗体标题被改为 Unregistered 还是出现 Internal Error 都是这个函数触发,它每 11 秒运行一次。

此猜想被证实无效
WinEdt 作者可能利用了线性同余随机数(LCG)的一些特性,并且可能采用了部分检测算法,所以导致一个一开始被判断为“有效的”注册码会在一段时间后被判定为无效。对部分检测算法有兴趣的可以参考:http://www.brandonstaggs.com/2007/07/26/implementing-a-partial-serial-number-verification-system-in-delphi/。
-----------------------------------------------------------------
正文开始。
各位看官好!今天我想给大家带来的是 WinEdt10.x 软件的注册算法分析,调试采用的版本是最新发布的 WinEdt 10.2 版本,不过我也看了之前的若干版本,比如 5.x、7.x,它们的注册算法其实与 10.x 版本没有本质区别,唯一不同的是 5.x 版本并没有对输入的注册码进行异或运算,而且注册码的范围也小很多(之后会有解释),因此 5.x 版本会比较好导出注册算法。从本质上来看,这个软件的注册算法还是比较容易的,至少从汇编代码译出算法流程比较顺畅,不过不知道为什么在网上搜了一大圈没有看到有人对它进行分析,可能是因为相比于做注册机,这个软件采用爆破法、注册表法破解更加方便吧。关于爆破法,可以参考论坛另一篇由 whisperzzzz 写的文章:http://www.52pojie.cn/thread-583476-1-1.html。新人第一次写文章,如有谬误还请大神指教!下面正式开始。

WinEdt 10.2 可以在 http://www.winedt.com/download.html 自行下载。

首先,按照惯例,说一下用到的工具(调试环境:Windows 10.15063):
(1)EXEInfoPE:用来查壳,判定编译器类型。
(2)IDR:Delphi代码反编译工具,本文中采用它主要是为了确定一些Delphi库函数的含义。
(3)Ollydbg 吾爱专版:调试工具。
上述工具都可以在吾爱爱盘中找到或者在吾爱破解工具包中找到。



查壳结果:没有壳,Delphi XE6 编译。
打开这个软件,直奔主题找到 Help 菜单下的 Register WinEdt …,一个熟悉的注册框弹出,随意输入一些字符试试,出现以下对话框:



于是打开 Ollydbg,搜索上述字符串,找到它的引用位置,Enter 跳到引用它的位置。



需要注意,有些软件会对敏感字符串进行加密,比如 TeamSpeak 3(近期将会写另外一篇破文来讲它,注册机早于去年9月就写完),不过 WinEdt 没有那么变态,因此可以直接找到上述提示字符串以及引用的位置。
向上一直看,可以看到前面都是一些分支条件判断(不同的注册结果提示):



于是再继续向上查找,发现关键代码,如下图所示:



Ollydbg 注释说明这些语句是注册表写入语句,根据前置知识知道这款软件注册成功时会向注册表写入注册信息,因此用于判断注册是否成功的语句就在前面,具体位置是 0x0065F8DE:



需要注意,WinEdt是有32位和64位两个版本的,由于32位寄存器个数、函数参数存放位置都比较显然,并且Ollydbg只能调试32位的程序,因此本文采用的是WinEdt 10.2 32位版本。通常对于同一个软件来说,其32位的程序注册算法也和64位程序的算法一致,因此不影响分析结果。
前面说到关键判断在于0x0065F8DE,因此果断下断,看到它下面的代码是:
test al, al
je   WinEdt.0065FFB6

由于0x0065FFB6这个位置已经超过读写注册表的代码,因此可以判断WinEdt.0065CB74这个函数必须返回1才算注册成功,于是继续向上看,在0x0065F8B2看到以下代码:



此处看到两个奇怪的常量:0xD8907FC以及0x9AF13BC,引起警觉,下断。通常如果在程序中看到一些奇怪常量是需要注意的,因为有不少的哈希函数就会用到一些特定常量,因此如果汇编代码中看到一些常量可以去Google下,看看是不是某种算法用到的常数。不过很可惜,这边的常数不是什么已知算法的常数,然而这里却是开始调试的好的切入点,因此下断以后直接开始调试,在程序的注册码输入框中输入一些字符,如下所示:



注意:这里Registered是我破解完了以后注册过所以它会显示注册时间。另外这时候输入的字符就不是完全随意的了,如果知道此软件的常见注册码形式就很好。WinEdit的注册码形式根据之前的提示可以知道,应该是一个19-digit的数字,这里为了图简单,所以直接输入一个数字,没有管它的位数。于是自然的,Ollydbg命中了我们刚才的断点:0x0065F8B2,观察此时的寄存器内容:



eax = 0x37832D7C
edx = 0x0000011A

又是一个很奇怪的数字,这种情况下可以用计算器把它化为十进制看看结果(运行时产生的奇怪数字通常是和输入相关的),于是得到以下结果:

于是可以确认,这个edx eax此时就等于输入的注册码。同时注意到,由于输入的注册码应该有19位那么多,因此显然注册算法中都应该是 long long类型操作,比如这里就用到了edx eax两个32bit寄存器。因此很明显,之前看到的代码的作用就是将输入的注册码与0x9AF13BC 、0xD8907FC分别异或,并且存入local.910这两个堆栈位置,到此还没有什么特别的。



继续看这段代码,可以看到在对输入注册码进行异或之后没多久就进入了关键的判决函数,也即0x0065CB74,因此这当中的汇编应该就是进行参数传递,现在看看它给0065CB74传递了哪些参数(由于前面说过注册码都是long long操作,因此以下用local.ab表示一个long long类型变量):

1.   push local.910 (参数1)
2.   eax = 0x5C8655C (参数2)
具体看eax到底是什么东西,于是当Ollydbg命中0x65F8DE处的断点时,查看寄存器:



可以看到,eax就是传递了我们所输入的Name字符串的地址,到目前可以明确:WinEdt.0065CB74的两个参数分别为输入的Name,以及输入的注册码的与0x9AF13BC 、0xD8907FC异或后的结果。这里需要提一点是,因为我们之前已经通过EXEInfoPE看过这个软件没有加壳,并且采用Delphi编译,因此其实对于特定编译器我们是能够知道它的参数传递方式的,比如优先使用哪个寄存器,参数压栈顺序等等。接下来F7步入这个函数继续看:



忽略前几行的现场保护汇编代码,从0x0065CB7D开始看,可以看到第一个调用的函数是0x004087C0,显然这个函数与目前的地址空间0x0065xxxx不同,因此它应该是一个动态链接库的函数(由于我不是科班做破解,因此这边可能概念上有问题),这时候我们的IDR就可以派上用场了,IDR中的显示如下,也就是说0x0040B7C0是一个Delphi库函数LStrAddRef,它的作用是给字符串增加引用计数的,显然和注册算法无关,继续往下走。



顺带一提,如果能确定Delphi版本,自己在Ollydbg中加载(Symbol)符号库应该也是可以的,这样就不需要用IDR了,不过我这边就怎么简单怎么来吧。此外,采用IDR的好处是它可以解析出一些窗体、控件的信息,其实对破解也有很大帮助。下面看到第二个调用的函数是0x0065A44C,它的代码如下,经过分析可以发现这段代码的作用就是设置edx eax = 0x00000000 0x002FEFD7,虽然其中它也用到了Random,但是它Random都是在做一些恒等的判断,比如:Random(1)<1,因此虽然觉得这段代码很怪异,然而先默认这里的结果就是edx eax = 0x00000000 0x002FEFD7。需要注意,0x2FEFD7这个数字十分重要,之后会有多个地方用到这个数字,因此个人很怀疑这个一个区分不同程序版本的Magic Number,也就是对于不同的版本,作者只需要换一下这个值就能保证两个版本的注册码不同(这个猜想没有经过证实)。




下面继续看代码,在调用完0x0065A44C之后立马执行了下面的代码:
push edx    … edx =0x00000000
push eax    … eax =0x2FEFD7
mov eax, arg.1    … (输入的注册码异或结果)
mov edx, arg.2    … (输入的注册码异或结果)

然后调用了0x0040D614,显然这里又是一个库函数,IDR查之可以知道它是llmod函数,也就是long long类型的取余函数,于是可以知道这里进行了如下运算:
(edx eax) = (edx eax) % 0x2FEFD7

类似的Delphi中的数学运算函数还有如下几个,查资料易知这些函数的运算都是用当前寄存器edx eax 乘以/除以/取模 当前栈顶的数据,并且函数执行完以后会平衡堆栈,也就是将栈顶已经计算过的两个元素出栈。后续如果碰到数学运算函数将不再赘述。
0x0040D534——lldiv
0x0040D510——llmul

前面说到它调用的第三个函数是llmod,整个调用过程如下:



因此可以把这段代码解释为:local.34 = arg.12 % 0x2FEFD7
用同样的思路,继续向下看代码,可以知道之后做了如下运算:local.56 = 0x2FEFD7/ 2



记得看这段代码时候一定要注意对于0x0065A44C的调用,因为前面说过,只要调用这个函数,edx eax 就会被重新设置为 0x00000000, 0x002FEFD7。

下面是第三步操作,结果为local.78 = @65C818(输入的Name)



注意:这里暂时先不管0065C818函数对于输入的Name得到了什么结果,继续看代码,可以看出从0x0065CBF0开始到0x65CC8C是一个大的循环,跟踪调试可以发现其实这段代码就是对输入的Name这个字符串的每个字符进行处理,具体的处理方法就是进行上面给出的那几种数学运算,结果分别保存在local.56以及local.78中(与之前的结果累加),在此不再赘述。这里需要注意的是Delphi的字符串内存结构与C语言不同,因此它可以不用调用类似strlen的函数就可以知道字符串的长度。C语言字符串结果就是最后是一个0x00,Delphi字符串的结构如下所示(摘自CSDN“一如当初”的博客):

01~02 字节是代码页
03~04 字节表示每个字符所占的字节数(ANSI为1,Unicode为2)
05~08 字节是该字符串的引用计数
09~12 字节是该字符串的字符个数
13~?? 字节就是字符串实际的内容了
??    最后一个字节是00,字符串的结束符

之后一路分析到0x0065CD4E,可以发现整个函数基本上就是在对输入的Name以及注册码进行一系列的数学运算,在此不再赘述计算过程,之后会给出注册码必须满足的条件,有兴趣的可以按照这几个条件看懂相应的汇编过程。下面到了0x0065A4BC,显然这里又是一个重要的判决函数,可以看到它输入的参数分别是:
edx eax —— 经过一系列运算后的初步结果
arg.12—— 输入的注册码



到了这里,其实激动人心的时候就已经到了,因为汇编代码告诉我们,它将程序计算结果与我们的输入同时送入0x0065A4BC,显然这里就是要比较注册码对不对的时候了,这里直接给出结果,它判断的是:arg.12 % 0x2FEFD7 == (edx eax) % 0x2FEFD7
显然,要得到注册码,就必须满足这个等式,这里记录为等式(1)。同理,之后程序在0x65CD91还调用了0x65CA24,其实也是对于输入的注册码的判断,将这里的判断条件分别记为等式(2)和不等式(3),下面直接给出这三个判断条件。

(0) at = @65C959(0x25, Name)
(1) x % 0x2FEFD7 = (int(((x % 0x2FEFD7 +0x03) * y - (x % 0x2FEFD7) * (x % 0x2FEFD7) - (x % 0x2FEFD7)) / 2)) % 0x2FEFD7
(2) x % 0x25 = int((0x0B * at + (at % 0x25) *(at % 0x25) - (x % 0x25) * (x % 0x25)) / 0x0B) % 0x25
(3) x % 0x25 != int((0x0B * at + (at % 0x25)* (at % 0x25) - (x % 0x25) * (x % 0x25) + 0x0B - 0x01) / 0x0B) % 0x25 - 0x0B *0x25

其中 at 是调用地址为 0x65C959 这个函数的结果,它只与我们输入的 Name 有关,y 其实就是上述注册算法中的 local.78 这个变量的值,这里不给出具体表达式了。
现在的关键是对于这几个条件如何求解?首先先看第二个等式:
x % 0x25 = int((0x0B * at + (at % 0x25) *(at % 0x25) - (x % 0x25) * (x % 0x25)) / 0x0B) % 0x25
虽然它是一个余数方程,但是可以把 x % 0x25 当作一个变量,由余数性质可以知道它的范围在 0~0x25 之间,因此很容易可以枚举出结果。同样对于第一个方程我们也可以枚举出 x % 0x2FEFD7 的结果(其中在枚举 x % 0x25 的结果时别忘记判断第三个不等式是否成立)。现在我们有同余方程组:
x % 0x25 = a
x % 0x2FEFD7 = b
注意:根据最新研究成果,除了上述这三个条件必须满足,同时还要满足本帖顶部的 4 个附加条件,也即增加了 4 个同余方程。
根据中国剩余定理,可以很容易求得满足这两个同余方程的最小数字 c,同时可以知道满足这两个同余方程的结果的通解为:x = c + m * lcm(0x25, 0x2FEFD7),其中 m 是一个整数,lcm 是 0x25 与 0x2FEFD7的最小公倍数。注意这里的所有 x 都是指输入的注册码异或后的结果。

到这里大家一定觉得,那么我们不就得到注册码了么?其实这里的问题在于,由于注册算法要求我们输入的值在 1111 111 111 111 111 111 ~ 8 888 888 888 888 888 888 之间,因此你根据上述同余方程组求出的结果不一定满足这个范围要求。同时软件中还对一些特殊的注册码进行了屏蔽,具体的地址为 0x0065C3F7,因此当你好不容易求出一个注册码的时候,可能软件也会判断出它是一个无效的注册码。

前面说到,由于我们输入的注册码先要经过异或,而异或相当于定义了一个映射,因此虽然我们可以根据同余方程找到 x,但是映射完了以后不一定落在上述那么大的空间内,这也是我个人比较头疼的问题,相当于说一个线性的枚举过程可能因为异或变成一个非线性的结果序列,而软件最终的要求又是你必须在给定的范围内,这就导致枚举注册码会有一定困难。虽然如此,不过就我个人来说,采用枚举的方式也很快获得了注册码 (Python 代码),因此虽然理论上直接枚举的效率不高,不过用这种方法至少能够获得注册码。

本来是想放出注册机的,但是由于之前在 R4P3 论坛的悲惨遭遇(之前在那里发布了以前破的 TeamSpeak 3 SDK 的注册机结果被人再次逆向窃取),于是这次就不再放出注册机了,有问题的可以站内私信我。其实有了以上的过程,要自己写个注册机也不难。另一方面,做这篇破文主要是为了研究注册算法,因此放不放注册机其实我觉得也无关紧要。需要 Ollydbg UDD 文件的可以站内私信我,不过最重要的一个 UDD 文件可能被我不小心损坏了,反正能给的我就尽量给吧。最后给出一张我的注册结果:

注册码:
Cracker TeCHiScy
1130140925535334280
(根据最新成果,上述注册码已经更新,各位可以测试看是否还会出现 Internal Error 提示)



-----------------------------------------------------------------
附录:
1. 中国剩余定理参考代码:https://rosettacode.org/wiki/Chinese_remainder_theorem
2. 几个对于输入 Name 的处理函数(以 Python 代码给出):
@7A7698 【参数说明】:Name 为输入的用户名
def at_7A7698(Name):
    esp8C = len(Name) * len(Name)
    esi = 0x3
    Name = Name[::1]
    for c in Name:
      if((c >= 'a' and c <= 'z') or (c >= 'A' and c <= 'Z')):
            c = c.upper()
      esi = esi + 0x01
      esp8C = esp8C + ((esi * 0xAC1) % 0xE7B5 + 1) * ord(c)
    esp8C = esp8C % MagicNumber
    return esp8C

@660AF4 【参数说明】:Name 为输入的用户名
def at_660AF4(Name):
    esp8C = MagicNumber // 0x07
    ebx = 0xF
    Name = Name[::1]
    for c in Name:
      ebx = ebx + 0x02
      esp8C = esp8C + ord(c) * ebx
    esp8C = esp8C % MagicNumber
    return esp8C


@8605A0 【参数说明】:Name 为输入的用户名
def at_8605A0(Name):
    esp8C = len(Name)
    ebx = 0x7
    Name = Name[::-1]
    for c in Name:
      ebx = ebx + 1
      esp8C = esp8C + ((ebx * 0xAC1) % 0xE7B5 + 0x01) * ord(c)
    esp8C = esp8C % MagicNumber
    return esp8C


@6AA281 【参数说明】:Name 为输入的用户名
def at_6AA281(Name):
    esp8C = MagicNumber // 0x05
    ebx = 0x42
    Name = Name[::-1]
    for c in Name:
      ebx = -ebx
      esp8C = esp8C + ord(c) * ebx
    # 0x006AA2B2 -> abs()
    esp8C = abs(esp8C) % MagicNumber
    return esp8C

@65C959 【参数说明】:arg1 固定为 0x25, Name 为输入的用户名
def at_65C959(arg1, Name):
    l56_0 = MagicNumber // 0x0B
    edi = 0x21
    for c in Name:
      if ord(c) > 0x20:
            edi = 0 - edi
            l56_0 = l56_0 + ord(c) * ord(c) * edi
    l56_0 = abs(l56_0) % MagicNumber
    if l56_0 < 0x63:
      l56_0 = 0x1A0A
    l34_0 = l56_0 % arg1            
    return l34_0

TeCHiScy 发表于 2018-4-1 23:50

songyuan 发表于 2018-3-20 16:58
楼主,最近使用起来发现注册码虽然可以激活,但是过些天还是会提示未注册,希望楼主能够克服困难,继续加油

这个问题我在自己使用的过程中也有发现,应该是 WinEdt 的小概率检测暗桩。这个问题可以解决,不过需要长时间调试程序,运气好的话一天就能有现象出现,运气不好需要几天。好在这个问题影响不大,你关掉重新打开会恢复到”注册状态“。最近一段时间我也在写论文,因此空了之后我会想办法解决。

TeCHiScy 发表于 2018-4-2 15:29

xyreg 发表于 2018-4-2 14:41
这个软件不知道从哪个版本起就随机检测一下,注册也会非注册

可以知道的是 10.x 版本之后都有随机检测,如果要详尽测试所有检测点会比较费力,所以目前先把那些很频繁出现的点位给解决了。

Deteriorator 发表于 2017-4-5 17:29

楼主是在xp系统里破解的吗?

Deteriorator 发表于 2017-4-5 17:36

多谢楼主,WinEdt 10 亲测激活

Hmily 发表于 2017-4-5 18:52

IDR直接导出map然后用od加载map看起来也方便,od还能直接加载delphi的符号?

回归自然 发表于 2017-4-5 20:34


学习一下 谢楼主分享

rxxcy 发表于 2017-4-5 20:39

观摩一下大神

可坏 发表于 2017-4-5 23:48

好文必须顶

liu0604 发表于 2017-4-6 08:08

跪舔膜拜当中,收藏起来 夜深人静的时候拿出来~~~学习学习~~~

ufo2273810 发表于 2017-4-6 10:07

感谢分享,大神辛苦了。

yuxing818 发表于 2017-4-6 11:55

谢谢楼主分享
页: [1] 2 3 4 5 6 7 8 9 10
查看完整版本: WinEdt 10.2 注册算法分析