Skip to content

File tree

9 files changed

+247
-0
lines changed

9 files changed

+247
-0
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@
1919
android:theme="@style/Theme.AndroidExample"
2020
tools:ignore="HardcodedDebugMode"
2121
tools:targetApi="31">
22+
<activity
23+
android:name=".sohooker.SoHookerActivity"
24+
android:exported="false"
25+
android:theme="@style/Theme.AndroidExample" />
2226
<activity
2327
android:name=".retrofit.RetrofitActivity"
2428
android:exported="false"

app/src/main/cpp/CMakeLists.txt

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -429,3 +429,27 @@ target_link_libraries(
429429
# 链接 log 库
430430
${log-lib}
431431
)
432+
433+
## so hooker ##########################################################################################
434+
435+
find_package(shadowhook REQUIRED CONFIG)
436+
437+
add_library(
438+
# 设置库的名称
439+
sohooker
440+
441+
# 设置库的类型
442+
SHARED
443+
444+
# 设置源文件路径
445+
sohooker.cpp
446+
)
447+
448+
target_link_libraries(
449+
sohooker
450+
# 链接 log 库
451+
${log-lib}
452+
# 链接 shadowhook
453+
shadowhook::shadowhook
454+
)
455+

app/src/main/cpp/sohooker.cpp

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
#include <jni.h>
2+
#include <android/log.h>
3+
#include "shadowhook.h"
4+
#include <link.h>
5+
#include <string.h>
6+
7+
#define TAG "sohooker"
8+
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__)
9+
10+
// 原始函数指针类型
11+
typedef bool (*orig_ca_func_t)(JNIEnv *, jclass, jobject);
12+
13+
static orig_ca_func_t orig_ca_func = nullptr;
14+
15+
// 替换函数
16+
static bool fake_ca(JNIEnv *env, jclass clazz, jobject context) {
17+
LOGI("🚀 fake_ca called, always return true");
18+
return true;
19+
}
20+
21+
/**
22+
* @brief 获取指定共享库(.so 文件)的加载基址。
23+
*
24+
* @param libname 要查找的共享库名称的子串,例如 "libtarget.so"。
25+
* 支持模糊匹配(通过 strstr),无需提供完整路径。
26+
*
27+
* @return void* 返回找到的库的加载基地址(dlpi_addr),
28+
* 如果未找到匹配的库,则返回 nullptr。
29+
*
30+
* @example
31+
* void* base = get_library_base("libexample.so");
32+
* if (base) {
33+
* printf("libexample.so 加载基址为: %p\n", base);
34+
* }
35+
*/
36+
void *get_library_base(const char *libname) {
37+
// 定义一个回调数据结构,用于在遍历时传递库名和保存找到的基地址
38+
struct callback_data {
39+
const char *libname;
40+
void *base;
41+
} data = {libname, nullptr};
42+
43+
// 使用 dl_iterate_phdr 遍历所有已加载的动态库,传递一个匿名函数(lambda)处理 item
44+
dl_iterate_phdr([](struct dl_phdr_info *info, size_t, void *data) {
45+
46+
// 将传入的 data 指针强制转换为 callback_data 类型
47+
auto *cb = (callback_data *) data;
48+
49+
// 判断当前库名中是否包含目标库名
50+
if (info->dlpi_name && strstr(info->dlpi_name, cb->libname)) {
51+
// 找到匹配库,保存其基地址
52+
cb->base = (void *) info->dlpi_addr;
53+
54+
// 返回 1 表示停止迭代(即已经找到了目标库)
55+
return 1;
56+
}
57+
58+
// 继续查找其他库
59+
return 0;
60+
}, &data);
61+
62+
// 返回找到的基址
63+
return data.base;
64+
}
65+
66+
void print_arch_info() {
67+
#if defined(__aarch64__)
68+
LOGI("Current architecture: arm64-v8a");
69+
#elif defined(__arm__)
70+
LOGI("Current architecture: armeabi-v7a");
71+
#elif defined(__i386__)
72+
LOGI("Current architecture: x86");
73+
#elif defined(__x86_64__)
74+
LOGI("Current architecture: x86_64");
75+
#else
76+
LOGI("Unknown architecture");
77+
#endif
78+
}
79+
80+
81+
// 在库加载时自动执行
82+
__attribute__((constructor)) static void init_hook() {
83+
// 初始化 ShadowHook
84+
shadowhook_init(SHADOWHOOK_MODE_UNIQUE, true); // debug 开启日志
85+
86+
// 获取 so 基址
87+
void *base = get_library_base("libhexymsb.so");
88+
if (!base) {
89+
LOGI("❌ libhexymsb.so not loaded yet");
90+
return;
91+
}
92+
93+
uintptr_t target_addr = 0;
94+
95+
// 适配不同的架构
96+
#ifdef __aarch64__
97+
// arm64-v8a
98+
target_addr = (uintptr_t) base + 0x09C8; // 函数内存地址 = so 基址 + 函数偏移
99+
#else
100+
// armeabi-v7a
101+
target_addr = (uintptr_t) base + 0x0610;
102+
#endif
103+
104+
// 打印当前设备架构信息
105+
print_arch_info();
106+
107+
LOGI("🎯 hooking address: %p", (void *) target_addr);
108+
109+
// Hook
110+
shadowhook_hook_func_addr(
111+
(void *) target_addr, // target
112+
(void *) fake_ca, // replacement
113+
(void **) &orig_ca_func // backup
114+
);
115+
}

