Loading... # 《加密与解密》-演示版保护技术 ## 序列号保护方式 序列号/注册码:用户把自己信息(用户名、邮件地址、机器特征码等)给公司,公司根据信息利用预先编写的一个用于计算注册码的程序(注册机/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()函数 ~~~c UINT SetTimer (HWND hWnd, UINT nIDEvent, UINT uElaspe, TIMERPROC lpTimerFunc) ~~~ * hWnd:窗口句柄。若计时器到时,系统将向这个窗口发送WM_TIMER消息 * nIDEvent:计时器标识 * uElapse:指定计时器时间间隔(毫秒单位) * TIMERPROC:回调函数。若计时器超时,将调用此函数。若参数为NULL,超时时将向相应的窗口发送WM_TIMER消息 2. 高精度的多媒体计时器 应用程序可以跳过调用timeSetEvent()函数来启动一个媒体计时器 3. GetTickCount()函数:返回的是系统自成功启动以来所经过的时间(毫秒) 4. timeGetTime()函数:返回Windows自启动后所经过的时间(毫秒) ### 时间限制 考虑周全软件需要保存两个时间值: * 安装时间:第一次运行/安装软件时记录,最好存储在多个地方,防止解密者通过修改或删除,使得软件可以无限使用 * 最近一次运行的日期:防止用户将机器日期改回去。每次退出取出该日期与当前比较,若当前日期大于该日期,则替换。每次启动也要读取该日期,与当前日期比较,若该日期大于当前日期,说明用户修改了机器时间 ### 拆解时间限制保护 * 直接跳过SetTimer函数(jmp) * 利用WM_TIMER消息(0x0113),搜索113字串,定位跳转函数 ## 菜单功能限制 这类程序一般是Demo版的,菜单或窗口的部分选项为灰色,无法使用。这种受限程序一般分为2种 * 试用版和正式版完全不同:G了,没法获取正式版 * 同一个文件,注册后就可以用正式版:破解启动! ### 相关函数 1. EnableMenuItem(): ~~~c BOOL EnableMenuItem(HMENU hMenu, UINT uIDEnableItem, UINT uEnable) ~~~ * hMenu:菜单句柄 * uIDEnableItem:想要允许或禁止的一个菜单条目的标识符 * uEnable:控制标志,包括MF_ENABLED、MF_GRAYED、MF_DISABLED、MF_BYCOMMAND、MF_BYPOSITION 2. EnableWindow(): ~~~c BOOL EnableWindow(HWND hWnd, BOOL bEnable) ~~~ * hwnd:窗口句柄 * hEnable:TRUE允许,FALSE禁止 ### 拆解菜单限制保护 可以在上述函数调用时修改enable的标志,比如push 1改为push 0 ## KeyFile保护 KeyFile是一种利用文件来注册软件的保护方式,其内容是一些加密或未加密的数据,其中可能包含用户名、注册码等信息 ## 网络验证 这种做法可以将一些关键数据放到服务器上。破解网络验证的思路是拦截服务器返回的数据包,分析程序如何处理数据包的 ### 相关函数 1. send(): ~~~c int send( SOCKET s, // 套接字描述符 const char FAR *buf, // 缓冲区 int len, // 实际要发送数据的字节数 int flags // 附加标志,一般为0 ); ~~~ 2. recv(): ~~~c 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 2. GetLogicalDrives():获取逻辑驱动器符号 3. GetLogicalDriveStrings():获取当前所有逻辑驱动器的根驱动器路径 ~~~c DWORD GetLogicalDriveStrings( DWORD nBufferLength, // 缓冲区大小 LPTSTR lpBuffer // 缓冲区地址,成功返回结果形式为“c:\d:\” ) ~~~ 4. GetFileAttributes():判断指定文件的属性 ## 只运行一个实例 Windows多任务OS,应用程序可以多次运行形成多个运行实例,但基于安全性等考虑,要求程序只运行1个实例 ### 实现方法 1. 查找窗口法:FindWindowA、GetWindowText查找具有相同窗口名和标题的窗口 2. 使用互斥对象:CreateMutexA函数实现 3. 使用共享区块 最后修改:2024 年 05 月 06 日 © 允许规范转载 打赏 赞赏作者 支付宝微信 赞 0 如果觉得我的文章对你有用,请随意赞赏