无界趣连投屏软件逆向分析与补丁制作

查看 32|回复 3
作者:caoyuze   
一、前言
上次我们分析了TC Games投屏软件,这次我们来分析另外一款类似的软件:无界趣连。
二、分析
1. 查壳


1.png (39.6 KB, 下载次数: 0)
下载附件
1
2024-6-30 06:48 上传

C++,原生Win32写的界面,没有用QT、MFC之类的界面库,还是比较狠的(虽然当年大二的Windows程序设计已经基本忘完了,但还记得原生Win32写界面是真的难受),无壳。
2. 分析
(1)首先,我们来使用一下VIP的功能,比如键位,看看它有什么提示:


2.png (1.88 MB, 下载次数: 0)
下载附件
2
2024-6-30 06:48 上传

我们猜测在使用VIP功能的时候,会调用判断是否是VIP的函数进行判断,如果不是就弹出提示框。如图所示,在我们使用键盘的时候弹出来了个提示框,这种提示框没见过,不知道是调用的什么API,从关键函数入手这条路就暂时行不通了。
(2)那么,接下来我们就用x64dbg进行调试,搜索一下关键字符串,看能不能从这里入手:


3.png (84.08 KB, 下载次数: 0)
下载附件
3
2024-6-30 06:48 上传

芜湖,没有查到提示框内的整个字符串,那么去找弹出提示框的函数代码的这条路基本就被堵死了,不过好在有点收获,有会员相关的关键字眼。
(3)接下来,我们就从开发者的角度分析这个软件。首先,它要联网获取账户信息,必定在我们登录账户时会进行网络请求,请求成功后,会把账户的信息的所有字段存放在内存中,以供判断账户的状态,比如是否是会员、会员到期时间等等,那么我们就可以从账户信息的字段名入手,这里我们再重新搜vip,看有没有关键信息:


4.png (108.19 KB, 下载次数: 0)
下载附件
4
2024-6-30 06:48 上传

果不其然,让我们找到了突破口,我们进去看看:


5.png (176.68 KB, 下载次数: 0)
下载附件
5
2024-6-30 06:48 上传

这几个push入栈的字符串参数应该就是账户信息的字段名,而且这几个地方都有共同点,都调用ldremote.D63160的函数,还都只传了1个字符串参数,那么不难猜出,ldremote.D63160的查询字段信息的函数大致是这样的:
int* GetInfo(string field) {
    xxx;
    xxx;
    return xxx;
}
而这部分整个查询所有用户信息字段函数的执行逻辑大致这样:
int Init() {
    xxx;
    int* viplevel = GetInfo("viplevel");
    xxx;
    int* viptype = GetInfo("viptype");
    xxx;
    int* astrict = GetInfo("astrict");
    xxx;
    int* cutofftime = GetInfo("cutofftime");
    xxx;
    return xxx;
}
(4)这里,我们先不急着去分析GetInfo(名字不一定是这个)函数的具体汇编代码,我们先在这几个Call ldremote.D63160处下断点,先来看看我们当前的没有VIP的账户的这几个字段查询返回结果是什么:


6.png (204.79 KB, 下载次数: 0)
下载附件
6
2024-6-30 06:48 上传

函数调用完后,一般结果的地址是存放在eax寄存器中的,然后再内存窗口中我们跳转到eax地址,查看具体的返回结果值:


7.png (68.47 KB, 下载次数: 0)
下载附件
7
2024-6-30 06:48 上传



8.png (58.31 KB, 下载次数: 0)
下载附件
8
2024-6-30 06:48 上传



9.png (68.99 KB, 下载次数: 0)
下载附件
9
2024-6-30 06:48 上传

我们看到viplevel、viptype、astrict和cutofftime字段的查询结果值分别是0、1、1和1719064261,从这几个字段的英文来看,这几个分别代表了VIP等级、VIP类型、限制(暂时不知道有什么用)和截至时间。这里我们注意到cutofftime的1719064261这个值是不是很熟悉?没错,很明显这个就是时间戳,那么我们接下来去随便找个在线的网站将这个值计算下具体日期是什么:


10.png (28 KB, 下载次数: 0)
下载附件
10
2024-6-30 06:48 上传

