Android逆向-某麦网APK抢票接口加密参数分析


一、前言

针对某麦网部分演唱会门票仅能在app渠道抢票的问题,本文研究了APK的抢票接口并编写了抢票工具。本文介绍的顺序为环境搭建、抓包、trace分析、接口参数获取、rpc调用实现,以及最终的功能实现。通过阅读本文,你将学到反抓包技术破解、Frida hook、jadx apk逆向技术,并能对淘系APP的运行逻辑有所了解。本文仅用于学习交流,严禁用于非法用途。

关键词:frida, damai.cn, Android逆向
先放成功截图:图片

二、环境搭建

目前Android模拟器竞品很多,选择Bluestacks 5是因为它能和windows的hyper-v完美兼容,root过程也相对简单。

  1. 下载安装Bluestacks。

  2. 运行Bluestacks Multi-instance Manager,发现默认安装的版本为Android Pie 64bit版本,即Android 9.0。此时退出bluestack所有程序。
    图片

  3. 关闭bluestack后编辑bluestacks配置文件, %programdata%\BlueStacks_nxt\bluestacks.conf
    > 由于作者安装时C盘空间不足,真实的bluestacks.confD:\BlueStacks_nxt\bluestacks.conf,大家也根据实际情况调整
    图片

  4. 在配置文件中查找root关键词,对应值修改为1,共两处。

    bst.feature.rooting="1"
    bst.instance.Pie64.enable_root_access="1"
  5. 启动bluestack模拟器,安装 Root Checker APP,点击验证root,即可发现root已开启图片

三、adb调试

  1. bluestack设置-高级中打开Adb调试,并记录下端口

图片

  1. 打开主机命令行,运行 adb connect localhost:6652,端口号修改为上一步的端口号,即可连接。再运行adb devices,如有对应设备则连接成功。
  2. 进入adb shell,执行su进入root权限,命令行标识由$变为#,即表示adb 进入root权限成功

frida环境

frida是大名鼎鼎的动态分析的hook神器,用它可以直接访问修改二进制的内存、函数和对象,非常方便。它对于Android的支持也是很完美。

frida的运行采用C/S架构,客户端为电脑端的开发环境,服务器端为Android,均需对应部署搭建。

客户端环境搭建(Windows)

firda客户端基于python3开发,因此首先需要配置好python3的运行环境,然后执行 pip install frida-tools即可完成安装。运行 frida --version可验证frida版本。

(py3) PS E:\TEMP\damai> frida --version
16.0.19

服务器 环境搭建(Android)

环境搭建第二步是在Android模拟器中运行frida-server。这样可以让Frida通过ADB/USB调试与我们的Android模拟器连接。

  1. 下载frida-server
    最新的frida-server可以从 https://github.com/frida/frida/releases 下载。请注意下载与设备匹配的架构。如果您的模拟器是x86_64,请下载相应版本的frida-server。本文使用的版本为 frida-server-16.0.18-android-x86_64.xz
    图片
  2. 传入Android模拟器。

将下载后的.xz文件解压,将frida-server传入Android模拟器

adb push frida-server /data/local/tmp/
  1. 运行 frida-server

使用adb root以root模式重新启动ADB,并通过adb shell重新进入shell的访问。进入shell后,进入我们放置frida-server的目录并为其授予执行权限:

cd /data/local/tmp/
chmod +x frida-server

执行:./frida-server ,运行frida-server,并保持本shell窗口开启。

成功截图:

图片

有些情况下,应用程序会检测在是否在模拟器中运行,但对某麦网app的分析暂无影响。

  1. 测试是否连接成功

在window端运行frida-ps命令:

图片

看到一堆熟悉的Android进程,我们就连接成功啦

  1. 转发frida-server端口 (可选)

frida-server跑在Android端,frida需要通过连接frida-server。上一步使用adb的方式连接,frida认为是USB模式,需要-U命令。frida也支持依赖端口的远程连接模式,在某些场景下更加灵活。可以通过端口转发的方式实现此功能。

adb forward tcp:27042 tcp:27042
adb forward tcp:27043 tcp:27043

抓包

1. 搞定tcpdump

本文基于termux安装使用tcpdump。

首先安装termux apk。

图片

打开termux运行:

  • 挂载存储
termux-setup-storage
## 会弹出授权框,点允许
ls ~/storage/
## 如果出现dcim, downloads等目录,即表示成功
  • 安装tcpdump
