1. 程式人生 > >android build類分析 hook靜態欄位

android build類分析 hook靜態欄位

一、緣由:
使用xposed hook build類下 DEVICE MODEL VERSION MANUFACTURER等靜態欄位

使用XposedHelpers.setStaticObjectField() hook 失敗!

XposedHelpers.setStaticObjectField(android.os.Build.class, "MODEL", null);

使用 反射 hook 失敗!

 Field model = Build.class.getDeclaredField("MODEL");
 model.setAccessible(true);
 model.set
(Build.class, pre.getString("model", null));

二、Build原始碼分析
開啟build原始碼(/frameworks/base/core/Java/android/os/Build.java)
Build類中主要是一些成員屬性

public class Build {
    /** Value used for when a build property is unknown. */
    public static final String UNKNOWN = "unknown";

    /** Either a changelist number, or a label like "M4-rc20". */
public static final String ID = getString("ro.build.id"); /** A build ID string meant for displaying to the user */ public static final String DISPLAY = getString("ro.build.display.id"); /** The name of the overall product. */ public static final String PRODUCT = getString("ro.product.name"
); /** The name of the industrial design. */ public static final String DEVICE = getString("ro.product.device"); /** The name of the underlying board, like "goldfish". */ public static final String BOARD = getString("ro.product.board"); /** The name of the instruction set (CPU type + ABI convention) of native code. */ public static final String CPU_ABI = getString("ro.product.cpu.abi"); /** The name of the second instruction set (CPU type + ABI convention) of native code. */ public static final String CPU_ABI2 = getString("ro.product.cpu.abi2"); /** The manufacturer of the product/hardware. */ public static final String MANUFACTURER = getString("ro.product.manufacturer"); /** The brand (e.g., carrier) the software is customized for, if any. */ public static final String BRAND = getString("ro.product.brand"); /** The end-user-visible name for the end product. */ public static final String MODEL = getString("ro.product.model"); /** The system bootloader version number. */ public static final String BOOTLOADER = getString("ro.bootloader"); /** * The radio firmware version number. * * @deprecated The radio firmware version is frequently not * available when this class is initialized, leading to a blank or * "unknown" value for this string. Use * {@link #getRadioVersion} instead. */ @Deprecated public static final String RADIO = getString(TelephonyProperties.PROPERTY_BASEBAND_VERSION); /** The name of the hardware (from the kernel command line or /proc). */ public static final String HARDWARE = getString("ro.hardware"); /** A hardware serial number, if available. Alphanumeric only, case-insensitive. */ public static final String SERIAL = getString("ro.serialno");

三、Build類獲取系統資訊流程

Build類中除Build. UNKNOWN(這個常量是直接返回”unknown”)之外的每個常量都是通過

private static String getString(String property);

這個內部靜態方法來獲取的。在getString()方法中呼叫了Systemproperties類的
public static String get(String key);方法來獲取這些值。

Systemproperties類是android.os中標記為@hide的一個類,無法直接訪問(但可以通過反射方式訪問)。在Systemproperties類呼叫了 private static native String native_get(String key);
這個native方法。此native方法的程式碼 在Android原始碼的
“frameworks/base/core/jni/android_os_SystemProperties.cpp”檔案中。Systemproperties類程式碼程式碼如下。

public class SystemProperties
{
    public static final int PROP_NAME_MAX = 31;
    public static final int PROP_VALUE_MAX = 91;

    private static native String native_get(String key);
    private static native String native_get(String key, String def);
    private static native int native_get_int(String key, int def);
    private static native long native_get_long(String key, long def);
    private static native boolean native_get_boolean(String key, boolean def);
    private static native void native_set(String key, String def);

