关于java:爬虫系列2-打开App逆向潘多拉魔盒

3次阅读

共计 13335 个字符,预计需要花费 34 分钟才能阅读完成。

【爬虫系列】2. 关上 App 逆向“潘多拉魔盒”
一、前言

  • 近年大多产品要不 Web 端性能受限、要不间接就没有 Web 端,间接从 Web 端抓数据这个路子越来越难
  • 通过代理软件 (whistle | fiddler|Charles) 抓 HTTP 申请基本操作了,相干教程自行学习了。
  • Android 7 之后,非 Root 状况下零碎间接不信赖用户自行装置的 cert 证书,间接导致 App HTTP 抓包更加麻烦了;iOS 抓包倒是简略不少,信赖证书之后一路绿灯,甚至还能够花 168 买个 iOS 端的本地抓包工具
  • 大失所望的在于:有时候“辛辛苦苦”搞掂了 HTTP 抓包,拿过去一看,小小的“sign”字段躺在申请体外面,每个申请都会变动,“数据获取”的大门开了一条缝又关上了,着实让人好受

那么 …
到底有没有方法呢?
下一步怎么办呢?

PS:本文所有操作均基于 Android App,iOS 不在本学习教程内(臣妾也不会啊)

二、先验常识

2.0 外围思路

不反对在 Docs 外粘贴 block
2.1 劝退提醒

  • 能看懂 Java 代码,晓得 JAVA_HOME、ANDROID_HOME,能独立配置 Android Studio、Maven 仓库
  • 懂一丢丢命令行操作,懂 Git 基本操作,会从 Github 捞代码(别 TM Download zip)
  • 有一丢丢 Android 开发常识,至多晓得 adb 操作,apk 文件是什么,能运行起 gralwe

PS:如果都不会,倡议早点洗洗睡。
PPS:或者加钱请我一对一领导。

2.2 工作环境筹备

  • JDK8 本地环境(别问为什么是 JDK8,就是任性)
    ➜ ~ java -version
    openjdk version “1.8.0_292”
    OpenJDK Runtime Environment (AdoptOpenJDK)(build 1.8.0_292-b10)
    OpenJDK 64-Bit Server VM (AdoptOpenJDK)(build 25.292-b10, mixed mode)
  • skylot/jadx(Dex to Java decompiler)git clone + 配置好 jdax 工具 -> 用户反编译 Apk
  • install Android Studio 下载 + 装置,网络好的状况下,间接把 Android NDK 21.0 也搞掂就最好了
  • 差点忘了说,要有一台 Android 手机,有一根数据线 …

三、开搞
3.0 今天天气真好
PS: 让我想想,搞谁家的 App 比拟乏味 …
PPS:并不是针对谁,只是“学习学习”一下优良代码

3.1 豆瓣 App 签名破解(Java 原生)

  • 翻了下以前的我的项目,如同豆瓣 App 是本地签名,所以就它了
  • 学习目标:豆瓣小组 API 破解
    先捞个 curl 看看 http 接口申请
    curl –location –request GET ‘https://frodo.douban.com/api/…’ \
    –header ‘Authorization;’ \
    –header ‘User-Agent: api-client/1 com.douban.frodo/7.1.0(205) Android/29 product/perseus vendor/Xiaomi model/Mi MIX 3 rom/miui6 network/wifi udid/a0f9cde79ec841a748625f766273e8f4333ed9c1 platform/mobile nd/1’ \
    –header ‘Cookie: bid=EGo8Z7aSUAI’

能够看到这里申请失常返回,也能看到数据了。

然而咱们切换一下其余小组的数据,很快就能看到“invalid_request_996”,签名谬误的提醒。

仔细的敌人,大略曾经看到了“_sig”这个字段了,显著就是“sign”的简写。
好了,开始干活。

豆瓣 App- 下载

下载 apk

wget https://img2.doubanio.com/dae…

应用 jadx 反编译 apk,输入到 douban-src 文件夹

jadx -d douban-src com.douban.frodo_douban_7.18.1_231.apk

应用 android-studio 关上 douban-src 我的项目

studio douban-src

反编译后果

Android 我的项目代码

最简略的方法,间接搜寻字段“_sig”

大略能找到这段代码:

@android.webkit.JavascriptInterface
public java.lang.String decoratorUrl(java.lang.String str) {

try {if (!com.douban.frodo.baseproject.rexxar.RexxarConstant.a(android.net.Uri.parse(str).getHost())) {return str;}
    android.net.Uri.Builder appendQueryParameter = android.net.Uri.parse(str).buildUpon().appendQueryParameter("udid", 
    com.douban.frodo.baseproject.util.FrodoUtils.a()).appendQueryParameter("rom", 
    com.douban.frodo.baseproject.util.Utils.i()).appendQueryParameter("apikey", 
    com.douban.frodo.baseproject.util.FrodoUtils.c()).appendQueryParameter(com.umeng.commonsdk.proguard.d.ao, "rexxar_new");
    if (com.douban.frodo.utils.AppContext.b() != null) {appendQueryParameter.appendQueryParameter("channel", com.douban.frodo.utils.AppContext.b().market);
    }
    android.util.Pair<java.lang.String, java.lang.String> a 
           = com.douban.frodo.network.ApiSignatureHelper.a(str, "GET", null);
    if (a != null) {appendQueryParameter.appendQueryParameter("_sig", (java.lang.String) a.first);
        appendQueryParameter.appendQueryParameter("_ts", (java.lang.String) a.second);
    }
    str = appendQueryParameter.build().toString();
    return str;
} catch (java.lang.Exception e) {e.printStackTrace();
}

}

显著签名实现在“com.douban.frodo.network.ApiSignatureHelper”这个类,
应用了 a 办法对 str 变量签名,返回了 _sig 和 _ts,而后扔给 QueryParameter。

package com.douban.frodo.network;

public class ApiSignatureHelper {

static android.util.Pair<java.lang.String, java.lang.String> a(okhttp3.Request request) {if (request == null) {return null;}
    java.lang.String header = request.header(com.douban.push.internal.api.Request.HEADER_AUTHORIZATION);
    if (!android.text.TextUtils.isEmpty(header)) {header = header.substring(7);
    }
    return a(request.url().toString(), request.method(), header);
}

public static android.util.Pair<java.lang.String, java.lang.String> a(java.lang.String str, java.lang.String str2, java.lang.String str3) {if (android.text.TextUtils.isEmpty(str)) {return null;}
    java.lang.String str4 = com.douban.frodo.network.FrodoApi.a().e.b;
    if (android.text.TextUtils.isEmpty(str4)) {return null;}
    java.lang.StringBuilder sb = new java.lang.StringBuilder();
    sb.append(str2);
    java.lang.String encodedPath = okhttp3.HttpUrl.parse(str).encodedPath();
    if (encodedPath == null) {return null;}
    java.lang.String decode = android.net.Uri.decode(encodedPath);
    if (decode == null) {return null;}
    if (decode.endsWith("/")) {decode = decode.substring(0, decode.length() - 1);
    }
    sb.append(jodd.util.StringPool.AMPERSAND);
    sb.append(android.net.Uri.encode(decode));
    if (!android.text.TextUtils.isEmpty(str3)) {sb.append(jodd.util.StringPool.AMPERSAND);
        sb.append(str3);
    }
    long currentTimeMillis = java.lang.System.currentTimeMillis() / 1000;
    sb.append(jodd.util.StringPool.AMPERSAND);
    sb.append(currentTimeMillis);
    return new android.util.Pair<>(com.douban.frodo.utils.crypto.HMACHash1.a(str4, sb.toString()), java.lang.String.valueOf(currentTimeMillis));
}

}

