夜雨聆风学习资料网

ARTICLE · 1019328

某影视APP-P2P去签实战

某影视APP-P2P去签实战

前言

AI太牛逼了 。感觉发什么都没意思。。。

看一堆人发dy、xhs、好像这些都是有手就行了。

算了。别搞技术了,研究思路就行了。

随便玩玩吧

这里随便水一下。后面都随缘发了。

版本:5.0.0

包名:com.pathrecall.gj

加固:360 pp_hlsProtected(libjiagu_sdk_pp_hlsProtected.so + libpp_hls.so)

目标两件:

  1. 无限观影
  2. 重打包

广告

主要体现有两个 如下图:

1、

2、

这里弹窗hook 就不多说了 直接hook外层activity 也可以。

后面播放判断的逻辑 如下图所示

C5758.m19826() || m19824() >= m19808()    → 直接播    → 否则走激励广告

混淆后类名:com.fnmobi.sdk.library.ᐧʻˎ,这里要看下对应的关系 方便后续改smail 最后确认在 classes8.dex。

如下图所示,可以看到还是有一些莫名其妙的符号混淆

这里总结了下:

ʽﹶm19826KEY_PREF_FREE_AD
,是否免广告
ʽᵢKEY_PREF_IS_FREE_AD_ALL
,全免,返回 Boolean
ʽゼm19824KEY_PREF_PLAYVIEWNUM
,今天已经看过的次数
ʽיm19808KEY_PREF_MAXVIEWNUM
,上限,默认 5

每天会走 C4006.m14325 把 PLAYVIEWNUM 清零。

Frida

Java 层直接改四个 getter 的返回值。ʽᵢ 返回的是 Boolean 对象,不是 boolean,要用 Boolean.valueOf(true)。

