《加密与解密》-演示版保护技术

序列号保护方式

序列号/注册码:用户把自己信息(用户名、邮件地址、机器特征码等)给公司,公司根据信息利用预先编写的一个用于计算注册码的程序(注册机/keygen)算出一个序列号,通过邮件等形式发给用户。用户使用注册信息和序列号完成验证,软件就会取消限制(时间、功能等)

序列号保护机制

软件验证序列号,其实就是验证用户名和序列号之间的数学映射关系

  • 用户名自变量,函数$F$求注册码:$序列号=F(用户名)$,很脆弱

破解思路:提取函数$F$;修改比较指令绕开检查

  • 通过注册码验证用户名正确性:仍然是$序列号=F(用户名)$,但F是一个可逆变换,软件检查注册码时,利用$F$的逆变化$F^{-1}$对用户输入的注册码进行变换,结果与用户名相同则说明是正确的注册码

破解思路:修改比较指令;逆向$F^{-1}$得到$F$;穷举法

  • 通过对等函数检查注册码:$F_{1}(用户名)=F_{2}(序列号)$
  • 同时将用户名和注册码作为自变量:$特定值=F_3(用户名,序列号)$

注册码的复杂性问题归根到底是一个数学问题

如何攻击序列号保护机制

  • 跟踪输入注册码后的判断找到注册码:软件会调用一些标准的API将用户输入的注册码字符串复制到自己的缓冲区中,常用的包括GetWindowTextA(W)、GetDlgItemTextA(W)、GetDlgItemInt、hmemcpy等,此外对话框也是切入点,如MessageBoxA(W)、MessageBoxExA(W)、ShowWindow、MessageBoxIndirectA(W)、CreateDialogParamA(W)、CreateDialogIndirectParamA(W)、DialogBoxParamA(W)、DialogBoxIndirectParamA(W)等
  • 跟踪程序启动时对注册码的判断过程:程序每次启动时都需要将注册码读出并加以判断,根据序列号的位置,如注册表,使用RegQueryValueExA(W);INI文件,使用GetPrivateProfileStringA(W)、GetPrivateProfileIntA(W)、GetProfileIntA(W)、GetProfileStringA(W);一般文件,使用CreateFileA(W)、_lopen()等

数据约束性:明文比较注册码的保护方式中,正确的注册码会在某个时刻出现在内存中

利用消息断点:按下和释放鼠标时将发送消息,可以用这个消息下断点找事件代码

利用提示信息:就是关键字符串定位

字符串比较形式

  1. 寄存器直接比较
  2. 函数比较,通常是bool函数
  3. 串比较:repz cmpsd

警告窗口(Nag)

Nag窗口用来提醒用户购买正式版本,去除Nag的方法

  • 修改程序的资源:设置Nag窗口属性为不可见变相去除,若要完全去除要找到创建窗口的代码并跳过,显示窗口的常用函数有MessgaeBoxA(W)、MessageBoxExA(W)、DialogBoxParamA(W)、ShowWindow、CreateWIndowExA(W)等
  • 静态分析
  • 动态分析

时间限制

两种限制:限制每次运行的时长;每次运行的时长时间不限,但有时间限制

计时器

在Windows中,计时器的选择如下

  1. setTimer()函数

    UINT SetTimer (HWND hWnd, UINT nIDEvent, UINT uElaspe, TIMERPROC lpTimerFunc)
    • hWnd:窗口句柄。若计时器到时,系统将向这个窗口发送WM_TIMER消息
    • nIDEvent:计时器标识
    • uElapse:指定计时器时间间隔(毫秒单位)
    • TIMERPROC:回调函数。若计时器超时,将调用此函数。若参数为NULL,超时时将向相应的窗口发送WM_TIMER消息
  2. 高精度的多媒体计时器

应用程序可以跳过调用timeSetEvent()函数来启动一个媒体计时器

  1. GetTickCount()函数:返回的是系统自成功启动以来所经过的时间(毫秒)
  2. timeGetTime()函数:返回Windows自启动后所经过的时间(毫秒)

