Skip to content

Commit 0054dd5

Browse files
test: add android demo example
1 parent 2db01be commit 0054dd5

33 files changed

+816
-0
lines changed

demo/.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
/build

demo/build.gradle

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
plugins {
2+
id 'com.android.application'
3+
}
4+
5+
android {
6+
namespace 'com.sanfengandroid.demo'
7+
compileSdk rootProject.targetSdk
8+
9+
defaultConfig {
10+
minSdk 21
11+
targetSdk rootProject.targetSdk
12+
versionCode 100
13+
versionName "1.0"
14+
15+
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
16+
consumerProguardFiles "consumer-rules.pro"
17+
externalNativeBuild {
18+
cmake {
19+
cppFlags ''
20+
}
21+
}
22+
}
23+
24+
if (rootProject.hasSign) {
25+
signingConfigs {
26+
sign {
27+
storeFile file(rootProject.storeFile)
28+
storePassword rootProject.storePassword
29+
keyAlias rootProject.keyAlias
30+
keyPassword rootProject.keyPassword
31+
}
32+
}
33+
}
34+
35+
buildTypes {
36+
release {
37+
minifyEnabled false
38+
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
39+
}
40+
}
41+
compileOptions {
42+
sourceCompatibility JavaVersion.VERSION_1_8
43+
targetCompatibility JavaVersion.VERSION_1_8
44+
}
45+
externalNativeBuild {
46+
cmake {
47+
path file('src/main/cpp/CMakeLists.txt')
48+
version '3.22.1'
49+
}
50+
}
51+
}
52+
53+
dependencies {
54+
testImplementation 'junit:junit:4.13.2'
55+
implementation(project(':library'))
56+
androidTestImplementation 'androidx.test.ext:junit:1.2.1'
57+
androidTestImplementation 'androidx.test.espresso:espresso-core:3.6.1'
58+
}

demo/consumer-rules.pro

Whitespace-only changes.

demo/proguard-rules.pro

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
# Add project specific ProGuard rules here.
2+
# You can control the set of applied configuration files using the
3+
# proguardFiles setting in build.gradle.
4+
#
5+
# For more details, see
6+
# http://developer.android.com/guide/developing/tools/proguard.html
7+
8+
# If your project uses WebView with JS, uncomment the following
9+
# and specify the fully qualified class name to the JavaScript interface
10+
# class:
11+
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
12+
# public *;
13+
#}
14+
15+
# Uncomment this to preserve the line number information for
16+
# debugging stack traces.
17+
#-keepattributes SourceFile,LineNumberTable
18+
19+
# If you keep the line number information, uncomment this to
20+
# hide the original source file name.
21+
#-renamesourcefileattribute SourceFile
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
package com.sanfengandroid.demo;
2+
3+
import static org.junit.Assert.*;
4+
5+
import android.content.Context;
6+
import androidx.test.ext.junit.runners.AndroidJUnit4;
7+
import androidx.test.platform.app.InstrumentationRegistry;
8+
import org.junit.Test;
9+
import org.junit.runner.RunWith;
10+
11+
/**
12+
* Instrumented test, which will execute on an Android device.
13+
*
14+
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
15+
*/
16+
@RunWith(AndroidJUnit4.class)
17+
public class ExampleInstrumentedTest {
18+
@Test
19+
public void useAppContext() {
20+
// Context of the app under test.
21+
Context appContext =
22+
InstrumentationRegistry.getInstrumentation().getTargetContext();
23+
assertEquals("com.sanfengandroid.demo.test", appContext.getPackageName());
24+
}
25+
}

demo/src/main/AndroidManifest.xml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
3+
4+
<application
5+
android:allowBackup="true"
6+
android:icon="@mipmap/ic_launcher"
7+
android:label="@string/app_name"
8+
android:roundIcon="@mipmap/ic_launcher_round"
9+
android:supportsRtl="true"
10+
android:theme="@style/Theme.FakelinkerDemo">
11+
<activity
12+
android:name=".MainActivity"
13+
android:exported="true">
14+
<intent-filter>
15+
<action android:name="android.intent.action.MAIN" />
16+
<category android:name="android.intent.category.LAUNCHER" />
17+
</intent-filter>
18+
</activity>
19+
</application>
20+
</manifest>