看到这个代码就应该开心了,17 – 45 行都是在解决传入的参数,
次要用了 android.net.Uri 提取参数之后,拼装成了一个用“&”宰割的字符串,用相熟的语言自行处理就完事了。
最初,签名是调用了“com.douban.frodo.utils.crypto.HMACHash1.a”,还须要持续跟进去。
不过这个名字“HMAC-SHA1”曾经给了不少的信息了。

跟代码进入到 com.douban.frodo.utils.crypto.HMACHash1 类,于是看到了上面的代码:
package com.douban.frodo.utils.crypto;

public class HMACHash1 {

public static final java.lang.String a(java.lang.String str, 
java.lang.String str2) {
    try {
        javax.crypto.spec.SecretKeySpec secretKeySpec = new javax.crypto.spec.SecretKeySpec(str.getBytes(), com.douban.live.internal.LiveHelper.HMAC_SHA1);
        javax.crypto.Mac instance = javax.crypto.Mac.getInstance(com.douban.live.internal.LiveHelper.HMAC_SHA1);
        instance.init(secretKeySpec);
        return android.util.Base64.encodeToString(instance.doFinal(str2.getBytes()), 2);
    } catch (java.lang.Exception e) {e.printStackTrace();
        return null;
    }
}

}

纯 Java 实现的一个 HMAC_SHA1 加密算法。
str 是 SecretKey(ZenoConfig 的某个变量),str2 是传入的签名数据。

当初只剩最初一个问题了,SecretKey 怎么拿?
回到下面代码,能够看到:
java.lang.String str4 = com.douban.frodo.network.FrodoApi.a().e.b;
// str4 就是传到 HMACHash1 的 key,e 这里是 ZenoConfig

这里能够看到 ZenoConfig.b 就传入的 str3,也就是咱们要找的 SecretKey
持续翻就能看到

java.lang.String d2 = com.douban.frodo.baseproject.util.FrodoUtils.d();

builder.c = d2;

com.douban.zeno.ZenoConfig zenoConfig = new com.douban.zeno.ZenoConfig(

    builder.a, builder.b, builder.c, builder.d, builder.e, 
    builder.f, builder.g, builder.h, builder.i, builder.j);

package com.douban.frodo.baseproject.util;

public class FrodoUtils {

private static java.lang.String a;
private static java.lang.String b;
private static java.lang.String c;
private static java.lang.String d;
private static java.lang.String e;

public static java.lang.String e() {return "frodo://app/oauth/callback/";}

public static java.lang.String a() {if (android.text.TextUtils.isEmpty(a)) {a = com.douban.amonsul.MobileStat.g((android.content.Context) com.douban.frodo.utils.AppContext.a());
    }
    return a;
}

public static void a(java.lang.String str) {e = str;}

public static java.lang.String b() {return e;}

public static java.lang.String c() {return b;}

public static java.lang.String d() {return c;}

@android.annotation.SuppressLint({"PackageManagerGetSignatures"})
public static void a(boolean z) {if (android.text.TextUtils.isEmpty(b)) {b = "74CwfJd4+7LYgFhXi1cx0IQC35UQqYVFycCE+EVyw1E=";}
    if (android.text.TextUtils.isEmpty(c)) {c = "bHUvfbiVZUmm2sQRKwiAcw==";}
    if (z) {
        try {
            java.lang.String encodeToString = android.util.Base64.encodeToString(com.douban.frodo.utils.AppContext.a().getPackageManager().getPackageInfo(com.douban.frodo.utils.AppContext.a().getPackageName(),
             64).signatures[0].toByteArray(), 0);
            b = com.douban.frodo.utils.crypto.AES.a(b, encodeToString);
            c = com.douban.frodo.utils.crypto.AES.a(c, encodeToString);
        } catch (android.content.pm.PackageManager.NameNotFoundException e2) {e2.printStackTrace();
        }
    }
}

public static java.lang.String f() {if (android.text.TextUtils.isEmpty(d)) {return "d40568d833";}
    return d;
}

}