pkg install root-repo
pkg install sudo tcpdump
  • 运行抓包
sudo tcpdump -i any -s 0 -w ~/storage/downloads/capture.pcap
  • tcpdump 成功截图:
    图片

之后就可以把downloads目录下的抓包文件拷贝到电脑上,用wireshark打开做进一步分析。

2. 解密https流量

Wireshark解密https流量的方法和原理介绍有很多,可参考以下文章,本文不再赘述。

wireshark解密技术的重点在于拿到客户端通信的密钥日志文件(ssl key log),像下面这种:

图片

在Android中实现抓取ssl key log需要hook系统的SSL相关函数,可以用frida实现。

  • 首先将下面的hook代码保存为sslkeyfilelog.js
// sslkeyfilelog.js
function startTLSKeyLogger(SSL_CTX_new, SSL_CTX_set_keylog_callback) {
    console.log("start----")
    function keyLogger(ssl, line) {
        console.log(new NativePointer(line).readCString());
    }
    const keyLogCallback = new NativeCallback(keyLogger, 'void', ['pointer', 'pointer']);

    Interceptor.attach(SSL_CTX_new, {
        onLeave: function(retval) {
            const ssl = new NativePointer(retval);
            const SSL_CTX_set_keylog_callbackFn = new NativeFunction(SSL_CTX_set_keylog_callback, 'void', ['pointer', 'pointer']);
            SSL_CTX_set_keylog_callbackFn(ssl, keyLogCallback);
        }
    });
}
startTLSKeyLogger(
    Module.findExportByName('libssl.so', 'SSL_CTX_new'),
    Module.findExportByName('libssl.so', 'SSL_CTX_set_keylog_callback')
)
  • 然后用frida加载运行hook
frida -U -l .\sslkeyfilelog.js  -f cn.damai

图片

  • 最后,抓包结束后将得到的key保存到sslkey.txt,格式是下面这样的,不要掺杂别的。
CLIENT_RANDOM 557e6dc49faec93dddd41d8c55d3a0084c44031f14d66f68e3b7fb53d3f9586d 886de4677511305bfeaee5ffb072652cbfba626af1465d09dc1f29103fd947c997f6f28962189ee809944887413d8a20
CLIENT_RANDOM e66fb5d6735f0b803426fa88c3692e8b9a1f4dca37956187b22de11f1797e875 65a07797c144ecc86026a44bbc85b5c57873218ce5684dc22d4d4ee9b754eb1961a0789e2086601f5b0441c35d76c448

在运行Frida Hook获取sslkey的同时,运行tcpdump抓包。抓包中依次测试获取详情页、选择价位、提交订单等操作,并对应记录下执行操作的时间,方便后续分析。

抓包完成后,用wireshark打开tcpdump抓包获得的pcap文件,在wireshark首选项-protocols-TLS中,设置 (Pre)-Master-Secret log filename为上述sslkey.txt。

图片

即可实现https流量的解密。

流量分析

某麦网的API流

在此铺垫一下,通过前期对某麦网PC端和移动端H5的分析,某麦网购票的工作流程大概为:

  1. 获得详情:接口为mtop.alibaba.damai.detail.getdetail。基于某演出的id(itemId)获得演出的详细信息,包括详情、场次、票档(SkuId)价位及状态信息,
  2. 构建订单:接口为mtop.trade.order.build.h5。发送 演出id+数量+票档id(itemId_count_skuId),得到提交订单所需的表单信息,包括观众、收货地址等。
  3. 提交订单:接口为mtop.trade.order.create.h5。对上一步构建订单得到的表单参数作出修改后,发送给服务器,得到最后的订单提交结果和支付信息。

apk流量分析

首先用过滤器http && tcp.dstport==443,得到向服务器发送的https包,如下图:

图片

可以看到大量向服务器请求的数据包,但其中有很多干扰的图片请求,因为修改过滤器把图片过滤一下。过滤器:http && tcp.dstport==443 and !(http.request.uri contains ".webp" or http.request.uri contains ".jpg" or http.request.uri contains ".png")

结果清爽了很多。

订单构建(order.build)

根据之前记录的操作的时间,以及对网页版的分析结果,笔者注意到了下图的这条流量:

图片

然后我们右键选择这条流量包,点击追踪http流,可以看到对应的响应包。

图片

图片

