7 Commits

Author SHA1 Message Date
Yuuki
ad2b785f1e Merge branch 'feature/add_inject_hide' of https://github.com/jiqiu2022/Zygisk-MyInjector into feature/add_inject_hide 2025-10-25 21:10:58 +08:00
Yuuki
29a678c6dd Update injectHide.kpm 2025-10-25 21:10:55 +08:00
jiqiu2021
6a79189290 feat:修改kpm config的路径 2025-10-25 20:16:44 +08:00
Yuuki
7e23ed7281 Update injectHide.kpm 2025-10-25 18:21:11 +08:00
Yuuki
aba55bfa57 Create injectHide.kpm 2025-10-25 17:43:04 +08:00
jiqiu2021
ab46a223f1 feat:增加kpm注入功能 2025-10-25 17:28:47 +08:00
jiqiu2021
3e5453ecf8 feat:移除旧版无用代码 2025-10-25 16:50:51 +08:00
14 changed files with 953 additions and 298 deletions

View File

@@ -18,10 +18,14 @@ public class ConfigManager {
public static final String MODULE_PATH = "/data/adb/modules/zygisk-myinjector";
public static final String CONFIG_FILE = MODULE_PATH + "/config.json";
public static final String SO_STORAGE_DIR = MODULE_PATH + "/so_files";
public static final String KPM_MODULE_PATH = MODULE_PATH + "/injectHide.kpm";
public static final String KPM_HIDE_CONFIG = "/data/local/tmp/kpm_hide_config.txt";
private static final String KPM_MODULE_NAME = "hideInject";
private final Context context;
private final Gson gson;
private ModuleConfig config;
private final Object kpmLock = new Object(); // 用于同步 KPM 操作
static {
// Configure Shell to use root
@@ -651,6 +655,258 @@ public class ConfigManager {
}
}
// ==================== KPM Module Management ====================
/**
* 检查 KPM 模块是否已加载
*/
public boolean isKpmModuleLoaded() {
Shell.Result result = Shell.cmd("lsmod | grep " + KPM_MODULE_NAME).exec();
return result.isSuccess() && !result.getOut().isEmpty();
}
/**
* 加载 KPM 模块
*/
public boolean loadKpmModule() {
if (!isRootAvailable()) {
Log.e(TAG, "Root access not available!");
return false;
}
// Check if module file exists
Shell.Result checkResult = Shell.cmd("test -f \"" + KPM_MODULE_PATH + "\" && echo 'exists'").exec();
if (!checkResult.isSuccess() || checkResult.getOut().isEmpty()) {
Log.e(TAG, "KPM module file not found: " + KPM_MODULE_PATH);
return false;
}
// Check if already loaded
if (isKpmModuleLoaded()) {
Log.i(TAG, "KPM module already loaded");
return true;
}
// Load module
Shell.Result result = Shell.cmd("insmod \"" + KPM_MODULE_PATH + "\"").exec();
if (result.isSuccess()) {
Log.i(TAG, "KPM module loaded successfully");
return true;
} else {
Log.e(TAG, "Failed to load KPM module: " + String.join("\n", result.getErr()));
return false;
}
}
/**
* 卸载 KPM 模块
*/
public boolean unloadKpmModule() {
if (!isRootAvailable()) {
Log.e(TAG, "Root access not available!");
return false;
}
if (!isKpmModuleLoaded()) {
Log.i(TAG, "KPM module not loaded");
return true;
}
Shell.Result result = Shell.cmd("rmmod " + KPM_MODULE_NAME).exec();
if (result.isSuccess()) {
Log.i(TAG, "KPM module unloaded successfully");
return true;
} else {
Log.e(TAG, "Failed to unload KPM module: " + String.join("\n", result.getErr()));
return false;
}
}
/**
* 重新加载 KPM 模块
*/
public boolean reloadKpmModule() {
// 使用锁防止并发重载
synchronized (kpmLock) {
Log.i(TAG, "Reloading KPM module...");
// Unload first
if (isKpmModuleLoaded()) {
if (!unloadKpmModule()) {
Log.e(TAG, "Failed to unload module before reload");
return false;
}
// Give kernel more time to cleanup (1 second for safety)
// 等待时间足够让内核完全清理资源
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Log.w(TAG, "Sleep interrupted during module reload", e);
Thread.currentThread().interrupt();
return false;
}
// Verify unload was successful
int retries = 5;
for (int i = 0; i < retries; i++) {
if (!isKpmModuleLoaded()) {
break;
}
Log.w(TAG, "Module still loaded, waiting... (" + (i+1) + "/" + retries + ")");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
}
if (isKpmModuleLoaded()) {
Log.e(TAG, "Module still loaded after unload attempts, aborting reload");
return false;
}
}
// Load again
boolean success = loadKpmModule();
if (success) {
Log.i(TAG, "Module reloaded successfully");
} else {
Log.e(TAG, "Failed to load module after unload");
}
return success;
} // synchronized
}
/**
* 更新 KPM 隐藏配置并重载模块
*/
public boolean updateKpmHideConfig(List<String> soNames) {
if (!isRootAvailable()) {
Log.e(TAG, "Root access not available!");
return false;
}
// Build config content
StringBuilder configContent = new StringBuilder();
for (String soName : soNames) {
configContent.append(soName).append("\n");
}
// Write to temp file
String tempFile = context.getCacheDir() + "/kpm_hide_config.txt";
try {
java.io.FileWriter writer = new java.io.FileWriter(tempFile);
writer.write(configContent.toString());
writer.close();
// Ensure module directory exists
Shell.cmd("mkdir -p \"" + MODULE_PATH + "\"").exec();
// Ensure /data/local/tmp exists and is writable
Shell.cmd("mkdir -p /data/local/tmp && chmod 777 /data/local/tmp").exec();
// Copy to /data/local/tmp with root
Shell.Result copyResult = Shell.cmd("cp \"" + tempFile + "\" \"" + KPM_HIDE_CONFIG + "\"").exec();
if (!copyResult.isSuccess()) {
Log.e(TAG, "Failed to copy KPM config: " + String.join("\n", copyResult.getErr()));
return false;
}
Shell.cmd("chmod 666 \"" + KPM_HIDE_CONFIG + "\"").exec();
// Clean up temp file
new File(tempFile).delete();
Log.i(TAG, "KPM hide config updated with " + soNames.size() + " entries");
// Reload module to apply changes
return reloadKpmModule();
} catch (Exception e) {
Log.e(TAG, "Failed to update KPM config", e);
return false;
}
}
/**
* 获取当前隐藏的 SO 列表
*/
public List<String> getHiddenSoList() {
List<String> hiddenList = new ArrayList<>();
Shell.Result result = Shell.cmd("cat \"" + KPM_HIDE_CONFIG + "\"").exec();
if (result.isSuccess()) {
for (String line : result.getOut()) {
String trimmed = line.trim();
if (!trimmed.isEmpty()) {
hiddenList.add(trimmed);
}
}
}
return hiddenList;
}
/**
* 添加 SO 到隐藏列表
*/
public boolean addSoToHideList(String soName) {
List<String> hiddenList = getHiddenSoList();
if (!hiddenList.contains(soName)) {
hiddenList.add(soName);
return updateKpmHideConfig(hiddenList);
}
return true;
}
/**
* 从隐藏列表移除 SO
*/
public boolean removeSoFromHideList(String soName) {
List<String> hiddenList = getHiddenSoList();
if (hiddenList.remove(soName)) {
return updateKpmHideConfig(hiddenList);
}
return true;
}
/**
* 获取所有可隐藏的 SO 文件列表(从已启用应用中提取)
*/
public List<String> getAvailableSoList() {
List<String> availableSos = new ArrayList<>();
// Always include our injector library
availableSos.add("libmyinjector.so");
// Add SOs from all enabled apps
for (Map.Entry<String, AppConfig> entry : config.perAppConfig.entrySet()) {
AppConfig appConfig = entry.getValue();
if (appConfig.enabled && appConfig.soFiles != null) {
for (SoFile soFile : appConfig.soFiles) {
if (!availableSos.contains(soFile.name)) {
availableSos.add(soFile.name);
}
}
}
}
// Add global SO files
if (config.globalSoFiles != null) {
for (SoFile soFile : config.globalSoFiles) {
if (!availableSos.contains(soFile.name)) {
availableSos.add(soFile.name);
}
}
}
return availableSos;
}
// Data classes
public static class ModuleConfig {
public boolean enabled = true;

View File

@@ -0,0 +1,105 @@
package com.jiqiu.configapp;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
/**
* SO 文件隐藏列表适配器
*/
public class HideSoAdapter extends RecyclerView.Adapter<HideSoAdapter.ViewHolder> {
private final List<KpmHideFragment.HideSoItem> items;
private final OnItemCheckedChangeListener listener;
public interface OnItemCheckedChangeListener {
void onItemCheckedChanged(KpmHideFragment.HideSoItem item, boolean isChecked);
}
public HideSoAdapter(List<KpmHideFragment.HideSoItem> items,
OnItemCheckedChangeListener listener) {
this.items = items;
this.listener = listener;
}
@NonNull
@Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext())
.inflate(R.layout.item_hide_so, parent, false);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(@NonNull ViewHolder holder, int position) {
KpmHideFragment.HideSoItem item = items.get(position);
holder.bind(item);
}
@Override
public int getItemCount() {
return items.size();
}
class ViewHolder extends RecyclerView.ViewHolder {
private final CheckBox cbHide;
private final TextView tvSoName;
private final TextView tvSoStatus;
private final TextView tvFixedBadge;
public ViewHolder(@NonNull View itemView) {
super(itemView);
cbHide = itemView.findViewById(R.id.cbHide);
tvSoName = itemView.findViewById(R.id.tvSoName);
tvSoStatus = itemView.findViewById(R.id.tvSoStatus);
tvFixedBadge = itemView.findViewById(R.id.tvFixedBadge);
}
public void bind(KpmHideFragment.HideSoItem item) {
tvSoName.setText(item.soName);
// 设置勾选状态
cbHide.setOnCheckedChangeListener(null); // 先移除监听器避免触发
cbHide.setChecked(item.isHidden);
// 显示状态
if (item.isFixed) {
tvSoStatus.setText("必需项 - 始终隐藏");
tvSoStatus.setTextColor(itemView.getContext().getResources()
.getColor(android.R.color.holo_green_dark, null));
tvFixedBadge.setVisibility(View.VISIBLE);
cbHide.setEnabled(false);
cbHide.setChecked(true); // 固定项始终勾选
} else {
tvSoStatus.setText(item.isHidden ? "已隐藏" : "未隐藏");
tvSoStatus.setTextColor(itemView.getContext().getResources()
.getColor(android.R.color.darker_gray, null));
tvFixedBadge.setVisibility(View.GONE);
cbHide.setEnabled(true);
}
// 设置点击监听器
cbHide.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (listener != null && !item.isFixed) {
listener.onItemCheckedChanged(item, isChecked);
}
});
// 整行点击也触发勾选
itemView.setOnClickListener(v -> {
if (!item.isFixed) {
cbHide.setChecked(!cbHide.isChecked());
}
});
}
}
}

