本次尝试在Android-9.0.0_r10中集成Hook框架
- 创建hook服务进行hook配置的管理
创建接口文件 /frameworks/base/core/java/android/app/antiy/IAntiyHook.aidl
package android.app.antiy;
interface IAntiyHook
{
String readConfig();
String readFile(String path);
void writeFile(String path,String data);
}
- 添加接口到编译链 /frameworks/base/Android.bp
"core/java/android/app/antiy/IAntiyHook.aidl",
- 创建服务 /frameworks/base/services/core/java/com/android/server/AntiyHookServer.java
package com.android.server;
import android.content.Context;
import android.util.Log;
import android.app.antiy.IAntiyHook;
import java.io.File;
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.RandomAccessFile;
public class AntiyHookService extends IAntiyHook.Stub{
public final String TAG = "AntiyHookService";
public Context mContext;
public AntiyHookService(Context c){
mContext = c;
}
/*
* read hook info
*/
@Override
public String readConfig(){
try {
return readFile("/sdcard/antiy/config.js");
}catch (Exception e) {
Log.e(TAG, e.toString());
}
return null;
}
@Override
public String readFile(String path){
try{
return readFileAll(path);
}catch (Exception e) {
Log.e(TAG, e.toString());
}
return null;
}
@Override
public void writeFile(String path,String data){
try{
writeTxtToFile(data,path);
}catch (Exception e) {
Log.e(TAG, e.toString());
}
}
public String readFileAll(String path) {
File file = new File(path);
StringBuilder sb=new StringBuilder();
if (file != null && file.exists()) {
InputStream inputStream = null;
BufferedReader bufferedReader = null;
try {
inputStream = new FileInputStream(file);
bufferedReader = new BufferedReader(new InputStreamReader(
inputStream));
String outData;
while((outData=bufferedReader.readLine())!=null){
sb.append(outData+"\n");
}
} catch (Throwable t) {
} finally {
try {
if (bufferedReader != null) {
bufferedReader.close();
}
} catch (Exception e) {
Log.e(TAG, "readFileAll" + e.toString());
}
try {
if (inputStream != null) {
inputStream.close();
}
} catch (Exception e) {
Log.e(TAG, "readFileAll" + e.toString());
}
}
}
return sb.toString();
}
// make dir
public void makeRootDirectory(String filePath) {
File file = null;
try {
Log.d("FileHelper", "makeRootDirectory "+filePath);
file = new File(filePath);
if (!file.exists()) {
boolean isok= file.mkdir();
Log.d("FileHelper", "makeRootDirectory "+filePath+" "+isok);
}
} catch (Exception e) {
Log.e(TAG, e+"");
}
}
// make fike
public File makeFilePath(String filePath, String fileName) {
File file = null;
makeRootDirectory(filePath);
try {
file = new File(filePath +"/"+ fileName);
if (!file.exists()) {
file.createNewFile();
}
} catch (Exception e) {
e.printStackTrace();
}
return file;
}
public void writeTxtToFile(String strcontent, String filePath) {
String strFilePath = filePath;
String strContent = strcontent + "\n"; // \r\n 结尾会变成 ^M
try {
File file = new File(strFilePath);
makeFilePath(file.getParent(),file.getName());
if (!file.exists()) {
file.getParentFile().mkdirs();
file.createNewFile();
}
RandomAccessFile raf = new RandomAccessFile(file, "rwd");
raf.setLength(0);
// 写文件的位置标记,从文件开头开始,后续读取文件内容从该标记开始
long writePosition = raf.getFilePointer();
raf.seek(writePosition);
raf.write(strContent.getBytes());
raf.close();
} catch (Exception e) {
Log.e(TAG,"Error on write File:" + e);
}
}
}
- 创建服务管理器 /frameworks/base/core/java/android/app/antiy/AntiyHookManager.java
package android.app.antiy;
import android.app.antiy.IAntiyHook;
import android.content.Context;
import android.os.RemoteException;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
public class AntiyHookManager {
public final String TAG = "AntiyHookManager";
public final IAntiyHook mAntiyHook;
public Context mContext;
public HookDex hookdex;
public HookSo hookso;
public AntiyHookManager(Context c, IAntiyHook f){
mAntiyHook = f;
mContext = c;
Log.e(TAG, "AntiyHookManager init" );
}
public class HookDex{
public boolean open = false;
public String[] packageName;
public String pluginPath;
public HookDex(JSONObject js){
try{
open = js.getBoolean("open");
JSONArray names = js.getJSONArray("packageName");
if (names != null){
packageName = new String[names.length()];
for (int i = 0; i < names.length(); i++){
packageName[i] = names.getString(i);
}
}
pluginPath = js.getString("pluginPath");
} catch(Exception e){
Log.e(TAG, "HookDex:" + e.toString());
}
}
}
public class HookSo{
public boolean open = false;
public String[] packageName;
public String pluginPath;
public HookSo(JSONObject js){
try{
open = js.getBoolean("open");
JSONArray names = js.getJSONArray("packageName");
if (names != null){
packageName = new String[names.length()];
for (int i = 0; i < names.length(); i++){
packageName[i] = names.getString(i);
}
}
pluginPath = js.getString("pluginPath");
} catch(Exception e){
Log.e(TAG, "HookSo:" + e.toString());
}
}
}
public void getConfig() throws RemoteException, JSONException {
try{
String conf = mAntiyHook.readConfig();
Log.i(TAG, "getConfig -> " + conf);
JSONObject arr = new JSONObject(conf);
hookdex = new HookDex(arr.getJSONObject("hookDex"));
Log.i(TAG, String.valueOf(hookdex.open));
if (hookdex.packageName != null){
for (int i = 0; i < hookdex.packageName.length; i++){
Log.i(TAG, hookdex.packageName[i]);
}
}
Log.i(TAG, hookdex.pluginPath.toString());
hookso = new HookSo(arr.getJSONObject("hookSo"));
Log.i(TAG, String.valueOf(hookso.open));
if (hookso.packageName != null){
for (int i = 0; i < hookso.packageName.length; i++){
Log.i(TAG, hookso.packageName[i]);
}
}
Log.i(TAG, hookso.pluginPath.toString());
} catch (Exception e){
Log.e(TAG, "getConfig" + e.toString());
}
}
public boolean checkHookDex(String processName){
try{
if (processName != null){
if (hookdex.open){
if (hookdex.packageName != null){
for (String s:hookdex.packageName){
if (processName.contains(s)){
Log.i(TAG, "hook -> " + String.valueOf(processName));
return true;
}
}
}
}
}
}catch (Exception e){
Log.e(TAG, "checkHookDex:" + e.toString());
}
return false;
}
}
- 注册服务并绑定服务到服务管理器
添加服务名称 /frameworks/base/core/java/android/content/Context.java
public static final String ANTIY_HOOK_SERVICE = "antiy_hook";
- 注册服务 /frameworks/base/services/java/com/android/server/SystemServer.java
traceBeginAndSlog("StartAntiyHookService");
AntiyHookService antiyhookservice= new AntiyHookService(context);
ServiceManager.addService(Context.ANTIY_HOOK_SERVICE, antiyhookservice);
traceEnd();
- 绑定服务 /frameworks/base/core/java/android/app/SystemServiceRegistry.java
registerService(Context.ANTIY_HOOK_SERVICE, AntiyHookManager.class,
new CachedServiceFetcher<AntiyHookManager>() {
@Override
public AntiyHookManager createService(ContextImpl ctx) throws ServiceNotFoundException {
IBinder b = ServiceManager.getService(Context.ANTIY_HOOK_SERVICE);
return b == null ? null : new AntiyHookManager(ctx, IAntiyHook.Stub.asInterface(b));
}});
- 创建hook框架实现类 /frameworks/base/core/java/android/app/AntiyHook.java
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;
public class AntiyHook {
public static final String TAG = "AntiyHook";
static {
init(Build.VERSION.SDK_INT); //android 9 = 28
}
public static void moveToData(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 startHook(ClassLoader originLoader, String path){
try{
Log.i(TAG, "in AntiyHook startHook");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.CUPCAKE) {
DexClassLoader pluginLoader = new DexClassLoader(
path, null, null, originLoader);
doHookDefault(pluginLoader, originLoader);
}
} 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, 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, 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) {
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();
}
- 创建hook框架类native实现 /frameworks/base/core/jni/android_app_AntiyHook.cpp
#include <nativehelper/JNIHelp.h>
#include "jni.h"
#include <utils/Log.h>
#include <android/log.h>
#include <stdint.h>
#include <sys/mman.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <stdlib.h>
#include <dlfcn.h>
#define NEED_CLASS_VISIBLY_INITIALIZED
#define pointer_size sizeof(void*)
#define roundUpToPtrSize(v) (v + pointer_size - 1 - ((v + pointer_size - 1) & (pointer_size - 1)))
#define read32(addr) *((uint32_t *)(addr))
#define write32(addr, value) *((uint32_t *)(addr)) = (value)
#define readAddr(addr) *((void **)(addr))
#define writeAddr(addr, value) *((void **)(addr)) = (value)
#define TRAMPOLINE_SPACE_SIZE 4096 // 4k mem page size
#define HOOK_TAG "AntiyHookService"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO,HOOK_TAG,__VA_ARGS__)
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN,HOOK_TAG,__VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,HOOK_TAG,__VA_ARGS__)
namespace android
{
int SDKVersion;
static uint32_t OFFSET_entry_point_from_interpreter_in_ArtMethod;
static uint32_t OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod;
static uint32_t OFFSET_ArtMehod_in_Object;
static uint32_t OFFSET_access_flags_in_ArtMethod;
static uint32_t kAccNative = 0x0100;
static uint32_t kAccCompileDontBother = 0x01000000;
static unsigned char *currentTrampolineOff = 0;
static unsigned char *trampolineSpaceEnd = 0;
typedef void (*InitClassFunc)(void *, void *, int);
static char *classLinker = NULL;
static InitClassFunc MakeInitializedClassesVisiblyInitialized = NULL;
static const size_t kPointerSize = sizeof(void *);
static const size_t kVTablePosition = 2;
static unsigned char *allocTrampolineSpace();
#if defined(__arm__)
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 20 F0 90 E5 ; ldr pc, [r0, 0x20]
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
unsigned char trampoline[] = {
0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
0x00, 0x00, 0x9f, 0xe5,
0x20, 0xf0, 0x90, 0xe5,
0x78, 0x56, 0x34, 0x12
};
// 0c 00 9F E5 ; ldr r0, [pc, #12]
// 01 00 2d e9 ; push {r0}
// 00 00 9F E5 ; ldr r0, [pc, #0]
// 00 80 bd e8 ; pop {pc}
// 78 56 34 12 ; 0x12345678 (addr of the hook method)
// 78 56 34 12 ; 0x12345678 (original entry point of the target method)
unsigned char trampolineForBackup[] = {
0x0c, 0x00, 0x9f, 0xe5,
0x01, 0x00, 0x2d, 0xe9,
0x00, 0x00, 0x9f, 0xe5,
0x00, 0x80, 0xbd, 0xe8,
0x78, 0x56, 0x34, 0x12,
0x78, 0x56, 0x34, 0x12
};
#elif defined(__aarch64__)
// 60 00 00 58 ; ldr x0, 12
// 10 00 40 F8 ; ldr x16, [x0, #0x00]
// 00 02 1f d6 ; br x16
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
unsigned char trampoline[] = {
0x00, 0x00, 0x00, 0x00, // code_size_ in OatQuickMethodHeader
0x60, 0x00, 0x00, 0x58,
0x10, 0x00, 0x40, 0xf8,
0x00, 0x02, 0x1f, 0xd6,
0x78, 0x56, 0x34, 0x12,
0x89, 0x67, 0x45, 0x23
};
// 60 00 00 58 ; ldr x0, 12
// 90 00 00 58 ; ldr x16, 16
// 00 02 1f d6 ; br x16
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (addr of the hook method)
// 78 56 34 12
// 89 67 45 23 ; 0x2345678912345678 (original entry point of the target method)
unsigned char trampolineForBackup[] = {
0x60, 0x00, 0x00, 0x58,
0x90, 0x00, 0x00, 0x58,
0x00, 0x02, 0x1f, 0xd6,
0x78, 0x56, 0x34, 0x12,
0x89, 0x67, 0x45, 0x23,
0x78, 0x56, 0x34, 0x12,
0x89, 0x67, 0x45, 0x23
};
#endif
void *genTrampoline(void *toMethod, void *entrypoint) {
size_t trampolineSize = entrypoint != NULL ? sizeof(trampolineForBackup) : sizeof(trampoline);
if(currentTrampolineOff+trampolineSize > trampolineSpaceEnd) {
currentTrampolineOff = allocTrampolineSpace();
if (currentTrampolineOff == NULL) {
return NULL;
} else {
trampolineSpaceEnd = currentTrampolineOff + TRAMPOLINE_SPACE_SIZE;
}
}
unsigned char *targetAddr = currentTrampolineOff;
if(entrypoint != NULL) {
memcpy(targetAddr, trampolineForBackup, sizeof(trampolineForBackup));
}
else {
memcpy(targetAddr, trampoline,
sizeof(trampoline));
}
#if defined(__arm__)
if(entrypoint) {
memcpy(targetAddr + 20, &entrypoint, pointer_size);
memcpy(targetAddr + 16, &toMethod, pointer_size);
}
else {
memcpy(targetAddr + 12, &toMethod, pointer_size);
}
#elif defined(__aarch64__)
if(entrypoint) {
memcpy(targetAddr + 20, &entrypoint, pointer_size);
memcpy(targetAddr + 12, &toMethod, pointer_size);
}
else {
memcpy(targetAddr + 16, &toMethod, pointer_size);
}
#endif
if(entrypoint == NULL) {
targetAddr += 4;
}
currentTrampolineOff += roundUpToPtrSize(trampolineSize);
return targetAddr;
}
void setupTrampoline(uint8_t offset) {
#if defined(__arm__)
trampoline[8] = offset;
#elif defined(__aarch64__)
trampoline[9] |= offset << 4;
trampoline[10] |= offset >> 4;
#endif
}
static unsigned char *allocTrampolineSpace() {
unsigned char *buf = (unsigned char *)mmap(NULL, TRAMPOLINE_SPACE_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
MAP_ANONYMOUS | MAP_PRIVATE, -1, 0);
if (buf == MAP_FAILED) {
LOGE("mmap failed, errno = %s", strerror(errno));
return NULL;
}
else {
return buf;
}
}
static int isValidAddress(const void *p) {
if (!p) {
return 0;
}
int ret = 1;
int fd = open("/dev/random", O_WRONLY);
size_t len = sizeof(int32_t);
if (fd != -1) {
if (write(fd, p, len) < 0) {
ret = 0;
}
close(fd);
} else {
ret = 0;
}
return ret;
}
static int commonFindOffset(void *start, size_t max_count, size_t step, void *value, int start_search_offset) {
if (NULL == start) {
return -1;
}
if (start_search_offset > (int)max_count) {
return -1;
}
for (int i = start_search_offset; i <= (int)max_count; i += step) {
void *current_value = *(void **) ((size_t) start + i);
if (value == current_value) {
return i;
}
}
return -1;
}
static int searchClassLinkerOffset(JavaVM *vm, void *runtime_instance, JNIEnv *env, void *libart_handle) {
#ifndef NEED_CLASS_VISIBLY_INITIALIZED
return -1;
#else
void *class_linker_vtable = dlsym(libart_handle, "_ZTVN3art11ClassLinkerE");
if (class_linker_vtable != NULL) {
class_linker_vtable = (char *) class_linker_vtable + kPointerSize * kVTablePosition;
}
int jvm_offset_in_runtime = commonFindOffset(runtime_instance, 2000, 4, (void *) vm, 200);
if (jvm_offset_in_runtime < 0) {
LOGE("failed to find JavaVM in Runtime");
return -1;
}
int step = 4;
int class_linker_offset_value = -1;
for (int i = jvm_offset_in_runtime - step; i > 0; i -= step) {
void *class_linker_addr = *(void **) ((uintptr_t) runtime_instance + i);
if (class_linker_addr == NULL || !isValidAddress(class_linker_addr)) {
continue;
}
if (class_linker_vtable != NULL) {
if (*(void **) class_linker_addr == class_linker_vtable) {
class_linker_offset_value = i;
break;
}
} else {
void *intern_table_addr = *(void **) (
(uintptr_t) runtime_instance +
i -
kPointerSize);
if (isValidAddress(intern_table_addr)) {
for (int j = 200; j < 500; j += step) {
void *intern_table = *(void **) (
(uintptr_t) class_linker_addr + j);
if (intern_table_addr == intern_table) {
class_linker_offset_value = i;
break;
}
}
}
}
if (class_linker_offset_value > 0) {
break;
}
}
return class_linker_offset_value;
#endif
}
static int findInitClassSymbols(JNIEnv *env) {
#ifndef NEED_CLASS_VISIBLY_INITIALIZED
return 1;
#else
JavaVM *jvm = NULL;
env->GetJavaVM(&jvm);
void *handle = dlopen("libart.so", RTLD_LAZY);
if(handle == NULL) {
LOGE("failed to find libart.so handle");
return 1;
}
else {
void *runtime_bss = dlsym(handle, "_ZN3art7Runtime9instance_E");
if(!runtime_bss) {
LOGE("failed to find Runtime::instance symbol");
return 1;
}
char *runtime = (char*)readAddr(runtime_bss);
if(!runtime) {
LOGE("Runtime::instance value is NULL");
return 1;
}
int class_linker_offset_in_Runtime = searchClassLinkerOffset(jvm, runtime, env, handle);
if(class_linker_offset_in_Runtime < 0) {
LOGE("failed to find class_linker offset in Runtime");
return 1;
}
classLinker = (char*)readAddr(runtime + class_linker_offset_in_Runtime);
MakeInitializedClassesVisiblyInitialized = (InitClassFunc)dlsym(handle,
"_ZN3art11ClassLinker40MakeInitializedClassesVisiblyInitializedEPNS_6ThreadEb");
if(!MakeInitializedClassesVisiblyInitialized) {
LOGE("failed to find MakeInitializedClassesVisiblyInitialized symbol");
return 1;
}
}
return 0;
#endif
}
jlong __attribute__((naked)) android_app_AntiyHook_getThread(JNIEnv *env, jclass clazz) {
#if defined(__aarch64__)
__asm__(
"mov x0, x19\n"
"ret\n"
);
#elif defined(__arm__)
__asm__(
"mov r0, r9\n"
"bx lr\n"
);
#endif
}
static int shouldVisiblyInit() {
return 0;
}
jboolean android_app_AntiyHook_shouldVisiblyInit(JNIEnv *env, jclass clazz) {
return shouldVisiblyInit() != 0;
}
jint android_app_AntiyHook_visiblyInit(JNIEnv *env, jclass clazz, jlong thread) {
if(!shouldVisiblyInit()) {
return 0;
}
if(!classLinker || !MakeInitializedClassesVisiblyInitialized) {
if(findInitClassSymbols(env) != 0) {
LOGE("failed to find symbols: classLinker %p, MakeInitializedClassesVisiblyInitialized %p",
classLinker, MakeInitializedClassesVisiblyInitialized);
return 1;
}
}
MakeInitializedClassesVisiblyInitialized(classLinker, (void *)thread, 1);
return 0;
}
void android_app_AntiyHook_init(JNIEnv *env, jclass clazz, jint sdkVersion) {
SDKVersion = sdkVersion;
kAccCompileDontBother = 0x02000000;
OFFSET_ArtMehod_in_Object = 0;
OFFSET_access_flags_in_ArtMethod = 4;
OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod = roundUpToPtrSize(4 * 4 + 2 * 2) + pointer_size;
setupTrampoline(OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
}
static uint32_t getFlags(char *method) {
uint32_t access_flags = (uint32_t)read32(method + OFFSET_access_flags_in_ArtMethod);
return access_flags;
}
static void setFlags(char *method, uint32_t access_flags) {
write32(method + OFFSET_access_flags_in_ArtMethod, access_flags);
}
static void setNonCompilable(void *method) {
uint32_t access_flags = getFlags((char*)method);
access_flags |= kAccCompileDontBother;
setFlags((char*)method, access_flags);
}
static int replaceMethod(void *fromMethod, void *toMethod, int isBackup) {
void *newEntrypoint = NULL;
if(isBackup) {
void *originEntrypoint = readAddr((char *) toMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod);
newEntrypoint = genTrampoline(toMethod, originEntrypoint);
}
else {
newEntrypoint = genTrampoline(toMethod, NULL);
}
if (newEntrypoint) {
writeAddr((char *) fromMethod + OFFSET_entry_point_from_quick_compiled_code_in_ArtMethod,
newEntrypoint);
} else {
LOGE("failed to allocate space for trampoline of target method");
return 1;
}
if (OFFSET_entry_point_from_interpreter_in_ArtMethod != 0) {
void *interpEntrypoint = readAddr((char *) toMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod);
writeAddr((char *) fromMethod + OFFSET_entry_point_from_interpreter_in_ArtMethod,
interpEntrypoint);
}
if(SDKVersion >= __ANDROID_API_O__) {
uint32_t access_flags = getFlags((char*)fromMethod);
access_flags |= kAccNative;
setFlags((char*)fromMethod, access_flags);
}
return 0;
}
static int doBackupAndHook(void *targetMethod, void *hookMethod, void *backupMethod) {
int res = 0;
setNonCompilable(targetMethod);
if(backupMethod) setNonCompilable(backupMethod);
if (backupMethod) {
res += replaceMethod(backupMethod, targetMethod, 1);
}
res += replaceMethod(targetMethod, hookMethod, 0);
return res;
}
static void *getArtMethod(JNIEnv *env, jobject jmethod) {
void *artMethod = NULL;
if(jmethod == NULL) {
return artMethod;
}
artMethod = (void *) env->FromReflectedMethod(jmethod);
return artMethod;
}
jboolean android_app_AntiyHook_backupAndHookNative(JNIEnv *env, jclass clazz,
jobject target, jobject hook,
jobject backup) {
if (!doBackupAndHook(
getArtMethod(env, target),
getArtMethod(env, hook),
getArtMethod(env, backup)
)) {
env->NewGlobalRef(hook);
if(backup) env->NewGlobalRef(backup);
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
jobject android_app_AntiyHook_findMethodNative(JNIEnv *env, jclass clazz,
jclass targetClass, jstring methodName,
jstring methodSig) {
const char *c_methodName = env->GetStringUTFChars(methodName, NULL);
const char *c_methodSig = env->GetStringUTFChars(methodSig, NULL);
jobject ret = NULL;
jmethodID method = env->GetMethodID(targetClass, c_methodName, c_methodSig);
if (!env->ExceptionCheck()) {
ret = env->ToReflectedMethod(targetClass, method, JNI_FALSE);
} else {
env->ExceptionClear();
method = env->GetStaticMethodID(targetClass, c_methodName, c_methodSig);
if (!env->ExceptionCheck()) {
ret = env->ToReflectedMethod(targetClass, method, JNI_TRUE);
} else {
env->ExceptionClear();
}
}
env->ReleaseStringUTFChars(methodName, c_methodName);
env->ReleaseStringUTFChars(methodSig, c_methodSig);
return ret;
}
static const JNINativeMethod sMethods[] = {
/* name, signature, funcPtr */
{"backupAndHookNative", "(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z", (void*)android_app_AntiyHook_backupAndHookNative},
{"findMethodNative", "(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", (void*)android_app_AntiyHook_findMethodNative},
{"init", "(I)V", (void*)android_app_AntiyHook_init},
{"shouldVisiblyInit", "()Z", (void*)android_app_AntiyHook_shouldVisiblyInit},
{"visiblyInit", "(J)I", (void*)android_app_AntiyHook_visiblyInit},
{"getThread", "()J", (void*)android_app_AntiyHook_getThread},
};
int register_android_app_AntiyHook(JNIEnv* env)
{
return jniRegisterNativeMethods(env, "android/app/AntiyHook",
sMethods, NELEM(sMethods));
}
};
- 动态注册native函数 /frameworks/base/core/jni/AndroidRuntime.cpp
REG_JNI(register_android_app_AntiyHook),
extern int register_android_app_AntiyHook(JNIEnv *env);
- 添加native层文件到编译链 /frameworks/base/core/jni/Android.bp
"android_app_AntiyHook.cpp",
- 添加注入点
- /frameworks/base/core/java/android/app/Application
/* 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());
AntiyHookManager hook = (AntiyHookManager)context.getSystemService(Context.ANTIY_HOOK_SERVICE);
hook.getConfig();
if (hook.checkHookDex(getProcessName())){
String path = "/data/data/" + context.getPackageName() + "/AntiyPlugin.apk";
AntiyHook.moveToData(hook.hookdex.pluginPath, path);
AntiyHook.startHook(context.getClassLoader(), path);
}
} catch(Exception e){
Log.e("AntiyHook_Application", e.toString());
}
// add end
}
- 配置Selinux
- system\sepolicy\private\service_contexts 和 system\sepolicy\prebuilts\api\26-28\private\service_contexts
添加:antiy_hook u:object_r:antiy_hook_service:s0
- system\sepolicy\public\service.te 和 system\sepolicy\prebuilts\api\26-28\public\service.te
添加:type antiy_hook_service, app_api_service, system_api_service, system_server_service, service_manager_type;
- /system/sepolicy/prebuilts/api/28/public/untrusted_app.te 和 /system/sepolicy/public/untrusted_app.te
新增:allow untrusted_app antiy_hook_service:service_manager find;
新增:allow untrusted_app_27 antiy_hook_service:service_manager find;
新增:allow untrusted_app_25 antiy_hook_service:service_manager find;
- 添加sdcard读写执行权限:
- /frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
在forkSystemServer函数setgroups=后面添加1015
-|system +
|sepolicy|
|private
|system_server.te
修改:allow system_server sdcard_type:dir { getattr search }; 为 allow system_server sdcard_type:dir { create_dir_perms rw_file_perms};
修改:allow system_server configfs:file { getattr open create unlink write }; 为 allow system_server configfs:file { getattr open create unlink write read };
新建:allow system_server sdcard_type:file { create_file_perms rw_file_perms };
修改:allow system_server media_rw_data_file:file { getattr read write append}; 为 allow system_server media_rw_data_file:file { getattr read write append open };
删除:neverallow system_server sdcard_type:dir { open read write };
删除:neverallow system_server sdcard_type:file rw_file_perms;
|prebuilts|
|api|
|26.0|
|private|
|system_server.te //修改同上
|27.0|
|private|
|system_server.te //修改同上
|28.0|
|private|
|system_server.te //修改同上,此文件必须与/sepolicy/private/system_server.te完全一致
- 添加不信任APP的data目录文件执行权限
************************************************************
以下路径的untrusted_app.te文件中添加下面内容:
新增:allow untrusted_app app_data_file:file { execute };
新增:allow untrusted_app_27 app_data_file:file { execute };
新增:allow untrusted_app_25 app_data_file:file { execute };
************************************************************
-system|
|sepolicy|
|public|
|untrusted_app.te
|perbuilt|
|api|
|28.0|
|public|
|untrusted_app.te
- 删除不允许执行的权限
************************************************************
以下路径的app_neverallow.te文件中注释掉:
# neverallow all_untrusted_apps service_manager_type:service_manager add;
************************************************************
-system|
|sepolicy|
|private|
|app_neverallow.te
|perbuilt|
|api|
|28.0|
|private|
|app_neverallow.te
- 编译源码并刷机
- 配置文件config.js需要push到 /sdcard/antiy/路径下
{
"hookDex":{
"open":true, //hook开关
"packageName":[ //hook包名列表,如果要hook所有应用,则此项填[]
"me.wsj.fengyun",
"com.learn.development",
"cn.com.essence.stock",
"com.ss.android.article.news"
],
"pluginPath":"/data/local/tmp/AntiyPlugin.apk" //hook插件的存放路径,建议不要放在sdcard路径下,否则需要给应用开sdcard权限才能正常hook
},
"hookSo":{
"open":false,
"packageName":[
"me.wsj.fengyun",
"com.learn.development",
"cn.com.essence.stock"
],
"pluginPath":"/data/local/tmp/hook.txt"
}
}
- 编写hook插件
插件为一个apk文件形式,此hook框架的原理为,注入此插件中的dex文件,然后遍历插件中 HookInfo类的 hookClassNames 成员,依次对待hook的类的信息进行提取并将待hook的函数地址替换为插件中的回调函数来完成hook
- 插件必须存在类:com.antiy.HookInfo,代码参考为如下:
package com.antiy;
import com.antiy.base.Hook_ActivityThread_currentPackageName;
import com.antiy.base.Hook_AntiyHook_startHook;
import com.antiy.base.Hook_DexClassLoader_init;
import com.antiy.hook.Hook_Activity_startActivity;
import com.antiy.hook.Hook_learnApp_AppInfo_getappname;
public class HookInfo {
public static String[] hookClassNames = { //待hook的函数列表,将所有需要hook的函数的hook类都写在此变量中
Hook_ActivityThread_currentPackageName.class.getName(),
Hook_AntiyHook_startHook.class.getName(),
Hook_DexClassLoader_init.class.getName(),
Hook_Activity_startActivity.class.getName(),
Hook_learnApp_AppInfo_getappname.class.getName()
};
}
待hook函数的编写形式:
- 非静态函数的hook
package com.antiy.hook;
import android.content.Intent;
import android.util.Log;
public class Hook_Activity_startActivity {
public static final String TAG = "AntiyHookPlugin";
public static String className = "android.app.Activity"; //待hook的函数的类名
public static String methodName = "startActivity"; //待hook的函数名
public static String methodSig = "(Landroid/content/Intent;)V"; //函数签名信息
public static void hook(Object thiz, Intent intent) { //当应用中startActivity函数被调用时,将会调用此函数。非静态函数的第一个参数为this指针,所以此处的第一个参数为:Object thiz,此回调函数的参数个数比原函数对一个this指针
Log.w(TAG, "in Hook_Activity_startActivity hook:" + intent.toString());
backup(thiz, intent); //调用原函数(startActivity)
}
public static void backup(Object thiz, Intent intent) { //如果要调用原函数,则必须重写此回调函数,如果不需要调用原函数,则不需要重写此函数
Log.w(TAG, "in Hook_Activity_startActivity_ backup:" + intent.toString());
}
}
- 静态函数的hook
package com.antiy.base;
import android.util.Log;
public class Hook_ActivityThread_currentPackageName {
public static final String TAG = "AntiyHookPlugin";
public static String className = "android.app.ActivityThread"; //待hook的函数的类名
public static String methodName = "currentPackageName"; //待hook的函数名
public static String methodSig = "()Ljava/lang/String;"; //函数签名信息
public static String hook() { //静态函数的参数列表与原函数参数列表一致,无需添加this指针
Log.w(TAG, "in Hook_ActivityThread_currentPackageName hook:");
return backup();
}
public static String backup() {
Log.w(TAG, "in Hook_ActivityThread_currentPackageName backup:");
return "backup"; //此处返回的值并不影响原函数的返回值,所以你可以认为backup函数内的函数体并不会执行,你只需要重写该函数就行,函数内容可不写
}
}
- 应用测试
hook头条的startActivity函数
hook结果