序列号保护一把梭

序列号保护

​ 首先来看看常见的序列号(又称注册码)保护的工作原理。从网上下载的共享软件(Shareware)一般都有使用时间或功能上的限制,如果超过了共享软件的试用期,就必须到这个软件的公司去注册方能继续使用。注册过程一般是用户把自己的信息(例如用户名、电子邮件地址、机器特征码等)告诉软件公司,软件公司根据用户的信息,利用预先编写的一个用于计算注册码的程序(称为注册机,KeyGen)算出一个序列号,并以电子邮件等形式将其发给用户。用户得到序列号后,在软件中输入注册信息和序列号。当注册信息验证通过后,软件就会取消各种限制,例如时间限制、功能限制等,从而成为完全正式版本。软件每次启动时,会从磁盘文件或系统注册表中读取注册信息并对其进行检查。如果注册信息正确,则以完全正式版的模式运行,否则将作为有功能限制或时间限制的版本来运行。注册用户可以根据所拥有的注册信息得到相应的售后服务。当软件推出新版本后,注册用户还可以向软件作者提供自己的注册信息,以获得版本升级服务。这种保护实现起来比较简单,不需要额外的成本,用户购买也非常方便。网上大部分的软件都是以这种方式实现保护的。

  1. 序列号保护机制

​ 软件验证序列号,其实就是验证用户名和序列号之间的数学映射关系。因为这个映射关系是由软件的设计者制定的,所以各个软件生成序列号的算法是不同的。显然,映射关系越复杂,序列号就越不容易被破解。根据映射关系的不同,程序检查序列号有如下4种基本方法。

​ (1)将用户名等信息作为自变量,通过函数F变换之后得到注册码

​ 将这个注册码和用户输入的注册码进行字符串比较或者数值比较,以确定用户是否为合法用户,公式如下。

序列号 = F(用户名)

​ 因为负责验证注册码合法性的代码是在用户的机器上运行的,所以用户可以利用调试器等工具来分析程序验证注册码的过程。由于通过上述方法计算出来的序列号是以明文形式在内存中出现的,我们很容易就能在内存中找到它,从而获得注册码。这种方法在检查注册码合法性的同时,也在用户机器上再现了生成注册码的过程(即在用户机器上执行了函数F)。实际上,这是非常不安全的,因为不论函数F有多么复杂,解密者只需把函数F的实现代码从软件中提取出来,就可编制一个通用的计算注册码程序了。由此可见,这种检查注册码的方法是极其脆弱的。解密者也可通过修改比较指令的方法来通过注册码检查。

​ (2)通过注册码验证用户名的正确性

​ 软件作者在给注册用户生成注册码的时候,使用的仍然是上面那种变换。这里要求F是个可逆变换。而软件在检查注册码的时候,是利用F的逆变换F^-1对用户输人的注册码进行变换的。如果变换的结果和用户名相同,则说明是正确的注册码,即

用户名 = F^-1(序列号)

​ 可以看到,用来生成注册码的函数F未直接出现在软件代码中,而且正确注册码的明文也未出现在内存中。所以,这种检查注册码的方法比第1种方法要安全一些。

​ 破解这种注册码检查方法时,除了可以采用修改比较指令的办法,还有如下考虑:

​ 因为F\^-1的实现代码是包含在软件中的,所以可以通过F\^-1找出其逆变换,即函数F,从而得到正确的注册码或者写出注册机。给定一个用户名,利用穷举法找到一个满足式的用户名 = F^-1(序列号)序列号。这只适用于穷举难度不大的函数。
​ 给定一个序列号,利用式用户名 = F^-1(序列号)变换得出一个用户名(当然,这个用户名中一般包含不可显示字符),从而得到一个正确的用户名序列号对。

​ (3)通过对等函数检查注册码

​ 如果输入的用户名和序列号满足式用户名 = F^-1(序列号),则认为是正确的注册码。采用这种方法,同样可以实现在内存中不出现正确注册码的明文。如果F2是一个可逆函数,则本方法实际上是第2种方法的个推广,解密方法也类似。

