mirror of
https://github.com/LSPosed/LSPosed.git
synced 2025-05-07 07:26:44 +08:00
init
commit
83e5721b88
25
Home.md
Normal file
25
Home.md
Normal file
@ -0,0 +1,25 @@
|
||||
# LSPosed official wiki
|
||||
|
||||
基于 Riru/Zygisk 的 ART hook 框架,提供与原版 Xposed 相同的 API, 使用 [LSPlant](https://github.com/LSPosed/LSPlant) 进行 hook。
|
||||
|
||||
> Xposed 框架是一套开放源代码的框架服务,可以在不修改APK文件的情况下修改目标应用的运行,基于它可以制作功能强大的模块,且在功能不冲突的情况下同时运作。
|
||||
|
||||
LSPosed 框架和原版 Xposed 框架的不同之处
|
||||
|
||||
1. LSPosed 支持 Android 8.1 以后的版本
|
||||
2. LSPosed 只有注入被勾选的应用,其他应用运行在干净的环境
|
||||
3. 对于不需要注入系统服务的模块,重启目标应用即可激活
|
||||
4. LSPosed 极难被检测,文件系统没有可疑痕迹,不需要安装独立的管理器应用
|
||||
|
||||
## Chat group / 用户讨论群组
|
||||
|
||||
- Telegram channel: [@LSPosed](https://t.me/LSPosed)
|
||||
|
||||
## Catalog / 目录
|
||||
|
||||
### For User / 用户
|
||||
- [How to use it](https://github.com/LSPosed/LSPosed/wiki/How-to-use-it) / [如何使用](https://github.com/LSPosed/LSPosed/wiki/%E5%A6%82%E4%BD%95%E4%BD%BF%E7%94%A8)
|
||||
### For Developer / 开发者
|
||||
- [New XSharedPreferences](https://github.com/LSPosed/LSPosed/wiki/New-XSharedPreferences)
|
||||
- [Module Scope](https://github.com/LSPosed/LSPosed/wiki/Module-Scope)
|
||||
- [Native Hook](https://github.com/LSPosed/LSPosed/wiki/Native-Hook)
|
19
How-to-use-it.md
Normal file
19
How-to-use-it.md
Normal file
@ -0,0 +1,19 @@
|
||||
## Installation
|
||||
1. Make sure you have [Magisk](https://topjohnwu.github.io/Magisk/install.html) 23.0+ and [Riru](https://github.com/RikkaApps/Riru#install) 25.0+ installed on your device.
|
||||
1. Go to Magisk's `Modules` tab, search and install LSPosed.
|
||||
1. Reboot your device and wait for a while, you will see the LSPosed icon.
|
||||
1. Launch LSPosed and check the activation status.
|
||||
|
||||
### Install & Activate Modules
|
||||
1. LSPosed is a framework and it cannot work without modules. You can browser from the `Repository` page from the app or the [web](http://modules.lsposed.org/).
|
||||
1. Download and install modules as normal apps.
|
||||
1. After installation, go to LSPosed's `Modules` page and click the module you installed, tick the enable switch.
|
||||
1. Select apps to which the modules should apply. Most of the modules now support displaying recommended scope, which LSPosed will select automatically.
|
||||
1. Some obsolete modules need to inject into each app. It's so dangerous that LSPosed doesn't support `Select All` and requires the user to tick each app one by one manually (the same policy as Magisk Hide).
|
||||
|
||||
### Dual App / Multiuser Support
|
||||
1. Before that, you should notice that many obsolete modules were born in the era when multiuser was not popular and thus they may not support multiuser well. There's nothing LSPosed could do to make them work on multiuser mode.
|
||||
1. Moreover, the LSPosed Manager will only work on the primary user, and it can manage modules and apps from all users. You DON'T have to install it on the other users.
|
||||
1. As long as your device has multiple users, LSPosed shows a tab for each user on the `Modules` page. Switch between tabs to manager modules from different users.
|
||||
1. You should install modules to the user on which you want it to work. You can do it manually (recommended) by switching to that user and install the modules. Or you can do it via LSPosed Manager (not recommended as long as you can do it manually).
|
||||
|
28
Module-Scope.md
Normal file
28
Module-Scope.md
Normal file
@ -0,0 +1,28 @@
|
||||
### Background
|
||||
|
||||
Module scopes are required in LSPosed to activate a module. Of course, user can select their own scope for a specific module, but it's too advance for a user to determine the real module scope. So in LSPosed, a new meta-data is introduced for module developers to declare its scope.
|
||||
|
||||
### Example usage
|
||||
If a module wants to hook package `com.example.a` and `com.example.b`, in `AndroidManifest.xml`, a meta-data named `xposedscope` should be defined as:
|
||||
```
|
||||
<meta-data
|
||||
android:name="xposedscope"
|
||||
android:resource="@array/example_scope" />
|
||||
```
|
||||
And in `array.xml`, a string array named `example_scope` (this name could be arbitrarily changed) should be defined as:
|
||||
```
|
||||
<string-array name="example_scope" >
|
||||
<item>com.example.a</item>
|
||||
<item>com.example.b</item>
|
||||
</string-array>
|
||||
```
|
||||
|
||||
If no [alternative resources](https://developer.android.com/guide/topics/resources/providing-resources#AlternativeResources), can hardcode it:
|
||||
```
|
||||
<meta-data
|
||||
android:name="xposedscope"
|
||||
android:value="com.example.a;com.example.b" />
|
||||
```
|
||||
|
||||
### Advantage
|
||||
By defining such a meta-data, all apps within this array will be selected by default when the module is enabled in the manager.
|
141
Native-Hook.md
Normal file
141
Native-Hook.md
Normal file
@ -0,0 +1,141 @@
|
||||
To use the native hook, you have to know how to write an NDK project first.
|
||||
|
||||
### How Native Hook Works
|
||||
|
||||
Native Hook can help you hook native functions of an app. Whenever a new native library loaded, LSPosed will invoke a callback of the module and the module can then perform hooks on this native library.
|
||||
|
||||
### Header File
|
||||
|
||||
To use Native hook, you have to first create a header file with the following content:
|
||||
```c++
|
||||
|
||||
typedef int (*HookFunType)(void *func, void *replace, void **backup);
|
||||
|
||||
typedef int (*UnhookFunType)(void *func);
|
||||
|
||||
typedef void (*NativeOnModuleLoaded)(const char *name, void *handle);
|
||||
|
||||
typedef struct {
|
||||
uint32_t version;
|
||||
HookFunType hook_func;
|
||||
UnhookFunType unhook_func;
|
||||
} NativeAPIEntries;
|
||||
|
||||
typedef NativeOnModuleLoaded (*NativeInit)(const NativeAPIEntries *entries);
|
||||
```
|
||||
|
||||
### Entries
|
||||
|
||||
After creating the header file, you can then create a source file with the entry function:
|
||||
```c++
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||
NativeOnModuleLoaded native_init(const NativeAPIEntries *entries);
|
||||
```
|
||||
|
||||
Notice the name `native_init` has to be kept and exported, so we have to use the attribute `[[gnu::visibility("default")]] [[gnu::used]]` for this purpose. `extern "C"` is to make the name `native_init` to be in c style.
|
||||
|
||||
The `NativeAPIEntries` object contains necessary util functions for you to hook (Do not try to modify its content. It will crash.). Currently, they are `hook_fun` and `unhook_func`, which can be used to hook and unhook functions respectively. The return value of the function is the callback function which will be called once a library is loaded by `dlopen`. We will talk about this callback function later.
|
||||
|
||||
You can perform hooks on system libraries in `native_init`.
|
||||
|
||||
|
||||
### Callback
|
||||
|
||||
The callback function should be in type `NativeOnModuleLoaded`. It will be called by LSPosed each time a library is loaded. It receives two parameters: `name` is the name of the loaded library (e.g. `/xxx/libtarget.so`); `handle` is the handle of the library which you can use to `dlsym`. You can perform `dlsym` of this library to get the pointer of the target function can call the `hook_func`.
|
||||
|
||||
### JNIEnv Hooks
|
||||
|
||||
You may try to hook some JNIEnv functions like `GetMethodId`. Since the function is in `libart.so` which you cannot easily `dlopen`, you can try to create `JNI_OnLoad` to let Android System give you a `JavaVm` and then get a `JNIEnv` once your library is loaded. Then you can get the JNIEnv functions by the `JNIEnv` object.
|
||||
|
||||
### Tell Entries to Framework
|
||||
|
||||
Naturally, like Java hook, you have to tell which libraries of your module contain `native_init`. You can do this by placing the name of your libraries to the `assets/native_init` file of your apk. Then you have to manually `System.loadLibrary` your module once your module is loaded.
|
||||
|
||||
|
||||
### Simple Example
|
||||
Here is a simple example which use native hook:
|
||||
|
||||
assets/native_init
|
||||
```
|
||||
libexample.so
|
||||
```
|
||||
|
||||
assets/xposed_init
|
||||
```
|
||||
org.lsposed.example.MainHook
|
||||
```
|
||||
|
||||
AndroidManifest.xml
|
||||
```xml
|
||||
<application ...
|
||||
android:multiArch="true"
|
||||
android:extractNativeLibs="false"
|
||||
>
|
||||
```
|
||||
|
||||
MainHook.kt
|
||||
```kotlin
|
||||
package org.lsposed.example
|
||||
|
||||
class MainHook : IXposedHookLoadPackage {
|
||||
override fun handleLoadPackage(lpparam: XC_LoadPackage.LoadPackageParam) {
|
||||
if (lpparam.packageName == "org.lsposed.target") {
|
||||
try {
|
||||
System.loadLibrary("example")
|
||||
} catch (e: Throwable) {
|
||||
LogUtil.e(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
example.cc
|
||||
```c++
|
||||
static HookFunType hook_func = nullptr;
|
||||
|
||||
int (*backup)();
|
||||
|
||||
int fake() {
|
||||
return backup() + 1;
|
||||
}
|
||||
|
||||
FILE *(*backup_fopen)(const char *filename, const char *mode);
|
||||
|
||||
FILE *fake_fopen(const char *filename, const char *mode) {
|
||||
if (strstr(filename, "banned")) return nullptr;
|
||||
return backup_fopen(filename, mode);
|
||||
}
|
||||
|
||||
jclass (*backup_FindClass)(JNIEnv *env, const char *name);
|
||||
jclass fake_FindClass(JNIEnv *env, const char *name)
|
||||
{
|
||||
if(!strcmp(name, "dalvik/system/BaseDexClassLoader"))
|
||||
return nullptr;
|
||||
return backup_FindClass(env, name);
|
||||
}
|
||||
|
||||
void on_library_loaded(const char *name, void *handle) {
|
||||
// hooks on `libtarget.so`
|
||||
if (std::string(name).ends_with("libtarget.so")) {
|
||||
void *target = dlsym(handle, "target_fun");
|
||||
hook_func(target, (void *) fake, (void **) &backup);
|
||||
}
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||
jint JNI_OnLoad(JavaVM *jvm, void*) {
|
||||
JNIEnv *env = nullptr;
|
||||
jvm->GetEnv((void **)&env, JNI_VERSION_1_6);
|
||||
hook_func((void *)env->functions->FindClass, (void *)fake_FindClass, (void **)&backup_FindClass);
|
||||
return JNI_VERSION_1_6;
|
||||
}
|
||||
|
||||
extern "C" [[gnu::visibility("default")]] [[gnu::used]]
|
||||
NativeOnModuleLoaded native_init(const NativeAPIEntries *entries) {
|
||||
hook_func = entries->hook_func;
|
||||
// system hooks
|
||||
hook_func((void*) fopen, (void*) fake_fopen, (void**) &backup_fopen);
|
||||
return on_library_loaded;
|
||||
}
|
||||
```
|
169
New-XSharedPreferences.md
Normal file
169
New-XSharedPreferences.md
Normal file
@ -0,0 +1,169 @@
|
||||
## New XSharedPreferences
|
||||
|
||||
Since LSPosed API 93, a new `XSharedPreferences` is provided targeted for modules with sdk > 27. Modules developers can enable this new `XSharedPreferences` support by modifying the meta-data of your modules to set `xposedminversion` to 93 or above, or add `xposedsharedprefs` meta-data.
|
||||
|
||||
|
||||
### For the module
|
||||
When enabled, LSPosed will automatically hook `ContextImpl.getPreferencesDir()` so that your `Context.getSharedPreferences` and `PreferenceFragment` could work without further modification. Also, LSPosed hooks `ContextImpl.checkMode(int)` so that you can easily get a world readable shared preference by passing `Context.MODE_WORLD_READABLE` to `Context.getSharedPreferences`.
|
||||
|
||||
If you are using `xposedsharedprefs` meta-data to indicate your module will use this new feature, you probably want to ensure this feature is working properly. The following code snippet will do the magic:
|
||||
<details>
|
||||
<summary>Java</summary>
|
||||
|
||||
```java
|
||||
SharedPreferences pref;
|
||||
try {
|
||||
pref = context.getSharedPreferences(MY_PREF_NAME, Context.MODE_WORLD_READABLE);
|
||||
} catch (SecurityException ignored) {
|
||||
// The new XSharedPreferences is not enabled or module's not loading
|
||||
pref = null; // other fallback, if any
|
||||
}
|
||||
```
|
||||
</details>
|
||||
<details>
|
||||
<summary>Kotlin</summary>
|
||||
|
||||
```kotlin
|
||||
val pref = try {
|
||||
context.getSharedPreferences(MY_PREF_NAME, Context.MODE_WORLD_READABLE)
|
||||
} catch (e: SecurityException) {
|
||||
// The new XSharedPreferences is not enabled or module's not loading
|
||||
null // other fallback, if any
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
Notice that LSPosed will help you to make the preference world readable if and only if you are getting preference in mode `Context.MODE_WORLD_READABLE`.
|
||||
|
||||
### For the hooked app
|
||||
|
||||
To read the preference from the hooked app, you can simply use `XSharedPreferences(String packageName)` or `XSharedPreferences(String packageName, String prefFileName)` to retrieve the preference. Notice that you cannot use the `XSharedPreferences(File prefFile)` because the preference file is stored in a random directory.
|
||||
|
||||
You should check the permission before you read anything from the module. You should also load the preference if you need it. Try to split the preference file to multiple files for different purposes. Here's an example:
|
||||
|
||||
<details>
|
||||
<summary>Java</summary>
|
||||
|
||||
```java
|
||||
public class XposedInit implements IXposedHookLoadPackage, IXposedHookZygoteInit, IXposedHookInitPackageResources {
|
||||
|
||||
private static SharedPreferences getPref(String path) {
|
||||
SharedPreferences pref = new XSharedPreferences(BuildConfig.APPLICATION_ID, path);
|
||||
return pref.getFile().canRead() ? pref : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initZygote(IXposedHookZygoteInit.StartupParam startupParam) {
|
||||
SharedPreferences pref = getPref("zygote_conf");
|
||||
if (pref != null) {
|
||||
// do things with it
|
||||
} else {
|
||||
Log.e(TAG, "Cannot load pref for zygote properly");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleLoadPackage(XC_LoadPackage.LoadPackageParam lpparam) {
|
||||
SharedPreferences pref;
|
||||
switch (lpparam.packageName) {
|
||||
case PACKAGE_NAME_A:
|
||||
pref = getPref("a_conf");
|
||||
if (pref != null) {
|
||||
// do things with it for A
|
||||
} else {
|
||||
Log.e(TAG, "Cannot load pref for A properly");
|
||||
}
|
||||
break;
|
||||
case PACKAGE_NAME_B:
|
||||
pref = getPref("b_conf");
|
||||
if (pref != null) {
|
||||
// do things with it for B
|
||||
} else {
|
||||
Log.e(TAG, "Cannot load pref for B properly");
|
||||
}
|
||||
break;
|
||||
case BuildConfig.APPLICATION_ID:
|
||||
// hook myself
|
||||
break;
|
||||
default:
|
||||
// skip
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handleInitPackageResources(XC_InitPackageResources.InitPackageResourcesParam resparam) {
|
||||
// similar to handleLoadPackage
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
<details>
|
||||
<summary>Kotlin</summary>
|
||||
|
||||
```kotlin
|
||||
class XposedInit : IXposedHookLoadPackage, IXposedHookZygoteInit, IXposedHookInitPackageResources {
|
||||
|
||||
companion object {
|
||||
fun getPref(path: String) : SharedPreferences? {
|
||||
val pref = XSharedPreferences(BuildConfig.APPLICATION_ID, path)
|
||||
return if(pref.file.canRead()) pref else null
|
||||
}
|
||||
|
||||
// lazy loads when needed
|
||||
val prefForZygote by lazy { getPref("zygote_conf") }
|
||||
|
||||
// lazy loads when needed
|
||||
val prefForA by lazy { getPref("a_conf") }
|
||||
|
||||
// lazy loads when needed
|
||||
val prefForB by lazy { getPref("b_conf") }
|
||||
}
|
||||
|
||||
override fun initZygote(startupParam: StartupParam) {
|
||||
prefForZygote?.let {
|
||||
// do things with it
|
||||
} ?: Log.e(TAG, "Cannot load pref for zygote properly")
|
||||
}
|
||||
|
||||
|
||||
override fun handleLoadPackage(lpparam: LoadPackageParam) {
|
||||
when(lpparam.packageName) {
|
||||
PACKAGE_NAME_A -> {
|
||||
prefForA?.let {
|
||||
// do things with it for A
|
||||
} ?: Log.e(TAG, "Cannot load pref for A properly")
|
||||
}
|
||||
|
||||
PACKAGE_NAME_B -> {
|
||||
prefForB?.let {
|
||||
// do things with it for B
|
||||
} ?: Log.e(TAG, "Cannot load pref for B properly")
|
||||
|
||||
}
|
||||
|
||||
BuildConfig.APPLICATION_ID -> {
|
||||
// hook myself
|
||||
}
|
||||
|
||||
default -> {
|
||||
// skip
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun handleInitPackageResources(resparam: XC_InitPackageResources.InitPackageResourcesParam) {
|
||||
// similar to handleLoadPackage
|
||||
}
|
||||
}
|
||||
```
|
||||
</details>
|
||||
|
||||
### Preference change listener in a hooked process
|
||||
It is possible to register preference change listener within a hooked process to listen for changes of the preferences of a module.
|
||||
Call `registerOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)` on a XSharedPreferences instance passing
|
||||
reference to your listener. XSharedPreferences will start watching for changes on the physical file that belongs to given XSharedPreferences instance and notify specified listener every time change on the preference file happens; allowing module running within a hooked process to react on the changes of the module preferences.
|
||||
|
||||
**Note that by design it is not possible to determine which particular preference changed and thus preference key in listener's callback invocation will always be null.**
|
||||
Typical scenario would thus be to call `reload()` on the XSharedPrefereces instance to get a fresh set of the preferences after the change happened.
|
||||
|
||||
To stop watching for preference changes call `unregisterOnSharedPreferenceChangeListener(OnSharedPreferenceChangeListener listener)` passing a reference to the previously registered listener.
|
1
_Footer.md
Normal file
1
_Footer.md
Normal file
@ -0,0 +1 @@
|
||||
LSPosed Official Wiki
|
18
如何使用.md
Normal file
18
如何使用.md
Normal file
@ -0,0 +1,18 @@
|
||||
## 安装
|
||||
1. 确保你的设备上已安装 [Magisk](https://topjohnwu.github.io/Magisk/install.html) 和 [Riru](https://github.com/RikkaApps/Riru#install)。
|
||||
1. 进入Magisk的 "模块 "标签,搜索并安装 LSPosed。
|
||||
1. 重新启动你的设备,等待一段时间,你会看到 LSPosed 图标。
|
||||
1. 启动 LSPosed,检查激活状态。
|
||||
|
||||
### 安装和激活模块
|
||||
1. LSPosed 是一个框架,没有模块它就不能工作。你可以在应用内或[网页](http://modules.lsposed.org/)上浏览模块。
|
||||
1. 下载并安装模块,就像普通的应用程序一样。
|
||||
1. 安装后,进入LSPosed的`模块`页面,点击你安装的模块,勾选启用开关。
|
||||
1. 选择模块应该适用的应用程序。大多数模块现在支持显示推荐范围,LSPosed会自动选择。
|
||||
1. 一些过时的模块需要注入到每个应用程序中。这很危险,LSPosed不支持 "全选",需要用户手动逐个勾选每个应用(与Magisk Hide的策略相同)。
|
||||
|
||||
### 双开/多用户支持
|
||||
1. 在这之前,你应该注意到,许多过时的模块诞生于多用户不流行的年代,因此它们可能不能很好地支持多用户。LSPosed没有办法使它们在多用户模式下工作。
|
||||
1. 此外,LSPosed管理器将只在主用户上工作,它可以管理所有用户的模块和应用程序。你不需要在其他用户上安装它。
|
||||
1. 只要你的设备有多个用户,LSPosed就会在 "模块 "页面为每个用户显示一个标签。在不同的标签之间切换,可以管理不同用户的模块。
|
||||
1. 你应该把模块安装到你希望它工作的用户上。你可以通过切换到该用户并安装模块来手动完成(推荐)。或者你可以通过LSPosed管理器来做(不推荐,只要你能手动完成)。
|
Loading…
x
Reference in New Issue
Block a user