    public static String get(String key) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get(key);
    }
    public static String get(String key, String def) {
        if (key.length() > PROP_NAME_MAX) {
            throw new IllegalArgumentException("key.length > " + PROP_NAME_MAX);
        }
        return native_get(key, def);
    }

android_os_SystemProperties.cpp程式碼如下。

#define LOG_TAG "SysPropJNI"

#include "cutils/properties.h"
#include "utils/misc.h"
#include <utils/Log.h>
#include "jni.h"
#include "android_runtime/AndroidRuntime.h"
#include <nativehelper/JNIHelp.h>

namespace android
{

static jstring SystemProperties_getSS(JNIEnv *env, jobject clazz,
                                      jstring keyJ, jstring defJ)
{
    int len;
    const char* key;
    char buf[PROPERTY_VALUE_MAX];
    jstring rvJ = NULL;

    if (keyJ == NULL) {
        jniThrowNullPointerException(env, "key must not be null.");
        goto error;
    }

    key = env->GetStringUTFChars(keyJ, NULL);

    len = property_get(key, buf, "");
    if ((len <= 0) && (defJ != NULL)) {
        rvJ = defJ;
    } else if (len >= 0) {
        rvJ = env->NewStringUTF(buf);
    } else {
        rvJ = env->NewStringUTF("");
    }

    env->ReleaseStringUTFChars(keyJ, key);

error:
    return rvJ;
}

static jstring SystemProperties_getS(JNIEnv *env, jobject clazz,
                                      jstring keyJ)
{
    return SystemProperties_getSS(env, clazz, keyJ, NULL);
}

可以看到SystemProperties_getSS中呼叫了property_get(),property_get()是libcutils提供的一個api,property_get()又呼叫了libc中的 __system_property_get()函式來訪問系統共享記憶體中的資料。

四、Build類獲取到的系統資訊來源
在Android系統啟動時,“init”守護程序(原始碼位於:device/system/init)將啟動一個屬性服務並分配一段共享記憶體來儲存各項屬性。屬性服務啟動後呼叫libc中的__system_property_init()函式。__system_property_init()函式將依次讀取。/default.prop; /system/build.prop; /system/default.prop; /data/local.prop四個檔案,將四個檔案中配置的各項屬性讀入到共享記憶體中。Build類中的常量大都來源於幾個配置檔案。/system/build.prop是其中最重要的一個配置檔案,大多數Build中的常量都是從這個配置檔案中獲取到的。
/system/build.prop檔案是Android系統在編譯時由MakeFile生成,MakeFile中會呼叫Makefile中呼叫build/tools/buildinfo.sh指令碼,將相關配置寫入到build.prop檔案中。
<圖片引用:http://blog.csdn.net/ccpat/article/details/44776313>
這裡寫圖片描述

在系統初始化時,Android將分配一個共享記憶體區來儲存的屬性。這些是由“init”守護程序完成的,其原始碼位於:device/system/init。“init”守護程序將啟動一個屬性服務。

屬性服務在“init”守護程序中執行。每一個客戶端想要設定屬性時,必須連線屬性服務,再向其傳送資訊。屬性服務將會在共享記憶體區中修改和建立屬性。任何客戶端想獲得屬性資訊,可以從共享記憶體直接讀取。這提高了讀取效能。 客戶端應用程式可以呼叫libcutils中的API函式以GET/SET屬性資訊。libcutils的原始碼位於:device/libs/cutils。API函式是:

int property_get(const char *key, char *value, const char *default_value);
int property_set(const char *key, const char *value);

而libcutils又呼叫libc中的 __system_property_xxx 函式獲得共享記憶體中的屬性。libc的原始碼位於:device/system/bionic。

屬性服務呼叫libc中的__system_property_init函式來初始化屬性系統的共享記憶體。當啟動屬性服務時,將從以下檔案中載入預設屬性:

/default.prop
/system/build.prop
/system/default.prop
/data/local.prop

屬性將會以上述順序載入。後加載的屬性將覆蓋原先的值。這些屬性載入之後,最後載入的屬性會被保持在/data/property中。

特別屬性 如果屬性名稱以“ro.”開頭,那麼這個屬性被視為只讀屬性。一旦設定,屬性值不能改變。

如果屬性名稱以“persist.”開頭,當設定這個屬性時,其值也將寫入/data/property。

如果屬性名稱以“net.”開頭,當設定這個屬性時,“net.change”屬性將會自動設定,以加入到最後修改的屬性名。(這是很巧妙的。 netresolve模組的使用這個屬性來追蹤在net.*屬性上的任何變化。)

屬性“ ctrl.start ”和“ ctrl.stop ”是用來啟動和停止服務。

每一項服務必須在/init.rc中定義.系統啟動時,與init守護程序將解析init.rc和啟動屬性服務。一旦收到設定“ ctrl.start ”屬性的請求,屬性服務將使用該屬性值作為服務名找到該服務,啟動該服務。這項服務的啟動結果將會放入“ init.svc.<服務名>“屬性中 。客戶端應用程式可以輪詢那個屬性值,以確定結果

五、屬性系統工作原理
Android屬性系統由有三個程序,一組屬性檔案和一塊共享記憶體組成。這塊共享記憶體儲存著系統中所有的屬性記錄,只有Property service能寫這塊共享記憶體,並且Property service負責將屬性檔案中的屬性記錄載入到共享記憶體中。

屬性讀取程序(property consumer)把這塊共享記憶體對映到自己的程序空間,然後直接讀取它。屬性設定程序(property setter)也載入這塊共享到他的程序空間,但是他不能直接寫這塊共享記憶體。當他需要增加或者修改屬性的時候,通過Unix Socket發生屬性給Property service,Property service將代表設定程序寫入共享記憶體和屬性檔案。

Property service運行於init程序中。init程序首先建立一塊共享記憶體,並把他的控制代碼fd存放在這塊記憶體中,init程序通過mmap帶MAP_SHARE標誌的系統呼叫,把這塊記憶體對映到他的虛擬空間中,最終這塊記憶體所有的更新將會被所有對映這塊共享記憶體的程序看到。共享記憶體控制代碼fd和共享記憶體大小儲存在系統環境變數“ANDROID_PROPERTY_WORKSPACE”中,所有的程序包括屬性設定程序和屬性讀取程序都將通過這個系統環境變數獲得共享記憶體的控制代碼fd和大小,然後把這塊記憶體對映到他們自己的虛擬空間。

然後,init程序將會從以下檔案中載入屬性:

1: /default.prop
2: /system/build.prop
3: /system/default.prop
4: /data/local.prop

下一步是啟動Property service。這步中,將會建立一個Unix Socket伺服器,這個Socket有一個聞名的名稱“/dev/socket/property_service”。最後init進入死迴圈,等待socket的連線請求。

在讀取程序中,當它初始化libc庫的時候,將會獲得屬性系統共享記憶體的控制代碼和大小(bionic/libc/bionic/libc_init_common.c __libc_init_common函式)。並把這塊共享記憶體對映到自己的程序虛擬空間中(bionic/libc/bionic/system_properties.c __system_properties_init函式)。這樣讀取程序將會向訪問普通記憶體一樣訪問屬性系統的共享記憶體了。

當前,屬性不能被刪除。也就是說一旦屬性被建立,將不可以被刪除,但是它們可以被修改。

六、測試xposed框架 hook
<三、Build類獲取系統資訊流程> 中得知 在java層呼叫了
Systemproperties類的public static String get()方法來獲取build類中靜態欄位的值.
hook “android.os.Systemproperties” 類的 get()方法

新建回撥函式類 SystemPropertiesHook

public class SystemPropertiesHook extends XC_MethodHook
{
    public SystemPropertiesHook()
    {
        super();
    }

