Skip to content

Commit

Permalink
first commit
Browse files Browse the repository at this point in the history
  • Loading branch information
sanfengAndroid committed Jan 19, 2021
1 parent 1b1bb44 commit 87c9624
Show file tree
Hide file tree
Showing 58 changed files with 8,470 additions and 1 deletion.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
/build
/.cxx
/.gradle
# Prerequisites
*.d

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
# fake-linker
Modify Android linker to provide loading module and hook function
![NPM](https://img.shields.io/badge/License-Apache2-blue)[![文档](https://img.shields.io/badge/Document-Chinese-green.svg)](README_CN.md)

Modify Android linker to provide loading module and hook function.
81 changes: 81 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# fake-linker

## 项目描述

进程内动态查找和修改 `Linker` 数据,对外提供基于 `LD_PRELOAD` 模式加载 `PLT hook` 模块和各种操作 `soinfo``namespace`的功能。原理分析请查看[Android 动态修改Linker实现LD_PRELOAD全局库PLT Hook](https://sanfengandroid.github.io/2021/01/10/Android-%E5%8A%A8%E6%80%81%E4%BF%AE%E6%94%B9Linker%E5%AE%9E%E7%8E%B0LD-PRELOAD%E5%85%A8%E5%B1%80%E5%BA%93PLT-Hook/)

## 支持的Android版本

支持版本:`Android 5.0 ~ Android 11+`,支持架构:`x86``x86_64``arm``arm64`

## 构建

1. 源码构建
> 将其作为 `Android Library` 添加到 Android 项目中,主模块添加其依赖,修改[`build.gradle`](build.gradle)中的 `buildApi` 变量编译指定Api等级
2. 使用构建库
> 将下载的 `native` 库导入到 Android 项目中的jniLibs.srcDirs
## Hook 模块开发

1. 复制导出头文件(源码在 `cpp` 目录下的 [export](src/main/cpp/export) 目录)到 `Hook` 模块
2. 自定义实现[`linker_export.h`](src/main/cpp/export/linker_export.h)中的 `fake_load_library_init` 导出函数
3. 调用各种实现方法,查看[`linker_export.h`](src/main/cpp/export/linker_export.h)中的 `RemoteInvokeInterface` 定义
4. 正常编写Hook方法如: `open``dlopen``dlsym`方法等等
5. Hook模块区分Android7.0以下(无命名空间,`soinfo handle`),Android7.0及以上(有命名空间,`soinfo handle`)

## Java层初始化

1. 正确安装库到指定位置,将 `fake-linker``Hook` 模块安装到应用有权限访问的路径,如: `/data/local/tmp`,自身使用则可调用系统方法直接加载
模块已经集成了安装可执行文件,Java层调用 [FileInstaller](src/main/java/com/sanfengandroid/fakelinker/FileInstaller.java) 类下的方法安装,内部已经处理了各个不同的平台架构
2. 通过`FileInstaller`设置对于不同Hook需要设置不同的 `Selinux``uid, gid` 文件属性
3. 加载并初始化 `fake-linker` 模块,调用[`FakeLinker.initFakeLinker`](src/main/java/com/sanfengandroid/fakelinker/FakeLinker.java)方法,内部会加载Hook模块并回调 `fake_load_library_init` 方法完成模块初始化

## 其它描述

1. 项目不同于直接设置 `LD_PRELOAD` 环境变量,直接设置通常无法拦截 `dlsym` 方法,因为一旦拦截则需要自己实现查找符号且高版本还有`caller`地址限制,而该模块通过中转模块 `fake-linker` 提供调用`dlsym`方法,因此Hook模块可以拦截`dlsym`,且提供更多 `Linker` 相关功能
2. Android各个版本`Linker`都有对应的修改,因此模块依赖手机的 `api` 等级,不同等级加载对应模块即可,内部使用`NDK`版本为最新版,该版本已经删除 `Api-25`,代码内是支持 `Api-25` 需要切换低版本`NDK`来编译,需要自己适配代码
3. 其它更多使用请等待后续另一仓库

## 注意事项

1. Hook系统进程时请做好备份删除工作,避免因为出错导致系统进程死亡无法开机的情况
2. 根据模块加载时机,重定位已经加载的模块
## 使用示例
1.`Hook`模块设置为全局库 `remote->CallCommonFunction(kCFAddSoinfoToGlobal, kSPAddress, nullptr, kSPNull, nullptr, &error_code);`
2. 重定位一些已经加载完成的模块 `remote->CallCommonFunction(kCFCallManualRelinks, kSPAddress, nullptr, kSPNames, libs, &error_code);`
```c++
static const RemoteInvokeInterface *remote;
static jint HookJniRegisterNatives(JNIEnv *env, jclass c, const JNINativeMethod *methods, jint nMethods) {
LOG("start register native function %p", __builtin_return_address(0));
jint ret = original_functions->RegisterNatives(env, c, methods, nMethods);
if (ret != JNI_ERR && !original_functions->ExceptionCheck(env)) {
std::string cls = JNIHelper::GetClassName(env, c);
for (int i = 0; i < nMethods; ++i) {
LOG("native register class: %s, method name: %s, function signature: %s, register address: %p", cls.c_str(), methods[i].name, methods[i].signature, methods[i].fnPtr);
}
}
return ret;
}

static void InitHook() {
int error_code;
// 将本模块添加到全局模块中,这会影响之后加载的所有模块
remote->CallCommonFunction(kCFAddSoinfoToGlobal, kSPAddress, nullptr, kSPNull, nullptr, &error_code);
if (error_code != kErrorNo) {
LOGE("init global soinfo error, error code: %x", error_code);
return;
}
VarLengthObject<const char *> *libs;
// 重新解析以下模块的导入表,因为以下模块在我们还未加载前就已经加载完成了,所有重新链接使其符号链接到我们的Hook方法
// java代码也主要使用是以下几个库,重链接也意味着Hook的java的核心导入函数
libs = VaArgsToVarLengthObject<const char *>(5, "libjavacore.so", "libnativehelper.so", "libnativeloader.so", "libart.so", "libopenjdk.so");
remote->CallCommonFunction(kCFCallManualRelinks, kSPAddress, nullptr, kSPNames, libs, &error_code);
// Hook JNI 接口,监听RegisterNatives方法
remote_->HookJniNative(offsetof(JNINativeInterface, RegisterNatives), (void *)HookJniRegisterNatives, nullptr);
}
C_API void fake_load_library_init(JNIEnv *env, void *fake_soinfo, const RemoteInvokeInterface *interface,
const char *cache_path, const char *config_path, const char *_process_name){
remote = interface;
InitHook();
}
```
107 changes: 107 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2020 XpFilter by beich.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

plugins {
id 'com.android.library'
}

def buildApi = rootProject.ext.buildApi
def linkerModuleName = 'fake-linker'
def hookInstallModuleName = 'hook-install'
def mVersionCode = 1000
def mVersionName = "fake-linker-1.0"

android {
compileSdkVersion 30
buildToolsVersion "30.0.3"

defaultConfig {
minSdkVersion 21
targetSdkVersion 30
versionCode mVersionCode
versionName mVersionName

testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles "consumer-rules.pro"

externalNativeBuild {
cmake {
cppFlags ""
cFlags ""
abiFilters 'armeabi-v7a', 'x86', 'arm64-v8a', 'x86_64'
arguments "-DANDROID_PLATFORM=${buildApi}", "-DLINKER_MODULE_NAME=${linkerModuleName}",
"-DHOOK_INSTALL_MODULE_NAME=${hookInstallModuleName}",
"-DHOOK_LOG_LEVEL=${rootProject.ext.logLevel}",
"-DMODULE_VERSION=${mVersionCode}",
"-DMODULE_VERSION_NAME=\"${mVersionName}\""

}
}

buildConfigField 'String', 'LINKER_MODULE_NAME', "\"${linkerModuleName}\""
buildConfigField 'String', 'HOOK_INSTALL_MODULE_NAME', "\"${hookInstallModuleName}\""
}

buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
sourceSets{
release{
jniLibs.srcDirs = ["src/main/libs/release"]
}
debug{
jniLibs.srcDirs = ["src/main/libs/debug"]
}
}

externalNativeBuild {
cmake {
path "src/main/cpp/CMakeLists.txt"
}
}
ndkVersion '22.0.7026061'
}

tasks.whenTaskAdded { task ->
if (task.name.startsWith('merge') && task.name.endsWith('JniLibFolders')) {
task.doLast {
it.outputs.files.files.each { File dir ->
dir.eachFileRecurse {
if (it.name == "lib${linkerModuleName}-${buildApi}.so") {
println '找到当前正在编译的fake-linker模块版本,需要删除'
it.delete()
}
}
}
}
}
}
dependencies {

implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1'
testImplementation 'junit:junit:4.13.1'
implementation 'eu.chainfire:libsuperuser:1.0.0.201811281328'
androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
}
Empty file added consumer-rules.pro
Empty file.
21 changes: 21 additions & 0 deletions proguard-rules.pro
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
19 changes: 19 additions & 0 deletions src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2020 XpFilter by beich.
~
~ Licensed under the Apache License, Version 2.0 (the "License");
~ you may not use this file except in compliance with the License.
~ You may obtain a copy of the License at
~
~ http://www.apache.org/licenses/LICENSE-2.0
~
~ Unless required by applicable law or agreed to in writing, software
~ distributed under the License is distributed on an "AS IS" BASIS,
~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.sanfengandroid.fakelinker">
</manifest>
Binary file added src/main/assets/arm64-v8a/hook-install
Binary file not shown.
Binary file added src/main/assets/armeabi-v7a/hook-install
Binary file not shown.
Binary file added src/main/assets/x86/hook-install
Binary file not shown.
Binary file added src/main/assets/x86_64/hook-install
Binary file not shown.
106 changes: 106 additions & 0 deletions src/main/cpp/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html

# Sets the minimum version of CMake required to build the native library.

cmake_minimum_required(VERSION 3.4.1)

# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.
string(TOLOWER ${CMAKE_BUILD_TYPE} THIS_BUILD_TYPE)

set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} -Os -Oz -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -Werror")
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} -Os -Oz -fno-rtti -fno-exceptions -ffunction-sections -fdata-sections -fvisibility=hidden -fvisibility-inlines-hidden -Werror")
set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -Werror")
set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -Werror")

