☞ Github ☜ ☞ Gitee ☜ 說明 Binder作為Android系統跨進程通信的核心機制。網上也有很多深度講解該機制的文章,如: Android跨進程通信詳解Binder機制原理 Android系統核心機制Binder【系列】 這些文章和系統源碼可以很好幫助我們理解Binder的實現原 ...
說明
Binder
作為Android
系統跨進程通信的核心機制。網上也有很多深度講解該機制的文章,如:
這些文章和系統源碼可以很好幫助我們理解Binder的實現原理和設計理念,為攔截做準備。藉助Binder攔截可以我們可以擴展出那些能力呢:
- 虛擬化的能力,多年前就出現的應用免安裝運行類產品如:
VirtualApp
/DroidPlugin
/平行空間/雙開大師/應用分身等。 - 測試驗證的能力,通常為
Framework
層功能開發。 - 檢測第三方
SDK
或模塊系統服務調用訪問情況(特別是敏感API
調用)。 - 逆向分析應用底層服務介面調用實現。
- 第三方
ROM
擴展Framework
服務。
現有方案
一直以來實時分析和攔截進程的Binder
通信是通過Java
層的AIDL
介面代理來實現的。藉助於Android
系統Binder
服務介面設計的規範,上層的介面均繼承於IBinder
。
如一下為代理目標對象的所有的介面API
的方法:
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Proxy;
private static void getInterface(Class<?> cls, final HashSet<Class<?>> ss) {
Class<?>[] ii;
do {
ii = cls.getInterfaces();
for (final Class<?> i : ii) {
if (ss.add(i)) {
getInterface(i, ss);
}
}
cls = cls.getSuperclass();
} while (cls != null);
}
private static Class<?>[] getInterfaces(Class<?> cls) {
final HashSet<Class<?>> ss = new LinkedHashSet<Class<?>>();
getInterface(cls, ss);
if (0 < ss.size()) {
return ss.toArray(new Class<?>[ss.size()]);
}
return null;
}
public static Object createProxy(Object org, InvocationHandler cb) {
try {
Class<?> cls = org.getClass();
Class<?>[] cc = getInterfaces(cls);
return Proxy.newProxyInstance(cls.getClassLoader(), cc, cb);
} catch (Throwable e) {
Logger.e(e);
} finally {
// TODO release fix proxy name
}
return null;
}
1、對於已經生成的Binder
服務對象,在應用進程可參與實現邏輯之前就已經緩存了,我們需要找到並且進行替換(AMS、PMS、WMS等
),如AMS
在Android 8.0之後的緩存如下:
// source code: http://aospxref.com/android-9.0.0_r61/xref/frameworks/base/core/java/android/app/ActivityManager.java
package android.app;
public class ActivityManager {
public static IActivityManager getService() {
return IActivityManagerSingleton.get();
}
private static final Singleton<IActivityManager> IActivityManagerSingleton =
new Singleton<IActivityManager>() {
@Override
protected IActivityManager create() {
final IBinder b = ServiceManager.getService(Context.ACTIVITY_SERVICE);
final IActivityManager am = IActivityManager.Stub.asInterface(b);
return am;
}
};
}
因此我們需要找到並且替換它,如:
Object obj;
if (Build.VERSION.SDK_INT < 26) {// <= 7.0
obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManagerNative", "gDefault");
} else {// 8.0 <=
obj = ReflectUtils.getStaticFieldValue("android.app.ActivityManager", "IActivityManagerSingleton");
}
Object inst = ReflectUtils.getFieldValue(obj, "mInstance");
ReflectUtils.setFieldValue(obj, "mInstance", createProxy(inst));
2、對於後續運行過程中才獲取的Binder
服務,則需要代理ServiceManager
,源碼如下:
// source code: http://aospxref.com/android-9.0.0_r61/xref/frameworks/base/core/java/android/os/ServiceManager.java
package android.os;
public final class ServiceManager {
private static final String TAG = "ServiceManager";
private static IServiceManager sServiceManager;
}
因此我們的代理如下:
Class<?> cls = ReflectUtils.findClass("android.os.ServiceManager");
Object org = ReflectUtils.getStaticFieldValue(cls, "sServiceManager");
Object pxy = new createProxy(org);
if (null != pxy) {
ReflectUtils.setStaticFieldValue(getGlobalClass(), "sServiceManager", pxy);
}
這樣每次在第一次訪問該服務時,就會調用IServiceManager
中的getService
的方法,而該方法已經被我們代理攔截,我們可以通過參數可以識別當前獲取的是哪個服務,然後將獲取的服務對象代理後在繼續返回即可。
但是:
這樣的方案並不能攔截進程中所有的Binder
服務。我們面臨幾大問題:
-
首先,Android源碼越來越龐大,瞭解所有的服務工作量很大,因此有哪些服務已經被緩存排查非常困難。
-
其次,廠商越來越鐘情於擴展自定義服務,這些服務不開源,識別和適配更加耗時。
-
再次,有一部分服務只有
native
實現,並不能通過Java
層的介面代理進行攔截(如:Sensor/Audio/Video/Camera服務等
)。// source code: http://aospxref.com/android-13.0.0_r3/xref/frameworks/av/camera/ICamera.cpp class BpCamera: public BpInterface<ICamera> { public: explicit BpCamera(const sp<IBinder>& impl) : BpInterface<ICamera>(impl) { } // start recording mode, must call setPreviewTarget first status_t startRecording() { ALOGV("startRecording"); Parcel data, reply; data.writeInterfaceToken(ICamera::getInterfaceDescriptor()); remote()->transact(START_RECORDING, data, &reply); return reply.readInt32(); } }
新方案:基於底層攔截
原理
我們都知道Binder
在應用進程運行原理如下圖:
不管是Java
層還是native
層的介面調用,最後都會通過ioctl
函數訪問共用記憶體空間,達到跨進程訪問數據交換的目的。因此我們只要攔截ioctl
函數,即可完成對所有Binder
通信數據的攔截。底層攔截有以下優勢:
1)可以攔截所有的Binder通信。
2)底層攔截穩定,高相容性。從Android 4.x
至Android 14
,近10年的系統版本演進,涉及到Binder
底層通信適配僅兩次;一次是支持64位進程(當時需要同時相容32位和64位進程訪問Binder
服務)。另一次是華為鴻蒙系統的誕生,華為ROM
在Binder
通信協議中增加了新的標識欄位。
要解決的問題
如何攔截
C/C++
層的函數攔截,並不像Java
層一樣系統提供了較為穩定的代理工具,在這裡不是我們本期討論的重點,可以直接採用網上開源的Hook
框架:
- https://github.com/bytedance/android-inline-hook
- https://github.com/bytedance/bhook
- https://github.com/asLody/whale
如何過濾
ioctl
函數為系統底層設備訪問函數,調用及其頻繁,而Binder
通信調用只是其中調用者之一,因此需要快速識別非Binder
通信調用,不影響程式性能。
函數定義:
#include <sys/ioctl.h>
int ioctl(int fildes, unsigned long request, ...);
request
的參數定義:
// source code: http://aospxref.com/android-14.0.0_r2/xref/bionic/libc/kernel/uapi/linux/android/binder.h
#define BINDER_WRITE_READ _IOWR('b', 1, struct binder_write_read)
#define BINDER_SET_IDLE_TIMEOUT _IOW('b', 3, __s64)
#define BINDER_SET_MAX_THREADS _IOW('b', 5, __u32)
#define BINDER_SET_IDLE_PRIORITY _IOW('b', 6, __s32)
#define BINDER_SET_CONTEXT_MGR _IOW('b', 7, __s32)
#define BINDER_THREAD_EXIT _IOW('b', 8, __s32)
#define BINDER_VERSION _IOWR('b', 9, struct binder_version)
#define BINDER_GET_NODE_DEBUG_INFO _IOWR('b', 11, struct binder_node_debug_info)
#define BINDER_GET_NODE_INFO_FOR_REF _IOWR('b', 12, struct binder_node_info_for_ref)
#define BINDER_SET_CONTEXT_MGR_EXT _IOW('b', 13, struct flat_binder_object)
#define BINDER_FREEZE _IOW('b', 14, struct binder_freeze_info)
#define BINDER_GET_FROZEN_INFO _IOWR('b', 15, struct binder_frozen_status_info)
#define BINDER_ENABLE_ONEWAY_SPAM_DETECTION _IOW('b', 16, __u32)
#define BINDER_GET_EXTENDED_ERROR _IOWR('b', 17, struct binder_extended_error)
對應的源碼:
// source code: http://aospxref.com/android-14.0.0_r2/xref/frameworks/native/libs/binder/IPCThreadState.cpp
void IPCThreadState::threadDestructor(void *st) {
ioctl(self->mProcess->mDriverFD, BINDER_THREAD_EXIT, 0);
}
status_t IPCThreadState::getProcessFreezeInfo(pid_t pid, uint32_t *sync_received, uint32_t *async_received) {
return ioctl(self()->mProcess->mDriverFD, BINDER_GET_FROZEN_INFO, &info);
}
status_t IPCThreadState::freeze(pid_t pid, bool enable, uint32_t timeout_ms) {
return ioctl(self()->mProcess->mDriverFD, BINDER_FREEZE, &info) < 0);
}
void IPCThreadState::logExtendedError() {
ioctl(self()->mProcess->mDriverFD, BINDER_GET_EXTENDED_ERROR, &ee) < 0);
}
status_t IPCThreadState::talkWithDriver(bool doReceive) {
// 實際Binder調用通信
return ioctl(mProcess->mDriverFD, BINDER_WRITE_READ, &bwr);
}
快速過濾:
static int ioctl_hook(int fd, int cmd, void* arg) {
if (cmd != BINDER_WRITE_READ || !arg || g_ioctl_disabled) {
return g_ioctl_func(fd, cmd, arg);
}
}
如何解析
目標源碼:http://aospxref.com/android-14.0.0_r2/xref/frameworks/native/libs/binder
重點解析發送(即BC_TRANSACTION
和BC_REPLY
)和接收(即BR_TRANSACTION
和BR_REPLY
)的類型數據。
如何修改數據
修改數據分為以下幾種:
1)修複調用時參數數據。
2)修複調用後返回的結果數據。
如果數據修複不改變當前數據的長度,只是內容的變化,則可以直接通過地址進行修改。否則需要創建新的記憶體進行修改後將新的數據地址設置到BINDER_WRITE_READ
結構的buffer
成員。此時處理好記憶體的釋放問題。
3)直接攔截本次調用。
為了保障穩定性,不打斷Binder
的調用流程(通常這也是攔截和逆向方案保障穩定的最重要原則之一)。我們可以將目標函數code
修改成父類處理的通用方法,然後通過修複調用的返回值即可完成攔截。
方案實現
數據解析
Binder調用數據結構如下:
解析bwr
bwr
即binder_write_read
,從源碼中瞭解到ioctl
的BINDER_WRITE_READ
類型的arg
數據結構為:
struct binder_write_read {
// 調用時傳入的數據
binder_size_t write_size;// call data
binder_size_t write_consumed;// call data
binder_uintptr_t write_buffer;// call data
// 結果返回數據
binder_size_t read_size;// recv data
binder_size_t read_consumed;// recv data
binder_uintptr_t read_buffer;// recv data
};
不管是傳入還是返回的數據,都是一組BC命令或BR命令,也就是說一次調用上層會打包幾個命令一起傳遞。因此我們需要通過迴圈來找到我們的命令。
void binder_find_for_bc(struct binder_write_read& bwr) {
binder_uintptr_t cmds = bwr.write_buffer;
binder_uintptr_t end = cmds + (binder_uintptr_t)bwr.write_size;
binder_txn_st* txn = NULL;
while (0 < cmds && cmds < end && !txn) {
// 由於每次Binder通信數據量的限制,Binder設計每次調用有且僅包含一個有效的參數命令,因此只要找到即可,其他類型則直接跳過忽略
cmds = binder_parse_cmds_bc(cmds, txn);
}
}
dump
數據如下:
write_buffer:0xb400007107d1d400, write_consumed:68, write_size:68
00000000: 00 63 40 40 14 00 00 00 00 00 00 00 00 00 00 00 .c@@............
00000010: 00 00 00 00 01 00 00 00 12 00 00 00 00 00 00 00 ................
00000020: 00 00 00 00 54 00 00 00 00 00 00 00 00 00 00 00 ....T...........
00000030: 00 00 00 00 00 4d 3a ac 70 00 00 b4 00 00 00 00 .....M:.p.......
00000040: 00 00 00 00 ....
BR_NOOP: 0x720c
BR_TRANSACTION_COMPLETE: 0x7206
BR_REPLY: 0
解析txn
txn
即binder_transaction_data
,Binder方法調用的方法參數信息定義如下:
struct binder_transaction_data {
union {
__u32 handle;
binder_uintptr_t ptr;
} target;// 目標服務句柄,server端使用
binder_uintptr_t cookie;// 緩存的Binder進行訪問
__u32 code;//方法編號
__u32 flags;// 標識,如是否為 oneway
__s32 sender_pid;
__u32 sender_euid;
binder_size_t data_size;// 數據長度
binder_size_t offsets_size;// 若包含對象,則對象數據大小
union {
struct {
binder_uintptr_t buffer;// Binder方法參數值地址
binder_uintptr_t offsets;// Binder方法參數對象數據地址
} ptr;
__u8 buf[8];
} data;
};
dumo
數據如下:
Trace : target: 1 cookie: 0 code: 23 flags: 0x12(READ REPLY)
Trace : pid: 0 uid: 0 size: 196 offs:8
Trace : 00000000: 00 00 00 80 ff ff ff ff 54 53 59 53 1c 00 00 00 ........TSYS....
Trace : 00000010: 61 00 6e 00 64 00 72 00 6f 00 69 00 64 00 2e 00 a.n.d.r.o.i.d...
Trace : 00000020: 61 00 70 00 70 00 2e 00 49 00 41 00 63 00 74 00 a.p.p...I.A.c.t.
Trace : 00000030: 69 00 76 00 69 00 74 00 79 00 4d 00 61 00 6e 00 i.v.i.t.y.M.a.n.
Trace : 00000040: 61 00 67 00 65 00 72 00 00 00 00 00 85 2a 62 73 a.g.e.r......*bs
Trace : 00000050: 13 01 00 00 00 38 dd 2a 71 00 00 b4 00 05 e9 31 .....8.*q......1
Trace : 00000060: 71 00 00 b4 01 00 00 0c 1a 00 00 00 63 00 6f 00 q...........c.o.
Trace : 00000070: 6d 00 2e 00 69 00 66 00 6d 00 61 00 2e 00 74 00 m...i.f.m.a...t.
Trace : 00000080: 72 00 61 00 6e 00 73 00 65 00 63 00 2e 00 63 00 r.a.n.s.e.c...c.
Trace : 00000090: 6f 00 6e 00 74 00 61 00 69 00 6e 00 65 00 72 00 o.n.t.a.i.n.e.r.
Trace : 000000a0: 00 00 00 00 08 00 00 00 73 00 65 00 74 00 74 00 ........s.e.t.t.
Trace : 000000b0: 69 00 6e 00 67 00 73 00 00 00 00 00 00 00 00 00 i.n.g.s.........
Trace : 000000c0: 01 00 00 00 ....
Trace : binder object offs:0x4c type:0x73622a85 flags:0x113 ptr:0x2add3800 cookie:0x31e90500
解析服務名
Binder
通信數據頭如下,即可解析出目標服務名:
void find_server_name(const binder_txn_st* txn) {
const int32_t* ptr = reinterpret_cast<const int32_t*>(txn->data.ptr.buffer);
++ ptr;// skip strict model
if (29 <= sdkVersion()) ++ ptr;// 10.0 <=, skip flags(ff ff ff ff)
int32_t nameLen = *ptr;
const uint16_t* name16 = (const uint16_t*)(ptr+1);
}
解析方法名
Binder
通信數據中標識該服務方法的參數是txn->code
。AIDL
定義類在編譯後會為每個方法自動生成靜態的方法。
如定義的Binder
介面方法為:
interface IDemo {
void test();
void test2();
}
則編譯後生成的類為:
class IDemo$Stub {
void test();
void test2();
static final int TRANSACTION_test = 1;
static final int TRANSACTION_test2 = 2;
}
因此我們可以通過反射的方式,找到服務名對應的類所有靜態成員變數,然後找到與code
值相等的成員即為此方法。
這裡可能需要解決私有API的限制解除問題。
// 可直接使用工程工具類
TstClassPrinter.printStubByCodes("android.app.IActivityManager", 13, 16, 67);
日誌輸出如下:
解析數據
首先需要藉助數據封裝類Parcel
。
// souce code:
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/native/include/binder/Parcel.h
// http://aospxref.com/android-14.0.0_r2/xref/frameworks/native/libs/binder/Parcel.cpp
藉助該類可以解析一些比較簡單的數據,快速的找到目標內容。而對於比較複雜的數據,如參數值為Intent
,該參數類型嵌套了多層的Parcelable
成員,因此在native
層通過Parcel
來解析,相容性比較差。因此我們選擇通過回調到Java層來解析,修改後再格式化為native
的buffer
數據。這裡需要處理好Java
和native
層的數據交換問題,以及回收。
native
層:
// 創建
jobject obtain(JNIEnv* env) {
jclass jcls = env->FindClass("android/os/Parcel");
jmethodID method = env->GetStaticMethodID(jcls, "obtain", "()Landroid/os/Parcel;");
if (!method) return NULL;
mParcelObj = env->CallStaticObjectMethod(jcls, method);
if (!mParcelObj) return NULL;
if (0 < mUparcel->dataSize()) {
method = env->GetMethodID(sParcelClass, "setDataPosition", "(I)V");
if (method) {
unmarshall(env, mUparcel->data(), mUparcel->dataSize());
env->CallVoidMethod(mParcelObj, method, mUparcel->dataPosition());
}
}
return mParcelObj;
}
// 回收
void recycle(JNIEnv* env) {
jclass jcls = env->FindClass("android/os/Parcel");
jmethodID method = env->GetMethodID(jcls, "recycle", "()V");
if (method) {
env->CallVoidMethod(mParcelObj, method);
}
if (mParcelObj) {
env->DeleteLocalRef(mParcelObj);
}
mParcelObj = NULL;
}
Java
層:
public static void clearHttpLink(Parcel p/*IN*/, Parcel q/*OUT*/) {
try {
Intent ii = Intent.CREATOR.createFromParcel(pp);
// TODO something ...
// write new data
q.appendFrom(p, p.dataPosition(), p.dataAvail());
} catch (Throwable e) {
e.printStackTrace();
}
}
數據攔截
Binder的數據解析和列印不會改變原數據內容,因此相對簡單,如果要對數據進行修改,則相對複雜一些。修複的數據需要替換原數據,因此需要進行如下操作。
1、數據替換。
將txn
中方法參數的數據指針指向新創建的數據區。
int binder_replace_txn_for_br(binder_txn_st *txn, ParcelEx* reply, binder_size_t _pos) {
size_t size = reply->ipcDataSize();
uint8_t* repData = (uint8_t*)malloc(size + txn->offsets_size);
memcpy(repData, reply->data(), size);
if (0 < txn->offsets_size) {
binder_replace_objects(txn, repData, _pos, ((int)size) - ((int)(txn->data_size)));
}
txn->data.ptr.buffer = reinterpret_cast<uintptr_t>(repData);
txn->data_size = size;
return 0;
}
2、修正對象指針。
如果傳入的參數包含Binder對象,如register
方法的Observe
。因此修複的數據可能導致偏移的地址前移或者後移,因此需要重新計算偏移,如:
void replaceObjects(binder_txn_st *txn, uint8_t* objData, binder_size_t _pos, int _off) {
binder_size_t* offs = reinterpret_cast<binder_size_t*>(txn->data.ptr.offsets);
unsigned count = txn->offsets_size / sizeof(binder_size_t);
while (0 < count--) {
if (0 != memcmp(objData + (int)(*offs), (uint8_t*)txn->data.ptr.buffer + (int)(*offs), sizeof(binder_size_t))) {
*offs += _off;
}
++ offs;
}
}
3、記憶體釋放。
需要保存原地址A
和新的地址AA
的映射關係到自定義的記憶體池中。
當Binder
通信命令出現BC_FREE_BUFFER
和BR_FREE_BUFFER
時,則通過該命令要釋放的AA
地址,然後從記憶體池找到與之對應A
的地址,並設置回去讓上層繼續釋放,完成記憶體使用的閉環。
case BC_FREE_BUFFER:
{
uintptr_t* buffPtr = (uintptr_t *)cmd;
uintptr_t ptr = MemPool::detach(*buffPtr);
if (__UNLIKELY(0 != ptr)) {
*buffPtr = ptr;// set origin buffer
}
cmd += sizeof(uintptr_t);// move to next command
} break;
附:
如果你有需要,可以直接使用我們已經封裝好的SDK
來實現相應的功能,該項目已經開源,可以直接使用,參考【集成文檔】。