一、反调试破解及脱壳 – AnyDVD HD 8.5.1.0逆向破解全纪录

  这次的逆向目标选择了AnyDVD HD这款用来去除蓝光及DVD加密保护的软件。我找了一遍现有的程序,发现似乎只有AnyDVD HD这个软件可以破解我手上正版演唱会蓝光光盘。正版蓝光一般都有加密保护,Windows系统由于没有购买相关专利许可,因此并没有操作系统本地的播放软件,想要在PC上播放或者拷贝加密蓝光光盘只有靠破解,AnyDVD就是这样一款破解软件。因为SlySoft AnyDVD的特殊性,它在2016年已经因为法规死过一次了,几个月后又以Redfox的名义复活,然而之前SlySoft的许可证都不能用于Redfox的新版本了。正因为这样,我才不买它的“终身”许可证呢,说不定哪天就又死了。

  然而从目前互联网上能找到的资源来看,似乎还没有很好的AnyDVD破解版,找到的破解版不是不能用,就是加载蓝光光盘时出错。虽然AnyDVD HD有21天的试用许可证,但是试用许可证是和电脑硬件绑定的,对于我这种时不时的需求来说根本不够用,于是我就决定自己上,破解AnyDVD HD!

0x01 安装过程分析

AnyDVD安装过程

  首先是安装过程分析,软件的安装过程可以看到执行了哪些命令,可以看到安装程序除了软件自身目录内的文件之外,还在System32文件夹内添加了ElbyCDIO.dll这个动态链接库,以及在驱动程序目录drivers中添加了ElbyCDIO.sysAnyDVD.sys以及RegKill.sys三个驱动程序。可以打开注册表编辑器在HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Services中看到添加的相应驱动程序服务AnyDVD以及ElbyCDIO,并且ElbyCDIO为自启动。

ElbyCDIO服务注册表信息

0x02 静态分析及动态调试

1. 初步分析

AnyDVD.exe

  AnyDVD.exe是程序的入口文件,分析过程自然也要从程序入口开始,使用IDA反编译看了一下发现它的逻辑其实非常简单,就是设置了一些命令行参数然后去执行AnyDVDtray.exe,对比一下文件大小也可以发现,AnyDVDtray.exe要大得多,因此判断程序的主要逻辑都在AnyDVDtray.exe中,而AnyDVD.exe只是一个启动器。

AnyDVDtray.exe

  AnyDVDtray.exe这个文件内容非常庞大,直接看肯定是看不出来什么的,因此直接用动态调试边执行边研究。我是在虚拟机中运行的,正常运行会弹出提示框“Need full license to run in a virtual machine!”(我将系统语言更改为了英文,这样多语言环境下内存中的字符串都是ASCII码方便辨认),因此我第一个目标是定位这个弹出框的代码位置。根据弹出框的样式可以判断这应该是使用了Win32 API函数MessageBoxA,因此对MessageBoxA函数调用进行断点即可,然而这时我却遇到了一个问题,程序并没有在断点处停下,而是直接返回错误代码退出了。经过一些尝试后发现,有断点的情况下有可能会发生两种情况,一种是弹出框的内容变成了“File corrupted! Please run a virus-check, then reinstall AnyDVD.”,另一种是程序运行着就直接没有弹出框退出了。

  对于第一种情况,这些弹出框的字符串都可以在<AnyDVD安装目录>\language\AnyDVDen.lng中找到,因此可以确定这些弹出框就是AnyDVD程序自己的代码逻辑,并不是系统。进一步分析发现,只要对文件.text段内容进行了修改就会报此错误,因此这应该是软件的防修改措施。而软件断点int 3会将断点处的机器码修改为0xCC,也相当于修改了代码内容,所以会触发报错。为了破解能够继续进行,我们也必须首先破解软件的防修改。

  对于第二种情况,经过一些尝试发现,调试器无论是单步执行、软件断点还是硬件断点无法中断在某一条语句之后,这很有可能是软件的反调试措施。动态调试可以看到这个语句是sub_7B71C0中调用动态链接库ElbyCDIO.dllElbyCDIO_Perform函数中的DeviceIoControlDeviceIoControl是Win32 API,这是一个对驱动程序进行调用的函数,因此需要对ElbyCDIO.dll文件进行进一步分析。