if (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "arm64-v8a" OR ${CMAKE_ANDROID_ARCH_ABI} STREQUAL "x86_64")
add_definitions(-DUSE_RELA)
else ()
add_definitions(-D__work_around_b_24465209__)
endif ()

if (${CMAKE_ANDROID_ARCH_ABI} STREQUAL "arm64-v8a" OR ${CMAKE_ANDROID_ARCH_ABI} STREQUAL "armeabi-v7a")
SET(NEON_SRC linker/linker_gnu_hash_neon.cpp)
else ()
SET(NEON_SRC)
endif ()

include_directories(common)
include_directories(export)

set(CMAKE_LIBRARY_OUTPUT_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/../libs/${THIS_BUILD_TYPE}/${CMAKE_ANDROID_ARCH_ABI})
message(WARNING "当前版本:${ANDROID_PLATFORM},输出目录:${CMAKE_LIBRARY_OUTPUT_DIRECTORY}, 编译类型:${THIS_BUILD_TYPE},模块名:${LINKER_MODULE_NAME},安装模块${HOOK_INSTALL_MODULE_NAME}")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/module_config.h.in" "${CMAKE_CURRENT_SOURCE_DIR}/common/module_config.h")

add_library( # Sets the name of the library.
${LINKER_MODULE_NAME}-${ANDROID_PLATFORM}

# Sets the library as a shared library.
SHARED

# Provides a relative path to your source file(s).
linker_main.cpp

linker/fake_linker.cpp
linker/linker_globals.cpp
linker/linker_namespaces.cpp
linker/linker_soinfo.cpp
linker/linker_util.cpp
linker/linker_common_types.cpp
linker/linker_block_allocator.cpp
linker/local_block_allocator.cpp
linker/linker_export.cpp
${NEON_SRC}

# JNI Hook
linker/art/hook_jni_native_interface_impl.cpp
# JNI Common
common/jni_helper.cpp
common/maps_util.cpp
)

