Android进程间使用AIDL通信

前言

最近在分析某款软件的功能,该软件通过usb或者wifi与计算机上的应用进行交互,而一些重要功能,又通过android服务,调用其它进程完成。为了了解内部的运行机制,辅助进行逆向分析,因此学习了android通过aidl进行进程间通信。这样,使得在逆向过程中,熟练掌握逆向出的代码。
这里假设服务端提供加法计算的算法,客户端软件在界面上提示用户输入2个数字,用户点击计算后,通过访问服务端导出的service,调用aidl接口,得到计算结果。更新界面显示,将计算结果显示在用户界面。
本文拷贝的代码比较多,解释比较少。如果你对读代码不感兴趣,那么就像聪明的一休那样,就到这里、就到这里吧,这样更轻松,?

1. AIDL

a) AIDL:Android Interface Definition Language,即Android接口定义语言。
b) 创建测试用的AIDL,客户端和服务端使用相同的aidl文件。使用Android Studio时,生成的文件路径总是与服务或者客户的名称相联系,不能独立出来,没找到解决办法。后来放弃掉,改用Eclipse,搞定。

package com.jim.aidl.test;

interface ITestAidl{
	double Add(double a, double b);
}

将ITestAidl.aidl保存后,Eclipse自动在gen下的com.jim.aidl.test路径下,生成ITestAidl.java

2. 服务端

a) 创建TestAidlService服务类,继承Service。这个服务类中,创建子类,继承AIDL接口的Stub。

package com.jim.aidlserver;

import com.jim.aidl.test.ITestAidl.Stub;

import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.util.Log;

public class TestAidlService extends Service {
	private TestAidl testAidl = new TestAidl();
	
	@Override
	public void onCreate(){
		super.onCreate();
		Log.i("zjm", "onCreate called");
	}
	@Override
	public int onStartCommand(Intent intent, int flags, int startId) {
		Log.i("zjm", "onStartCommand called");
		return super.onStartCommand(intent, flags, startId);
	}
	@Override
	public IBinder onBind(Intent intent) {
		// TODO Auto-generated method stub
		Log.i("zjm", "onBind called");
		return testAidl;
	}
	@Override
	public void onDestroy(){
		super.onDestroy();
		Log.i("zjm", "onDestory called");
	}

	public class TestAidl extends Stub{
		@Override
		public double Add(double a, double b){
			return a+b;
		}
	}
}

b) 在AndroidManifest.xml中,增加对服务的导出。

	<permission android:name="android.permission.user.ZJM_SERVICE"></permission>
	<Application>
        <service android:name=".TestAidlService" android:exported="true" android:permission="android.permission.user.ZJM_SERVICE">
            <intent-filter >
                <action android:name="com.jim.test.AIDL_SERVICE" />
            </intent-filter>
        </service>
    </Application>

3.客户端

a) 在AndroidManifest.xml文件中,增进权限申请。

	<uses-permission android:name="android.permission.user.ZJM_SERVICE" />

b) 创建远程服务有三种方法,分别是Intent的setComponentName, setClassName,和setPackage。据有些对android源码的分析,setClassName最终会调用setComponentName。详细调用方法,请参考代码示例。

package com.zjm.aidlclient;

import com.jim.aidl.test.ITestAidl;

import android.app.Activity;
import android.app.Service;
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;


