此文并不是一天写完的,以至于验证结果之前的内容 用的是上一个版本17(1.6.0) ,验证结果之后内容是18(1.6.1);但思路都是一样的,两个版本只是更换了类的包名、方法名(可能是编译发版的问题?)等,hook的时候我重新反编译了一次,获取新版本的hook点,所以代理里会和上面分析出来的类不大一样。sign算法的代码也是一样的。

前言

他们家的应用,对IOS的支持一直不友好;这个语音跟安卓查了也给大版本,而且IOS的还被AppStore下架了;就想着要不要自己抓下安卓的接口搞个网页来用。

测试了安卓的,有防抓包SSL Pinning;按照以前的经验,使用Xposed模块JustTrustMe解决;发现并没有效果,打开代理还是断网,网上搜索了下发现双端验证的SSL Pinning使用这个方式是无效的,但并没有找到合适的解决方案。

但是在使用wireshark的时候,发现这个App会把接口打印到日志里。而且我刚好用IOS测试过抓包,是可以正常抓包的;只是IOS里面有发现请求头包含sign,那他肯定就对参数进行签名了呀,就算有能拿到接口不拿到sign算法也是无济于事。遂反编译代码,开始获取接口、参数、sign等。

准备

  1. BlackDex

         App加壳了,用于脱壳。
    
  2. 安卓真机一台:vivo Xplay6

         用夜神模拟器安装`BlackDex`脱壳失败,真机成功。
    
  3. JADX

         用于从 Android Dex 和 Apk 文件生成 Java 源代码的命令行和 GUI 工具 
    
         Command line and GUI tools for producing Java source code from Android Dex and Apk files 
    
  4. Xposed

         用于帮助分析和验证结果。
    
  5. Android Studio

         写Xposed模块代码、连接ADB查看日志
    
  6. PostMan

         随便,能发送请求就行
    

关键词定位

我准备先从sign入手,因为我觉得其他比如获取接口会简单一点,事实也是如此。

搜索sign.png

枚举、常量、注解,我认为这三个是比较重要的,所以就挨个往下看了下。

枚举

搜索枚举

这个字符串很像是个啊,但点进去看,里面的一直值是接口里没见过的,下一个

搜索枚举2

#### 常量

有个包名包含taobao的就不看了,应该是第三方的。

KeyProvider.SIGN也没有被哪里用到,GG

注解

从图一可以看出来,注解是Headersign只是value。点进去这个注解看, 就一个value

注解1

搜索了一下这个注解retrofit2.http.value,看到了一个文章:https://blog.csdn.net/SilenceOO/article/details/77460607

这个也确实是用来设置请求头的,那就顺着往下看就好了。

https://www.jsls9.top/usr/uploads/2022/08/4076473712.png

其实这个有注解的地方,@POST注解里的值,点进去就能看到是接口。有的地方看起像是接口参数,而且有的接口是有@Header("sign")有些是没有的,猜测是不是有些接口不需要签名。

看到这种统用注解,第一想法是会不会使用aop做处理,所以搜索了下Header.class,无果。

但直接搜索这个api类e.l.d.g.a.d.a.class,发现了重点

apiClass1

找个第一个参数是sign的方法看一下,能看出来变量i2是请求的参数,并且通过c.o(i2)处理了下。

追踪进去,这怎么看都是一个sign算法

sing.png

通过这些文件又对比了接口接口文件,发现只有POST请求才使用了sign

验证结果

通过上面的排查,找到了疑似sign算法;接下来就是验证结果是不是对的了。

具体怎么验证?

我是Xposed进行hook,因为我还想看到都要哪些参数。

具体Xposed的介绍可以看我上一篇博文。

XposedHelpers.findAndHookMethod(
                "e.w.f.c",
                utils.getClassLoader(),
                "w",
                Map.class,
                new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        Log.d("shupian", "hook 后");
                        int length = param.args.length;
                        Log.d("shupian", String.valueOf(length));
                        Log.d("shupian", JSON.toJSONString(param.args[0]));
                        if(param.args[0] instanceof Map){
                            Map<String, String> map = (Map<String, String>) param.args[0];
                            for(String key : map.keySet()){
                                Log.d("shupian", "key【"+key+"】" + "value【"+ map.get(key) +"】" );
                            }

                        }

                        String result = (String) param.getResult();
                        Log.d("shupian", "返回值:【"+ result +"】" );

                    }

                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.d("shupian", "hook 前");
                        int length = param.args.length;
                        Log.d("shupian", String.valueOf(length));
                        if(param.args[0] instanceof Map){
                            Map<String, String> map = (Map<String, String>) param.args[0];
                            for(String key : map.keySet()){
                                Log.d("shupian", "key【"+key+"】" + "value【"+ map.get(key) +"】" );
                            }

                        }

                        String result = (String) param.getResult();
                        Log.d("shupian", "返回值:【"+ result +"】" );

                    }
                }
        );

        XposedHelpers.findAndHookMethod(
                "e.w.f.c",
                utils.getClassLoader(),
                "v",
                byte[].class,
                new XC_MethodHook() {
                    @Override
                    protected void afterHookedMethod(MethodHookParam param) throws Throwable {
                        Log.d("shupian", "wwwwwwwwwwwwwwwwww后");
                        Log.d("shupian", param.args[0].toString());
                        String result = (String) param.getResult();
                        Log.d("shupian", "返回值:【"+ result +"】" );

                    }

                    @Override
                    protected void beforeHookedMethod(MethodHookParam param) throws Throwable {
                        Log.d("shupian", "wwwwwwwwwwwwwwwwww前");
                        Log.d("shupian", param.args[0].toString());
                        String result = (String) param.getResult();
                        Log.d("shupian", "返回值:【"+ result +"】" );


                    }
                }
        );

一共hook了两个方法,第一个是签名算法的入口,只能拿到一些参与签名的参数;第二个是签名算法retrue前调用的一个方法,就一个md5操作,这个拿到了签名后的结果。

signLog1.png

那这个参数先登录下看看

signTest01

因为短信验证码用过了,所以会提示错误,随便改一个参数都会提示{"msg":"签名错误","code":103}

可以证明我们hook的方法就是sign算法

提取出来算法测试一下,用同样的参数看看能不能输出同样的值。

sign02.png

OK。完美提取出sign算法