响应包里有些中文使用了UTF-8编码,可以点击右下角的Show data as,选择UTF-8,便可以正常显示。此时可以点击另存为,保存为txt文件,方便后续分析。

图片

订单构建的请求包中核心的数据部分为图中青色圈出来的部分,使用URL解码后为:

{"buyNow":"true","buyParam":"716435462268_2_5005943905715","exParams":"{\"atomSplit\":\"1\",\"channel\":\"damai_app\",\"coVersion\":\"2.0\",\"coupon\":\"true\",\"seatInfo\":\"\",\"umpChannel\":\"10001\",\"websiteLanguage\":\"zh_CN_#Hans\"}"}

buyParam为最核心的部分,拼接方式为演出id+数量+票档id。其他部分只需照抄。

请求包中还包含大量的各种加密参数、ID,而破解实现自动购票脚本的关键就在于如何通过代码的方式拿到这些加密参数。

订单构建的响应包为订单提交表单的各项参数,用于生成“确认订单”的表单。

图片

图片

订单提交(order.create)

按照同样的方式可以找到订单提交包,订单提交包的API路径为/gw/mtop.trade.order.create

图片

其中青色圈出来的部分为data发送的核心数据,对数据用URL解码后为:

{"feature":"{\"gzip\":\"true\"}","params":"H4sIAAAAAAA.................AAWk3NKAAA\n"}

看起来像是把原始数据用gzip压缩后又使用了base64编码,尝试解码:

import base64
import gzip
import json

# 解码后变为python dict
decode_data=base64.b64decode(params_str.replace("\\n",""))
decompressed_data=gzip.decompress(decode_data).decode("utf-8")
params=json.loads(decompressed_data)

with open("reverse\order.create-params.json","w") as f:
    json.dump(params,f,indent=2)

解码成功,存到order.create-params.json,

图片

解码后发现order.create发送的data参数和order.build请求返回的结果很相似,增加了一些用户对表单操作的记录。

图片

order.create请求的header中的各种加密参数和order.build一致。

order.create请求的返回结果中包含了订单创建是否成功的结果以及支付链接.

TRACE分析

通过前面对流量的分析,我们已经知道客户端向服务器发送的核心数据和加密参数,核心数据的拼接相对简单,但加密参数怎么获得还比较困难。因此,下面要开始分析加密参数的生成方法。本章节主要采用frida trace动态分析和jadx静态分析相结合的方式,旨在找到加密参数生成的核心函数和输入输出数据的格式。

根据文章 ( app安卓逆向x-sign,x-sgext,x_mini_wua,x_umt加密参数解析 ),其中数据包的加密参数和本文的某麦网很类似,而且提到了 mtopsdk.security.InnerSignImpl 生成的加密函数,本文也参考了这篇文章的思路进行分析。

跟踪 InnerSignImpl

运行frida-trace -U -j "*InnerSignImpl*!*" 大麦,执行选座提交订单的操作,发现确实有结果输出:

(py3) PS E:\TEMP\damai> frida-trace -U -j "*InnerSignImpl*!*" 大麦
Instrumenting...
InnerSignImpl$1.$init: Loaded handler at "E:\\TEMP\\damai\\__handlers__\\mtopsdk.security.InnerSignImpl_1\\_init.js"
....此处省略...
InnerSignImpl.init: Loaded handler at "E:\\TEMP\\damai\\__handlers__\\mtopsdk.security.InnerSignImpl\\init.js"
Started tracing 27 functions. Press Ctrl+C to stop.
           /* TID 0x144f */
  6725 ms  InnerSignImpl.getUnifiedSign("<instance: java.util.HashMap>", "<instance: java.util.HashMap>", "23781390", null, true)
  6726 ms     | InnerSignImpl.convertInnerBaseStrMap("<instance: java.util.Map, $className: java.util.HashMap>", "23781390", true)
  6726 ms     | <= "<instance: java.util.Map, $className: java.util.HashMap>"
  6727 ms     | InnerSignImpl.getMiddleTierEnv()
  6727 ms     | <= 0
  6737 ms  <= "<instance: java.util.HashMap>"

点击发送请求时,调用了InnerSignImpl.getUnifiedSign函数。但是输入参数和数据参数均为HashMap类型,结果中未显示具体内容。从结果输出中猜测frida-trace是通过对需要hook的函数在__handlers__下生成js文件,并调用js文件进行hook操作的,因此笔者修改了“handlers\mtopsdk.security.InnerSignImpl\getUnifiedSign.js”,使其能正确输出HashMap类型。