果然,这个就是我的账户的VIP到期时间(刚注册的时候送了3天会员,VIP到期时间就是这个)。同时,也进一步证实了ldremote.D63160处的函数功能和我们猜想的一样,就是查询用户字段信息的功能。
(5)接下来,我们就分析下ldremote.D63160处的字段信息查询函数的具体逻辑:


11.png (77.07 KB, 下载次数: 0)
下载附件
11
2024-6-30 06:49 上传

这里我们看到,[ebp+8]的值还是我们传入的查询的字符串参数,而这里又调用了ldremote.D63010处的函数,传入的参数还是这个字符串,调用完成后,然后执行test eax, eax的指令,也就是对eax & eax,若结果为0,则会往函数的后半段跳,若不为0,则eax的值加上0x10之后再返回。我们再来看一下ldremote.D63010处函数的具体内容:


12.png (135.23 KB, 下载次数: 0)
下载附件
12
2024-6-30 06:49 上传

经过简单分析,可以看出来ldremote.D63010大致是在进行字符串查找的操作,在结合一些寄存器的对应变量值,可以总结出:ldremote.D63010的函数是对请求的账户信息的返回的结果(可能是json,也可能是其他的自己定义的存放键值对的数据格式)进行处理和查询,若结果中有这个字段,则返回此字符串字段的地址,若没有,则返回结果是0,而此函数返回后,在ldremote.D63160这个信息查询函数中,接着判断eax & eax是否为0,若不为0,则说明有这个字段,同时返回eax+0x10的地址,也就是存放这个字段对应的结果的地址,若没有查询到这个字段,则返回0x14CB85C这个地址,经过调试,这个地址存放的值为0,那么整个字段信息查询函数的功能也就清晰明了了:
int* GetInfo(string field) {
    int address = GetAddressFromResult(field);
    if (address == 0) {
        return 0x14CB85C; // 0x14CB85C地址存放的数值是0
    } else {
        return address + 0x10;
    }
}
这里只是大致写了下伪代码,不要在意细节,我们看到address即为我们要查询的字段的字符串指针地址,address+0x10即为此字段的值存放的地址,那么,整个程序的初始获取账户信息的逻辑我们就清晰明了了。
3. 修改
(1)那么,接下来,我们就从获取到的账户信息入手,看能否解锁VIP功能。首先,我们能想到的是从cutofftime字段入手,因为到期时间大于现在的时间,账户才能有VIP功能,那么接下来,我们可以这样修改,在上面的GetInfo函数执行ret指令之前,判断我们传入的field字段具体是什么,若是cutofftime字段,我们则把eax存放的返回结果地址指向的内存的值进行修改,就达到了我们想要的修改数据的效果。
(2)话不多说,我们开始修改汇编代码,我们先找一片空白区域,用于编写我们的判断field参数和修改返回结果的汇编代码,比如说汇编代码的最后面:


13.png (83.66 KB, 下载次数: 0)
下载附件
13
2024-6-30 06:49 上传

这里我们先把00空指令区域改成int3指令(强迫症,不改也行),然后再写我们自己的汇编代码:


14.png (46.39 KB, 下载次数: 0)
下载附件
14
2024-6-30 06:49 上传

这里最前面还要加1条add eax,0x10的原因是,jmp指令占5个字节,而前面的ret之前


15.png (20.26 KB, 下载次数: 0)
下载附件
15
2024-6-30 06:49 上传

只有4个字节,所以我们只能把add指令改成jmp指令跳转到我们的自定义汇编代码位置,这之后再执行add指令,如图所示:


16.png (22.4 KB, 下载次数: 0)
下载附件
16
2024-6-30 06:49 上传

在我们的自定义汇编代码中,做的操作就是,判断查询字段的字符串开头4位是否为“cuto”(cutofftime整个字符串需要10个字节来存,为了方便,这里只判断前4个字节即可达到效果),这里要注意字节存储方式是小端:


17.png (16.63 KB, 下载次数: 0)
下载附件
17
2024-6-30 06:49 上传

“cuto”的ASCII码是63 75 74 6F,小端存储模式(低地址存低位)我们就需要反着来,判断是否为0x6F747563,若是的话就代表GetInfo函数的参数是“cutofftime”,查询的是用户的会员到期时间,然后我们就把eax寄存器存放的返回结果地址的内容改为0x7FFFFFFF,即32位有符号数据的最大值,这个时间戳转换成时间就是2038年(才想起来原来Windows的KMS38激活到2038年就是这么来的),这样就达到了我们的目的,然后我们先保存一个版本,看看这样修改有没有达到效果:


