搞的大的,爆破某外卖平台的x-sign(代码分析篇)

2,649 阅读5分钟

本次分析的样本为:

  • elm10.14.3.apk

抓包分析

还是按照逆向的套路,先抓个包,这里有一点需要注意,如果用手机端的小黄鸟抓包,一些H5请求是抓不到的,这里推荐使用VProxid将流量劫持到电脑,通过电脑端的抓包工具查看,可以参考这篇文章

看下抓包的内容

抓包

吐槽下,这里的请求头真是够多的,不愧是大厂,就是不一样。这么多请求头,到底哪个应该是关键的呢?根据经验,应该是x-sign,已经用红框框出来了,验证猜测也很简单,把这个请求头删除,再请求下就知道了。 所以,根据抓包和验证的结果,可以得出,这里的x-sign就是验证请求数据的签名。

查找关键代码

通过抓包找到了数据请求的关键字,接下来就是到代码中查看这个x-sign是如何生成的。我是比较喜欢用mt管理器来查看代码的,当然你也可以用其他的查看逆向代码的软件,比如Jeb。先用mt管理器查看下这个软件的信息

app信息

可以看到app没有加固,省去了脱壳的步骤,大厂就是自信。

直接查看dex文件,然后,搜索关键字x-sign

搜索

查看搜索结果

搜索结果

搜索出来与x-sign有关的结果有13个,也还行,不是太多。接下来就是通过算法助手.appHook每个搜索到的类,找出生成x-sign的方法,逆向有时就是一个体力活。

经过一番Hook,最后定位到InnerSignImpl类中的getUnifiedSign方法,看一下算法助手.appHook的内容

Hook的方法

看下Hook的结果

Hook的结果

随便点进去一个看下详细信息

Hook详情

看下这个方法的返回值,里面不仅有x-sign,还有其他的几个请求头也在里面,已经可以基本确认x-sign是在调用这个方法后生成的了,接着看下这个方法的代码

    public HashMap<String, String> getUnifiedSign(HashMap<String, String> hashMap, HashMap<String, String> hashMap2, String str, String str2, boolean z, String str3) {
        ....
            try {
                HashMap hashMap3 = new HashMap();
                String str4 = convertInnerBaseStrMap(hashMap, str, true).get("INPUT");
                if (StringUtils.isBlank(str4)) {
                    TBSdkLog.e(TAG, getInstanceId() + " [getUnifiedSign]get sign failed with sign data empty ", "appKeyIndex=" + this.mtopConfig.appKeyIndex + ",authCode=" + this.mtopConfig.authCode);
                    AppMethodBeat.o(107546);
                    return null;
                }
                hashMap3.put("appkey", str);
                hashMap3.put("data", str4);
                hashMap3.put("useWua", Boolean.valueOf(z));
                hashMap3.put("env", Integer.valueOf(getMiddleTierEnv()));
                hashMap3.put("authCode", str2);
                hashMap3.put("extendParas", hashMap2);
                hashMap3.put("requestId", str3);
                hashMap3.put("api", hashMap.get("api"));
                HashMap<String, String> securityFactors = this.mUnifiedSign.getSecurityFactors(hashMap3);
                if (securityFactors == null || securityFactors.isEmpty()) {
                    TBSdkLog.e(TAG, getInstanceId() + " [getUnifiedSign]get sign failed with no output ", "appKeyIndex=" + this.mtopConfig.appKeyIndex + ",authCode=" + this.mtopConfig.authCode);
                    AppMethodBeat.o(107546);
                    return null;
                }
                AppMethodBeat.o(107546);
                return securityFactors;
            } 
            ...
        }
    }

上面代码是经过精简后的,根据返回值可以确定,主要的代码是下面这部分

主要代码