// __handlers__\mtopsdk.security.InnerSignImpl\getUnifiedSign.js

{onEnter(log, args, state) {
 // 增加了HashMap2Str函数,将HashMap类型转换为字符串
    function HashMap2Str(params_hm) {
      var HashMap=Java.use('java.util.HashMap');
      var args_map=Java.cast(params_hm,HashMap);
      return args_map.toString();
  };
     // 当调用函数时,输出函数参数
    log(`InnerSignImpl.getUnifiedSign(${HashMap2Str(args[0])},${HashMap2Str(args[1])},${args[2]},${args[3]})`);
  }, onLeave(log, retval, state) {
      function HashMap2Str(params_hm) {
        var HashMap=Java.use('java.util.HashMap');
        var args_map=Java.cast(params_hm,HashMap);
        return args_map.toString();	};
    if (retval !== undefined) {
     // 当函数运行结束时,输出函数结果
      log(`<= ${HashMap2Str(retval)}`);
    } }}

再次运行frida-trace,输出的结果已经可以看到具体内容了:

(py3) PS E:\TEMP\damai> frida-trace -U -j "*InnerSignImpl*!*" 大麦
        ......
Started tracing 27 functions. Press Ctrl+C to stop.
           /* TID 0x15ab */
  2653 ms  InnerSignImpl.getUnifiedSign({data={"itemId":"719193771661","performId":"211232892","skuParamListJson":"[{\"count\":1,\"price\":36000,\"priceId\":\"251592963\"}]","dmChannel":"*@damai_android_*","channel_from":"damai_market","appType":"1","osType":"2","calculateTag":"0_0_0_0","source":"10101","version":"6000168"}, deviceId=null, sid=13abe677c5076a4fa3382afc38a96a04, uid=2215803849550, x-features=27, appKey=23781390, api=mtop.damai.item.calcticketprice, utdid=ZF3KUN8khtQDAIlImefp4RYz, ttid=10005890@damai_android_8.5.4, t=1684828096, v=2.0},{pageId=, pageName=},23781390,null)
  2654 ms     | InnerSignImpl.convertInnerBaseStrMap("<instance: java.util.Map, $className: java.util.HashMap>", "23781390", true)
  2655 ms     | <= "<instance: java.util.Map, $className: java.util.HashMap>"
  2655 ms     | InnerSignImpl.getMiddleTierEnv()
  2655 ms     | <= 0
  2662 ms  <= {x-sgext=JA2qmBOxRVDxFRzca3r9BZibqJqvn7uerZOriayYu4mpnKCeoJiunKGZu5qqyfmaqJqhmvqYr5n8zPyJqImpmbvLrImomqidu5m7m7uYu5u7mLuYu5u7m7ubqYmtiaiJqImoiaiJqImoiaiJu8+7iaCf/cypnruaqJqomruau5j8y7uau4mgiaiJqInf6fDIu5o=, x-umt=+D0B/05LPEvOgwKIQ1x+SeV5wNE6NzOo, x-mini-wua=atASnVJw3vGX1Tw3Y/zDaVZkDUbLxOxtlUmgDOnIjMTBcMPMqQJLpnxoOWEL53Fq/OPcQZiMpDXWNvDz8UQkI5mtkZvIcDN1oxZnuH0M22LHKar4rnO/xm4LtAiniKgYtfgMGK3stXuCmvtE4raIhROimslSk7hCkxaL/DYuLzBLYwXmNyr9UZi1g, x-sign=azG34N002xAAK0H9KwNr3txWFMxzW0H7ROfkLQK+Db7ueJHktR/yP/0TcdPFzoYf36zd9lJYMsHCmYX3EcoFnJPMk2pxu0H7QbtB+0}

可以看到返回结果中包含了 x-sgext,x-umt,x-mini-wua,x-sign 等加密参数。至此,前面的一大堆分析也算有了小的收获。但对比流量分析结果中的发送参数,还是缺失了很多参数。下面我们继续跟踪,找出剩下的参数。

跟踪 mtopsdk

调研发现淘系的apk都包含mtopsdk,猜想会不会有公开的官方文档描述mtopsdk的使用方法,因此我们就找到了 【阿里云mtopsdk Android接入文档】 。其中介绍了请求构建的流程为,笔者重点关注了请求构建和发送的部分:

// 3. 请求构建
// 3.1生成MtopRequest实例
MtopRequest request = new MtopRequest();
// 3.2 生成MtopBuilder实例
MtopBuilder builder = instance.build(MtopRequest request, String ttid);
// 4. 请求发送
// 4.2 异步调用
ApiID apiId = builder.addListener(new MyListener).asyncRequest();

因此我们不妨大胆一些,直接跟踪所有对mtopsdk中函数的调用。

(py3) PS E:\TEMP\damai> frida-trace -U -j "*mtopsdk*!*" 大麦

图片

输出的结果大概有2000行,直接看太费劲,我们复制到文本编辑器里做进一步分析。

我们按照阿里的官方文档介绍的流程,对应可以找到在输出的trace中找到一些关键的日志。

# MtopRequest初始化
  3249 ms  MtopRequest.$init()
  3249 ms  MtopRequest.setApiName("mtop.trade.order.build")
  3249 ms  MtopRequest.setVersion("4.0")
  3249 ms  MtopRequest.setNeedSession(true)
  3249 ms  MtopRequest.setNeedEcode(true)
  3249 ms  MtopRequest.setData("{\"buyNow\":\"true\",\"buyParam\":\"7191937661_1_51826442779\",\"exParams\":\"{\\\"atomSplit\\\":\\\"1\\\",\\\"channel\\\":\\\"damai_app\\\",\\\"coVersion\\\":\\\"2.0\\\",\\\"coupon\\\":\\\"true\\\",\\\"seatInfo\\\":\\\"\\\",\\\"umpChannel\\\":\\\"10001\\\",\\\"websiteLanguage\\\":\\\"zh_CN_#Hans\\\"}\"}")

# MtopBuilder初始化
  3251 ms  MtopBuilder.$init("<instance: mtopsdk.mtop.intf.Mtop>", "<instance: mtopsdk.mtop.domain.MtopRequest>", null)

# MtopBuilder发送异步请求
3268 ms  MtopBuilder.asyncRequest()

# 参数构建
3301 ms     |    |    | InnerProtocolParamBuilderImpl.buildParams("<instance: mtopsdk.framework.domain.MtopContext>")
3391 ms     |    |    | <= "<instance: java.util.Map, $className: java.util.HashMap>",{wua=CofS_+7HCuvRCdz1EN8ICI6A4ZBCJwgY1hi+Bsivjcijs8GggmUxLQQUVTEQ5mYYtPuV7R2QNG5JEONIJRfmzjxFXMrs9AHdepIuqoJJJAyewWALprRnjIAu75t47Tm/RU9xRi7IEo9w0P2aCquLzf7uhiO8JEDSRK/ZdVhURBbof7reFtzEBoYYeIPgnwz7CL3kRlbyqyJcYKxO7ZmmVq1PtMXF2HGJqRSDjdv9l4mySJljIQzBmpX393L6eO1ZQVG1fpp6RaCRcFF+UgfjJXaeMFziHzfQF7KfUQZIeAJV/4GyVEE2f55RwPluOTuQubXQnq+qu41a0V5oyEOFXMoQRYFZzLOv3CjwkiIXsqJFeIHc=, x-sgext=JA0VLKcO8e9Fqqhj38VJuiwkHCUbIA8jGCwUNh0mDzYdIxQhFCcVJxskDyUedk0lHCUVJU4nGyZIc0g2HDYdJg90GDYcJRwiDyYPJA8kDyQPJA8kDyQPJA8nDyQPJQ8lDyUPJQ8lDyUPJQ82STYPLRlwSiUcNhwlHCUcNhw2HnFNNhw2Dy0PJQ8lD1JvfU42HA==, nq=WIFI, data={"buyNow":"true","buyParam":"719193771661_1_5182956442779","exParams":"{\"atomSplit\":\"1\",\"channel\":\"damai_app\",\"coVersion\":\"2.0\",\"coupon\":\"true\",\"seatInfo\":\"\",\"umpChannel\":\"10001\",\"websiteLanguage\":\"zh_CN_#Hans\"}"}, pv=6.3, sign=azG34N002xAAKiYA2sv237H04abW2iYKIxaD3GVPak+JifYV0u6VzpriFiKiP+HuuF26BzWpVTClaOIGdjtibfQ99JomGiYKJhomCi, deviceId=null, sid=13abe677c5076a4fa3382afc38a96a04, uid=2215803849550, x-features=27, x-app-conf-v=0, x-mini-wua=a3gSvx5K5/NRy/W8+fDouCSQ6VSmMK3awHwo5X+IayY7JL5SwHtiL0soynSAvCobk01qRQ2fQcTvZWakhmhA9xlNOKdwvxdA5nZ4Tno2asO5e7EvSMj6yqVYAXZZUBjZPUOBw3vpH8L2GUq9Gi6MTszU57a58+hJE2BCGTVsxhRonDw1Nnxp74Ffm, appKey=23781390, api=mtop.trade.order.build, umt=+D0B/05LPEvOgwKIQ1x+SeV5wNE6NzOo, f-refer=mtop, utdid=ZF3KUN8khtQDAIlImefp4RYz, netType=WIFI, x-app-ver=8.5.4, x-c-traceid=ZF3KUN8khtQDAIlImefp4RYz1684829318230001316498, ttid=10005890@damai_android_8.5.4, t=1684829318, v=4.0, user-agent=MTOPSDK/3.1.1.7 (Android;9;samsung;SM-S908E)}

