Add SandHook-hooklib to source tree

This commit is contained in:
kotori0 2020-12-11 03:31:56 +08:00
parent 8aa619f282
commit 35bdae390b
87 changed files with 6887 additions and 31 deletions

View File

@ -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
}

View File

@ -4,8 +4,8 @@ android {
compileSdkVersion androidCompileSdkVersion.toInteger()
defaultConfig {
minSdkVersion 26
targetSdkVersion 28
minSdkVersion minSdkVersion
targetSdkVersion targetSdkVersion
versionCode 1
versionName "1.0"
}

View File

@ -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) {

View File

@ -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)
}
}
}
}
}

View File

@ -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

View File

@ -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

View File

@ -1,4 +1,6 @@
androidCompileSdkVersion=30
androidCompileNdkVersion=22.0.6917172
android.prefabVersion=1.1.2
apiCode=93
apiCode=93
minSdkVersion=26
targetSdkVersion=30

1
sandhook-annotation/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

View File

@ -0,0 +1,8 @@
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
}
sourceCompatibility = "1.7"
targetCompatibility = "1.7"

View File

@ -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();
}

View File

@ -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>";
}

View File

@ -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>";
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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();
}

View File

@ -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 "";
}

View File

@ -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 {
}

View File

@ -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 {
}

2
sandhook-hooklib/.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
/build
/.cxx

View File

@ -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"
}
}
}
}
}

23
sandhook-hooklib/proguard-rules.pro vendored Normal file
View File

@ -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.** { *; }

View File

@ -0,0 +1,2 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.swift.sandhook.lib" />

View File

@ -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)

View File

@ -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) {
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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();
}

View File

@ -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

View File

@ -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

View File

@ -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);
};
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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));
}
}

View File

@ -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;
}

View File

@ -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

View File

@ -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

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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;
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,6 @@
package com.swift.sandhook;
public class ArtMethodSizeTest {
public final static void method1(){}
public final static void method2(){}
}

View File

@ -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();
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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();
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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.
}
}
}
});
}
}
}

View File

@ -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);
}
}

View File

@ -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/";
}
}

View File

@ -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);
}
}
}

View File

@ -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});
}
}

View File

@ -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;
}
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}
}

View File

@ -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() {}
}

View File

@ -0,0 +1,3 @@
<resources>
<string name="app_name">hooklib</string>
</resources>

View File

@ -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'

View File

@ -5,7 +5,7 @@ android {
ndkVersion androidCompileNdkVersion
defaultConfig {
minSdkVersion 26
minSdkVersion minSdkVersion
}
sourceSets {