于是咱们也就晓得了,所谓的“SecretKey”,其实就是 c = “bHUvfbiVZUmm2sQRKwiAcw==”; AES 加密之后的值,encodeToString 就是 com.douban.frodo.utils.AppContext.a().getPackageName()包名信息。如果开发过安卓 App 大略会晓得,这段代码用来获取以后利用的签名的,这是安卓的一种防篡改的平安机制。
尽管咱们没方法间接拿到 com.douban.frodo.utils.AppContext.a().getPackageName(),不过,其余利用也能够获取已装置利用的签名信息,只须要把对应 app 的包名作为参数传入。
于是 …
Application application=(Application)getApplicationContext();
PackageInfo packageInfo=application.getPackageManager().getPackageInfo(“com.douban.frodo”,PackageManager.GET_SIGNATURES);
String sign=Base64.encodeToString(packageInfo.signatures[0].toByteArray(),0);

最初咱们把下面的签名代码跑一下,便能够失去 SecretKey = “bf7dddc7c9cfe6f7”

很好,很给力,显著能够上班了。
代码?
我 …
也 …
懒 …
得 …
写 …

须要的敌人,能够到 豆瓣 app 签名算法剖析与解密 自取。

3.2 ratel-core Android 逆向剖析工具套件

  • GitHub – virjarRatel/ratel-core: 平头哥的外围代码
  • 简介 · Ratel 文档
    平头哥(ratel)是一个 Android 逆向剖析工具套件,他提供一系列渐进式 app 逆向剖析工具。
    同时平头哥也是一个 app 二次开发的沙箱环境,反对在免 root 环境下 hook 和重定义 app 性能。
    对于大部分 app 来说,平头哥关上了潘多拉魔盒,请不要在受权之外守法应用平头哥(仅倡议用于集体定制化应用、app 攻防平安钻研等畛域),在 ratel 官网受权之外违规应用 ratel 造成的一些结果由使用者自定承当
    平头哥是一个 app 逆向剖析的生态,开发进度历时 3 年。目前正思考推出商业版本的开源化。

同时作为一套欠缺闭环的工具链,平头哥的相干性能是十分多的。

  • 根本的 hook 任意 app 性能,免 root 能力
  • 分身和多开能力(目前曾经在生产验证过一台手机分身 100 个设施)
  • 设施指纹反抗能力:试验发现曾经能够应答某些大厂
  • 群控能力:内置 SupperAppium 模块,其开源计划:https://bbs.pediy.com/thread-…
  • 定时工作治理, 大多为了反对无电脑的群控(无 USB 的脱机群控)
  • 热公布:插件模块通过后端热发,对集群所有设施失效。且反对回滚
  • RDP:目前市面上惟一还能够实现对微信等大型 app 实现 smali 重打包的功能模块
  • 脱壳:内置指令 dump 级别脱壳机,能够免 root 脱壳
  • 兼容和适配:在 2000 多台设施,兼容测试过 500 款来自利用市场的抽样 app。笼罩 Android5.0-Android10.0(Android11 曾经在内测中)
  • 免 root IDA 调试,内置 JustTrustMe
  • 内置 socketMonitor(比肉丝的 R0Cpature 早呈现 3 年,晚期甚至反对线程跳跃追踪,用以解决异步问题)
  • SplitApk:GoogleAppStore Android 安装包散发格局
  • 多种重打包计划反对:appendDex、rebuildDex、zelda、shell
  • 生态:微信机器人、模仿定位、多开账号资源备份还原

一句话概括:非 ROOT 状况下,通过插件机制轻易改现有 App 性能。

于是,咱们来试试水。

3.2.1 筹备 ratel-core 编译环境

  • JDK8
  • Android Studio + NDK21.0 (NDK 订正历史记录  |  Android NDK  |  Android Developers)
  • adb
    export ANDROID_NDK_HOME=/Users/liguobao/Library/Android/sdk/ndk/21.0.6113669
    export ANDROID_SDK_ROOT=”/Users/liguobao/Library/Android/sdk”
    export PATH=”${PATH}:${ANDROID_SDK_ROOT}/tools:${ANDROID_SDK_ROOT}/platform-tools”