F1(用户名)=F2(序列号)

​ 上面3种检查注册码的方法采用的自变量都只有1个,自变量是用户名或注册码。

​ (4)同时将用户名和注册码作为自变量(即采用二元函数)

​ 这种检查注册码的方法将采用如下判断规则:当对用户名和序列号进行变换时,如果得出的结果和某个特定的值相等,则认为是合法的用户名序列号对。

特定值=F3(用户名,序列号)

​ 这个算法看上去相当不错,用户名与序列号之间的关系不再那么清晰了。但是,同时可能失去了用户名与序列号的一一对应关系,软件开发者很可能无法写出注册机。所以,必须维护用户名与序列号之间的唯一性。建一个数据库就可以了。当然,也可根据这一思路把用户名和序列号分为几个部分来构造多元的算法。

特定值=Fn(用户名1,用户名2…序列号1,序列号2)

​ 以上所说的都是注册码与用户名相关的情祝。实际上,注册码也可以与用户名没有关系,这完全取决于软件作者的考虑。
​ 可见,注册码的复杂性问题归根到底是一个数学问题。设计难以求逆的算法。当然,即使检查注册码的算法再复杂,如果可执行程序可以被任意修改,解密者还是可以通过修改比较跳转指令使程序成为注册版。所以,仅有好的算法是不够的,还要结合软件完整性检查等方法。

  1. 如何攻击序列号保护机制

​ 若要找到序列号,或者修改判断序列号之后的跳转指令,最重要的是利用各种工具来定位判断序列号的代码段。