sub_7B71C0中调用动态链接库ElbyCDIO.dll中的ElbyCDIO_Perform函数,可以看到程序中几乎所有的字符串都是加密的。

  虽然软件可执行文件中几乎所有常量字符串都是加密的,并且存在反调试措施等困难,我们还是利用动态调试分析以及MessageBoxA位于静态存储区的lpText参数硬件读写断点确定了“File corrupted! …”弹出框的语句位置为sub_7B6FE0中的MessageBoxA

sub_7B6FE0函数反编译C代码

  而sub_7B6FE0仅在sub_7B7CD0中有调用,并且调用ElbyCDIO_Perform的函数sub_7B71C0也仅在sub_7B7CD0中有调用。研究了下sub_7B7CD0的反编译代码后,我猜测这个函数整个是用来进行反调试与防修改的,但是对于这个函数我还是有许多的疑惑,并不能彻底理解它是在做什么,而且它的交叉引用非常多,而且似乎都是成对出现。为了验证我的猜测,我先尝试将整个sub_7B7CD0函数bypass,然而程序在后面的地方崩溃了,代码似乎执行到了非有效机器码的区域,这说明sub_7B7CD0还有其他的作用,对于这个函数我直到破解了反调试措施之后才完全明白了它的作用,具体的内容在后文中叙述。

2. 破解反调试

ElbyCDIO.dll

  前面提到ElbyCDIO_Perform可能和反调试措施有关,因此先来分析该函数,这个函数是__thiscall,因此它是ElbyCDIO类的成员函数Perform。它的内容非常简单,就是对设备hObject执行dwIoControlCode0x22E093DeviceIoControl调用,设备句柄hObject一般通过Win32 API函数CreateFileA获得。hObject是静态存储区数据,因此很容易追踪到赋值位置,可以发现它正是在sub_10003EF0函数中通过CreateFileA函数得到,lpFileName参数为\\.\ELBYCDIO,因此判断调用DeviceIoControl后执行的代码应该在ElbyCDIO.sys文件中。

ElbyCDIO_Perform函数反编译C代码

ElbyCDIO.sys

  在分析ElbyCDIO.sys之前,我们首先要对Windows驱动程序模型有大概的了解。具体来说,我需要知道在用户程序调用DeviceIoControl之后,驱动程序中运行的相应代码在哪里。通过文档可以知道,Windows会将用户程序的调用信息封装成一个IRP结构体传递给驱动程序的DispatchDeviceControl例程,而该标准例程的入口点一般在DriverEntry例程中进行绑定,因此先来分析DriverEntry函数,DispatchDeviceControl例程对应的IRP主要函数代码为IRP_MJ_DEVICE_CONTROL,在wdm.h头文件中可以查到#define IRP_MJ_DEVICE_CONTROL 0x0e,因此DispatchDeviceControl例程的入口点就是sub_13440

DriverEntry函数中标准例程的绑定(sub_13C00函数反编译C代码)

  在sub_13440函数中,我果然发现了IOCTL代码0x22E093的相关代码,并且很容易注意到ThreadHideFromDebugger这个参数。网上一查,果然ZwSetInformationThread的这个调用就是用来反调试的,这也是之前我调试时程序会莫名其妙退出的原因。那现在破解反调试就简单了,直接跳过这个调用就好。

sub_13440函数中IoControlCode0x22E093的反编译C代码部分

  然而此时还有一个麻烦点在于,我们的修改是在驱动程序中,而Windows限制了只有包含有效数字签名的sys驱动程序文件才能够运行。因此直接修改后的sys文件还无法执行,为了能执行修改后的驱动程序,我们先删去sys文件中原有的数字签名。使用WinHex打开ElbyCDIO.sys,数字签名位于文件的末尾,不难找到数字签名的起始位置,即0x8200,数字签名开头几个字节为数字签名长度,即0x2478,先把末尾的数字签名全部删去,之后在文件中搜索字节序列00 82 00 00 78 24(即数字签名地址加长度,在文件开头部分,注意大小端),将其全部置为零,这样就完全删去了原有的数字签名,得到了一个未被签名的驱动程序文件。

ElbyCDIO.sys中嵌入的数字签名信息

  接下来可以使用测试签名的方式运行修改后的驱动程序。首先需要配置系统使其允许加载已进行测试签名的驱动程序,之后可以利用MakeCert工具生成一个测试证书,最后用SignTool工具以及生成的测试证书对sys驱动程序文件进行数字签名,这样sys文件就可以成功执行了。MakeCert以及SignTool工具包含在Windows SDK中,也可以在安装Visual Studio后打开Developer PowerShell直接使用。签完名后用系统管理员权限打开PowerShell,将修改后的sys文件替换原来的C:\Windows\System32\drivers\ElbyCDIO.sys文件后,使用命令net start ElbyCDIO启动服务即可。

3. 加密代码段脱壳

AnyDVDtray.exe

sub_7B7CD0函数反编译C代码

  破解了反调试措施之后,就可以在动态调试中随心所欲的断点了,这回再来考察sub_7B7CD0这个函数。之前由于sub_7B71C0中包含反调试措施,直接跳过后,程序会在((int (*)(void))dword_D01FB8[3 * a1 - 3])()这个函数指针执行的地方发生异常,程序似乎执行到了非有效代码的位置。函数指针的赋值位于sub_7B7070中是固定值,因此可以直接在指针处进行硬件断点。

sub_7B7070函数反编译C代码

  这样我发现,程序运行到这里时,这个函数指针前后区域的代码都变成了有效代码,而这个函数指针所指的函数只是简单地返回一个整数。

解密后函数指针处的反编译汇编代码

  这下我彻底明白sub_7B7CD0这个函数的作用了,AnyDVDtray.exe中不只含有.text代码段和.data数据段,还含有.kitext1~.kitext6(没有.kitext4)五个具有读、写和执行权限的代码段,以及.kidata1~.kidata6(没有.kidata4)五个具有读写权限的数据段,这些段都是加密的,而sub_7B7CD0就是对这些段进行解密与再加密。

AnyDVDtray.exe程序段

  sub_7B7CD0的第一个参数int a1对应.kitext与.kidata段的序号。第二个参数char a2指示进行加密还是解密,0为解密,否则为加密。dword_D01FC0[3 * v4]用于储存相应段加解密的状态,0为处于加密状态,1为处于解密状态。dword_D01FB8[3 * v4]为测试.kitext段是否解密成功的函数,它只是简单的返回对应段的序号,如果.text段有修改,则解密不能成功,测试函数无法执行会抛出异常,之后在sub_7B7CD0对异常进行捕获,并在sub_7B6FE0中弹出“File corrupted! …”的错误框后退出程序。

sub_7B7CD0中对异常进行了捕获,如果测试函数不能执行,则弹框并退出程序。

  dword_D01FBC[3 * v4]为测试.kidata段是否解密成功的数据,解密后应等于相应段的序号。根据多次测试发现,修改.kitext段一些字节后,解密后只有修改的这几个字节无法正确解密,而修改.text段一些字节后,整个.kitext段都会解密错误,因此我推断应该是使用了对称加密算法并且是流式加密,以.text段整个的哈希值为密钥进行加解密,这样一来既实现了程序的防修改,又完成了对代码段的加壳。加解密的具体操作位于IOCTL代码为0x22E093的驱动程序代码中,byte_D66BD4byte_D66BE2dword_D66E0E这些数据位于.codata数据段中,根据名字猜测是与驱动程序共用的数据段,被用来向驱动程序传递需要进行加解密的信息。sub_7B7CD0在程序中总是成对出现,在需要用到相应段之前进行解密,用完后再加密。

  这时最好的方法是破解程序的加解密算法,让我们可以在程序之外得到解密的代码,并且使得.text段修改后程序也可以正常加解密。然而这是相对困难的,因此我采用一种更为简单的方式,我可以让程序本身帮我解密代码。只要不修改.text段以及对应段内容,就可以通过动态调试获取解密后的代码段。获取所有解密后的.ki段后,就可以在文件中替换掉本身加密了的段,之后bypass整个sub_7B7CD0函数即可实现加密段的脱壳。

  这样一来,就完成了程序的反调试破解以及加密代码段脱壳,可以说此次逆向已经取得了阶段性的成果,破解进行到这里感觉已经成功了一半了吧。

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注