[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() {
|
public static boolean isPermissive() {
|
||||||
try {
|
try {
|
||||||
return LSPosedManagerServiceClient.isPermissive();
|
return LSPosedManagerServiceClient.isPermissive();
|
||||||
|
|
|
||||||
|
|
@ -115,16 +115,6 @@ public class LSPosedManagerServiceClient {
|
||||||
service.setVerboseLog(enabled);
|
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 {
|
public static boolean isPermissive() throws RemoteException, NullPointerException {
|
||||||
ensureService();
|
ensureService();
|
||||||
return service.isPermissive();
|
return service.isPermissive();
|
||||||
|
|
|
||||||
|
|
@ -92,7 +92,7 @@ public class MainActivity extends BaseActivity {
|
||||||
String installXposedVersion = ConfigManager.getXposedVersionName();
|
String installXposedVersion = ConfigManager.getXposedVersionName();
|
||||||
int cardBackgroundColor;
|
int cardBackgroundColor;
|
||||||
if (installXposedVersion != null) {
|
if (installXposedVersion != null) {
|
||||||
binding.statusTitle.setText(getString(R.string.Activated, ConfigManager.getVariantString()));
|
binding.statusTitle.setText(getString(R.string.Activated, "YAHFA"));
|
||||||
if (!ConfigManager.isPermissive()) {
|
if (!ConfigManager.isPermissive()) {
|
||||||
if (Helpers.currentHoliday == Helpers.Holidays.LUNARNEWYEAR) {
|
if (Helpers.currentHoliday == Helpers.Holidays.LUNARNEWYEAR) {
|
||||||
cardBackgroundColor = 0xfff05654;
|
cardBackgroundColor = 0xfff05654;
|
||||||
|
|
|
||||||
|
|
@ -41,7 +41,7 @@ val commitCount = Git(repo).log().add(refId).call().count()
|
||||||
|
|
||||||
val defaultManagerPackageName by extra("io.github.lsposed.manager")
|
val defaultManagerPackageName by extra("io.github.lsposed.manager")
|
||||||
val verCode by extra(commitCount + 4200)
|
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 androidTargetSdkVersion by extra(30)
|
||||||
val androidMinSdkVersion by extra(27)
|
val androidMinSdkVersion by extra(27)
|
||||||
val androidBuildToolsVersion by extra("30.0.3")
|
val androidBuildToolsVersion by extra("30.0.3")
|
||||||
|
|
|
||||||
|
|
@ -66,7 +66,6 @@ val verName: String by rootProject.extra
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation("dev.rikka.ndk:riru:10")
|
implementation("dev.rikka.ndk:riru:10")
|
||||||
implementation("com.android.tools.build:apksig:4.1.2")
|
implementation("com.android.tools.build:apksig:4.1.2")
|
||||||
implementation(project(":sandhook-hooklib"))
|
|
||||||
compileOnly(project(":hiddenapi-stubs"))
|
compileOnly(project(":hiddenapi-stubs"))
|
||||||
compileOnly("androidx.annotation:annotation:1.1.0")
|
compileOnly("androidx.annotation:annotation:1.1.0")
|
||||||
implementation(project(":interface"))
|
implementation(project(":interface"))
|
||||||
|
|
|
||||||
|
|
@ -5,8 +5,6 @@ interface ILSPApplicationService {
|
||||||
|
|
||||||
IBinder requestManagerBinder() = 3;
|
IBinder requestManagerBinder() = 3;
|
||||||
|
|
||||||
int getVariant() = 4;
|
|
||||||
|
|
||||||
boolean isResourcesHookEnabled() = 5;
|
boolean isResourcesHookEnabled() = 5;
|
||||||
|
|
||||||
String[] getModulesList(String processName) = 6;
|
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/include)
|
||||||
target_include_directories(dobby PUBLIC Dobby/builtin-plugin/BionicLinkerRestriction)
|
target_include_directories(dobby PUBLIC Dobby/builtin-plugin/BionicLinkerRestriction)
|
||||||
|
|
||||||
add_subdirectory(SandHook)
|
|
||||||
target_include_directories(sandhook.lspd PUBLIC SandHook)
|
|
||||||
|
|
||||||
add_subdirectory(DexBuilder)
|
add_subdirectory(DexBuilder)
|
||||||
target_include_directories(dex_builder PUBLIC 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_package(riru REQUIRED CONFIG)
|
||||||
find_library(log-lib log)
|
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) \
|
#define JNI_GetMethodID(env, class, name, sig) \
|
||||||
env->GetMethodID(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, ...) \
|
#define JNI_CallObjectMethod(env, obj, ...) \
|
||||||
env->CallObjectMethod(obj, __VA_ARGS__); \
|
env->CallObjectMethod(obj, __VA_ARGS__); \
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@
|
||||||
#include "jni_helper.h"
|
#include "jni_helper.h"
|
||||||
#include "jni/art_class_linker.h"
|
#include "jni/art_class_linker.h"
|
||||||
#include "jni/yahfa.h"
|
#include "jni/yahfa.h"
|
||||||
#include "jni/sandhook.h"
|
|
||||||
#include "jni/resources_hook.h"
|
#include "jni/resources_hook.h"
|
||||||
#include <dl_util.h>
|
#include <dl_util.h>
|
||||||
#include <art/runtime/jni_env_ext.h>
|
#include <art/runtime/jni_env_ext.h>
|
||||||
|
|
@ -122,7 +121,6 @@ namespace lspd {
|
||||||
RegisterYahfa(env);
|
RegisterYahfa(env);
|
||||||
RegisterPendingHooks(env);
|
RegisterPendingHooks(env);
|
||||||
RegisterNativeAPI(env);
|
RegisterNativeAPI(env);
|
||||||
RegisterSandHook(env);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
jclass
|
jclass
|
||||||
|
|
|
||||||
|
|
@ -44,7 +44,7 @@ namespace lspd {
|
||||||
|
|
||||||
static JNINativeMethod gMethods[] = {
|
static JNINativeMethod gMethods[] = {
|
||||||
LSP_NATIVE_METHOD(ClassLinker, setEntryPointsToInterpreter,
|
LSP_NATIVE_METHOD(ClassLinker, setEntryPointsToInterpreter,
|
||||||
"(Ljava/lang/reflect/Member;)V")
|
"(Ljava/lang/reflect/Executable;)V")
|
||||||
};
|
};
|
||||||
|
|
||||||
void RegisterArtClassLinker(JNIEnv *env) {
|
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[] = {
|
static JNINativeMethod gMethods[] = {
|
||||||
LSP_NATIVE_METHOD(Yahfa, init, "(I)V"),
|
LSP_NATIVE_METHOD(Yahfa, init, "(I)V"),
|
||||||
LSP_NATIVE_METHOD(Yahfa, findMethodNative,
|
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,
|
LSP_NATIVE_METHOD(Yahfa, backupAndHookNative,
|
||||||
"(Ljava/lang/Object;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
|
"(Ljava/lang/reflect/Executable;Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)Z"),
|
||||||
LSP_NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Member;)V"),
|
LSP_NATIVE_METHOD(Yahfa, recordHooked, "(Ljava/lang/reflect/Executable;)V"),
|
||||||
LSP_NATIVE_METHOD(Yahfa, isHooked, "(Ljava/lang/reflect/Member;)Z"),
|
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;"),
|
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;
|
package de.robv.android.xposed;
|
||||||
|
|
||||||
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.Member;
|
import java.lang.reflect.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
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;
|
import static io.github.lsposed.lspd.nativebridge.PendingHooks.recordPendingMethodNative;
|
||||||
|
|
||||||
public final class PendingHooks {
|
public final class PendingHooks {
|
||||||
|
|
||||||
// GuardedBy("PendingHooks.class")
|
// GuardedBy("PendingHooks.class")
|
||||||
private static final ConcurrentHashMap<Class<?>, ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo>>
|
private static final ConcurrentHashMap<Class<?>, ConcurrentHashMap<Executable, XposedBridge.AdditionalHookInfo>>
|
||||||
sPendingHooks = new ConcurrentHashMap<>();
|
sPendingHooks = new ConcurrentHashMap<>();
|
||||||
|
|
||||||
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
public synchronized static void hookPendingMethod(Class<?> clazz) {
|
||||||
if (sPendingHooks.containsKey(clazz)) {
|
if (sPendingHooks.containsKey(clazz)) {
|
||||||
for (Map.Entry<Member, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
for (Map.Entry<Executable, XposedBridge.AdditionalHookInfo> hook : sPendingHooks.get(clazz).entrySet()) {
|
||||||
LSPdConfigGlobal.getHookProvider().hookMethod(hook.getKey(), hook.getValue());
|
YahfaHooker.hookMethod(hook.getKey(), hook.getValue());
|
||||||
}
|
}
|
||||||
sPendingHooks.remove(clazz);
|
sPendingHooks.remove(clazz);
|
||||||
}
|
}
|
||||||
|
|
@ -46,7 +47,7 @@ public final class PendingHooks {
|
||||||
|
|
||||||
public synchronized static void recordPendingMethod(Method hookMethod,
|
public synchronized static void recordPendingMethod(Method hookMethod,
|
||||||
XposedBridge.AdditionalHookInfo additionalInfo) {
|
XposedBridge.AdditionalHookInfo additionalInfo) {
|
||||||
ConcurrentHashMap<Member, XposedBridge.AdditionalHookInfo> pending =
|
ConcurrentHashMap<Executable, XposedBridge.AdditionalHookInfo> pending =
|
||||||
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(), aClass -> new ConcurrentHashMap<>());
|
sPendingHooks.computeIfAbsent(hookMethod.getDeclaringClass(), aClass -> new ConcurrentHashMap<>());
|
||||||
|
|
||||||
pending.put(hookMethod, additionalInfo);
|
pending.put(hookMethod, additionalInfo);
|
||||||
|
|
|
||||||
|
|
@ -25,31 +25,26 @@ import android.content.res.TypedArray;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
|
|
||||||
import io.github.lsposed.lspd.BuildConfig;
|
import io.github.lsposed.lspd.BuildConfig;
|
||||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
|
||||||
|
|
||||||
import java.lang.reflect.AccessibleObject;
|
import java.lang.reflect.AccessibleObject;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Executable;
|
||||||
import java.lang.reflect.InvocationTargetException;
|
import java.lang.reflect.InvocationTargetException;
|
||||||
import java.lang.reflect.Member;
|
import java.lang.reflect.Member;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
import java.lang.reflect.Modifier;
|
||||||
import java.nio.ByteBuffer;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
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_InitPackageResources;
|
||||||
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
import de.robv.android.xposed.callbacks.XC_InitZygote;
|
||||||
import de.robv.android.xposed.callbacks.XC_LoadPackage;
|
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.ModuleLogger;
|
||||||
import io.github.lsposed.lspd.nativebridge.ResourcesHook;
|
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;
|
import static de.robv.android.xposed.XposedHelpers.setObjectField;
|
||||||
|
|
||||||
|
|
@ -91,7 +86,6 @@ public final class XposedBridge {
|
||||||
|
|
||||||
public static volatile ClassLoader dummyClassLoader = null;
|
public static volatile ClassLoader dummyClassLoader = null;
|
||||||
|
|
||||||
@ApiSensitive(Level.MIDDLE)
|
|
||||||
public static void initXResources() {
|
public static void initXResources() {
|
||||||
if (dummyClassLoader != null) {
|
if (dummyClassLoader != null) {
|
||||||
return;
|
return;
|
||||||
|
|
@ -108,22 +102,10 @@ public final class XposedBridge {
|
||||||
} catch (Resources.NotFoundException nfe) {
|
} catch (Resources.NotFoundException nfe) {
|
||||||
XposedBridge.log(nfe);
|
XposedBridge.log(nfe);
|
||||||
}
|
}
|
||||||
XposedBridge.removeFinalFlagNative(resClass);
|
ResourcesHook.removeFinalFlagNative(resClass);
|
||||||
XposedBridge.removeFinalFlagNative(taClass);
|
ResourcesHook.removeFinalFlagNative(taClass);
|
||||||
ClassLoader myCL = XposedBridge.class.getClassLoader();
|
ClassLoader myCL = XposedBridge.class.getClassLoader();
|
||||||
dummyClassLoader = ResourcesHook.buildDummyClassLoader(myCL.getParent(), resClass, taClass);
|
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.XResourcesSuperClass");
|
||||||
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
|
dummyClassLoader.loadClass("xposed.dummy.XTypedArraySuperClass");
|
||||||
setObjectField(myCL, "parent", dummyClassLoader);
|
setObjectField(myCL, "parent", dummyClassLoader);
|
||||||
|
|
@ -183,7 +165,7 @@ public final class XposedBridge {
|
||||||
* @see #hookAllConstructors
|
* @see #hookAllConstructors
|
||||||
*/
|
*/
|
||||||
public static XC_MethodHook.Unhook hookMethod(Member hookMethod, XC_MethodHook callback) {
|
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());
|
throw new IllegalArgumentException("Only methods and constructors can be hooked: " + hookMethod.toString());
|
||||||
} else if (hookMethod.getDeclaringClass().isInterface()) {
|
} else if (hookMethod.getDeclaringClass().isInterface()) {
|
||||||
throw new IllegalArgumentException("Cannot hook interfaces: " + hookMethod.toString());
|
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());
|
throw new IllegalArgumentException("Cannot hook abstract methods: " + hookMethod.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Executable targetMethod = (Executable) hookMethod;
|
||||||
|
|
||||||
if (callback == null) {
|
if (callback == null) {
|
||||||
throw new IllegalArgumentException("callback should not be null!");
|
throw new IllegalArgumentException("callback should not be null!");
|
||||||
}
|
}
|
||||||
|
|
@ -198,10 +182,10 @@ public final class XposedBridge {
|
||||||
boolean newMethod = false;
|
boolean newMethod = false;
|
||||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||||
synchronized (sHookedMethodCallbacks) {
|
synchronized (sHookedMethodCallbacks) {
|
||||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
callbacks = sHookedMethodCallbacks.get(targetMethod);
|
||||||
if (callbacks == null) {
|
if (callbacks == null) {
|
||||||
callbacks = new CopyOnWriteSortedSet<>();
|
callbacks = new CopyOnWriteSortedSet<>();
|
||||||
sHookedMethodCallbacks.put(hookMethod, callbacks);
|
sHookedMethodCallbacks.put(targetMethod, callbacks);
|
||||||
newMethod = true;
|
newMethod = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -209,9 +193,8 @@ public final class XposedBridge {
|
||||||
|
|
||||||
if (newMethod) {
|
if (newMethod) {
|
||||||
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
|
AdditionalHookInfo additionalInfo = new AdditionalHookInfo(callbacks);
|
||||||
Member reflectMethod = LSPdConfigGlobal.getHookProvider().findMethodNative(hookMethod);
|
if (!YahfaHooker.shouldDelayHook(targetMethod)) {
|
||||||
if (reflectMethod != null) {
|
DynamicBridge.hookMethod(targetMethod, (AdditionalHookInfo) additionalInfo);
|
||||||
LSPdConfigGlobal.getHookProvider().hookMethod(reflectMethod, (AdditionalHookInfo) additionalInfo);
|
|
||||||
} else {
|
} else {
|
||||||
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
|
PendingHooks.recordPendingMethod((Method)hookMethod, additionalInfo);
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +214,6 @@ public final class XposedBridge {
|
||||||
*/
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
public static void unhookMethod(Member hookMethod, XC_MethodHook callback) {
|
||||||
LSPdConfigGlobal.getHookProvider().unhookMethod(hookMethod);
|
|
||||||
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
CopyOnWriteSortedSet<XC_MethodHook> callbacks;
|
||||||
synchronized (sHookedMethodCallbacks) {
|
synchronized (sHookedMethodCallbacks) {
|
||||||
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
callbacks = sHookedMethodCallbacks.get(hookMethod);
|
||||||
|
|
@ -348,16 +330,11 @@ public final class XposedBridge {
|
||||||
args = EMPTY_ARRAY;
|
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");
|
throw new IllegalArgumentException("method must be of type Method or Constructor");
|
||||||
}
|
}
|
||||||
|
|
||||||
long methodId = LSPdConfigGlobal.getHookProvider().getMethodId(method);
|
return YahfaHooker.invokeOriginalMethod((Executable) method, thisObject, args);
|
||||||
return LSPdConfigGlobal.getHookProvider().invokeOriginalMethod(method, methodId, thisObject, args);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void removeFinalFlagNative(Class clazz) {
|
|
||||||
LSPdConfigGlobal.getHookProvider().removeFinalFlagNative(clazz);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @hide */
|
/** @hide */
|
||||||
|
|
|
||||||
|
|
@ -23,12 +23,9 @@ package de.robv.android.xposed;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.content.res.Resources;
|
import android.content.res.Resources;
|
||||||
|
|
||||||
import java.io.BufferedReader;
|
|
||||||
import java.io.ByteArrayOutputStream;
|
import java.io.ByteArrayOutputStream;
|
||||||
import java.io.Closeable;
|
import java.io.Closeable;
|
||||||
import java.io.File;
|
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileReader;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
@ -41,15 +38,11 @@ import java.math.BigInteger;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
import java.util.WeakHashMap;
|
import java.util.WeakHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
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.ClassUtils;
|
||||||
import external.org.apache.commons.lang3.reflect.MemberUtils;
|
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.XC_LoadPackage;
|
||||||
import de.robv.android.xposed.callbacks.XCallback;
|
import de.robv.android.xposed.callbacks.XCallback;
|
||||||
import hidden.HiddenApiBridge;
|
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.NativeAPI;
|
||||||
|
import io.github.lsposed.lspd.nativebridge.ResourcesHook;
|
||||||
|
|
||||||
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
import static de.robv.android.xposed.XposedBridge.hookAllMethods;
|
||||||
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
import static de.robv.android.xposed.XposedBridge.sInitPackageResourcesCallbacks;
|
||||||
|
|
@ -106,13 +104,12 @@ public final class XposedInit {
|
||||||
hookResources();
|
hookResources();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiSensitive(Level.MIDDLE)
|
|
||||||
private static void hookResources() throws Throwable {
|
private static void hookResources() throws Throwable {
|
||||||
if (!serviceClient.isResourcesHookEnabled() || disableResources) {
|
if (!serviceClient.isResourcesHookEnabled() || disableResources) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!LSPdConfigGlobal.getHookProvider().initXResourcesNative()) {
|
if (!ResourcesHook.initXResourcesNative()) {
|
||||||
Log.e(TAG, "Cannot hook resources");
|
Log.e(TAG, "Cannot hook resources");
|
||||||
disableResources = true;
|
disableResources = true;
|
||||||
return;
|
return;
|
||||||
|
|
@ -209,7 +206,6 @@ public final class XposedInit {
|
||||||
XResources.init(latestResKey);
|
XResources.init(latestResKey);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ApiSensitive(Level.MIDDLE)
|
|
||||||
private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) {
|
private static XResources cloneToXResources(XC_MethodHook.MethodHookParam param, String resDir) {
|
||||||
Object result = param.getResult();
|
Object result = param.getResult();
|
||||||
if (result == null || result instanceof XResources ||
|
if (result == null || result instanceof XResources ||
|
||||||
|
|
|
||||||
|
|
@ -22,13 +22,12 @@ package de.robv.android.xposed.callbacks;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
|
||||||
import io.github.lsposed.lspd.config.LSPdConfigGlobal;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
|
||||||
import de.robv.android.xposed.IModuleContext;
|
import de.robv.android.xposed.IModuleContext;
|
||||||
import de.robv.android.xposed.XposedBridge;
|
import de.robv.android.xposed.XposedBridge;
|
||||||
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
import de.robv.android.xposed.XposedBridge.CopyOnWriteSortedSet;
|
||||||
|
import io.github.lsposed.lspd.deopt.PrebuiltMethodsDeopter;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Base class for Xposed callbacks.
|
* 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
|
// deopt methods in system apps or priv-apps, this would be not necessary
|
||||||
// only if we found out how to recompile their apks
|
// only if we found out how to recompile their apks
|
||||||
XC_LoadPackage.LoadPackageParam lpp = (XC_LoadPackage.LoadPackageParam) param;
|
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)
|
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