笔者注意到了InnerProtocolParamBuilderImpl.buildParams函数的输出结果完全覆盖了需要的各类加密参数,其输入类型是MtopContext。从jadx逆向的apk代码中可以找到MtopContext类,即包含Mtop生命周期的各个类的一个容器。

public class MtopContext {
    public ApiID apiId;
    public String baseUrl;
    public MtopBuilder mtopBuilder;
    public Mtop mtopInstance;
    public MtopListener mtopListener;
    public MtopRequest mtopRequest;
    public MtopResponse mtopResponse;
    public Request networkRequest;
    public Response networkResponse;
    public MtopNetworkProp property = new MtopNetworkProp();
    public Map<String, String> protocolParams;
    public Map<String, String> queryParams;
    public ResponseSource responseSource;
    public String seqNo;
    @NonNull
    public MtopStatistics stats;
}

所以现在的问题变为如何能够构建出来MtopContext,然后调用buildParams函数生成各类加密参数。

分析业务模块与mtopsdk的交互过程

在写本文复盘分析过程的时候,笔者发现仅依赖mtopsdk的调用过程其实已经可以得到MtopContext的全部生成逻辑了。但所谓当局者迷,笔者在当时分析的时候还是一头雾水。因此在此也介绍一下笔者的思考逻辑。

当时看着mtopsdk的调用过程,感觉很复杂。但是猜想从用户点击操作->业务代码->mtopsdk的数据流,以及模块间高内聚低耦合的原则,所以猜想模块间的调用不会很复杂,所以笔者就想分析业务代码与mtopsdk的调用逻辑。所以就想跟踪主要业务代码的trace。所以笔者继续跟踪trace,运行frida-trace -U -j "*cn.damai*!*" 大麦 ,以分析cn.damai包的调用过程,在其中发现了 NcovSkuFragment.buyNow() 函数,看起来是和购买紧密相关的函数。又找到DMBaseMtopRequest类。

图片

但是在这里有点卡住了,因为只找到了构建MtopRequest,并未在cn.damai的trace日志中并未发现其他对mtop的调用。

然后笔者又尝试搜索和api(order.build)相关的代码,找到了:

图片

然而并没有多大用处。

然后,作者又读了大量的源代码,终于定位到了 com.taobao.tao.remotebusiness.MtopBussiness这个关键类。

图片

笔者本以为com.taobao开头的代码不是那么重要,所以最开始把这个类完全忽略了。但通过对源码的阅读,发现这个类是motpsdk中MtopBuilder类的子类,主要负责管理业务代码和Mtopsdk的交互。

因此我们继续通过trace跟踪MtopBussiness类。运行frida-trace -U -j "*!*buyNow*" -j "com.taobao.tao.remotebusiness.MtopBusiness!*" -j "*MtopContext!*" -j "*mtopsdk.mtop.intf.MtopBuilder!*" 大麦

图片

现在业务代码和mtopsdk的交互就很清晰了,红色的部分是业务代码的函数,绿色的部分是mtopsdk的函数。

0x06 hook得到接口参数

通过以上对trace的分析,已经知道了具体执行的操作,因此我们可以使用frida编写js代码,直接调用APK中的类,实现功能调用。

先展示一个简单的示例,用于构建一个自定义的MtopRequest 类:

// new_request.js
Java.perform(function () {
    const MtopRequest = Java.use("mtopsdk.mtop.domain.MtopRequest");
    let myMtopRequest = MtopRequest.$new();
    myMtopRequest.setApiName("mtop.trade.order.build");
    //item_id + count + ski_id  716435462268_1_5005943905715
    myMtopRequest.setData("{\"buyNow\":\"true\",\"buyParam\":\"716435462268_1_5005943905715\",\"exParams\":\"{\\\"atomSplit\\\":\\\"1\\\",\\\"channel\\\":\\\"damai_app\\\",\\\"coVersion\\\":\\\"2.0\\\",\\\"coupon\\\":\\\"true\\\",\\\"seatInfo\\\":\\\"\\\",\\\"umpChannel\\\":\\\"10001\\\",\\\"websiteLanguage\\\":\\\"zh_CN_#Hans\\\"}\"}")
    myMtopRequest.setNeedEcode(true);
    myMtopRequest.setNeedSession(true);
    myMtopRequest.setVersion("4.0");
    console.log(`${myMtopRequest}`)
});

再使用运行命令 frida -U -l .\reverse\new_request.js 大麦,以在某麦Apk中执行js hook代码。运行之后即可输出笔者自己构建的MtopRequest实例。(frida真的很奇妙!)

图片

有了上面的结果,下面继续完善这个示例,添加MtopBussiness的构建过程和输出过程

       //引入Java中的类
const MtopBusiness = Java.use("com.taobao.tao.remotebusiness.MtopBusiness");
const MtopBuilder = Java.use("mtopsdk.mtop.intf.MtopBuilder");
// let RemoteBusiness = Java.use("com.taobao.tao.remotebusiness.RemoteBusiness");
const MethodEnum = Java.use("mtopsdk.mtop.domain.MethodEnum");
const MtopListenerProxyFactory = Java.use("com.taobao.tao.remotebusiness.listener.MtopListenerProxyFactory");
const System = Java.use('java.lang.System');
const ApiID = Java.use("mtopsdk.mtop.common.ApiID");
const MtopStatistics = Java.use("mtopsdk.mtop.util.MtopStatistics");
const InnerProtocolParamBuilderImpl = Java.use('mtopsdk.mtop.protocol.builder.impl.InnerProtocolParamBuilderImpl');

// create MtopBusiness
let myMtopBusiness = MtopBusiness.build(myMtopRequest);
myMtopBusiness.useWua();
myMtopBusiness.reqMethod(MethodEnum.POST.value);
myMtopBusiness.setCustomDomain("mtop.damai.cn");
myMtopBusiness.setBizId(24);
myMtopBusiness.setErrorNotifyAfterCache(true);
myMtopBusiness.reqStartTime = System.currentTimeMillis();
myMtopBusiness.isCancelled = false;
myMtopBusiness.isCached = false;
myMtopBusiness.clazz = null;
myMtopBusiness.requestType = 0;
myMtopBusiness.requestContext = null;
myMtopBusiness.mtopCommitStatData(false);
myMtopBusiness.sendStartTime = System.currentTimeMillis();

let createListenerProxy = myMtopBusiness.$super.createListenerProxy(myMtopBusiness.$super.listener.value);
let createMtopContext = myMtopBusiness.createMtopContext(createListenerProxy);
let myMtopStatistics = MtopStatistics.$new(null, null); //创建一个空的统计类
createMtopContext.stats.value = myMtopStatistics;
myMtopBusiness.$super.mtopContext.value = createMtopContext;
createMtopContext.apiId.value = ApiID.$new(null, createMtopContext);

let myMtopContext = createMtopContext;
myMtopContext.mtopRequest.value = myMtopRequest;
let myInnerProtocolParamBuilderImpl = InnerProtocolParamBuilderImpl.$new();
let res = myInnerProtocolParamBuilderImpl.buildParams(myMtopContext);
console.log(`myInnerProtocolParamBuilderImpl.buildParams => ${HashMap2Str(res)}`)

再次执行frida -U -l .\reverse\new_request.js 大麦,输出结果如下图,此时已能根据笔者任意构建的请求data输出其他加密参数:

图片

对于order.create的原理类似,此处不再赘述。

![图片](data:image/svg+xml,<%3Fxml version=’1.0’ encoding=’UTF-8’%3F>)

原文链接:https://github.com/m2kar/m2kar.github.io/issues/21