public class MainActivity extends Activity {
	private ITestAidl testAidl = null;
	private EditText num1;
	private EditText num2;
	private TextView tv;
	private ServiceConnection conn = new ServiceConnection(){
		@Override
		public void onServiceConnected(ComponentName name, IBinder service){
			try{
				testAidl = ITestAidl.Stub.asInterface(service);
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
		@Override
		public void onServiceDisconnected(ComponentName name){
			testAidl = null;
		}
	};
	
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        num1 = (EditText)findViewById(R.id.num1);
        num2 = (EditText)findViewById(R.id.num2);
        tv = (TextView)findViewById(R.id.result);
        Button btn_add = (Button)findViewById(R.id.btn_add);
        
        // 调用远程service的方法一
        //Intent intent = new Intent();
        //intent.setComponent(new ComponentName("com.jim.aidlserver", "com.jim.aidlserver.TestAidlService"));
        // 调用remote service的方法二
        Intent intent = new Intent();
        intent.setClassName("com.jim.aidlserver", "com.jim.aidlserver.TestAidlService");
        // 调用remote service的方法三
        //Intent intent = new Intent("com.jim.test.AIDL_SERVICE");
        //intent.setPackage("com.jim.aidlserver");
        boolean suc =bindService(intent, conn, Service.BIND_AUTO_CREATE);
        //failed to connent to server,just return
        if (!suc) {
        	return;
        }
        
        btn_add.setOnClickListener(new OnClickListener(){
        	@Override
        	public void onClick(View arg0){
        		try{
        			double d1 = Double.valueOf(num1.getText().toString());
        			double d2 = Double.valueOf(num2.getText().toString());
        			double sum = testAidl.Add(d1, d2);
        			tv.setText("结果:" + Double.toString(sum));
        			
        		}catch(RemoteException e) {
        			e.printStackTrace();
        		}
        	}
        });
    }

在服务的onServiceConnected函数中,调用接口的Stub的asInterface接口函数,获得远程调用接口。对这个接口,调用内部提供的方法,实现远程计算的功能。
注意:

  1. 服务端必须运行起来,bindService才能成功。
  2. bindService如果失败,返回false,而不是引起异常。在实际的编程过程中,要注意判断返回值和捕获异常的区别。

4.AIDL接口分析

如果只是编程使用AIDL,则下面的分析可以不看。但是,如果要对混淆后的aidl实现的功能进行分析,则需要理解这个接口,否则将会迷失在其中。接口被编译成java文件,eclipse生成的代码很差,我这里展示下逆向出来的代码。

package com.jim.aidl.test;

import android.os.Binder;
import android.os.IBinder;
import android.os.IInterface;
import android.os.Parcel;
import android.os.RemoteException;

public interface ITestAidl extends IInterface {
    public static abstract class Stub extends Binder implements ITestAidl {
        private static final String DESCRIPTOR = "com.jim.aidl.test.ITestAidl";
        static final int TRANSACTION_Add = 1;
        private static class Proxy implements ITestAidl {
            private IBinder mRemote;

            Proxy(IBinder remote) {
                this.mRemote = remote;
            }

            public IBinder asBinder() {
                return this.mRemote;
            }

            public String getInterfaceDescriptor() {
                return Stub.DESCRIPTOR;
            }

            public double Add(double a, double b) throws RemoteException {
                Parcel _data = Parcel.obtain();
                Parcel _reply = Parcel.obtain();
                try {
                    _data.writeInterfaceToken(Stub.DESCRIPTOR);
                    _data.writeDouble(a);
                    _data.writeDouble(b);
                    this.mRemote.transact(1, _data, _reply, 0);
                    _reply.readException();
                    double _result = _reply.readDouble();
                    return _result;
                } finally {
                    _reply.recycle();
                    _data.recycle();
                }
         }
        public Stub() {
            attachInterface(this, DESCRIPTOR);
        }

        public static ITestAidl asInterface(IBinder obj) {
            if (obj == null) {
                return null;
            }
            IInterface iin = obj.queryLocalInterface(DESCRIPTOR);
            if (iin == null || !(iin instanceof ITestAidl)) {
                return new Proxy(obj);
            }
            return (ITestAidl) iin;
        }

        public IBinder asBinder() {
            return this;
        }

        public boolean onTransact(int code, Parcel data, Parcel reply, int flags) throws RemoteException {
            switch (code) {
                case 1:
                    data.enforceInterface(DESCRIPTOR);
                    double _result = Add(data.readDouble(), data.readDouble());
                    reply.writeNoException();
                    reply.writeDouble(_result);
                    return true;
                default:
                    return super.onTransact(code, data, reply, flags);
            }
        }
    }
    double Add(double d, double d2) throws RemoteException;
}

这个看上去比源代码的可读性强。
抽象类Stub继承Binder类,继承了ITestAidl声明的接口。
在服务端,构造Stub的子类,实现ITestAidl接口需要完成的功能。在onBind函数中,返回此类的实例。
在客户端,asInterface函数,返回的是Stub中的Proxy类对象。客户端调用Add函数,会调用Proxy中的Add函数。Add中的mBinder.transact函数,将调用编号和参数发送到服务端。
在服务端,Stub抽象类中的onTransact函数被调用,客户端Add函数的调用,对应于case 1的情况。case 1中对Add函数的调用,则是调用服务端对接口的实现类的Add函数。服务端接口实现类Add返回后,返回值通过reply.write,将返回值传送回客户端。
客户端Proxy类中的Add函数,将服务端的返回值返回到上层调用。

5. 回调接口

当服务端后耗时操作,或者服务端需要向客户端发送事件消息等,一个好的解决方法是回调。
这里为什么要研究回调?因为逆向分析的代码中有回调 ?
回调接口也是利用aidl实现,只是之前的服务端变成了发起函数调用的客户端。而之前的客户端,则成了实现函数操作的服务端。
实验中,我们假设Add函数需要长时间计算后才能计算出结果(通过sleep 20秒实现效果),如果我们不立即返回,客户端就会出现RNA现象。
5.1 增加ITestCallback接口

package com.jim.aidl.test;

interface ITestCallback{
   void result(double r);
}

5.2 修改ITestAidl接口,接口中导入回调函数类

package com.jim.aidl.test;
import com.jim.aidl.test.ITestCallback;

interface ITestAidl{
   double Add(double a, double b);
   void registerCallback(ITestCallback testCallback);
   void unregisterCallback(ITestCallback testCallback);
}

5.3 客户端注册回调接口
客户端回调接口类实现,回调函数被调用后,将结果显示在对应的TextView中。

package com.zjm.aidlclient;

import android.os.RemoteException;
import android.widget.TextView;

import com.jim.aidl.test.ITestCallback.Stub;

public class TestCallback extends Stub {
	TextView tv;
	TestCallback(TextView tv) {
		this.tv = tv;
	}
	@Override
	public void result(double r) throws RemoteException {
		// TODO Auto-generated method stub
		tv.setText("result:"+ r);
	}
}

在主界面protected void onCreate(Bundle savedInstanceState)中,增加

private TestCallback testCallback = null;

在主界面的onCreate函数中,创建testCallback

testCallback = new TestCallback(tv);

在ServiceConnection的onServiceConnected中,增加

testAidl.registerCallback(testCallback);

在ServiceConnection的onServiceDisconnected中,增加

testAidl.unregisterCallback(testCallback);

5.4 服务端实现回调
服务端对Stub的实现

	public class TestAidl extends Stub{
		private final RemoteCallbackList<ITestCallback> mCallbacks = new RemoteCallbackList<ITestCallback>();
		@Override
		public double Add(double a, double b){
			doInBackground(a, b);
			return 0;
		}
		@Override
		public void registerCallback(ITestCallback testCallback){
			if (null != testCallback)
				mCallbacks.register(testCallback);
		}
		@Override
		public void unregisterCallback(ITestCallback testCallback){
			if (null != testCallback)
				mCallbacks.unregister(testCallback);
		}
		class MyThread extends Thread{
			double x;
			double y;
			MyThread(double a, double b) { x=a; y=b; }
			@Override
			public void run(){
				double r = x + y;
				try{
					Thread.sleep(20000);
				}catch(Exception e){
					e.printStackTrace();
				}

				if (mCallbacks==null || mCallbacks.getRegisteredCallbackCount() <=0)
					return;
				int count = mCallbacks.beginBroadcast();
				try{
					for (int i=0; i<count; i++) {
						mCallbacks.getBroadcastItem(i).result(r);
					}
				}catch(RemoteException e) {
					e.printStackTrace();
				}finally{
					mCallbacks.finishBroadcast();
				}
			}			
		}
		public void doInBackground(double a, double b){
			new MyThread(a, b).start();
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值