参考下面这个配置好本地的 ANDROID_NDK_HOME + ANDROID_SDK_ROOT
PS:这玩意如果本人不相熟,多翻翻教程。

下载 https://github.com/virjarRate… 源码
$ git clone https://github.com/virjarRate…
$ cd ratel-core/

编译代码 + 查看环境配置

$ ./script/create-dist.sh

以上操作都没问题,都失常编译之后,

就能够应用 ./script/ratel.sh 来重打包 App 了。

➜ script git:(master) cd dist
➜ dist git:(master) ./ratel.sh ~/Downloads/leyoujia.apk

失常状况下会生成一个新的 apk 文件,最初一步就是把这个 apk 装置到手机上了。

装置好了之后,在手机端关上 App,App 不解体的话就阐明胜利了。
PS:遇到解体的状况,去 GitHub 提 Issues。

3.2.2 筹备 ratel-module 我的项目

  • 重打包之后的 Apk,就是开了“后门”的 App
  • 插件就是给了咱们自定义 App + 操纵 App 的能力
  • https://github.com/virjarRate…
    同时能够“Ratel Manager”到手机端,通过此 App 治理插件和查看 App 感化信息。
  • Ratel Manager 也能够在源码外面编译,或者间接下载老版本。

“乐有家”就是刚刚重打包感化的 App。
$ git clone https://github.com/virjarRate…
$ cd ratel-module-template/
$ ./template.sh ~/Downloads/leyoujia.apk

而后在 android-studio 中关上 ratel-module-template 整个我的项目,
等一下 Index 代码和还原相干包文件之列的。

试一下编译装置“crack- 乐有家”到手机端是不是 OK。

失常状况下,这个插件 App 会间接启动,
而且在 Ratel Manager 的模块外面能够看到新的插件 App

  • 开关一下 Ratel status,在页面多下拉几次触发刷新机制

默认插件我的项目曾经有了一个乏味的性能 —“插入悬浮按钮”
// 增加悬浮窗
private static void addFloatingButtonForActivity(final RC_LoadPackage.LoadPackageParam lpparam) {

RposedHelpers.findAndHookMethod(Activity.class, "onCreate", Bundle.class, new RC_MethodHook() {
    @Override
    protected void afterHookedMethod(final MethodHookParam param) throws Throwable {new Handler(Looper.getMainLooper())
                .postDelayed(new Runnable() {
                    @Override
                    public void run() {createAndAttachFloatingButtonOnActivity((Activity) param.thisObject);
                    }
                }, 1000);
    }

    private void createAndAttachFloatingButtonOnActivity(Activity activity) {Context context = RatelToolKit.ratelResourceInterface.createContext(lpparam.modulePath, HookEntry.class.getClassLoader(), RatelToolKit.sContext);

        FrameLayout frameLayout = (FrameLayout) activity.getWindow().getDecorView();
        LayoutInflater.from(context).cloneInContext(context)
                .inflate(R.layout.float_button, frameLayout);

    }
});

}

到这里,ratel-module 插件我的项目曾经失常运行了,
Demo 代码咱们也曾经跑起来了。

3.2.3 再做点乏味的玩意?
A. 间接信赖所有本地用户 cert 证书
// package ratel.com.jjs.android.butler;
// public class HookEntry implements IRposedHookLoadPackage {}
// handleLoadPackage 函数外面新增一行代码,而后从新编译装置,
// 手机上操作刷新插件后从新关上 App
JustTrustMe.trustAllCertificate();

于是 HTTPS 申请无所遁形。

