| |

Execute Native Binaries Android Q+ (No Root)

The change to block exec() on application data files for targetAPI >= Q is working-as-intended. Please see https://android-review.googlesource.com/c/platform/system/sepolicy/+/804149 for background on this change. Calling exec() on writable application files is a W^X (https://en.wikipedia.org/wiki/W%5EX) violation and represents an unsafe application practice. Executable code should always be loaded from the application APK.

While exec() no longer works on files within the application home directory, it continues to be supported for files within the read-only /data/app directory. In particular, it should be possible to package the binaries into your application’s native libs directory and enable android:extractNativeLibs=true, and then call exec() on the /data/app artifacts. A similar approach is done with the wrap.sh functionality, documented at https://developer.android.com/ndk/guides/wrap-script#packaging_wrapsh .

https://issuetracker.google.com/issues/128554619

Android brought another big change to how the applications execute pre-compiled native binaries or shell scripts in their device, after android API level 28, the application will no longer have permission to execute anything in its */data/data* directory!

What does this mean to you?

Your application will no longer be able to download executable binaries and execute them dynamically! All the required executable binaries required by your app need to be there available at compile-time itself!

What about changing targetApi from 29 to 28?

Downgrading your target API will fix the problem but it is not a good solution, Every new android API releases fixed bugs and introduce new efficient features, Any app targeting older APIs are bound to be obsolete sometime later plus they can’t even be published in play store due to their outdated security and concerns, therefore downgrading is not a valid option, So what’s the solution?

The Solution

Using the hacky trick that the android installer while installing can extract all the libraries to the */data/app* folder and this place is executable (even after Android Q)!

The catch? Any executable binary/script should be packed inside the app itself and this is done on compile-time only, ie after installing the app you can no longer change or add other binaries you want to execute! Packaging your binary in the app itself is the only way!

  1. Set extractNativeLibs to true in AndroidManifest.xml
android:extractNativeLibs="true"

2. Make the following changes into app-level Gradle script

Inside dependencies add:

implementation fileTree(dir: "$buildDir/native-libs", include: 'native-libs.jar')

and copy-paste this:

task nativeLibsToJar(type: Jar, description: 'create a jar archive of the native libs') {
    destinationDir file("$buildDir/native-libs")
    baseName 'native-libs'
    from fileTree(dir: 'mybins', include: '**/*')
    into 'lib/'
}
tasks.withType(JavaCompile)
{
    compileTask -> compileTask.dependsOn(nativeLibsToJar)
}

3. Finally create a new directory named mybins and create platform-specific directories inside it ie: [arm64-v8a, arm64-v8a, x86], and finally add any binary/shell script into these folders! Overall your structure will look like this:

project-folder/
├─ build/
├─ libs/
├─ src/
├─ mybins/
│  ├─ arm64-v8a/
│  │  ├─ ARM 64bit Binaries
│  ├─ arm64-v8a/
│  │  ├─ ARM 32bit Binaries
│  ├─ x86/
│  │  ├─ Intel 32bit Binaries

4. Finally execute any shell command in the path /data/app/<your package unique identifier>/lib/<architecture>/binary A sample code below showing how to do it:

I’d recommend KtSh (A free opensource android library) for executing simple and advanced shell commands (GitHub link here), to use this library add to your app-level dependencies:

implementation 'com.jaredrummler:ktsh:1.0.0'

and finally, execute the following code:

String basedir = appContext.getApplicationInfo().nativeLibraryDir;
String command = String.format("cd %s;./<executable name>", basedir);
Shell shell = new Shell("sh");
Command.Result result = shell.run(command);
System.out.println(result.stdout());
System.out.println(result.stderr());

See the output into logs and verify your executable properly running!

Just some explanation and my quick bonus tips before ending:

  1. Using getApplicationInfo().nativeLibraryDir will automatically give the appropriate architecture location of the executable you need to execute.
  2. Taking advantage of this you can pack different architecture of executable/script and execute them properly according to architecture of android processor.
  3. One major problem is that unlike data/data folder, the data/app folder is not readable or writeable, so any configurations need to be stored outside.
  4. The gradle script simply copies the files and binaries to apk without checking .so extension, instead of doing Step 2 you can even rename your binaries to end with extension .so and place them in android required native libraries (jniLibs/) and finally execute them with .so extension placed (Like ./binary.so

Hopefully, this article helps someone! if you have any problem/query make sure to comment below, I would help you to the best of my knowledge! With that being said take care and Happy Hacking!

Similar Posts

Leave a Reply

Your email address will not be published. Required fields are marked *