demo/src/main/cpp/CMakeLists.txt

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
2+
# For more information about using CMake with Android Studio, read the
3+
# documentation: https://d.android.com/studio/projects/add-native-code.html.
4+
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.
5+
6+
# Sets the minimum CMake version required for this project.
7+
cmake_minimum_required(VERSION 3.4.1)
8+
project("demo")
9+
10+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
11+
set(CMAKE_CXX_STANDARD 17)
12+
13+
if(${CMAKE_BUILD_TYPE} STREQUAL "Debug")
14+
set(FAKELINKER_LOG_LEVEL 0)
15+
endif()
16+
17+
set(FAKELINKER_DIR ${CMAKE_CURRENT_SOURCE_DIR}/../../../../library/src/main/cpp)
18+
19+
add_subdirectory(${FAKELINKER_DIR} lib)
20+
21+
find_library(log-lib log)
22+
23+
add_library(static_demo SHARED
24+
static_demo.cpp
25+
)
26+
27+
target_link_libraries(static_demo
28+
PRIVATE
29+
fakelinker_static
30+
${log-lib}
31+
)
32+
33+
set_target_properties(static_demo PROPERTIES
34+
LINK_FLAGS
35+
"${LINK_FLAGS} -Wl,--gc-sections,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/symbol.map.txt\""
36+
)
37+
38+
add_library(shared_demo SHARED
39+
shared_demo.cpp
40+
)
41+
42+
target_link_libraries(shared_demo
43+
PRIVATE
44+
fakelinker
45+
${log-lib}
46+
)
47+
48+
set_target_properties(shared_demo PROPERTIES
49+
LINK_FLAGS
50+
"${LINK_FLAGS} -Wl,--gc-sections,--version-script=\"${CMAKE_CURRENT_SOURCE_DIR}/symbol.map.txt\""
51+
)
52+
53+
add_library(native_test SHARED
54+
native_test.cpp
55+
)
56+
target_link_libraries(native_test PRIVATE
57+
${log-lib}
58+
)

demo/src/main/cpp/native_test.cpp

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
//
2+
// Created by beichen on 2025/3/12.
3+
//
4+
#include <android/log.h>
5+
#include <jni.h>
6+
#include <unistd.h>
7+
8+
#define LOG_TAG "NativeTest"
9+
10+
#define LOGE(format, ...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, format, ##__VA_ARGS__);
11+
12+
extern "C" JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
13+
JNIEnv *env;
14+
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
15+
LOGE("JNI environment error");
16+
return JNI_EVERSION;
17+
}
18+
19+
if (access("/test/hook/path", F_OK) != 0) {
20+
LOGE("hook module error");
21+
return JNI_ERR;
22+
}
23+
return JNI_VERSION_1_6;
24+
}

demo/src/main/cpp/shared_demo.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
//
2+
// Created by beichen on 2025/3/12.
3+
//
4+
#include <string.h>
5+
6+
#include <alog.h>
7+
#include <fake_linker.h>
8+
#include <macros.h>
9+
10+
static int (*orig_access)(const char *pathname, int mode) = nullptr;
11+
12+
int g_log_level = 0;
13+
14+
C_API API_PUBLIC int access(const char *pathname, int mode) {
15+
LOGW("shared hook access function path: %s, mode: %d", pathname, mode);
16+
if (strncmp(pathname, "/test/hook/path", 15) == 0) {
17+
LOGW("fake access /test/hook/path");
18+
return 0;
19+
}
20+
// Does not block other calls
21+
return orig_access(pathname, mode);
22+
}
23+
24+
C_API JNIEXPORT void fakelinker_module_init(JNIEnv *env, SoinfoPtr fake_soinfo, const FakeLinker *fake_linker) {
25+
if (!fake_linker->is_init_success()) {
26+
LOGE("fakelinker environment initialization failed, cannot continue testing");
27+
return;
28+
}
29+
SoinfoPtr libc = fake_linker->soinfo_find(SoinfoFindType::kSTName, "libc.so", nullptr);
30+
if (!libc) {
31+
LOGE("fakelinker find libc soinfo failed");
32+
return;
33+
}
34+
35+
orig_access = reinterpret_cast<int (*)(const char *pathname, int mode)>(
36+
fake_linker->soinfo_get_export_symbol_address(libc, "access", nullptr));
37+
if (!orig_access) {
38+
LOGE("fakelinker find libc symbol access failed");
39+
return;
40+
}
41+
// Use so itself as a global library and hook all so loaded later
42+
if (!fake_linker->soinfo_add_to_global(fake_soinfo)) {
43+
LOGE("fakelinker add global soinfo failed");
44+
return;
45+
}
46+
const char *loaded_libs[] = {
47+
"libjavacore.so",
48+
};
49+
// Since libjavacore has been loaded before we load it, symbol relocation will not be triggered.
50+
// At this time, manually relocate it to make the hook take effect.
51+
if (!fake_linker->call_manual_relocation_by_names(fake_soinfo, 1, loaded_libs)) {
52+
LOGE("relocation libjavacore.so failed");
53+
return;
54+
}
55+
LOGW("fakelinker shared load successfully");
56+
}

