Add SandHook-hooklib to source tree
This commit is contained in:
parent
8aa619f282
commit
35bdae390b
|
|
@ -24,6 +24,7 @@ allprojects {
|
|||
templateLib64Path = templateRootPath + "/system/lib64/"
|
||||
templateEtcPath = templateRootPath + "/system/etc/"
|
||||
hiddenApiStubJarFilePath = project(":hiddenapi-stubs").buildDir.absolutePath + "/libs/framework-stub.jar"
|
||||
zipPathMagiskReleasePath = project(":edxp-core").projectDir.path + "/build/tmp/release/magisk"
|
||||
}
|
||||
repositories {
|
||||
google()
|
||||
|
|
@ -34,8 +35,3 @@ allprojects {
|
|||
task clean(type: Delete) {
|
||||
delete rootProject.buildDir
|
||||
}
|
||||
|
||||
ext {
|
||||
minSdkVersion = 23
|
||||
targetSdkVersion = 28
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,8 +4,8 @@ android {
|
|||
compileSdkVersion androidCompileSdkVersion.toInteger()
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 28
|
||||
minSdkVersion minSdkVersion
|
||||
targetSdkVersion targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
}
|
||||
|
|
|
|||
|
|
@ -41,7 +41,6 @@ ext {
|
|||
sandhook_authors = "solohsu, MlgmXyysd & ganyao114"
|
||||
|
||||
riruModuleId = "edxp"
|
||||
zipPathMagiskRelease = "$buildDir/tmp/release/magisk"
|
||||
|
||||
moduleMinRiruApiVersion = 10
|
||||
moduleMinRiruVersionName = "v23.0"
|
||||
|
|
@ -120,13 +119,16 @@ afterEvaluate {
|
|||
def variantLowered = variant.name.toLowerCase()
|
||||
|
||||
backends.each { backend ->
|
||||
|
||||
def backendLowered = backend.toLowerCase()
|
||||
def backendCapped = backendLowered.capitalize()
|
||||
def authorList = property("${backendLowered}" + "_authors")
|
||||
def magiskModuleId = property("${backendLowered}" + "_module_id")
|
||||
|
||||
project.logger.lifecycle("delete file")
|
||||
delete file(zipPathMagiskReleasePath)
|
||||
|
||||
def prepareJarsTask = task("prepareJars${backendCapped}${variantCapped}") {
|
||||
project.logger.lifecycle("prepareJarsTask")
|
||||
dependsOn cleanTemplate
|
||||
dependsOn tasks.getByPath(":dexmaker:copyDex${variantCapped}")
|
||||
dependsOn tasks.getByPath(":dalvikdx:copyDex${variantCapped}")
|
||||
|
|
@ -136,7 +138,6 @@ afterEvaluate {
|
|||
|
||||
def prepareMagiskFilesTask = task("prepareMagiskFiles${backendCapped}${variantCapped}", type: Delete) {
|
||||
dependsOn prepareJarsTask, "assemble${variantCapped}"
|
||||
delete file(zipPathMagiskRelease)
|
||||
doFirst {
|
||||
copy {
|
||||
from "${projectDir}/tpl/edconfig.tpl"
|
||||
|
|
@ -169,12 +170,12 @@ afterEvaluate {
|
|||
doLast {
|
||||
copy {
|
||||
from "${projectDir}/template_override"
|
||||
into zipPathMagiskRelease
|
||||
into zipPathMagiskReleasePath
|
||||
exclude exclude_list
|
||||
}
|
||||
copy {
|
||||
from "${projectDir}/template_override"
|
||||
into zipPathMagiskRelease
|
||||
into zipPathMagiskReleasePath
|
||||
include 'riru.sh'
|
||||
filter { line ->
|
||||
line.replaceAll('%%%RIRU_MODULE_ID%%%', riruModuleId)
|
||||
|
|
@ -185,23 +186,27 @@ afterEvaluate {
|
|||
eol: FixCrLfFilter.CrLf.newInstance("lf"))
|
||||
}
|
||||
copy {
|
||||
include "libriru_edxp.so"
|
||||
from "$libPathRelease/armeabi-v7a"
|
||||
into "$zipPathMagiskRelease/system/lib"
|
||||
into "$zipPathMagiskReleasePath/system/lib"
|
||||
}
|
||||
copy {
|
||||
include "libriru_edxp.so"
|
||||
from "$libPathRelease/arm64-v8a"
|
||||
into "$zipPathMagiskRelease/system/lib64"
|
||||
into "$zipPathMagiskReleasePath/system/lib64"
|
||||
}
|
||||
copy {
|
||||
include "libriru_edxp.so"
|
||||
from "$libPathRelease/x86"
|
||||
into "$zipPathMagiskRelease/system_x86/lib"
|
||||
into "$zipPathMagiskReleasePath/system_x86/lib"
|
||||
}
|
||||
copy {
|
||||
include "libriru_edxp.so"
|
||||
from "$libPathRelease/x86_64"
|
||||
into "$zipPathMagiskRelease/system_x86/lib64"
|
||||
into "$zipPathMagiskReleasePath/system_x86/lib64"
|
||||
}
|
||||
// generate sha1sum
|
||||
fileTree(zipPathMagiskRelease).matching {
|
||||
fileTree(zipPathMagiskReleasePath).matching {
|
||||
exclude "README.md", "META-INF"
|
||||
}.visit { f ->
|
||||
if (f.directory) return
|
||||
|
|
@ -214,7 +219,7 @@ afterEvaluate {
|
|||
dependsOn prepareMagiskFilesTask
|
||||
archiveName "${module_name}-${backend}-${project.version}-${variantLowered}.zip"
|
||||
destinationDir file("$projectDir/release")
|
||||
from "$zipPathMagiskRelease"
|
||||
from "$zipPathMagiskReleasePath"
|
||||
}
|
||||
|
||||
task("push${backendCapped}${variantCapped}", type: Exec) {
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "com.elderdrivers.riru.edxp.sandhook"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 28
|
||||
minSdkVersion minSdkVersion
|
||||
targetSdkVersion targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled false
|
||||
|
|
@ -28,7 +28,7 @@ android {
|
|||
dependencies {
|
||||
compileOnly project(':hiddenapi-stubs')
|
||||
implementation project(':edxp-common')
|
||||
implementation 'com.swift.sandhook:hooklib:4.2.1'
|
||||
implementation project(':sandhook-hooklib')
|
||||
compileOnly files(project(":dexmaker").tasks.getByName("makeJarRelease").outputs)
|
||||
}
|
||||
|
||||
|
|
@ -62,21 +62,21 @@ afterEvaluate {
|
|||
task("copyDex${variantNameCapped}", type: Copy) {
|
||||
dependsOn "assemble${variantNameCapped}"
|
||||
dependsOn tasks.getByPath(":edxp-common:copyCommonProperties")
|
||||
dependsOn tasks.getByPath(":sandhook-hooklib:copySandHook${variantNameCapped}LibraryToMagiskTemplate")
|
||||
def dexOutPath = variant.name.contains("release") ?
|
||||
"${buildDir}/intermediates/dex/${variantNameLowered}/minify${variantNameCapped}WithR8" :
|
||||
"${buildDir}/intermediates/dex/${variantNameLowered}/mergeDex${variantNameCapped}"
|
||||
from (dexOutPath){
|
||||
rename("classes.dex", "edxp.dex")
|
||||
}
|
||||
destinationDir file(templateRootPath + "system/framework/")
|
||||
destinationDir file(zipPathMagiskReleasePath + "/system/framework/")
|
||||
outputs.upToDateWhen { false }
|
||||
doLast {
|
||||
copy {
|
||||
from file(myTemplatePath)
|
||||
into file(templateRootPath)
|
||||
into file(zipPathMagiskReleasePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
Binary file not shown.
Binary file not shown.
|
|
@ -8,8 +8,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "com.elderdrivers.riru.edxp.yahfa"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 28
|
||||
minSdkVersion minSdkVersion
|
||||
targetSdkVersion targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled false
|
||||
|
|
|
|||
|
|
@ -8,8 +8,8 @@ android {
|
|||
|
||||
defaultConfig {
|
||||
applicationId "com.elderdrivers.riru.edxp.yahfa"
|
||||
minSdkVersion 26
|
||||
targetSdkVersion 28
|
||||
minSdkVersion minSdkVersion
|
||||
targetSdkVersion targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
multiDexEnabled false
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
androidCompileSdkVersion=30
|
||||
androidCompileNdkVersion=22.0.6917172
|
||||
android.prefabVersion=1.1.2
|
||||
apiCode=93
|
||||
apiCode=93
|
||||
minSdkVersion=26
|
||||
targetSdkVersion=30
|
||||
|
|
@ -0,0 +1 @@
|
|||
/build
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
apply plugin: 'java-library'
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(dir: 'libs', include: ['*.jar'])
|
||||
}
|
||||
|
||||
sourceCompatibility = "1.7"
|
||||
targetCompatibility = "1.7"
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HookClass {
|
||||
Class<?> value();
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HookMethod {
|
||||
String value() default "<init>";
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD,ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HookMethodBackup {
|
||||
String value() default "<init>";
|
||||
}
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HookMode {
|
||||
|
||||
int AUTO = 0;
|
||||
int INLINE = 1;
|
||||
int REPLACE = 2;
|
||||
|
||||
int value() default AUTO;
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface HookReflectClass {
|
||||
String value();
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD,ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MethodParams {
|
||||
Class<?>[] value();
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD,ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface MethodReflectParams {
|
||||
|
||||
String BOOLEAN = "boolean";
|
||||
String BYTE = "byte";
|
||||
String CHAR = "char";
|
||||
String DOUBLE = "double";
|
||||
String FLOAT = "float";
|
||||
String INT = "int";
|
||||
String LONG = "long";
|
||||
String SHORT = "short";
|
||||
|
||||
String[] value();
|
||||
}
|
||||
|
|
@ -0,0 +1,12 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface Param {
|
||||
String value() default "";
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.FIELD})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface SkipParamCheck {
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.swift.sandhook.annotation;
|
||||
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
@Target({ElementType.PARAMETER})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
public @interface ThisObject {
|
||||
}
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
/build
|
||||
/.cxx
|
||||
|
|
@ -0,0 +1,66 @@
|
|||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion targetSdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion minSdkVersion
|
||||
targetSdkVersion targetSdkVersion
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
//arguments "-DCMAKE_BUILD_TYPE=Release"
|
||||
}
|
||||
}
|
||||
ndk {
|
||||
abiFilters 'armeabi-v7a', 'arm64-v8a'
|
||||
}
|
||||
}
|
||||
|
||||
externalNativeBuild {
|
||||
cmake {
|
||||
path "src/main/cpp/CMakeLists.txt"
|
||||
}
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled false
|
||||
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation fileTree(include: ['*.jar'], dir: 'libs')
|
||||
api project(':sandhook-annotation')
|
||||
}
|
||||
|
||||
afterEvaluate {
|
||||
android.libraryVariants.all { variant ->
|
||||
def variantNameCapped = variant.name.capitalize()
|
||||
def variantNameLowered = variant.name.toLowerCase()
|
||||
|
||||
task("copySandHook${variantNameCapped}LibraryToMagiskTemplate") {
|
||||
def libPathRelease = "${buildDir}/intermediates/cmake/${variantNameLowered}/obj"
|
||||
doLast {
|
||||
project.logger.lifecycle(libPathRelease)
|
||||
copy {
|
||||
include "libsandhook.edxp.so"
|
||||
from "${libPathRelease}/armeabi-v7a"
|
||||
into "${zipPathMagiskReleasePath}/system/lib"
|
||||
}
|
||||
copy {
|
||||
project.logger.lifecycle(zipPathMagiskReleasePath)
|
||||
include "libsandhook.edxp.so"
|
||||
from "${libPathRelease}/arm64-v8a"
|
||||
into "${zipPathMagiskReleasePath}/system/lib64"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
# Add project specific ProGuard rules here.
|
||||
# You can control the set of applied configuration files using the
|
||||
# proguardFiles setting in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Uncomment this to preserve the line number information for
|
||||
# debugging stack traces.
|
||||
#-keepattributes SourceFile,LineNumberTable
|
||||
|
||||
# If you keep the line number information, uncomment this to
|
||||
# hide the original source file name.
|
||||
#-renamesourcefileattribute SourceFile
|
||||
|
||||
-keep class com.swift.sandhook.** { *; }
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.swift.sandhook.lib" />
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(sandhook.edxp)
|
||||
|
||||
ENABLE_LANGUAGE(ASM)
|
||||
|
||||
add_definitions(-std=c++11)
|
||||
|
||||
set(${PROJECT_NAME}_SOURCES
|
||||
sandhook.cpp
|
||||
trampoline/trampoline.cpp
|
||||
trampoline/trampoline_manager.cpp
|
||||
utils/dlfcn_nougat.cpp
|
||||
utils/hide_api.cpp
|
||||
utils/utils.cpp
|
||||
utils/offset.cpp
|
||||
utils/elf_util.cpp
|
||||
casts/cast_art_method.cpp
|
||||
casts/cast_compiler_options.cpp
|
||||
art/art_method.cpp
|
||||
art/art_compiler_options.cpp
|
||||
art/art_classlinker.cpp
|
||||
trampoline/arch/arm32.S
|
||||
trampoline/arch/arm64.S
|
||||
inst/insts_arm32.cpp
|
||||
inst/insts_arm64.cpp
|
||||
nativehook/native_hook.cpp
|
||||
)
|
||||
|
||||
add_library(${PROJECT_NAME}
|
||||
SHARED
|
||||
${${PROJECT_NAME}_SOURCES})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} log)
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
//
|
||||
// Created by 双草酸酯 on 11/27/20.
|
||||
//
|
||||
#include "../includes/art_classlinker.h"
|
||||
|
||||
using namespace art;
|
||||
void ClassLinker::MakeInitializedClassesVisiblyInitialized(void* self, bool wait) {
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/2/24.
|
||||
//
|
||||
|
||||
#include "../includes/art_compiler_options.h"
|
||||
#include "../includes/cast_compiler_options.h"
|
||||
#include "../includes/hide_api.h"
|
||||
|
||||
using namespace SandHook;
|
||||
using namespace art;
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
size_t CompilerOptions::getInlineMaxCodeUnits() {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return 0;
|
||||
return CastCompilerOptions::inlineMaxCodeUnits->get(this);
|
||||
}
|
||||
|
||||
bool CompilerOptions::setInlineMaxCodeUnits(size_t units) {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return false;
|
||||
CastCompilerOptions::inlineMaxCodeUnits->set(this, units);
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,197 @@
|
|||
//
|
||||
// Created by swift on 2019/2/3.
|
||||
//
|
||||
#include <cstdint>
|
||||
#include "../includes/art_method.h"
|
||||
#include "../includes/cast_art_method.h"
|
||||
#include "../includes/hide_api.h"
|
||||
#include "../includes/utils.h"
|
||||
|
||||
extern int SDK_INT;
|
||||
extern bool DEBUG;
|
||||
|
||||
using namespace art::mirror;
|
||||
using namespace SandHook;
|
||||
|
||||
// Non-intrinsics: Caches whether we can use fast-path in the interpreter invokes.
|
||||
// Intrinsics: These bits are part of the intrinsic ordinal.
|
||||
static constexpr uint32_t kAccFastInterpreterToInterpreterInvoke = 0x40000000; // method.
|
||||
|
||||
void ArtMethod::tryDisableInline() {
|
||||
if (SDK_INT < ANDROID_O)
|
||||
return;
|
||||
uint32_t accessFlag = getAccessFlags();
|
||||
accessFlag &= ~ 0x08000000;
|
||||
setAccessFlags(accessFlag);
|
||||
}
|
||||
|
||||
void ArtMethod::disableInterpreterForO() {
|
||||
if (SDK_INT >= ANDROID_O && SDK_INT < ANDROID_R && DEBUG) {
|
||||
setNative();
|
||||
}
|
||||
}
|
||||
|
||||
void ArtMethod::disableFastInterpreterForQ() {
|
||||
if (SDK_INT < ANDROID_Q)
|
||||
return;
|
||||
uint32_t accessFlag = getAccessFlags();
|
||||
accessFlag &= ~kAccFastInterpreterToInterpreterInvoke;
|
||||
setAccessFlags(accessFlag);
|
||||
}
|
||||
|
||||
void ArtMethod::disableCompilable() {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return;
|
||||
uint32_t accessFlag = getAccessFlags();
|
||||
if (SDK_INT >= ANDROID_O2) {
|
||||
accessFlag |= 0x02000000;
|
||||
accessFlag |= 0x00800000;
|
||||
} else {
|
||||
accessFlag |= 0x01000000;
|
||||
}
|
||||
setAccessFlags(accessFlag);
|
||||
}
|
||||
|
||||
bool ArtMethod::isAbstract() {
|
||||
uint32_t accessFlags = getAccessFlags();
|
||||
return ((accessFlags & 0x0400) != 0);
|
||||
}
|
||||
|
||||
bool ArtMethod::isNative() {
|
||||
uint32_t accessFlags = getAccessFlags();
|
||||
return ((accessFlags & 0x0100) != 0);
|
||||
}
|
||||
|
||||
bool ArtMethod::isStatic() {
|
||||
uint32_t accessFlags = getAccessFlags();
|
||||
return ((accessFlags & 0x0008) != 0);
|
||||
}
|
||||
|
||||
bool ArtMethod::isCompiled() {
|
||||
return getQuickCodeEntry() != CastArtMethod::quickToInterpreterBridge &&
|
||||
getQuickCodeEntry() != CastArtMethod::genericJniStub;
|
||||
}
|
||||
|
||||
bool ArtMethod::isThumbCode() {
|
||||
#if defined(__arm__)
|
||||
return (reinterpret_cast<Size>(getQuickCodeEntry()) & 0x1) == 0x1;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
void ArtMethod::setAccessFlags(uint32_t flags) {
|
||||
CastArtMethod::accessFlag->set(this, flags);
|
||||
}
|
||||
|
||||
void ArtMethod::setPrivate() {
|
||||
uint32_t accessFlag = getAccessFlags();
|
||||
accessFlag &= ~ 0x0001;
|
||||
accessFlag &= ~ 0x0004;
|
||||
accessFlag |= 0x0002;
|
||||
setAccessFlags(accessFlag);
|
||||
}
|
||||
|
||||
void ArtMethod::setStatic() {
|
||||
uint32_t accessFlag = getAccessFlags();
|
||||
accessFlag |= 0x0008;
|
||||
setAccessFlags(accessFlag);
|
||||
};
|
||||
|
||||
|
||||
void ArtMethod::setNative() {
|
||||
uint32_t accessFlag = getAccessFlags();
|
||||
accessFlag |= 0x0100;
|
||||
setAccessFlags(accessFlag);
|
||||
}
|
||||
|
||||
uint32_t ArtMethod::getAccessFlags() {
|
||||
return CastArtMethod::accessFlag->get(this);
|
||||
}
|
||||
|
||||
uint32_t ArtMethod::getDexMethodIndex() {
|
||||
return CastArtMethod::dexMethodIndex->get(this);
|
||||
}
|
||||
|
||||
void* ArtMethod::getQuickCodeEntry() {
|
||||
return CastArtMethod::entryPointQuickCompiled->get(this);
|
||||
}
|
||||
|
||||
void* ArtMethod::getInterpreterCodeEntry() {
|
||||
return CastArtMethod::entryPointFromInterpreter->get(this);
|
||||
}
|
||||
|
||||
GCRoot ArtMethod::getDeclaringClass() {
|
||||
return CastArtMethod::declaringClass->get(this);
|
||||
}
|
||||
|
||||
uint16_t ArtMethod::getHotnessCount() {
|
||||
return CastArtMethod::hotnessCount->get(this);
|
||||
}
|
||||
|
||||
void ArtMethod::setQuickCodeEntry(void *entry) {
|
||||
CastArtMethod::entryPointQuickCompiled->set(this, entry);
|
||||
}
|
||||
|
||||
void ArtMethod::setJniCodeEntry(void *entry) {
|
||||
CastArtMethod::entryPointFromJNI->set(this, entry);
|
||||
}
|
||||
|
||||
void ArtMethod::setInterpreterCodeEntry(void *entry) {
|
||||
CastArtMethod::entryPointFromInterpreter->set(this, entry);
|
||||
}
|
||||
|
||||
void ArtMethod::setDexCacheResolveList(void *list) {
|
||||
CastArtMethod::dexCacheResolvedMethods->set(this, list);
|
||||
}
|
||||
|
||||
void ArtMethod::setDexCacheResolveItem(uint32_t index, void* item) {
|
||||
CastArtMethod::dexCacheResolvedMethods->setElement(this, index, item);
|
||||
}
|
||||
|
||||
void ArtMethod::setDeclaringClass(GCRoot classPtr) {
|
||||
CastArtMethod::declaringClass->set(this, classPtr);
|
||||
}
|
||||
|
||||
void ArtMethod::setHotnessCount(uint16_t count) {
|
||||
CastArtMethod::hotnessCount->set(this, count);
|
||||
}
|
||||
|
||||
bool ArtMethod::compile(JNIEnv* env) {
|
||||
if (isCompiled())
|
||||
return true;
|
||||
//some unknown error when trigger jit for jni method manually
|
||||
if (isNative())
|
||||
return false;
|
||||
Size threadId = getAddressFromJavaByCallMethod(env, "com/swift/sandhook/SandHook", "getThreadId");
|
||||
if (threadId == 0)
|
||||
return false;
|
||||
return compileMethod(this, reinterpret_cast<void *>(threadId)) && isCompiled();
|
||||
}
|
||||
|
||||
bool ArtMethod::deCompile() {
|
||||
if (!isCompiled())
|
||||
return true;
|
||||
if ((isNative() && CastArtMethod::canGetJniBridge) || (!isNative() && CastArtMethod::canGetInterpreterBridge)) {
|
||||
setQuickCodeEntry(isNative() ? CastArtMethod::genericJniStub : CastArtMethod::quickToInterpreterBridge);
|
||||
if (SDK_INT < ANDROID_N) {
|
||||
//TODO SetEntryPointFromInterpreterCode
|
||||
}
|
||||
flushCache();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void ArtMethod::flushCache() {
|
||||
// flushCacheExt(reinterpret_cast<Size>(this), size());
|
||||
}
|
||||
|
||||
void ArtMethod::backup(ArtMethod *backup) {
|
||||
memcpy(backup, this, size());
|
||||
}
|
||||
|
||||
Size ArtMethod::size() {
|
||||
return CastArtMethod::size;
|
||||
}
|
||||
|
|
@ -0,0 +1,290 @@
|
|||
//
|
||||
// Created by swift on 2019/2/3.
|
||||
//
|
||||
|
||||
#include "../includes/cast_art_method.h"
|
||||
#include "../includes/utils.h"
|
||||
#include "../includes/never_call.h"
|
||||
#include "../includes/log.h"
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class CastDexCacheResolvedMethods : public ArrayMember<art::mirror::ArtMethod, void *> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
|
||||
if (SDK_INT >= ANDROID_P)
|
||||
return getParentSize() + 1;
|
||||
int offset = 0;
|
||||
Size addr = getAddressFromJava(jniEnv, "com/swift/sandhook/SandHookMethodResolver",
|
||||
"resolvedMethodsAddress");
|
||||
if (addr != 0) {
|
||||
offset = findOffset(p, getParentSize(), 2, addr);
|
||||
if (offset >= 0) {
|
||||
return static_cast<Size>(offset);
|
||||
}
|
||||
}
|
||||
if (SDK_INT == ANDROID_M) {
|
||||
return 4;
|
||||
} else if (SDK_INT >= ANDROID_L && SDK_INT <= ANDROID_L2) {
|
||||
return 4 * 3;
|
||||
}
|
||||
return getParentSize() + 1;
|
||||
}
|
||||
|
||||
public:
|
||||
Size arrayStart(mirror::ArtMethod *parent) override {
|
||||
void *p = IMember<mirror::ArtMethod, void *>::get(parent);
|
||||
if (SDK_INT <= ANDROID_M) {
|
||||
return reinterpret_cast<Size>(p) + 4 * 3;
|
||||
} else {
|
||||
return reinterpret_cast<Size>(p);
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class CastEntryPointFormInterpreter : public IMember<art::mirror::ArtMethod, void *> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
|
||||
if (SDK_INT == ANDROID_L2) {
|
||||
return RoundUpToPtrSize(4 * 7 + 4 * 2);
|
||||
} else if (SDK_INT == ANDROID_M) {
|
||||
return getParentSize() - 3 * BYTE_POINT;
|
||||
} else if (SDK_INT <= ANDROID_L) {
|
||||
Size addr = getAddressFromJava(jniEnv, "com/swift/sandhook/SandHookMethodResolver",
|
||||
"entryPointFromInterpreter");
|
||||
int offset = 0;
|
||||
if (addr != 0) {
|
||||
offset = findOffset(p, getParentSize(), 2, addr);
|
||||
if (offset >= 0) {
|
||||
return static_cast<Size>(offset);
|
||||
}
|
||||
}
|
||||
return getParentSize() - 4 * 8 - 4 * 4;
|
||||
}
|
||||
else
|
||||
return getParentSize() + 1;
|
||||
}
|
||||
};
|
||||
|
||||
class CastEntryPointQuickCompiled : public IMember<art::mirror::ArtMethod, void *> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
|
||||
if (SDK_INT >= ANDROID_M) {
|
||||
return getParentSize() - BYTE_POINT;
|
||||
} else if (SDK_INT <= ANDROID_L) {
|
||||
Size addr = getAddressFromJava(jniEnv, "com/swift/sandhook/SandHookMethodResolver",
|
||||
"entryPointFromCompiledCode");
|
||||
int offset = 0;
|
||||
if (addr != 0) {
|
||||
offset = findOffset(p, getParentSize(), 2, addr);
|
||||
if (offset >= 0) {
|
||||
return static_cast<Size>(offset);
|
||||
}
|
||||
}
|
||||
return getParentSize() - 4 - 2 * BYTE_POINT;
|
||||
} else {
|
||||
return CastArtMethod::entryPointFromInterpreter->getOffset() + 2 * BYTE_POINT;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class CastEntryPointFromJni : public IMember<art::mirror::ArtMethod, void *> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
|
||||
Size jniAddr = reinterpret_cast<Size>(Java_com_swift_sandhook_ClassNeverCall_neverCallNative);
|
||||
int offset = findOffset(p, getParentSize(), 2, jniAddr);
|
||||
if (offset >= 0) {
|
||||
return static_cast<Size>(offset);
|
||||
}
|
||||
if (SDK_INT >= ANDROID_L2 && SDK_INT <= ANDROID_N) {
|
||||
return getParentSize() - 2 * BYTE_POINT;
|
||||
} else {
|
||||
return getParentSize() - 8 * 2 - 4 * 4;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class CastAccessFlag : public IMember<art::mirror::ArtMethod, uint32_t> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
|
||||
uint32_t accessFlag = getIntFromJava(jniEnv, "com/swift/sandhook/SandHook",
|
||||
"testAccessFlag");
|
||||
if (accessFlag == 0) {
|
||||
accessFlag = 524313;
|
||||
//kAccPublicApi
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
accessFlag |= 0x10000000;
|
||||
}
|
||||
}
|
||||
int offset = findOffset(p, getParentSize(), 2, accessFlag);
|
||||
if (offset < 0) {
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
return 4;
|
||||
} else if (SDK_INT == ANDROID_L2) {
|
||||
return 20;
|
||||
} else if (SDK_INT == ANDROID_L) {
|
||||
return 56;
|
||||
} else {
|
||||
return getParentSize() + 1;
|
||||
}
|
||||
} else {
|
||||
return static_cast<size_t>(offset);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
class CastShadowClass : public IMember<art::mirror::ArtMethod, GCRoot> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, mirror::ArtMethod *p) override {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return getParentSize() + 1;
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class CastDexMethodIndex : public IMember<art::mirror::ArtMethod, uint32_t> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::mirror::ArtMethod *p) override {
|
||||
if (SDK_INT >= ANDROID_P) {
|
||||
return CastArtMethod::accessFlag->getOffset()
|
||||
+ CastArtMethod::accessFlag->size()
|
||||
+ sizeof(uint32_t);
|
||||
}
|
||||
int offset = 0;
|
||||
jint index = getIntFromJava(jniEnv, "com/swift/sandhook/SandHookMethodResolver",
|
||||
"dexMethodIndex");
|
||||
if (index != 0) {
|
||||
offset = findOffset(p, getParentSize(), 2, static_cast<uint32_t>(index));
|
||||
if (offset >= 0) {
|
||||
return static_cast<Size>(offset);
|
||||
}
|
||||
}
|
||||
return getParentSize() + 1;
|
||||
}
|
||||
};
|
||||
|
||||
class CastHotnessCount : public IMember<art::mirror::ArtMethod, uint16_t> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, mirror::ArtMethod *p) override {
|
||||
if (SDK_INT <= ANDROID_N)
|
||||
return getParentSize() + 1;
|
||||
return CastArtMethod::dexMethodIndex->getOffset()
|
||||
+ CastArtMethod::dexMethodIndex->size()
|
||||
+ sizeof(uint16_t);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
void CastArtMethod::init(JNIEnv *env) {
|
||||
//init ArtMethodSize
|
||||
jclass sizeTestClass = env->FindClass("com/swift/sandhook/ArtMethodSizeTest");
|
||||
jobject artMethod1 = getMethodObject(env, "com.swift.sandhook.ArtMethodSizeTest", "method1");
|
||||
jobject artMethod2 = getMethodObject(env, "com.swift.sandhook.ArtMethodSizeTest", "method2");
|
||||
|
||||
env->CallStaticVoidMethod(sizeTestClass, env->FromReflectedMethod(artMethod1));
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
|
||||
art::mirror::ArtMethod *m1 = getArtMethod(env, artMethod1);
|
||||
art::mirror::ArtMethod *m2 = getArtMethod(env, artMethod2);
|
||||
|
||||
size = m2 - m1;
|
||||
|
||||
//init Members
|
||||
|
||||
accessFlag = new CastAccessFlag();
|
||||
accessFlag->init(env, m1, size);
|
||||
|
||||
entryPointFromInterpreter = new CastEntryPointFormInterpreter();
|
||||
entryPointFromInterpreter->init(env, m1, size);
|
||||
|
||||
entryPointQuickCompiled = new CastEntryPointQuickCompiled();
|
||||
entryPointQuickCompiled->init(env, m1, size);
|
||||
|
||||
dexMethodIndex = new CastDexMethodIndex();
|
||||
dexMethodIndex->init(env, m1, size);
|
||||
|
||||
dexCacheResolvedMethods = new CastDexCacheResolvedMethods();
|
||||
dexCacheResolvedMethods->init(env, m1, size);
|
||||
|
||||
declaringClass = new CastShadowClass();
|
||||
declaringClass->init(env, m1, size);
|
||||
|
||||
|
||||
hotnessCount = new CastHotnessCount();
|
||||
hotnessCount->init(env, m1, size);
|
||||
|
||||
auto neverCallTestClass = "com.swift.sandhook.ClassNeverCall";
|
||||
|
||||
art::mirror::ArtMethod *neverCall = getArtMethod(env, getMethodObject(env,
|
||||
neverCallTestClass,
|
||||
"neverCall"));
|
||||
art::mirror::ArtMethod *neverCall2 = getArtMethod(env, getMethodObject(env,
|
||||
neverCallTestClass,
|
||||
"neverCall2"));
|
||||
|
||||
bool beAot = entryPointQuickCompiled->get(neverCall) != entryPointQuickCompiled->get(neverCall2);
|
||||
if (beAot) {
|
||||
quickToInterpreterBridge = getInterpreterBridge(false);
|
||||
if (quickToInterpreterBridge == nullptr) {
|
||||
quickToInterpreterBridge = entryPointQuickCompiled->get(neverCall);
|
||||
canGetInterpreterBridge = false;
|
||||
}
|
||||
} else {
|
||||
quickToInterpreterBridge = entryPointQuickCompiled->get(neverCall);
|
||||
}
|
||||
|
||||
|
||||
art::mirror::ArtMethod *neverCallNative = getArtMethod(env, getMethodObject(env,
|
||||
neverCallTestClass,
|
||||
"neverCallNative"));
|
||||
art::mirror::ArtMethod *neverCallNative2 = getArtMethod(env, getMethodObject(env,
|
||||
neverCallTestClass,
|
||||
"neverCallNative2"));
|
||||
|
||||
beAot = entryPointQuickCompiled->get(neverCallNative) != entryPointQuickCompiled->get(neverCallNative2);
|
||||
if (beAot) {
|
||||
genericJniStub = getInterpreterBridge(true);
|
||||
if (genericJniStub == nullptr) {
|
||||
genericJniStub = entryPointQuickCompiled->get(neverCallNative);
|
||||
canGetJniBridge = false;
|
||||
}
|
||||
} else {
|
||||
genericJniStub = entryPointQuickCompiled->get(neverCallNative);
|
||||
}
|
||||
|
||||
entryPointFromJNI = new CastEntryPointFromJni();
|
||||
entryPointFromJNI->init(env, neverCallNative, size);
|
||||
|
||||
art::mirror::ArtMethod *neverCallStatic = getArtMethod(env, getMethodObject(env,
|
||||
neverCallTestClass,
|
||||
"neverCallStatic"));
|
||||
staticResolveStub = entryPointQuickCompiled->get(neverCallStatic);
|
||||
|
||||
}
|
||||
|
||||
void CastArtMethod::copy(art::mirror::ArtMethod *from, art::mirror::ArtMethod *to) {
|
||||
memcpy(to, from, size);
|
||||
}
|
||||
|
||||
Size CastArtMethod::size = 0;
|
||||
IMember<art::mirror::ArtMethod, void *> *CastArtMethod::entryPointQuickCompiled = nullptr;
|
||||
IMember<art::mirror::ArtMethod, void *> *CastArtMethod::entryPointFromInterpreter = nullptr;
|
||||
IMember<art::mirror::ArtMethod, void *> *CastArtMethod::entryPointFromJNI = nullptr;
|
||||
ArrayMember<art::mirror::ArtMethod, void *> *CastArtMethod::dexCacheResolvedMethods = nullptr;
|
||||
IMember<art::mirror::ArtMethod, uint32_t> *CastArtMethod::dexMethodIndex = nullptr;
|
||||
IMember<art::mirror::ArtMethod, uint32_t> *CastArtMethod::accessFlag = nullptr;
|
||||
IMember<art::mirror::ArtMethod, GCRoot> *CastArtMethod::declaringClass = nullptr;
|
||||
IMember<art::mirror::ArtMethod, uint16_t> *CastArtMethod::hotnessCount = nullptr;
|
||||
void *CastArtMethod::quickToInterpreterBridge = nullptr;
|
||||
void *CastArtMethod::genericJniStub = nullptr;
|
||||
void *CastArtMethod::staticResolveStub = nullptr;
|
||||
bool CastArtMethod::canGetInterpreterBridge = true;
|
||||
bool CastArtMethod::canGetJniBridge = true;
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/2/24.
|
||||
//
|
||||
|
||||
#include "../includes/cast_compiler_options.h"
|
||||
#include "../includes/hide_api.h"
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
|
||||
class CastInlineMaxCodeUnits : public IMember<art::CompilerOptions, size_t> {
|
||||
protected:
|
||||
Size calOffset(JNIEnv *jniEnv, art::CompilerOptions *p) override {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return getParentSize() + 1;
|
||||
if (SDK_INT >= ANDROID_O) {
|
||||
return BYTE_POINT + 5 * sizeof(size_t);
|
||||
} else {
|
||||
return BYTE_POINT + 6 * sizeof(size_t);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
void CastCompilerOptions::init(JNIEnv *jniEnv) {
|
||||
inlineMaxCodeUnits->init(jniEnv, nullptr, sizeof(art::CompilerOptions));
|
||||
}
|
||||
|
||||
IMember<art::CompilerOptions, size_t>* CastCompilerOptions::inlineMaxCodeUnits = new CastInlineMaxCodeUnits();
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/1/12.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_ARCH_H
|
||||
#define SANDHOOK_ARCH_H
|
||||
|
||||
#define BYTE_POINT sizeof(void*)
|
||||
|
||||
typedef size_t Size;
|
||||
|
||||
//32bit
|
||||
#if defined(__i386__) || defined(__arm__)
|
||||
//64bit
|
||||
#elif defined(__aarch64__) || defined(__x86_64__)
|
||||
#else
|
||||
#endif
|
||||
|
||||
#if defined(__arm__)
|
||||
static void clearCacheArm32(char* begin, char *end)
|
||||
{
|
||||
const int syscall = 0xf0002;
|
||||
__asm __volatile (
|
||||
"mov r0, %0\n"
|
||||
"mov r1, %1\n"
|
||||
"mov r3, %2\n"
|
||||
"mov r2, #0x0\n"
|
||||
"svc 0x00000000\n"
|
||||
:
|
||||
: "r" (begin), "r" (end), "r" (syscall)
|
||||
: "r0", "r1", "r3"
|
||||
);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define ANDROID_K 19
|
||||
#define ANDROID_L 21
|
||||
#define ANDROID_L2 22
|
||||
#define ANDROID_M 23
|
||||
#define ANDROID_N 24
|
||||
#define ANDROID_N2 25
|
||||
#define ANDROID_O 26
|
||||
#define ANDROID_O2 27
|
||||
#define ANDROID_P 28
|
||||
#define ANDROID_Q 29
|
||||
#define ANDROID_R 30
|
||||
|
||||
#endif //SANDHOOK_ARCH_H
|
||||
|
|
@ -0,0 +1,34 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/1/17.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_BASE_H
|
||||
#define SANDHOOK_BASE_H
|
||||
|
||||
#define FUNCTION_START(x) \
|
||||
.text; \
|
||||
.align 4; \
|
||||
.global x; \
|
||||
x: \
|
||||
|
||||
#define FUNCTION_START_T(x) \
|
||||
.syntax unified; \
|
||||
.text; \
|
||||
.align 4; \
|
||||
.thumb; \
|
||||
.thumb_func; \
|
||||
.global x; \
|
||||
x: \
|
||||
|
||||
#define FUNCTION_END(x) .size x, .-x
|
||||
|
||||
#define REPLACEMENT_HOOK_TRAMPOLINE replacement_hook_trampoline
|
||||
#define INLINE_HOOK_TRAMPOLINE inline_hook_trampoline
|
||||
#define DIRECT_JUMP_TRAMPOLINE direct_jump_trampoline
|
||||
#define CALL_ORIGIN_TRAMPOLINE call_origin_trampoline
|
||||
|
||||
#define INLINE_HOOK_TRAMPOLINE_T inline_hook_trampoline_t
|
||||
#define DIRECT_JUMP_TRAMPOLINE_T direct_jump_trampoline_t
|
||||
#define CALL_ORIGIN_TRAMPOLINE_T call_origin_trampoline_t
|
||||
|
||||
#endif //SANDHOOK_BASE_H
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Created by 双草酸酯 on 11/27/20.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_ART_CLASSLINKER_H
|
||||
#define SANDHOOK_ART_CLASSLINKER_H
|
||||
|
||||
#endif //SANDHOOK_ART_CLASSLINKER_H
|
||||
namespace art {
|
||||
class ClassLinker {
|
||||
public:
|
||||
void MakeInitializedClassesVisiblyInitialized(void* self, bool wait);
|
||||
};
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/2/23.
|
||||
//
|
||||
|
||||
#ifndef ART_COMPILER_OPTIONS_H
|
||||
#define ART_COMPILER_OPTIONS_H
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
namespace art {
|
||||
class CompilerOptions {
|
||||
public:
|
||||
void* compiler_filter_;
|
||||
size_t huge_method_threshold_;
|
||||
size_t large_method_threshold_;
|
||||
size_t small_method_threshold_;
|
||||
size_t tiny_method_threshold_;
|
||||
size_t num_dex_methods_threshold_;
|
||||
size_t inline_depth_limit_;
|
||||
size_t inline_max_code_units_;
|
||||
|
||||
size_t getInlineMaxCodeUnits();
|
||||
bool setInlineMaxCodeUnits(size_t units);
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif //ART_COMPILER_OPTIONS_H
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/2/23.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_ART_JIT_H
|
||||
#define SANDHOOK_ART_JIT_H
|
||||
|
||||
namespace art {
|
||||
namespace jit {
|
||||
|
||||
//7.0 - 9.0
|
||||
class JitCompiler {
|
||||
public:
|
||||
virtual ~JitCompiler();
|
||||
std::unique_ptr<art::CompilerOptions> compilerOptions;
|
||||
};
|
||||
|
||||
class Jit {
|
||||
public:
|
||||
//void* getCompilerOptions();
|
||||
};
|
||||
|
||||
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_ART_JIT_H
|
||||
|
|
@ -0,0 +1,87 @@
|
|||
/*
|
||||
*
|
||||
* Copyright (c) 2011 The Android Open Source Project
|
||||
* Copyright (c) 2015, alipay.com
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef ART_H
|
||||
#define ART_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "arch.h"
|
||||
|
||||
//7.0 - 10.0
|
||||
#define GCRoot uint32_t
|
||||
|
||||
namespace art {
|
||||
namespace mirror {
|
||||
class Object {
|
||||
public:
|
||||
};
|
||||
class Class: public Object {
|
||||
public:
|
||||
};
|
||||
|
||||
class ArtField {
|
||||
public:
|
||||
};
|
||||
|
||||
class ArtMethod {
|
||||
public:
|
||||
|
||||
bool isAbstract();
|
||||
bool isNative();
|
||||
bool isStatic();
|
||||
bool isCompiled();
|
||||
bool isThumbCode();
|
||||
|
||||
void setAccessFlags(uint32_t flags);
|
||||
void disableCompilable();
|
||||
void tryDisableInline();
|
||||
void disableInterpreterForO();
|
||||
void disableFastInterpreterForQ();
|
||||
void setPrivate();
|
||||
void setStatic();
|
||||
void setNative();
|
||||
|
||||
void setQuickCodeEntry(void* entry);
|
||||
void setJniCodeEntry(void* entry);
|
||||
void setInterpreterCodeEntry(void* entry);
|
||||
void setDexCacheResolveList(void* list);
|
||||
void setDexCacheResolveItem(uint32_t index, void* item);
|
||||
void setDeclaringClass(GCRoot classPtr);
|
||||
void setHotnessCount(uint16_t count);
|
||||
|
||||
void* getQuickCodeEntry();
|
||||
void* getInterpreterCodeEntry();
|
||||
uint32_t getAccessFlags();
|
||||
uint32_t getDexMethodIndex();
|
||||
GCRoot getDeclaringClass();
|
||||
uint16_t getHotnessCount();
|
||||
|
||||
bool compile(JNIEnv* env);
|
||||
bool deCompile();
|
||||
void flushCache();
|
||||
void backup(ArtMethod* backup);
|
||||
|
||||
static Size size();
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif //ART_H
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/2/23.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_ART_RUNTIME_H
|
||||
#define SANDHOOK_ART_RUNTIME_H
|
||||
|
||||
#include "art_jit.h"
|
||||
|
||||
namespace art {
|
||||
class Runtime {
|
||||
|
||||
public:
|
||||
jit::Jit* getJit();
|
||||
};
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_ART_RUNTIME_H
|
||||
|
|
@ -0,0 +1,123 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/1/12.
|
||||
//
|
||||
|
||||
|
||||
#ifndef SANDHOOK_ICAST_H
|
||||
#define SANDHOOK_ICAST_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <string.h>
|
||||
#include <jni.h>
|
||||
#include "arch.h"
|
||||
#include "utils.h"
|
||||
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
template <typename T>
|
||||
class cast {
|
||||
public:
|
||||
cast(T t) {
|
||||
this->origin = t;
|
||||
};
|
||||
|
||||
virtual Size getSize() { return sizeof(T); };
|
||||
|
||||
private:
|
||||
T origin;
|
||||
};
|
||||
|
||||
template <typename PType, typename MType>
|
||||
class IMember {
|
||||
public:
|
||||
|
||||
virtual void init(JNIEnv *jniEnv, PType* p, Size size) {
|
||||
this->parentSize = size;
|
||||
offset = calOffset(jniEnv, p);
|
||||
}
|
||||
|
||||
Size size() {
|
||||
return sizeof(MType);
|
||||
}
|
||||
|
||||
virtual Size getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
virtual Size getParentSize() {
|
||||
return parentSize;
|
||||
}
|
||||
|
||||
virtual MType get(PType* p) {
|
||||
if (offset > parentSize)
|
||||
return 0;
|
||||
return *reinterpret_cast<MType*>((Size)p + getOffset());
|
||||
};
|
||||
|
||||
virtual void set(PType* p, MType t) {
|
||||
if (offset > parentSize)
|
||||
return;
|
||||
memcpy(reinterpret_cast<void *>((Size)p + getOffset()), &t, size());
|
||||
};
|
||||
|
||||
template<typename T>
|
||||
int findOffset(void *start, size_t len, size_t step, T value) {
|
||||
|
||||
if (nullptr == start) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= len; i += step) {
|
||||
T current_value = *reinterpret_cast<T *>((size_t) start + i);
|
||||
if (value == current_value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
private:
|
||||
Size offset = 0;
|
||||
protected:
|
||||
Size parentSize = 0;
|
||||
virtual Size calOffset(JNIEnv *jniEnv, PType* p) = 0;
|
||||
|
||||
};
|
||||
|
||||
template<typename PType, typename ElementType>
|
||||
class ArrayMember : public IMember<PType, void*> {
|
||||
public:
|
||||
|
||||
virtual void init(JNIEnv *jniEnv, PType* p, Size parentSize) override {
|
||||
IMember<PType,void*>::init(jniEnv, p, parentSize);
|
||||
elementSize = calElementSize(jniEnv, p);
|
||||
}
|
||||
|
||||
virtual Size getElementSize() {
|
||||
return elementSize;
|
||||
}
|
||||
|
||||
virtual Size arrayStart(PType* parent) {
|
||||
void* p = IMember<PType,void*>::get(parent);
|
||||
return reinterpret_cast<Size>(p);
|
||||
}
|
||||
|
||||
using IMember<PType,void*>::getParentSize;
|
||||
|
||||
virtual void setElement(PType* parent, int position, ElementType elementPoint) {
|
||||
Size array = arrayStart(parent);
|
||||
memcpy(reinterpret_cast<void*>(array + position * getElementSize()), &elementPoint, getElementSize());
|
||||
}
|
||||
|
||||
private:
|
||||
Size elementSize = 0;
|
||||
protected:
|
||||
virtual Size calElementSize(JNIEnv *jniEnv, PType* p) {
|
||||
return sizeof(ElementType);
|
||||
};
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_ICAST_H
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/1/12.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_CAST_ART_METHOD_H
|
||||
#define SANDHOOK_CAST_ART_METHOD_H
|
||||
|
||||
#include "cast.h"
|
||||
#include "trampoline_manager.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class CastArtMethod {
|
||||
public:
|
||||
static Size size;
|
||||
static IMember<art::mirror::ArtMethod, void*>* entryPointQuickCompiled;
|
||||
static IMember<art::mirror::ArtMethod, void*>* entryPointFromInterpreter;
|
||||
static IMember<art::mirror::ArtMethod, void*>* entryPointFromJNI;
|
||||
static ArrayMember<art::mirror::ArtMethod,void*>* dexCacheResolvedMethods;
|
||||
static IMember<art::mirror::ArtMethod, uint32_t>* dexMethodIndex;
|
||||
static IMember<art::mirror::ArtMethod, uint32_t>* accessFlag;
|
||||
static IMember<art::mirror::ArtMethod, GCRoot>* declaringClass;
|
||||
static IMember<art::mirror::ArtMethod, uint16_t>* hotnessCount;
|
||||
static void* quickToInterpreterBridge;
|
||||
static void* genericJniStub;
|
||||
static void* staticResolveStub;
|
||||
static bool canGetJniBridge;
|
||||
static bool canGetInterpreterBridge;
|
||||
|
||||
static void init(JNIEnv *env);
|
||||
static void copy(art::mirror::ArtMethod* from, art::mirror::ArtMethod* to);
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_CAST_ART_METHOD_H
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,24 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/1/12.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_CAST_COMPILER_OPTIONS_H
|
||||
#define SANDHOOK_CAST_COMPILER_OPTIONS_H
|
||||
|
||||
#include "cast.h"
|
||||
#include "art_compiler_options.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class CastCompilerOptions {
|
||||
public:
|
||||
static void init(JNIEnv *jniEnv);
|
||||
static IMember<art::CompilerOptions, size_t>* inlineMaxCodeUnits;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_CAST_COMPILER_OPTIONS_H
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,17 @@
|
|||
#ifndef DLFCN_NOUGAT_H
|
||||
#define DLFCN_NOUGAT_H
|
||||
|
||||
//see implementation in https://tech.meituan.com/2017/07/20/android-remote-debug.html
|
||||
extern "C" {
|
||||
int fake_dlclose(void *handle);
|
||||
|
||||
void *fake_dlopen(const char *filename, int flags);
|
||||
|
||||
void *fake_dlsym(void *handle, const char *name);
|
||||
|
||||
const char *fake_dlerror();
|
||||
|
||||
void *getSymCompat(const char *filename, const char *name);
|
||||
}
|
||||
|
||||
#endif //DLFCN_NOUGAT_H
|
||||
|
|
@ -0,0 +1,72 @@
|
|||
//
|
||||
// Created by Swift Gan on 2019/3/14.
|
||||
//
|
||||
#ifndef SANDHOOK_ELF_UTIL_H
|
||||
#define SANDHOOK_ELF_UTIL_H
|
||||
|
||||
#include <linux/elf.h>
|
||||
|
||||
#if defined(__LP64__)
|
||||
typedef Elf64_Ehdr Elf_Ehdr;
|
||||
typedef Elf64_Shdr Elf_Shdr;
|
||||
typedef Elf64_Addr Elf_Addr;
|
||||
typedef Elf64_Dyn Elf_Dyn;
|
||||
typedef Elf64_Rela Elf_Rela;
|
||||
typedef Elf64_Sym Elf_Sym;
|
||||
typedef Elf64_Off Elf_Off;
|
||||
|
||||
#define ELF_R_SYM(i) ELF64_R_SYM(i)
|
||||
#else
|
||||
typedef Elf32_Ehdr Elf_Ehdr;
|
||||
typedef Elf32_Shdr Elf_Shdr;
|
||||
typedef Elf32_Addr Elf_Addr;
|
||||
typedef Elf32_Dyn Elf_Dyn;
|
||||
typedef Elf32_Rel Elf_Rela;
|
||||
typedef Elf32_Sym Elf_Sym;
|
||||
typedef Elf32_Off Elf_Off;
|
||||
|
||||
#define ELF_R_SYM(i) ELF32_R_SYM(i)
|
||||
#endif
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class ElfImg {
|
||||
public:
|
||||
|
||||
ElfImg(const char* elf);
|
||||
|
||||
Elf_Addr getSymbOffset(const char* name);
|
||||
|
||||
void* getModuleBase(const char* name);
|
||||
|
||||
Elf_Addr getSymbAddress(const char* name);
|
||||
|
||||
~ElfImg();
|
||||
|
||||
private:
|
||||
const char* elf = nullptr;
|
||||
void* base = nullptr;
|
||||
char* buffer = nullptr;
|
||||
off_t size = 0;
|
||||
off_t bias = -4396;
|
||||
Elf_Ehdr* header = nullptr;
|
||||
Elf_Shdr* section_header = nullptr;
|
||||
Elf_Shdr* symtab = nullptr;
|
||||
Elf_Shdr* strtab = nullptr;
|
||||
Elf_Shdr* dynsym = nullptr;
|
||||
Elf_Off dynsym_count = 0;
|
||||
Elf_Sym* symtab_start = nullptr;
|
||||
Elf_Sym* dynsym_start = nullptr;
|
||||
Elf_Sym* strtab_start = nullptr;
|
||||
Elf_Off symtab_count = 0;
|
||||
Elf_Off symstr_offset = 0;
|
||||
Elf_Off symstr_offset_for_symtab = 0;
|
||||
Elf_Off symtab_offset = 0;
|
||||
Elf_Off dynsym_offset = 0;
|
||||
Elf_Off symtab_size = 0;
|
||||
Elf_Off dynsym_size = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_ELF_UTIL_H
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Created by swift on 2019/1/21.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_HIDE_API_H
|
||||
#define SANDHOOK_HIDE_API_H
|
||||
|
||||
#include <jni.h>
|
||||
#include "dlfcn_nougat.h"
|
||||
#include "dlfcn.h"
|
||||
#include <memory>
|
||||
#include "../includes/art_compiler_options.h"
|
||||
#include "../includes/art_jit.h"
|
||||
#include "../includes/art_method.h"
|
||||
|
||||
#if defined(__aarch64__)
|
||||
# define __get_tls() ({ void** __val; __asm__("mrs %0, tpidr_el0" : "=r"(__val)); __val; })
|
||||
#elif defined(__arm__)
|
||||
# define __get_tls() ({ void** __val; __asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(__val)); __val; })
|
||||
#endif
|
||||
|
||||
#define TLS_SLOT_ART_THREAD 7
|
||||
|
||||
using namespace art::mirror;
|
||||
|
||||
extern "C" {
|
||||
|
||||
void initHideApi(JNIEnv *env);
|
||||
bool compileMethod(void *artMethod, void *thread);
|
||||
|
||||
void suspendVM();
|
||||
void resumeVM();
|
||||
|
||||
bool canGetObject();
|
||||
jobject getJavaObject(JNIEnv* env, void* thread, void* address);
|
||||
void *getCurrentThread();
|
||||
|
||||
art::jit::JitCompiler* getGlobalJitCompiler();
|
||||
|
||||
art::CompilerOptions* getCompilerOptions(art::jit::JitCompiler* compiler);
|
||||
|
||||
art::CompilerOptions* getGlobalCompilerOptions();
|
||||
|
||||
bool disableJitInline(art::CompilerOptions* compilerOptions);
|
||||
|
||||
void* getInterpreterBridge(bool isNative);
|
||||
|
||||
bool replaceUpdateCompilerOptionsQ();
|
||||
|
||||
bool forceProcessProfiles();
|
||||
|
||||
bool hookClassInit(void(*callback)(void*));
|
||||
|
||||
JNIEnv *attachAndGetEvn();
|
||||
|
||||
ArtMethod* getArtMethod(JNIEnv *env, jobject method);
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_HIDE_API_H
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
//
|
||||
// Created by swift on 2019/2/3.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_INST_VISTOR_H
|
||||
#define SANDHOOK_INST_VISTOR_H
|
||||
|
||||
#include <cstdint>
|
||||
#include "arch.h"
|
||||
|
||||
#define CASE(inst,mask,match,type) \
|
||||
if ((inst & mask) == match) { return type; } \
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
union Arm32Code {
|
||||
uint32_t code;
|
||||
struct {
|
||||
uint32_t cond:4;
|
||||
uint32_t empty:2;
|
||||
uint32_t opcode:4;
|
||||
uint32_t s:1;
|
||||
uint32_t rn:4;
|
||||
uint32_t rd:4;
|
||||
uint32_t operand2:12;
|
||||
} units;
|
||||
};
|
||||
|
||||
union Arm16Code {
|
||||
uint16_t code;
|
||||
struct {
|
||||
uint32_t cond:16;
|
||||
} units;
|
||||
};
|
||||
|
||||
enum InstArch {
|
||||
ARM32 = 0,
|
||||
Thumb16,
|
||||
Thumb32,
|
||||
Arm64,
|
||||
X86,
|
||||
X64
|
||||
};
|
||||
|
||||
enum class InstType_Thumb32 {
|
||||
// BLX <label>
|
||||
BLX_THUMB32 = 0,
|
||||
// BL <label>
|
||||
BL_THUMB32,
|
||||
// B.W <label>
|
||||
B1_THUMB32,
|
||||
// B.W <label>
|
||||
B2_THUMB32,
|
||||
// ADR.W Rd, <label>
|
||||
ADR1_THUMB32,
|
||||
// ADR.W Rd, <label>
|
||||
ADR2_THUMB32,
|
||||
// LDR.W Rt, <label>
|
||||
LDR_THUMB32,
|
||||
// TBB [PC, Rm]
|
||||
TBB_THUMB32,
|
||||
// TBH [PC, Rm, LSL #1]
|
||||
TBH_THUMB32,
|
||||
PC_NO_RELATED
|
||||
};
|
||||
|
||||
enum class InstType_Thumb16 {
|
||||
// B <label>
|
||||
B1_THUMB16 = 0,
|
||||
// B <label>
|
||||
B2_THUMB16,
|
||||
// BX PC
|
||||
BX_THUMB16,
|
||||
// ADD <Rdn>, PC (Rd != PC, Rn != PC) 在对ADD进行修正时,
|
||||
//采用了替换PC为Rr的方法,当Rd也为PC时,由于之前更改了Rr的值,
|
||||
//可能会影响跳转后的正常功能。
|
||||
ADD_THUMB16,
|
||||
// MOV Rd, PC
|
||||
MOV_THUMB16,
|
||||
// ADR Rd, <label>
|
||||
ADR_THUMB16,
|
||||
// LDR Rt, <label>
|
||||
LDR_THUMB16,
|
||||
PC_NO_RELATED
|
||||
};
|
||||
|
||||
enum class InstType_Arm64 {
|
||||
CBZ_CBNZ = 0,
|
||||
B_COND,
|
||||
TBZ_TBNZ,
|
||||
B_BL,
|
||||
LDR_LIT,
|
||||
ADR_ADRP,
|
||||
PC_NO_RELATED
|
||||
};
|
||||
|
||||
class Inst {
|
||||
public:
|
||||
virtual int instLen() const = 0;
|
||||
|
||||
virtual InstArch instArch() const = 0;
|
||||
|
||||
virtual bool pcRelated() = 0;
|
||||
|
||||
virtual Size bin() = 0;
|
||||
};
|
||||
|
||||
class InstVisitor {
|
||||
public:
|
||||
virtual bool visit(Inst* inst, Size offset, Size length) = 0;
|
||||
};
|
||||
|
||||
class InstDecode {
|
||||
public:
|
||||
static void decode(void* codeStart, Size codeLen, InstVisitor* visitor);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_INST_VISTOR_H
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/2/15.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_LOG_H
|
||||
#define SANDHOOK_LOG_H
|
||||
|
||||
#include "android/log.h"
|
||||
|
||||
#define TAG "SandHook-Native"
|
||||
|
||||
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
|
||||
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__)
|
||||
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__)
|
||||
|
||||
#endif //SANDHOOK_LOG_H
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/4/12.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_NATIVE_HOOK_H
|
||||
#define SANDHOOK_NATIVE_HOOK_H
|
||||
|
||||
#include "sandhook.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class NativeHook {
|
||||
public:
|
||||
static bool hookDex2oat(bool disableDex2oat);
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_NATIVE_HOOK_H
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
//
|
||||
// Created by swift on 2019/6/3.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_NEVER_CALL_H
|
||||
#define SANDHOOK_NEVER_CALL_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_ClassNeverCall_neverCallNative(JNIEnv *env, jobject instance);
|
||||
|
||||
#endif //SANDHOOK_NEVER_CALL_H
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
//
|
||||
// Created by swift on 2019/2/3.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_OFFSET_H
|
||||
#define SANDHOOK_OFFSET_H
|
||||
|
||||
#include <unistd.h>
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class Offset {
|
||||
public:
|
||||
|
||||
template<typename T>
|
||||
static int findOffset(void *start, size_t len, size_t step, T value);
|
||||
|
||||
template<typename T>
|
||||
static int findOffsetWithCB1(void *start, size_t len, size_t step, bool func(int, T));
|
||||
|
||||
template<typename T>
|
||||
static int findOffsetWithCB2(void *start1, void *start2, size_t len, size_t step, bool func(T, T));
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_OFFSET_H
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/4/12.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_SANDHOOK_H
|
||||
#define SANDHOOK_SANDHOOK_H
|
||||
|
||||
#include <jni.h>
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT bool nativeHookNoBackup(void* origin, void* hook);
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void* findSym(const char *elf, const char *sym_name);
|
||||
|
||||
#endif //SANDHOOK_SANDHOOK_H
|
||||
|
|
@ -0,0 +1,231 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/1/17.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_TRAMPOLINE_H
|
||||
#define SANDHOOK_TRAMPOLINE_H
|
||||
|
||||
#include <cstdint>
|
||||
#include <string.h>
|
||||
#include "arch.h"
|
||||
#include "arch_base.h"
|
||||
#include "stdlib.h"
|
||||
#include <sys/mman.h>
|
||||
|
||||
#define Code unsigned char *
|
||||
|
||||
#if defined(__i386__)
|
||||
#define SIZE_REPLACEMENT_HOOK_TRAMPOLINE 4 * 9
|
||||
#define OFFSET_REPLACEMENT_ADDR_ART_METHOD 4 * 5
|
||||
#define OFFSET_REPLACEMENT_OFFSET_ENTRY_CODE 4 * 7
|
||||
|
||||
#define SIZE_DIRECT_JUMP_TRAMPOLINE 4 * 4
|
||||
#define OFFSET_JUMP_ADDR_TARGET 4 * 2
|
||||
|
||||
#define SIZE_INLINE_HOOK_TRAMPOLINE 4 * 24
|
||||
#define OFFSET_INLINE_ADDR_ORIGIN_METHOD 4 * 10
|
||||
#define OFFSET_INLINE_ORIGIN_CODE 4 * 12
|
||||
#define OFFSET_INLINE_OFFSET_ENTRY_CODE 4 * 20
|
||||
#define OFFSET_INLINE_ADDR_HOOK_METHOD 4 * 22
|
||||
#elif defined(__x86_64__)
|
||||
#define SIZE_REPLACEMENT_HOOK_TRAMPOLINE 4 * 9
|
||||
#define OFFSET_REPLACEMENT_ADDR_ART_METHOD 4 * 5
|
||||
#define OFFSET_REPLACEMENT_OFFSET_ENTRY_CODE 4 * 7
|
||||
|
||||
#define SIZE_DIRECT_JUMP_TRAMPOLINE 4 * 4
|
||||
#define OFFSET_JUMP_ADDR_TARGET 4 * 2
|
||||
|
||||
#define SIZE_INLINE_HOOK_TRAMPOLINE 4 * 24
|
||||
#define OFFSET_INLINE_ADDR_ORIGIN_METHOD 4 * 10
|
||||
#define OFFSET_INLINE_ORIGIN_CODE 4 * 12
|
||||
#define OFFSET_INLINE_OFFSET_ENTRY_CODE 4 * 20
|
||||
#define OFFSET_INLINE_ADDR_HOOK_METHOD 4 * 22
|
||||
#elif defined(__arm__)
|
||||
#define SIZE_REPLACEMENT_HOOK_TRAMPOLINE 4 * 5
|
||||
#define OFFSET_REPLACEMENT_ART_METHOD 4 * 3
|
||||
#define OFFSET_REPLACEMENT_OFFSET_CODE_ENTRY 4 * 4
|
||||
|
||||
#define SIZE_DIRECT_JUMP_TRAMPOLINE 4 * 2
|
||||
#define OFFSET_JUMP_ADDR_TARGET 4 * 1
|
||||
|
||||
#define SIZE_INLINE_HOOK_TRAMPOLINE 4 * 17
|
||||
#define OFFSET_INLINE_ORIGIN_CODE 4 * 6
|
||||
#define OFFSET_INLINE_ORIGIN_ART_METHOD 4 * 13
|
||||
#define OFFSET_INLINE_ADDR_ORIGIN_CODE_ENTRY 4 * 14
|
||||
#define OFFSET_INLINE_HOOK_ART_METHOD 4 * 15
|
||||
#define OFFSET_INLINE_ADDR_HOOK_CODE_ENTRY 4 * 16
|
||||
#define OFFSET_INLINE_OP_ORIGIN_OFFSET_CODE 4 * 11
|
||||
|
||||
#define SIZE_CALL_ORIGIN_TRAMPOLINE 4 * 4
|
||||
#define OFFSET_CALL_ORIGIN_ART_METHOD 4 * 2
|
||||
#define OFFSET_CALL_ORIGIN_JUMP_ADDR 4 * 3
|
||||
|
||||
#define SIZE_ORIGIN_PLACE_HOLDER 4 * 3
|
||||
#elif defined(__aarch64__)
|
||||
#define SIZE_REPLACEMENT_HOOK_TRAMPOLINE 4 * 8
|
||||
#define OFFSET_REPLACEMENT_ART_METHOD 4 * 4
|
||||
#define OFFSET_REPLACEMENT_OFFSET_CODE_ENTRY 4 * 6
|
||||
|
||||
#define SIZE_DIRECT_JUMP_TRAMPOLINE 4 * 4
|
||||
#define OFFSET_JUMP_ADDR_TARGET 4 * 2
|
||||
|
||||
#define SIZE_INLINE_HOOK_TRAMPOLINE 4 * 23
|
||||
#define OFFSET_INLINE_ORIGIN_CODE 4 * 7
|
||||
#define OFFSET_INLINE_ORIGIN_ART_METHOD 4 * 15
|
||||
#define OFFSET_INLINE_ADDR_ORIGIN_CODE_ENTRY 4 * 17
|
||||
#define OFFSET_INLINE_HOOK_ART_METHOD 4 * 19
|
||||
#define OFFSET_INLINE_ADDR_HOOK_CODE_ENTRY 4 * 21
|
||||
|
||||
#define SIZE_CALL_ORIGIN_TRAMPOLINE 4 * 7
|
||||
#define OFFSET_CALL_ORIGIN_ART_METHOD 4 * 3
|
||||
#define OFFSET_CALL_ORIGIN_JUMP_ADDR 4 * 5
|
||||
|
||||
#define SIZE_ORIGIN_PLACE_HOLDER 4 * 4
|
||||
#else
|
||||
#endif
|
||||
|
||||
extern "C" void DIRECT_JUMP_TRAMPOLINE();
|
||||
extern "C" void INLINE_HOOK_TRAMPOLINE();
|
||||
extern "C" void REPLACEMENT_HOOK_TRAMPOLINE();
|
||||
extern "C" void CALL_ORIGIN_TRAMPOLINE();
|
||||
|
||||
#if defined(__arm__)
|
||||
#include <unistd.h>
|
||||
extern "C" void DIRECT_JUMP_TRAMPOLINE_T();
|
||||
extern "C" void INLINE_HOOK_TRAMPOLINE_T();
|
||||
extern "C" void CALL_ORIGIN_TRAMPOLINE_T();
|
||||
#endif
|
||||
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
//deal with little or big edn
|
||||
union Code32Bit {
|
||||
uint32_t code;
|
||||
struct {
|
||||
uint32_t op1:8;
|
||||
uint32_t op2:8;
|
||||
uint32_t op3:8;
|
||||
uint32_t op4:8;
|
||||
} op;
|
||||
};
|
||||
|
||||
class Trampoline {
|
||||
public:
|
||||
Code code;
|
||||
|
||||
Trampoline() = default;
|
||||
|
||||
virtual void init() {
|
||||
codeLen = codeLength();
|
||||
tempCode = templateCode();
|
||||
}
|
||||
|
||||
void setThumb(bool thumb) {
|
||||
isThumb = thumb;
|
||||
}
|
||||
|
||||
bool isThumbCode() {
|
||||
return isThumb;
|
||||
}
|
||||
|
||||
void setExecuteSpace(Code start) {
|
||||
code = start;
|
||||
memcpy(code, tempCode, codeLen);
|
||||
flushCache(reinterpret_cast<Size>(code), codeLen);
|
||||
}
|
||||
|
||||
void setEntryCodeOffset(Size offSet) {
|
||||
this->codeEntryOffSet = offSet;
|
||||
}
|
||||
|
||||
void codeCopy(Code src, Size targetOffset, Size len) {
|
||||
memcpy(reinterpret_cast<void*>((Size)code + targetOffset), src, len);
|
||||
flushCache((Size)code + targetOffset, len);
|
||||
}
|
||||
|
||||
static bool flushCache(Size addr, Size len) {
|
||||
#if defined(__arm__)
|
||||
//clearCacheArm32(reinterpret_cast<char*>(addr), reinterpret_cast<char*>(addr + len));
|
||||
int i = cacheflush(addr, addr + len, 0);
|
||||
if (i == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#elif defined(__aarch64__)
|
||||
char *begin = reinterpret_cast<char *>(addr);
|
||||
__builtin___clear_cache(begin, begin + len);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
void clone(Code dest) {
|
||||
memcpy(dest, code, codeLen);
|
||||
}
|
||||
|
||||
Code getCode() {
|
||||
if (isThumbCode()) {
|
||||
return getThumbCodePcAddress(code);
|
||||
} else {
|
||||
return code;
|
||||
}
|
||||
}
|
||||
|
||||
Size getCodeLen() {
|
||||
return codeLen;
|
||||
}
|
||||
|
||||
bool isBigEnd(void) {
|
||||
int i = 1;
|
||||
unsigned char *pointer;
|
||||
pointer = (unsigned char *) &i;
|
||||
return *pointer == 0;
|
||||
}
|
||||
|
||||
//tweak imm of a 32bit asm code
|
||||
void tweakOpImm(Size codeOffset, unsigned char imm) {
|
||||
Code32Bit code32Bit;
|
||||
code32Bit.code = *reinterpret_cast<uint32_t*>(((Size)code + codeOffset));
|
||||
if (isBigEnd()) {
|
||||
code32Bit.op.op2 = imm;
|
||||
} else {
|
||||
code32Bit.op.op3 = imm;
|
||||
}
|
||||
codeCopy(reinterpret_cast<Code>(&code32Bit.code), codeOffset, 4);
|
||||
flushCache((Size)code + codeOffset, 4);
|
||||
}
|
||||
|
||||
//work for thumb
|
||||
static Code getThumbCodeAddress(Code code) {
|
||||
Size addr = reinterpret_cast<Size>(code) & (~0x00000001);
|
||||
return reinterpret_cast<Code>(addr);
|
||||
}
|
||||
|
||||
static Code getThumbCodePcAddress(Code code) {
|
||||
Size addr = reinterpret_cast<Size>(code) & (~0x00000001);
|
||||
return reinterpret_cast<Code>(addr + 1);
|
||||
}
|
||||
|
||||
void* getEntryCodeAddr(void* method) {
|
||||
return reinterpret_cast<void*>((Size)method + codeEntryOffSet);
|
||||
}
|
||||
|
||||
Code getEntryCode(void* method) {
|
||||
Code entryCode = *reinterpret_cast<Code*>((Size)method + codeEntryOffSet);
|
||||
return entryCode;
|
||||
}
|
||||
|
||||
protected:
|
||||
virtual Size codeLength() = 0;
|
||||
virtual Code templateCode() = 0;
|
||||
private:
|
||||
Code tempCode;
|
||||
Size codeLen;
|
||||
Size codeEntryOffSet;
|
||||
bool isThumb = false;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //SANDHOOK_TRAMPOLINE_H
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
//
|
||||
// Created by swift on 2019/1/20.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_TRAMPOLINE_MANAGER_H
|
||||
#define SANDHOOK_TRAMPOLINE_MANAGER_H
|
||||
|
||||
#include "map"
|
||||
#include "list"
|
||||
#include "../trampoline/trampoline.cpp"
|
||||
#include "../utils/lock.h"
|
||||
#include <sys/mman.h>
|
||||
#include "art_method.h"
|
||||
#include "log.h"
|
||||
#include <unistd.h>
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
#define MMAP_PAGE_SIZE sysconf(_SC_PAGESIZE)
|
||||
#define EXE_BLOCK_SIZE MMAP_PAGE_SIZE
|
||||
|
||||
using namespace art;
|
||||
|
||||
|
||||
class HookTrampoline {
|
||||
public:
|
||||
|
||||
HookTrampoline() = default;
|
||||
|
||||
Trampoline* replacement = nullptr;
|
||||
Trampoline* inlineJump = nullptr;
|
||||
Trampoline* inlineSecondory = nullptr;
|
||||
Trampoline* callOrigin = nullptr;
|
||||
Trampoline* hookNative = nullptr;
|
||||
|
||||
Code originCode = nullptr;
|
||||
};
|
||||
|
||||
class TrampolineManager {
|
||||
public:
|
||||
TrampolineManager() = default;
|
||||
|
||||
static TrampolineManager &get();
|
||||
|
||||
void init(Size quickCompileOffset) {
|
||||
this->quickCompileOffset = quickCompileOffset;
|
||||
}
|
||||
|
||||
Code allocExecuteSpace(Size size);
|
||||
|
||||
//java hook
|
||||
HookTrampoline* installReplacementTrampoline(mirror::ArtMethod* originMethod, mirror::ArtMethod* hookMethod, mirror::ArtMethod* backupMethod);
|
||||
HookTrampoline* installInlineTrampoline(mirror::ArtMethod* originMethod, mirror::ArtMethod* hookMethod, mirror::ArtMethod* backupMethod);
|
||||
|
||||
//native hook
|
||||
HookTrampoline* installNativeHookTrampolineNoBackup(void* origin, void* hook);
|
||||
|
||||
bool canSafeInline(mirror::ArtMethod* method);
|
||||
|
||||
uint32_t sizeOfEntryCode(mirror::ArtMethod* method);
|
||||
|
||||
HookTrampoline* getHookTrampoline(mirror::ArtMethod* method) {
|
||||
return trampolines[method];
|
||||
}
|
||||
|
||||
bool methodHooked(ArtMethod *method) {
|
||||
return trampolines.find(method) != trampolines.end();
|
||||
}
|
||||
|
||||
bool memUnprotect(Size addr, Size len) {
|
||||
long pagesize = sysconf(_SC_PAGESIZE);
|
||||
unsigned alignment = (unsigned)((unsigned long long)addr % pagesize);
|
||||
int i = mprotect((void *) (addr - alignment), (size_t) (alignment + len),
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
if (i == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
Code getEntryCode(void* method) {
|
||||
Code entryCode = *reinterpret_cast<Code*>((Size)method + quickCompileOffset);
|
||||
return entryCode;
|
||||
}
|
||||
|
||||
static bool isThumbCode(Size codeAddr) {
|
||||
return (codeAddr & 0x1) == 0x1;
|
||||
}
|
||||
|
||||
static void checkThumbCode(Trampoline* trampoline, Code code) {
|
||||
#if defined(__arm__)
|
||||
trampoline->setThumb(isThumbCode(reinterpret_cast<Size>(code)));
|
||||
#endif
|
||||
}
|
||||
|
||||
static Code getThumbCodeAddress(Code code) {
|
||||
Size addr = reinterpret_cast<Size>(code) & (~0x00000001);
|
||||
return reinterpret_cast<Code>(addr);
|
||||
}
|
||||
|
||||
bool inlineSecurityCheck = true;
|
||||
bool skipAllCheck = false;
|
||||
private:
|
||||
|
||||
Size quickCompileOffset;
|
||||
std::map<mirror::ArtMethod*,HookTrampoline*> trampolines;
|
||||
std::list<Code> executeSpaceList = std::list<Code>();
|
||||
std::mutex allocSpaceLock;
|
||||
std::mutex installLock;
|
||||
Size executePageOffset = 0;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_TRAMPOLINE_MANAGER_H
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
//
|
||||
// Created by 甘尧 on 2019/1/13.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_UTILS_H
|
||||
#define SANDHOOK_UTILS_H
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <sys/mman.h>
|
||||
#include "jni.h"
|
||||
#include "../includes/arch.h"
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <type_traits>
|
||||
|
||||
#define RoundUpToPtrSize(x) (x + BYTE_POINT - 1 - ((x + BYTE_POINT - 1) & (BYTE_POINT - 1)))
|
||||
|
||||
extern "C" {
|
||||
|
||||
Size getAddressFromJava(JNIEnv *env, const char *className, const char *fieldName);
|
||||
|
||||
Size callStaticMethodAddr(JNIEnv *env, const char *className, const char *method, const char *sig, ...);
|
||||
|
||||
jobject callStaticMethodObject(JNIEnv *env, const char *className, const char *method, const char *sig, ...);
|
||||
|
||||
jobject getMethodObject(JNIEnv *env, const char *clazz, const char *method);
|
||||
|
||||
Size getAddressFromJavaByCallMethod(JNIEnv *env, const char *className, const char *methodName);
|
||||
|
||||
jint getIntFromJava(JNIEnv *env, const char *className, const char *fieldName);
|
||||
|
||||
bool getBooleanFromJava(JNIEnv *env, const char *className, const char *fieldName);
|
||||
|
||||
bool munprotect(size_t addr, size_t len);
|
||||
|
||||
bool flushCacheExt(Size addr, Size len);
|
||||
|
||||
}
|
||||
|
||||
|
||||
#endif //SANDHOOK_UTILS_H
|
||||
|
|
@ -0,0 +1,134 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/2/11.
|
||||
//
|
||||
|
||||
#if defined(__arm__)
|
||||
|
||||
#include "../includes/inst.h"
|
||||
#include "../includes/trampoline.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class InstThumb32 : public Inst {
|
||||
public:
|
||||
|
||||
Arm32Code code;
|
||||
InstType_Thumb32 instType = InstType_Thumb32::PC_NO_RELATED;
|
||||
|
||||
InstThumb32(uint32_t code) {
|
||||
this->code.code = code;
|
||||
instType = initType();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int instLen() const override {
|
||||
return 4;
|
||||
}
|
||||
|
||||
InstArch instArch() const override {
|
||||
return Thumb32;
|
||||
}
|
||||
|
||||
bool pcRelated() override {
|
||||
return instType < InstType_Thumb32::PC_NO_RELATED;
|
||||
}
|
||||
|
||||
Size bin() override {
|
||||
return code.code;
|
||||
}
|
||||
|
||||
InstType_Thumb32 initType() {
|
||||
CASE(code.code, 0xF800D000, 0xF000C000, InstType_Thumb32::BLX_THUMB32)
|
||||
CASE(code.code, 0xF800D000, 0xF000D000, InstType_Thumb32::BL_THUMB32)
|
||||
CASE(code.code, 0xF800D000, 0xF0008000, InstType_Thumb32::B1_THUMB32)
|
||||
CASE(code.code, 0xF800D000, 0xF0009000, InstType_Thumb32::B2_THUMB32)
|
||||
CASE(code.code, 0xFBFF8000, 0xF2AF0000, InstType_Thumb32::ADR1_THUMB32)
|
||||
CASE(code.code, 0xFBFF8000, 0xF20F0000, InstType_Thumb32::ADR2_THUMB32)
|
||||
CASE(code.code, 0xFF7F0000, 0xF85F0000, InstType_Thumb32::LDR_THUMB32)
|
||||
CASE(code.code, 0xFFFF00F0, 0xE8DF0000, InstType_Thumb32::TBB_THUMB32)
|
||||
CASE(code.code, 0xFFFF00F0, 0xE8DF0010, InstType_Thumb32::TBH_THUMB32)
|
||||
return InstType_Thumb32::PC_NO_RELATED;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
class InstThumb16 : public Inst {
|
||||
public:
|
||||
|
||||
|
||||
Arm16Code code;
|
||||
InstType_Thumb16 instType = InstType_Thumb16::PC_NO_RELATED;
|
||||
|
||||
InstThumb16(uint16_t code) {
|
||||
this->code.code = code;
|
||||
instType = initType();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int instLen() const override {
|
||||
return 2;
|
||||
}
|
||||
|
||||
InstArch instArch() const override {
|
||||
return Thumb16;
|
||||
}
|
||||
|
||||
bool pcRelated() override {
|
||||
return instType < InstType_Thumb16 ::PC_NO_RELATED;
|
||||
}
|
||||
|
||||
Size bin() override {
|
||||
return code.code;
|
||||
}
|
||||
|
||||
InstType_Thumb16 initType() {
|
||||
CASE(code.code, 0xF000, 0xD000, InstType_Thumb16::B1_THUMB16)
|
||||
CASE(code.code, 0xF800, 0xE000, InstType_Thumb16::B2_THUMB16)
|
||||
CASE(code.code, 0xFFF8, 0x4778, InstType_Thumb16::BX_THUMB16)
|
||||
CASE(code.code, 0xFF78, 0x4478, InstType_Thumb16::ADD_THUMB16)
|
||||
CASE(code.code, 0xFF78, 0x4678, InstType_Thumb16::MOV_THUMB16)
|
||||
CASE(code.code, 0xF800, 0xA000, InstType_Thumb16::ADR_THUMB16)
|
||||
CASE(code.code, 0xF800, 0x4800, InstType_Thumb16::LDR_THUMB16)
|
||||
return InstType_Thumb16::PC_NO_RELATED;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
bool isThumbCode(Size codeAddr) {
|
||||
return (codeAddr & 0x1) == 0x1;
|
||||
}
|
||||
|
||||
bool isThumb32(uint16_t code) {
|
||||
return ((code & 0xF000) == 0xF000) || ((code & 0xF800) == 0xE800);
|
||||
}
|
||||
|
||||
void InstDecode::decode(void *codeStart, Size codeLen, InstVisitor *visitor) {
|
||||
Size offset = 0;
|
||||
Inst* inst = nullptr;
|
||||
if (isThumbCode(reinterpret_cast<Size>(codeStart))) {
|
||||
codeStart = Trampoline::getThumbCodeAddress(static_cast<Code>(codeStart));
|
||||
Size codeAddr = reinterpret_cast<Size>(codeStart);
|
||||
while (offset < codeLen) {
|
||||
uint16_t ram16 = *reinterpret_cast<uint16_t*>(codeAddr + offset);
|
||||
uint32_t ram32 = *reinterpret_cast<uint32_t*>(codeAddr + offset);
|
||||
if (isThumb32(ram16)) {
|
||||
//thumb32
|
||||
inst = new InstThumb32(ram32);
|
||||
} else {
|
||||
//thumb16
|
||||
inst = new InstThumb16(ram16);
|
||||
}
|
||||
if (!visitor->visit(inst, offset, codeLen)) {
|
||||
delete inst;
|
||||
break;
|
||||
}
|
||||
offset += inst->instLen();
|
||||
delete inst;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,74 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/2/11.
|
||||
//
|
||||
|
||||
#if defined(__aarch64__)
|
||||
|
||||
#include "../includes/inst.h"
|
||||
#include "../includes/trampoline.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class InstArm64 : public Inst {
|
||||
|
||||
public:
|
||||
|
||||
Arm32Code code;
|
||||
InstType_Arm64 instType;
|
||||
|
||||
InstArm64(uint32_t code) {
|
||||
this->code.code = code;
|
||||
instType = initType();
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
int instLen() const override {
|
||||
return 4;
|
||||
}
|
||||
|
||||
InstArch instArch() const override {
|
||||
return Arm64;
|
||||
}
|
||||
|
||||
bool pcRelated() override {
|
||||
return instType < InstType_Arm64::PC_NO_RELATED;
|
||||
}
|
||||
|
||||
Size bin() override {
|
||||
return code.code;
|
||||
}
|
||||
|
||||
private:
|
||||
|
||||
InstType_Arm64 initType() {
|
||||
CASE(code.code, 0x7e000000, 0x34000000, InstType_Arm64::CBZ_CBNZ);
|
||||
CASE(code.code, 0xff000010, 0x54000000, InstType_Arm64::B_COND);
|
||||
CASE(code.code, 0x7e000000, 0x36000000, InstType_Arm64::TBZ_TBNZ);
|
||||
CASE(code.code, 0x7c000000, 0x14000000, InstType_Arm64::B_BL);
|
||||
CASE(code.code, 0x3b000000, 0x18000000, InstType_Arm64::LDR_LIT);
|
||||
CASE(code.code, 0x1f000000, 0x10000000, InstType_Arm64::ADR_ADRP);
|
||||
return InstType_Arm64::PC_NO_RELATED;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
void InstDecode::decode(void *codeStart, Size codeLen, InstVisitor *visitor) {
|
||||
Size offset = 0;
|
||||
Inst *inst = nullptr;
|
||||
codeStart = Trampoline::getThumbCodeAddress(static_cast<Code>(codeStart));
|
||||
Size codeAddr = reinterpret_cast<Size>(codeStart);
|
||||
while (offset < codeLen) {
|
||||
uint32_t ram32 = *reinterpret_cast<uint32_t *>(codeAddr + offset);
|
||||
inst = new InstArm64(ram32);
|
||||
if (!visitor->visit(inst, offset, codeLen)) {
|
||||
delete inst;
|
||||
break;
|
||||
}
|
||||
offset += inst->instLen();
|
||||
delete inst;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,96 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/4/12.
|
||||
//
|
||||
|
||||
#include <syscall.h>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "../includes/native_hook.h"
|
||||
#include "../includes/arch.h"
|
||||
#include "../includes/log.h"
|
||||
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
int inline getArrayItemCount(char *const array[]) {
|
||||
int i;
|
||||
for (i = 0; array[i]; ++i);
|
||||
return i;
|
||||
}
|
||||
|
||||
bool isSandHooker(char *const args[]) {
|
||||
int orig_arg_count = getArrayItemCount(args);
|
||||
|
||||
for (int i = 0; i < orig_arg_count; i++) {
|
||||
if (strstr(args[i], "SandHooker")) {
|
||||
LOGE("skip dex2oat hooker!");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
char **build_new_argv(char *const argv[]) {
|
||||
|
||||
int orig_argv_count = getArrayItemCount(argv);
|
||||
|
||||
int new_argv_count = orig_argv_count + 2;
|
||||
char **new_argv = (char **) malloc(new_argv_count * sizeof(char *));
|
||||
int cur = 0;
|
||||
for (int i = 0; i < orig_argv_count; ++i) {
|
||||
new_argv[cur++] = argv[i];
|
||||
}
|
||||
|
||||
if (SDK_INT >= ANDROID_L2 && SDK_INT < ANDROID_Q) {
|
||||
new_argv[cur++] = (char *) "--compile-pic";
|
||||
}
|
||||
if (SDK_INT >= ANDROID_M) {
|
||||
new_argv[cur++] = (char *) (SDK_INT > ANDROID_N2 ? "--inline-max-code-units=0" : "--inline-depth-limit=0");
|
||||
}
|
||||
|
||||
new_argv[cur] = NULL;
|
||||
|
||||
return new_argv;
|
||||
}
|
||||
|
||||
int fake_execve_disable_inline(const char *pathname, char *argv[], char *const envp[]) {
|
||||
if (strstr(pathname, "dex2oat")) {
|
||||
if (SDK_INT >= ANDROID_N && isSandHooker(argv)) {
|
||||
LOGE("skip dex2oat!");
|
||||
return -1;
|
||||
}
|
||||
char **new_args = build_new_argv(argv);
|
||||
LOGE("dex2oat by disable inline!");
|
||||
int ret = static_cast<int>(syscall(__NR_execve, pathname, new_args, envp));
|
||||
free(new_args);
|
||||
return ret;
|
||||
}
|
||||
int ret = static_cast<int>(syscall(__NR_execve, pathname, argv, envp));
|
||||
return ret;
|
||||
}
|
||||
|
||||
int fake_execve_disable_oat(const char *pathname, char *argv[], char *const envp[]) {
|
||||
if (strstr(pathname, "dex2oat")) {
|
||||
LOGE("skip dex2oat!");
|
||||
return -1;
|
||||
}
|
||||
return static_cast<int>(syscall(__NR_execve, pathname, argv, envp));
|
||||
}
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
volatile bool hasHookedDex2oat = false;
|
||||
|
||||
bool NativeHook::hookDex2oat(bool disableDex2oat) {
|
||||
if (hasHookedDex2oat)
|
||||
return false;
|
||||
|
||||
hasHookedDex2oat = true;
|
||||
return nativeHookNoBackup(reinterpret_cast<void *>(execve),
|
||||
reinterpret_cast<void *>(disableDex2oat ? fake_execve_disable_oat : fake_execve_disable_inline));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,567 @@
|
|||
#include "includes/sandhook.h"
|
||||
#include "includes/cast_art_method.h"
|
||||
#include "includes/trampoline_manager.h"
|
||||
#include "includes/hide_api.h"
|
||||
#include "includes/cast_compiler_options.h"
|
||||
#include "includes/log.h"
|
||||
#include "includes/native_hook.h"
|
||||
#include "includes/elf_util.h"
|
||||
#include "includes/never_call.h"
|
||||
#include <jni.h>
|
||||
|
||||
SandHook::TrampolineManager &trampolineManager = SandHook::TrampolineManager::get();
|
||||
|
||||
extern "C" int SDK_INT = 0;
|
||||
extern "C" bool DEBUG = false;
|
||||
|
||||
enum HookMode {
|
||||
AUTO = 0,
|
||||
INLINE = 1,
|
||||
REPLACE = 2
|
||||
};
|
||||
|
||||
HookMode gHookMode = AUTO;
|
||||
|
||||
void ensureMethodCached(art::mirror::ArtMethod *hookMethod, art::mirror::ArtMethod *backupMethod) {
|
||||
if (SDK_INT >= ANDROID_P)
|
||||
return;
|
||||
|
||||
SandHook::StopTheWorld stopTheWorld;
|
||||
|
||||
uint32_t index = backupMethod->getDexMethodIndex();
|
||||
if (SDK_INT < ANDROID_O2) {
|
||||
hookMethod->setDexCacheResolveItem(index, backupMethod);
|
||||
} else {
|
||||
int cacheSize = 1024;
|
||||
Size slotIndex = index % cacheSize;
|
||||
Size newCachedMethodsArray = reinterpret_cast<Size>(calloc(cacheSize, BYTE_POINT * 2));
|
||||
unsigned int one = 1;
|
||||
memcpy(reinterpret_cast<void *>(newCachedMethodsArray + BYTE_POINT), &one, 4);
|
||||
memcpy(reinterpret_cast<void *>(newCachedMethodsArray + BYTE_POINT * 2 * slotIndex),
|
||||
(&backupMethod),
|
||||
BYTE_POINT
|
||||
);
|
||||
memcpy(reinterpret_cast<void *>(newCachedMethodsArray + BYTE_POINT * 2 * slotIndex + BYTE_POINT),
|
||||
&index,
|
||||
4
|
||||
);
|
||||
hookMethod->setDexCacheResolveList(&newCachedMethodsArray);
|
||||
}
|
||||
}
|
||||
|
||||
void ensureDeclareClass(JNIEnv *env, jclass type, jobject originMethod,
|
||||
jobject backupMethod) {
|
||||
if (originMethod == NULL || backupMethod == NULL)
|
||||
return;
|
||||
art::mirror::ArtMethod* origin = getArtMethod(env, originMethod);
|
||||
art::mirror::ArtMethod* backup = getArtMethod(env, backupMethod);
|
||||
if (origin->getDeclaringClass() != backup->getDeclaringClass()) {
|
||||
LOGW("declaring class has been moved!");
|
||||
backup->setDeclaringClass(origin->getDeclaringClass());
|
||||
}
|
||||
}
|
||||
|
||||
bool doHookWithReplacement(JNIEnv* env,
|
||||
art::mirror::ArtMethod *originMethod,
|
||||
art::mirror::ArtMethod *hookMethod,
|
||||
art::mirror::ArtMethod *backupMethod) {
|
||||
|
||||
if (!hookMethod->compile(env)) {
|
||||
hookMethod->disableCompilable();
|
||||
}
|
||||
|
||||
if (SDK_INT > ANDROID_N && SDK_INT < ANDROID_Q) {
|
||||
forceProcessProfiles();
|
||||
}
|
||||
if ((SDK_INT >= ANDROID_N && SDK_INT <= ANDROID_P)
|
||||
|| (SDK_INT >= ANDROID_Q && !originMethod->isAbstract())) {
|
||||
originMethod->setHotnessCount(0);
|
||||
}
|
||||
|
||||
if (backupMethod != nullptr) {
|
||||
originMethod->backup(backupMethod);
|
||||
backupMethod->disableCompilable();
|
||||
if (!backupMethod->isStatic()) {
|
||||
backupMethod->setPrivate();
|
||||
}
|
||||
backupMethod->flushCache();
|
||||
}
|
||||
|
||||
originMethod->disableCompilable();
|
||||
hookMethod->disableCompilable();
|
||||
hookMethod->flushCache();
|
||||
|
||||
originMethod->disableInterpreterForO();
|
||||
originMethod->disableFastInterpreterForQ();
|
||||
|
||||
SandHook::HookTrampoline* hookTrampoline = trampolineManager.installReplacementTrampoline(originMethod, hookMethod, backupMethod);
|
||||
if (hookTrampoline != nullptr) {
|
||||
originMethod->setQuickCodeEntry(hookTrampoline->replacement->getCode());
|
||||
void* entryPointFormInterpreter = hookMethod->getInterpreterCodeEntry();
|
||||
if (entryPointFormInterpreter != NULL) {
|
||||
originMethod->setInterpreterCodeEntry(entryPointFormInterpreter);
|
||||
}
|
||||
if (hookTrampoline->callOrigin != nullptr) {
|
||||
backupMethod->setQuickCodeEntry(hookTrampoline->callOrigin->getCode());
|
||||
backupMethod->flushCache();
|
||||
}
|
||||
originMethod->flushCache();
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool doHookWithInline(JNIEnv* env,
|
||||
art::mirror::ArtMethod *originMethod,
|
||||
art::mirror::ArtMethod *hookMethod,
|
||||
art::mirror::ArtMethod *backupMethod) {
|
||||
|
||||
//fix >= 8.1
|
||||
if (!hookMethod->compile(env)) {
|
||||
hookMethod->disableCompilable();
|
||||
}
|
||||
|
||||
originMethod->disableCompilable();
|
||||
if (SDK_INT > ANDROID_N && SDK_INT < ANDROID_Q) {
|
||||
forceProcessProfiles();
|
||||
}
|
||||
if ((SDK_INT >= ANDROID_N && SDK_INT <= ANDROID_P)
|
||||
|| (SDK_INT >= ANDROID_Q && !originMethod->isAbstract())) {
|
||||
originMethod->setHotnessCount(0);
|
||||
}
|
||||
originMethod->flushCache();
|
||||
|
||||
SandHook::HookTrampoline* hookTrampoline = trampolineManager.installInlineTrampoline(originMethod, hookMethod, backupMethod);
|
||||
|
||||
if (hookTrampoline == nullptr)
|
||||
return false;
|
||||
|
||||
hookMethod->flushCache();
|
||||
if (hookTrampoline->callOrigin != nullptr) {
|
||||
//backup
|
||||
originMethod->backup(backupMethod);
|
||||
backupMethod->setQuickCodeEntry(hookTrampoline->callOrigin->getCode());
|
||||
backupMethod->disableCompilable();
|
||||
if (!backupMethod->isStatic()) {
|
||||
backupMethod->setPrivate();
|
||||
}
|
||||
backupMethod->flushCache();
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_initNative(JNIEnv *env, jclass type, jint sdk, jboolean debug) {
|
||||
SDK_INT = sdk;
|
||||
DEBUG = debug;
|
||||
initHideApi(env);
|
||||
SandHook::CastArtMethod::init(env);
|
||||
SandHook::CastCompilerOptions::init(env);
|
||||
trampolineManager.init(SandHook::CastArtMethod::entryPointQuickCompiled->getOffset());
|
||||
return JNI_TRUE;
|
||||
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jint JNICALL
|
||||
Java_com_swift_sandhook_SandHook_hookMethod(JNIEnv *env, jclass type, jobject originMethod,
|
||||
jobject hookMethod, jobject backupMethod, jint hookMode) {
|
||||
|
||||
art::mirror::ArtMethod* origin = getArtMethod(env, originMethod);
|
||||
art::mirror::ArtMethod* hook = getArtMethod(env, hookMethod);
|
||||
art::mirror::ArtMethod* backup = backupMethod == NULL ? nullptr : getArtMethod(env,
|
||||
backupMethod);
|
||||
|
||||
bool isInlineHook = false;
|
||||
|
||||
int mode = reinterpret_cast<int>(hookMode);
|
||||
|
||||
if (mode == INLINE) {
|
||||
if (!origin->isCompiled()) {
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
isInlineHook = origin->compile(env);
|
||||
}
|
||||
} else {
|
||||
isInlineHook = true;
|
||||
}
|
||||
goto label_hook;
|
||||
} else if (mode == REPLACE) {
|
||||
isInlineHook = false;
|
||||
goto label_hook;
|
||||
}
|
||||
|
||||
if (origin->isAbstract()) {
|
||||
isInlineHook = false;
|
||||
} else if (gHookMode != AUTO) {
|
||||
if (gHookMode == INLINE) {
|
||||
isInlineHook = origin->compile(env);
|
||||
} else {
|
||||
isInlineHook = false;
|
||||
}
|
||||
} else if (SDK_INT >= ANDROID_O) {
|
||||
isInlineHook = false;
|
||||
} else if (!origin->isCompiled()) {
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
isInlineHook = origin->compile(env);
|
||||
} else {
|
||||
isInlineHook = false;
|
||||
}
|
||||
} else {
|
||||
isInlineHook = true;
|
||||
}
|
||||
|
||||
|
||||
label_hook:
|
||||
//suspend other threads
|
||||
SandHook::StopTheWorld stopTheWorld;
|
||||
if (isInlineHook && trampolineManager.canSafeInline(origin)) {
|
||||
return doHookWithInline(env, origin, hook, backup) ? INLINE : -1;
|
||||
} else {
|
||||
return doHookWithReplacement(env, origin, hook, backup) ? REPLACE : -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_SandHook_ensureMethodCached(JNIEnv *env, jclass type, jobject hook,
|
||||
jobject backup) {
|
||||
art::mirror::ArtMethod* hookeMethod = getArtMethod(env, hook);
|
||||
art::mirror::ArtMethod* backupMethod = backup == NULL ? nullptr : getArtMethod(env, backup);
|
||||
ensureMethodCached(hookeMethod, backupMethod);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_compileMethod(JNIEnv *env, jclass type, jobject member) {
|
||||
|
||||
if (member == NULL)
|
||||
return JNI_FALSE;
|
||||
art::mirror::ArtMethod* method = getArtMethod(env, member);
|
||||
|
||||
if (method == nullptr)
|
||||
return JNI_FALSE;
|
||||
|
||||
if (!method->isCompiled()) {
|
||||
SandHook::StopTheWorld stopTheWorld;
|
||||
if (!method->compile(env)) {
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
method->disableCompilable();
|
||||
method->flushCache();
|
||||
}
|
||||
return JNI_FALSE;
|
||||
} else {
|
||||
return JNI_TRUE;
|
||||
}
|
||||
} else {
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_deCompileMethod(JNIEnv *env, jclass type, jobject member, jboolean disableJit) {
|
||||
|
||||
if (member == NULL)
|
||||
return JNI_FALSE;
|
||||
art::mirror::ArtMethod* method = getArtMethod(env, member);
|
||||
|
||||
if (method == nullptr)
|
||||
return JNI_FALSE;
|
||||
|
||||
if (disableJit) {
|
||||
method->disableCompilable();
|
||||
}
|
||||
|
||||
if (method->isCompiled()) {
|
||||
SandHook::StopTheWorld stopTheWorld;
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
method->disableCompilable();
|
||||
}
|
||||
return static_cast<jboolean>(method->deCompile());
|
||||
} else {
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jobject JNICALL
|
||||
Java_com_swift_sandhook_SandHook_getObjectNative(JNIEnv *env, jclass type, jlong thread,
|
||||
jlong address) {
|
||||
return getJavaObject(env, thread ? reinterpret_cast<void *>(thread) : getCurrentThread(), reinterpret_cast<void *>(address));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_canGetObject(JNIEnv *env, jclass type) {
|
||||
return static_cast<jboolean>(canGetObject());
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_SandHook_setHookMode(JNIEnv *env, jclass type, jint mode) {
|
||||
gHookMode = static_cast<HookMode>(mode);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_SandHook_setInlineSafeCheck(JNIEnv *env, jclass type, jboolean check) {
|
||||
trampolineManager.inlineSecurityCheck = check;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_SandHook_skipAllSafeCheck(JNIEnv *env, jclass type, jboolean skip) {
|
||||
trampolineManager.skipAllCheck = skip;
|
||||
}
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_is64Bit(JNIEnv *env, jclass type) {
|
||||
return static_cast<jboolean>(BYTE_POINT == 8);
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_disableVMInline(JNIEnv *env, jclass type) {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return JNI_FALSE;
|
||||
replaceUpdateCompilerOptionsQ();
|
||||
art::CompilerOptions* compilerOptions = getGlobalCompilerOptions();
|
||||
if (compilerOptions == nullptr)
|
||||
return JNI_FALSE;
|
||||
return static_cast<jboolean>(disableJitInline(compilerOptions));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_disableDex2oatInline(JNIEnv *env, jclass type, jboolean disableDex2oat) {
|
||||
return static_cast<jboolean>(SandHook::NativeHook::hookDex2oat(disableDex2oat));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_setNativeEntry(JNIEnv *env, jclass type, jobject origin, jobject hook, jlong jniTrampoline) {
|
||||
if (origin == nullptr || hook == NULL)
|
||||
return JNI_FALSE;
|
||||
art::mirror::ArtMethod* hookMethod = getArtMethod(env, hook);
|
||||
art::mirror::ArtMethod* originMethod = getArtMethod(env, origin);
|
||||
originMethod->backup(hookMethod);
|
||||
hookMethod->setNative();
|
||||
hookMethod->setQuickCodeEntry(SandHook::CastArtMethod::genericJniStub);
|
||||
hookMethod->setJniCodeEntry(reinterpret_cast<void *>(jniTrampoline));
|
||||
hookMethod->disableCompilable();
|
||||
hookMethod->flushCache();
|
||||
return JNI_TRUE;
|
||||
}
|
||||
|
||||
|
||||
static jclass class_pending_hook = nullptr;
|
||||
static jmethodID method_class_init = nullptr;
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT jboolean JNICALL
|
||||
Java_com_swift_sandhook_SandHook_initForPendingHook(JNIEnv *env, jclass type) {
|
||||
class_pending_hook = static_cast<jclass>(env->NewGlobalRef(
|
||||
env->FindClass("com/swift/sandhook/PendingHookHandler")));
|
||||
method_class_init = env->GetStaticMethodID(class_pending_hook, "onClassInit", "(J)V");
|
||||
auto class_init_handler = [](void *clazz_ptr) {
|
||||
attachAndGetEvn()->CallStaticVoidMethod(class_pending_hook, method_class_init, (jlong) clazz_ptr);
|
||||
attachAndGetEvn()->ExceptionClear();
|
||||
};
|
||||
return static_cast<jboolean>(hookClassInit(class_init_handler));
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_ClassNeverCall_neverCallNative(JNIEnv *env, jobject instance) {
|
||||
int a = 1 + 1;
|
||||
int b = a + 1;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_ClassNeverCall_neverCallNative2(JNIEnv *env, jobject instance) {
|
||||
int a = 4 + 3;
|
||||
int b = 9 + 6;
|
||||
}
|
||||
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void JNICALL
|
||||
Java_com_swift_sandhook_test_TestClass_jni_1test(JNIEnv *env, jobject instance) {
|
||||
int a = 1 + 1;
|
||||
int b = a + 1;
|
||||
}
|
||||
|
||||
//native hook
|
||||
extern "C"
|
||||
JNIEXPORT bool nativeHookNoBackup(void* origin, void* hook) {
|
||||
|
||||
if (origin == nullptr || hook == nullptr)
|
||||
return false;
|
||||
|
||||
SandHook::StopTheWorld stopTheWorld;
|
||||
|
||||
return trampolineManager.installNativeHookTrampolineNoBackup(origin, hook) != nullptr;
|
||||
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT void* findSym(const char *elf, const char *sym_name) {
|
||||
SandHook::ElfImg elfImg(elf);
|
||||
return reinterpret_cast<void *>(elfImg.getSymbAddress(sym_name));
|
||||
}
|
||||
|
||||
static JNINativeMethod jniSandHook[] = {
|
||||
{
|
||||
"initNative",
|
||||
"(IZ)Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_initNative
|
||||
},
|
||||
{
|
||||
"hookMethod",
|
||||
"(Ljava/lang/reflect/Member;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;I)I",
|
||||
(void *) Java_com_swift_sandhook_SandHook_hookMethod
|
||||
},
|
||||
{
|
||||
"ensureMethodCached",
|
||||
"(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V",
|
||||
(void *) Java_com_swift_sandhook_SandHook_ensureMethodCached
|
||||
},
|
||||
{
|
||||
"ensureDeclareClass",
|
||||
"(Ljava/lang/reflect/Member;Ljava/lang/reflect/Method;)V",
|
||||
(void *) ensureDeclareClass
|
||||
},
|
||||
{
|
||||
"compileMethod",
|
||||
"(Ljava/lang/reflect/Member;)Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_compileMethod
|
||||
},
|
||||
{
|
||||
"deCompileMethod",
|
||||
"(Ljava/lang/reflect/Member;Z)Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_deCompileMethod
|
||||
},
|
||||
{
|
||||
"getObjectNative",
|
||||
"(JJ)Ljava/lang/Object;",
|
||||
(void *) Java_com_swift_sandhook_SandHook_getObjectNative
|
||||
},
|
||||
{
|
||||
"canGetObject",
|
||||
"()Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_canGetObject
|
||||
},
|
||||
{
|
||||
"setHookMode",
|
||||
"(I)V",
|
||||
(void *) Java_com_swift_sandhook_SandHook_setHookMode
|
||||
},
|
||||
{
|
||||
"setInlineSafeCheck",
|
||||
"(Z)V",
|
||||
(void *) Java_com_swift_sandhook_SandHook_setInlineSafeCheck
|
||||
},
|
||||
{
|
||||
"skipAllSafeCheck",
|
||||
"(Z)V",
|
||||
(void *) Java_com_swift_sandhook_SandHook_skipAllSafeCheck
|
||||
},
|
||||
{
|
||||
"is64Bit",
|
||||
"()Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_is64Bit
|
||||
},
|
||||
{
|
||||
"disableVMInline",
|
||||
"()Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_disableVMInline
|
||||
},
|
||||
{
|
||||
"disableDex2oatInline",
|
||||
"(Z)Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_disableDex2oatInline
|
||||
},
|
||||
{
|
||||
"setNativeEntry",
|
||||
"(Ljava/lang/reflect/Member;Ljava/lang/reflect/Member;J)Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_setNativeEntry
|
||||
},
|
||||
{
|
||||
"initForPendingHook",
|
||||
"()Z",
|
||||
(void *) Java_com_swift_sandhook_SandHook_initForPendingHook
|
||||
}
|
||||
};
|
||||
|
||||
static JNINativeMethod jniNeverCall[] = {
|
||||
{
|
||||
"neverCallNative",
|
||||
"()V",
|
||||
(void *) Java_com_swift_sandhook_ClassNeverCall_neverCallNative
|
||||
},
|
||||
{
|
||||
"neverCallNative2",
|
||||
"()V",
|
||||
(void *) Java_com_swift_sandhook_ClassNeverCall_neverCallNative2
|
||||
}
|
||||
};
|
||||
|
||||
static bool registerNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *jniMethods, int methods) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
return false;
|
||||
}
|
||||
return env->RegisterNatives(clazz, jniMethods, methods) >= 0;
|
||||
}
|
||||
|
||||
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
|
||||
|
||||
const char* CLASS_SAND_HOOK = "com/swift/sandhook/SandHook";
|
||||
const char* CLASS_NEVER_CALL = "com/swift/sandhook/ClassNeverCall";
|
||||
|
||||
int jniMethodSize = sizeof(JNINativeMethod);
|
||||
|
||||
JNIEnv *env = NULL;
|
||||
|
||||
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!registerNativeMethods(env, CLASS_SAND_HOOK, jniSandHook, sizeof(jniSandHook) / jniMethodSize)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!registerNativeMethods(env, CLASS_NEVER_CALL, jniNeverCall, sizeof(jniNeverCall) / jniMethodSize)) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
LOGW("JNI Loaded");
|
||||
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
extern "C"
|
||||
JNIEXPORT bool JNI_Load_Ex(JNIEnv* env, jclass classSandHook, jclass classNeverCall) {
|
||||
int jniMethodSize = sizeof(JNINativeMethod);
|
||||
|
||||
if (env == nullptr || classSandHook == nullptr || classNeverCall == nullptr)
|
||||
return false;
|
||||
|
||||
if (env->RegisterNatives(classSandHook, jniSandHook, sizeof(jniSandHook) / jniMethodSize) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (env->RegisterNatives(classNeverCall, jniNeverCall, sizeof(jniNeverCall) / jniMethodSize) < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
LOGW("JNI Loaded");
|
||||
return true;
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
#include "../../includes/arch_base.h"
|
||||
|
||||
#if defined(__arm__)
|
||||
|
||||
#define Reg0 ip
|
||||
//need restore
|
||||
#define RegT ip
|
||||
#define RegMethod r0
|
||||
|
||||
FUNCTION_START(REPLACEMENT_HOOK_TRAMPOLINE)
|
||||
ldr RegMethod, addr_art_method
|
||||
ldr Reg0, addr_code_entry
|
||||
ldr pc, [Reg0]
|
||||
addr_art_method:
|
||||
.long 0
|
||||
addr_code_entry:
|
||||
.long 0
|
||||
FUNCTION_END(REPLACEMENT_HOOK_TRAMPOLINE)
|
||||
|
||||
|
||||
#define SIZE_JUMP #0x8
|
||||
FUNCTION_START(DIRECT_JUMP_TRAMPOLINE)
|
||||
ldr pc, addr_target
|
||||
addr_target:
|
||||
.long 0
|
||||
FUNCTION_END(DIRECT_JUMP_TRAMPOLINE)
|
||||
|
||||
|
||||
|
||||
FUNCTION_START(INLINE_HOOK_TRAMPOLINE)
|
||||
ldr Reg0, origin_art_method
|
||||
cmp RegMethod, Reg0
|
||||
bne origin_code
|
||||
ldr RegMethod, hook_art_method
|
||||
ldr Reg0, addr_hook_code_entry
|
||||
ldr pc, [Reg0]
|
||||
origin_code:
|
||||
.long 0
|
||||
.long 0
|
||||
nop
|
||||
ldr Reg0, addr_origin_code_entry
|
||||
ldr Reg0, [Reg0]
|
||||
add Reg0, Reg0, SIZE_JUMP
|
||||
mov pc, Reg0
|
||||
origin_art_method:
|
||||
.long 0
|
||||
addr_origin_code_entry:
|
||||
.long 0
|
||||
hook_art_method:
|
||||
.long 0
|
||||
addr_hook_code_entry:
|
||||
.long 0
|
||||
FUNCTION_END(INLINE_HOOK_TRAMPOLINE)
|
||||
|
||||
|
||||
FUNCTION_START(CALL_ORIGIN_TRAMPOLINE)
|
||||
ldr RegMethod, origin_method
|
||||
ldr pc, addr_origin
|
||||
origin_method:
|
||||
.long 0
|
||||
addr_origin:
|
||||
.long 0
|
||||
FUNCTION_END(CALL_ORIGIN_TRAMPOLINE)
|
||||
|
||||
//thumb-2
|
||||
FUNCTION_START_T(DIRECT_JUMP_TRAMPOLINE_T)
|
||||
ldr pc, addr_target_t
|
||||
addr_target_t:
|
||||
.long 0
|
||||
FUNCTION_END(DIRECT_JUMP_TRAMPOLINE_T)
|
||||
|
||||
|
||||
FUNCTION_START_T(INLINE_HOOK_TRAMPOLINE_T)
|
||||
//4 byte
|
||||
ldr RegT, origin_art_method_t
|
||||
//2 byte
|
||||
cmp RegMethod, RegT
|
||||
nop
|
||||
//2 byte
|
||||
bne origin_code_t
|
||||
nop
|
||||
//4 byte
|
||||
ldr RegMethod, hook_art_method_t
|
||||
//4 byte
|
||||
ldr RegT, addr_hook_code_entry_t
|
||||
//4 byte
|
||||
ldr pc, [RegT]
|
||||
origin_code_t:
|
||||
//4 byte
|
||||
.long 0
|
||||
//4 byte
|
||||
.long 0
|
||||
//4byte
|
||||
nop
|
||||
nop
|
||||
//4 byte
|
||||
ldr RegT, addr_origin_code_entry_t
|
||||
//4 byte
|
||||
ldr RegT, [RegT]
|
||||
//4 byte
|
||||
add RegT, RegT, SIZE_JUMP
|
||||
//2 byte
|
||||
mov pc, RegT
|
||||
nop
|
||||
origin_art_method_t:
|
||||
.long 0
|
||||
addr_origin_code_entry_t:
|
||||
.long 0
|
||||
hook_art_method_t:
|
||||
.long 0
|
||||
addr_hook_code_entry_t:
|
||||
.long 0
|
||||
FUNCTION_END(INLINE_HOOK_TRAMPOLINE_T)
|
||||
|
||||
FUNCTION_START_T(CALL_ORIGIN_TRAMPOLINE_T)
|
||||
ldr RegMethod, origin_method_t
|
||||
ldr pc, addr_origin_t
|
||||
origin_method_t:
|
||||
.long 0
|
||||
addr_origin_t:
|
||||
.long 0
|
||||
FUNCTION_END(CALL_ORIGIN_TRAMPOLINE_T)
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
#include "../../includes/arch_base.h"
|
||||
|
||||
/**
|
||||
*
|
||||
//aarch64 ART 寄存器使用策略
|
||||
|
||||
|
||||
// Method register on invoke.
|
||||
// 储存正在调用的代码
|
||||
static const vixl::aarch64::Register kArtMethodRegister = vixl::aarch64::x0;
|
||||
|
||||
//参数传递
|
||||
static const vixl::aarch64::Register kParameterCoreRegisters[] = {
|
||||
vixl::aarch64::x1,
|
||||
vixl::aarch64::x2,
|
||||
vixl::aarch64::x3,
|
||||
vixl::aarch64::x4,
|
||||
vixl::aarch64::x5,
|
||||
vixl::aarch64::x6,
|
||||
vixl::aarch64::x7
|
||||
};
|
||||
|
||||
//
|
||||
const vixl::aarch64::CPURegList vixl_reserved_core_registers(vixl::aarch64::ip0,
|
||||
vixl::aarch64::ip1);
|
||||
|
||||
//浮点计算
|
||||
static const vixl::aarch64::FPRegister kParameterFPRegisters[] = {
|
||||
vixl::aarch64::d0,
|
||||
vixl::aarch64::d1,
|
||||
vixl::aarch64::d2,
|
||||
vixl::aarch64::d3,
|
||||
vixl::aarch64::d4,
|
||||
vixl::aarch64::d5,
|
||||
vixl::aarch64::d6,
|
||||
vixl::aarch64::d7
|
||||
};
|
||||
|
||||
// Thread Register.
|
||||
// 线程
|
||||
const vixl::aarch64::Register tr = vixl::aarch64::x19;
|
||||
|
||||
// Marking Register.
|
||||
// GC 标记
|
||||
const vixl::aarch64::Register mr = vixl::aarch64::x20;
|
||||
|
||||
// Callee-save registers AAPCS64, without x19 (Thread Register) (nor
|
||||
// x20 (Marking Register) when emitting Baker read barriers).
|
||||
const vixl::aarch64::CPURegList callee_saved_core_registers(
|
||||
vixl::aarch64::CPURegister::kRegister,
|
||||
vixl::aarch64::kXRegSize,
|
||||
((kEmitCompilerReadBarrier && kUseBakerReadBarrier)
|
||||
? vixl::aarch64::x21.GetCode()
|
||||
: vixl::aarch64::x20.GetCode()),
|
||||
vixl::aarch64::x30.GetCode());
|
||||
|
||||
|
||||
结论,x16/x17
|
||||
|
||||
X16 = IP0
|
||||
Stub 中有使用
|
||||
尽量使用 X17
|
||||
|
||||
*
|
||||
*/
|
||||
|
||||
|
||||
#if defined(__aarch64__)
|
||||
|
||||
#define Reg0 x17
|
||||
#define Reg1 x16
|
||||
#define RegMethod x0
|
||||
|
||||
FUNCTION_START(REPLACEMENT_HOOK_TRAMPOLINE)
|
||||
ldr RegMethod, addr_art_method
|
||||
ldr Reg0, addr_code_entry
|
||||
ldr Reg0, [Reg0]
|
||||
br Reg0
|
||||
addr_art_method:
|
||||
.long 0
|
||||
.long 0
|
||||
addr_code_entry:
|
||||
.long 0
|
||||
.long 0
|
||||
FUNCTION_END(REPLACEMENT_HOOK_TRAMPOLINE)
|
||||
|
||||
#define SIZE_JUMP #0x10
|
||||
FUNCTION_START(DIRECT_JUMP_TRAMPOLINE)
|
||||
ldr Reg0, addr_target
|
||||
br Reg0
|
||||
addr_target:
|
||||
.long 0
|
||||
.long 0
|
||||
FUNCTION_END(DIRECT_JUMP_TRAMPOLINE)
|
||||
|
||||
FUNCTION_START(INLINE_HOOK_TRAMPOLINE)
|
||||
ldr Reg0, origin_art_method
|
||||
cmp RegMethod, Reg0
|
||||
bne origin_code
|
||||
ldr RegMethod, hook_art_method
|
||||
ldr Reg0, addr_hook_code_entry
|
||||
ldr Reg0, [Reg0]
|
||||
br Reg0
|
||||
origin_code:
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
.long 0
|
||||
ldr Reg0, addr_origin_code_entry
|
||||
ldr Reg0, [Reg0]
|
||||
add Reg0, Reg0, SIZE_JUMP
|
||||
br Reg0
|
||||
origin_art_method:
|
||||
.long 0
|
||||
.long 0
|
||||
addr_origin_code_entry:
|
||||
.long 0
|
||||
.long 0
|
||||
hook_art_method:
|
||||
.long 0
|
||||
.long 0
|
||||
addr_hook_code_entry:
|
||||
.long 0
|
||||
.long 0
|
||||
FUNCTION_END(INLINE_HOOK_TRAMPOLINE)
|
||||
|
||||
FUNCTION_START(CALL_ORIGIN_TRAMPOLINE)
|
||||
ldr RegMethod, call_origin_art_method
|
||||
ldr Reg0, addr_call_origin_code
|
||||
br Reg0
|
||||
call_origin_art_method:
|
||||
.long 0
|
||||
.long 0
|
||||
addr_call_origin_code:
|
||||
.long 0
|
||||
.long 0
|
||||
FUNCTION_END(CALL_ORIGIN_TRAMPOLINE)
|
||||
|
||||
#endif
|
||||
|
|
@ -0,0 +1,144 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/1/17.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_TRAMPOLINE_CPP
|
||||
#define SANDHOOK_TRAMPOLINE_CPP
|
||||
|
||||
#include "../includes/trampoline.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class DirectJumpTrampoline : public Trampoline {
|
||||
public:
|
||||
|
||||
DirectJumpTrampoline() : Trampoline::Trampoline() {}
|
||||
|
||||
void setJumpTarget(Code target) {
|
||||
codeCopy(reinterpret_cast<Code>(&target), OFFSET_JUMP_ADDR_TARGET, BYTE_POINT);
|
||||
}
|
||||
|
||||
protected:
|
||||
Size codeLength() override {
|
||||
return SIZE_DIRECT_JUMP_TRAMPOLINE;
|
||||
}
|
||||
|
||||
Code templateCode() override {
|
||||
#if defined(__arm__)
|
||||
if (isThumbCode()) {
|
||||
return getThumbCodeAddress(reinterpret_cast<Code>(DIRECT_JUMP_TRAMPOLINE_T));
|
||||
} else {
|
||||
return reinterpret_cast<Code>(DIRECT_JUMP_TRAMPOLINE);
|
||||
}
|
||||
#else
|
||||
return reinterpret_cast<Code>(DIRECT_JUMP_TRAMPOLINE);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
class ReplacementHookTrampoline : public Trampoline {
|
||||
public:
|
||||
|
||||
void setHookMethod(Code hookMethod) {
|
||||
codeCopy(reinterpret_cast<Code>(&hookMethod), OFFSET_REPLACEMENT_ART_METHOD, BYTE_POINT);
|
||||
void* codeEntry = getEntryCodeAddr(hookMethod);
|
||||
codeCopy(reinterpret_cast<Code>(&codeEntry), OFFSET_REPLACEMENT_OFFSET_CODE_ENTRY, BYTE_POINT);
|
||||
}
|
||||
|
||||
protected:
|
||||
Size codeLength() override {
|
||||
return SIZE_REPLACEMENT_HOOK_TRAMPOLINE;
|
||||
}
|
||||
|
||||
Code templateCode() override {
|
||||
return reinterpret_cast<Code>(REPLACEMENT_HOOK_TRAMPOLINE);
|
||||
}
|
||||
};
|
||||
|
||||
class InlineHookTrampoline : public Trampoline {
|
||||
public:
|
||||
|
||||
void setOriginMethod(Code originMethod) {
|
||||
codeCopy(reinterpret_cast<Code>(&originMethod), OFFSET_INLINE_ORIGIN_ART_METHOD, BYTE_POINT);
|
||||
void* codeEntry = getEntryCodeAddr(originMethod);
|
||||
codeCopy(reinterpret_cast<Code>(&codeEntry), OFFSET_INLINE_ADDR_ORIGIN_CODE_ENTRY, BYTE_POINT);
|
||||
}
|
||||
|
||||
void setHookMethod(Code hookMethod) {
|
||||
codeCopy(reinterpret_cast<Code>(&hookMethod), OFFSET_INLINE_HOOK_ART_METHOD, BYTE_POINT);
|
||||
void* codeEntry = getEntryCodeAddr(hookMethod);
|
||||
codeCopy(reinterpret_cast<Code>(&codeEntry), OFFSET_INLINE_ADDR_HOOK_CODE_ENTRY, BYTE_POINT);
|
||||
}
|
||||
|
||||
// void setEntryCodeOffset(Size offSet) {
|
||||
// codeCopy(reinterpret_cast<Code>(&offSet), OFFSET_INLINE_OFFSET_ENTRY_CODE, BYTE_POINT);
|
||||
// #if defined(__arm__)
|
||||
// Code32Bit offset32;
|
||||
// offset32.code = offSet;
|
||||
// unsigned char offsetOP = isBigEnd() ? offset32.op.op2 : offset32.op.op1;
|
||||
// tweakOpImm(OFFSET_INLINE_OP_OFFSET_CODE, offsetOP);
|
||||
// #endif
|
||||
// }
|
||||
|
||||
void setOriginCode(Code originCode) {
|
||||
codeCopy(originCode, OFFSET_INLINE_ORIGIN_CODE, SIZE_DIRECT_JUMP_TRAMPOLINE);
|
||||
}
|
||||
|
||||
void setOriginCode(Code originCode, Size codeLen) {
|
||||
codeCopy(originCode, OFFSET_INLINE_ORIGIN_CODE, codeLen);
|
||||
}
|
||||
|
||||
Code getCallOriginCode() {
|
||||
return reinterpret_cast<Code>((Size)getCode() + OFFSET_INLINE_ORIGIN_CODE);
|
||||
}
|
||||
|
||||
protected:
|
||||
Size codeLength() override {
|
||||
return SIZE_INLINE_HOOK_TRAMPOLINE;
|
||||
}
|
||||
|
||||
Code templateCode() override {
|
||||
#if defined(__arm__)
|
||||
if (isThumbCode()) {
|
||||
return getThumbCodeAddress(reinterpret_cast<Code>(INLINE_HOOK_TRAMPOLINE_T));
|
||||
} else {
|
||||
return reinterpret_cast<Code>(INLINE_HOOK_TRAMPOLINE);
|
||||
}
|
||||
#else
|
||||
return reinterpret_cast<Code>(INLINE_HOOK_TRAMPOLINE);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
class CallOriginTrampoline : public Trampoline {
|
||||
public:
|
||||
|
||||
void setOriginMethod(Code originMethod) {
|
||||
codeCopy(reinterpret_cast<Code>(&originMethod), OFFSET_CALL_ORIGIN_ART_METHOD, BYTE_POINT);
|
||||
}
|
||||
|
||||
void setOriginCode(Code originCode) {
|
||||
codeCopy(reinterpret_cast<Code>(&originCode), OFFSET_CALL_ORIGIN_JUMP_ADDR, BYTE_POINT);
|
||||
}
|
||||
|
||||
protected:
|
||||
Size codeLength() override {
|
||||
return SIZE_CALL_ORIGIN_TRAMPOLINE;
|
||||
}
|
||||
|
||||
Code templateCode() override {
|
||||
#if defined(__arm__)
|
||||
if (isThumbCode()) {
|
||||
return getThumbCodeAddress(reinterpret_cast<Code>(CALL_ORIGIN_TRAMPOLINE_T));
|
||||
} else {
|
||||
return reinterpret_cast<Code>(CALL_ORIGIN_TRAMPOLINE);
|
||||
}
|
||||
#else
|
||||
return reinterpret_cast<Code>(CALL_ORIGIN_TRAMPOLINE);
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_TRAMPOLINE_CPP
|
||||
|
|
@ -0,0 +1,324 @@
|
|||
//
|
||||
// Created by swift on 2019/1/20.
|
||||
//
|
||||
#include "../includes/trampoline_manager.h"
|
||||
#include "../includes/trampoline.h"
|
||||
#include "../includes/inst.h"
|
||||
#include "../includes/log.h"
|
||||
|
||||
extern int SDK_INT;
|
||||
#define SWITCH_SETX0 false
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
|
||||
uint32_t TrampolineManager::sizeOfEntryCode(mirror::ArtMethod *method) {
|
||||
Code codeEntry = getEntryCode(method);
|
||||
if (codeEntry == nullptr)
|
||||
return 0;
|
||||
#if defined(__arm__)
|
||||
if (isThumbCode(reinterpret_cast<Size>(codeEntry))) {
|
||||
codeEntry = getThumbCodeAddress(codeEntry);
|
||||
}
|
||||
#endif
|
||||
uint32_t size = *reinterpret_cast<uint32_t *>((Size)codeEntry - 4);
|
||||
return size;
|
||||
}
|
||||
|
||||
class PCRelatedCheckVisitor : public InstVisitor {
|
||||
public:
|
||||
|
||||
bool pcRelated = false;
|
||||
bool canSafeBackup = true;
|
||||
|
||||
int instSize = 0;
|
||||
|
||||
TrampolineManager* trampolineManager;
|
||||
|
||||
PCRelatedCheckVisitor(TrampolineManager* t) {
|
||||
trampolineManager = t;
|
||||
}
|
||||
|
||||
bool visit(Inst *inst, Size offset, Size length) override {
|
||||
|
||||
instSize += inst->instLen();
|
||||
|
||||
if (inst->pcRelated()) {
|
||||
LOGW("found pc related inst: %zx !", inst->bin());
|
||||
if (trampolineManager->inlineSecurityCheck) {
|
||||
pcRelated = true;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (instSize > SIZE_ORIGIN_PLACE_HOLDER) {
|
||||
canSafeBackup = false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
class InstSizeNeedBackupVisitor : public InstVisitor {
|
||||
public:
|
||||
|
||||
Size instSize = 0;
|
||||
|
||||
bool visit(Inst *inst, Size offset, Size length) override {
|
||||
instSize += inst->instLen();
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
bool TrampolineManager::canSafeInline(mirror::ArtMethod *method) {
|
||||
|
||||
if (skipAllCheck)
|
||||
return true;
|
||||
|
||||
//check size
|
||||
if (method->isCompiled()) {
|
||||
uint32_t originCodeSize = sizeOfEntryCode(method);
|
||||
if (originCodeSize < SIZE_DIRECT_JUMP_TRAMPOLINE) {
|
||||
LOGW("can not inline due to origin code is too small(size is %d)", originCodeSize);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//check pc relate inst & backup inst len
|
||||
PCRelatedCheckVisitor visitor(this);
|
||||
|
||||
InstDecode::decode(method->getQuickCodeEntry(), SIZE_DIRECT_JUMP_TRAMPOLINE, &visitor);
|
||||
|
||||
return (!visitor.pcRelated) && visitor.canSafeBackup;
|
||||
}
|
||||
|
||||
Code TrampolineManager::allocExecuteSpace(Size size) {
|
||||
if (size > EXE_BLOCK_SIZE)
|
||||
return 0;
|
||||
AutoLock autoLock(allocSpaceLock);
|
||||
void* mmapRes;
|
||||
Code exeSpace = 0;
|
||||
if (executeSpaceList.size() == 0) {
|
||||
goto label_alloc_new_space;
|
||||
} else if (executePageOffset + size > EXE_BLOCK_SIZE) {
|
||||
goto label_alloc_new_space;
|
||||
} else {
|
||||
exeSpace = executeSpaceList.back();
|
||||
Code retSpace = exeSpace + executePageOffset;
|
||||
executePageOffset += size;
|
||||
return retSpace;
|
||||
}
|
||||
label_alloc_new_space:
|
||||
mmapRes = mmap(NULL, EXE_BLOCK_SIZE, PROT_READ | PROT_WRITE | PROT_EXEC,
|
||||
MAP_ANON | MAP_PRIVATE, -1, 0);
|
||||
if (mmapRes == MAP_FAILED) {
|
||||
return 0;
|
||||
}
|
||||
memset(mmapRes, 0, EXE_BLOCK_SIZE);
|
||||
exeSpace = static_cast<Code>(mmapRes);
|
||||
executeSpaceList.push_back(exeSpace);
|
||||
executePageOffset = size;
|
||||
return exeSpace;
|
||||
}
|
||||
|
||||
HookTrampoline* TrampolineManager::installReplacementTrampoline(mirror::ArtMethod *originMethod,
|
||||
mirror::ArtMethod *hookMethod,
|
||||
mirror::ArtMethod *backupMethod) {
|
||||
AutoLock autoLock(installLock);
|
||||
|
||||
if (trampolines.count(originMethod) != 0)
|
||||
return getHookTrampoline(originMethod);
|
||||
HookTrampoline* hookTrampoline = new HookTrampoline();
|
||||
ReplacementHookTrampoline* replacementHookTrampoline = nullptr;
|
||||
CallOriginTrampoline* callOriginTrampoline = nullptr;
|
||||
Code replacementHookTrampolineSpace;
|
||||
Code callOriginTrampolineSpace;
|
||||
|
||||
replacementHookTrampoline = new ReplacementHookTrampoline();
|
||||
replacementHookTrampoline->init();
|
||||
replacementHookTrampolineSpace = allocExecuteSpace(replacementHookTrampoline->getCodeLen());
|
||||
if (replacementHookTrampolineSpace == 0) {
|
||||
LOGE("hook error due to can not alloc execute space!");
|
||||
goto label_error;
|
||||
}
|
||||
replacementHookTrampoline->setExecuteSpace(replacementHookTrampolineSpace);
|
||||
replacementHookTrampoline->setEntryCodeOffset(quickCompileOffset);
|
||||
replacementHookTrampoline->setHookMethod(reinterpret_cast<Code>(hookMethod));
|
||||
hookTrampoline->replacement = replacementHookTrampoline;
|
||||
hookTrampoline->originCode = static_cast<Code>(originMethod->getQuickCodeEntry());
|
||||
|
||||
if (SWITCH_SETX0 && SDK_INT >= ANDROID_N && backupMethod != nullptr) {
|
||||
callOriginTrampoline = new CallOriginTrampoline();
|
||||
checkThumbCode(callOriginTrampoline, getEntryCode(originMethod));
|
||||
callOriginTrampoline->init();
|
||||
callOriginTrampolineSpace = allocExecuteSpace(callOriginTrampoline->getCodeLen());
|
||||
if (callOriginTrampolineSpace == 0)
|
||||
goto label_error;
|
||||
callOriginTrampoline->setExecuteSpace(callOriginTrampolineSpace);
|
||||
callOriginTrampoline->setOriginMethod(reinterpret_cast<Code>(originMethod));
|
||||
Code originCode = getEntryCode(originMethod);
|
||||
if (callOriginTrampoline->isThumbCode()) {
|
||||
originCode = callOriginTrampoline->getThumbCodePcAddress(originCode);
|
||||
}
|
||||
callOriginTrampoline->setOriginCode(originCode);
|
||||
hookTrampoline->callOrigin = callOriginTrampoline;
|
||||
}
|
||||
|
||||
trampolines[originMethod] = hookTrampoline;
|
||||
return hookTrampoline;
|
||||
|
||||
label_error:
|
||||
delete hookTrampoline;
|
||||
delete replacementHookTrampoline;
|
||||
if (callOriginTrampoline != nullptr)
|
||||
delete callOriginTrampoline;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HookTrampoline* TrampolineManager::installInlineTrampoline(mirror::ArtMethod *originMethod,
|
||||
mirror::ArtMethod *hookMethod,
|
||||
mirror::ArtMethod *backupMethod) {
|
||||
|
||||
AutoLock autoLock(installLock);
|
||||
|
||||
if (trampolines.count(originMethod) != 0)
|
||||
return getHookTrampoline(originMethod);
|
||||
HookTrampoline* hookTrampoline = new HookTrampoline();
|
||||
InlineHookTrampoline* inlineHookTrampoline = nullptr;
|
||||
DirectJumpTrampoline* directJumpTrampoline = nullptr;
|
||||
CallOriginTrampoline* callOriginTrampoline = nullptr;
|
||||
Code inlineHookTrampolineSpace;
|
||||
Code callOriginTrampolineSpace;
|
||||
Code originEntry;
|
||||
Size sizeNeedBackup = SIZE_DIRECT_JUMP_TRAMPOLINE;
|
||||
InstSizeNeedBackupVisitor instVisitor;
|
||||
|
||||
InstDecode::decode(originMethod->getQuickCodeEntry(), SIZE_DIRECT_JUMP_TRAMPOLINE, &instVisitor);
|
||||
sizeNeedBackup = instVisitor.instSize;
|
||||
|
||||
//生成二段跳板
|
||||
inlineHookTrampoline = new InlineHookTrampoline();
|
||||
checkThumbCode(inlineHookTrampoline, getEntryCode(originMethod));
|
||||
inlineHookTrampoline->init();
|
||||
inlineHookTrampolineSpace = allocExecuteSpace(inlineHookTrampoline->getCodeLen());
|
||||
if (inlineHookTrampolineSpace == 0) {
|
||||
LOGE("hook error due to can not alloc execute space!");
|
||||
goto label_error;
|
||||
}
|
||||
inlineHookTrampoline->setExecuteSpace(inlineHookTrampolineSpace);
|
||||
inlineHookTrampoline->setEntryCodeOffset(quickCompileOffset);
|
||||
inlineHookTrampoline->setOriginMethod(reinterpret_cast<Code>(originMethod));
|
||||
inlineHookTrampoline->setHookMethod(reinterpret_cast<Code>(hookMethod));
|
||||
if (inlineHookTrampoline->isThumbCode()) {
|
||||
inlineHookTrampoline->setOriginCode(inlineHookTrampoline->getThumbCodeAddress(getEntryCode(originMethod)), sizeNeedBackup);
|
||||
} else {
|
||||
inlineHookTrampoline->setOriginCode(getEntryCode(originMethod), sizeNeedBackup);
|
||||
}
|
||||
hookTrampoline->inlineSecondory = inlineHookTrampoline;
|
||||
|
||||
//注入 EntryCode
|
||||
directJumpTrampoline = new DirectJumpTrampoline();
|
||||
checkThumbCode(directJumpTrampoline, getEntryCode(originMethod));
|
||||
directJumpTrampoline->init();
|
||||
originEntry = getEntryCode(originMethod);
|
||||
if (!memUnprotect(reinterpret_cast<Size>(originEntry), directJumpTrampoline->getCodeLen())) {
|
||||
LOGE("hook error due to can not write origin code!");
|
||||
goto label_error;
|
||||
}
|
||||
|
||||
if (directJumpTrampoline->isThumbCode()) {
|
||||
originEntry = directJumpTrampoline->getThumbCodeAddress(originEntry);
|
||||
}
|
||||
|
||||
directJumpTrampoline->setExecuteSpace(originEntry);
|
||||
directJumpTrampoline->setJumpTarget(inlineHookTrampoline->getCode());
|
||||
hookTrampoline->inlineJump = directJumpTrampoline;
|
||||
|
||||
//备份原始方法
|
||||
if (backupMethod != nullptr) {
|
||||
callOriginTrampoline = new CallOriginTrampoline();
|
||||
checkThumbCode(callOriginTrampoline, getEntryCode(originMethod));
|
||||
callOriginTrampoline->init();
|
||||
callOriginTrampolineSpace = allocExecuteSpace(callOriginTrampoline->getCodeLen());
|
||||
if (callOriginTrampolineSpace == 0) {
|
||||
|
||||
goto label_error;
|
||||
}
|
||||
callOriginTrampoline->setExecuteSpace(callOriginTrampolineSpace);
|
||||
callOriginTrampoline->setOriginMethod(reinterpret_cast<Code>(originMethod));
|
||||
Code originCode = nullptr;
|
||||
if (callOriginTrampoline->isThumbCode()) {
|
||||
originCode = callOriginTrampoline->getThumbCodePcAddress(inlineHookTrampoline->getCallOriginCode());
|
||||
#if defined(__arm__)
|
||||
Code originRemCode = callOriginTrampoline->getThumbCodePcAddress(originEntry + sizeNeedBackup);
|
||||
Size offset = originRemCode - getEntryCode(originMethod);
|
||||
if (offset != directJumpTrampoline->getCodeLen()) {
|
||||
Code32Bit offset32;
|
||||
offset32.code = offset;
|
||||
uint8_t offsetOP = callOriginTrampoline->isBigEnd() ? offset32.op.op4 : offset32.op.op1;
|
||||
inlineHookTrampoline->tweakOpImm(OFFSET_INLINE_OP_ORIGIN_OFFSET_CODE, offsetOP);
|
||||
}
|
||||
#endif
|
||||
} else {
|
||||
originCode = inlineHookTrampoline->getCallOriginCode();
|
||||
}
|
||||
callOriginTrampoline->setOriginCode(originCode);
|
||||
hookTrampoline->callOrigin = callOriginTrampoline;
|
||||
}
|
||||
trampolines[originMethod] = hookTrampoline;
|
||||
return hookTrampoline;
|
||||
|
||||
label_error:
|
||||
delete hookTrampoline;
|
||||
if (inlineHookTrampoline != nullptr) {
|
||||
delete inlineHookTrampoline;
|
||||
}
|
||||
if (directJumpTrampoline != nullptr) {
|
||||
delete directJumpTrampoline;
|
||||
}
|
||||
if (callOriginTrampoline != nullptr) {
|
||||
delete callOriginTrampoline;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
HookTrampoline* TrampolineManager::installNativeHookTrampolineNoBackup(void *origin,
|
||||
void *hook) { HookTrampoline* hookTrampoline = new HookTrampoline();
|
||||
DirectJumpTrampoline* directJumpTrampoline = new DirectJumpTrampoline();
|
||||
|
||||
if (!memUnprotect(reinterpret_cast<Size>(origin), directJumpTrampoline->getCodeLen())) {
|
||||
LOGE("hook error due to can not write origin code!");
|
||||
goto label_error;
|
||||
}
|
||||
|
||||
directJumpTrampoline->init();
|
||||
|
||||
#if defined(__arm__)
|
||||
checkThumbCode(directJumpTrampoline, reinterpret_cast<Code>(origin));
|
||||
if (directJumpTrampoline->isThumbCode()) {
|
||||
origin = directJumpTrampoline->getThumbCodeAddress(reinterpret_cast<Code>(origin));
|
||||
}
|
||||
if (isThumbCode(reinterpret_cast<Size>(hook))) {
|
||||
hook = directJumpTrampoline->getThumbCodePcAddress(reinterpret_cast<Code>(hook));
|
||||
}
|
||||
#endif
|
||||
|
||||
directJumpTrampoline->setExecuteSpace(reinterpret_cast<Code>(origin));
|
||||
directJumpTrampoline->setJumpTarget(reinterpret_cast<Code>(hook));
|
||||
hookTrampoline->inlineJump = directJumpTrampoline;
|
||||
directJumpTrampoline->flushCache(reinterpret_cast<Size>(origin), directJumpTrampoline->getCodeLen());
|
||||
hookTrampoline->hookNative = directJumpTrampoline;
|
||||
return hookTrampoline;
|
||||
|
||||
label_error:
|
||||
delete hookTrampoline;
|
||||
delete directJumpTrampoline;
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TrampolineManager &TrampolineManager::get() {
|
||||
static TrampolineManager trampolineManager;
|
||||
return trampolineManager;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,296 @@
|
|||
// Copyright (c) 2016 avs333
|
||||
//
|
||||
// Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
// of this software and associated documentation files (the "Software"), to deal
|
||||
// in the Software without restriction, including without limitation the rights
|
||||
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
// copies of the Software, and to permit persons to whom the Software is
|
||||
// furnished to do so, subject to the following conditions:
|
||||
//
|
||||
// The above copyright notice and this permission notice shall be included in all
|
||||
// copies or substantial portions of the Software.
|
||||
//
|
||||
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
// SOFTWARE.
|
||||
|
||||
//fork from https://github.com/avs333/Nougat_dlfunctions
|
||||
//do some modify
|
||||
//support all cpu abi such as x86, x86_64
|
||||
//support filename search if filename is not start with '/'
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <unistd.h>
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <elf.h>
|
||||
#include <android/log.h>
|
||||
#include <dlfcn.h>
|
||||
#include <string>
|
||||
#include "../includes/arch.h"
|
||||
#include "../includes/log.h"
|
||||
|
||||
#define TAG_NAME "nougat_dlfcn"
|
||||
|
||||
#define log_info(fmt, args...) __android_log_print(ANDROID_LOG_INFO, TAG_NAME, (const char *) fmt, ##args)
|
||||
#define log_err(fmt, args...) __android_log_print(ANDROID_LOG_ERROR, TAG_NAME, (const char *) fmt, ##args)
|
||||
|
||||
#ifdef LOG_DBG
|
||||
#define log_dbg log_info
|
||||
#else
|
||||
#define log_dbg(...)
|
||||
#endif
|
||||
|
||||
#ifdef __LP64__
|
||||
#define Elf_Ehdr Elf64_Ehdr
|
||||
#define Elf_Shdr Elf64_Shdr
|
||||
#define Elf_Sym Elf64_Sym
|
||||
#else
|
||||
#define Elf_Ehdr Elf32_Ehdr
|
||||
#define Elf_Shdr Elf32_Shdr
|
||||
#define Elf_Sym Elf32_Sym
|
||||
#endif
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
|
||||
struct ctx {
|
||||
void *load_addr;
|
||||
void *dynstr;
|
||||
void *dynsym;
|
||||
int nsyms;
|
||||
off_t bias;
|
||||
};
|
||||
|
||||
extern "C" {
|
||||
|
||||
int fake_dlclose(void *handle) {
|
||||
if (handle) {
|
||||
struct ctx *ctx = (struct ctx *) handle;
|
||||
if (ctx->dynsym) free(ctx->dynsym); /* we're saving dynsym and dynstr */
|
||||
if (ctx->dynstr) free(ctx->dynstr); /* from library file just in case */
|
||||
free(ctx);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
char *rtrim(char *str)
|
||||
{
|
||||
if (str == NULL || *str == '\0')
|
||||
{
|
||||
return str;
|
||||
}
|
||||
int len = static_cast<int>(strlen(str));
|
||||
char *p = str + len - 1;
|
||||
while (p >= str && isspace(*p))
|
||||
{
|
||||
*p = '\0'; --p;
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/* flags are ignored */
|
||||
void *fake_dlopen_with_path(char *libpath, int flags) {
|
||||
FILE *maps;
|
||||
char buff[256];
|
||||
struct ctx *ctx = 0;
|
||||
off_t load_addr, size;
|
||||
int k, fd = -1, found = 0;
|
||||
char *shoff;
|
||||
char* p;
|
||||
Elf_Ehdr *elf = (Elf_Ehdr *) MAP_FAILED;
|
||||
|
||||
#define fatal(fmt, args...) do { log_err(fmt,##args); goto err_exit; } while(0)
|
||||
|
||||
maps = fopen("/proc/self/maps", "r");
|
||||
if (!maps) fatal("failed to open maps");
|
||||
|
||||
while (fgets(buff, sizeof(buff), maps)) {
|
||||
if ((strstr(buff, "r-xp") || strstr(buff, "r--p")) && strstr(buff, libpath)) {
|
||||
found = 1;
|
||||
__android_log_print(ANDROID_LOG_DEBUG, "dlopen", "%s\n", buff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
fclose(maps);
|
||||
|
||||
if (!found) fatal("%s not found in my userspace", libpath);
|
||||
|
||||
if (sscanf(buff, "%lx", &load_addr) != 1)
|
||||
fatal("failed to read load address for %s", libpath);
|
||||
|
||||
log_info("%s loaded in Android at 0x%08lx", libpath, load_addr);
|
||||
|
||||
/* Now, mmap the same library once again */
|
||||
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
p = std::strtok(buff, " ");
|
||||
while (p) {
|
||||
p = std::strtok(NULL, " ");
|
||||
if (p) {
|
||||
libpath = rtrim(p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fd = open(libpath, O_RDONLY);
|
||||
if (fd < 0) fatal("failed to open %s", libpath);
|
||||
|
||||
size = lseek(fd, 0, SEEK_END);
|
||||
if (size <= 0) fatal("lseek() failed for %s", libpath);
|
||||
|
||||
elf = (Elf_Ehdr *) mmap(0, size, PROT_READ, MAP_SHARED, fd, 0);
|
||||
close(fd);
|
||||
fd = -1;
|
||||
|
||||
if (elf == MAP_FAILED) fatal("mmap() failed for %s", libpath);
|
||||
|
||||
ctx = (struct ctx *) calloc(1, sizeof(struct ctx));
|
||||
if (!ctx) fatal("no memory for %s", libpath);
|
||||
|
||||
ctx->load_addr = (void *) load_addr;
|
||||
shoff = ((char *) elf) + elf->e_shoff;
|
||||
|
||||
for (k = 0; k < elf->e_shnum; k++, shoff += elf->e_shentsize) {
|
||||
|
||||
Elf_Shdr *sh = (Elf_Shdr *) shoff;
|
||||
log_dbg("%s: k=%d shdr=%p type=%x", __func__, k, sh, sh->sh_type);
|
||||
|
||||
switch (sh->sh_type) {
|
||||
|
||||
case SHT_DYNSYM:
|
||||
if (ctx->dynsym) fatal("%s: duplicate DYNSYM sections", libpath); /* .dynsym */
|
||||
ctx->dynsym = malloc(sh->sh_size);
|
||||
if (!ctx->dynsym) fatal("%s: no memory for .dynsym", libpath);
|
||||
memcpy(ctx->dynsym, ((char *) elf) + sh->sh_offset, sh->sh_size);
|
||||
ctx->nsyms = (sh->sh_size / sizeof(Elf_Sym));
|
||||
break;
|
||||
|
||||
case SHT_STRTAB:
|
||||
if (ctx->dynstr) break; /* .dynstr is guaranteed to be the first STRTAB */
|
||||
ctx->dynstr = malloc(sh->sh_size);
|
||||
if (!ctx->dynstr) fatal("%s: no memory for .dynstr", libpath);
|
||||
memcpy(ctx->dynstr, ((char *) elf) + sh->sh_offset, sh->sh_size);
|
||||
break;
|
||||
|
||||
case SHT_PROGBITS:
|
||||
if (!ctx->dynstr || !ctx->dynsym) break;
|
||||
/* won't even bother checking against the section name */
|
||||
ctx->bias = (off_t) sh->sh_addr - (off_t) sh->sh_offset;
|
||||
k = elf->e_shnum; /* exit for */
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
munmap(elf, size);
|
||||
elf = 0;
|
||||
|
||||
if (!ctx->dynstr || !ctx->dynsym) fatal("dynamic sections not found in %s", libpath);
|
||||
|
||||
#undef fatal
|
||||
|
||||
log_dbg("%s: ok, dynsym = %p, dynstr = %p", libpath, ctx->dynsym, ctx->dynstr);
|
||||
|
||||
return ctx;
|
||||
|
||||
err_exit:
|
||||
if (fd >= 0) close(fd);
|
||||
if (elf != MAP_FAILED) munmap(elf, size);
|
||||
fake_dlclose(ctx);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
#if defined(__LP64__)
|
||||
static const char *const kSystemLibDir = "/system/lib64/";
|
||||
static const char *const kOdmLibDir = "/odm/lib64/";
|
||||
static const char *const kVendorLibDir = "/vendor/lib64/";
|
||||
#else
|
||||
static const char* const kSystemLibDir = "/system/lib/";
|
||||
static const char* const kOdmLibDir = "/odm/lib/";
|
||||
static const char* const kVendorLibDir = "/vendor/lib/";
|
||||
#endif
|
||||
|
||||
void *fake_dlopen(char *filename, int flags) {
|
||||
if (strlen(filename) > 0 && filename[0] == '/') {
|
||||
return fake_dlopen_with_path(filename, flags);
|
||||
} else {
|
||||
char buf[512] = {0};
|
||||
void *handle = NULL;
|
||||
//sysmtem
|
||||
strcpy(buf, kSystemLibDir);
|
||||
strcat(buf, filename);
|
||||
handle = fake_dlopen_with_path(buf, flags);
|
||||
if (handle) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
//odm
|
||||
memset(buf, 0, sizeof(buf));
|
||||
strcpy(buf, kOdmLibDir);
|
||||
strcat(buf, filename);
|
||||
handle = fake_dlopen_with_path(buf, flags);
|
||||
if (handle) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
//vendor
|
||||
memset(buf, 0, sizeof(buf));
|
||||
strcpy(buf, kVendorLibDir);
|
||||
strcat(buf, filename);
|
||||
handle = fake_dlopen_with_path(buf, flags);
|
||||
if (handle) {
|
||||
return handle;
|
||||
}
|
||||
|
||||
return fake_dlopen_with_path(filename, flags);
|
||||
}
|
||||
}
|
||||
|
||||
void *fake_dlsym(void *handle, const char *name) {
|
||||
int k;
|
||||
struct ctx *ctx = (struct ctx *) handle;
|
||||
Elf_Sym *sym = (Elf_Sym *) ctx->dynsym;
|
||||
char *strings = (char *) ctx->dynstr;
|
||||
|
||||
for (k = 0; k < ctx->nsyms; k++, sym++)
|
||||
if (strcmp(strings + sym->st_name, name) == 0) {
|
||||
/* NB: sym->st_value is an offset into the section for relocatables,
|
||||
but a VMA for shared libs or exe files, so we have to subtract the bias */
|
||||
void *ret = (char *) ctx->load_addr + sym->st_value - ctx->bias;
|
||||
log_info("%s found at %p", name, ret);
|
||||
return ret;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
const char *fake_dlerror() {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
|
||||
void *getSymCompat(const char *filename, const char *name) {
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
void* handle = fake_dlopen(const_cast<char *>(filename), RTLD_NOW);
|
||||
if (handle) {
|
||||
void* ret = fake_dlsym(handle, name);
|
||||
fake_dlclose(handle);
|
||||
return ret;
|
||||
}
|
||||
} else {
|
||||
void* handle = dlopen(filename, RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (handle) {
|
||||
return dlsym(handle, name);
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,171 @@
|
|||
//
|
||||
// Created by Swift Gan on 2019/3/14.
|
||||
//
|
||||
#include <malloc.h>
|
||||
#include <string.h>
|
||||
#include <sys/mman.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
#include <assert.h>
|
||||
#include "../includes/elf_util.h"
|
||||
#include "../includes/log.h"
|
||||
|
||||
using namespace SandHook;
|
||||
|
||||
ElfImg::ElfImg(const char *elf) {
|
||||
this->elf = elf;
|
||||
//load elf
|
||||
int fd = open(elf, O_RDONLY);
|
||||
if (fd < 0) {
|
||||
LOGE("failed to open %s", elf);
|
||||
return;
|
||||
}
|
||||
|
||||
size = lseek(fd, 0, SEEK_END);
|
||||
if (size <= 0) {
|
||||
LOGE("lseek() failed for %s", elf);
|
||||
}
|
||||
|
||||
header = reinterpret_cast<Elf_Ehdr *>(mmap(0, size, PROT_READ, MAP_SHARED, fd, 0));
|
||||
|
||||
close(fd);
|
||||
|
||||
section_header = reinterpret_cast<Elf_Shdr *>(((size_t) header) + header->e_shoff);
|
||||
|
||||
size_t shoff = reinterpret_cast<size_t>(section_header);
|
||||
char *section_str = reinterpret_cast<char *>(section_header[header->e_shstrndx].sh_offset +
|
||||
((size_t) header));
|
||||
|
||||
for (int i = 0; i < header->e_shnum; i++, shoff += header->e_shentsize) {
|
||||
Elf_Shdr *section_h = (Elf_Shdr *) shoff;
|
||||
char *sname = section_h->sh_name + section_str;
|
||||
Elf_Off entsize = section_h->sh_entsize;
|
||||
switch (section_h->sh_type) {
|
||||
case SHT_DYNSYM:
|
||||
if (bias == -4396) {
|
||||
dynsym = section_h;
|
||||
dynsym_offset = section_h->sh_offset;
|
||||
dynsym_size = section_h->sh_size;
|
||||
dynsym_count = dynsym_size / entsize;
|
||||
dynsym_start = reinterpret_cast<Elf_Sym *>(((size_t) header) + dynsym_offset);
|
||||
}
|
||||
break;
|
||||
case SHT_SYMTAB:
|
||||
if (strcmp(sname, ".symtab") == 0) {
|
||||
symtab = section_h;
|
||||
symtab_offset = section_h->sh_offset;
|
||||
symtab_size = section_h->sh_size;
|
||||
symtab_count = symtab_size / entsize;
|
||||
symtab_start = reinterpret_cast<Elf_Sym *>(((size_t) header) + symtab_offset);
|
||||
}
|
||||
break;
|
||||
case SHT_STRTAB:
|
||||
if (bias == -4396) {
|
||||
strtab = section_h;
|
||||
symstr_offset = section_h->sh_offset;
|
||||
strtab_start = reinterpret_cast<Elf_Sym *>(((size_t) header) + symstr_offset);
|
||||
}
|
||||
if (strcmp(sname, ".strtab") == 0) {
|
||||
symstr_offset_for_symtab = section_h->sh_offset;
|
||||
}
|
||||
break;
|
||||
case SHT_PROGBITS:
|
||||
if (strtab == nullptr || dynsym == nullptr) break;
|
||||
if (bias == -4396) {
|
||||
bias = (off_t) section_h->sh_addr - (off_t) section_h->sh_offset;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!symtab_offset) {
|
||||
LOGW("can't find symtab from sections\n");
|
||||
}
|
||||
|
||||
//load module base
|
||||
base = getModuleBase(elf);
|
||||
}
|
||||
|
||||
ElfImg::~ElfImg() {
|
||||
//open elf file local
|
||||
if (buffer) {
|
||||
free(buffer);
|
||||
buffer = nullptr;
|
||||
}
|
||||
//use mmap
|
||||
if (header) {
|
||||
munmap(header, size);
|
||||
}
|
||||
}
|
||||
|
||||
Elf_Addr ElfImg::getSymbOffset(const char *name) {
|
||||
Elf_Addr _offset = 0;
|
||||
|
||||
//search dynmtab
|
||||
if (dynsym_start != nullptr && strtab_start != nullptr) {
|
||||
Elf_Sym *sym = dynsym_start;
|
||||
char *strings = (char *) strtab_start;
|
||||
int k;
|
||||
for (k = 0; k < dynsym_count; k++, sym++)
|
||||
if (strcmp(strings + sym->st_name, name) == 0) {
|
||||
_offset = sym->st_value;
|
||||
LOGD("find %s: %x\n", elf, _offset);
|
||||
return _offset;
|
||||
}
|
||||
}
|
||||
|
||||
//search symtab
|
||||
if (symtab_start != nullptr && symstr_offset_for_symtab != 0) {
|
||||
for (int i = 0; i < symtab_count; i++) {
|
||||
unsigned int st_type = ELF_ST_TYPE(symtab_start[i].st_info);
|
||||
char *st_name = reinterpret_cast<char *>(((size_t) header) + symstr_offset_for_symtab +
|
||||
symtab_start[i].st_name);
|
||||
if (st_type == STT_FUNC && symtab_start[i].st_size) {
|
||||
if (strcmp(st_name, name) == 0) {
|
||||
_offset = symtab_start[i].st_value;
|
||||
LOGD("find %s: %x\n", elf, _offset);
|
||||
return _offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
Elf_Addr ElfImg::getSymbAddress(const char *name) {
|
||||
Elf_Addr offset = getSymbOffset(name);
|
||||
if (offset > 0 && base != nullptr) {
|
||||
return static_cast<Elf_Addr>((size_t) base + offset - bias);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
void *ElfImg::getModuleBase(const char *name) {
|
||||
FILE *maps;
|
||||
char buff[256];
|
||||
off_t load_addr;
|
||||
int found = 0;
|
||||
maps = fopen("/proc/self/maps", "r");
|
||||
while (fgets(buff, sizeof(buff), maps)) {
|
||||
if ((strstr(buff, "r-xp") || strstr(buff, "r--p")) && strstr(buff, name)) {
|
||||
found = 1;
|
||||
__android_log_print(ANDROID_LOG_DEBUG, "dlopen", "%s\n", buff);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
LOGE("failed to read load address for %s", name);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (sscanf(buff, "%lx", &load_addr) != 1)
|
||||
LOGE("failed to read load address for %s", name);
|
||||
|
||||
fclose(maps);
|
||||
|
||||
LOGD("get module base %s: %lu", name, load_addr);
|
||||
|
||||
return reinterpret_cast<void *>(load_addr);
|
||||
}
|
||||
|
|
@ -0,0 +1,367 @@
|
|||
//
|
||||
// Created by swift on 2019/1/21.
|
||||
//
|
||||
#include "../includes/hide_api.h"
|
||||
#include "../includes/arch.h"
|
||||
#include "../includes/elf_util.h"
|
||||
#include "../includes/log.h"
|
||||
#include "../includes/utils.h"
|
||||
#include "../includes/trampoline_manager.h"
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
void* (*jitLoad)(bool*) = nullptr;
|
||||
void* jitCompilerHandle = nullptr;
|
||||
bool (*jitCompileMethod)(void*, void*, void*, bool) = nullptr;
|
||||
bool (*jitCompileMethodQ)(void*, void*, void*, bool, bool) = nullptr;
|
||||
|
||||
void (*innerSuspendVM)() = nullptr;
|
||||
void (*innerResumeVM)() = nullptr;
|
||||
|
||||
jobject (*addWeakGlobalRef)(JavaVM *, void *, void *) = nullptr;
|
||||
|
||||
art::jit::JitCompiler** globalJitCompileHandlerAddr = nullptr;
|
||||
|
||||
//for Android Q
|
||||
void (**origin_jit_update_options)(void *) = nullptr;
|
||||
|
||||
void (*profileSaver_ForceProcessProfiles)() = nullptr;
|
||||
|
||||
//for Android R
|
||||
void *jniIdManager = nullptr;
|
||||
ArtMethod *(*origin_DecodeArtMethodId)(void *thiz, jmethodID jmethodId) = nullptr;
|
||||
ArtMethod *replace_DecodeArtMethodId(void *thiz, jmethodID jmethodId) {
|
||||
jniIdManager = thiz;
|
||||
return origin_DecodeArtMethodId(thiz, jmethodId);
|
||||
}
|
||||
|
||||
bool (*origin_ShouldUseInterpreterEntrypoint)(ArtMethod *artMethod, const void* quick_code) = nullptr;
|
||||
bool replace_ShouldUseInterpreterEntrypoint(ArtMethod *artMethod, const void* quick_code) {
|
||||
if (SandHook::TrampolineManager::get().methodHooked(artMethod) && quick_code != nullptr) {
|
||||
return false;
|
||||
}
|
||||
return origin_ShouldUseInterpreterEntrypoint(artMethod, quick_code);
|
||||
}
|
||||
|
||||
// paths
|
||||
const char* art_lib_path;
|
||||
const char* jit_lib_path;
|
||||
|
||||
JavaVM* jvm;
|
||||
|
||||
void *(*hook_native)(void* origin, void *replace) = nullptr;
|
||||
|
||||
void (*class_init_callback)(void*) = nullptr;
|
||||
|
||||
void (*backup_fixup_static_trampolines)(void *, void *) = nullptr;
|
||||
|
||||
void initHideApi(JNIEnv* env) {
|
||||
|
||||
env->GetJavaVM(&jvm);
|
||||
|
||||
if (BYTE_POINT == 8) {
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
art_lib_path = "/lib64/libart.so";
|
||||
jit_lib_path = "/lib64/libart-compiler.so";
|
||||
} else {
|
||||
art_lib_path = "/system/lib64/libart.so";
|
||||
jit_lib_path = "/system/lib64/libart-compiler.so";
|
||||
}
|
||||
} else {
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
art_lib_path = "/lib/libart.so";
|
||||
jit_lib_path = "/lib/libart-compiler.so";
|
||||
} else {
|
||||
art_lib_path = "/system/lib/libart.so";
|
||||
jit_lib_path = "/system/lib/libart-compiler.so";
|
||||
}
|
||||
}
|
||||
|
||||
//init compile
|
||||
if (SDK_INT >= ANDROID_N) {
|
||||
globalJitCompileHandlerAddr = reinterpret_cast<art::jit::JitCompiler **>(getSymCompat(art_lib_path, "_ZN3art3jit3Jit20jit_compiler_handle_E"));
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
jitCompileMethodQ = reinterpret_cast<bool (*)(void *, void *, void *, bool,
|
||||
bool)>(getSymCompat(jit_lib_path, "jit_compile_method"));
|
||||
} else {
|
||||
jitCompileMethod = reinterpret_cast<bool (*)(void *, void *, void *,
|
||||
bool)>(getSymCompat(jit_lib_path,
|
||||
"jit_compile_method"));
|
||||
}
|
||||
jitLoad = reinterpret_cast<void* (*)(bool*)>(getSymCompat(jit_lib_path, "jit_load"));
|
||||
bool generate_debug_info = false;
|
||||
|
||||
if (jitLoad != nullptr) {
|
||||
jitCompilerHandle = (jitLoad)(&generate_debug_info);
|
||||
} else {
|
||||
jitCompilerHandle = getGlobalJitCompiler();
|
||||
}
|
||||
|
||||
if (jitCompilerHandle != nullptr) {
|
||||
art::CompilerOptions* compilerOptions = getCompilerOptions(
|
||||
reinterpret_cast<art::jit::JitCompiler *>(jitCompilerHandle));
|
||||
disableJitInline(compilerOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//init suspend
|
||||
innerSuspendVM = reinterpret_cast<void (*)()>(getSymCompat(art_lib_path,
|
||||
"_ZN3art3Dbg9SuspendVMEv"));
|
||||
innerResumeVM = reinterpret_cast<void (*)()>(getSymCompat(art_lib_path,
|
||||
"_ZN3art3Dbg8ResumeVMEv"));
|
||||
|
||||
|
||||
//init for getObject & JitCompiler
|
||||
const char* add_weak_ref_sym;
|
||||
if (SDK_INT < ANDROID_M) {
|
||||
add_weak_ref_sym = "_ZN3art9JavaVMExt22AddWeakGlobalReferenceEPNS_6ThreadEPNS_6mirror6ObjectE";
|
||||
} else if (SDK_INT < ANDROID_N) {
|
||||
add_weak_ref_sym = "_ZN3art9JavaVMExt16AddWeakGlobalRefEPNS_6ThreadEPNS_6mirror6ObjectE";
|
||||
} else {
|
||||
add_weak_ref_sym = SDK_INT <= ANDROID_N2
|
||||
? "_ZN3art9JavaVMExt16AddWeakGlobalRefEPNS_6ThreadEPNS_6mirror6ObjectE"
|
||||
: "_ZN3art9JavaVMExt16AddWeakGlobalRefEPNS_6ThreadENS_6ObjPtrINS_6mirror6ObjectEEE";
|
||||
}
|
||||
|
||||
addWeakGlobalRef = reinterpret_cast<jobject (*)(JavaVM *, void *,
|
||||
void *)>(getSymCompat(art_lib_path, add_weak_ref_sym));
|
||||
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
origin_jit_update_options = reinterpret_cast<void (**)(void *)>(getSymCompat(art_lib_path, "_ZN3art3jit3Jit20jit_update_options_E"));
|
||||
}
|
||||
|
||||
if (SDK_INT > ANDROID_N) {
|
||||
profileSaver_ForceProcessProfiles = reinterpret_cast<void (*)()>(getSymCompat(art_lib_path, "_ZN3art12ProfileSaver20ForceProcessProfilesEv"));
|
||||
}
|
||||
|
||||
//init native hook lib
|
||||
void* native_hook_handle = dlopen("libsandhook-native.so", RTLD_LAZY | RTLD_GLOBAL);
|
||||
if (native_hook_handle) {
|
||||
hook_native = reinterpret_cast<void *(*)(void *, void *)>(dlsym(native_hook_handle, "SandInlineHook"));
|
||||
} else {
|
||||
hook_native = reinterpret_cast<void *(*)(void *, void *)>(getSymCompat(
|
||||
"libsandhook-native.so", "SandInlineHook"));
|
||||
}
|
||||
|
||||
if (SDK_INT >= ANDROID_R && hook_native) {
|
||||
const char *symbol_decode_method = sizeof(void*) == 8 ? "_ZN3art3jni12JniIdManager15DecodeGenericIdINS_9ArtMethodEEEPT_m" : "_ZN3art3jni12JniIdManager15DecodeGenericIdINS_9ArtMethodEEEPT_j";
|
||||
void *decodeArtMethod = getSymCompat(art_lib_path, symbol_decode_method);
|
||||
if (art_lib_path != nullptr) {
|
||||
origin_DecodeArtMethodId = reinterpret_cast<ArtMethod *(*)(void *,
|
||||
jmethodID)>(hook_native(
|
||||
decodeArtMethod,
|
||||
reinterpret_cast<void *>(replace_DecodeArtMethodId)));
|
||||
}
|
||||
void *shouldUseInterpreterEntrypoint = getSymCompat(art_lib_path,
|
||||
"_ZN3art11ClassLinker30ShouldUseInterpreterEntrypointEPNS_9ArtMethodEPKv");
|
||||
if (shouldUseInterpreterEntrypoint != nullptr) {
|
||||
origin_ShouldUseInterpreterEntrypoint = reinterpret_cast<bool (*)(ArtMethod *,
|
||||
const void *)>(hook_native(
|
||||
shouldUseInterpreterEntrypoint,
|
||||
reinterpret_cast<void *>(replace_ShouldUseInterpreterEntrypoint)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool canCompile() {
|
||||
if (SDK_INT >= ANDROID_R)
|
||||
return false;
|
||||
if (getGlobalJitCompiler() == nullptr) {
|
||||
LOGE("JIT not init!");
|
||||
return false;
|
||||
}
|
||||
JNIEnv *env;
|
||||
jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
|
||||
return getBooleanFromJava(env, "com/swift/sandhook/SandHookConfig",
|
||||
"compiler");
|
||||
}
|
||||
|
||||
bool compileMethod(void* artMethod, void* thread) {
|
||||
if (jitCompilerHandle == nullptr)
|
||||
return false;
|
||||
if (!canCompile()) return false;
|
||||
|
||||
//backup thread flag and state because of jit compile function will modify thread state
|
||||
uint32_t old_flag_and_state = *((uint32_t *) thread);
|
||||
bool ret;
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
if (jitCompileMethodQ == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ret = jitCompileMethodQ(jitCompilerHandle, artMethod, thread, false, false);
|
||||
} else {
|
||||
if (jitCompileMethod == nullptr) {
|
||||
return false;
|
||||
}
|
||||
ret= jitCompileMethod(jitCompilerHandle, artMethod, thread, false);
|
||||
}
|
||||
memcpy(thread, &old_flag_and_state, 4);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void suspendVM() {
|
||||
if (innerSuspendVM == nullptr || innerResumeVM == nullptr)
|
||||
return;
|
||||
innerSuspendVM();
|
||||
}
|
||||
|
||||
void resumeVM() {
|
||||
if (innerSuspendVM == nullptr || innerResumeVM == nullptr)
|
||||
return;
|
||||
innerResumeVM();
|
||||
}
|
||||
|
||||
bool canGetObject() {
|
||||
return addWeakGlobalRef != nullptr;
|
||||
}
|
||||
|
||||
void *getCurrentThread() {
|
||||
return __get_tls()[TLS_SLOT_ART_THREAD];
|
||||
}
|
||||
|
||||
jobject getJavaObject(JNIEnv* env, void* thread, void* address) {
|
||||
if (addWeakGlobalRef == nullptr)
|
||||
return nullptr;
|
||||
|
||||
jobject object = addWeakGlobalRef(jvm, thread, address);
|
||||
if (object == nullptr)
|
||||
return nullptr;
|
||||
|
||||
jobject result = env->NewLocalRef(object);
|
||||
env->DeleteWeakGlobalRef(object);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
art::jit::JitCompiler* getGlobalJitCompiler() {
|
||||
if (SDK_INT < ANDROID_N)
|
||||
return nullptr;
|
||||
if (globalJitCompileHandlerAddr == nullptr)
|
||||
return nullptr;
|
||||
return *globalJitCompileHandlerAddr;
|
||||
}
|
||||
|
||||
art::CompilerOptions* getCompilerOptions(art::jit::JitCompiler* compiler) {
|
||||
if (compiler == nullptr)
|
||||
return nullptr;
|
||||
return compiler->compilerOptions.get();
|
||||
}
|
||||
|
||||
art::CompilerOptions* getGlobalCompilerOptions() {
|
||||
return getCompilerOptions(getGlobalJitCompiler());
|
||||
}
|
||||
|
||||
bool disableJitInline(art::CompilerOptions* compilerOptions) {
|
||||
if (compilerOptions == nullptr)
|
||||
return false;
|
||||
size_t originOptions = compilerOptions->getInlineMaxCodeUnits();
|
||||
//maybe a real inlineMaxCodeUnits
|
||||
if (originOptions > 0 && originOptions <= 1024) {
|
||||
compilerOptions->setInlineMaxCodeUnits(0);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void* getInterpreterBridge(bool isNative) {
|
||||
SandHook::ElfImg libart(art_lib_path);
|
||||
if (isNative) {
|
||||
return reinterpret_cast<void *>(libart.getSymbAddress("art_quick_generic_jni_trampoline"));
|
||||
} else {
|
||||
return reinterpret_cast<void *>(libart.getSymbAddress("art_quick_to_interpreter_bridge"));
|
||||
}
|
||||
}
|
||||
|
||||
//to replace jit_update_option
|
||||
void fake_jit_update_options(void* handle) {
|
||||
//do nothing
|
||||
LOGW("android q: art request update compiler options");
|
||||
return;
|
||||
}
|
||||
|
||||
bool replaceUpdateCompilerOptionsQ() {
|
||||
if (SDK_INT < ANDROID_Q)
|
||||
return false;
|
||||
if (origin_jit_update_options == nullptr
|
||||
|| *origin_jit_update_options == nullptr)
|
||||
return false;
|
||||
*origin_jit_update_options = fake_jit_update_options;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool forceProcessProfiles() {
|
||||
if (profileSaver_ForceProcessProfiles == nullptr)
|
||||
return false;
|
||||
profileSaver_ForceProcessProfiles();
|
||||
return true;
|
||||
}
|
||||
|
||||
void replaceFixupStaticTrampolines(void *thiz, void *clazz_ptr) {
|
||||
backup_fixup_static_trampolines(thiz, clazz_ptr);
|
||||
if (class_init_callback) {
|
||||
class_init_callback(clazz_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
bool hookClassInit(void(*callback)(void*)) {
|
||||
void* symFixupStaticTrampolines = getSymCompat(art_lib_path, "_ZN3art11ClassLinker22FixupStaticTrampolinesENS_6ObjPtrINS_6mirror5ClassEEE");
|
||||
|
||||
if (symFixupStaticTrampolines == nullptr) {
|
||||
//huawei lon-al00 android 7.0 api level 24
|
||||
symFixupStaticTrampolines = getSymCompat(art_lib_path,
|
||||
"_ZN3art11ClassLinker22FixupStaticTrampolinesEPNS_6mirror5ClassE");
|
||||
}
|
||||
if (symFixupStaticTrampolines == nullptr || hook_native == nullptr)
|
||||
return false;
|
||||
backup_fixup_static_trampolines = reinterpret_cast<void (*)(void *, void *)>(hook_native(
|
||||
symFixupStaticTrampolines, (void *) replaceFixupStaticTrampolines));
|
||||
if (backup_fixup_static_trampolines) {
|
||||
class_init_callback = callback;
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
JNIEnv *getEnv() {
|
||||
JNIEnv *env;
|
||||
jvm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6);
|
||||
return env;
|
||||
}
|
||||
|
||||
JNIEnv *attachAndGetEvn() {
|
||||
JNIEnv *env = getEnv();
|
||||
if (env == nullptr) {
|
||||
jvm->AttachCurrentThread(&env, nullptr);
|
||||
}
|
||||
return env;
|
||||
}
|
||||
|
||||
static bool isIndexId(jmethodID mid) {
|
||||
return (reinterpret_cast<uintptr_t>(mid) % 2) != 0;
|
||||
}
|
||||
|
||||
ArtMethod* getArtMethod(JNIEnv *env, jobject method) {
|
||||
jmethodID methodId = env->FromReflectedMethod(method);
|
||||
if (SDK_INT >= ANDROID_R && isIndexId(methodId)) {
|
||||
if (origin_DecodeArtMethodId == nullptr || jniIdManager == nullptr) {
|
||||
auto res = callStaticMethodAddr(env, "com/swift/sandhook/SandHook", "getArtMethod",
|
||||
"(Ljava/lang/reflect/Member;)J", method);
|
||||
return reinterpret_cast<ArtMethod *>(res);
|
||||
} else {
|
||||
return origin_DecodeArtMethodId(jniIdManager, methodId);
|
||||
}
|
||||
} else {
|
||||
return reinterpret_cast<ArtMethod *>(methodId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,30 @@
|
|||
//
|
||||
// Created by SwiftGan on 2019/1/21.
|
||||
//
|
||||
|
||||
#ifndef SANDHOOK_LOCK_H
|
||||
#define SANDHOOK_LOCK_H
|
||||
|
||||
#include "mutex"
|
||||
#include "../includes/hide_api.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
class AutoLock {
|
||||
public:
|
||||
inline AutoLock(std::mutex& mutex) : mLock(mutex) { mLock.lock(); }
|
||||
inline AutoLock(std::mutex* mutex) : mLock(*mutex) { mLock.lock(); }
|
||||
inline ~AutoLock() { mLock.unlock(); }
|
||||
private:
|
||||
std::mutex& mLock;
|
||||
};
|
||||
|
||||
class StopTheWorld {
|
||||
public:
|
||||
inline StopTheWorld() { suspendVM(); }
|
||||
inline ~StopTheWorld() { resumeVM(); }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_LOCK_H
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
//
|
||||
// Created by swift on 2019/2/3.
|
||||
//
|
||||
|
||||
#include "../includes/offset.h"
|
||||
|
||||
namespace SandHook {
|
||||
|
||||
template<typename T>
|
||||
int Offset::findOffset(void *start, size_t len, size_t step, T value) {
|
||||
|
||||
if (nullptr == start) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= len; i += step) {
|
||||
T current_value = *reinterpret_cast<T *>((size_t) start + i);
|
||||
if (value == current_value) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int Offset::findOffsetWithCB1(void *start, size_t len, size_t step, bool func(int, T)) {
|
||||
|
||||
if (nullptr == start) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= len; i += step) {
|
||||
T current_value = *reinterpret_cast<T *>((size_t) start + i);
|
||||
if (func(i, current_value)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
int Offset::findOffsetWithCB2(void *start1, void *start2, size_t len, size_t step, bool func(T, T)) {
|
||||
|
||||
if (nullptr == start1 || nullptr == start2) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
for (int i = 0; i <= len; i += step) {
|
||||
T v1 = *reinterpret_cast<T *>((size_t) start1 + i);
|
||||
T v2 = *reinterpret_cast<T *>((size_t) start2 + i);
|
||||
if (func(v1, v2)) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,143 @@
|
|||
//
|
||||
// Created by swift on 2019/2/3.
|
||||
//
|
||||
|
||||
#include <cstring>
|
||||
#include "../includes/utils.h"
|
||||
|
||||
extern "C" {
|
||||
|
||||
Size getAddressFromJava(JNIEnv *env, const char *className, const char *fieldName) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
printf("find class error !");
|
||||
return 0;
|
||||
}
|
||||
jfieldID id = env->GetStaticFieldID(clazz, fieldName, "J");
|
||||
if (id == NULL) {
|
||||
printf("find field error !");
|
||||
return 0;
|
||||
}
|
||||
return static_cast<Size>(env->GetStaticLongField(clazz, id));
|
||||
}
|
||||
|
||||
Size callStaticMethodAddr(JNIEnv *env, const char *className, const char *method, const char *sig, ...) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
printf("find class error !");
|
||||
return 0;
|
||||
}
|
||||
jmethodID id = env->GetStaticMethodID(clazz, method, sig);
|
||||
if (id == NULL) {
|
||||
printf("find field error !");
|
||||
return 0;
|
||||
}
|
||||
va_list vas;
|
||||
va_start(vas, sig);
|
||||
auto res = static_cast<Size>(env->CallStaticLongMethodV(clazz, id, vas));
|
||||
env->ExceptionClear();
|
||||
va_end(vas);
|
||||
return res;
|
||||
}
|
||||
|
||||
jobject callStaticMethodObject(JNIEnv *env, const char *className, const char *method, const char *sig, ...) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
printf("find class error !");
|
||||
return 0;
|
||||
}
|
||||
jmethodID id = env->GetStaticMethodID(clazz, method, sig);
|
||||
if (id == NULL) {
|
||||
printf("find field error !");
|
||||
return 0;
|
||||
}
|
||||
va_list vas;
|
||||
va_start(vas, sig);
|
||||
auto res = env->CallStaticObjectMethodV(clazz, id, vas);
|
||||
env->ExceptionClear();
|
||||
va_end(vas);
|
||||
return res;
|
||||
}
|
||||
|
||||
jobject getMethodObject(JNIEnv *env, const char *clazz, const char *method) {
|
||||
auto methodStr = env->NewStringUTF(method);
|
||||
auto clazzStr = env->NewStringUTF(clazz);
|
||||
auto res = callStaticMethodObject(env, "com/swift/sandhook/SandHook", "getJavaMethod",
|
||||
"(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/Object;", clazzStr, methodStr);
|
||||
env->ExceptionClear();
|
||||
env->DeleteLocalRef(methodStr);
|
||||
env->DeleteLocalRef(clazzStr);
|
||||
return res;
|
||||
}
|
||||
|
||||
Size getAddressFromJavaByCallMethod(JNIEnv *env, const char *className, const char *methodName) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
printf("find class error !");
|
||||
return 0;
|
||||
}
|
||||
jmethodID id = env->GetStaticMethodID(clazz, methodName, "()J");
|
||||
if (id == NULL) {
|
||||
printf("find field error !");
|
||||
return 0;
|
||||
}
|
||||
auto res = env->CallStaticLongMethodA(clazz, id, nullptr);
|
||||
env->ExceptionClear();
|
||||
return static_cast<Size>(res);
|
||||
}
|
||||
|
||||
jint getIntFromJava(JNIEnv *env, const char *className, const char *fieldName) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
printf("find class error !");
|
||||
return 0;
|
||||
}
|
||||
jfieldID id = env->GetStaticFieldID(clazz, fieldName, "I");
|
||||
if (id == NULL) {
|
||||
printf("find field error !");
|
||||
return 0;
|
||||
}
|
||||
return env->GetStaticIntField(clazz, id);
|
||||
}
|
||||
|
||||
bool getBooleanFromJava(JNIEnv *env, const char *className, const char *fieldName) {
|
||||
jclass clazz = env->FindClass(className);
|
||||
if (clazz == NULL) {
|
||||
printf("find class error !");
|
||||
return false;
|
||||
}
|
||||
jfieldID id = env->GetStaticFieldID(clazz, fieldName, "Z");
|
||||
if (id == NULL) {
|
||||
printf("find field error !");
|
||||
return false;
|
||||
}
|
||||
return env->GetStaticBooleanField(clazz, id);
|
||||
}
|
||||
|
||||
bool munprotect(size_t addr, size_t len) {
|
||||
long pagesize = sysconf(_SC_PAGESIZE);
|
||||
unsigned alignment = (unsigned) ((unsigned long long) addr % pagesize);
|
||||
int i = mprotect((void *) (addr - alignment), (size_t) (alignment + len),
|
||||
PROT_READ | PROT_WRITE | PROT_EXEC);
|
||||
if (i == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool flushCacheExt(Size addr, Size len) {
|
||||
#if defined(__arm__)
|
||||
int i = cacheflush(addr, addr + len, 0);
|
||||
if (i == -1) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
#elif defined(__aarch64__)
|
||||
char *begin = reinterpret_cast<char *>(addr);
|
||||
__builtin___clear_cache(begin, begin + len);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
public class ArtMethodSizeTest {
|
||||
public final static void method1(){}
|
||||
public final static void method2(){}
|
||||
}
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
public class ClassNeverCall {
|
||||
private void neverCall() {}
|
||||
private void neverCall2() {
|
||||
Log.e("ClassNeverCall", "ClassNeverCall2");
|
||||
}
|
||||
private static void neverCallStatic() {}
|
||||
private native void neverCallNative();
|
||||
private native void neverCallNative2();
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
|
||||
public class HookLog {
|
||||
|
||||
public static final String TAG = "SandHook";
|
||||
|
||||
public static boolean DEBUG = SandHookConfig.DEBUG;
|
||||
|
||||
public static int v(String s) {
|
||||
return Log.v(TAG, s);
|
||||
}
|
||||
|
||||
public static int i(String s) {
|
||||
return Log.i(TAG, s);
|
||||
}
|
||||
|
||||
public static int d(String s) {
|
||||
return Log.d(TAG, s);
|
||||
}
|
||||
|
||||
public static int w(String s) {
|
||||
return Log.w(TAG, s);
|
||||
}
|
||||
|
||||
public static int e(String s) {
|
||||
return Log.e(TAG, s);
|
||||
}
|
||||
|
||||
public static int e(String s, Throwable t) {
|
||||
return Log.e(TAG, s, t);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
import com.swift.sandhook.wrapper.HookErrorException;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.Vector;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
// Pending for hook static method
|
||||
// When Init class error!
|
||||
public class PendingHookHandler {
|
||||
|
||||
private static Map<Class, Vector<HookWrapper.HookEntity>> pendingHooks = new ConcurrentHashMap<>();
|
||||
|
||||
private static boolean canUsePendingHook;
|
||||
|
||||
static {
|
||||
//init native hook
|
||||
if (SandHookConfig.delayHook) {
|
||||
canUsePendingHook = SandHook.initForPendingHook();
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean canWork() {
|
||||
return canUsePendingHook && SandHook.canGetObject() && !SandHookConfig.DEBUG;
|
||||
}
|
||||
|
||||
public static synchronized void addPendingHook(HookWrapper.HookEntity hookEntity) {
|
||||
Vector<HookWrapper.HookEntity> entities = pendingHooks.get(hookEntity.target.getDeclaringClass());
|
||||
if (entities == null) {
|
||||
entities = new Vector<>();
|
||||
pendingHooks.put(hookEntity.target.getDeclaringClass(), entities);
|
||||
}
|
||||
entities.add(hookEntity);
|
||||
}
|
||||
|
||||
public static void onClassInit(long clazz_ptr) {
|
||||
if (clazz_ptr == 0)
|
||||
return;
|
||||
Class clazz = (Class) SandHook.getObject(clazz_ptr);
|
||||
if (clazz == null)
|
||||
return;
|
||||
Vector<HookWrapper.HookEntity> entities = pendingHooks.get(clazz);
|
||||
if (entities == null)
|
||||
return;
|
||||
for (HookWrapper.HookEntity entity:entities) {
|
||||
HookLog.w("do pending hook for method: " + entity.target.toString());
|
||||
try {
|
||||
entity.initClass = false;
|
||||
SandHook.hook(entity);
|
||||
} catch (HookErrorException e) {
|
||||
HookLog.e("Pending Hook Error!", e);
|
||||
}
|
||||
}
|
||||
pendingHooks.remove(clazz);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,416 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.swift.sandhook.annotation.HookMode;
|
||||
import com.swift.sandhook.blacklist.HookBlackList;
|
||||
import com.swift.sandhook.utils.ClassStatusUtils;
|
||||
import com.swift.sandhook.utils.FileUtils;
|
||||
import com.swift.sandhook.utils.ReflectionUtils;
|
||||
import com.swift.sandhook.utils.Unsafe;
|
||||
import com.swift.sandhook.wrapper.HookErrorException;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class SandHook {
|
||||
|
||||
static Map<Member,HookWrapper.HookEntity> globalHookEntityMap = new ConcurrentHashMap<>();
|
||||
static Map<Method,HookWrapper.HookEntity> globalBackupMap = new ConcurrentHashMap<>();
|
||||
|
||||
private static HookModeCallBack hookModeCallBack;
|
||||
public static void setHookModeCallBack(HookModeCallBack hookModeCallBack) {
|
||||
SandHook.hookModeCallBack = hookModeCallBack;
|
||||
}
|
||||
|
||||
private static HookResultCallBack hookResultCallBack;
|
||||
public static void setHookResultCallBack(HookResultCallBack hookResultCallBack) {
|
||||
SandHook.hookResultCallBack = hookResultCallBack;
|
||||
}
|
||||
|
||||
public static Class artMethodClass;
|
||||
|
||||
public static Field nativePeerField;
|
||||
public static Method testOffsetMethod1;
|
||||
public static Method testOffsetMethod2;
|
||||
public static Object testOffsetArtMethod1;
|
||||
public static Object testOffsetArtMethod2;
|
||||
|
||||
public static int testAccessFlag;
|
||||
|
||||
static {
|
||||
SandHookConfig.libLoader.loadLib();
|
||||
init();
|
||||
}
|
||||
|
||||
private static boolean init() {
|
||||
initTestOffset();
|
||||
initThreadPeer();
|
||||
SandHookMethodResolver.init();
|
||||
return initNative(SandHookConfig.SDK_INT, SandHookConfig.DEBUG);
|
||||
}
|
||||
|
||||
private static void initThreadPeer() {
|
||||
try {
|
||||
nativePeerField = getField(Thread.class, "nativePeer");
|
||||
} catch (NoSuchFieldException e) {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
public static void addHookClass(Class... hookWrapperClass) throws HookErrorException {
|
||||
HookWrapper.addHookClass(hookWrapperClass);
|
||||
}
|
||||
|
||||
public static void addHookClass(ClassLoader classLoader, Class... hookWrapperClass) throws HookErrorException {
|
||||
HookWrapper.addHookClass(classLoader, hookWrapperClass);
|
||||
}
|
||||
|
||||
public static synchronized void hook(HookWrapper.HookEntity entity) throws HookErrorException {
|
||||
|
||||
if (entity == null)
|
||||
throw new HookErrorException("null hook entity");
|
||||
|
||||
Member target = entity.target;
|
||||
Method hook = entity.hook;
|
||||
Method backup = entity.backup;
|
||||
|
||||
if (target == null || hook == null)
|
||||
throw new HookErrorException("null input");
|
||||
|
||||
if (globalHookEntityMap.containsKey(entity.target))
|
||||
throw new HookErrorException("method <" + entity.target.toString() + "> has been hooked!");
|
||||
|
||||
if (HookBlackList.canNotHook(target))
|
||||
throw new HookErrorException("method <" + entity.target.toString() + "> can not hook, because of in blacklist!");
|
||||
|
||||
|
||||
if (SandHookConfig.delayHook && PendingHookHandler.canWork() && ClassStatusUtils.isStaticAndNoInited(entity.target)) {
|
||||
PendingHookHandler.addPendingHook(entity);
|
||||
return;
|
||||
} else if (entity.initClass) {
|
||||
resolveStaticMethod(target);
|
||||
}
|
||||
|
||||
resolveStaticMethod(backup);
|
||||
|
||||
if (backup != null && entity.resolveDexCache) {
|
||||
SandHookMethodResolver.resolveMethod(hook, backup);
|
||||
}
|
||||
if (target instanceof Method) {
|
||||
((Method)target).setAccessible(true);
|
||||
}
|
||||
|
||||
int mode = HookMode.AUTO;
|
||||
if (hookModeCallBack != null) {
|
||||
mode = hookModeCallBack.hookMode(target);
|
||||
}
|
||||
|
||||
globalHookEntityMap.put(entity.target, entity);
|
||||
|
||||
int res;
|
||||
if (mode != HookMode.AUTO) {
|
||||
res = hookMethod(target, hook, backup, mode);
|
||||
} else {
|
||||
HookMode hookMode = hook.getAnnotation(HookMode.class);
|
||||
res = hookMethod(target, hook, backup, hookMode == null ? HookMode.AUTO : hookMode.value());
|
||||
}
|
||||
|
||||
if (res > 0 && backup != null) {
|
||||
backup.setAccessible(true);
|
||||
}
|
||||
|
||||
entity.hookMode = res;
|
||||
|
||||
if (hookResultCallBack != null) {
|
||||
hookResultCallBack.hookResult(res > 0, entity);
|
||||
}
|
||||
|
||||
if (res < 0) {
|
||||
globalHookEntityMap.remove(entity.target);
|
||||
throw new HookErrorException("hook method <" + entity.target.toString() + "> error in native!");
|
||||
}
|
||||
|
||||
if (entity.backup != null) {
|
||||
globalBackupMap.put(entity.backup, entity);
|
||||
}
|
||||
|
||||
HookLog.d("method <" + entity.target.toString() + "> hook <" + (res == HookMode.INLINE ? "inline" : "replacement") + "> success!");
|
||||
}
|
||||
|
||||
public final static Object callOriginMethod(Member originMethod, Object thiz, Object... args) throws Throwable {
|
||||
HookWrapper.HookEntity hookEntity = globalHookEntityMap.get(originMethod);
|
||||
if (hookEntity == null || hookEntity.backup == null)
|
||||
return null;
|
||||
return callOriginMethod(hookEntity.backupIsStub, originMethod, hookEntity.backup, thiz, args);
|
||||
}
|
||||
|
||||
public final static Object callOriginByBackup(Method backupMethod, Object thiz, Object... args) throws Throwable {
|
||||
HookWrapper.HookEntity hookEntity = globalBackupMap.get(backupMethod);
|
||||
if (hookEntity == null)
|
||||
return null;
|
||||
return callOriginMethod(hookEntity.backupIsStub, hookEntity.target, backupMethod, thiz, args);
|
||||
}
|
||||
|
||||
public final static Object callOriginMethod(Member originMethod, Method backupMethod, Object thiz, Object[] args) throws Throwable {
|
||||
return callOriginMethod(true, originMethod, backupMethod, thiz, args);
|
||||
}
|
||||
|
||||
public final static Object callOriginMethod(boolean backupIsStub, Member originMethod, Method backupMethod, Object thiz, Object[] args) throws Throwable {
|
||||
//reset declaring class
|
||||
if (!backupIsStub && SandHookConfig.SDK_INT >= Build.VERSION_CODES.N) {
|
||||
//holder in stack to avoid moving gc
|
||||
Class originClassHolder = originMethod.getDeclaringClass();
|
||||
ensureDeclareClass(originMethod, backupMethod);
|
||||
}
|
||||
if (Modifier.isStatic(originMethod.getModifiers())) {
|
||||
try {
|
||||
return backupMethod.invoke(null, args);
|
||||
} catch (InvocationTargetException throwable) {
|
||||
if (throwable.getCause() != null) {
|
||||
throw throwable.getCause();
|
||||
} else {
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
return backupMethod.invoke(thiz, args);
|
||||
} catch (InvocationTargetException throwable) {
|
||||
if (throwable.getCause() != null) {
|
||||
throw throwable.getCause();
|
||||
} else {
|
||||
throw throwable;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public final static void ensureBackupMethod(Method backupMethod) {
|
||||
if (SandHookConfig.SDK_INT < Build.VERSION_CODES.N)
|
||||
return;
|
||||
HookWrapper.HookEntity entity = globalBackupMap.get(backupMethod);
|
||||
if (entity != null) {
|
||||
ensureDeclareClass(entity.target, backupMethod);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean resolveStaticMethod(Member method) {
|
||||
//ignore result, just call to trigger resolve
|
||||
if (method == null)
|
||||
return true;
|
||||
try {
|
||||
if (method instanceof Method && Modifier.isStatic(method.getModifiers())) {
|
||||
((Method) method).setAccessible(true);
|
||||
((Method) method).invoke(new Object(), getFakeArgs((Method) method));
|
||||
}
|
||||
} catch (ExceptionInInitializerError classInitError) {
|
||||
//may need hook later
|
||||
return false;
|
||||
} catch (Throwable throwable) {
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static Object[] getFakeArgs(Method method) {
|
||||
Class[] pars = method.getParameterTypes();
|
||||
if (pars == null || pars.length == 0) {
|
||||
return new Object[]{new Object()};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getObject(long address) {
|
||||
if (address == 0) {
|
||||
return null;
|
||||
}
|
||||
long threadSelf = getThreadId();
|
||||
return getObjectNative(threadSelf, address);
|
||||
}
|
||||
|
||||
public static boolean canGetObjectAddress() {
|
||||
return Unsafe.support();
|
||||
}
|
||||
|
||||
public static long getObjectAddress(Object object) {
|
||||
return Unsafe.getObjectAddress(object);
|
||||
}
|
||||
|
||||
private static void initTestOffset() {
|
||||
// make test methods sure resolved!
|
||||
ArtMethodSizeTest.method1();
|
||||
ArtMethodSizeTest.method2();
|
||||
// get test methods
|
||||
try {
|
||||
testOffsetMethod1 = ArtMethodSizeTest.class.getDeclaredMethod("method1");
|
||||
testOffsetMethod2 = ArtMethodSizeTest.class.getDeclaredMethod("method2");
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new RuntimeException("SandHook init error", e);
|
||||
}
|
||||
initTestAccessFlag();
|
||||
}
|
||||
|
||||
private static void initTestAccessFlag() {
|
||||
if (hasJavaArtMethod()) {
|
||||
try {
|
||||
loadArtMethod();
|
||||
Field fieldAccessFlags = getField(artMethodClass, "accessFlags");
|
||||
testAccessFlag = (int) fieldAccessFlags.get(testOffsetArtMethod1);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
Field fieldAccessFlags = getField(Method.class, "accessFlags");
|
||||
testAccessFlag = (int) fieldAccessFlags.get(testOffsetMethod1);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void loadArtMethod() {
|
||||
try {
|
||||
Field fieldArtMethod = getField(Method.class, "artMethod");
|
||||
testOffsetArtMethod1 = fieldArtMethod.get(testOffsetMethod1);
|
||||
testOffsetArtMethod2 = fieldArtMethod.get(testOffsetMethod2);
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
} catch (NoSuchFieldException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static boolean hasJavaArtMethod() {
|
||||
if (SandHookConfig.SDK_INT >= Build.VERSION_CODES.O)
|
||||
return false;
|
||||
if (artMethodClass != null)
|
||||
return true;
|
||||
try {
|
||||
if (SandHookConfig.initClassLoader == null) {
|
||||
artMethodClass = Class.forName("java.lang.reflect.ArtMethod");
|
||||
} else {
|
||||
artMethodClass = Class.forName("java.lang.reflect.ArtMethod", true, SandHookConfig.initClassLoader);
|
||||
}
|
||||
return true;
|
||||
} catch (ClassNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Field getField(Class topClass, String fieldName) throws NoSuchFieldException {
|
||||
while (topClass != null && topClass != Object.class) {
|
||||
try {
|
||||
Field field = topClass.getDeclaredField(fieldName);
|
||||
field.setAccessible(true);
|
||||
return field;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
topClass = topClass.getSuperclass();
|
||||
}
|
||||
throw new NoSuchFieldException(fieldName);
|
||||
}
|
||||
|
||||
public static long getThreadId() {
|
||||
if (nativePeerField == null)
|
||||
return 0;
|
||||
try {
|
||||
if (nativePeerField.getType() == int.class) {
|
||||
return nativePeerField.getInt(Thread.currentThread());
|
||||
} else {
|
||||
return nativePeerField.getLong(Thread.currentThread());
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object getJavaMethod(String className, String methodName) {
|
||||
if (className == null)
|
||||
return null;
|
||||
Class clazz = null;
|
||||
try {
|
||||
clazz = Class.forName(className);
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return clazz.getDeclaredMethod(methodName);
|
||||
} catch (NoSuchMethodException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getArtMethod(Member member) {
|
||||
return SandHookMethodResolver.getArtMethod(member);
|
||||
}
|
||||
|
||||
public static boolean passApiCheck() {
|
||||
return ReflectionUtils.passApiCheck();
|
||||
}
|
||||
|
||||
//disable JIT/AOT Profile
|
||||
public static boolean tryDisableProfile(String selfPackageName) {
|
||||
if (SandHookConfig.SDK_INT < Build.VERSION_CODES.N)
|
||||
return false;
|
||||
try {
|
||||
File profile = new File("/data/misc/profiles/cur/" + SandHookConfig.curUser + "/" + selfPackageName + "/primary.prof");
|
||||
if (!profile.getParentFile().exists()) return false;
|
||||
try {
|
||||
profile.delete();
|
||||
profile.createNewFile();
|
||||
} catch (Throwable throwable) {}
|
||||
FileUtils.chmod(profile.getAbsolutePath(), FileUtils.FileMode.MODE_IRUSR);
|
||||
return true;
|
||||
} catch (Throwable throwable) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static native boolean initNative(int sdk, boolean debug);
|
||||
|
||||
public static native void setHookMode(int hookMode);
|
||||
|
||||
//default on!
|
||||
public static native void setInlineSafeCheck(boolean check);
|
||||
public static native void skipAllSafeCheck(boolean skip);
|
||||
|
||||
private static native int hookMethod(Member originMethod, Method hookMethod, Method backupMethod, int hookMode);
|
||||
|
||||
public static native void ensureMethodCached(Method hook, Method backup);
|
||||
public static native void ensureDeclareClass(Member origin, Method backup);
|
||||
|
||||
public static native boolean compileMethod(Member member);
|
||||
public static native boolean deCompileMethod(Member member, boolean disableJit);
|
||||
|
||||
public static native boolean canGetObject();
|
||||
public static native Object getObjectNative(long thread, long address);
|
||||
|
||||
public static native boolean is64Bit();
|
||||
|
||||
public static native boolean disableVMInline();
|
||||
|
||||
public static native boolean disableDex2oatInline(boolean disableDex2oat);
|
||||
|
||||
public static native boolean setNativeEntry(Member origin, Member hook, long nativeEntry);
|
||||
|
||||
public static native boolean initForPendingHook();
|
||||
|
||||
@FunctionalInterface
|
||||
public interface HookModeCallBack {
|
||||
int hookMode(Member originMethod);
|
||||
}
|
||||
|
||||
@FunctionalInterface
|
||||
public interface HookResultCallBack {
|
||||
void hookResult(boolean success, HookWrapper.HookEntity hookEntity);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
import android.annotation.SuppressLint;
|
||||
import android.os.Build;
|
||||
|
||||
import com.swift.sandhook.lib.BuildConfig;
|
||||
|
||||
public class SandHookConfig {
|
||||
|
||||
public volatile static int SDK_INT = Build.VERSION.SDK_INT;
|
||||
//Debug status of hook target process
|
||||
public volatile static boolean DEBUG = true;
|
||||
//Enable compile with jit
|
||||
public volatile static boolean compiler = SDK_INT < 29;
|
||||
public volatile static ClassLoader initClassLoader;
|
||||
public volatile static int curUser = 0;
|
||||
public volatile static boolean delayHook = true;
|
||||
|
||||
public volatile static String libSandHookPath;
|
||||
public volatile static LibLoader libLoader = new LibLoader() {
|
||||
@SuppressLint("UnsafeDynamicallyLoadedCode")
|
||||
@Override
|
||||
public void loadLib() {
|
||||
if (SandHookConfig.libSandHookPath == null) {
|
||||
System.loadLibrary("sandhook");
|
||||
} else {
|
||||
System.load(SandHookConfig.libSandHookPath);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
public interface LibLoader {
|
||||
void loadLib();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,167 @@
|
|||
package com.swift.sandhook;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static com.swift.sandhook.SandHook.artMethodClass;
|
||||
import static com.swift.sandhook.SandHook.getField;
|
||||
import static com.swift.sandhook.SandHook.hasJavaArtMethod;
|
||||
|
||||
public class SandHookMethodResolver {
|
||||
|
||||
public static Field resolvedMethodsField;
|
||||
public static Field dexCacheField;
|
||||
public static Field dexMethodIndexField;
|
||||
public static Field artMethodField;
|
||||
public static Field fieldEntryPointFromCompiledCode;
|
||||
public static Field fieldEntryPointFromInterpreter;
|
||||
|
||||
public static boolean canResolvedInJava = false;
|
||||
public static boolean isArtMethod = false;
|
||||
|
||||
public static long resolvedMethodsAddress = 0;
|
||||
public static int dexMethodIndex = 0;
|
||||
|
||||
public static long entryPointFromCompiledCode = 0;
|
||||
public static long entryPointFromInterpreter = 0;
|
||||
|
||||
public static Method testMethod;
|
||||
public static Object testArtMethod;
|
||||
|
||||
public static void init() {
|
||||
testMethod = SandHook.testOffsetMethod1;
|
||||
checkSupport();
|
||||
}
|
||||
|
||||
private static void checkSupport() {
|
||||
try {
|
||||
artMethodField = getField(Method.class, "artMethod");
|
||||
|
||||
testArtMethod = artMethodField.get(testMethod);
|
||||
|
||||
if (hasJavaArtMethod() && testArtMethod.getClass() == artMethodClass) {
|
||||
checkSupportForArtMethod();
|
||||
isArtMethod = true;
|
||||
} else if (testArtMethod instanceof Long) {
|
||||
checkSupportForArtMethodId();
|
||||
isArtMethod = false;
|
||||
} else {
|
||||
canResolvedInJava = false;
|
||||
}
|
||||
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
|
||||
public static long getArtMethod(Member member) {
|
||||
if (artMethodField == null)
|
||||
return 0;
|
||||
try {
|
||||
return (long) artMethodField.get(member);
|
||||
} catch (IllegalAccessException e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
// may 5.0
|
||||
private static void checkSupportForArtMethod() throws Exception {
|
||||
try {
|
||||
dexMethodIndexField = getField(artMethodClass, "dexMethodIndex");
|
||||
} catch (NoSuchFieldException e) {
|
||||
//may 4.4
|
||||
dexMethodIndexField = getField(artMethodClass, "methodDexIndex");
|
||||
}
|
||||
dexCacheField = getField(Class.class, "dexCache");
|
||||
Object dexCache = dexCacheField.get(testMethod.getDeclaringClass());
|
||||
resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods");
|
||||
if (resolvedMethodsField.get(dexCache) instanceof Object[]) {
|
||||
canResolvedInJava = true;
|
||||
}
|
||||
try {
|
||||
try {
|
||||
dexMethodIndex = (int) dexMethodIndexField.get(testArtMethod);
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
try {
|
||||
fieldEntryPointFromCompiledCode = getField(artMethodClass, "entryPointFromQuickCompiledCode");
|
||||
} catch (Throwable e) {
|
||||
fieldEntryPointFromCompiledCode = getField(artMethodClass, "entryPointFromCompiledCode");
|
||||
}
|
||||
if (fieldEntryPointFromCompiledCode.getType() == int.class) {
|
||||
entryPointFromCompiledCode = fieldEntryPointFromCompiledCode.getInt(testArtMethod);
|
||||
} else if (fieldEntryPointFromCompiledCode.getType() == long.class) {
|
||||
entryPointFromCompiledCode = fieldEntryPointFromCompiledCode.getLong(testArtMethod);
|
||||
}
|
||||
fieldEntryPointFromInterpreter = getField(artMethodClass, "entryPointFromInterpreter");
|
||||
if (fieldEntryPointFromInterpreter.getType() == int.class) {
|
||||
entryPointFromInterpreter = fieldEntryPointFromInterpreter.getInt(testArtMethod);
|
||||
} else if (fieldEntryPointFromCompiledCode.getType() == long.class) {
|
||||
entryPointFromInterpreter = fieldEntryPointFromInterpreter.getLong(testArtMethod);
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
}
|
||||
|
||||
// may 6.0
|
||||
private static void checkSupportForArtMethodId() throws Exception {
|
||||
dexMethodIndexField = getField(Method.class, "dexMethodIndex");
|
||||
dexMethodIndex = (int) dexMethodIndexField.get(testMethod);
|
||||
dexCacheField = getField(Class.class, "dexCache");
|
||||
Object dexCache = dexCacheField.get(testMethod.getDeclaringClass());
|
||||
resolvedMethodsField = getField(dexCache.getClass(), "resolvedMethods");
|
||||
Object resolvedMethods = resolvedMethodsField.get(dexCache);
|
||||
if (resolvedMethods instanceof Long) {
|
||||
canResolvedInJava = false;
|
||||
resolvedMethodsAddress = (long) resolvedMethods;
|
||||
} else if (resolvedMethods instanceof long[]) {
|
||||
//64bit
|
||||
canResolvedInJava = true;
|
||||
} else if (resolvedMethods instanceof int[]) {
|
||||
//32bit
|
||||
canResolvedInJava = true;
|
||||
}
|
||||
}
|
||||
|
||||
public static void resolveMethod(Method hook, Method backup) {
|
||||
if (canResolvedInJava && artMethodField != null) {
|
||||
// in java
|
||||
try {
|
||||
resolveInJava(hook, backup);
|
||||
} catch (Exception e) {
|
||||
// in native
|
||||
resolveInNative(hook, backup);
|
||||
}
|
||||
} else {
|
||||
// in native
|
||||
resolveInNative(hook, backup);
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolveInJava(Method hook, Method backup) throws Exception {
|
||||
Object dexCache = dexCacheField.get(hook.getDeclaringClass());
|
||||
if (isArtMethod) {
|
||||
Object artMethod = artMethodField.get(backup);
|
||||
int dexMethodIndex = (int) dexMethodIndexField.get(artMethod);
|
||||
Object resolvedMethods = resolvedMethodsField.get(dexCache);
|
||||
((Object[])resolvedMethods)[dexMethodIndex] = artMethod;
|
||||
} else {
|
||||
int dexMethodIndex = (int) dexMethodIndexField.get(backup);
|
||||
Object resolvedMethods = resolvedMethodsField.get(dexCache);
|
||||
if (resolvedMethods instanceof long[]) {
|
||||
long artMethod = (long) artMethodField.get(backup);
|
||||
((long[])resolvedMethods)[dexMethodIndex] = artMethod;
|
||||
} else if (resolvedMethods instanceof int[]) {
|
||||
int artMethod = Long.valueOf((long)artMethodField.get(backup)).intValue();
|
||||
((int[])resolvedMethods)[dexMethodIndex] = artMethod;
|
||||
} else {
|
||||
throw new UnsupportedOperationException("un support");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void resolveInNative(Method hook, Method backup) {
|
||||
SandHook.ensureMethodCached(hook, backup);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,48 @@
|
|||
package com.swift.sandhook.blacklist;
|
||||
|
||||
import com.swift.sandhook.SandHookConfig;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
public class HookBlackList {
|
||||
|
||||
public static Set<String> methodBlackList = new HashSet<>();
|
||||
public static Set<Class> classBlackList = new HashSet<>();
|
||||
|
||||
public static Set<String> methodUseInHookBridge = new HashSet<>();
|
||||
public static Set<String> methodUseInHookStub = new HashSet<>();
|
||||
|
||||
static {
|
||||
methodBlackList.add("java.lang.reflect.Method.invoke");
|
||||
methodBlackList.add("java.lang.reflect.AccessibleObject.setAccessible");
|
||||
|
||||
methodUseInHookBridge.add("java.lang.Class.getDeclaredField");
|
||||
methodUseInHookBridge.add("java.lang.reflect.InvocationTargetException.getCause");
|
||||
|
||||
methodUseInHookStub.add("java.lang.Object.equals");
|
||||
methodUseInHookStub.add("java.lang.Class.isPrimitive");
|
||||
}
|
||||
|
||||
public final static boolean canNotHook(Member origin) {
|
||||
if (classBlackList.contains(origin.getDeclaringClass()))
|
||||
return true;
|
||||
String name = origin.getDeclaringClass().getName() + "." + origin.getName();
|
||||
return methodBlackList.contains(name);
|
||||
}
|
||||
|
||||
public final static boolean canNotHookByBridge(Member origin) {
|
||||
String name = origin.getDeclaringClass().getName() + "." + origin.getName();
|
||||
return methodUseInHookBridge.contains(name);
|
||||
}
|
||||
|
||||
public final static boolean canNotHookByStub(Member origin) {
|
||||
if (SandHookConfig.SDK_INT >= 29 && Thread.class.equals(origin.getDeclaringClass())) {
|
||||
return true;
|
||||
}
|
||||
String name = origin.getDeclaringClass().getName() + "." + origin.getName();
|
||||
return methodUseInHookStub.contains(name);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,101 @@
|
|||
package com.swift.sandhook.utils;
|
||||
|
||||
import android.os.Build;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookConfig;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* @author Lody
|
||||
*/
|
||||
public class ArtDexOptimizer {
|
||||
|
||||
/**
|
||||
* Optimize the dex in compile mode.
|
||||
*
|
||||
* @param dexFilePath dex file path
|
||||
* @param oatFilePath oat file path
|
||||
* @throws IOException
|
||||
*/
|
||||
public static void dexoatAndDisableInline(String dexFilePath, String oatFilePath) throws IOException {
|
||||
final File oatFile = new File(oatFilePath);
|
||||
if (!oatFile.exists()) {
|
||||
oatFile.getParentFile().mkdirs();
|
||||
}
|
||||
|
||||
final List<String> commandAndParams = new ArrayList<>();
|
||||
commandAndParams.add("dex2oat");
|
||||
// for 7.1.1, duplicate class fix
|
||||
if (SandHookConfig.SDK_INT >= 24) {
|
||||
commandAndParams.add("--runtime-arg");
|
||||
commandAndParams.add("-classpath");
|
||||
commandAndParams.add("--runtime-arg");
|
||||
commandAndParams.add("&");
|
||||
}
|
||||
commandAndParams.add("--dex-file=" + dexFilePath);
|
||||
commandAndParams.add("--oat-file=" + oatFilePath);
|
||||
commandAndParams.add("--instruction-set=" + (SandHook.is64Bit() ? "arm64" : "arm"));
|
||||
commandAndParams.add("--compiler-filter=everything");
|
||||
if (SandHookConfig.SDK_INT >= 22 && SandHookConfig.SDK_INT < 29) {
|
||||
commandAndParams.add("--compile-pic");
|
||||
}
|
||||
if (SandHookConfig.SDK_INT > 25) {
|
||||
commandAndParams.add("--inline-max-code-units=0");
|
||||
} else {
|
||||
if (Build.VERSION.SDK_INT >= 23) {
|
||||
commandAndParams.add("--inline-depth-limit=0");
|
||||
}
|
||||
}
|
||||
|
||||
final ProcessBuilder pb = new ProcessBuilder(commandAndParams);
|
||||
pb.redirectErrorStream(true);
|
||||
final Process dex2oatProcess = pb.start();
|
||||
StreamConsumer.consumeInputStream(dex2oatProcess.getInputStream());
|
||||
StreamConsumer.consumeInputStream(dex2oatProcess.getErrorStream());
|
||||
try {
|
||||
final int ret = dex2oatProcess.waitFor();
|
||||
if (ret != 0) {
|
||||
throw new IOException("dex2oat works unsuccessfully, exit code: " + ret);
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
throw new IOException("dex2oat is interrupted, msg: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
private static class StreamConsumer {
|
||||
static final Executor STREAM_CONSUMER = Executors.newSingleThreadExecutor();
|
||||
|
||||
static void consumeInputStream(final InputStream is) {
|
||||
STREAM_CONSUMER.execute(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (is == null) {
|
||||
return;
|
||||
}
|
||||
final byte[] buffer = new byte[256];
|
||||
try {
|
||||
while ((is.read(buffer)) > 0) {
|
||||
// To satisfy checkstyle rules.
|
||||
}
|
||||
} catch (IOException ignored) {
|
||||
// Ignored.
|
||||
} finally {
|
||||
try {
|
||||
is.close();
|
||||
} catch (Exception ignored) {
|
||||
// Ignored.
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.swift.sandhook.utils;
|
||||
|
||||
import com.swift.sandhook.SandHookConfig;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public class ClassStatusUtils {
|
||||
|
||||
static Field fieldStatusOfClass;
|
||||
|
||||
static {
|
||||
try {
|
||||
fieldStatusOfClass = Class.class.getDeclaredField("status");
|
||||
fieldStatusOfClass.setAccessible(true);
|
||||
} catch (NoSuchFieldException e) {
|
||||
}
|
||||
}
|
||||
|
||||
public static int getClassStatus(Class clazz, boolean isUnsigned) {
|
||||
if (clazz == null) {
|
||||
return 0;
|
||||
}
|
||||
int status = 0;
|
||||
try {
|
||||
status = fieldStatusOfClass.getInt(clazz);
|
||||
} catch (Throwable e) {
|
||||
}
|
||||
if (isUnsigned) {
|
||||
status = (int) (toUnsignedLong(status) >> (32 - 4));
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
public static long toUnsignedLong(int x) {
|
||||
return ((long) x) & 0xffffffffL;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 5.0-8.0: kInitialized = 10 int
|
||||
* 8.1: kInitialized = 11 int
|
||||
* 9.0: kInitialized = 14 uint8_t
|
||||
*/
|
||||
public static boolean isInitialized(Class clazz) {
|
||||
if (fieldStatusOfClass == null)
|
||||
return true;
|
||||
if (SandHookConfig.SDK_INT >= 28) {
|
||||
return getClassStatus(clazz, true) == 14;
|
||||
} else if (SandHookConfig.SDK_INT == 27) {
|
||||
return getClassStatus(clazz, false) == 11;
|
||||
} else {
|
||||
return getClassStatus(clazz, false) == 10;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isStaticAndNoInited(Member hookMethod) {
|
||||
if (hookMethod == null || hookMethod instanceof Constructor) {
|
||||
return false;
|
||||
}
|
||||
Class declaringClass = hookMethod.getDeclaringClass();
|
||||
return Modifier.isStatic(hookMethod.getModifiers())
|
||||
&& !ClassStatusUtils.isInitialized(declaringClass);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,119 @@
|
|||
package com.swift.sandhook.utils;
|
||||
|
||||
import android.os.Build;
|
||||
import android.system.Os;
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.swift.sandhook.HookLog;
|
||||
import com.swift.sandhook.SandHookConfig;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.BufferedWriter;
|
||||
import java.io.File;
|
||||
import java.io.FileReader;
|
||||
import java.io.FileWriter;
|
||||
import java.io.IOException;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public static final boolean IS_USING_PROTECTED_STORAGE = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N;
|
||||
|
||||
/**
|
||||
* Delete a file or a directory and its children.
|
||||
*
|
||||
* @param file The directory to delete.
|
||||
* @throws IOException Exception when problem occurs during deleting the directory.
|
||||
*/
|
||||
public static void delete(File file) throws IOException {
|
||||
|
||||
for (File childFile : file.listFiles()) {
|
||||
|
||||
if (childFile.isDirectory()) {
|
||||
delete(childFile);
|
||||
} else {
|
||||
if (!childFile.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!file.delete()) {
|
||||
throw new IOException();
|
||||
}
|
||||
}
|
||||
|
||||
public static String readLine(File file) {
|
||||
try (BufferedReader reader = new BufferedReader(new FileReader(file))) {
|
||||
return reader.readLine();
|
||||
} catch (Throwable throwable) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
public static void writeLine(File file, String line) {
|
||||
try {
|
||||
file.createNewFile();
|
||||
} catch (IOException ex) {
|
||||
}
|
||||
try (BufferedWriter writer = new BufferedWriter(new FileWriter(file))) {
|
||||
writer.write(line);
|
||||
writer.flush();
|
||||
} catch (Throwable throwable) {
|
||||
HookLog.e("error writing line to file " + file + ": " + throwable.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String getPackageName(String dataDir) {
|
||||
if (TextUtils.isEmpty(dataDir)) {
|
||||
HookLog.e("getPackageName using empty dataDir");
|
||||
return "";
|
||||
}
|
||||
int lastIndex = dataDir.lastIndexOf("/");
|
||||
if (lastIndex < 0) {
|
||||
return dataDir;
|
||||
}
|
||||
return dataDir.substring(lastIndex + 1);
|
||||
}
|
||||
|
||||
public static void chmod(String path, int mode) throws Exception {
|
||||
if (SandHookConfig.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
try {
|
||||
Os.chmod(path, mode);
|
||||
return;
|
||||
} catch (Exception e) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
|
||||
File file = new File(path);
|
||||
String cmd = "chmod ";
|
||||
if (file.isDirectory()) {
|
||||
cmd += " -R ";
|
||||
}
|
||||
String cmode = String.format("%o", mode);
|
||||
Runtime.getRuntime().exec(cmd + cmode + " " + path).waitFor();
|
||||
}
|
||||
|
||||
public interface FileMode {
|
||||
int MODE_ISUID = 04000;
|
||||
int MODE_ISGID = 02000;
|
||||
int MODE_ISVTX = 01000;
|
||||
int MODE_IRUSR = 00400;
|
||||
int MODE_IWUSR = 00200;
|
||||
int MODE_IXUSR = 00100;
|
||||
int MODE_IRGRP = 00040;
|
||||
int MODE_IWGRP = 00020;
|
||||
int MODE_IXGRP = 00010;
|
||||
int MODE_IROTH = 00004;
|
||||
int MODE_IWOTH = 00002;
|
||||
int MODE_IXOTH = 00001;
|
||||
|
||||
int MODE_755 = MODE_IRUSR | MODE_IWUSR | MODE_IXUSR
|
||||
| MODE_IRGRP | MODE_IXGRP
|
||||
| MODE_IROTH | MODE_IXOTH;
|
||||
}
|
||||
|
||||
public static String getDataPathPrefix() {
|
||||
return IS_USING_PROTECTED_STORAGE ? "/data/user_de/0/" : "/data/data/";
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,129 @@
|
|||
package com.swift.sandhook.utils;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
|
||||
public class ParamWrapper {
|
||||
|
||||
private static boolean is64Bit;
|
||||
|
||||
static {
|
||||
is64Bit = SandHook.is64Bit();
|
||||
}
|
||||
|
||||
public static boolean support(Class objectType) {
|
||||
if (is64Bit) {
|
||||
return objectType != float.class && objectType != double.class;
|
||||
} else {
|
||||
return objectType != float.class && objectType != double.class && objectType != long.class;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object addressToObject(Class objectType, long address) {
|
||||
if (is64Bit) {
|
||||
return addressToObject64(objectType, address);
|
||||
} else {
|
||||
return addressToObject32(objectType, (int) address);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object addressToObject64(Class objectType, long address) {
|
||||
if (objectType == null)
|
||||
return null;
|
||||
if (objectType.isPrimitive()) {
|
||||
if (objectType == int.class) {
|
||||
return (int)address;
|
||||
} else if (objectType == long.class) {
|
||||
return address;
|
||||
} else if (objectType == short.class) {
|
||||
return (short)address;
|
||||
} else if (objectType == byte.class) {
|
||||
return (byte)address;
|
||||
} else if (objectType == char.class) {
|
||||
return (char)address;
|
||||
} else if (objectType == boolean.class) {
|
||||
return address != 0;
|
||||
} else {
|
||||
throw new RuntimeException("unknown type: " + objectType.toString());
|
||||
}
|
||||
} else {
|
||||
return SandHook.getObject(address);
|
||||
}
|
||||
}
|
||||
|
||||
public static Object addressToObject32(Class objectType, int address) {
|
||||
if (objectType == null)
|
||||
return null;
|
||||
if (objectType.isPrimitive()) {
|
||||
if (objectType == int.class) {
|
||||
return address;
|
||||
} else if (objectType == short.class) {
|
||||
return (short)address;
|
||||
} else if (objectType == byte.class) {
|
||||
return (byte)address;
|
||||
} else if (objectType == char.class) {
|
||||
return (char)address;
|
||||
} else if (objectType == boolean.class) {
|
||||
return address != 0;
|
||||
} else {
|
||||
throw new RuntimeException("unknown type: " + objectType.toString());
|
||||
}
|
||||
} else {
|
||||
return SandHook.getObject(address);
|
||||
}
|
||||
}
|
||||
|
||||
public static long objectToAddress(Class objectType, Object object) {
|
||||
if (is64Bit) {
|
||||
return objectToAddress64(objectType, object);
|
||||
} else {
|
||||
return objectToAddress32(objectType, object);
|
||||
}
|
||||
}
|
||||
|
||||
public static int objectToAddress32(Class objectType, Object object) {
|
||||
if (object == null)
|
||||
return 0;
|
||||
if (objectType.isPrimitive()) {
|
||||
if (objectType == int.class) {
|
||||
return (int) object;
|
||||
} else if (objectType == short.class) {
|
||||
return (short)object;
|
||||
} else if (objectType == byte.class) {
|
||||
return (byte)object;
|
||||
} else if (objectType == char.class) {
|
||||
return (char)object;
|
||||
} else if (objectType == boolean.class) {
|
||||
return Boolean.TRUE.equals(object) ? 1 : 0;
|
||||
} else {
|
||||
throw new RuntimeException("unknown type: " + objectType.toString());
|
||||
}
|
||||
} else {
|
||||
return (int) SandHook.getObjectAddress(object);
|
||||
}
|
||||
}
|
||||
|
||||
public static long objectToAddress64(Class objectType, Object object) {
|
||||
if (object == null)
|
||||
return 0;
|
||||
if (objectType.isPrimitive()) {
|
||||
if (objectType == int.class) {
|
||||
return (int)object;
|
||||
} else if (objectType == long.class) {
|
||||
return (long) object;
|
||||
} else if (objectType == short.class) {
|
||||
return (short)object;
|
||||
} else if (objectType == byte.class) {
|
||||
return (byte)object;
|
||||
} else if (objectType == char.class) {
|
||||
return (char)object;
|
||||
} else if (objectType == boolean.class) {
|
||||
return Boolean.TRUE.equals(object) ? 1 : 0;
|
||||
} else {
|
||||
throw new RuntimeException("unknown type: " + objectType.toString());
|
||||
}
|
||||
} else {
|
||||
return SandHook.getObjectAddress(object);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
package com.swift.sandhook.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
//
|
||||
// Created by Swift Gan on 2019/3/15.
|
||||
//
|
||||
|
||||
|
||||
//bypass hidden api on Android 9 - 10
|
||||
public class ReflectionUtils {
|
||||
|
||||
public static Method forNameMethod;
|
||||
public static Method getMethodMethod;
|
||||
|
||||
static Class vmRuntimeClass;
|
||||
static Method addWhiteListMethod;
|
||||
|
||||
static Object vmRuntime;
|
||||
|
||||
static {
|
||||
try {
|
||||
getMethodMethod = Class.class.getDeclaredMethod("getDeclaredMethod", String.class, Class[].class);
|
||||
forNameMethod = Class.class.getDeclaredMethod("forName", String.class);
|
||||
vmRuntimeClass = (Class) forNameMethod.invoke(null, "dalvik.system.VMRuntime");
|
||||
addWhiteListMethod = (Method) getMethodMethod.invoke(vmRuntimeClass, "setHiddenApiExemptions", new Class[]{String[].class});
|
||||
Method getVMRuntimeMethod = (Method) getMethodMethod.invoke(vmRuntimeClass, "getRuntime", null);
|
||||
vmRuntime = getVMRuntimeMethod.invoke(null);
|
||||
} catch (Exception e) {
|
||||
Log.e("ReflectionUtils", "error get methods", e);
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean passApiCheck() {
|
||||
try {
|
||||
addReflectionWhiteList("Landroid/",
|
||||
"Lcom/android/",
|
||||
"Ljava/lang/",
|
||||
"Ldalvik/system/",
|
||||
"Llibcore/io/");
|
||||
return true;
|
||||
} catch (Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
//methidSigs like Lcom/swift/sandhook/utils/ReflectionUtils;->vmRuntime:java/lang/Object; (from hidden policy list)
|
||||
public static void addReflectionWhiteList(String... memberSigs) throws Throwable {
|
||||
addWhiteListMethod.invoke(vmRuntime, new Object[] {memberSigs});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,124 @@
|
|||
/*
|
||||
* Copyright 2014-2015 Marvin Wißfeld
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.swift.sandhook.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.swift.sandhook.HookLog;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public final class Unsafe {
|
||||
private static final String TAG = "Unsafe";
|
||||
|
||||
private static Object unsafe;
|
||||
private static Class unsafeClass;
|
||||
|
||||
private static Method arrayBaseOffsetMethod,
|
||||
arrayIndexScaleMethod,
|
||||
getIntMethod,
|
||||
getLongMethod;
|
||||
|
||||
private volatile static boolean supported = false;
|
||||
|
||||
private static Class objectArrayClass = Object[].class;
|
||||
|
||||
static {
|
||||
try {
|
||||
unsafeClass = Class.forName("sun.misc.Unsafe");
|
||||
Field theUnsafe = unsafeClass.getDeclaredField("theUnsafe");
|
||||
theUnsafe.setAccessible(true);
|
||||
unsafe = theUnsafe.get(null);
|
||||
} catch (Exception e) {
|
||||
try {
|
||||
final Field theUnsafe = unsafeClass.getDeclaredField("THE_ONE");
|
||||
theUnsafe.setAccessible(true);
|
||||
unsafe = theUnsafe.get(null);
|
||||
} catch (Exception e2) {
|
||||
Log.w(TAG, "Unsafe not found o.O");
|
||||
}
|
||||
}
|
||||
if (unsafe != null) {
|
||||
try {
|
||||
arrayBaseOffsetMethod = unsafeClass.getDeclaredMethod("arrayBaseOffset", Class.class);
|
||||
arrayIndexScaleMethod = unsafeClass.getDeclaredMethod("arrayIndexScale", Class.class);
|
||||
getIntMethod = unsafeClass.getDeclaredMethod("getInt", Object.class, long.class);
|
||||
getLongMethod = unsafeClass.getDeclaredMethod("getLong", Object.class, long.class);
|
||||
supported = true;
|
||||
} catch (Exception e) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean support() {
|
||||
return supported;
|
||||
}
|
||||
|
||||
private Unsafe() {
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static int arrayBaseOffset(Class cls) {
|
||||
try {
|
||||
return (int) arrayBaseOffsetMethod.invoke(unsafe, cls);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static int arrayIndexScale(Class cls) {
|
||||
try {
|
||||
return (int) arrayIndexScaleMethod.invoke(unsafe, cls);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static int getInt(Object array, long offset) {
|
||||
try {
|
||||
return (int) getIntMethod.invoke(unsafe, array, offset);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static long getLong(Object array, long offset) {
|
||||
try {
|
||||
return (long) getLongMethod.invoke(unsafe, array, offset);
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static long getObjectAddress(Object obj) {
|
||||
try {
|
||||
Object[] array = new Object[]{obj};
|
||||
if (arrayIndexScale(objectArrayClass) == 8) {
|
||||
return getLong(array, arrayBaseOffset(objectArrayClass));
|
||||
} else {
|
||||
return 0xffffffffL & getInt(array, arrayBaseOffset(objectArrayClass));
|
||||
}
|
||||
} catch (Exception e) {
|
||||
HookLog.e("get object address error", e);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
package com.swift.sandhook.wrapper;
|
||||
|
||||
public class HookErrorException extends Exception {
|
||||
public HookErrorException(String s) {
|
||||
super(s);
|
||||
}
|
||||
|
||||
public HookErrorException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,443 @@
|
|||
package com.swift.sandhook.wrapper;
|
||||
|
||||
import android.text.TextUtils;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.annotation.HookClass;
|
||||
import com.swift.sandhook.annotation.HookMethod;
|
||||
import com.swift.sandhook.annotation.HookMethodBackup;
|
||||
import com.swift.sandhook.annotation.HookReflectClass;
|
||||
import com.swift.sandhook.annotation.MethodParams;
|
||||
import com.swift.sandhook.annotation.MethodReflectParams;
|
||||
import com.swift.sandhook.annotation.Param;
|
||||
import com.swift.sandhook.annotation.SkipParamCheck;
|
||||
import com.swift.sandhook.annotation.ThisObject;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
public class HookWrapper {
|
||||
|
||||
public static void addHookClass(Class<?>... classes) throws HookErrorException {
|
||||
addHookClass(null, classes);
|
||||
}
|
||||
|
||||
public static void addHookClass(ClassLoader classLoader, Class<?>... classes) throws HookErrorException {
|
||||
for (Class clazz:classes) {
|
||||
addHookClass(classLoader, clazz);
|
||||
}
|
||||
}
|
||||
|
||||
public static void addHookClass(ClassLoader classLoader, Class<?> clazz) throws HookErrorException {
|
||||
Class targetHookClass = getTargetHookClass(classLoader, clazz);
|
||||
if (targetHookClass == null)
|
||||
throw new HookErrorException("error hook wrapper class :" + clazz.getName());
|
||||
Map<Member,HookEntity> hookEntityMap = getHookMethods(classLoader, targetHookClass, clazz);
|
||||
try {
|
||||
fillBackupMethod(classLoader, clazz, hookEntityMap);
|
||||
} catch (Throwable throwable) {
|
||||
throw new HookErrorException("fillBackupMethod error!", throwable);
|
||||
}
|
||||
for (HookEntity entity:hookEntityMap.values()) {
|
||||
SandHook.hook(entity);
|
||||
}
|
||||
}
|
||||
|
||||
private static void fillBackupMethod(ClassLoader classLoader,Class<?> clazz, Map<Member, HookEntity> hookEntityMap) {
|
||||
Field[] fields = null;
|
||||
try {
|
||||
fields = clazz.getDeclaredFields();
|
||||
} catch (Throwable throwable) {}
|
||||
if (fields == null || fields.length == 0)
|
||||
return;
|
||||
if (hookEntityMap.isEmpty())
|
||||
return;
|
||||
for (Field field:fields) {
|
||||
if (!Modifier.isStatic(field.getModifiers()))
|
||||
continue;
|
||||
HookMethodBackup hookMethodBackup = field.getAnnotation(HookMethodBackup.class);
|
||||
if (hookMethodBackup == null)
|
||||
continue;
|
||||
for (HookEntity hookEntity:hookEntityMap.values()) {
|
||||
if (TextUtils.equals(hookEntity.isCtor() ? "<init>" : hookEntity.target.getName(), hookMethodBackup.value()) && samePars(classLoader, field, hookEntity.pars)) {
|
||||
field.setAccessible(true);
|
||||
if (hookEntity.backup == null) {
|
||||
hookEntity.backup = StubMethodsFactory.getStubMethod();
|
||||
hookEntity.hookIsStub = true;
|
||||
hookEntity.resolveDexCache = false;
|
||||
}
|
||||
if (hookEntity.backup == null)
|
||||
continue;
|
||||
try {
|
||||
if (field.getType() == Method.class) {
|
||||
field.set(null, hookEntity.backup);
|
||||
} else if (field.getType() == HookEntity.class) {
|
||||
field.set(null, hookEntity);
|
||||
}
|
||||
} catch (IllegalAccessException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static Map<Member, HookEntity> getHookMethods(ClassLoader classLoader, Class targetHookClass, Class<?> hookWrapperClass) throws HookErrorException {
|
||||
Map<Member,HookEntity> hookEntityMap = new HashMap<>();
|
||||
Method[] methods = null;
|
||||
try {
|
||||
methods = hookWrapperClass.getDeclaredMethods();
|
||||
} catch (Throwable throwable) {
|
||||
}
|
||||
if (methods == null || methods.length == 0)
|
||||
throw new HookErrorException("error hook wrapper class :" + targetHookClass.getName());
|
||||
for (Method method:methods) {
|
||||
HookMethod hookMethodAnno = method.getAnnotation(HookMethod.class);
|
||||
HookMethodBackup hookMethodBackupAnno = method.getAnnotation(HookMethodBackup.class);
|
||||
String methodName;
|
||||
Member foundMethod;
|
||||
Class[] pars;
|
||||
if (hookMethodAnno != null) {
|
||||
methodName = hookMethodAnno.value();
|
||||
pars = parseMethodPars(classLoader, method);
|
||||
try {
|
||||
if (methodName.equals("<init>")) {
|
||||
foundMethod = targetHookClass.getConstructor(pars);
|
||||
} else {
|
||||
foundMethod = targetHookClass.getDeclaredMethod(methodName, pars);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new HookErrorException("can not find target method: " + methodName, e);
|
||||
}
|
||||
if (!method.isAnnotationPresent(SkipParamCheck.class)) {
|
||||
checkSignature(foundMethod, method, pars);
|
||||
}
|
||||
HookEntity entity = hookEntityMap.get(foundMethod);
|
||||
if (entity == null) {
|
||||
entity = new HookEntity(foundMethod);
|
||||
hookEntityMap.put(foundMethod, entity);
|
||||
}
|
||||
entity.pars = pars;
|
||||
entity.hook = method;
|
||||
} else if (hookMethodBackupAnno != null) {
|
||||
methodName = hookMethodBackupAnno.value();
|
||||
pars = parseMethodPars(classLoader, method);
|
||||
try {
|
||||
if (methodName.equals("<init>")) {
|
||||
foundMethod = targetHookClass.getConstructor(pars);
|
||||
} else {
|
||||
foundMethod = targetHookClass.getDeclaredMethod(methodName, pars);
|
||||
}
|
||||
} catch (NoSuchMethodException e) {
|
||||
throw new HookErrorException("can not find target method: " + methodName, e);
|
||||
}
|
||||
if (!method.isAnnotationPresent(SkipParamCheck.class)) {
|
||||
checkSignature(foundMethod, method, pars);
|
||||
}
|
||||
HookEntity entity = hookEntityMap.get(foundMethod);
|
||||
if (entity == null) {
|
||||
entity = new HookEntity(foundMethod);
|
||||
hookEntityMap.put(foundMethod, entity);
|
||||
}
|
||||
entity.pars = pars;
|
||||
entity.backup = method;
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
return hookEntityMap;
|
||||
}
|
||||
|
||||
private static Class[] parseMethodPars(ClassLoader classLoader, Method method) throws HookErrorException {
|
||||
MethodParams methodParams = method.getAnnotation(MethodParams.class);
|
||||
MethodReflectParams methodReflectParams = method.getAnnotation(MethodReflectParams.class);
|
||||
if (methodParams != null) {
|
||||
return methodParams.value();
|
||||
} else if (methodReflectParams != null) {
|
||||
if (methodReflectParams.value().length == 0)
|
||||
return null;
|
||||
Class[] pars = new Class[methodReflectParams.value().length];
|
||||
for (int i = 0;i < methodReflectParams.value().length; i++) {
|
||||
try {
|
||||
pars[i] = classNameToClass(methodReflectParams.value()[i], classLoader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new HookErrorException("hook method pars error: " + method.getName(), e);
|
||||
}
|
||||
}
|
||||
return pars;
|
||||
} else if (getParsCount(method) > 0) {
|
||||
if (getParsCount(method) == 1) {
|
||||
if (hasThisObject(method)) {
|
||||
return parseMethodParsNew(classLoader, method);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return parseMethodParsNew(classLoader, method);
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Class[] parseMethodPars(ClassLoader classLoader, Field field) throws HookErrorException {
|
||||
MethodParams methodParams = field.getAnnotation(MethodParams.class);
|
||||
MethodReflectParams methodReflectParams = field.getAnnotation(MethodReflectParams.class);
|
||||
if (methodParams != null) {
|
||||
return methodParams.value();
|
||||
} else if (methodReflectParams != null) {
|
||||
if (methodReflectParams.value().length == 0)
|
||||
return null;
|
||||
Class[] pars = new Class[methodReflectParams.value().length];
|
||||
for (int i = 0;i < methodReflectParams.value().length; i++) {
|
||||
try {
|
||||
pars[i] = classNameToClass(methodReflectParams.value()[i], classLoader);
|
||||
} catch (ClassNotFoundException e) {
|
||||
throw new HookErrorException("hook method pars error: " + field.getName(), e);
|
||||
}
|
||||
}
|
||||
return pars;
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private static Class[] parseMethodParsNew(ClassLoader classLoader, Method method) throws HookErrorException {
|
||||
Class[] hookMethodPars = method.getParameterTypes();
|
||||
if (hookMethodPars == null || hookMethodPars.length == 0)
|
||||
return null;
|
||||
Annotation[][] annotations = method.getParameterAnnotations();
|
||||
Class[] realPars = null;
|
||||
int parIndex = 0;
|
||||
for (int i = 0;i < annotations.length;i ++) {
|
||||
Class hookPar = hookMethodPars[i];
|
||||
Annotation[] methodAnnos = annotations[i];
|
||||
if (i == 0) {
|
||||
//check thisObject
|
||||
if (isThisObject(methodAnnos)) {
|
||||
realPars = new Class[annotations.length - 1];
|
||||
continue;
|
||||
} else {
|
||||
//static method
|
||||
realPars = new Class[annotations.length];
|
||||
}
|
||||
}
|
||||
try {
|
||||
realPars[parIndex] = getRealParType(classLoader, hookPar, methodAnnos, method.isAnnotationPresent(SkipParamCheck.class));
|
||||
} catch (Exception e) {
|
||||
throw new HookErrorException("hook method <" + method.getName() + "> parser pars error", e);
|
||||
}
|
||||
parIndex++;
|
||||
}
|
||||
return realPars;
|
||||
}
|
||||
|
||||
private static Class getRealParType(ClassLoader classLoader, Class hookPar, Annotation[] annotations, boolean skipCheck) throws Exception {
|
||||
if (annotations == null || annotations.length == 0)
|
||||
return hookPar;
|
||||
for (Annotation annotation:annotations) {
|
||||
if (annotation instanceof Param) {
|
||||
Param param = (Param) annotation;
|
||||
if (TextUtils.isEmpty(param.value()))
|
||||
return hookPar;
|
||||
Class realPar = classNameToClass(param.value(), classLoader);
|
||||
if (skipCheck || realPar.equals(hookPar) || hookPar.isAssignableFrom(realPar)) {
|
||||
return realPar;
|
||||
} else {
|
||||
throw new ClassCastException("hook method par cast error!");
|
||||
}
|
||||
}
|
||||
}
|
||||
return hookPar;
|
||||
}
|
||||
|
||||
private static boolean hasThisObject(Method method) {
|
||||
Annotation[][] annotations = method.getParameterAnnotations();
|
||||
if (annotations == null || annotations.length == 0)
|
||||
return false;
|
||||
Annotation[] thisParAnno = annotations[0];
|
||||
return isThisObject(thisParAnno);
|
||||
}
|
||||
|
||||
private static boolean isThisObject(Annotation[] annotations) {
|
||||
if (annotations == null || annotations.length == 0)
|
||||
return false;
|
||||
for (Annotation annotation:annotations) {
|
||||
if (annotation instanceof ThisObject)
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private static int getParsCount(Method method) {
|
||||
Class[] hookMethodPars = method.getParameterTypes();
|
||||
return hookMethodPars == null ? 0 : hookMethodPars.length;
|
||||
}
|
||||
|
||||
private static Class classNameToClass(String name, ClassLoader classLoader) throws ClassNotFoundException {
|
||||
Class clazz;
|
||||
switch (name) {
|
||||
case MethodReflectParams.BOOLEAN:
|
||||
clazz = boolean.class;
|
||||
break;
|
||||
case MethodReflectParams.BYTE:
|
||||
clazz = byte.class;
|
||||
break;
|
||||
case MethodReflectParams.CHAR:
|
||||
clazz = char.class;
|
||||
break;
|
||||
case MethodReflectParams.DOUBLE:
|
||||
clazz = double.class;
|
||||
break;
|
||||
case MethodReflectParams.FLOAT:
|
||||
clazz = float.class;
|
||||
break;
|
||||
case MethodReflectParams.INT:
|
||||
clazz = int.class;
|
||||
break;
|
||||
case MethodReflectParams.LONG:
|
||||
clazz = long.class;
|
||||
break;
|
||||
case MethodReflectParams.SHORT:
|
||||
clazz = short.class;
|
||||
break;
|
||||
default:
|
||||
if (classLoader == null) {
|
||||
clazz = Class.forName(name);
|
||||
} else {
|
||||
clazz = Class.forName(name, true, classLoader);
|
||||
}
|
||||
}
|
||||
return clazz;
|
||||
}
|
||||
|
||||
|
||||
private static boolean samePars(ClassLoader classLoader, Field field, Class[] par) {
|
||||
try {
|
||||
Class[] parsOnField = parseMethodPars(classLoader, field);
|
||||
if (parsOnField == null && field.isAnnotationPresent(SkipParamCheck.class))
|
||||
return true;
|
||||
if (par == null) {
|
||||
par = new Class[0];
|
||||
}
|
||||
if (parsOnField == null)
|
||||
parsOnField = new Class[0];
|
||||
if (par.length != parsOnField.length)
|
||||
return false;
|
||||
for (int i = 0;i < par.length;i++) {
|
||||
if (par[i] != parsOnField[i])
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
} catch (HookErrorException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static Class getTargetHookClass(ClassLoader classLoader, Class<?> hookWrapperClass) {
|
||||
HookClass hookClass = hookWrapperClass.getAnnotation(HookClass.class);
|
||||
HookReflectClass hookReflectClass = hookWrapperClass.getAnnotation(HookReflectClass.class);
|
||||
if (hookClass != null) {
|
||||
return hookClass.value();
|
||||
} else if (hookReflectClass != null) {
|
||||
try {
|
||||
if (classLoader == null) {
|
||||
return Class.forName(hookReflectClass.value());
|
||||
} else {
|
||||
return Class.forName(hookReflectClass.value(), true, classLoader);
|
||||
}
|
||||
} catch (ClassNotFoundException e) {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static void checkSignature(Member origin, Method fake, Class[] originPars) throws HookErrorException {
|
||||
if (!Modifier.isStatic(fake.getModifiers()))
|
||||
throw new HookErrorException("hook method must static! - " + fake.getName());
|
||||
if (origin instanceof Constructor) {
|
||||
if (!fake.getReturnType().equals(Void.TYPE))
|
||||
throw new HookErrorException("error return type! - " + fake.getName());
|
||||
} else if (origin instanceof Method) {
|
||||
Class originRet = ((Method)origin).getReturnType();
|
||||
if (originRet != fake.getReturnType() && !originRet.isAssignableFrom(originRet))
|
||||
throw new HookErrorException("error return type! - " + fake.getName());
|
||||
}
|
||||
Class[] fakePars = fake.getParameterTypes();
|
||||
if (fakePars == null)
|
||||
fakePars = new Class[0];
|
||||
if (originPars == null)
|
||||
originPars = new Class[0];
|
||||
if (originPars.length == 0 && fakePars.length == 0)
|
||||
return;
|
||||
int parOffset = 0;
|
||||
if (!Modifier.isStatic(origin.getModifiers())) {
|
||||
parOffset = 1;
|
||||
if (fakePars.length == 0)
|
||||
throw new HookErrorException("first par must be this! " + fake.getName());
|
||||
if (fakePars[0] != origin.getDeclaringClass() && !fakePars[0].isAssignableFrom(origin.getDeclaringClass()))
|
||||
throw new HookErrorException("first par must be this! " + fake.getName());
|
||||
if (fakePars.length != originPars.length + 1)
|
||||
throw new HookErrorException("hook method pars must match the origin method! " + fake.getName());
|
||||
} else {
|
||||
if (fakePars.length != originPars.length)
|
||||
throw new HookErrorException("hook method pars must match the origin method! " + fake.getName());
|
||||
}
|
||||
for (int i = 0;i < originPars.length;i++) {
|
||||
if (fakePars[i + parOffset] != originPars[i] && !fakePars[i + parOffset].isAssignableFrom(originPars[i]))
|
||||
throw new HookErrorException("hook method pars must match the origin method! " + fake.getName());
|
||||
}
|
||||
}
|
||||
|
||||
public static class HookEntity {
|
||||
|
||||
public Member target;
|
||||
public Method hook;
|
||||
public Method backup;
|
||||
|
||||
public boolean hookIsStub = false;
|
||||
public boolean resolveDexCache = true;
|
||||
public boolean backupIsStub = true;
|
||||
public boolean initClass = true;
|
||||
|
||||
public Class[] pars;
|
||||
public int hookMode;
|
||||
|
||||
public HookEntity(Member target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
public HookEntity(Member target, Method hook, Method backup) {
|
||||
this.target = target;
|
||||
this.hook = hook;
|
||||
this.backup = backup;
|
||||
}
|
||||
|
||||
public HookEntity(Member target, Method hook, Method backup, boolean resolveDexCache) {
|
||||
this.target = target;
|
||||
this.hook = hook;
|
||||
this.backup = backup;
|
||||
this.resolveDexCache = resolveDexCache;
|
||||
}
|
||||
|
||||
public boolean isCtor() {
|
||||
return target instanceof Constructor;
|
||||
}
|
||||
|
||||
public Object callOrigin(Object thiz, Object... args) throws Throwable {
|
||||
return SandHook.callOriginMethod(backupIsStub, target, backup, thiz, args);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,340 @@
|
|||
package com.swift.sandhook.wrapper;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
public class StubMethodsFactory {
|
||||
|
||||
final static int maxStub = 300;
|
||||
private static volatile int curStub = 0;
|
||||
|
||||
private static Method proxyGenClass;
|
||||
|
||||
static {
|
||||
try {
|
||||
proxyGenClass = Proxy.class.getDeclaredMethod("generateProxy",String.class,Class[].class,ClassLoader.class,Method[].class,Class[][].class);
|
||||
proxyGenClass.setAccessible(true);
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
public static synchronized Method getStubMethod() {
|
||||
while (curStub <= maxStub) {
|
||||
try {
|
||||
return StubMethodsFactory.class.getDeclaredMethod("stub" + curStub++);
|
||||
} catch (NoSuchMethodException e) {
|
||||
}
|
||||
}
|
||||
try {
|
||||
curStub++;
|
||||
Class proxyClass = (Class)proxyGenClass.invoke(null,"SandHookerStubClass_" + curStub, null, StubMethodsFactory.class.getClassLoader(), new Method[]{proxyGenClass}, null);
|
||||
return proxyClass.getDeclaredMethods()[0];
|
||||
} catch (Throwable e) {
|
||||
e.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public void stub0() {}
|
||||
public void stub1() {}
|
||||
public void stub2() {}
|
||||
public void stub3() {}
|
||||
public void stub4() {}
|
||||
public void stub5() {}
|
||||
public void stub6() {}
|
||||
public void stub7() {}
|
||||
public void stub8() {}
|
||||
public void stub9() {}
|
||||
public void stub10() {}
|
||||
public void stub11() {}
|
||||
public void stub12() {}
|
||||
public void stub13() {}
|
||||
public void stub14() {}
|
||||
public void stub15() {}
|
||||
public void stub16() {}
|
||||
public void stub17() {}
|
||||
public void stub18() {}
|
||||
public void stub19() {}
|
||||
public void stub20() {}
|
||||
public void stub21() {}
|
||||
public void stub22() {}
|
||||
public void stub23() {}
|
||||
public void stub24() {}
|
||||
public void stub25() {}
|
||||
public void stub26() {}
|
||||
public void stub27() {}
|
||||
public void stub28() {}
|
||||
public void stub29() {}
|
||||
public void stub30() {}
|
||||
public void stub31() {}
|
||||
public void stub32() {}
|
||||
public void stub33() {}
|
||||
public void stub34() {}
|
||||
public void stub35() {}
|
||||
public void stub36() {}
|
||||
public void stub37() {}
|
||||
public void stub38() {}
|
||||
public void stub39() {}
|
||||
public void stub40() {}
|
||||
public void stub41() {}
|
||||
public void stub42() {}
|
||||
public void stub43() {}
|
||||
public void stub44() {}
|
||||
public void stub45() {}
|
||||
public void stub46() {}
|
||||
public void stub47() {}
|
||||
public void stub48() {}
|
||||
public void stub49() {}
|
||||
public void stub50() {}
|
||||
public void stub51() {}
|
||||
public void stub52() {}
|
||||
public void stub53() {}
|
||||
public void stub54() {}
|
||||
public void stub55() {}
|
||||
public void stub56() {}
|
||||
public void stub57() {}
|
||||
public void stub58() {}
|
||||
public void stub59() {}
|
||||
public void stub60() {}
|
||||
public void stub61() {}
|
||||
public void stub62() {}
|
||||
public void stub63() {}
|
||||
public void stub64() {}
|
||||
public void stub65() {}
|
||||
public void stub66() {}
|
||||
public void stub67() {}
|
||||
public void stub68() {}
|
||||
public void stub69() {}
|
||||
public void stub70() {}
|
||||
public void stub71() {}
|
||||
public void stub72() {}
|
||||
public void stub73() {}
|
||||
public void stub74() {}
|
||||
public void stub75() {}
|
||||
public void stub76() {}
|
||||
public void stub77() {}
|
||||
public void stub78() {}
|
||||
public void stub79() {}
|
||||
public void stub80() {}
|
||||
public void stub81() {}
|
||||
public void stub82() {}
|
||||
public void stub83() {}
|
||||
public void stub84() {}
|
||||
public void stub85() {}
|
||||
public void stub86() {}
|
||||
public void stub87() {}
|
||||
public void stub88() {}
|
||||
public void stub89() {}
|
||||
public void stub90() {}
|
||||
public void stub91() {}
|
||||
public void stub92() {}
|
||||
public void stub93() {}
|
||||
public void stub94() {}
|
||||
public void stub95() {}
|
||||
public void stub96() {}
|
||||
public void stub97() {}
|
||||
public void stub98() {}
|
||||
public void stub99() {}
|
||||
public void stub100() {}
|
||||
public void stub101() {}
|
||||
public void stub102() {}
|
||||
public void stub103() {}
|
||||
public void stub104() {}
|
||||
public void stub105() {}
|
||||
public void stub106() {}
|
||||
public void stub107() {}
|
||||
public void stub108() {}
|
||||
public void stub109() {}
|
||||
public void stub110() {}
|
||||
public void stub111() {}
|
||||
public void stub112() {}
|
||||
public void stub113() {}
|
||||
public void stub114() {}
|
||||
public void stub115() {}
|
||||
public void stub116() {}
|
||||
public void stub117() {}
|
||||
public void stub118() {}
|
||||
public void stub119() {}
|
||||
public void stub120() {}
|
||||
public void stub121() {}
|
||||
public void stub122() {}
|
||||
public void stub123() {}
|
||||
public void stub124() {}
|
||||
public void stub125() {}
|
||||
public void stub126() {}
|
||||
public void stub127() {}
|
||||
public void stub128() {}
|
||||
public void stub129() {}
|
||||
public void stub130() {}
|
||||
public void stub131() {}
|
||||
public void stub132() {}
|
||||
public void stub133() {}
|
||||
public void stub134() {}
|
||||
public void stub135() {}
|
||||
public void stub136() {}
|
||||
public void stub137() {}
|
||||
public void stub138() {}
|
||||
public void stub139() {}
|
||||
public void stub140() {}
|
||||
public void stub141() {}
|
||||
public void stub142() {}
|
||||
public void stub143() {}
|
||||
public void stub144() {}
|
||||
public void stub145() {}
|
||||
public void stub146() {}
|
||||
public void stub147() {}
|
||||
public void stub148() {}
|
||||
public void stub149() {}
|
||||
public void stub150() {}
|
||||
public void stub151() {}
|
||||
public void stub152() {}
|
||||
public void stub153() {}
|
||||
public void stub154() {}
|
||||
public void stub155() {}
|
||||
public void stub156() {}
|
||||
public void stub157() {}
|
||||
public void stub158() {}
|
||||
public void stub159() {}
|
||||
public void stub160() {}
|
||||
public void stub161() {}
|
||||
public void stub162() {}
|
||||
public void stub163() {}
|
||||
public void stub164() {}
|
||||
public void stub165() {}
|
||||
public void stub166() {}
|
||||
public void stub167() {}
|
||||
public void stub168() {}
|
||||
public void stub169() {}
|
||||
public void stub170() {}
|
||||
public void stub171() {}
|
||||
public void stub172() {}
|
||||
public void stub173() {}
|
||||
public void stub174() {}
|
||||
public void stub175() {}
|
||||
public void stub176() {}
|
||||
public void stub177() {}
|
||||
public void stub178() {}
|
||||
public void stub179() {}
|
||||
public void stub180() {}
|
||||
public void stub181() {}
|
||||
public void stub182() {}
|
||||
public void stub183() {}
|
||||
public void stub184() {}
|
||||
public void stub185() {}
|
||||
public void stub186() {}
|
||||
public void stub187() {}
|
||||
public void stub188() {}
|
||||
public void stub189() {}
|
||||
public void stub190() {}
|
||||
public void stub191() {}
|
||||
public void stub192() {}
|
||||
public void stub193() {}
|
||||
public void stub194() {}
|
||||
public void stub195() {}
|
||||
public void stub196() {}
|
||||
public void stub197() {}
|
||||
public void stub198() {}
|
||||
public void stub199() {}
|
||||
public void stub200() {}
|
||||
public void stub201() {}
|
||||
public void stub202() {}
|
||||
public void stub203() {}
|
||||
public void stub204() {}
|
||||
public void stub205() {}
|
||||
public void stub206() {}
|
||||
public void stub207() {}
|
||||
public void stub208() {}
|
||||
public void stub209() {}
|
||||
public void stub210() {}
|
||||
public void stub211() {}
|
||||
public void stub212() {}
|
||||
public void stub213() {}
|
||||
public void stub214() {}
|
||||
public void stub215() {}
|
||||
public void stub216() {}
|
||||
public void stub217() {}
|
||||
public void stub218() {}
|
||||
public void stub219() {}
|
||||
public void stub220() {}
|
||||
public void stub221() {}
|
||||
public void stub222() {}
|
||||
public void stub223() {}
|
||||
public void stub224() {}
|
||||
public void stub225() {}
|
||||
public void stub226() {}
|
||||
public void stub227() {}
|
||||
public void stub228() {}
|
||||
public void stub229() {}
|
||||
public void stub230() {}
|
||||
public void stub231() {}
|
||||
public void stub232() {}
|
||||
public void stub233() {}
|
||||
public void stub234() {}
|
||||
public void stub235() {}
|
||||
public void stub236() {}
|
||||
public void stub237() {}
|
||||
public void stub238() {}
|
||||
public void stub239() {}
|
||||
public void stub240() {}
|
||||
public void stub241() {}
|
||||
public void stub242() {}
|
||||
public void stub243() {}
|
||||
public void stub244() {}
|
||||
public void stub245() {}
|
||||
public void stub246() {}
|
||||
public void stub247() {}
|
||||
public void stub248() {}
|
||||
public void stub249() {}
|
||||
public void stub250() {}
|
||||
public void stub251() {}
|
||||
public void stub252() {}
|
||||
public void stub253() {}
|
||||
public void stub254() {}
|
||||
public void stub255() {}
|
||||
public void stub256() {}
|
||||
public void stub257() {}
|
||||
public void stub258() {}
|
||||
public void stub259() {}
|
||||
public void stub260() {}
|
||||
public void stub261() {}
|
||||
public void stub262() {}
|
||||
public void stub263() {}
|
||||
public void stub264() {}
|
||||
public void stub265() {}
|
||||
public void stub266() {}
|
||||
public void stub267() {}
|
||||
public void stub268() {}
|
||||
public void stub269() {}
|
||||
public void stub270() {}
|
||||
public void stub271() {}
|
||||
public void stub272() {}
|
||||
public void stub273() {}
|
||||
public void stub274() {}
|
||||
public void stub275() {}
|
||||
public void stub276() {}
|
||||
public void stub277() {}
|
||||
public void stub278() {}
|
||||
public void stub279() {}
|
||||
public void stub280() {}
|
||||
public void stub281() {}
|
||||
public void stub282() {}
|
||||
public void stub283() {}
|
||||
public void stub284() {}
|
||||
public void stub285() {}
|
||||
public void stub286() {}
|
||||
public void stub287() {}
|
||||
public void stub288() {}
|
||||
public void stub289() {}
|
||||
public void stub290() {}
|
||||
public void stub291() {}
|
||||
public void stub292() {}
|
||||
public void stub293() {}
|
||||
public void stub294() {}
|
||||
public void stub295() {}
|
||||
public void stub296() {}
|
||||
public void stub297() {}
|
||||
public void stub298() {}
|
||||
public void stub299() {}
|
||||
public void stub300() {}
|
||||
}
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
<resources>
|
||||
<string name="app_name">hooklib</string>
|
||||
</resources>
|
||||
|
|
@ -1 +1 @@
|
|||
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-service', ':apk-parser:library'
|
||||
include ':edxp-core', ':xposed-bridge', ':hiddenapi-stubs', ':dexmaker', ':dalvikdx', ':edxp-common', ':edxp-yahfa', ':edxp-sandhook', ':edxp-service', ':apk-parser:library', ':sandhook-hooklib', ':sandhook-annotation'
|
||||
|
|
@ -5,7 +5,7 @@ android {
|
|||
ndkVersion androidCompileNdkVersion
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 26
|
||||
minSdkVersion minSdkVersion
|
||||
}
|
||||
|
||||
sourceSets {
|
||||
|
|
|
|||
Loading…
Reference in New Issue