TEC-8 硬件系统模拟软件逆向分析
TEC-8模拟软件整体实现逻辑
本软件是一个图形化界面,开启了很多窗口。32位,调用了很多Win32 api,本次计时器的破解突破口就在这。
使用这个程序,用户主要靠鼠标点击,拖动,滑轮滚动,键盘输入等操作。这些操作本质是通过外设向操作系统发送了一些消息,操作系统通过窗口句柄把消息发送给对应窗口过程函数进行处理进而把处理结果通过窗口再次呈现给用户。
由此引入windows的消息处理机制,本文不做细讲,有兴趣的可以自行搜索
主要成果
- 不用联网
- timekey.txt不用更新,但这个文件不能少
- license.txt内容任修改,避免溯源
- 倒计时65年
- jz,jc指令offset值更新,是4位有符号整数,原来的程序是4位无符号
主要的加密流程
先运行以下软件看看逻辑,发现Timekey文件出错
这其实是一个提示,说明进入子线程一定是要读取Timekey文件内容然后进行比较的,我们的主要目的是逆向分析程序检测Timekey的过程,并实现绕过,因此可以由寻找引用字符串Timekey的函数入手。
发现引用到的代码位置在sub_43F450内,加密函数整体的逻辑显得比较复杂,调用了很多不知道功能的函数。
但是,发现在引用了TEC8Timekey.txt字符串后,下面调用了系统函数检验了某文件是否存在:
猜测这就是检验TECtimekey.txt文件是否存在。可以在43FCE1处下断点,然后先把文件夹里面的timekey.txt移出去,再按F9进行动调,最后的结果是程序在43FCE8处跳转到4414BE处,也就是伪代码的lable_78。
将txt文件还原到文件夹中,程序在43FCE8顺序执行。
那么,从43FCE1到4414BE里面则是实现了验证函数和比较逻辑。
目测了一下汇编代码,发现程序顺序执行的特征较为明显,没有打量循环的嵌套,而且最后一定会执行到4414BE,因此我们从4414BE往上几个代码块的首地址均下一个断点,看看补充了错误的Timekey.txt文件最先跳转到哪个地址,最后执行到4414BE,如下图:
我一共往上下了5个断点,上图显示了后4个,第一个的地址是44125B,需要注意的是不要下断点下多了,不然可能会断在加密函数而不是比较逻辑上。
从头开始执行,程序最后断在了第一个断点44125B上,这个函数逻辑疑点很多:
首先是两处跳转逻辑,再是这个SetVisible函数名总感觉在提示些什么内容,好像就是使窗口可视化的函数,极有可能是启动子线程的关键函数。
因此,尝试改变函数的逻辑让他在44118A和44119A处不跳转到44125B位置,而去执行SetVisible函数看看有什么效果。(源码应该是两处jz,我这边已经改成了jnz),动调最后可以验证发现子线程正常开启。
这说明关键比较对应的伪代码如下图:
将更改好的字节码apply到exe文件即可。
联网功能破解
程序如何联网?无非是发送一些请求,然后接受一些数据。我们暂且抛开程序联网的目的是什么,先定位到它是由哪个函数执行的。要发送数据肯定得先有本机地址和目标地址吧,这就注定了程序一定有相关的程序获取本机地址,而且应该使用相关封装好了的API函数去执行发送和接受数据的请求。
下图是通过检索 “recv” 字符串定位到的函数:
通过交叉引用看看谁调用了他们:
还是不太想看干了啥,我们最后的目的是干掉函数的联网请求,有两种方法,
- 在加密算法中禁止函数调用该联网函数
- 结合联网函数的返回值绕过逻辑判断条件
继续向上搜索调用该函数的函数两次,就发现回到了加密算法部分,如下图所示:
发现if语句中有多次相同的联网请求函数,尝试修改if判断条件使其永真,于是将其中一条语句通过修改jnz为jz改为非,
patch文件以后,关闭网络连接发现仍然可以正常启动程序,至此成功绕过程序需要联网的要求
timekey破解
开发者给出的限制是Timekey.txt里面的内容需要定期更新,大概是要每一个月更新一次。那么程序肯定是要读取timekey.txt里面的内容的。txt里面的具体内容是年份月份加一个长字符串,推测是程序先获取一个本机时间或者互联网时间,拿这个时间去进行一个加密运算,最后把得到的加密结果与后面的长字符串进行比对,如果结果相同则通过验证。
那么现在破解的思路有两个:
- 研究验证函数,可以在生成加密结果的位置下断点将生成的结果提取出来放进txt文件中
- 改变程序的控制流,在加密所得结果与txt文件中原本的字符串进行比较的地方下断点,改变程序原来的跳转逻辑
对于第一种方法,每一个月要更新一次txt文件,还是很麻烦,不如第二种方法来的优雅
具体的破解过程在 “主要的加密流程” 中已经给出,本来以为已经结束了,但是在把timekey.txt文件中2023.6月对应的密钥删除后程序执行失败,如下图所示:
原因肯定是检测了timekey.txt中有没有当前的网络时间或者本机时间。我们执行到这一步的时候已经处理掉了程序的联网功能,猜测应该是拿txt里面的时间和本机时间进行了比较,验证这个猜测是通过修改本机时间为2022.12(txt文件里面有),成功执行主函数。
动调找到关键跳转,本来想的是找到对比两个字符串的地方,但是后来发现找的不是很方便,最后还是屈服于打七寸了,下图呈现了一个关键跳转。
原来4400DF处是jnz,执行完后会直接跳过我们执行主函数的语句,因此修改为jz,修改后主程序可以正常执行了。
计时器破解
接上一篇破解报告,我们在跟到开启新线程,也就是关键跳转指令之后,发现TEC-8功能界面(以下统称子进程)立刻被打开,说明图形化窗口已经加载好,计时器也已经加载完毕。按照传统的单步方式动调肯定是行不通了。
新的思路是从正向开发的角度来看,如果你写一个程序要运行一个计时器,你会采取什么样的思路?
我们结合计时器运行的方式来看:
- 计时器是倒计时态,在剩余时间大于1分钟时,他是每减少一分钟更新一次计时器的打印;在剩余时间小于1分钟时,他是每一秒钟更新一次打印
程序员都是懒的,能调用就不会自己写,我们可以上网搜索一下写一个计时器会调用什么函数:
因此新的思路就是在开启新的计时器,也就是调用其系统函数的地方下断点,然后跟几步让其返回调用的函数,我们就可以看到哪个地方调用了这个系统函数进而确定哪个地方开启了计时器。
根据搜索的反馈,我们更偏向于SetTimer函数,因为他可以设置定时器时间间隔和过程函数,比较符合我们刚才观察到的定时器运行结果。去IDA里面的Import栏(导入表)找找:
(快捷搜索键ctrl+f),搜索一下找到了,在对应的汇编界面下断点。
程序停在了子线程调用之前,符合我们的最初猜测,因为不能等图像化界面加载完毕之后再去初始化计时器。但是貌似这个SetTimer函数被调用了不下一次,我们反复按了F9键9次左右才进入子线程,说实话有点难跟。关键一次的调用应该是在父线程完成了对license.txt和timekey的校验然后对子进程初始化的时候进行的,如下图:
接着跟几步,从库函数中返回到主逻辑里面来。
遂发现是箭头所指的调用,但是其定时器执行函数为0,由此判断并不是我们研究的逻辑。
但是现在我们确定了本软件一定是使用了这个Settimer这个系统调用并且重复使用了多次,不排除除了计时器以外还有其他功能实现需要调用他。
既然掐不了头那就去尾吧,定时器结束后子线程立刻终止,这中间是如何实现的?这就回到了开头的windows消息机制:
当SetTimer定时器结束后需要终止子线程时,可以调用PostThreadMessage或PostQuitMessage函数向子线程发送消息,通知它退出。具体而言:
- 使用PostThreadMessage函数向指定的线程发送消息。
- 使用PostQuitMessage函数向当前线程发送WM_QUIT消息,表示要退出消息循环,并结束线程。
在消息循环中检查到WM_QUIT消息时,即可退出线程。需要注意的是,在使用PostThreadMessage函数向子线程发送消息时,必须保证消息循环正常运行,否则子线程将无法接收到消息。
计时器窗口本身属于子线程,因此用和SetTimeer同样的方法下断,然后等计时器倒计时结束,当时等了30分钟,果然断在了PostQuitMessage上。由于发送了退出消息后动调就结束了,我们通过交叉引用的方式去看哪个函数调用了PostQuitMessage,通过两次交叉引用定位到了关键代码:
图中sub_6c37D4调用了PostQuitMessage,0x7fffffff原本是1800,也就是半个小时。至此定时器破解完成。
offset 偏移量更改
ce找内存下硬写断点查内存
用cheat engine挂载TEC-8的进程
可以看到现在PC的值为1,用ce首次扫描1。
然后继续点击单小拍到23拍,pc值更新为0x04,用ce再次扫描0x04(16进制)。如下图:
可以发现满足上述变化条件的内存地址比较少,我们一个一个去定位。
我们点击这4个地址,让他呈现在下面的窗口中,点击他们的数值进行改写,随便改写一个数值,然后点击单小拍,我们看PC的值是否更新,更新后可以唯一确定保存PC值的地址为0x7D799078
具体来说,pc的值更新完之后需要将值再次写入对应的地址,进而显示在PC的编辑框中,我们需要用专业的调试器动调这个进程。在0x7D799078下一个4字节的硬件写入断点,这样就可以在更新pc值之前找到实现加法的函数位置。
清空当前页面的所有操作,再次跟到第22拍,然后下硬件写入断点,按一次单小拍
最后定位到的函数位置如下图:
最初断在了4363A7,我单步跟了几步,发现ECX的值已经更新为我们需要的,往下看几眼就能看到4363E7处有一个赋值操作,操作地址刚好是edx里面我们刚才下的硬件写入断点。
由此关键函数已经定位。
代码改写
现在的问题是程序原来对数据处理有问题,1011应该处理为-5而不是11,因此我们需要在有限的地址空间内patch程序,使其正常的执行判断功能:
很快就能想到利用符号位下手,如果符号位为1,即和数字8做与运算结果不为0,就应该处理为负数,即-16。
如果为0,即说明小于8,直接相加即可。
我观察到只要在4363A7之后,4363BE之前改写完毕,即不影响esi最后的值,然后把ecx赋值正确即可,下面是我的汇编代码:
其中,[esp+10h]是动调到这一步发现的结果,保存的是offset的值
改写完之后apply patch即可。
至此所有任务结束
总结
关于修改offset偏移量,最开始没有想着用ce下硬写,我猜想加法操作应该是某一个窗口的窗口过程函数,在接收到一个开发者自定义的消息之后拿offset和pc做加法,因此想着在dispatchmessage函数下断点去跟踪一个具体的消息,但是不能确定做加法运算的窗口时哪一个,尝试跟了一个可能的消息,步入系统函数之后也出不来了,查资料发现消息发给了操作系统处理,对这一部分机理不是很理解,可能是导致动调失败的关键。等到学习了操作系统之后再进行有关方向的研究吧。
另外此软件本身还有些功能有缺陷,比如检验74181逻辑和运算功能时电平信号对应功能和书本上不一致的问题。鉴于老师的要求就不公开软件了,仅分享逆向思路