functionlog() {var a = [];for (var i = 0; i < arguments.length; i++) a.push(String(arguments[i]));console.log("[unlimited] " + a.join(" "));}Java.perform(function () {var C5758 = Java.use("com.fnmobi.sdk.library.ᐧʻˎ");// KEY_PREF_FREE_AD    C5758["ʽﹶ"].implementation = function () {        log("C5758.isFreeAd is called, return true");returntrue;    };// KEY_PREF_IS_FREE_AD_ALL,返回 Boolean    C5758["ʽᵢ"].implementation = function () {        log("C5758.isFreeAdAll is called, return true");return Java.use("java.lang.Boolean").valueOf(true);    };// KEY_PREF_PLAYVIEWNUM 已看次数    C5758["ʽゼ"].implementation = function () {        log("C5758.getPlayViewNum is called, return 99999");return99999;    };// KEY_PREF_MAXVIEWNUM 上限    C5758["ʽי"].implementation = function () {        log("C5758.getMaxViewNum is called, return 0");return0;    };    log("unlimited watch hooks installed");});

这样就能播放了。其实这个逻辑比较简单。

重打包

我觉得这一块比较麻烦。因为你重打包 正常都直接过了。但是你使用mt 或者其他工具 直接就会闪退。。。。

这一版会比较麻烦。。他不闪退 就是视频无法播放

上文提到了 主要逻辑是在 classes8.dex

这里我们看看

这里改完classes8.dex 需要保持原大小。因为我发现改完要重新算下hash 。用zip -d 去签名,再 zip -u 只更新这一个 dex

字节码

const/4 v0, 1 是 12 10,不是 12 01。第一次写反了,就会导致VerifyError。

FREE_AD / MAX / PLAY 三个方法 insns_size = 12,FREE_AD_ALL 是 16,后面还要留 Boolean.valueOf。

12 10 0f 00     # const/4 v0,1 ; return v0     ʽﹶ FREE_AD12 00 0f 00     # const/4 v0,0 ; return v0     ʽי MAXVIEWNUM14 00 9f 86 01 00 0f 00   # const v0, 99999     ʽゼ PLAYVIEWNUM

360 加固

重打包还会触发360加固 可能会导致闪退。。 安装报毒。

具体逻辑如下

MyRootApplication.attachBaseContext 里会调

invoke-static {p0}, Lcom/jiagu/sdk/pp_hlsProtected;->install(Landroid/app/Application;)V

对应 opcode:71 10 43 40 00 00(offset 0x409326)。

Java 层 install() 是空壳,活在 JNI_OnLoad。改过 dex 再让它跑,MultiDex 之后直接 SIG 9。

所以把这 6 字节 nop 掉:

71 10 43 40 00 00  →  00 00 00 00 00 00

这样就能直接过了。但是过了之后 依然不行。

因为直接nop掉会导致 链接拼接错误。哎 真麻烦。。。

最后的体现如下

怎么刷新没用。

其实后面通过抓包发现 他应该是播放地址有问题。

当 CommonEachBean.getIs_p2p() == 1 时,不走原始 vod_url,而是:

http://127.0.0.1:{MyRootApplication.ʽℼ}/resource.m3u8?src={Base64(vod_url)}&ck={ck}

封装在 com.fnmobi.sdk.library.ˉـ.ʽᵢ(C4006.m14331)。无 ck 的版本是 ʽﹳ(m14333)。

端口来自 com.pp.hls.load:

new com.pp.hls().load("<p2p-app-key>",          // 32 位 hex,so 里 / smali 里都有"com.xxx.xxx","3",    extDir, extDir,    p2pConfig,                // ᐧʻˎ.ʽ͋() 拼出来的配置串"1");

返回值会重新 写进 me.genuine.cher.sub.app.base.MyRootApplication.ʽℼ

这里重新用下frida 去hook看看。

frida如下:

functionlog() {var a = [];for (var i = 0; i < arguments.length; i++) a.push(String(arguments[i]));console.log("[play] " + a.join(" "));}Java.perform(function () {var Bean = Java.use("me.genuine.cher.sub.app.contentEntry.detail.CommonEachBean");    Bean.getIs_p2p.implementation = function () {var r = this.getIs_p2p();        log("getIs_p2p=" + r + " vod=" + this.getVod_url() + " ck=" + this.getCk());return r;    };var App = Java.use("me.genuine.cher.sub.app.base.MyRootApplication");    log("p2p_port_field=" + App.ʽℼ.value);var Hls = Java.use("com.pp.hls");    Hls.load.overload("java.lang.String", "java.lang.String", "java.lang.String","java.lang.String", "java.lang.String", "java.lang.String", "java.lang.String"    ).implementation = function (a, b, c, d, e, f, g) {        log("hls.load key=" + a + " pkg=" + b + " mode=" + c);var r = this.load(a, b, c, d, e, f, g);        log("hls.load ret=" + r + " portField=" + App.ʽℼ.value);return r;    };var C4006 = Java.use("com.fnmobi.sdk.library.ˉـ");    C4006["ʽᵢ"].implementation = function (s) {var r = this["ʽᵢ"](s);        log("wrap.m14331 in=" + s + " out=" + r);return r;    };    C4006["ʽﹳ"].implementation = function (s) {var r = this["ʽﹳ"](s);        log("wrap.m14333 in=" + s + " out=" + r);return r;    };});

结果:

hls.load ret=-1 portField=-1getIs_p2p=1vod=http://cdn.xxx.com/vod/m3u8/.../index.m3u8wrap.m14331 out=http://127.0.0.1:-1/resource.m3u8?src=...

端口 -1,Exo 报地址异常。原包同一条 hook,hls.load ret 是正常正数端口。

这里看到了 portField=-1, ret=-1   代表有问题。那这里就要重新拼接。

这里注意几个点:

1. 把 ˉـ.ʽᵢ / ʽﹳ 整段改成 return p0

2. 只把 CommonEachBean.getIs_p2p 改成 return 0

片源要走本地 P2P(libpp_hls.so)。它会读安装签名;重签后 hls.load 返回 -1,地址变成 http://127.0.0.1:0/...,界面就是「播放异常」。CDN 直链是 403,不走代理播不了。

P2P 签名校验

刚刚讲到了 P2P 代理。所以代表P2P 其实有个签名校验。

这里也不想用ida看了。直接让ai帮我读一下libpp_hls.so 的明文串:

最后发现

getPackageManagergetPackageInfo(Ljava/lang/String;I)Landroid/content/pm/PackageInfo;signatures[Landroid/content/pm/Signature;toByteArrayjava/security/MessageDigest

JNI 里 flags 写死 0x40(GET_SIGNATURES),把证书 toByteArray 再 MD5 成 hex。这个 hash 当应用身份交给 tracker。

重签之后 hash 对不上,load 直接 -1。

这里不能直接nop掉。。 因为你nop掉就读不到原证书了。

所以要从 META-INF/CERT.RSA  那这个东西是什么。

其实这玩意就是是 APK v1 签名里的证书文件。

本质上欺骗签名就是伪造这个。

具体frida代码如下: 这里具体hash我就不贴了。。。

var PKG = "com.xxx.xxx";var CER_HEX = "<原包证书 DER hex>";functionhexToBytes(hex) {var arr = [];for (var i = 0; i < hex.length; i += 2) arr.push(parseInt(hex.substr(i, 2), 16));return Java.array("byte", arr);}Java.perform(function () {var Signature = Java.use("android.content.pm.Signature");var origSig = Signature.$new(hexToBytes(CER_HEX));var PM = Java.use("android.app.ApplicationPackageManager");    PM.getPackageInfo.overload("java.lang.String", "int").implementation = function (name, flags) {var pi = this.getPackageInfo(name, flags);if (name === PKG && (flags & 0x40)) {            pi.signatures.value = Java.array("android.content.pm.Signature", [origSig]);console.log("[spoof] getPackageInfo flags=0x" + (flags >>> 0).toString(16));        }return pi;    };var App = Java.use("me.genuine.cher.sub.app.base.MyRootApplication");var Hls = Java.use("com.pp.hls");var ctx = Java.use("android.app.ActivityThread").currentApplication();var path = ctx.getExternalFilesDir("").getAbsolutePath();var cfg = Java.use("com.fnmobi.sdk.library.ᐧʻˎ")["ʽ͋"]();var r = Hls.$new().load("<p2p-app-key>",        PKG,"3",        path, path, cfg, "1"    );console.log("[spoof] hls.load ret=" + r + " portField=" + App.ʽℼ.value);if (r > 0) App.ʽℼ.value = r;});

实际结果:

getPackageInfo flags=0x40hls.load ret=7000port written 7000

设备上:

LISTEN  0.0.0.0:7000

签名骗过,P2P 就起来了。后面就是把这套写进包。

最后:还要Hook一个内置方法。ActivityThread.sPackageManager。不能直接让ai改getPackageInfo。因为他默认会返回本包。

结论:

最终结果如下:

classes8.dex(原大小不变):

  • ᐧʻˎ 四个 getter → 免广告 / 次数 99999 / 上限 0
  • pp_hlsProtected.install nop(0x409326),避免改 dex 被杀
  • MyRootApplication 去掉 final(0x182994:11 00 00 00 → 01 00 00 00)
  • classes10.dex:IPackageManager 代理,把本包 signatures 换成原包证书
  • AndroidManifest.xml:Application 换成 SigSpoofAppImplXX
  • 重签 v1/v2/v3

相关学习资料