[core] Completely remove SandHook (#293)
This commit is contained in:
parent
370a400abb
commit
7d52c215ad
|
|
@ -163,37 +163,6 @@ public class ConfigManager {
|
|||
}
|
||||
}
|
||||
|
||||
public static int getVariant() {
|
||||
try {
|
||||
return LSPosedManagerServiceClient.getVariant();
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
public static String getVariantString() {
|
||||
int variant = getVariant();
|
||||
switch (variant) {
|
||||
case 1:
|
||||
return "YAHFA";
|
||||
case 2:
|
||||
return "SandHook";
|
||||
default:
|
||||
return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean setVariant(int variant) {
|
||||
try {
|
||||
LSPosedManagerServiceClient.setVariant(variant);
|
||||
return true;
|
||||
} catch (RemoteException | NullPointerException e) {
|
||||
Log.e(App.TAG, Log.getStackTraceString(e));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean isPermissive() {
|
||||
try {
|
||||
return LSPosedManagerServiceClient.isPermissive();
|
||||
|
|
|
|||
|
|
@ -115,16 +115,6 @@ public class LSPosedManagerServiceClient {
|
|||
service.setVerboseLog(enabled);
|
||||
}
|
||||
|
||||
public static int getVariant() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.getVariant();
|
||||
}
|
||||
|
||||
public static void setVariant(int variant) throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
service.setVariant(variant);
|
||||
}
|
||||
|
||||
public static boolean isPermissive() throws RemoteException, NullPointerException {
|
||||
ensureService();
|
||||
return service.isPermissive();
|
||||
|
|
|
|||
|
|
@ -92,7 +92,7 @@ public class MainActivity extends BaseActivity {
|
|||
String installXposedVersion = ConfigManager.getXposedVersionName();
|
||||
int cardBackgroundColor;
|
||||
if (installXposedVersion != null) {
|
||||
binding.statusTitle.setText(getString(R.string.Activated, ConfigManager.getVariantString()));
|
||||
binding.statusTitle.setText(getString(R.string.Activated, "YAHFA"));
|
||||
if (!ConfigManager.isPermissive()) {
|
||||
if (Helpers.currentHoliday == Helpers.Holidays.LUNARNEWYEAR) {
|
||||
cardBackgroundColor = 0xfff05654;
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ val commitCount = Git(repo).log().add(refId).call().count()
|
|||
|
||||
val defaultManagerPackageName by extra("io.github.lsposed.manager")
|
||||
val verCode by extra(commitCount + 4200)
|
||||
val verName by extra("v1.2.2")
|
||||
val verName by extra("v1.3.0")
|
||||
val androidTargetSdkVersion by extra(30)
|
||||
val androidMinSdkVersion by extra(27)
|
||||
val androidBuildToolsVersion by extra("30.0.3")
|
||||
|
|
|
|||
|
|
@ -66,7 +66,6 @@ val verName: String by rootProject.extra
|
|||
dependencies {
|
||||
implementation("dev.rikka.ndk:riru:10")
|
||||
implementation("com.android.tools.build:apksig:4.1.2")
|
||||
implementation(project(":sandhook-hooklib"))
|
||||
compileOnly(project(":hiddenapi-stubs"))
|
||||
compileOnly("androidx.annotation:annotation:1.1.0")
|
||||
implementation(project(":interface"))
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ interface ILSPApplicationService {
|
|||
|
||||
IBinder requestManagerBinder() = 3;
|
||||
|
||||
int getVariant() = 4;
|
||||
|
||||
boolean isResourcesHookEnabled() = 5;
|
||||
|
||||
String[] getModulesList(String processName) = 6;
|
||||
|
|
|
|||
|
|
@ -15,8 +15,5 @@ add_subdirectory(Dobby)
|
|||
target_include_directories(dobby PUBLIC Dobby/include)
|
||||
target_include_directories(dobby PUBLIC Dobby/builtin-plugin/BionicLinkerRestriction)
|
||||
|
||||
add_subdirectory(SandHook)
|
||||
target_include_directories(sandhook.lspd PUBLIC SandHook)
|
||||
|
||||
add_subdirectory(DexBuilder)
|
||||
target_include_directories(dex_builder PUBLIC DexBuilder)
|
||||
|
|
|
|||
|
|
@ -1,36 +0,0 @@
|
|||
cmake_minimum_required(VERSION 3.4.1)
|
||||
project(sandhook.lspd)
|
||||
|
||||
ENABLE_LANGUAGE(ASM)
|
||||
|
||||
add_definitions(-std=c++11)
|
||||
|
||||
if (${CMAKE_ANDROID_ARCH_ABI} STREQUAL armeabi-v7a OR ${CMAKE_ANDROID_ARCH_ABI} STREQUAL arm64-v8a)
|
||||
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
|
||||
)
|
||||
else()
|
||||
set(${PROJECT_NAME}_SOURCES dummy.cpp)
|
||||
endif()
|
||||
add_library(${PROJECT_NAME}
|
||||
STATIC
|
||||
${${PROJECT_NAME}_SOURCES})
|
||||
|
||||
target_link_libraries(${PROJECT_NAME} log)
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Created by 双草酸酯 on 11/27/20.
|
||||
//
|
||||
#include "../includes/art_classlinker.h"
|
||||
|
||||
using namespace art;
|
||||
void ClassLinker::MakeInitializedClassesVisiblyInitialized(void* self, bool wait) {
|
||||
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -1,197 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
|
@ -1,290 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
//
|
||||
// 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_Q) {
|
||||
return BYTE_POINT + 3 * sizeof(size_t);
|
||||
}
|
||||
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();
|
||||
|
||||
}
|
||||
|
|
@ -1,10 +0,0 @@
|
|||
//
|
||||
// Created by Kotori0 on 2021/1/30.
|
||||
//
|
||||
#include "sandhook.h"
|
||||
#include "includes/log.h"
|
||||
|
||||
bool JNI_Load_Ex(JNIEnv* env, jclass classSandHook, jclass classNeverCall) {
|
||||
LOGE("Sandhook: Unsupported platform.");
|
||||
return false;
|
||||
}
|
||||
|
|
@ -1,49 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
#define ANDROID_S 31
|
||||
|
||||
#endif //SANDHOOK_ARCH_H
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// 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);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,66 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* 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_RUNTIME_GC_COLLECTOR_TYPE_H_
|
||||
#define ART_RUNTIME_GC_COLLECTOR_TYPE_H_
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace art {
|
||||
namespace gc {
|
||||
|
||||
// Which types of collections are able to be performed.
|
||||
enum CollectorType {
|
||||
// No collector selected.
|
||||
kCollectorTypeNone,
|
||||
// Non concurrent mark-sweep.
|
||||
kCollectorTypeMS,
|
||||
// Concurrent mark-sweep.
|
||||
kCollectorTypeCMS,
|
||||
// Semi-space / mark-sweep hybrid, enables compaction.
|
||||
kCollectorTypeSS,
|
||||
// Heap trimming collector, doesn't do any actual collecting.
|
||||
kCollectorTypeHeapTrim,
|
||||
// A (mostly) concurrent copying collector.
|
||||
kCollectorTypeCC,
|
||||
// The background compaction of the concurrent copying collector.
|
||||
kCollectorTypeCCBackground,
|
||||
// Instrumentation critical section fake collector.
|
||||
kCollectorTypeInstrumentation,
|
||||
// Fake collector for adding or removing application image spaces.
|
||||
kCollectorTypeAddRemoveAppImageSpace,
|
||||
// Fake collector used to implement exclusion between GC and debugger.
|
||||
kCollectorTypeDebugger,
|
||||
// A homogeneous space compaction collector used in background transition
|
||||
// when both foreground and background collector are CMS.
|
||||
kCollectorTypeHomogeneousSpaceCompact,
|
||||
// Class linker fake collector.
|
||||
kCollectorTypeClassLinker,
|
||||
// JIT Code cache fake collector.
|
||||
kCollectorTypeJitCodeCache,
|
||||
// Hprof fake collector.
|
||||
kCollectorTypeHprof,
|
||||
// Fake collector for installing/removing a system-weak holder.
|
||||
kCollectorTypeAddRemoveSystemWeakHolder,
|
||||
// Fake collector type for GetObjectsAllocated
|
||||
kCollectorTypeGetObjectsAllocated,
|
||||
// Fake collector type for ScopedGCCriticalSection
|
||||
kCollectorTypeCriticalSection,
|
||||
};
|
||||
} // namespace gc
|
||||
} // namespace art
|
||||
|
||||
#endif // ART_RUNTIME_GC_COLLECTOR_TYPE_H_
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2014 The Android Open Source Project
|
||||
*
|
||||
* 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_RUNTIME_GC_GC_CAUSE_H_
|
||||
#define ART_RUNTIME_GC_GC_CAUSE_H_
|
||||
|
||||
#include <iosfwd>
|
||||
|
||||
namespace art {
|
||||
namespace gc {
|
||||
|
||||
// What caused the GC?
|
||||
enum GcCause {
|
||||
// Invalid GC cause used as a placeholder.
|
||||
kGcCauseNone,
|
||||
// GC triggered by a failed allocation. Thread doing allocation is blocked waiting for GC before
|
||||
// retrying allocation.
|
||||
kGcCauseForAlloc,
|
||||
// A background GC trying to ensure there is free memory ahead of allocations.
|
||||
kGcCauseBackground,
|
||||
// An explicit System.gc() call.
|
||||
kGcCauseExplicit,
|
||||
// GC triggered for a native allocation when NativeAllocationGcWatermark is exceeded.
|
||||
// (This may be a blocking GC depending on whether we run a non-concurrent collector).
|
||||
kGcCauseForNativeAlloc,
|
||||
// GC triggered for a collector transition.
|
||||
kGcCauseCollectorTransition,
|
||||
// Not a real GC cause, used when we disable moving GC (currently for GetPrimitiveArrayCritical).
|
||||
kGcCauseDisableMovingGc,
|
||||
// Not a real GC cause, used when we trim the heap.
|
||||
kGcCauseTrim,
|
||||
// Not a real GC cause, used to implement exclusion between GC and instrumentation.
|
||||
kGcCauseInstrumentation,
|
||||
// Not a real GC cause, used to add or remove app image spaces.
|
||||
kGcCauseAddRemoveAppImageSpace,
|
||||
// Not a real GC cause, used to implement exclusion between GC and debugger.
|
||||
kGcCauseDebugger,
|
||||
// GC triggered for background transition when both foreground and background collector are CMS.
|
||||
kGcCauseHomogeneousSpaceCompact,
|
||||
// Class linker cause, used to guard filling art methods with special values.
|
||||
kGcCauseClassLinker,
|
||||
// Not a real GC cause, used to implement exclusion between code cache metadata and GC.
|
||||
kGcCauseJitCodeCache,
|
||||
// Not a real GC cause, used to add or remove system-weak holders.
|
||||
kGcCauseAddRemoveSystemWeakHolder,
|
||||
// Not a real GC cause, used to prevent hprof running in the middle of GC.
|
||||
kGcCauseHprof,
|
||||
// Not a real GC cause, used to prevent GetObjectsAllocated running in the middle of GC.
|
||||
kGcCauseGetObjectsAllocated,
|
||||
// GC cause for the profile saver.
|
||||
kGcCauseProfileSaver,
|
||||
// GC cause for running an empty checkpoint.
|
||||
kGcCauseRunEmptyCheckpoint,
|
||||
};
|
||||
} // namespace gc
|
||||
} // namespace art
|
||||
|
||||
#endif // ART_RUNTIME_GC_GC_CAUSE_H_
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
/*
|
||||
*
|
||||
* 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
|
||||
|
|
@ -1,18 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,39 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
|
||||
|
|
@ -1,24 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
||||
|
||||
|
|
@ -1,17 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// 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 *);
|
||||
void resumeVM(void *);
|
||||
|
||||
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
|
||||
|
|
@ -1,122 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
|
||||
virtual ~Inst() = default;
|
||||
};
|
||||
|
||||
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
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,16 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,231 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,41 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,96 +0,0 @@
|
|||
//
|
||||
// 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));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,566 +0,0 @@
|
|||
#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;
|
||||
SandHook::CastCompilerOptions::init(env);
|
||||
initHideApi(env);
|
||||
SandHook::CastArtMethod::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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
@ -1,9 +0,0 @@
|
|||
//
|
||||
// Created by Kotori0 on 2021/1/29.
|
||||
//
|
||||
|
||||
#ifndef EDXPOSED_SANDHOOK_H
|
||||
#define EDXPOSED_SANDHOOK_H
|
||||
#include <jni.h>
|
||||
bool JNI_Load_Ex(JNIEnv* env, jclass classSandHook, jclass classNeverCall);
|
||||
#endif //EDXPOSED_SANDHOOK_H
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,139 +0,0 @@
|
|||
#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
|
||||
|
|
@ -1,144 +0,0 @@
|
|||
//
|
||||
// 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
|
||||
|
|
@ -1,324 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,296 +0,0 @@
|
|||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
//
|
||||
// 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);
|
||||
}
|
||||
|
|
@ -1,337 +0,0 @@
|
|||
//
|
||||
// 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"
|
||||
#include "../includes/art_collector_type.h"
|
||||
#include "../includes/art_gc_cause.h"
|
||||
|
||||
extern int SDK_INT;
|
||||
|
||||
extern "C" {
|
||||
|
||||
|
||||
void* jitCompilerHandle = nullptr;
|
||||
bool (*jitCompileMethod)(void*, void*, void*, bool) = nullptr;
|
||||
bool (*jitCompileMethodQ)(void*, void*, void*, bool, bool) = nullptr;
|
||||
|
||||
void (*scoped_suspend_all_ctor)(void *, const char *, bool) = nullptr;
|
||||
void (*scoped_suspend_all_dtor)(void *) = nullptr;
|
||||
void (*scoped_gc_critical_section_ctor)(void *, void *, art::gc::GcCause, art::gc::CollectorType) = nullptr;
|
||||
void (*scoped_gc_critical_section_dtor)(void *) = 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;
|
||||
|
||||
jfieldID fieldArtMethod = nullptr;
|
||||
|
||||
// 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"));
|
||||
}
|
||||
auto jit_load = getSymCompat(jit_lib_path, "jit_load");
|
||||
if (jit_load) {
|
||||
if (SDK_INT >= ANDROID_Q) {
|
||||
// Android 10:void* jit_load()
|
||||
// Android 11: JitCompilerInterface* jit_load()
|
||||
jitCompilerHandle = reinterpret_cast<void*(*)()>(jit_load)();
|
||||
} else {
|
||||
// void* jit_load(bool* generate_debug_info)
|
||||
bool generate_debug_info = false;
|
||||
jitCompilerHandle = reinterpret_cast<void*(*)(void*)>(jit_load)(&generate_debug_info);
|
||||
}
|
||||
} else {
|
||||
jitCompilerHandle = getGlobalJitCompiler();
|
||||
}
|
||||
|
||||
if (jitCompilerHandle != nullptr) {
|
||||
art::CompilerOptions* compilerOptions = getCompilerOptions(
|
||||
reinterpret_cast<art::jit::JitCompiler *>(jitCompilerHandle));
|
||||
disableJitInline(compilerOptions);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
//init suspend
|
||||
scoped_suspend_all_ctor = reinterpret_cast<decltype(scoped_suspend_all_ctor)>(getSymCompat(art_lib_path,
|
||||
"_ZN3art16ScopedSuspendAllC2EPKcb"));
|
||||
scoped_suspend_all_dtor = reinterpret_cast<decltype(scoped_suspend_all_dtor)>(getSymCompat(art_lib_path,
|
||||
"_ZN3art16ScopedSuspendAllD2Ev"));
|
||||
scoped_gc_critical_section_ctor = reinterpret_cast<decltype(scoped_gc_critical_section_ctor)>(getSymCompat(art_lib_path,
|
||||
"_ZN3art2gc23ScopedGCCriticalSectionC2EPNS_6ThreadENS0_7GcCauseENS0_13CollectorTypeE"));
|
||||
scoped_gc_critical_section_dtor = reinterpret_cast<decltype(scoped_gc_critical_section_dtor)>(getSymCompat(art_lib_path,
|
||||
"_ZN3art2gc23ScopedGCCriticalSectionD2Ev"));
|
||||
|
||||
//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"));
|
||||
}
|
||||
|
||||
if (SDK_INT >=ANDROID_R) {
|
||||
auto classExecutable = env->FindClass("java/lang/reflect/Executable");
|
||||
fieldArtMethod = env->GetFieldID(classExecutable, "artMethod", "J");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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(void * thiz) {
|
||||
if (scoped_suspend_all_ctor == nullptr || scoped_suspend_all_dtor == nullptr || scoped_gc_critical_section_ctor == nullptr || scoped_gc_critical_section_dtor == nullptr)
|
||||
return;
|
||||
scoped_gc_critical_section_ctor(thiz, getCurrentThread(), art::gc::kGcCauseDebugger, art::gc::kCollectorTypeDebugger);
|
||||
scoped_suspend_all_ctor(thiz, "Sandhook", false);
|
||||
}
|
||||
|
||||
void resumeVM(void * thiz) {
|
||||
if (scoped_suspend_all_ctor == nullptr || scoped_suspend_all_dtor == nullptr || scoped_gc_critical_section_ctor == nullptr || scoped_gc_critical_section_dtor == nullptr)
|
||||
return;
|
||||
scoped_gc_critical_section_dtor(thiz);
|
||||
scoped_suspend_all_dtor(thiz);
|
||||
}
|
||||
|
||||
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");
|
||||
}
|
||||
|
||||
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) {
|
||||
if (SDK_INT >= ANDROID_R) {
|
||||
return reinterpret_cast<ArtMethod *>(env->GetLongField(method, fieldArtMethod));
|
||||
} else {
|
||||
jmethodID methodId = env->FromReflectedMethod(method);
|
||||
return reinterpret_cast<ArtMethod *>(methodId);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
//
|
||||
// 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(this); }
|
||||
inline ~StopTheWorld() { resumeVM(this); }
|
||||
private:
|
||||
void* self_;
|
||||
const char* section_name_;
|
||||
const char* old_no_suspend_reason_;
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif //SANDHOOK_LOCK_H
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,143 +0,0 @@
|
|||
//
|
||||
// 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;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -29,4 +29,4 @@ SET(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -Wl,--dynamic-list=${CMAK
|
|||
|
||||
find_package(riru REQUIRED CONFIG)
|
||||
find_library(log-lib log)
|
||||
target_link_libraries(riru_lspd yahfa riru::riru android dobby sandhook.lspd dex_builder ${log-lib})
|
||||
target_link_libraries(riru_lspd yahfa riru::riru android dobby dex_builder ${log-lib})
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ ALWAYS_INLINE static int ClearException(JNIEnv *env) {
|
|||
|
||||
#define JNI_GetMethodID(env, class, name, sig) \
|
||||
env->GetMethodID(class, name, sig); \
|
||||
if (ClearException(env)) LOGE("GetMethodID " #name)
|
||||
//if (ClearException(env)) LOGE("GetMethodID " #name)
|
||||
|
||||
#define JNI_CallObjectMethod(env, obj, ...) \
|
||||
env->CallObjectMethod(obj, __VA_ARGS__); \
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@
|
|||
#include "jni_helper.h"
|
||||
#include "jni/art_class_linker.h"
|
||||
#include "jni/yahfa.h"
|
||||
#include "jni/sandhook.h"
|
||||
#include "jni/resources_hook.h"
|
||||
#include <dl_util.h>
|
||||
#include <art/runtime/jni_env_ext.h>
|
||||
|
|
@ -122,7 +121,6 @@ namespace lspd {
|
|||
RegisterYahfa(env);
|
||||
RegisterPendingHooks(env);
|
||||
RegisterNativeAPI(env);
|
||||
RegisterSandHook(env);
|
||||
}
|
||||
|
||||
jclass
|
||||
|
|
|
|||
|
|
@ -44,7 +44,7 @@ namespace lspd {
|
|||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
LSP_NATIVE_METHOD(ClassLinker, setEntryPointsToInterpreter,
|
||||
"(Ljava/lang/reflect/Member;)V")
|
||||
"(Ljava/lang/reflect/Executable;)V")
|
||||
};
|
||||
|
||||
void RegisterArtClassLinker(JNIEnv *env) {
|
||||
|
|
|
|||
|
|
@ -1,37 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
#include <sandhook.h>
|
||||
#include "sandhook.h"
|
||||
#include "native_util.h"
|
||||
|
||||
namespace lspd {
|
||||
LSP_DEF_NATIVE_METHOD(bool, SandHook, init, jclass classSandHook, jclass classNeverCall) {
|
||||
return JNI_Load_Ex(env, classSandHook, classNeverCall);
|
||||
}
|
||||
|
||||
static JNINativeMethod gMethods[] = {
|
||||
LSP_NATIVE_METHOD(SandHook, init,
|
||||
"(Ljava/lang/Class;Ljava/lang/Class;)Z"),
|
||||
};
|
||||
|
||||
void RegisterSandHook(JNIEnv *env) {
|
||||
REGISTER_LSP_NATIVE_METHODS(SandHook);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,32 +0,0 @@
|
|||
/*
|
||||
* This file is part of LSPosed.
|
||||
*
|
||||
* LSPosed is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* LSPosed is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with LSPosed. If not, see <https://www.gnu.org/licenses/>.
|
||||
*
|
||||
* Copyright (C) 2021 LSPosed Contributors
|
||||
*/
|
||||
|
||||
//
|
||||
// Created by loves on 2/18/2021.
|
||||
//
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "jni.h"
|
||||
|
||||
namespace lspd {
|
||||
void RegisterSandHook(JNIEnv *env);
|
||||
};
|
||||
|
||||
|
||||
|
|
@ -173,11 +173,11 @@ namespace lspd {
|
|||
static JNINativeMethod gMethods[] = {
|
||||
LSP_NATIVE_METHOD(Yahfa, init, "(I)V"),
|
||||
LSP_NATIVE_METHOD(Yahfa, findMethodNative,
|
||||
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Member;"),
|
||||
"(Ljava/lang/Class;Ljava/lang/String;Ljava/lang/String;)Ljava/lang/reflect/Executable;"),
|
||||
LSP_NATIVE_METHOD(Yahfa, backupAndHookNative,
|
||||
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
|
||||
LSP_NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Member;)V"),
|
||||
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Member;)Z"),
|
||||
"(Ljava/lang/reflect/Executable;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
|
||||
LSP_NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Executable;)V"),
|
||||
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Executable;)Z"),
|
||||
LSP_NATIVE_METHOD(Yahfa, buildHooker, "(Ljava/lang/ClassLoader;Ljava/lang/Class;[Ljava/lang/Class;Ljava/lang/String;)Ljava/lang/Class;"),
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -1,57 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat;
|
||||
|
||||
import io.github.lsposed.lspd.util.ProxyClassLoader;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.methodgen.SandHookXposedBridge;
|
||||
import com.swift.sandhook.xposedcompat.utils.ApplicationUtils;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
public class XposedCompat {
|
||||
public static volatile ClassLoader classLoader;
|
||||
|
||||
//try to use internal stub hooker & backup method to speed up hook
|
||||
public static volatile boolean useInternalStub = true;
|
||||
public static volatile boolean useNewCallBackup = true;
|
||||
public static volatile boolean retryWhenCallOriginError = false;
|
||||
|
||||
private static ClassLoader sandHookXposedClassLoader;
|
||||
|
||||
public static void addHookers(ClassLoader classLoader, Class[] hookers) {
|
||||
if (hookers == null)
|
||||
return;
|
||||
for (Class hooker : hookers) {
|
||||
try {
|
||||
HookWrapper.addHookClass(classLoader, hooker);
|
||||
} catch (Throwable throwable) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static void onForkProcess() {
|
||||
classLoader = null;
|
||||
sandHookXposedClassLoader = null;
|
||||
}
|
||||
|
||||
public static ClassLoader getClassLoader() {
|
||||
if (classLoader == null) {
|
||||
classLoader = getSandHookXposedClassLoader(ApplicationUtils.currentApplication().getClassLoader(), XposedCompat.class.getClassLoader());
|
||||
}
|
||||
return classLoader;
|
||||
}
|
||||
|
||||
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||
SandHookXposedBridge.hookMethod(hookMethod, additionalHookInfo);
|
||||
}
|
||||
|
||||
public static ClassLoader getSandHookXposedClassLoader(ClassLoader appOriginClassLoader, ClassLoader sandBoxHostClassLoader) {
|
||||
if (sandHookXposedClassLoader != null) {
|
||||
return sandHookXposedClassLoader;
|
||||
} else {
|
||||
sandHookXposedClassLoader = new ProxyClassLoader(sandBoxHostClassLoader, appOriginClassLoader);
|
||||
return sandHookXposedClassLoader;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
public interface CallOriginCallBack {
|
||||
long call(long... args) throws Throwable;
|
||||
}
|
||||
|
|
@ -1,93 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.utils.ParamWrapper;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
|
||||
public class HookMethodEntity {
|
||||
|
||||
public Member origin;
|
||||
public Method hook;
|
||||
public Method backup;
|
||||
public Class[] parType;
|
||||
public Class retType;
|
||||
|
||||
public HookMethodEntity(Member origin, Method hook, Method backup) {
|
||||
this.origin = origin;
|
||||
this.hook = hook;
|
||||
this.backup = backup;
|
||||
}
|
||||
|
||||
public Object[] getArgs(long... addresses) {
|
||||
if (addresses == null || addresses.length == 0)
|
||||
return new Object[0];
|
||||
if (parType == null || parType.length == 0)
|
||||
return new Object[0];
|
||||
int argStart = 0;
|
||||
if (!isStatic()) {
|
||||
argStart = 1;
|
||||
}
|
||||
Object[] args = new Object[parType.length];
|
||||
for (int i = argStart;i < parType.length + argStart;i++) {
|
||||
args[i - argStart] = getArg(i - argStart, addresses[i]);
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public long[] getArgsAddress(long[] oldAddress, Object... args) {
|
||||
if (oldAddress == null || oldAddress.length == 0)
|
||||
return new long[0];
|
||||
long[] addresses;
|
||||
int argStart = 0;
|
||||
if (!isStatic()) {
|
||||
argStart = 1;
|
||||
addresses = new long[oldAddress.length + 1];
|
||||
addresses[0] = oldAddress[0];
|
||||
} else {
|
||||
addresses = new long[oldAddress.length];
|
||||
}
|
||||
for (int i = 0;i < parType.length;i++) {
|
||||
addresses[i + argStart] = ParamWrapper.objectToAddress(parType[i], args[i]);
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public Object getThis(long address) {
|
||||
if (isStatic())
|
||||
return null;
|
||||
return SandHook.getObject(address);
|
||||
}
|
||||
|
||||
public Object getArg(int index, long address) {
|
||||
return ParamWrapper.addressToObject(parType[index], address);
|
||||
}
|
||||
|
||||
public Object getResult(long address) {
|
||||
if (isVoid())
|
||||
return null;
|
||||
return ParamWrapper.addressToObject(retType, address);
|
||||
}
|
||||
|
||||
public long getResultAddress(Object result) {
|
||||
if (isVoid())
|
||||
return 0;
|
||||
return ParamWrapper.objectToAddress(retType, result);
|
||||
}
|
||||
|
||||
public boolean isVoid() {
|
||||
return retType == null || Void.TYPE.equals(retType);
|
||||
}
|
||||
|
||||
public boolean isConstructor() {
|
||||
return origin instanceof Constructor;
|
||||
}
|
||||
|
||||
public boolean isStatic() {
|
||||
return Modifier.isStatic(origin.getModifiers());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,411 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.hookstub;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import io.github.lsposed.lspd.BuildConfig;
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookMethodResolver;
|
||||
import com.swift.sandhook.utils.ParamWrapper;
|
||||
import com.swift.sandhook.wrapper.StubMethodsFactory;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.utils.DexLog;
|
||||
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
|
||||
public class HookStubManager {
|
||||
|
||||
public static volatile boolean is64Bit;
|
||||
//64bits arg0 - arg7 is in reg x1 - x7 and > 7 is in stack, but can not match
|
||||
public final static int MAX_64_ARGS = 7;
|
||||
|
||||
public static int MAX_STUB_ARGS = 0;
|
||||
|
||||
public static int[] stubSizes;
|
||||
|
||||
public static boolean hasStubBackup;
|
||||
|
||||
public static AtomicInteger[] curUseStubIndexes;
|
||||
|
||||
public static int ALL_STUB = 0;
|
||||
|
||||
public static Member[] originMethods;
|
||||
public static HookMethodEntity[] hookMethodEntities;
|
||||
public static XposedBridge.AdditionalHookInfo[] additionalHookInfos;
|
||||
|
||||
static {
|
||||
is64Bit = SandHook.is64Bit();
|
||||
Class stubClass = is64Bit ? MethodHookerStubs64.class : MethodHookerStubs32.class;
|
||||
stubSizes = (int[]) XposedHelpers.getStaticObjectField(stubClass, "stubSizes");
|
||||
Boolean hasBackup = (Boolean) XposedHelpers.getStaticObjectField(stubClass, "hasStubBackup");
|
||||
hasStubBackup = hasBackup != null && (hasBackup && !XposedCompat.useNewCallBackup);
|
||||
if (stubSizes != null && stubSizes.length > 0) {
|
||||
MAX_STUB_ARGS = stubSizes.length - 1;
|
||||
curUseStubIndexes = new AtomicInteger[MAX_STUB_ARGS + 1];
|
||||
for (int i = 0; i < MAX_STUB_ARGS + 1; i++) {
|
||||
curUseStubIndexes[i] = new AtomicInteger(0);
|
||||
ALL_STUB += stubSizes[i];
|
||||
}
|
||||
originMethods = new Member[ALL_STUB];
|
||||
hookMethodEntities = new HookMethodEntity[ALL_STUB];
|
||||
additionalHookInfos = new XposedBridge.AdditionalHookInfo[ALL_STUB];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public static HookMethodEntity getHookMethodEntity(Member origin, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||
|
||||
if (!support()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class[] parType;
|
||||
Class retType;
|
||||
boolean isStatic = Modifier.isStatic(origin.getModifiers());
|
||||
|
||||
if (origin instanceof Method) {
|
||||
Method method = (Method) origin;
|
||||
retType = method.getReturnType();
|
||||
parType = method.getParameterTypes();
|
||||
} else if (origin instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) origin;
|
||||
retType = Void.TYPE;
|
||||
parType = constructor.getParameterTypes();
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!ParamWrapper.support(retType))
|
||||
return null;
|
||||
|
||||
int needStubArgCount = isStatic ? 0 : 1;
|
||||
|
||||
if (parType != null) {
|
||||
needStubArgCount += parType.length;
|
||||
if (needStubArgCount > MAX_STUB_ARGS)
|
||||
return null;
|
||||
if (is64Bit && needStubArgCount > MAX_64_ARGS)
|
||||
return null;
|
||||
for (Class par:parType) {
|
||||
if (!ParamWrapper.support(par))
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
parType = new Class[0];
|
||||
}
|
||||
|
||||
synchronized (HookStubManager.class) {
|
||||
StubMethodsInfo stubMethodInfo = getStubMethodPair(is64Bit, needStubArgCount);
|
||||
if (stubMethodInfo == null)
|
||||
return null;
|
||||
HookMethodEntity entity = new HookMethodEntity(origin, stubMethodInfo.hook, stubMethodInfo.backup);
|
||||
entity.retType = retType;
|
||||
entity.parType = parType;
|
||||
if (hasStubBackup && !tryCompileAndResolveCallOriginMethod(entity.backup, stubMethodInfo.args, stubMethodInfo.index)) {
|
||||
DexLog.w("internal stub <" + entity.hook.getName() + "> call origin compile failure, skip use internal stub");
|
||||
return null;
|
||||
} else {
|
||||
int id = getMethodId(stubMethodInfo.args, stubMethodInfo.index);
|
||||
originMethods[id] = origin;
|
||||
hookMethodEntities[id] = entity;
|
||||
additionalHookInfos[id] = additionalHookInfo;
|
||||
return entity;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMethodId(int args, int index) {
|
||||
int id = index;
|
||||
for (int i = 0;i < args;i++) {
|
||||
id += stubSizes[i];
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
public static String getHookMethodName(int index) {
|
||||
return "stub_hook_" + index;
|
||||
}
|
||||
|
||||
public static String getBackupMethodName(int index) {
|
||||
return "stub_backup_" + index;
|
||||
}
|
||||
|
||||
public static String getCallOriginClassName(int args, int index) {
|
||||
return "call_origin_" + args + "_" + index;
|
||||
}
|
||||
|
||||
|
||||
static class StubMethodsInfo {
|
||||
int args = 0;
|
||||
int index = 0;
|
||||
Method hook;
|
||||
Method backup;
|
||||
|
||||
public StubMethodsInfo(int args, int index, Method hook, Method backup) {
|
||||
this.args = args;
|
||||
this.index = index;
|
||||
this.hook = hook;
|
||||
this.backup = backup;
|
||||
}
|
||||
}
|
||||
|
||||
private static synchronized StubMethodsInfo getStubMethodPair(boolean is64Bit, int stubArgs) {
|
||||
|
||||
stubArgs = getMatchStubArgsCount(stubArgs);
|
||||
|
||||
if (stubArgs < 0)
|
||||
return null;
|
||||
|
||||
int curUseStubIndex = curUseStubIndexes[stubArgs].getAndIncrement();
|
||||
Class[] pars = getFindMethodParTypes(is64Bit, stubArgs);
|
||||
try {
|
||||
if (is64Bit) {
|
||||
Method hook = MethodHookerStubs64.class.getDeclaredMethod(getHookMethodName(curUseStubIndex), pars);
|
||||
Method backup = hasStubBackup ? MethodHookerStubs64.class.getDeclaredMethod(getBackupMethodName(curUseStubIndex), pars) : StubMethodsFactory.getStubMethod();
|
||||
if (hook == null || backup == null)
|
||||
return null;
|
||||
return new StubMethodsInfo(stubArgs, curUseStubIndex, hook, backup);
|
||||
} else {
|
||||
Method hook = MethodHookerStubs32.class.getDeclaredMethod(getHookMethodName(curUseStubIndex), pars);
|
||||
Method backup = hasStubBackup ? MethodHookerStubs32.class.getDeclaredMethod(getBackupMethodName(curUseStubIndex), pars) : StubMethodsFactory.getStubMethod();
|
||||
if (hook == null || backup == null)
|
||||
return null;
|
||||
return new StubMethodsInfo(stubArgs, curUseStubIndex, hook, backup);
|
||||
}
|
||||
} catch (Throwable throwable) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Method getCallOriginMethod(int args, int index) {
|
||||
Class stubClass = is64Bit ? MethodHookerStubs64.class : MethodHookerStubs32.class;
|
||||
String className = stubClass.getName();
|
||||
className += "$";
|
||||
className += getCallOriginClassName(args, index);
|
||||
try {
|
||||
Class callOriginClass = Class.forName(className, true, stubClass.getClassLoader());
|
||||
return callOriginClass.getDeclaredMethod("call", long[].class);
|
||||
} catch (Throwable e) {
|
||||
Log.e("HookStubManager", "load call origin class error!", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean tryCompileAndResolveCallOriginMethod(Method backupMethod, int args, int index) {
|
||||
Method method = getCallOriginMethod(args, index);
|
||||
if (method != null) {
|
||||
SandHookMethodResolver.resolveMethod(method, backupMethod);
|
||||
return SandHook.compileMethod(method);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static int getMatchStubArgsCount(int stubArgs) {
|
||||
for (int i = stubArgs;i <= MAX_STUB_ARGS;i++) {
|
||||
if (curUseStubIndexes[i].get() < stubSizes[i])
|
||||
return i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
public static Class[] getFindMethodParTypes(boolean is64Bit, int stubArgs) {
|
||||
if (stubArgs == 0)
|
||||
return null;
|
||||
Class[] args = new Class[stubArgs];
|
||||
if (is64Bit) {
|
||||
for (int i = 0;i < stubArgs;i++) {
|
||||
args[i] = long.class;
|
||||
}
|
||||
} else {
|
||||
for (int i = 0;i < stubArgs;i++) {
|
||||
args[i] = int.class;
|
||||
}
|
||||
}
|
||||
return args;
|
||||
}
|
||||
|
||||
public static long hookBridge(int id, CallOriginCallBack callOrigin, long... stubArgs) throws Throwable {
|
||||
|
||||
Member originMethod = originMethods[id];
|
||||
HookMethodEntity entity = hookMethodEntities[id];
|
||||
|
||||
Object thiz = null;
|
||||
Object[] args = null;
|
||||
|
||||
if (hasArgs(stubArgs)) {
|
||||
thiz = entity.getThis(stubArgs[0]);
|
||||
args = entity.getArgs(stubArgs);
|
||||
}
|
||||
|
||||
if (thiz == null)
|
||||
{
|
||||
thiz = originMethod.getDeclaringClass();
|
||||
}
|
||||
|
||||
DexLog.printMethodHookIn(originMethod);
|
||||
|
||||
Object[] snapshot = additionalHookInfos[id].callbacks.getSnapshot();
|
||||
|
||||
if (snapshot == null || snapshot.length == 0) {
|
||||
if (hasStubBackup) {
|
||||
return callOrigin.call(stubArgs);
|
||||
} else {
|
||||
return callOrigin(entity, originMethod, thiz, args);
|
||||
}
|
||||
}
|
||||
|
||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
||||
|
||||
param.method = originMethod;
|
||||
param.thisObject = thiz;
|
||||
param.args = args;
|
||||
|
||||
int beforeIdx = 0;
|
||||
do {
|
||||
try {
|
||||
((XC_MethodHook) snapshot[beforeIdx]).callBeforeHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||
if( BuildConfig.DEBUG ) XposedBridge.log(t);
|
||||
param.setResult(null);
|
||||
param.returnEarly = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.returnEarly) {
|
||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||
beforeIdx++;
|
||||
break;
|
||||
}
|
||||
} while (++beforeIdx < snapshot.length);
|
||||
|
||||
// call original method if not requested otherwise
|
||||
if (!param.returnEarly) {
|
||||
try {
|
||||
if (hasStubBackup) {
|
||||
//prepare new args
|
||||
long[] newArgs = entity.getArgsAddress(stubArgs, param.args);
|
||||
param.setResult(entity.getResult(callOrigin.call(newArgs)));
|
||||
} else {
|
||||
param.setResult(SandHook.callOriginMethod(originMethod, entity.backup, thiz, param.args));
|
||||
}
|
||||
} catch (Throwable e) {
|
||||
if( BuildConfig.DEBUG ) XposedBridge.log(e);
|
||||
param.setThrowable(e);
|
||||
}
|
||||
}
|
||||
|
||||
// call "after method" callbacks
|
||||
int afterIdx = beforeIdx - 1;
|
||||
do {
|
||||
Object lastResult = param.getResult();
|
||||
Throwable lastThrowable = param.getThrowable();
|
||||
|
||||
try {
|
||||
((XC_MethodHook) snapshot[afterIdx]).callAfterHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
if( BuildConfig.DEBUG ) XposedBridge.log(t);
|
||||
if (lastThrowable == null)
|
||||
param.setResult(lastResult);
|
||||
else
|
||||
param.setThrowable(lastThrowable);
|
||||
}
|
||||
} while (--afterIdx >= 0);
|
||||
if (!param.hasThrowable()) {
|
||||
return entity.getResultAddress(param.getResult());
|
||||
} else {
|
||||
throw param.getThrowable();
|
||||
}
|
||||
}
|
||||
|
||||
public static Object hookBridge(Member origin, Method backup, XposedBridge.AdditionalHookInfo additionalHookInfo, Object thiz, Object... args) throws Throwable {
|
||||
|
||||
|
||||
DexLog.printMethodHookIn(origin);
|
||||
|
||||
Object[] snapshot = additionalHookInfo.callbacks.getSnapshot();
|
||||
|
||||
if (snapshot == null || snapshot.length == 0) {
|
||||
return SandHook.callOriginMethod(origin, backup, thiz, args);
|
||||
}
|
||||
|
||||
XC_MethodHook.MethodHookParam param = new XC_MethodHook.MethodHookParam();
|
||||
|
||||
param.method = origin;
|
||||
param.thisObject = thiz;
|
||||
param.args = args;
|
||||
|
||||
int beforeIdx = 0;
|
||||
do {
|
||||
try {
|
||||
((XC_MethodHook) snapshot[beforeIdx]).callBeforeHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
if( BuildConfig.DEBUG ) XposedBridge.log(t);
|
||||
// reset result (ignoring what the unexpectedly exiting callback did)
|
||||
param.setResult(null);
|
||||
param.returnEarly = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (param.returnEarly) {
|
||||
// skip remaining "before" callbacks and corresponding "after" callbacks
|
||||
beforeIdx++;
|
||||
break;
|
||||
}
|
||||
} while (++beforeIdx < snapshot.length);
|
||||
|
||||
// call original method if not requested otherwise
|
||||
if (!param.returnEarly) {
|
||||
try {
|
||||
param.setResult(SandHook.callOriginMethod(true, origin, backup, thiz, param.args));
|
||||
} catch (Throwable e) {
|
||||
if( BuildConfig.DEBUG ) XposedBridge.log(e);
|
||||
param.setThrowable(e);
|
||||
}
|
||||
}
|
||||
|
||||
// call "after method" callbacks
|
||||
int afterIdx = beforeIdx - 1;
|
||||
do {
|
||||
Object lastResult = param.getResult();
|
||||
Throwable lastThrowable = param.getThrowable();
|
||||
|
||||
try {
|
||||
((XC_MethodHook) snapshot[afterIdx]).callAfterHookedMethod(param);
|
||||
} catch (Throwable t) {
|
||||
if( BuildConfig.DEBUG ) XposedBridge.log(t);
|
||||
if (lastThrowable == null)
|
||||
param.setResult(lastResult);
|
||||
else
|
||||
param.setThrowable(lastThrowable);
|
||||
}
|
||||
} while (--afterIdx >= 0);
|
||||
if (!param.hasThrowable()) {
|
||||
return param.getResult();
|
||||
} else {
|
||||
throw param.getThrowable();
|
||||
}
|
||||
}
|
||||
|
||||
public final static long callOrigin(HookMethodEntity entity, Member origin, Object thiz, Object[] args) throws Throwable {
|
||||
Object res = SandHook.callOriginMethod(true, origin, entity.backup, thiz, args);
|
||||
return entity.getResultAddress(res);
|
||||
}
|
||||
|
||||
private static boolean hasArgs(long... args) {
|
||||
return args != null && args.length > 0;
|
||||
}
|
||||
|
||||
public static boolean support() {
|
||||
return MAX_STUB_ARGS > 0 && SandHook.canGetObject() && SandHook.canGetObjectAddress();
|
||||
}
|
||||
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
|
@ -1,22 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ErrorCatch {
|
||||
|
||||
public static Object callOriginError(Member originMethod, Method backupMethod, Object thiz, Object[] args) throws Throwable {
|
||||
if (XposedCompat.retryWhenCallOriginError) {
|
||||
Log.w("SandHook", "method <" + originMethod.toString() + "> use invoke to call origin!");
|
||||
return SandHook.callOriginMethod(originMethod, backupMethod, thiz, args);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
public interface HookMaker {
|
||||
void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader) throws Exception;
|
||||
Method getHookMethod();
|
||||
Method getBackupMethod();
|
||||
Method getCallBackupMethod();
|
||||
}
|
||||
|
|
@ -1,703 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookMethodResolver;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.utils.DexLog;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import dalvik.system.InMemoryDexClassLoader;
|
||||
import de.robv.android.xposed.XC_MethodHook;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import external.com.android.dx.BinaryOp;
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.Comparison;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.FieldId;
|
||||
import external.com.android.dx.Label;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.MethodId;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoBoxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.canCache;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getSha1Hex;
|
||||
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||
|
||||
public class HookerDexMaker implements HookMaker {
|
||||
|
||||
public static final String METHOD_NAME_BACKUP = "backup";
|
||||
public static final String METHOD_NAME_HOOK = "hook";
|
||||
public static final String METHOD_NAME_CALL_BACKUP = "callBackup";
|
||||
public static final String METHOD_NAME_SETUP = "setup";
|
||||
public static final String METHOD_NAME_LOG = "printMethodHookIn";
|
||||
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
|
||||
private static final String CLASS_DESC_PREFIX = "L";
|
||||
private static final String CLASS_NAME_PREFIX = "SandHooker";
|
||||
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
|
||||
private static final String FIELD_NAME_METHOD = "method";
|
||||
private static final String FIELD_NAME_BACKUP_METHOD = "backupMethod";
|
||||
private static final String PARAMS_FIELD_NAME_METHOD = "method";
|
||||
private static final String PARAMS_FIELD_NAME_THIS_OBJECT = "thisObject";
|
||||
private static final String PARAMS_FIELD_NAME_ARGS = "args";
|
||||
private static final String CALLBACK_METHOD_NAME_BEFORE = "callBeforeHookedMethod";
|
||||
private static final String CALLBACK_METHOD_NAME_AFTER = "callAfterHookedMethod";
|
||||
private static final TypeId<Throwable> throwableTypeId = TypeId.get(Throwable.class);
|
||||
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
|
||||
private static final TypeId<Method> methodTypeId = TypeId.get(Method.class);
|
||||
private static final TypeId<XC_MethodHook> callbackTypeId = TypeId.get(XC_MethodHook.class);
|
||||
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
|
||||
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
|
||||
private static final TypeId<XposedBridge.CopyOnWriteSortedSet> callbacksTypeId
|
||||
= TypeId.get(XposedBridge.CopyOnWriteSortedSet.class);
|
||||
private static final TypeId<XC_MethodHook.MethodHookParam> paramTypeId
|
||||
= TypeId.get(XC_MethodHook.MethodHookParam.class);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setResultMethodId =
|
||||
paramTypeId.getMethod(TypeId.VOID, "setResult", TypeId.OBJECT);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Void> setThrowableMethodId =
|
||||
paramTypeId.getMethod(TypeId.VOID, "setThrowable", throwableTypeId);
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Object> getResultMethodId =
|
||||
paramTypeId.getMethod(TypeId.OBJECT, "getResult");
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Throwable> getThrowableMethodId =
|
||||
paramTypeId.getMethod(throwableTypeId, "getThrowable");
|
||||
private static final MethodId<XC_MethodHook.MethodHookParam, Boolean> hasThrowableMethodId =
|
||||
paramTypeId.getMethod(TypeId.BOOLEAN, "hasThrowable");
|
||||
private static final MethodId<XC_MethodHook, Void> callAfterCallbackMethodId =
|
||||
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_AFTER, paramTypeId);
|
||||
private static final MethodId<XC_MethodHook, Void> callBeforeCallbackMethodId =
|
||||
callbackTypeId.getMethod(TypeId.VOID, CALLBACK_METHOD_NAME_BEFORE, paramTypeId);
|
||||
private static final FieldId<XC_MethodHook.MethodHookParam, Boolean> returnEarlyFieldId =
|
||||
paramTypeId.getField(TypeId.BOOLEAN, "returnEarly");
|
||||
private static final TypeId<XposedBridge> xposedBridgeTypeId = TypeId.get(XposedBridge.class);
|
||||
private static final MethodId<XposedBridge, Void> logThrowableMethodId =
|
||||
xposedBridgeTypeId.getMethod(TypeId.VOID, "log", throwableTypeId);
|
||||
|
||||
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
|
||||
private FieldId<?, Member> mMethodFieldId;
|
||||
private FieldId<?, Method> mBackupMethodFieldId;
|
||||
private MethodId<?, ?> mBackupMethodId;
|
||||
private MethodId<?, ?> mCallBackupMethodId;
|
||||
private MethodId<?, ?> mHookMethodId;
|
||||
private MethodId<?, ?> mPrintLogMethodId;
|
||||
private MethodId<?, ?> mSandHookCallOriginMethodId;
|
||||
|
||||
private TypeId<?> mHookerTypeId;
|
||||
private TypeId<?>[] mParameterTypeIds;
|
||||
private Class<?>[] mActualParameterTypes;
|
||||
private Class<?> mReturnType;
|
||||
private TypeId<?> mReturnTypeId;
|
||||
private boolean mIsStatic;
|
||||
// TODO use this to generate methods
|
||||
private boolean mHasThrowable;
|
||||
|
||||
private DexMaker mDexMaker;
|
||||
private Member mMember;
|
||||
private XposedBridge.AdditionalHookInfo mHookInfo;
|
||||
private ClassLoader mAppClassLoader;
|
||||
private Class<?> mHookClass;
|
||||
private Method mHookMethod;
|
||||
private Method mBackupMethod;
|
||||
private Method mCallBackupMethod;
|
||||
|
||||
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
|
||||
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
|
||||
int offset = 0;
|
||||
if (!isStatic) {
|
||||
parameterTypeIds[0] = TypeId.OBJECT;
|
||||
offset = 1;
|
||||
}
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
|
||||
}
|
||||
return parameterTypeIds;
|
||||
}
|
||||
|
||||
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
if (isStatic) {
|
||||
return parameterTypes;
|
||||
}
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = parameterSize + 1;
|
||||
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
|
||||
int offset = 1;
|
||||
newParameterTypes[0] = Object.class;
|
||||
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
|
||||
return newParameterTypes;
|
||||
}
|
||||
|
||||
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader) throws Exception {
|
||||
if (member instanceof Method) {
|
||||
Method method = (Method) member;
|
||||
mIsStatic = Modifier.isStatic(method.getModifiers());
|
||||
mReturnType = method.getReturnType();
|
||||
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|
||||
|| mReturnType.isPrimitive()) {
|
||||
mReturnTypeId = TypeId.get(mReturnType);
|
||||
} else {
|
||||
// all others fallback to plain Object for convenience
|
||||
mReturnType = Object.class;
|
||||
mReturnTypeId = TypeId.OBJECT;
|
||||
}
|
||||
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = method.getExceptionTypes().length > 0;
|
||||
} else if (member instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) member;
|
||||
mIsStatic = false;
|
||||
mReturnType = void.class;
|
||||
mReturnTypeId = TypeId.VOID;
|
||||
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = constructor.getExceptionTypes().length > 0;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
|
||||
}
|
||||
mMember = member;
|
||||
mHookInfo = hookInfo;
|
||||
if (appClassLoader == null
|
||||
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
|
||||
mAppClassLoader = this.getClass().getClassLoader();
|
||||
} else {
|
||||
mAppClassLoader = appClassLoader;
|
||||
}
|
||||
|
||||
mDexMaker = new DexMaker();
|
||||
// Generate a Hooker class.
|
||||
String className = getClassName(mMember);
|
||||
String dexName = className + ".jar";
|
||||
|
||||
HookWrapper.HookEntity hookEntity = null;
|
||||
//try load cache first
|
||||
try {
|
||||
ClassLoader loader = mDexMaker.loadClassDirect(mAppClassLoader, new File(serviceClient.getCachePath("")), dexName);
|
||||
if (loader != null) {
|
||||
hookEntity = loadHookerClass(loader, className);
|
||||
}
|
||||
} catch (Throwable throwable) {}
|
||||
|
||||
//do generate
|
||||
if (hookEntity == null) {
|
||||
hookEntity = doMake(className, dexName);
|
||||
}
|
||||
SandHook.hook(hookEntity);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity doMake(String className, String dexName) throws Exception {
|
||||
mHookerTypeId = TypeId.get(CLASS_DESC_PREFIX + className + ";");
|
||||
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
|
||||
generateFields();
|
||||
generateSetupMethod();
|
||||
if (XposedCompat.retryWhenCallOriginError) {
|
||||
generateBackupAndCallOriginCheckMethod();
|
||||
} else {
|
||||
generateBackupMethod();
|
||||
}
|
||||
generateCallBackupMethod();
|
||||
generateHookMethod();
|
||||
|
||||
ClassLoader loader;
|
||||
if (canCache) {
|
||||
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(serviceClient.getCachePath("")), dexName, true);
|
||||
File dexFile = new File(serviceClient.getCachePath(dexName));
|
||||
dexFile.setWritable(true, false);
|
||||
dexFile.setReadable(true, false);
|
||||
} else {
|
||||
//can not write file
|
||||
byte[] dexBytes = mDexMaker.generate();
|
||||
loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader);
|
||||
}
|
||||
return loadHookerClass(loader, className);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity loadHookerClass(ClassLoader loader, String className) throws Exception {
|
||||
mHookClass = loader.loadClass(className);
|
||||
// Execute our newly-generated code in-process.
|
||||
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP, mActualParameterTypes);
|
||||
mCallBackupMethod = mHookClass.getMethod(METHOD_NAME_CALL_BACKUP, mActualParameterTypes);
|
||||
SandHook.resolveStaticMethod(mCallBackupMethod);
|
||||
SandHookMethodResolver.resolveMethod(mCallBackupMethod, mBackupMethod);
|
||||
SandHook.compileMethod(mCallBackupMethod);
|
||||
mHookClass.getMethod(METHOD_NAME_SETUP, Member.class, Method.class, XposedBridge.AdditionalHookInfo.class).invoke(null, mMember, mBackupMethod, mHookInfo);
|
||||
return new HookWrapper.HookEntity(mMember, mHookMethod, mBackupMethod);
|
||||
}
|
||||
|
||||
private String getClassName(Member originMethod) {
|
||||
return CLASS_NAME_PREFIX + "_" + getSha1Hex(originMethod.toString());
|
||||
}
|
||||
|
||||
public Method getHookMethod() {
|
||||
return mHookMethod;
|
||||
}
|
||||
|
||||
public Method getBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Method getCallBackupMethod() {
|
||||
return mCallBackupMethod;
|
||||
}
|
||||
|
||||
public Class getHookClass() {
|
||||
return mHookClass;
|
||||
}
|
||||
|
||||
private void generateFields() {
|
||||
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
|
||||
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
|
||||
mBackupMethodFieldId = mHookerTypeId.getField(methodTypeId, FIELD_NAME_BACKUP_METHOD);
|
||||
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mBackupMethodFieldId, Modifier.STATIC, null);
|
||||
}
|
||||
|
||||
private void generateSetupMethod() {
|
||||
MethodId<?, Void> setupMethodId = mHookerTypeId.getMethod(
|
||||
TypeId.VOID, METHOD_NAME_SETUP, memberTypeId, methodTypeId, hookInfoTypeId);
|
||||
Code code = mDexMaker.declare(setupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
// init logic
|
||||
// get parameters
|
||||
Local<Member> method = code.getParameter(0, memberTypeId);
|
||||
Local<Method> backupMethod = code.getParameter(1, methodTypeId);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.getParameter(2, hookInfoTypeId);
|
||||
// save params to static
|
||||
code.sput(mMethodFieldId, method);
|
||||
code.sput(mBackupMethodFieldId, backupMethod);
|
||||
code.sput(mHookInfoFieldId, hookInfo);
|
||||
code.returnVoid();
|
||||
}
|
||||
|
||||
private void generateBackupMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
MethodId<?, ?> errLogMethod = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), "printCallOriginError", memberTypeId);
|
||||
|
||||
|
||||
//very very important!!!!!!!!!!!
|
||||
//add a try cache block avoid inline
|
||||
Label tryCatchBlock = new Label();
|
||||
|
||||
code.addCatchClause(throwableTypeId, tryCatchBlock);
|
||||
code.sget(mMethodFieldId, method);
|
||||
code.invokeStatic(errLogMethod, null, method);
|
||||
// start of try
|
||||
code.mark(tryCatchBlock);
|
||||
|
||||
// do nothing
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// we have limited the returnType to primitives or Object, so this should be safe
|
||||
code.returnValue(resultLocals.get(mReturnTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateBackupAndCallOriginCheckMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_BACKUP, mParameterTypeIds);
|
||||
mSandHookCallOriginMethodId = TypeId.get(ErrorCatch.class).getMethod(TypeId.get(Object.class), "callOriginError", memberTypeId, methodTypeId, TypeId.get(Object.class), TypeId.get(Object[].class));
|
||||
MethodId<?, ?> errLogMethod = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), "printCallOriginError", methodTypeId);
|
||||
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
Local<Method> backupMethod = code.newLocal(methodTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT);
|
||||
Label tryCatchBlock = new Label();
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
|
||||
|
||||
//very very important!!!!!!!!!!!
|
||||
//add a try cache block avoid inline
|
||||
|
||||
|
||||
// start of try
|
||||
code.addCatchClause(throwableTypeId, tryCatchBlock);
|
||||
code.sget(mMethodFieldId, method);
|
||||
code.invokeStatic(errLogMethod, null, method);
|
||||
//call origin by invoke
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
code.sget(mBackupMethodFieldId, backupMethod);
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
|
||||
// offset = mIsStatic ? 0 : 1;
|
||||
// for (int i = offset; i < allArgsLocals.length; i++) {
|
||||
// code.loadConstant(argIndex, i - offset);
|
||||
// code.aget(resultObj, args, argIndex);
|
||||
// autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true);
|
||||
// }
|
||||
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mSandHookCallOriginMethodId, null, method, backupMethod, thisObject, args);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
code.invokeStatic(mSandHookCallOriginMethodId, resultObj, method, backupMethod, thisObject, args);
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
|
||||
code.mark(tryCatchBlock);
|
||||
// do nothing
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// we have limited the returnType to primitives or Object, so this should be safe
|
||||
code.returnValue(resultLocals.get(mReturnTypeId));
|
||||
}
|
||||
}
|
||||
|
||||
private void generateCallBackupMethod() {
|
||||
mCallBackupMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_CALL_BACKUP, mParameterTypeIds);
|
||||
Code code = mDexMaker.declare(mCallBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
// just call backup and return its result
|
||||
|
||||
Local localOrigin = code.newLocal(memberTypeId);
|
||||
Local localBackup = code.newLocal(methodTypeId);
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
|
||||
code.sget(mMethodFieldId, localOrigin);
|
||||
code.sget(mBackupMethodFieldId, localBackup);
|
||||
|
||||
MethodId methodId = TypeId.get(SandHook.class).getMethod(TypeId.get(Void.TYPE), "ensureBackupMethod", memberTypeId, methodTypeId);
|
||||
code.invokeStatic(methodId, null, localOrigin, localBackup);
|
||||
|
||||
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
Local result = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||
code.returnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
private void generateHookMethod() {
|
||||
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
|
||||
mPrintLogMethodId = TypeId.get(DexLog.class).getMethod(TypeId.get(Void.TYPE), METHOD_NAME_LOG, TypeId.get(Member.class));
|
||||
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
// code starts
|
||||
|
||||
// prepare common labels
|
||||
Label noHookReturn = new Label();
|
||||
Label incrementAndCheckBefore = new Label();
|
||||
Label tryBeforeCatch = new Label();
|
||||
Label noExceptionBefore = new Label();
|
||||
Label checkAndCallBackup = new Label();
|
||||
Label beginCallBefore = new Label();
|
||||
Label beginCallAfter = new Label();
|
||||
Label tryOrigCatch = new Label();
|
||||
Label noExceptionOrig = new Label();
|
||||
Label tryAfterCatch = new Label();
|
||||
Label decrementAndCheckAfter = new Label();
|
||||
Label noBackupThrowable = new Label();
|
||||
Label throwThrowable = new Label();
|
||||
// prepare locals
|
||||
Local<Boolean> disableHooks = code.newLocal(TypeId.BOOLEAN);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.newLocal(hookInfoTypeId);
|
||||
Local<XposedBridge.CopyOnWriteSortedSet> callbacks = code.newLocal(callbacksTypeId);
|
||||
Local<Object[]> snapshot = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> snapshotLen = code.newLocal(TypeId.INT);
|
||||
Local<Object> callbackObj = code.newLocal(TypeId.OBJECT);
|
||||
Local<XC_MethodHook> callback = code.newLocal(callbackTypeId);
|
||||
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT); // as a temp Local
|
||||
Local<Integer> one = code.newLocal(TypeId.INT);
|
||||
Local<Object> nullObj = code.newLocal(TypeId.OBJECT);
|
||||
Local<Throwable> throwable = code.newLocal(throwableTypeId);
|
||||
|
||||
Local<XC_MethodHook.MethodHookParam> param = code.newLocal(paramTypeId);
|
||||
Local<Member> method = code.newLocal(memberTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Boolean> returnEarly = code.newLocal(TypeId.BOOLEAN);
|
||||
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
|
||||
Local<Integer> beforeIdx = code.newLocal(TypeId.INT);
|
||||
Local<Object> lastResult = code.newLocal(TypeId.OBJECT);
|
||||
Local<Throwable> lastThrowable = code.newLocal(throwableTypeId);
|
||||
Local<Boolean> hasThrowable = code.newLocal(TypeId.BOOLEAN);
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
code.loadConstant(one, 1);
|
||||
code.loadConstant(snapshotLen, 0);
|
||||
code.loadConstant(nullObj, null);
|
||||
|
||||
code.sget(mMethodFieldId, method);
|
||||
//print log
|
||||
code.invokeStatic(mPrintLogMethodId, null, method);
|
||||
|
||||
// check XposedBridge.disableHooks flag
|
||||
|
||||
FieldId<XposedBridge, Boolean> disableHooksField =
|
||||
xposedBridgeTypeId.getField(TypeId.BOOLEAN, "disableHooks");
|
||||
code.sget(disableHooksField, disableHooks);
|
||||
// disableHooks == true => no hooking
|
||||
code.compareZ(Comparison.NE, noHookReturn, disableHooks);
|
||||
|
||||
// check callbacks length
|
||||
code.sget(mHookInfoFieldId, hookInfo);
|
||||
code.iget(hookInfoTypeId.getField(callbacksTypeId, "callbacks"), callbacks, hookInfo);
|
||||
code.invokeVirtual(callbacksTypeId.getMethod(objArrayTypeId, "getSnapshot"), snapshot, callbacks);
|
||||
code.arrayLength(snapshotLen, snapshot);
|
||||
// snapshotLen == 0 => no hooking
|
||||
code.compareZ(Comparison.EQ, noHookReturn, snapshotLen);
|
||||
|
||||
// start hooking
|
||||
|
||||
// prepare hooking locals
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
// create param
|
||||
code.newInstance(param, paramTypeId.getConstructor());
|
||||
// set method, thisObject, args
|
||||
code.iput(paramTypeId.getField(memberTypeId, PARAMS_FIELD_NAME_METHOD), param, method);
|
||||
code.iput(paramTypeId.getField(TypeId.OBJECT, PARAMS_FIELD_NAME_THIS_OBJECT), param, thisObject);
|
||||
code.iput(paramTypeId.getField(objArrayTypeId, PARAMS_FIELD_NAME_ARGS), param, args);
|
||||
|
||||
// call beforeCallbacks
|
||||
code.loadConstant(beforeIdx, 0);
|
||||
|
||||
code.mark(beginCallBefore);
|
||||
// start of try
|
||||
code.addCatchClause(throwableTypeId, tryBeforeCatch);
|
||||
|
||||
code.aget(callbackObj, snapshot, beforeIdx);
|
||||
code.cast(callback, callbackObj);
|
||||
code.invokeVirtual(callBeforeCallbackMethodId, null, callback, param);
|
||||
code.jump(noExceptionBefore);
|
||||
|
||||
// end of try
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
|
||||
// start of catch
|
||||
code.mark(tryBeforeCatch);
|
||||
code.moveException(throwable);
|
||||
code.invokeStatic(logThrowableMethodId, null, throwable);
|
||||
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||
code.loadConstant(returnEarly, false);
|
||||
code.iput(returnEarlyFieldId, param, returnEarly);
|
||||
code.jump(incrementAndCheckBefore);
|
||||
|
||||
// no exception when calling beforeCallbacks
|
||||
code.mark(noExceptionBefore);
|
||||
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||
// if returnEarly == false, continue
|
||||
code.compareZ(Comparison.EQ, incrementAndCheckBefore, returnEarly);
|
||||
// returnEarly == true, break
|
||||
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||
code.jump(checkAndCallBackup);
|
||||
|
||||
// increment and check to continue
|
||||
code.mark(incrementAndCheckBefore);
|
||||
code.op(BinaryOp.ADD, beforeIdx, beforeIdx, one);
|
||||
code.compare(Comparison.LT, beginCallBefore, beforeIdx, snapshotLen);
|
||||
|
||||
// check and call backup
|
||||
code.mark(checkAndCallBackup);
|
||||
code.iget(returnEarlyFieldId, returnEarly, param);
|
||||
// if returnEarly == true, go to call afterCallbacks directly
|
||||
code.compareZ(Comparison.NE, noExceptionOrig, returnEarly);
|
||||
// try to call backup
|
||||
// try start
|
||||
code.addCatchClause(throwableTypeId, tryOrigCatch);
|
||||
// we have to load args[] to paramLocals
|
||||
// because args[] may be changed in beforeHookedMethod
|
||||
// should consider first param is thisObj if hooked method is not static
|
||||
offset = mIsStatic ? 0 : 1;
|
||||
for (int i = offset; i < allArgsLocals.length; i++) {
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
code.aget(resultObj, args, argIndex);
|
||||
autoUnboxIfNecessary(code, allArgsLocals[i], resultObj, resultLocals, true);
|
||||
}
|
||||
// get pre-created Local with a matching typeId
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
// TODO maybe keep preset result to do some magic?
|
||||
code.invokeVirtual(setResultMethodId, null, param, nullObj);
|
||||
} else {
|
||||
Local returnedResult = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, returnedResult, allArgsLocals);
|
||||
// save returnedResult to resultObj as a Object
|
||||
autoBoxIfNecessary(code, resultObj, returnedResult);
|
||||
// save resultObj to param
|
||||
code.invokeVirtual(setResultMethodId, null, param, resultObj);
|
||||
}
|
||||
// go to call afterCallbacks
|
||||
code.jump(noExceptionOrig);
|
||||
// try end
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
// catch
|
||||
code.mark(tryOrigCatch);
|
||||
code.moveException(throwable);
|
||||
// exception occurred when calling backup, save throwable to param
|
||||
code.invokeVirtual(setThrowableMethodId, null, param, throwable);
|
||||
|
||||
code.mark(noExceptionOrig);
|
||||
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||
|
||||
// call afterCallbacks
|
||||
code.mark(beginCallAfter);
|
||||
// save results of backup calling
|
||||
code.invokeVirtual(getResultMethodId, lastResult, param);
|
||||
code.invokeVirtual(getThrowableMethodId, lastThrowable, param);
|
||||
// try start
|
||||
code.addCatchClause(throwableTypeId, tryAfterCatch);
|
||||
code.aget(callbackObj, snapshot, beforeIdx);
|
||||
code.cast(callback, callbackObj);
|
||||
code.invokeVirtual(callAfterCallbackMethodId, null, callback, param);
|
||||
// all good, just continue
|
||||
code.jump(decrementAndCheckAfter);
|
||||
// try end
|
||||
code.removeCatchClause(throwableTypeId);
|
||||
// catch
|
||||
code.mark(tryAfterCatch);
|
||||
code.moveException(throwable);
|
||||
code.invokeStatic(logThrowableMethodId, null, throwable);
|
||||
// if lastThrowable == null, go to recover lastResult
|
||||
code.compareZ(Comparison.EQ, noBackupThrowable, lastThrowable);
|
||||
// lastThrowable != null, recover lastThrowable
|
||||
code.invokeVirtual(setThrowableMethodId, null, param, lastThrowable);
|
||||
// continue
|
||||
code.jump(decrementAndCheckAfter);
|
||||
code.mark(noBackupThrowable);
|
||||
// recover lastResult and continue
|
||||
code.invokeVirtual(setResultMethodId, null, param, lastResult);
|
||||
// decrement and check continue
|
||||
code.mark(decrementAndCheckAfter);
|
||||
code.op(BinaryOp.SUBTRACT, beforeIdx, beforeIdx, one);
|
||||
code.compareZ(Comparison.GE, beginCallAfter, beforeIdx);
|
||||
|
||||
// callbacks end
|
||||
// return
|
||||
code.invokeVirtual(hasThrowableMethodId, hasThrowable, param);
|
||||
// if hasThrowable, throw the throwable and return
|
||||
code.compareZ(Comparison.NE, throwThrowable, hasThrowable);
|
||||
// return getResult
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.returnVoid();
|
||||
} else {
|
||||
// getResult always return an Object, so save to resultObj
|
||||
code.invokeVirtual(getResultMethodId, resultObj, param);
|
||||
// have to unbox it if returnType is primitive
|
||||
// casting Object
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
// return
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
// throw throwable
|
||||
code.mark(throwThrowable);
|
||||
code.invokeVirtual(getThrowableMethodId, throwable, param);
|
||||
code.throwValue(throwable);
|
||||
|
||||
// call backup and return
|
||||
code.mark(noHookReturn);
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mBackupMethodId, null, allArgsLocals);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
Local result = resultLocals.get(mReturnTypeId);
|
||||
code.invokeStatic(mBackupMethodId, result, allArgsLocals);
|
||||
code.returnValue(result);
|
||||
}
|
||||
}
|
||||
|
||||
private Local[] createParameterLocals(Code code) {
|
||||
Local[] paramLocals = new Local[mParameterTypeIds.length];
|
||||
for (int i = 0; i < mParameterTypeIds.length; i++) {
|
||||
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
|
||||
}
|
||||
return paramLocals;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,313 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.hookstub.HookStubManager;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Map;
|
||||
|
||||
import dalvik.system.InMemoryDexClassLoader;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedHelpers;
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.FieldId;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.MethodId;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoBoxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.autoUnboxIfNecessary;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.canCache;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.createResultLocals;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getObjTypeIdIfPrimitive;
|
||||
import static com.swift.sandhook.xposedcompat.utils.DexMakerUtils.getSha1Hex;
|
||||
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||
|
||||
public class HookerDexMakerNew implements HookMaker {
|
||||
|
||||
public static final String METHOD_NAME_BACKUP = "backup";
|
||||
public static final String METHOD_NAME_HOOK = "hook";
|
||||
public static final TypeId<Object[]> objArrayTypeId = TypeId.get(Object[].class);
|
||||
private static final String CLASS_DESC_PREFIX = "L";
|
||||
private static final String CLASS_NAME_PREFIX = "SandHookerNew";
|
||||
private static final String FIELD_NAME_HOOK_INFO = "additionalHookInfo";
|
||||
private static final String FIELD_NAME_METHOD = "method";
|
||||
private static final String FIELD_NAME_BACKUP_METHOD = "backupMethod";
|
||||
private static final TypeId<Member> memberTypeId = TypeId.get(Member.class);
|
||||
private static final TypeId<Method> methodTypeId = TypeId.get(Method.class);
|
||||
private static final TypeId<XposedBridge.AdditionalHookInfo> hookInfoTypeId
|
||||
= TypeId.get(XposedBridge.AdditionalHookInfo.class);
|
||||
|
||||
|
||||
private FieldId<?, XposedBridge.AdditionalHookInfo> mHookInfoFieldId;
|
||||
private FieldId<?, Member> mMethodFieldId;
|
||||
private FieldId<?, Method> mBackupMethodFieldId;
|
||||
private MethodId<?, ?> mHookMethodId;
|
||||
private MethodId<?, ?> mBackupMethodId;
|
||||
private MethodId<?, ?> mSandHookBridgeMethodId;
|
||||
|
||||
private TypeId<?> mHookerTypeId;
|
||||
private TypeId<?>[] mParameterTypeIds;
|
||||
private Class<?>[] mActualParameterTypes;
|
||||
private Class<?> mReturnType;
|
||||
private TypeId<?> mReturnTypeId;
|
||||
private boolean mIsStatic;
|
||||
// TODO use this to generate methods
|
||||
private boolean mHasThrowable;
|
||||
|
||||
private DexMaker mDexMaker;
|
||||
private Member mMember;
|
||||
private XposedBridge.AdditionalHookInfo mHookInfo;
|
||||
private ClassLoader mAppClassLoader;
|
||||
private Class<?> mHookClass;
|
||||
private Method mHookMethod;
|
||||
private Method mBackupMethod;
|
||||
|
||||
private static TypeId<?>[] getParameterTypeIds(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = isStatic ? parameterSize : parameterSize + 1;
|
||||
TypeId<?>[] parameterTypeIds = new TypeId<?>[targetParameterSize];
|
||||
int offset = 0;
|
||||
if (!isStatic) {
|
||||
parameterTypeIds[0] = TypeId.OBJECT;
|
||||
offset = 1;
|
||||
}
|
||||
for (int i = 0; i < parameterTypes.length; i++) {
|
||||
parameterTypeIds[i + offset] = TypeId.get(parameterTypes[i]);
|
||||
}
|
||||
return parameterTypeIds;
|
||||
}
|
||||
|
||||
private static Class<?>[] getParameterTypes(Class<?>[] parameterTypes, boolean isStatic) {
|
||||
if (isStatic) {
|
||||
return parameterTypes;
|
||||
}
|
||||
int parameterSize = parameterTypes.length;
|
||||
int targetParameterSize = parameterSize + 1;
|
||||
Class<?>[] newParameterTypes = new Class<?>[targetParameterSize];
|
||||
int offset = 1;
|
||||
newParameterTypes[0] = Object.class;
|
||||
System.arraycopy(parameterTypes, 0, newParameterTypes, offset, parameterTypes.length);
|
||||
return newParameterTypes;
|
||||
}
|
||||
|
||||
public void start(Member member, XposedBridge.AdditionalHookInfo hookInfo,
|
||||
ClassLoader appClassLoader) throws Exception {
|
||||
if (member instanceof Method) {
|
||||
Method method = (Method) member;
|
||||
mIsStatic = Modifier.isStatic(method.getModifiers());
|
||||
mReturnType = method.getReturnType();
|
||||
if (mReturnType.equals(Void.class) || mReturnType.equals(void.class)
|
||||
|| mReturnType.isPrimitive()) {
|
||||
mReturnTypeId = TypeId.get(mReturnType);
|
||||
} else {
|
||||
// all others fallback to plain Object for convenience
|
||||
mReturnType = Object.class;
|
||||
mReturnTypeId = TypeId.OBJECT;
|
||||
}
|
||||
mParameterTypeIds = getParameterTypeIds(method.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(method.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = method.getExceptionTypes().length > 0;
|
||||
} else if (member instanceof Constructor) {
|
||||
Constructor constructor = (Constructor) member;
|
||||
mIsStatic = false;
|
||||
mReturnType = void.class;
|
||||
mReturnTypeId = TypeId.VOID;
|
||||
mParameterTypeIds = getParameterTypeIds(constructor.getParameterTypes(), mIsStatic);
|
||||
mActualParameterTypes = getParameterTypes(constructor.getParameterTypes(), mIsStatic);
|
||||
mHasThrowable = constructor.getExceptionTypes().length > 0;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + member.toString());
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
throw new IllegalArgumentException("Cannot hook abstract methods: " + member.toString());
|
||||
} else {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + member.toString());
|
||||
}
|
||||
mMember = member;
|
||||
mHookInfo = hookInfo;
|
||||
if (appClassLoader == null
|
||||
|| appClassLoader.getClass().getName().equals("java.lang.BootClassLoader")) {
|
||||
mAppClassLoader = this.getClass().getClassLoader();
|
||||
} else {
|
||||
mAppClassLoader = appClassLoader;
|
||||
}
|
||||
|
||||
mDexMaker = new DexMaker();
|
||||
// Generate a Hooker class.
|
||||
String className = getClassName(mMember);
|
||||
String dexName = className + ".jar";
|
||||
|
||||
HookWrapper.HookEntity hookEntity = null;
|
||||
//try load cache first
|
||||
try {
|
||||
ClassLoader loader = mDexMaker.loadClassDirect(mAppClassLoader, new File(serviceClient.getCachePath("")), dexName);
|
||||
if (loader != null) {
|
||||
hookEntity = loadHookerClass(loader, className);
|
||||
}
|
||||
} catch (Throwable throwable) {}
|
||||
|
||||
//do generate
|
||||
if (hookEntity == null) {
|
||||
hookEntity = doMake(className, dexName);
|
||||
}
|
||||
SandHook.hook(hookEntity);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity doMake(String className, String dexName) throws Exception {
|
||||
mHookerTypeId = TypeId.get(CLASS_DESC_PREFIX + className + ";");
|
||||
mDexMaker.declare(mHookerTypeId, className + ".generated", Modifier.PUBLIC, TypeId.OBJECT);
|
||||
generateFields();
|
||||
generateHookMethod();
|
||||
generateBackupMethod();
|
||||
|
||||
ClassLoader loader;
|
||||
if (!canCache) {
|
||||
byte[] dexBytes = mDexMaker.generate();
|
||||
loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader);
|
||||
return loadHookerClass(loader, className);
|
||||
}
|
||||
// Create the dex file and load it.
|
||||
try {
|
||||
loader = mDexMaker.generateAndLoad(mAppClassLoader, new File(serviceClient.getCachePath("")), dexName, true);
|
||||
File dexFile = new File(serviceClient.getCachePath(dexName));
|
||||
dexFile.setWritable(true, false);
|
||||
dexFile.setReadable(true, false);
|
||||
} catch (IOException e) {
|
||||
//can not write file
|
||||
byte[] dexBytes = mDexMaker.generate();
|
||||
loader = new InMemoryDexClassLoader(ByteBuffer.wrap(dexBytes), mAppClassLoader);
|
||||
}
|
||||
return loadHookerClass(loader, className);
|
||||
}
|
||||
|
||||
private HookWrapper.HookEntity loadHookerClass(ClassLoader loader, String className) throws Exception {
|
||||
mHookClass = loader.loadClass(className);
|
||||
// Execute our newly-generated code in-process.
|
||||
mHookMethod = mHookClass.getMethod(METHOD_NAME_HOOK, mActualParameterTypes);
|
||||
mBackupMethod = mHookClass.getMethod(METHOD_NAME_BACKUP);
|
||||
setup(mHookClass);
|
||||
return new HookWrapper.HookEntity(mMember, mHookMethod, mBackupMethod, false);
|
||||
}
|
||||
|
||||
private void setup(Class mHookClass) {
|
||||
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_METHOD, mMember);
|
||||
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_BACKUP_METHOD, mBackupMethod);
|
||||
XposedHelpers.setStaticObjectField(mHookClass, FIELD_NAME_HOOK_INFO, mHookInfo);
|
||||
}
|
||||
|
||||
private String getClassName(Member originMethod) {
|
||||
return CLASS_NAME_PREFIX + "_" + getSha1Hex(originMethod.toString());
|
||||
}
|
||||
|
||||
public Method getHookMethod() {
|
||||
return mHookMethod;
|
||||
}
|
||||
|
||||
public Method getBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Method getCallBackupMethod() {
|
||||
return mBackupMethod;
|
||||
}
|
||||
|
||||
public Class getHookClass() {
|
||||
return mHookClass;
|
||||
}
|
||||
|
||||
private void generateFields() {
|
||||
mHookInfoFieldId = mHookerTypeId.getField(hookInfoTypeId, FIELD_NAME_HOOK_INFO);
|
||||
mMethodFieldId = mHookerTypeId.getField(memberTypeId, FIELD_NAME_METHOD);
|
||||
mBackupMethodFieldId = mHookerTypeId.getField(methodTypeId, FIELD_NAME_BACKUP_METHOD);
|
||||
mDexMaker.declare(mHookInfoFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mMethodFieldId, Modifier.STATIC, null);
|
||||
mDexMaker.declare(mBackupMethodFieldId, Modifier.STATIC, null);
|
||||
}
|
||||
|
||||
private void generateBackupMethod() {
|
||||
mBackupMethodId = mHookerTypeId.getMethod(TypeId.VOID, METHOD_NAME_BACKUP);
|
||||
Code code = mDexMaker.declare(mBackupMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
code.returnVoid();
|
||||
}
|
||||
|
||||
private void generateHookMethod() {
|
||||
mHookMethodId = mHookerTypeId.getMethod(mReturnTypeId, METHOD_NAME_HOOK, mParameterTypeIds);
|
||||
mSandHookBridgeMethodId = TypeId.get(HookStubManager.class).getMethod(TypeId.get(Object.class), "hookBridge", memberTypeId, methodTypeId, hookInfoTypeId, TypeId.get(Object.class), TypeId.get(Object[].class));
|
||||
|
||||
Code code = mDexMaker.declare(mHookMethodId, Modifier.PUBLIC | Modifier.STATIC);
|
||||
|
||||
Local<Member> originMethod = code.newLocal(memberTypeId);
|
||||
Local<Method> backupMethod = code.newLocal(methodTypeId);
|
||||
Local<XposedBridge.AdditionalHookInfo> hookInfo = code.newLocal(hookInfoTypeId);
|
||||
Local<Object> thisObject = code.newLocal(TypeId.OBJECT);
|
||||
Local<Object[]> args = code.newLocal(objArrayTypeId);
|
||||
Local<Integer> actualParamSize = code.newLocal(TypeId.INT);
|
||||
Local<Integer> argIndex = code.newLocal(TypeId.INT);
|
||||
Local<Object> resultObj = code.newLocal(TypeId.OBJECT);
|
||||
|
||||
Local[] allArgsLocals = createParameterLocals(code);
|
||||
Map<TypeId, Local> resultLocals = createResultLocals(code);
|
||||
|
||||
|
||||
code.loadConstant(args, null);
|
||||
code.loadConstant(argIndex, 0);
|
||||
code.sget(mMethodFieldId, originMethod);
|
||||
code.sget(mBackupMethodFieldId, backupMethod);
|
||||
code.sget(mHookInfoFieldId, hookInfo);
|
||||
|
||||
int paramsSize = mParameterTypeIds.length;
|
||||
int offset = 0;
|
||||
// thisObject
|
||||
if (mIsStatic) {
|
||||
// thisObject = null
|
||||
code.loadConstant(thisObject, null);
|
||||
} else {
|
||||
// thisObject = args[0]
|
||||
offset = 1;
|
||||
code.move(thisObject, allArgsLocals[0]);
|
||||
}
|
||||
|
||||
// actual args (exclude thisObject if this is not a static method)
|
||||
code.loadConstant(actualParamSize, paramsSize - offset);
|
||||
code.newArray(args, actualParamSize);
|
||||
for (int i = offset; i < paramsSize; i++) {
|
||||
Local parameter = allArgsLocals[i];
|
||||
// save parameter to resultObj as Object
|
||||
autoBoxIfNecessary(code, resultObj, parameter);
|
||||
code.loadConstant(argIndex, i - offset);
|
||||
// save Object to args
|
||||
code.aput(args, argIndex, resultObj);
|
||||
}
|
||||
|
||||
if (mReturnTypeId.equals(TypeId.VOID)) {
|
||||
code.invokeStatic(mSandHookBridgeMethodId, null, originMethod, backupMethod, hookInfo, thisObject, args);
|
||||
code.returnVoid();
|
||||
} else {
|
||||
code.invokeStatic(mSandHookBridgeMethodId, resultObj, originMethod, backupMethod, hookInfo, thisObject, args);
|
||||
TypeId objTypeId = getObjTypeIdIfPrimitive(mReturnTypeId);
|
||||
Local matchObjLocal = resultLocals.get(objTypeId);
|
||||
code.cast(matchObjLocal, resultObj);
|
||||
// have to use matching typed Object(Integer, Double ...) to do unboxing
|
||||
Local toReturn = resultLocals.get(mReturnTypeId);
|
||||
autoUnboxIfNecessary(code, toReturn, matchObjLocal, resultLocals, true);
|
||||
code.returnValue(toReturn);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Local[] createParameterLocals(Code code) {
|
||||
Local[] paramLocals = new Local[mParameterTypeIds.length];
|
||||
for (int i = 0; i < mParameterTypeIds.length; i++) {
|
||||
paramLocals[i] = code.getParameter(i, mParameterTypeIds[i]);
|
||||
}
|
||||
return paramLocals;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.methodgen;
|
||||
|
||||
import android.os.Build;
|
||||
import android.os.Process;
|
||||
import android.os.Trace;
|
||||
|
||||
import io.github.lsposed.lspd.nativebridge.Yahfa;
|
||||
import io.github.lsposed.lspd.util.ClassLoaderUtils;
|
||||
import io.github.lsposed.lspd.util.FileUtils;
|
||||
import com.swift.sandhook.SandHook;
|
||||
import com.swift.sandhook.SandHookConfig;
|
||||
import com.swift.sandhook.blacklist.HookBlackList;
|
||||
import com.swift.sandhook.wrapper.HookWrapper;
|
||||
import com.swift.sandhook.xposedcompat.XposedCompat;
|
||||
import com.swift.sandhook.xposedcompat.hookstub.HookMethodEntity;
|
||||
import com.swift.sandhook.xposedcompat.hookstub.HookStubManager;
|
||||
import com.swift.sandhook.xposedcompat.utils.DexLog;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
|
||||
public final class SandHookXposedBridge {
|
||||
|
||||
private static final Map<Member, Method> hookedInfo = new ConcurrentHashMap<>();
|
||||
private static HookMaker defaultHookMaker = XposedCompat.useNewCallBackup ? new HookerDexMakerNew() : new HookerDexMaker();
|
||||
|
||||
public static Map<Member, HookMethodEntity> entityMap = new ConcurrentHashMap<>();
|
||||
|
||||
public static void onForkPost() {
|
||||
XposedCompat.onForkProcess();
|
||||
}
|
||||
|
||||
public static boolean hooked(Member member) {
|
||||
return hookedInfo.containsKey(member) || entityMap.containsKey(member);
|
||||
}
|
||||
|
||||
public static synchronized void hookMethod(Member hookMethod, XposedBridge.AdditionalHookInfo additionalHookInfo) {
|
||||
|
||||
if (!checkMember(hookMethod)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (hookedInfo.containsKey(hookMethod) || entityMap.containsKey(hookMethod)) {
|
||||
DexLog.w("already hook method:" + hookMethod.toString());
|
||||
return;
|
||||
}
|
||||
|
||||
Yahfa.recordHooked(hookMethod); // in case static method got reset.
|
||||
|
||||
try {
|
||||
Trace.beginSection("SandXposed");
|
||||
long timeStart = System.currentTimeMillis();
|
||||
HookMethodEntity stub = null;
|
||||
if (XposedCompat.useInternalStub && !HookBlackList.canNotHookByStub(hookMethod) && !HookBlackList.canNotHookByBridge(hookMethod)) {
|
||||
stub = HookStubManager.getHookMethodEntity(hookMethod, additionalHookInfo);
|
||||
}
|
||||
if (stub != null) {
|
||||
SandHook.hook(new HookWrapper.HookEntity(hookMethod, stub.hook, stub.backup, false));
|
||||
entityMap.put(hookMethod, stub);
|
||||
} else {
|
||||
HookMaker hookMaker;
|
||||
if (HookBlackList.canNotHookByBridge(hookMethod)) {
|
||||
hookMaker = new HookerDexMaker();
|
||||
} else {
|
||||
hookMaker = defaultHookMaker;
|
||||
}
|
||||
hookMaker.start(hookMethod, additionalHookInfo,
|
||||
ClassLoaderUtils.createProxyClassLoader(
|
||||
hookMethod.getDeclaringClass().getClassLoader()));
|
||||
hookedInfo.put(hookMethod, hookMaker.getCallBackupMethod());
|
||||
}
|
||||
DexLog.d("hook method <" + hookMethod.toString() + "> cost " + (System.currentTimeMillis() - timeStart) + " ms, by " + (stub != null ? "internal stub" : "dex maker"));
|
||||
Trace.endSection();
|
||||
} catch (Exception e) {
|
||||
DexLog.e("error occur when hook method <" + hookMethod.toString() + ">", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean checkMember(Member member) {
|
||||
|
||||
if (member instanceof Method) {
|
||||
return true;
|
||||
} else if (member instanceof Constructor<?>) {
|
||||
return true;
|
||||
} else if (member.getDeclaringClass().isInterface()) {
|
||||
DexLog.e("Cannot hook interfaces: " + member.toString());
|
||||
return false;
|
||||
} else if (Modifier.isAbstract(member.getModifiers())) {
|
||||
DexLog.e("Cannot hook abstract methods: " + member.toString());
|
||||
return false;
|
||||
} else {
|
||||
DexLog.e("Only methods and constructors can be hooked: " + member.toString());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public static Object invokeOriginalMethod(Member method, Object thisObject, Object[] args)
|
||||
throws Throwable {
|
||||
return SandHook.callOriginMethod(method, thisObject, args);
|
||||
}
|
||||
|
||||
public static void init() {
|
||||
SandHookConfig.libSandHookPath = "";
|
||||
SandHookConfig.libLoader = new SandHookConfig.LibLoader() {
|
||||
@Override
|
||||
public void loadLib() {
|
||||
//do it in loadDexAndInit
|
||||
}
|
||||
};
|
||||
SandHookConfig.DEBUG = true;
|
||||
SandHookConfig.compiler = false;
|
||||
//already impl in lspd
|
||||
SandHookConfig.delayHook = false;
|
||||
//use when call origin
|
||||
HookBlackList.methodBlackList.add("java.lang.reflect.isStatic");
|
||||
HookBlackList.methodBlackList.add("java.lang.reflect.Method.getModifiers");
|
||||
if (Build.VERSION.SDK_INT >= 29) {
|
||||
//unknown bug, disable tmp
|
||||
//TODO Fix
|
||||
XposedCompat.useInternalStub = false;
|
||||
}
|
||||
//in zygote disable compile
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
|
@ -1,34 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
public class ApplicationUtils {
|
||||
|
||||
private static Class classActivityThread;
|
||||
private static Method currentApplicationMethod;
|
||||
|
||||
static Application application;
|
||||
|
||||
public static Application currentApplication() {
|
||||
if (application != null)
|
||||
return application;
|
||||
if (currentApplicationMethod == null) {
|
||||
try {
|
||||
classActivityThread = Class.forName("android.app.ActivityThread");
|
||||
currentApplicationMethod = classActivityThread.getDeclaredMethod("currentApplication");
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
if (currentApplicationMethod == null)
|
||||
return null;
|
||||
try {
|
||||
application = (Application) currentApplicationMethod.invoke(null);
|
||||
} catch (Exception e) {
|
||||
}
|
||||
return application;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
|
||||
import android.util.Log;
|
||||
|
||||
import io.github.lsposed.lspd.BuildConfig;
|
||||
|
||||
import java.lang.reflect.Member;
|
||||
|
||||
|
||||
public class DexLog {
|
||||
|
||||
public static final String TAG = "SandXposed";
|
||||
|
||||
public static volatile boolean DEBUG = BuildConfig.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) {
|
||||
if (DEBUG) {
|
||||
return Log.d(TAG, s);
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
public static void printMethodHookIn(Member member) {
|
||||
if (DEBUG && member != null) {
|
||||
Log.d("SandHook", "method <" + member.toString() + "> hook in");
|
||||
}
|
||||
}
|
||||
|
||||
public static void printCallOriginError(Member member) {
|
||||
Log.e("SandHook", "method <" + member.toString() + "> call origin error!");
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -1,255 +0,0 @@
|
|||
package com.swift.sandhook.xposedcompat.utils;
|
||||
import io.github.lsposed.lspd.util.Utils;
|
||||
|
||||
import java.io.File;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import external.com.android.dx.Code;
|
||||
import external.com.android.dx.Local;
|
||||
import external.com.android.dx.TypeId;
|
||||
|
||||
import static io.github.lsposed.lspd.config.LSPApplicationServiceClient.serviceClient;
|
||||
|
||||
public class DexMakerUtils {
|
||||
|
||||
public static boolean canCache = true;
|
||||
|
||||
static {
|
||||
File cacheDir = new File(serviceClient.getCachePath(""));
|
||||
if(!cacheDir.canRead() || !cacheDir.canWrite()) {
|
||||
Utils.logW("Cache disabled");
|
||||
canCache = false;
|
||||
}
|
||||
}
|
||||
|
||||
private static volatile Method addInstMethod, specMethod;
|
||||
|
||||
public static void autoBoxIfNecessary(Code code, Local<Object> target, Local source) {
|
||||
String boxMethod = "valueOf";
|
||||
TypeId<?> boxTypeId;
|
||||
TypeId typeId = source.getType();
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
boxTypeId = TypeId.get(Boolean.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BOOLEAN), target, source);
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
boxTypeId = TypeId.get(Byte.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.BYTE), target, source);
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
boxTypeId = TypeId.get(Character.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.CHAR), target, source);
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
boxTypeId = TypeId.get(Double.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.DOUBLE), target, source);
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
boxTypeId = TypeId.get(Float.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.FLOAT), target, source);
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
boxTypeId = TypeId.get(Integer.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.INT), target, source);
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
boxTypeId = TypeId.get(Long.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.LONG), target, source);
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
boxTypeId = TypeId.get(Short.class);
|
||||
code.invokeStatic(boxTypeId.getMethod(boxTypeId, boxMethod, TypeId.SHORT), target, source);
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
code.loadConstant(target, null);
|
||||
} else {
|
||||
code.move(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
public static void autoUnboxIfNecessary(Code code, Local target, Local source,
|
||||
Map<TypeId, Local> tmpLocals, boolean castObj) {
|
||||
String unboxMethod;
|
||||
TypeId typeId = target.getType();
|
||||
TypeId<?> boxTypeId;
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
unboxMethod = "booleanValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Boolean;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.BOOLEAN, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
unboxMethod = "byteValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Byte;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.BYTE, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
unboxMethod = "charValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Character;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.CHAR, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
unboxMethod = "doubleValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Double;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.DOUBLE, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
unboxMethod = "floatValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Float;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.FLOAT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
unboxMethod = "intValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Integer;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.INT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
unboxMethod = "longValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Long;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.LONG, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
unboxMethod = "shortValue";
|
||||
boxTypeId = TypeId.get("Ljava/lang/Short;");
|
||||
Local boxTypedLocal = tmpLocals.get(boxTypeId);
|
||||
code.cast(boxTypedLocal, source);
|
||||
code.invokeVirtual(boxTypeId.getMethod(TypeId.SHORT, unboxMethod), target, boxTypedLocal);
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
code.loadConstant(target, null);
|
||||
} else if (castObj) {
|
||||
code.cast(target, source);
|
||||
} else {
|
||||
code.move(target, source);
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<TypeId, Local> createResultLocals(Code code) {
|
||||
HashMap<TypeId, Local> resultMap = new HashMap<>();
|
||||
Local<Boolean> booleanLocal = code.newLocal(TypeId.BOOLEAN);
|
||||
Local<Byte> byteLocal = code.newLocal(TypeId.BYTE);
|
||||
Local<Character> charLocal = code.newLocal(TypeId.CHAR);
|
||||
Local<Double> doubleLocal = code.newLocal(TypeId.DOUBLE);
|
||||
Local<Float> floatLocal = code.newLocal(TypeId.FLOAT);
|
||||
Local<Integer> intLocal = code.newLocal(TypeId.INT);
|
||||
Local<Long> longLocal = code.newLocal(TypeId.LONG);
|
||||
Local<Short> shortLocal = code.newLocal(TypeId.SHORT);
|
||||
Local<Void> voidLocal = code.newLocal(TypeId.VOID);
|
||||
Local<Object> objectLocal = code.newLocal(TypeId.OBJECT);
|
||||
|
||||
Local<Object> booleanObjLocal = code.newLocal(TypeId.get("Ljava/lang/Boolean;"));
|
||||
Local<Object> byteObjLocal = code.newLocal(TypeId.get("Ljava/lang/Byte;"));
|
||||
Local<Object> charObjLocal = code.newLocal(TypeId.get("Ljava/lang/Character;"));
|
||||
Local<Object> doubleObjLocal = code.newLocal(TypeId.get("Ljava/lang/Double;"));
|
||||
Local<Object> floatObjLocal = code.newLocal(TypeId.get("Ljava/lang/Float;"));
|
||||
Local<Object> intObjLocal = code.newLocal(TypeId.get("Ljava/lang/Integer;"));
|
||||
Local<Object> longObjLocal = code.newLocal(TypeId.get("Ljava/lang/Long;"));
|
||||
Local<Object> shortObjLocal = code.newLocal(TypeId.get("Ljava/lang/Short;"));
|
||||
Local<Object> voidObjLocal = code.newLocal(TypeId.get("Ljava/lang/Void;"));
|
||||
|
||||
// backup need initialized locals
|
||||
code.loadConstant(booleanLocal, false);
|
||||
code.loadConstant(byteLocal, (byte) 0);
|
||||
code.loadConstant(charLocal, '\0');
|
||||
code.loadConstant(doubleLocal,0.0);
|
||||
code.loadConstant(floatLocal,0.0f);
|
||||
code.loadConstant(intLocal, 0);
|
||||
code.loadConstant(longLocal, 0L);
|
||||
code.loadConstant(shortLocal, (short) 0);
|
||||
code.loadConstant(voidLocal, null);
|
||||
code.loadConstant(objectLocal, null);
|
||||
// all to null
|
||||
code.loadConstant(booleanObjLocal, null);
|
||||
code.loadConstant(byteObjLocal, null);
|
||||
code.loadConstant(charObjLocal, null);
|
||||
code.loadConstant(doubleObjLocal, null);
|
||||
code.loadConstant(floatObjLocal, null);
|
||||
code.loadConstant(intObjLocal, null);
|
||||
code.loadConstant(longObjLocal, null);
|
||||
code.loadConstant(shortObjLocal, null);
|
||||
code.loadConstant(voidObjLocal, null);
|
||||
// package all
|
||||
resultMap.put(TypeId.BOOLEAN, booleanLocal);
|
||||
resultMap.put(TypeId.BYTE, byteLocal);
|
||||
resultMap.put(TypeId.CHAR, charLocal);
|
||||
resultMap.put(TypeId.DOUBLE, doubleLocal);
|
||||
resultMap.put(TypeId.FLOAT, floatLocal);
|
||||
resultMap.put(TypeId.INT, intLocal);
|
||||
resultMap.put(TypeId.LONG, longLocal);
|
||||
resultMap.put(TypeId.SHORT, shortLocal);
|
||||
resultMap.put(TypeId.VOID, voidLocal);
|
||||
resultMap.put(TypeId.OBJECT, objectLocal);
|
||||
|
||||
resultMap.put(TypeId.get("Ljava/lang/Boolean;"), booleanObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Byte;"), byteObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Character;"), charObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Double;"), doubleObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Float;"), floatObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Integer;"), intObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Long;"), longObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Short;"), shortObjLocal);
|
||||
resultMap.put(TypeId.get("Ljava/lang/Void;"), voidObjLocal);
|
||||
|
||||
return resultMap;
|
||||
}
|
||||
|
||||
public static TypeId getObjTypeIdIfPrimitive(TypeId typeId) {
|
||||
if (typeId.equals(TypeId.BOOLEAN)) {
|
||||
return TypeId.get("Ljava/lang/Boolean;");
|
||||
} else if (typeId.equals(TypeId.BYTE)) {
|
||||
return TypeId.get("Ljava/lang/Byte;");
|
||||
} else if (typeId.equals(TypeId.CHAR)) {
|
||||
return TypeId.get("Ljava/lang/Character;");
|
||||
} else if (typeId.equals(TypeId.DOUBLE)) {
|
||||
return TypeId.get("Ljava/lang/Double;");
|
||||
} else if (typeId.equals(TypeId.FLOAT)) {
|
||||
return TypeId.get("Ljava/lang/Float;");
|
||||
} else if (typeId.equals(TypeId.INT)) {
|
||||
return TypeId.get("Ljava/lang/Integer;");
|
||||
} else if (typeId.equals(TypeId.LONG)) {
|
||||
return TypeId.get("Ljava/lang/Long;");
|
||||
} else if (typeId.equals(TypeId.SHORT)) {
|
||||
return TypeId.get("Ljava/lang/Short;");
|
||||
} else if (typeId.equals(TypeId.VOID)) {
|
||||
return TypeId.get("Ljava/lang/Void;");
|
||||
} else {
|
||||
return typeId;
|
||||
}
|
||||
}
|
||||
|
||||
public static void returnRightValue(Code code, Class<?> returnType, Map<Class, Local> resultLocals) {
|
||||
String unboxMethod;
|
||||
TypeId<?> boxTypeId;
|
||||
code.returnValue(resultLocals.get(returnType));
|
||||
}
|
||||
|
||||
public static String MD5(String source) {
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("MD5");
|
||||
messageDigest.update(source.getBytes());
|
||||
return new BigInteger(1, messageDigest.digest()).toString(32);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
return source;
|
||||
}
|
||||
|
||||
public static String getSha1Hex(String text) {
|
||||
final MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-1");
|
||||
byte[] result = digest.digest(text.getBytes(StandardCharsets.UTF_8));
|
||||
StringBuilder sb = new StringBuilder();
|
||||
for (byte b : result) {
|
||||
sb.append(String.format("%02x", b));
|
||||
}
|
||||
return sb.toString();
|
||||
} catch (Exception e) {
|
||||
DexLog.e("error hashing target method: " + text, e);
|
||||
}
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
|
@ -20,25 +20,26 @@
|
|||
|
||||
package de.robv.android.xposed;
|
||||
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||
import io.github.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import static io.github.lsposed.lspd.nativebridge.PendingHooks.recordPendingMethodNative;
|
||||
|
||||
public final class PendingHooks {
|
||||
|
||||
// GuardedBy("PendingHooks.class")
|
||||
private static final ConcurrentHashMap<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>
|
||||
private static final ConcurrentHashMap<Class<?>, ConcurrentHashMap<Executable, XposedBridge.AdditionalHookInfo>>
|
||||
sPendingHooks = new ConcurrentHashMap<>();
|
||||
|
||||
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
||||
if (sPendingHooks.containsKey(clazz)) {
|
||||
for (Map.Entry<Member, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
||||
LSPdConfigGlobal.getHookProvider().hookMethod(hook.getKey(), hook.getValue());
|
||||
for (Map.Entry<Executable, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
||||
YahfaHooker.hookMethod(hook.getKey(), hook.getValue());
|
||||
}
|
||||
sPendingHooks.remove(clazz);
|
||||
}
|
||||
|
|
@ -46,7 +47,7 @@ public final class PendingHooks {
|
|||
|
||||
public synchronized static void recordPendingMethod(Method hookMethod,
|
||||
XposedBridge.AdditionalHookInfo additionalInfo) {
|
||||
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
|
||||
ConcurrentHashMap<Executable, XposedBridge.AdditionalHookInfo> pending =
|
||||
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(), aClass -> new ConcurrentHashMap<>());
|
||||
|
||||
pending.put(hookMethod, additionalInfo);
|
||||
|
|
|
|||
|
|
@ -25,31 +25,26 @@ import android.content.res.TypedArray;
|
|||
import android.util.Log;
|
||||
|
||||
import io.github.lsposed.lspd.BuildConfig;
|
||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||
|
||||
import java.lang.reflect.AccessibleObject;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Executable;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Member;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import dalvik.system.InMemoryDexClassLoader;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
import de.robv.android.xposed.callbacks.XC_InitPackageResources;
|
||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import external.com.android.dx.DexMaker;
|
||||
import external.com.android.dx.TypeId;
|
||||
import io.github.lsposed.lspd.nativebridge.ModuleLogger;
|
||||
import io.github.lsposed.lspd.nativebridge.ResourcesHook;
|
||||
import io.github.lsposed.lspd.yahfa.dexmaker.DynamicBridge;
|
||||
import io.github.lsposed.lspd.yahfa.hooker.YahfaHooker;
|
||||
|
||||
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
||||
|
||||
|
|
@ -91,7 +86,6 @@ public final class XposedBridge {
|
|||
|
||||
public static volatile ClassLoader dummyClassLoader = null;
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
public static void initXResources() {
|
||||
if (dummyClassLoader != null) {
|
||||
return;
|
||||
|
|
@ -108,22 +102,10 @@ public final class XposedBridge {
|
|||
} catch (Resources.NotFoundException nfe) {
|
||||
XposedBridge.log(nfe);
|
||||
}
|
||||
XposedBridge.removeFinalFlagNative(resClass);
|
||||
XposedBridge.removeFinalFlagNative(taClass);
|
||||
ResourcesHook.removeFinalFlagNative(resClass);
|
||||
ResourcesHook.removeFinalFlagNative(taClass);
|
||||
ClassLoader myCL = XposedBridge.class.getClassLoader();
|
||||
dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass);
|
||||
if (dummyClassLoader == null) {
|
||||
XposedBridge.log("Dexbuilder fails, fallback to dexmaker");
|
||||
DexMaker dexMaker = new DexMaker();
|
||||
dexMaker.declare(TypeId.get("Lxposed/dummy/XResourcesSuperClass;"),
|
||||
"XResourcesSuperClass.java",
|
||||
Modifier.PUBLIC, TypeId.get(resClass));
|
||||
dexMaker.declare(TypeId.get("Lxposed/dummy/XTypedArraySuperClass;"),
|
||||
"XTypedArraySuperClass.java",
|
||||
Modifier.PUBLIC, TypeId.get(taClass));
|
||||
dummyClassLoader = new InMemoryDexClassLoader(
|
||||
ByteBuffer.wrap(dexMaker.generate()), myCL.getParent());
|
||||
}
|
||||
dummyClassLoader.loadClass("xposed.dummy.XResourcesSuperClass");
|
||||
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
|
||||
setObjectField(myCL, "parent", dummyClassLoader);
|
||||
|
|
@ -183,7 +165,7 @@ public final class XposedBridge {
|
|||
* @see #hookAllConstructors
|
||||
*/
|
||||
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||
if (!(hookMethod instanceof Method) && !(hookMethod instanceof Constructor<?>)) {
|
||||
if (!(hookMethod instanceof Executable)) {
|
||||
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
|
||||
} else if (hookMethod.getDeclaringClass().isInterface()) {
|
||||
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
|
||||
|
|
@ -191,6 +173,8 @@ public final class XposedBridge {
|
|||
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
|
||||
}
|
||||
|
||||
Executable targetMethod = (Executable) hookMethod;
|
||||
|
||||
if (callback == null) {
|
||||
throw new IllegalArgumentException("callback should not be null!");
|
||||
}
|
||||
|
|
@ -198,10 +182,10 @@ public final class XposedBridge {
|
|||
boolean newMethod = false;
|
||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
synchronized (sHookedMethodCallbacks) {
|
||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
||||
callbacks = sHookedMethodCallbacks.get(targetMethod);
|
||||
if (callbacks == null) {
|
||||
callbacks = new CopyOnWriteSortedSet<>();
|
||||
sHookedMethodCallbacks.put(hookMethod, callbacks);
|
||||
sHookedMethodCallbacks.put(targetMethod, callbacks);
|
||||
newMethod = true;
|
||||
}
|
||||
}
|
||||
|
|
@ -209,9 +193,8 @@ public final class XposedBridge {
|
|||
|
||||
if (newMethod) {
|
||||
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
|
||||
Member reflectMethod = LSPdConfigGlobal.getHookProvider().findMethodNative(hookMethod);
|
||||
if (reflectMethod != null) {
|
||||
LSPdConfigGlobal.getHookProvider().hookMethod(reflectMethod, (AdditionalHookInfo) additionalInfo);
|
||||
if (!YahfaHooker.shouldDelayHook(targetMethod)) {
|
||||
DynamicBridge.hookMethod(targetMethod, (AdditionalHookInfo) additionalInfo);
|
||||
} else {
|
||||
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
|
||||
}
|
||||
|
|
@ -231,7 +214,6 @@ public final class XposedBridge {
|
|||
*/
|
||||
@Deprecated
|
||||
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||
LSPdConfigGlobal.getHookProvider().unhookMethod(hookMethod);
|
||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||
synchronized (sHookedMethodCallbacks) {
|
||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
||||
|
|
@ -348,16 +330,11 @@ public final class XposedBridge {
|
|||
args = EMPTY_ARRAY;
|
||||
}
|
||||
|
||||
if (!(method instanceof Method) && !(method instanceof Constructor)) {
|
||||
if (!(method instanceof Executable)) {
|
||||
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
||||
}
|
||||
|
||||
long methodId = LSPdConfigGlobal.getHookProvider().getMethodId(method);
|
||||
return LSPdConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
|
||||
}
|
||||
|
||||
private static void removeFinalFlagNative(Class clazz) {
|
||||
LSPdConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
|
||||
return YahfaHooker.invokeOriginalMethod((Executable) method, thisObject, args);
|
||||
}
|
||||
|
||||
/** @hide */
|
||||
|
|
|
|||
|
|
@ -23,12 +23,9 @@ package de.robv.android.xposed;
|
|||
import android.content.res.AssetManager;
|
||||
import android.content.res.Resources;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.Closeable;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
|
@ -41,15 +38,11 @@ import java.math.BigInteger;
|
|||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.WeakHashMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
import dalvik.system.DexFile;
|
||||
import external.org.apache.commons.lang3.ClassUtils;
|
||||
import external.org.apache.commons.lang3.reflect.MemberUtils;
|
||||
|
||||
|
|
|
|||
|
|
@ -54,10 +54,8 @@ import de.robv.android.xposed.callbacks.XC_InitZygote;
|
|||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
||||
import de.robv.android.xposed.callbacks.XCallback;
|
||||
import hidden.HiddenApiBridge;
|
||||
import io.github.lsposed.lspd.annotation.ApiSensitive;
|
||||
import io.github.lsposed.lspd.annotation.Level;
|
||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||
import io.github.lsposed.lspd.nativebridge.NativeAPI;
|
||||
import io.github.lsposed.lspd.nativebridge.ResourcesHook;
|
||||
|
||||
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
||||
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
||||
|
|
@ -106,13 +104,12 @@ public final class XposedInit {
|
|||
hookResources();
|
||||
}
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
private static void hookResources() throws Throwable {
|
||||
if (!serviceClient.isResourcesHookEnabled() || disableResources) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!LSPdConfigGlobal.getHookProvider().initXResourcesNative()) {
|
||||
if (!ResourcesHook.initXResourcesNative()) {
|
||||
Log.e(TAG, "Cannot hook resources");
|
||||
disableResources = true;
|
||||
return;
|
||||
|
|
@ -209,7 +206,6 @@ public final class XposedInit {
|
|||
XResources.init(latestResKey);
|
||||
}
|
||||
|
||||
@ApiSensitive(Level.MIDDLE)
|
||||
private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) {
|
||||
Object result = param.getResult();
|
||||
if (result == null || result instanceof XResources ||
|
||||
|
|
|
|||
|
|
@ -22,13 +22,12 @@ package de.robv.android.xposed.callbacks;
|
|||
|
||||
import android.os.Bundle;
|
||||
|
||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
import de.robv.android.xposed.IModuleContext;
|
||||
import de.robv.android.xposed.XposedBridge;
|
||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||
import io.github.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
||||
|
||||
/**
|
||||
* Base class for Xposed callbacks.
|
||||
|
|
@ -126,7 +125,7 @@ public abstract class XCallback implements Comparable<XCallback>, IModuleContext
|
|||
// deopt methods in system apps or priv-apps, this would be not necessary
|
||||
// only if we found out how to recompile their apks
|
||||
XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param;
|
||||
LSPdConfigGlobal.getHookProvider().deoptMethods(lpp.packageName, lpp.classLoader);
|
||||
PrebuiltMethodsDeopter.deoptMethods(lpp.packageName, lpp.classLoader);
|
||||
}
|
||||
|
||||
if (param.callbacks == null)
|
||||
|
|
|
|||
|
|
@ -1,65 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import static external.com.android.dex.EncodedValueReader.ENCODED_ANNOTATION;
|
||||
|
||||
/**
|
||||
* An annotation.
|
||||
*/
|
||||
public final class Annotation implements Comparable<Annotation> {
|
||||
private final Dex dex;
|
||||
private final byte visibility;
|
||||
private final EncodedValue encodedAnnotation;
|
||||
|
||||
public Annotation(Dex dex, byte visibility, EncodedValue encodedAnnotation) {
|
||||
this.dex = dex;
|
||||
this.visibility = visibility;
|
||||
this.encodedAnnotation = encodedAnnotation;
|
||||
}
|
||||
|
||||
public byte getVisibility() {
|
||||
return visibility;
|
||||
}
|
||||
|
||||
public EncodedValueReader getReader() {
|
||||
return new EncodedValueReader(encodedAnnotation, ENCODED_ANNOTATION);
|
||||
}
|
||||
|
||||
public int getTypeIndex() {
|
||||
EncodedValueReader reader = getReader();
|
||||
reader.readAnnotation();
|
||||
return reader.getAnnotationType();
|
||||
}
|
||||
|
||||
public void writeTo(Dex.Section out) {
|
||||
out.writeByte(visibility);
|
||||
encodedAnnotation.writeTo(out);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Annotation other) {
|
||||
return encodedAnnotation.compareTo(other.encodedAnnotation);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return dex == null
|
||||
? visibility + " " + getTypeIndex()
|
||||
: visibility + " " + dex.typeNames().get(getTypeIndex());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,55 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.Dex.Section;
|
||||
import external.com.android.dex.util.Unsigned;
|
||||
|
||||
/**
|
||||
* A call_site_id_item: https://source.android.com/devices/tech/dalvik/dex-format#call-site-id-item
|
||||
*/
|
||||
public class CallSiteId implements Comparable<CallSiteId> {
|
||||
|
||||
private final Dex dex;
|
||||
private final int offset;
|
||||
|
||||
public CallSiteId(Dex dex, int offset) {
|
||||
this.dex = dex;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(CallSiteId o) {
|
||||
return Unsigned.compare(offset, o.offset);
|
||||
}
|
||||
|
||||
public int getCallSiteOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public void writeTo(Section out) {
|
||||
out.writeInt(offset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (dex == null) {
|
||||
return String.valueOf(offset);
|
||||
}
|
||||
return dex.protoIds().get(offset).toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,104 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
public final class ClassData {
|
||||
private final Field[] staticFields;
|
||||
private final Field[] instanceFields;
|
||||
private final Method[] directMethods;
|
||||
private final Method[] virtualMethods;
|
||||
|
||||
public ClassData(Field[] staticFields, Field[] instanceFields,
|
||||
Method[] directMethods, Method[] virtualMethods) {
|
||||
this.staticFields = staticFields;
|
||||
this.instanceFields = instanceFields;
|
||||
this.directMethods = directMethods;
|
||||
this.virtualMethods = virtualMethods;
|
||||
}
|
||||
|
||||
public Field[] getStaticFields() {
|
||||
return staticFields;
|
||||
}
|
||||
|
||||
public Field[] getInstanceFields() {
|
||||
return instanceFields;
|
||||
}
|
||||
|
||||
public Method[] getDirectMethods() {
|
||||
return directMethods;
|
||||
}
|
||||
|
||||
public Method[] getVirtualMethods() {
|
||||
return virtualMethods;
|
||||
}
|
||||
|
||||
public Field[] allFields() {
|
||||
Field[] result = new Field[staticFields.length + instanceFields.length];
|
||||
System.arraycopy(staticFields, 0, result, 0, staticFields.length);
|
||||
System.arraycopy(instanceFields, 0, result, staticFields.length, instanceFields.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Method[] allMethods() {
|
||||
Method[] result = new Method[directMethods.length + virtualMethods.length];
|
||||
System.arraycopy(directMethods, 0, result, 0, directMethods.length);
|
||||
System.arraycopy(virtualMethods, 0, result, directMethods.length, virtualMethods.length);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static class Field {
|
||||
private final int fieldIndex;
|
||||
private final int accessFlags;
|
||||
|
||||
public Field(int fieldIndex, int accessFlags) {
|
||||
this.fieldIndex = fieldIndex;
|
||||
this.accessFlags = accessFlags;
|
||||
}
|
||||
|
||||
public int getFieldIndex() {
|
||||
return fieldIndex;
|
||||
}
|
||||
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
}
|
||||
|
||||
public static class Method {
|
||||
private final int methodIndex;
|
||||
private final int accessFlags;
|
||||
private final int codeOffset;
|
||||
|
||||
public Method(int methodIndex, int accessFlags, int codeOffset) {
|
||||
this.methodIndex = methodIndex;
|
||||
this.accessFlags = accessFlags;
|
||||
this.codeOffset = codeOffset;
|
||||
}
|
||||
|
||||
public int getMethodIndex() {
|
||||
return methodIndex;
|
||||
}
|
||||
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
public int getCodeOffset() {
|
||||
return codeOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,103 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
/**
|
||||
* A type definition.
|
||||
*/
|
||||
public final class ClassDef {
|
||||
public static final int NO_INDEX = -1;
|
||||
private final Dex buffer;
|
||||
private final int offset;
|
||||
private final int typeIndex;
|
||||
private final int accessFlags;
|
||||
private final int supertypeIndex;
|
||||
private final int interfacesOffset;
|
||||
private final int sourceFileIndex;
|
||||
private final int annotationsOffset;
|
||||
private final int classDataOffset;
|
||||
private final int staticValuesOffset;
|
||||
|
||||
public ClassDef(Dex buffer, int offset, int typeIndex, int accessFlags,
|
||||
int supertypeIndex, int interfacesOffset, int sourceFileIndex,
|
||||
int annotationsOffset, int classDataOffset, int staticValuesOffset) {
|
||||
this.buffer = buffer;
|
||||
this.offset = offset;
|
||||
this.typeIndex = typeIndex;
|
||||
this.accessFlags = accessFlags;
|
||||
this.supertypeIndex = supertypeIndex;
|
||||
this.interfacesOffset = interfacesOffset;
|
||||
this.sourceFileIndex = sourceFileIndex;
|
||||
this.annotationsOffset = annotationsOffset;
|
||||
this.classDataOffset = classDataOffset;
|
||||
this.staticValuesOffset = staticValuesOffset;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
|
||||
public int getTypeIndex() {
|
||||
return typeIndex;
|
||||
}
|
||||
|
||||
public int getSupertypeIndex() {
|
||||
return supertypeIndex;
|
||||
}
|
||||
|
||||
public int getInterfacesOffset() {
|
||||
return interfacesOffset;
|
||||
}
|
||||
|
||||
public short[] getInterfaces() {
|
||||
return buffer.readTypeList(interfacesOffset).getTypes();
|
||||
}
|
||||
|
||||
public int getAccessFlags() {
|
||||
return accessFlags;
|
||||
}
|
||||
|
||||
public int getSourceFileIndex() {
|
||||
return sourceFileIndex;
|
||||
}
|
||||
|
||||
public int getAnnotationsOffset() {
|
||||
return annotationsOffset;
|
||||
}
|
||||
|
||||
public int getClassDataOffset() {
|
||||
return classDataOffset;
|
||||
}
|
||||
|
||||
public int getStaticValuesOffset() {
|
||||
return staticValuesOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (buffer == null) {
|
||||
return typeIndex + " " + supertypeIndex;
|
||||
}
|
||||
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append(buffer.typeNames().get(typeIndex));
|
||||
if (supertypeIndex != NO_INDEX) {
|
||||
result.append(" extends ").append(buffer.typeNames().get(supertypeIndex));
|
||||
}
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
public final class Code {
|
||||
private final int registersSize;
|
||||
private final int insSize;
|
||||
private final int outsSize;
|
||||
private final int debugInfoOffset;
|
||||
private final short[] instructions;
|
||||
private final Try[] tries;
|
||||
private final CatchHandler[] catchHandlers;
|
||||
|
||||
public Code(int registersSize, int insSize, int outsSize, int debugInfoOffset,
|
||||
short[] instructions, Try[] tries, CatchHandler[] catchHandlers) {
|
||||
this.registersSize = registersSize;
|
||||
this.insSize = insSize;
|
||||
this.outsSize = outsSize;
|
||||
this.debugInfoOffset = debugInfoOffset;
|
||||
this.instructions = instructions;
|
||||
this.tries = tries;
|
||||
this.catchHandlers = catchHandlers;
|
||||
}
|
||||
|
||||
public int getRegistersSize() {
|
||||
return registersSize;
|
||||
}
|
||||
|
||||
public int getInsSize() {
|
||||
return insSize;
|
||||
}
|
||||
|
||||
public int getOutsSize() {
|
||||
return outsSize;
|
||||
}
|
||||
|
||||
public int getDebugInfoOffset() {
|
||||
return debugInfoOffset;
|
||||
}
|
||||
|
||||
public short[] getInstructions() {
|
||||
return instructions;
|
||||
}
|
||||
|
||||
public Try[] getTries() {
|
||||
return tries;
|
||||
}
|
||||
|
||||
public CatchHandler[] getCatchHandlers() {
|
||||
return catchHandlers;
|
||||
}
|
||||
|
||||
public static class Try {
|
||||
final int startAddress;
|
||||
final int instructionCount;
|
||||
final int catchHandlerIndex;
|
||||
|
||||
Try(int startAddress, int instructionCount, int catchHandlerIndex) {
|
||||
this.startAddress = startAddress;
|
||||
this.instructionCount = instructionCount;
|
||||
this.catchHandlerIndex = catchHandlerIndex;
|
||||
}
|
||||
|
||||
public int getStartAddress() {
|
||||
return startAddress;
|
||||
}
|
||||
|
||||
public int getInstructionCount() {
|
||||
return instructionCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns this try's catch handler <strong>index</strong>. Note that
|
||||
* this is distinct from the its catch handler <strong>offset</strong>.
|
||||
*/
|
||||
public int getCatchHandlerIndex() {
|
||||
return catchHandlerIndex;
|
||||
}
|
||||
}
|
||||
|
||||
public static class CatchHandler {
|
||||
final int[] typeIndexes;
|
||||
final int[] addresses;
|
||||
final int catchAllAddress;
|
||||
final int offset;
|
||||
|
||||
public CatchHandler(int[] typeIndexes, int[] addresses, int catchAllAddress, int offset) {
|
||||
this.typeIndexes = typeIndexes;
|
||||
this.addresses = addresses;
|
||||
this.catchAllAddress = catchAllAddress;
|
||||
this.offset = offset;
|
||||
}
|
||||
|
||||
public int[] getTypeIndexes() {
|
||||
return typeIndexes;
|
||||
}
|
||||
|
||||
public int[] getAddresses() {
|
||||
return addresses;
|
||||
}
|
||||
|
||||
public int getCatchAllAddress() {
|
||||
return catchAllAddress;
|
||||
}
|
||||
|
||||
public int getOffset() {
|
||||
return offset;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,819 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.Code.CatchHandler;
|
||||
import external.com.android.dex.Code.Try;
|
||||
import external.com.android.dex.MethodHandle.MethodHandleType;
|
||||
import external.com.android.dex.util.ByteInput;
|
||||
import external.com.android.dex.util.ByteOutput;
|
||||
import external.com.android.dex.util.FileUtils;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
import java.io.UTFDataFormatException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.AbstractList;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.RandomAccess;
|
||||
import java.util.zip.Adler32;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipFile;
|
||||
|
||||
/**
|
||||
* The bytes of a dex file in memory for reading and writing. All int offsets
|
||||
* are unsigned.
|
||||
*/
|
||||
public final class Dex {
|
||||
private static final int CHECKSUM_OFFSET = 8;
|
||||
private static final int CHECKSUM_SIZE = 4;
|
||||
private static final int SIGNATURE_OFFSET = CHECKSUM_OFFSET + CHECKSUM_SIZE;
|
||||
private static final int SIGNATURE_SIZE = 20;
|
||||
// Provided as a convenience to avoid a memory allocation to benefit Dalvik.
|
||||
// Note: libcore.util.EmptyArray cannot be accessed when this code isn't run on Dalvik.
|
||||
static final short[] EMPTY_SHORT_ARRAY = new short[0];
|
||||
|
||||
private ByteBuffer data;
|
||||
private final TableOfContents tableOfContents = new TableOfContents();
|
||||
private int nextSectionStart = 0;
|
||||
private final StringTable strings = new StringTable();
|
||||
private final TypeIndexToDescriptorIndexTable typeIds = new TypeIndexToDescriptorIndexTable();
|
||||
private final TypeIndexToDescriptorTable typeNames = new TypeIndexToDescriptorTable();
|
||||
private final ProtoIdTable protoIds = new ProtoIdTable();
|
||||
private final FieldIdTable fieldIds = new FieldIdTable();
|
||||
private final MethodIdTable methodIds = new MethodIdTable();
|
||||
|
||||
/**
|
||||
* Creates a new dex that reads from {@code data}. It is an error to modify
|
||||
* {@code data} after using it to create a dex buffer.
|
||||
*/
|
||||
public Dex(byte[] data) throws IOException {
|
||||
this(ByteBuffer.wrap(data));
|
||||
}
|
||||
|
||||
private Dex(ByteBuffer data) throws IOException {
|
||||
this.data = data;
|
||||
this.data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
this.tableOfContents.readFrom(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new empty dex of the specified size.
|
||||
*/
|
||||
public Dex(int byteCount) throws IOException {
|
||||
this.data = ByteBuffer.wrap(new byte[byteCount]);
|
||||
this.data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dex buffer of the dex in {@code in}, and closes {@code in}.
|
||||
*/
|
||||
public Dex(InputStream in) throws IOException {
|
||||
try {
|
||||
loadFrom(in);
|
||||
} finally {
|
||||
in.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new dex buffer from the dex file {@code file}.
|
||||
*/
|
||||
public Dex(File file) throws IOException {
|
||||
if (FileUtils.hasArchiveSuffix(file.getName())) {
|
||||
ZipFile zipFile = new ZipFile(file);
|
||||
ZipEntry entry = zipFile.getEntry(DexFormat.DEX_IN_JAR_NAME);
|
||||
if (entry != null) {
|
||||
try (InputStream inputStream = zipFile.getInputStream(entry)) {
|
||||
loadFrom(inputStream);
|
||||
}
|
||||
zipFile.close();
|
||||
} else {
|
||||
throw new DexException("Expected " + DexFormat.DEX_IN_JAR_NAME + " in " + file);
|
||||
}
|
||||
} else if (file.getName().endsWith(".dex")) {
|
||||
try (InputStream inputStream = new FileInputStream(file)) {
|
||||
loadFrom(inputStream);
|
||||
}
|
||||
} else {
|
||||
throw new DexException("unknown output extension: " + file);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* It is the caller's responsibility to close {@code in}.
|
||||
*/
|
||||
private void loadFrom(InputStream in) throws IOException {
|
||||
ByteArrayOutputStream bytesOut = new ByteArrayOutputStream();
|
||||
byte[] buffer = new byte[8192];
|
||||
|
||||
int count;
|
||||
while ((count = in.read(buffer)) != -1) {
|
||||
bytesOut.write(buffer, 0, count);
|
||||
}
|
||||
|
||||
this.data = ByteBuffer.wrap(bytesOut.toByteArray());
|
||||
this.data.order(ByteOrder.LITTLE_ENDIAN);
|
||||
this.tableOfContents.readFrom(this);
|
||||
}
|
||||
|
||||
private static void checkBounds(int index, int length) {
|
||||
if (index < 0 || index >= length) {
|
||||
throw new IndexOutOfBoundsException("index:" + index + ", length=" + length);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTo(OutputStream out) throws IOException {
|
||||
byte[] buffer = new byte[8192];
|
||||
ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
|
||||
data.clear();
|
||||
while (data.hasRemaining()) {
|
||||
int count = Math.min(buffer.length, data.remaining());
|
||||
data.get(buffer, 0, count);
|
||||
out.write(buffer, 0, count);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTo(File dexOut) throws IOException {
|
||||
try (OutputStream out = new FileOutputStream(dexOut)) {
|
||||
writeTo(out);
|
||||
}
|
||||
}
|
||||
|
||||
public TableOfContents getTableOfContents() {
|
||||
return tableOfContents;
|
||||
}
|
||||
|
||||
public Section open(int position) {
|
||||
if (position < 0 || position >= data.capacity()) {
|
||||
throw new IllegalArgumentException("position=" + position
|
||||
+ " length=" + data.capacity());
|
||||
}
|
||||
ByteBuffer sectionData = data.duplicate();
|
||||
sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary?
|
||||
sectionData.position(position);
|
||||
sectionData.limit(data.capacity());
|
||||
return new Section("section", sectionData);
|
||||
}
|
||||
|
||||
public Section appendSection(int maxByteCount, String name) {
|
||||
if ((maxByteCount & 3) != 0) {
|
||||
throw new IllegalStateException("Not four byte aligned!");
|
||||
}
|
||||
int limit = nextSectionStart + maxByteCount;
|
||||
ByteBuffer sectionData = data.duplicate();
|
||||
sectionData.order(ByteOrder.LITTLE_ENDIAN); // necessary?
|
||||
sectionData.position(nextSectionStart);
|
||||
sectionData.limit(limit);
|
||||
Section result = new Section(name, sectionData);
|
||||
nextSectionStart = limit;
|
||||
return result;
|
||||
}
|
||||
|
||||
public int getLength() {
|
||||
return data.capacity();
|
||||
}
|
||||
|
||||
public int getNextSectionStart() {
|
||||
return nextSectionStart;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a copy of the the bytes of this dex.
|
||||
*/
|
||||
public byte[] getBytes() {
|
||||
ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
|
||||
byte[] result = new byte[data.capacity()];
|
||||
data.position(0);
|
||||
data.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public List<String> strings() {
|
||||
return strings;
|
||||
}
|
||||
|
||||
public List<Integer> typeIds() {
|
||||
return typeIds;
|
||||
}
|
||||
|
||||
public List<String> typeNames() {
|
||||
return typeNames;
|
||||
}
|
||||
|
||||
public List<ProtoId> protoIds() {
|
||||
return protoIds;
|
||||
}
|
||||
|
||||
public List<FieldId> fieldIds() {
|
||||
return fieldIds;
|
||||
}
|
||||
|
||||
public List<MethodId> methodIds() {
|
||||
return methodIds;
|
||||
}
|
||||
|
||||
public Iterable<ClassDef> classDefs() {
|
||||
return new ClassDefIterable();
|
||||
}
|
||||
|
||||
public TypeList readTypeList(int offset) {
|
||||
if (offset == 0) {
|
||||
return TypeList.EMPTY;
|
||||
}
|
||||
return open(offset).readTypeList();
|
||||
}
|
||||
|
||||
public ClassData readClassData(ClassDef classDef) {
|
||||
int offset = classDef.getClassDataOffset();
|
||||
if (offset == 0) {
|
||||
throw new IllegalArgumentException("offset == 0");
|
||||
}
|
||||
return open(offset).readClassData();
|
||||
}
|
||||
|
||||
public Code readCode(ClassData.Method method) {
|
||||
int offset = method.getCodeOffset();
|
||||
if (offset == 0) {
|
||||
throw new IllegalArgumentException("offset == 0");
|
||||
}
|
||||
return open(offset).readCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the signature of all but the first 32 bytes of this dex. The
|
||||
* first 32 bytes of dex files are not specified to be included in the
|
||||
* signature.
|
||||
*/
|
||||
public byte[] computeSignature() throws IOException {
|
||||
MessageDigest digest;
|
||||
try {
|
||||
digest = MessageDigest.getInstance("SHA-1");
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
byte[] buffer = new byte[8192];
|
||||
ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
|
||||
data.limit(data.capacity());
|
||||
data.position(SIGNATURE_OFFSET + SIGNATURE_SIZE);
|
||||
while (data.hasRemaining()) {
|
||||
int count = Math.min(buffer.length, data.remaining());
|
||||
data.get(buffer, 0, count);
|
||||
digest.update(buffer, 0, count);
|
||||
}
|
||||
return digest.digest();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the checksum of all but the first 12 bytes of {@code dex}.
|
||||
*/
|
||||
public int computeChecksum() throws IOException {
|
||||
Adler32 adler32 = new Adler32();
|
||||
byte[] buffer = new byte[8192];
|
||||
ByteBuffer data = this.data.duplicate(); // positioned ByteBuffers aren't thread safe
|
||||
data.limit(data.capacity());
|
||||
data.position(CHECKSUM_OFFSET + CHECKSUM_SIZE);
|
||||
while (data.hasRemaining()) {
|
||||
int count = Math.min(buffer.length, data.remaining());
|
||||
data.get(buffer, 0, count);
|
||||
adler32.update(buffer, 0, count);
|
||||
}
|
||||
return (int) adler32.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates the signature and checksum of the dex file {@code out} and
|
||||
* writes them to the file.
|
||||
*/
|
||||
public void writeHashes() throws IOException {
|
||||
open(SIGNATURE_OFFSET).write(computeSignature());
|
||||
open(CHECKSUM_OFFSET).writeInt(computeChecksum());
|
||||
}
|
||||
|
||||
/**
|
||||
* Look up a descriptor index from a type index. Cheaper than:
|
||||
* {@code open(tableOfContents.typeIds.off + (index * SizeOf.TYPE_ID_ITEM)).readInt();}
|
||||
*/
|
||||
public int descriptorIndexFromTypeIndex(int typeIndex) {
|
||||
checkBounds(typeIndex, tableOfContents.typeIds.size);
|
||||
int position = tableOfContents.typeIds.off + (SizeOf.TYPE_ID_ITEM * typeIndex);
|
||||
return data.getInt(position);
|
||||
}
|
||||
|
||||
|
||||
public final class Section implements ByteInput, ByteOutput {
|
||||
private final String name;
|
||||
private final ByteBuffer data;
|
||||
private final int initialPosition;
|
||||
|
||||
private Section(String name, ByteBuffer data) {
|
||||
this.name = name;
|
||||
this.data = data;
|
||||
this.initialPosition = data.position();
|
||||
}
|
||||
|
||||
public int getPosition() {
|
||||
return data.position();
|
||||
}
|
||||
|
||||
public int readInt() {
|
||||
return data.getInt();
|
||||
}
|
||||
|
||||
public short readShort() {
|
||||
return data.getShort();
|
||||
}
|
||||
|
||||
public int readUnsignedShort() {
|
||||
return readShort() & 0xffff;
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte readByte() {
|
||||
return data.get();
|
||||
}
|
||||
|
||||
public byte[] readByteArray(int length) {
|
||||
byte[] result = new byte[length];
|
||||
data.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public short[] readShortArray(int length) {
|
||||
if (length == 0) {
|
||||
return EMPTY_SHORT_ARRAY;
|
||||
}
|
||||
short[] result = new short[length];
|
||||
for (int i = 0; i < length; i++) {
|
||||
result[i] = readShort();
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
public int readUleb128() {
|
||||
return Leb128.readUnsignedLeb128(this);
|
||||
}
|
||||
|
||||
public int readUleb128p1() {
|
||||
return Leb128.readUnsignedLeb128(this) - 1;
|
||||
}
|
||||
|
||||
public int readSleb128() {
|
||||
return Leb128.readSignedLeb128(this);
|
||||
}
|
||||
|
||||
public void writeUleb128p1(int i) {
|
||||
writeUleb128(i + 1);
|
||||
}
|
||||
|
||||
public TypeList readTypeList() {
|
||||
int size = readInt();
|
||||
short[] types = readShortArray(size);
|
||||
alignToFourBytes();
|
||||
return new TypeList(Dex.this, types);
|
||||
}
|
||||
|
||||
public String readString() {
|
||||
int offset = readInt();
|
||||
int savedPosition = data.position();
|
||||
int savedLimit = data.limit();
|
||||
data.position(offset);
|
||||
data.limit(data.capacity());
|
||||
try {
|
||||
int expectedLength = readUleb128();
|
||||
String result = Mutf8.decode(this, new char[expectedLength]);
|
||||
if (result.length() != expectedLength) {
|
||||
throw new DexException("Declared length " + expectedLength
|
||||
+ " doesn't match decoded length of " + result.length());
|
||||
}
|
||||
return result;
|
||||
} catch (UTFDataFormatException e) {
|
||||
throw new DexException(e);
|
||||
} finally {
|
||||
data.position(savedPosition);
|
||||
data.limit(savedLimit);
|
||||
}
|
||||
}
|
||||
|
||||
public FieldId readFieldId() {
|
||||
int declaringClassIndex = readUnsignedShort();
|
||||
int typeIndex = readUnsignedShort();
|
||||
int nameIndex = readInt();
|
||||
return new FieldId(Dex.this, declaringClassIndex, typeIndex, nameIndex);
|
||||
}
|
||||
|
||||
public MethodId readMethodId() {
|
||||
int declaringClassIndex = readUnsignedShort();
|
||||
int protoIndex = readUnsignedShort();
|
||||
int nameIndex = readInt();
|
||||
return new MethodId(Dex.this, declaringClassIndex, protoIndex, nameIndex);
|
||||
}
|
||||
|
||||
public ProtoId readProtoId() {
|
||||
int shortyIndex = readInt();
|
||||
int returnTypeIndex = readInt();
|
||||
int parametersOffset = readInt();
|
||||
return new ProtoId(Dex.this, shortyIndex, returnTypeIndex, parametersOffset);
|
||||
}
|
||||
|
||||
public CallSiteId readCallSiteId() {
|
||||
int offset = readInt();
|
||||
return new CallSiteId(Dex.this, offset);
|
||||
}
|
||||
|
||||
public MethodHandle readMethodHandle() {
|
||||
MethodHandleType methodHandleType = MethodHandleType.fromValue(readUnsignedShort());
|
||||
int unused1 = readUnsignedShort();
|
||||
int fieldOrMethodId = readUnsignedShort();
|
||||
int unused2 = readUnsignedShort();
|
||||
return new MethodHandle(Dex.this, methodHandleType, unused1, fieldOrMethodId, unused2);
|
||||
}
|
||||
|
||||
public ClassDef readClassDef() {
|
||||
int offset = getPosition();
|
||||
int type = readInt();
|
||||
int accessFlags = readInt();
|
||||
int supertype = readInt();
|
||||
int interfacesOffset = readInt();
|
||||
int sourceFileIndex = readInt();
|
||||
int annotationsOffset = readInt();
|
||||
int classDataOffset = readInt();
|
||||
int staticValuesOffset = readInt();
|
||||
return new ClassDef(Dex.this, offset, type, accessFlags, supertype,
|
||||
interfacesOffset, sourceFileIndex, annotationsOffset, classDataOffset,
|
||||
staticValuesOffset);
|
||||
}
|
||||
|
||||
private Code readCode() {
|
||||
int registersSize = readUnsignedShort();
|
||||
int insSize = readUnsignedShort();
|
||||
int outsSize = readUnsignedShort();
|
||||
int triesSize = readUnsignedShort();
|
||||
int debugInfoOffset = readInt();
|
||||
int instructionsSize = readInt();
|
||||
short[] instructions = readShortArray(instructionsSize);
|
||||
Try[] tries;
|
||||
CatchHandler[] catchHandlers;
|
||||
if (triesSize > 0) {
|
||||
if (instructions.length % 2 == 1) {
|
||||
readShort(); // padding
|
||||
}
|
||||
|
||||
/*
|
||||
* We can't read the tries until we've read the catch handlers.
|
||||
* Unfortunately they're in the opposite order in the dex file
|
||||
* so we need to read them out-of-order.
|
||||
*/
|
||||
Section triesSection = open(data.position());
|
||||
skip(triesSize * SizeOf.TRY_ITEM);
|
||||
catchHandlers = readCatchHandlers();
|
||||
tries = triesSection.readTries(triesSize, catchHandlers);
|
||||
} else {
|
||||
tries = new Try[0];
|
||||
catchHandlers = new CatchHandler[0];
|
||||
}
|
||||
return new Code(registersSize, insSize, outsSize, debugInfoOffset, instructions,
|
||||
tries, catchHandlers);
|
||||
}
|
||||
|
||||
private CatchHandler[] readCatchHandlers() {
|
||||
int baseOffset = data.position();
|
||||
int catchHandlersSize = readUleb128();
|
||||
CatchHandler[] result = new CatchHandler[catchHandlersSize];
|
||||
for (int i = 0; i < catchHandlersSize; i++) {
|
||||
int offset = data.position() - baseOffset;
|
||||
result[i] = readCatchHandler(offset);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private Try[] readTries(int triesSize, CatchHandler[] catchHandlers) {
|
||||
Try[] result = new Try[triesSize];
|
||||
for (int i = 0; i < triesSize; i++) {
|
||||
int startAddress = readInt();
|
||||
int instructionCount = readUnsignedShort();
|
||||
int handlerOffset = readUnsignedShort();
|
||||
int catchHandlerIndex = findCatchHandlerIndex(catchHandlers, handlerOffset);
|
||||
result[i] = new Try(startAddress, instructionCount, catchHandlerIndex);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private int findCatchHandlerIndex(CatchHandler[] catchHandlers, int offset) {
|
||||
for (int i = 0; i < catchHandlers.length; i++) {
|
||||
CatchHandler catchHandler = catchHandlers[i];
|
||||
if (catchHandler.getOffset() == offset) {
|
||||
return i;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
||||
private CatchHandler readCatchHandler(int offset) {
|
||||
int size = readSleb128();
|
||||
int handlersCount = Math.abs(size);
|
||||
int[] typeIndexes = new int[handlersCount];
|
||||
int[] addresses = new int[handlersCount];
|
||||
for (int i = 0; i < handlersCount; i++) {
|
||||
typeIndexes[i] = readUleb128();
|
||||
addresses[i] = readUleb128();
|
||||
}
|
||||
int catchAllAddress = size <= 0 ? readUleb128() : -1;
|
||||
return new CatchHandler(typeIndexes, addresses, catchAllAddress, offset);
|
||||
}
|
||||
|
||||
private ClassData readClassData() {
|
||||
int staticFieldsSize = readUleb128();
|
||||
int instanceFieldsSize = readUleb128();
|
||||
int directMethodsSize = readUleb128();
|
||||
int virtualMethodsSize = readUleb128();
|
||||
ClassData.Field[] staticFields = readFields(staticFieldsSize);
|
||||
ClassData.Field[] instanceFields = readFields(instanceFieldsSize);
|
||||
ClassData.Method[] directMethods = readMethods(directMethodsSize);
|
||||
ClassData.Method[] virtualMethods = readMethods(virtualMethodsSize);
|
||||
return new ClassData(staticFields, instanceFields, directMethods, virtualMethods);
|
||||
}
|
||||
|
||||
private ClassData.Field[] readFields(int count) {
|
||||
ClassData.Field[] result = new ClassData.Field[count];
|
||||
int fieldIndex = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
fieldIndex += readUleb128(); // field index diff
|
||||
int accessFlags = readUleb128();
|
||||
result[i] = new ClassData.Field(fieldIndex, accessFlags);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private ClassData.Method[] readMethods(int count) {
|
||||
ClassData.Method[] result = new ClassData.Method[count];
|
||||
int methodIndex = 0;
|
||||
for (int i = 0; i < count; i++) {
|
||||
methodIndex += readUleb128(); // method index diff
|
||||
int accessFlags = readUleb128();
|
||||
int codeOff = readUleb128();
|
||||
result[i] = new ClassData.Method(methodIndex, accessFlags, codeOff);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a byte array containing the bytes from {@code start} to this
|
||||
* section's current position.
|
||||
*/
|
||||
private byte[] getBytesFrom(int start) {
|
||||
int end = data.position();
|
||||
byte[] result = new byte[end - start];
|
||||
data.position(start);
|
||||
data.get(result);
|
||||
return result;
|
||||
}
|
||||
|
||||
public Annotation readAnnotation() {
|
||||
byte visibility = readByte();
|
||||
int start = data.position();
|
||||
new EncodedValueReader(this, EncodedValueReader.ENCODED_ANNOTATION).skipValue();
|
||||
return new Annotation(Dex.this, visibility, new EncodedValue(getBytesFrom(start)));
|
||||
}
|
||||
|
||||
public EncodedValue readEncodedArray() {
|
||||
int start = data.position();
|
||||
new EncodedValueReader(this, EncodedValueReader.ENCODED_ARRAY).skipValue();
|
||||
return new EncodedValue(getBytesFrom(start));
|
||||
}
|
||||
|
||||
public void skip(int count) {
|
||||
if (count < 0) {
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
data.position(data.position() + count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips bytes until the position is aligned to a multiple of 4.
|
||||
*/
|
||||
public void alignToFourBytes() {
|
||||
data.position((data.position() + 3) & ~3);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes 0x00 until the position is aligned to a multiple of 4.
|
||||
*/
|
||||
public void alignToFourBytesWithZeroFill() {
|
||||
while ((data.position() & 3) != 0) {
|
||||
data.put((byte) 0);
|
||||
}
|
||||
}
|
||||
|
||||
public void assertFourByteAligned() {
|
||||
if ((data.position() & 3) != 0) {
|
||||
throw new IllegalStateException("Not four byte aligned!");
|
||||
}
|
||||
}
|
||||
|
||||
public void write(byte[] bytes) {
|
||||
this.data.put(bytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeByte(int b) {
|
||||
data.put((byte) b);
|
||||
}
|
||||
|
||||
public void writeShort(short i) {
|
||||
data.putShort(i);
|
||||
}
|
||||
|
||||
public void writeUnsignedShort(int i) {
|
||||
short s = (short) i;
|
||||
if (i != (s & 0xffff)) {
|
||||
throw new IllegalArgumentException("Expected an unsigned short: " + i);
|
||||
}
|
||||
writeShort(s);
|
||||
}
|
||||
|
||||
public void write(short[] shorts) {
|
||||
for (short s : shorts) {
|
||||
writeShort(s);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeInt(int i) {
|
||||
data.putInt(i);
|
||||
}
|
||||
|
||||
public void writeUleb128(int i) {
|
||||
try {
|
||||
Leb128.writeUnsignedLeb128(this, i);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new DexException("Section limit " + data.limit() + " exceeded by " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeSleb128(int i) {
|
||||
try {
|
||||
Leb128.writeSignedLeb128(this, i);
|
||||
} catch (ArrayIndexOutOfBoundsException e) {
|
||||
throw new DexException("Section limit " + data.limit() + " exceeded by " + name);
|
||||
}
|
||||
}
|
||||
|
||||
public void writeStringData(String value) {
|
||||
try {
|
||||
int length = value.length();
|
||||
writeUleb128(length);
|
||||
write(Mutf8.encode(value));
|
||||
writeByte(0);
|
||||
} catch (UTFDataFormatException e) {
|
||||
throw new AssertionError();
|
||||
}
|
||||
}
|
||||
|
||||
public void writeTypeList(TypeList typeList) {
|
||||
short[] types = typeList.getTypes();
|
||||
writeInt(types.length);
|
||||
for (short type : types) {
|
||||
writeShort(type);
|
||||
}
|
||||
alignToFourBytesWithZeroFill();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes used by this section.
|
||||
*/
|
||||
public int used() {
|
||||
return data.position() - initialPosition;
|
||||
}
|
||||
}
|
||||
|
||||
private final class StringTable extends AbstractList<String> implements RandomAccess {
|
||||
@Override
|
||||
public String get(int index) {
|
||||
checkBounds(index, tableOfContents.stringIds.size);
|
||||
return open(tableOfContents.stringIds.off + (index * SizeOf.STRING_ID_ITEM))
|
||||
.readString();
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return tableOfContents.stringIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class TypeIndexToDescriptorIndexTable extends AbstractList<Integer>
|
||||
implements RandomAccess {
|
||||
@Override
|
||||
public Integer get(int index) {
|
||||
return descriptorIndexFromTypeIndex(index);
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return tableOfContents.typeIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class TypeIndexToDescriptorTable extends AbstractList<String>
|
||||
implements RandomAccess {
|
||||
@Override
|
||||
public String get(int index) {
|
||||
return strings.get(descriptorIndexFromTypeIndex(index));
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return tableOfContents.typeIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class ProtoIdTable extends AbstractList<ProtoId> implements RandomAccess {
|
||||
@Override
|
||||
public ProtoId get(int index) {
|
||||
checkBounds(index, tableOfContents.protoIds.size);
|
||||
return open(tableOfContents.protoIds.off + (SizeOf.PROTO_ID_ITEM * index))
|
||||
.readProtoId();
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return tableOfContents.protoIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class FieldIdTable extends AbstractList<FieldId> implements RandomAccess {
|
||||
@Override
|
||||
public FieldId get(int index) {
|
||||
checkBounds(index, tableOfContents.fieldIds.size);
|
||||
return open(tableOfContents.fieldIds.off + (SizeOf.MEMBER_ID_ITEM * index))
|
||||
.readFieldId();
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return tableOfContents.fieldIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class MethodIdTable extends AbstractList<MethodId> implements RandomAccess {
|
||||
@Override
|
||||
public MethodId get(int index) {
|
||||
checkBounds(index, tableOfContents.methodIds.size);
|
||||
return open(tableOfContents.methodIds.off + (SizeOf.MEMBER_ID_ITEM * index))
|
||||
.readMethodId();
|
||||
}
|
||||
@Override
|
||||
public int size() {
|
||||
return tableOfContents.methodIds.size;
|
||||
}
|
||||
}
|
||||
|
||||
private final class ClassDefIterator implements Iterator<ClassDef> {
|
||||
private final Dex.Section in = open(tableOfContents.classDefs.off);
|
||||
private int count = 0;
|
||||
|
||||
@Override
|
||||
public boolean hasNext() {
|
||||
return count < tableOfContents.classDefs.size;
|
||||
}
|
||||
@Override
|
||||
public ClassDef next() {
|
||||
if (!hasNext()) {
|
||||
throw new NoSuchElementException();
|
||||
}
|
||||
count++;
|
||||
return in.readClassDef();
|
||||
}
|
||||
@Override
|
||||
public void remove() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
|
||||
private final class ClassDefIterable implements Iterable<ClassDef> {
|
||||
@Override
|
||||
public Iterator<ClassDef> iterator() {
|
||||
return !tableOfContents.classDefs.exists()
|
||||
? Collections.<ClassDef>emptySet().iterator()
|
||||
: new ClassDefIterator();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,33 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.ExceptionWithContext;
|
||||
|
||||
/**
|
||||
* Thrown when there's a format problem reading, writing, or generally
|
||||
* processing a dex file.
|
||||
*/
|
||||
public class DexException extends ExceptionWithContext {
|
||||
public DexException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DexException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,170 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
/**
|
||||
* Constants that show up in and are otherwise related to {@code .dex}
|
||||
* files, and helper methods for same.
|
||||
*/
|
||||
public final class DexFormat {
|
||||
private DexFormat() {}
|
||||
|
||||
/** API level to target in order to generate const-method-handle and const-method-type */
|
||||
public static final int API_CONST_METHOD_HANDLE = 28;
|
||||
|
||||
/** API level to target in order to generate invoke-polymorphic and invoke-custom */
|
||||
public static final int API_METHOD_HANDLES = 26;
|
||||
|
||||
/** API level to target in order to define default and static interface methods */
|
||||
public static final int API_DEFINE_INTERFACE_METHODS = 24;
|
||||
|
||||
/** API level to target in order to invoke default and static interface methods */
|
||||
public static final int API_INVOKE_INTERFACE_METHODS = 24;
|
||||
|
||||
/** API level at which the invocation of static interface methods is permitted by dx.
|
||||
* This value has been determined experimentally by testing on different VM versions. */
|
||||
public static final int API_INVOKE_STATIC_INTERFACE_METHODS = 21;
|
||||
|
||||
/** API level to target in order to suppress extended opcode usage */
|
||||
public static final int API_NO_EXTENDED_OPCODES = 13;
|
||||
|
||||
/**
|
||||
* API level to target in order to produce the most modern file
|
||||
* format
|
||||
*/
|
||||
public static final int API_CURRENT = API_CONST_METHOD_HANDLE;
|
||||
|
||||
/** dex file version number for API level 28 and earlier */
|
||||
public static final String VERSION_FOR_API_28 = "039";
|
||||
|
||||
/** dex file version number for API level 26 and earlier */
|
||||
public static final String VERSION_FOR_API_26 = "038";
|
||||
|
||||
/** dex file version number for API level 24 and earlier */
|
||||
public static final String VERSION_FOR_API_24 = "037";
|
||||
|
||||
/** dex file version number for API level 13 and earlier */
|
||||
public static final String VERSION_FOR_API_13 = "035";
|
||||
|
||||
/**
|
||||
* Dex file version number for dalvik.
|
||||
* <p>
|
||||
* Note: Dex version 36 was loadable in some versions of Dalvik but was never fully supported or
|
||||
* completed and is not considered a valid dex file format.
|
||||
* </p>
|
||||
*/
|
||||
public static final String VERSION_CURRENT = VERSION_FOR_API_28;
|
||||
|
||||
/**
|
||||
* file name of the primary {@code .dex} file inside an
|
||||
* application or library {@code .jar} file
|
||||
*/
|
||||
public static final String DEX_IN_JAR_NAME = "classes.dex";
|
||||
|
||||
/** common prefix for all dex file "magic numbers" */
|
||||
public static final String MAGIC_PREFIX = "dex\n";
|
||||
|
||||
/** common suffix for all dex file "magic numbers" */
|
||||
public static final String MAGIC_SUFFIX = "\0";
|
||||
|
||||
/**
|
||||
* value used to indicate endianness of file contents
|
||||
*/
|
||||
public static final int ENDIAN_TAG = 0x12345678;
|
||||
|
||||
/**
|
||||
* Maximum addressable field or method index.
|
||||
* The largest addressable member is 0xffff, in the "instruction formats" spec as field@CCCC or
|
||||
* meth@CCCC.
|
||||
*/
|
||||
public static final int MAX_MEMBER_IDX = 0xFFFF;
|
||||
|
||||
/**
|
||||
* Maximum addressable type index.
|
||||
* The largest addressable type is 0xffff, in the "instruction formats" spec as type@CCCC.
|
||||
*/
|
||||
public static final int MAX_TYPE_IDX = 0xFFFF;
|
||||
|
||||
/**
|
||||
* Returns the API level corresponding to the given magic number,
|
||||
* or {@code -1} if the given array is not a well-formed dex file
|
||||
* magic number.
|
||||
*
|
||||
* @param magic array of bytes containing DEX file magic string
|
||||
* @return API level corresponding to magic string if valid, -1 otherwise.
|
||||
*/
|
||||
public static int magicToApi(byte[] magic) {
|
||||
if (magic.length != 8) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
if ((magic[0] != 'd') || (magic[1] != 'e') || (magic[2] != 'x') || (magic[3] != '\n') ||
|
||||
(magic[7] != '\0')) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
String version = "" + ((char) magic[4]) + ((char) magic[5]) +((char) magic[6]);
|
||||
|
||||
if (version.equals(VERSION_FOR_API_13)) {
|
||||
return API_NO_EXTENDED_OPCODES;
|
||||
} else if (version.equals(VERSION_FOR_API_24)) {
|
||||
return API_DEFINE_INTERFACE_METHODS;
|
||||
} else if (version.equals(VERSION_FOR_API_26)) {
|
||||
return API_METHOD_HANDLES;
|
||||
} else if (version.equals(VERSION_FOR_API_28)) {
|
||||
return API_CONST_METHOD_HANDLE;
|
||||
} else if (version.equals(VERSION_CURRENT)) {
|
||||
return API_CURRENT;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the magic number corresponding to the given target API level.
|
||||
*
|
||||
* @param targetApiLevel level of API (minimum supported value 13).
|
||||
* @return Magic string corresponding to API level supplied.
|
||||
*/
|
||||
public static String apiToMagic(int targetApiLevel) {
|
||||
String version;
|
||||
|
||||
if (targetApiLevel >= API_CURRENT) {
|
||||
version = VERSION_CURRENT;
|
||||
} else if (targetApiLevel >= API_CONST_METHOD_HANDLE) {
|
||||
version = VERSION_FOR_API_28;
|
||||
} else if (targetApiLevel >= API_METHOD_HANDLES) {
|
||||
version = VERSION_FOR_API_26;
|
||||
} else if (targetApiLevel >= API_DEFINE_INTERFACE_METHODS) {
|
||||
version = VERSION_FOR_API_24;
|
||||
} else {
|
||||
version = VERSION_FOR_API_13;
|
||||
}
|
||||
|
||||
return MAGIC_PREFIX + version + MAGIC_SUFFIX;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether a DEX file magic string is supported.
|
||||
* @param magic string from DEX file
|
||||
* @return
|
||||
*/
|
||||
public static boolean isSupportedDexMagic(byte[] magic) {
|
||||
int api = magicToApi(magic);
|
||||
return api > 0;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,30 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2013 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
/**
|
||||
* Thrown when there's an index overflow writing a dex file.
|
||||
*/
|
||||
public final class DexIndexOverflowException extends DexException {
|
||||
public DexIndexOverflowException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DexIndexOverflowException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,59 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.ByteArrayByteInput;
|
||||
import external.com.android.dex.util.ByteInput;
|
||||
|
||||
/**
|
||||
* An encoded value or array.
|
||||
*/
|
||||
public final class EncodedValue implements Comparable<EncodedValue> {
|
||||
private final byte[] data;
|
||||
|
||||
public EncodedValue(byte[] data) {
|
||||
this.data = data;
|
||||
}
|
||||
|
||||
public ByteInput asByteInput() {
|
||||
return new ByteArrayByteInput(data);
|
||||
}
|
||||
|
||||
public byte[] getBytes() {
|
||||
return data;
|
||||
}
|
||||
|
||||
public void writeTo(Dex.Section out) {
|
||||
out.write(data);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(EncodedValue other) {
|
||||
int size = Math.min(data.length, other.data.length);
|
||||
for (int i = 0; i < size; i++) {
|
||||
if (data[i] != other.data[i]) {
|
||||
return (data[i] & 0xff) - (other.data[i] & 0xff);
|
||||
}
|
||||
}
|
||||
return data.length - other.data.length;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return Integer.toHexString(data[0] & 0xff) + "...(" + data.length + ")";
|
||||
}
|
||||
}
|
||||
|
|
@ -1,187 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.ByteInput;
|
||||
import external.com.android.dex.util.ByteOutput;
|
||||
|
||||
/**
|
||||
* Read and write {@code encoded_value} primitives.
|
||||
*/
|
||||
public final class EncodedValueCodec {
|
||||
private EncodedValueCodec() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a signed integral to {@code out}.
|
||||
*/
|
||||
public static void writeSignedIntegralValue(ByteOutput out, int type, long value) {
|
||||
/*
|
||||
* Figure out how many bits are needed to represent the value,
|
||||
* including a sign bit: The bit count is subtracted from 65
|
||||
* and not 64 to account for the sign bit. The xor operation
|
||||
* has the effect of leaving non-negative values alone and
|
||||
* unary complementing negative values (so that a leading zero
|
||||
* count always returns a useful number for our present
|
||||
* purpose).
|
||||
*/
|
||||
int requiredBits = 65 - Long.numberOfLeadingZeros(value ^ (value >> 63));
|
||||
|
||||
// Round up the requiredBits to a number of bytes.
|
||||
int requiredBytes = (requiredBits + 0x07) >> 3;
|
||||
|
||||
/*
|
||||
* Write the header byte, which includes the type and
|
||||
* requiredBytes - 1.
|
||||
*/
|
||||
out.writeByte(type | ((requiredBytes - 1) << 5));
|
||||
|
||||
// Write the value, per se.
|
||||
while (requiredBytes > 0) {
|
||||
out.writeByte((byte) value);
|
||||
value >>= 8;
|
||||
requiredBytes--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes an unsigned integral to {@code out}.
|
||||
*/
|
||||
public static void writeUnsignedIntegralValue(ByteOutput out, int type, long value) {
|
||||
// Figure out how many bits are needed to represent the value.
|
||||
int requiredBits = 64 - Long.numberOfLeadingZeros(value);
|
||||
if (requiredBits == 0) {
|
||||
requiredBits = 1;
|
||||
}
|
||||
|
||||
// Round up the requiredBits to a number of bytes.
|
||||
int requiredBytes = (requiredBits + 0x07) >> 3;
|
||||
|
||||
/*
|
||||
* Write the header byte, which includes the type and
|
||||
* requiredBytes - 1.
|
||||
*/
|
||||
out.writeByte(type | ((requiredBytes - 1) << 5));
|
||||
|
||||
// Write the value, per se.
|
||||
while (requiredBytes > 0) {
|
||||
out.writeByte((byte) value);
|
||||
value >>= 8;
|
||||
requiredBytes--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a right-zero-extended value to {@code out}.
|
||||
*/
|
||||
public static void writeRightZeroExtendedValue(ByteOutput out, int type, long value) {
|
||||
// Figure out how many bits are needed to represent the value.
|
||||
int requiredBits = 64 - Long.numberOfTrailingZeros(value);
|
||||
if (requiredBits == 0) {
|
||||
requiredBits = 1;
|
||||
}
|
||||
|
||||
// Round up the requiredBits to a number of bytes.
|
||||
int requiredBytes = (requiredBits + 0x07) >> 3;
|
||||
|
||||
// Scootch the first bits to be written down to the low-order bits.
|
||||
value >>= 64 - (requiredBytes * 8);
|
||||
|
||||
/*
|
||||
* Write the header byte, which includes the type and
|
||||
* requiredBytes - 1.
|
||||
*/
|
||||
out.writeByte(type | ((requiredBytes - 1) << 5));
|
||||
|
||||
// Write the value, per se.
|
||||
while (requiredBytes > 0) {
|
||||
out.writeByte((byte) value);
|
||||
value >>= 8;
|
||||
requiredBytes--;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a signed integer.
|
||||
*
|
||||
* @param zwidth byte count minus one
|
||||
*/
|
||||
public static int readSignedInt(ByteInput in, int zwidth) {
|
||||
int result = 0;
|
||||
for (int i = zwidth; i >= 0; i--) {
|
||||
result = (result >>> 8) | ((in.readByte() & 0xff) << 24);
|
||||
}
|
||||
result >>= (3 - zwidth) * 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an unsigned integer.
|
||||
*
|
||||
* @param zwidth byte count minus one
|
||||
* @param fillOnRight true to zero fill on the right; false on the left
|
||||
*/
|
||||
public static int readUnsignedInt(ByteInput in, int zwidth, boolean fillOnRight) {
|
||||
int result = 0;
|
||||
if (!fillOnRight) {
|
||||
for (int i = zwidth; i >= 0; i--) {
|
||||
result = (result >>> 8) | ((in.readByte() & 0xff) << 24);
|
||||
}
|
||||
result >>>= (3 - zwidth) * 8;
|
||||
} else {
|
||||
for (int i = zwidth; i >= 0; i--) {
|
||||
result = (result >>> 8) | ((in.readByte() & 0xff) << 24);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a signed long.
|
||||
*
|
||||
* @param zwidth byte count minus one
|
||||
*/
|
||||
public static long readSignedLong(ByteInput in, int zwidth) {
|
||||
long result = 0;
|
||||
for (int i = zwidth; i >= 0; i--) {
|
||||
result = (result >>> 8) | ((in.readByte() & 0xffL) << 56);
|
||||
}
|
||||
result >>= (7 - zwidth) * 8;
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read an unsigned long.
|
||||
*
|
||||
* @param zwidth byte count minus one
|
||||
* @param fillOnRight true to zero fill on the right; false on the left
|
||||
*/
|
||||
public static long readUnsignedLong(ByteInput in, int zwidth, boolean fillOnRight) {
|
||||
long result = 0;
|
||||
if (!fillOnRight) {
|
||||
for (int i = zwidth; i >= 0; i--) {
|
||||
result = (result >>> 8) | ((in.readByte() & 0xffL) << 56);
|
||||
}
|
||||
result >>>= (7 - zwidth) * 8;
|
||||
} else {
|
||||
for (int i = zwidth; i >= 0; i--) {
|
||||
result = (result >>> 8) | ((in.readByte() & 0xffL) << 56);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,307 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.ByteInput;
|
||||
|
||||
/**
|
||||
* Pull parser for encoded values.
|
||||
*/
|
||||
public final class EncodedValueReader {
|
||||
public static final int ENCODED_BYTE = 0x00;
|
||||
public static final int ENCODED_SHORT = 0x02;
|
||||
public static final int ENCODED_CHAR = 0x03;
|
||||
public static final int ENCODED_INT = 0x04;
|
||||
public static final int ENCODED_LONG = 0x06;
|
||||
public static final int ENCODED_FLOAT = 0x10;
|
||||
public static final int ENCODED_DOUBLE = 0x11;
|
||||
public static final int ENCODED_METHOD_TYPE = 0x15;
|
||||
public static final int ENCODED_METHOD_HANDLE = 0x16;
|
||||
public static final int ENCODED_STRING = 0x17;
|
||||
public static final int ENCODED_TYPE = 0x18;
|
||||
public static final int ENCODED_FIELD = 0x19;
|
||||
public static final int ENCODED_ENUM = 0x1b;
|
||||
public static final int ENCODED_METHOD = 0x1a;
|
||||
public static final int ENCODED_ARRAY = 0x1c;
|
||||
public static final int ENCODED_ANNOTATION = 0x1d;
|
||||
public static final int ENCODED_NULL = 0x1e;
|
||||
public static final int ENCODED_BOOLEAN = 0x1f;
|
||||
|
||||
/** placeholder type if the type is not yet known */
|
||||
private static final int MUST_READ = -1;
|
||||
|
||||
protected final ByteInput in;
|
||||
private int type = MUST_READ;
|
||||
private int annotationType;
|
||||
private int arg;
|
||||
|
||||
public EncodedValueReader(ByteInput in) {
|
||||
this.in = in;
|
||||
}
|
||||
|
||||
public EncodedValueReader(EncodedValue in) {
|
||||
this(in.asByteInput());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new encoded value reader whose only value is the specified
|
||||
* known type. This is useful for encoded values without a type prefix,
|
||||
* such as class_def_item's encoded_array or annotation_item's
|
||||
* encoded_annotation.
|
||||
*/
|
||||
public EncodedValueReader(ByteInput in, int knownType) {
|
||||
this.in = in;
|
||||
this.type = knownType;
|
||||
}
|
||||
|
||||
public EncodedValueReader(EncodedValue in, int knownType) {
|
||||
this(in.asByteInput(), knownType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the next value to read.
|
||||
*/
|
||||
public int peek() {
|
||||
if (type == MUST_READ) {
|
||||
int argAndType = in.readByte() & 0xff;
|
||||
type = argAndType & 0x1f;
|
||||
arg = (argAndType & 0xe0) >> 5;
|
||||
}
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins reading the elements of an array, returning the array's size. The
|
||||
* caller must follow up by calling a read method for each element in the
|
||||
* array. For example, this reads a byte array: <pre> {@code
|
||||
* int arraySize = readArray();
|
||||
* for (int i = 0, i < arraySize; i++) {
|
||||
* readByte();
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public int readArray() {
|
||||
checkType(ENCODED_ARRAY);
|
||||
type = MUST_READ;
|
||||
return Leb128.readUnsignedLeb128(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Begins reading the fields of an annotation, returning the number of
|
||||
* fields. The caller must follow up by making alternating calls to {@link
|
||||
* #readAnnotationName()} and another read method. For example, this reads
|
||||
* an annotation whose fields are all bytes: <pre> {@code
|
||||
* int fieldCount = readAnnotation();
|
||||
* int annotationType = getAnnotationType();
|
||||
* for (int i = 0; i < fieldCount; i++) {
|
||||
* readAnnotationName();
|
||||
* readByte();
|
||||
* }
|
||||
* }</pre>
|
||||
*/
|
||||
public int readAnnotation() {
|
||||
checkType(ENCODED_ANNOTATION);
|
||||
type = MUST_READ;
|
||||
annotationType = Leb128.readUnsignedLeb128(in);
|
||||
return Leb128.readUnsignedLeb128(in);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the type of the annotation just returned by {@link
|
||||
* #readAnnotation()}. This method's value is undefined unless the most
|
||||
* recent call was to {@link #readAnnotation()}.
|
||||
*/
|
||||
public int getAnnotationType() {
|
||||
return annotationType;
|
||||
}
|
||||
|
||||
public int readAnnotationName() {
|
||||
return Leb128.readUnsignedLeb128(in);
|
||||
}
|
||||
|
||||
public byte readByte() {
|
||||
checkType(ENCODED_BYTE);
|
||||
type = MUST_READ;
|
||||
return (byte) EncodedValueCodec.readSignedInt(in, arg);
|
||||
}
|
||||
|
||||
public short readShort() {
|
||||
checkType(ENCODED_SHORT);
|
||||
type = MUST_READ;
|
||||
return (short) EncodedValueCodec.readSignedInt(in, arg);
|
||||
}
|
||||
|
||||
public char readChar() {
|
||||
checkType(ENCODED_CHAR);
|
||||
type = MUST_READ;
|
||||
return (char) EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readInt() {
|
||||
checkType(ENCODED_INT);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readSignedInt(in, arg);
|
||||
}
|
||||
|
||||
public long readLong() {
|
||||
checkType(ENCODED_LONG);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readSignedLong(in, arg);
|
||||
}
|
||||
|
||||
public float readFloat() {
|
||||
checkType(ENCODED_FLOAT);
|
||||
type = MUST_READ;
|
||||
return Float.intBitsToFloat(EncodedValueCodec.readUnsignedInt(in, arg, true));
|
||||
}
|
||||
|
||||
public double readDouble() {
|
||||
checkType(ENCODED_DOUBLE);
|
||||
type = MUST_READ;
|
||||
return Double.longBitsToDouble(EncodedValueCodec.readUnsignedLong(in, arg, true));
|
||||
}
|
||||
|
||||
public int readMethodType() {
|
||||
checkType(ENCODED_METHOD_TYPE);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readMethodHandle() {
|
||||
checkType(ENCODED_METHOD_HANDLE);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readString() {
|
||||
checkType(ENCODED_STRING);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readType() {
|
||||
checkType(ENCODED_TYPE);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readField() {
|
||||
checkType(ENCODED_FIELD);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readEnum() {
|
||||
checkType(ENCODED_ENUM);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public int readMethod() {
|
||||
checkType(ENCODED_METHOD);
|
||||
type = MUST_READ;
|
||||
return EncodedValueCodec.readUnsignedInt(in, arg, false);
|
||||
}
|
||||
|
||||
public void readNull() {
|
||||
checkType(ENCODED_NULL);
|
||||
type = MUST_READ;
|
||||
}
|
||||
|
||||
public boolean readBoolean() {
|
||||
checkType(ENCODED_BOOLEAN);
|
||||
type = MUST_READ;
|
||||
return arg != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skips a single value, including its nested values if it is an array or
|
||||
* annotation.
|
||||
*/
|
||||
public void skipValue() {
|
||||
switch (peek()) {
|
||||
case ENCODED_BYTE:
|
||||
readByte();
|
||||
break;
|
||||
case ENCODED_SHORT:
|
||||
readShort();
|
||||
break;
|
||||
case ENCODED_CHAR:
|
||||
readChar();
|
||||
break;
|
||||
case ENCODED_INT:
|
||||
readInt();
|
||||
break;
|
||||
case ENCODED_LONG:
|
||||
readLong();
|
||||
break;
|
||||
case ENCODED_FLOAT:
|
||||
readFloat();
|
||||
break;
|
||||
case ENCODED_DOUBLE:
|
||||
readDouble();
|
||||
break;
|
||||
case ENCODED_METHOD_TYPE:
|
||||
readMethodType();
|
||||
break;
|
||||
case ENCODED_METHOD_HANDLE:
|
||||
readMethodHandle();
|
||||
break;
|
||||
case ENCODED_STRING:
|
||||
readString();
|
||||
break;
|
||||
case ENCODED_TYPE:
|
||||
readType();
|
||||
break;
|
||||
case ENCODED_FIELD:
|
||||
readField();
|
||||
break;
|
||||
case ENCODED_ENUM:
|
||||
readEnum();
|
||||
break;
|
||||
case ENCODED_METHOD:
|
||||
readMethod();
|
||||
break;
|
||||
case ENCODED_ARRAY:
|
||||
for (int i = 0, size = readArray(); i < size; i++) {
|
||||
skipValue();
|
||||
}
|
||||
break;
|
||||
case ENCODED_ANNOTATION:
|
||||
for (int i = 0, size = readAnnotation(); i < size; i++) {
|
||||
readAnnotationName();
|
||||
skipValue();
|
||||
}
|
||||
break;
|
||||
case ENCODED_NULL:
|
||||
readNull();
|
||||
break;
|
||||
case ENCODED_BOOLEAN:
|
||||
readBoolean();
|
||||
break;
|
||||
default:
|
||||
throw new DexException("Unexpected type: " + Integer.toHexString(type));
|
||||
}
|
||||
}
|
||||
|
||||
private void checkType(int expected) {
|
||||
if (peek() != expected) {
|
||||
throw new IllegalStateException(
|
||||
String.format("Expected %x but was %x", expected, peek()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.Unsigned;
|
||||
|
||||
public final class FieldId implements Comparable<FieldId> {
|
||||
private final Dex dex;
|
||||
private final int declaringClassIndex;
|
||||
private final int typeIndex;
|
||||
private final int nameIndex;
|
||||
|
||||
public FieldId(Dex dex, int declaringClassIndex, int typeIndex, int nameIndex) {
|
||||
this.dex = dex;
|
||||
this.declaringClassIndex = declaringClassIndex;
|
||||
this.typeIndex = typeIndex;
|
||||
this.nameIndex = nameIndex;
|
||||
}
|
||||
|
||||
public int getDeclaringClassIndex() {
|
||||
return declaringClassIndex;
|
||||
}
|
||||
|
||||
public int getTypeIndex() {
|
||||
return typeIndex;
|
||||
}
|
||||
|
||||
public int getNameIndex() {
|
||||
return nameIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(FieldId other) {
|
||||
if (declaringClassIndex != other.declaringClassIndex) {
|
||||
return Unsigned.compare(declaringClassIndex, other.declaringClassIndex);
|
||||
}
|
||||
if (nameIndex != other.nameIndex) {
|
||||
return Unsigned.compare(nameIndex, other.nameIndex);
|
||||
}
|
||||
return Unsigned.compare(typeIndex, other.typeIndex); // should always be 0
|
||||
}
|
||||
|
||||
public void writeTo(Dex.Section out) {
|
||||
out.writeUnsignedShort(declaringClassIndex);
|
||||
out.writeUnsignedShort(typeIndex);
|
||||
out.writeInt(nameIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (dex == null) {
|
||||
return declaringClassIndex + " " + typeIndex + " " + nameIndex;
|
||||
}
|
||||
return dex.typeNames().get(typeIndex) + "." + dex.strings().get(nameIndex);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2008 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.ByteInput;
|
||||
import external.com.android.dex.util.ByteOutput;
|
||||
|
||||
/**
|
||||
* Reads and writes DWARFv3 LEB 128 signed and unsigned integers. See DWARF v3
|
||||
* section 7.6.
|
||||
*/
|
||||
public final class Leb128 {
|
||||
private Leb128() {
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of bytes in the unsigned LEB128 encoding of the
|
||||
* given value.
|
||||
*
|
||||
* @param value the value in question
|
||||
* @return its write size, in bytes
|
||||
*/
|
||||
public static int unsignedLeb128Size(int value) {
|
||||
// TODO: This could be much cleverer.
|
||||
|
||||
int remaining = value >> 7;
|
||||
int count = 0;
|
||||
|
||||
while (remaining != 0) {
|
||||
remaining >>= 7;
|
||||
count++;
|
||||
}
|
||||
|
||||
return count + 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an signed integer from {@code in}.
|
||||
*/
|
||||
public static int readSignedLeb128(ByteInput in) {
|
||||
int result = 0;
|
||||
int cur;
|
||||
int count = 0;
|
||||
int signBits = -1;
|
||||
|
||||
do {
|
||||
cur = in.readByte() & 0xff;
|
||||
result |= (cur & 0x7f) << (count * 7);
|
||||
signBits <<= 7;
|
||||
count++;
|
||||
} while (((cur & 0x80) == 0x80) && count < 5);
|
||||
|
||||
if ((cur & 0x80) == 0x80) {
|
||||
throw new DexException("invalid LEB128 sequence");
|
||||
}
|
||||
|
||||
// Sign extend if appropriate
|
||||
if (((signBits >> 1) & result) != 0 ) {
|
||||
result |= signBits;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads an unsigned integer from {@code in}.
|
||||
*/
|
||||
public static int readUnsignedLeb128(ByteInput in) {
|
||||
int result = 0;
|
||||
int cur;
|
||||
int count = 0;
|
||||
|
||||
do {
|
||||
cur = in.readByte() & 0xff;
|
||||
result |= (cur & 0x7f) << (count * 7);
|
||||
count++;
|
||||
} while (((cur & 0x80) == 0x80) && count < 5);
|
||||
|
||||
if ((cur & 0x80) == 0x80) {
|
||||
throw new DexException("invalid LEB128 sequence");
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@code value} as an unsigned integer to {@code out}, starting at
|
||||
* {@code offset}. Returns the number of bytes written.
|
||||
*/
|
||||
public static void writeUnsignedLeb128(ByteOutput out, int value) {
|
||||
int remaining = value >>> 7;
|
||||
|
||||
while (remaining != 0) {
|
||||
out.writeByte((byte) ((value & 0x7f) | 0x80));
|
||||
value = remaining;
|
||||
remaining >>>= 7;
|
||||
}
|
||||
|
||||
out.writeByte((byte) (value & 0x7f));
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes {@code value} as a signed integer to {@code out}, starting at
|
||||
* {@code offset}. Returns the number of bytes written.
|
||||
*/
|
||||
public static void writeSignedLeb128(ByteOutput out, int value) {
|
||||
int remaining = value >> 7;
|
||||
boolean hasMore = true;
|
||||
int end = ((value & Integer.MIN_VALUE) == 0) ? 0 : -1;
|
||||
|
||||
while (hasMore) {
|
||||
hasMore = (remaining != end)
|
||||
|| ((remaining & 1) != ((value >> 6) & 1));
|
||||
|
||||
out.writeByte((byte) ((value & 0x7f) | (hasMore ? 0x80 : 0)));
|
||||
value = remaining;
|
||||
remaining >>= 7;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,132 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.Dex.Section;
|
||||
import external.com.android.dex.util.Unsigned;
|
||||
|
||||
/**
|
||||
* A method_handle_item:
|
||||
* https://source.android.com/devices/tech/dalvik/dex-format#method-handle-item
|
||||
*/
|
||||
public class MethodHandle implements Comparable<MethodHandle> {
|
||||
|
||||
/**
|
||||
* A method handle type code:
|
||||
* https://source.android.com/devices/tech/dalvik/dex-format#method-handle-type-codes
|
||||
*/
|
||||
public enum MethodHandleType {
|
||||
METHOD_HANDLE_TYPE_STATIC_PUT(0x00),
|
||||
METHOD_HANDLE_TYPE_STATIC_GET(0x01),
|
||||
METHOD_HANDLE_TYPE_INSTANCE_PUT(0x02),
|
||||
METHOD_HANDLE_TYPE_INSTANCE_GET(0x03),
|
||||
METHOD_HANDLE_TYPE_INVOKE_STATIC(0x04),
|
||||
METHOD_HANDLE_TYPE_INVOKE_INSTANCE(0x05),
|
||||
METHOD_HANDLE_TYPE_INVOKE_DIRECT(0x06),
|
||||
METHOD_HANDLE_TYPE_INVOKE_CONSTRUCTOR(0x07),
|
||||
METHOD_HANDLE_TYPE_INVOKE_INTERFACE(0x08);
|
||||
|
||||
private final int value;
|
||||
|
||||
MethodHandleType(int value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
static MethodHandleType fromValue(int value) {
|
||||
for (MethodHandleType methodHandleType : values()) {
|
||||
if (methodHandleType.value == value) {
|
||||
return methodHandleType;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException(String.valueOf(value));
|
||||
}
|
||||
|
||||
public boolean isField() {
|
||||
switch (this) {
|
||||
case METHOD_HANDLE_TYPE_STATIC_PUT:
|
||||
case METHOD_HANDLE_TYPE_STATIC_GET:
|
||||
case METHOD_HANDLE_TYPE_INSTANCE_PUT:
|
||||
case METHOD_HANDLE_TYPE_INSTANCE_GET:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final Dex dex;
|
||||
private final MethodHandleType methodHandleType;
|
||||
private final int unused1;
|
||||
private final int fieldOrMethodId;
|
||||
private final int unused2;
|
||||
|
||||
public MethodHandle(
|
||||
Dex dex,
|
||||
MethodHandleType methodHandleType,
|
||||
int unused1,
|
||||
int fieldOrMethodId,
|
||||
int unused2) {
|
||||
this.dex = dex;
|
||||
this.methodHandleType = methodHandleType;
|
||||
this.unused1 = unused1;
|
||||
this.fieldOrMethodId = fieldOrMethodId;
|
||||
this.unused2 = unused2;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MethodHandle o) {
|
||||
if (methodHandleType != o.methodHandleType) {
|
||||
return methodHandleType.compareTo(o.methodHandleType);
|
||||
}
|
||||
return Unsigned.compare(fieldOrMethodId, o.fieldOrMethodId);
|
||||
}
|
||||
|
||||
public MethodHandleType getMethodHandleType() {
|
||||
return methodHandleType;
|
||||
}
|
||||
|
||||
public int getUnused1() {
|
||||
return unused1;
|
||||
}
|
||||
|
||||
public int getFieldOrMethodId() {
|
||||
return fieldOrMethodId;
|
||||
}
|
||||
|
||||
public int getUnused2() {
|
||||
return unused2;
|
||||
}
|
||||
|
||||
public void writeTo(Section out) {
|
||||
out.writeUnsignedShort(methodHandleType.value);
|
||||
out.writeUnsignedShort(unused1);
|
||||
out.writeUnsignedShort(fieldOrMethodId);
|
||||
out.writeUnsignedShort(unused2);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (dex == null) {
|
||||
return methodHandleType + " " + fieldOrMethodId;
|
||||
}
|
||||
return methodHandleType
|
||||
+ " "
|
||||
+ (methodHandleType.isField()
|
||||
? dex.fieldIds().get(fieldOrMethodId)
|
||||
: dex.methodIds().get(fieldOrMethodId));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.Unsigned;
|
||||
|
||||
public final class MethodId implements Comparable<MethodId> {
|
||||
private final Dex dex;
|
||||
private final int declaringClassIndex;
|
||||
private final int protoIndex;
|
||||
private final int nameIndex;
|
||||
|
||||
public MethodId(Dex dex, int declaringClassIndex, int protoIndex, int nameIndex) {
|
||||
this.dex = dex;
|
||||
this.declaringClassIndex = declaringClassIndex;
|
||||
this.protoIndex = protoIndex;
|
||||
this.nameIndex = nameIndex;
|
||||
}
|
||||
|
||||
public int getDeclaringClassIndex() {
|
||||
return declaringClassIndex;
|
||||
}
|
||||
|
||||
public int getProtoIndex() {
|
||||
return protoIndex;
|
||||
}
|
||||
|
||||
public int getNameIndex() {
|
||||
return nameIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(MethodId other) {
|
||||
if (declaringClassIndex != other.declaringClassIndex) {
|
||||
return Unsigned.compare(declaringClassIndex, other.declaringClassIndex);
|
||||
}
|
||||
if (nameIndex != other.nameIndex) {
|
||||
return Unsigned.compare(nameIndex, other.nameIndex);
|
||||
}
|
||||
return Unsigned.compare(protoIndex, other.protoIndex);
|
||||
}
|
||||
|
||||
public void writeTo(Dex.Section out) {
|
||||
out.writeUnsignedShort(declaringClassIndex);
|
||||
out.writeUnsignedShort(protoIndex);
|
||||
out.writeInt(nameIndex);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (dex == null) {
|
||||
return declaringClassIndex + " " + protoIndex + " " + nameIndex;
|
||||
}
|
||||
return dex.typeNames().get(declaringClassIndex)
|
||||
+ "." + dex.strings().get(nameIndex)
|
||||
+ dex.readTypeList(dex.protoIds().get(protoIndex).getParametersOffset());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.ByteInput;
|
||||
import java.io.UTFDataFormatException;
|
||||
|
||||
/**
|
||||
* Modified UTF-8 as described in the dex file format spec.
|
||||
*
|
||||
* <p>Derived from libcore's MUTF-8 encoder at java.nio.charset.ModifiedUtf8.
|
||||
*/
|
||||
public final class Mutf8 {
|
||||
private Mutf8() {}
|
||||
|
||||
/**
|
||||
* Decodes bytes from {@code in} into {@code out} until a delimiter 0x00 is
|
||||
* encountered. Returns a new string containing the decoded characters.
|
||||
*/
|
||||
public static String decode(ByteInput in, char[] out) throws UTFDataFormatException {
|
||||
int s = 0;
|
||||
while (true) {
|
||||
char a = (char) (in.readByte() & 0xff);
|
||||
if (a == 0) {
|
||||
return new String(out, 0, s);
|
||||
}
|
||||
out[s] = a;
|
||||
if (a < '\u0080') {
|
||||
s++;
|
||||
} else if ((a & 0xe0) == 0xc0) {
|
||||
int b = in.readByte() & 0xff;
|
||||
if ((b & 0xC0) != 0x80) {
|
||||
throw new UTFDataFormatException("bad second byte");
|
||||
}
|
||||
out[s++] = (char) (((a & 0x1F) << 6) | (b & 0x3F));
|
||||
} else if ((a & 0xf0) == 0xe0) {
|
||||
int b = in.readByte() & 0xff;
|
||||
int c = in.readByte() & 0xff;
|
||||
if (((b & 0xC0) != 0x80) || ((c & 0xC0) != 0x80)) {
|
||||
throw new UTFDataFormatException("bad second or third byte");
|
||||
}
|
||||
out[s++] = (char) (((a & 0x0F) << 12) | ((b & 0x3F) << 6) | (c & 0x3F));
|
||||
} else {
|
||||
throw new UTFDataFormatException("bad byte");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the number of bytes the modified UTF8 representation of 's' would take.
|
||||
*/
|
||||
private static long countBytes(String s, boolean shortLength) throws UTFDataFormatException {
|
||||
long result = 0;
|
||||
final int length = s.length();
|
||||
for (int i = 0; i < length; ++i) {
|
||||
char ch = s.charAt(i);
|
||||
if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
|
||||
++result;
|
||||
} else if (ch <= 2047) {
|
||||
result += 2;
|
||||
} else {
|
||||
result += 3;
|
||||
}
|
||||
if (shortLength && result > 65535) {
|
||||
throw new UTFDataFormatException("String more than 65535 UTF bytes long");
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encodes the modified UTF-8 bytes corresponding to {@code s} into {@code
|
||||
* dst}, starting at {@code offset}.
|
||||
*/
|
||||
public static void encode(byte[] dst, int offset, String s) {
|
||||
final int length = s.length();
|
||||
for (int i = 0; i < length; i++) {
|
||||
char ch = s.charAt(i);
|
||||
if (ch != 0 && ch <= 127) { // U+0000 uses two bytes.
|
||||
dst[offset++] = (byte) ch;
|
||||
} else if (ch <= 2047) {
|
||||
dst[offset++] = (byte) (0xc0 | (0x1f & (ch >> 6)));
|
||||
dst[offset++] = (byte) (0x80 | (0x3f & ch));
|
||||
} else {
|
||||
dst[offset++] = (byte) (0xe0 | (0x0f & (ch >> 12)));
|
||||
dst[offset++] = (byte) (0x80 | (0x3f & (ch >> 6)));
|
||||
dst[offset++] = (byte) (0x80 | (0x3f & ch));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array containing the <i>modified UTF-8</i> form of {@code s}.
|
||||
*/
|
||||
public static byte[] encode(String s) throws UTFDataFormatException {
|
||||
int utfCount = (int) countBytes(s, true);
|
||||
byte[] result = new byte[utfCount];
|
||||
encode(result, 0, s);
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,70 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.Unsigned;
|
||||
|
||||
public final class ProtoId implements Comparable<ProtoId> {
|
||||
private final Dex dex;
|
||||
private final int shortyIndex;
|
||||
private final int returnTypeIndex;
|
||||
private final int parametersOffset;
|
||||
|
||||
public ProtoId(Dex dex, int shortyIndex, int returnTypeIndex, int parametersOffset) {
|
||||
this.dex = dex;
|
||||
this.shortyIndex = shortyIndex;
|
||||
this.returnTypeIndex = returnTypeIndex;
|
||||
this.parametersOffset = parametersOffset;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(ProtoId other) {
|
||||
if (returnTypeIndex != other.returnTypeIndex) {
|
||||
return Unsigned.compare(returnTypeIndex, other.returnTypeIndex);
|
||||
}
|
||||
return Unsigned.compare(parametersOffset, other.parametersOffset);
|
||||
}
|
||||
|
||||
public int getShortyIndex() {
|
||||
return shortyIndex;
|
||||
}
|
||||
|
||||
public int getReturnTypeIndex() {
|
||||
return returnTypeIndex;
|
||||
}
|
||||
|
||||
public int getParametersOffset() {
|
||||
return parametersOffset;
|
||||
}
|
||||
|
||||
public void writeTo(Dex.Section out) {
|
||||
out.writeInt(shortyIndex);
|
||||
out.writeInt(returnTypeIndex);
|
||||
out.writeInt(parametersOffset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (dex == null) {
|
||||
return shortyIndex + " " + returnTypeIndex + " " + parametersOffset;
|
||||
}
|
||||
|
||||
return dex.strings().get(shortyIndex)
|
||||
+ ": " + dex.typeNames().get(returnTypeIndex)
|
||||
+ " " + dex.readTypeList(parametersOffset);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,123 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
public final class SizeOf {
|
||||
private SizeOf() {}
|
||||
|
||||
public static final int UBYTE = 1;
|
||||
public static final int USHORT = 2;
|
||||
public static final int UINT = 4;
|
||||
|
||||
public static final int SIGNATURE = UBYTE * 20;
|
||||
|
||||
/**
|
||||
* magic ubyte[8]
|
||||
* checksum uint
|
||||
* signature ubyte[20]
|
||||
* file_size uint
|
||||
* header_size uint
|
||||
* endian_tag uint
|
||||
* link_size uint
|
||||
* link_off uint
|
||||
* map_off uint
|
||||
* string_ids_size uint
|
||||
* string_ids_off uint
|
||||
* type_ids_size uint
|
||||
* type_ids_off uint
|
||||
* proto_ids_size uint
|
||||
* proto_ids_off uint
|
||||
* field_ids_size uint
|
||||
* field_ids_off uint
|
||||
* method_ids_size uint
|
||||
* method_ids_off uint
|
||||
* class_defs_size uint
|
||||
* class_defs_off uint
|
||||
* data_size uint
|
||||
* data_off uint
|
||||
*/
|
||||
public static final int HEADER_ITEM = (8 * UBYTE) + UINT + SIGNATURE + (20 * UINT); // 0x70
|
||||
|
||||
/**
|
||||
* string_data_off uint
|
||||
*/
|
||||
public static final int STRING_ID_ITEM = UINT;
|
||||
|
||||
/**
|
||||
* descriptor_idx uint
|
||||
*/
|
||||
public static final int TYPE_ID_ITEM = UINT;
|
||||
|
||||
/**
|
||||
* type_idx ushort
|
||||
*/
|
||||
public static final int TYPE_ITEM = USHORT;
|
||||
|
||||
/**
|
||||
* shorty_idx uint
|
||||
* return_type_idx uint
|
||||
* return_type_idx uint
|
||||
*/
|
||||
public static final int PROTO_ID_ITEM = UINT + UINT + UINT;
|
||||
|
||||
/**
|
||||
* class_idx ushort
|
||||
* type_idx/proto_idx ushort
|
||||
* name_idx uint
|
||||
*/
|
||||
public static final int MEMBER_ID_ITEM = USHORT + USHORT + UINT;
|
||||
|
||||
/**
|
||||
* class_idx uint
|
||||
* access_flags uint
|
||||
* superclass_idx uint
|
||||
* interfaces_off uint
|
||||
* source_file_idx uint
|
||||
* annotations_off uint
|
||||
* class_data_off uint
|
||||
* static_values_off uint
|
||||
*/
|
||||
public static final int CLASS_DEF_ITEM = 8 * UINT;
|
||||
|
||||
/**
|
||||
* type ushort
|
||||
* unused ushort
|
||||
* size uint
|
||||
* offset uint
|
||||
*/
|
||||
public static final int MAP_ITEM = USHORT + USHORT + UINT + UINT;
|
||||
|
||||
/**
|
||||
* start_addr uint
|
||||
* insn_count ushort
|
||||
* handler_off ushort
|
||||
*/
|
||||
public static final int TRY_ITEM = UINT + USHORT + USHORT;
|
||||
|
||||
/**
|
||||
* call_site_off uint
|
||||
*/
|
||||
public static final int CALL_SITE_ID_ITEM = UINT;
|
||||
|
||||
/**
|
||||
* method_handle_type ushort
|
||||
* unused ushort
|
||||
* field_or_method_id ushort
|
||||
* unused ushort
|
||||
*/
|
||||
public static final int METHOD_HANDLE_ITEM = USHORT + USHORT + USHORT + USHORT;
|
||||
}
|
||||
|
|
@ -1,246 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* The file header and map.
|
||||
*/
|
||||
public final class TableOfContents {
|
||||
|
||||
/*
|
||||
* TODO: factor out ID constants.
|
||||
*/
|
||||
|
||||
public final Section header = new Section(0x0000);
|
||||
public final Section stringIds = new Section(0x0001);
|
||||
public final Section typeIds = new Section(0x0002);
|
||||
public final Section protoIds = new Section(0x0003);
|
||||
public final Section fieldIds = new Section(0x0004);
|
||||
public final Section methodIds = new Section(0x0005);
|
||||
public final Section classDefs = new Section(0x0006);
|
||||
public final Section callSiteIds = new Section(0x0007);
|
||||
public final Section methodHandles = new Section(0x0008);
|
||||
public final Section mapList = new Section(0x1000);
|
||||
public final Section typeLists = new Section(0x1001);
|
||||
public final Section annotationSetRefLists = new Section(0x1002);
|
||||
public final Section annotationSets = new Section(0x1003);
|
||||
public final Section classDatas = new Section(0x2000);
|
||||
public final Section codes = new Section(0x2001);
|
||||
public final Section stringDatas = new Section(0x2002);
|
||||
public final Section debugInfos = new Section(0x2003);
|
||||
public final Section annotations = new Section(0x2004);
|
||||
public final Section encodedArrays = new Section(0x2005);
|
||||
public final Section annotationsDirectories = new Section(0x2006);
|
||||
public final Section[] sections = {
|
||||
header, stringIds, typeIds, protoIds, fieldIds, methodIds, classDefs, mapList, callSiteIds,
|
||||
methodHandles, typeLists, annotationSetRefLists, annotationSets, classDatas, codes,
|
||||
stringDatas, debugInfos, annotations, encodedArrays, annotationsDirectories
|
||||
};
|
||||
|
||||
public int apiLevel;
|
||||
public int checksum;
|
||||
public byte[] signature;
|
||||
public int fileSize;
|
||||
public int linkSize;
|
||||
public int linkOff;
|
||||
public int dataSize;
|
||||
public int dataOff;
|
||||
|
||||
public TableOfContents() {
|
||||
signature = new byte[20];
|
||||
}
|
||||
|
||||
public void readFrom(Dex dex) throws IOException {
|
||||
readHeader(dex.open(0));
|
||||
readMap(dex.open(mapList.off));
|
||||
computeSizesFromOffsets();
|
||||
}
|
||||
|
||||
private void readHeader(Dex.Section headerIn) throws UnsupportedEncodingException {
|
||||
byte[] magic = headerIn.readByteArray(8);
|
||||
|
||||
if (!DexFormat.isSupportedDexMagic(magic)) {
|
||||
String msg =
|
||||
String.format("Unexpected magic: [0x%02x, 0x%02x, 0x%02x, 0x%02x, "
|
||||
+ "0x%02x, 0x%02x, 0x%02x, 0x%02x]",
|
||||
magic[0], magic[1], magic[2], magic[3],
|
||||
magic[4], magic[5], magic[6], magic[7]);
|
||||
throw new DexException(msg);
|
||||
}
|
||||
|
||||
apiLevel = DexFormat.magicToApi(magic);
|
||||
checksum = headerIn.readInt();
|
||||
signature = headerIn.readByteArray(20);
|
||||
fileSize = headerIn.readInt();
|
||||
int headerSize = headerIn.readInt();
|
||||
if (headerSize != SizeOf.HEADER_ITEM) {
|
||||
throw new DexException("Unexpected header: 0x" + Integer.toHexString(headerSize));
|
||||
}
|
||||
int endianTag = headerIn.readInt();
|
||||
if (endianTag != DexFormat.ENDIAN_TAG) {
|
||||
throw new DexException("Unexpected endian tag: 0x" + Integer.toHexString(endianTag));
|
||||
}
|
||||
linkSize = headerIn.readInt();
|
||||
linkOff = headerIn.readInt();
|
||||
mapList.off = headerIn.readInt();
|
||||
if (mapList.off == 0) {
|
||||
throw new DexException("Cannot merge dex files that do not contain a map");
|
||||
}
|
||||
stringIds.size = headerIn.readInt();
|
||||
stringIds.off = headerIn.readInt();
|
||||
typeIds.size = headerIn.readInt();
|
||||
typeIds.off = headerIn.readInt();
|
||||
protoIds.size = headerIn.readInt();
|
||||
protoIds.off = headerIn.readInt();
|
||||
fieldIds.size = headerIn.readInt();
|
||||
fieldIds.off = headerIn.readInt();
|
||||
methodIds.size = headerIn.readInt();
|
||||
methodIds.off = headerIn.readInt();
|
||||
classDefs.size = headerIn.readInt();
|
||||
classDefs.off = headerIn.readInt();
|
||||
dataSize = headerIn.readInt();
|
||||
dataOff = headerIn.readInt();
|
||||
}
|
||||
|
||||
private void readMap(Dex.Section in) throws IOException {
|
||||
int mapSize = in.readInt();
|
||||
Section previous = null;
|
||||
for (int i = 0; i < mapSize; i++) {
|
||||
short type = in.readShort();
|
||||
in.readShort(); // unused
|
||||
Section section = getSection(type);
|
||||
int size = in.readInt();
|
||||
int offset = in.readInt();
|
||||
|
||||
if ((section.size != 0 && section.size != size)
|
||||
|| (section.off != -1 && section.off != offset)) {
|
||||
throw new DexException("Unexpected map value for 0x" + Integer.toHexString(type));
|
||||
}
|
||||
|
||||
section.size = size;
|
||||
section.off = offset;
|
||||
|
||||
if (previous != null && previous.off > section.off) {
|
||||
throw new DexException("Map is unsorted at " + previous + ", " + section);
|
||||
}
|
||||
|
||||
previous = section;
|
||||
}
|
||||
Arrays.sort(sections);
|
||||
}
|
||||
|
||||
public void computeSizesFromOffsets() {
|
||||
int end = dataOff + dataSize;
|
||||
for (int i = sections.length - 1; i >= 0; i--) {
|
||||
Section section = sections[i];
|
||||
if (section.off == -1) {
|
||||
continue;
|
||||
}
|
||||
if (section.off > end) {
|
||||
throw new DexException("Map is unsorted at " + section);
|
||||
}
|
||||
section.byteCount = end - section.off;
|
||||
end = section.off;
|
||||
}
|
||||
}
|
||||
|
||||
private Section getSection(short type) {
|
||||
for (Section section : sections) {
|
||||
if (section.type == type) {
|
||||
return section;
|
||||
}
|
||||
}
|
||||
throw new IllegalArgumentException("No such map item: " + type);
|
||||
}
|
||||
|
||||
public void writeHeader(Dex.Section out, int api) throws IOException {
|
||||
out.write(DexFormat.apiToMagic(api).getBytes("UTF-8"));
|
||||
out.writeInt(checksum);
|
||||
out.write(signature);
|
||||
out.writeInt(fileSize);
|
||||
out.writeInt(SizeOf.HEADER_ITEM);
|
||||
out.writeInt(DexFormat.ENDIAN_TAG);
|
||||
out.writeInt(linkSize);
|
||||
out.writeInt(linkOff);
|
||||
out.writeInt(mapList.off);
|
||||
out.writeInt(stringIds.size);
|
||||
out.writeInt(stringIds.off);
|
||||
out.writeInt(typeIds.size);
|
||||
out.writeInt(typeIds.off);
|
||||
out.writeInt(protoIds.size);
|
||||
out.writeInt(protoIds.off);
|
||||
out.writeInt(fieldIds.size);
|
||||
out.writeInt(fieldIds.off);
|
||||
out.writeInt(methodIds.size);
|
||||
out.writeInt(methodIds.off);
|
||||
out.writeInt(classDefs.size);
|
||||
out.writeInt(classDefs.off);
|
||||
out.writeInt(dataSize);
|
||||
out.writeInt(dataOff);
|
||||
}
|
||||
|
||||
public void writeMap(Dex.Section out) throws IOException {
|
||||
int count = 0;
|
||||
for (Section section : sections) {
|
||||
if (section.exists()) {
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
out.writeInt(count);
|
||||
for (Section section : sections) {
|
||||
if (section.exists()) {
|
||||
out.writeShort(section.type);
|
||||
out.writeShort((short) 0);
|
||||
out.writeInt(section.size);
|
||||
out.writeInt(section.off);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class Section implements Comparable<Section> {
|
||||
public final short type;
|
||||
public int size = 0;
|
||||
public int off = -1;
|
||||
public int byteCount = 0;
|
||||
|
||||
public Section(int type) {
|
||||
this.type = (short) type;
|
||||
}
|
||||
|
||||
public boolean exists() {
|
||||
return size > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(Section section) {
|
||||
if (off != section.off) {
|
||||
return off < section.off ? -1 : 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format("Section[type=%#x,off=%#x,size=%#x]", type, off, size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,57 +0,0 @@
|
|||
/*
|
||||
* Copyright (C) 2011 The Android Open Source Project
|
||||
*
|
||||
* 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 external.com.android.dex;
|
||||
|
||||
import external.com.android.dex.util.Unsigned;
|
||||
|
||||
public final class TypeList implements Comparable<TypeList> {
|
||||
|
||||
public static final TypeList EMPTY = new TypeList(null, Dex.EMPTY_SHORT_ARRAY);
|
||||
|
||||
private final Dex dex;
|
||||
private final short[] types;
|
||||
|
||||
public TypeList(Dex dex, short[] types) {
|
||||
this.dex = dex;
|
||||
this.types = types;
|
||||
}
|
||||
|
||||
public short[] getTypes() {
|
||||
return types;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int compareTo(TypeList other) {
|
||||
for (int i = 0; i < types.length && i < other.types.length; i++) {
|
||||
if (types[i] != other.types[i]) {
|
||||
return Unsigned.compare(types[i], other.types[i]);
|
||||
}
|
||||
}
|
||||
return Unsigned.compare(types.length, other.types.length);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
StringBuilder result = new StringBuilder();
|
||||
result.append("(");
|
||||
for (int i = 0, typesLength = types.length; i < typesLength; i++) {
|
||||
result.append(dex != null ? dex.typeNames().get(types[i]) : types[i]);
|
||||
}
|
||||
result.append(")");
|
||||
return result.toString();
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue