This commit is contained in:
chiteroman 2023-11-20 00:42:21 +01:00
parent 05bdfd3ff1
commit bd55606e21
88 changed files with 17440 additions and 6 deletions

15
.gitignore vendored Normal file
View File

@ -0,0 +1,15 @@
*.iml
.gradle
/local.properties
/.idea/caches
/.idea/libraries
/.idea/modules.xml
/.idea/workspace.xml
/.idea/navEditor.xml
/.idea/assetWizardSettings.xml
.DS_Store
/build
/captures
.externalNativeBuild
.cxx
local.properties

3
.idea/.gitignore vendored Normal file
View File

@ -0,0 +1,3 @@
# Default ignored files
/shelf/
/workspace.xml

6
.idea/compiler.xml Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<bytecodeTargetLevel target="17" />
</component>
</project>

20
.idea/gradle.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleMigrationSettings" migrationVersion="1" />
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="jbr-17" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
</GradleProjectSettings>
</option>
</component>
</project>

View File

@ -0,0 +1,20 @@
<component name="InspectionProjectProfileManager">
<profile version="1.0">
<option name="myName" value="Project Default" />
<inspection_tool class="FieldCanBeLocal" enabled="true" level="WARNING" enabled_by_default="true">
<option name="EXCLUDE_ANNOS">
<value>
<list size="1">
<item index="0" class="java.lang.String" itemvalue="android.annotation.TargetApi" />
</list>
</value>
</option>
<option name="IGNORE_FIELDS_USED_IN_MULTIPLE_METHODS" value="true" />
</inspection_tool>
<inspection_tool class="SpellCheckingInspection" enabled="false" level="TYPO" enabled_by_default="false">
<option name="processCode" value="true" />
<option name="processLiterals" value="true" />
<option name="processComments" value="true" />
</inspection_tool>
</profile>
</component>

9
.idea/misc.xml Normal file
View File

@ -0,0 +1,9 @@
<project version="4">
<component name="ExternalStorageConfigurationManager" enabled="true" />
<component name="ProjectRootManager" version="2" languageLevel="JDK_17" default="true" project-jdk-name="jbr-17" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
</project>

7
.idea/vcs.xml Normal file
View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$/app/src/main/cpp/libcxx" vcs="Git" />
</component>
</project>

1
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

45
app/build.gradle.kts Normal file
View File

@ -0,0 +1,45 @@
plugins {
id("com.android.application")
}
android {
namespace = "es.chiteroman.playintegrityfix"
compileSdk = 34
ndkVersion = "26.1.10909125"
buildToolsVersion = "34.0.0"
defaultConfig {
applicationId = "es.chiteroman.playintegrityfix"
minSdk = 26
targetSdk = 34
versionCode = 1
versionName = "1.0"
externalNativeBuild {
ndk {
//noinspection ChromeOsAbiSupport
abiFilters += setOf("armeabi-v7a", "arm64-v8a")
jobs = 4
}
}
}
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
}
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
externalNativeBuild {
ndkBuild {
path = file("src/main/cpp/Android.mk")
}
}
}

3
app/proguard-rules.pro vendored Normal file
View File

@ -0,0 +1,3 @@
-keep class es.chiteroman.playintegrityfix.EntryPoint {init();}
-keep class es.chiteroman.playintegrityfix.CustomProvider
-keep class es.chiteroman.playintegrityfix.CustomKeyStoreSpi

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest />

View File

@ -0,0 +1,33 @@
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := zygisk
LOCAL_SRC_FILES := main.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/*.c)
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/common/*.c)
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/third_party/xdl/*.c)
ifeq ($(TARGET_ARCH_ABI),armeabi-v7a)
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/arch/arm/*.c)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/arch/arm
endif
ifeq ($(TARGET_ARCH_ABI),arm64-v8a)
LOCAL_SRC_FILES += $(wildcard $(LOCAL_PATH)/shadowhook/arch/arm64/*.c)
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/arch/arm64
endif
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/common
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/include
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/third_party/bsd
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/third_party/lss
LOCAL_C_INCLUDES += $(LOCAL_PATH)/shadowhook/third_party/xdl
LOCAL_STATIC_LIBRARIES := libcxx
LOCAL_LDLIBS := -llog
include $(BUILD_SHARED_LIBRARY)
include $(LOCAL_PATH)/libcxx/Android.mk

View File

@ -0,0 +1,5 @@
APP_ABI := armeabi-v7a arm64-v8a
APP_CFLAGS := -DNDEBUG -Oz -fvisibility=hidden -fvisibility-inlines-hidden -ffunction-sections -fdata-sections
APP_CPPFLAGS := -std=c++20 -fno-exceptions -fno-rtti
APP_STL := none
APP_PLATFORM := android-26

@ -0,0 +1 @@
Subproject commit 12c8f4e93f196a700137e983dcceeac43cf807f2

211
app/src/main/cpp/main.cpp Normal file
View File

@ -0,0 +1,211 @@
#include <android/log.h>
#include <sys/system_properties.h>
#include <unistd.h>
#include <string>
#include <vector>
#include <filesystem>
#include "zygisk.hpp"
#include "shadowhook.h"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, "PIF/Native", __VA_ARGS__)
#define FIRST_API_LEVEL "32"
#define DEX_FILE_PATH "/data/adb/modules/playintegrityfix/classes.dex"
#define PROP_FILE_PATH "/data/adb/modules/playintegrityfix/pif.prop"
typedef void (*T_Callback)(void *, const char *, const char *, uint32_t);
static volatile T_Callback propCallback = nullptr;
static void modify_callback(void *cookie, const char *name, const char *value, uint32_t serial) {
if (cookie == nullptr || name == nullptr || value == nullptr || propCallback == nullptr) return;
std::string_view prop(name);
if (prop.compare("ro.product.first_api_level") == 0) {
LOGD("Property '%s' with value '%s' is now spoofed to '%s'", name, value, FIRST_API_LEVEL);
value = FIRST_API_LEVEL;
}
if (!prop.starts_with("cache")) LOGD("[%s] -> %s", name, value);
return propCallback(cookie, name, value, serial);
}
static void (*o_system_property_read_callback)(const prop_info *, T_Callback, void *);
static void
my_system_property_read_callback(const prop_info *pi, T_Callback callback, void *cookie) {
if (pi == nullptr || callback == nullptr || cookie == nullptr) {
return o_system_property_read_callback(pi, callback, cookie);
}
propCallback = callback;
return o_system_property_read_callback(pi, modify_callback, cookie);
}
static void doHook() {
shadowhook_init(SHADOWHOOK_MODE_UNIQUE, true);
void *handle = shadowhook_hook_sym_name(
"libc.so",
"__system_property_read_callback",
reinterpret_cast<void *>(my_system_property_read_callback),
reinterpret_cast<void **>(&o_system_property_read_callback)
);
if (handle == nullptr) {
LOGD("Couldn't find '__system_property_read_callback' handle. Report to @chiteroman");
return;
}
LOGD("Found '__system_property_read_callback' handle at %p", handle);
}
static bool needHook() {
char rawApi[2];
if (__system_property_get("ro.product.first_api_level", rawApi) < 1) return true;
int api = std::stoi(rawApi);
return api > 32;
}
class PlayIntegrityFix : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
auto rawProcess = env->GetStringUTFChars(args->nice_name, nullptr);
std::string_view process(rawProcess);
bool isGms = process.starts_with("com.google.android.gms");
isGmsUnstable = process.compare("com.google.android.gms.unstable") == 0;
env->ReleaseStringUTFChars(args->nice_name, rawProcess);
if (isGms) api->setOption(zygisk::FORCE_DENYLIST_UNMOUNT);
if (isGmsUnstable) {
auto rawDir = env->GetStringUTFChars(args->app_data_dir, nullptr);
std::string dir(rawDir);
env->ReleaseStringUTFChars(args->app_data_dir, rawDir);
LOGD("GMS data dir: %s", dir.c_str());
int fd = api->connectCompanion();
int strSize = static_cast<int>(dir.size());
write(fd, &strSize, sizeof(strSize));
write(fd, dir.data(), dir.size());
dir.clear();
dir.shrink_to_fit();
long size;
read(fd, &size, sizeof(size));
moduleDex.resize(size);
read(fd, moduleDex.data(), size);
close(fd);
hook = needHook();
if (hook) return;
}
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
}
void postAppSpecialize(const zygisk::AppSpecializeArgs *args) override {
if (!isGmsUnstable) return;
if (hook) doHook();
if (!moduleDex.empty()) injectDex();
}
void preServerSpecialize(zygisk::ServerSpecializeArgs *args) override {
api->setOption(zygisk::DLCLOSE_MODULE_LIBRARY);
}
private:
zygisk::Api *api = nullptr;
JNIEnv *env = nullptr;
bool isGmsUnstable = false;
bool hook = false;
std::vector<char> moduleDex;
void injectDex() {
LOGD("get system classloader");
auto clClass = env->FindClass("java/lang/ClassLoader");
auto getSystemClassLoader = env->GetStaticMethodID(clClass, "getSystemClassLoader",
"()Ljava/lang/ClassLoader;");
auto systemClassLoader = env->CallStaticObjectMethod(clClass, getSystemClassLoader);
LOGD("create buffer");
auto buf = env->NewDirectByteBuffer(moduleDex.data(), static_cast<jlong>(moduleDex.size()));
LOGD("create class loader");
auto dexClClass = env->FindClass("dalvik/system/InMemoryDexClassLoader");
auto dexClInit = env->GetMethodID(dexClClass, "<init>",
"(Ljava/nio/ByteBuffer;Ljava/lang/ClassLoader;)V");
auto dexCl = env->NewObject(dexClClass, dexClInit, buf, systemClassLoader);
LOGD("load class");
auto loadClass = env->GetMethodID(clClass, "loadClass",
"(Ljava/lang/String;)Ljava/lang/Class;");
auto entryClassName = env->NewStringUTF("es.chiteroman.playintegrityfix.EntryPoint");
auto entryClassObj = env->CallObjectMethod(dexCl, loadClass, entryClassName);
LOGD("call init");
auto entryClass = (jclass) entryClassObj;
auto entryInit = env->GetStaticMethodID(entryClass, "init", "()V");
env->CallStaticVoidMethod(entryClass, entryInit);
LOGD("Injected %d bytes to the process", static_cast<int>(moduleDex.size()));
}
};
static void companion(int fd) {
int strSize;
read(fd, &strSize, sizeof(strSize));
std::string propFile;
propFile.resize(strSize);
read(fd, propFile.data(), strSize);
propFile = propFile + "/cache/pif.prop";
std::filesystem::copy_file(PROP_FILE_PATH, propFile,
std::filesystem::copy_options::overwrite_existing);
std::filesystem::permissions(propFile, std::filesystem::perms::owner_read |
std::filesystem::perms::group_read |
std::filesystem::perms::others_read);
propFile.clear();
propFile.shrink_to_fit();
FILE *file = fopen(DEX_FILE_PATH, "rb");
fseek(file, 0, SEEK_END);
long size = ftell(file);
fseek(file, 0, SEEK_SET);
char buffer[size];
fread(buffer, 1, size, file);
fclose(file);
write(fd, &size, sizeof(size));
write(fd, buffer, size);
}
REGISTER_ZYGISK_MODULE(PlayIntegrityFix)
REGISTER_ZYGISK_COMPANION(companion)

View File

@ -0,0 +1,446 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_a32.h"
#include <inttypes.h>
#include <sh_util.h>
#include <stdint.h>
#include "sh_log.h"
// https://developer.arm.com/documentation/ddi0406/latest
// https://developer.arm.com/documentation/ddi0597/latest
typedef enum {
IGNORED = 0,
B_A1,
BX_A1,
BL_IMM_A1,
BLX_IMM_A2,
ADD_REG_A1,
ADD_REG_PC_A1,
SUB_REG_A1,
SUB_REG_PC_A1,
ADR_A1,
ADR_A2,
MOV_REG_A1,
MOV_REG_PC_A1,
LDR_LIT_A1,
LDR_LIT_PC_A1,
LDRB_LIT_A1,
LDRD_LIT_A1,
LDRH_LIT_A1,
LDRSB_LIT_A1,
LDRSH_LIT_A1,
LDR_REG_A1,
LDR_REG_PC_A1,
LDRB_REG_A1,
LDRD_REG_A1,
LDRH_REG_A1,
LDRSB_REG_A1,
LDRSH_REG_A1
} sh_a32_type_t;
static sh_a32_type_t sh_a32_get_type(uint32_t inst) {
if (((inst & 0x0F000000u) == 0x0A000000) && ((inst & 0xF0000000) != 0xF0000000))
return B_A1;
else if (((inst & 0x0FFFFFFFu) == 0x012FFF1F) && ((inst & 0xF0000000) != 0xF0000000))
return BX_A1;
else if (((inst & 0x0F000000u) == 0x0B000000) && ((inst & 0xF0000000) != 0xF0000000))
return BL_IMM_A1;
else if ((inst & 0xFE000000) == 0xFA000000)
return BLX_IMM_A2;
else if (((inst & 0x0FE00010u) == 0x00800000) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x0010F000u) != 0x0010F000) && ((inst & 0x000F0000u) != 0x000D0000) &&
(((inst & 0x000F0000u) == 0x000F0000) || ((inst & 0x0000000Fu) == 0x0000000F)))
return ((inst & 0x0000F000u) == 0x0000F000) ? ADD_REG_PC_A1 : ADD_REG_A1;
else if (((inst & 0x0FE00010u) == 0x00400000) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x0010F000u) != 0x0010F000) && ((inst & 0x000F0000u) != 0x000D0000) &&
(((inst & 0x000F0000u) == 0x000F0000) || ((inst & 0x0000000Fu) == 0x0000000F)))
return ((inst & 0x0000F000u) == 0x0000F000) ? SUB_REG_PC_A1 : SUB_REG_A1;
else if (((inst & 0x0FFF0000u) == 0x028F0000) && ((inst & 0xF0000000) != 0xF0000000))
return ADR_A1;
else if (((inst & 0x0FFF0000u) == 0x024F0000) && ((inst & 0xF0000000) != 0xF0000000))
return ADR_A2;
else if (((inst & 0x0FEF001Fu) == 0x01A0000F) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x0010F000u) != 0x0010F000) &&
(!(((inst & 0x0000F000u) == 0x0000F000) && ((inst & 0x00000FF0u) != 0x00000000))))
return ((inst & 0x0000F000u) == 0x0000F000) ? MOV_REG_PC_A1 : MOV_REG_A1;
else if (((inst & 0x0F7F0000u) == 0x051F0000) && ((inst & 0xF0000000) != 0xF0000000))
return ((inst & 0x0000F000u) == 0x0000F000) ? LDR_LIT_PC_A1 : LDR_LIT_A1;
else if (((inst & 0x0F7F0000u) == 0x055F0000) && ((inst & 0xF0000000) != 0xF0000000))
return LDRB_LIT_A1;
else if (((inst & 0x0F7F00F0u) == 0x014F00D0) && ((inst & 0xF0000000) != 0xF0000000))
return LDRD_LIT_A1;
else if (((inst & 0x0F7F00F0u) == 0x015F00B0) && ((inst & 0xF0000000) != 0xF0000000))
return LDRH_LIT_A1;
else if (((inst & 0x0F7F00F0u) == 0x015F00D0) && ((inst & 0xF0000000) != 0xF0000000))
return LDRSB_LIT_A1;
else if (((inst & 0x0F7F00F0u) == 0x015F00F0) && ((inst & 0xF0000000) != 0xF0000000))
return LDRSH_LIT_A1;
else if (((inst & 0x0E5F0010u) == 0x061F0000) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x01200000u) != 0x00200000))
return ((inst & 0x0000F000u) == 0x0000F000) ? LDR_REG_PC_A1 : LDR_REG_A1;
else if (((inst & 0x0E5F0010u) == 0x065F0000) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x01200000u) != 0x00200000))
return LDRB_REG_A1;
else if (((inst & 0x0E5F0FF0u) == 0x000F00D0) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x01200000u) != 0x00200000))
return LDRD_REG_A1;
else if (((inst & 0x0E5F0FF0u) == 0x001F00B0) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x01200000u) != 0x00200000))
return LDRH_REG_A1;
else if (((inst & 0x0E5F0FF0u) == 0x001F00D0) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x01200000u) != 0x00200000))
return LDRSB_REG_A1;
else if (((inst & 0x0E5F0FF0u) == 0x001F00F0) && ((inst & 0xF0000000) != 0xF0000000) &&
((inst & 0x01200000u) != 0x00200000))
return LDRSH_REG_A1;
else
return IGNORED;
}
size_t sh_a32_get_rewrite_inst_len(uint32_t inst) {
static uint8_t map[] = {
4, // IGNORED
12, // B_A1
12, // BX_A1
16, // BL_IMM_A1
16, // BLX_IMM_A2
32, // ADD_REG_A1
32, // ADD_REG_PC_A1
32, // SUB_REG_A1
32, // SUB_REG_PC_A1
12, // ADR_A1
12, // ADR_A2
32, // MOV_REG_A1
12, // MOV_REG_PC_A1
24, // LDR_LIT_A1
36, // LDR_LIT_PC_A1
24, // LDRB_LIT_A1
24, // LDRD_LIT_A1
24, // LDRH_LIT_A1
24, // LDRSB_LIT_A1
24, // LDRSH_LIT_A1
32, // LDR_REG_A1
36, // LDR_REG_PC_A1
32, // LDRB_REG_A1
32, // LDRD_REG_A1
32, // LDRH_REG_A1
32, // LDRSB_REG_A1
32 // LDRSH_REG_A1
};
return (size_t)(map[sh_a32_get_type(inst)]);
}
static bool sh_a32_is_addr_need_fix(uintptr_t addr, sh_a32_rewrite_info_t *rinfo) {
return (rinfo->overwrite_start_addr <= addr && addr < rinfo->overwrite_end_addr);
}
static uintptr_t sh_a32_fix_addr(uintptr_t addr, sh_a32_rewrite_info_t *rinfo) {
if (rinfo->overwrite_start_addr <= addr && addr < rinfo->overwrite_end_addr) {
uintptr_t cursor_addr = rinfo->overwrite_start_addr;
size_t offset = 0;
for (size_t i = 0; i < rinfo->rewrite_inst_lens_cnt; i++) {
if (cursor_addr >= addr) break;
cursor_addr += 4;
offset += rinfo->rewrite_inst_lens[i];
}
uintptr_t fixed_addr = (uintptr_t)rinfo->rewrite_buf + offset;
SH_LOG_INFO("a32 rewrite: fix addr %" PRIxPTR " -> %" PRIxPTR, addr, fixed_addr);
return fixed_addr;
}
return addr;
}
static size_t sh_a32_rewrite_b(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type,
sh_a32_rewrite_info_t *rinfo) {
uint32_t cond;
if (type == B_A1 || type == BL_IMM_A1 || type == BX_A1)
cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
else
// type == BLX_IMM_A2
cond = 0xE; // 1110 None (AL)
uint32_t addr;
if (type == B_A1 || type == BL_IMM_A1) {
uint32_t imm24 = SH_UTIL_GET_BITS_32(inst, 23, 0);
uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(imm24 << 2u, 26u);
addr = pc + imm32; // arm -> arm
} else if (type == BLX_IMM_A2) {
uint32_t h = SH_UTIL_GET_BIT_32(inst, 24);
uint32_t imm24 = SH_UTIL_GET_BITS_32(inst, 23, 0);
uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32((imm24 << 2u) | (h << 1u), 26u);
addr = SH_UTIL_SET_BIT0(pc + imm32); // arm -> thumb
} else {
// type == BX_A1
// BX PC
// PC must be even, and the "arm" instruction must be at a 4-byte aligned address,
// so the instruction set must keep "arm" unchanged.
addr = pc; // arm -> arm
}
addr = sh_a32_fix_addr(addr, rinfo);
size_t idx = 0;
if (type == BL_IMM_A1 || type == BLX_IMM_A2) {
buf[idx++] = 0x028FE008u | (cond << 28u); // ADD<c> LR, PC, #8
}
buf[idx++] = 0x059FF000u | (cond << 28u); // LDR<c> PC, [PC, #0]
buf[idx++] = 0xEA000000; // B #0
buf[idx++] = addr;
return idx * 4; // 12 or 16
}
static size_t sh_a32_rewrite_add_or_sub(uint32_t *buf, uint32_t inst, uintptr_t pc) {
// ADD{S}<c> <Rd>, <Rn>, PC{, <shift>} or ADD{S}<c> <Rd>, PC, <Rm>{, <shift>}
// SUB{S}<c> <Rd>, <Rn>, PC{, <shift>} or SUB{S}<c> <Rd>, PC, <Rm>{, <shift>}
uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
uint32_t rn = SH_UTIL_GET_BITS_32(inst, 19, 16);
uint32_t rm = SH_UTIL_GET_BITS_32(inst, 3, 0);
uint32_t rd = SH_UTIL_GET_BITS_32(inst, 15, 12);
uint32_t rx; // r0 - r3
for (rx = 3;; --rx)
if (rx != rn && rx != rm && rx != rd) break;
if (rd == 0xF) // Rd == PC
{
uint32_t ry; // r0 - r4
for (ry = 4;; --ry)
if (ry != rn && ry != rm && ry != rd && ry != rx) break;
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000005; // B #20
buf[2] = 0xE92D8000 | (1u << rx) | (1u << ry); // PUSH {Rx, Ry, PC}
buf[3] = 0xE59F0008 | (rx << 12u); // LDR Rx, [PC, #8]
if (rn == 0xF)
// Rn == PC
buf[4] =
(inst & 0x0FF00FFFu) | 0xE0000000 | (ry << 12u) | (rx << 16u); // ADD/SUB Ry, Rx, Rm{, <shift>}
else
// Rm == PC
buf[4] = (inst & 0x0FFF0FF0u) | 0xE0000000 | (ry << 12u) | rx; // ADD/SUB Ry, Rn, Rx{, <shift>}
buf[5] = 0xE58D0008 | (ry << 12u); // STR Ry, [SP, #8]
buf[6] = 0xE8BD8000 | (1u << rx) | (1u << ry); // POP {Rx, Ry, PC}
buf[7] = pc;
return 32;
} else {
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000005; // B #20
buf[2] = 0xE52D0004 | (rx << 12u); // PUSH {Rx}
buf[3] = 0xE59F0008 | (rx << 12u); // LDR Rx, [PC, #8]
if (rn == 0xF)
// Rn == PC
buf[4] = (inst & 0x0FF0FFFFu) | 0xE0000000 | (rx << 16u); // ADD/SUB{S} Rd, Rx, Rm{, <shift>}
else
// Rm == PC
buf[4] = (inst & 0x0FFFFFF0u) | 0xE0000000 | rx; // ADD/SUB{S} Rd, Rn, Rx{, <shift>}
buf[5] = 0xE49D0004 | (rx << 12u); // POP {Rx}
buf[6] = 0xEA000000; // B #0
buf[7] = pc;
return 32;
}
}
static size_t sh_a32_rewrite_adr(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type,
sh_a32_rewrite_info_t *rinfo) {
uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
uint32_t rd = SH_UTIL_GET_BITS_32(inst, 15, 12); // r0 - r15
uint32_t imm12 = SH_UTIL_GET_BITS_32(inst, 11, 0);
uint32_t imm32 = sh_util_arm_expand_imm(imm12);
uint32_t addr = (type == ADR_A1 ? (SH_UTIL_ALIGN_4(pc) + imm32) : (SH_UTIL_ALIGN_4(pc) - imm32));
if (sh_a32_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
buf[0] = 0x059F0000u | (cond << 28u) | (rd << 12u); // LDR<c> Rd, [PC, #0]
buf[1] = 0xEA000000; // B #0
buf[2] = addr;
return 12;
}
static size_t sh_a32_rewrite_mov(uint32_t *buf, uint32_t inst, uintptr_t pc) {
// MOV{S}<c> <Rd>, PC
uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
uint32_t rd = SH_UTIL_GET_BITS_32(inst, 15, 12);
uint32_t rx = (rd == 0) ? 1 : 0;
if (rd == 0xF) // Rd == PC (MOV PC, PC)
{
buf[0] = 0x059FF000u | (cond << 28u); // LDR<c> PC, [PC, #0]
buf[1] = 0xEA000000; // B #0
buf[2] = pc;
return 12;
} else {
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000005; // B #20
buf[2] = 0xE52D0004 | (rx << 12u); // PUSH {Rx}
buf[3] = 0xE59F0008 | (rx << 12u); // LDR Rx, [PC, #8]
buf[4] = (inst & 0x0FFFFFF0u) | 0xE0000000 | rx; // MOV{S} Rd, Rx{, <shift> #<amount>/RRX}
buf[5] = 0xE49D0004 | (rx << 12u); // POP {Rx}
buf[6] = 0xEA000000; // B #0
buf[7] = pc;
return 32;
}
}
static size_t sh_a32_rewrite_ldr_lit(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type,
sh_a32_rewrite_info_t *rinfo) {
uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
uint32_t u = SH_UTIL_GET_BIT_32(inst, 23);
uint32_t rt = SH_UTIL_GET_BITS_16(inst, 15, 12);
uint32_t imm32;
if (type == LDR_LIT_A1 || type == LDR_LIT_PC_A1 || type == LDRB_LIT_A1)
imm32 = SH_UTIL_GET_BITS_32(inst, 11, 0);
else
imm32 = (SH_UTIL_GET_BITS_32(inst, 11, 8) << 4u) + SH_UTIL_GET_BITS_32(inst, 3, 0);
uint32_t addr = (u ? (SH_UTIL_ALIGN_4(pc) + imm32) : (SH_UTIL_ALIGN_4(pc) - imm32));
if (sh_a32_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
if (type == LDR_LIT_PC_A1 && rt == 0xF) {
// Rt == PC
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000006; // B #24
buf[2] = 0xE92D0003; // PUSH {R0, R1}
buf[3] = 0xE59F0000; // LDR R0, [PC, #0]
buf[4] = 0xEA000000; // B #0
buf[5] = addr; //
buf[6] = 0xE5900000; // LDR R0, [R0]
buf[7] = 0xE58D0004; // STR R0, [SP, #4]
buf[8] = 0xE8BD8001; // POP {R0, PC}
return 36;
} else {
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000003; // B #12
buf[2] = 0xE59F0000 | (rt << 12u); // LDR Rt, [PC, #0]
buf[3] = 0xEA000000; // B #0
buf[4] = addr; //
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wswitch"
switch (type) {
case LDR_LIT_A1:
buf[5] = 0xE5900000 | (rt << 16u) | (rt << 12u); // LDR Rt, [Rt]
break;
case LDRB_LIT_A1:
buf[5] = 0xE5D00000 | (rt << 16u) | (rt << 12u); // LDRB Rt, [Rt]
break;
case LDRD_LIT_A1:
buf[5] = 0xE1C000D0 | (rt << 16u) | (rt << 12u); // LDRD Rt, [Rt]
break;
case LDRH_LIT_A1:
buf[5] = 0xE1D000B0 | (rt << 16u) | (rt << 12u); // LDRH Rt, [Rt]
break;
case LDRSB_LIT_A1:
buf[5] = 0xE1D000D0 | (rt << 16u) | (rt << 12u); // LDRSB Rt, [Rt]
break;
case LDRSH_LIT_A1:
buf[5] = 0xE1D000F0 | (rt << 16u) | (rt << 12u); // LDRSH Rt, [Rt]
break;
}
#pragma clang diagnostic pop
return 24;
}
}
static size_t sh_a32_rewrite_ldr_reg(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_type_t type) {
// LDR<c> <Rt>, [PC,+/-<Rm>{, <shift>}]{!}
// ......
uint32_t cond = SH_UTIL_GET_BITS_32(inst, 31, 28);
uint32_t rt = SH_UTIL_GET_BITS_16(inst, 15, 12);
uint32_t rt2 = rt + 1;
uint32_t rm = SH_UTIL_GET_BITS_16(inst, 3, 0);
uint32_t rx; // r0 - r3
for (rx = 3;; --rx)
if (rx != rt && rx != rt2 && rx != rm) break;
if (type == LDR_REG_PC_A1 && rt == 0xF) {
// Rt == PC
uint32_t ry; // r0 - r4
for (ry = 4;; --ry)
if (ry != rt && ry != rt2 && ry != rm && ry != rx) break;
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000006; // B #24
buf[2] = 0xE92D8000 | (1u << rx) | (1u << ry); // PUSH {Rx, Ry, PC}
buf[3] = 0xE59F0000 | (rx << 12u); // LDR Rx, [PC, #8]
buf[4] = 0xEA000000; // B #0
buf[5] = pc;
buf[6] =
(inst & 0x0FF00FFFu) | 0xE0000000 | (rx << 16u) | (ry << 12u); // LDRxx Ry, [Rx],+/-Rm{, <shift>}
buf[7] = 0xE58D0008 | (ry << 12u); // STR Ry, [SP, #8]
buf[8] = 0xE8BD8000 | (1u << rx) | (1u << ry); // POP {Rx, Ry, PC}
return 36;
} else {
buf[0] = 0x0A000000u | (cond << 28u); // B<c> #0
buf[1] = 0xEA000005; // B #20
buf[2] = 0xE52D0004 | (rx << 12u); // PUSH {Rx}
buf[3] = 0xE59F0000 | (rx << 12u); // LDR Rx, [PC, #0]
buf[4] = 0xEA000000; // B #0
buf[5] = pc;
buf[6] = (inst & 0x0FF0FFFFu) | 0xE0000000 | (rx << 16u); // LDRxx Rt, [Rx],+/-Rm{, <shift>}
buf[7] = 0xE49D0004 | (rx << 12u); // POP {Rx}
return 32;
}
}
size_t sh_a32_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_rewrite_info_t *rinfo) {
sh_a32_type_t type = sh_a32_get_type(inst);
SH_LOG_INFO("a32 rewrite: type %d, inst %" PRIx32, type, inst);
// We will only overwrite 4 to 8 bytes on A32, so PC cannot be in the coverage.
// In this case, the add/sub/mov/ldr_reg instruction does not need to consider
// the problem of PC in the coverage area when rewriting.
if (type == B_A1 || type == BX_A1 || type == BL_IMM_A1 || type == BLX_IMM_A2)
return sh_a32_rewrite_b(buf, inst, pc, type, rinfo);
else if (type == ADD_REG_A1 || type == ADD_REG_PC_A1 || type == SUB_REG_A1 || type == SUB_REG_PC_A1)
return sh_a32_rewrite_add_or_sub(buf, inst, pc);
else if (type == ADR_A1 || type == ADR_A2)
return sh_a32_rewrite_adr(buf, inst, pc, type, rinfo);
else if (type == MOV_REG_A1 || type == MOV_REG_PC_A1)
return sh_a32_rewrite_mov(buf, inst, pc);
else if (type == LDR_LIT_A1 || type == LDR_LIT_PC_A1 || type == LDRB_LIT_A1 || type == LDRD_LIT_A1 ||
type == LDRH_LIT_A1 || type == LDRSB_LIT_A1 || type == LDRSH_LIT_A1)
return sh_a32_rewrite_ldr_lit(buf, inst, pc, type, rinfo);
else if (type == LDR_REG_A1 || type == LDR_REG_PC_A1 || type == LDRB_REG_A1 || type == LDRD_REG_A1 ||
type == LDRH_REG_A1 || type == LDRSB_REG_A1 || type == LDRSH_REG_A1)
return sh_a32_rewrite_ldr_reg(buf, inst, pc, type);
else {
// IGNORED
buf[0] = inst;
return 4;
}
}
size_t sh_a32_absolute_jump(uint32_t *buf, uintptr_t addr) {
buf[0] = 0xE51FF004; // LDR PC, [PC, #-4]
buf[1] = addr;
return 8;
}
size_t sh_a32_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc) {
buf[0] = 0xEA000000 | (((addr - pc) & 0x03FFFFFFu) >> 2u); // B <label>
return 4;
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stddef.h>
#include <stdint.h>
typedef struct {
uintptr_t overwrite_start_addr;
uintptr_t overwrite_end_addr;
uint32_t *rewrite_buf;
size_t rewrite_buf_offset;
size_t rewrite_inst_lens[2];
size_t rewrite_inst_lens_cnt;
} sh_a32_rewrite_info_t;
size_t sh_a32_get_rewrite_inst_len(uint32_t inst);
size_t sh_a32_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a32_rewrite_info_t *rinfo);
size_t sh_a32_absolute_jump(uint32_t *buf, uintptr_t addr);
size_t sh_a32_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc);

View File

@ -0,0 +1,523 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_inst.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include "sh_a32.h"
#include "sh_config.h"
#include "sh_enter.h"
#include "sh_exit.h"
#include "sh_log.h"
#include "sh_sig.h"
#include "sh_t16.h"
#include "sh_t32.h"
#include "sh_txx.h"
#include "sh_util.h"
#include "shadowhook.h"
static void sh_inst_get_thumb_rewrite_info(sh_inst_t *self, uintptr_t target_addr,
sh_txx_rewrite_info_t *rinfo) {
memset(rinfo, 0, sizeof(sh_txx_rewrite_info_t));
size_t idx = 0;
uintptr_t target_addr_offset = 0;
uintptr_t pc = target_addr + 4;
size_t rewrite_len = 0;
while (rewrite_len < self->backup_len) {
// IT block
sh_t16_it_t it;
if (sh_t16_parse_it(&it, *((uint16_t *)(target_addr + target_addr_offset)), pc)) {
rewrite_len += (2 + it.insts_len);
size_t it_block_idx = idx++;
size_t it_block_len = 4 + 4; // IT-else + IT-then
for (size_t i = 0, j = 0; i < it.insts_cnt; i++) {
bool is_thumb32 = sh_util_is_thumb32((uintptr_t)(&(it.insts[j])));
if (is_thumb32) {
it_block_len += sh_t32_get_rewrite_inst_len(it.insts[j], it.insts[j + 1]);
rinfo->inst_lens[idx++] = 0;
rinfo->inst_lens[idx++] = 0;
j += 2;
} else {
it_block_len += sh_t16_get_rewrite_inst_len(it.insts[j]);
rinfo->inst_lens[idx++] = 0;
j += 1;
}
}
rinfo->inst_lens[it_block_idx] = it_block_len;
target_addr_offset += (2 + it.insts_len);
pc += (2 + it.insts_len);
}
// not IT block
else {
bool is_thumb32 = sh_util_is_thumb32(target_addr + target_addr_offset);
size_t inst_len = (is_thumb32 ? 4 : 2);
rewrite_len += inst_len;
if (is_thumb32) {
rinfo->inst_lens[idx++] =
sh_t32_get_rewrite_inst_len(*((uint16_t *)(target_addr + target_addr_offset)),
*((uint16_t *)(target_addr + target_addr_offset + 2)));
rinfo->inst_lens[idx++] = 0;
} else
rinfo->inst_lens[idx++] =
sh_t16_get_rewrite_inst_len(*((uint16_t *)(target_addr + target_addr_offset)));
target_addr_offset += inst_len;
pc += inst_len;
}
}
rinfo->start_addr = target_addr;
rinfo->end_addr = target_addr + rewrite_len;
rinfo->buf = (uint16_t *)self->enter_addr;
rinfo->buf_offset = 0;
rinfo->inst_lens_cnt = idx;
}
static int sh_inst_hook_thumb_rewrite(sh_inst_t *self, uintptr_t target_addr, uintptr_t *orig_addr,
uintptr_t *orig_addr2, size_t *rewrite_len) {
// backup original instructions (length: 4 or 8 or 10)
memcpy((void *)(self->backup), (void *)target_addr, self->backup_len);
// package the information passed to rewrite
sh_txx_rewrite_info_t rinfo;
sh_inst_get_thumb_rewrite_info(self, target_addr, &rinfo);
// backup and rewrite original instructions
uintptr_t target_addr_offset = 0;
uintptr_t pc = target_addr + 4;
*rewrite_len = 0;
while (*rewrite_len < self->backup_len) {
// IT block
sh_t16_it_t it;
if (sh_t16_parse_it(&it, *((uint16_t *)(target_addr + target_addr_offset)), pc)) {
*rewrite_len += (2 + it.insts_len);
// save space holder point of IT-else B instruction
uintptr_t enter_inst_else_p = self->enter_addr + rinfo.buf_offset;
rinfo.buf_offset += 2; // B<c> <label>
rinfo.buf_offset += 2; // NOP
// rewrite IT block
size_t enter_inst_else_len = 4; // B<c> + NOP + B + NOP
size_t enter_inst_then_len = 0; // B + NOP
uintptr_t enter_inst_then_p = 0;
for (size_t i = 0, j = 0; i < it.insts_cnt; i++) {
if (i == it.insts_else_cnt) {
// save space holder point of IT-then (for B instruction)
enter_inst_then_p = self->enter_addr + rinfo.buf_offset;
rinfo.buf_offset += 2; // B <label>
rinfo.buf_offset += 2; // NOP
// fill IT-else B instruction
sh_t16_rewrite_it_else((uint16_t *)enter_inst_else_p, (uint16_t)enter_inst_else_len, &it);
}
// rewrite instructions in IT block
bool is_thumb32 = sh_util_is_thumb32((uintptr_t)(&(it.insts[j])));
size_t len;
if (is_thumb32)
len = sh_t32_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset), it.insts[j],
it.insts[j + 1], it.pcs[i], &rinfo);
else
len = sh_t16_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset), it.insts[j], it.pcs[i],
&rinfo);
if (0 == len) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
rinfo.buf_offset += len;
j += (is_thumb32 ? 2 : 1);
// save the total offset for ELSE/THEN in enter
if (i < it.insts_else_cnt)
enter_inst_else_len += len;
else
enter_inst_then_len += len;
if (i == it.insts_cnt - 1) {
// fill IT-then B instruction
sh_t16_rewrite_it_then((uint16_t *)enter_inst_then_p, (uint16_t)enter_inst_then_len);
}
}
target_addr_offset += (2 + it.insts_len);
pc += (2 + it.insts_len);
}
// not IT block
else {
bool is_thumb32 = sh_util_is_thumb32(target_addr + target_addr_offset);
size_t inst_len = (is_thumb32 ? 4 : 2);
*rewrite_len += inst_len;
// rewrite original instructions (fill in enter)
SH_LOG_INFO("thumb rewrite: offset %zu, pc %" PRIxPTR, rinfo.buf_offset, pc);
size_t len;
if (is_thumb32)
len = sh_t32_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset),
*((uint16_t *)(target_addr + target_addr_offset)),
*((uint16_t *)(target_addr + target_addr_offset + 2)), pc, &rinfo);
else
len = sh_t16_rewrite((uint16_t *)(self->enter_addr + rinfo.buf_offset),
*((uint16_t *)(target_addr + target_addr_offset)), pc, &rinfo);
if (0 == len) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
rinfo.buf_offset += len;
target_addr_offset += inst_len;
pc += inst_len;
}
}
SH_LOG_INFO("thumb rewrite: len %zu to %zu", *rewrite_len, rinfo.buf_offset);
// absolute jump back to remaining original instructions (fill in enter)
rinfo.buf_offset += sh_t32_absolute_jump((uint16_t *)(self->enter_addr + rinfo.buf_offset), true,
SH_UTIL_SET_BIT0(target_addr + *rewrite_len));
sh_util_clear_cache(self->enter_addr, rinfo.buf_offset);
// save original function address
if (NULL != orig_addr) __atomic_store_n(orig_addr, SH_UTIL_SET_BIT0(self->enter_addr), __ATOMIC_SEQ_CST);
if (NULL != orig_addr2) __atomic_store_n(orig_addr2, SH_UTIL_SET_BIT0(self->enter_addr), __ATOMIC_SEQ_CST);
return 0;
}
#ifdef SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
static bool sh_inst_thumb_is_long_enough(uintptr_t target_addr, size_t overwrite_len, xdl_info_t *dlinfo) {
if (overwrite_len <= dlinfo->dli_ssize) return true;
// check align-4 in the end of symbol
if ((overwrite_len == dlinfo->dli_ssize + 2) && ((target_addr + dlinfo->dli_ssize) % 4 == 2)) {
uintptr_t sym_end = target_addr + dlinfo->dli_ssize;
if (0 != sh_util_mprotect(sym_end, 2, PROT_READ | PROT_WRITE | PROT_EXEC)) return false;
// should be zero-ed
if (0 != *((uint16_t *)sym_end)) return false;
// should not belong to any symbol
void *dlcache = NULL;
xdl_info_t dlinfo2;
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
xdl_addr((void *)SH_UTIL_SET_BIT0(sym_end), &dlinfo2, &dlcache);
} else {
SH_SIG_TRY(SIGSEGV, SIGBUS) {
xdl_addr((void *)SH_UTIL_SET_BIT0(sym_end), &dlinfo2, &dlcache);
}
SH_SIG_CATCH() {
memset(&dlinfo2, 0, sizeof(dlinfo2));
SH_LOG_WARN("thumb detect tail aligned: crashed");
}
SH_SIG_EXIT
}
xdl_addr_clean(&dlcache);
if (NULL != dlinfo2.dli_sname) return false;
// trust here is useless alignment data
return true;
}
return false;
}
#endif
#ifdef SH_CONFIG_TRY_WITH_EXIT
// B T4: [-16M, +16M - 2]
#define SH_INST_T32_B_RANGE_LOW (16777216)
#define SH_INST_T32_B_RANGE_HIGH (16777214)
static int sh_inst_hook_thumb_with_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
int r;
target_addr = SH_UTIL_CLEAR_BIT0(target_addr);
uintptr_t pc = target_addr + 4;
self->backup_len = 4;
#ifdef SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
if (!sh_inst_thumb_is_long_enough(target_addr, self->backup_len, dlinfo))
return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
#else
if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
#endif
// alloc an exit for absolute jump
sh_t32_absolute_jump((uint16_t *)self->exit, true, new_addr);
if (0 != (r = sh_exit_alloc(&self->exit_addr, &self->exit_type, pc, dlinfo, (uint8_t *)(self->exit),
sizeof(self->exit), SH_INST_T32_B_RANGE_LOW, SH_INST_T32_B_RANGE_HIGH)))
return r;
// rewrite
if (0 != sh_util_mprotect(target_addr, dlinfo->dli_ssize, PROT_READ | PROT_WRITE | PROT_EXEC)) {
r = SHADOWHOOK_ERRNO_MPROT;
goto err;
}
size_t rewrite_len = 0;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = sh_inst_hook_thumb_rewrite(self, target_addr, orig_addr, orig_addr2, &rewrite_len);
}
SH_SIG_CATCH() {
r = SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
goto err;
}
SH_SIG_EXIT
if (0 != r) goto err;
// relative jump to the exit by overwriting the head of original function
sh_t32_relative_jump((uint16_t *)self->trampo, self->exit_addr, pc);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) goto err;
SH_LOG_INFO("thumb: hook (WITH EXIT) OK. target %" PRIxPTR " -> exit %" PRIxPTR " -> new %" PRIxPTR
" -> enter %" PRIxPTR " -> remaining %" PRIxPTR,
target_addr, self->exit_addr, new_addr, self->enter_addr,
SH_UTIL_SET_BIT0(target_addr + rewrite_len));
return 0;
err:
sh_exit_free(self->exit_addr, self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit));
self->exit_addr = 0; // this is a flag for with-exit or without-exit
return r;
}
#endif
static int sh_inst_hook_thumb_without_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
int r;
target_addr = SH_UTIL_CLEAR_BIT0(target_addr);
bool is_align4 = (0 == (target_addr % 4));
self->backup_len = (is_align4 ? 8 : 10);
#ifdef SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED
if (!sh_inst_thumb_is_long_enough(target_addr, self->backup_len, dlinfo))
return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
#else
if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
#endif
// rewrite
if (0 != sh_util_mprotect(target_addr, dlinfo->dli_ssize, PROT_READ | PROT_WRITE | PROT_EXEC))
return SHADOWHOOK_ERRNO_MPROT;
size_t rewrite_len = 0;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = sh_inst_hook_thumb_rewrite(self, target_addr, orig_addr, orig_addr2, &rewrite_len);
}
SH_SIG_CATCH() {
return SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
}
SH_SIG_EXIT
if (0 != r) return r;
// absolute jump to new function address by overwriting the head of original function
sh_t32_absolute_jump((uint16_t *)self->trampo, is_align4, new_addr);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) return r;
SH_LOG_INFO("thumb: hook (WITHOUT EXIT) OK. target %" PRIxPTR " -> new %" PRIxPTR " -> enter %" PRIxPTR
" -> remaining %" PRIxPTR,
target_addr, new_addr, self->enter_addr, SH_UTIL_SET_BIT0(target_addr + rewrite_len));
return 0;
}
static int sh_inst_hook_arm_rewrite(sh_inst_t *self, uintptr_t target_addr, uintptr_t *orig_addr,
uintptr_t *orig_addr2) {
// backup original instructions (length: 4 or 8)
memcpy((void *)(self->backup), (void *)target_addr, self->backup_len);
// package the information passed to rewrite
sh_a32_rewrite_info_t rinfo;
rinfo.overwrite_start_addr = target_addr;
rinfo.overwrite_end_addr = target_addr + self->backup_len;
rinfo.rewrite_buf = (uint32_t *)self->enter_addr;
rinfo.rewrite_buf_offset = 0;
rinfo.rewrite_inst_lens_cnt = self->backup_len / 4;
for (uintptr_t i = 0; i < self->backup_len; i += 4)
rinfo.rewrite_inst_lens[i / 4] = sh_a32_get_rewrite_inst_len(*((uint32_t *)(target_addr + i)));
// rewrite original instructions (fill in enter)
uintptr_t pc = target_addr + 8;
for (uintptr_t i = 0; i < self->backup_len; i += 4, pc += 4) {
size_t offset = sh_a32_rewrite((uint32_t *)(self->enter_addr + rinfo.rewrite_buf_offset),
*((uint32_t *)(target_addr + i)), pc, &rinfo);
if (0 == offset) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
rinfo.rewrite_buf_offset += offset;
}
// absolute jump back to remaining original instructions (fill in enter)
rinfo.rewrite_buf_offset += sh_a32_absolute_jump((uint32_t *)(self->enter_addr + rinfo.rewrite_buf_offset),
target_addr + self->backup_len);
sh_util_clear_cache(self->enter_addr, rinfo.rewrite_buf_offset);
// save original function address
if (NULL != orig_addr) __atomic_store_n(orig_addr, self->enter_addr, __ATOMIC_SEQ_CST);
if (NULL != orig_addr2) __atomic_store_n(orig_addr2, self->enter_addr, __ATOMIC_SEQ_CST);
return 0;
}
#ifdef SH_CONFIG_TRY_WITH_EXIT
// B A1: [-32M, +32M - 4]
#define SH_INST_A32_B_RANGE_LOW (33554432)
#define SH_INST_A32_B_RANGE_HIGH (33554428)
static int sh_inst_hook_arm_with_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
int r;
uintptr_t pc = target_addr + 8;
self->backup_len = 4;
if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
// alloc an exit for absolute jump
sh_a32_absolute_jump(self->exit, new_addr);
if (0 != (r = sh_exit_alloc(&self->exit_addr, &self->exit_type, pc, dlinfo, (uint8_t *)(self->exit),
sizeof(self->exit), SH_INST_A32_B_RANGE_LOW, SH_INST_A32_B_RANGE_HIGH)))
return r;
// rewrite
if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC)) {
r = SHADOWHOOK_ERRNO_MPROT;
goto err;
}
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = sh_inst_hook_arm_rewrite(self, target_addr, orig_addr, orig_addr2);
}
SH_SIG_CATCH() {
r = SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
goto err;
}
SH_SIG_EXIT
if (0 != r) goto err;
// relative jump to the exit by overwriting the head of original function
sh_a32_relative_jump(self->trampo, self->exit_addr, pc);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) goto err;
SH_LOG_INFO("a32: hook (WITH EXIT) OK. target %" PRIxPTR " -> exit %" PRIxPTR " -> new %" PRIxPTR
" -> enter %" PRIxPTR " -> remaining %" PRIxPTR,
target_addr, self->exit_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
return 0;
err:
sh_exit_free(self->exit_addr, self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit));
self->exit_addr = 0; // this is a flag for with-exit or without-exit
return r;
}
#endif
static int sh_inst_hook_arm_without_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
int r;
self->backup_len = 8;
if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
// rewrite
if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC))
return SHADOWHOOK_ERRNO_MPROT;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = sh_inst_hook_arm_rewrite(self, target_addr, orig_addr, orig_addr2);
}
SH_SIG_CATCH() {
return SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
}
SH_SIG_EXIT
if (0 != r) return r;
// absolute jump to new function address by overwriting the head of original function
sh_a32_absolute_jump(self->trampo, new_addr);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) return r;
SH_LOG_INFO("a32: hook (WITHOUT EXIT) OK. target %" PRIxPTR " -> new %" PRIxPTR " -> enter %" PRIxPTR
" -> remaining %" PRIxPTR,
target_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
return 0;
}
int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
uintptr_t *orig_addr, uintptr_t *orig_addr2) {
self->enter_addr = sh_enter_alloc();
if (0 == self->enter_addr) return SHADOWHOOK_ERRNO_HOOK_ENTER;
int r;
if (SH_UTIL_IS_THUMB(target_addr)) {
#ifdef SH_CONFIG_TRY_WITH_EXIT
if (0 == (r = sh_inst_hook_thumb_with_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
return r;
#endif
if (0 ==
(r = sh_inst_hook_thumb_without_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
return r;
} else {
#ifdef SH_CONFIG_TRY_WITH_EXIT
if (0 == (r = sh_inst_hook_arm_with_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
return r;
#endif
if (0 == (r = sh_inst_hook_arm_without_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
return r;
}
// hook failed
if (NULL != orig_addr) *orig_addr = 0;
if (NULL != orig_addr2) *orig_addr2 = 0;
sh_enter_free(self->enter_addr);
return r;
}
int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr) {
int r;
bool is_thumb = SH_UTIL_IS_THUMB(target_addr);
if (is_thumb) target_addr = SH_UTIL_CLEAR_BIT0(target_addr);
// restore the instructions at the target address
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = memcmp((void *)target_addr, self->trampo, self->backup_len);
}
SH_SIG_CATCH() {
return SHADOWHOOK_ERRNO_UNHOOK_CMP_CRASH;
}
SH_SIG_EXIT
if (0 != r) return SHADOWHOOK_ERRNO_UNHOOK_TRAMPO_MISMATCH;
if (0 != (r = sh_util_write_inst(target_addr, self->backup, self->backup_len))) return r;
__atomic_thread_fence(__ATOMIC_SEQ_CST);
// free memory space for exit
if (0 != self->exit_addr)
if (0 !=
(r = sh_exit_free(self->exit_addr, self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit))))
return r;
// free memory space for enter
sh_enter_free(self->enter_addr);
SH_LOG_INFO("%s: unhook OK. target %" PRIxPTR, is_thumb ? "thumb" : "a32", target_addr);
return 0;
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdint.h>
#include "xdl.h"
typedef struct {
uint32_t trampo[4]; // align 16 // length == backup_len
uint8_t backup[16]; // align 16
uint16_t backup_len; // == 4 or 8 or 10
uint16_t exit_type;
uintptr_t exit_addr;
uint32_t exit[2];
uintptr_t enter_addr;
} sh_inst_t;
int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
uintptr_t *orig_addr, uintptr_t *orig_addr2);
int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr);

View File

@ -0,0 +1,284 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Pengying Xu (xupengying@bytedance.com) on 2021-04-11.
#include "sh_t16.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "sh_log.h"
#include "sh_util.h"
// https://developer.arm.com/documentation/ddi0406/latest
// https://developer.arm.com/documentation/ddi0597/latest
typedef enum {
IGNORED = 0,
IT_T1,
B_T1,
B_T2,
BX_T1,
ADD_REG_T2,
MOV_REG_T1,
ADR_T1,
LDR_LIT_T1,
CBZ_T1,
CBNZ_T1
} sh_t16_type_t;
static sh_t16_type_t sh_t16_get_type(uint16_t inst) {
if (((inst & 0xFF00u) == 0xBF00) && ((inst & 0x000Fu) != 0x0000) && ((inst & 0x00F0u) != 0x00F0))
return IT_T1;
else if (((inst & 0xF000u) == 0xD000) && ((inst & 0x0F00u) != 0x0F00) && ((inst & 0x0F00u) != 0x0E00))
return B_T1;
else if ((inst & 0xF800u) == 0xE000)
return B_T2;
else if ((inst & 0xFFF8u) == 0x4778)
return BX_T1;
else if (((inst & 0xFF78u) == 0x4478) && ((inst & 0x0087u) != 0x0085))
return ADD_REG_T2;
else if ((inst & 0xFF78u) == 0x4678)
return MOV_REG_T1;
else if ((inst & 0xF800u) == 0xA000)
return ADR_T1;
else if ((inst & 0xF800u) == 0x4800)
return LDR_LIT_T1;
else if ((inst & 0xFD00u) == 0xB100)
return CBZ_T1;
else if ((inst & 0xFD00u) == 0xB900)
return CBNZ_T1;
else
return IGNORED;
}
size_t sh_t16_get_rewrite_inst_len(uint16_t inst) {
static uint8_t map[] = {
4, // IGNORED
0, // IT_T1
12, // B_T1
8, // B_T2
8, // BX_T1
16, // ADD_REG_T2
12, // MOV_REG_T1
8, // ADR_T1
12, // LDR_LIT_T1
12, // CBZ_T1
12 // CBNZ_T1
};
return (size_t)(map[sh_t16_get_type(inst)]);
}
static size_t sh_t16_rewrite_b(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_t16_type_t type,
sh_txx_rewrite_info_t *rinfo) {
uint32_t addr;
if (type == B_T1) {
uint32_t imm8 = SH_UTIL_GET_BITS_16(inst, 7, 0);
addr = pc + SH_UTIL_SIGN_EXTEND_32(imm8 << 1u, 9u);
addr = SH_UTIL_SET_BIT0(addr); // thumb -> thumb
} else if (type == B_T2) {
uint32_t imm11 = SH_UTIL_GET_BITS_16(inst, 10, 0);
addr = pc + SH_UTIL_SIGN_EXTEND_32(imm11 << 1u, 12u);
addr = SH_UTIL_SET_BIT0(addr); // thumb -> thumb
} else {
// type == BX_T1
// BX PC
// PC must be even, and the "BX PC" instruction must be at a 4-byte aligned address,
// so the instruction set must be exchanged from "thumb" to "arm".
addr = pc; // thumb -> arm
}
addr = sh_txx_fix_addr(addr, rinfo);
size_t idx = 0;
if (type == B_T1) {
buf[idx++] = inst & 0xFF00u; // B<c> #0
buf[idx++] = 0xE003; // B PC, #6
}
buf[idx++] = 0xF8DF; // LDR.W PC, [PC]
buf[idx++] = 0xF000; // ...
buf[idx++] = addr & 0xFFFFu;
buf[idx++] = addr >> 16u;
return idx * 2; // 8 or 12
}
static size_t sh_t16_rewrite_add(uint16_t *buf, uint16_t inst, uintptr_t pc) {
// ADD<c> <Rdn>, PC
uint16_t dn = SH_UTIL_GET_BIT_16(inst, 7);
uint16_t rdn = SH_UTIL_GET_BITS_16(inst, 2, 0);
uint16_t rd = (uint16_t)(dn << 3u) | rdn;
uint16_t rx = (rd == 0) ? 1 : 0; // r0 - r1
buf[0] = (uint16_t)(0xB400u | (1u << rx)); // PUSH {Rx}
buf[1] = 0x4802u | (uint16_t)(rx << 8u); // LDR Rx, [PC, #8]
buf[2] = (inst & 0xFF87u) | (uint16_t)(rx << 3u); // ADD Rd, Rx
buf[3] = (uint16_t)(0xBC00u | (1u << rx)); // POP {Rx}
buf[4] = 0xE002; // B #4
buf[5] = 0xBF00;
buf[6] = pc & 0xFFFFu;
buf[7] = pc >> 16u;
return 16;
}
static size_t sh_t16_rewrite_mov(uint16_t *buf, uint16_t inst, uintptr_t pc) {
// MOV<c> <Rd>, PC
uint16_t D = SH_UTIL_GET_BIT_16(inst, 7);
uint16_t rd = SH_UTIL_GET_BITS_16(inst, 2, 0);
uint16_t d = (uint16_t)(D << 3u) | rd; // r0 - r15
buf[0] = 0xF8DF; // LDR.W Rd, [PC, #4]
buf[1] = (uint16_t)(d << 12u) + 4u; // ...
buf[2] = 0xE002; // B #4
buf[3] = 0xBF00; // NOP
buf[4] = pc & 0xFFFFu;
buf[5] = pc >> 16u;
return 12;
}
static size_t sh_t16_rewrite_adr(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
// ADR<c> <Rd>, <label>
uint16_t rd = SH_UTIL_GET_BITS_16(inst, 10, 8); // r0 - r7
uint16_t imm8 = SH_UTIL_GET_BITS_16(inst, 7, 0);
uint32_t addr = SH_UTIL_ALIGN_4(pc) + (uint32_t)(imm8 << 2u);
if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
buf[0] = 0x4800u | (uint16_t)(rd << 8u); // LDR Rd, [PC]
buf[1] = 0xE001; // B #2
buf[2] = addr & 0xFFFFu;
buf[3] = addr >> 16u;
return 8;
}
static size_t sh_t16_rewrite_ldr(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
// LDR<c> <Rt>, <label>
uint16_t rt = SH_UTIL_GET_BITS_16(inst, 10, 8); // r0 - r7
uint16_t imm8 = SH_UTIL_GET_BITS_16(inst, 7, 0);
uint32_t addr = SH_UTIL_ALIGN_4(pc) + (uint32_t)(imm8 << 2u);
if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
buf[0] = 0x4800u | (uint16_t)(rt << 8u); // LDR Rt, [PC]
buf[1] = 0xE001; // B #2
buf[2] = addr & 0xFFFFu;
buf[3] = addr >> 16u;
buf[4] = 0x6800u | (uint16_t)(rt << 3u) | rt; // LDR Rt, [Rt]
buf[5] = 0xBF00; // NOP
return 12;
}
static size_t sh_t16_rewrite_cb(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
// CB{N}Z <Rn>, <label>
uint16_t i = SH_UTIL_GET_BIT_16(inst, 9);
uint16_t imm5 = SH_UTIL_GET_BITS_16(inst, 7, 3);
uint32_t imm32 = (uint32_t)(i << 6u) | (uint32_t)(imm5 << 1u);
uint32_t addr = SH_UTIL_SET_BIT0(pc + imm32); // thumb -> thumb
addr = sh_txx_fix_addr(addr, rinfo);
buf[0] = inst & 0xFD07u; // CB(N)Z Rn, #0
buf[1] = 0xE003; // B PC, #6
buf[2] = 0xF8DF; // LDR.W PC, [PC]
buf[3] = 0xF000; // ...
buf[4] = addr & 0xFFFFu;
buf[5] = addr >> 16u;
return 12;
}
size_t sh_t16_rewrite(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo) {
sh_t16_type_t type = sh_t16_get_type(inst);
SH_LOG_INFO("t16 rewrite: type %d, inst %" PRIx16, type, inst);
if (type == B_T1 || type == B_T2 || type == BX_T1)
return sh_t16_rewrite_b(buf, inst, pc, type, rinfo);
else if (type == ADD_REG_T2)
return sh_t16_rewrite_add(buf, inst, pc);
else if (type == MOV_REG_T1)
return sh_t16_rewrite_mov(buf, inst, pc);
else if (type == ADR_T1)
return sh_t16_rewrite_adr(buf, inst, pc, rinfo);
else if (type == LDR_LIT_T1)
return sh_t16_rewrite_ldr(buf, inst, pc, rinfo);
else if (type == CBZ_T1 || type == CBNZ_T1)
return sh_t16_rewrite_cb(buf, inst, pc, rinfo);
else {
// IGNORED
buf[0] = inst;
buf[1] = 0xBF00; // NOP
return 4;
}
}
static size_t sh_t16_get_it_insts_count(uint16_t inst) {
if ((inst & 0x1u) != 0) return 4;
if ((inst & 0x2u) != 0) return 3;
if ((inst & 0x4u) != 0) return 2;
return 1;
}
bool sh_t16_parse_it(sh_t16_it_t *it, uint16_t inst, uintptr_t pc) {
if (IT_T1 != sh_t16_get_type(inst)) return false;
SH_LOG_INFO("t16 rewrite: type IT, inst %" PRIx16, inst);
// address of the first inst in the IT block, skip the IT inst itself (2 bytes)
uintptr_t target_addr = pc - 4 + 2;
it->firstcond = (uint8_t)(inst >> 4u);
uint8_t firstcond_0 = it->firstcond & 1u;
memset(it, 0, sizeof(sh_t16_it_t));
it->insts_cnt = sh_t16_get_it_insts_count(inst);
size_t insts_idx = 0, pcs_idx = 0;
for (int parse_else = 1; parse_else >= 0; parse_else--) // round 0: parse ELSE, round 1: THEN
{
uintptr_t target_offset = 0;
for (size_t i = 0; i < it->insts_cnt; i++) {
bool is_thumb32 = sh_util_is_thumb32(target_addr + target_offset);
uint8_t mask_x = (uint8_t)(inst >> (uint16_t)(4 - i)) & 1u;
if ((parse_else && mask_x != firstcond_0) || // parse ELSE or
(!parse_else && mask_x == firstcond_0)) // parse THEN
{
it->insts[insts_idx++] = *((uint16_t *)(target_addr + target_offset));
if (is_thumb32) it->insts[insts_idx++] = *((uint16_t *)(target_addr + target_offset + 2));
it->pcs[pcs_idx++] = target_addr + target_offset + 4;
if (parse_else) it->insts_else_cnt++;
}
target_offset += (is_thumb32 ? 4 : 2);
}
}
it->insts_len = insts_idx * 2;
return true;
}
void sh_t16_rewrite_it_else(uint16_t *buf, uint16_t imm9, sh_t16_it_t *it) {
buf[0] = 0xD000u | (uint16_t)(it->firstcond << 8u) | (uint16_t)(imm9 >> 1u); // B<c> <label>
buf[1] = 0xBF00; // NOP
}
void sh_t16_rewrite_it_then(uint16_t *buf, uint16_t imm12) {
buf[0] = 0xE000u | (uint16_t)(imm12 >> 1u); // B <label>
buf[1] = 0xBF00; // NOP
}

View File

@ -0,0 +1,46 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Pengying Xu (xupengying@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "sh_txx.h"
typedef struct {
uint16_t insts[8];
size_t insts_len; // 2 - 16 (bytes)
size_t insts_cnt; // 1 - 4
size_t insts_else_cnt; // 0 - 3
uintptr_t pcs[4];
uint8_t firstcond;
uint8_t padding[3];
} sh_t16_it_t;
bool sh_t16_parse_it(sh_t16_it_t *it, uint16_t inst, uintptr_t pc);
void sh_t16_rewrite_it_else(uint16_t *buf, uint16_t imm9, sh_t16_it_t *it);
void sh_t16_rewrite_it_then(uint16_t *buf, uint16_t imm12);
size_t sh_t16_get_rewrite_inst_len(uint16_t inst);
size_t sh_t16_rewrite(uint16_t *buf, uint16_t inst, uintptr_t pc, sh_txx_rewrite_info_t *rinfo);

View File

@ -0,0 +1,408 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_t32.h"
#include <inttypes.h>
#include <stdint.h>
#include "sh_log.h"
#include "sh_util.h"
// https://developer.arm.com/documentation/ddi0406/latest
// https://developer.arm.com/documentation/ddi0597/latest
typedef enum {
IGNORED = 0,
B_T3,
B_T4,
BL_IMM_T1,
BLX_IMM_T2,
ADR_T2,
ADR_T3,
LDR_LIT_T2,
LDR_LIT_PC_T2,
LDRB_LIT_T1,
LDRD_LIT_T1,
LDRH_LIT_T1,
LDRSB_LIT_T1,
LDRSH_LIT_T1,
PLD_LIT_T1,
PLI_LIT_T3,
TBB_T1,
TBH_T1,
VLDR_LIT_T1
} sh_t32_type_t;
static sh_t32_type_t sh_t32_get_type(uint32_t inst) {
if (((inst & 0xF800D000) == 0xF0008000) && ((inst & 0x03800000u) != 0x03800000u))
return B_T3;
else if ((inst & 0xF800D000) == 0xF0009000)
return B_T4;
else if ((inst & 0xF800D000) == 0xF000D000)
return BL_IMM_T1;
else if ((inst & 0xF800D000) == 0xF000C000)
return BLX_IMM_T2;
else if ((inst & 0xFBFF8000) == 0xF2AF0000)
return ADR_T2;
else if ((inst & 0xFBFF8000) == 0xF20F0000)
return ADR_T3;
else if ((inst & 0xFF7F0000) == 0xF85F0000)
return ((inst & 0x0000F000u) == 0x0000F000) ? LDR_LIT_PC_T2 : LDR_LIT_T2;
else if (((inst & 0xFF7F0000) == 0xF81F0000) && ((inst & 0xF000u) != 0xF000u))
return LDRB_LIT_T1;
else if ((inst & 0xFF7F0000) == 0xE95F0000)
return LDRD_LIT_T1;
else if (((inst & 0xFF7F0000) == 0xF83F0000) && ((inst & 0xF000u) != 0xF000u))
return LDRH_LIT_T1;
else if (((inst & 0xFF7F0000) == 0xF91F0000) && ((inst & 0xF000u) != 0xF000u))
return LDRSB_LIT_T1;
else if (((inst & 0xFF7F0000) == 0xF93F0000) && ((inst & 0xF000u) != 0xF000u))
return LDRSH_LIT_T1;
else if ((inst & 0xFF7FF000) == 0xF81FF000)
return PLD_LIT_T1;
else if ((inst & 0xFF7FF000) == 0xF91FF000)
return PLI_LIT_T3;
else if ((inst & 0xFFF0FFF0) == 0xE8D0F000)
return TBB_T1;
else if ((inst & 0xFFF0FFF0) == 0xE8D0F010)
return TBH_T1;
else if ((inst & 0xFF3F0C00) == 0xED1F0800)
return VLDR_LIT_T1;
else
return IGNORED;
}
size_t sh_t32_get_rewrite_inst_len(uint16_t high_inst, uint16_t low_inst) {
static uint8_t map[] = {
4, // IGNORED
12, // B_T3
8, // B_T4
12, // BL_IMM_T1
12, // BLX_IMM_T2
12, // ADR_T2
12, // ADR_T3
16, // LDR_LIT_T2
24, // LDR_LIT_PC_T2
16, // LDRB_LIT_T1
16, // LDRD_LIT_T1
16, // LDRH_LIT_T1
16, // LDRSB_LIT_T1
16, // LDRSH_LIT_T1
20, // PLD_LIT_T1
20, // PLI_LIT_T3
32, // TBB_T1
32, // TBH_T1
24 // VLDR_LIT_T1
};
uint32_t inst = (uint32_t)(high_inst << 16u) | low_inst;
return (size_t)(map[sh_t32_get_type(inst)]);
}
static size_t sh_t32_rewrite_b(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
uint32_t j1 = SH_UTIL_GET_BIT_16(low_inst, 13);
uint32_t j2 = SH_UTIL_GET_BIT_16(low_inst, 11);
uint32_t s = SH_UTIL_GET_BIT_16(high_inst, 10);
uint32_t i1 = !(j1 ^ s);
uint32_t i2 = !(j2 ^ s);
uint32_t addr;
if (type == B_T3) {
uint32_t x =
(s << 20u) | (j2 << 19u) | (j1 << 18u) | ((high_inst & 0x3Fu) << 12u) | ((low_inst & 0x7FFu) << 1u);
uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 21u);
addr = SH_UTIL_SET_BIT0(pc + imm32); // thumb -> thumb
} else if (type == B_T4) {
uint32_t x =
(s << 24u) | (i1 << 23u) | (i2 << 22u) | ((high_inst & 0x3FFu) << 12u) | ((low_inst & 0x7FFu) << 1u);
uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 25u);
addr = SH_UTIL_SET_BIT0(pc + imm32); // thumb -> thumb
} else if (type == BL_IMM_T1) {
uint32_t x =
(s << 24u) | (i1 << 23u) | (i2 << 22u) | ((high_inst & 0x3FFu) << 12u) | ((low_inst & 0x7FFu) << 1u);
uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 25u);
addr = SH_UTIL_SET_BIT0(pc + imm32); // thumb -> thumb
} else // type == BLX_IMM_T2
{
uint32_t x =
(s << 24u) | (i1 << 23u) | (i2 << 22u) | ((high_inst & 0x3FFu) << 12u) | ((low_inst & 0x7FEu) << 1u);
uint32_t imm32 = SH_UTIL_SIGN_EXTEND_32(x, 25u);
// In BL and BLX instructions, only when the target instruction set is "arm",
// you need to do 4-byte alignment for PC.
addr = SH_UTIL_ALIGN_4(pc) + imm32; // thumb -> arm, align4
}
addr = sh_txx_fix_addr(addr, rinfo);
size_t idx = 0;
if (type == B_T3) {
uint32_t cond = SH_UTIL_GET_BITS_16(high_inst, 9, 6);
buf[idx++] = 0xD000u | (uint16_t)(cond << 8u); // B<c> #0
buf[idx++] = 0xE003; // B #6
} else if (type == BL_IMM_T1 || type == BLX_IMM_T2) {
buf[idx++] = 0xF20F; // ADD LR, PC, #9
buf[idx++] = 0x0E09; // ...
}
buf[idx++] = 0xF8DF; // LDR.W PC, [PC]
buf[idx++] = 0xF000; // ...
buf[idx++] = addr & 0xFFFFu;
buf[idx++] = addr >> 16u;
return idx * 2; // 8 or 12
}
static size_t sh_t32_rewrite_adr(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
uint32_t rt = SH_UTIL_GET_BITS_16(low_inst, 11, 8); // r0 - r14
uint32_t i = SH_UTIL_GET_BIT_16(high_inst, 10);
uint32_t imm3 = SH_UTIL_GET_BITS_16(low_inst, 14, 12);
uint32_t imm8 = SH_UTIL_GET_BITS_16(low_inst, 7, 0);
uint32_t imm32 = (i << 11u) | (imm3 << 8u) | imm8;
uint32_t addr = (type == ADR_T2 ? (SH_UTIL_ALIGN_4(pc) - imm32) : (SH_UTIL_ALIGN_4(pc) + imm32));
if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
buf[0] = 0xF8DF; // LDR.W Rt, [PC, #4]
buf[1] = (uint16_t)(rt << 12u) + 4u; // ...
buf[2] = 0xE002; // B #4
buf[3] = 0xBF00; // NOP
buf[4] = addr & 0xFFFFu;
buf[5] = addr >> 16u;
return 12;
}
static size_t sh_t32_rewrite_ldr(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
uint32_t u = SH_UTIL_GET_BIT_16(high_inst, 7);
uint32_t rt = SH_UTIL_GET_BITS_16(low_inst, 15, 12); // r0 - r15
uint32_t rt2 = 0; // r0 - r15
uint32_t addr;
if (type == LDRD_LIT_T1) {
rt2 = SH_UTIL_GET_BITS_16(low_inst, 11, 8);
uint32_t imm8 = SH_UTIL_GET_BITS_16(low_inst, 7, 0);
addr = (u ? SH_UTIL_ALIGN_4(pc) + (imm8 << 2u) : SH_UTIL_ALIGN_4(pc) - (imm8 << 2u));
} else {
uint32_t imm12 = (uint32_t)SH_UTIL_GET_BITS_16(low_inst, 11, 0);
addr = (u ? SH_UTIL_ALIGN_4(pc) + imm12 : SH_UTIL_ALIGN_4(pc) - imm12);
}
if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
if (type == LDR_LIT_PC_T2 && rt == 0xF) // Rt == PC
{
buf[0] = 0xB403; // PUSH {R0, R1}
buf[1] = 0xBF00; // NOP
buf[2] = 0xF8DF; // LDR.W R0, [PC, #4]
buf[3] = 0x0004; // ...
buf[4] = 0xE002; // B #4
buf[5] = 0xBF00; // NOP
buf[6] = addr & 0xFFFFu; //
buf[7] = addr >> 16u; //
buf[8] = 0xF8D0; // LDR.W R0, [R0]
buf[9] = 0x0000; // ...
buf[10] = 0x9001; // STR R0, [SP, #4]
buf[11] = 0xBD01; // POP {R0, PC}
return 24;
} else {
buf[0] = 0xF8DF; // LDR.W Rt, [PC, #4]
buf[1] = (uint16_t)(rt << 12u) | 4u; // ...
buf[2] = 0xE002; // B #4
buf[3] = 0xBF00; // NOP
buf[4] = addr & 0xFFFFu;
buf[5] = addr >> 16u;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wswitch"
switch (type) {
case LDR_LIT_T2:
buf[6] = (uint16_t)(0xF8D0 + rt); // LDR.W Rt, [Rt]
buf[7] = (uint16_t)(rt << 12u); // ...
break;
case LDRB_LIT_T1:
buf[6] = (uint16_t)(0xF890 + rt); // LDRB.W Rt, [Rt]
buf[7] = (uint16_t)(rt << 12u); // ...
break;
case LDRD_LIT_T1:
buf[6] = (uint16_t)(0xE9D0 + rt); // LDRD Rt, Rt2, [Rt]
buf[7] = (uint16_t)(rt << 12u) + (uint16_t)(rt2 << 8u); // ...
break;
case LDRH_LIT_T1:
buf[6] = (uint16_t)(0xF8B0 + rt); // LDRH.W Rt, [Rt]
buf[7] = (uint16_t)(rt << 12u); // ...
break;
case LDRSB_LIT_T1:
buf[6] = (uint16_t)(0xF990 + rt); // LDRSB.W Rt, [Rt]
buf[7] = (uint16_t)(rt << 12u); // ...
break;
case LDRSH_LIT_T1:
buf[6] = (uint16_t)(0xF9B0 + rt); // LDRSH.W Rt, [Rt]
buf[7] = (uint16_t)(rt << 12u); // ...
break;
}
#pragma clang diagnostic pop
return 16;
}
}
static size_t sh_t32_rewrite_pl(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
uint32_t u = SH_UTIL_GET_BIT_16(high_inst, 7);
uint32_t imm12 = SH_UTIL_GET_BITS_16(low_inst, 11, 0);
uint32_t addr = (u ? SH_UTIL_ALIGN_4(pc) + imm12 : SH_UTIL_ALIGN_4(pc) - imm12);
addr = sh_txx_fix_addr(addr, rinfo);
buf[0] = 0xB401; // PUSH {R0}
buf[1] = 0xBF00; // NOP
buf[2] = 0xF8DF; // LDR.W R0, [PC, #8]
buf[3] = 0x0008; // ...
if (type == PLD_LIT_T1) {
buf[4] = 0xF890; // PLD [R0]
buf[5] = 0xF000; // ...
} else {
buf[4] = 0xF990; // PLI [R0]
buf[5] = 0xF000; // ...
}
buf[6] = 0xBC01; // POP {R0}
buf[7] = 0xE001; // B #2
buf[8] = addr & 0xFFFFu;
buf[9] = addr >> 16u;
return 20;
}
static size_t sh_t32_rewrite_tb(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_t32_type_t type, sh_txx_rewrite_info_t *rinfo) {
// If TBB/TBH is not the last instruction that needs to be rewritten,
// the rewriting can NOT be completed.
uintptr_t target_addr = SH_UTIL_CLEAR_BIT0(pc - 4);
if (target_addr + 4 != rinfo->end_addr) return 0; // rewrite failed
uint32_t rn = SH_UTIL_GET_BITS_16(high_inst, 3, 0);
uint32_t rm = SH_UTIL_GET_BITS_16(low_inst, 3, 0);
uint32_t rx, ry; // r0 - r7
for (rx = 7;; --rx)
if (rx != rn && rx != rm) break;
for (ry = 7;; --ry)
if (ry != rn && ry != rm && ry != rx) break;
buf[0] = (uint16_t)(0xB500u | (1u << rx) | (1u << ry)); // PUSH {Rx, Ry, LR}
buf[1] = 0xBF00; // NOP
buf[2] = 0xF8DF; // LDR.W Rx, [PC, #20]
buf[3] = (uint16_t)(rx << 12u) | 20u; // ...
if (type == TBB_T1) {
buf[4] = (uint16_t)(0xEB00u | (rn == 0xF ? rx : rn)); // ADD.W Ry, Rx|Rn, Rm
buf[5] = (uint16_t)(0x0000u | (ry << 8u) | rm); // ...
buf[6] = (uint16_t)(0x7800u | (ry << 3u) | ry); // LDRB Ry, [Ry]
buf[7] = 0xBF00; // NOP
} else {
buf[4] = (uint16_t)(0xEB00u | (rn == 0xF ? rx : rn)); // ADD.W Ry, Rx|Rn, Rm, LSL #1
buf[5] = (uint16_t)(0x0040u | (ry << 8u) | rm); // ...
buf[6] = (uint16_t)(0x8800u | (ry << 3u) | ry); // LDRH Ry, [Ry]
buf[7] = 0xBF00; // NOP
}
buf[8] = (uint16_t)(0xEB00u | rx); // ADD Rx, Rx, Ry, LSL #1
buf[9] = (uint16_t)(0x0040u | (rx << 8u) | ry); // ...
buf[10] = (uint16_t)(0x3001u | (rx << 8u)); // ADD Rx, #1
buf[11] = (uint16_t)(0x9002u | (rx << 8u)); // STR Rx, [SP, #8]
buf[12] = (uint16_t)(0xBD00u | (1u << rx) | (1u << ry)); // POP {Rx, Ry, PC}
buf[13] = 0xBF00; // NOP
buf[14] = pc & 0xFFFFu;
buf[15] = pc >> 16u;
return 32;
}
static size_t sh_t32_rewrite_vldr(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_txx_rewrite_info_t *rinfo) {
uint32_t u = SH_UTIL_GET_BIT_16(high_inst, 7);
uint32_t D = SH_UTIL_GET_BIT_16(high_inst, 6);
uint32_t vd = SH_UTIL_GET_BITS_16(low_inst, 15, 12);
uint32_t size = SH_UTIL_GET_BITS_16(low_inst, 9, 8);
uint32_t imm8 = SH_UTIL_GET_BITS_16(low_inst, 7, 0);
uint32_t esize = (8u << size);
uint32_t imm32 = (esize == 16 ? imm8 << 1u : imm8 << 2u);
uint32_t addr = (u ? SH_UTIL_ALIGN_4(pc) + imm32 : SH_UTIL_ALIGN_4(pc) - imm32);
if (sh_txx_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
buf[0] = 0xB401; // PUSH {R0}
buf[1] = 0xBF00; // NOP
buf[2] = 0xF8DF; // LDR.W R0, [PC, #4]
buf[3] = 0x0004; // ...
buf[4] = 0xE002; // B #4
buf[5] = 0xBF00; // NOP
buf[6] = addr & 0xFFFFu; //
buf[7] = addr >> 16u; //
buf[8] = (uint16_t)(0xED90u | D << 6u); // VLDR Sd|Dd, [R0]
buf[9] = (uint16_t)(0x800u | vd << 12u | size << 8u); // ...
buf[10] = 0xBC01; // POP {R0}
buf[11] = 0xBF00; // NOP
return 24;
}
size_t sh_t32_rewrite(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_txx_rewrite_info_t *rinfo) {
uint32_t inst = (uint32_t)(high_inst << 16u) | low_inst;
sh_t32_type_t type = sh_t32_get_type(inst);
SH_LOG_INFO("t32 rewrite: type %d, high inst %" PRIx16 ", low inst %" PRIx16, type, high_inst, low_inst);
if (type == B_T3 || type == B_T4 || type == BL_IMM_T1 || type == BLX_IMM_T2)
return sh_t32_rewrite_b(buf, high_inst, low_inst, pc, type, rinfo);
else if (type == ADR_T2 || type == ADR_T3)
return sh_t32_rewrite_adr(buf, high_inst, low_inst, pc, type, rinfo);
else if (type == LDR_LIT_T2 || type == LDR_LIT_PC_T2 || type == LDRB_LIT_T1 || type == LDRD_LIT_T1 ||
type == LDRH_LIT_T1 || type == LDRSB_LIT_T1 || type == LDRSH_LIT_T1)
return sh_t32_rewrite_ldr(buf, high_inst, low_inst, pc, type, rinfo);
else if (type == PLD_LIT_T1 || type == PLI_LIT_T3)
return sh_t32_rewrite_pl(buf, high_inst, low_inst, pc, type, rinfo);
else if (type == TBB_T1 || type == TBH_T1)
return sh_t32_rewrite_tb(buf, high_inst, low_inst, pc, type, rinfo);
else if (type == VLDR_LIT_T1)
return sh_t32_rewrite_vldr(buf, high_inst, low_inst, pc, rinfo);
else {
// IGNORED
buf[0] = high_inst;
buf[1] = low_inst;
return 4;
}
}
size_t sh_t32_absolute_jump(uint16_t *buf, bool is_align4, uintptr_t addr) {
size_t i = 0;
if (!is_align4) buf[i++] = 0xBF00; // NOP
buf[i++] = 0xF8DF; // LDR.W PC, [PC]
buf[i++] = 0xF000; // ...
buf[i++] = addr & 0xFFFFu;
buf[i++] = addr >> 16u;
return i * 2;
}
size_t sh_t32_relative_jump(uint16_t *buf, uintptr_t addr, uintptr_t pc) {
uint32_t imm32 = addr - pc;
uint32_t s = SH_UTIL_GET_BIT_32(imm32, 24);
uint32_t i1 = SH_UTIL_GET_BIT_32(imm32, 23);
uint32_t i2 = SH_UTIL_GET_BIT_32(imm32, 22);
uint32_t imm10 = SH_UTIL_GET_BITS_32(imm32, 21, 12);
uint32_t imm11 = SH_UTIL_GET_BITS_32(imm32, 11, 1);
uint32_t j1 = (!i1) ^ s;
uint32_t j2 = (!i2) ^ s;
buf[0] = (uint16_t)(0xF000u | (s << 10u) | imm10);
buf[1] = (uint16_t)(0x9000u | (j1 << 13u) | (j2 << 11u) | imm11);
return 4;
}

View File

@ -0,0 +1,36 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include "sh_txx.h"
size_t sh_t32_get_rewrite_inst_len(uint16_t high_inst, uint16_t low_inst);
size_t sh_t32_rewrite(uint16_t *buf, uint16_t high_inst, uint16_t low_inst, uintptr_t pc,
sh_txx_rewrite_info_t *rinfo);
size_t sh_t32_absolute_jump(uint16_t *buf, bool is_align4, uintptr_t addr);
size_t sh_t32_relative_jump(uint16_t *buf, uintptr_t addr, uintptr_t pc);

View File

@ -0,0 +1,60 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_txx.h"
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <string.h>
#include "sh_log.h"
#include "sh_util.h"
bool sh_txx_is_addr_need_fix(uintptr_t addr, sh_txx_rewrite_info_t *rinfo) {
return (rinfo->start_addr <= addr && addr < rinfo->end_addr);
}
uintptr_t sh_txx_fix_addr(uintptr_t addr, sh_txx_rewrite_info_t *rinfo) {
bool is_thumb = SH_UTIL_IS_THUMB(addr);
if (is_thumb) addr = SH_UTIL_CLEAR_BIT0(addr);
if (rinfo->start_addr <= addr && addr < rinfo->end_addr) {
uintptr_t cursor_addr = rinfo->start_addr;
size_t offset = 0;
for (size_t i = 0; i < rinfo->inst_lens_cnt; i++) {
if (cursor_addr >= addr) break;
cursor_addr += 2;
offset += rinfo->inst_lens[i];
}
uintptr_t fixed_addr = (uintptr_t)rinfo->buf + offset;
if (is_thumb) fixed_addr = SH_UTIL_SET_BIT0(fixed_addr);
SH_LOG_INFO("txx rewrite: fix addr %" PRIxPTR " -> %" PRIxPTR, addr, fixed_addr);
return fixed_addr;
}
if (is_thumb) addr = SH_UTIL_SET_BIT0(addr);
return addr;
}

View File

@ -0,0 +1,39 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
typedef struct {
uintptr_t start_addr;
uintptr_t end_addr;
uint16_t *buf;
size_t buf_offset;
size_t inst_lens[13]; // 26 / 2 = 13
size_t inst_lens_cnt;
} sh_txx_rewrite_info_t;
bool sh_txx_is_addr_need_fix(uintptr_t addr, sh_txx_rewrite_info_t *rinfo);
uintptr_t sh_txx_fix_addr(uintptr_t addr, sh_txx_rewrite_info_t *rinfo);

View File

@ -0,0 +1,310 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_a64.h"
#include <inttypes.h>
#include <sh_util.h>
#include <stdint.h>
#include "sh_log.h"
// https://developer.arm.com/documentation/ddi0487/latest
// https://developer.arm.com/documentation/ddi0602/latest
typedef enum {
IGNORED = 0,
B,
B_COND,
BL,
ADR,
ADRP,
LDR_LIT_32,
LDR_LIT_64,
LDRSW_LIT,
PRFM_LIT,
LDR_SIMD_LIT_32,
LDR_SIMD_LIT_64,
LDR_SIMD_LIT_128,
CBZ,
CBNZ,
TBZ,
TBNZ
} sh_a64_type_t;
static sh_a64_type_t sh_a64_get_type(uint32_t inst) {
if ((inst & 0xFC000000) == 0x14000000)
return B;
else if ((inst & 0xFF000010) == 0x54000000)
return B_COND;
else if ((inst & 0xFC000000) == 0x94000000)
return BL;
else if ((inst & 0x9F000000) == 0x10000000)
return ADR;
else if ((inst & 0x9F000000) == 0x90000000)
return ADRP;
else if ((inst & 0xFF000000) == 0x18000000)
return LDR_LIT_32;
else if ((inst & 0xFF000000) == 0x58000000)
return LDR_LIT_64;
else if ((inst & 0xFF000000) == 0x98000000)
return LDRSW_LIT;
else if ((inst & 0xFF000000) == 0xD8000000)
return PRFM_LIT;
else if ((inst & 0xFF000000) == 0x1C000000)
return LDR_SIMD_LIT_32;
else if ((inst & 0xFF000000) == 0x5C000000)
return LDR_SIMD_LIT_64;
else if ((inst & 0xFF000000) == 0x9C000000)
return LDR_SIMD_LIT_128;
else if ((inst & 0x7F000000u) == 0x34000000)
return CBZ;
else if ((inst & 0x7F000000u) == 0x35000000)
return CBNZ;
else if ((inst & 0x7F000000u) == 0x36000000)
return TBZ;
else if ((inst & 0x7F000000u) == 0x37000000)
return TBNZ;
else
return IGNORED;
}
size_t sh_a64_get_rewrite_inst_len(uint32_t inst) {
static uint8_t map[] = {
4, // IGNORED
20, // B
28, // B_COND
20, // BL
16, // ADR
16, // ADRP
20, // LDR_LIT_32
20, // LDR_LIT_64
20, // LDRSW_LIT
28, // PRFM_LIT
28, // LDR_SIMD_LIT_32
28, // LDR_SIMD_LIT_64
28, // LDR_SIMD_LIT_128
24, // CBZ
24, // CBNZ
24, // TBZ
24 // TBNZ
};
return (size_t)(map[sh_a64_get_type(inst)]);
}
static bool sh_a64_is_addr_need_fix(uintptr_t addr, sh_a64_rewrite_info_t *rinfo) {
return (rinfo->start_addr <= addr && addr < rinfo->end_addr);
}
static uintptr_t sh_a64_fix_addr(uintptr_t addr, sh_a64_rewrite_info_t *rinfo) {
if (rinfo->start_addr <= addr && addr < rinfo->end_addr) {
uintptr_t cursor_addr = rinfo->start_addr;
size_t offset = 0;
for (size_t i = 0; i < rinfo->inst_lens_cnt; i++) {
if (cursor_addr >= addr) break;
cursor_addr += 4;
offset += rinfo->inst_lens[i];
}
uintptr_t fixed_addr = (uintptr_t)rinfo->buf + offset;
SH_LOG_INFO("a64 rewrite: fix addr %" PRIxPTR " -> %" PRIxPTR, addr, fixed_addr);
return fixed_addr;
}
return addr;
}
static size_t sh_a64_rewrite_b(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_type_t type,
sh_a64_rewrite_info_t *rinfo) {
uint64_t imm64;
if (type == B_COND) {
uint64_t imm19 = SH_UTIL_GET_BITS_32(inst, 23, 5);
imm64 = SH_UTIL_SIGN_EXTEND_64(imm19 << 2u, 21u);
} else {
uint64_t imm26 = SH_UTIL_GET_BITS_32(inst, 25, 0);
imm64 = SH_UTIL_SIGN_EXTEND_64(imm26 << 2u, 28u);
}
uint64_t addr = pc + imm64;
addr = sh_a64_fix_addr(addr, rinfo);
size_t idx = 0;
if (type == B_COND) {
buf[idx++] = (inst & 0xFF00001F) | 0x40u; // B.<cond> #8
buf[idx++] = 0x14000006; // B #24
}
buf[idx++] = 0x58000051; // LDR X17, #8
buf[idx++] = 0x14000003; // B #12
buf[idx++] = addr & 0xFFFFFFFF;
buf[idx++] = addr >> 32u;
if (type == BL)
buf[idx++] = 0xD63F0220; // BLR X17
else
buf[idx++] = 0xD61F0220; // BR X17
return idx * 4; // 20 or 28
}
static size_t sh_a64_rewrite_adr(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_type_t type,
sh_a64_rewrite_info_t *rinfo) {
uint32_t xd = SH_UTIL_GET_BITS_32(inst, 4, 0);
uint64_t immlo = SH_UTIL_GET_BITS_32(inst, 30, 29);
uint64_t immhi = SH_UTIL_GET_BITS_32(inst, 23, 5);
uint64_t addr;
if (type == ADR)
addr = pc + SH_UTIL_SIGN_EXTEND_64((immhi << 2u) | immlo, 21u);
else // ADRP
addr = (pc & 0xFFFFFFFFFFFFF000) + SH_UTIL_SIGN_EXTEND_64((immhi << 14u) | (immlo << 12u), 33u);
if (sh_a64_is_addr_need_fix(addr, rinfo)) return 0; // rewrite failed
buf[0] = 0x58000040u | xd; // LDR Xd, #8
buf[1] = 0x14000003; // B #12
buf[2] = addr & 0xFFFFFFFF;
buf[3] = addr >> 32u;
return 16;
}
static size_t sh_a64_rewrite_ldr(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_type_t type,
sh_a64_rewrite_info_t *rinfo) {
uint32_t rt = SH_UTIL_GET_BITS_32(inst, 4, 0);
uint64_t imm19 = SH_UTIL_GET_BITS_32(inst, 23, 5);
uint64_t offset = SH_UTIL_SIGN_EXTEND_64((imm19 << 2u), 21u);
uint64_t addr = pc + offset;
if (sh_a64_is_addr_need_fix(addr, rinfo)) {
if (type != PRFM_LIT) return 0; // rewrite failed
addr = sh_a64_fix_addr(addr, rinfo);
}
if (type == LDR_LIT_32 || type == LDR_LIT_64 || type == LDRSW_LIT) {
buf[0] = 0x58000060u | rt; // LDR Xt, #12
if (type == LDR_LIT_32)
buf[1] = 0xB9400000 | rt | (rt << 5u); // LDR Wt, [Xt]
else if (type == LDR_LIT_64)
buf[1] = 0xF9400000 | rt | (rt << 5u); // LDR Xt, [Xt]
else
// LDRSW_LIT
buf[1] = 0xB9800000 | rt | (rt << 5u); // LDRSW Xt, [Xt]
buf[2] = 0x14000003; // B #12
buf[3] = addr & 0xFFFFFFFF;
buf[4] = addr >> 32u;
return 20;
} else {
buf[0] = 0xA93F47F0; // STP X16, X17, [SP, -0x10]
buf[1] = 0x58000091; // LDR X17, #16
if (type == PRFM_LIT)
buf[2] = 0xF9800220 | rt; // PRFM Rt, [X17]
else if (type == LDR_SIMD_LIT_32)
buf[2] = 0xBD400220 | rt; // LDR St, [X17]
else if (type == LDR_SIMD_LIT_64)
buf[2] = 0xFD400220 | rt; // LDR Dt, [X17]
else
// LDR_SIMD_LIT_128
buf[2] = 0x3DC00220u | rt; // LDR Qt, [X17]
buf[3] = 0xF85F83F1; // LDR X17, [SP, -0x8]
buf[4] = 0x14000003; // B #12
buf[5] = addr & 0xFFFFFFFF;
buf[6] = addr >> 32u;
return 28;
}
}
static size_t sh_a64_rewrite_cb(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo) {
uint64_t imm19 = SH_UTIL_GET_BITS_32(inst, 23, 5);
uint64_t offset = SH_UTIL_SIGN_EXTEND_64((imm19 << 2u), 21u);
uint64_t addr = pc + offset;
addr = sh_a64_fix_addr(addr, rinfo);
buf[0] = (inst & 0xFF00001F) | 0x40u; // CB(N)Z Rt, #8
buf[1] = 0x14000005; // B #20
buf[2] = 0x58000051; // LDR X17, #8
buf[3] = 0xd61f0220; // BR X17
buf[4] = addr & 0xFFFFFFFF;
buf[5] = addr >> 32u;
return 24;
}
static size_t sh_a64_rewrite_tb(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo) {
uint64_t imm14 = SH_UTIL_GET_BITS_32(inst, 18, 5);
uint64_t offset = SH_UTIL_SIGN_EXTEND_64((imm14 << 2u), 16u);
uint64_t addr = pc + offset;
addr = sh_a64_fix_addr(addr, rinfo);
buf[0] = (inst & 0xFFF8001F) | 0x40u; // TB(N)Z Rt, #<imm>, #8
buf[1] = 0x14000005; // B #20
buf[2] = 0x58000051; // LDR X17, #8
buf[3] = 0xd61f0220; // BR X17
buf[4] = addr & 0xFFFFFFFF;
buf[5] = addr >> 32u;
return 24;
}
size_t sh_a64_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo) {
sh_a64_type_t type = sh_a64_get_type(inst);
SH_LOG_INFO("a64 rewrite: type %d, inst %" PRIx32, type, inst);
if (type == B || type == B_COND || type == BL)
return sh_a64_rewrite_b(buf, inst, pc, type, rinfo);
else if (type == ADR || type == ADRP)
return sh_a64_rewrite_adr(buf, inst, pc, type, rinfo);
else if (type == LDR_LIT_32 || type == LDR_LIT_64 || type == LDRSW_LIT || type == PRFM_LIT ||
type == LDR_SIMD_LIT_32 || type == LDR_SIMD_LIT_64 || type == LDR_SIMD_LIT_128)
return sh_a64_rewrite_ldr(buf, inst, pc, type, rinfo);
else if (type == CBZ || type == CBNZ)
return sh_a64_rewrite_cb(buf, inst, pc, rinfo);
else if (type == TBZ || type == TBNZ)
return sh_a64_rewrite_tb(buf, inst, pc, rinfo);
else {
// IGNORED
buf[0] = inst;
return 4;
}
}
size_t sh_a64_absolute_jump_with_br(uint32_t *buf, uintptr_t addr) {
buf[0] = 0x58000051; // LDR X17, #8
buf[1] = 0xd61f0220; // BR X17
buf[2] = addr & 0xFFFFFFFF;
buf[3] = addr >> 32u;
return 16;
}
// Use RET instead of BR to bypass arm64 BTI.
//
// ref:
// https://developer.arm.com/documentation/102433/0100/Jump-oriented-programming
// https://developer.arm.com/documentation/ddi0602/2023-06/Base-Instructions/BTI--Branch-Target-Identification-?lang=en
// https://github.com/torvalds/linux/commit/8ef8f360cf30be12382f89ff48a57fbbd9b31c14
// https://android-review.googlesource.com/c/platform/bionic/+/1242754
// https://developer.android.com/ndk/guides/abis#armv9_enabling_pac_and_bti_for_cc
// https://developer.arm.com/documentation/100067/0612/armclang-Command-line-Options/-mbranch-protection
size_t sh_a64_absolute_jump_with_ret(uint32_t *buf, uintptr_t addr) {
buf[0] = 0x58000051; // LDR X17, #8
buf[1] = 0xd65f0220; // RET X17
buf[2] = addr & 0xFFFFFFFF;
buf[3] = addr >> 32u;
return 16;
}
size_t sh_a64_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc) {
buf[0] = 0x14000000u | (((addr - pc) & 0x0FFFFFFFu) >> 2u); // B <label>
return 4;
}

View File

@ -0,0 +1,44 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "sh_inst.h"
typedef struct {
uintptr_t start_addr;
uintptr_t end_addr;
uint32_t *buf;
size_t buf_offset;
size_t inst_lens[4];
size_t inst_lens_cnt;
} sh_a64_rewrite_info_t;
size_t sh_a64_get_rewrite_inst_len(uint32_t inst);
size_t sh_a64_rewrite(uint32_t *buf, uint32_t inst, uintptr_t pc, sh_a64_rewrite_info_t *rinfo);
size_t sh_a64_absolute_jump_with_br(uint32_t *buf, uintptr_t addr);
size_t sh_a64_absolute_jump_with_ret(uint32_t *buf, uintptr_t addr);
size_t sh_a64_relative_jump(uint32_t *buf, uintptr_t addr, uintptr_t pc);

View File

@ -0,0 +1,203 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_inst.h"
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include "sh_a64.h"
#include "sh_config.h"
#include "sh_enter.h"
#include "sh_exit.h"
#include "sh_log.h"
#include "sh_sig.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "xdl.h"
static int sh_inst_hook_rewrite(sh_inst_t *self, uintptr_t target_addr, uintptr_t *orig_addr,
uintptr_t *orig_addr2) {
// backup original instructions (length: 4 or 16)
memcpy((void *)(self->backup), (void *)target_addr, self->backup_len);
// package the information passed to rewrite
sh_a64_rewrite_info_t rinfo;
rinfo.start_addr = target_addr;
rinfo.end_addr = target_addr + self->backup_len;
rinfo.buf = (uint32_t *)self->enter_addr;
rinfo.buf_offset = 0;
rinfo.inst_lens_cnt = self->backup_len / 4;
for (uintptr_t i = 0; i < self->backup_len; i += 4)
rinfo.inst_lens[i / 4] = sh_a64_get_rewrite_inst_len(*((uint32_t *)(target_addr + i)));
// rewrite original instructions (fill in enter)
uintptr_t pc = target_addr;
for (uintptr_t i = 0; i < self->backup_len; i += 4, pc += 4) {
size_t offset = sh_a64_rewrite((uint32_t *)(self->enter_addr + rinfo.buf_offset),
*((uint32_t *)(target_addr + i)), pc, &rinfo);
if (0 == offset) return SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED;
rinfo.buf_offset += offset;
}
// absolute jump back to remaining original instructions (fill in enter)
rinfo.buf_offset += sh_a64_absolute_jump_with_ret((uint32_t *)(self->enter_addr + rinfo.buf_offset),
target_addr + self->backup_len);
sh_util_clear_cache(self->enter_addr, rinfo.buf_offset);
// save original function address
if (NULL != orig_addr) __atomic_store_n(orig_addr, self->enter_addr, __ATOMIC_SEQ_CST);
if (NULL != orig_addr2) __atomic_store_n(orig_addr2, self->enter_addr, __ATOMIC_SEQ_CST);
return 0;
}
#ifdef SH_CONFIG_TRY_WITH_EXIT
// B: [-128M, +128M - 4]
#define SH_INST_A64_B_RANGE_LOW (134217728)
#define SH_INST_A64_B_RANGE_HIGH (134217724)
static int sh_inst_hook_with_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
int r;
uintptr_t pc = target_addr;
self->backup_len = 4;
if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
// alloc an exit for absolute jump
sh_a64_absolute_jump_with_br(self->exit, new_addr);
if (0 !=
(r = sh_exit_alloc(&self->exit_addr, (uint16_t *)&self->exit_type, pc, dlinfo, (uint8_t *)(self->exit),
sizeof(self->exit), SH_INST_A64_B_RANGE_LOW, SH_INST_A64_B_RANGE_HIGH)))
return r;
// rewrite
if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC)) {
r = SHADOWHOOK_ERRNO_MPROT;
goto err;
}
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = sh_inst_hook_rewrite(self, target_addr, orig_addr, orig_addr2);
}
SH_SIG_CATCH() {
r = SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
goto err;
}
SH_SIG_EXIT
if (0 != r) goto err;
// relative jump to the exit by overwriting the head of original function
sh_a64_relative_jump(self->trampo, self->exit_addr, pc);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) goto err;
SH_LOG_INFO("a64: hook (WITH EXIT) OK. target %" PRIxPTR " -> exit %" PRIxPTR " -> new %" PRIxPTR
" -> enter %" PRIxPTR " -> remaining %" PRIxPTR,
target_addr, self->exit_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
return 0;
err:
sh_exit_free(self->exit_addr, (uint16_t)self->exit_type, (uint8_t *)(self->exit), sizeof(self->exit));
self->exit_addr = 0; // this is a flag for with-exit or without-exit
return r;
}
#endif
static int sh_inst_hook_without_exit(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo,
uintptr_t new_addr, uintptr_t *orig_addr, uintptr_t *orig_addr2) {
int r;
self->backup_len = 16;
if (dlinfo->dli_ssize < self->backup_len) return SHADOWHOOK_ERRNO_HOOK_SYMSZ;
// rewrite
if (0 != sh_util_mprotect(target_addr, self->backup_len, PROT_READ | PROT_WRITE | PROT_EXEC))
return SHADOWHOOK_ERRNO_MPROT;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = sh_inst_hook_rewrite(self, target_addr, orig_addr, orig_addr2);
}
SH_SIG_CATCH() {
return SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH;
}
SH_SIG_EXIT
if (0 != r) return r;
// absolute jump to new function address by overwriting the head of original function
sh_a64_absolute_jump_with_br(self->trampo, new_addr);
__atomic_thread_fence(__ATOMIC_SEQ_CST);
if (0 != (r = sh_util_write_inst(target_addr, self->trampo, self->backup_len))) return r;
SH_LOG_INFO("a64: hook (WITHOUT EXIT) OK. target %" PRIxPTR " -> new %" PRIxPTR " -> enter %" PRIxPTR
" -> remaining %" PRIxPTR,
target_addr, new_addr, self->enter_addr, target_addr + self->backup_len);
return 0;
}
int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
uintptr_t *orig_addr, uintptr_t *orig_addr2) {
self->enter_addr = sh_enter_alloc();
if (0 == self->enter_addr) return SHADOWHOOK_ERRNO_HOOK_ENTER;
int r;
#ifdef SH_CONFIG_TRY_WITH_EXIT
if (0 == (r = sh_inst_hook_with_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2))) return r;
#endif
if (0 == (r = sh_inst_hook_without_exit(self, target_addr, dlinfo, new_addr, orig_addr, orig_addr2)))
return r;
// hook failed
if (NULL != orig_addr) *orig_addr = 0;
if (NULL != orig_addr2) *orig_addr2 = 0;
sh_enter_free(self->enter_addr);
return r;
}
int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr) {
int r;
// restore the instructions at the target address
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = memcmp((void *)target_addr, self->trampo, self->backup_len);
}
SH_SIG_CATCH() {
return SHADOWHOOK_ERRNO_UNHOOK_CMP_CRASH;
}
SH_SIG_EXIT
if (0 != r) return SHADOWHOOK_ERRNO_UNHOOK_TRAMPO_MISMATCH;
if (0 != (r = sh_util_write_inst(target_addr, self->backup, self->backup_len))) return r;
__atomic_thread_fence(__ATOMIC_SEQ_CST);
// free memory space for exit
if (0 != self->exit_addr)
if (0 != (r = sh_exit_free(self->exit_addr, (uint16_t)self->exit_type, (uint8_t *)(self->exit),
sizeof(self->exit))))
return r;
// free memory space for enter
sh_enter_free(self->enter_addr);
SH_LOG_INFO("a64: unhook OK. target %" PRIxPTR, target_addr);
return 0;
}

View File

@ -0,0 +1,42 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "xdl.h"
typedef struct {
uint32_t trampo[4]; // align 16 // length == backup_len
uint8_t backup[16]; // align 16
uint32_t backup_len; // == 4 or 16
uint32_t exit_type;
uintptr_t exit_addr;
uint32_t exit[4];
uintptr_t enter_addr;
} sh_inst_t;
int sh_inst_hook(sh_inst_t *self, uintptr_t target_addr, xdl_info_t *dlinfo, uintptr_t new_addr,
uintptr_t *orig_addr, uintptr_t *orig_addr2);
int sh_inst_unhook(sh_inst_t *self, uintptr_t target_addr);

View File

@ -0,0 +1,291 @@
// Copyright (c) 2021-2023 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
// version 1.0.4
#include "bytesig.h"
#include <dlfcn.h>
#include <errno.h>
#include <pthread.h>
#include <setjmp.h>
#include <signal.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include <sys/syscall.h>
#include <unistd.h>
#define BYTESIG_DEBUG 0
#if BYTESIG_DEBUG
#include <android/log.h>
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#define BYTESIG_LOG(fmt, ...) __android_log_print(ANDROID_LOG_INFO, "bytesig_tag", fmt, ##__VA_ARGS__)
#pragma clang diagnostic pop
#else
#define BYTESIG_LOG(fmt, ...)
#endif
typedef enum {
BYTESIG_STATUS_UNAVAILABLE = 0,
BYTESIG_STATUS_SIG32 = 1, // use sigset_t
BYTESIG_STATUS_SIG64 = 2 // use sigset64_t
} bytesig_status_t;
static bytesig_status_t bytesig_status = BYTESIG_STATUS_UNAVAILABLE;
extern __attribute((weak)) int sigfillset64(sigset64_t *);
extern __attribute((weak)) int sigemptyset64(sigset64_t *);
extern __attribute((weak)) int sigaddset64(sigset64_t *, int);
extern __attribute((weak)) int sigismember64(const sigset64_t *, int);
typedef int (*bytesig_sigaction64_t)(int, const struct sigaction64 *, struct sigaction64 *);
typedef int (*bytesig_sigaction_t)(int, const struct sigaction *, struct sigaction *);
typedef int (*bytesig_sigprocmask64_t)(int, const sigset64_t *, sigset64_t *);
typedef int (*bytesig_sigprocmask_t)(int, const sigset_t *, sigset_t *);
static void *bytesig_sigaction; // point to libc's sigaction64() or libc's sigaction()
static void *bytesig_sigprocmask; // point to libc's sigprocmask() or libc's sigprocmask64()
__attribute__((constructor)) static void bytesig_ctor(void) {
void *libc = dlopen("libc.so", RTLD_LOCAL);
if (__predict_false(NULL == libc)) return;
if (__predict_true(NULL != sigfillset64 && NULL != sigemptyset64 && NULL != sigaddset64 &&
NULL != sigismember64)) {
if (__predict_true(NULL != (bytesig_sigaction = dlsym(libc, "sigaction64")) &&
NULL != (bytesig_sigprocmask = dlsym(libc, "sigprocmask64")))) {
bytesig_status = BYTESIG_STATUS_SIG64;
goto end;
}
}
if (__predict_true(NULL != (bytesig_sigaction = dlsym(libc, "sigaction")) &&
NULL != (bytesig_sigprocmask = dlsym(libc, "sigprocmask")))) {
bytesig_status = BYTESIG_STATUS_SIG32;
}
end:
dlclose(libc);
}
#define BYTESIG_PROTECTED_THREADS_MAX 256
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct {
pid_t tids[BYTESIG_PROTECTED_THREADS_MAX];
sigjmp_buf *jbufs[BYTESIG_PROTECTED_THREADS_MAX];
union {
struct sigaction64 prev_action64;
struct sigaction prev_action;
};
} bytesig_signal_t;
#pragma clang diagnostic pop
// array index is signal number, corresponds to signals 1 to 31, except 9 and 19
static bytesig_signal_t *bytesig_signal_array[__SIGRTMIN];
static void bytesig_sigorset64(sigset64_t *dest, sigset64_t *left, sigset64_t *right) {
sigemptyset64(dest);
for (size_t i = 1; i < sizeof(sigset64_t) * CHAR_BIT; i++)
if (sigismember64(left, (int)i) == 1 || sigismember64(right, (int)i) == 1) sigaddset64(dest, (int)i);
}
static void bytesig_sigorset(sigset_t *dest, sigset_t *left, sigset_t *right) {
sigemptyset(dest);
for (size_t i = 1; i < sizeof(sigset_t) * CHAR_BIT; i++)
if (sigismember(left, (int)i) == 1 || sigismember(right, (int)i) == 1) sigaddset(dest, (int)i);
}
__attribute__((noinline)) static void bytesig_handler_internal(int signum, siginfo_t *siginfo,
void *context) {
bytesig_signal_t *sig = bytesig_signal_array[signum];
// check protect info & do siglongjmp
pid_t tid = gettid();
if (__predict_false(0 == tid)) tid = (pid_t)syscall(SYS_gettid);
for (size_t i = 0; i < BYTESIG_PROTECTED_THREADS_MAX; i++) {
if (__predict_false(tid == __atomic_load_n(&(sig->tids[i]), __ATOMIC_RELAXED))) {
BYTESIG_LOG("siglongjmp signal %d (code %d) at %zu", signum, siginfo->si_code, i);
unsigned int ret_signum = (((unsigned int)signum & 0xFFU) << 16U);
unsigned int ret_code = 0U;
if (siginfo->si_code > 0)
ret_code = (((unsigned int)(siginfo->si_code) & 0xFFU) << 8U);
else if (siginfo->si_code < 0)
ret_code = (unsigned int)(-(siginfo->si_code)) & 0xFFU;
int ret_val = (int)(ret_signum | ret_code);
siglongjmp(*(__atomic_load_n(&(sig->jbufs[i]), __ATOMIC_RELAXED)), ret_val);
}
}
#define SET_THREAD_SIGNAL_MASK(suffix) \
do { \
sigset##suffix##_t prev_mask; \
bytesig_sigorset##suffix(&prev_mask, &(((ucontext_t *)context)->uc_sigmask##suffix), \
&(sig->prev_action##suffix.sa_mask)); \
if (0 == ((unsigned int)(sig->prev_action##suffix.sa_flags) & (unsigned int)SA_NODEFER)) \
sigaddset##suffix(&prev_mask, signum); \
sigaddset##suffix(&prev_mask, SIGPIPE); \
sigaddset##suffix(&prev_mask, SIGUSR1); \
sigaddset##suffix(&prev_mask, SIGQUIT); \
((bytesig_sigprocmask##suffix##_t)bytesig_sigprocmask)(SIG_SETMASK, &prev_mask, NULL); \
} while (0)
// set thread signal mask
if (BYTESIG_STATUS_SIG64 == bytesig_status)
SET_THREAD_SIGNAL_MASK(64);
else
SET_THREAD_SIGNAL_MASK();
}
// https://llvm.org/docs/CodeGenerator.html#tail-call-optimization
// https://clang.llvm.org/docs/AttributeReference.html#disable-tail-calls
//__attribute__((disable_tail_calls))
static void bytesig_handler(int signum, siginfo_t *siginfo, void *context) {
bytesig_handler_internal(signum, siginfo, context);
#define CALL_PREVIOUS_SIGNAL_HANDLER(suffix) \
do { \
if (__predict_true(sig->prev_action##suffix.sa_flags & SA_SIGINFO)) \
sig->prev_action##suffix.sa_sigaction(signum, siginfo, context); \
else if (SIG_DFL != sig->prev_action##suffix.sa_handler && \
SIG_IGN != sig->prev_action##suffix.sa_handler) \
sig->prev_action##suffix.sa_handler(signum); \
} while (0)
// call previous signal handler
bytesig_signal_t *sig = bytesig_signal_array[signum];
if (BYTESIG_STATUS_SIG64 == bytesig_status)
CALL_PREVIOUS_SIGNAL_HANDLER(64);
else
CALL_PREVIOUS_SIGNAL_HANDLER();
}
int bytesig_init(int signum) {
if (__predict_false(signum <= 0 || signum >= __SIGRTMIN || signum == SIGKILL || signum == SIGSTOP))
return -1;
if (__predict_false(BYTESIG_STATUS_UNAVAILABLE == bytesig_status)) return -1;
if (__predict_false(NULL != bytesig_signal_array[signum])) return -1;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
int ret = -1;
if (__predict_false(NULL != bytesig_signal_array[signum])) goto end;
bytesig_signal_t *sig = calloc(1, sizeof(bytesig_signal_t));
if (__predict_false(NULL == sig)) goto end;
#define SA_EXPOSE_TAGBITS 0x00000800
#define REGISTER_SIGNAL_HANDLER(suffix) \
do { \
struct sigaction##suffix act; \
memset(&act, 0, sizeof(struct sigaction##suffix)); \
sigfillset##suffix(&act.sa_mask); \
act.sa_sigaction = bytesig_handler; \
act.sa_flags = SA_SIGINFO | SA_ONSTACK | SA_RESTART | SA_EXPOSE_TAGBITS; \
if (__predict_false( \
0 != \
((bytesig_sigaction##suffix##_t)bytesig_sigaction)(signum, &act, &sig->prev_action##suffix))) { \
free(sig); \
goto end; \
} \
} while (0)
// register the signal handler, we start off with all signals blocked
if (BYTESIG_STATUS_SIG64 == bytesig_status)
REGISTER_SIGNAL_HANDLER(64);
else
REGISTER_SIGNAL_HANDLER();
bytesig_signal_array[signum] = sig;
ret = 0; // OK
end:
pthread_mutex_unlock(&lock);
return ret;
}
void bytesig_protect(pid_t tid, sigjmp_buf *jbuf, const int signums[], size_t signums_cnt) {
for (size_t i = 0; i < signums_cnt; i++) {
int signum = signums[i];
if (__predict_false(signum <= 0 || signum >= __SIGRTMIN || signum == SIGKILL || signum == SIGSTOP))
continue;
bytesig_signal_t *sig = bytesig_signal_array[signum];
if (__predict_false(NULL == sig)) continue;
// check repeated thread
bool repeated = false;
for (size_t j = 0; j < BYTESIG_PROTECTED_THREADS_MAX; j++) {
if (__predict_false(tid == sig->tids[j])) {
repeated = true;
break;
}
}
if (__predict_false(repeated)) continue;
// save thread-ID and jump buffer
size_t j = 0;
while (1) {
if (0 == sig->tids[j]) {
pid_t expected = 0;
if (__atomic_compare_exchange_n(&sig->tids[j], &expected, tid, false, __ATOMIC_ACQUIRE,
__ATOMIC_RELAXED)) {
sig->jbufs[j] = jbuf;
BYTESIG_LOG("protect_start signal %d at %zu", signum, j);
break; // finished
}
}
j++;
if (__predict_false(BYTESIG_PROTECTED_THREADS_MAX == j)) j = 0;
}
}
}
void bytesig_unprotect(pid_t tid, const int signums[], size_t signums_cnt) {
for (size_t i = 0; i < signums_cnt; i++) {
int signum = signums[i];
if (__predict_false(signum <= 0 || signum >= __SIGRTMIN || signum == SIGKILL || signum == SIGSTOP))
continue;
bytesig_signal_t *sig = bytesig_signal_array[signum];
if (__predict_false(NULL == sig)) continue;
// free thread-ID and jump buffer
for (size_t j = 0; j < BYTESIG_PROTECTED_THREADS_MAX; j++) {
if (tid == sig->tids[j]) {
sig->jbufs[j] = NULL;
__atomic_store_n(&sig->tids[j], 0, __ATOMIC_RELEASE);
BYTESIG_LOG("protect_end signal %d at %zu", signum, j);
break; // finished
}
}
}
}

View File

@ -0,0 +1,157 @@
// Copyright (c) 2021-2023 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
// version 1.0.4
/*
* #include "bytesig.h"
*
* void init_once(void)
* {
* bytesig_init(SIGSEGV);
* bytesig_init(SIGBUS);
* bytesig_init(SIGILL);
* bytesig_init(SIGABRT);
* // ......
* }
*
* void unstable_func(void)
* {
* int *p = NULL;
*
* //
* // usage 1
* //
* BYTESIG_TRY(SIGSEGV, SIGBUS)
* {
* *p = 1;
* }
* BYTESIG_CATCH(signum, code)
* {
* LOG("signum %d (code %d)", signum, code);
* }
* BYTESIG_EXIT
*
* //
* // usage 2
* //
* BYTESIG_TRY(SIGSEGV, SIGBUS)
* {
* *p = 2;
* }
* BYTESIG_CATCH(signum)
* {
* LOG("signum %d", signum);
* }
* BYTESIG_EXIT
*
* //
* // usage 3
* //
* BYTESIG_TRY(SIGILL)
* {
* func_maybe_illed();
* }
* BYTESIG_CATCH()
* {
* do_something();
* }
* BYTESIG_EXIT
*
* //
* // usage 4
* //
* BYTESIG_TRY(SIGABRT)
* {
* func_maybe_aborted();
* }
* BYTESIG_EXIT
* }
*/
#ifndef BYTEDANCE_BYTESIG
#define BYTEDANCE_BYTESIG
#include <setjmp.h>
#include <signal.h>
#include <stddef.h>
#include <sys/syscall.h>
#include <unistd.h>
#define BYTESIG_TRY(...) \
do { \
pid_t _bytesig_tid_ = gettid(); \
if (0 == _bytesig_tid_) _bytesig_tid_ = (pid_t)syscall(SYS_gettid); \
sigjmp_buf _bytesig_jbuf_; \
int _bytesig_sigs_[] = {__VA_ARGS__}; \
bytesig_protect(_bytesig_tid_, &_bytesig_jbuf_, _bytesig_sigs_, sizeof(_bytesig_sigs_) / sizeof(int)); \
int _bytesig_protected_ = 1; \
int _bytesig_ex_ = sigsetjmp(_bytesig_jbuf_, 1); \
if (0 == _bytesig_ex_) {
#define BYTESIG_CATCH_2(signum_, code_) \
} \
else { \
bytesig_unprotect(_bytesig_tid_, _bytesig_sigs_, sizeof(_bytesig_sigs_) / sizeof(int)); \
_bytesig_protected_ = 0; \
int signum_ = (int)(((unsigned int)_bytesig_ex_ & 0xFF0000U) >> 16U); \
int code_ = 0; \
if (((unsigned int)_bytesig_ex_ & 0xFF00U) > 0) \
code_ = (int)(((unsigned int)_bytesig_ex_ & 0xFF00U) >> 8U); \
else if (((unsigned int)_bytesig_ex_ & 0xFFU) > 0) \
code_ = -((int)((unsigned int)_bytesig_ex_ & 0xFFU)); \
(void)signum_; \
(void)code_;
#define BYTESIG_CATCH_1(signum_) BYTESIG_CATCH_2(signum_, _bytesig_code_)
#define BYTESIG_CATCH_0() BYTESIG_CATCH_1(_bytesig_signum_)
#define FUNC_CHOOSER(_f1, _f2, _f3, ...) _f3
#define FUNC_RECOMPOSER(argsWithParentheses) FUNC_CHOOSER argsWithParentheses
#define CHOOSE_FROM_ARG_COUNT(...) FUNC_RECOMPOSER((__VA_ARGS__, BYTESIG_CATCH_2, BYTESIG_CATCH_1, ))
#define NO_ARG_EXPANDER() , , BYTESIG_CATCH_0
#define MACRO_CHOOSER(...) CHOOSE_FROM_ARG_COUNT(NO_ARG_EXPANDER __VA_ARGS__())
#define BYTESIG_CATCH(...) MACRO_CHOOSER(__VA_ARGS__)(__VA_ARGS__)
#define BYTESIG_EXIT \
} \
if (1 == _bytesig_protected_) \
bytesig_unprotect(_bytesig_tid_, _bytesig_sigs_, sizeof(_bytesig_sigs_) / sizeof(int)); \
} \
while (0) \
;
#ifdef __cplusplus
extern "C" {
#endif
int bytesig_init(int signum);
void bytesig_protect(pid_t tid, sigjmp_buf *jbuf, const int signums[], size_t signums_cnt);
void bytesig_unprotect(pid_t tid, const int signums[], size_t signums_cnt);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,55 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
// Global debugging.
//
// Note that in some cases these logs themselves can cause a crash.
//
//#define SH_CONFIG_DEBUG
// Operation record of hook and unhook.
//
// Disabling it can reduce hook/unhook latency, memory footprint and file size.
//
#define SH_CONFIG_OPERATION_RECORDS
// Crash signal protection.
//
// Do not disable it in a production environment.
//
#define SH_CONFIG_SIG_PROT
// When hooking, try to find an exit for the relative jump for the dynamic library.
//
// Do not disable it in a production environment.
//
#define SH_CONFIG_TRY_WITH_EXIT
// When hooking the function of the thumb instruction, if the function is too short,
// try to use the gap aligned at the end of the function.
//
// Do not disable it in a production environment.
//
#define SH_CONFIG_DETECT_THUMB_TAIL_ALIGNED

View File

@ -0,0 +1,107 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_errno.h"
#include <pthread.h>
#include <stdbool.h>
#include "shadowhook.h"
static int sh_errno_global = SHADOWHOOK_ERRNO_UNINIT;
static pthread_key_t sh_errno_tls_key;
int sh_errno_init(void) {
if (__predict_false(0 != pthread_key_create(&sh_errno_tls_key, NULL))) {
sh_errno_global = SHADOWHOOK_ERRNO_INIT_ERRNO;
return -1;
}
sh_errno_global = SHADOWHOOK_ERRNO_OK;
return 0;
}
void sh_errno_reset(void) {
sh_errno_set(0);
}
void sh_errno_set(int error_number) {
if (sh_errno_global == SHADOWHOOK_ERRNO_UNINIT || sh_errno_global == SHADOWHOOK_ERRNO_INIT_ERRNO) return;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wint-to-void-pointer-cast"
pthread_setspecific(sh_errno_tls_key, (void *)error_number);
#pragma clang diagnostic pop
}
int sh_errno_get(void) {
if (sh_errno_global == SHADOWHOOK_ERRNO_UNINIT || sh_errno_global == SHADOWHOOK_ERRNO_INIT_ERRNO)
return sh_errno_global;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wvoid-pointer-to-int-cast"
return (int)(pthread_getspecific(sh_errno_tls_key));
#pragma clang diagnostic pop
}
const char *sh_errno_to_errmsg(int error_number) {
static const char *msg[] = {/* 0 */ "OK",
/* 1 */ "Pending task",
/* 2 */ "Not initialized",
/* 3 */ "Invalid argument",
/* 4 */ "Out of memory",
/* 5 */ "MProtect failed",
/* 6 */ "Write to arbitrary address crashed",
/* 7 */ "Init errno mod failed",
/* 8 */ "Init bytesig SIGSEGV mod failed",
/* 9 */ "Init bytesig SIGBUS mod failed",
/* 10 */ "Init enter mod failed",
/* 11 */ "Init safe mod failed",
/* 12 */ "Init linker mod failed",
/* 13 */ "Init hub mod failed",
/* 14 */ "Create hub failed",
/* 15 */ "Monitor dlopen failed",
/* 16 */ "Create monitor thread failed",
/* 17 */ "Open ELF crashed",
/* 18 */ "Find symbol in ELF failed",
/* 19 */ "Find symbol in ELF crashed",
/* 20 */ "Duplicate hook",
/* 21 */ "Dladdr crashed",
/* 22 */ "Find dlinfo failed",
/* 23 */ "Symbol size too small",
/* 24 */ "Alloc enter failed",
/* 25 */ "Instruction rewrite crashed",
/* 26 */ "Instruction rewrite failed",
/* 27 */ "Switch not found",
/* 28 */ "Verify original instruction crashed",
/* 29 */ "Verify original instruction failed",
/* 30 */ "Exit instruction mismatch",
/* 31 */ "Free exit crashed",
/* 32 */ "Unhook on an error status task",
/* 33 */ "Unhook on an unfinished task",
/* 34 */ "ELF with an unsupported architecture",
/* 35 */ "Linker with an unsupported architecture"};
if (error_number < 0 || error_number >= (int)(sizeof(msg) / sizeof(msg[0]))) return "Unknown error number";
return msg[error_number];
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include "sh_log.h"
#define SH_ERRNO_SET_RET_ERRNUM(errnum) SH_ERRNO_SET_RET((errnum), (errnum))
#define SH_ERRNO_SET_RET_FAIL(errnum) SH_ERRNO_SET_RET((errnum), -1)
#define SH_ERRNO_SET_RET_NULL(errnum) SH_ERRNO_SET_RET((errnum), NULL)
#define SH_ERRNO_SET_RET(errnum, ret) \
do { \
sh_errno_set((errnum)); \
return (ret); \
} while (0)
int sh_errno_init(void);
void sh_errno_reset(void);
void sh_errno_set(int error_number);
int sh_errno_get(void);
const char *sh_errno_to_errmsg(int error_number);

View File

@ -0,0 +1,53 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_log.h"
#include <android/log.h>
#include <stdbool.h>
#include "sh_config.h"
android_LogPriority sh_log_priority =
#ifdef SH_CONFIG_DEBUG
ANDROID_LOG_INFO
#else
ANDROID_LOG_SILENT
#endif
;
bool sh_log_get_debuggable(void) {
return sh_log_priority <= ANDROID_LOG_INFO;
}
void sh_log_set_debuggable(bool debuggable) {
#ifdef SH_CONFIG_DEBUG
(void)debuggable;
sh_log_priority = ANDROID_LOG_INFO;
#else
if (__predict_false(debuggable))
sh_log_priority = ANDROID_LOG_INFO;
else
sh_log_priority = ANDROID_LOG_SILENT;
#endif
}

View File

@ -0,0 +1,70 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <android/log.h>
#include <inttypes.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdint.h>
extern android_LogPriority sh_log_priority;
#define SH_LOG_TAG "shadowhook_tag"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
// Notice:
// We don't use ANDROID_LOG_DEBUG, because some Android devices will filter out ANDROID_LOG_DEBUG.
#ifdef SH_CONFIG_DEBUG
#define SH_LOG_DEBUG(fmt, ...) \
do { \
if (sh_log_priority <= ANDROID_LOG_INFO) \
__android_log_print(ANDROID_LOG_INFO, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
} while (0)
#else
#define SH_LOG_DEBUG(fmt, ...)
#endif
#define SH_LOG_INFO(fmt, ...) \
do { \
if (__predict_false(sh_log_priority <= ANDROID_LOG_INFO)) \
__android_log_print(ANDROID_LOG_INFO, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
} while (0)
#define SH_LOG_WARN(fmt, ...) \
do { \
if (__predict_false(sh_log_priority <= ANDROID_LOG_WARN)) \
__android_log_print(ANDROID_LOG_WARN, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
} while (0)
#define SH_LOG_ERROR(fmt, ...) \
do { \
if (__predict_false(sh_log_priority <= ANDROID_LOG_ERROR)) \
__android_log_print(ANDROID_LOG_ERROR, SH_LOG_TAG, fmt, ##__VA_ARGS__); \
} while (0)
#define SH_LOG_ALWAYS_SHOW(fmt, ...) __android_log_print(ANDROID_LOG_WARN, SH_LOG_TAG, fmt, ##__VA_ARGS__)
#pragma clang diagnostic pop
bool sh_log_get_debuggable(void);
void sh_log_set_debuggable(bool debuggable);

View File

@ -0,0 +1,49 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include "sh_config.h"
#ifdef SH_CONFIG_SIG_PROT
#include "bytesig.h"
#define SH_SIG_TRY BYTESIG_TRY
#define SH_SIG_CATCH BYTESIG_CATCH
#define SH_SIG_EXIT BYTESIG_EXIT
#else
#define SH_SIG_TRY(...) \
do { \
if (0 == 0) {
#define SH_SIG_CATCH(...) \
} \
else {
#define SH_SIG_EXIT \
} \
} \
while (0) \
;
#endif

View File

@ -0,0 +1,172 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_trampo.h"
#include <pthread.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/time.h>
#include "queue.h"
#include "sh_util.h"
#define SH_TRAMPO_PAGE_SZ 4096
#define SH_TRAMPO_ALIGN 4
void sh_trampo_init_mgr(sh_trampo_mgr_t *mem_mgr, const char *page_name, size_t trampo_size,
time_t delay_sec) {
SLIST_INIT(&mem_mgr->pages);
pthread_mutex_init(&mem_mgr->pages_lock, NULL);
mem_mgr->page_name = page_name;
mem_mgr->trampo_size = SH_UTIL_ALIGN_END(trampo_size, SH_TRAMPO_ALIGN);
mem_mgr->delay_sec = delay_sec;
}
uintptr_t sh_trampo_alloc(sh_trampo_mgr_t *mem_mgr, uintptr_t hint, uintptr_t range_low,
uintptr_t range_high) {
uintptr_t mem = 0;
uintptr_t new_ptr;
uintptr_t new_ptr_prctl = (uintptr_t)MAP_FAILED;
size_t count = SH_TRAMPO_PAGE_SZ / mem_mgr->trampo_size;
if (range_low > hint) range_low = hint;
if (range_high > UINTPTR_MAX - hint) range_high = UINTPTR_MAX - hint;
struct timeval now;
if (mem_mgr->delay_sec > 0) gettimeofday(&now, NULL);
pthread_mutex_lock(&mem_mgr->pages_lock);
// try to find an unused mem
sh_trampo_page_t *page;
SLIST_FOREACH(page, &mem_mgr->pages, link) {
// check hit range
uintptr_t page_trampo_start = page->ptr;
uintptr_t page_trampo_end = page->ptr + SH_TRAMPO_PAGE_SZ - mem_mgr->trampo_size;
if (hint > 0 && ((page_trampo_end < hint - range_low) || (hint + range_high < page_trampo_start)))
continue;
for (uintptr_t i = 0; i < count; i++) {
size_t flags_idx = i / 32;
uint32_t mask = (uint32_t)1 << (i % 32);
if (0 == (page->flags[flags_idx] & mask)) // check flag
{
// check timestamp
if (mem_mgr->delay_sec > 0 &&
(now.tv_sec <= page->timestamps[i] || now.tv_sec - page->timestamps[i] <= mem_mgr->delay_sec))
continue;
// check hit range
uintptr_t cur = page->ptr + (mem_mgr->trampo_size * i);
if (hint > 0 && ((cur < hint - range_low) || (hint + range_high < cur))) continue;
// OK
page->flags[flags_idx] |= mask;
mem = cur;
memset((void *)mem, 0, mem_mgr->trampo_size);
goto end;
}
}
}
// alloc a new mem page
new_ptr = (uintptr_t)(mmap(hint > 0 ? (void *)(hint - range_low) : NULL, SH_TRAMPO_PAGE_SZ,
PROT_READ | PROT_WRITE | PROT_EXEC, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
if ((uintptr_t)MAP_FAILED == new_ptr) goto err;
new_ptr_prctl = new_ptr;
// check hit range
if (hint > 0 && ((hint - range_low >= new_ptr + SH_TRAMPO_PAGE_SZ - mem_mgr->trampo_size) ||
(hint + range_high < new_ptr)))
goto err;
// create a new trampo-page info
if (NULL == (page = calloc(1, sizeof(sh_trampo_page_t)))) goto err;
memset((void *)new_ptr, 0, SH_TRAMPO_PAGE_SZ);
page->ptr = new_ptr;
new_ptr = (uintptr_t)MAP_FAILED;
if (NULL == (page->flags = calloc(1, SH_UTIL_ALIGN_END(count, 32) / 8))) goto err;
page->timestamps = NULL;
if (mem_mgr->delay_sec > 0) {
if (NULL == (page->timestamps = calloc(1, count * sizeof(time_t)))) goto err;
}
SLIST_INSERT_HEAD(&mem_mgr->pages, page, link);
// alloc mem from the new mem page
for (uintptr_t i = 0; i < count; i++) {
size_t flags_idx = i / 32;
uint32_t mask = (uint32_t)1 << (i % 32);
// check hit range
uintptr_t find = page->ptr + (mem_mgr->trampo_size * i);
if (hint > 0 && ((find < hint - range_low) || (hint + range_high < find))) continue;
// OK
page->flags[flags_idx] |= mask;
mem = find;
break;
}
if (0 == mem) abort();
end:
pthread_mutex_unlock(&mem_mgr->pages_lock);
if ((uintptr_t)MAP_FAILED != new_ptr_prctl)
prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, new_ptr_prctl, SH_TRAMPO_PAGE_SZ, mem_mgr->page_name);
return mem;
err:
pthread_mutex_unlock(&mem_mgr->pages_lock);
if (NULL != page) {
if (0 != page->ptr) munmap((void *)page->ptr, SH_TRAMPO_PAGE_SZ);
if (NULL != page->flags) free(page->flags);
if (NULL != page->timestamps) free(page->timestamps);
free(page);
}
if ((uintptr_t)MAP_FAILED != new_ptr) munmap((void *)new_ptr, SH_TRAMPO_PAGE_SZ);
return 0;
}
void sh_trampo_free(sh_trampo_mgr_t *mem_mgr, uintptr_t mem) {
struct timeval now;
if (mem_mgr->delay_sec > 0) gettimeofday(&now, NULL);
pthread_mutex_lock(&mem_mgr->pages_lock);
sh_trampo_page_t *page;
SLIST_FOREACH(page, &mem_mgr->pages, link) {
if (page->ptr <= mem && mem < page->ptr + SH_TRAMPO_PAGE_SZ) {
uintptr_t i = (mem - page->ptr) / mem_mgr->trampo_size;
size_t flags_idx = i / 32;
uint32_t mask = (uint32_t)1 << (i % 32);
if (mem_mgr->delay_sec > 0) page->timestamps[i] = now.tv_sec;
page->flags[flags_idx] &= ~mask;
break;
}
}
pthread_mutex_unlock(&mem_mgr->pages_lock);
}

View File

@ -0,0 +1,52 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <sys/time.h>
#include "queue.h"
typedef struct sh_trampo_page {
uintptr_t ptr;
uint32_t *flags;
time_t *timestamps;
SLIST_ENTRY(sh_trampo_page, ) link;
} sh_trampo_page_t;
typedef SLIST_HEAD(sh_trampo_page_list, sh_trampo_page, ) sh_trampo_page_list_t;
typedef struct sh_trampo_mgr {
sh_trampo_page_list_t pages;
pthread_mutex_t pages_lock;
const char *page_name;
size_t trampo_size;
time_t delay_sec;
} sh_trampo_mgr_t;
void sh_trampo_init_mgr(sh_trampo_mgr_t *mem_mgr, const char *page_name, size_t trampo_size,
time_t delay_sec);
uintptr_t sh_trampo_alloc(sh_trampo_mgr_t *mem_mgr, uintptr_t hint, uintptr_t low_offset,
uintptr_t high_offset);
void sh_trampo_free(sh_trampo_mgr_t *mem_mgr, uintptr_t mem);

View File

@ -0,0 +1,538 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_util.h"
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#include "sh_log.h"
#include "sh_sig.h"
#include "shadowhook.h"
int sh_util_mprotect(uintptr_t addr, size_t len, int prot) {
uintptr_t start = SH_UTIL_PAGE_START(addr);
uintptr_t end = SH_UTIL_PAGE_END(addr + len - 1);
return mprotect((void *)start, end - start, prot);
}
void sh_util_clear_cache(uintptr_t addr, size_t len) {
__builtin___clear_cache((char *)addr, (char *)(addr + len));
}
bool sh_util_is_thumb32(uintptr_t target_addr) {
uint16_t opcode = *((uint16_t *)target_addr);
int tmp = opcode >> 11u;
return (tmp == 0x1d) || (tmp == 0x1e) || (tmp == 0x1f);
}
static uint32_t sh_util_ror(uint32_t val, uint32_t n, uint32_t shift) {
uint32_t m = shift % n;
return (val >> m) | (val << (n - m));
}
uint32_t sh_util_arm_expand_imm(uint32_t opcode) {
uint32_t imm = SH_UTIL_GET_BITS_32(opcode, 7, 0);
uint32_t amt = 2 * SH_UTIL_GET_BITS_32(opcode, 11, 8);
return amt == 0 ? imm : sh_util_ror(imm, 32, amt);
}
int sh_util_write_inst(uintptr_t target_addr, void *inst, size_t inst_len) {
if (0 != sh_util_mprotect(target_addr, inst_len, PROT_READ | PROT_WRITE | PROT_EXEC))
return SHADOWHOOK_ERRNO_MPROT;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
if ((4 == inst_len) && (0 == target_addr % 4))
__atomic_store_n((uint32_t *)target_addr, *((uint32_t *)inst), __ATOMIC_SEQ_CST);
else if ((8 == inst_len) && (0 == target_addr % 8))
__atomic_store_n((uint64_t *)target_addr, *((uint64_t *)inst), __ATOMIC_SEQ_CST);
#ifdef __LP64__
else if ((16 == inst_len) && (0 == target_addr % 16))
__atomic_store_n((__int128 *)target_addr, *((__int128 *)inst), __ATOMIC_SEQ_CST);
#endif
else
memcpy((void *)target_addr, inst, inst_len);
sh_util_clear_cache(target_addr, inst_len);
}
SH_SIG_CATCH() {
return SHADOWHOOK_ERRNO_WRITE_CRASH;
}
SH_SIG_EXIT
return 0; // OK
}
static bool sh_util_starts_with(const char *str, const char *start) {
while (*str && *str == *start) {
str++;
start++;
}
return '\0' == *start;
}
static int sh_util_get_api_level_from_build_prop(void) {
char buf[128];
int api_level = -1;
FILE *fp = fopen("/system/build.prop", "r");
if (__predict_false(NULL == fp)) goto end;
while (fgets(buf, sizeof(buf), fp)) {
if (__predict_false(sh_util_starts_with(buf, "ro.build.version.sdk="))) {
api_level = atoi(buf + 21);
break;
}
}
fclose(fp);
end:
return (api_level > 0) ? api_level : -1;
}
int sh_util_get_api_level(void) {
static int xdl_util_api_level = -1;
if (__predict_false(xdl_util_api_level < 0)) {
int api_level = android_get_device_api_level();
if (__predict_false(api_level < 0))
api_level = sh_util_get_api_level_from_build_prop(); // compatible with unusual models
if (__predict_false(api_level < __ANDROID_API_J__)) api_level = __ANDROID_API_J__;
__atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST);
}
return xdl_util_api_level;
}
int sh_util_write(int fd, const char *buf, size_t buf_len) {
if (fd < 0) return -1;
const char *ptr = buf;
size_t nleft = buf_len;
while (nleft > 0) {
errno = 0;
ssize_t nwritten = write(fd, ptr, nleft);
if (nwritten <= 0) {
if (nwritten < 0 && errno == EINTR)
nwritten = 0; // call write() again
else
return -1; // error
}
nleft -= (size_t)nwritten;
ptr += nwritten;
}
return 0;
}
/* Nonzero if YEAR is a leap year (every 4 years,
except every 100th isn't, and every 400th is). */
#define SH_UTIL_ISLEAP(year) ((year) % 4 == 0 && ((year) % 100 != 0 || (year) % 400 == 0))
#define SH_UTIL_SECS_PER_HOUR (60 * 60)
#define SH_UTIL_SECS_PER_DAY (SH_UTIL_SECS_PER_HOUR * 24)
#define SH_UTIL_DIV(a, b) ((a) / (b) - ((a) % (b) < 0))
#define SH_UTIL_LEAPS_THRU_END_OF(y) (SH_UTIL_DIV(y, 4) - SH_UTIL_DIV(y, 100) + SH_UTIL_DIV(y, 400))
static const unsigned short int sh_util_mon_yday[2][13] = {
/* Normal years. */
{0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365},
/* Leap years. */
{0, 31, 60, 91, 121, 152, 182, 213, 244, 274, 305, 335, 366}};
/* Compute the `struct tm' representation of *T,
offset GMTOFF seconds east of UTC,
and store year, yday, mon, mday, wday, hour, min, sec into *RESULT.
Return RESULT if successful. */
struct tm *sh_util_localtime_r(const time_t *timep, long gmtoff, struct tm *result) {
time_t days, rem, y;
const unsigned short int *ip;
if (NULL == result) return NULL;
result->tm_gmtoff = gmtoff;
days = ((*timep) / SH_UTIL_SECS_PER_DAY);
rem = ((*timep) % SH_UTIL_SECS_PER_DAY);
rem += gmtoff;
while (rem < 0) {
rem += SH_UTIL_SECS_PER_DAY;
--days;
}
while (rem >= SH_UTIL_SECS_PER_DAY) {
rem -= SH_UTIL_SECS_PER_DAY;
++days;
}
result->tm_hour = (int)(rem / SH_UTIL_SECS_PER_HOUR);
rem %= SH_UTIL_SECS_PER_HOUR;
result->tm_min = (int)(rem / 60L);
result->tm_sec = (int)(rem % 60L);
/* January 1, 1970 was a Thursday. */
result->tm_wday = (int)(4 + days) % 7;
if (result->tm_wday < 0) result->tm_wday += 7;
y = 1970;
while (days < 0 || days >= (SH_UTIL_ISLEAP(y) ? 366 : 365)) {
/* Guess a corrected year, assuming 365 days per year. */
time_t yg = y + days / 365 - (days % 365 < 0);
/* Adjust DAYS and Y to match the guessed year. */
days -= ((yg - y) * 365 + SH_UTIL_LEAPS_THRU_END_OF(yg - 1) - SH_UTIL_LEAPS_THRU_END_OF(y - 1));
y = yg;
}
result->tm_year = (int)(y - 1900);
if (result->tm_year != y - 1900) {
/* The year cannot be represented due to overflow. */
errno = EOVERFLOW;
return NULL;
}
result->tm_yday = (int)days;
ip = sh_util_mon_yday[SH_UTIL_ISLEAP(y)];
for (y = 11; days < (long int)ip[y]; --y) continue;
days -= ip[y];
result->tm_mon = (int)y;
result->tm_mday = (int)(days + 1);
return result;
}
static unsigned sh_util_parse_decimal(const char *format, int *ppos) {
const char *p = format + *ppos;
unsigned result = 0;
for (;;) {
int ch = *p;
unsigned d = (unsigned)(ch - '0');
if (d >= 10U) {
break;
}
result = result * 10 + d;
p++;
}
*ppos = (int)(p - format);
return result;
}
static void sh_util_format_unsigned(char *buf, size_t buf_size, uint64_t value, int base, int caps) {
char *p = buf;
char *end = buf + buf_size - 1;
// Generate digit string in reverse order.
while (value) {
unsigned d = (unsigned)(value % (uint64_t)base);
value /= (uint64_t)base;
if (p != end) {
char ch;
if (d < 10) {
ch = '0' + (char)d;
} else {
ch = (caps ? 'A' : 'a') + (char)(d - 10);
}
*p++ = ch;
}
}
// Special case for 0.
if (p == buf) {
if (p != end) {
*p++ = '0';
}
}
*p = '\0';
// Reverse digit string in-place.
size_t length = (size_t)(p - buf);
for (size_t i = 0, j = length - 1; i < j; ++i, --j) {
char ch = buf[i];
buf[i] = buf[j];
buf[j] = ch;
}
}
static void sh_util_format_integer(char *buf, size_t buf_size, uint64_t value, char conversion) {
// Decode the conversion specifier.
int is_signed = (conversion == 'd' || conversion == 'i' || conversion == 'o');
int base = 10;
if (conversion == 'x' || conversion == 'X') {
base = 16;
} else if (conversion == 'o') {
base = 8;
}
int caps = (conversion == 'X');
if (is_signed && (int64_t)(value) < 0) {
buf[0] = '-';
buf += 1;
buf_size -= 1;
value = (uint64_t)(-(int64_t)(value));
}
sh_util_format_unsigned(buf, buf_size, value, base, caps);
}
// format stream
typedef struct {
size_t total;
char *pos;
size_t avail;
} sh_util_stream_t;
static void sh_util_stream_init(sh_util_stream_t *self, char *buffer, size_t buffer_size) {
self->total = 0;
self->pos = buffer;
self->avail = buffer_size;
if (self->avail > 0) self->pos[0] = '\0';
}
static size_t sh_util_stream_total(sh_util_stream_t *self) {
return self->total;
}
static void sh_util_stream_send(sh_util_stream_t *self, const char *data, int len) {
if (len < 0) {
len = (int)strlen(data);
}
self->total += (size_t)len;
if (self->avail <= 1) {
// no space to put anything else
return;
}
if ((size_t)len >= self->avail) {
len = (int)(self->avail - 1);
}
memcpy(self->pos, data, (size_t)len);
self->pos += len;
self->pos[0] = '\0';
self->avail -= (size_t)len;
}
static void sh_util_stream_send_repeat(sh_util_stream_t *self, char ch, int count) {
char pad[8];
memset(pad, ch, sizeof(pad));
const int pad_size = (int)(sizeof(pad));
while (count > 0) {
int avail = count;
if (avail > pad_size) {
avail = pad_size;
}
sh_util_stream_send(self, pad, avail);
count -= avail;
}
}
static void sh_util_stream_vformat(sh_util_stream_t *self, const char *format, va_list args) {
int nn = 0;
for (;;) {
int mm;
int padZero = 0;
int padLeft = 0;
char sign = '\0';
int width = -1;
int prec = -1;
size_t bytelen = sizeof(int);
int slen;
char buffer[32]; // temporary buffer used to format numbers
char c;
// first, find all characters that are not 0 or '%', then send them to the output directly
mm = nn;
do {
c = format[mm];
if (c == '\0' || c == '%') break;
mm++;
} while (1);
if (mm > nn) {
sh_util_stream_send(self, format + nn, mm - nn);
nn = mm;
}
// is this it ? then exit
if (c == '\0') break;
// nope, we are at a '%' modifier
nn++; // skip it
// parse flags
for (;;) {
c = format[nn++];
if (c == '\0') {
// single trailing '%' ?
c = '%';
sh_util_stream_send(self, &c, 1);
return;
} else if (c == '0') {
padZero = 1;
continue;
} else if (c == '-') {
padLeft = 1;
continue;
} else if (c == ' ' || c == '+') {
sign = c;
continue;
}
break;
}
// parse field width
if ((c >= '0' && c <= '9')) {
nn--;
width = (int)(sh_util_parse_decimal(format, &nn));
c = format[nn++];
}
// parse precision
if (c == '.') {
prec = (int)(sh_util_parse_decimal(format, &nn));
c = format[nn++];
}
// length modifier
switch (c) {
case 'h':
bytelen = sizeof(short);
if (format[nn] == 'h') {
bytelen = sizeof(char);
nn += 1;
}
c = format[nn++];
break;
case 'l':
bytelen = sizeof(long);
if (format[nn] == 'l') {
bytelen = sizeof(long long);
nn += 1;
}
c = format[nn++];
break;
case 'z':
bytelen = sizeof(size_t);
c = format[nn++];
break;
case 't':
bytelen = sizeof(ptrdiff_t);
c = format[nn++];
break;
default:;
}
// conversion specifier
const char *str = buffer;
if (c == 's') {
// string
str = va_arg(args, const char *);
if (str == NULL) {
str = "(null)";
}
} else if (c == 'c') {
// character
// NOTE: char is promoted to int when passed through the stack
buffer[0] = (char)(va_arg(args, int));
buffer[1] = '\0';
} else if (c == 'p') {
uint64_t value = (uintptr_t)(va_arg(args, void *));
buffer[0] = '0';
buffer[1] = 'x';
sh_util_format_integer(buffer + 2, sizeof(buffer) - 2, value, 'x');
} else if (c == 'd' || c == 'i' || c == 'o' || c == 'u' || c == 'x' || c == 'X') {
// integers - first read value from stack
uint64_t value;
int is_signed = (c == 'd' || c == 'i' || c == 'o');
// NOTE: int8_t and int16_t are promoted to int when passed through the stack
switch (bytelen) {
case 1:
value = (uint8_t)(va_arg(args, int));
break;
case 2:
value = (uint16_t)(va_arg(args, int));
break;
case 4:
value = va_arg(args, uint32_t);
break;
case 8:
value = va_arg(args, uint64_t);
break;
default:
return; // should not happen
}
// sign extension, if needed
if (is_signed) {
int shift = (int)(64 - 8 * bytelen);
value = (uint64_t)(((int64_t)(value << shift)) >> shift);
}
// format the number properly into our buffer
sh_util_format_integer(buffer, sizeof(buffer), value, c);
} else if (c == '%') {
buffer[0] = '%';
buffer[1] = '\0';
} else {
//__assert(__FILE__, __LINE__, "conversion specifier unsupported");
return;
}
// if we are here, 'str' points to the content that must be outputted.
// handle padding and alignment now
slen = (int)strlen(str);
if (sign != '\0' || prec != -1) {
//__assert(__FILE__, __LINE__, "sign/precision unsupported");
return;
}
if (slen < width && !padLeft) {
char padChar = padZero ? '0' : ' ';
sh_util_stream_send_repeat(self, padChar, width - slen);
}
sh_util_stream_send(self, str, slen);
if (slen < width && padLeft) {
char padChar = padZero ? '0' : ' ';
sh_util_stream_send_repeat(self, padChar, width - slen);
}
}
}
size_t sh_util_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list args) {
sh_util_stream_t stream;
sh_util_stream_init(&stream, buffer, buffer_size);
sh_util_stream_vformat(&stream, format, args);
return sh_util_stream_total(&stream);
}
size_t sh_util_snprintf(char *buffer, size_t buffer_size, const char *format, ...) {
va_list args;
va_start(args, format);
size_t buffer_len = sh_util_vsnprintf(buffer, buffer_size, format, args);
va_end(args);
return buffer_len;
}

View File

@ -0,0 +1,96 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <android/api-level.h>
#include <errno.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <time.h>
#define SH_UTIL_ALIGN_START(x, align) ((uintptr_t)(x) & ~((uintptr_t)(align)-1))
#define SH_UTIL_ALIGN_END(x, align) (((uintptr_t)(x) + (uintptr_t)(align)-1) & ~((uintptr_t)(align)-1))
#define SH_UTIL_PAGE_START(x) SH_UTIL_ALIGN_START(x, 0x1000)
#define SH_UTIL_PAGE_END(x) SH_UTIL_ALIGN_END(x, 0x1000)
#define SH_UTIL_IS_THUMB(addr) ((addr)&1u)
#define SH_UTIL_CLEAR_BIT0(addr) ((addr)&0xFFFFFFFE)
#define SH_UTIL_SET_BIT0(addr) ((addr) | 1u)
#define SH_UTIL_ALIGN_4(pc) ((pc)&0xFFFFFFFC)
#define SH_UTIL_SIGN_EXTEND_32(n, len) \
((SH_UTIL_GET_BIT_32(n, len - 1) > 0) ? ((n) | (0xFFFFFFFF << (len))) : n)
#define SH_UTIL_SIGN_EXTEND_64(n, len) \
((SH_UTIL_GET_BIT_64(n, len - 1) > 0) ? ((n) | (0xFFFFFFFFFFFFFFFF << (len))) : n)
#define SH_UTIL_GET_BIT_16(n, idx) ((uint16_t)((n) << (15u - (idx))) >> 15u)
#define SH_UTIL_GET_BITS_16(n, high, low) ((uint16_t)((n) << (15u - (high))) >> (15u - (high) + (low)))
#define SH_UTIL_GET_BIT_32(n, idx) ((uint32_t)((n) << (31u - (idx))) >> 31u)
#define SH_UTIL_GET_BITS_32(n, high, low) ((uint32_t)((n) << (31u - (high))) >> (31u - (high) + (low)))
#define SH_UTIL_GET_BIT_64(n, idx) ((uint64_t)((n) << (63u - (idx))) >> 63u)
#define SH_UTIL_GET_BITS_64(n, high, low) ((uint64_t)((n) << (63u - (high))) >> (63u - (high) + (low)))
#define SH_UTIL_TEMP_FAILURE_RETRY(exp) \
({ \
__typeof__(exp) _rc; \
do { \
errno = 0; \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; \
})
#define SH_UTIL_MAX(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a > _b ? _a : _b; \
})
#define SH_UTIL_MIN(a, b) \
({ \
__typeof__(a) _a = (a); \
__typeof__(b) _b = (b); \
_a < _b ? _a : _b; \
})
int sh_util_mprotect(uintptr_t addr, size_t len, int prot);
void sh_util_clear_cache(uintptr_t addr, size_t len);
bool sh_util_is_thumb32(uintptr_t target_addr);
uint32_t sh_util_arm_expand_imm(uint32_t opcode);
int sh_util_write_inst(uintptr_t target_addr, void *inst, size_t inst_len);
int sh_util_get_api_level(void);
int sh_util_write(int fd, const char *buf, size_t buf_len);
struct tm *sh_util_localtime_r(const time_t *timep, long gmtoff, struct tm *result);
size_t sh_util_vsnprintf(char *buffer, size_t buffer_size, const char *format, va_list args);
size_t sh_util_snprintf(char *buffer, size_t buffer_size, const char *format, ...);

View File

@ -0,0 +1,191 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// shadowhook is now credited to all its contributors.
//
// Before shadowhook was open sourced in February 2022, it was developed by
// the following developers from ByteDance's App Health Team:
//
// Kelun Cai (caikelun@bytedance.com)
// Pengying Xu (xupengying@bytedance.com)
//
#ifndef BYTEDANCE_SHADOWHOOK_H
#define BYTEDANCE_SHADOWHOOK_H
#include <stdbool.h>
#include <stdint.h>
#define SHADOWHOOK_VERSION "1.0.8"
#define SHADOWHOOK_ERRNO_OK 0
#define SHADOWHOOK_ERRNO_PENDING 1
#define SHADOWHOOK_ERRNO_UNINIT 2
#define SHADOWHOOK_ERRNO_INVALID_ARG 3
#define SHADOWHOOK_ERRNO_OOM 4
#define SHADOWHOOK_ERRNO_MPROT 5
#define SHADOWHOOK_ERRNO_WRITE_CRASH 6
#define SHADOWHOOK_ERRNO_INIT_ERRNO 7
#define SHADOWHOOK_ERRNO_INIT_SIGSEGV 8
#define SHADOWHOOK_ERRNO_INIT_SIGBUS 9
#define SHADOWHOOK_ERRNO_INIT_ENTER 10
#define SHADOWHOOK_ERRNO_INIT_SAFE 11
#define SHADOWHOOK_ERRNO_INIT_LINKER 12
#define SHADOWHOOK_ERRNO_INIT_HUB 13
#define SHADOWHOOK_ERRNO_HUB_CREAT 14
#define SHADOWHOOK_ERRNO_MONITOR_DLOPEN 15
#define SHADOWHOOK_ERRNO_MONITOR_THREAD 16
#define SHADOWHOOK_ERRNO_HOOK_DLOPEN_CRASH 17
#define SHADOWHOOK_ERRNO_HOOK_DLSYM 18
#define SHADOWHOOK_ERRNO_HOOK_DLSYM_CRASH 19
#define SHADOWHOOK_ERRNO_HOOK_DUP 20
#define SHADOWHOOK_ERRNO_HOOK_DLADDR_CRASH 21
#define SHADOWHOOK_ERRNO_HOOK_DLINFO 22
#define SHADOWHOOK_ERRNO_HOOK_SYMSZ 23
#define SHADOWHOOK_ERRNO_HOOK_ENTER 24
#define SHADOWHOOK_ERRNO_HOOK_REWRITE_CRASH 25
#define SHADOWHOOK_ERRNO_HOOK_REWRITE_FAILED 26
#define SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND 27
#define SHADOWHOOK_ERRNO_UNHOOK_CMP_CRASH 28
#define SHADOWHOOK_ERRNO_UNHOOK_TRAMPO_MISMATCH 29
#define SHADOWHOOK_ERRNO_UNHOOK_EXIT_MISMATCH 30
#define SHADOWHOOK_ERRNO_UNHOOK_EXIT_CRASH 31
#define SHADOWHOOK_ERRNO_UNHOOK_ON_ERROR 32
#define SHADOWHOOK_ERRNO_UNHOOK_ON_UNFINISHED 33
#define SHADOWHOOK_ERRNO_ELF_ARCH_MISMATCH 34
#define SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH 35
#ifdef __cplusplus
extern "C" {
#endif
const char *shadowhook_get_version(void);
// init
typedef enum {
SHADOWHOOK_MODE_SHARED = 0, // a function can be hooked multiple times
SHADOWHOOK_MODE_UNIQUE = 1 // a function can only be hooked once, and hooking again will report an error
} shadowhook_mode_t;
int shadowhook_init(shadowhook_mode_t mode, bool debuggable);
int shadowhook_get_init_errno(void);
// get and set attributes
#define SHADOWHOOK_IS_SHARED_MODE (SHADOWHOOK_MODE_SHARED == shadowhook_get_mode())
#define SHADOWHOOK_IS_UNIQUE_MODE (SHADOWHOOK_MODE_UNIQUE == shadowhook_get_mode())
shadowhook_mode_t shadowhook_get_mode(void);
bool shadowhook_get_debuggable(void);
void shadowhook_set_debuggable(bool debuggable);
bool shadowhook_get_recordable(void);
void shadowhook_set_recordable(bool recordable);
// get error-number and error message
int shadowhook_get_errno(void);
const char *shadowhook_to_errmsg(int error_number);
// hook and unhook
typedef void (*shadowhook_hooked_t)(int error_number, const char *lib_name, const char *sym_name,
void *sym_addr, void *new_addr, void *orig_addr, void *arg);
void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr);
void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr);
void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr);
void *shadowhook_hook_sym_name_callback(const char *lib_name, const char *sym_name, void *new_addr,
void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg);
int shadowhook_unhook(void *stub);
// get operation records
#define SHADOWHOOK_RECORD_ITEM_ALL 0x3FF // 0b1111111111
#define SHADOWHOOK_RECORD_ITEM_TIMESTAMP (1 << 0)
#define SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME (1 << 1)
#define SHADOWHOOK_RECORD_ITEM_OP (1 << 2)
#define SHADOWHOOK_RECORD_ITEM_LIB_NAME (1 << 3)
#define SHADOWHOOK_RECORD_ITEM_SYM_NAME (1 << 4)
#define SHADOWHOOK_RECORD_ITEM_SYM_ADDR (1 << 5)
#define SHADOWHOOK_RECORD_ITEM_NEW_ADDR (1 << 6)
#define SHADOWHOOK_RECORD_ITEM_BACKUP_LEN (1 << 7)
#define SHADOWHOOK_RECORD_ITEM_ERRNO (1 << 8)
#define SHADOWHOOK_RECORD_ITEM_STUB (1 << 9)
char *shadowhook_get_records(uint32_t item_flags);
void shadowhook_dump_records(int fd, uint32_t item_flags);
// helper functions for "get symbol-address from library-name and symbol-name"
void *shadowhook_dlopen(const char *lib_name);
void shadowhook_dlclose(void *handle);
void *shadowhook_dlsym(void *handle, const char *sym_name);
void *shadowhook_dlsym_dynsym(void *handle, const char *sym_name);
void *shadowhook_dlsym_symtab(void *handle, const char *sym_name);
// for internal use
void *shadowhook_get_prev_func(void *func);
void shadowhook_pop_stack(void *return_address);
void shadowhook_allow_reentrant(void *return_address);
void shadowhook_disallow_reentrant(void *return_address);
void *shadowhook_get_return_address(void);
#ifdef __cplusplus
}
#endif
// call previous function in proxy-function
#ifdef __cplusplus
#define SHADOWHOOK_CALL_PREV(func, ...) \
((decltype(&(func)))shadowhook_get_prev_func((void *)(func)))(__VA_ARGS__)
#else
#define SHADOWHOOK_CALL_PREV(func, func_sig, ...) \
((func_sig)shadowhook_get_prev_func((void *)(func)))(__VA_ARGS__)
#endif
// pop stack in proxy-function (for C/C++)
#define SHADOWHOOK_POP_STACK() \
do { \
if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_pop_stack(__builtin_return_address(0)); \
} while (0)
// pop stack in proxy-function (for C++ only)
#ifdef __cplusplus
class ShadowhookStackScope {
public:
ShadowhookStackScope(void *return_address) : return_address_(return_address) {}
~ShadowhookStackScope() {
if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_pop_stack(return_address_);
}
private:
void *return_address_;
};
#define SHADOWHOOK_STACK_SCOPE() ShadowhookStackScope shadowhook_stack_scope_obj(__builtin_return_address(0))
#endif
// allow reentrant of the current proxy-function
#define SHADOWHOOK_ALLOW_REENTRANT() \
do { \
if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_allow_reentrant(__builtin_return_address(0)); \
} while (0)
// disallow reentrant of the current proxy-function
#define SHADOWHOOK_DISALLOW_REENTRANT() \
do { \
if (SHADOWHOOK_IS_SHARED_MODE) shadowhook_disallow_reentrant(__builtin_return_address(0)); \
} while (0)
// get return address in proxy-function
#define SHADOWHOOK_RETURN_ADDRESS() \
((void *)(SHADOWHOOK_IS_SHARED_MODE ? shadowhook_get_return_address() : __builtin_return_address(0)))
#endif

View File

@ -0,0 +1,47 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_enter.h"
#include <stdint.h>
#include "sh_trampo.h"
#define SH_ENTER_PAGE_NAME "shadowhook-enter"
#define SH_ENTER_SZ 256
#define SH_ENTER_DELAY_SEC 10
static sh_trampo_mgr_t sh_enter_trampo_mgr;
int sh_enter_init(void) {
sh_trampo_init_mgr(&sh_enter_trampo_mgr, SH_ENTER_PAGE_NAME, SH_ENTER_SZ, SH_ENTER_DELAY_SEC);
return 0;
}
uintptr_t sh_enter_alloc(void) {
return sh_trampo_alloc(&sh_enter_trampo_mgr, 0, 0, 0);
}
void sh_enter_free(uintptr_t enter) {
sh_trampo_free(&sh_enter_trampo_mgr, enter);
}

View File

@ -0,0 +1,30 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdint.h>
int sh_enter_init(void);
uintptr_t sh_enter_alloc(void);
void sh_enter_free(uintptr_t enter);

View File

@ -0,0 +1,420 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_exit.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/mman.h>
#include "sh_config.h"
#include "sh_log.h"
#include "sh_sig.h"
#include "sh_trampo.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "xdl.h"
#define SH_EXIT_PAGE_NAME "shadowhook-exit"
#if defined(__arm__)
#define SH_EXIT_SZ 8
#elif defined(__aarch64__)
#define SH_EXIT_SZ 16
#endif
#define SH_EXIT_DELAY_SEC 2
#define SH_EXIT_GAPS_CAP 16
// Used to identify whether the ELF gap has been zero-filled,
// and also serves as a guard instruction.
#if defined(__arm__)
#define SH_EXIT_CLEAR_FLAG 0xE1200070BE00BE00 // BKPT #0(thumb) + BKPT #0(thumb) + BKPT #0(arm)
#elif defined(__aarch64__)
#define SH_EXIT_CLEAR_FLAG 0xD4200000D4200000 // BRK #0 + BRK #0
#endif
#define SH_EXIT_TYPE_OUT_LIBRARY 0
#define SH_EXIT_TYPE_IN_LIBRARY 1
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
static pthread_mutex_t sh_exit_lock = PTHREAD_MUTEX_INITIALIZER;
static sh_trampo_mgr_t sh_exit_trampo_mgr;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct {
uintptr_t load_bias;
const ElfW(Phdr) *dlpi_phdr;
ElfW(Half) dlpi_phnum;
} sh_exit_elfinfo_t;
#pragma clang diagnostic pop
static sh_exit_elfinfo_t sh_exit_app_process_info;
static sh_exit_elfinfo_t sh_exit_linker_info;
static sh_exit_elfinfo_t sh_exit_vdso_info; // vdso may not exist
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
static void sh_exit_init_elfinfo(unsigned long type, sh_exit_elfinfo_t *info) {
if (__predict_false(NULL == getauxval)) goto err;
uintptr_t val = (uintptr_t)getauxval(type);
if (__predict_false(0 == val)) goto err;
// get base
uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val);
if (__predict_false(0 != memcmp((void *)base, ELFMAG, SELFMAG))) goto err;
// ELF info
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
ElfW(Half) dlpi_phnum = ehdr->e_phnum;
// get load_bias
uintptr_t min_vaddr = UINTPTR_MAX;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
}
}
if (__predict_false(UINTPTR_MAX == min_vaddr || base < min_vaddr)) goto err;
uintptr_t load_bias = base - min_vaddr;
info->load_bias = load_bias;
info->dlpi_phdr = dlpi_phdr;
info->dlpi_phnum = dlpi_phnum;
return;
err:
info->load_bias = 0;
info->dlpi_phdr = NULL;
info->dlpi_phnum = 0;
}
void sh_exit_init(void) {
// init for out-library mode
sh_trampo_init_mgr(&sh_exit_trampo_mgr, SH_EXIT_PAGE_NAME, SH_EXIT_SZ, SH_EXIT_DELAY_SEC);
// init for in-library mode
sh_exit_init_elfinfo(AT_PHDR, &sh_exit_app_process_info);
sh_exit_init_elfinfo(AT_BASE, &sh_exit_linker_info);
sh_exit_init_elfinfo(AT_SYSINFO_EHDR, &sh_exit_vdso_info);
}
// out-library mode:
//
// We store the shellcode for exit in mmaped memory near the PC.
//
static int sh_exit_alloc_out_library(uintptr_t *exit_addr, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
size_t exit_len, size_t range_low, size_t range_high) {
(void)dlinfo;
uintptr_t addr = sh_trampo_alloc(&sh_exit_trampo_mgr, pc, range_low, range_high);
if (0 == addr) return -1;
memcpy((void *)addr, exit, exit_len);
sh_util_clear_cache(addr, exit_len);
*exit_addr = addr;
return 0;
}
static void sh_exit_free_out_library(uintptr_t exit_addr) {
sh_trampo_free(&sh_exit_trampo_mgr, exit_addr);
}
// in-library mode:
//
// We store the shellcode for exit in the memory gaps in the ELF.
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct {
uintptr_t start;
uintptr_t end;
bool need_fill_zero;
bool readable;
} sh_exit_gap_t;
#pragma clang diagnostic pop
static size_t sh_exit_get_gaps(xdl_info_t *dlinfo, sh_exit_gap_t *gaps, size_t gaps_cap,
bool elf_loaded_by_kernel) {
size_t gaps_used = 0;
for (size_t i = 0; i < dlinfo->dlpi_phnum; i++) {
// current LOAD segment
const ElfW(Phdr) *cur_phdr = &(dlinfo->dlpi_phdr[i]);
if (PT_LOAD != cur_phdr->p_type) continue;
// next LOAD segment
const ElfW(Phdr) *next_phdr = NULL;
if (!elf_loaded_by_kernel) {
for (size_t j = i + 1; j < dlinfo->dlpi_phnum; j++) {
if (PT_LOAD == dlinfo->dlpi_phdr[j].p_type) {
next_phdr = &(dlinfo->dlpi_phdr[j]);
break;
}
}
}
uintptr_t cur_end = (uintptr_t)dlinfo->dli_fbase + cur_phdr->p_vaddr + cur_phdr->p_memsz;
uintptr_t cur_page_end = SH_UTIL_PAGE_END(cur_end);
uintptr_t cur_file_end = (uintptr_t)dlinfo->dli_fbase + cur_phdr->p_vaddr + cur_phdr->p_filesz;
uintptr_t cur_file_page_end = SH_UTIL_PAGE_END(cur_file_end);
uintptr_t next_page_start =
(NULL == next_phdr ? cur_page_end
: SH_UTIL_PAGE_START((uintptr_t)dlinfo->dli_fbase + next_phdr->p_vaddr));
sh_exit_gap_t gap = {0, 0, false, false};
if (cur_phdr->p_flags & PF_X) {
// From: last PF_X page's unused memory tail space.
// To: next page start.
gap.start = SH_UTIL_ALIGN_END(cur_end, 0x10);
gap.end = next_page_start;
gap.need_fill_zero = true;
gap.readable = (cur_phdr->p_flags & PF_R && cur_page_end == next_page_start);
} else if (cur_page_end > cur_file_page_end) {
// From: last .bss page(which must NOT be file backend)'s unused memory tail space.
// To: next page start.
gap.start = SH_UTIL_ALIGN_END(cur_end, 0x10);
gap.end = next_page_start;
gap.need_fill_zero = false;
gap.readable = (cur_phdr->p_flags & PF_R && cur_page_end == next_page_start);
} else if (next_page_start > cur_page_end) {
// Entire unused memory pages.
gap.start = cur_page_end;
gap.end = next_page_start;
gap.need_fill_zero = true;
gap.readable = false;
}
if ((gap.need_fill_zero && gap.end > gap.start + 0x10) || (!gap.need_fill_zero && gap.end > gap.start)) {
SH_LOG_INFO("exit: gap, %" PRIxPTR " - %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR " - %" PRIxPTR
"), NFZ %d, READABLE %d",
gap.start, gap.end, (uintptr_t)dlinfo->dli_fbase, gap.start - (uintptr_t)dlinfo->dli_fbase,
gap.end - (uintptr_t)dlinfo->dli_fbase, gap.need_fill_zero ? 1 : 0, gap.readable ? 1 : 0);
gaps[gaps_used].start = gap.start;
gaps[gaps_used].end = gap.end;
gaps[gaps_used].need_fill_zero = gap.need_fill_zero;
gaps[gaps_used].readable = gap.readable;
gaps_used++;
}
if (gaps_used >= gaps_cap) break;
}
return gaps_used;
}
static bool sh_exit_is_in_elf_range(uintptr_t pc, sh_exit_elfinfo_t *info) {
if (pc < info->load_bias) return false;
uintptr_t vaddr = pc - info->load_bias;
for (size_t i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
if (PT_LOAD != phdr->p_type) continue;
if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true;
}
return false;
}
static bool sh_exit_is_elf_loaded_by_kernel(uintptr_t pc) {
if (NULL == sh_exit_app_process_info.dlpi_phdr) return true;
if (sh_exit_is_in_elf_range(pc, &sh_exit_app_process_info)) return true;
if (NULL == sh_exit_linker_info.dlpi_phdr) return true;
if (sh_exit_is_in_elf_range(pc, &sh_exit_linker_info)) return true;
// vdso may not exist
if (NULL != sh_exit_vdso_info.dlpi_phdr)
if (sh_exit_is_in_elf_range(pc, &sh_exit_vdso_info)) return true;
return false;
}
static bool sh_exit_is_zero(uintptr_t buf, size_t buf_len) {
for (uintptr_t i = buf; i < buf + buf_len; i += sizeof(uintptr_t))
if (*((uintptr_t *)i) != 0) return false;
return true;
}
static int sh_exit_fill_zero(uintptr_t start, uintptr_t end, bool readable, xdl_info_t *dlinfo) {
size_t size = end - start;
bool set_prot_rwx = false;
if (!readable) {
if (0 != sh_util_mprotect(start, size, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1;
set_prot_rwx = true;
}
if (*((uint64_t *)start) != SH_EXIT_CLEAR_FLAG) {
SH_LOG_INFO("exit: gap fill zero, %" PRIxPTR " - %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR
" - %" PRIxPTR "), READABLE %d",
start, end, (uintptr_t)dlinfo->dli_fbase, start - (uintptr_t)dlinfo->dli_fbase,
end - (uintptr_t)dlinfo->dli_fbase, readable ? 1 : 0);
if (!set_prot_rwx)
if (0 != sh_util_mprotect(start, size, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1;
memset((void *)start, 0, size);
*((uint64_t *)(start)) = SH_EXIT_CLEAR_FLAG;
sh_util_clear_cache(start, size);
}
return 0;
}
static int sh_exit_try_alloc_in_library(uintptr_t *exit_addr, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
size_t exit_len, size_t range_low, size_t range_high, uintptr_t start,
uintptr_t end) {
if (pc >= range_low) start = SH_UTIL_MAX(start, pc - range_low);
start = SH_UTIL_ALIGN_END(start, exit_len);
if (range_high <= UINTPTR_MAX - pc) end = SH_UTIL_MIN(end - exit_len, pc + range_high);
end = SH_UTIL_ALIGN_START(end, exit_len);
if (end < start) return -1;
SH_LOG_INFO("exit: gap resize, %" PRIxPTR " - %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR
" - %" PRIxPTR ")",
start, end, (uintptr_t)dlinfo->dli_fbase, start - (uintptr_t)dlinfo->dli_fbase,
end - (uintptr_t)dlinfo->dli_fbase);
for (uintptr_t cur = start; cur <= end; cur += exit_len) {
// check if the current space has been used
if (!sh_exit_is_zero(cur, exit_len)) continue;
// write shellcode to the current location
if (0 != sh_util_mprotect(cur, exit_len, PROT_READ | PROT_WRITE | PROT_EXEC)) return -1;
memcpy((void *)cur, exit, exit_len);
sh_util_clear_cache(cur, exit_len);
*exit_addr = cur;
SH_LOG_INFO("exit: in-library alloc, at %" PRIxPTR " (load_bias %" PRIxPTR ", %" PRIxPTR "), len %zu",
cur, (uintptr_t)dlinfo->dli_fbase, cur - (uintptr_t)dlinfo->dli_fbase, exit_len);
return 0;
}
return -1;
}
static int sh_exit_alloc_in_library(uintptr_t *exit_addr, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
size_t exit_len, size_t range_low, size_t range_high) {
int r = -1;
*exit_addr = 0;
bool elf_loaded_by_kernel = sh_exit_is_elf_loaded_by_kernel(pc);
pthread_mutex_lock(&sh_exit_lock);
SH_SIG_TRY(SIGSEGV, SIGBUS) {
sh_exit_gap_t gaps[SH_EXIT_GAPS_CAP];
size_t gaps_used = sh_exit_get_gaps(dlinfo, gaps, SH_EXIT_GAPS_CAP, elf_loaded_by_kernel);
for (size_t i = 0; i < gaps_used; i++) {
// fill zero
if (gaps[i].need_fill_zero) {
if (0 != sh_exit_fill_zero(gaps[i].start, gaps[i].end, gaps[i].readable, dlinfo)) return -1;
}
if (0 == (r = sh_exit_try_alloc_in_library(exit_addr, pc, dlinfo, exit, exit_len, range_low, range_high,
gaps[i].start, gaps[i].end)))
break;
}
}
SH_SIG_CATCH() {
r = -1;
*exit_addr = 0;
SH_LOG_WARN("exit: alloc crashed");
}
SH_SIG_EXIT
pthread_mutex_unlock(&sh_exit_lock);
return r;
}
static int sh_exit_free_in_library(uintptr_t exit_addr, uint8_t *exit, size_t exit_len) {
int r;
pthread_mutex_lock(&sh_exit_lock);
SH_SIG_TRY(SIGSEGV, SIGBUS) {
if (0 != memcmp((void *)exit_addr, exit, exit_len)) {
r = SHADOWHOOK_ERRNO_UNHOOK_EXIT_MISMATCH;
goto err;
}
if (0 != sh_util_mprotect((uintptr_t)exit_addr, exit_len, PROT_READ | PROT_WRITE | PROT_EXEC)) {
r = SHADOWHOOK_ERRNO_MPROT;
goto err;
}
memset((void *)exit_addr, 0, exit_len);
sh_util_clear_cache((uintptr_t)exit_addr, exit_len);
r = 0;
err:;
}
SH_SIG_CATCH() {
r = SHADOWHOOK_ERRNO_UNHOOK_EXIT_CRASH;
SH_LOG_WARN("exit: free crashed");
}
SH_SIG_EXIT
pthread_mutex_unlock(&sh_exit_lock);
return r;
}
int sh_exit_alloc(uintptr_t *exit_addr, uint16_t *exit_type, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
size_t exit_len, size_t range_low, size_t range_high) {
int r;
// (1) try out-library mode first. Because ELF gaps are a valuable non-renewable resource.
*exit_type = SH_EXIT_TYPE_OUT_LIBRARY;
r = sh_exit_alloc_out_library(exit_addr, pc, dlinfo, exit, exit_len, range_low, range_high);
if (0 == r) goto ok;
// (2) try in-library mode.
*exit_type = SH_EXIT_TYPE_IN_LIBRARY;
r = sh_exit_alloc_in_library(exit_addr, pc, dlinfo, exit, exit_len, range_low, range_high);
if (0 == r) goto ok;
return r;
ok:
SH_LOG_INFO("exit: alloc %s library, exit %" PRIxPTR ", pc %" PRIxPTR ", distance %" PRIxPTR
", range [-%zx, %zx]",
(*exit_type == SH_EXIT_TYPE_OUT_LIBRARY ? "out" : "in"), *exit_addr, pc,
(pc > *exit_addr ? pc - *exit_addr : *exit_addr - pc), range_low, range_high);
return 0;
}
int sh_exit_free(uintptr_t exit_addr, uint16_t exit_type, uint8_t *exit, size_t exit_len) {
if (SH_EXIT_TYPE_OUT_LIBRARY == exit_type) {
(void)exit, (void)exit_len;
sh_exit_free_out_library(exit_addr);
return 0;
} else
return sh_exit_free_in_library(exit_addr, exit, exit_len);
}
#pragma clang diagnostic pop

View File

@ -0,0 +1,34 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include "xdl.h"
void sh_exit_init(void);
int sh_exit_alloc(uintptr_t *exit_addr, uint16_t *exit_type, uintptr_t pc, xdl_info_t *dlinfo, uint8_t *exit,
size_t exit_len, size_t range_low, size_t range_high);
int sh_exit_free(uintptr_t exit_addr, uint16_t exit_type, uint8_t *exit, size_t exit_len);

View File

@ -0,0 +1,538 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_hub.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include "queue.h"
#include "sh_log.h"
#include "sh_safe.h"
#include "sh_sig.h"
#include "sh_trampo.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "tree.h"
#define SH_HUB_TRAMPO_PAGE_NAME "shadowhook-hub-trampo"
#define SH_HUB_TRAMPO_DELAY_SEC 5
#define SH_HUB_STACK_NAME "shadowhook-hub-stack"
#define SH_HUB_STACK_SIZE 4096
#define SH_HUB_STACK_FRAME_MAX 127 // keep sizeof(sh_hub_stack_t) < 4K
#define SH_HUB_THREAD_MAX 1024
#define SH_HUB_DELAY_SEC 10
#define SH_HUB_FRAME_FLAG_NONE ((uintptr_t)0)
#define SH_HUB_FRAME_FLAG_ALLOW_REENTRANT ((uintptr_t)(1 << 0))
// proxy for each hook-task in the same target-address
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct sh_hub_proxy {
void *func;
bool enabled;
SLIST_ENTRY(sh_hub_proxy, ) link;
} sh_hub_proxy_t;
#pragma clang diagnostic pop
// proxy list for each hub
typedef SLIST_HEAD(sh_hub_proxy_list, sh_hub_proxy, ) sh_hub_proxy_list_t;
// frame in the stack
typedef struct {
sh_hub_proxy_list_t proxies;
uintptr_t orig_addr;
void *return_address;
uintptr_t flags;
} sh_hub_frame_t;
// stack for each thread
typedef struct {
size_t frames_cnt;
sh_hub_frame_t frames[SH_HUB_STACK_FRAME_MAX];
} sh_hub_stack_t;
// hub for each target-address
struct sh_hub {
sh_hub_proxy_list_t proxies;
pthread_mutex_t proxies_lock;
uintptr_t orig_addr;
uintptr_t trampo;
time_t destroy_ts;
LIST_ENTRY(sh_hub, ) link;
};
// hub list for delayed-destroy staging
typedef LIST_HEAD(sh_hub_list, sh_hub, ) sh_hub_list_t;
// global data for hub delayed-destroy staging
static sh_hub_list_t sh_hub_delayed_destroy;
static pthread_mutex_t sh_hub_delayed_destroy_lock;
// global data for trampo
static sh_trampo_mgr_t sh_hub_trampo_mgr;
// global data for stack
static pthread_key_t sh_hub_stack_tls_key;
static sh_hub_stack_t *sh_hub_stack_cache;
static uint8_t *sh_hub_stack_cache_used;
// hub trampoline template
extern void *sh_hub_trampo_template_data __attribute__((visibility("hidden")));
__attribute__((naked)) static void sh_hub_trampo_template(void) {
#if defined(__arm__)
__asm__(
// Save parameter registers, LR
"push { r0 - r3, lr } \n"
// Call sh_hub_push_stack()
"ldr r0, hub_ptr \n"
"mov r1, lr \n"
"ldr ip, push_stack \n"
"blx ip \n"
// Save the hook function's address to IP register
"mov ip, r0 \n"
// Restore parameter registers, LR
"pop { r0 - r3, lr } \n"
// Call hook function
"bx ip \n"
"sh_hub_trampo_template_data:"
".global sh_hub_trampo_template_data;"
"push_stack:"
".word 0;"
"hub_ptr:"
".word 0;");
#elif defined(__aarch64__)
__asm__(
// Save parameter registers, XR(X8), LR
"stp x0, x1, [sp, #-0xd0]! \n"
"stp x2, x3, [sp, #0x10] \n"
"stp x4, x5, [sp, #0x20] \n"
"stp x6, x7, [sp, #0x30] \n"
"stp x8, lr, [sp, #0x40] \n"
"stp q0, q1, [sp, #0x50] \n"
"stp q2, q3, [sp, #0x70] \n"
"stp q4, q5, [sp, #0x90] \n"
"stp q6, q7, [sp, #0xb0] \n"
// Call sh_hub_push_stack()
"ldr x0, hub_ptr \n"
"mov x1, lr \n"
"ldr x16, push_stack \n"
"blr x16 \n"
// Save the hook function's address to IP register
"mov x16, x0 \n"
// Restore parameter registers, XR(X8), LR
"ldp q6, q7, [sp, #0xb0] \n"
"ldp q4, q5, [sp, #0x90] \n"
"ldp q2, q3, [sp, #0x70] \n"
"ldp q0, q1, [sp, #0x50] \n"
"ldp x8, lr, [sp, #0x40] \n"
"ldp x6, x7, [sp, #0x30] \n"
"ldp x4, x5, [sp, #0x20] \n"
"ldp x2, x3, [sp, #0x10] \n"
"ldp x0, x1, [sp], #0xd0 \n"
// Call hook function
"br x16 \n"
"sh_hub_trampo_template_data:"
".global sh_hub_trampo_template_data;"
"push_stack:"
".quad 0;"
"hub_ptr:"
".quad 0;");
#endif
}
static void *sh_hub_trampo_template_start(void) {
#if defined(__arm__) && defined(__thumb__)
return (void *)((uintptr_t)&sh_hub_trampo_template - 1);
#else
return (void *)&sh_hub_trampo_template;
#endif
}
static sh_hub_stack_t *sh_hub_stack_create(void) {
// get stack from global cache
for (size_t i = 0; i < SH_HUB_THREAD_MAX; i++) {
uint8_t *used = &(sh_hub_stack_cache_used[i]);
if (0 == *used) {
uint8_t expected = 0;
if (__atomic_compare_exchange_n(used, &expected, 1, false, __ATOMIC_ACQUIRE, __ATOMIC_RELAXED)) {
sh_hub_stack_t *stack = &(sh_hub_stack_cache[i]);
stack->frames_cnt = 0;
SH_LOG_DEBUG("hub: get stack from global cache[%zu] %p", i, (void *)stack);
return stack; // OK
}
}
}
// create new stack by mmap
int prot = PROT_READ | PROT_WRITE;
int flags = MAP_PRIVATE | MAP_ANONYMOUS;
void *buf = sh_safe_mmap(NULL, SH_HUB_STACK_SIZE, prot, flags, -1, 0);
if (MAP_FAILED == buf) return NULL; // failed
sh_safe_prctl(PR_SET_VMA, PR_SET_VMA_ANON_NAME, (unsigned long)buf, SH_HUB_STACK_SIZE,
(unsigned long)SH_HUB_STACK_NAME);
sh_hub_stack_t *stack = (sh_hub_stack_t *)buf;
stack->frames_cnt = 0;
return stack; // OK
}
static void sh_hub_stack_destroy(void *buf) {
if (NULL == buf) return;
if ((uintptr_t)sh_hub_stack_cache <= (uintptr_t)buf &&
(uintptr_t)buf < ((uintptr_t)sh_hub_stack_cache + SH_HUB_THREAD_MAX * sizeof(sh_hub_stack_t))) {
// return stack to global cache
size_t i = ((uintptr_t)buf - (uintptr_t)sh_hub_stack_cache) / sizeof(sh_hub_stack_t);
uint8_t *used = &(sh_hub_stack_cache_used[i]);
if (1 != *used) abort();
__atomic_store_n(used, 0, __ATOMIC_RELEASE);
SH_LOG_DEBUG("hub: return stack to global cache[%zu] %p", i, buf);
} else {
// munmap stack
munmap(buf, SH_HUB_STACK_SIZE);
}
}
int sh_hub_init(void) {
LIST_INIT(&sh_hub_delayed_destroy);
pthread_mutex_init(&sh_hub_delayed_destroy_lock, NULL);
// init TLS key
if (__predict_false(0 != pthread_key_create(&sh_hub_stack_tls_key, sh_hub_stack_destroy))) return -1;
// init hub's stack cache
if (__predict_false(NULL == (sh_hub_stack_cache = malloc(SH_HUB_THREAD_MAX * sizeof(sh_hub_stack_t)))))
return -1;
if (__predict_false(NULL == (sh_hub_stack_cache_used = calloc(SH_HUB_THREAD_MAX, sizeof(uint8_t)))))
return -1;
// init hub's trampoline manager
size_t code_size = (uintptr_t)(&sh_hub_trampo_template_data) - (uintptr_t)(sh_hub_trampo_template_start());
size_t data_size = sizeof(void *) + sizeof(void *);
sh_trampo_init_mgr(&sh_hub_trampo_mgr, SH_HUB_TRAMPO_PAGE_NAME, code_size + data_size,
SH_HUB_TRAMPO_DELAY_SEC);
return 0;
}
static void *sh_hub_push_stack(sh_hub_t *self, void *return_address) {
sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
// create stack, only once
if (__predict_false(NULL == stack)) {
if (__predict_false(NULL == (stack = sh_hub_stack_create()))) goto end;
sh_safe_pthread_setspecific(sh_hub_stack_tls_key, (void *)stack);
}
// check whether a recursive call occurred
bool recursive = false;
for (size_t i = stack->frames_cnt; i > 0; i--) {
sh_hub_frame_t *frame = &stack->frames[i - 1];
if (0 == (frame->flags & SH_HUB_FRAME_FLAG_ALLOW_REENTRANT) && (frame->orig_addr == self->orig_addr)) {
// recursive call found
recursive = true;
break;
}
}
// find and return the first enabled proxy's function in the proxy-list
// (does not include the original function)
if (!recursive) {
sh_hub_proxy_t *proxy;
SLIST_FOREACH(proxy, &self->proxies, link) {
if (proxy->enabled) {
// push a new frame for the current proxy
if (stack->frames_cnt >= SH_HUB_STACK_FRAME_MAX) goto end;
stack->frames_cnt++;
SH_LOG_DEBUG("hub: frames_cnt++ = %zu", stack->frames_cnt);
sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
frame->proxies = self->proxies;
frame->orig_addr = self->orig_addr;
frame->return_address = return_address;
frame->flags = SH_HUB_FRAME_FLAG_NONE;
// return the first enabled proxy's function
SH_LOG_DEBUG("hub: push_stack() return first enabled proxy %p", proxy->func);
return proxy->func;
}
}
}
// if not found enabled proxy in the proxy-list, or recursive call found,
// just return the original-function
end:
SH_LOG_DEBUG("hub: push_stack() return orig_addr %p", (void *)self->orig_addr);
return (void *)self->orig_addr;
}
void sh_hub_pop_stack(void *return_address) {
sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
if (0 == stack->frames_cnt) return;
sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
// only the first proxy will actually execute pop-stack()
if (frame->return_address == return_address) {
stack->frames_cnt--;
SH_LOG_DEBUG("hub: frames_cnt-- = %zu", stack->frames_cnt);
}
}
sh_hub_t *sh_hub_create(uintptr_t target_addr, uintptr_t *trampo) {
size_t code_size = (uintptr_t)(&sh_hub_trampo_template_data) - (uintptr_t)(sh_hub_trampo_template_start());
size_t data_size = sizeof(void *) + sizeof(void *);
sh_hub_t *self = malloc(sizeof(sh_hub_t));
if (NULL == self) return NULL;
SLIST_INIT(&self->proxies);
pthread_mutex_init(&self->proxies_lock, NULL);
self->orig_addr = 0;
// alloc memory for trampoline
if (0 == (self->trampo = sh_trampo_alloc(&sh_hub_trampo_mgr, 0, 0, 0))) {
free(self);
return NULL;
}
// fill in code
SH_SIG_TRY(SIGSEGV, SIGBUS) {
memcpy((void *)self->trampo, sh_hub_trampo_template_start(), code_size);
}
SH_SIG_CATCH() {
sh_trampo_free(&sh_hub_trampo_mgr, self->trampo);
free(self);
SH_LOG_WARN("hub: fill in code crashed");
return NULL;
}
SH_SIG_EXIT
// file in data
void **data = (void **)(self->trampo + code_size);
*data++ = (void *)sh_hub_push_stack;
*data = (void *)self;
// clear CPU cache
sh_util_clear_cache(self->trampo, code_size + data_size);
#if defined(__arm__) && defined(__thumb__)
*trampo = self->trampo + 1;
#else
*trampo = self->trampo;
#endif
SH_LOG_INFO("hub: create trampo for target_addr %" PRIxPTR " at %" PRIxPTR ", size %zu + %zu = %zu",
target_addr, *trampo, code_size, data_size, code_size + data_size);
return self;
}
static void sh_hub_destroy_inner(sh_hub_t *self) {
pthread_mutex_destroy(&self->proxies_lock);
if (0 != self->trampo) sh_trampo_free(&sh_hub_trampo_mgr, self->trampo);
while (!SLIST_EMPTY(&self->proxies)) {
sh_hub_proxy_t *proxy = SLIST_FIRST(&self->proxies);
SLIST_REMOVE_HEAD(&self->proxies, link);
free(proxy);
}
free(self);
}
void sh_hub_destroy(sh_hub_t *self, bool with_delay) {
if (SHADOWHOOK_IS_SHARED_MODE) {
struct timeval now;
gettimeofday(&now, NULL);
if (!LIST_EMPTY(&sh_hub_delayed_destroy)) {
pthread_mutex_lock(&sh_hub_delayed_destroy_lock);
sh_hub_t *hub, *hub_tmp;
LIST_FOREACH_SAFE(hub, &sh_hub_delayed_destroy, link, hub_tmp)
if (now.tv_sec - hub->destroy_ts > SH_HUB_DELAY_SEC) {
LIST_REMOVE(hub, link);
sh_hub_destroy_inner(hub);
}
pthread_mutex_unlock(&sh_hub_delayed_destroy_lock);
}
if (with_delay) {
self->destroy_ts = now.tv_sec;
sh_trampo_free(&sh_hub_trampo_mgr, self->trampo);
self->trampo = 0;
pthread_mutex_lock(&sh_hub_delayed_destroy_lock);
LIST_INSERT_HEAD(&sh_hub_delayed_destroy, self, link);
pthread_mutex_unlock(&sh_hub_delayed_destroy_lock);
} else
sh_hub_destroy_inner(self);
} else
sh_hub_destroy_inner(self);
}
uintptr_t sh_hub_get_orig_addr(sh_hub_t *self) {
return self->orig_addr;
}
uintptr_t *sh_hub_get_orig_addr_addr(sh_hub_t *self) {
return &self->orig_addr;
}
int sh_hub_add_proxy(sh_hub_t *self, uintptr_t func) {
int r = SHADOWHOOK_ERRNO_OK;
pthread_mutex_lock(&self->proxies_lock);
// check repeated funcion
sh_hub_proxy_t *proxy;
SLIST_FOREACH(proxy, &self->proxies, link) {
if (proxy->enabled && proxy->func == (void *)func) {
r = SHADOWHOOK_ERRNO_HOOK_DUP;
goto end;
}
}
// try to re-enable an exists item
SLIST_FOREACH(proxy, &self->proxies, link) {
if (proxy->func == (void *)func) {
if (!proxy->enabled) __atomic_store_n((bool *)&proxy->enabled, true, __ATOMIC_SEQ_CST);
SH_LOG_INFO("hub: add(re-enable) func %" PRIxPTR, func);
goto end;
}
}
// create new item
if (NULL == (proxy = malloc(sizeof(sh_hub_proxy_t)))) {
r = SHADOWHOOK_ERRNO_OOM;
goto end;
}
proxy->func = (void *)func;
proxy->enabled = true;
// insert to the head of the proxy-list
// equivalent to: SLIST_INSERT_HEAD(&self->proxies, proxy, link);
// but: __ATOMIC_RELEASE ensures readers see only fully-constructed item
SLIST_NEXT(proxy, link) = SLIST_FIRST(&self->proxies);
__atomic_store_n((uintptr_t *)(&SLIST_FIRST(&self->proxies)), (uintptr_t)proxy, __ATOMIC_RELEASE);
SH_LOG_INFO("hub: add(new) func %" PRIxPTR, func);
end:
pthread_mutex_unlock(&self->proxies_lock);
return r;
}
int sh_hub_del_proxy(sh_hub_t *self, uintptr_t func, bool *have_enabled_proxy) {
*have_enabled_proxy = false;
pthread_mutex_lock(&self->proxies_lock);
sh_hub_proxy_t *proxy;
bool deleted = false;
SLIST_FOREACH(proxy, &self->proxies, link) {
if (proxy->func == (void *)func) {
if (proxy->enabled) __atomic_store_n((bool *)&proxy->enabled, false, __ATOMIC_SEQ_CST);
deleted = true;
SH_LOG_INFO("hub: del func %" PRIxPTR, func);
}
if (proxy->enabled && !*have_enabled_proxy) *have_enabled_proxy = true;
if (deleted && *have_enabled_proxy) break;
}
pthread_mutex_unlock(&self->proxies_lock);
return deleted ? 0 : -1;
}
static sh_hub_frame_t *sh_hub_get_current_frame(void *return_address) {
sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
if (0 == stack->frames_cnt) return NULL;
sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
return frame->return_address == return_address ? frame : NULL;
}
void sh_hub_allow_reentrant(void *return_address) {
sh_hub_frame_t *frame = sh_hub_get_current_frame(return_address);
if (NULL != frame) {
frame->flags |= SH_HUB_FRAME_FLAG_ALLOW_REENTRANT;
SH_LOG_DEBUG("hub: allow reentrant frame %p", return_address);
}
}
void sh_hub_disallow_reentrant(void *return_address) {
sh_hub_frame_t *frame = sh_hub_get_current_frame(return_address);
if (NULL != frame) {
frame->flags &= ~SH_HUB_FRAME_FLAG_ALLOW_REENTRANT;
SH_LOG_DEBUG("hub: disallow reentrant frame %p", return_address);
}
}
void *sh_hub_get_prev_func(void *func) {
sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
if (0 == stack->frames_cnt) sh_safe_abort(); // called in a non-hook status?
sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
// find and return the next enabled hook-function in the hook-chain
bool found = false;
sh_hub_proxy_t *proxy;
SLIST_FOREACH(proxy, &(frame->proxies), link) {
if (!found) {
if (proxy->func == func) found = true;
} else {
if (proxy->enabled) break;
}
}
if (NULL != proxy) {
SH_LOG_DEBUG("hub: get_prev_func() return next enabled proxy %p", proxy->func);
return proxy->func;
}
SH_LOG_DEBUG("hub: get_prev_func() return orig_addr %p", (void *)frame->orig_addr);
// did not find, return the original-function
return (void *)frame->orig_addr;
}
void *sh_hub_get_return_address(void) {
sh_hub_stack_t *stack = (sh_hub_stack_t *)sh_safe_pthread_getspecific(sh_hub_stack_tls_key);
if (0 == stack->frames_cnt) sh_safe_abort(); // called in a non-hook status?
sh_hub_frame_t *frame = &stack->frames[stack->frames_cnt - 1];
return frame->return_address;
}

View File

@ -0,0 +1,45 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stdint.h>
int sh_hub_init(void);
typedef struct sh_hub sh_hub_t;
sh_hub_t *sh_hub_create(uintptr_t target_addr, uintptr_t *trampo);
void sh_hub_destroy(sh_hub_t *self, bool with_delay);
uintptr_t sh_hub_get_orig_addr(sh_hub_t *self);
uintptr_t *sh_hub_get_orig_addr_addr(sh_hub_t *self);
int sh_hub_add_proxy(sh_hub_t *self, uintptr_t func);
int sh_hub_del_proxy(sh_hub_t *self, uintptr_t func, bool *have_enabled_proxy);
void *sh_hub_get_prev_func(void *func);
void sh_hub_pop_stack(void *return_address);
void sh_hub_allow_reentrant(void *return_address);
void sh_hub_disallow_reentrant(void *return_address);
void *sh_hub_get_return_address(void);

View File

@ -0,0 +1,139 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include <errno.h>
#include <jni.h>
#include <stdlib.h>
#include <unistd.h>
#include "sh_log.h"
#include "shadowhook.h"
#define SH_JNI_VERSION JNI_VERSION_1_6
#define SH_JNI_CLASS_NAME "com/bytedance/shadowhook/ShadowHook"
static jstring sh_jni_get_version(JNIEnv *env, jobject thiz) {
(void)thiz;
return (*env)->NewStringUTF(env, shadowhook_get_version());
}
static jint sh_jni_init(JNIEnv *env, jobject thiz, jint mode, jboolean debuggable) {
(void)env, (void)thiz;
return shadowhook_init(0 == mode ? SHADOWHOOK_MODE_SHARED : SHADOWHOOK_MODE_UNIQUE, (bool)debuggable);
}
static jint sh_jni_get_init_errno(JNIEnv *env, jobject thiz) {
(void)env, (void)thiz;
return shadowhook_get_init_errno();
}
static jint sh_jni_get_mode(JNIEnv *env, jobject thiz) {
(void)env, (void)thiz;
return SHADOWHOOK_MODE_SHARED == shadowhook_get_mode() ? 0 : 1;
}
static jboolean sh_jni_get_debuggable(JNIEnv *env, jobject thiz) {
(void)env, (void)thiz;
return shadowhook_get_debuggable();
}
static void sh_jni_set_debuggable(JNIEnv *env, jobject thiz, jboolean debuggable) {
(void)env, (void)thiz;
shadowhook_set_debuggable((bool)debuggable);
}
static jboolean sh_jni_get_recordable(JNIEnv *env, jobject thiz) {
(void)env, (void)thiz;
return shadowhook_get_recordable();
}
static void sh_jni_set_recordable(JNIEnv *env, jobject thiz, jboolean recordable) {
(void)env, (void)thiz;
shadowhook_set_recordable((bool)recordable);
}
static jstring sh_jni_to_errmsg(JNIEnv *env, jobject thiz, jint error_number) {
(void)thiz;
return (*env)->NewStringUTF(env, shadowhook_to_errmsg(error_number));
}
static jstring sh_jni_get_records(JNIEnv *env, jobject thiz, jint item_flags) {
(void)thiz;
char *str = shadowhook_get_records((uint32_t)item_flags);
if (NULL == str) return NULL;
jstring jstr = (*env)->NewStringUTF(env, str);
free(str);
return jstr;
}
static jstring sh_jni_get_arch(JNIEnv *env, jobject thiz) {
(void)thiz;
#if defined(__arm__)
char *arch = "arm";
#elif defined(__aarch64__)
char *arch = "arm64";
#else
char *arch = "unsupported";
#endif
return (*env)->NewStringUTF(env, arch);
}
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
(void)reserved;
if (__predict_false(NULL == vm)) return JNI_ERR;
JNIEnv *env;
if (__predict_false(JNI_OK != (*vm)->GetEnv(vm, (void **)&env, SH_JNI_VERSION))) return JNI_ERR;
if (__predict_false(NULL == env || NULL == *env)) return JNI_ERR;
jclass cls;
if (__predict_false(NULL == (cls = (*env)->FindClass(env, SH_JNI_CLASS_NAME)))) return JNI_ERR;
JNINativeMethod m[] = {{"nativeGetVersion", "()Ljava/lang/String;", (void *)sh_jni_get_version},
{"nativeInit", "(IZ)I", (void *)sh_jni_init},
{"nativeGetInitErrno", "()I", (void *)sh_jni_get_init_errno},
{"nativeGetMode", "()I", (void *)sh_jni_get_mode},
{"nativeGetDebuggable", "()Z", (void *)sh_jni_get_debuggable},
{"nativeSetDebuggable", "(Z)V", (void *)sh_jni_set_debuggable},
{"nativeGetRecordable", "()Z", (void *)sh_jni_get_recordable},
{"nativeSetRecordable", "(Z)V", (void *)sh_jni_set_recordable},
{"nativeToErrmsg", "(I)Ljava/lang/String;", (void *)sh_jni_to_errmsg},
{"nativeGetRecords", "(I)Ljava/lang/String;", (void *)sh_jni_get_records},
{"nativeGetArch", "()Ljava/lang/String;", (void *)sh_jni_get_arch}};
if (__predict_false(0 != (*env)->RegisterNatives(env, cls, m, sizeof(m) / sizeof(m[0])))) return JNI_ERR;
return SH_JNI_VERSION;
}

View File

@ -0,0 +1,409 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_linker.h"
#include <dlfcn.h>
#include <pthread.h>
#include <stddef.h>
#include <stdint.h>
#include <string.h>
#include "sh_log.h"
#include "sh_recorder.h"
#include "sh_sig.h"
#include "sh_switch.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "xdl.h"
#ifndef __LP64__
#define SH_LINKER_BASENAME "linker"
#else
#define SH_LINKER_BASENAME "linker64"
#endif
#define SH_LINKER_SYM_G_DL_MUTEX "__dl__ZL10g_dl_mutex"
#define SH_LINKER_SYM_DO_DLOPEN_L "__dl__Z9do_dlopenPKciPK17android_dlextinfo"
#define SH_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv"
#define SH_LINKER_SYM_DO_DLOPEN_O "__dl__Z9do_dlopenPKciPK17android_dlextinfoPKv"
static bool sh_linker_dlopen_hooked = false;
static sh_linker_post_dlopen_t sh_linker_post_dlopen;
static void *sh_linker_post_dlopen_arg;
static pthread_mutex_t *sh_linker_g_dl_mutex;
static uintptr_t sh_linker_dlopen_addr; // save address of dlopen(==4.x) or do_dlopen(>=5.0)
static xdl_info_t sh_linker_dlopen_dlinfo;
#if defined(__arm__) && __ANDROID_API__ < __ANDROID_API_L__
static uintptr_t sh_linker_dlfcn[6];
static const char *sh_linker_dlfcn_name[6] = {"dlopen", "dlerror", "dlsym",
"dladdr", "dlclose", "dl_unwind_find_exidx"};
#endif
__attribute__((constructor)) static void sh_linker_ctor(void) {
sh_linker_dlopen_addr = (uintptr_t)dlopen;
#if defined(__arm__) && __ANDROID_API__ < __ANDROID_API_L__
sh_linker_dlfcn[0] = (uintptr_t)dlopen;
sh_linker_dlfcn[1] = (uintptr_t)dlerror;
sh_linker_dlfcn[2] = (uintptr_t)dlsym;
sh_linker_dlfcn[3] = (uintptr_t)dladdr;
sh_linker_dlfcn[4] = (uintptr_t)dlclose;
sh_linker_dlfcn[5] = (uintptr_t)dl_unwind_find_exidx;
#endif
}
static void *sh_linker_get_base_addr(xdl_info_t *dlinfo) {
uintptr_t vaddr_min = UINTPTR_MAX;
for (size_t i = 0; i < dlinfo->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlinfo->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type && vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr;
}
if (UINTPTR_MAX == vaddr_min)
return dlinfo->dli_fbase; // should not happen
else
return (void *)((uintptr_t)dlinfo->dli_fbase + SH_UTIL_PAGE_START(vaddr_min));
}
static bool sh_linker_check_arch(xdl_info_t *dlinfo) {
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)sh_linker_get_base_addr(dlinfo);
#if defined(__LP64__)
#define SH_LINKER_ELF_CLASS ELFCLASS64
#define SH_LINKER_ELF_MACHINE EM_AARCH64
#else
#define SH_LINKER_ELF_CLASS ELFCLASS32
#define SH_LINKER_ELF_MACHINE EM_ARM
#endif
if (0 != memcmp(ehdr->e_ident, ELFMAG, SELFMAG)) return false;
if (SH_LINKER_ELF_CLASS != ehdr->e_ident[EI_CLASS]) return false;
if (SH_LINKER_ELF_MACHINE != ehdr->e_machine) return false;
return true;
}
int sh_linker_init(void) {
memset(&sh_linker_dlopen_dlinfo, 0, sizeof(sh_linker_dlopen_dlinfo));
int api_level = sh_util_get_api_level();
if (__predict_true(api_level >= __ANDROID_API_L__)) {
sh_linker_dlopen_addr = 0;
void *handle = xdl_open(SH_LINKER_BASENAME, XDL_DEFAULT);
if (__predict_false(NULL == handle)) return -1;
xdl_info(handle, XDL_DI_DLINFO, (void *)&sh_linker_dlopen_dlinfo);
sh_linker_dlopen_dlinfo.dli_fname = SH_LINKER_BASENAME;
// get g_dl_mutex
sh_linker_g_dl_mutex = (pthread_mutex_t *)(xdl_dsym(handle, SH_LINKER_SYM_G_DL_MUTEX, NULL));
// get do_dlopen
if (api_level >= __ANDROID_API_O__)
sh_linker_dlopen_dlinfo.dli_sname = SH_LINKER_SYM_DO_DLOPEN_O;
else if (api_level >= __ANDROID_API_N__)
sh_linker_dlopen_dlinfo.dli_sname = SH_LINKER_SYM_DO_DLOPEN_N;
else
sh_linker_dlopen_dlinfo.dli_sname = SH_LINKER_SYM_DO_DLOPEN_L;
sh_linker_dlopen_dlinfo.dli_saddr =
xdl_dsym(handle, sh_linker_dlopen_dlinfo.dli_sname, &(sh_linker_dlopen_dlinfo.dli_ssize));
sh_linker_dlopen_addr = (uintptr_t)sh_linker_dlopen_dlinfo.dli_saddr;
xdl_close(handle);
}
return (0 != sh_linker_dlopen_addr && (0 != sh_linker_g_dl_mutex || api_level < __ANDROID_API_L__)) ? 0
: -1;
}
const char *sh_linker_match_dlfcn(uintptr_t target_addr) {
#if defined(__arm__) && __ANDROID_API__ < __ANDROID_API_L__
if (sh_util_get_api_level() < __ANDROID_API_L__)
for (size_t i = 0; i < sizeof(sh_linker_dlfcn) / sizeof(sh_linker_dlfcn[0]); i++)
if (sh_linker_dlfcn[i] == target_addr) return sh_linker_dlfcn_name[i];
#else
(void)target_addr;
#endif
return NULL;
}
bool sh_linker_need_to_hook_dlopen(uintptr_t target_addr) {
return SHADOWHOOK_IS_UNIQUE_MODE && !sh_linker_dlopen_hooked && target_addr == sh_linker_dlopen_addr;
}
typedef void *(*sh_linker_proxy_dlopen_t)(const char *, int);
static sh_linker_proxy_dlopen_t sh_linker_orig_dlopen;
static void *sh_linker_proxy_dlopen(const char *filename, int flag) {
void *handle;
if (SHADOWHOOK_IS_SHARED_MODE)
handle = SHADOWHOOK_CALL_PREV(sh_linker_proxy_dlopen, sh_linker_proxy_dlopen_t, filename, flag);
else
handle = sh_linker_orig_dlopen(filename, flag);
if (NULL != handle) sh_linker_post_dlopen(sh_linker_post_dlopen_arg);
if (SHADOWHOOK_IS_SHARED_MODE) SHADOWHOOK_POP_STACK();
return handle;
}
typedef void *(*sh_linker_proxy_do_dlopen_l_t)(const char *, int, const void *);
static sh_linker_proxy_do_dlopen_l_t sh_linker_orig_do_dlopen_l;
static void *sh_linker_proxy_do_dlopen_l(const char *name, int flags, const void *extinfo) {
void *handle;
if (SHADOWHOOK_IS_SHARED_MODE)
handle = SHADOWHOOK_CALL_PREV(sh_linker_proxy_do_dlopen_l, sh_linker_proxy_do_dlopen_l_t, name, flags,
extinfo);
else
handle = sh_linker_orig_do_dlopen_l(name, flags, extinfo);
if (NULL != handle) sh_linker_post_dlopen(sh_linker_post_dlopen_arg);
if (SHADOWHOOK_IS_SHARED_MODE) SHADOWHOOK_POP_STACK();
return handle;
}
typedef void *(*sh_linker_proxy_do_dlopen_n_t)(const char *, int, const void *, void *);
static sh_linker_proxy_do_dlopen_n_t sh_linker_orig_do_dlopen_n;
static void *sh_linker_proxy_do_dlopen_n(const char *name, int flags, const void *extinfo,
void *caller_addr) {
void *handle;
if (SHADOWHOOK_IS_SHARED_MODE)
handle = SHADOWHOOK_CALL_PREV(sh_linker_proxy_do_dlopen_n, sh_linker_proxy_do_dlopen_n_t, name, flags,
extinfo, caller_addr);
else
handle = sh_linker_orig_do_dlopen_n(name, flags, extinfo, caller_addr);
if (NULL != handle) sh_linker_post_dlopen(sh_linker_post_dlopen_arg);
if (SHADOWHOOK_IS_SHARED_MODE) SHADOWHOOK_POP_STACK();
return handle;
}
int sh_linker_hook_dlopen(sh_linker_post_dlopen_t post_dlopen, void *post_dlopen_arg) {
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static int result = SHADOWHOOK_ERRNO_MONITOR_DLOPEN;
if (sh_linker_dlopen_hooked) return result;
pthread_mutex_lock(&lock);
if (sh_linker_dlopen_hooked) goto end;
// try hooking-dlopen only once
sh_linker_dlopen_hooked = true;
// do init for SHARED mode
if (SHADOWHOOK_IS_SHARED_MODE)
if (0 != sh_linker_init()) goto end;
// save post callback ptr before hooking
sh_linker_post_dlopen = post_dlopen;
sh_linker_post_dlopen_arg = post_dlopen_arg;
// hook for dlopen() or do_dlopen()
int (*hook)(uintptr_t, uintptr_t, uintptr_t *, size_t *, xdl_info_t *) =
SHADOWHOOK_IS_SHARED_MODE ? sh_switch_hook : sh_switch_hook_invisible;
int api_level = sh_util_get_api_level();
size_t backup_len = 0;
int r;
if (api_level < __ANDROID_API_L__) {
// get & check dlinfo
r = sh_linker_get_dlinfo_by_addr((void *)sh_linker_dlopen_addr, &sh_linker_dlopen_dlinfo, NULL, 0, NULL,
0, false);
if (SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH == r) result = SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH;
if (0 != r) goto end;
// hook
r = hook(sh_linker_dlopen_addr, (uintptr_t)sh_linker_proxy_dlopen, (uintptr_t *)&sh_linker_orig_dlopen,
&backup_len, &sh_linker_dlopen_dlinfo);
// record
sh_recorder_add_hook(r, true, sh_linker_dlopen_addr, SH_LINKER_BASENAME, "dlopen",
(uintptr_t)sh_linker_proxy_dlopen, backup_len, UINTPTR_MAX,
(uintptr_t)(__builtin_return_address(0)));
if (0 != r) goto end;
} else {
// check dlinfo
if (!sh_linker_check_arch(&sh_linker_dlopen_dlinfo)) {
result = SHADOWHOOK_ERRNO_LINKER_ARCH_MISMATCH;
goto end;
}
uintptr_t proxy;
uintptr_t *orig;
if (api_level >= __ANDROID_API_N__) {
proxy = (uintptr_t)sh_linker_proxy_do_dlopen_n;
orig = (uintptr_t *)&sh_linker_orig_do_dlopen_n;
} else {
proxy = (uintptr_t)sh_linker_proxy_do_dlopen_l;
orig = (uintptr_t *)&sh_linker_orig_do_dlopen_l;
}
// hook
pthread_mutex_lock(sh_linker_g_dl_mutex);
r = hook(sh_linker_dlopen_addr, proxy, orig, &backup_len, &sh_linker_dlopen_dlinfo);
pthread_mutex_unlock(sh_linker_g_dl_mutex);
// record
sh_recorder_add_hook(r, true, sh_linker_dlopen_addr, SH_LINKER_BASENAME,
sh_linker_dlopen_dlinfo.dli_sname, proxy, backup_len, UINTPTR_MAX,
(uintptr_t)(__builtin_return_address(0)));
if (0 != r) goto end;
}
// OK
result = 0;
end:
pthread_mutex_unlock(&lock);
SH_LOG_INFO("linker: hook dlopen %s, return: %d", 0 == result ? "OK" : "FAILED", result);
return result;
}
int sh_linker_get_dlinfo_by_addr(void *addr, xdl_info_t *dlinfo, char *lib_name, size_t lib_name_sz,
char *sym_name, size_t sym_name_sz, bool ignore_symbol_check) {
// dladdr()
bool crashed = false;
void *dlcache = NULL;
int r = 0;
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
r = xdl_addr((void *)addr, dlinfo, &dlcache);
} else {
SH_SIG_TRY(SIGSEGV, SIGBUS) {
r = xdl_addr((void *)addr, dlinfo, &dlcache);
}
SH_SIG_CATCH() {
crashed = true;
}
SH_SIG_EXIT
}
SH_LOG_INFO("task: get dlinfo by target addr: target_addr %p, sym_name %s, sym_sz %zu, load_bias %" PRIxPTR
", pathname %s",
addr, NULL == dlinfo->dli_sname ? "(NULL)" : dlinfo->dli_sname, dlinfo->dli_ssize,
(uintptr_t)dlinfo->dli_fbase, NULL == dlinfo->dli_fname ? "(NULL)" : dlinfo->dli_fname);
// check error
if (crashed) {
r = SHADOWHOOK_ERRNO_HOOK_DLADDR_CRASH;
goto end;
}
if (0 == r || NULL == dlinfo->dli_fname) {
r = SHADOWHOOK_ERRNO_HOOK_DLINFO;
goto end;
}
if (!sh_linker_check_arch(dlinfo)) {
r = SHADOWHOOK_ERRNO_ELF_ARCH_MISMATCH;
goto end;
}
if (NULL == dlinfo->dli_sname) {
if (ignore_symbol_check) {
dlinfo->dli_saddr = addr;
dlinfo->dli_sname = "unknown";
dlinfo->dli_ssize = 1024; // big enough
} else {
const char *matched_dlfcn_name = NULL;
if (NULL == (matched_dlfcn_name = sh_linker_match_dlfcn((uintptr_t)addr))) {
r = SHADOWHOOK_ERRNO_HOOK_DLINFO;
goto end;
} else {
dlinfo->dli_saddr = addr;
dlinfo->dli_sname = matched_dlfcn_name;
dlinfo->dli_ssize = 4; // safe length, only relative jumps are allowed
SH_LOG_INFO("task: match dlfcn, target_addr %p, sym_name %s", addr, matched_dlfcn_name);
}
}
}
if (0 == dlinfo->dli_ssize) {
r = SHADOWHOOK_ERRNO_HOOK_SYMSZ;
goto end;
}
if (NULL != lib_name) strlcpy(lib_name, dlinfo->dli_fname, lib_name_sz);
if (NULL != sym_name) strlcpy(sym_name, dlinfo->dli_sname, sym_name_sz);
r = 0;
end:
xdl_addr_clean(&dlcache);
return r;
}
int sh_linker_get_dlinfo_by_sym_name(const char *lib_name, const char *sym_name, xdl_info_t *dlinfo,
char *real_lib_name, size_t real_lib_name_sz) {
// open library
bool crashed = false;
void *handle = NULL;
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
handle = xdl_open(lib_name, XDL_DEFAULT);
} else {
SH_SIG_TRY(SIGSEGV, SIGBUS) {
handle = xdl_open(lib_name, XDL_DEFAULT);
}
SH_SIG_CATCH() {
crashed = true;
}
SH_SIG_EXIT
}
if (crashed) return SHADOWHOOK_ERRNO_HOOK_DLOPEN_CRASH;
if (NULL == handle) return SHADOWHOOK_ERRNO_PENDING;
// get dlinfo
xdl_info(handle, XDL_DI_DLINFO, (void *)dlinfo);
// check error
if (!sh_linker_check_arch(dlinfo)) {
xdl_close(handle);
return SHADOWHOOK_ERRNO_ELF_ARCH_MISMATCH;
}
// lookup symbol address
crashed = false;
void *addr = NULL;
size_t sym_size = 0;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
// do xdl_sym() or xdl_dsym() in an dlclosed-ELF will cause a crash
addr = xdl_sym(handle, sym_name, &sym_size);
if (NULL == addr) addr = xdl_dsym(handle, sym_name, &sym_size);
}
SH_SIG_CATCH() {
crashed = true;
}
SH_SIG_EXIT
// close library
xdl_close(handle);
if (crashed) return SHADOWHOOK_ERRNO_HOOK_DLSYM_CRASH;
if (NULL == addr) return SHADOWHOOK_ERRNO_HOOK_DLSYM;
dlinfo->dli_fname = lib_name;
dlinfo->dli_sname = sym_name;
dlinfo->dli_saddr = addr;
dlinfo->dli_ssize = sym_size;
if (NULL != real_lib_name) strlcpy(real_lib_name, dlinfo->dli_fname, real_lib_name_sz);
return 0;
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "xdl.h"
int sh_linker_init(void);
const char *sh_linker_match_dlfcn(uintptr_t target_addr);
bool sh_linker_need_to_hook_dlopen(uintptr_t target_addr);
typedef void (*sh_linker_post_dlopen_t)(void *arg);
int sh_linker_hook_dlopen(sh_linker_post_dlopen_t post_dlopen, void *post_dlopen_arg);
int sh_linker_get_dlinfo_by_addr(void *addr, xdl_info_t *dlinfo, char *lib_name, size_t lib_name_sz,
char *sym_name, size_t sym_name_sz, bool ignore_symbol_check);
int sh_linker_get_dlinfo_by_sym_name(const char *lib_name, const char *sym_name, xdl_info_t *dlinfo,
char *real_lib_name, size_t real_lib_name_sz);

View File

@ -0,0 +1,517 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_recorder.h"
#include "sh_config.h"
#ifdef SH_CONFIG_OPERATION_RECORDS
#include <inttypes.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/time.h>
#include "sh_sig.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "xdl.h"
#define SH_RECORDER_OP_HOOK_SYM_ADDR 0
#define SH_RECORDER_OP_HOOK_SYM_NAME 1
#define SH_RECORDER_OP_UNHOOK 2
#define SH_RECORDER_LIB_NAME_MAX 512
#define SH_RECORDER_SYM_NAME_MAX 1024
#define SH_RECORDER_STRINGS_BUF_EXPAND_STEP (1024 * 16)
#define SH_RECORDER_STRINGS_BUF_MAX (1024 * 128)
#define SH_RECORDER_RECORDS_BUF_EXPAND_STEP (1024 * 32)
#define SH_RECORDER_RECORDS_BUF_MAX (1024 * 384)
#define SH_RECORDER_OUTPUT_BUF_EXPAND_STEP (1024 * 128)
#define SH_RECORDER_OUTPUT_BUF_MAX (1024 * 1024)
static bool sh_recorder_recordable = false;
bool sh_recorder_get_recordable(void) {
return sh_recorder_recordable;
}
void sh_recorder_set_recordable(bool recordable) {
sh_recorder_recordable = recordable;
}
typedef struct {
void *ptr;
size_t cap;
size_t sz;
pthread_mutex_t lock;
} sh_recorder_buf_t;
static int sh_recorder_buf_append(sh_recorder_buf_t *buf, size_t step, size_t max, const void *header,
size_t header_sz, const void *body, size_t body_sz) {
size_t needs = (header_sz + (NULL != body ? body_sz : 0));
if (needs > step) return -1;
if (buf->cap - buf->sz < needs) {
size_t new_cap = buf->cap + step;
if (new_cap > max) return -1;
void *new_ptr = realloc(buf->ptr, new_cap);
if (NULL == new_ptr) return -1;
buf->ptr = new_ptr;
buf->cap = new_cap;
}
memcpy((void *)((uintptr_t)buf->ptr + buf->sz), header, header_sz);
if (NULL != body) memcpy((void *)((uintptr_t)buf->ptr + buf->sz + header_sz), body, body_sz);
buf->sz += needs;
return 0;
}
static void sh_recorder_buf_free(sh_recorder_buf_t *buf) {
if (NULL != buf->ptr) {
free(buf->ptr);
buf->ptr = NULL;
}
}
static sh_recorder_buf_t sh_recorder_strings = {NULL, 0, 0, PTHREAD_MUTEX_INITIALIZER};
static sh_recorder_buf_t sh_recorder_records = {NULL, 0, 0, PTHREAD_MUTEX_INITIALIZER};
static bool sh_recorder_error = false;
typedef struct {
uint16_t str_len; // body length, in order to speed up the search
} __attribute__((packed)) sh_recorder_str_header_t;
// +body: string, including the terminating null byte ('\0')
typedef struct {
uint64_t op : 8;
uint64_t error_number : 8;
uint64_t ts_ms : 48;
uintptr_t stub;
uint16_t caller_lib_name_idx;
uint8_t backup_len;
uint16_t lib_name_idx;
uint16_t sym_name_idx;
uintptr_t sym_addr;
uintptr_t new_addr;
} __attribute__((packed)) sh_recorder_record_hook_header_t;
// no body
typedef struct {
uint64_t op : 8;
uint64_t error_number : 8;
uint64_t ts_ms : 48;
uintptr_t stub;
uint16_t caller_lib_name_idx;
} __attribute__((packed)) sh_recorder_record_unhook_header_t;
// no body
static int sh_recorder_add_str(const char *str, size_t str_len, uint16_t *str_idx) {
uint16_t idx = 0;
bool ok = false;
pthread_mutex_lock(&sh_recorder_strings.lock);
// find in existing strings
size_t i = 0;
while (i < sh_recorder_strings.sz) {
sh_recorder_str_header_t *header = (sh_recorder_str_header_t *)((uintptr_t)sh_recorder_strings.ptr + i);
if (header->str_len == str_len) {
void *tmp = (void *)((uintptr_t)sh_recorder_strings.ptr + i + sizeof(header->str_len));
if (0 == memcmp(tmp, str, str_len)) {
*str_idx = idx;
ok = true;
break; // OK
}
}
i += (sizeof(sh_recorder_str_header_t) + header->str_len + 1);
idx++;
if (idx == UINT16_MAX) break; // failed
}
// insert a new string
if (!ok && idx < UINT16_MAX) {
// append new string
sh_recorder_str_header_t header = {(uint16_t)str_len};
if (0 == sh_recorder_buf_append(&sh_recorder_strings, SH_RECORDER_STRINGS_BUF_EXPAND_STEP,
SH_RECORDER_STRINGS_BUF_MAX, &header, sizeof(header), str, str_len + 1)) {
*str_idx = idx;
ok = true; // OK
}
}
pthread_mutex_unlock(&sh_recorder_strings.lock);
return ok ? 0 : -1;
}
static char *sh_recorder_find_str(uint16_t idx) {
uint16_t cur_idx = 0;
size_t i = 0;
while (i < sh_recorder_strings.sz && cur_idx < idx) {
sh_recorder_str_header_t *header = (sh_recorder_str_header_t *)((uintptr_t)sh_recorder_strings.ptr + i);
i += (sizeof(sh_recorder_str_header_t) + header->str_len + 1);
cur_idx++;
}
if (cur_idx != idx) return "error";
sh_recorder_str_header_t *header = (sh_recorder_str_header_t *)((uintptr_t)sh_recorder_strings.ptr + i);
return (char *)((uintptr_t)header + sizeof(sh_recorder_str_header_t));
}
static long sh_recorder_tz = LONG_MAX;
static uint64_t sh_recorder_get_timestamp_ms(void) {
struct timeval tv;
gettimeofday(&tv, NULL);
if (LONG_MAX == sh_recorder_tz) {
// localtime_r() will call getenv() without lock protection,
// and will crash when encountering concurrent setenv() calls.
// We really encountered.
sh_recorder_tz = 0;
// struct tm tm;
// if (NULL != localtime_r((time_t *)(&(tv.tv_sec)), &tm)) sh_recorder_tz = tm.tm_gmtoff;
}
return (uint64_t)tv.tv_sec * 1000 + (uint64_t)tv.tv_usec / 1000;
}
static size_t sh_recorder_format_timestamp_ms(uint64_t ts_ms, char *buf, size_t buf_len) {
time_t sec = (time_t)(ts_ms / 1000);
time_t msec = (time_t)(ts_ms % 1000);
struct tm tm;
sh_util_localtime_r(&sec, sh_recorder_tz, &tm);
return sh_util_snprintf(buf, buf_len, "%04d-%02d-%02dT%02d:%02d:%02d.%03ld%c%02ld:%02ld,",
tm.tm_year + 1900, tm.tm_mon + 1, tm.tm_mday, tm.tm_hour, tm.tm_min, tm.tm_sec,
msec, sh_recorder_tz < 0 ? '-' : '+', labs(sh_recorder_tz / 3600),
labs(sh_recorder_tz % 3600));
}
static const char *sh_recorder_get_base_name(const char *lib_name) {
const char *p = strrchr(lib_name, '/');
if (NULL != p && '\0' != *(p + 1))
return p + 1;
else
return lib_name;
}
static int sh_recorder_get_base_name_by_addr_iterator(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
uintptr_t addr = *pkg++;
char *base_name = (char *)*pkg++;
size_t base_name_sz = (size_t)*pkg;
for (size_t i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
if (PT_LOAD != phdr->p_type) continue;
if (addr < (uintptr_t)(info->dlpi_addr + phdr->p_vaddr) ||
addr >= (uintptr_t)(info->dlpi_addr + phdr->p_vaddr + phdr->p_memsz))
continue;
// get lib_name from path_name
const char *p;
if (NULL == info->dlpi_name || '\0' == info->dlpi_name[0])
p = "unknown";
else {
p = strrchr(info->dlpi_name, '/');
if (NULL == p || '\0' == *(p + 1))
p = info->dlpi_name;
else
p++;
}
strlcpy(base_name, p, base_name_sz);
return 1; // OK
}
return 0; // continue
}
static void sh_recorder_get_base_name_by_addr(uintptr_t addr, char *base_name, size_t base_name_sz) {
base_name[0] = '\0';
uintptr_t pkg[3] = {addr, (uintptr_t)base_name, (uintptr_t)base_name_sz};
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
xdl_iterate_phdr(sh_recorder_get_base_name_by_addr_iterator, pkg, XDL_DEFAULT);
} else {
SH_SIG_TRY(SIGSEGV, SIGBUS) {
xdl_iterate_phdr(sh_recorder_get_base_name_by_addr_iterator, pkg, XDL_DEFAULT);
}
SH_SIG_EXIT
}
if ('\0' == base_name[0]) strlcpy(base_name, "unknown", base_name_sz);
}
int sh_recorder_add_hook(int error_number, bool is_hook_sym_addr, uintptr_t sym_addr, const char *lib_name,
const char *sym_name, uintptr_t new_addr, size_t backup_len, uintptr_t stub,
uintptr_t caller_addr) {
if (!sh_recorder_recordable) return 0;
if (sh_recorder_error) return -1;
// lib_name
if (NULL == lib_name) return -1;
lib_name = sh_recorder_get_base_name(lib_name);
size_t lib_name_len = strlen(lib_name);
if (0 == lib_name_len || lib_name_len > SH_RECORDER_LIB_NAME_MAX) return -1;
// sym_name
if (NULL == sym_name) return -1;
size_t sym_name_len = strlen(sym_name);
if (0 == sym_name_len || sym_name_len > SH_RECORDER_SYM_NAME_MAX) return -1;
// caller_lib_name
char caller_lib_name[SH_RECORDER_LIB_NAME_MAX];
sh_recorder_get_base_name_by_addr(caller_addr, caller_lib_name, sizeof(caller_lib_name));
size_t caller_lib_name_len = strlen(caller_lib_name);
// add strings to strings-pool
uint16_t lib_name_idx, sym_name_idx, caller_lib_name_idx;
if (0 != sh_recorder_add_str(lib_name, lib_name_len, &lib_name_idx)) goto err;
if (0 != sh_recorder_add_str(sym_name, sym_name_len, &sym_name_idx)) goto err;
if (0 != sh_recorder_add_str(caller_lib_name, caller_lib_name_len, &caller_lib_name_idx)) goto err;
// append new hook record
sh_recorder_record_hook_header_t header = {
is_hook_sym_addr ? SH_RECORDER_OP_HOOK_SYM_ADDR : SH_RECORDER_OP_HOOK_SYM_NAME,
(uint8_t)error_number,
sh_recorder_get_timestamp_ms(),
stub,
caller_lib_name_idx,
(uint8_t)backup_len,
lib_name_idx,
sym_name_idx,
sym_addr,
new_addr};
pthread_mutex_lock(&sh_recorder_records.lock);
int r = sh_recorder_buf_append(&sh_recorder_records, SH_RECORDER_RECORDS_BUF_EXPAND_STEP,
SH_RECORDER_RECORDS_BUF_MAX, &header, sizeof(header), NULL, 0);
pthread_mutex_unlock(&sh_recorder_records.lock);
if (0 != r) goto err;
return 0;
err:
sh_recorder_error = true;
return -1;
}
int sh_recorder_add_unhook(int error_number, uintptr_t stub, uintptr_t caller_addr) {
if (!sh_recorder_recordable) return 0;
if (sh_recorder_error) return -1;
char caller_lib_name[SH_RECORDER_LIB_NAME_MAX];
sh_recorder_get_base_name_by_addr(caller_addr, caller_lib_name, sizeof(caller_lib_name));
size_t caller_lib_name_len = strlen(caller_lib_name);
uint16_t caller_lib_name_idx;
if (0 != sh_recorder_add_str(caller_lib_name, caller_lib_name_len, &caller_lib_name_idx)) goto err;
sh_recorder_record_unhook_header_t header = {SH_RECORDER_OP_UNHOOK, (uint8_t)error_number,
sh_recorder_get_timestamp_ms(), stub, caller_lib_name_idx};
pthread_mutex_lock(&sh_recorder_records.lock);
int r = sh_recorder_buf_append(&sh_recorder_records, SH_RECORDER_RECORDS_BUF_EXPAND_STEP,
SH_RECORDER_RECORDS_BUF_MAX, &header, sizeof(header), NULL, 0);
pthread_mutex_unlock(&sh_recorder_records.lock);
if (0 != r) goto err;
return 0;
err:
sh_recorder_error = true;
return -1;
}
static const char *sh_recorder_get_op_name(uint8_t op) {
switch (op) {
case SH_RECORDER_OP_HOOK_SYM_ADDR:
return "hook_sym_addr";
case SH_RECORDER_OP_HOOK_SYM_NAME:
return "hook_sym_name";
case SH_RECORDER_OP_UNHOOK:
return "unhook";
default:
return "error";
}
}
static void sh_recorder_output(char **str, int fd, uint32_t item_flags) {
if (NULL == sh_recorder_records.ptr || 0 == sh_recorder_records.sz) return;
sh_recorder_buf_t output = {NULL, 0, 0, PTHREAD_MUTEX_INITIALIZER};
pthread_mutex_lock(&sh_recorder_records.lock);
pthread_mutex_lock(&sh_recorder_strings.lock);
char line[SH_RECORDER_LIB_NAME_MAX * 2 + SH_RECORDER_SYM_NAME_MAX + 256];
size_t line_sz;
size_t i = 0;
while (i < sh_recorder_records.sz) {
line_sz = 0;
sh_recorder_record_hook_header_t *header =
(sh_recorder_record_hook_header_t *)((uintptr_t)sh_recorder_records.ptr + i);
if (item_flags & SHADOWHOOK_RECORD_ITEM_TIMESTAMP)
line_sz += sh_recorder_format_timestamp_ms(header->ts_ms, line + line_sz, sizeof(line) - line_sz);
if (item_flags & SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
sh_recorder_find_str(header->caller_lib_name_idx));
if (item_flags & SHADOWHOOK_RECORD_ITEM_OP)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
sh_recorder_get_op_name(header->op));
if ((item_flags & SHADOWHOOK_RECORD_ITEM_LIB_NAME) && header->op != SH_RECORDER_OP_UNHOOK)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
sh_recorder_find_str(header->lib_name_idx));
if ((item_flags & SHADOWHOOK_RECORD_ITEM_SYM_NAME) && header->op != SH_RECORDER_OP_UNHOOK)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%s,",
sh_recorder_find_str(header->sym_name_idx));
if ((item_flags & SHADOWHOOK_RECORD_ITEM_SYM_ADDR) && header->op != SH_RECORDER_OP_UNHOOK)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIxPTR ",", header->sym_addr);
if ((item_flags & SHADOWHOOK_RECORD_ITEM_NEW_ADDR) && header->op != SH_RECORDER_OP_UNHOOK)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIxPTR ",", header->new_addr);
if ((item_flags & SHADOWHOOK_RECORD_ITEM_BACKUP_LEN) && header->op != SH_RECORDER_OP_UNHOOK)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIu8 ",", header->backup_len);
if (item_flags & SHADOWHOOK_RECORD_ITEM_ERRNO)
line_sz +=
sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIu8 ",", header->error_number);
if (item_flags & SHADOWHOOK_RECORD_ITEM_STUB)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "%" PRIxPTR ",", header->stub);
line[line_sz - 1] = '\n';
if (NULL != str) {
// append to string
if (0 != sh_recorder_buf_append(&output, SH_RECORDER_OUTPUT_BUF_EXPAND_STEP, SH_RECORDER_OUTPUT_BUF_MAX,
line, line_sz, NULL, 0)) {
sh_recorder_buf_free(&output);
break; // failed
}
} else {
// write to FD
if (0 != sh_util_write(fd, line, line_sz)) break; // failed
}
i += (SH_RECORDER_OP_UNHOOK == header->op ? sizeof(sh_recorder_record_unhook_header_t)
: sizeof(sh_recorder_record_hook_header_t));
}
pthread_mutex_unlock(&sh_recorder_strings.lock);
pthread_mutex_unlock(&sh_recorder_records.lock);
// error message
if (sh_recorder_error) {
line_sz = 0;
if (item_flags & SHADOWHOOK_RECORD_ITEM_TIMESTAMP)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "9999-99-99T00:00:00.000+00:00,");
if (item_flags & SHADOWHOOK_RECORD_ITEM_CALLER_LIB_NAME)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "error,");
if (item_flags & SHADOWHOOK_RECORD_ITEM_OP)
line_sz += sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "error,");
if (0 == line_sz) line_sz = sh_util_snprintf(line + line_sz, sizeof(line) - line_sz, "error,");
line[line_sz - 1] = '\n';
if (NULL != str) {
// append to string
if (0 != sh_recorder_buf_append(&output, SH_RECORDER_OUTPUT_BUF_EXPAND_STEP, SH_RECORDER_OUTPUT_BUF_MAX,
line, line_sz, NULL, 0)) {
sh_recorder_buf_free(&output);
return; // failed
}
} else {
// write to FD
if (0 != sh_util_write(fd, line, line_sz)) return; // failed
}
}
// return string
if (NULL != str) {
if (0 != sh_recorder_buf_append(&output, SH_RECORDER_OUTPUT_BUF_EXPAND_STEP, SH_RECORDER_OUTPUT_BUF_MAX,
"", 1, NULL, 0)) {
sh_recorder_buf_free(&output);
return; // failed
}
*str = output.ptr;
}
}
char *sh_recorder_get(uint32_t item_flags) {
if (!sh_recorder_recordable) return NULL;
if (0 == (item_flags & SHADOWHOOK_RECORD_ITEM_ALL)) return NULL;
char *str = NULL;
sh_recorder_output(&str, -1, item_flags);
return str;
}
void sh_recorder_dump(int fd, uint32_t item_flags) {
if (!sh_recorder_recordable) return;
if (0 == (item_flags & SHADOWHOOK_RECORD_ITEM_ALL)) return;
if (fd < 0) return;
sh_recorder_output(NULL, fd, item_flags);
}
#else
bool sh_recorder_get_recordable(void) {
return false;
}
void sh_recorder_set_recordable(bool recordable) {
(void)recordable;
}
int sh_recorder_add_hook(int error_number, bool is_hook_sym_addr, uintptr_t sym_addr, const char *lib_name,
const char *sym_name, uintptr_t new_addr, size_t backup_len, uintptr_t stub,
uintptr_t caller_addr) {
(void)error_number, (void)is_hook_sym_addr, (void)sym_addr, (void)lib_name, (void)sym_name, (void)new_addr,
(void)backup_len, (void)stub, (void)caller_addr;
return 0;
}
int sh_recorder_add_unhook(int error_number, uintptr_t stub, uintptr_t caller_addr) {
(void)error_number, (void)stub, (void)caller_addr;
return 0;
}
char *sh_recorder_get(uint32_t item_flags) {
(void)item_flags;
return NULL;
}
void sh_recorder_dump(int fd, uint32_t item_flags) {
(void)fd, (void)item_flags;
}
#endif

View File

@ -0,0 +1,37 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stdint.h>
bool sh_recorder_get_recordable(void);
void sh_recorder_set_recordable(bool recordable);
int sh_recorder_add_hook(int error_number, bool is_hook_sym_addr, uintptr_t sym_addr, const char *lib_name,
const char *sym_name, uintptr_t new_addr, size_t backup_len, uintptr_t stub,
uintptr_t caller_addr);
int sh_recorder_add_unhook(int error_number, uintptr_t stub, uintptr_t caller_addr);
char *sh_recorder_get(uint32_t item_flags);
void sh_recorder_dump(int fd, uint32_t item_flags);

View File

@ -0,0 +1,133 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_safe.h"
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
#include "sh_util.h"
#include "xdl.h"
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wreserved-id-macro"
#pragma clang diagnostic ignored "-Wvariadic-macros"
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#pragma clang diagnostic ignored "-Wsign-conversion"
#pragma clang diagnostic ignored "-Wpacked"
#pragma clang diagnostic ignored "-Wshorten-64-to-32"
#include "linux_syscall_support.h"
#pragma clang diagnostic pop
#define SH_SAFE_IDX_PTHREAD_GETSPECIFIC 0
#define SH_SAFE_IDX_PTHREAD_SETSPECIFIC 1
#define SH_SAFE_IDX_ABORT 2
#define SH_SAFE_IDX_SZ 3
typedef struct {
uintptr_t target_addr;
uintptr_t orig_addr;
} sh_safe_addr_t;
static sh_safe_addr_t sh_safe_addrs[SH_SAFE_IDX_SZ];
static int sh_safe_api_level;
static int sh_safe_init_func(void *handle, const char *symbol, size_t idx) {
sh_safe_addrs[idx].target_addr = (uintptr_t)(xdl_sym(handle, symbol, NULL));
if (__predict_false(0 == sh_safe_addrs[idx].target_addr)) return -1;
sh_safe_addrs[idx].orig_addr = 0;
return 0;
}
int sh_safe_init(void) {
sh_safe_api_level = sh_util_get_api_level();
void *handle = xdl_open("libc.so", XDL_DEFAULT);
if (NULL == handle) return -1;
int r = -1;
if (__predict_false(0 != sh_safe_init_func(handle, "pthread_getspecific", SH_SAFE_IDX_PTHREAD_GETSPECIFIC)))
goto end;
if (__predict_false(0 != sh_safe_init_func(handle, "pthread_setspecific", SH_SAFE_IDX_PTHREAD_SETSPECIFIC)))
goto end;
if (__predict_false(0 != sh_safe_init_func(handle, "abort", SH_SAFE_IDX_ABORT))) goto end;
r = 0;
end:
xdl_close(handle);
return r;
}
uintptr_t *sh_safe_get_orig_addr_addr(uintptr_t target_addr) {
for (size_t i = 0; i < SH_SAFE_IDX_SZ; i++) {
if (sh_safe_addrs[i].target_addr == target_addr) {
return &sh_safe_addrs[i].orig_addr;
}
}
return NULL;
}
static uintptr_t sh_safe_get_orig_addr(size_t idx) {
sh_safe_addr_t *addr = &sh_safe_addrs[idx];
return 0 != addr->orig_addr ? addr->orig_addr : addr->target_addr;
}
void *sh_safe_pthread_getspecific(pthread_key_t key) {
uintptr_t addr = sh_safe_get_orig_addr(SH_SAFE_IDX_PTHREAD_GETSPECIFIC);
return ((void *(*)(pthread_key_t))addr)(key);
}
int sh_safe_pthread_setspecific(pthread_key_t key, const void *value) {
if (sh_safe_api_level >= __ANDROID_API_M__) {
uintptr_t addr = sh_safe_get_orig_addr(SH_SAFE_IDX_PTHREAD_SETSPECIFIC);
return ((int (*)(pthread_key_t, const void *))addr)(key, value);
} else {
// Before Android M, pthread_setspecific() will call pthread_mutex_lock() and
// pthread_mutex_unlock(). So if we use pthread_setspecific() in hub's trampo,
// we will NOT be able to hook pthread_mutex_lock() and pthread_mutex_unlock().
void **tls;
#if defined(__aarch64__)
__asm__("mrs %0, tpidr_el0" : "=r"(tls));
#elif defined(__arm__)
__asm__("mrc p15, 0, %0, c13, c0, 3" : "=r"(tls));
#endif
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wcast-qual"
tls[key] = (void *)value;
#pragma clang diagnostic pop
return 0;
}
}
void sh_safe_abort(void) {
uintptr_t addr = sh_safe_get_orig_addr(SH_SAFE_IDX_ABORT);
((void (*)(void))addr)();
}
void *sh_safe_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset) {
return sys_mmap(addr, length, prot, flags, fd, offset);
}
int sh_safe_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4,
unsigned long arg5) {
return sys_prctl(option, arg2, arg3, arg4, arg5);
}

View File

@ -0,0 +1,37 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
int sh_safe_init(void);
uintptr_t *sh_safe_get_orig_addr_addr(uintptr_t target_addr);
void *sh_safe_pthread_getspecific(pthread_key_t key);
int sh_safe_pthread_setspecific(pthread_key_t key, const void *value);
void sh_safe_abort(void);
void *sh_safe_mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
int sh_safe_prctl(int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5);

View File

@ -0,0 +1,343 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_switch.h"
#include <inttypes.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "sh_config.h"
#include "sh_errno.h"
#include "sh_hub.h"
#include "sh_inst.h"
#include "sh_linker.h"
#include "sh_log.h"
#include "sh_safe.h"
#include "sh_sig.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "tree.h"
#include "xdl.h"
// switch
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct sh_switch {
sh_inst_t inst; // align 16
uintptr_t target_addr;
sh_hub_t *hub;
RB_ENTRY(sh_switch) link;
} sh_switch_t;
#pragma clang diagnostic pop
// switch tree
static __inline__ int sh_switch_cmp(sh_switch_t *a, sh_switch_t *b) {
if (a->target_addr == b->target_addr)
return 0;
else
return a->target_addr > b->target_addr ? 1 : -1;
}
typedef RB_HEAD(sh_switch_tree, sh_switch) sh_switch_tree_t;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wunused-function"
RB_GENERATE_STATIC(sh_switch_tree, sh_switch, link, sh_switch_cmp)
#pragma clang diagnostic pop
// switch tree object
static sh_switch_tree_t sh_switches = RB_INITIALIZER(&sh_switches);
static pthread_rwlock_t sh_switches_lock = PTHREAD_RWLOCK_INITIALIZER;
static sh_switch_t *sh_switch_find(uintptr_t target_addr) {
sh_switch_t key = {.target_addr = target_addr};
pthread_rwlock_rdlock(&sh_switches_lock);
sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
pthread_rwlock_unlock(&sh_switches_lock);
return self;
}
static int sh_switch_create(sh_switch_t **self, uintptr_t target_addr, uintptr_t *hub_trampo) {
*self = memalign(16, sizeof(sh_switch_t));
if (NULL == *self) return SHADOWHOOK_ERRNO_OOM;
memset(&(*self)->inst, 0, sizeof((*self)->inst));
(*self)->target_addr = target_addr;
(*self)->hub = NULL;
if (NULL != hub_trampo) {
if (NULL == ((*self)->hub = sh_hub_create(target_addr, hub_trampo))) {
free(self);
return SHADOWHOOK_ERRNO_HUB_CREAT;
}
}
return 0;
}
static void sh_switch_destroy(sh_switch_t *self, bool hub_with_delay) {
if (NULL != self->hub) sh_hub_destroy(self->hub, hub_with_delay);
free(self);
}
static void sh_switch_dump_enter(sh_switch_t *self) {
#ifdef SH_CONFIG_DEBUG
char buf[1024];
size_t len = 0;
size_t zero = 0;
for (size_t i = 0; i < 128; i++) {
uint16_t inst = ((uint16_t *)(self->inst.enter_addr))[i];
zero = (0 == inst ? zero + 1 : 0);
if (zero > 4) break;
len += (size_t)snprintf(buf + len, sizeof(buf) - len, "%04" PRIx16 " ", inst);
}
SH_LOG_DEBUG("switch: enter < %s>", buf);
#else
(void)self;
#endif
}
static int sh_switch_hook_unique(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
size_t *backup_len, xdl_info_t *dlinfo) {
sh_switch_t *self = sh_switch_find(target_addr);
if (NULL != self) return SHADOWHOOK_ERRNO_HOOK_DUP;
// alloc new switch
int r;
if (0 != (r = sh_switch_create(&self, target_addr, NULL))) return r;
sh_switch_t *useless = NULL;
pthread_rwlock_wrlock(&sh_switches_lock); // SYNC - start
// insert new switch to switch-tree
if (NULL != RB_INSERT(sh_switch_tree, &sh_switches, self)) {
useless = self;
r = SHADOWHOOK_ERRNO_HOOK_DUP;
goto end;
}
// do hook
if (0 != (r = sh_inst_hook(&self->inst, target_addr, dlinfo, new_addr, orig_addr, NULL))) {
RB_REMOVE(sh_switch_tree, &sh_switches, self);
useless = self;
goto end;
}
*backup_len = self->inst.backup_len;
sh_switch_dump_enter(self);
end:
pthread_rwlock_unlock(&sh_switches_lock); // SYNC - end
if (NULL != useless) sh_switch_destroy(useless, false);
return r;
}
static int sh_switch_hook_shared(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
size_t *backup_len, xdl_info_t *dlinfo) {
int r;
pthread_rwlock_rdlock(&sh_switches_lock); // SYNC(read) - start
sh_switch_t key = {.target_addr = target_addr};
sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
if (NULL != self) // already exists
{
// add an new proxy to hub
if (NULL != orig_addr) *orig_addr = sh_hub_get_orig_addr(self->hub);
r = sh_hub_add_proxy(self->hub, new_addr);
pthread_rwlock_unlock(&sh_switches_lock); // SYNC(read) - end
*backup_len = self->inst.backup_len;
return r;
}
pthread_rwlock_unlock(&sh_switches_lock); // SYNC(read) - end
// first hook for this target_addr
// alloc new switch
uintptr_t hub_trampo;
if (0 != (r = sh_switch_create(&self, target_addr, &hub_trampo))) return r;
sh_switch_t *useless = NULL;
pthread_rwlock_wrlock(&sh_switches_lock); // SYNC - start
// insert new switch to switch-tree
sh_switch_t *exists;
if (NULL != (exists = RB_INSERT(sh_switch_tree, &sh_switches, self))) {
// already exists
useless = self;
if (NULL != orig_addr) *orig_addr = sh_hub_get_orig_addr(exists->hub);
r = sh_hub_add_proxy(exists->hub, new_addr);
*backup_len = exists->inst.backup_len;
} else {
// do hook
uintptr_t *safe_orig_addr_addr = sh_safe_get_orig_addr_addr(target_addr);
if (0 != (r = sh_inst_hook(&self->inst, target_addr, dlinfo, hub_trampo,
sh_hub_get_orig_addr_addr(self->hub), safe_orig_addr_addr))) {
RB_REMOVE(sh_switch_tree, &sh_switches, self);
useless = self;
goto end;
}
*backup_len = self->inst.backup_len;
sh_switch_dump_enter(self);
// return original-address
if (NULL != orig_addr) *orig_addr = sh_hub_get_orig_addr(self->hub);
// add proxy to hub
if (0 != (r = sh_hub_add_proxy(self->hub, new_addr))) {
sh_inst_unhook(&self->inst, target_addr);
*backup_len = 0;
RB_REMOVE(sh_switch_tree, &sh_switches, self);
useless = self;
goto end;
}
}
end:
pthread_rwlock_unlock(&sh_switches_lock); // SYNC - end
if (NULL != useless) sh_switch_destroy(useless, false);
return r;
}
int sh_switch_hook(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr, size_t *backup_len,
xdl_info_t *dlinfo) {
int r;
if (SHADOWHOOK_IS_UNIQUE_MODE)
r = sh_switch_hook_unique(target_addr, new_addr, orig_addr, backup_len, dlinfo);
else
r = sh_switch_hook_shared(target_addr, new_addr, orig_addr, backup_len, dlinfo);
if (0 == r)
SH_LOG_INFO("switch: hook in %s mode OK: target_addr %" PRIxPTR ", new_addr %" PRIxPTR,
SHADOWHOOK_IS_UNIQUE_MODE ? "UNIQUE" : "SHARED", target_addr, new_addr);
return r;
}
static int sh_switch_hook_unique_invisible(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
size_t *backup_len, xdl_info_t *dlinfo) {
pthread_rwlock_wrlock(&sh_switches_lock); // SYNC - start
// do hook
sh_inst_t inst;
int r = sh_inst_hook(&inst, target_addr, dlinfo, new_addr, orig_addr, NULL);
pthread_rwlock_unlock(&sh_switches_lock); // SYNC - end
*backup_len = inst.backup_len;
return r;
}
int sh_switch_hook_invisible(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
size_t *backup_len, xdl_info_t *dlinfo) {
int r;
if (SHADOWHOOK_IS_UNIQUE_MODE)
r = sh_switch_hook_unique_invisible(target_addr, new_addr, orig_addr, backup_len, dlinfo);
else
r = sh_switch_hook_shared(target_addr, new_addr, orig_addr, backup_len, dlinfo);
if (0 == r)
SH_LOG_INFO("switch: hook(invisible) in %s mode OK: target_addr %" PRIxPTR ", new_addr %" PRIxPTR,
SHADOWHOOK_IS_UNIQUE_MODE ? "UNIQUE" : "SHARED", target_addr, new_addr);
return r;
}
static int sh_switch_unhook_unique(uintptr_t target_addr) {
int r;
sh_switch_t *useless = NULL;
pthread_rwlock_wrlock(&sh_switches_lock); // SYNC - start
sh_switch_t key = {.target_addr = target_addr};
sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
if (NULL == self) {
r = SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND;
goto end;
}
r = sh_inst_unhook(&self->inst, target_addr);
RB_REMOVE(sh_switch_tree, &sh_switches, self);
useless = self;
end:
pthread_rwlock_unlock(&sh_switches_lock); // SYNC - end
if (NULL != useless) sh_switch_destroy(useless, false);
return r;
}
static int sh_switch_unhook_shared(uintptr_t target_addr, uintptr_t new_addr) {
int r;
sh_switch_t *useless = NULL;
pthread_rwlock_wrlock(&sh_switches_lock); // SYNC - start
sh_switch_t key = {.target_addr = target_addr};
sh_switch_t *self = RB_FIND(sh_switch_tree, &sh_switches, &key);
if (NULL == self) {
r = SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND;
goto end;
}
// delete proxy in hub
bool have_enabled_proxy;
if (0 != sh_hub_del_proxy(self->hub, new_addr, &have_enabled_proxy)) {
r = SHADOWHOOK_ERRNO_UNHOOK_NOTFOUND;
goto end;
}
r = 0;
// unhook inst, remove current switch from switch-tree
if (!have_enabled_proxy) {
r = sh_inst_unhook(&self->inst, target_addr);
uintptr_t *safe_orig_addr_addr = sh_safe_get_orig_addr_addr(target_addr);
if (NULL != safe_orig_addr_addr) __atomic_store_n(safe_orig_addr_addr, 0, __ATOMIC_SEQ_CST);
RB_REMOVE(sh_switch_tree, &sh_switches, self);
useless = self;
}
end:
pthread_rwlock_unlock(&sh_switches_lock); // SYNC - end
if (NULL != useless) sh_switch_destroy(useless, true);
return r;
}
int sh_switch_unhook(uintptr_t target_addr, uintptr_t new_addr) {
int r;
if (SHADOWHOOK_IS_UNIQUE_MODE) {
r = sh_switch_unhook_unique(target_addr);
if (0 == r) SH_LOG_INFO("switch: unhook in UNIQUE mode OK: target_addr %" PRIxPTR, target_addr);
} else {
r = sh_switch_unhook_shared(target_addr, new_addr);
if (0 == r)
SH_LOG_INFO("switch: unhook in SHARED mode OK: target_addr %" PRIxPTR ", new_addr %" PRIxPTR,
target_addr, new_addr);
}
return r;
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdint.h>
#include "xdl.h"
int sh_switch_hook(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr, size_t *backup_len,
xdl_info_t *dlinfo);
int sh_switch_unhook(uintptr_t target_addr, uintptr_t new_addr);
int sh_switch_hook_invisible(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
size_t *backup_len, xdl_info_t *dlinfo);

View File

@ -0,0 +1,333 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "sh_task.h"
#include <inttypes.h>
#include <malloc.h>
#include <poll.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <sys/eventfd.h>
#include <unistd.h>
#include "queue.h"
#include "sh_config.h"
#include "sh_errno.h"
#include "sh_linker.h"
#include "sh_log.h"
#include "sh_recorder.h"
#include "sh_sig.h"
#include "sh_switch.h"
#include "sh_util.h"
#include "shadowhook.h"
#include "xdl.h"
#define SH_TASK_THREAD_NAME "shadowhook-task"
// task
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
struct sh_task {
char *lib_name;
char *sym_name;
uintptr_t target_addr;
uintptr_t new_addr;
uintptr_t *orig_addr;
shadowhook_hooked_t hooked;
void *hooked_arg;
uintptr_t caller_addr;
bool finished;
bool error;
bool ignore_symbol_check;
TAILQ_ENTRY(sh_task, ) link;
};
#pragma clang diagnostic pop
// task queue
typedef TAILQ_HEAD(sh_task_queue, sh_task, ) sh_task_queue_t;
// task queue object
static sh_task_queue_t sh_tasks = TAILQ_HEAD_INITIALIZER(sh_tasks);
static pthread_rwlock_t sh_tasks_lock = PTHREAD_RWLOCK_INITIALIZER;
static int sh_tasks_unfinished_cnt = 0;
static int sh_task_eventfd;
static int thread_started_result = SHADOWHOOK_ERRNO_MONITOR_THREAD;
sh_task_t *sh_task_create_by_target_addr(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
bool ignore_symbol_check, uintptr_t caller_addr) {
sh_task_t *self = malloc(sizeof(sh_task_t));
if (NULL == self) return NULL;
self->lib_name = NULL;
self->sym_name = NULL;
self->target_addr = target_addr;
self->new_addr = new_addr;
self->orig_addr = orig_addr;
self->hooked = NULL;
self->hooked_arg = NULL;
self->caller_addr = caller_addr;
self->finished = false;
self->error = false;
self->ignore_symbol_check = ignore_symbol_check;
return self;
}
sh_task_t *sh_task_create_by_sym_name(const char *lib_name, const char *sym_name, uintptr_t new_addr,
uintptr_t *orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
uintptr_t caller_addr) {
sh_task_t *self = malloc(sizeof(sh_task_t));
if (NULL == self) return NULL;
if (NULL == (self->lib_name = strdup(lib_name))) goto err;
if (NULL == (self->sym_name = strdup(sym_name))) goto err;
self->target_addr = 0;
self->new_addr = new_addr;
self->orig_addr = orig_addr;
self->hooked = hooked;
self->hooked_arg = hooked_arg;
self->caller_addr = caller_addr;
self->finished = false;
self->error = false;
self->ignore_symbol_check = false;
return self;
err:
if (NULL != self->lib_name) free(self->lib_name);
if (NULL != self->sym_name) free(self->sym_name);
free(self);
return NULL;
}
void sh_task_destroy(sh_task_t *self) {
if (NULL != self->lib_name) free(self->lib_name);
if (NULL != self->sym_name) free(self->sym_name);
free(self);
}
static void sh_task_do_callback(sh_task_t *self, int error_number) {
if (NULL != self->hooked)
self->hooked(error_number, self->lib_name, self->sym_name, (void *)self->target_addr,
(void *)self->new_addr, self->orig_addr, self->hooked_arg);
}
static int sh_task_hook_pending(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size, (void)arg;
pthread_rwlock_rdlock(&sh_tasks_lock);
sh_task_t *task;
TAILQ_FOREACH(task, &sh_tasks, link) {
if (task->finished) continue;
if ('/' == info->dlpi_name[0] && NULL == strstr(info->dlpi_name, task->lib_name)) continue;
if ('/' != info->dlpi_name[0] && NULL == strstr(task->lib_name, info->dlpi_name)) continue;
xdl_info_t dlinfo;
char real_lib_name[512];
int r = sh_linker_get_dlinfo_by_sym_name(task->lib_name, task->sym_name, &dlinfo, real_lib_name,
sizeof(real_lib_name));
task->target_addr = (uintptr_t)dlinfo.dli_saddr;
if (SHADOWHOOK_ERRNO_PENDING != r) {
size_t backup_len = 0;
if (0 == r) {
r = sh_switch_hook(task->target_addr, task->new_addr, task->orig_addr, &backup_len, &dlinfo);
if (0 != r) task->error = true;
} else {
strlcpy(real_lib_name, task->lib_name, sizeof(real_lib_name));
task->error = true;
}
sh_recorder_add_hook(r, false, task->target_addr, real_lib_name, task->sym_name, task->new_addr,
backup_len, (uintptr_t)task, task->caller_addr);
task->finished = true;
sh_task_do_callback(task, r);
if (0 == __atomic_sub_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST)) break;
}
}
pthread_rwlock_unlock(&sh_tasks_lock);
return __atomic_load_n(&sh_tasks_unfinished_cnt, __ATOMIC_SEQ_CST) > 0 ? 0 : 1;
}
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
static void sh_task_post_dlopen_callback(void *arg) {
(void)arg;
if (0 == thread_started_result && __atomic_load_n(&sh_tasks_unfinished_cnt, __ATOMIC_SEQ_CST) > 0) {
uint64_t ev_val = 1;
SH_UTIL_TEMP_FAILURE_RETRY(write(sh_task_eventfd, &ev_val, sizeof(ev_val)));
}
}
__noreturn static void *sh_task_thread_func(void *arg) {
(void)arg;
pthread_t thread = pthread_self();
pthread_setname_np(thread, SH_TASK_THREAD_NAME);
pthread_detach(thread);
struct pollfd ev = {.fd = sh_task_eventfd, .events = POLLIN, .revents = 0};
while (1) {
int n = SH_UTIL_TEMP_FAILURE_RETRY(poll(&ev, 1, -1));
if (n < 0) {
sleep(1);
continue;
} else if (n > 0) {
uint64_t ev_val;
SH_UTIL_TEMP_FAILURE_RETRY(read(sh_task_eventfd, &ev_val, sizeof(ev_val)));
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
xdl_iterate_phdr(sh_task_hook_pending, NULL, XDL_DEFAULT);
} else {
SH_SIG_TRY(SIGSEGV, SIGBUS) {
xdl_iterate_phdr(sh_task_hook_pending, NULL, XDL_DEFAULT);
}
SH_SIG_CATCH() {
SH_LOG_WARN("task: dliterate crashed");
}
SH_SIG_EXIT
}
}
}
}
#pragma clang diagnostic pop
static int sh_task_start_monitor(bool start_thread) {
static bool thread_started = false;
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_t thread;
int r;
// hook linker dlopen()
if (0 != (r = sh_linker_hook_dlopen(sh_task_post_dlopen_callback, NULL))) return r;
if (!start_thread) return 0;
// start thread
if (thread_started) return thread_started_result;
pthread_mutex_lock(&lock);
if (thread_started) goto end;
if (0 > (sh_task_eventfd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC))) goto end;
if (0 != pthread_create(&thread, NULL, &sh_task_thread_func, NULL)) goto end;
// OK
thread_started_result = 0;
end:
thread_started = true;
pthread_mutex_unlock(&lock);
SH_LOG_INFO("task: start monitor %s, return: %d", 0 == thread_started_result ? "OK" : "FAILED",
thread_started_result);
return thread_started_result;
}
int sh_task_hook(sh_task_t *self) {
int r;
bool is_hook_sym_addr = true;
char real_lib_name[512] = "unknown";
char real_sym_name[1024] = "unknown";
size_t backup_len = 0;
// find target-address by library-name and symbol-name
xdl_info_t dlinfo;
memset(&dlinfo, 0, sizeof(xdl_info_t));
if (0 == self->target_addr) {
is_hook_sym_addr = false;
strlcpy(real_lib_name, self->lib_name, sizeof(real_lib_name));
strlcpy(real_sym_name, self->sym_name, sizeof(real_sym_name));
r = sh_linker_get_dlinfo_by_sym_name(self->lib_name, self->sym_name, &dlinfo, real_lib_name,
sizeof(real_lib_name));
if (SHADOWHOOK_ERRNO_PENDING == r) {
// we need to start monitor linker dlopen for handle the pending task
if (0 != (r = sh_task_start_monitor(true))) goto end;
r = SHADOWHOOK_ERRNO_PENDING;
goto end;
}
if (0 != r) goto end; // error
self->target_addr = (uintptr_t)dlinfo.dli_saddr; // OK
} else {
r = sh_linker_get_dlinfo_by_addr((void *)self->target_addr, &dlinfo, real_lib_name, sizeof(real_lib_name),
real_sym_name, sizeof(real_sym_name), self->ignore_symbol_check);
if (0 != r) goto end; // error
}
// In UNIQUE mode, if external users are hooking the linker dlopen() or do_dlopen(),
// we MUST hook this method with invisible for ourself first.
if (sh_linker_need_to_hook_dlopen(self->target_addr)) {
SH_LOG_INFO("task: hook dlopen/do_dlopen internal. target-address %" PRIxPTR, self->target_addr);
if (0 != (r = sh_task_start_monitor(false))) goto end;
}
// hook by target-address
r = sh_switch_hook(self->target_addr, self->new_addr, self->orig_addr, &backup_len, &dlinfo);
self->finished = true;
end:
if (0 == r || SHADOWHOOK_ERRNO_PENDING == r) // "PENDING" is NOT an error
{
pthread_rwlock_wrlock(&sh_tasks_lock);
TAILQ_INSERT_TAIL(&sh_tasks, self, link);
if (!self->finished) __atomic_add_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST);
pthread_rwlock_unlock(&sh_tasks_lock);
}
// record
sh_recorder_add_hook(r, is_hook_sym_addr, self->target_addr, real_lib_name, real_sym_name, self->new_addr,
backup_len, (uintptr_t)self, self->caller_addr);
return r;
}
int sh_task_unhook(sh_task_t *self, uintptr_t caller_addr) {
pthread_rwlock_wrlock(&sh_tasks_lock);
TAILQ_REMOVE(&sh_tasks, self, link);
if (!self->finished) __atomic_sub_fetch(&sh_tasks_unfinished_cnt, 1, __ATOMIC_SEQ_CST);
pthread_rwlock_unlock(&sh_tasks_lock);
// check task status
int r;
if (self->error) {
r = SHADOWHOOK_ERRNO_UNHOOK_ON_ERROR;
goto end;
}
if (!self->finished) {
r = SHADOWHOOK_ERRNO_UNHOOK_ON_UNFINISHED;
goto end;
}
// do unhook
r = sh_switch_unhook(self->target_addr, self->new_addr);
end:
// record
sh_recorder_add_unhook(r, (uintptr_t)self, caller_addr);
return r;
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#pragma once
#include <stdbool.h>
#include <stdint.h>
#include "shadowhook.h"
typedef struct sh_task sh_task_t;
sh_task_t *sh_task_create_by_target_addr(uintptr_t target_addr, uintptr_t new_addr, uintptr_t *orig_addr,
bool ignore_symbol_check, uintptr_t caller_addr);
sh_task_t *sh_task_create_by_sym_name(const char *lib_name, const char *sym_name, uintptr_t new_addr,
uintptr_t *orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
uintptr_t caller_addr);
void sh_task_destroy(sh_task_t *self);
int sh_task_hook(sh_task_t *self);
int sh_task_unhook(sh_task_t *self, uintptr_t caller_addr);

View File

@ -0,0 +1,328 @@
// Copyright (c) 2021-2022 ByteDance Inc.
//
// 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.
//
// Created by Kelun Cai (caikelun@bytedance.com) on 2021-04-11.
#include "shadowhook.h"
#include <pthread.h>
#include <stdbool.h>
#include <stdlib.h>
#include "bytesig.h"
#include "sh_enter.h"
#include "sh_errno.h"
#include "sh_exit.h"
#include "sh_hub.h"
#include "sh_linker.h"
#include "sh_log.h"
#include "sh_recorder.h"
#include "sh_safe.h"
#include "sh_sig.h"
#include "sh_switch.h"
#include "sh_task.h"
#include "sh_util.h"
#include "xdl.h"
#define GOTO_ERR(errnum) \
do { \
r = errnum; \
goto err; \
} while (0)
static int shadowhook_init_errno = SHADOWHOOK_ERRNO_UNINIT;
static shadowhook_mode_t shadowhook_mode = SHADOWHOOK_MODE_SHARED;
const char *shadowhook_get_version(void) {
return "shadowhook version " SHADOWHOOK_VERSION;
}
int shadowhook_init(shadowhook_mode_t mode, bool debuggable) {
bool do_init = false;
if (__predict_true(SHADOWHOOK_ERRNO_UNINIT == shadowhook_init_errno)) {
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
pthread_mutex_lock(&lock);
if (__predict_true(SHADOWHOOK_ERRNO_UNINIT == shadowhook_init_errno)) {
do_init = true;
shadowhook_mode = mode;
sh_log_set_debuggable(debuggable);
#define GOTO_END(errnum) \
do { \
shadowhook_init_errno = errnum; \
goto end; \
} while (0)
if (__predict_false(0 != sh_errno_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_ERRNO);
if (__predict_false(0 != bytesig_init(SIGSEGV))) GOTO_END(SHADOWHOOK_ERRNO_INIT_SIGSEGV);
if (__predict_false(0 != bytesig_init(SIGBUS))) GOTO_END(SHADOWHOOK_ERRNO_INIT_SIGBUS);
if (__predict_false(0 != sh_enter_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_ENTER);
sh_exit_init();
if (SHADOWHOOK_MODE_SHARED == shadowhook_mode) {
if (__predict_false(0 != sh_safe_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_SAFE);
if (__predict_false(0 != sh_hub_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_HUB);
} else {
if (__predict_false(0 != sh_linker_init())) GOTO_END(SHADOWHOOK_ERRNO_INIT_LINKER);
}
#undef GOTO_END
shadowhook_init_errno = SHADOWHOOK_ERRNO_OK;
}
end:
pthread_mutex_unlock(&lock);
}
SH_LOG_ALWAYS_SHOW("%s: shadowhook init(mode: %s, debuggable: %s), return: %d, real-init: %s",
shadowhook_get_version(), SHADOWHOOK_MODE_SHARED == mode ? "SHARED" : "UNIQUE",
debuggable ? "true" : "false", shadowhook_init_errno, do_init ? "yes" : "no");
SH_ERRNO_SET_RET_ERRNUM(shadowhook_init_errno);
}
int shadowhook_get_init_errno(void) {
return shadowhook_init_errno;
}
shadowhook_mode_t shadowhook_get_mode(void) {
return shadowhook_mode;
}
bool shadowhook_get_debuggable(void) {
return sh_log_get_debuggable();
}
void shadowhook_set_debuggable(bool debuggable) {
sh_log_set_debuggable(debuggable);
}
bool shadowhook_get_recordable(void) {
return sh_recorder_get_recordable();
}
void shadowhook_set_recordable(bool recordable) {
sh_recorder_set_recordable(recordable);
}
int shadowhook_get_errno(void) {
return sh_errno_get();
}
const char *shadowhook_to_errmsg(int error_number) {
return sh_errno_to_errmsg(error_number);
}
static void *shadowhook_hook_addr_impl(void *sym_addr, void *new_addr, void **orig_addr,
bool ignore_symbol_check, uintptr_t caller_addr) {
SH_LOG_INFO("shadowhook: hook_%s_addr(%p, %p) ...", ignore_symbol_check ? "func" : "sym", sym_addr,
new_addr);
sh_errno_reset();
int r;
if (NULL == sym_addr || NULL == new_addr) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
if (SHADOWHOOK_ERRNO_OK != shadowhook_init_errno) GOTO_ERR(shadowhook_init_errno);
// create task
sh_task_t *task =
sh_task_create_by_target_addr((uintptr_t)sym_addr, (uintptr_t)new_addr, (uintptr_t *)orig_addr,
ignore_symbol_check, (uintptr_t)caller_addr);
if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);
// do hook
r = sh_task_hook(task);
if (0 != r) {
sh_task_destroy(task);
GOTO_ERR(r);
}
// OK
SH_LOG_INFO("shadowhook: hook_%s_addr(%p, %p) OK. return: %p", ignore_symbol_check ? "func" : "sym",
sym_addr, new_addr, (void *)task);
SH_ERRNO_SET_RET(SHADOWHOOK_ERRNO_OK, (void *)task);
err:
SH_LOG_ERROR("shadowhook: hook_%s_addr(%p, %p) FAILED. %d - %s", ignore_symbol_check ? "func" : "sym",
sym_addr, new_addr, r, sh_errno_to_errmsg(r));
SH_ERRNO_SET_RET_NULL(r);
}
void *shadowhook_hook_func_addr(void *func_addr, void *new_addr, void **orig_addr) {
const void *caller_addr = __builtin_return_address(0);
return shadowhook_hook_addr_impl(func_addr, new_addr, orig_addr, true, (uintptr_t)caller_addr);
}
void *shadowhook_hook_sym_addr(void *sym_addr, void *new_addr, void **orig_addr) {
const void *caller_addr = __builtin_return_address(0);
return shadowhook_hook_addr_impl(sym_addr, new_addr, orig_addr, false, (uintptr_t)caller_addr);
}
static void *shadowhook_hook_sym_name_impl(const char *lib_name, const char *sym_name, void *new_addr,
void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg,
uintptr_t caller_addr) {
SH_LOG_INFO("shadowhook: hook_sym_name(%s, %s, %p) ...", lib_name, sym_name, new_addr);
sh_errno_reset();
int r;
if (NULL == lib_name || NULL == sym_name || NULL == new_addr) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
if (SHADOWHOOK_ERRNO_OK != shadowhook_init_errno) GOTO_ERR(shadowhook_init_errno);
// create task
sh_task_t *task =
sh_task_create_by_sym_name(lib_name, sym_name, (uintptr_t)new_addr, (uintptr_t *)orig_addr, hooked,
hooked_arg, (uintptr_t)caller_addr);
if (NULL == task) GOTO_ERR(SHADOWHOOK_ERRNO_OOM);
// do hook
r = sh_task_hook(task);
if (0 != r && SHADOWHOOK_ERRNO_PENDING != r) {
sh_task_destroy(task);
GOTO_ERR(r);
}
// OK
SH_LOG_INFO("shadowhook: hook_sym_name(%s, %s, %p) OK. return: %p. %d - %s", lib_name, sym_name, new_addr,
(void *)task, r, sh_errno_to_errmsg(r));
SH_ERRNO_SET_RET(r, (void *)task);
err:
SH_LOG_ERROR("shadowhook: hook_sym_name(%s, %s, %p) FAILED. %d - %s", lib_name, sym_name, new_addr, r,
sh_errno_to_errmsg(r));
SH_ERRNO_SET_RET_NULL(r);
}
void *shadowhook_hook_sym_name(const char *lib_name, const char *sym_name, void *new_addr, void **orig_addr) {
const void *caller_addr = __builtin_return_address(0);
return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, NULL, NULL,
(uintptr_t)caller_addr);
}
void *shadowhook_hook_sym_name_callback(const char *lib_name, const char *sym_name, void *new_addr,
void **orig_addr, shadowhook_hooked_t hooked, void *hooked_arg) {
const void *caller_addr = __builtin_return_address(0);
return shadowhook_hook_sym_name_impl(lib_name, sym_name, new_addr, orig_addr, hooked, hooked_arg,
(uintptr_t)caller_addr);
}
int shadowhook_unhook(void *stub) {
const void *caller_addr = __builtin_return_address(0);
SH_LOG_INFO("shadowhook: unhook(%p) ...", stub);
sh_errno_reset();
int r;
if (NULL == stub) GOTO_ERR(SHADOWHOOK_ERRNO_INVALID_ARG);
if (SHADOWHOOK_ERRNO_OK != shadowhook_init_errno) GOTO_ERR(shadowhook_init_errno);
sh_task_t *task = (sh_task_t *)stub;
r = sh_task_unhook(task, (uintptr_t)caller_addr);
sh_task_destroy(task);
if (0 != r) GOTO_ERR(r);
// OK
SH_LOG_INFO("shadowhook: unhook(%p) OK", stub);
SH_ERRNO_SET_RET_ERRNUM(SHADOWHOOK_ERRNO_OK);
err:
SH_LOG_ERROR("shadowhook: unhook(%p) FAILED. %d - %s", stub, r, sh_errno_to_errmsg(r));
SH_ERRNO_SET_RET_FAIL(r);
}
char *shadowhook_get_records(uint32_t item_flags) {
return sh_recorder_get(item_flags);
}
void shadowhook_dump_records(int fd, uint32_t item_flags) {
sh_recorder_dump(fd, item_flags);
}
void *shadowhook_dlopen(const char *lib_name) {
void *handle = NULL;
if (sh_util_get_api_level() >= __ANDROID_API_L__) {
handle = xdl_open(lib_name, XDL_DEFAULT);
} else {
SH_SIG_TRY(SIGSEGV, SIGBUS) {
handle = xdl_open(lib_name, XDL_DEFAULT);
}
SH_SIG_CATCH() {
SH_LOG_WARN("shadowhook: dlopen crashed - %s", lib_name);
}
SH_SIG_EXIT
}
return handle;
}
void shadowhook_dlclose(void *handle) {
xdl_close(handle);
}
void *shadowhook_dlsym(void *handle, const char *sym_name) {
void *addr = shadowhook_dlsym_dynsym(handle, sym_name);
if (NULL == addr) addr = shadowhook_dlsym_symtab(handle, sym_name);
return addr;
}
void *shadowhook_dlsym_dynsym(void *handle, const char *sym_name) {
void *addr = NULL;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
addr = xdl_sym(handle, sym_name, NULL);
}
SH_SIG_CATCH() {
SH_LOG_WARN("shadowhook: dlsym_dynsym crashed - %p, %s", handle, sym_name);
}
SH_SIG_EXIT
return addr;
}
void *shadowhook_dlsym_symtab(void *handle, const char *sym_name) {
void *addr = NULL;
SH_SIG_TRY(SIGSEGV, SIGBUS) {
addr = xdl_dsym(handle, sym_name, NULL);
}
SH_SIG_CATCH() {
SH_LOG_WARN("shadowhook: dlsym_symtab crashed - %p, %s", handle, sym_name);
}
SH_SIG_EXIT
return addr;
}
void *shadowhook_get_prev_func(void *func) {
if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
return sh_hub_get_prev_func(func);
}
void shadowhook_pop_stack(void *return_address) {
if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
sh_hub_pop_stack(return_address);
}
void shadowhook_allow_reentrant(void *return_address) {
if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
sh_hub_allow_reentrant(return_address);
}
void shadowhook_disallow_reentrant(void *return_address) {
if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
sh_hub_disallow_reentrant(return_address);
}
void *shadowhook_get_return_address(void) {
if (__predict_false(SHADOWHOOK_IS_UNIQUE_MODE)) abort();
return sh_hub_get_return_address();
}

View File

@ -0,0 +1,551 @@
/*-
* Copyright (c) 1991, 1993
* The Regents of the University of California. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. Neither the name of the University nor the names of its contributors
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*/
#ifndef QUEUE_H
#define QUEUE_H
/* #include <sys/cdefs.h> */
#define QUEUE_CONTAINER_OF(ptr, type, field) ((type *)((char *)(ptr) - ((char *)&((type *)0)->field)))
/*
* This file defines four types of data structures: singly-linked lists,
* singly-linked tail queues, lists and tail queues.
*
* A singly-linked list is headed by a single forward pointer. The elements
* are singly linked for minimum space and pointer manipulation overhead at
* the expense of O(n) removal for arbitrary elements. New elements can be
* added to the list after an existing element or at the head of the list.
* Elements being removed from the head of the list should use the explicit
* macro for this purpose for optimum efficiency. A singly-linked list may
* only be traversed in the forward direction. Singly-linked lists are ideal
* for applications with large datasets and few or no removals or for
* implementing a LIFO queue.
*
* A singly-linked tail queue is headed by a pair of pointers, one to the
* head of the list and the other to the tail of the list. The elements are
* singly linked for minimum space and pointer manipulation overhead at the
* expense of O(n) removal for arbitrary elements. New elements can be added
* to the list after an existing element, at the head of the list, or at the
* end of the list. Elements being removed from the head of the tail queue
* should use the explicit macro for this purpose for optimum efficiency.
* A singly-linked tail queue may only be traversed in the forward direction.
* Singly-linked tail queues are ideal for applications with large datasets
* and few or no removals or for implementing a FIFO queue.
*
* A list is headed by a single forward pointer (or an array of forward
* pointers for a hash table header). The elements are doubly linked
* so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before
* or after an existing element or at the head of the list. A list
* may be traversed in either direction.
*
* A tail queue is headed by a pair of pointers, one to the head of the
* list and the other to the tail of the list. The elements are doubly
* linked so that an arbitrary element can be removed without a need to
* traverse the list. New elements can be added to the list before or
* after an existing element, at the head of the list, or at the end of
* the list. A tail queue may be traversed in either direction.
*
* For details on the use of these macros, see the queue(3) manual page.
*
* SLIST LIST STAILQ TAILQ
* _HEAD + + + +
* _HEAD_INITIALIZER + + + +
* _ENTRY + + + +
* _INIT + + + +
* _EMPTY + + + +
* _FIRST + + + +
* _NEXT + + + +
* _PREV - + - +
* _LAST - - + +
* _FOREACH + + + +
* _FOREACH_FROM + + + +
* _FOREACH_SAFE + + + +
* _FOREACH_FROM_SAFE + + + +
* _FOREACH_REVERSE - - - +
* _FOREACH_REVERSE_FROM - - - +
* _FOREACH_REVERSE_SAFE - - - +
* _FOREACH_REVERSE_FROM_SAFE - - - +
* _INSERT_HEAD + + + +
* _INSERT_BEFORE - + - +
* _INSERT_AFTER + + + +
* _INSERT_TAIL - - + +
* _CONCAT - - + +
* _REMOVE_AFTER + - + -
* _REMOVE_HEAD + - + -
* _REMOVE + + + +
* _SWAP + + + +
*
*/
/*
* Singly-linked List declarations.
*/
#define SLIST_HEAD(name, type, qual) \
struct name { \
struct type *qual slh_first; /* first element */ \
}
#define SLIST_HEAD_INITIALIZER(head) \
{ NULL }
#define SLIST_ENTRY(type, qual) \
struct { \
struct type *qual sle_next; /* next element */ \
}
/*
* Singly-linked List functions.
*/
#define SLIST_INIT(head) do { \
SLIST_FIRST((head)) = NULL; \
} while (0)
#define SLIST_EMPTY(head) ((head)->slh_first == NULL)
#define SLIST_FIRST(head) ((head)->slh_first)
#define SLIST_NEXT(elm, field) ((elm)->field.sle_next)
#define SLIST_FOREACH(var, head, field) \
for ((var) = SLIST_FIRST((head)); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var); \
(var) = SLIST_NEXT((var), field))
#define SLIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = SLIST_FIRST((head)); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : SLIST_FIRST((head))); \
(var) && ((tvar) = SLIST_NEXT((var), field), 1); \
(var) = (tvar))
#define SLIST_INSERT_HEAD(head, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_FIRST((head)); \
SLIST_FIRST((head)) = (elm); \
} while (0)
#define SLIST_INSERT_AFTER(slistelm, elm, field) do { \
SLIST_NEXT((elm), field) = SLIST_NEXT((slistelm), field); \
SLIST_NEXT((slistelm), field) = (elm); \
} while (0)
#define SLIST_REMOVE_AFTER(elm, field) do { \
SLIST_NEXT(elm, field) = \
SLIST_NEXT(SLIST_NEXT(elm, field), field); \
} while (0)
#define SLIST_REMOVE_HEAD(head, field) do { \
SLIST_FIRST((head)) = SLIST_NEXT(SLIST_FIRST((head)), field); \
} while (0)
#define SLIST_REMOVE(head, elm, type, field) do { \
if (SLIST_FIRST((head)) == (elm)) { \
SLIST_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = SLIST_FIRST((head)); \
while (SLIST_NEXT(curelm, field) != (elm)) \
curelm = SLIST_NEXT(curelm, field); \
SLIST_REMOVE_AFTER(curelm, field); \
} \
} while (0)
#define SLIST_SWAP(head1, head2, type) do { \
struct type *swap_first = SLIST_FIRST(head1); \
SLIST_FIRST(head1) = SLIST_FIRST(head2); \
SLIST_FIRST(head2) = swap_first; \
} while (0)
/*
* List declarations.
*/
#define LIST_HEAD(name, type, qual) \
struct name { \
struct type *qual lh_first; /* first element */ \
}
#define LIST_HEAD_INITIALIZER(head) \
{ NULL }
#define LIST_ENTRY(type, qual) \
struct { \
struct type *qual le_next; /* next element */ \
struct type *qual *le_prev; /* address of previous next element */ \
}
/*
* List functions.
*/
#define LIST_INIT(head) do { \
LIST_FIRST((head)) = NULL; \
} while (0)
#define LIST_EMPTY(head) ((head)->lh_first == NULL)
#define LIST_FIRST(head) ((head)->lh_first)
#define LIST_NEXT(elm, field) ((elm)->field.le_next)
#define LIST_PREV(elm, head, type, field) \
((elm)->field.le_prev == &LIST_FIRST((head)) ? NULL : \
QUEUE_CONTAINER_OF((elm)->field.le_prev, struct type, field.le_next))
#define LIST_FOREACH(var, head, field) \
for ((var) = LIST_FIRST((head)); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var); \
(var) = LIST_NEXT((var), field))
#define LIST_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = LIST_FIRST((head)); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : LIST_FIRST((head))); \
(var) && ((tvar) = LIST_NEXT((var), field), 1); \
(var) = (tvar))
#define LIST_INSERT_HEAD(head, elm, field) do { \
if ((LIST_NEXT((elm), field) = LIST_FIRST((head))) != NULL) \
LIST_FIRST((head))->field.le_prev = &LIST_NEXT((elm), field); \
LIST_FIRST((head)) = (elm); \
(elm)->field.le_prev = &LIST_FIRST((head)); \
} while (0)
#define LIST_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.le_prev = (listelm)->field.le_prev; \
LIST_NEXT((elm), field) = (listelm); \
*(listelm)->field.le_prev = (elm); \
(listelm)->field.le_prev = &LIST_NEXT((elm), field); \
} while (0)
#define LIST_INSERT_AFTER(listelm, elm, field) do { \
if ((LIST_NEXT((elm), field) = LIST_NEXT((listelm), field)) != NULL) \
LIST_NEXT((listelm), field)->field.le_prev = \
&LIST_NEXT((elm), field); \
LIST_NEXT((listelm), field) = (elm); \
(elm)->field.le_prev = &LIST_NEXT((listelm), field); \
} while (0)
#define LIST_REMOVE(elm, field) do { \
if (LIST_NEXT((elm), field) != NULL) \
LIST_NEXT((elm), field)->field.le_prev = \
(elm)->field.le_prev; \
*(elm)->field.le_prev = LIST_NEXT((elm), field); \
} while (0)
#define LIST_SWAP(head1, head2, type, field) do { \
struct type *swap_tmp = LIST_FIRST((head1)); \
LIST_FIRST((head1)) = LIST_FIRST((head2)); \
LIST_FIRST((head2)) = swap_tmp; \
if ((swap_tmp = LIST_FIRST((head1))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head1)); \
if ((swap_tmp = LIST_FIRST((head2))) != NULL) \
swap_tmp->field.le_prev = &LIST_FIRST((head2)); \
} while (0)
/*
* Singly-linked Tail queue declarations.
*/
#define STAILQ_HEAD(name, type, qual) \
struct name { \
struct type *qual stqh_first;/* first element */ \
struct type *qual *stqh_last;/* addr of last next element */ \
}
#define STAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).stqh_first }
#define STAILQ_ENTRY(type, qual) \
struct { \
struct type *qual stqe_next; /* next element */ \
}
/*
* Singly-linked Tail queue functions.
*/
#define STAILQ_INIT(head) do { \
STAILQ_FIRST((head)) = NULL; \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_EMPTY(head) ((head)->stqh_first == NULL)
#define STAILQ_FIRST(head) ((head)->stqh_first)
#define STAILQ_NEXT(elm, field) ((elm)->field.stqe_next)
#define STAILQ_LAST(head, type, field) \
(STAILQ_EMPTY((head)) ? NULL : \
QUEUE_CONTAINER_OF((head)->stqh_last, struct type, field.stqe_next))
#define STAILQ_FOREACH(var, head, field) \
for((var) = STAILQ_FIRST((head)); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var); \
(var) = STAILQ_NEXT((var), field))
#define STAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = STAILQ_FIRST((head)); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : STAILQ_FIRST((head))); \
(var) && ((tvar) = STAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define STAILQ_INSERT_HEAD(head, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_FIRST((head))) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_FIRST((head)) = (elm); \
} while (0)
#define STAILQ_INSERT_AFTER(head, tqelm, elm, field) do { \
if ((STAILQ_NEXT((elm), field) = STAILQ_NEXT((tqelm), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
STAILQ_NEXT((tqelm), field) = (elm); \
} while (0)
#define STAILQ_INSERT_TAIL(head, elm, field) do { \
STAILQ_NEXT((elm), field) = NULL; \
*(head)->stqh_last = (elm); \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_CONCAT(head1, head2) do { \
if (!STAILQ_EMPTY((head2))) { \
*(head1)->stqh_last = (head2)->stqh_first; \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_INIT((head2)); \
} \
} while (0)
#define STAILQ_REMOVE_AFTER(head, elm, field) do { \
if ((STAILQ_NEXT(elm, field) = \
STAILQ_NEXT(STAILQ_NEXT(elm, field), field)) == NULL) \
(head)->stqh_last = &STAILQ_NEXT((elm), field); \
} while (0)
#define STAILQ_REMOVE_HEAD(head, field) do { \
if ((STAILQ_FIRST((head)) = \
STAILQ_NEXT(STAILQ_FIRST((head)), field)) == NULL) \
(head)->stqh_last = &STAILQ_FIRST((head)); \
} while (0)
#define STAILQ_REMOVE(head, elm, type, field) do { \
if (STAILQ_FIRST((head)) == (elm)) { \
STAILQ_REMOVE_HEAD((head), field); \
} \
else { \
struct type *curelm = STAILQ_FIRST((head)); \
while (STAILQ_NEXT(curelm, field) != (elm)) \
curelm = STAILQ_NEXT(curelm, field); \
STAILQ_REMOVE_AFTER(head, curelm, field); \
} \
} while (0)
#define STAILQ_SWAP(head1, head2, type) do { \
struct type *swap_first = STAILQ_FIRST(head1); \
struct type **swap_last = (head1)->stqh_last; \
STAILQ_FIRST(head1) = STAILQ_FIRST(head2); \
(head1)->stqh_last = (head2)->stqh_last; \
STAILQ_FIRST(head2) = swap_first; \
(head2)->stqh_last = swap_last; \
if (STAILQ_EMPTY(head1)) \
(head1)->stqh_last = &STAILQ_FIRST(head1); \
if (STAILQ_EMPTY(head2)) \
(head2)->stqh_last = &STAILQ_FIRST(head2); \
} while (0)
/*
* Tail queue declarations.
*/
#define TAILQ_HEAD(name, type, qual) \
struct name { \
struct type *qual tqh_first; /* first element */ \
struct type *qual *tqh_last; /* addr of last next element */ \
}
#define TAILQ_HEAD_INITIALIZER(head) \
{ NULL, &(head).tqh_first }
#define TAILQ_ENTRY(type, qual) \
struct { \
struct type *qual tqe_next; /* next element */ \
struct type *qual *tqe_prev; /* address of previous next element */ \
}
/*
* Tail queue functions.
*/
#define TAILQ_INIT(head) do { \
TAILQ_FIRST((head)) = NULL; \
(head)->tqh_last = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_EMPTY(head) ((head)->tqh_first == NULL)
#define TAILQ_FIRST(head) ((head)->tqh_first)
#define TAILQ_NEXT(elm, field) ((elm)->field.tqe_next)
#define TAILQ_PREV(elm, headname, field) \
(*(((struct headname *)((elm)->field.tqe_prev))->tqh_last))
#define TAILQ_LAST(head, headname) \
(*(((struct headname *)((head)->tqh_last))->tqh_last))
#define TAILQ_FOREACH(var, head, field) \
for ((var) = TAILQ_FIRST((head)); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_FROM(var, head, field) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var); \
(var) = TAILQ_NEXT((var), field))
#define TAILQ_FOREACH_SAFE(var, head, field, tvar) \
for ((var) = TAILQ_FIRST((head)); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_FROM_SAFE(var, head, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_FIRST((head))); \
(var) && ((tvar) = TAILQ_NEXT((var), field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE(var, head, headname, field) \
for ((var) = TAILQ_LAST((head), headname); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_FROM(var, head, headname, field) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var); \
(var) = TAILQ_PREV((var), headname, field))
#define TAILQ_FOREACH_REVERSE_SAFE(var, head, headname, field, tvar) \
for ((var) = TAILQ_LAST((head), headname); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_FOREACH_REVERSE_FROM_SAFE(var, head, headname, field, tvar) \
for ((var) = ((var) ? (var) : TAILQ_LAST((head), headname)); \
(var) && ((tvar) = TAILQ_PREV((var), headname, field), 1); \
(var) = (tvar))
#define TAILQ_INSERT_HEAD(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_FIRST((head))) != NULL) \
TAILQ_FIRST((head))->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_FIRST((head)) = (elm); \
(elm)->field.tqe_prev = &TAILQ_FIRST((head)); \
} while (0)
#define TAILQ_INSERT_BEFORE(listelm, elm, field) do { \
(elm)->field.tqe_prev = (listelm)->field.tqe_prev; \
TAILQ_NEXT((elm), field) = (listelm); \
*(listelm)->field.tqe_prev = (elm); \
(listelm)->field.tqe_prev = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_INSERT_AFTER(head, listelm, elm, field) do { \
if ((TAILQ_NEXT((elm), field) = TAILQ_NEXT((listelm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
&TAILQ_NEXT((elm), field); \
else \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
TAILQ_NEXT((listelm), field) = (elm); \
(elm)->field.tqe_prev = &TAILQ_NEXT((listelm), field); \
} while (0)
#define TAILQ_INSERT_TAIL(head, elm, field) do { \
TAILQ_NEXT((elm), field) = NULL; \
(elm)->field.tqe_prev = (head)->tqh_last; \
*(head)->tqh_last = (elm); \
(head)->tqh_last = &TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_CONCAT(head1, head2, field) do { \
if (!TAILQ_EMPTY(head2)) { \
*(head1)->tqh_last = (head2)->tqh_first; \
(head2)->tqh_first->field.tqe_prev = (head1)->tqh_last; \
(head1)->tqh_last = (head2)->tqh_last; \
TAILQ_INIT((head2)); \
} \
} while (0)
#define TAILQ_REMOVE(head, elm, field) do { \
if ((TAILQ_NEXT((elm), field)) != NULL) \
TAILQ_NEXT((elm), field)->field.tqe_prev = \
(elm)->field.tqe_prev; \
else \
(head)->tqh_last = (elm)->field.tqe_prev; \
*(elm)->field.tqe_prev = TAILQ_NEXT((elm), field); \
} while (0)
#define TAILQ_SWAP(head1, head2, type, field) do { \
struct type *swap_first = (head1)->tqh_first; \
struct type **swap_last = (head1)->tqh_last; \
(head1)->tqh_first = (head2)->tqh_first; \
(head1)->tqh_last = (head2)->tqh_last; \
(head2)->tqh_first = swap_first; \
(head2)->tqh_last = swap_last; \
if ((swap_first = (head1)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head1)->tqh_first; \
else \
(head1)->tqh_last = &(head1)->tqh_first; \
if ((swap_first = (head2)->tqh_first) != NULL) \
swap_first->field.tqe_prev = &(head2)->tqh_first; \
else \
(head2)->tqh_last = &(head2)->tqh_first; \
} while (0)
#endif

View File

@ -0,0 +1,759 @@
/*-
* Copyright 2002 Niels Provos <provos@citi.umich.edu>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef TREE_H
#define TREE_H
/*
* This file defines data structures for different types of trees:
* splay trees and red-black trees.
*
* A splay tree is a self-organizing data structure. Every operation
* on the tree causes a splay to happen. The splay moves the requested
* node to the root of the tree and partly rebalances it.
*
* This has the benefit that request locality causes faster lookups as
* the requested nodes move to the top of the tree. On the other hand,
* every lookup causes memory writes.
*
* The Balance Theorem bounds the total access time for m operations
* and n inserts on an initially empty tree as O((m + n)lg n). The
* amortized cost for a sequence of m accesses to a splay tree is O(lg n);
*
* A red-black tree is a binary search tree with the node color as an
* extra attribute. It fulfills a set of conditions:
* - every search path from the root to a leaf consists of the
* same number of black nodes,
* - each red node (except for the root) has a black parent,
* - each leaf node is black.
*
* Every operation on a red-black tree is bounded as O(lg n).
* The maximum height of a red-black tree is 2lg (n+1).
*/
#define SPLAY_HEAD(name, type) \
struct name { \
struct type *sph_root; /* root of the tree */ \
}
#define SPLAY_INITIALIZER(root) \
{ NULL }
#define SPLAY_INIT(root) do { \
(root)->sph_root = NULL; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ENTRY(type) \
struct { \
struct type *spe_left; /* left element */ \
struct type *spe_right; /* right element */ \
}
#define SPLAY_LEFT(elm, field) (elm)->field.spe_left
#define SPLAY_RIGHT(elm, field) (elm)->field.spe_right
#define SPLAY_ROOT(head) (head)->sph_root
#define SPLAY_EMPTY(head) (SPLAY_ROOT(head) == NULL)
/* SPLAY_ROTATE_{LEFT,RIGHT} expect that tmp hold SPLAY_{RIGHT,LEFT} */
#define SPLAY_ROTATE_RIGHT(head, tmp, field) do { \
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(tmp, field); \
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
(head)->sph_root = tmp; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ROTATE_LEFT(head, tmp, field) do { \
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(tmp, field); \
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
(head)->sph_root = tmp; \
} while (/*CONSTCOND*/ 0)
#define SPLAY_LINKLEFT(head, tmp, field) do { \
SPLAY_LEFT(tmp, field) = (head)->sph_root; \
tmp = (head)->sph_root; \
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field); \
} while (/*CONSTCOND*/ 0)
#define SPLAY_LINKRIGHT(head, tmp, field) do { \
SPLAY_RIGHT(tmp, field) = (head)->sph_root; \
tmp = (head)->sph_root; \
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field); \
} while (/*CONSTCOND*/ 0)
#define SPLAY_ASSEMBLE(head, node, left, right, field) do { \
SPLAY_RIGHT(left, field) = SPLAY_LEFT((head)->sph_root, field); \
SPLAY_LEFT(right, field) = SPLAY_RIGHT((head)->sph_root, field);\
SPLAY_LEFT((head)->sph_root, field) = SPLAY_RIGHT(node, field); \
SPLAY_RIGHT((head)->sph_root, field) = SPLAY_LEFT(node, field); \
} while (/*CONSTCOND*/ 0)
/* Generates prototypes and inline functions */
#define SPLAY_PROTOTYPE(name, type, field, cmp) \
void name##_SPLAY(struct name *, struct type *); \
void name##_SPLAY_MINMAX(struct name *, int); \
struct type *name##_SPLAY_INSERT(struct name *, struct type *); \
struct type *name##_SPLAY_REMOVE(struct name *, struct type *); \
\
/* Finds the node with the same key as elm */ \
static __inline struct type * \
name##_SPLAY_FIND(struct name *head, struct type *elm) \
{ \
if (SPLAY_EMPTY(head)) \
return(NULL); \
name##_SPLAY(head, elm); \
if ((cmp)(elm, (head)->sph_root) == 0) \
return (head->sph_root); \
return (NULL); \
} \
\
static __inline struct type * \
name##_SPLAY_NEXT(struct name *head, struct type *elm) \
{ \
name##_SPLAY(head, elm); \
if (SPLAY_RIGHT(elm, field) != NULL) { \
elm = SPLAY_RIGHT(elm, field); \
while (SPLAY_LEFT(elm, field) != NULL) { \
elm = SPLAY_LEFT(elm, field); \
} \
} else \
elm = NULL; \
return (elm); \
} \
\
static __inline struct type * \
name##_SPLAY_MIN_MAX(struct name *head, int val) \
{ \
name##_SPLAY_MINMAX(head, val); \
return (SPLAY_ROOT(head)); \
}
/* Main splay operation.
* Moves node close to the key of elm to top
*/
#define SPLAY_GENERATE(name, type, field, cmp) \
struct type * \
name##_SPLAY_INSERT(struct name *head, struct type *elm) \
{ \
if (SPLAY_EMPTY(head)) { \
SPLAY_LEFT(elm, field) = SPLAY_RIGHT(elm, field) = NULL; \
} else { \
int __comp; \
name##_SPLAY(head, elm); \
__comp = (cmp)(elm, (head)->sph_root); \
if(__comp < 0) { \
SPLAY_LEFT(elm, field) = SPLAY_LEFT((head)->sph_root, field);\
SPLAY_RIGHT(elm, field) = (head)->sph_root; \
SPLAY_LEFT((head)->sph_root, field) = NULL; \
} else if (__comp > 0) { \
SPLAY_RIGHT(elm, field) = SPLAY_RIGHT((head)->sph_root, field);\
SPLAY_LEFT(elm, field) = (head)->sph_root; \
SPLAY_RIGHT((head)->sph_root, field) = NULL; \
} else \
return ((head)->sph_root); \
} \
(head)->sph_root = (elm); \
return (NULL); \
} \
\
struct type * \
name##_SPLAY_REMOVE(struct name *head, struct type *elm) \
{ \
struct type *__tmp; \
if (SPLAY_EMPTY(head)) \
return (NULL); \
name##_SPLAY(head, elm); \
if ((cmp)(elm, (head)->sph_root) == 0) { \
if (SPLAY_LEFT((head)->sph_root, field) == NULL) { \
(head)->sph_root = SPLAY_RIGHT((head)->sph_root, field);\
} else { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
(head)->sph_root = SPLAY_LEFT((head)->sph_root, field);\
name##_SPLAY(head, elm); \
SPLAY_RIGHT((head)->sph_root, field) = __tmp; \
} \
return (elm); \
} \
return (NULL); \
} \
\
void \
name##_SPLAY(struct name *head, struct type *elm) \
{ \
struct type __node, *__left, *__right, *__tmp; \
int __comp; \
\
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
__left = __right = &__node; \
\
while ((__comp = (cmp)(elm, (head)->sph_root)) != 0) { \
if (__comp < 0) { \
__tmp = SPLAY_LEFT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if ((cmp)(elm, __tmp) < 0){ \
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKLEFT(head, __right, field); \
} else if (__comp > 0) { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if ((cmp)(elm, __tmp) > 0){ \
SPLAY_ROTATE_LEFT(head, __tmp, field); \
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKRIGHT(head, __left, field); \
} \
} \
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
} \
\
/* Splay with either the minimum or the maximum element \
* Used to find minimum or maximum element in tree. \
*/ \
void name##_SPLAY_MINMAX(struct name *head, int __comp) \
{ \
struct type __node, *__left, *__right, *__tmp; \
\
SPLAY_LEFT(&__node, field) = SPLAY_RIGHT(&__node, field) = NULL;\
__left = __right = &__node; \
\
while (1) { \
if (__comp < 0) { \
__tmp = SPLAY_LEFT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if (__comp < 0){ \
SPLAY_ROTATE_RIGHT(head, __tmp, field); \
if (SPLAY_LEFT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKLEFT(head, __right, field); \
} else if (__comp > 0) { \
__tmp = SPLAY_RIGHT((head)->sph_root, field); \
if (__tmp == NULL) \
break; \
if (__comp > 0) { \
SPLAY_ROTATE_LEFT(head, __tmp, field); \
if (SPLAY_RIGHT((head)->sph_root, field) == NULL)\
break; \
} \
SPLAY_LINKRIGHT(head, __left, field); \
} \
} \
SPLAY_ASSEMBLE(head, &__node, __left, __right, field); \
}
#define SPLAY_NEGINF -1
#define SPLAY_INF 1
#define SPLAY_INSERT(name, x, y) name##_SPLAY_INSERT(x, y)
#define SPLAY_REMOVE(name, x, y) name##_SPLAY_REMOVE(x, y)
#define SPLAY_FIND(name, x, y) name##_SPLAY_FIND(x, y)
#define SPLAY_NEXT(name, x, y) name##_SPLAY_NEXT(x, y)
#define SPLAY_MIN(name, x) (SPLAY_EMPTY(x) ? NULL \
: name##_SPLAY_MIN_MAX(x, SPLAY_NEGINF))
#define SPLAY_MAX(name, x) (SPLAY_EMPTY(x) ? NULL \
: name##_SPLAY_MIN_MAX(x, SPLAY_INF))
#define SPLAY_FOREACH(x, name, head) \
for ((x) = SPLAY_MIN(name, head); \
(x) != NULL; \
(x) = SPLAY_NEXT(name, head, x))
/* Macros that define a red-black tree */
#define RB_HEAD(name, type) \
struct name { \
struct type *rbh_root; /* root of the tree */ \
}
#define RB_INITIALIZER(root) \
{ NULL }
#define RB_INIT(root) do { \
(root)->rbh_root = NULL; \
} while (/*CONSTCOND*/ 0)
#define RB_BLACK 0
#define RB_RED 1
#define RB_ENTRY(type) \
struct { \
struct type *rbe_left; /* left element */ \
struct type *rbe_right; /* right element */ \
struct type *rbe_parent; /* parent element */ \
int rbe_color; /* node color */ \
}
#define RB_LEFT(elm, field) (elm)->field.rbe_left
#define RB_RIGHT(elm, field) (elm)->field.rbe_right
#define RB_PARENT(elm, field) (elm)->field.rbe_parent
#define RB_COLOR(elm, field) (elm)->field.rbe_color
#define RB_ROOT(head) (head)->rbh_root
#define RB_EMPTY(head) (RB_ROOT(head) == NULL)
#define RB_SET(elm, parent, field) do { \
RB_PARENT(elm, field) = parent; \
RB_LEFT(elm, field) = RB_RIGHT(elm, field) = NULL; \
RB_COLOR(elm, field) = RB_RED; \
} while (/*CONSTCOND*/ 0)
#define RB_SET_BLACKRED(black, red, field) do { \
RB_COLOR(black, field) = RB_BLACK; \
RB_COLOR(red, field) = RB_RED; \
} while (/*CONSTCOND*/ 0)
#ifndef RB_AUGMENT
#define RB_AUGMENT(x) do {} while (0)
#endif
#define RB_ROTATE_LEFT(head, elm, tmp, field) do { \
(tmp) = RB_RIGHT(elm, field); \
if ((RB_RIGHT(elm, field) = RB_LEFT(tmp, field)) != NULL) { \
RB_PARENT(RB_LEFT(tmp, field), field) = (elm); \
} \
RB_AUGMENT(elm); \
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
else \
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
} else \
(head)->rbh_root = (tmp); \
RB_LEFT(tmp, field) = (elm); \
RB_PARENT(elm, field) = (tmp); \
RB_AUGMENT(tmp); \
if ((RB_PARENT(tmp, field))) \
RB_AUGMENT(RB_PARENT(tmp, field)); \
} while (/*CONSTCOND*/ 0)
#define RB_ROTATE_RIGHT(head, elm, tmp, field) do { \
(tmp) = RB_LEFT(elm, field); \
if ((RB_LEFT(elm, field) = RB_RIGHT(tmp, field)) != NULL) { \
RB_PARENT(RB_RIGHT(tmp, field), field) = (elm); \
} \
RB_AUGMENT(elm); \
if ((RB_PARENT(tmp, field) = RB_PARENT(elm, field)) != NULL) { \
if ((elm) == RB_LEFT(RB_PARENT(elm, field), field)) \
RB_LEFT(RB_PARENT(elm, field), field) = (tmp); \
else \
RB_RIGHT(RB_PARENT(elm, field), field) = (tmp); \
} else \
(head)->rbh_root = (tmp); \
RB_RIGHT(tmp, field) = (elm); \
RB_PARENT(elm, field) = (tmp); \
RB_AUGMENT(tmp); \
if ((RB_PARENT(tmp, field))) \
RB_AUGMENT(RB_PARENT(tmp, field)); \
} while (/*CONSTCOND*/ 0)
/* Generates prototypes and inline functions */
#define RB_PROTOTYPE(name, type, field, cmp) \
RB_PROTOTYPE_INTERNAL(name, type, field, cmp,)
#define RB_PROTOTYPE_STATIC(name, type, field, cmp) \
RB_PROTOTYPE_INTERNAL(name, type, field, cmp, static)
#define RB_PROTOTYPE_INTERNAL(name, type, field, cmp, attr) \
attr void name##_RB_INSERT_COLOR(struct name *, struct type *); \
attr void name##_RB_REMOVE_COLOR(struct name *, struct type *, struct type *);\
attr struct type *name##_RB_REMOVE(struct name *, struct type *); \
attr struct type *name##_RB_INSERT(struct name *, struct type *); \
attr struct type *name##_RB_FIND(struct name *, struct type *); \
attr struct type *name##_RB_NFIND(struct name *, struct type *); \
attr struct type *name##_RB_NEXT(struct type *); \
attr struct type *name##_RB_PREV(struct type *); \
attr struct type *name##_RB_MINMAX(struct name *, int); \
\
/* Main rb operation.
* Moves node close to the key of elm to top
*/
#define RB_GENERATE(name, type, field, cmp) \
RB_GENERATE_INTERNAL(name, type, field, cmp,)
#define RB_GENERATE_STATIC(name, type, field, cmp) \
RB_GENERATE_INTERNAL(name, type, field, cmp, static)
#define RB_GENERATE_INTERNAL(name, type, field, cmp, attr) \
attr void \
name##_RB_INSERT_COLOR(struct name *head, struct type *elm) \
{ \
struct type *parent, *gparent, *tmp; \
while ((parent = RB_PARENT(elm, field)) != NULL && \
RB_COLOR(parent, field) == RB_RED) { \
gparent = RB_PARENT(parent, field); \
if (parent == RB_LEFT(gparent, field)) { \
tmp = RB_RIGHT(gparent, field); \
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
RB_COLOR(tmp, field) = RB_BLACK; \
RB_SET_BLACKRED(parent, gparent, field);\
elm = gparent; \
continue; \
} \
if (RB_RIGHT(parent, field) == elm) { \
RB_ROTATE_LEFT(head, parent, tmp, field);\
tmp = parent; \
parent = elm; \
elm = tmp; \
} \
RB_SET_BLACKRED(parent, gparent, field); \
RB_ROTATE_RIGHT(head, gparent, tmp, field); \
} else { \
tmp = RB_LEFT(gparent, field); \
if (tmp && RB_COLOR(tmp, field) == RB_RED) { \
RB_COLOR(tmp, field) = RB_BLACK; \
RB_SET_BLACKRED(parent, gparent, field);\
elm = gparent; \
continue; \
} \
if (RB_LEFT(parent, field) == elm) { \
RB_ROTATE_RIGHT(head, parent, tmp, field);\
tmp = parent; \
parent = elm; \
elm = tmp; \
} \
RB_SET_BLACKRED(parent, gparent, field); \
RB_ROTATE_LEFT(head, gparent, tmp, field); \
} \
} \
RB_COLOR(head->rbh_root, field) = RB_BLACK; \
} \
\
attr void \
name##_RB_REMOVE_COLOR(struct name *head, struct type *parent, struct type *elm) \
{ \
struct type *tmp; \
while ((elm == NULL || RB_COLOR(elm, field) == RB_BLACK) && \
elm != RB_ROOT(head)) { \
if (RB_LEFT(parent, field) == elm) { \
tmp = RB_RIGHT(parent, field); \
if (RB_COLOR(tmp, field) == RB_RED) { \
RB_SET_BLACKRED(tmp, parent, field); \
RB_ROTATE_LEFT(head, parent, tmp, field);\
tmp = RB_RIGHT(parent, field); \
} \
if ((RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
(RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
RB_COLOR(tmp, field) = RB_RED; \
elm = parent; \
parent = RB_PARENT(elm, field); \
} else { \
if (RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK) {\
struct type *oleft; \
if ((oleft = RB_LEFT(tmp, field)) \
!= NULL) \
RB_COLOR(oleft, field) = RB_BLACK;\
RB_COLOR(tmp, field) = RB_RED; \
RB_ROTATE_RIGHT(head, tmp, oleft, field);\
tmp = RB_RIGHT(parent, field); \
} \
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
RB_COLOR(parent, field) = RB_BLACK; \
if (RB_RIGHT(tmp, field)) \
RB_COLOR(RB_RIGHT(tmp, field), field) = RB_BLACK;\
RB_ROTATE_LEFT(head, parent, tmp, field);\
elm = RB_ROOT(head); \
break; \
} \
} else { \
tmp = RB_LEFT(parent, field); \
if (RB_COLOR(tmp, field) == RB_RED) { \
RB_SET_BLACKRED(tmp, parent, field); \
RB_ROTATE_RIGHT(head, parent, tmp, field);\
tmp = RB_LEFT(parent, field); \
} \
if ((RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) &&\
(RB_RIGHT(tmp, field) == NULL || \
RB_COLOR(RB_RIGHT(tmp, field), field) == RB_BLACK)) {\
RB_COLOR(tmp, field) = RB_RED; \
elm = parent; \
parent = RB_PARENT(elm, field); \
} else { \
if (RB_LEFT(tmp, field) == NULL || \
RB_COLOR(RB_LEFT(tmp, field), field) == RB_BLACK) {\
struct type *oright; \
if ((oright = RB_RIGHT(tmp, field)) \
!= NULL) \
RB_COLOR(oright, field) = RB_BLACK;\
RB_COLOR(tmp, field) = RB_RED; \
RB_ROTATE_LEFT(head, tmp, oright, field);\
tmp = RB_LEFT(parent, field); \
} \
RB_COLOR(tmp, field) = RB_COLOR(parent, field);\
RB_COLOR(parent, field) = RB_BLACK; \
if (RB_LEFT(tmp, field)) \
RB_COLOR(RB_LEFT(tmp, field), field) = RB_BLACK;\
RB_ROTATE_RIGHT(head, parent, tmp, field);\
elm = RB_ROOT(head); \
break; \
} \
} \
} \
if (elm) \
RB_COLOR(elm, field) = RB_BLACK; \
} \
\
attr struct type * \
name##_RB_REMOVE(struct name *head, struct type *elm) \
{ \
struct type *child, *parent, *old = elm; \
int color; \
if (RB_LEFT(elm, field) == NULL) \
child = RB_RIGHT(elm, field); \
else if (RB_RIGHT(elm, field) == NULL) \
child = RB_LEFT(elm, field); \
else { \
struct type *left; \
elm = RB_RIGHT(elm, field); \
while ((left = RB_LEFT(elm, field)) != NULL) \
elm = left; \
child = RB_RIGHT(elm, field); \
parent = RB_PARENT(elm, field); \
color = RB_COLOR(elm, field); \
if (child) \
RB_PARENT(child, field) = parent; \
if (parent) { \
if (RB_LEFT(parent, field) == elm) \
RB_LEFT(parent, field) = child; \
else \
RB_RIGHT(parent, field) = child; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = child; \
if (RB_PARENT(elm, field) == old) \
parent = elm; \
(elm)->field = (old)->field; \
if (RB_PARENT(old, field)) { \
if (RB_LEFT(RB_PARENT(old, field), field) == old)\
RB_LEFT(RB_PARENT(old, field), field) = elm;\
else \
RB_RIGHT(RB_PARENT(old, field), field) = elm;\
RB_AUGMENT(RB_PARENT(old, field)); \
} else \
RB_ROOT(head) = elm; \
RB_PARENT(RB_LEFT(old, field), field) = elm; \
if (RB_RIGHT(old, field)) \
RB_PARENT(RB_RIGHT(old, field), field) = elm; \
if (parent) { \
left = parent; \
do { \
RB_AUGMENT(left); \
} while ((left = RB_PARENT(left, field)) != NULL); \
} \
goto color; \
} \
parent = RB_PARENT(elm, field); \
color = RB_COLOR(elm, field); \
if (child) \
RB_PARENT(child, field) = parent; \
if (parent) { \
if (RB_LEFT(parent, field) == elm) \
RB_LEFT(parent, field) = child; \
else \
RB_RIGHT(parent, field) = child; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = child; \
color: \
if (color == RB_BLACK) \
name##_RB_REMOVE_COLOR(head, parent, child); \
return (old); \
} \
\
/* Inserts a node into the RB tree */ \
attr struct type * \
name##_RB_INSERT(struct name *head, struct type *elm) \
{ \
struct type *tmp; \
struct type *parent = NULL; \
int comp = 0; \
tmp = RB_ROOT(head); \
while (tmp) { \
parent = tmp; \
comp = (cmp)(elm, parent); \
if (comp < 0) \
tmp = RB_LEFT(tmp, field); \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
RB_SET(elm, parent, field); \
if (parent != NULL) { \
if (comp < 0) \
RB_LEFT(parent, field) = elm; \
else \
RB_RIGHT(parent, field) = elm; \
RB_AUGMENT(parent); \
} else \
RB_ROOT(head) = elm; \
name##_RB_INSERT_COLOR(head, elm); \
return (NULL); \
} \
\
/* Finds the node with the same key as elm */ \
attr struct type * \
name##_RB_FIND(struct name *head, struct type *elm) \
{ \
struct type *tmp = RB_ROOT(head); \
int comp; \
while (tmp) { \
comp = cmp(elm, tmp); \
if (comp < 0) \
tmp = RB_LEFT(tmp, field); \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
return (NULL); \
} \
\
/* Finds the first node greater than or equal to the search key */ \
attr struct type * \
name##_RB_NFIND(struct name *head, struct type *elm) \
{ \
struct type *tmp = RB_ROOT(head); \
struct type *res = NULL; \
int comp; \
while (tmp) { \
comp = cmp(elm, tmp); \
if (comp < 0) { \
res = tmp; \
tmp = RB_LEFT(tmp, field); \
} \
else if (comp > 0) \
tmp = RB_RIGHT(tmp, field); \
else \
return (tmp); \
} \
return (res); \
} \
\
/* ARGSUSED */ \
attr struct type * \
name##_RB_NEXT(struct type *elm) \
{ \
if (RB_RIGHT(elm, field)) { \
elm = RB_RIGHT(elm, field); \
while (RB_LEFT(elm, field)) \
elm = RB_LEFT(elm, field); \
} else { \
if (RB_PARENT(elm, field) && \
(elm == RB_LEFT(RB_PARENT(elm, field), field))) \
elm = RB_PARENT(elm, field); \
else { \
while (RB_PARENT(elm, field) && \
(elm == RB_RIGHT(RB_PARENT(elm, field), field)))\
elm = RB_PARENT(elm, field); \
elm = RB_PARENT(elm, field); \
} \
} \
return (elm); \
} \
\
/* ARGSUSED */ \
attr struct type * \
name##_RB_PREV(struct type *elm) \
{ \
if (RB_LEFT(elm, field)) { \
elm = RB_LEFT(elm, field); \
while (RB_RIGHT(elm, field)) \
elm = RB_RIGHT(elm, field); \
} else { \
if (RB_PARENT(elm, field) && \
(elm == RB_RIGHT(RB_PARENT(elm, field), field))) \
elm = RB_PARENT(elm, field); \
else { \
while (RB_PARENT(elm, field) && \
(elm == RB_LEFT(RB_PARENT(elm, field), field)))\
elm = RB_PARENT(elm, field); \
elm = RB_PARENT(elm, field); \
} \
} \
return (elm); \
} \
\
attr struct type * \
name##_RB_MINMAX(struct name *head, int val) \
{ \
struct type *tmp = RB_ROOT(head); \
struct type *parent = NULL; \
while (tmp) { \
parent = tmp; \
if (val < 0) \
tmp = RB_LEFT(tmp, field); \
else \
tmp = RB_RIGHT(tmp, field); \
} \
return (parent); \
}
#define RB_NEGINF -1
#define RB_INF 1
#define RB_INSERT(name, x, y) name##_RB_INSERT(x, y)
#define RB_REMOVE(name, x, y) name##_RB_REMOVE(x, y)
#define RB_FIND(name, x, y) name##_RB_FIND(x, y)
#define RB_NFIND(name, x, y) name##_RB_NFIND(x, y)
#define RB_NEXT(name, x, y) name##_RB_NEXT(y)
#define RB_PREV(name, x, y) name##_RB_PREV(y)
#define RB_MIN(name, x) name##_RB_MINMAX(x, RB_NEGINF)
#define RB_MAX(name, x) name##_RB_MINMAX(x, RB_INF)
#define RB_FOREACH(x, name, head) \
for ((x) = RB_MIN(name, head); \
(x) != NULL; \
(x) = name##_RB_NEXT(x))
#define RB_FOREACH_FROM(x, name, y) \
for ((x) = (y); \
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_SAFE(x, name, head, y) \
for ((x) = RB_MIN(name, head); \
((x) != NULL) && ((y) = name##_RB_NEXT(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_REVERSE(x, name, head) \
for ((x) = RB_MAX(name, head); \
(x) != NULL; \
(x) = name##_RB_PREV(x))
#define RB_FOREACH_REVERSE_FROM(x, name, y) \
for ((x) = (y); \
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
(x) = (y))
#define RB_FOREACH_REVERSE_SAFE(x, name, head, y) \
for ((x) = RB_MAX(name, head); \
((x) != NULL) && ((y) = name##_RB_PREV(x), (x) != NULL); \
(x) = (y))
#endif

View File

@ -0,0 +1,28 @@
Copyright (c) 2005-2011, Google Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are
met:
Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above
copyright notice, this list of conditions and the following disclaimer
in the documentation and/or other materials provided with the
distribution.
Neither the name of Google Inc. nor the names of its
contributors may be used to endorse or promote products derived from
this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2020-2023 HexHacking Team
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.

View File

@ -0,0 +1,910 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-10-04.
#include "xdl.h"
#include <android/api-level.h>
#include <elf.h>
#include <fcntl.h>
#include <inttypes.h>
#include <link.h>
#include <pthread.h>
#include <stdbool.h>
#include <stddef.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/auxv.h>
#include <sys/mman.h>
#include <sys/prctl.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#include "xdl_iterate.h"
#include "xdl_linker.h"
#include "xdl_lzma.h"
#include "xdl_util.h"
#ifndef __LP64__
#define XDL_LIB_PATH "/system/lib"
#else
#define XDL_LIB_PATH "/system/lib64"
#endif
#define XDL_DYNSYM_IS_EXPORT_SYM(shndx) (SHN_UNDEF != (shndx))
#define XDL_SYMTAB_IS_EXPORT_SYM(shndx) \
(SHN_UNDEF != (shndx) && !((shndx) >= SHN_LORESERVE && (shndx) <= SHN_HIRESERVE))
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct xdl {
char *pathname;
uintptr_t load_bias;
const ElfW(Phdr) *dlpi_phdr;
ElfW(Half) dlpi_phnum;
struct xdl *next; // to next xdl obj for cache in xdl_addr()
void *linker_handle; // hold handle returned by xdl_linker_force_dlopen()
//
// (1) for searching symbols from .dynsym
//
bool dynsym_try_load;
ElfW(Sym) *dynsym; // .dynsym
const char *dynstr; // .dynstr
// .hash (SYSV hash for .dynstr)
struct {
const uint32_t *buckets;
uint32_t buckets_cnt;
const uint32_t *chains;
uint32_t chains_cnt;
} sysv_hash;
// .gnu.hash (GNU hash for .dynstr)
struct {
const uint32_t *buckets;
uint32_t buckets_cnt;
const uint32_t *chains;
uint32_t symoffset;
const ElfW(Addr) *bloom;
uint32_t bloom_cnt;
uint32_t bloom_shift;
} gnu_hash;
//
// (2) for searching symbols from .symtab
//
bool symtab_try_load;
uintptr_t base;
ElfW(Sym) *symtab; // .symtab
size_t symtab_cnt;
char *strtab; // .strtab
size_t strtab_sz;
} xdl_t;
#pragma clang diagnostic pop
// load from memory
static int xdl_dynsym_load(xdl_t *self) {
// find the dynamic segment
ElfW(Dyn) *dynamic = NULL;
for (size_t i = 0; i < self->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
if (PT_DYNAMIC == phdr->p_type) {
dynamic = (ElfW(Dyn) *)(self->load_bias + phdr->p_vaddr);
break;
}
}
if (NULL == dynamic) return -1;
// iterate the dynamic segment
for (ElfW(Dyn) *entry = dynamic; entry && entry->d_tag != DT_NULL; entry++) {
switch (entry->d_tag) {
case DT_SYMTAB: //.dynsym
self->dynsym = (ElfW(Sym) *)(self->load_bias + entry->d_un.d_ptr);
break;
case DT_STRTAB: //.dynstr
self->dynstr = (const char *)(self->load_bias + entry->d_un.d_ptr);
break;
case DT_HASH: //.hash
self->sysv_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
self->sysv_hash.chains_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
self->sysv_hash.buckets = &(((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2]);
self->sysv_hash.chains = &(self->sysv_hash.buckets[self->sysv_hash.buckets_cnt]);
break;
case DT_GNU_HASH: //.gnu.hash
self->gnu_hash.buckets_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[0];
self->gnu_hash.symoffset = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[1];
self->gnu_hash.bloom_cnt = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[2];
self->gnu_hash.bloom_shift = ((const uint32_t *)(self->load_bias + entry->d_un.d_ptr))[3];
self->gnu_hash.bloom = (const ElfW(Addr) *)(self->load_bias + entry->d_un.d_ptr + 16);
self->gnu_hash.buckets = (const uint32_t *)(&(self->gnu_hash.bloom[self->gnu_hash.bloom_cnt]));
self->gnu_hash.chains = (const uint32_t *)(&(self->gnu_hash.buckets[self->gnu_hash.buckets_cnt]));
break;
default:
break;
}
}
if (NULL == self->dynsym || NULL == self->dynstr ||
(0 == self->sysv_hash.buckets_cnt && 0 == self->gnu_hash.buckets_cnt)) {
self->dynsym = NULL;
self->dynstr = NULL;
self->sysv_hash.buckets_cnt = 0;
self->gnu_hash.buckets_cnt = 0;
return -1;
}
return 0;
}
static void *xdl_read_file_to_heap(int file_fd, size_t file_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= file_sz) return NULL;
if (data_offset + data_len > file_sz) return NULL;
if (data_offset != (size_t)lseek(file_fd, (off_t)data_offset, SEEK_SET)) return NULL;
void *data = malloc(data_len);
if (NULL == data) return NULL;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wgnu-statement-expression"
if ((ssize_t)data_len != XDL_UTIL_TEMP_FAILURE_RETRY(read(file_fd, data, data_len)))
#pragma clang diagnostic pop
{
free(data);
return NULL;
}
return data;
}
static void *xdl_read_file_to_heap_by_section(int file_fd, size_t file_sz, ElfW(Shdr) *shdr) {
return xdl_read_file_to_heap(file_fd, file_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
static void *xdl_read_memory_to_heap(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= mem_sz) return NULL;
if (data_offset + data_len > mem_sz) return NULL;
void *data = malloc(data_len);
if (NULL == data) return NULL;
memcpy(data, (void *)((uintptr_t)mem + data_offset), data_len);
return data;
}
static void *xdl_read_memory_to_heap_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
return xdl_read_memory_to_heap(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
static void *xdl_get_memory(void *mem, size_t mem_sz, size_t data_offset, size_t data_len) {
if (0 == data_len) return NULL;
if (data_offset >= mem_sz) return NULL;
if (data_offset + data_len > mem_sz) return NULL;
return (void *)((uintptr_t)mem + data_offset);
}
static void *xdl_get_memory_by_section(void *mem, size_t mem_sz, ElfW(Shdr) *shdr) {
return xdl_get_memory(mem, mem_sz, (size_t)shdr->sh_offset, shdr->sh_size);
}
// load from disk and memory
static int xdl_symtab_load_from_debugdata(xdl_t *self, int file_fd, size_t file_sz,
ElfW(Shdr) *shdr_debugdata) {
void *debugdata = NULL;
ElfW(Shdr) *shdrs = NULL;
int r = -1;
// get zipped .gnu_debugdata
uint8_t *debugdata_zip = (uint8_t *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_debugdata);
if (NULL == debugdata_zip) return -1;
// get unzipped .gnu_debugdata
size_t debugdata_sz;
if (0 != xdl_lzma_decompress(debugdata_zip, shdr_debugdata->sh_size, (uint8_t **)&debugdata, &debugdata_sz))
goto end;
// get ELF header
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)debugdata;
if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
// get section headers
shdrs = (ElfW(Shdr) *)xdl_read_memory_to_heap(debugdata, debugdata_sz, (size_t)ehdr->e_shoff,
ehdr->e_shentsize * ehdr->e_shnum);
if (NULL == shdrs) goto end;
// get .shstrtab
if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
char *shstrtab = (char *)xdl_get_memory_by_section(debugdata, debugdata_sz, shdrs + ehdr->e_shstrndx);
if (NULL == shstrtab) goto end;
// find .symtab & .strtab
for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
char *shdr_name = shstrtab + shdr->sh_name;
if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
// get & check associated .strtab section
if (shdr->sh_link >= ehdr->e_shnum) continue;
ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
if (SHT_STRTAB != shdr_strtab->sh_type) continue;
// get .symtab & .strtab
ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr);
if (NULL == symtab) continue;
char *strtab = (char *)xdl_read_memory_to_heap_by_section(debugdata, debugdata_sz, shdr_strtab);
if (NULL == strtab) {
free(symtab);
continue;
}
// OK
self->symtab = symtab;
self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
self->strtab = strtab;
self->strtab_sz = shdr_strtab->sh_size;
r = 0;
break;
}
}
end:
free(debugdata_zip);
if (NULL != debugdata) free(debugdata);
if (NULL != shdrs) free(shdrs);
return r;
}
// load from disk and memory
static int xdl_symtab_load(xdl_t *self) {
if ('[' == self->pathname[0]) return -1;
int r = -1;
ElfW(Shdr) *shdrs = NULL;
char *shstrtab = NULL;
// get base address
uintptr_t vaddr_min = UINTPTR_MAX;
for (size_t i = 0; i < self->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(self->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (vaddr_min > phdr->p_vaddr) vaddr_min = phdr->p_vaddr;
}
}
if (UINTPTR_MAX == vaddr_min) return -1;
self->base = self->load_bias + vaddr_min;
// open file
int flags = O_RDONLY | O_CLOEXEC;
int file_fd;
if ('/' == self->pathname[0]) {
file_fd = open(self->pathname, flags);
} else {
char full_pathname[1024];
// try the fast method
snprintf(full_pathname, sizeof(full_pathname), "%s/%s", XDL_LIB_PATH, self->pathname);
file_fd = open(full_pathname, flags);
if (file_fd < 0) {
// try the slow method
if (0 != xdl_iterate_get_full_pathname(self->base, full_pathname, sizeof(full_pathname))) return -1;
file_fd = open(full_pathname, flags);
}
}
if (file_fd < 0) return -1;
struct stat st;
if (0 != fstat(file_fd, &st)) goto end;
size_t file_sz = (size_t)st.st_size;
// get ELF header
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)self->base;
if (0 == ehdr->e_shnum || ehdr->e_shentsize != sizeof(ElfW(Shdr))) goto end;
// get section headers
shdrs = (ElfW(Shdr) *)xdl_read_file_to_heap(file_fd, file_sz, (size_t)ehdr->e_shoff,
ehdr->e_shentsize * ehdr->e_shnum);
if (NULL == shdrs) goto end;
// get .shstrtab
if (SHN_UNDEF == ehdr->e_shstrndx || ehdr->e_shstrndx >= ehdr->e_shnum) goto end;
shstrtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdrs + ehdr->e_shstrndx);
if (NULL == shstrtab) goto end;
// find .symtab & .strtab
for (ElfW(Shdr) *shdr = shdrs; shdr < shdrs + ehdr->e_shnum; shdr++) {
char *shdr_name = shstrtab + shdr->sh_name;
if (SHT_SYMTAB == shdr->sh_type && 0 == strcmp(".symtab", shdr_name)) {
// get & check associated .strtab section
if (shdr->sh_link >= ehdr->e_shnum) continue;
ElfW(Shdr) *shdr_strtab = shdrs + shdr->sh_link;
if (SHT_STRTAB != shdr_strtab->sh_type) continue;
// get .symtab & .strtab
ElfW(Sym) *symtab = (ElfW(Sym) *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr);
if (NULL == symtab) continue;
char *strtab = (char *)xdl_read_file_to_heap_by_section(file_fd, file_sz, shdr_strtab);
if (NULL == strtab) {
free(symtab);
continue;
}
// OK
self->symtab = symtab;
self->symtab_cnt = shdr->sh_size / shdr->sh_entsize;
self->strtab = strtab;
self->strtab_sz = shdr_strtab->sh_size;
r = 0;
break;
} else if (SHT_PROGBITS == shdr->sh_type && 0 == strcmp(".gnu_debugdata", shdr_name)) {
if (0 == xdl_symtab_load_from_debugdata(self, file_fd, file_sz, shdr)) {
// OK
r = 0;
break;
}
}
}
end:
close(file_fd);
if (NULL != shdrs) free(shdrs);
if (NULL != shstrtab) free(shstrtab);
return r;
}
static xdl_t *xdl_find_from_auxv(unsigned long type, const char *pathname) {
if (NULL == getauxval) return NULL; // API level < 18
uintptr_t val = (uintptr_t)getauxval(type);
if (0 == val) return NULL;
// get base
uintptr_t base = (AT_PHDR == type ? (val & (~0xffful)) : val);
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return NULL;
// ELF info
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
const ElfW(Phdr) *dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
ElfW(Half) dlpi_phnum = ehdr->e_phnum;
// get bias
uintptr_t min_vaddr = UINTPTR_MAX;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
}
}
if (UINTPTR_MAX == min_vaddr || base < min_vaddr) return NULL;
uintptr_t load_bias = base - min_vaddr;
// create xDL object
xdl_t *self;
if (NULL == (self = calloc(1, sizeof(xdl_t)))) return NULL;
if (NULL == (self->pathname = strdup(pathname))) {
free(self);
return NULL;
}
self->load_bias = load_bias;
self->dlpi_phdr = dlpi_phdr;
self->dlpi_phnum = dlpi_phnum;
self->dynsym_try_load = false;
self->symtab_try_load = false;
return self;
}
static int xdl_find_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
xdl_t **self = (xdl_t **)*pkg++;
const char *filename = (const char *)*pkg;
// check load_bias
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0;
// check pathname
if ('[' == filename[0]) {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
} else if ('/' == filename[0]) {
if ('/' == info->dlpi_name[0]) {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
} else {
if (!xdl_util_ends_with(filename, info->dlpi_name)) return 0;
}
} else {
if ('/' == info->dlpi_name[0]) {
if (!xdl_util_ends_with(info->dlpi_name, filename)) return 0;
} else {
if (0 != strcmp(info->dlpi_name, filename)) return 0;
}
}
// found the target ELF
if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // return failed
if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
free(*self);
*self = NULL;
return 1; // return failed
}
(*self)->load_bias = info->dlpi_addr;
(*self)->dlpi_phdr = info->dlpi_phdr;
(*self)->dlpi_phnum = info->dlpi_phnum;
(*self)->dynsym_try_load = false;
(*self)->symtab_try_load = false;
return 1; // return OK
}
static xdl_t *xdl_find(const char *filename) {
// from auxv (linker, vDSO)
xdl_t *self = NULL;
if (xdl_util_ends_with(filename, XDL_UTIL_LINKER_BASENAME))
self = xdl_find_from_auxv(AT_BASE, XDL_UTIL_LINKER_PATHNAME);
else if (xdl_util_ends_with(filename, XDL_UTIL_VDSO_BASENAME))
self = xdl_find_from_auxv(AT_SYSINFO_EHDR, XDL_UTIL_VDSO_BASENAME);
// from auxv (app_process)
const char *basename, *pathname;
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
if (xdl_util_get_api_level() < __ANDROID_API_L__) {
basename = XDL_UTIL_APP_PROCESS_BASENAME_K;
pathname = XDL_UTIL_APP_PROCESS_PATHNAME_K;
} else
#endif
{
basename = XDL_UTIL_APP_PROCESS_BASENAME;
pathname = XDL_UTIL_APP_PROCESS_PATHNAME;
}
if (xdl_util_ends_with(filename, basename)) self = xdl_find_from_auxv(AT_PHDR, pathname);
if (NULL != self) return self;
// from dl_iterate_phdr
uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)filename};
xdl_iterate_phdr(xdl_find_iterate_cb, pkg, XDL_DEFAULT);
return self;
}
static void *xdl_open_always_force(const char *filename) {
// always force dlopen()
void *linker_handle = xdl_linker_force_dlopen(filename);
if (NULL == linker_handle) return NULL;
// find
xdl_t *self = xdl_find(filename);
if (NULL == self)
dlclose(linker_handle);
else
self->linker_handle = linker_handle;
return (void *)self;
}
static void *xdl_open_try_force(const char *filename) {
// find
xdl_t *self = xdl_find(filename);
if (NULL != self) return (void *)self;
// try force dlopen()
void *linker_handle = xdl_linker_force_dlopen(filename);
if (NULL == linker_handle) return NULL;
// find again
self = xdl_find(filename);
if (NULL == self)
dlclose(linker_handle);
else
self->linker_handle = linker_handle;
return (void *)self;
}
void *xdl_open(const char *filename, int flags) {
if (NULL == filename) return NULL;
if (flags & XDL_ALWAYS_FORCE_LOAD)
return xdl_open_always_force(filename);
else if (flags & XDL_TRY_FORCE_LOAD)
return xdl_open_try_force(filename);
else
return xdl_find(filename);
}
void *xdl_close(void *handle) {
if (NULL == handle) return NULL;
xdl_t *self = (xdl_t *)handle;
if (NULL != self->pathname) free(self->pathname);
if (NULL != self->symtab) free(self->symtab);
if (NULL != self->strtab) free(self->strtab);
void *linker_handle = self->linker_handle;
free(self);
return linker_handle;
}
static uint32_t xdl_sysv_hash(const uint8_t *name) {
uint32_t h = 0, g;
while (*name) {
h = (h << 4) + *name++;
g = h & 0xf0000000;
h ^= g;
h ^= g >> 24;
}
return h;
}
static uint32_t xdl_gnu_hash(const uint8_t *name) {
uint32_t h = 5381;
while (*name) {
h += (h << 5) + *name++;
}
return h;
}
static ElfW(Sym) *xdl_dynsym_find_symbol_use_sysv_hash(xdl_t *self, const char *sym_name) {
uint32_t hash = xdl_sysv_hash((const uint8_t *)sym_name);
for (uint32_t i = self->sysv_hash.buckets[hash % self->sysv_hash.buckets_cnt]; 0 != i;
i = self->sysv_hash.chains[i]) {
ElfW(Sym) *sym = self->dynsym + i;
if (0 != strcmp(self->dynstr + sym->st_name, sym_name)) continue;
return sym;
}
return NULL;
}
static ElfW(Sym) *xdl_dynsym_find_symbol_use_gnu_hash(xdl_t *self, const char *sym_name) {
uint32_t hash = xdl_gnu_hash((const uint8_t *)sym_name);
static uint32_t elfclass_bits = sizeof(ElfW(Addr)) * 8;
size_t word = self->gnu_hash.bloom[(hash / elfclass_bits) % self->gnu_hash.bloom_cnt];
size_t mask = 0 | (size_t)1 << (hash % elfclass_bits) |
(size_t)1 << ((hash >> self->gnu_hash.bloom_shift) % elfclass_bits);
// if at least one bit is not set, this symbol is surely missing
if ((word & mask) != mask) return NULL;
// ignore STN_UNDEF
uint32_t i = self->gnu_hash.buckets[hash % self->gnu_hash.buckets_cnt];
if (i < self->gnu_hash.symoffset) return NULL;
// loop through the chain
while (1) {
ElfW(Sym) *sym = self->dynsym + i;
uint32_t sym_hash = self->gnu_hash.chains[i - self->gnu_hash.symoffset];
if ((hash | (uint32_t)1) == (sym_hash | (uint32_t)1)) {
if (0 == strcmp(self->dynstr + sym->st_name, sym_name)) {
return sym;
}
}
// chain ends with an element with the lowest bit set to 1
if (sym_hash & (uint32_t)1) break;
i++;
}
return NULL;
}
void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size) {
if (NULL == handle || NULL == symbol) return NULL;
if (NULL != symbol_size) *symbol_size = 0;
xdl_t *self = (xdl_t *)handle;
// load .dynsym only once
if (!self->dynsym_try_load) {
self->dynsym_try_load = true;
if (0 != xdl_dynsym_load(self)) return NULL;
}
// find symbol
if (NULL == self->dynsym) return NULL;
ElfW(Sym) *sym = NULL;
if (self->gnu_hash.buckets_cnt > 0) {
// use GNU hash (.gnu.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
sym = xdl_dynsym_find_symbol_use_gnu_hash(self, symbol);
}
if (NULL == sym && self->sysv_hash.buckets_cnt > 0) {
// use SYSV hash (.hash -> .dynsym -> .dynstr), O(x) + O(1) + O(1)
sym = xdl_dynsym_find_symbol_use_sysv_hash(self, symbol);
}
if (NULL == sym || !XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) return NULL;
if (NULL != symbol_size) *symbol_size = sym->st_size;
return (void *)(self->load_bias + sym->st_value);
}
// clang-format off
/*
* For internal symbols in .symtab, LLVM may add some suffixes (for example for thinLTO).
* The format of the suffix is: ".xxxx.[hash]". LLVM may add multiple suffixes at once.
* The symbol name after removing these all suffixes is called canonical name.
*
* Because the hash part in the suffix may change when recompiled, so here we only match
* the canonical name.
*
* IN ADDITION: According to C/C++ syntax, it is illegal for a function name to contain
* dot character('.'), either in the middle or at the end.
*
* samples:
*
* symbol name in .symtab lookup is match
* ---------------------- ---------------- --------
* abcd abc N
* abcd abcd Y
* abcd.llvm.10190306339727611508 abc N
* abcd.llvm.10190306339727611508 abcd Y
* abcd.llvm.10190306339727611508 abcd. N
* abcd.llvm.10190306339727611508 abcd.llvm Y
* abcd.llvm.10190306339727611508 abcd.llvm. N
* abcd.__uniq.513291356003753 abcd.__uniq.51329 N
* abcd.__uniq.513291356003753 abcd.__uniq.513291356003753 Y
*/
// clang-format on
static inline bool xdl_dsym_is_match(const char *str, const char *sym, size_t str_len) {
if (__predict_false(0 == str_len)) return false;
do {
if (*str != *sym) return __predict_false('.' == *str && '\0' == *sym);
str++;
sym++;
if ('\0' == *str) break;
} while (0 != --str_len);
return true;
}
void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size) {
if (NULL == handle || NULL == symbol) return NULL;
if (NULL != symbol_size) *symbol_size = 0;
xdl_t *self = (xdl_t *)handle;
// load .symtab only once
if (!self->symtab_try_load) {
self->symtab_try_load = true;
if (0 != xdl_symtab_load(self)) return NULL;
}
// find symbol
if (NULL == self->symtab) return NULL;
for (size_t i = 0; i < self->symtab_cnt; i++) {
ElfW(Sym) *sym = self->symtab + i;
if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) continue;
// if (0 != strncmp(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue;
if (!xdl_dsym_is_match(self->strtab + sym->st_name, symbol, self->strtab_sz - sym->st_name)) continue;
if (NULL != symbol_size) *symbol_size = sym->st_size;
return (void *)(self->load_bias + sym->st_value);
}
return NULL;
}
static bool xdl_elf_is_match(uintptr_t load_bias, const ElfW(Phdr) *dlpi_phdr, ElfW(Half) dlpi_phnum,
uintptr_t addr) {
if (addr < load_bias) return false;
uintptr_t vaddr = addr - load_bias;
for (size_t i = 0; i < dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(dlpi_phdr[i]);
if (PT_LOAD != phdr->p_type) continue;
if (phdr->p_vaddr <= vaddr && vaddr < phdr->p_vaddr + phdr->p_memsz) return true;
}
return false;
}
static int xdl_open_by_addr_iterate_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size;
uintptr_t *pkg = (uintptr_t *)arg;
xdl_t **self = (xdl_t **)*pkg++;
uintptr_t addr = *pkg;
if (xdl_elf_is_match(info->dlpi_addr, info->dlpi_phdr, info->dlpi_phnum, addr)) {
// found the target ELF
if (NULL == ((*self) = calloc(1, sizeof(xdl_t)))) return 1; // failed
if (NULL == ((*self)->pathname = strdup(info->dlpi_name))) {
free(*self);
*self = NULL;
return 1; // failed
}
(*self)->load_bias = info->dlpi_addr;
(*self)->dlpi_phdr = info->dlpi_phdr;
(*self)->dlpi_phnum = info->dlpi_phnum;
(*self)->dynsym_try_load = false;
(*self)->symtab_try_load = false;
return 1; // OK
}
return 0; // mismatch
}
static void *xdl_open_by_addr(void *addr) {
if (NULL == addr) return NULL;
xdl_t *self = NULL;
uintptr_t pkg[2] = {(uintptr_t)&self, (uintptr_t)addr};
xdl_iterate_phdr(xdl_open_by_addr_iterate_cb, pkg, XDL_DEFAULT);
return (void *)self;
}
static bool xdl_sym_is_match(ElfW(Sym) *sym, uintptr_t offset, bool is_symtab) {
if (is_symtab) {
if (!XDL_SYMTAB_IS_EXPORT_SYM(sym->st_shndx)) false;
} else {
if (!XDL_DYNSYM_IS_EXPORT_SYM(sym->st_shndx)) false;
}
return ELF_ST_TYPE(sym->st_info) != STT_TLS && offset >= sym->st_value &&
offset < sym->st_value + sym->st_size;
}
static ElfW(Sym) *xdl_sym_by_addr(void *handle, void *addr) {
xdl_t *self = (xdl_t *)handle;
// load .dynsym only once
if (!self->dynsym_try_load) {
self->dynsym_try_load = true;
if (0 != xdl_dynsym_load(self)) return NULL;
}
// find symbol
if (NULL == self->dynsym) return NULL;
uintptr_t offset = (uintptr_t)addr - self->load_bias;
if (self->gnu_hash.buckets_cnt > 0) {
const uint32_t *chains_all = self->gnu_hash.chains - self->gnu_hash.symoffset;
for (size_t i = 0; i < self->gnu_hash.buckets_cnt; i++) {
uint32_t n = self->gnu_hash.buckets[i];
if (n < self->gnu_hash.symoffset) continue;
do {
ElfW(Sym) *sym = self->dynsym + n;
if (xdl_sym_is_match(sym, offset, false)) return sym;
} while ((chains_all[n++] & 1) == 0);
}
} else if (self->sysv_hash.chains_cnt > 0) {
for (size_t i = 0; i < self->sysv_hash.chains_cnt; i++) {
ElfW(Sym) *sym = self->dynsym + i;
if (xdl_sym_is_match(sym, offset, false)) return sym;
}
}
return NULL;
}
static ElfW(Sym) *xdl_dsym_by_addr(void *handle, void *addr) {
xdl_t *self = (xdl_t *)handle;
// load .symtab only once
if (!self->symtab_try_load) {
self->symtab_try_load = true;
if (0 != xdl_symtab_load(self)) return NULL;
}
// find symbol
if (NULL == self->symtab) return NULL;
uintptr_t offset = (uintptr_t)addr - self->load_bias;
for (size_t i = 0; i < self->symtab_cnt; i++) {
ElfW(Sym) *sym = self->symtab + i;
if (xdl_sym_is_match(sym, offset, true)) return sym;
}
return NULL;
}
int xdl_addr(void *addr, xdl_info_t *info, void **cache) {
if (NULL == addr || NULL == info || NULL == cache) return 0;
memset(info, 0, sizeof(Dl_info));
// find handle from cache
xdl_t *handle = NULL;
for (handle = *((xdl_t **)cache); NULL != handle; handle = handle->next)
if (xdl_elf_is_match(handle->load_bias, handle->dlpi_phdr, handle->dlpi_phnum, (uintptr_t)addr)) break;
// create new handle, save handle to cache
if (NULL == handle) {
handle = (xdl_t *)xdl_open_by_addr(addr);
if (NULL == handle) return 0;
handle->next = *(xdl_t **)cache;
*(xdl_t **)cache = handle;
}
// we have at least: load_bias, pathname, dlpi_phdr, dlpi_phnum
info->dli_fbase = (void *)handle->load_bias;
info->dli_fname = handle->pathname;
info->dli_sname = NULL;
info->dli_saddr = 0;
info->dli_ssize = 0;
info->dlpi_phdr = handle->dlpi_phdr;
info->dlpi_phnum = (size_t)handle->dlpi_phnum;
// keep looking for: symbol name, symbol offset, symbol size
ElfW(Sym) *sym;
if (NULL != (sym = xdl_sym_by_addr((void *)handle, addr))) {
info->dli_sname = handle->dynstr + sym->st_name;
info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
info->dli_ssize = sym->st_size;
} else if (NULL != (sym = xdl_dsym_by_addr((void *)handle, addr))) {
info->dli_sname = handle->strtab + sym->st_name;
info->dli_saddr = (void *)(handle->load_bias + sym->st_value);
info->dli_ssize = sym->st_size;
}
return 1;
}
void xdl_addr_clean(void **cache) {
if (NULL == cache) return;
xdl_t *handle = *((xdl_t **)cache);
while (NULL != handle) {
xdl_t *tmp = handle;
handle = handle->next;
xdl_close(tmp);
}
*cache = NULL;
}
int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags) {
if (NULL == callback) return 0;
return xdl_iterate_phdr_impl(callback, data, flags);
}
int xdl_info(void *handle, int request, void *info) {
if (NULL == handle || XDL_DI_DLINFO != request || NULL == info) return -1;
xdl_t *self = (xdl_t *)handle;
xdl_info_t *dlinfo = (xdl_info_t *)info;
dlinfo->dli_fbase = (void *)self->load_bias;
dlinfo->dli_fname = self->pathname;
dlinfo->dli_sname = NULL;
dlinfo->dli_saddr = 0;
dlinfo->dli_ssize = 0;
dlinfo->dlpi_phdr = self->dlpi_phdr;
dlinfo->dlpi_phnum = (size_t)self->dlpi_phnum;
return 0;
}

View File

@ -0,0 +1,92 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-10-04.
//
// xDL version: 2.1.0
//
// xDL is an enhanced implementation of the Android DL series functions.
// For more information, documentation, and the latest version please check:
// https://github.com/hexhacking/xDL
//
#ifndef IO_GITHUB_HEXHACKING_XDL
#define IO_GITHUB_HEXHACKING_XDL
#include <dlfcn.h>
#include <link.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct {
// same as Dl_info:
const char *dli_fname; // Pathname of shared object that contains address.
void *dli_fbase; // Address at which shared object is loaded.
const char *dli_sname; // Name of nearest symbol with address lower than addr.
void *dli_saddr; // Exact address of symbol named in dli_sname.
// added by xDL:
size_t dli_ssize; // Symbol size of nearest symbol with address lower than addr.
const ElfW(Phdr) *dlpi_phdr; // Pointer to array of ELF program headers for this object.
size_t dlpi_phnum; // Number of items in dlpi_phdr.
} xdl_info_t;
//
// Default value for flags in both xdl_open() and xdl_iterate_phdr().
//
#define XDL_DEFAULT 0x00
//
// Enhanced dlopen() / dlclose() / dlsym().
//
#define XDL_TRY_FORCE_LOAD 0x01
#define XDL_ALWAYS_FORCE_LOAD 0x02
void *xdl_open(const char *filename, int flags);
void *xdl_close(void *handle);
void *xdl_sym(void *handle, const char *symbol, size_t *symbol_size);
void *xdl_dsym(void *handle, const char *symbol, size_t *symbol_size);
//
// Enhanced dladdr().
//
int xdl_addr(void *addr, xdl_info_t *info, void **cache);
void xdl_addr_clean(void **cache);
//
// Enhanced dl_iterate_phdr().
//
#define XDL_FULL_PATHNAME 0x01
int xdl_iterate_phdr(int (*callback)(struct dl_phdr_info *, size_t, void *), void *data, int flags);
//
// Custom dlinfo().
//
#define XDL_DI_DLINFO 1 // type of info: xdl_info_t
int xdl_info(void *handle, int request, void *info);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,297 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-10-04.
#include "xdl_iterate.h"
#include <android/api-level.h>
#include <ctype.h>
#include <dlfcn.h>
#include <elf.h>
#include <inttypes.h>
#include <link.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#include <sys/auxv.h>
#include "xdl.h"
#include "xdl_linker.h"
#include "xdl_util.h"
/*
* =========================================================================================================
* API-LEVEL ANDROID-VERSION SOLUTION
* =========================================================================================================
* 16 4.1 /proc/self/maps
* 17 4.2 /proc/self/maps
* 18 4.3 /proc/self/maps
* 19 4.4 /proc/self/maps
* 20 4.4W /proc/self/maps
* ---------------------------------------------------------------------------------------------------------
* 21 5.0 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
* 22 5.1 dl_iterate_phdr() + __dl__ZL10g_dl_mutex + linker/linker64 from getauxval(3)
* ---------------------------------------------------------------------------------------------------------
* 23 >= 6.0 dl_iterate_phdr() + linker/linker64 from getauxval(3)
* =========================================================================================================
*/
extern __attribute((weak)) int dl_iterate_phdr(int (*)(struct dl_phdr_info *, size_t, void *), void *);
extern __attribute((weak)) unsigned long int getauxval(unsigned long int);
static uintptr_t xdl_iterate_get_min_vaddr(struct dl_phdr_info *info) {
uintptr_t min_vaddr = UINTPTR_MAX;
for (size_t i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
if (min_vaddr > phdr->p_vaddr) min_vaddr = phdr->p_vaddr;
}
}
return min_vaddr;
}
static int xdl_iterate_open_or_rewind_maps(FILE **maps) {
if (NULL == *maps) {
*maps = fopen("/proc/self/maps", "r");
if (NULL == *maps) return -1;
} else
rewind(*maps);
return 0;
}
static int xdl_iterate_get_pathname_from_maps(uintptr_t base, char *buf, size_t buf_len, FILE **maps) {
// open or rewind maps-file
if (0 != xdl_iterate_open_or_rewind_maps(maps)) return -1; // failed
char line[1024];
while (fgets(line, sizeof(line), *maps)) {
// check base address
uintptr_t start, end;
if (2 != sscanf(line, "%" SCNxPTR "-%" SCNxPTR " r", &start, &end)) continue;
if (base < start) break; // failed
if (base >= end) continue;
// get pathname
char *pathname = strchr(line, '/');
if (NULL == pathname) break; // failed
xdl_util_trim_ending(pathname);
// found it
strlcpy(buf, pathname, buf_len);
return 0; // OK
}
return -1; // failed
}
static int xdl_iterate_by_linker_cb(struct dl_phdr_info *info, size_t size, void *arg) {
uintptr_t *pkg = (uintptr_t *)arg;
xdl_iterate_phdr_cb_t cb = (xdl_iterate_phdr_cb_t)*pkg++;
void *cb_arg = (void *)*pkg++;
FILE **maps = (FILE **)*pkg++;
uintptr_t linker_load_bias = *pkg++;
int flags = (int)*pkg;
// ignore invalid ELF
if (0 == info->dlpi_addr || NULL == info->dlpi_name || '\0' == info->dlpi_name[0]) return 0;
// ignore linker if we have returned it already
if (linker_load_bias == info->dlpi_addr) return 0;
struct dl_phdr_info info_fixed;
info_fixed.dlpi_addr = info->dlpi_addr;
info_fixed.dlpi_name = info->dlpi_name;
info_fixed.dlpi_phdr = info->dlpi_phdr;
info_fixed.dlpi_phnum = info->dlpi_phnum;
info = &info_fixed;
// fix dlpi_phdr & dlpi_phnum (from memory)
if (NULL == info->dlpi_phdr || 0 == info->dlpi_phnum) {
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)info->dlpi_addr;
info->dlpi_phdr = (ElfW(Phdr) *)(info->dlpi_addr + ehdr->e_phoff);
info->dlpi_phnum = ehdr->e_phnum;
}
// fix dlpi_name (from /proc/self/maps)
if ('/' != info->dlpi_name[0] && '[' != info->dlpi_name[0] && (0 != (flags & XDL_FULL_PATHNAME))) {
// get base address
uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(info);
if (UINTPTR_MAX == min_vaddr) return 0; // ignore this ELF
uintptr_t base = (uintptr_t)(info->dlpi_addr + min_vaddr);
char buf[1024];
if (0 != xdl_iterate_get_pathname_from_maps(base, buf, sizeof(buf), maps)) return 0; // ignore this ELF
info->dlpi_name = (const char *)buf;
}
// callback
return cb(info, size, cb_arg);
}
static uintptr_t xdl_iterate_get_linker_base(void) {
if (NULL == getauxval) return 0;
uintptr_t base = (uintptr_t)getauxval(AT_BASE);
if (0 == base) return 0;
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) return 0;
return base;
}
static int xdl_iterate_do_callback(xdl_iterate_phdr_cb_t cb, void *cb_arg, uintptr_t base,
const char *pathname, uintptr_t *load_bias) {
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
struct dl_phdr_info info;
info.dlpi_name = pathname;
info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
info.dlpi_phnum = ehdr->e_phnum;
// get load bias
uintptr_t min_vaddr = xdl_iterate_get_min_vaddr(&info);
if (UINTPTR_MAX == min_vaddr) return 0; // ignore invalid ELF
info.dlpi_addr = (ElfW(Addr))(base - min_vaddr);
if (NULL != load_bias) *load_bias = info.dlpi_addr;
return cb(&info, sizeof(struct dl_phdr_info), cb_arg);
}
static int xdl_iterate_by_linker(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
if (NULL == dl_iterate_phdr) return 0;
int api_level = xdl_util_get_api_level();
FILE *maps = NULL;
int r;
// dl_iterate_phdr(3) does NOT contain linker/linker64 when Android version < 8.1 (API level 27).
// Here we always try to get linker base address from auxv.
uintptr_t linker_load_bias = 0;
uintptr_t linker_base = xdl_iterate_get_linker_base();
if (0 != linker_base) {
if (0 !=
(r = xdl_iterate_do_callback(cb, cb_arg, linker_base, XDL_UTIL_LINKER_PATHNAME, &linker_load_bias)))
return r;
}
// for other ELF
uintptr_t pkg[5] = {(uintptr_t)cb, (uintptr_t)cb_arg, (uintptr_t)&maps, linker_load_bias, (uintptr_t)flags};
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_lock();
r = dl_iterate_phdr(xdl_iterate_by_linker_cb, pkg);
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) xdl_linker_unlock();
if (NULL != maps) fclose(maps);
return r;
}
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
static int xdl_iterate_by_maps(xdl_iterate_phdr_cb_t cb, void *cb_arg) {
FILE *maps = fopen("/proc/self/maps", "r");
if (NULL == maps) return 0;
int r = 0;
char buf1[1024], buf2[1024];
char *line = buf1;
uintptr_t prev_base = 0;
bool try_next_line = false;
while (fgets(line, sizeof(buf1), maps)) {
// Try to find an ELF which loaded by linker.
uintptr_t base, offset;
char exec;
if (3 != sscanf(line, "%" SCNxPTR "-%*" SCNxPTR " r%*c%cp %" SCNxPTR " ", &base, &exec, &offset))
goto clean;
if ('-' == exec && 0 == offset) {
// r--p
prev_base = base;
line = (line == buf1 ? buf2 : buf1);
try_next_line = true;
continue;
} else if (exec == 'x') {
// r-xp
char *pathname = NULL;
if (try_next_line && 0 != offset) {
char *prev = (line == buf1 ? buf2 : buf1);
char *prev_pathname = strchr(prev, '/');
if (NULL == prev_pathname) goto clean;
pathname = strchr(line, '/');
if (NULL == pathname) goto clean;
xdl_util_trim_ending(prev_pathname);
xdl_util_trim_ending(pathname);
if (0 != strcmp(prev_pathname, pathname)) goto clean;
// we found the line with r-xp in the next line
base = prev_base;
offset = 0;
}
if (0 != offset) goto clean;
// get pathname
if (NULL == pathname) {
pathname = strchr(line, '/');
if (NULL == pathname) goto clean;
xdl_util_trim_ending(pathname);
}
if (0 != memcmp((void *)base, ELFMAG, SELFMAG)) goto clean;
ElfW(Ehdr) *ehdr = (ElfW(Ehdr) *)base;
struct dl_phdr_info info;
info.dlpi_name = pathname;
info.dlpi_phdr = (const ElfW(Phdr) *)(base + ehdr->e_phoff);
info.dlpi_phnum = ehdr->e_phnum;
// callback
if (0 != (r = xdl_iterate_do_callback(cb, cb_arg, base, pathname, NULL))) break;
}
clean:
try_next_line = false;
}
fclose(maps);
return r;
}
#endif
int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags) {
// iterate by /proc/self/maps in Android 4.x (Android 4.x only supports arm32 and x86)
#if (defined(__arm__) || defined(__i386__)) && __ANDROID_API__ < __ANDROID_API_L__
if (xdl_util_get_api_level() < __ANDROID_API_L__) return xdl_iterate_by_maps(cb, cb_arg);
#endif
// iterate by dl_iterate_phdr()
return xdl_iterate_by_linker(cb, cb_arg, flags);
}
int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len) {
FILE *maps = NULL;
int r = xdl_iterate_get_pathname_from_maps(base, buf, buf_len, &maps);
if (NULL != maps) fclose(maps);
return r;
}

View File

@ -0,0 +1,43 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-10-04.
#ifndef IO_GITHUB_HEXHACKING_XDL_ITERATE
#define IO_GITHUB_HEXHACKING_XDL_ITERATE
#include <link.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef int (*xdl_iterate_phdr_cb_t)(struct dl_phdr_info *info, size_t size, void *arg);
int xdl_iterate_phdr_impl(xdl_iterate_phdr_cb_t cb, void *cb_arg, int flags);
int xdl_iterate_get_full_pathname(uintptr_t base, char *buf, size_t buf_len);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,234 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2021-02-21.
#include "xdl_linker.h"
#include <dlfcn.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdio.h>
#include "xdl.h"
#include "xdl_iterate.h"
#include "xdl_util.h"
#define XDL_LINKER_SYM_MUTEX "__dl__ZL10g_dl_mutex"
#define XDL_LINKER_SYM_DLOPEN_EXT_N "__dl__ZL10dlopen_extPKciPK17android_dlextinfoPv"
#define XDL_LINKER_SYM_DO_DLOPEN_N "__dl__Z9do_dlopenPKciPK17android_dlextinfoPv"
#define XDL_LINKER_SYM_DLOPEN_O "__dl__Z8__dlopenPKciPKv"
#define XDL_LINKER_SYM_LOADER_DLOPEN_P "__loader_dlopen"
#ifndef __LP64__
#define LIB "lib"
#else
#define LIB "lib64"
#endif
typedef void *(*xdl_linker_dlopen_n_t)(const char *, int, const void *, void *);
typedef void *(*xdl_linker_dlopen_o_t)(const char *, int, const void *);
static pthread_mutex_t *xdl_linker_mutex = NULL;
static void *xdl_linker_dlopen = NULL;
typedef enum { MATCH_PREFIX, MATCH_SUFFIX } xdl_linker_match_type_t;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wpadded"
typedef struct {
xdl_linker_match_type_t type;
const char *value;
} xdl_linker_match_t;
#pragma clang diagnostic pop
typedef struct {
void *addr;
xdl_linker_match_t *matches;
size_t matches_cursor;
} xdl_linker_caller_t;
// https://source.android.com/docs/core/architecture/vndk/linker-namespace
// The following rules are loose and incomplete, you can add more according to your needs.
static xdl_linker_match_t xdl_linker_match_default[] = {{MATCH_SUFFIX, "/libc.so"}};
static xdl_linker_match_t xdl_linker_match_art[] = {{MATCH_SUFFIX, "/libart.so"}};
static xdl_linker_match_t xdl_linker_match_sphal[] = {{MATCH_PREFIX, "/vendor/" LIB "/egl/"},
{MATCH_PREFIX, "/vendor/" LIB "/hw/"},
{MATCH_PREFIX, "/vendor/" LIB "/"},
{MATCH_PREFIX, "/odm/" LIB "/"}};
static xdl_linker_match_t xdl_linker_match_vndk[] = {{MATCH_PREFIX, "/apex/com.android.vndk.v"},
{MATCH_PREFIX, "/vendor/" LIB "/vndk-sp/"},
{MATCH_PREFIX, "/odm/" LIB "/vndk-sp/"}};
static xdl_linker_caller_t xdl_linker_callers[] = {
{NULL, xdl_linker_match_default, sizeof(xdl_linker_match_default) / sizeof(xdl_linker_match_t)},
{NULL, xdl_linker_match_art, sizeof(xdl_linker_match_art) / sizeof(xdl_linker_match_t)},
{NULL, xdl_linker_match_sphal, sizeof(xdl_linker_match_sphal) / sizeof(xdl_linker_match_t)},
{NULL, xdl_linker_match_vndk, sizeof(xdl_linker_match_vndk) / sizeof(xdl_linker_match_t)}};
static void xdl_linker_init_symbols_impl(void) {
// find linker from: /proc/self/maps (API level < 18) or getauxval (API level >= 18)
void *handle = xdl_open(XDL_UTIL_LINKER_BASENAME, XDL_DEFAULT);
if (NULL == handle) return;
int api_level = xdl_util_get_api_level();
if (__ANDROID_API_L__ == api_level || __ANDROID_API_L_MR1__ == api_level) {
// == Android 5.x
xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
} else if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
// == Android 7.x
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_EXT_N, NULL);
if (NULL == xdl_linker_dlopen) {
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DO_DLOPEN_N, NULL);
xdl_linker_mutex = (pthread_mutex_t *)xdl_dsym(handle, XDL_LINKER_SYM_MUTEX, NULL);
}
} else if (__ANDROID_API_O__ == api_level || __ANDROID_API_O_MR1__ == api_level) {
// == Android 8.x
xdl_linker_dlopen = xdl_dsym(handle, XDL_LINKER_SYM_DLOPEN_O, NULL);
} else if (api_level >= __ANDROID_API_P__) {
// >= Android 9.0
xdl_linker_dlopen = xdl_sym(handle, XDL_LINKER_SYM_LOADER_DLOPEN_P, NULL);
}
xdl_close(handle);
}
static void xdl_linker_init_symbols(void) {
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static bool inited = false;
if (!inited) {
pthread_mutex_lock(&lock);
if (!inited) {
xdl_linker_init_symbols_impl();
inited = true;
}
pthread_mutex_unlock(&lock);
}
}
void xdl_linker_lock(void) {
xdl_linker_init_symbols();
if (NULL != xdl_linker_mutex) pthread_mutex_lock(xdl_linker_mutex);
}
void xdl_linker_unlock(void) {
if (NULL != xdl_linker_mutex) pthread_mutex_unlock(xdl_linker_mutex);
}
static void *xdl_linker_get_caller_addr(struct dl_phdr_info *info) {
for (size_t i = 0; i < info->dlpi_phnum; i++) {
const ElfW(Phdr) *phdr = &(info->dlpi_phdr[i]);
if (PT_LOAD == phdr->p_type) {
return (void *)(info->dlpi_addr + phdr->p_vaddr);
}
}
return NULL;
}
static void xdl_linker_save_caller_addr(struct dl_phdr_info *info, xdl_linker_caller_t *caller,
size_t cursor) {
void *addr = xdl_linker_get_caller_addr(info);
if (NULL != addr) {
caller->addr = addr;
caller->matches_cursor = cursor;
}
}
static int xdl_linker_get_caller_addr_cb(struct dl_phdr_info *info, size_t size, void *arg) {
(void)size, (void)arg;
if (0 == info->dlpi_addr || NULL == info->dlpi_name) return 0; // continue
int ret = 1; // OK
for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
xdl_linker_caller_t *caller = &xdl_linker_callers[i];
for (size_t j = 0; j < caller->matches_cursor; j++) {
xdl_linker_match_t *match = &caller->matches[j];
switch (match->type) {
case MATCH_PREFIX:
if (xdl_util_starts_with(info->dlpi_name, match->value)) {
xdl_linker_save_caller_addr(info, caller, j);
}
break;
case MATCH_SUFFIX:
if (xdl_util_ends_with(info->dlpi_name, match->value)) {
xdl_linker_save_caller_addr(info, caller, j);
}
break;
}
}
if (NULL == caller->addr || 0 != caller->matches_cursor) ret = 0; // continue
}
return ret;
}
static void xdl_linker_init_caller_addr_impl(void) {
xdl_iterate_phdr_impl(xdl_linker_get_caller_addr_cb, NULL, XDL_DEFAULT);
}
static void xdl_linker_init_caller_addr(void) {
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static bool inited = false;
if (!inited) {
pthread_mutex_lock(&lock);
if (!inited) {
xdl_linker_init_caller_addr_impl();
inited = true;
}
pthread_mutex_unlock(&lock);
}
}
void *xdl_linker_force_dlopen(const char *filename) {
int api_level = xdl_util_get_api_level();
if (api_level <= __ANDROID_API_M__) {
// <= Android 6.0
return dlopen(filename, RTLD_NOW);
} else {
xdl_linker_init_symbols();
if (NULL == xdl_linker_dlopen) return NULL;
xdl_linker_init_caller_addr();
void *handle = NULL;
if (__ANDROID_API_N__ == api_level || __ANDROID_API_N_MR1__ == api_level) {
// == Android 7.x
xdl_linker_lock();
for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
xdl_linker_caller_t *caller = &xdl_linker_callers[i];
if (NULL != caller->addr) {
handle = ((xdl_linker_dlopen_n_t)xdl_linker_dlopen)(filename, RTLD_NOW, NULL, caller->addr);
if (NULL != handle) break;
}
}
xdl_linker_unlock();
} else {
// >= Android 8.0
for (size_t i = 0; i < sizeof(xdl_linker_callers) / sizeof(xdl_linker_callers[0]); i++) {
xdl_linker_caller_t *caller = &xdl_linker_callers[i];
if (NULL != caller->addr) {
handle = ((xdl_linker_dlopen_o_t)xdl_linker_dlopen)(filename, RTLD_NOW, caller->addr);
if (NULL != handle) break;
}
}
}
return handle;
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2021-02-21.
#ifndef IO_GITHUB_HEXHACKING_XDL_LINKER
#define IO_GITHUB_HEXHACKING_XDL_LINKER
#ifdef __cplusplus
extern "C" {
#endif
void xdl_linker_lock(void);
void xdl_linker_unlock(void);
void *xdl_linker_force_dlopen(const char *filename);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,187 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-11-08.
#include "xdl_lzma.h"
#include <ctype.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "xdl.h"
#include "xdl_util.h"
// LZMA library pathname & symbol names
#ifndef __LP64__
#define XDL_LZMA_PATHNAME "/system/lib/liblzma.so"
#else
#define XDL_LZMA_PATHNAME "/system/lib64/liblzma.so"
#endif
#define XDL_LZMA_SYM_CRCGEN "CrcGenerateTable"
#define XDL_LZMA_SYM_CRC64GEN "Crc64GenerateTable"
#define XDL_LZMA_SYM_CONSTRUCT "XzUnpacker_Construct"
#define XDL_LZMA_SYM_ISFINISHED "XzUnpacker_IsStreamWasFinished"
#define XDL_LZMA_SYM_FREE "XzUnpacker_Free"
#define XDL_LZMA_SYM_CODE "XzUnpacker_Code"
// LZMA data type definition
#define SZ_OK 0
typedef struct ISzAlloc ISzAlloc;
typedef const ISzAlloc *ISzAllocPtr;
struct ISzAlloc {
void *(*Alloc)(ISzAllocPtr p, size_t size);
void (*Free)(ISzAllocPtr p, void *address); /* address can be 0 */
};
typedef enum {
CODER_STATUS_NOT_SPECIFIED, /* use main error code instead */
CODER_STATUS_FINISHED_WITH_MARK, /* stream was finished with end mark. */
CODER_STATUS_NOT_FINISHED, /* stream was not finished */
CODER_STATUS_NEEDS_MORE_INPUT /* you must provide more input bytes */
} ECoderStatus;
typedef enum {
CODER_FINISH_ANY, /* finish at any point */
CODER_FINISH_END /* block must be finished at the end */
} ECoderFinishMode;
// LZMA function type definition
typedef void (*xdl_lzma_crcgen_t)(void);
typedef void (*xdl_lzma_crc64gen_t)(void);
typedef void (*xdl_lzma_construct_t)(void *, ISzAllocPtr);
typedef int (*xdl_lzma_isfinished_t)(const void *);
typedef void (*xdl_lzma_free_t)(void *);
typedef int (*xdl_lzma_code_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, ECoderFinishMode,
ECoderStatus *);
typedef int (*xdl_lzma_code_q_t)(void *, uint8_t *, size_t *, const uint8_t *, size_t *, int,
ECoderFinishMode, ECoderStatus *);
// LZMA function pointor
static xdl_lzma_construct_t xdl_lzma_construct = NULL;
static xdl_lzma_isfinished_t xdl_lzma_isfinished = NULL;
static xdl_lzma_free_t xdl_lzma_free = NULL;
static void *xdl_lzma_code = NULL;
// LZMA init
static void xdl_lzma_init() {
void *lzma = xdl_open(XDL_LZMA_PATHNAME, XDL_TRY_FORCE_LOAD);
if (NULL == lzma) return;
xdl_lzma_crcgen_t crcgen = NULL;
xdl_lzma_crc64gen_t crc64gen = NULL;
if (NULL == (crcgen = (xdl_lzma_crcgen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRCGEN, NULL))) goto end;
if (NULL == (crc64gen = (xdl_lzma_crc64gen_t)xdl_sym(lzma, XDL_LZMA_SYM_CRC64GEN, NULL))) goto end;
if (NULL == (xdl_lzma_construct = (xdl_lzma_construct_t)xdl_sym(lzma, XDL_LZMA_SYM_CONSTRUCT, NULL)))
goto end;
if (NULL == (xdl_lzma_isfinished = (xdl_lzma_isfinished_t)xdl_sym(lzma, XDL_LZMA_SYM_ISFINISHED, NULL)))
goto end;
if (NULL == (xdl_lzma_free = (xdl_lzma_free_t)xdl_sym(lzma, XDL_LZMA_SYM_FREE, NULL))) goto end;
if (NULL == (xdl_lzma_code = xdl_sym(lzma, XDL_LZMA_SYM_CODE, NULL))) goto end;
crcgen();
crc64gen();
end:
xdl_close(lzma);
}
// LZMA internal alloc / free
static void *xdl_lzma_internal_alloc(ISzAllocPtr p, size_t size) {
(void)p;
return malloc(size);
}
static void xdl_lzma_internal_free(ISzAllocPtr p, void *address) {
(void)p;
free(address);
}
int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size) {
size_t src_offset = 0;
size_t dst_offset = 0;
size_t src_remaining;
size_t dst_remaining;
ISzAlloc alloc = {.Alloc = xdl_lzma_internal_alloc, .Free = xdl_lzma_internal_free};
long long state[4096 / sizeof(long long)]; // must be enough, 8-bit aligned
ECoderStatus status;
int api_level = xdl_util_get_api_level();
// init and check
static pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
static bool inited = false;
if (!inited) {
pthread_mutex_lock(&lock);
if (!inited) {
xdl_lzma_init();
inited = true;
}
pthread_mutex_unlock(&lock);
}
if (NULL == xdl_lzma_code) return -1;
xdl_lzma_construct(&state, &alloc);
*dst_size = 2 * src_size;
*dst = NULL;
do {
*dst_size *= 2;
if (NULL == (*dst = realloc(*dst, *dst_size))) {
xdl_lzma_free(&state);
return -1;
}
src_remaining = src_size - src_offset;
dst_remaining = *dst_size - dst_offset;
int result;
if (api_level >= __ANDROID_API_Q__) {
xdl_lzma_code_q_t lzma_code_q = (xdl_lzma_code_q_t)xdl_lzma_code;
result = lzma_code_q(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining, 1,
CODER_FINISH_ANY, &status);
} else {
xdl_lzma_code_t lzma_code = (xdl_lzma_code_t)xdl_lzma_code;
result = lzma_code(&state, *dst + dst_offset, &dst_remaining, src + src_offset, &src_remaining,
CODER_FINISH_ANY, &status);
}
if (SZ_OK != result) {
free(*dst);
xdl_lzma_free(&state);
return -1;
}
src_offset += src_remaining;
dst_offset += dst_remaining;
} while (status == CODER_STATUS_NOT_FINISHED);
xdl_lzma_free(&state);
if (!xdl_lzma_isfinished(&state)) {
free(*dst);
return -1;
}
*dst_size = dst_offset;
*dst = realloc(*dst, *dst_size);
return 0;
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-11-08.
#ifndef IO_GITHUB_HEXHACKING_XDL_LZMA
#define IO_GITHUB_HEXHACKING_XDL_LZMA
#include <stddef.h>
#include <stdint.h>
#ifdef __cplusplus
extern "C" {
#endif
int xdl_lzma_decompress(uint8_t *src, size_t src_size, uint8_t **dst, size_t *dst_size);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,95 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-10-04.
#include "xdl_util.h"
#include <android/api-level.h>
#include <ctype.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
bool xdl_util_starts_with(const char *str, const char *start) {
while (*str && *str == *start) {
str++;
start++;
}
return '\0' == *start;
}
bool xdl_util_ends_with(const char *str, const char *ending) {
size_t str_len = strlen(str);
size_t ending_len = strlen(ending);
if (ending_len > str_len) return false;
return 0 == strcmp(str + (str_len - ending_len), ending);
}
size_t xdl_util_trim_ending(char *start) {
char *end = start + strlen(start);
while (start < end && isspace((int)(*(end - 1)))) {
end--;
*end = '\0';
}
return (size_t)(end - start);
}
static int xdl_util_get_api_level_from_build_prop(void) {
char buf[128];
int api_level = -1;
FILE *fp = fopen("/system/build.prop", "r");
if (NULL == fp) goto end;
while (fgets(buf, sizeof(buf), fp)) {
if (xdl_util_starts_with(buf, "ro.build.version.sdk=")) {
api_level = atoi(buf + 21);
break;
}
}
fclose(fp);
end:
return (api_level > 0) ? api_level : -1;
}
int xdl_util_get_api_level(void) {
static int xdl_util_api_level = -1;
if (xdl_util_api_level < 0) {
int api_level = android_get_device_api_level();
if (api_level < 0)
api_level = xdl_util_get_api_level_from_build_prop(); // compatible with unusual models
if (api_level < __ANDROID_API_J__) api_level = __ANDROID_API_J__;
__atomic_store_n(&xdl_util_api_level, api_level, __ATOMIC_SEQ_CST);
}
return xdl_util_api_level;
}

View File

@ -0,0 +1,71 @@
// Copyright (c) 2020-2023 HexHacking Team
//
// 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.
//
// Created by caikelun on 2020-10-04.
#ifndef IO_GITHUB_HEXHACKING_XDL_UTIL
#define IO_GITHUB_HEXHACKING_XDL_UTIL
#include <errno.h>
#include <stdbool.h>
#include <stddef.h>
#ifndef __LP64__
#define XDL_UTIL_LINKER_BASENAME "linker"
#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker"
#define XDL_UTIL_APP_PROCESS_BASENAME "app_process32"
#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process32"
#define XDL_UTIL_APP_PROCESS_BASENAME_K "app_process"
#define XDL_UTIL_APP_PROCESS_PATHNAME_K "/system/bin/app_process"
#else
#define XDL_UTIL_LINKER_BASENAME "linker64"
#define XDL_UTIL_LINKER_PATHNAME "/system/bin/linker64"
#define XDL_UTIL_APP_PROCESS_BASENAME "app_process64"
#define XDL_UTIL_APP_PROCESS_PATHNAME "/system/bin/app_process64"
#endif
#define XDL_UTIL_VDSO_BASENAME "[vdso]"
#define XDL_UTIL_TEMP_FAILURE_RETRY(exp) \
({ \
__typeof__(exp) _rc; \
do { \
errno = 0; \
_rc = (exp); \
} while (_rc == -1 && errno == EINTR); \
_rc; \
})
#ifdef __cplusplus
extern "C" {
#endif
bool xdl_util_starts_with(const char *str, const char *start);
bool xdl_util_ends_with(const char *str, const char *ending);
size_t xdl_util_trim_ending(char *start);
int xdl_util_get_api_level(void);
#ifdef __cplusplus
}
#endif
#endif

384
app/src/main/cpp/zygisk.hpp Normal file
View File

@ -0,0 +1,384 @@
/* Copyright 2022-2023 John "topjohnwu" Wu
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
* REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
* AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
* INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
* LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/
// This is the public API for Zygisk modules.
// DO NOT MODIFY ANY CODE IN THIS HEADER.
#pragma once
#include <jni.h>
#define ZYGISK_API_VERSION 2
/*
***************
* Introduction
***************
On Android, all app processes are forked from a special daemon called "Zygote".
For each new app process, zygote will fork a new process and perform "specialization".
This specialization operation enforces the Android security sandbox on the newly forked
process to make sure that 3rd party application code is only loaded after it is being
restricted within a sandbox.
On Android, there is also this special process called "system_server". This single
process hosts a significant portion of system services, which controls how the
Android operating system and apps interact with each other.
The Zygisk framework provides a way to allow developers to build modules and run custom
code before and after system_server and any app processes' specialization.
This enable developers to inject code and alter the behavior of system_server and app processes.
Please note that modules will only be loaded after zygote has forked the child process.
THIS MEANS ALL OF YOUR CODE RUNS IN THE APP/SYSTEM_SERVER PROCESS, NOT THE ZYGOTE DAEMON!
*********************
* Development Guide
*********************
Define a class and inherit zygisk::ModuleBase to implement the functionality of your module.
Use the macro REGISTER_ZYGISK_MODULE(className) to register that class to Zygisk.
Example code:
static jint (*orig_logger_entry_max)(JNIEnv *env);
static jint my_logger_entry_max(JNIEnv *env) { return orig_logger_entry_max(env); }
class ExampleModule : public zygisk::ModuleBase {
public:
void onLoad(zygisk::Api *api, JNIEnv *env) override {
this->api = api;
this->env = env;
}
void preAppSpecialize(zygisk::AppSpecializeArgs *args) override {
JNINativeMethod methods[] = {
{ "logger_entry_max_payload_native", "()I", (void*) my_logger_entry_max },
};
api->hookJniNativeMethods(env, "android/util/Log", methods, 1);
*(void **) &orig_logger_entry_max = methods[0].fnPtr;
}
private:
zygisk::Api *api;
JNIEnv *env;
};
REGISTER_ZYGISK_MODULE(ExampleModule)
-----------------------------------------------------------------------------------------
Since your module class's code runs with either Zygote's privilege in pre[XXX]Specialize,
or runs in the sandbox of the target process in post[XXX]Specialize, the code in your class
never runs in a true superuser environment.
If your module require access to superuser permissions, you can create and register
a root companion handler function. This function runs in a separate root companion
daemon process, and an Unix domain socket is provided to allow you to perform IPC between
your target process and the root companion process.
Example code:
static void example_handler(int socket) { ... }
REGISTER_ZYGISK_COMPANION(example_handler)
*/
namespace zygisk {
struct Api;
struct AppSpecializeArgs;
struct ServerSpecializeArgs;
class ModuleBase {
public:
// This method is called as soon as the module is loaded into the target process.
// A Zygisk API handle will be passed as an argument.
virtual void onLoad([[maybe_unused]] Api *api, [[maybe_unused]] JNIEnv *env) {}
// This method is called before the app process is specialized.
// At this point, the process just got forked from zygote, but no app specific specialization
// is applied. This means that the process does not have any sandbox restrictions and
// still runs with the same privilege of zygote.
//
// All the arguments that will be sent and used for app specialization is passed as a single
// AppSpecializeArgs object. You can read and overwrite these arguments to change how the app
// process will be specialized.
//
// If you need to run some operations as superuser, you can call Api::connectCompanion() to
// get a socket to do IPC calls with a root companion process.
// See Api::connectCompanion() for more info.
virtual void preAppSpecialize([[maybe_unused]] AppSpecializeArgs *args) {}
// This method is called after the app process is specialized.
// At this point, the process has all sandbox restrictions enabled for this application.
// This means that this method runs with the same privilege of the app's own code.
virtual void postAppSpecialize([[maybe_unused]] const AppSpecializeArgs *args) {}
// This method is called before the system server process is specialized.
// See preAppSpecialize(args) for more info.
virtual void preServerSpecialize([[maybe_unused]] ServerSpecializeArgs *args) {}
// This method is called after the system server process is specialized.
// At this point, the process runs with the privilege of system_server.
virtual void postServerSpecialize([[maybe_unused]] const ServerSpecializeArgs *args) {}
};
struct AppSpecializeArgs {
// Required arguments. These arguments are guaranteed to exist on all Android versions.
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jint &mount_external;
jstring &se_info;
jstring &nice_name;
jstring &instruction_set;
jstring &app_data_dir;
// Optional arguments. Please check whether the pointer is null before de-referencing
jboolean *const is_child_zygote;
jboolean *const is_top_app;
jobjectArray *const pkg_data_info_list;
jobjectArray *const whitelisted_data_info_list;
jboolean *const mount_data_dirs;
jboolean *const mount_storage_dirs;
AppSpecializeArgs() = delete;
};
struct ServerSpecializeArgs {
jint &uid;
jint &gid;
jintArray &gids;
jint &runtime_flags;
jlong &permitted_capabilities;
jlong &effective_capabilities;
ServerSpecializeArgs() = delete;
};
namespace internal {
struct api_table;
template <class T> void entry_impl(api_table *, JNIEnv *);
}
// These values are used in Api::setOption(Option)
enum Option : int {
// Force Magisk's denylist unmount routines to run on this process.
//
// Setting this option only makes sense in preAppSpecialize.
// The actual unmounting happens during app process specialization.
//
// Set this option to force all Magisk and modules' files to be unmounted from the
// mount namespace of the process, regardless of the denylist enforcement status.
FORCE_DENYLIST_UNMOUNT = 0,
// When this option is set, your module's library will be dlclose-ed after post[XXX]Specialize.
// Be aware that after dlclose-ing your module, all of your code will be unmapped from memory.
// YOU MUST NOT ENABLE THIS OPTION AFTER HOOKING ANY FUNCTIONS IN THE PROCESS.
DLCLOSE_MODULE_LIBRARY = 1,
};
// Bit masks of the return value of Api::getFlags()
enum StateFlag : uint32_t {
// The user has granted root access to the current process
PROCESS_GRANTED_ROOT = (1u << 0),
// The current process was added on the denylist
PROCESS_ON_DENYLIST = (1u << 1),
};
// All API methods will stop working after post[XXX]Specialize as Zygisk will be unloaded
// from the specialized process afterwards.
struct Api {
// Connect to a root companion process and get a Unix domain socket for IPC.
//
// This API only works in the pre[XXX]Specialize methods due to SELinux restrictions.
//
// The pre[XXX]Specialize methods run with the same privilege of zygote.
// If you would like to do some operations with superuser permissions, register a handler
// function that would be called in the root process with REGISTER_ZYGISK_COMPANION(func).
// Another good use case for a companion process is that if you want to share some resources
// across multiple processes, hold the resources in the companion process and pass it over.
//
// The root companion process is ABI aware; that is, when calling this method from a 32-bit
// process, you will be connected to a 32-bit companion process, and vice versa for 64-bit.
//
// Returns a file descriptor to a socket that is connected to the socket passed to your
// module's companion request handler. Returns -1 if the connection attempt failed.
int connectCompanion();
// Get the file descriptor of the root folder of the current module.
//
// This API only works in the pre[XXX]Specialize methods.
// Accessing the directory returned is only possible in the pre[XXX]Specialize methods
// or in the root companion process (assuming that you sent the fd over the socket).
// Both restrictions are due to SELinux and UID.
//
// Returns -1 if errors occurred.
int getModuleDir();
// Set various options for your module.
// Please note that this method accepts one single option at a time.
// Check zygisk::Option for the full list of options available.
void setOption(Option opt);
// Get information about the current process.
// Returns bitwise-or'd zygisk::StateFlag values.
uint32_t getFlags();
// Hook JNI native methods for a class
//
// Lookup all registered JNI native methods and replace it with your own methods.
// The original function pointer will be saved in each JNINativeMethod's fnPtr.
// If no matching class, method name, or signature is found, that specific JNINativeMethod.fnPtr
// will be set to nullptr.
void hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods);
// Hook functions in the PLT (Procedure Linkage Table) of ELFs loaded in memory.
//
// Parsing /proc/[PID]/maps will give you the memory map of a process. As an example:
//
// <address> <perms> <offset> <dev> <inode> <pathname>
// 56b4346000-56b4347000 r-xp 00002000 fe:00 235 /system/bin/app_process64
// (More details: https://man7.org/linux/man-pages/man5/proc.5.html)
//
// For ELFs loaded in memory with pathname matching `regex`, replace function `symbol` with `newFunc`.
// If `oldFunc` is not nullptr, the original function pointer will be saved to `oldFunc`.
void pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc);
// For ELFs loaded in memory with pathname matching `regex`, exclude hooks registered for `symbol`.
// If `symbol` is nullptr, then all symbols will be excluded.
void pltHookExclude(const char *regex, const char *symbol);
// Commit all the hooks that was previously registered.
// Returns false if an error occurred.
bool pltHookCommit();
private:
internal::api_table *tbl;
template <class T> friend void internal::entry_impl(internal::api_table *, JNIEnv *);
};
// Register a class as a Zygisk module
#define REGISTER_ZYGISK_MODULE(clazz) \
void zygisk_module_entry(zygisk::internal::api_table *table, JNIEnv *env) { \
zygisk::internal::entry_impl<clazz>(table, env); \
}
// Register a root companion request handler function for your module
//
// The function runs in a superuser daemon process and handles a root companion request from
// your module running in a target process. The function has to accept an integer value,
// which is a Unix domain socket that is connected to the target process.
// See Api::connectCompanion() for more info.
//
// NOTE: the function can run concurrently on multiple threads.
// Be aware of race conditions if you have globally shared resources.
#define REGISTER_ZYGISK_COMPANION(func) \
void zygisk_companion_entry(int client) { func(client); }
/*********************************************************
* The following is internal ABI implementation detail.
* You do not have to understand what it is doing.
*********************************************************/
namespace internal {
struct module_abi {
long api_version;
ModuleBase *impl;
void (*preAppSpecialize)(ModuleBase *, AppSpecializeArgs *);
void (*postAppSpecialize)(ModuleBase *, const AppSpecializeArgs *);
void (*preServerSpecialize)(ModuleBase *, ServerSpecializeArgs *);
void (*postServerSpecialize)(ModuleBase *, const ServerSpecializeArgs *);
module_abi(ModuleBase *module) : api_version(ZYGISK_API_VERSION), impl(module) {
preAppSpecialize = [](auto m, auto args) { m->preAppSpecialize(args); };
postAppSpecialize = [](auto m, auto args) { m->postAppSpecialize(args); };
preServerSpecialize = [](auto m, auto args) { m->preServerSpecialize(args); };
postServerSpecialize = [](auto m, auto args) { m->postServerSpecialize(args); };
}
};
struct api_table {
// Base
void *impl;
bool (*registerModule)(api_table *, module_abi *);
void (*hookJniNativeMethods)(JNIEnv *, const char *, JNINativeMethod *, int);
void (*pltHookRegister)(const char *, const char *, void *, void **);
void (*pltHookExclude)(const char *, const char *);
bool (*pltHookCommit)();
int (*connectCompanion)(void * /* impl */);
void (*setOption)(void * /* impl */, Option);
int (*getModuleDir)(void * /* impl */);
uint32_t (*getFlags)(void * /* impl */);
};
template <class T>
void entry_impl(api_table *table, JNIEnv *env) {
static Api api;
api.tbl = table;
static T module;
ModuleBase *m = &module;
static module_abi abi(m);
if (!table->registerModule(table, &abi)) return;
m->onLoad(&api, env);
}
} // namespace internal
inline int Api::connectCompanion() {
return tbl->connectCompanion ? tbl->connectCompanion(tbl->impl) : -1;
}
inline int Api::getModuleDir() {
return tbl->getModuleDir ? tbl->getModuleDir(tbl->impl) : -1;
}
inline void Api::setOption(Option opt) {
if (tbl->setOption) tbl->setOption(tbl->impl, opt);
}
inline uint32_t Api::getFlags() {
return tbl->getFlags ? tbl->getFlags(tbl->impl) : 0;
}
inline void Api::hookJniNativeMethods(JNIEnv *env, const char *className, JNINativeMethod *methods, int numMethods) {
if (tbl->hookJniNativeMethods) tbl->hookJniNativeMethods(env, className, methods, numMethods);
}
inline void Api::pltHookRegister(const char *regex, const char *symbol, void *newFunc, void **oldFunc) {
if (tbl->pltHookRegister) tbl->pltHookRegister(regex, symbol, newFunc, oldFunc);
}
inline void Api::pltHookExclude(const char *regex, const char *symbol) {
if (tbl->pltHookExclude) tbl->pltHookExclude(regex, symbol);
}
inline bool Api::pltHookCommit() {
return tbl->pltHookCommit != nullptr && tbl->pltHookCommit();
}
} // namespace zygisk
extern "C" {
[[gnu::visibility("default"), maybe_unused]]
void zygisk_module_entry(zygisk::internal::api_table *, JNIEnv *);
[[gnu::visibility("default"), maybe_unused]]
void zygisk_companion_entry(int);
} // extern "C"

View File

@ -0,0 +1,105 @@
package es.chiteroman.playintegrityfix;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.Key;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.Certificate;
import java.security.cert.CertificateException;
import java.util.Date;
import java.util.Enumeration;
import java.util.Locale;
public final class CustomKeyStoreSpi extends KeyStoreSpi {
public static volatile KeyStoreSpi keyStoreSpi;
@Override
public Key engineGetKey(String alias, char[] password) throws NoSuchAlgorithmException, UnrecoverableKeyException {
return keyStoreSpi.engineGetKey(alias, password);
}
@Override
public Certificate[] engineGetCertificateChain(String alias) {
for (StackTraceElement e : Thread.currentThread().getStackTrace()) {
if (e.getClassName().toLowerCase(Locale.ROOT).contains("droidguard")) {
EntryPoint.LOG("DroidGuard detected!");
throw new UnsupportedOperationException();
}
}
return keyStoreSpi.engineGetCertificateChain(alias);
}
@Override
public Certificate engineGetCertificate(String alias) {
return keyStoreSpi.engineGetCertificate(alias);
}
@Override
public Date engineGetCreationDate(String alias) {
return keyStoreSpi.engineGetCreationDate(alias);
}
@Override
public void engineSetKeyEntry(String alias, Key key, char[] password, Certificate[] chain) throws KeyStoreException {
keyStoreSpi.engineSetKeyEntry(alias, key, password, chain);
}
@Override
public void engineSetKeyEntry(String alias, byte[] key, Certificate[] chain) throws KeyStoreException {
keyStoreSpi.engineSetKeyEntry(alias, key, chain);
}
@Override
public void engineSetCertificateEntry(String alias, Certificate cert) throws KeyStoreException {
keyStoreSpi.engineSetCertificateEntry(alias, cert);
}
@Override
public void engineDeleteEntry(String alias) throws KeyStoreException {
keyStoreSpi.engineDeleteEntry(alias);
}
@Override
public Enumeration<String> engineAliases() {
return keyStoreSpi.engineAliases();
}
@Override
public boolean engineContainsAlias(String alias) {
return keyStoreSpi.engineContainsAlias(alias);
}
@Override
public int engineSize() {
return keyStoreSpi.engineSize();
}
@Override
public boolean engineIsKeyEntry(String alias) {
return keyStoreSpi.engineIsKeyEntry(alias);
}
@Override
public boolean engineIsCertificateEntry(String alias) {
return keyStoreSpi.engineIsCertificateEntry(alias);
}
@Override
public String engineGetCertificateAlias(Certificate cert) {
return keyStoreSpi.engineGetCertificateAlias(cert);
}
@Override
public void engineStore(OutputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException {
keyStoreSpi.engineStore(stream, password);
}
@Override
public void engineLoad(InputStream stream, char[] password) throws CertificateException, IOException, NoSuchAlgorithmException {
keyStoreSpi.engineLoad(stream, password);
}
}

View File

@ -0,0 +1,18 @@
package es.chiteroman.playintegrityfix;
import java.security.Provider;
public final class CustomProvider extends Provider {
CustomProvider(Provider provider) {
super(provider.getName(), provider.getVersion(), provider.getInfo());
putAll(provider);
this.put("KeyStore.AndroidKeyStore", CustomKeyStoreSpi.class.getName());
}
@Override
public synchronized Service getService(String type, String algorithm) {
EntryPoint.spoofDevice();
return super.getService(type, algorithm);
}
}

View File

@ -0,0 +1,85 @@
package es.chiteroman.playintegrityfix;
import android.os.Build;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.lang.reflect.Field;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.KeyStoreSpi;
import java.security.Provider;
import java.security.Security;
import java.util.Properties;
public final class EntryPoint {
private static final Properties props = new Properties();
private static final File file = new File("/data/data/com.google.android.gms/cache/pif.prop");
public static void init() {
try (FileInputStream inputStream = new FileInputStream(file)) {
props.load(inputStream);
LOG("Loaded " + props.size() + " properties!");
} catch (IOException e) {
LOG("Couldn't load properties file: " + e);
return;
}
spoofDevice();
spoofProvider();
}
private static void spoofProvider() {
final String KEYSTORE = "AndroidKeyStore";
try {
Provider provider = Security.getProvider(KEYSTORE);
KeyStore keyStore = KeyStore.getInstance(KEYSTORE);
Field f = keyStore.getClass().getDeclaredField("keyStoreSpi");
f.setAccessible(true);
CustomKeyStoreSpi.keyStoreSpi = (KeyStoreSpi) f.get(keyStore);
f.setAccessible(false);
CustomProvider customProvider = new CustomProvider(provider);
Security.removeProvider(KEYSTORE);
Security.insertProviderAt(customProvider, 1);
LOG("Spoof KeyStoreSpi and Provider done!");
} catch (KeyStoreException e) {
LOG("Couldn't find KeyStore: " + e);
} catch (NoSuchFieldException e) {
LOG("Couldn't find field: " + e);
} catch (IllegalAccessException e) {
LOG("Couldn't change access of field: " + e);
}
}
public static void spoofDevice() {
props.forEach((field, value) -> setProp((String) field, (String) value));
}
private static void setProp(String name, String value) {
try {
Field field = Build.class.getDeclaredField(name);
field.setAccessible(true);
String oldValue = (String) field.get(null);
field.set(null, value);
field.setAccessible(false);
if (value.equals(oldValue)) return;
LOG(String.format("[%s]: %s -> %s", name, oldValue, value));
} catch (NoSuchFieldException e) {
LOG(String.format("Couldn't find '%s' field name.", name));
} catch (IllegalAccessException e) {
LOG(String.format("Couldn't modify '%s' field value.", name));
}
}
public static void LOG(String msg) {
Log.d("PIF/Java", msg);
}
}

3
build.gradle.kts Normal file
View File

@ -0,0 +1,3 @@
plugins {
id("com.android.application") version "8.1.3" apply false
}

View File

@ -1,5 +1,11 @@
# v13
# v13.1
Device verdict is green again x2.
For normal users:
- Custom props! You can edit them in file 'pif.prop'
If you are using xiaomi.eu expect to fail all tests.
For devs:
- Source code published again.
- Drop support for emulators (x86 and x86_64).
- Use ShadowHook instead Dobby.
- Updated libcxx.
- 'classes.dex' is back to module folder instead hardcoding it in libs.

21
gradle.properties Normal file
View File

@ -0,0 +1,21 @@
# Project-wide Gradle settings.
# IDE (e.g. Android Studio) users:
# Gradle settings configured through the IDE *will override*
# any settings specified in this file.
# For more details on how to configure your build environment visit
# http://www.gradle.org/docs/current/userguide/build_environment.html
# Specifies the JVM arguments used for the daemon process.
# The setting is particularly useful for tweaking memory settings.
org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
# When configured, Gradle will run in incubating parallel mode.
# This option should only be used with decoupled projects. More details, visit
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
# org.gradle.parallel=true
# AndroidX package structure to make it clearer which packages are bundled with the
# Android operating system, and which are packaged with your app's APK
# https://developer.android.com/topic/libraries/support-library/androidx-rn
android.useAndroidX=true
# Enables namespacing of each library's R class so that its R class includes only the
# resources declared in the library itself and none from the library's dependencies,
# thereby reducing the size of the R class for that library
android.nonTransitiveRClass=true

BIN
gradle/wrapper/gradle-wrapper.jar vendored Normal file

Binary file not shown.

View File

@ -0,0 +1,6 @@
#Tue Oct 31 00:21:30 CET 2023
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-8.4-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

185
gradlew vendored Normal file
View File

@ -0,0 +1,185 @@
#!/usr/bin/env sh
#
# Copyright 2015 the original author or authors.
#
# 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
#
# https://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.
#
##############################################################################
##
## Gradle start up script for UN*X
##
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
PRG="$0"
# Need this for relative symlinks.
while [ -h "$PRG" ] ; do
ls=`ls -ld "$PRG"`
link=`expr "$ls" : '.*-> \(.*\)$'`
if expr "$link" : '/.*' > /dev/null; then
PRG="$link"
else
PRG=`dirname "$PRG"`"/$link"
fi
done
SAVED="`pwd`"
cd "`dirname \"$PRG\"`/" >/dev/null
APP_HOME="`pwd -P`"
cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
warn () {
echo "$*"
}
die () {
echo
echo "$*"
echo
exit 1
}
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
;;
Darwin* )
darwin=true
;;
MINGW* )
msys=true
;;
NONSTOP* )
nonstop=true
;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD="$JAVA_HOME/jre/sh/java"
else
JAVACMD="$JAVA_HOME/bin/java"
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD="java"
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
MAX_FD="$MAX_FD_LIMIT"
fi
ulimit -n $MAX_FD
if [ $? -ne 0 ] ; then
warn "Could not set maximum file descriptor limit: $MAX_FD"
fi
else
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
fi
fi
# For Darwin, add options to specify how the application appears in the dock
if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
# For Cygwin or MSYS, switch paths to Windows format before running java
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
SEP=""
for dir in $ROOTDIRSRAW ; do
ROOTDIRS="$ROOTDIRS$SEP$dir"
SEP="|"
done
OURCYGPATTERN="(^($ROOTDIRS))"
# Add a user-defined pattern to the cygpath arguments
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
fi
# Now convert the arguments - kludge to limit ourselves to /bin/sh
i=0
for arg in "$@" ; do
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
else
eval `echo args$i`="\"$arg\""
fi
i=`expr $i + 1`
done
case $i in
0) set -- ;;
1) set -- "$args0" ;;
2) set -- "$args0" "$args1" ;;
3) set -- "$args0" "$args1" "$args2" ;;
4) set -- "$args0" "$args1" "$args2" "$args3" ;;
5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
# Escape application args
save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
exec "$JAVACMD" "$@"

89
gradlew.bat vendored Normal file
View File

@ -0,0 +1,89 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

17
settings.gradle.kts Normal file
View File

@ -0,0 +1,17 @@
pluginManagement {
repositories {
google()
mavenCentral()
gradlePluginPortal()
}
}
dependencyResolutionManagement {
repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)
repositories {
google()
mavenCentral()
}
}
rootProject.name = "PlayIntegrityFix"
include(":app")

View File

@ -1,6 +1,6 @@
{
"version": "v13.0",
"versionCode": 130,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v13/PlayIntegrityFix_v13.zip",
"version": "v13.1",
"versionCode": 131,
"zipUrl": "https://github.com/chiteroman/PlayIntegrityFix/releases/download/v13.1/PlayIntegrityFix_v13.1.zip",
"changelog": "https://raw.githubusercontent.com/chiteroman/PlayIntegrityFix/main/changelog.md"
}