修改源码在应用启动初期加载Dobby插件
前言
上次分享了,源码集成YAHFA,有个同学私信,怎么Hook so层,作为一个爱分享的人,今天给你解决方案。这次就接着把上次YAHFA框架集成的代码稍微改一下,不用大动,不明白的人,先看一下上次的YAHFA框架集成。
先在Application类,插入加载so的代码
– frameworks/base/core/java/android/app/Application.java在上次的基础上,插几行代码即可 /* package */ final void attach(Context context) { attachBaseContext(context); mLoadedApk = ContextImpl.getImpl(context).mPackageInfo; // add start try{ JLog.print_info(“Process-Info”, “Application->attach ProcessName:” + getProcessName()); if (isHook == false){ AntiyHookManager hook = (AntiyHookManager)context.getSystemService(Context.ANTIY_HOOK_SERVICE); hook.getConfig(); if (hook.checkHookDex(getProcessName())){ int flags = context.getApplicationInfo().flags; if (flags > 0 && ((flags&ApplicationInfo.FLAG_SYSTEM)!=1)){ String apkPath = “/data/data/” + context.getPackageName() + “/AntiyPlugin.apk”; String soPath = “/data/data/” + context.getPackageName() + “/AntiyPlugin.so”; AntiyHook.moveApkToData(hook.hookdex.pluginPath, apkPath); AntiyHook.unZipApkSoToData(hook.hookdex.pluginPath, soPath); AntiyHook.startHook(context.getClassLoader(), apkPath, soPath); isHook = true; } } } } catch(Exception e){ Log.e(“AntiyHook_Application”, e.toString()); } // add end }
AntiyHook类在上次帖子的基础上,稍加修改,加一个函数即可(Antiy暴露了我的东家)
package android.app;import android.content.Context;import android.os.Build;import android.os.Environment;import android.util.Log;import java.io.File;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import java.io.OutputStream;import java.lang.reflect.Constructor;import java.lang.reflect.Method;import java.lang.reflect.Modifier;import java.util.ArrayList;import java.util.Arrays;import java.io.BufferedReader;import java.io.FileInputStream;import java.io.InputStreamReader;import dalvik.system.DexClassLoader;import android.os.FileUtils;import java.util.zip.ZipEntry;import java.util.zip.ZipInputStream;public class AntiyHook { public static final String TAG = “AntiyHook”; static { init(Build.VERSION.SDK_INT); //android 9 = 28 } public static void unZipApkSoToData(String srcApkPath, String dstSoPath){ try { File zipFile = new File(srcApkPath); if(!zipFile.exists()) { return ; } ZipInputStream zin = new ZipInputStream(new FileInputStream(zipFile)); ZipEntry entry; while ( (entry = zin.getNextEntry()) != null){ if ( entry.getName().contains(“.so”)){ if(System.getProperty(“os.arch”).indexOf(“64”) >= 0) { if ( entry.getName().contains(“arm64-v8a”)){ Log.i(TAG, “copy so file :” + entry.getName()); writeSoFile(zin, dstSoPath); } }else{ if ( entry.getName().contains(“armeabi-v7a”)){ Log.i(TAG, “copy so file :” + entry.getName()); writeSoFile(zin, dstSoPath); } } } } zin.closeEntry(); } catch (Exception e) { Log.e(TAG, e.toString()); } } public static void writeSoFile(ZipInputStream zin, String path){ FileOutputStream out = null; try{ File fi = new File(path); if (!fi.exists()) { fi.getParentFile().mkdirs(); fi.createNewFile(); } out = new FileOutputStream(fi); int len; byte[] buffer = new byte[2048]; while ((len = zin.read(buffer)) != -1) { out.write(buffer, 0, len); out.flush(); } } catch (Exception e){ Log.e(TAG, e.toString()); } finally { try{ if (out != null){ out.close(); } } catch (Exception e){ Log.e(TAG, e.toString()); } setPermission(path); } } public static void moveApkToData(String srcFileName, String dstFileName){ deleteDataFile(dstFileName); InputStream in = null; OutputStream out = null; try { in = new FileInputStream(srcFileName); out = new FileOutputStream(dstFileName); byte[] bytes = new byte[1024]; int i; while ((i = in.read(bytes)) != -1) out.write(bytes, 0, i); } catch (IOException e) { Log.e(TAG, e.toString()); } finally { try { if (in != null) in.close(); if (out != null){ out.flush(); out.close(); } } catch (IOException e) { Log.e(TAG, e.toString()); } setPermission(dstFileName); } } public static void setPermission(String path){ try{ File file = new File(path); if (file.exists()){ int perm = FileUtils.S_IRWXU | FileUtils.S_IRWXG | FileUtils.S_IRWXO; FileUtils.setPermissions(path, perm, -1, -1);//将权限改为777 } }catch (Exception e) { Log.e(TAG, e.toString()); } } public static void deleteDataFile(String fileName){ try { File file = new File(fileName); if (file.exists()){ file.delete(); } } catch (Exception e) { Log.e(TAG, e.toString()); } } public static void loadPluginSo(String soPath){ try{ File file = new File(soPath); if (file.exists()){ Log.i(TAG, “load so src:” + soPath); System.load(soPath); file.delete();//用完就删否则不会更新 } } catch (Exception e) { Log.e(TAG, e.toString()); } } public static void startHook(ClassLoader originLoader, String apkPath, String soPath){ try{ Log.i(TAG, “in AntiyHook startHook”); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { DexClassLoader pluginLoader = new DexClassLoader( apkPath, null, null, originLoader); doHookDefault(pluginLoader, originLoader); } loadPluginSo(soPath); } catch (Exception e) { Log.e(TAG, e.toString()); } } public static void doHookDefault(ClassLoader pluginLoader, ClassLoader originLoader) { try { Log.i(TAG, “in AntiyHook doHookDefault”); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) { Class hookInfoClass = Class.forName(“com.antiy.HookInfo”, true, pluginLoader); String[] hookClassNames = (String[]) hookInfoClass.getField(“hookClassNames”).get(null); for (String hookClassName : hookClassNames) { doHookItemDefault(pluginLoader, hookClassName, originLoader); } } } catch (Exception e) { Log.e(TAG, “doHookDefault:” + e.toString()); } } public static void doHookItemDefault(ClassLoader patchClassLoader, String hookItemName, ClassLoader originClassLoader) { try { Log.i(TAG, “in AntiyHook doHookItemDefault:” + hookItemName); Class hookItem = Class.forName(hookItemName, true, patchClassLoader); String className = (String) hookItem.getField(“className”).get(null); String methodName = (String) hookItem.getField(“methodName”).get(null); String methodSig = (String) hookItem.getField(“methodSig”).get(null); if (className == null || className.equals(“”)) { Log.w(TAG, “No target class. Skipping…”); return; } Class clazz = Class.forName(className, true, originClassLoader); if (clazz == null){ Log.e(TAG, “Cannot find class ” + className); return; } Method hook = null; Method backup = null; for (Method method : hookItem.getDeclaredMethods()) { if (method.getName().equals(“hook”) && Modifier.isStatic(method.getModifiers())) { hook = method; } else if (method.getName().equals(“backup”) && Modifier.isStatic(method.getModifiers())) { backup = method; } } if (hook == null) { Log.e(TAG, “Cannot find hook for ” + className + “:” + methodName); return; } // has to visibly init the classes // see the comment for function Utils.initClass() if(initClass() != 0) { Log.e(TAG, “Utils.initClass failed”); } findAndBackupAndHook(clazz, methodName, methodSig, hook, backup); } catch (Exception e) { Log.e(TAG, “doHookItemDefault:” + e.toString()); } } public static void findAndHook(Class targetClass, String methodName, String methodSig, Method hook) { hook(findMethod(targetClass, methodName, methodSig), hook); } public static void findAndBackupAndHook(Class targetClass, String methodName, String methodSig, Method hook, Method backup) { Log.i(TAG, “in AntiyHook findAndBackupAndHook:” + methodName); backupAndHook(findMethod(targetClass, methodName, methodSig), hook, backup); } public static void hook(Object target, Method hook) { backupAndHook(target, hook, null); } public static void backupAndHook(Object target, Method hook, Method backup) { if (target == null) { throw new IllegalArgumentException(“null target method”); } if (hook == null) { throw new IllegalArgumentException(“null hook method”); } if (!Modifier.isStatic(hook.getModifiers())) { throw new IllegalArgumentException(“Hook must be a static method: ” + hook); } checkCompatibleMethods(target, hook, “Original”, “Hook”); if (backup != null) { if (!Modifier.isStatic(backup.getModifiers())) { throw new IllegalArgumentException(“Backup must be a static method: ” + backup); } checkCompatibleMethods(target, backup, “Original”, “Backup”); } if(initClass() != 0) { Log.e(TAG, “initClass failed”); } if (!backupAndHookNative(target, hook, backup)) { throw new RuntimeException(“Failed to hook ” + target + ” with ” + hook); } } private static Object findMethod(Class cls, String methodName, String methodSig) { if (cls == null) { throw new IllegalArgumentException(“null class”); } if (methodName == null) { throw new IllegalArgumentException(“null method name”); } if (methodSig == null) { throw new IllegalArgumentException(“null method signature”); } return findMethodNative(cls, methodName, methodSig); } private static void checkCompatibleMethods(Object original, Method replacement, String originalName, String replacementName) { ArrayList> replacementParams = new ArrayList(Arrays.asList(replacement.getParameterTypes())); if (original instanceof Method && !Modifier.isStatic(((Method) original).getModifiers())) { originalParams.add(0, ((Method) original).getDeclaringClass()); } else if (original instanceof Constructor) { originalParams.add(0, ((Constructor) original).getDeclaringClass()); } if (!Modifier.isStatic(replacement.getModifiers())) { replacementParams.add(0, replacement.getDeclaringClass()); } if (original instanceof Method && !replacement.getReturnType().isAssignableFrom(((Method) original).getReturnType())) { throw new IllegalArgumentException(“Incompatible return types. ” + originalName + “: ” + ((Method) original).getReturnType() + “, ” + replacementName + “: ” + replacement.getReturnType()); } else if (original instanceof Constructor) { if (replacement.getReturnType().equals(Void.class)) { throw new IllegalArgumentException(“Incompatible return types. ” + “” + “: ” + “V” + “, ” + replacementName + “: ” + replacement.getReturnType()); } } if (originalParams.size() != replacementParams.size()) { throw new IllegalArgumentException(“Number of arguments don’t match. ” + originalName + “: ” + originalParams.size() + “, ” + replacementName + “: ” + replacementParams.size()); } for (int i = 0; i < originalParams.size(); i++) { if (!replacementParams.get(i).isAssignableFrom(originalParams.get(i))) { throw new IllegalArgumentException("Incompatible argument #" + i + ": " + originalName + ": " + originalParams.get(i) + ", " + replacementName + ": " + replacementParams.get(i)); } } } private static native boolean backupAndHookNative(Object target, Method hook, Method backup); public static native Object findMethodNative(Class targetClass, String methodName, String methodSig); private static native void init(int sdkVersion); public static int initClass() { if(shouldVisiblyInit()) { long thread = getThread(); return visiblyInit(thread); } else { return 0; } } private static native boolean shouldVisiblyInit(); private static native int visiblyInit(long thread); private static native long getThread();}
然后编译源码刷机
这样在应用启动初期,就会加载含有Dobby框架的so,现在来写插件,在插件中集成Dobby
1 修改CMakefile文件把so名称设置为AntiyPlugin.so
2 新建libs文件夹放入dobby.a静态库(参考:Dobby集成 – 简书)
3 在cpp文件夹下放入dobby头文件
4 编写代码hook open试试
定义函数指针typedef int(*pFunc_Open)(const char *, int , …);pFunc_Open Hook::orgin_open = nullptr; 定义用于替换的函数int fake_open(const char *path, int flag, …){ va_list args; va_start(args, flag); int mode = va_arg(args, int); va_end(args); LOGI(“open hook path -> %s “, path); return orgin_open(path, flag, mode); //调用原函数} 对open函数进行hookvoid hook_open() { void* addr = DobbySymbolResolver(“libc.so”, “open”); if (addr != nullptr){ if (DobbyHook((void*)addr, (void*)(fake_open), (void**)&orgin_open) == RT_SUCCESS){ LOGI(“open hook success”); }else{ LOGI(“open hook failed”); } }}jint JNI_OnLoad(JavaVM *vm, void *reserved) { JNIEnv *env = NULL; jint result = -1; //获取JNIEnv环境 if ((*vm)->GetEnv(vm, (void **)&env, JNI_VERSION_1_4) != JNI_OK) { printf(“JavaVm fail to get JNIEnv\n”); return -1; } … hook_open(); …}
其他就跟上次帖子集成YAHFA时的操作一致,放入config.js, push 插件到/data/local/tmp/就行了
现在来看一下效果:
(上面这个应用会检测调试信息,通过对open函数的hook,也能发现部分踪迹, /proc/self/status文件可以查看是否被调试)