// 这是context赋值 @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); try { // 创建两个文件夹payload_odex、payload_lib,私有的,可写的文件目录 File odex = this.getDir(“payload_odex”, MODE_PRIVATE); File libs = this.getDir(“payload_lib”, MODE_PRIVATE); odexPath = odex.getAbsolutePath(); libPath = libs.getAbsolutePath(); apkFileName = odex.getAbsolutePath() + “/payload.apk”; File dexFile = new File(apkFileName); Log.i(“demo”, “apk size:”+dexFile.length()); if (!dexFile.exists()) { dexFile.createNewFile(); //在payload_odex文件夹内,创建payload.apk // 读取程序classes.dex文件 byte[] dexdata = this.readDexFileFromApk();
// 分离出解壳后的apk文件已用于动态加载 this.splitPayLoadFromDex(dexdata); } // 配置动态加载环境 Object currentActivityThread = RefInvoke.invokeStaticMethod( “android.app.ActivityThread”, “currentActivityThread”, new Class[] {}, new Object[] {});//获取主线程对象 String packageName = this.getPackageName();//当前apk的包名 ArrayMap mPackages = (ArrayMap) RefInvoke.getFieldOjbect( “android.app.ActivityThread”, currentActivityThread, “mPackages”); WeakReference wr = (WeakReference) mPackages.get(packageName); // 创建被加壳apk的DexClassLoader对象 加载apk内的类和本地代码(c/c++代码) DexClassLoader dLoader = new DexClassLoader(apkFileName, odexPath, libPath, (ClassLoader) RefInvoke.getFieldOjbect( “android.app.LoadedApk”, wr.get(), “mClassLoader”)); //把当前进程的mClassLoader设置成了被加壳apk的DexClassLoader RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mClassLoader”, wr.get(), dLoader);
Log.i(“demo”,“classloader:”+dLoader); try{ Object actObj = dLoader.loadClass(“com.example.sourceapk.MainActivity”); Log.i(“demo”, “actObj:”+actObj); }catch(Exception e){ Log.i(“demo”, “activity:”+Log.getStackTraceString(e)); } } catch (Exception e) { Log.i(“demo”, “error:”+Log.getStackTraceString(e)); e.printStackTrace(); } }
这里需要注意的一个问题,就是我们需要找到一个时机,就是在壳程序还没有运行起来的时候,来加载源程序的APK,执行它的onCreate方法,那么这个时机不能太晚,不然的话,就是运行壳程序,而不是源程序了。查看源码我们知道。Application中有一个方法:attachBaseContext这个方法,它在Application的onCreate方法执行前就会执行了,所以我们的工作就需要在这里进行。 A) 从APK中获取到DEX文件
/**
- 从apk包里面获取dex文件内容(byte)
- @return
- @throws IOException
*/ private byte[] readDexFileFromApk() throws IOException { ByteArrayOutputStream dexByteArrayOutputStream = new ByteArrayOutputStream(); ZipInputStream localZipInputStream = new ZipInputStream( new BufferedInputStream(new FileInputStream( this.getApplicationInfo().sourceDir))); while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry(); if (localZipEntry == null) { localZipInputStream.close(); break; } if (localZipEntry.getName().equals(“classes.dex”)) { byte[] arrayOfByte = new byte[1024]; while (true) { int i = localZipInputStream.read(arrayOfByte); if (i == -1) break; dexByteArrayOutputStream.write(arrayOfByte, 0, i); } } localZipInputStream.closeEntry(); } localZipInputStream.close(); return dexByteArrayOutputStream.toByteArray(); }
B) 从壳程序DEX中得到源程序APK文件
/**
- 释放被加壳的apk文件,so文件
- @param data
- @throws IOException
*/ private void splitPayLoadFromDex(byte[] apkdata) throws IOException { int ablen = apkdata.length; //取被加壳apk的长度 这里的长度取值,对应加壳时长度的赋值都可以做些简化 byte[] dexlen = new byte[4]; System.arraycopy(apkdata, ablen - 4, dexlen, 0, 4); ByteArrayInputStream bais = new ByteArrayInputStream(dexlen); DataInputStream in = new DataInputStream(bais); int readInt = in.readInt(); System.out.println(Integer.toHexString(readInt)); byte[] newdex = new byte[readInt]; //把被加壳的源程序apk内容拷贝到newdex中 System.arraycopy(apkdata, ablen - 4 - readInt, newdex, 0, readInt); //这里应该加上对于apk的解密操作,若加壳是加密处理的话
// 对源程序Apk进行解密 newdex = decrypt(newdex);
// 写入apk文件 File file = new File(apkFileName); try { FileOutputStream localFileOutputStream = new FileOutputStream(file); localFileOutputStream.write(newdex); localFileOutputStream.close(); } catch (IOException localIOException) { throw new RuntimeException(localIOException); }
// 分析被加壳的apk文件 ZipInputStream localZipInputStream = new ZipInputStream( new BufferedInputStream(new FileInputStream(file))); while (true) { ZipEntry localZipEntry = localZipInputStream.getNextEntry(); // 这个也遍历子目录 if (localZipEntry == null) { localZipInputStream.close(); break; } // 取出被加壳apk用到的so文件,放到libPath中(data/data/包名/payload_lib) String name = localZipEntry.getName(); if (name.startsWith(“lib/”) && name.endsWith(".so")) { File storeFile = new File(libPath + “/”
- name.substring(name.lastIndexOf(’/’)));
storeFile.createNewFile(); FileOutputStream fos = new FileOutputStream(storeFile); byte[] arrayOfByte = new byte[1024]; while (true) { int i = localZipInputStream.read(arrayOfByte); if (i == -1) break; fos.write(arrayOfByte, 0, i); } fos.flush(); fos.close(); } localZipInputStream.closeEntry(); } localZipInputStream.close(); }
C) 解密源程序APK
//直接返回数据,读者可以添加自己解密方法 private byte[] decrypt(byte[] srcdata) { for(int i=0;i<srcdata.length;i++){ srcdata[i] = (byte)(0xFF ^ srcdata[i]); } return srcdata; }
@Override public void onCreate() { { //loadResources(apkFileName); Log.i(“demo”, “onCreate”); // 如果源应用配置有Appliction对象,则替换为源应用Applicaiton,以便不影响源程序逻辑。 String appClassName = null; try { ApplicationInfo ai = this.getPackageManager() .getApplicationInfo(this.getPackageName(), PackageManager.GET_META_DATA); Bundle bundle = ai.metaData; if (bundle != null && bundle.containsKey(“APPLICATION_CLASS_NAME”)) { appClassName = bundle.getString(“APPLICATION_CLASS_NAME”);//className 是配置在xml文件中的。 } else { Log.i(“demo”, “have no application class name”); return; } } catch (NameNotFoundException e) { Log.i(“demo”, “error:”+Log.getStackTraceString(e)); e.printStackTrace(); } //有值的话调用该Applicaiton Object currentActivityThread = RefInvoke.invokeStaticMethod( “android.app.ActivityThread”, “currentActivityThread”, new Class[] {}, new Object[] {}); Object mBoundApplication = RefInvoke.getFieldOjbect( “android.app.ActivityThread”, currentActivityThread, “mBoundApplication”); Object loadedApkInfo = RefInvoke.getFieldOjbect( “android.app.ActivityThread$AppBindData”, mBoundApplication, “info”); //把当前进程的mApplication 设置成了null RefInvoke.setFieldOjbect(“android.app.LoadedApk”, “mApplication”, loadedApkInfo, null); Object oldApplication = RefInvoke.getFieldOjbect( “android.app.ActivityThread”, currentActivityThread, “mInitialApplication”); //http://www.codeceo.com/article/android-context.html ArrayList mAllApplications = (ArrayList) RefInvoke .getFieldOjbect(“android.app.ActivityThread”, currentActivityThread, “mAllApplications”); mAllApplications.remove(oldApplication); // 删除oldApplication
ApplicationInfo appinfo_In_LoadedApk = (ApplicationInfo) RefInvoke .getFieldOjbect(“android.app.LoadedApk”, loadedApkInfo, “mApplicationInfo”); ApplicationInfo appinfo_In_AppBindData = (ApplicationInfo) RefInvoke .getFieldOjbect(“android.app.ActivityThread$AppBindData”, mBoundApplication, “appInfo”); appinfo_In_LoadedApk.className = appClassName; appinfo_In_AppBindData.className = appClassName; Application app = (Application) RefInvoke.invokeMethod( “android.app.LoadedApk”, “makeApplication”, loadedApkInfo, new Class[] { boolean.class, Instrumentation.class }, new Object[] { false, null }); // 执行 makeApplication(false,null) RefInvoke.setFieldOjbect(“android.app.ActivityThread”, “mInitialApplication”, currentActivityThread, app);
ArrayMap mProviderMap = (ArrayMap) RefInvoke.getFieldOjbect( “android.app.ActivityThread”, currentActivityThread, “mProviderMap”); Iterator it = mProviderMap.values().iterator(); while (it.hasNext()) { Object providerClientRecord = it.next(); Object localProvider = RefInvoke.getFieldOjbect( “android.app.ActivityThread$ProviderClientRecord”, providerClientRecord, “mLocalProvider”); RefInvoke.setFieldOjbect(“android.content.ContentProvider”, “mContext”, localProvider, app); } Log.i(“demo”, “app:”+app); app.onCreate(); } }
直接在壳程序的Application中的onCreate方法中进行就可以了。这里还可以看到是通过AndroidManifest.xml中的meta标签获取源程序APK中的Application对象的。 下面来看一下AndroidManifest.xml文件中的内容:
<applicatio n android:allowBackup=“true” android:icon="@drawable/ic_launcher" android:label="@string/app_name" android:name=“com.example.packapk.ProxyApplication”>
这里我们定义了源程序APK的Application类名。?项目下载
三.运行程序
下面就看看程序的运行步骤:
- 第一步:得到源程序APK文件和壳程序的DEX文件 运行源程序和壳程序项目,之后得到这两个文件(将壳程序的classes.dex文件改名为SourceApk.dex),然后使用加密工具进行加壳。
- 第二步:替换壳程序中的classes.dex文件 我们在第一步中得到加壳之后的classes.dex文件之后,将其与PackApk.apk中的原classes.dex文件替换。
- 第三步:在第二步的时候得到替换之后的PackApk.apk文件,这个文件因为被修改了,所以我们需要重新对它签名,不然运行也是报错的。 签名之后的文件就可以运行了,效果如下:
文件改名为SourceApk.dex),然后使用加密工具进行加壳。
- 第二步:替换壳程序中的classes.dex文件 我们在第一步中得到加壳之后的classes.dex文件之后,将其与PackApk.apk中的原classes.dex文件替换。
- 第三步:在第二步的时候得到替换之后的PackApk.apk文件,这个文件因为被修改了,所以我们需要重新对它签名,不然运行也是报错的。 签名之后的文件就可以运行了,效果如下:
[外链图片转存中…(img-XtvfYlCr-1643027731256)]
|