绿色框内的这句代码是返回x-sign的方法,继续跟进,看下这个方法。在这个类里面,查看mUnifiedSign成员变量在哪里赋值的,这个类里面只有一处赋值的地方,代码如下

 if (this.mUnifiedSign == null) {
                this.mUnifiedSign = (IUnifiedSecurityComponent) SecurityGuardManager.getInstance(mtopConfig.context).getInterface(IUnifiedSecurityComponent.class);
                if (this.mUnifiedSign != null) {
                    this.mUnifiedSign.init(hashMap2);
                } else if (TBSdkLog.isLogEnable(TBSdkLog.LogEnable.InfoEnable)) {
                    TBSdkLog.e(TAG, getInstanceId() + " [initMiddleTier]init sign failed");
                }
            }

继续跟的话,就到了SecurityGuardManager类中的getInterface方法,这个方法返回的是实现IUnifiedSecurityComponent接口的类,只有在运行时才能知道具体的对象。 现在怎么办呢?想要看这个成员变量的值是什么有两种方法:

  • 用Frida Hook内存中的对象,打印其成员变量的值。
  • 用AS动态调试,查看堆栈。

利用Frida来查看mUnifiedSign的值

先用Frida查看下内存中成员变量的值,相关代码如下

var InnerSignCls = Java.use('mtopsdk.security.InnerSignImpl');
        
		InnerSignCls.getUnifiedSign.implementation = function(a1,a2,a3,a4,a5,a6){

            var retval = this.getUnifiedSign(a1,a2,a3,a4,a5,a6);
			console.log(" ========== ");
			var fields = Java.cast(this.getClass(),Java.use('java.lang.Class')).getDeclaredFields();

		    for (var i = 0; i < fields.length; i++) {
		        var field = fields[i];
		        field.setAccessible(true);
		        var name = field.getName();
		        var value =field.get(this)
		        console.log("name:"+name+"\tvalue:"+value);
		    }

			console.log(" ========== ");
			return retval;
		}

查看hook的结果

hook结果

可以发现奇怪的事情,mUnifiedSign这个成员变量值为null,但是,明显x-sign是从这个变量里的方法得到的,现在只有一个可能,就是这个成员变量用完后,重新赋值为null了,没关系,还有动态调试的方式没试呢。

动态调试查看mUnifiedSign的值

关于如何利用AS动态调试smali代码,可以参考我的这篇文章:利用AndroidStudio动态调试smali源码。需要注意的一点就是新版的AS需要安装smali插件才能调试,插件已经放在了知识星球中。 这里就直接看动态调试的结果

动态调试结果

image

根据动态调试的结果,可以看出 this.mUnifiedSign.getSecurityFactors(hashMap3) 这句代码是利用了Java的动态代理,实际调用的方法是com.xxx.wireless.security.middletierplugin.d.d.a$a类中的invoke方法。那就到这个类中看下invoke方法,这时你会发现在dex文件中找不到这个类,可是动态调试的时候,这个类明显是存在的,但为什么在dex文件中找不到呢?那是因为这个类不在dex文件中,在运行时通过指定的ClassLoader从另外的文件中加载进来的。那是哪个文件呢?依然可以从上面动态调试的结果中看出,这个动态代理的类在 libsgmiddletire.so 文件中。这个类中的内容下篇再分析,这篇文章的内容已经不少了,讲多了消化不了。

总结

本文简单介绍了如何使用算法助手.app来hook方法,找到关键业务代码,同时介绍了如何通过Frida来查看类中的成员变量,最后,利用动态调试找到了隐藏的类。其实,动态调试可以发现很多东西,当利用Frida获取不到信息的时候,可以试下动态调试,条条大路通罗马。逆向就是这样,厂商会做一些反调试和隐藏关键代码的工作,我们一种方法不行就换种方法,只要最终能得到自己想要的就行。

本文的目的只有一个:就是学习更多的逆向技巧和思路。如果有人利用本文技术去进行非法商业获取利益带来的法律责任都是操作者自己承担,和本文以及作者没关系。

本文涉及到的代码项目可以去 爱码者说 知识星球自取,欢迎加入知识星球一起学习探讨技术。
关注公众号 爱码者说 及时获取最新推送文章。