18.png (36.89 KB, 下载次数: 0)
下载附件
18
2024-6-30 06:49 上传

运行一下修改后的ldremote1.exe:


19.png (45.42 KB, 下载次数: 0)
下载附件
19
2024-6-30 06:49 上传

芜湖,没有效果,如果是会员的话这个按钮就是“续费会员”,并且经过测试,连上手机后也还是没有键盘控制的功能,那么看来判断账户会员的关键字段就不是cutofftime了,这个只是用来显示你账户到期时间信息的。
(3)从cutofftime字段入手不行,那么我们再来换个思路,就从这个“开通会员”按钮来,若是会员,它就会变成“续费会员”,那么我们直接搜索“开通会员”字符串,并下断点:


20.png (31.05 KB, 下载次数: 0)
下载附件
20
2024-6-30 06:49 上传

这条push指令的位置应该就是关键点,我们过去看看:


21.png (57.73 KB, 下载次数: 0)
下载附件
21
2024-6-30 06:49 上传

我们来往前找找从哪里跳转到这部分代码的:


22.png (50.24 KB, 下载次数: 0)
下载附件
22
2024-6-30 06:49 上传

我们看到是从这里跳转过去的(地址变了不要在意,这里是我之前ldremote程序的分析和备注),经过分析,这里我们看到,若[eax+0x244]的值不为0,则为没有会员,显示“开通会员”按钮,若为0,则有会员,显示“续费会员”按钮,上面还有对[eax+240存放的值的判断],整体来说就是:当1
那么我们在cmp指令处下断点,并修改此时[eax+244]的值看有无效果:


23.png (121.73 KB, 下载次数: 0)
下载附件
23
2024-6-30 06:49 上传

然后继续运行程序:


24.png (57.86 KB, 下载次数: 0)
下载附件
24
2024-6-30 06:49 上传

芜湖,经过测试,果然有了VIP功能,在连上手机后,按键的功能也可以使用了,看来前面的cutofftime字段真的不影响会员功能的判断,仅仅是用来这里显示到期时间用的。
4. 再分析
(1)经过我们前面的分析与修改,发现它并没有具体的判断是否是会员的函数,而是从内存的一些特定地址拿值来判断的,这片内存区域应该就是存放用户信息字段值的位置。在前面的VIP功能判断的关键位置,我们看到关键的2个值是[eax+240]和[eax+244],但是我们并不知道这2个值是和前面的哪2个账户字段值相关联的,通过分析这部分判断前的代码区域也找不到头绪,那么接下来,我们就可以在前面调用GetInfo()函数的地方下断点,然后执行完之后,修改为别的特定返回结果,然后再在会员功能判断区域这里查看[eax+240]和[eax+244]的值是和前面哪2个我们修改后的返回结果相同。
(2)这里,我们分别把viptype查询结果改为3(和前面我们用到的关键值不一样即可),astrict查询结果改为4,cutofftime就不用改了,已经知道它的作用了,然后整个区域也没看到别的关键字眼,比如regtime、saveflowswitch、isbindphone等很明显就和VIP判断无关了:


25.png (102.03 KB, 下载次数: 0)
下载附件
25
2024-6-30 06:49 上传



26.png (108.89 KB, 下载次数: 0)
下载附件
26
2024-6-30 06:49 上传

这里我们要显示成8位1字节的数,因为后面关键判断的指令是 cmp byte ptr [eax+244], 0x0,是用1字节判断的。
(3)然后,我们再接着执行到前面说的VIP判断关键位置,看[eax+240]和[eax+244]分别是多少:


27.png (105.79 KB, 下载次数: 0)
下载附件
27
2024-6-30 06:49 上传

可以看到,[eax+240]的值是3,和我们前面的viptype一样,那么第1个判断关键就是viptype字段,而[eax+244]为1,没有和我们前面修改结果一样的字段,不急,我们重新再来一遍:


28.png (66.69 KB, 下载次数: 0)
下载附件
28
2024-6-30 06:49 上传



29.png (103.21 KB, 下载次数: 0)
下载附件
29
2024-6-30 06:49 上传