add_executable(
${HOOK_INSTALL_MODULE_NAME}

installer/hook_installer.cpp
)

# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.

find_library( # Sets the name of the path variable.
log-lib

# Specifies the name of the NDK library that
# you want CMake to locate.
log)


# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.

target_link_libraries( # Specifies the target library.
${LINKER_MODULE_NAME}-${ANDROID_PLATFORM}

# Links the target library to the log library
# included in the NDK.
${log-lib})
# 设置 CMAKE_LIBRARY_OUTPUT_DIRECTORY 不生效,不知为何,设置RUNTIME_OUTPUT_DIRECTORY生效
set_target_properties(${LINKER_MODULE_NAME}-${ANDROID_PLATFORM} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/../libs/${THIS_BUILD_TYPE}/${CMAKE_ANDROID_ARCH_ABI})

target_link_libraries(
${HOOK_INSTALL_MODULE_NAME}
${log-lib})

set_target_properties(${HOOK_INSTALL_MODULE_NAME} PROPERTIES
RUNTIME_OUTPUT_DIRECTORY
${CMAKE_CURRENT_SOURCE_DIR}/../assets/${CMAKE_ANDROID_ARCH_ABI})
19 changes: 19 additions & 0 deletions src/main/cpp/common/alog.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
//
// Created by beich on 2020/11/6.
//
#pragma once

#include <android/log.h>

extern int g_log_level;
#define LOG_TAG "HookLog"
#define _PRINT(v, format, ...) \
do { \
if(g_log_level < (v)) __android_log_print(v, LOG_TAG, format, ##__VA_ARGS__); \
}while(0)

#define LOGV(format, ...) _PRINT(ANDROID_LOG_VERBOSE, format, ##__VA_ARGS__)
#define LOGD(format, ...) _PRINT(ANDROID_LOG_DEBUG, format, ##__VA_ARGS__)
#define LOGI(format, ...) _PRINT(ANDROID_LOG_INFO, format, ##__VA_ARGS__)
#define LOGW(format, ...) _PRINT(ANDROID_LOG_WARN, format, ##__VA_ARGS__)
#define LOGE(format, ...) _PRINT(ANDROID_LOG_ERROR, format, ##__VA_ARGS__)
15 changes: 15 additions & 0 deletions src/main/cpp/common/gtype.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
//
// Created by lenovo-s on 2019/4/15.
//
#pragma once

#include <stdint.h>

#define LINE_MAX 4096

typedef void *gpointer;
typedef intptr_t gssize;
typedef uintptr_t gsize;
typedef uint64_t gaddress;
#define GSIZE_TO_POINTER(s) ((gpointer) (gsize) (s))
#define GPOINTER_TO_SIZE(p) ((gsize) (p))
Loading

0 comments on commit 87c9624

Please sign in to comment.