依然是转载chouchou大神,原帖地址:http://www.cnblogs.com/wanyuanchun/p/3829918.html
这是360的全球招募无线攻防中的第二题,题目要求如下:
1)请以重打包的形式将qihootest2.apk的程序包名改为 "com.qihoo.crack.StubApplication",使得在同一手机上面可以重复安装并正确运行;
2)请写个Application类,并在Manifest里面注册你的Application。同时要求使用该Application加载原包的Application;
题目所用apk下载地址:
http://pan.baidu.com/share/link?shareid=644965540&uk=839654349
首先,我们需要将apk反编译为smali文件。这里推荐使用apkIDE。
显然,哪里用了包名,哪里就需要修改:
①AndroidManifest.xml:package, application name, contentProvider。 ②smali文件中:所有com/qihoo/test改为com/qihoo/crack/StubApplication、所有com.qihoo.test改为com.qihoo.crack.StubApplication。 ③目录结构:将原目录com.qihoo.test改为com.qihoo.crack,然后在这个目录里面新建子目录StubApplication,最后将原来属于test目录的所有文件copy到StubApplication中。 |
至此,在smali中的修改工作就告一段落了。但仅仅这样是不行的,因为在APK中会调用libqihooTest.so中的native函数packageNameCheck()。这个函数是使用动态注册的方式进行注册的,在JNI_OnLoad函数中完成注册功能,使得原APK中的com.qihoo.test.MainActivity.packageNameCheck()同so中的packageNameCheck()函数相关联。我们可以把libqihootest.so拖到ida中查看其中的JNI_OnLoad函数,就可以发现该函数会调用如下JNI方法:
jclass testClass = (*env)->FindClass(env, “com/qihoo/test/Mainactivity”); Findclass的字符串参数使用硬编码写在so中。如果更改后的包名短于原来的包名,那么我们可以使用winhex直接修改这个so,不过这个方法明显不适合于本程序,所以只能另辟蹊径了。 |
前面的分析发现在libqihootest.so中的JNI_OnLoad函数中会调用FindClass(env, “com/qihoo/test/Mainactivity”),而我们更改过后的smali文件中是没有这个类的。所以如果不设法解决这个问题,程序肯定无法正常运行。
分析到此,解决方法就出来了:
1)在原来的smali文件中创建一个test.MainActivity类(注意是在com.qihoo目录下新建目录test,再在test目录下新建MainActivity类),然后将native方法都移植到这一个类中。
2)想法跳过JNI_OnLoad函数:也就是说,我们既需要运行libqihootest.so中的packageNameCheck等native函数,又不运行JNI_OnLoad函数。
我选择第二种。下面来详细分析如何实现第二种方法。
我们知道,一般情况下JNI_OnLoad函数是在使用System.loadLibrary载入so的时候第一个运行的native函数,而如果使用javah方式(静态注册)编写native代码的话,就可以省略JNI_OnLoad函数,所以我们有必要弄清JNI_OnLoad的实现机制。
System.loadLibrary也是一个native方法,它的调用的过程是: Dalvik/vm/native/java_lang_Runtime.cpp: Dalvik_java_lang_Runtime_nativeLoad ->Dalvik/vm/Native.cpp:dvmLoadNativeCode dvmLoadNativeCode 打开函数dvmLoadNativeCode,可以找到以下代码:
handle = dlopen(pathName, RTLD_LAZY); //获得指定库文件的句柄, //这个库文件就是System.loadLibrary(pathName)传递的参数 ….. vonLoad = dlsym(handle, "JNI_OnLoad"); //获取该文件的JNI_OnLoad函数的地址 if (vonLoad == NULL) { //如果找不到JNI_OnLoad,就说明这是用javah风格的代码了,那么就推迟解析 LOGD("No JNI_OnLoad found in %s %p, skipping init",pathName, classLoader); //这句话我们在logcat中经常看见! }else{ …. }
从上面的代码可以看出:System.loadLibrary函数首先会通过dlopen获取so文件的句柄,然后使用dlsym获取该JNI_OnLoad函数的地址,如果该地址为空,就说明没有此函数(这并不是错误)——隐喻就是so库使用javah的编码方式,此时不需要解析so中的函数,而是等java层调用native函数的时候再解析。
|
分析到此,我们就已经找到绕过JNI_OnLoad函数的方法了:参照System.loadLibrary的方式,使用dlopen、dlsym函数直接调用libqihootest.so中的packageNameCheck函数!
C代码如下:
/*callQihooSo.c*/
#include <string.h> #include <stdio.h> #include <jni.h> #include <dlfcn.h> //使用dlopen等函数的头文件 #include <android/log.h>
#define LOG_TAG "360TEST2" #define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
/*这里我直接使用javah的方式编写native代码*/ JNIEXPORT Java_com_qihoo_crack_StubApplication_MainActivity_packageNameCheck( JNIEnv* env, jobject obj){ void* filehandle =dlopen("/data/data/com.qihoo.crack.StubApplication/lib/libqihooTest.so", RTLD_LAZY ); //获取libqihooTest.so的句柄 if(filehandle){ void (*packageNameCheck)(JNIEnv *,jobject); packageNameCheck = (void (*)(JNIEnv *,jobject)) dlsym(filehandle, "packageNameCheck"); //找到.so文件中的函数 if(packageNameCheck){ packageNameCheck(env, obj); //传递参数 } else{ LOGI("get packageNameCheck func failed!"); } LOGI("success!"); }else{ LOGI("get file handle failed!"); } return ; }
JNIEXPORT Java_com_qihoo_crack_StubApplication_MainActivity_applicatioNameCheck( JNIEnv* env, jobject obj){ void* filehandle = dlopen("/data/data/com.qihoo.crack.StubApplication/lib/libqihooTest.so", RTLD_LAZY ); if(filehandle){ void (*applicatioNameCheck)(JNIEnv *,jobject); applicatioNameCheck = (void (*)(JNIEnv *,jobject)) dlsym(filehandle, "applicatioNameCheck"); //找到.so文件中的函数 if(applicatioNameCheck){ applicatioNameCheck(env, obj); //传递参数 return ; } else{ LOGI("get applicatioNameCheck func failed! "); } LOGI("success!"); }else{ LOGI("get file handle failed!"); } return ; } |
Android.mk如下:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_LDLIBS := -L . -ldl -llog #一定要加入ldl这个库,dyopen等函数需要 LOCAL_MODULE := callQihooSo include $(BUILD_SHARED_LIBRARY) |
接着像正常编译动态库文件一样编译。编译完成后将libcallQihooSo.so和libqihooTest.so一起放到反编译文件夹的lib/armeabi目录中,然后将MainAcitivity.smali中的System.loadLibrary(“qihooTest”),改为System.loadLibrary(“callQihooSo”),回编译、签名即可。
第一种方法个人觉得实用性不高,所以就不加以详细介绍了。第二种方法本质上就是一个调用第三方库的问题。只是有一点不同的就是:一般情况下调用第三方库需要在java层使用System.loadLibrary将第三方库文件加载到内存中,然后就可以直接使用第三方库中的函数,而不需要dlopen等函数了(详情参考http://blog.csdn.net/jiuyueguang/article/details/9450597)。
但本题是不能使用System.loadLibrary加载libqihooTest.so的,所以只能使用dlopen机制实现了。
主要原理就是参考文档:http://blogs.360.cn/blog/proxydelegate-application/
该文档介绍了Proxy/delegation Application框架的原理和实现。这里详细地描述下它的实现过程。
创建该工程的目的是为了得到实现这个框架的smali文件(反编译此apk),然后将相关的smali文件添加到题目apk反编译出来的smali文件夹的合适位置(避免我们直接写smali文件,减少工作量)。所以,为了方便文件的移植,我们新建工程的包名命名为“com.qihoo.crack.StubApplication”,工程的结构图如下图所示:
首先,创建一个ProxyApplication类:
package com.qihoo.crack.StubApplication;
import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList;
import android.app.Application; import android.content.Context; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.text.InputFilter.AllCaps; import android.util.Log;
public abstract class ProxyApplication extends Application{ protected abstract void initProxyApplication(); private static Context pContext = null; //保存ProxyApp的mContext,后面有用 private static String TAG = "proxy"; @Override public void onCreate() { // TODO Auto-generated method stub super.onCreate(); String className = "android.app.Application"; //默认的Application名 String key = "DELEGATE_APPLICATION_CLASS_NAME"; try { ApplicationInfo appInfo = getPackageManager().getApplicationInfo(super.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = appInfo.metaData; if(bundle != null && bundle.containsKey(key)){ className = bundle.getString(key); if(className.startsWith(".")){ className = super.getPackageName() + className; } }
Class delegateClass = Class.forName(className, true, getClassLoader()); Application delegate = (Application) delegateClass.newInstance();
//获取当前Application的applicationContext Application proxyApplication = (Application)getApplicationContext();
/*使用反射一一替换proxyApplicationContext,这是本程序的重难点*/ //首先更改proxy的mbaseContext中的成员mOuterContext Class contextImplClass = Class.forName("android.app.ContextImpl"); Field mOuterContext = contextImplClass.getDeclaredField("mOuterContext"); mOuterContext.setAccessible(true); mOuterContext.set(pContext, delegate);
//再获取context的mPackageInfo变量对象 Field mPackageInfoField = contextImplClass.getDeclaredField("mPackageInfo"); mPackageInfoField.setAccessible(true); Object mPackageInfo = mPackageInfoField.get(pContext); Log.d(TAG, "mPackageInfo: "+ mPackageInfo);
//修改mPackageInfo中的成员变量mApplication Class loadedApkClass = Class.forName("android.app.LoadedApk"); //mPackageInfo是android.app.LoadedApk类 Field mApplication = loadedApkClass.getDeclaredField("mApplication"); mApplication.setAccessible(true); mApplication.set(mPackageInfo, delegate);
//然后再获取mPackageInfo中的成员对象mActivityThread Class activityThreadClass = Class.forName("android.app.ActivityThread"); Field mAcitivityThreadField = loadedApkClass.getDeclaredField("mActivityThread"); mAcitivityThreadField.setAccessible(true); Object mActivityThread = mAcitivityThreadField.get(mPackageInfo);
//设置mActivityThread对象中的mInitialApplication Field mInitialApplicationField = activityThreadClass.getDeclaredField("mInitialApplication"); mInitialApplicationField.setAccessible(true); mInitialApplicationField.set(mActivityThread, delegate);
//最后是mActivityThread对象中的mAllApplications,注意这个是List Field mAllApplicationsField = activityThreadClass.getDeclaredField("mAllApplications"); mAllApplicationsField.setAccessible(true); ArrayList<Application> al = (ArrayList<Application>)mAllApplicationsField.get(mActivityThread); al.add(delegate); al.remove(proxyApplication);
//设置baseContext并调用onCreate Method attach = Application.class.getDeclaredMethod("attach", Context.class); attach.setAccessible(true); attach.invoke(delegate, pContext); delegate.onCreate();
} catch (NameNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ClassNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InstantiationException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalAccessException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchFieldException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (NoSuchMethodException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IllegalArgumentException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (InvocationTargetException e) { // TODO Auto-generated catch block e.printStackTrace(); } }
@Override public String getPackageName() { // TODO Auto-generated method stub return "Learning And Sharing!"; } @Override protected void attachBaseContext(Context base) { // TODO Auto-generated method stub super.attachBaseContext(base); pContext = base; Log.d(TAG, "attachBaseContext"); initProxyApplication(); } } |
这个代码是严格按照参考文档的框架写的。所以应当参照该文档阅读这些代码。这里主要说一说我在替换API层所有Application引用时遇到的困难。
由于我起先并不了解Android的context相关知识,所以对这一块完全是云里雾里。给大牛们留过小字条,也写过邮件,不过,大牛们都比较忙,所以一直没能得到解答。直到前段时间,请教了群里的“沧海一声呵”朋友(他才大一,你敢信?!!),才得到解决。
以下部分大牛们可以略过啦,现假设读者也同我一样是个android初学者。那么,要想理解和解决“替换API层的所有Application引用”,我们必须深刻理解android的Context机理。这方面的资料可以参考:
http://blog.csdn.net/qinjuning/article/details/7310620
以及我的另一篇博文:
http://www.cnblogs.com/wanyuanchun/p/3828603.html
当然,仅仅这篇文档,是不能让我们完全理解context的,我们还需要通过自己阅读分析Android关于context的源码来加以理解。比如在上面的代码中有一句:
//修改mPackageInfo中的成员变量mApplication Class loadedApkClass = Class.forName("android.app.LoadedApk"); //mPackageInfo是android.app.LoadedApk类
|
如果我们不阅读源码的话,是不可能知道mPackageInfo是android.app.LoadedApk类,而非想当然的android.app.PackageInfo类。
好了,由于篇幅有限,就不过多延伸了。下面继续介绍框架实现。
ProxyApplication类完成之后,就是编写MyProxyApplication类了。该类继承至ProxyApplication,代码很简单:
package com.qihoo.crack.StubApplication;
import android.app.Application; import android.content.Context; import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.os.Bundle; import android.util.Log;
public class MyProxyApplication extends ProxyApplication{ @Override protected void initProxyApplication() { // TODO Auto-generated method stub //在这里替换surrounding,实现自定义的classloader等功能 Log.d("proxy", "initProxyApplication"); } } |
由于题目只是要求加载Delegation Application,所以我们只在initProxyApplication函数中打印log即可。
最后就是修改AndroidManifest.xml文档了,修改后的文档为:
<application android:name="com.qihoo.crack.StubApplication.MyProxyApplication" android:allowBackup="true" android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:theme="@style/AppTheme" > <meta-data android:name="DELEGATE_APPLICATION_CLASS_NAME" android:value="com.qihoo.crack.StubApplication" > #注意,这里一定要填写正确,否则当我们检测当前application的时候,就会发现得到的application要么是默认的,要么是MyProxyApplication! </meta-data> <activity android:name="com.qihoo.crack.StubApplication.MainActivity" android:label="@string/app_name" > <intent-filter> <action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> </application> |
到此我们的proxyDemo APK已经编写完毕,将其打包成APK之后,反编译这个APK,然后提取出里面的MyProxyApplication.smali和ProxyApplication.smali文档,放到题目APK的smali/com/qihoo/crack/StubApplication目录中。再按照同样的方式修改题目APK的AndroidManifest.xml,编译、签名,生成APK即可。
最终效果图如下:
注意:第二个图,是错误的!正确的显示结果应该是com.qihoo.crack.StubApplication!错误原因是由于我当时在更改AndroidManifest.xml的时候,将META-DATA里面的value值写错了~~详情可见上面红字部分。
根据我个人的理解,此题第二问的应用范围还是很广的,如下文提及的APK加壳方案:
http://blog.csdn.net/androidsecurity/article/details/8678399
OK,技术方面就说到这里,作为一个初学者,我想谈谈一点技术之外的话题。
众所周知,解决一个问题,并不是唯一的目的,通过解决问题来学习知识才是我们追求的目标。同样的,我们在分享自己解决某个问题的方法技巧时,最好多花点时间叙述“我为什么要这么做”,而不是仅仅提及“我用什么方法解决了什么问题”。因为只有这样,才能做到真正的知识分享,我们才能向国外那样拥有很好的学习氛围(这个大家应该是深有体会吧~~)。所以我在这里厚颜代表广大的初学者们向各位大牛请求:在分享方法技术的时候,请多花点时间讲解“我为什么要这么做”,以及“该如何学到这方面的知识”吧!对于你们来说可能会耗费半小时的时间,但对新手来说可能就是半个月都不止了….