经过测试,若astrict字段查询结果=0,则[eax+244]=0,若查询结果>0,则[eax+244]=1,那么应该是经过了表达式转换,比如[eax+244]=astrict > 0 ? 1 : 0,总体看来,就是[eax+244]和astrict字段信息是有关联的。
5. 总结
经过上面的分析,我们要解锁VIP功能,需要:(1
三、补丁
1. 手改
根据我们第二节的总结,可以直接改汇编代码,在astrict字段信息查询之后执行jmp指令跳转到一块儿没有指令的内存区域,然后在这个区域手写汇编代码,将eax寄存器中指向的内存区域的值(即函数返回结果)修改为0,即可解锁VIP功能,这里具体代码细节就不展示了,参考第二节第3小节的修改cutofftime字段查询结果部分即可。但是,在软件更新后还需要再次手改,有些麻烦,那么接下来我们就可以使用补丁工具做一个HOOK补丁。
2. 使用Baymax工具
(1)刚玩逆向不久,前几天发现了Baymax这个补丁工具,非常好用,那么我们就用它来制作补丁。


30.png (26.9 KB, 下载次数: 0)
下载附件
30
2024-6-30 06:49 上传

(2)这里我们看到,可以做异常中断补丁或者搜索替换补丁,如果我们要用上面改汇编代码的方法的话,我们就选择搜索替换补丁。这里异常中断补丁也很好用,它相当于在搜索到的位置下断点,然后进行进行我们的补丁操作,比如可以修改寄存器的值、寄存器指向的内存区域的值等等:


31.png (68.48 KB, 下载次数: 0)
下载附件
31
2024-6-30 06:50 上传

但是还有个缺点,就是性能肯定不如搜索替换好,因为搜索替换补丁只用执行一次,直接就修改了程序的在内存中的汇编代码,而异常中断补丁在每次程序执行到我们的补丁位置时,都要执行相关的修改操作。
(3)这里我们就建1个异常中断补丁:


32.png (215.85 KB, 下载次数: 0)
下载附件
32
2024-6-30 06:50 上传

如图所示,分别填写虚拟地址、程序基址、首字节、补丁类型、补丁数据和频率,在0x00D9554A处,即查询字段信息函数执行完后,就修改eax寄存器指向的内存(也即查询结果)的数值为0。注意这里频率要选每次,因为程序可能是定时进行网络请求或者在有一些操作时进行网络请求,比如切换账户,肯定会重新请求查询账户信息,断点类型选择智能断点即可,智能断点基本上就是硬件断点,如果有问题可以切换为int3软件断点。
(4)补丁制作完成,我们查看补丁效果:


33.png (140.76 KB, 下载次数: 0)
下载附件
33
2024-6-30 06:50 上传

我们看到,补丁已经成功了,执行补丁后生成了2个dll文件,winmm.dll是劫持系统的winmm.dll,在程序调用winmm.dll的时候执行PYG.dll,而PYG.dll就是我们的补丁程序。
(5)其中断点的类型推荐使用int3软件断点,硬件断点的话需要设置中断模式为设置在所有线程(软件是多线程的,比如查询账户信息和界面更新可能不在一个线程),而且当补丁条目多的话,经过测试,我添加2条补丁(分别修改cutofftime和astrict字段查询结果)硬件断点会导致程序运行时CPU占用飙到30%多,可能硬件断点对于多线程多补丁支持不好,有问题,而使用int3软件断点模式就没有此问题。还有就是,补丁的寻址方式推荐使用特征码寻址,因为偏移地址可能变,比如某次程序员在我们要修改的地址前面又加了代码,那么我们要修改的地方的偏移地址肯定就增加了,而特征码搜索的话就不太怕这种情况,只要我们要改的位置的特征码不变,我们的补丁就一直有效。搞定,完美收工!

下载次数, 字段

caoyuze
OP
  

仅供交流学习使用:

无界趣连.zip
(1.6 MB, 下载次数: 23)
2024-6-30 07:25 上传
点击文件名下载附件
下载积分: 吾爱币 -1 CB

cyfwapj   

最强祖师游戏行不行?
Yifan2007   

比tcgame难多了还是直接拖dnspy然后改改伪代码来的爽
您需要登录后才可以回帖 登录 | 立即注册

返回顶部