目的是实现像Xposed插件的效果,脱离电脑也能使用frida

frida-Inject

使用方式

Frida-Inject 是一个基于 Frida 的工具,用于动态注入代码到运行中的进程中,以便进行应用程序的分析、逆向工程和调试。Frida 本身是一个强大的动态分析工具,它允许研究人员在运行时修改、监视和分析应用程序,无需访问应用程序的源代码。

Frida-Inject 就是类似于Frida-Server

不一样的是Frida-Server需要用usb链接电脑,通过电脑的frida-tools等frida命令来操作

而Frida-Inject直接可以在android内部执行frida命令

首先查看手机架构

1
adb shell getprop ro.product.cpu.abi

然后去官网下载frida-inject,版本不必与pc同步,与pc没有直接关联

下载完成后解压到/data/local/tmp下

赋予权限

1
chmod 777 frida-inject

直接可以使用了

1
nohup /data/local/tmp/frida-inject-16.1.4-android-arm64 -f  com.xxx.xxx  -s  /data/local/jsHookScripts/Hook.js &

这种做法是没有pc的时候的一种方案有,pc的话建议还是用frida-server

rpc

如果是想做rpc的话,但是手头又没有pc,那么frida-Inject是个非常好的选择

推荐使用Sekiro

1
2
3
4
5
6
7
8
9
10
11
var SekiroClient = function () { function e(e) { this.handlers = {}, this.isConnecting = !1, this.sekiroOption = e, e.serverHost = e.serverHost || "sekiro.iinti.cn", e.serverPort = e.serverPort || 5612, this.fridaSocketConfig = { family: "ipv4", host: e.serverHost, port: e.serverPort }, console.log("           welcome to use sekiro framework,\n      for more support please visit our website: https://iinti.cn\n"), this.doConnect() } return e.prototype.registerAction = function (e, t) { this.handlers[e] = t }, e.prototype.reConnect = function () { var e = this; console.log("sekiro try connection after 5s"), setTimeout(function () { return e.doConnect() }, 5e3) }, e.prototype.doConnect = function () { var e = this; this.isConnecting || (this.isConnecting = !0, console.log("sekiro connect to server-> " + this.fridaSocketConfig.host + ":" + this.fridaSocketConfig.port), Socket.connect(this.fridaSocketConfig).then(function (t) { e.isConnecting = !1, t.setNoDelay(!0), e.conn = t, e.connRead(), e.connWrite({ type: 16, serialNumber: -1, headers: { SEKIRO_GROUP: e.sekiroOption.sekiroGroup, SEKIRO_CLIENT_ID: e.sekiroOption.clientId } }) })["catch"](function (t) { e.isConnecting = !1, console.log("sekiro connect failed", t), e.reConnect() })) }, e.prototype.connWrite = function (e) { var t = this; this.conn.output.write(this.encodeSekiroPacket(e))["catch"](function (e) { console.log("sekiro write register cmd failed", e), t.reConnect() }) }, e.prototype.connRead = function () { var e = this; this.conn.input.read(1024).then(function (t) { return t.byteLength <= 0 ? (e.conn.close(), console.log("sekiro server lost!"), void e.reConnect()) : (e.onServerData(t), void setImmediate(function () { e.connRead() })) })["catch"](function (t) { console.log("sekiro read_loop error", t), e.reConnect() }) }, e.prototype.onServerData = function (e) { var t = this; if (this.readBuffer) { if (e) { var n = new ArrayBuffer(this.readBuffer.byteLength + e.byteLength), o = new Uint8Array(n); o.set(new Uint8Array(this.readBuffer), 0), o.set(new Uint8Array(e), this.readBuffer.byteLength), this.readBuffer = n } } else { if (!e) return; this.readBuffer = e } var r = this.decodeSekiroPacket(); r && (this.handleServerPkg(r), setImmediate(function () { return t.onServerData() })) }, e.prototype.encodeSekiroFastJSON = function (e) { var t = void 0; e.msg && (t = this.str2Uint8(e.msg)); var n = void 0; e.data && (n = this.str2Uint8(JSON.stringify(e.data))); var o = 8 + (t ? t.length : 0) + 4 + (n ? n.length : 0), r = new ArrayBuffer(o), i = new DataView(r); i.setInt32(0, e.status), i.setInt32(4, t ? t.length : 0); var s = 8; return t && (new Uint8Array(r, 8).set(t), s += t.length), i.setInt32(s, n ? n.length : 0), s += 4, n && new Uint8Array(r, s).set(n), r }, e.prototype.handleServerPkg = function (e) { if (0 == e.type) return void this.connWrite(e); if (32 != e.type) return void console.log("unknown server message:" + JSON.stringify(e)); var t = this, n = function (n) { t.connWrite({ type: 17, serialNumber: e.serialNumber, headers: { PAYLOAD_CONTENT_TYPE: "CONTENT_TYPE_SEKIRO_FAST_JSON" }, data: t.encodeSekiroFastJSON(n) }) }, o = { resolve: function (e) { n({ status: 0, data: e }) }, reject: function (e) { n({ status: -1, msg: e }) } }; if (!e.data) return void o.reject("sekiro system error, no request payload present!!"); var r = this.uint8toStr(new Uint8Array(e.data)); console.log("sekiro receive request: " + r); var i = JSON.parse(r); if (!i.action) return void o.reject("the param: {action} not presented!!"); var s = this.handlers[i.action]; if (!s) return void o.reject("sekiro no handler for this action"); try { s(i, o.resolve, o.reject) } catch (c) { o.reject("sekiro handler error:" + c + JSON.stringify(c)) } }, e.prototype.decodeSekiroPacket = function () { if (!this.readBuffer) return void 0; var e = new DataView(this.readBuffer), t = e.getInt32(0), n = e.getInt32(4); if (1936026473 != t || 1919889457 != n) return console.log("sekiro packet data"), this.conn.close().then(function () { console.log("sekiro close broken pipe") }), void (this.readBuffer = void 0); var o = e.getInt32(8); if (!(this.readBuffer.byteLength < o + 12)) { for (var r = e.getInt8(12), i = e.getInt32(13), s = e.getInt8(17), c = 18, a = {}, f = 0; s > f; f++) { var u = e.getInt8(c++), h = this.uint8toStr(new Uint8Array(this.readBuffer.slice(c, u))); c += u; var l = e.getInt8(c++), d = ""; l > 0 && (d = this.uint8toStr(new Uint8Array(this.readBuffer.slice(c, l))), c += l), a[h] = d } var p = void 0, y = o + 12 - c; return y > 0 && (p = this.readBuffer.slice(c, c + y)), this.readBuffer.byteLength == o + 12 ? this.readBuffer = void 0 : this.readBuffer = this.readBuffer.slice(c), { type: r, serialNumber: i, headers: a, data: p } } }, e.prototype.encodeSekiroPacket = function (e) { var t = 6, n = []; for (var o in e.headers) n.push(this.str2Uint8(o)), n.push(this.str2Uint8(e.headers[o])), t += 2; t += n.reduce(function (e, t) { return e + t.length }, 0), e.data && (t += e.data.byteLength); var r = new ArrayBuffer(t + 12), i = new DataView(r); i.setUint32(0, 1936026473), i.setUint32(4, 1919889457), i.setInt32(8, t), i.setInt8(12, e.type), i.setInt32(13, e.serialNumber), i.setInt8(17, Object.keys(e.headers).length); var s = 18; return n.forEach(function (e) { i.setInt8(s++, e.length), new Uint8Array(r, s).set(e), s += e.length }), e.data && new Uint8Array(r, s).set(new Uint8Array(e.data)), r }, e.prototype.uint8toStr = function (e) { for (var t, n, o = 0, r = Math.min(65536, e.length + 1), i = new Uint16Array(r), s = [], c = 0, a = function () { var a = o < e.length; if (!a || c >= r - 1) { var f = i.subarray(0, c), u = []; if (f.forEach(function (e) { return u.push(e) }), s.push(String.fromCharCode.apply(null, u)), !a) return { value: s.join("") }; e = e.subarray(o), o = 0, c = 0 } var h = e[o++]; if (0 === (128 & h)) i[c++] = h; else if (192 === (224 & h)) n = 63 & e[o++], i[c++] = (31 & h) << 6 | n; else if (224 === (240 & h)) n = 63 & e[o++], t = 63 & e[o++], i[c++] = (31 & h) << 12 | n << 6 | t; else if (240 === (248 & h)) { n = 63 & e[o++], t = 63 & e[o++]; var l = 63 & e[o++], d = (7 & h) << 18 | n << 12 | t << 6 | l; d > 65535 && (d -= 65536, i[c++] = d >>> 10 & 1023 | 55296, d = 56320 | 1023 & d), i[c++] = d } }; ;) { var f = a(); if ("object" == typeof f) return f.value } }, e.prototype.str2Uint8 = function (e) { for (var t = 0, n = e.length, o = 0, r = Math.max(32, n + (n >>> 1) + 7), i = new Uint8Array(r >>> 3 << 3); n > t;) { var s = e.charCodeAt(t++); if (s >= 55296 && 56319 >= s) { if (n > t) { var c = e.charCodeAt(t); 56320 === (64512 & c) && (++t, s = ((1023 & s) << 10) + (1023 & c) + 65536) } if (s >= 55296 && 56319 >= s) continue } if (o + 4 > i.length) { r += 8, r *= 1 + t / e.length * 2, r = r >>> 3 << 3; var a = new Uint8Array(r); a.set(i), i = a } if (0 !== (4294967168 & s)) { if (0 === (4294965248 & s)) i[o++] = s >>> 6 & 31 | 192; else if (0 === (4294901760 & s)) i[o++] = s >>> 12 & 15 | 224, i[o++] = s >>> 6 & 63 | 128; else { if (0 !== (4292870144 & s)) continue; i[o++] = s >>> 18 & 7 | 240, i[o++] = s >>> 12 & 63 | 128, i[o++] = s >>> 6 & 63 | 128 } i[o++] = 63 & s | 128 } else i[o++] = s } return i.slice ? i.slice(0, o) : i.subarray(0, o) }, e }();
function encrypt(str) {
/*
这里写导出操作
*/
}

var client = new SekiroClient({ sekiroGroup: "xxx", clientId: "xxx",serverHost:"xxx.xxx.xxx",serverPort:5621});
client.registerAction("testAction", function (request, resolve, reject) {
resolve(encrypt(request['Str']));
});

建立好连接后直接就能远程调用了

frida-Gadget

如果手机没有root权限,那么使用friada-Gadget是最好的选择

对于有so的app

简单说就是把frida-gadget.so 强行附加到app的so文件加载流程中,配合frida-gadget.so.config(配置frida-gadget,注入脚本路径等) 实现免root 注入

首先拉取https://github.com/nszdhd1/UtilScript/tree/main代码

该库的核心思想是将apk解包,感染so文件,再重打包签名

流程:

  1. 对apk解包

  2. 找到要注入的so文件名 比如是libfuai.so

  3. 执行命令python3 LIEFInjectFrida.py ./qiyuan.apk ./ libfuai.so -apksign -persistence

    第一个参数是要感染的apk文件路径,第二个是要导出的apk文件路径,第三个是要感染的so文件,第四个是签名,第五个不知道干嘛的最好加上

    假如是mac笔记本还需要对代码进行一些修改,例如路径的双斜杠等问题

    另外apk签名的apksignerNew.jar也比较老,会出现很多问题,这里建议直接使用brew安装后的apksigner命令

    修改为

    1
    2
    cmd = 'apksigner sign --ks %s --ks-key-alias %s --ks-pass pass:%s --key-pass pass:%s --out %s %s'% \
    (keystore, alias,pswd,aliaspswd,outfile,apk_path)
  4. 导出apk文件,安装到手机上,然后将js文件放入到事先配置好的路径下

    例如现在的配置是

    1
    2
    3
    4
    5
    6
    7
    {
    "interaction": {
    "type": "script",
    "path": "/data/local/tmp/frida_script.js",
    "on_change":"reload"
    }
    }

    那么只需要将hook代码写入到/data/local/tmp/frida_script.js,打开重签名的apk即可。

该方式的弊端就是无法避免apkd的签名校验,看网上大佬说的是最好还是用IO重定向的方式,可以过掉签名检测