​ 一种办法是通过跟踪输入注册码之后的判断找到注册码。通常用户会在一个编辑框中输入注册码,软件需要调用一些标准的API将用户输人的注册码字符串复制到自己的缓冲区中。利用调试器针对API设置断点的功能,就有可能找到判断注册码的地方。常用的API包括Get WindowTextA(W)、GetDIgItemTextA(W)、GetDlgItemInt,hmemcpy(仅Windows9x/Me)等。程序完成对注册码的判断流程后,一般会显示一个对话框,告诉用户注册码是否正确,这也是一个切人点。MessageBoxA(W)、MessageBoxExA(W)、Show Window、MessageBoxIndirectA(W)、CreateDialogParamA(W)、CreateDialog IndirectParamA(W)、DialogBoxParamA(W)、DialogBoxIndirectParamA(W)等API经常用于显示对话框。
​ 另一种办法是跟踪程序启动时对注册码的判断过程(因为程序每次启动时,都需要将注册码读出并加以判断),从而决定是否以注册版的模式工作。根据序列号存放位置的不同,可以使用不同的API断点。如果序列号存放在注册表中,可以使用RegQuery ValueExA(W)函数;如果序列号存放在INI文件中,可以使用GetPrivateProfileStringA(M、GetPrivateProfileIntA(W)、GetProfileIntA(W)、GetProfileStringA(w)等函数;如果序列号存放在一般的文件中,可以使用CreateFileA(M)、_lopent()等函数。

​ (1)数据约束性的秘诀

​ 这个概念是由+ORC提出的,只在用明文比较注册码的保护方式中使用。在大多数的序列号保护程序中,那个真正的、正确的注册码会于某个时刻出现在内存中。当然,它出现的位置是不定的,但多数情况下它会在一个范围之内,即存放用户输人序列号的内存地址±90h字节的地方。数据约束性(Data constraint)或者密码相邻性(Password proximity)的依据是:加密者在编程的时候需要留意保护功能是否“工作”,必须“看到”用户输入的数字,以及用户输入的转换结果和真正的密码之间的关系,这种联系必须经常地检查以调用这些代码。通常,它们会共同位于一个小的栈区域中(注意:参数或局部变量通常都是存储在栈中的,而软件作者一般会使用局部变量存放临时计算出来的注册码),使它们可以在同一个监视(Watch)窗口中出现。所以,在大多数情况下,真正的密码会在离保存用户输入密码不远的地方露出“马脚”。

​ 运行TraceMe.exe程序,输入用户名“pediy”,序列号“12121212”。单击“Check”按钮,TraceMe将提示序列号错误。不要关闭此提示窗口。运行十六进制工具WinHex,.单击菜单项“Tools'”→“RAM Editor'”或按“Alt+F9”快捷键,打开内存编辑工具。单击“TraceMe”选项,打开Primary Memory内存并查看。按“Ctrl+F”快捷键打开查找对话框,输入假序列号“12121212”,在附近会发现另一个字符串“2470”,这就是真序列号,结果如图所示。

​ OllyDbg也可以实现这种查找功能。用OllyDbg加载TraceMe,输入假序列号,单击“Check'”按钮直到出现错误提示框。按“Alt+M”快捷键打开内存窗口,在上面一行按“Ctl+B”快捷键打开搜索框,搜索刚输人的序列号“12121212”,如图所示。OllyDbg的数据查找功能非常有用,可以在当前进程的整个内存映像里查找数据。

​ (2)hmemcpy函数

​ hmemepy函数(俗称“万能断点”)是Windows9x系统的内部函数,它的作用是将内存中的一块数据复制到另一个地方。由于Windows9x系统频繁使用该函数处理各种字符串,将该函数作为断点是非常实用的,该函数也成为Windows9x/Me平台最常用的断点。Windows NT/2000以上版本的系统中没有这个断点,因为其内核和Windows9x完全不同。

​ (3)利用消息断点

​ 许多序列号保护软件都有一个按钮,当按下和释放鼠标时,将发送WM_LBUTTONDOWN(O201h)和WM LBUTTONUP(O202h)消息。因此,用这个消息下断点很容易就能找到按钮的事件代码。

​ (4)利用提示信息

​ 目前大多数软件在设计时采用了人机对话的方式。所谓人机对话,即软件在执行一段程序之后会显示一串提示信息,以反映该段程序运行后的状态。例如,在TraceMe实例中输入假序列号,会显示“序列号错误,再来一次”。可以用OllyDbg、IDA等反汇编工具查找相应的字符串,定位到相关代码处。

​ 用OllyDbg打开TraceMe..exe实例,单击右键,在弹出的快捷菜单中执行“Search for'”→“AIl referenced text strings”(“查找”→“所有参考文本字串”)命令,OllyDbg将列出程序中出现的字符串。但OllyDbg自带的这个功能对中文支持得不好,因此建议使用Ultra String Reference插件。安装插件后,在右键快捷菜单中执 行“Ultra String Reference ” → “Find ASCII'”命令,即可列出中文字符串,双击相关字符串即可定位到所需代码处。

  1. 字符串比较形式

​ 在序列号分析过程中,字符串处理是一个重点,因此我们必须掌握一定的分析技能。加密者为了有效防止解密者修改跳转指令,往往会采取一些技巧,从而迂回比较字符串。

​ (1)寄存器直接比较

mov eax []
mov ebx []
cmp eax,ebx
jz(jnz) xxxx

​ (2)函数比较a

mov eax []
mov ebx []
call xxxxxxxx
test eax,eax
jz(jnz)

​ 在这种情况下,call指令一般是一个BOOL函数,其结果通过eax返回。在分析时,要关注该call指令返回时处理eax的代码。call指令中的代码如下。

cmp xxx,xxx
    jz Lable
    xor eax,eax
Lable: pop edi
    pop esi
    pop ebp
    ret

​ (3)函数比较b

push xxxx
push xxxx
call xxxxxxxx
test eax,eax
jz(jnz)

​ (4)串比较

lea edi []
lea esi []
repz cmosd ;比较字符串
jz(jnz)
0 条评论
某人
表情
可输入 255
目录