首页
论坛
课程
招聘
[原创]某通Vpn V2.2.12 逆向破解Vip线路
2022-9-3 19:45 14523

[原创]某通Vpn V2.2.12 逆向破解Vip线路

2022-9-3 19:45
14523

项目环境:

提示:不提供成品,不贴地址。

 

目标Apk:某通V2.2.12
Frida :12.8.0

 

其他工具:

 

NP管理器、JADX、Fiddler

正文:

一.分析&动态修改

1.抓包

使用Fiddler对目标进行抓包,发现请求体存在加密部分,以4kA开头。

 

 

初步判断为base64加密。使用Frida对目标进行Hook。

1
2
3
4
5
6
7
8
9
10
11
12
var Base64Class = Java.use("android.util.Base64");
        Base64Class.encodeToString.overload("[B", "int").implementation = function(a,b){
 
            var StrCls = Java.use('java.lang.String');
            var OutStr = StrCls.$new(a);
 
            console.log("OutStr:"+ OutStr);
 
            var re =  this.encodeToString(a,b);       
            console.log(">>> Base64: " + re);
            return re;
        }

运行后,发现没有结果。说明该字段加密是另有方法。

 

没办法,直接Hook StringBuilder toString方法,通过字符串去定位。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var strCls = Java.use("java.lang.StringBuilder");
        strCls.toString.implementation = function(){
            var result = this.toString();
 
            if(result.toString().indexOf("4kA") >= 0 )
            {
                console.log(result.toString());                           
                        //打印堆栈
                var stack = threadinstance.currentThread().getStackTrace();
                console.log("Re call stack:" + Where(stack));
 
            }
            return result;
        }

打印结果

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
4kA1n8n-W0cpJ_YTL3FUVz2lLz2PFHL7uDipswFoFrCTLqj0LeFPFHLBuDMX2r1pJpA3zpFPFrn-2eFonHiP20cPFrnq2rL-S0TpJ_Y0V_WPFHldSH2pJpAsweFPFHi7s8nq2pFoJw7puDq1F_I75eAkWDM1F_IpL3WgLzFp5eA-ueFoVzW0V_YTJz10Vw7pWDVpJ_FPFrQZnkFoFHLBSwMZu8fB5rn7SpFPFrL-nkFoF17x90uV41nMS8-fshnomDqH4qqr9_in9inTDDtHDclInqq-sDnbVqdE4Hnnn_VlzclnSDtD43iTzNdDViWxchlV8UuZ9rIlLUnoWUfEDhLTnqRGnclnJD9H4ri-nqjkDinoSzuD80UP9q1MshU_SDlD8Xiq9_i-nc-TDzuEV0LrwrIk9eAR
Re call stack:dalvik.system.VMStack.getThreadStackTrace(Native Method)
java.lang.Thread.getStackTrace(Thread.java:1566)
java.lang.StringBuilder.toString(Native Method)
i3.x.b(:6)
b8.a.b(:8)
z8.c.h(:14)
m8.d$r.onClick(:2)
android.view.View.performClick(View.java:5637)
android.view.View$PerformClick.run(View.java:22429)
android.os.Handler.handleCallback(Handler.java:751)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:154)
android.app.ActivityThread.main(ActivityThread.java:6121)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

定位到i3.x.b,用Jadx反编译目标apk,搜索到该方法,可以发现入参是字符串,返回值是字符串。

 

 

用Frida objection插件,对i3.x.b进行Hook,App发送加密请求则触发。

1
android hooking watch class_method i3.x.b --dump-args --dump-backtrace --dump-return
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(agent) [1e9vrk1qy8w] Called i3.x.b(java.lang.String)
(agent) [1e9vrk1qy8w] Backtrace:
        i3.x.b(Native Method)
        b8.a.b(:8)
        z8.c.h(:14)
        m8.d$r.onClick(:2)
        android.view.View.performClick(View.java:5637)
        android.view.View$PerformClick.run(View.java:22429)
        android.os.Handler.handleCallback(Handler.java:751)
        android.os.Handler.dispatchMessage(Handler.java:95)
        android.os.Looper.loop(Looper.java:154)
        android.app.ActivityThread.main(ActivityThread.java:6121)
        java.lang.reflect.Method.invoke(Native Method)
        com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
        com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)
 
(agent) [1e9vrk1qy8w] Arguments i3.x.b({"device":18xxxxxx57,"cpuabi":"x86_64","country":"CN","vip":false,"version":1626,"lang":"ZH","apiver":9,"uid":0,"rand":"48155","it":1662189961,"ac":2,"pkg":"com.xxxx.vpn","sig":"L3ClLzcymzQhfzAifzYgV1YTVxYkfYLhfYiif_2XJzfYf31MLYmkVx1xLxV0V3PlL_WkVz1lVxWMJXcxf_neLY9efzqif_2XVzm7V_mlVY9hmcmlV_AeV1ieJxY7J3cgJz2T"})
(agent) [1e9vrk1qy8w] Return Value: 4kA1n8n-W0cpJ_YTL3FUVz2lLz2PFHL7uDipswFoFrCTLqj0LeFPFHLBuDMX2r1pJpA3zpFPFrn-2eFonHiP20cPFrnq2rL-S0TpJ_Y0V_WPFHldSH2pJpAsweFPFHi7s8nq2pFoJw7puDq1F_I75eAkWDM1F_IpL3ClLzcp5eA-ueFoVzW0V_YTJz10Vw7pWDVpJ_FPFrQZnkFoFHLBSwMZu8fB5rn7SpFPFrL-nkFoF17xm0lV4HLMS8-fshnomDqH4qqr9_in9inTDDtHDclInqq-sDnbVqdE4Hnnn_VlzclnSDtD43iTzNdDViWxchlV8UuZ9rIlSinT9XUEDhLTnqRGnclnJD9H4ri-nqjkDinoSzuD80UP9q1MshU_SDlD8Xiq9_i-nc-TDzuEV0LrwrIk9eAR

 

可以发现与抓包结果相一致。

2.展示Vip列表

在上一步Hook i3.x.b过程中时,是点击app刷新按钮。通过注册免费赠送的一天Vip,可以发现刷新按钮,是用来请求Vip服务器列表的。

 

通过抓包可以发现,请求Vip服务器列表,响应体中也是以4kA开头,本地apk肯定需要解密它。

 

 

既然加密在i3.x.b中,那么解密是不是也在i3.x这个类呢?

 

直接用objection hook i3.x

1
android hooking watch class i3.x
1
2
3
(agent) [1gv2xiqsb6h] Called i3.x.b(java.lang.String)
(agent) [1gv2xiqsb6h] Called i3.x.b(java.lang.String)
(agent) [1gv2xiqsb6h] Called i3.x.a(java.lang.String)

发现除了调用了i3.x.b,还调用了i3.x.a

 

那就hook i3.x.a,看看它的参数值。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(agent) [e5k5hzxxs5v] Called i3.x.a(java.lang.String)
(agent) [e5k5hzxxs5v] Backtrace:
        i3.x.a(Native Method)
        b8.a.d()
        b8.a$c.invoke(:2)
        p7.a.f(:3)
        p7.a.e(:11)
        p7.a$c.run()
        java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1133)
        java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:607)
        java.lang.Thread.run(Thread.java:761)
 
(agent) [e5k5hzxxs5v] Arguments i3.x.a(4kAbWg9k2H9GuiR0n8 ...[不展开了])
(agent) [e5k5hzxxs5v] Return Value: [object Object]

可以发现参数值,与抓包结果相一致。

 

那么重点来了,在没有登录的情况下,通过将带有vip列表的返回值,修改为i3.x.a的参数值,就可以实现展现vip列表了。

3.修改Vip天数

在展现Vip列表后,我们随机点击一个服务器,发现会自动跳转到登录页面,一定是在检测是否是Vip。

 

通过查找字符串资源,可以找到显示vip天数的字符串。

1
<string name="iw">%d天</string>

Jadx搜索R.string.iw,可以发现该文本的%d,取之于r8.b.a这个方法。

 

 

