天天看點

Android Native禁止使用系統私有庫詳解

系統私有庫指的是,存放在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的手段。

不過,網上仍然有很多繞過這個限制的方式,大家有興趣的可以自己發掘一下。

也可以來這裡與我們共同讨論技術

Android Native禁止使用系統私有庫詳解

繼續閱讀