文章目录
Android跨进程通信,binder传输数据过大导致Crash,异常捕获,监听异常的数值临界值,提前Hook拦截。
Java Crash捕获
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
Thread.UncaughtExceptionHandler uncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
Log.e("crash", "当前进程id:" + android.os.Process.myPid());
Log.e("crash", Log.getStackTraceString(e));
if (uncaughtExceptionHandler != null) {
uncaughtExceptionHandler.uncaughtException(t, e);
}
});
}
}
1.binder在做跨进程传输时,最大可以携带多少数据
测试代码,跨进程传输1m数据
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
startBinding();
mBtn = findViewById(R.id.btn);
mBtn.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putByteArray("binder_data", new byte[1 * 1024 * 1024]);
Intent intent = new Intent();
intent.putExtras(bundle);
ComponentName componentName = new ComponentName("com.example.myapplication", "com.example.myapplication.MainActivity");
intent.setComponent(componentName);
startActivity(intent);
});
}
不出意外崩了
1.1有时候这个1m的崩溃系统捕获不到异常,
把数据传输从1M改成了800k测试
还是崩了,崩溃的数据量大概是500bk(不崩溃)-600kb(崩溃)之间。
核心log
IActivityTaskManager是个aidl文件;
IActivityTaskManager$Stub是binder服务端的类,运行在system进程的
Proxy对象是运行在我们app进程的,称之为binder代理
Proxy对象通过transact方法调用到Stub对象的onTransact方法。这个异常发生在App进程,所以代码捕获到了这个异常。
2.监测异常,提前上报
大概500k就是危险的极限大小,容易发生异常。
hook拦截startActivity的方法,到达一个危险的大小容易崩溃的时候,我们就上报。
android.os.BinderProxy.transact(BinderProxy.java:510)
这里面Parcel有个dataSize记录数据的大小
Hook IActivityTaskManager
看日志调用流程,在startActivity时候就可以拦截数据,Instrumentation.execStartActivity
Caused by: android.os.TransactionTooLargeException: data parcel size 1049052 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:510)
at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3823)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1705)
at android.app.Activity.startActivityForResult(Activity.java:5173)
ActivityTaskManager对象
final IBinder b = ServiceManager.getService(Context.ACTIVITY_TASK_SERVICE);
return IActivityTaskManager.Stub.asInterface(b);
sCache,是个静态的ArrayMap对象:
@UnsupportedAppUsage
private static Map<String, IBinder> sCache = new ArrayMap<String, IBinder>();
反射换掉IActivityTaskManager对象的IBinder对象,IBinder有监控的transact方法。
invoke方法中,关注transact方法获得跨进程传输的参数的大小
IBinde的transact方法:Parcel data
public boolean transact(int code, @NonNull Parcel data, @Nullable Parcel reply, int flags)
throws RemoteException;
/**
* Returns the total amount of data contained in the parcel.
*/
public final int dataSize() {
return nativeDataSize(mNativePtr);
}
完整Demo
public static void hook() {
Log.e(TAG, "hook: ");
try {
Class serviceManager = Class.forName("android.os.ServiceManager");
Method getServiceMethod = serviceManager.getMethod("getService", String.class);
Field sCacheField = serviceManager.getDeclaredField("sCache");
sCacheField.setAccessible(true);
Map<String, IBinder> sCache = (Map<String, IBinder>) sCacheField.get(null);
Map<String, IBinder> sNewCache;
sNewCache = new ArrayMap<>();
sNewCache.putAll(sCache);
IBinder activityTaskRemoteBinder = (IBinder) getServiceMethod.invoke(null, "activity_task");
sNewCache.put("activity_task", (IBinder) Proxy.newProxyInstance(serviceManager.getClassLoader(),
new Class[]{IBinder.class},
new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
Log.e(TAG, "activity_task method = " + method.getName() + ", args = " + Arrays.toString(args));
if ("transact".equals(method.getName())) {
if (args != null && args.length > 1) {
Object arg = args[1];
if (arg instanceof Parcel) {
Parcel parcelArg = (Parcel) arg;
int dataSize = parcelArg.dataSize();
if (dataSize > 300 * 1024) {
// TODO 报警
Log.e(TAG, Log.getStackTraceString(new RuntimeException("[error]TransactionTooLargeException: 300Kb:" + dataSize)));
if (BuildConfig.DEBUG) {
if (dataSize > 512 * 1024) {
throw new RuntimeException("[error]TransactionTooLargeException:300Kb:" + dataSize);
}
}
}
}
}
}
return method.invoke(activityTaskRemoteBinder, args);
}
}));
sCacheField.set(null, sNewCache);
} catch (Exception e) {
e.printStackTrace();
}
}
测试
从日志看出我们是hook系统服务成功了。
总结:
- binder数据量过大崩溃,Java异常捕获机制捕获不到,提前拦截。
- DEBUG,超过512k,崩溃可以看到日志;
- ServiceManager中的sCache获取到原来activity_task对应的IBinder实例对象; IBinder是接口,通过动态代理创造一个IBinder的代理对象IBinderProxy; 把IBinderProxy放到ServiceManager的sCache,Application attachBaseContext中调用Hook方法;
扩展:Hook AMS绕过Manifest的Activity注册检测
思路,先启动一个注册的代理的Activity,然后绕过manifest的检测后,把代理的Activity替换成未注册的
package com.example.myapplication;
import android.content.Intent;
import android.os.Handler;
import android.os.Message;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.List;
public class HookUtil {
private static final String TARGET_INTENT = "target_intent";
// 使用代理的Activity替换需要启动的未注册的Activity
public static void hookAMS() {
try {
Class<?> clazz = Class.forName("android.app.ActivityTaskManager");
Field singletonField = clazz.getDeclaredField("IActivityTaskManagerSingleton");
singletonField.setAccessible(true);
Object singleton = singletonField.get(null);
Class<?> singletonClass = Class.forName("android.util.Singleton");
Field mInstanceField = singletonClass.getDeclaredField("mInstance");
mInstanceField.setAccessible(true);
Method getMethod = singletonClass.getMethod("get");
Object mInstance = getMethod.invoke(singleton);
Class IActivityTaskManagerClass = Class.forName("android.app.IActivityTaskManager");
Object mInstanceProxy = Proxy.newProxyInstance(Thread.currentThread().getContextClassLoader(),
new Class[]{IActivityTaskManagerClass}, new InvocationHandler() {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if ("startActivity".equals(method.getName())) {
int index = -1;
// 获取 Intent 参数在 args 数组中的index值
for (int i = 0; i < args.length; i++) {
if (args[i] instanceof Intent) {
index = i;
break;
}
}
// 生成代理proxyIntent
Intent proxyIntent = new Intent();
proxyIntent.setClassName("com.example.myapplication",
ProxyActivity.class.getName());
// 保存原始的Intent对象
Intent intent = (Intent) args[index];
proxyIntent.putExtra(TARGET_INTENT, intent);
// 使用proxyIntent替换数组中的Intent
args[index] = proxyIntent;
}
// 被代理对象调用
return method.invoke(mInstance, args);
}
});
// 用代理的对象替换系统的对象
mInstanceField.set(singleton, mInstanceProxy);
} catch (Exception e) {
e.printStackTrace();
}
}
// 需要启动的未注册的Activity 替换回来 ProxyActivity
public static void hookHandler() {
try {
Class<?> clazz = Class.forName("android.app.ActivityThread");
Field activityThreadField = clazz.getDeclaredField("sCurrentActivityThread");
activityThreadField.setAccessible(true);
Object activityThread = activityThreadField.get(null);
Field mHField = clazz.getDeclaredField("mH");
mHField.setAccessible(true);
final Handler mH = (Handler) mHField.get(activityThread);
Field mCallbackField = Handler.class.getDeclaredField("mCallback");
mCallbackField.setAccessible(true);
mCallbackField.set(mH, new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
switch (msg.what) {
case 159:
try {
Field mActivityCallbacksField = msg.obj.getClass()
.getDeclaredField("mActivityCallbacks");
mActivityCallbacksField.setAccessible(true);
List mActivityCallbacks = (List) mActivityCallbacksField.get(msg.obj);
for (int i = 0; i < mActivityCallbacks.size(); i++) {
if (mActivityCallbacks.get(i).getClass().getName()
.equals("android.app.servertransaction.LaunchActivityItem")) {
Object launchActivityItem = mActivityCallbacks.get(i);
Field mIntentField = launchActivityItem.getClass()
.getDeclaredField("mIntent");
mIntentField.setAccessible(true);
Intent proxyIntent = (Intent) mIntentField.get(launchActivityItem);
Intent intent = proxyIntent.getParcelableExtra(TARGET_INTENT);
if (intent != null) {
mIntentField.set(launchActivityItem, intent);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
break;
}
return false;
}
});
} catch (Exception e) {
e.printStackTrace();
}
}
}
补充:共享内存传递大数据,解决常规做法报异常TransactionTooLargeException
1.用putBinder方法传递数据
intent通过binder传递bitmap大数据
1.1客户端
public class MainActivity extends AppCompatActivity {
private ITest binder;
private Button mBtn;
private Bitmap bitmap;
private Button btnTest;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
btnTest = findViewById(R.id.btn_test);
btnTest.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.example.myapplication", "com.example.myapplication.ImageActivity");
intent.setComponent(componentName);
Bundle bundle = new Bundle();
bundle.putBinder("imageData", new ITest.Stub() {
@Override
public void request() throws RemoteException {
}
@Override
public Bitmap getBitmap() throws RemoteException {
return bitmap;
}
});
intent.putExtras(bundle);
startActivity(intent);
}
});
startBinding();
mBtn = findViewById(R.id.btn);
mBtn.setOnClickListener(v -> {
Bundle bundle = new Bundle();
bundle.putByteArray("binder_data", new byte[2]);
Intent intent = new Intent();
intent.putExtras(bundle);
ComponentName componentName = new ComponentName("com.example.myapplication", "com.example.myapplication.MainActivity");
intent.setComponent(componentName);
startActivity(intent);
});
bitmap = getBitmap();
}
private Bitmap getBitmap() {
Drawable drawable = getResources().getDrawable(R.drawable.test);
if (drawable instanceof BitmapDrawable) {
BitmapDrawable bitmapDrawable = (BitmapDrawable) drawable;
if (bitmapDrawable.getBitmap() != null) {
return bitmapDrawable.getBitmap();
}
}
return null;
}
private ServiceConnection connection = new ServiceConnection() {
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
binder = ITest.Stub.asInterface(service);
try {
binder.request();
} catch (RemoteException e) {
throw new RuntimeException(e);
}
}
@Override
public void onServiceDisconnected(ComponentName name) {
binder = null;
}
};
private void startBinding() {
Intent intent = new Intent();
ComponentName componentName = new ComponentName("com.example.myapplication", "com.example.myapplication.MyService");
intent.setComponent(componentName);
bindService(intent, connection, BIND_AUTO_CREATE);
}
@Override
protected void onDestroy() {
super.onDestroy();
unbindService(connection);
}
}
1.2服务端
public class ImageActivity extends AppCompatActivity {
private Button btnShow;
private ImageView ivShow;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
btnShow = findViewById(R.id.btn_show);
ivShow = findViewById(R.id.iv_show);
btnShow.setOnClickListener(new View.OnClickListener() {
@Override public void onClick(View v) {
Intent intent = getIntent();
Bundle bundle = intent.getExtras();
IBinder iBinder = bundle.getBinder("imageData");
ITest iTest = ITest.Stub.asInterface(iBinder);
try {
Bitmap bitmap = iTest.getBitmap();
if (bitmap != null) {
ivShow.setImageBitmap(bitmap);
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
});
}
}
1.3ITest.aidl
package com.qfh.test;
interface ITest {
void request();
Bitmap getBitmap();
}
资源图片,最终测试没有崩溃
2.putbinder的原理,为什么可以传递大图片?
如果直接传递大文件会报错
Caused by: android.os.TransactionTooLargeException: data parcel size 13131323121 bytes
at android.os.BinderProxy.transactNative(Native Method)
at android.os.BinderProxy.transact(BinderProxy.java:535)
at android.app.IActivityTaskManager$Stub$Proxy.startActivity(IActivityTaskManager.java:3904)
at android.app.Instrumentation.execStartActivity(Instrumentation.java:1738)
这是个native方法
最终会调用到这里
frameworks/base/core/jni/android_util_Binder.cpp
android_os_BinderProxy_transact,进行跨进程数据传输
android_os_BinderProxy_transact,signalExceptionForError处理异常的方法,parcelSize大于200K就会报错
但是不一定报错200k,因为我们前面的测试大概是500-1m的一个区间报错
android_os_BinderProxy_transact
status_t err = target->transact(code, *data, reply, flags);
2.1 ProcessState.cpp
这个类主要是打开binder驱动
(1 * 1024 * 1024) - (4096 *2)
//初始变量
ProcessState::ProcessState(const char* driver)
: mDriverName(String8(driver)),
mDriverFD(-1),
mVMStart(MAP_FAILED),
......
mMaxThreads(DEFAULT_MAX_BINDER_THREADS),
mStarvationStartTimeMs(0),
mThreadPoolStarted(false),
mThreadPoolSeq(1),
mCallRestriction(CallRestriction::NONE) {
......
//打开驱动
base::Result<int> opened = open_driver(driver);
if (opened.ok()) {
//映射内存大小8k-1m,Binder内存限制,BINDER_VM_SIZE = 1M-8kb
mVMStart = mmap(nullptr, BINDER_VM_SIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE,
opened.value(), 0);
}
}
2.2 binder_mmap
映射空间最多4M,binder_alloc_mmap_handler,vma用于分配绑定缓冲区
2.3 binder_alloc_mmap_handler
buffer_size最大4m,也就是缓冲区的大小,异步事务的空闲缓冲区大小最大2M
alloc->free_async_space = alloc->buffer_size / 2;
所以说binder驱动给每一个进程最多4m的内存空间,内核是8k-1m,用户空间则有3m,异步事务缓冲区空间等于buffer_size/2
3.为什么intent放入Bitmap就不行
3.1 Bundle写入到Parcel
3.2 执行bundle的writeToParcel
restoreAllowFds指的是是否允许携带文件描述符
Intent prepareToLeaveProcess 调用了Bundle#setAllowFds(false)表示不使用文件描述符fd
3.3 writeArrayMapInternal
传递是图片的话会被序列化,所以调这个if else,val.writeToParcel(this, 0);后面的0表示禁止携带fd文件描述符
传入的是BitMap,所以是走BitMap的writeToParcel方法
最终走native层的代码
android::Parcel* p = android::parcelForJavaObject(env, parcel);
匿名共享内存AshmemFd大于等于0 && bitmap不可变 && parcel允许带Fd,fd写入到parcel中
3.4 android::Parcel::WritableBlob blob
frameworks/native/libs/binder/Parcel.cpp
//不允许带FD 数据小于等于16k,图片写入parcel,所以默认intent传递大数据是走这个分支禁用掉了文件描述符以及限制了16k的大小
if (!mAllowFds || len <= BLOB_INPLACE_LIMIT) {
ALOGV("writeBlob: write in place");
status = writeInt32(BLOB_INPLACE);
if (status) return status;
void* ptr = writeInplace(len);
if (!ptr) return NO_MEMORY;
outBlob->init(-1, ptr, len, false);
return NO_ERROR;
}
//不满足条件 允许Fd && len > 16k,创建ashmem返回文件描述符FD
int fd = ashmem_create_region("Parcel Blob", len);
//ashmem 可读可写
int result = ashmem_set_prot_region(fd, PROT_READ | PROT_WRITE);
//fd,映射 len大小 mmap的空间
void* ptr = ::mmap(nullptr, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
//fd写入到parcel中
status = writeFileDescriptor(fd, true /*takeOwnership*/);
3.5 Parcel的writeStrongBinder
可以把一个Binder对象写入Parcel中
这个方法会把Binder对象组装到Parcel对象里面作为携带数据
putBinder来把IBinder对象
写入到Parcel中
二.使用自定义共享内存代码实现传输大数据
使用AIDL
1. client
private void sendLargeData() {
try {
// 读取assets目录下文件
InputStream inputStream = getAssets().open("large.jpg");
// 将inputStream转换成字节数组
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
byte[] byteArray = outputStream.toByteArray();
MemoryFile memoryFile = new MemoryFile("image", byteArray.length);
memoryFile.writeBytes(byteArray, 0, 0, byteArray.length);
FileDescriptor fileDescriptor = (FileDescriptor) invoke("android.os.MemoryFile", memoryFile, "getFileDescriptor");
ParcelFileDescriptor dup = ParcelFileDescriptor.dup(fileDescriptor);
binder.client2server(dup);
inputStream.close();
outputStream.close();
} catch (IOException | RemoteException e) {
throw new RuntimeException(e);
}
}
public static Object invoke(String className, Object instance, String methodName, Object... params) {
try {
Class<?> c = Class.forName(className);
if (params != null) {
int plength = params.length;
Class[] paramsTypes = new Class[plength];
for (int i = 0; i < plength; i++) {
paramsTypes[i] = params[i].getClass();
}
Method method = c.getDeclaredMethod(methodName, paramsTypes);
method.setAccessible(true);
return method.invoke(instance, params);
}
Method method = c.getDeclaredMethod(methodName);
method.setAccessible(true);
return method.invoke(instance);
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
2.server
@Override
public void client2server(ParcelFileDescriptor pfd) throws RemoteException {
try {
FileDescriptor fileDescriptor = pfd.getFileDescriptor();
// 根据FileDescriptor构建InputStream对象
InputStream fis = new FileInputStream(fileDescriptor);
// 创建字节数组输出流用于读取数据
ByteArrayOutputStream baos = new ByteArrayOutputStream();
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = fis.read(buffer)) != -1) {
baos.write(buffer, 0, bytesRead);
}
// 从InputStream中读取字节数组
byte[] data = baos.toByteArray();
Log.e(TAG, "client2server: " + data.length);
fis.close();
baos.close();
} catch (Exception e) {
}
}
运行测试
发现应用程序没有崩溃,并且服务端接收到了客户端穿过来的字节数组,