题目要求
-------------
1.请修改本apk的包名,使得在同一手机上面可以重复安装并正确运行;
2.请写个Application类,并在Manifest里面注册你的Application。同时要求使用该Application加载原包的Application;
3.本题3分,以非重打包的方式达到同等效果的不计分。
-------------
我参考了看雪上的文章,点击查看。
这篇文章记录做这道题的过程的一些细节。
第一问:
首先是第一问,重打包。可以用Notepad++之类的工具把Manifest里的包名全部替换成com.qihoo.crack,smali里的改成com/qihoo/crack,并且把包名对应的smali目录名改掉。重点是so里也有包名。
这个时候第一种方法是保留并删减com/qihoo/test,在smali里只留下这样的内容(对应的smali)就够了:
package com.qihoo.test; import android.app.Activity; public class MainActivity extends Activity { static { System.loadLibrary("qihooTest"); } public native void applicatioNameCheck(); public native void packageNameCheck(); }
然后新建目录com/qihoo/crack,把com/qihoo/test里的东西复制过去,在MainActivity中把
.super Landroid/app/Activity;
改成:
.super Lcom/qihoo/test/MainActivity;
其实这就对应了把MainActivity extends Activity改成了MainActivity extends com.qihoo.test.MainActivity,如此一来就可以直接使用 com.qihoo.test.MainActivity中的函数,不用创建对象(之前我在继承test的MainActivity的同时又用了它的对象来调用它的函数,不知道是不是有影响。。)。
注意要把com.qihoo.test.MainActivity中的方法改成public。
注意,如果把继承改掉,com.qihoo.crack中所有继承自Activity类的方法都要在smali中改成继承这个MainActivity,不然肯定找不到这个方法啊。比如:
invoke-direct {p0}, Landroid/app/Activity;-><init>()V .line 44 const/4 v0, 0x0 iput-object v0, p0, Lcom/qihoo/crack/MainActivity;->contentResolver:Landroid/content/ContentResolver;
上面第一行就得把改成:
invoke-direct {p0}, Lcom/qihoo/test/MainActivity;-><init>()V
因为Landroid/app/Activity根本没有被继承。
一开始我的思路是在Lcom/qihoo/crack/MainActivity中重写一个packageNameCheck函数,这个函数调用Lcom/qihoo/test/MainActivity中的packageNameCheck函数(这个方法好像是可以的,现在不想试了)。
还有个细节:
invoke-direct {p0}, Lcom/qihoo/crack/MainActivity;->packageNameCheck()V
中的invoke-direct需要改成invoke-virtual。原因我在后面的链接里总结了:http://www.cnblogs.com/larrylawrence/p/3985464.html
改smali常常是这样,一定要搞定每个细节,因为错了也没有Hint给你。
OK,至此,第一问(重打包)用第一种方法已经搞定了。如果不会,可以从看雪的那个链接里下载作者rebuilt好的apk反编译一下就一目了然了。
---------------------------------------------------------------------------------------------
下面分析第二种方法搞定第一问。
第二种方法就是原帖里的19楼的朋友提供的方法:
通过自己创建一个与修改后的包名想对应.so文件,再在这个.so文件去调用原来的.so文件中的这两个native函数。
好了,开始写c文件和Android.mk吧。首先在反编译出来的根目录下新建一个文件夹叫做jni,里面有:
CallqihooTest.c:
1 #include<dlfcn.h> 2 #include <jni.h> 3 #include <stdio.h> 4 #include <string.h> 5 6 void 7 Java_com_qihoo_crack_MainActivity_packageNameCheck( JNIEnv* env, 8 jobject obj ) 9 { 10 void* filehandle = dlopen("/data/data/com.qihoo.crack/lib/libqihooTest.so", RTLD_LAZY );//dlopen打开动态链接库 11 if(filehandle) 12 { 13 void (*packageNameCheck)(JNIEnv *,jobject); 14 packageNameCheck = (void (*)(JNIEnv *,jobject)) dlsym(filehandle, "packageNameCheck"); //找到.so文件中的函数 15 if(packageNameCheck) 16 { 17 packageNameCheck(env, obj); //传递参数调用 18 return ; 19 } 20 else 21 { 22 // LOGI("packageNameCheck is null"); 23 } 24 } 25 26 // return (*env)->NewStringUTF(env,"hello guys"); 27 } 28 29 void 30 Java_com_qihoo_crack_MainActivity_applicatioNameCheck( JNIEnv* env, 31 jobject obj ) 32 { 33 void* filehandle = dlopen("/data/data/com.qihoo.crack/lib/libqihooTest.so", RTLD_LAZY ); 34 if(filehandle) 35 { 36 void(*applicatioNameCheck)(JNIEnv *,jobject); 37 applicatioNameCheck = (void (*)(JNIEnv *,jobject)) dlsym(filehandle, "applicatioNameCheck"); 38 if(applicatioNameCheck) 39 { 40 applicatioNameCheck(env, obj); 41 return; 42 } 43 else 44 { 45 // LOGI("packageNameCheck is null"); 46 } 47 } 48 }
这个c文件里面的两个函数返回值都是void(java和jni对void命名一致),要想清楚,函数只是调用另一个so里的方法,没必要返回jstring。
说个题外话,静态注册JNI的函数名虽然长,但是命名规律看清楚之后完全不用借助javah啊,为什么那么多JNI hello world入门教程都要多此一举地大费周章地介绍javah呢(当时为了使用javah特意装了ubuntu折腾了半天,很多人说做技术需要「折腾」,我曾经也这么认为 但现在觉得人的精力是有限的,没必要做那些没什么意义的事情)。
还有个坑的地方,这个applicatioNameCheck仔细看是application是少一个n的,不知道是出题人故意的还是怎么着,反正恶心到我了。
Android.mk:
LOCAL_PATH:= $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := qihooTest LOCAL_SRC_FILES := libqihooTest.so include $(PREBUILT_SHARED_LIBRARY) include $(CLEAR_VARS) LOCAL_SRC_FILES:= CallqihooTest.c LOCAL_C_INCLUDES := /$(JNI_H_INCLUDE) LOCAL_SHARED_LIBRARIES := libutils LOCAL_PRELINK_MODULE := false LOCAL_LDLIBS := -L . -ldl -llog LOCAL_MODULE := CallqihooTest include $(BUILD_SHARED_LIBRARY)
还有一点,在MainActivity.smali里要把调用native方法里的:
invoke-direct {p0}, Lcom/qihoo/crack/MainActivity;->packageNameCheck()V
invoke-direct改成invoke-virtual。否则Logcat会提示NoSuchMethod Error。之前我分析的虚方法和直接方法的区别是「调用的方法在不在这个类里面」在这里好像不适用了,网上也找不到关于这两者的区别,思考一下,为什么加了一个so之后就要改成调用虚方法呢。如果说跟之前那个继承后在子类中调用父类中的方法的例子有什么共同点的话,那就是他们都是通过了一个「媒介」来调用这个方法;前者是经过了父类的媒介来调用so,后者是经过了CallqihooTest.so的媒介来调用qihooTest.so。
还要把MainActivity里的load的lib改成Callqihootest。
另外,还要把两个native方法改成public的,像这样:
.method public native packageNameCheck()V
否则会报错:java.lang.VerifyError。(为什么?)
每次修改c之后的步骤是这样的,ndk-build,把libs中生成的so们复制到lib里,apktoo b,在dist里找到build好的apk用signapk签名,运行。挺繁琐的。不过发现LogCat是可以查看运行错误信息的(原先以为只有ADT中有项目才能看呢),还挺欣慰。
这里提供一个第一问修改完成之后的apk。点击下载。
第二问:
「请写个Application类,并在Manifest里面注册你的Application。同时要求使用该Application加载原包的Application;」
做这题之前我甚至不知道application标签也可以对应一个类,之前开发中从没用过这个。
What is Application
Application和Activity,Service一样是android框架的一个系统组件,当android程序启动时系统会创建一个 application对象,用来存储系统的一些信息。通常我们是不需要指定一个Application的,这时系统会自动帮我们创建,如果需要创建自己 的Application,也很简单创建一个类继承 Application并在manifest的application标签中进行注册(只需要给Application标签增加个name属性把自己的 Application的名字定入即可)。
android系统会为每个程序运行时创建一个Application类的对象且仅创建一个,所以Application可以说是单例 (singleton)模式的一个类.且application对象的生命周期是整个程序中最长的,它的生命周期就等于这个程序的生命周期。因为它是全局 的单例的,所以在不同的Activity,Service中获得的对象都是同一个对象。所以通过Application来进行一些,数据传递,数据共享,数据缓存等操作。
具体可以看这个链接(Click)里的那个ApplicationDemo的源代码,一目了然,从源码里可以看到application是怎么全局存储数据的,而且application的onCreate确实是先于launcher Activity的onCreate方法的(因为先在application里setValue才能在FirstActivity里getValue)。同时,Android4.0之后的任务切换界面把应用划去之后确实是结束进程的,因为划去之后再打开,application的set的Value又变成初始值了。
接下来参考360博客的一篇文章:Android的Proxy/Delegate Application框架。
这篇文章里提到:
ContentProvider:onCreate()调用优先于Application:onCreate()。
在ContentProvider:onCreate()中,我们知道Application:onCreate()还没有运行,但已经可以使用getContext().getApplicationContext()函数获取Application对象,并访问其Context方法。显然,Android的API设计者不能允许此时获取的Application是“残废”的。结论是Application:attachBaseContext()必须要发生在ContentProvider:onCreate()之前,否则API将出现BUG;无论Android的系统版本如何变化,这一点也不能改变。
Application与ContentProvider的初始化次序是这样的:
Application:attachBaseContext()最早执行,然后是ContentProvider:onCreate(),
然后是Application:onCreate()。
声明,下面的代码转载自:http://www.cnblogs.com/wanyuanchun/p/3829918.html
写一个同样包名的apk,
新建一个类ProxyApplication.java:
1 package com.qihoo.crack; 2 3 import java.lang.reflect.Field; 4 import java.lang.reflect.InvocationTargetException; 5 import java.lang.reflect.Method; 6 import java.util.ArrayList; 7 8 import android.app.Application; 9 import android.content.Context; 10 import android.content.pm.ApplicationInfo; 11 import android.content.pm.PackageManager; 12 import android.content.pm.PackageManager.NameNotFoundException; 13 import android.os.Bundle; 14 import android.util.Log; 15 16 public abstract class ProxyApplication extends Application { 17 18 protected abstract void initProxyApplication(); 19 20 private static Context pContext = null; // 保存ProxyApp的mContext,后面有用 21 22 private static String TAG = "proxy"; 23 24 @Override 25 public void onCreate() { 26 27 // TODO Auto-generated method stub 28 29 super.onCreate(); 30 31 String className = "android.app.Application"; // 默认的Application名 32 String key = "DELEGATE_APPLICATION_CLASS_NAME"; 33 34 try { 35 /* (1)获取DelegateApplication的Class Name */ 36 ApplicationInfo appInfo = getPackageManager().getApplicationInfo( 37 super.getPackageName(), PackageManager.GET_META_DATA); 38 Bundle bundle = appInfo.metaData; 39 if (bundle != null && bundle.containsKey(key)) { 40 className = bundle.getString(key); 41 if (className.startsWith(".")) { 42 className = super.getPackageName() + className; 43 } 44 } 45 46 /* (2) 加载DelegateApplication并生成对象 */ 47 Class delegateClass = Class.forName(className, true, 48 getClassLoader()); 49 Application delegate = (Application) delegateClass.newInstance(); 50 51 // 获取当前Application的applicationContext .全局 52 Application proxyApplication = (Application) getApplicationContext(); 53 /* 使用反射一一替换proxyApplicationContext,这是本程序的重难点 */ 54 // 首先更改proxy的mbaseContext中的成员mOuterContext 55 56 Class contextImplClass = Class.forName("android.app.ContextImpl"); 57 58 Field mOuterContext = contextImplClass 59 .getDeclaredField("mOuterContext"); 60 61 mOuterContext.setAccessible(true); 62 63 mOuterContext.set(pContext, delegate); 64 65 // 再获取context的mPackageInfo变量对象 66 // ContextImp的分析可知,其方法的大多数操作都是直接调用其属性mPackageInfo(该属性类型为PackageInfo)的相关方法而来 67 68 Field mPackageInfoField = contextImplClass 69 .getDeclaredField("mPackageInfo"); 70 71 mPackageInfoField.setAccessible(true); 72 73 Object mPackageInfo = mPackageInfoField.get(pContext); 74 75 Log.d(TAG, "mPackageInfo: " + mPackageInfo); 76 77 // 修改mPackageInfo中的成员变量mApplication 78 79 Class loadedApkClass = Class.forName("android.app.LoadedApk"); // mPackageInfo是android.app.LoadedApk类 80 81 Field mApplication = loadedApkClass 82 .getDeclaredField("mApplication"); 83 84 mApplication.setAccessible(true); 85 86 mApplication.set(mPackageInfo, delegate); 87 88 // 然后再获取mPackageInfo中的成员对象mActivityThread 89 90 Class activityThreadClass = Class 91 .forName("android.app.ActivityThread"); 92 93 Field mAcitivityThreadField = loadedApkClass 94 .getDeclaredField("mActivityThread"); 95 96 mAcitivityThreadField.setAccessible(true); 97 98 Object mActivityThread = mAcitivityThreadField.get(mPackageInfo); 99 100 // 设置mActivityThread对象中的mInitialApplication 101 102 Field mInitialApplicationField = activityThreadClass 103 .getDeclaredField("mInitialApplication"); 104 105 mInitialApplicationField.setAccessible(true); 106 107 mInitialApplicationField.set(mActivityThread, delegate); 108 109 // 最后是mActivityThread对象中的mAllApplications,注意这个是List 110 111 Field mAllApplicationsField = activityThreadClass 112 .getDeclaredField("mAllApplications"); 113 114 mAllApplicationsField.setAccessible(true); 115 116 ArrayList<Application> al = (ArrayList<Application>) mAllApplicationsField 117 .get(mActivityThread); 118 119 al.add(delegate); 120 121 al.remove(proxyApplication); 122 123 // 设置baseContext并调用onCreate 124 125 Method attach = Application.class.getDeclaredMethod("attach", 126 Context.class); 127 128 attach.setAccessible(true); 129 130 attach.invoke(delegate, pContext); 131 132 delegate.onCreate(); 133 134 } catch (NameNotFoundException e) { 135 136 // TODO Auto-generated catch block 137 138 e.printStackTrace(); 139 140 } catch (ClassNotFoundException e) { 141 142 // TODO Auto-generated catch block 143 144 e.printStackTrace(); 145 146 } catch (InstantiationException e) { 147 148 // TODO Auto-generated catch block 149 150 e.printStackTrace(); 151 152 } catch (IllegalAccessException e) { 153 154 // TODO Auto-generated catch block 155 156 e.printStackTrace(); 157 158 } catch (NoSuchFieldException e) { 159 160 // TODO Auto-generated catch block 161 162 e.printStackTrace(); 163 164 } catch (NoSuchMethodException e) { 165 166 // TODO Auto-generated catch block 167 168 e.printStackTrace(); 169 170 } catch (IllegalArgumentException e) { 171 172 // TODO Auto-generated catch block 173 174 e.printStackTrace(); 175 176 } catch (InvocationTargetException e) { 177 178 // TODO Auto-generated catch block 179 180 e.printStackTrace(); 181 182 } 183 184 } 185 186 @Override 187 public String getPackageName() { 188 189 // TODO Auto-generated method stub 190 191 return ""; 192 193 } 194 195 @Override//子类里覆写,父类(Application)引用指向子类对象,在父类里调用这个被覆写的函数。 196 //attachBaseContext不在父类Application里,而是在Application的父类ContextWrapper里。 197 protected void attachBaseContext(Context base) { 198 199 // TODO Auto-generated method stub 200 201 super.attachBaseContext(base); 202 203 pContext = base; 204 205 Log.d(TAG, "attachBaseContext"); 206 207 initProxyApplication(); 208 209 } 210 211 212 }
再新建一个MyProxyApplication.java:
package com.qihoo.crack; import android.util.Log; public class MyProxyApplication extends ProxyApplication { @Override protected void initProxyApplication() { // TODO Auto-generated method stub // 在这里替换surrounding,实现自定义的classloader等功能 Log.d("proxy", "initProxyApplication"); } //Application:attachBaseContext()最早执行,也就是说系统自动执行这个类中的attachBaseContext()方法(也就是父类中的), //而父类ProxyApplication中重写了Application中的attachBaseContext()方法。 //Application与ContentProvider的初始化次序是这样的: //Application:attachBaseContext()最早执行,然后是ContentProvider:onCreate(), //然后是Application:onCreate()。 //Application:onCreate()也执行父类中的onCreate(); }
AndroidMainifest.xml:
<?xml version="1.0" encoding="utf-8"?> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="com.qihoo.crack" android:versionCode="1" android:versionName="1.0" > <uses-sdk android:minSdkVersion="8" android:targetSdkVersion="21" /> <application android:name="com.qihoo.crack.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" > </meta-data> <activity android:name=".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> </manifest>
这样的话,反编译一下,把ProxyApplication.smali和MyProxyApplication.smali拿到之前的文件夹里,再修改一下Manifest打包即可。
最终的apk点击这里下载。
还有一点不是很清楚,这样做的话我点击「查看当前Application」的按钮,还是显示com.qihoo.crack.StubApplication,按照WanChouChou的说法,就是应该显示StubApplication;也就是说这样就是为了「骗过」系统检测让它以为没检测,其实Application已经换过了?
试了一下,如果想让按钮提示别的,只要批量替换StubApplication的字符串为想要修改的字符串就行了。
而如果把meta-data标签去掉了,Application name中仍然是MyProxyApplication,那就会显示系统默认的Android.app.Application,说明MyProxyApplication.java没有被识别成一个正规的Application文件(因为MyProxyApplication继承的ProxyApplication中,说了如果找不到key,就使用默认的Application)。
这道题断断续续做了一个月了,到现在虽然还有些不懂,也算告一段落。好吧,到这里。十一结束,接下来论文要开题了。
Oct 7th 2014