系統私有庫指的是,存放在android系統/system/lib/和/vendor/lib下面,但是Android NDK中沒有公開API的lib庫。
從Android N開始(SDK >= 24),通過
dlopen
打開系統私有庫,或者lib庫中依賴系統私有庫,都會産生異常,甚至可能導緻app崩潰。具體可以閱讀 官方文檔 說明。
這個變更會有怎樣的影響呢?
曾經的美好
在以前,在ndk層面,我們是可以使用一些hack的手段得到系統的私有api的。
比如,你想使用虛拟機中的一些内部符号,在N以下版本,你可以這麼搞
void *handle = dlopen("libart.so", RTLD_NOW);
void *originFunc = dlsym(handle, "_ZNK3art6Thread13DumpJavaStackERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE");
這樣你就能得到
art::Thread::DumpJavaStack
的函數指針,然後愉快地調用它了。
晴天霹靂
但是到了N以後,
void *handle = dlopen("libart.so", RTLD_NOW);
// 沒問題,傳回了handle指針。
void *originFunc = dlsym(handle, "_ZNK3art6Thread13DumpJavaStackERNSt3__113basic_ostreamIcNS1_11char_traitsIcEEEE");
// 失敗!得到的originFunc為空!
這就很奇怪了,我們能夠得到
handle
指針,就說明
libart.so
是找到了。但是為什麼
libart.so
中卻沒有找到
art::Thread::DumpJavaStack
的符号呢?
看一下記憶體映射表,我們發現了一個有趣的東西
7de5d4d000-7de5d4e000 r-xp 00000000 fe:00 774 /system/fake-libs64/libart.so
7de5d4e000-7de5d4f000 r--p 00000000 fe:00 774 /system/fake-libs64/libart.so
7de5d4f000-7de5d50000 rw-p 00001000 fe:00 774 /system/fake-libs64/libart.so
... ...
7de6a04000-7de6feb000 r-xp 00000000 fe:00 1414 /system/lib64/libart.so
7de6feb000-7de6ffa000 r--p 005e6000 fe:00 1414 /system/lib64/libart.so
7de6ffa000-7de6ffd000 rw-p 005f5000 fe:00 1414 /system/lib64/libart.so
難怪,我們知道
dlopen
參數為
libart.so
的話,系統會先找到
/system/fake-libs64/libart.so
,而不是
/system/lib64/libart.so
。
而
/system/fake-libs64/libart.so
又是什麼鬼?從名字上看,就知道他是個假的libart。
在系統源碼檔案
art/libart_fake/README.md
中,我們找到了對他的解釋,
A fake libart made to satisfy some misbehaving apps that will attempt to link
against libart.so.
這就是為了以防你們這些圖謀不軌(misbehaving)的APP們做一些奇怪的事而專門設的套啊!
隻要你自己的lib庫依賴了
libart.so
或者試圖打開
libart.so
,在linker查找
libart.so
時,因為fake-libs路徑被設定在了查找路徑表的靠前處,就會先找到
/system/fake-libs64/libart.so
,而不是真正的
/system/lib64/libart.so
設定fake-libs代碼:
@ frameworks/base/core/java/android/app/LoadedApk.java
public static void makePaths(...) {
...
// Add fake libs into the library search path if we target prior to N.
if (aInfo.targetSdkVersion <= 23) {
outLibPaths.add("/system/fake-libs" +
(VMRuntime.is64BitAbi(aInfo.primaryCpuAbi) ? "64" : ""));
}
...
}
而這個
/system/fake-libs64/libart.so
的内容基本上的空的(
art/libart_fake/fake.cc
),是以在它裡面當然什麼符号都找不到啦~
霸王硬上弓
既然如此,那我們在
dlopen
中直接指定lib的絕對路徑總行了吧?像這樣:
void *handle = dlopen("/system/lib64/libart.so", RTLD_NOW);
可是很遺憾,它報了一個錯:
01-11 13:16:10.413 19869-19869/com.patch.demo E/linker: library "/system/lib64/libart.so" ("/system/lib64/libart.so")
needed or dlopened by "/data/app/com.patch.demo-1/lib/arm64/libbcpatch.so"
is not accessible for the namespace:
[name="classloader-namespace", ld_library_paths="",
default_library_paths="/data/app/com.patch.demo-1/lib/arm64:/system/fake-libs64:/data/app/com.patch.demo-1/base.apk!/lib/arm64-v8a",
permitted_paths="/data:/mnt/expand:/data/data/com.patch.demo"]
也就是說,你被允許通路的路徑(包含ld_library_paths、default_library_paths、permitted_paths)隻有
/data/app/com.patch.demo-1/lib/arm64
/system/fake-libs64
/data/app/com.patch.demo-1/base.apk!/lib/arm64-v8a
/data
/mnt/expand
/data/data/com.patch.demo
是以,試圖通路
/system/lib64/
下的
libart.so
當然是不行的啦。
真是魔高一尺道高一丈啊。
至此,我們算是知道了Google封殺在ndk中通路系統私有庫的方法。本質是在linker中加入一系列校驗機制來做限制。linker作為最基礎的lib庫連結器,所有連結行為都會被限制住。
緩兵之計
不過,在Android N,你可以指定APP的sdk為API級别23或更低。那麼,對于以下灰名單中的lib,仍然可以正常使用:
// TODO(dimitry): The grey-list is a workaround for http://b/26394120 ---
// gradually remove libraries from this list until it is gone.
static bool is_greylisted(const char* name, const soinfo* needed_by) {
static const char* const kLibraryGreyList[] = {
"libandroid_runtime.so",
"libbinder.so",
"libcrypto.so",
"libcutils.so",
"libexpat.so",
"libgui.so",
"libmedia.so",
"libnativehelper.so",
"libskia.so",
"libssl.so",
"libstagefright.so",
"libsqlite.so",
"libui.so",
"libutils.so",
"libvorbisidec.so",
nullptr
};
這樣的話,每次使用dlopen或者連結以上lib都會列印出一個警告,然後仍然正常執行原有功能。
同時Google也聲明了,在将來的版本會将這些lib的支援也一并移除。是以,這隻是提供了一個讓你盡快在代碼中去除相關依賴的過渡期。
可見,不久的将來就無法愉快地使用系統的非公開符号了。
突出重圍
那我們真的就沒辦法了嗎?
也不是絕對的,Android限制的隻是
dlopen
這個途徑,而我們通路記憶體是随心所欲的:)
方法就是,通過記憶體映射表找到
libart.so
的真實起始位置:
7de6a04000-7de6feb000 r-xp 00000000 fe:00 1414 /system/lib64/libart.so
7de6feb000-7de6ffa000 r--p 005e6000 fe:00 1414 /system/lib64/libart.so
7de6ffa000-7de6ffd000 rw-p 005f5000 fe:00 1414 /system/lib64/libart.so
然後在加載位址起始位置手動解析
libart.so
的elf格式,提取出所需符号的位置資訊。相當于你自己實作linker原本的查找邏輯。
當然,這種周遊記憶體解析elf的實作是比較複雜的。是以,這一次Google算是封死了一大波底層hack的手段。
不過,網上仍然有很多繞過這個限制的方式,大家有興趣的可以自己發掘一下。
也可以來這裡與我們共同讨論技術