通过对r8.b这个类进行hook,可以发现r8.b.a是返回vip天数,r8.b.b是判断是否为vip。

 

 

然后用Frida,对r8.b.a返回值修改为1,即可。

 

以上为动态修改方法。

二.静态修改

1.去签名校验

使用NP管理器的超强去除签名校验2.0,即可去除校验。

 

这里有一个巨坑,在后面踩到了。原因是lib文件夹中会多出一个libfuck.so文件,而发送获取vip服务器列表请求,会对lib文件夹的文件数量进行判断,导致请求一直发送不出去。

2.修改Vip天数

直接在r8.b.a中的smali代码,返回值永远返回1,即可。

3.修改请求地址

因为新安装打开的App,没有Vip列表,获取Vip服务器列表,需要从对方服务器获取。所以将带有Vip列表的响应保存起来,自己写个接口,将请求发送给自己的服务器即可。

 

通过Hook字符串定位,找到原获取Vip服务器列表请求的Url。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
var strCls = Java.use("java.lang.StringBuilder");
       strCls.toString.implementation = function(){
           var result = this.toString();
 
           if(result.toString().indexOf("get_config_v9") >= 0 )
           {
               console.log(result.toString());
 
               var stack = threadinstance.currentThread().getStackTrace();
               console.log("Re call stack:" + Where(stack));
 
           }
           return result;
       }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
https://xxxxx.xxxxxxxxxx.com/get_config_v9
Re call stack:dalvik.system.VMStack.getThreadStackTrace(Native Method)
java.lang.Thread.getStackTrace(Thread.java:1566)
java.lang.StringBuilder.toString(Native Method)
java.util.Formatter.toString(Formatter.java:2326)
java.lang.String.format(String.java:2670)
b8.a.b(:2)
z8.c.h(:14) a8.a.a .b
m8.d$r.onClick(:2)
android.view.View.performClick(View.java:5637)
android.view.View$PerformClick.run(View.java:22429)
android.os.Handler.handleCallback(Handler.java:751)
android.os.Handler.dispatchMessage(Handler.java:95)
android.os.Looper.loop(Looper.java:154)
android.app.ActivityThread.main(ActivityThread.java:6121)
java.lang.reflect.Method.invoke(Native Method)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:889)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:779)

发现该Url在b8.a.bString.format方法中生成。

 

 

查看smali代码,将v1的值改为修改的请求地址即可。

 

1
2
3
4
5
6
7
new-instance v7, Lp7/d;
 
const-string v1,"http://xxxxxx.com/xxx"
 
invoke-direct {v7, v0, v1}, Lp7/d;-><init>(Ljava/lang/Class;Ljava/lang/String;)V
 
sget-object v0, Lm7/a;->d:Lm7/a;

按道理来说,应该就可以了,结果我在这里卡了很长时间,就是遇到了之前的那个坑,请求发不出去。

 

那就看看原请求是怎么发送出去的。翻到之前打印的堆栈。

1
2
3
4
5
6
7
8
9
10
https://xxxxx.xxxxxxxx.com/get_config_v9
Rc Full call stack:dalvik.system.VMStack.getThreadStackTrace(Native Method)
java.lang.Thread.getStackTrace(Thread.java:1566)
java.lang.StringBuilder.toString(Native Method)
java.util.Formatter.toString(Formatter.java:2326)
java.lang.String.format(String.java:2670)
b8.a.b(:2)
z8.c.h(:14)
m8.d$r.onClick(:2)
...

通过一系列的hook发现,修改后的请求,可以触发z8.c.h,却触发不到b8.a.b,说明在z8.c.h遇到了某条件判断。

 

Jadx没有反编译出来,可以通过NP管理器查看。

 

通过hook可以发现l7.d.f是获取lib文件夹地址的。在下面的文件个数判断中,要将listFiles.length == 6改为listFiles.length == 7

 

发现下面有一个熟悉的方法,r8.b.b,这不是判断是否vip的方法嘛。

 

不管,先打包好,发送请求… … 失败… …

 

