剛剛開始學Android逆向,發現Frida是個好東西,于是趕緊下載研究一番。
下載源碼編譯,切換到最新版16.0.11, 編譯之前注意先更新nodejs。
wget -qO- https://deb.nodesource.com/setup_16.x | sudo -E bash - sudo apt-get install -y nodejs
然后執行:
make core-android-arm64 make tools-linux-x86_64 PYTHON=$HOME/miniconda3/bin/python
就可以成功生成frida-server以及frida相關tools,于是push到手機上執行。
redfin:/data/local/tmp # ./frida-server
{"type":"error","description":"Error: Java API not available","stack":"Error: Java API not available at _checkAvailable (frida/node_modules/frida-java-bridge/index.js:298:1) at _.perform (frida/node_modules/frida-java-bridge/index.js:203:1) at /internal-agent.js:490:6","fileName":"frida/node_modules/frida-java-bridge/index.js","lineNumber":298,"columnNumber":1}
報錯了,繼續往下試試。
把手機上的名為com.example.myapplication的demo app放到前臺,然后注入一個helloword級別的js。
setTimeout(
function() {
Java.perform(function() {
console.log("Hello frida!")
})
}
)
//test.js
然后執行:
(base) /data/code/OpenSource/crack/frida/build/frida-linux-x86_64/bin (16.0.11 ?) ./frida -U -l ../../../../frida_script/test.js com.example.myapplication
____
/ _ | Frida 16.0.11 - A world-class dynamic instrumentation toolkit
| (_| |
> _ | Commands:
/_/ |_| help -> Displays the help system
. . . . object? -> Display information about 'object'
. . . . exit/quit -> Exit
. . . .
. . . . More info at https://frida.re/docs/home/
. . . .
. . . . Connected to AOSP on redfin (id=0A051FDD4003BW)
Failed to spawn: cannot read properties of undefined (reading 'getRunningAppProcesses')
咳,出師不利,不過反正我也是做c++開發的,雖然不懂javascript, 連蒙帶猜也能看個差不多,那就研究研究報錯原因吧。
從現有信息來看,第一懷疑對象應該是那個報錯"Error: Java API not available", 也指明了報錯位置at _checkAvailable (frida/nodemodules/frida-java-bridge/index.js:298:1) at.perform (frida/node_modules/frida-java-bridge/index.js:203:1) ,那就在源碼里搜索下。
發現這個index.js位于build/tmp-android-arm64/frida-gum/bindings/gumjs/node_modules/frida-java-bridge目錄。然后看下perform實現:
perform (fn) {
this._checkAvailable();
if (!this._isAppProcess() || this.classFactory.loader !== null) {
try {
this.vm.perform(fn);
} catch (e) {
Script.nextTick(() => { throw e; });
}
} else {
this._pendingVmOps.push(fn);
if (this._pendingVmOps.length === 1) {
this._performPendingVmOpsWhenReady();
}
}
}
這個函數第一行就是_checkAvailable,跟進去看看。
_checkAvailable () {
if (!this.available) {
throw new Error('Java API not available');
}
}
果然報錯就是在這里,那么關鍵的判斷就是available了,再跳過去看看。
get available () {
return this._tryInitialize();
}
_tryInitialize () {
if (this._initialized) {
return true;
}
if (this._apiError !== null) {
throw this._apiError;
}
let api;
try {
api = getApi();
this.api = api;
} catch (e) {
this._apiError = e;
throw e;
}
if (api === null) {
return false; //只有這里返回為false
}
const vm = new VM(api);
this.vm = vm;
Types.initialize(vm);
ClassFactory._initialize(vm, api);
this.classFactory = new ClassFactory();
this._initialized = true;
return true;
}
那么只有一種可能,就是getApi()返回為null,再跟進去看看,這個getApi的有幾層跳轉如下:
// index.js
const getApi = require('./lib/api');
// ./lib/api.js
let { getApi, getAndroidVersion } = require('./android');
try {
getAndroidVersion();
} catch (e) {
getApi = require('./jvm').getApi;
}
module.exports = getApi;
// ./lib/android.js
function getApi () {
if (cachedApi === null) {
cachedApi = _getApi();
}
return cachedApi;
}
function _getApi () {
const vmModules = Process.enumerateModules()
.filter(m => /^lib(art|dvm).so$/.test(m.name))
.filter(m => !/\/system\/fake-libs/.test(m.path));
if (vmModules.length === 0) {
return null; // 這里返回了null
}
//以下代碼省略...
}
看代碼的意思是進程內沒找到加載的libart.so或者libdvm.so,真奇怪了,你可是android進程,沒有虛擬機咋跑的?
那就看下這個進程加載了什么,首先找到進程id。
redfin:/ # ps -ef | grep com.example.myapplication u0_a108 27168 21776 0 11:18:29 ? 00:00:03 com.example.myapplication root 28255 28250 35 14:11:06 pts/1 00:00:00 grep com.example.myapplication
然后檢查下maps。
redfin:/ # cat /proc/27168/maps | grep libart.so 1|redfin:/ #
果然沒有,我把maps的輸出保存下來,在編輯器里查看才發現了端倪,原來進程加載的so里有一個libartd.so,因為這個手機是pixel5,系統是我自己下載AOSP編譯的,選擇的是eng版,這樣編出來的系統so都是debug版,也就意味著后綴都有一個d,難怪frida找不到。
知道了原因,那解決方案就簡單了,把那段查找libart.so的代碼改成查找libartd.so即可。不過還有一個問題,這個frida-java-bridge是編譯的時候從網上下載的,我們本地修改會被覆蓋,那么就得研究下frida的編譯系統了,讓它使用我們本地的frida-java-bridge。
首先在源碼里搜索frida-java-bridge,果不其然,是在一個generate-runtime.py里面,代碼如下:
XACT_DEPS = {
"frida-java-bridge": "6.2.3",
"frida-objc-bridge": "7.0.2",
"frida-swift-bridge": "2.0.6"
}
def generate_runtime(backends, arch, endian, input_dir, gum_dir, capstone_incdir, libtcc_incdir, quickcompile, output_dir):
frida_compile = output_dir / "node_modules" / ".bin" / make_script_filename("frida-compile")
if not frida_compile.exists():
pkg_files = [output_dir / "package.json", output_dir / "package-lock.json"]
for f in pkg_files:
if f.exists():
f.unlink()
(output_dir / "tsconfig.json").write_text("{ \"files\": [], \"compilerOptions\": { \"typeRoots\": [] } }", encoding="utf-8")
node_modules = output_dir / "node_modules"
if node_modules.exists():
shutil.rmtree(node_modules)
npm = os.environ.get("NPM", make_script_filename("npm"))
try:
subprocess.run([npm, "init", "-y"],
capture_output=True,
cwd=output_dir,
check=True)
subprocess.run([npm, "install"] + [f"{name}@{version_spec}" for name, version_spec in RELAXED_DEPS.items()],
capture_output=True,
cwd=output_dir,
check=True)
subprocess.run([npm, "install", "-E"] + [f"{name}@{version_spec}" for name, version_spec in EXACT_DEPS.items()],
capture_output=True,
cwd=output_dir,
check=True) <=========這里下載了EXACT_DEPS里面的依賴項
看來是編譯的時候,用npm install -E把frida-java-bridge下載下來了,那么接下來就是要把這個依賴項改成我們本地的。
首先下載一個到本地:
git clone https://github.com/frida/frida-java-bridge.git
然后全局查找libart.so,改成libartd.so。

這時候需要用到npm link把我們本地的frida-java-bridge注冊到系統中。
(base) /data/code/OpenSource/crack/frida/frida-java-bridge (main ?) npm link up to date, audited 3 packages in 574ms found 0 vulnerabilities (base) /data/code/OpenSource/crack/frida/frida-java-bridge (main ?) npm install added 214 packages, and audited 215 packages in 1s 62 packages are looking for funding run `npm fund` for details found 0 vulnerabilities
然后要讓frida中的編譯腳本指向我們這個,也就是不要用npm install了,需要改成npm link, 修改generate-runtime.py代碼如下:

然后再重新編譯, 生成后推到手機執行。

果然沒有報錯了,非常完美!
安全牛
一顆小胡椒
中國信通院CAICT
Coremail郵件安全
關鍵基礎設施安全應急響應中心
骨哥說事
信息安全與通信保密雜志社
CNCERT國家工程研究中心
CNCERT國家工程研究中心
黑客技術與網絡安全
看雪學苑
一顆小胡椒