curl –location –request POST ‘https://steward.leyoujia.com/…’ \
–header ‘host: steward.leyoujia.com’ \
–header ‘clientid: e14becf6-2e13-43ae-b453-bd9cd35354a4’ \
–header ‘d: 0’ \
–header ‘latitude;’ \
–header ‘channel: online_32’ \
–header ‘imsi: 460110136201976’ \
–header ‘uuid: e14becf6-2e13-43ae-b453-bd9cd35354a4’ \
–header ‘ssid: 00000000378d5761ffffffffa488b92e’ \
–header ‘version: 8.1.9’ \
–header ‘mac: 64:BC:0C:44:64:01’ \
–header ‘network: WIFI’ \
–header ‘cit: 001729’ \
–header ‘sid: 38e5d3e9be36f0508dff7415ffffc076’ \
–header ‘phonemodel: Maru on the Nexus 5X’ \
–header ‘phoneos: android’ \
–header ‘carries: 0’ \
–header ‘imei: 35362607298355’ \
–header ‘aid: APP001’ \
–header ‘clientsign: 39ea04d3954db18df716e21978f009df’ \
–header ‘androidid: aabfdc5bb198e3b7’ \
–header ‘oaid: 00000000378d5761ffffffffa488b92e’ \
–header ‘longitude;’ \
–header ‘timestamp: 1639312110572’ \
–header ‘content-type: application/x-www-form-urlencoded’ \
–header ‘user-agent: okhttp/3.9.1’ \
–header ‘Connection: close’ \
–data-urlencode ‘cityCode=001729’

仔细一看,clientsign 签名赫然其中。
好家伙,又要开始搞事件了 ….
回到 3.1 的操作,生成一份“leyoujia-src”走起.
B. 从新开搞 leyoujia-src 代码
搜寻“clientSign”,大略就能看到代码了

java.lang.String encode32 = com.jjshome.common.utils.MD5.encode32(
com.jjshome.common.utils.MD5.encode32(sb.toString()));

package com.jjshome.common.utils;

public class MD5 {

/* JADX WARNING: type inference failed for: r2v2, types: [int] */
/* JADX WARNING: type inference failed for: r2v5 */
/* JADX WARNING: Multi-variable type inference failed */
public static java.lang.String encode32(java.lang.String str) {java.lang.StringBuffer stringBuffer = new java.lang.StringBuffer("");
    try {java.security.MessageDigest instance = java.security.MessageDigest.getInstance("MD5");
        instance.update(str.getBytes());
        byte[] digest = instance.digest();
        for (int i = 0; i < digest.length; i++) {byte b = digest[i];
            if (b < 0) {b += 256;}
            if (b < 16) {stringBuffer.append("0");
            }
            stringBuffer.append(java.lang.Integer.toHexString(b));
        }
    } catch (java.lang.Exception e) {e.printStackTrace();
    }
    return stringBuffer.toString();}

}

大略故事又成了什么看到 sb 字符串怎么来的了。
此处省略,自行折腾了。
PS:

  • 其实最初搞这玩意还是花了一个下午,哪些参数怎么拼接切实是有点蛋疼。
  • 好好读代码,好好看逻辑总是能搞掂的,敌人加油!

N1、总结

  • 学习学习就好,不要有危险的想法。
  • 攻防永远的绝对的,永远不可能一劳永逸。
  • 每天间隔“提篮桥”更进一步。
    N2、总结
  • 学习了 jadx 逆向工具的应用,温习了 Java 根底
  • 学习了 Ratel 平头哥工具,学习了 Android 基础知识

参考浏览:

  • 豆瓣 app 签名算法剖析与解密 – 天赐网络
  • Android 如何调用 so 文件
  • Linux 的 so 文件到底是干嘛的?浅析 Linux 的动态链接库
  • Android 的 so 文件加载机制 – 请叫我大苏 – 博客园
  • ratel 的应用

未完待续 ….

  • Ratel + Sekiro 高阶用法之“卷死”App 逆向的同行。
    敬请期待 …
正文完
 0