An Android library providing a complete solution for apps using root permissions.
libsu comes with 2 main components: the core module handles the creation of the Unix (root) shell process and wraps it with high level, robust Java APIs; the service module handles the launching, binding, and management of root services over IPC, allowing you to run Java/Kotlin and C/C++ code (via JNI) with root permissions.
android {
compileOptions {
// This library uses Java 8 features, this is required
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
repositories {
maven { url 'https://jitpack.io' }
}
dependencies {
def libsuVersion ='5.0.3'// The core module that provides APIs to a shell
implementation "com.github.topjohnwu.libsu:core:${libsuVersion}"// Optional: APIs for creating root services. Depends on ":core"
implementation "com.github.topjohnwu.libsu:service:${libsuVersion}"// Optional: Provides remote file system support
implementation "com.github.topjohnwu.libsu:nio:${libsuVersion}"
}Please note that this is a quick demo going through the key features of libsu. Please read the full Javadoc and check out the example app (:example) in this project for more details.
Similar to threads where there is a special "main thread", libsu also has the concept of the "main shell". For each process, there is a single globally shared "main shell" that is constructed on-demand and cached. Set default configurations before the main Shell instance is created:
publicclassSplashActivityextendsActivity {
static {
// Set settings before the main shell can be createdShell.enableVerboseLogging = BuildConfig.DEBUG;
Shell.setDefaultBuilder(Shell.Builder.create()
.setFlags(Shell.FLAG_REDIRECT_STDERR)
.setTimeout(10)
);
}
@OverrideprotectedvoidonCreate(BundlesavedInstanceState) {
super.onCreate(savedInstanceState);
// Preheat the main root shell in the splash screen// so the app can use it afterwards without interrupting// application flow (e.g. root permission prompt)Shell.getShell(shell -> {
// The main shell is now constructed and cached// Exit splash screen and enter main activityIntentintent = newIntent(this, MainActivity.class);
startActivity(intent);
finish();
});
}
}Shell operations can be performed through static Shell.cmd(...) methods that directly use the main root shell:
Shell.Resultresult;
// Execute commands synchronouslyresult = Shell.cmd("find /dev/block -iname boot").exec();
// Aside from commands, you can also load scripts from InputStream.// This is NOT like executing a script like "sh script.sh", but rather// more similar to sourcing the script (". script.sh").result = Shell.cmd(getResources().openRawResource(R.raw.script)).exec();
List<String> out = result.getOut(); // stdoutintcode = result.getCode(); // return code of the last commandbooleanok = result.isSuccess(); // return code == 0?// Async APIsShell.cmd("setenforce 0").submit(); // submit and don't care resultsShell.cmd("sleep 5", "echo hello").submit(result -> updateUI(result));
// Run tasks and output to specific ListsList<String> mmaps = newArrayList<>();
Shell.cmd("cat /proc/1/maps").to(mmaps).exec();
List<String> stdout = newArrayList<>();
List<String> stderr = newArrayList<>();
Shell.cmd("echo hello", "echo hello >&2").to(stdout, stderr).exec();
// Receive output in real-timeList<String> callbackList = newCallbackList<String>() {
@OverridepublicvoidonAddElement(Strings) { updateUI(s); }
};
Shell.cmd("for i in $(seq 5); do echo $i; sleep 1; done")
.to(callbackList)
.submit(result -> updateUI(result));Optionally, a similar concept to .bashrc, initialize shells with custom Shell.Initializer:
publicclassExampleInitializerextendsShell.Initializer {
@OverridepublicbooleanonInit(Contextcontext, Shellshell) {
InputStreambashrc = context.getResources().openRawResource(R.raw.bashrc);
// Here we use Shell instance APIs instead of Shell.cmd(...) static methodsshell.newJob()
.add(bashrc) /* Load a script */
.add("export ENV_VAR=VALUE") /* Run some commands */
.exec();
returntrue; // Return false to indicate initialization failed
}
}
Shell.Builderbuilder = /* Create a shell builder */ ;
builder.setInitializers(ExampleInitializer.class);If interacting with a root shell is too limited for your needs, you can also implement a root service to run complex code. A root service is similar to Bound Services but running in a root process. libsu uses Android's native IPC mechanism, binder, for communication between your root service and the main application process. In addition to running Java/Kotlin code, loading native libraries with JNI is also supported (android:extractNativeLibs=falseis allowed). For more details, please read the full Javadoc of RootService and check out the example app for more details. Add com.github.topjohnwu.libsu:service as a dependency to access RootService:
publicclassRootConnectionimplementsServiceConnection { ... }
publicclassExampleServiceextendsRootService {
@OverridepublicIBinderonBind(Intentintent) {
// return IBinder from Messenger or AIDL stub implementation
}
}
RootConnectionconnection = newRootConnection();
Intentintent = newIntent(context, ExampleService.class);
RootService.bind(intent, connection);If the application process creating the root service has a debugger attached, the root service will automatically enable debugging mode and wait for the debugger to attach. In Android Studio, go to "Run > Attach Debugger to Android Process", tick the "Show all processes" box, and you should be able to manually attach to the remote root process. Currently, only the "Java only" debugger is supported.
Add com.github.topjohnwu.libsu:nio as a dependency to access remote file system APIs:
// Create the file system service in the root process// For example, create and send the service back to the client in a RootServicepublicclassExampleServiceextendsRootService {
@OverridepublicIBinderonBind(Intentintent) {
returnFileSystemManager.getService();
}
}
// In the client processIBinderbinder = /* From the root service connection */;
FileSystemManagerremoteFS;
try {
remoteFS = FileSystemManager.getRemote(binder);
} catch (RemoteExceptione) {
// Handle errors
}
ExtendedFilebootBlock = remoteFS.getFile("/dev/block/by-name/boot");
if (bootBlock.exists()) {
ExtendedFilebootBackup = remoteFS.getFile("/data/boot.img");
try (InputStreamin = bootBlock.newInputStream();
OutputStreamout = bootBackup.newOutputStream()) {
// Do I/O stuffs...
} catch (IOExceptione) {
// Handle errors
}
}This project is licensed under the Apache License, Version 2.0. Please refer to LICENSE for the full text.