在测(po)试(jie)软件时,我最不愿意遇到的情况,就是目标软件有联网验证的功能。说要改代码吧,又不知道代码里在哪儿有暗桩,改掉所有的输入输出点也麻烦的很。说要断它的网吧,又不是所有软件都提供离线激活的功能。今天我介绍一种四两拨千斤的联网验证绕过方式,不用断网,字节码改动少,外加Python不到100行。。
最近工作开会总要用企业微信在线会议,得在电脑上投屏,还得开麦(不能多点登录所以也不能用手机的麦),电脑麦克风音质实在是太垃圾了,还有环境噪音,搜来搜去找到一个软件Krisp,在声卡驱动层做音频处理,全局降噪,非常贴合我预想的应用场景,。囿于网上没有破解,只能动用我们的聪明才智来“学习”一下了。
逆向第一步,我们得先知道,我们要逆向的软件用的是什么编程语言。Krisp这个软件很简单,打开安装目录,从 *.exe.config
文件和它引用的 Newtonsoft.Json
库就知道,这个软件是用C#编写的。
是C#就简单了。直接拖进dnSpy打开即可。这工具比当年的ILSpy强大了很多,能反编译,能调试,还能直接用C#改程序逻辑。而且很多.NET框架的程序发布出来都不带混淆的,变量、方法名写的清清楚楚,用dnSpy反编译一下,基本就和看程序源代码没有太大区别了。
在dnSpy里搜搜常见的关键字,通过关键字 trial (试用),很快就能定位到 Krisp.UI.ViewModels.KrispAppPageViewModel.UserProfileInfoChanged
函数,这个函数中除了trial还有一个模式叫unlimited。直接用dnSpy无脑给他改了。
嗯,至少界面上的试用限制已经不见了,表面上已经是尊贵的VIP客户了。
但这还不够,因为这只是程序里和UI相关的一个逻辑判断,类似的修改点肯定还有很多。很多逻辑判断都是通过和常量的对比实现,既不好找,而且一一修改也太麻烦了。
所以我们转换一下思路,程序授权状态是从网络下发的,那我们不妨以网络通信作为切入点。用我们上篇文章提到的Fiddler抓包看看,还是很简单,随便翻翻就能找到 https://api.krisp.ai/v2/user/profile/2/1
这个API。
那完事咱得给它改成啥呢?第一,JSON中 data.mode.name
这个参数得给它改成 unlimited 。第二,下面 data.settings
里有一大堆 available=True
的参数值,等试用期过后这些肯定就变成 False 了(我注册的账户还没过试用期,所以只是猜测)。所以我们截获数据修改JSON时,将这些值固定成现在的样子就好了。
好了,我们最喜闻乐见的“为什么不用xxxxx”环节又来了。今天没有很多的选手参赛,。简单对比一下。
方式1:修改API网址为自建API,进行MITM(最终选择方案)
优点:在程序中需要修改的点较少,只要把网址字符串改一下就行。如果能构造一个长度相同的网址,甚至不用反编译工具去改源程序,直接字符串替换就行了。程序后续更新后,维护破解版的工作量非常小。
方式2:修改客户端,在REST API请求点对返回值进行修改
缺点:破解新版本还得重头把所有步骤来一遍。而且不同API的请求可能还在不同的Class里面。光破解一次就很费精力了。
方式3:改hosts截获API请求
不可行,生产环境的API访问都是https的。这种又不是传统的MITM攻击,不能搞sslstrip。断网就没办法正常登录使用,正常功能都被破坏了。
而搭建API用的框架,就是 mitmproxy。顾名思义,这框架就是用来做中间人的。它与其他编程语言库最大的差异是,它提供的是 修改 Web请求的API(接近其他框架中middleware的功能),而其他Web框架提供的是 解析 Web请求的能力。比如Django,它把Web请求解析出字段,丢给我们,这个请求就发不出去了。如果我想把这个请求再发出去,就得把所有字段再拼起来,调用requests发出去。 如果目标应用有几十个API怎么办?麻烦不?我们写代码,只处理最重要的逻辑,不写无聊的CRUD,真正做到,不流失,不蒸发,零浪费!
其他MITM的工具在这里就不适用了。别的工具能放在docker容器里24小时跑在VPS上么?
我们搭建的这个山寨API,最重要的功能是能够让我们能够以类似编写middleware的方式对传入的请求、返回数据进行修改,同时没修改的API原封不动的透传回去。当然,写的代码越少越好。
既然明确了原理和工具,我们照着文档写代码就是了。不过说到文档,我得提醒一下,mitmproxy它压根就没有什么文档(微笑)。根据作者的经验,项目Github中的一些示例脚本,和 pydoc 命令行是两类比较不错的参考资料。按图索骥勉强也能写。
我们在运行 mitmproxy 时,需要用到它的reverse proxy模式,在这个模式下mitmproxy会接受普通的HTTP请求作为输入,而非特殊的HTTP Proxy请求。完整的运行参数如下:
其中,reverse:
后面的主机名称是随便输的,配合 keep_host_header
选项,同时在代码里手动将请求Header中的Host字段设置为 flow 的目的主机,只有这样才能实现在同一个端口同时反向代理多个目的主机。
下面一段代码实现的功能:
上段代码中我们定义的 request
函数就是 mitmproxy 提供的事件API,每条请求发出之前,这个函数就会被调用。接下来我们再实现另一个 response
函数,这个API会在mitmproxy获得Web请求的返回数据后触发(与上篇文章中的思路相似)。
而我们要做的事情很简单,将我们前文所说的部分——对 data.mode.name
和 data.settings
的修改全部实现即可。
最后,按代码示例里的格式,定义好class并声明插件列表,就可以运行 mitmdump 调用这个脚本了,在这里,我们将其起名为 krisp.py 并在8081端口上监听API请求。mitmdump和mitmproxy不同,后者是一个TUI程序,带用户界面的,前者纯运行功能,没有界面,可以类比为http协议的tcpdump,这个就很适合长期运行了。
当然啦,我们也不可能每次打开软件都运行这么一行命令,多low啊。我们可以将它封装成一个Docker容器,弄到服务器上去,这样它就可以一直开着不用管啦。
如果读者想部署一个暴露公网、持续运行的激活服务器的话,建议额外在前面挂一个反代,这样log rotate和rate limit都比较方便,最简单的办法是用docker compose在前面套一个traefik,这样连SSL证书也不用自己操心了。不过这一部分配置不在本文的讨论范围之内。
我们搭了一个假API,得让目标程序用起来。这也很简单。刚才抓包不是抓到两个域名么,api.krisp.ai
和 analytics.krisp.ai
。直接全局搜索改成自己的域名和mitmdump监听的端口,就成了。
这篇文章又是一个懒人的教程,两个工具,不到100行代码,实现了一个魔改版REST API,通过比较简单的方式破解了一个应用。有人会说,你这一个C#的程序又没混淆又没壳,这么简单的逆向这不是逗乐么?可再想想,基于抓包的思路哇,即使目标应用是个黑盒,猜API参数的含义还是要比猜寄存器的值简单吧。最重要的,还是搭建Web API的思路和 mitmproxy 框架的用法,这些也许会是其它场景能够用得上的知识宝藏。
Noise Cancelling App https://krisp.ai/
0xd4d/dnSpy: .NET debugger and assembly editor https://github.com/0xd4d/dnSpy
原文首发于 本人博客 与个人公众号: rabyte
[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!