!r8.b.b()的smali改为r8.b.b(),发现成功发送请求。

 

最终完成了apk的破解!

三.总结

破解过程中总感觉有点晕,但是最后写文章整理了一下,发现其实很简单… …

 

这可能就是当局者迷,破局者清吧。

 

最后感谢奋飞大佬的指点,还有大佬的文章~

 

期间遇到的其他问题:

1
2
3
Q1:破解了半天破不出来,换个版本?
 
A1:换个版本发现更晕。

后记

我真的很菜。学习Android逆向,看了一下IDM下载Frida和Jadx的时间,到破解完这个app,花了整整一个月。

很多知识其实并不完备。不过还是贴一下自己的学习过程,希望可以帮助到其他人。

 

1.看雪知识库-基础知识/Android安全:我大概过了一下,毕竟本身就是学计算机的,操作系统啥的要熟悉一丢丢

 

2.《Android软件安全权威指南》,非虫大佬的书:我也是大概过了一下,基本就是Android逆向的全面概括和简单实操。

 

3.Frida Hook 基本操作:这个必看,大佬整理的很全,感谢大佬!

 

4.Frida - Objection Hook工具:非常方便的Frida Hook工具,减少很多代码量。

 

5.Objection插件 - Wallbreaker的使用:分析 Java 类/对象结构,而且还能dump出实例中的各个属性值,6 。

 

6.公众号-奋飞安全 :奋飞大佬已经把sign解密玩出花了,实操文章很多,对我帮助很大,也很感谢有不懂的时候,问大佬,也一直回我。

 

7.抓包涉及的Fiddler、WireShark工具,还有SSL Pining等等。(一般我用两种方法,配置Fiddler系统根证书r0capture通杀脚本,基本上就能过大部分抓包了,这两个都需要root,后者需要真机)

 

8.呆小猴:我自己的博客,刚做,记录学习和一些实战。

 

基本上一个月就搞了这些,连Xposed也没装过,主要Frida比较适合我分析,又快又好。以后有时间可以去看看。


看雪2022 KCTF 秋季赛 防守篇规则,征题截止日期11月12日!(iPhone 14等你拿!)

最后于 2022-10-13 23:39 被柳哈辣子编辑 ,原因: 图片没上传上
收藏
点赞10
打赏
分享
最新回复 (33)
雪    币: 27
活跃值: 活跃值 (782)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
amwpecel 活跃值 2022-9-3 23:31
2
0

顶一个

最后于 2022-9-3 23:48 被amwpecel编辑 ,原因:
雪    币: 8
活跃值: 活跃值 (388)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
JWPL 活跃值 2022-9-4 07:12
3
0
顶一个
雪    币: 4668
活跃值: 活跃值 (1224)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
十年后 活跃值 2022-9-4 09:52
4
0
顶一个
雪    币: 350
活跃值: 活跃值 (453)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
院士 活跃值 2022-9-4 11:41
5
0
顶,看得云里雾里。
雪    币: 2328
活跃值: 活跃值 (1749)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
gamehack 活跃值 2022-9-4 12:17
6
0
顶一个
雪    币: 6434
活跃值: 活跃值 (1262)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chengdrgon 活跃值 2022-9-4 12:40
7
0
感谢分享,虽然不懂
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-4 15:09
8
0
院士 顶,看得云里雾里。[em_13]
啊,我以为自己写的很详细了
雪    币: 1318
活跃值: 活跃值 (1018)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
网络枭红 活跃值 2022-9-5 09:55
9
0

大哥,一直不太明白为什么能破解vip。vip的校验不应该是在服务端嘛。为什么改本地代码却能过掉服务器端的判断?

并且,是这一个app可以。还是所有的app vip 都可以用这种思路去破解vip?

