前言
上次分享了,源码集成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<Class<?>> originalParams;
if (original instanceof Method) {
originalParams = new ArrayList<>(Arrays.asList(((Method) original).getParameterTypes()));
} else if (original instanceof Constructor) {
originalParams = new ArrayList<>(Arrays.asList(((Constructor<?>) original).getParameterTypes()));
} else {
throw new IllegalArgumentException("Type of target method is wrong");
}
ArrayList<Class<?>> 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. " + "<init>" + ": " + "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函数进行hook
void 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文件可以查看是否被调试)