app/src/main/java/com/cyrus/example/MainActivity.kt

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ import com.cyrus.example.unicorn.UnicornActivity
3333
import com.cyrus.example.unidbg.UnidbgActivity
3434
import com.cyrus.example.vmp.VMPActivity
3535
import com.cyrus.example.retrofit.RetrofitActivity
36+
import com.cyrus.example.sohooker.SoHookerActivity
3637

3738

3839
class MainActivity : AppCompatActivity() {
@@ -287,5 +288,14 @@ class MainActivity : AppCompatActivity() {
287288
)
288289
startActivity(intent)
289290
}
291+
292+
// 动态篡改 so 函数返回值
293+
findViewById<Button>(R.id.button_sohooker).setOnClickListener {
294+
val intent = Intent(
295+
this@MainActivity,
296+
SoHookerActivity::class.java
297+
)
298+
startActivity(intent)
299+
}
290300
}
291301
}
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package com.cyrus.example.sohooker
2+
3+
4+
import android.os.Bundle
5+
import android.widget.Toast
6+
import androidx.activity.ComponentActivity
7+
import androidx.activity.compose.setContent
8+
import androidx.compose.foundation.layout.Column
9+
import androidx.compose.foundation.layout.Spacer
10+
import androidx.compose.foundation.layout.fillMaxSize
11+
import androidx.compose.foundation.layout.height
12+
import androidx.compose.foundation.layout.padding
13+
import androidx.compose.material3.Button
14+
import androidx.compose.material3.Text
15+
import androidx.compose.runtime.getValue
16+
import androidx.compose.runtime.mutableStateOf
17+
import androidx.compose.runtime.remember
18+
import androidx.compose.runtime.setValue
19+
import androidx.compose.ui.Modifier
20+
import androidx.compose.ui.graphics.Color
21+
import androidx.compose.ui.unit.dp
22+
import marmojkfnf.mnhosc.cswoel.wvfxxn.Bhubscfh
23+
24+
class SoHookerActivity : ComponentActivity() {
25+
26+
override fun onCreate(savedInstanceState: Bundle?) {
27+
super.onCreate(savedInstanceState)
28+
29+
val bhubscfh = Bhubscfh()
30+
31+
setContent {
32+
33+
var resultText by remember { mutableStateOf("点击按钮调用 native 方法") }
34+
35+
Column(
36+
modifier = Modifier
37+
.fillMaxSize()
38+
.padding(16.dp)
39+
) {
40+
41+
Button(onClick = {
42+
val result = bhubscfh.ca(this@SoHookerActivity)
43+
resultText = "调用结果:$result"
44+
}) {
45+
Text("调用 native 方法")
46+
}
47+
48+
Spacer(modifier = Modifier.height(16.dp))
49+
50+
Button(onClick = {
51+
try {
52+
System.loadLibrary("sohooker")
53+
Toast.makeText(this@SoHookerActivity, "sohooker 加载成功", Toast.LENGTH_SHORT)
54+
.show()
55+
} catch (e: Throwable) {
56+
e.printStackTrace()
57+
58+
Toast.makeText(
59+
this@SoHookerActivity,
60+
"sohooker 加载失败: ${e.message}",
61+
Toast.LENGTH_SHORT
62+
).show()
63+
}
64+
}) {
65+
Text("加载 sohooker 篡改 so 函数返回值")
66+
}
67+
68+
Spacer(modifier = Modifier.height(32.dp))
69+
70+
Text(resultText, color = Color.White)
71+
}
72+
}
73+
}
74+
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package marmojkfnf.mnhosc.cswoel.wvfxxn;
2+
3+
import android.content.Context;
4+
5+
public class Bhubscfh {
6+
7+
static {
8+
System.loadLibrary("hexymsb");
9+
}
10+
11+
public final native boolean ca(Context context);
12+
13+
}
5.77 KB
Binary file not shown.
3.91 KB
Binary file not shown.

app/src/main/res/layout/activity_main.xml

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -207,6 +207,13 @@
207207
android:layout_marginTop="12dp"
208208
android:text="Retrofit" />
209209

210+
<Button
211+
android:id="@+id/button_sohooker"
212+
android:layout_width="wrap_content"
213+
android:layout_height="wrap_content"
214+
android:layout_marginTop="12dp"
215+
android:text="动态篡改 so 函数返回值" />
216+
210217
</LinearLayout>
211218

212219
</ScrollView>

0 commit comments

Comments
 (0)