最后于 2022-9-5 09:56 被网络枭红编辑 ,原因:
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-5 12:29
10
0
网络枭红 大哥,一直不太明白为什么能破解vip。vip的校验不应该是在服务端嘛。为什么改本地代码却能过掉服务器端的判断?并且,是这一个app可以。还是所有的app vip 都可以用这种思路去破解vip?
我是女生hhhhh,这个要分情况,这个app我在连接vip线路的时候,发现并没有发送任何请求,说明连接的时候,判断方法在本地。但是获取vip服务器列表,需要在对方服务器获取,而这个app返回vip服务器信息也没有再次对vip时间进行校验,所以可以去破解。
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-5 12:39
11
0
如果之后再返回vip服务信息的时候,还带有vip时间,那么判断vip时间的方法,也必然在本地,通过修改本地方法,依然可以破解
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-5 12:46
12
0
网络枭红 大哥,一直不太明白为什么能破解vip。vip的校验不应该是在服务端嘛。为什么改本地代码却能过掉服务器端的判断?并且,是这一个app可以。还是所有的app vip 都可以用这种思路去破解vip?
还有你说的"vip的校验不应该是在服务端嘛。为什么改本地代码却能过掉服务器端的判断?",因为很多时候,服务器再判断,返回的时候,本地方法总要对返回的信息做处理,通过对方法的拦截,达到篡改的目的。
雪    币: 4153
活跃值: 活跃值 (4398)
能力值: ( LV9,RANK:181 )
在线值:
发帖
回帖
粉丝
nevinhappy 活跃值 2 2022-9-5 13:05
13
0
Ku通的PC,Anroid版本!!!
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-5 13:43
14
0
nevinhappy Ku通的PC,Anroid版本!!!
我对pc方面的逆向还一点都不了解
雪    币: 397
活跃值: 活跃值 (82)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
菜鸟一高 活跃值 2022-9-5 14:43
15
0
这个APP,在连接服务器的时候至少要进行一次VIP检测
雪    币: 649
活跃值: 活跃值 (2664)
能力值: ( LV6,RANK:98 )
在线值:
发帖
回帖
粉丝
还我六千雪币 活跃值 2022-9-5 15:18
16
0
你好,我也是女生
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-5 15:26
17
0
还我六千雪币 你好,我也是女生
握爪
雪    币: 4153
活跃值: 活跃值 (4398)
能力值: ( LV9,RANK:181 )
在线值:
发帖
回帖
粉丝
nevinhappy 活跃值 2 2022-9-5 15:38
18
0
柳哈辣子 我对pc方面的逆向还一点都不了解[em_80]
隔壁的:"[windows] 某通加速器最新破解vip教程分享"
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-5 15:47
19
0
nevinhappy 隔壁的:"[windows] 某通加速器最新破解vip教程分享"
找到了,刚学习逆向,还没有隔壁的账号,阅读要权限,以后有时间再看
雪    币: 3
活跃值: 活跃值 (74)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
godiand 活跃值 2022-9-5 16:29
20
0
牛蛙牛蛙,可以刘个威吗,教流一下
雪    币: 1
能力值: ( LV1,RANK:0 )
在线值:
发帖
回帖
粉丝
mb_zblpfhts 活跃值 2022-9-5 18:56
21
0
<p>
<a href="javascript:alert(document.cookie)">test</a>
</p>
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-6 21:10
22
0
网络枭红 大哥,一直不太明白为什么能破解vip。vip的校验不应该是在服务端嘛。为什么改本地代码却能过掉服务器端的判断?并且,是这一个app可以。还是所有的app vip 都可以用这种思路去破解vip?
私信看到了,我账号还没转正,app评论有人说了
雪    币: 1713
活跃值: 活跃值 (2990)
能力值: ( LV6,RANK:80 )
在线值:
发帖
回帖
粉丝
kzzll 活跃值 2022-9-8 02:11
23
0
分析到位
雪    币: 6434
活跃值: 活跃值 (1262)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
chengdrgon 活跃值 2022-9-8 10:01
24
0
感谢分享,不能收藏是咋回事?
雪    币: 261
活跃值: 活跃值 (344)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
柳哈辣子 活跃值 2022-9-8 10:03
25
0
chengdrgon 感谢分享,不能收藏是咋回事?
可以吖,你再试试
游客
登录 | 注册 方可回帖
返回