前言
最近在分析某款软件的功能,该软件通过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接口函数,获得远程调用接口。对这个接口,调用内部提供的方法,实现远程计算的功能。
注意:
- 服务端必须运行起来,bindService才能成功。
- 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();
}
}