    protected void beforeHookedMethod(XC_MethodHook.MethodHookParam param) throws Throwable
    {
        XSharedPreferences pre = new XSharedPreferences(this.getClass().getPackage().getName(), "deviceInfo");
        String methodName = param.method.getName();
        if (methodName.startsWith("get"))
        {
            Log.v("getDeviceInfo", "hook systemProperties ------>");
            XposedHelpers.setStaticObjectField(android.os.Build.class, "MODEL", pre.getString("model", null));
            XposedHelpers.setStaticObjectField(android.os.Build.class, "MANUFACTURER", pre.getString("manufacturer", null));
            XposedHelpers.setStaticObjectField(android.os.Build.class, "BRAND", pre.getString("brand", null));
            XposedHelpers.setStaticObjectField(android.os.Build.class, "HARDWARE", pre.getString("hardware", null));
            XposedHelpers.setStaticObjectField(android.os.Build.class, "RADIO", pre.getString("radio", null));
            XposedHelpers.setStaticObjectField(android.os.Build.class, "DEVICE", pre.getString("device", null));
        }
    }
}
public class XposedModule implements IXposedHookLoadPackage
{
    @Override
    public void handleLoadPackage(final XC_LoadPackage.LoadPackageParam lpparam) throws Throwable
    {
        SystemPropertiesHook systemPropertiesHook = new SystemPropertiesHook();
        //systemProperties hook
        XposedHelpers.findAndHookMethod("android.os.SystemProperties", lpparam.classLoader, "get", String.class, String.class, systemPropertiesHook);
    }
}

親測 hook 成功!