时间限制

考虑周全软件需要保存两个时间值:

  • 安装时间:第一次运行/安装软件时记录,最好存储在多个地方,防止解密者通过修改或删除,使得软件可以无限使用
  • 最近一次运行的日期:防止用户将机器日期改回去。每次退出取出该日期与当前比较,若当前日期大于该日期,则替换。每次启动也要读取该日期,与当前日期比较,若该日期大于当前日期,说明用户修改了机器时间

拆解时间限制保护

  • 直接跳过SetTimer函数(jmp)
  • 利用WM_TIMER消息(0x0113),搜索113字串,定位跳转函数

菜单功能限制

这类程序一般是Demo版的,菜单或窗口的部分选项为灰色,无法使用。这种受限程序一般分为2种

  • 试用版和正式版完全不同:G了,没法获取正式版
  • 同一个文件,注册后就可以用正式版:破解启动!

相关函数

  1. EnableMenuItem():

    BOOL EnableMenuItem(HMENU hMenu, UINT uIDEnableItem, UINT uEnable)
    • hMenu:菜单句柄
    • uIDEnableItem:想要允许或禁止的一个菜单条目的标识符
    • uEnable:控制标志,包括MF_ENABLED、MF_GRAYED、MF_DISABLED、MF_BYCOMMAND、MF_BYPOSITION
  2. EnableWindow():

    BOOL EnableWindow(HWND hWnd, BOOL bEnable)
    • hwnd:窗口句柄
    • hEnable:TRUE允许,FALSE禁止

拆解菜单限制保护

可以在上述函数调用时修改enable的标志,比如push 1改为push 0

KeyFile保护

KeyFile是一种利用文件来注册软件的保护方式,其内容是一些加密或未加密的数据,其中可能包含用户名、注册码等信息

网络验证

这种做法可以将一些关键数据放到服务器上。破解网络验证的思路是拦截服务器返回的数据包,分析程序如何处理数据包的

相关函数

  1. send():

    int send(
        SOCKET s, // 套接字描述符
        const char FAR *buf, // 缓冲区
        int len, // 实际要发送数据的字节数
        int flags // 附加标志,一般为0
    );
  2. recv():

    int recv(
        SOCKET s, // 套接字描述符
        char FAR *buf, // 缓冲区
        int len, // 缓冲区buf的长度
        int flags // 附加标志,一般为0
    )

破解网络验证的一般思路

如果网络验证的数据包内容固定,可以将数据包抓取,写一个本地服务端来模拟服务器;否则需要分析结构,找到相应算法

光盘检测

光盘检测是为了防止用户将正版拷贝安装在多台机器上且同时使用

相关函数

  1. GetDriveType():获取磁盘驱动器的类型

返回值:0-驱动器不能识别;1-根目录不存在;2-移动存储器;3-固定驱动器;4-远程驱动器;5-CD-ROM驱动器;6-RAM disk

  1. GetLogicalDrives():获取逻辑驱动器符号
  2. GetLogicalDriveStrings():获取当前所有逻辑驱动器的根驱动器路径

    DWORD GetLogicalDriveStrings(
        DWORD nBufferLength,    // 缓冲区大小
        LPTSTR lpBuffer            // 缓冲区地址,成功返回结果形式为“c:\d:\”
    )
  3. GetFileAttributes():判断指定文件的属性

只运行一个实例

Windows多任务OS,应用程序可以多次运行形成多个运行实例,但基于安全性等考虑,要求程序只运行1个实例

实现方法

  1. 查找窗口法:FindWindowA、GetWindowText查找具有相同窗口名和标题的窗口
  2. 使用互斥对象:CreateMutexA函数实现
  3. 使用共享区块
最后修改:2024 年 05 月 06 日
如果觉得我的文章对你有用,请随意赞赏