demo/src/main/cpp/static_demo.cpp

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
//
2+
// Created by beichen on 2025/3/12.
3+
//
4+
#include <string.h>
5+
6+
#include <alog.h>
7+
#include <fake_linker.h>
8+
#include <macros.h>
9+
10+
static int (*orig_access)(const char *pathname, int mode) = nullptr;
11+
12+
C_API API_PUBLIC int access(const char *pathname, int mode) {
13+
LOGW("static hook access function path: %s, mode: %d", pathname, mode);
14+
if (strncmp(pathname, "/test/hook/path", 15) == 0) {
15+
LOGW("fake access /test/hook/path");
16+
return 0;
17+
}
18+
// Does not block other calls
19+
return orig_access(pathname, mode);
20+
}
21+
22+
C_API JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
23+
JNIEnv *env;
24+
if (vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6) != JNI_OK) {
25+
LOGE("JNI environment error");
26+
return JNI_EVERSION;
27+
}
28+
if (init_fakelinker(env,
29+
static_cast<FakeLinkerMode>(FakeLinkerMode::kFMSoinfo | FakeLinkerMode::kFMNativeHook |
30+
FakeLinkerMode::kFMJavaRegister),
31+
nullptr) != 0) {
32+
LOGE("fakelinker environment error");
33+
return JNI_ERR;
34+
}
35+
36+
const FakeLinker *fake_linker = get_fakelinker();
37+
38+
SoinfoPtr libc = fake_linker->soinfo_find(SoinfoFindType::kSTName, "libc.so", nullptr);
39+
if (!libc) {
40+
LOGE("fakelinker find libc soinfo failed");
41+
return JNI_ERR;
42+
}
43+
44+
orig_access = reinterpret_cast<int (*)(const char *pathname, int mode)>(
45+
fake_linker->soinfo_get_export_symbol_address(libc, "access", nullptr));
46+
if (!orig_access) {
47+
LOGE("fakelinker find libc symbol access failed");
48+
return JNI_ERR;
49+
}
50+
51+
SoinfoPtr fake_soinfo = fake_linker->soinfo_find(SoinfoFindType::kSTAddress, nullptr, nullptr);
52+
if (fake_soinfo == nullptr) {
53+
LOGE("find self soinfo failed");
54+
return JNI_ERR;
55+
}
56+
57+
// Use so itself as a global library and hook all so loaded later
58+
if (!fake_linker->soinfo_add_to_global(fake_soinfo)) {
59+
LOGE("fakelinker add global soinfo failed");
60+
return JNI_ERR;
61+
}
62+
const char *loaded_libs[] = {
63+
"libjavacore.so",
64+
};
65+
// Since libjavacore has been loaded before we load it, symbol relocation will not be triggered.
66+
// At this time, manually relocate it to make the hook take effect.
67+
if (!fake_linker->call_manual_relocation_by_names(fake_soinfo, 1, loaded_libs)) {
68+
LOGE("relocation libjavacore.so failed");
69+
return JNI_ERR;
70+
}
71+
LOGW("fakelinker static load successfully");
72+
return JNI_VERSION_1_6;
73+
}

0 commit comments

Comments
 (0)