View File

@@ -0,0 +1,249 @@
package com.jiqiu.configapp;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.fragment.app.Fragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.card.MaterialCardView;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* KPM 隐藏功能 Fragment
* 管理 KPM 内核模块和 SO 文件隐藏配置
*/
public class KpmHideFragment extends Fragment {
private static final String TAG = "KpmHideFragment";
private TextView tvModuleStatus;
private TextView tvModuleInfo;
private TextView tvConfigPath;
private Button btnReloadModule;
private RecyclerView rvSoList;
private ConfigManager configManager;
private HideSoAdapter adapter;
private ExecutorService executor;
private Handler mainHandler;
@Nullable
@Override
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.fragment_kpm_hide, container, false);
}
@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
initViews(view);
initExecutor();
initConfigManager();
setupListeners();
loadData();
}
private void initViews(View view) {
tvModuleStatus = view.findViewById(R.id.tvModuleStatus);
tvModuleInfo = view.findViewById(R.id.tvModuleInfo);
tvConfigPath = view.findViewById(R.id.tvConfigPath);
btnReloadModule = view.findViewById(R.id.btnReloadModule);
rvSoList = view.findViewById(R.id.rvSoList);
rvSoList.setLayoutManager(new LinearLayoutManager(getContext()));
}
private void initExecutor() {
executor = Executors.newSingleThreadExecutor();
mainHandler = new Handler(Looper.getMainLooper());
}
private void initConfigManager() {
configManager = new ConfigManager(getContext());
}
private void setupListeners() {
btnReloadModule.setOnClickListener(v -> reloadModule());
}
private void loadData() {
// 显示配置路径
tvConfigPath.setText("配置文件: " + ConfigManager.KPM_HIDE_CONFIG);
// 异步加载模块状态和 SO 列表
executor.execute(() -> {
try {
final boolean isLoaded = configManager.isKpmModuleLoaded();
final List<String> availableSos = configManager.getAvailableSoList();
final List<String> hiddenSos = configManager.getHiddenSoList();
mainHandler.post(() -> {
updateModuleStatus(isLoaded);
setupSoList(availableSos, hiddenSos);
});
} catch (Exception e) {
Log.e(TAG, "Error loading data", e);
mainHandler.post(() -> {
Toast.makeText(getContext(), "加载数据失败: " + e.getMessage(),
Toast.LENGTH_SHORT).show();
});
}
});
}
private void updateModuleStatus(boolean isLoaded) {
if (isLoaded) {
tvModuleStatus.setText("● 已加载");
tvModuleStatus.setTextColor(getResources().getColor(android.R.color.holo_green_dark, null));
tvModuleInfo.setText("KPM 内核模块运行中\n模块名称: hideInject\n版本: 0.0.1");
} else {
tvModuleStatus.setText("● 未加载");
tvModuleStatus.setTextColor(getResources().getColor(android.R.color.holo_red_dark, null));
tvModuleInfo.setText("KPM 内核模块未运行\n请检查模块文件是否存在或手动重载");
}
}
private void setupSoList(List<String> availableSos, List<String> hiddenSos) {
List<HideSoItem> items = new ArrayList<>();
for (String soName : availableSos) {
HideSoItem item = new HideSoItem();
item.soName = soName;
item.isHidden = hiddenSos.contains(soName);
// libmyinjector.so 是固定隐藏的
item.isFixed = soName.equals("libmyinjector.so");
items.add(item);
}
adapter = new HideSoAdapter(items, this::onSoItemCheckedChanged);
rvSoList.setAdapter(adapter);
}
private void onSoItemCheckedChanged(HideSoItem item, boolean isChecked) {
if (item.isFixed) {
// 固定项不允许取消勾选
Toast.makeText(getContext(), "libmyinjector.so 是必需的,不能取消隐藏",
Toast.LENGTH_SHORT).show();
return;
}
// 异步更新配置
executor.execute(() -> {
try {
boolean success;
if (isChecked) {
success = configManager.addSoToHideList(item.soName);
} else {
success = configManager.removeSoFromHideList(item.soName);
}
final boolean finalSuccess = success;
mainHandler.post(() -> {
if (finalSuccess) {
item.isHidden = isChecked;
Toast.makeText(getContext(),
isChecked ? "已添加到隐藏列表" : "已从隐藏列表移除",
Toast.LENGTH_SHORT).show();
// 更新模块状态
refreshModuleStatus();
} else {
Toast.makeText(getContext(), "更新失败,请检查日志",
Toast.LENGTH_SHORT).show();
// 恢复原来的状态
if (adapter != null) {
adapter.notifyDataSetChanged();
}
}
});
} catch (Exception e) {
Log.e(TAG, "Error updating SO hide status", e);
mainHandler.post(() -> {
Toast.makeText(getContext(), "更新失败: " + e.getMessage(),
Toast.LENGTH_SHORT).show();
if (adapter != null) {
adapter.notifyDataSetChanged();
}
});
}
});
}
private void reloadModule() {
btnReloadModule.setEnabled(false);
btnReloadModule.setText("重载中...");
executor.execute(() -> {
try {
final boolean success = configManager.reloadKpmModule();
mainHandler.post(() -> {
btnReloadModule.setEnabled(true);
btnReloadModule.setText("重载模块");
if (success) {
Toast.makeText(getContext(), "模块重载成功", Toast.LENGTH_SHORT).show();
refreshModuleStatus();
} else {
Toast.makeText(getContext(), "模块重载失败,请查看日志",
Toast.LENGTH_SHORT).show();
}
});
} catch (Exception e) {
Log.e(TAG, "Error reloading module", e);
mainHandler.post(() -> {
btnReloadModule.setEnabled(true);
btnReloadModule.setText("重载模块");
Toast.makeText(getContext(), "重载失败: " + e.getMessage(),
Toast.LENGTH_SHORT).show();
});
}
});
}
private void refreshModuleStatus() {
executor.execute(() -> {
try {
final boolean isLoaded = configManager.isKpmModuleLoaded();
mainHandler.post(() -> updateModuleStatus(isLoaded));
} catch (Exception e) {
Log.e(TAG, "Error refreshing module status", e);
}
});
}
@Override
public void onDestroyView() {
super.onDestroyView();
if (executor != null && !executor.isShutdown()) {
executor.shutdown();
}
}
/**
* SO 隐藏项数据类
*/
public static class HideSoItem {
public String soName;
public boolean isHidden;
public boolean isFixed; // 是否是固定隐藏项(不可取消)
}
}

View File

@@ -19,6 +19,7 @@ public class MainActivity extends AppCompatActivity implements SettingsFragment.
private AppListFragment appListFragment;
private SettingsFragment settingsFragment;
private SoManagerFragment soManagerFragment;
private KpmHideFragment kpmHideFragment;
@Override
protected void onCreate(Bundle savedInstanceState) {
@@ -53,6 +54,9 @@ public class MainActivity extends AppCompatActivity implements SettingsFragment.
} else if (itemId == R.id.navigation_so_manager) {
showSoManagerFragment();
return true;
} else if (itemId == R.id.navigation_kpm_hide) {
showKpmHideFragment();
return true;
} else if (itemId == R.id.navigation_settings) {
showSettingsFragment();
return true;
@@ -75,6 +79,13 @@ public class MainActivity extends AppCompatActivity implements SettingsFragment.
showFragment(soManagerFragment);
}
private void showKpmHideFragment() {
if (kpmHideFragment == null) {
kpmHideFragment = new KpmHideFragment();
}
showFragment(kpmHideFragment);
}
private void showSettingsFragment() {
if (settingsFragment == null) {
settingsFragment = new SettingsFragment();

View File

@@ -0,0 +1,202 @@
<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".KpmHideFragment">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<!-- 标题 -->
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="KPM 注入隐藏"
android:textSize="18sp"
android:textStyle="bold"
android:layout_marginBottom="16dp" />
<!-- KPM 模块状态卡片 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="模块状态"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvModuleStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="● 检查中..."
android:textSize="14sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvModuleInfo"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="正在检查模块状态..."
android:textSize="12sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="12dp" />
<com.google.android.material.button.MaterialButton
android:id="@+id/btnReloadModule"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="重载模块"
style="@style/Widget.MaterialComponents.Button.OutlinedButton" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 配置说明卡片 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="2dp"
app:cardBackgroundColor="#FFF3E0">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text=" 使用说明"
android:textSize="14sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="• KPM 内核模块可以隐藏进程的内存映射信息\n• libmyinjector.so 将自动隐藏(不可取消)\n• 勾选其他 SO 文件可隐藏其在 /proc/[pid]/maps 中的显示\n• 每次更改都会自动重载内核模块以应用配置"
android:textSize="12sp"
android:lineSpacingExtra="4dp"
android:textColor="#E65100"
android:layout_marginBottom="8dp" />
<TextView
android:id="@+id/tvConfigPath"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="配置文件: /data/local/tmp/kpm_hide_config.txt"
android:textSize="10sp"
android:textColor="@android:color/darker_gray"
android:fontFamily="monospace" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- 固定隐藏项说明 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="🔒 固定隐藏项"
android:textSize="14sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="以下库文件将始终被隐藏:"
android:textSize="12sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="4dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="• libmyinjector.so (Zygisk 注入器)"
android:textSize="12sp"
android:fontFamily="monospace"
android:textColor="@android:color/holo_green_dark" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<!-- SO 隐藏列表 -->
<com.google.android.material.card.MaterialCardView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
app:cardElevation="2dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="16dp">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="可选隐藏 SO 列表"
android:textSize="16sp"
android:textStyle="bold"
android:layout_marginBottom="8dp" />
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="从已配置的应用中选择需要隐藏的 SO 文件"
android:textSize="12sp"
android:textColor="@android:color/darker_gray"
android:layout_marginBottom="12dp" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rvSoList"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:minHeight="100dp"
tools:listitem="@layout/item_hide_so" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
</LinearLayout>
</ScrollView>

View File

@@ -0,0 +1,58 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="12dp"
android:gravity="center_vertical"
android:background="?attr/selectableItemBackground">
<CheckBox
android:id="@+id/cbHide"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="12dp" />
<LinearLayout
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="1"
android:orientation="vertical">
<TextView
android:id="@+id/tvSoName"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="libexample.so"
android:textSize="14sp"
android:textStyle="bold"
android:fontFamily="monospace" />
<TextView
android:id="@+id/tvSoStatus"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="未隐藏"
android:textSize="12sp"
android:textColor="@android:color/darker_gray"
android:layout_marginTop="2dp" />
</LinearLayout>
<TextView
android:id="@+id/tvFixedBadge"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="固定"
android:textSize="10sp"
android:textColor="@android:color/white"
android:background="@android:color/holo_green_dark"
android:paddingStart="8dp"
android:paddingEnd="8dp"
android:paddingTop="2dp"
android:paddingBottom="2dp"
android:layout_marginStart="8dp"
android:visibility="gone" />
</LinearLayout>

View File

@@ -10,6 +10,11 @@
android:icon="@android:drawable/ic_menu_save"
android:title="@string/title_so_manager" />
<item
android:id="@+id/navigation_kpm_hide"
android:icon="@android:drawable/ic_secure"
android:title="@string/title_kpm_hide" />
<item
android:id="@+id/navigation_settings"
android:icon="@android:drawable/ic_menu_preferences"

View File

@@ -4,6 +4,7 @@
<!-- 底部导航 -->
<string name="title_apps">应用列表</string>
<string name="title_so_manager">SO库管理</string>
<string name="title_kpm_hide">KPM隐藏</string>
<string name="title_settings">全局设置</string>
<!-- 应用列表 -->
@@ -21,4 +22,25 @@
<!-- 关于 -->
<string name="about">关于</string>
<string name="app_description">MyInjector 配置应用,用于管理注入设置</string>
<!-- KPM 隐藏 -->
<string name="kpm_hide_title">KPM 注入隐藏</string>
<string name="kpm_module_status">模块状态</string>
<string name="kpm_module_loaded">已加载</string>
<string name="kpm_module_not_loaded">未加载</string>
<string name="kpm_module_info">KPM 内核模块信息</string>
<string name="kpm_reload_module">重载模块</string>
<string name="kpm_reload_success">模块重载成功</string>
<string name="kpm_reload_failed">模块重载失败</string>
<string name="kpm_config_path">配置文件路径</string>
<string name="kpm_fixed_items">固定隐藏项</string>
<string name="kpm_optional_items">可选隐藏 SO 列表</string>
<string name="kpm_usage_info">使用说明</string>
<string name="kpm_fixed_badge">固定</string>
<string name="kpm_hidden">已隐藏</string>
<string name="kpm_not_hidden">未隐藏</string>
<string name="kpm_add_success">已添加到隐藏列表</string>
<string name="kpm_remove_success">已从隐藏列表移除</string>
<string name="kpm_update_failed">更新失败</string>
<string name="kpm_fixed_cannot_uncheck">libmyinjector.so 是必需的,不能取消隐藏</string>
</resources>

View File

@@ -70,6 +70,10 @@ afterEvaluate {
from("$projectDir") {
include 'service.sh'
}
// Copy KPM module
from("$projectDir/kpm") {
include 'injectHide.kpm'
}
// Copy ConfigApp APK if it exists
def apkFile = file("$rootDir/configapp/build/outputs/apk/debug/configapp-debug.apk")
if (apkFile.exists()) {

BIN
module/kpm/injectHide.kpm Normal file

Binary file not shown.

View File

@@ -94,5 +94,46 @@ chown -R root:root /data/adb/modules/zygisk-myinjector
log "ConfigApp 安装脚本执行完成"
# ==================== KPM 模块加载 ====================
# KPM 模块路径
KPM_MODULE="$MODDIR/injectHide.kpm"
KPM_CONFIG="/data/local/tmp/kpm_hide_config.txt"
log "开始加载 KPM 内核模块"
# 检查 KPM 模块文件是否存在
if [ ! -f "$KPM_MODULE" ]; then
log "KPM 模块文件不存在: $KPM_MODULE"
else
log "找到 KPM 模块文件: $KPM_MODULE"
# 创建初始配置文件(如果不存在)
if [ ! -f "$KPM_CONFIG" ]; then
log "创建初始 KPM 配置文件"
# 确保 /data/local/tmp 目录存在且权限正确
mkdir -p /data/local/tmp
chmod 777 /data/local/tmp
echo "libmyinjector.so" > "$KPM_CONFIG"
chmod 666 "$KPM_CONFIG"
fi
# 等待一段时间确保系统稳定
sleep 3
# 加载 KPM 模块
log "正在加载 KPM 模块..."
insmod "$KPM_MODULE" 2>&1 | while read line; do
log "insmod: $line"
done
# 检查模块是否加载成功
if lsmod | grep -q "hideInject"; then
log "KPM 模块加载成功!"
else
log "KPM 模块加载失败,请检查日志"
fi
fi
# 脚本完成
exit 0

View File

@@ -1,10 +0,0 @@
//
// Created by Perfare on 2020/7/4.
//
#ifndef ZYGISK_IL2CPPDUMPER_GAME_H
#define ZYGISK_IL2CPPDUMPER_GAME_H
#define AimPackageName "com.tencent.mobileqq"
#endif //ZYGISK_IL2CPPDUMPER_GAME_H

View File

@@ -1,284 +0,0 @@
//
// Created by Perfare on 2020/7/4.
//
#include "hack.h"
#include "log.h"
#include "xdl.h"
#include <cstring>
#include <cstdio>
#include <unistd.h>
#include <sys/system_properties.h>
#include <dlfcn.h>
#include <jni.h>
#include <thread>
#include <sys/mman.h>
#include <linux/unistd.h>
#include <array>
#include <sys/stat.h>
//#include <asm-generic/fcntl.h>
#include <fcntl.h>
#include "newriruhide.h"
void load_so(const char *game_data_dir, JavaVM *vm, const char *soname) {
bool load = false;
LOGI("hack_start %s", game_data_dir);
// 构建新文件路径,使用传入的 soname 参数
char new_so_path[256];
snprintf(new_so_path, sizeof(new_so_path), "%s/files/%s.so", game_data_dir, soname);
// 构建源文件路径
char src_path[256];
snprintf(src_path, sizeof(src_path), "/data/local/tmp/%s.so", soname);
// 打开源文件
int src_fd = open(src_path, O_RDONLY);
if (src_fd < 0) {
LOGE("Failed to open %s: %s (errno: %d)", src_path, strerror(errno), errno);
return;
}
// 打开目标文件
int dest_fd = open(new_so_path, O_WRONLY | O_CREAT | O_TRUNC, 0644);
if (dest_fd < 0) {
LOGE("Failed to open %s", new_so_path);
close(src_fd);
return;
}
// 复制文件内容
char buffer[4096];
ssize_t bytes;
while ((bytes = read(src_fd, buffer, sizeof(buffer))) > 0) {
if (write(dest_fd, buffer, bytes) != bytes) {
LOGE("Failed to write to %s", new_so_path);
close(src_fd);
close(dest_fd);
return;
}
}
// 关闭文件描述符
close(src_fd);
close(dest_fd);
// 修改文件权限
if (chmod(new_so_path, 0755) != 0) {
LOGE("Failed to change permissions on %s: %s (errno: %d)", new_so_path, strerror(errno), errno);
return;
} else {
LOGI("Successfully changed permissions to 755 on %s", new_so_path);
}
// 加载 .so 文件
void *handle;
for (int i = 0; i < 10; i++) {
handle = dlopen(new_so_path, RTLD_NOW | RTLD_LOCAL);
if (handle) {
LOGI("Successfully loaded %s", new_so_path);
load = true;
char new_soname[256];
sprintf(new_soname, "%s.so", soname);
riru_hide(new_soname);
break;
} else {
LOGE("Failed to load %s: %s", new_so_path, dlerror());
sleep(1);
}
}
// 如果加载失败
if (!load) {
LOGI("%s.so not found in thread %d", soname, gettid());
}
// 查找 JNI_OnLoad 并调用
// void (*setupSignalHandler)();
// *(void **) (&setupSignalHandler) = dlsym(handle, "setupSignalHandler");
//
// if (setupSignalHandler) {
// LOGI("setupSignalHandler symbol found, calling setupSignalHandler.");
// setupSignalHandler(); // 调用找到的函数
// } else {
// LOGE("setupSignalHandler symbol not found in %s", new_so_path);
// }
}
void hack_start(const char *game_data_dir,JavaVM *vm) {
load_so(game_data_dir,vm,"test");
//如果要注入多个so那么就在这里不断的添加load_so函数即可
}
std::string GetLibDir(JavaVM *vms) {
JNIEnv *env = nullptr;
vms->AttachCurrentThread(&env, nullptr);
jclass activity_thread_clz = env->FindClass("android/app/ActivityThread");
if (activity_thread_clz != nullptr) {
jmethodID currentApplicationId = env->GetStaticMethodID(activity_thread_clz,
"currentApplication",
"()Landroid/app/Application;");
if (currentApplicationId) {
jobject application = env->CallStaticObjectMethod(activity_thread_clz,
currentApplicationId);
jclass application_clazz = env->GetObjectClass(application);
if (application_clazz) {
jmethodID get_application_info = env->GetMethodID(application_clazz,
"getApplicationInfo",
"()Landroid/content/pm/ApplicationInfo;");
if (get_application_info) {
jobject application_info = env->CallObjectMethod(application,
get_application_info);
jfieldID native_library_dir_id = env->GetFieldID(
env->GetObjectClass(application_info), "nativeLibraryDir",
"Ljava/lang/String;");
if (native_library_dir_id) {
auto native_library_dir_jstring = (jstring) env->GetObjectField(
application_info, native_library_dir_id);
auto path = env->GetStringUTFChars(native_library_dir_jstring, nullptr);
LOGI("lib dir %s", path);
std::string lib_dir(path);
env->ReleaseStringUTFChars(native_library_dir_jstring, path);
return lib_dir;
} else {
LOGE("nativeLibraryDir not found");
}
} else {
LOGE("getApplicationInfo not found");
}
} else {
LOGE("application class not found");
}
} else {
LOGE("currentApplication not found");
}
} else {
LOGE("ActivityThread not found");
}
return {};
}
static std::string GetNativeBridgeLibrary() {
auto value = std::array<char, PROP_VALUE_MAX>();
__system_property_get("ro.dalvik.vm.native.bridge", value.data());
return {value.data()};
}
struct NativeBridgeCallbacks {
uint32_t version;
void *initialize;
void *(*loadLibrary)(const char *libpath, int flag);
void *(*getTrampoline)(void *handle, const char *name, const char *shorty, uint32_t len);
void *isSupported;
void *getAppEnv;
void *isCompatibleWith;
void *getSignalHandler;
void *unloadLibrary;
void *getError;
void *isPathSupported;
void *initAnonymousNamespace;
void *createNamespace;
void *linkNamespaces;
void *(*loadLibraryExt)(const char *libpath, int flag, void *ns);
};
bool NativeBridgeLoad(const char *game_data_dir, int api_level, void *data, size_t length) {
//TODO 等待houdini初始化
sleep(5);
auto libart = dlopen("libart.so", RTLD_NOW);
auto JNI_GetCreatedJavaVMs = (jint (*)(JavaVM **, jsize, jsize *)) dlsym(libart,
"JNI_GetCreatedJavaVMs");
LOGI("JNI_GetCreatedJavaVMs %p", JNI_GetCreatedJavaVMs);
JavaVM *vms_buf[1];
JavaVM *vms;
jsize num_vms;
jint status = JNI_GetCreatedJavaVMs(vms_buf, 1, &num_vms);
if (status == JNI_OK && num_vms > 0) {
vms = vms_buf[0];
} else {
LOGE("GetCreatedJavaVMs error");
return false;
}
auto lib_dir = GetLibDir(vms);
if (lib_dir.empty()) {
LOGE("GetLibDir error");
return false;
}
if (lib_dir.find("/lib/x86") != std::string::npos) {
LOGI("no need NativeBridge");
munmap(data, length);
return false;
}
auto nb = dlopen("libhoudini.so", RTLD_NOW);
if (!nb) {
auto native_bridge = GetNativeBridgeLibrary();
LOGI("native bridge: %s", native_bridge.data());
nb = dlopen(native_bridge.data(), RTLD_NOW);
}
if (nb) {
LOGI("nb %p", nb);
auto callbacks = (NativeBridgeCallbacks *) dlsym(nb, "NativeBridgeItf");
if (callbacks) {
LOGI("NativeBridgeLoadLibrary %p", callbacks->loadLibrary);
LOGI("NativeBridgeLoadLibraryExt %p", callbacks->loadLibraryExt);
LOGI("NativeBridgeGetTrampoline %p", callbacks->getTrampoline);
int fd = syscall(__NR_memfd_create, "anon", MFD_CLOEXEC);
ftruncate(fd, (off_t) length);
void *mem = mmap(nullptr, length, PROT_WRITE, MAP_SHARED, fd, 0);
memcpy(mem, data, length);
munmap(mem, length);
munmap(data, length);
char path[PATH_MAX];
snprintf(path, PATH_MAX, "/proc/self/fd/%d", fd);
LOGI("arm path %s", path);
void *arm_handle;
if (api_level >= 26) {
arm_handle = callbacks->loadLibraryExt(path, RTLD_NOW, (void *) 3);
} else {
arm_handle = callbacks->loadLibrary(path, RTLD_NOW);
}
if (arm_handle) {
LOGI("arm handle %p", arm_handle);
auto init = (void (*)(JavaVM *, void *)) callbacks->getTrampoline(arm_handle,
"JNI_OnLoad",
nullptr, 0);
LOGI("JNI_OnLoad %p", init);
init(vms, (void *) game_data_dir);
return true;
}
close(fd);
}
}
return false;
}
void hack_prepare(const char *_data_dir, void *data, size_t length) {
LOGI("hack thread: %d", gettid());
int api_level = android_get_device_api_level();
LOGI("api level: %d", api_level);
#if defined(__i386__) || defined(__x86_64__)
if (!NativeBridgeLoad(_data_dir, api_level, data, length)) {
#endif
hack_start(_data_dir, nullptr);
#if defined(__i386__) || defined(__x86_64__)
}
#endif
}
#if defined(__arm__) || defined(__aarch64__)
JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
auto game_data_dir = (const char *) reserved;
std::thread hack_thread(hack_start, game_data_dir,vm);
hack_thread.detach();
return JNI_VERSION_1_6;
}
#endif

View File

@@ -11,7 +11,6 @@
#include <time.h>
#include "hack.h"
#include "zygisk.hpp"
#include "game.h"
#include "log.h"
#include "dlfcn.h"
#include "config.h"
@@ -30,9 +29,6 @@ public:
void preAppSpecialize(AppSpecializeArgs *args) override {
auto package_name = env->GetStringUTFChars(args->nice_name, nullptr);
auto app_data_dir = env->GetStringUTFChars(args->app_data_dir, nullptr);
// if (strcmp(package_name, AimPackageName) == 0){
// args->runtime_flags=8451;
// }
LOGI("preAppSpecialize %s %s %d", package_name, app_data_dir,args->runtime_flags);
preSpecialize(package_name, app_data_dir);