How to debug Android/iOS native library using GDB debugger?

Shubham Sonani
8 min readDec 3, 2023

--

Hi Guys, after a long time, I am writing a new blog that will help you guys while performing Android/iOS penetration testing. I am going to explain how an attacker can get sensitive information from native libraries/code which in turn can be called “Android or iOS native code debugging.” However, there are certain ways to get value from the native library which includes free debuggers and commercial debuggers. In this blog, as mentioned in the title, I am going to explain to you the native code used in Android and how data is processed, and at the end, I will use GDB (a free debugger) to retrieve the data at runtime. In other blogs, I will cover how to use this technique on commercial tools like JEB or IDAPro.

If you want to know about Java-based Android debugging, you can go through this blog, which covers in detail what is debugging and the method of how an attacker uses this to bypass certain protections implemented in Android or get sensitive information from the application.

Links ->

  1. Cygwin64
  2. MYSY
  3. GDB 13.2 with python 3
  4. FrankNDK
  5. Bypass debugger protection and debug native libraries using IDA Pro.
  6. Debug native libraries using JEB decompiler.

What is and why is native code used in apps?
Native codes are written in C/C++ and compiled into binary files that can be linked to mobile applications (in Android one can find it under the libs folder based on your device architecture). iOS apps are developed in Objective-C or Swift programming language. Some developers use this library (.so files) to hide some sensitive code like encryption keys, OTP generation, root detection, debugger detection, Frida detection, or some business logic. Now to connect Java code with Native C/C++ libraries, the JNI framework is used, which allows JVM to call and be called by native libraries. Further in detail, please check this blog to learn about native libraries.

First thing, GDB cannot be directly used in any OS as an aspect to debug Android applications. To do that, you need to build GDB and frankly speaking, it is a freaking hard job, especially in Windows. Don’t worry guys, I got you, I have provided a build for you guys which you can download from the above links. For iOS, if you have macOS, you can use its LLDB debugger which provides similar functionalities compared to GDB.

In this tutorial, I am showing a vulnerable application named “HPAndro” and its backdoor7 activity.

Step 1 — Recompile the app with debug permissions.
Step 2 — On opening the app in JADX-GUI, we can find the code for the backdoor7 activity, by simply using search functionality in JADX-GUI. In addition, you can use “system. loadlibrary(“ “)” which is a method to load dynamic library at runtime by JAVA code. In the screenshot below, we can see the use of the system.loadlibrary(“backdoor7”) and init() method.

1. Native library method

Step 3 — Further, in the code, we can see the “hello” method which takes the user input and sends the data to the native function, and in response, we get some boolean flag which if it is “true” then the application will show a hidden flag.

2. Hello Method

Step 4 — Open the library file using Ghidra, radare2, IDA Pro, or JEB, to see the code. The basic concept of code is, that it loads the user input first and then it generates 4-digit number using “strcat”, “ldr” methods, etc. Now at the end, we can see the “strcmp” method which will compare user input and generated code, and then a “true” or “false” flag will be sent to the JAVA code. At last, all the values are removed from the stack.

Note: The code which the tool will load will be in assembly language and the values are stored in registers or memory. The values at the left side register values from where we will get the data. I have opened the x86_64-based architecture lib.so file in IDA Pro. To see the actual C/C++ code, you can use Ghidra or IDA Pro.

3. Decompiled native code

Let’s get the secret value.

How to debug Android application native library using GDB (GNU debugger)?

Step 1 — From the Android NDK folder, copy the gdbserver file to /data/local/tmp folder of Android via the adb push command. The directory then should be like this (ignore the rest files).

4. file path

Step 2 — First navigate to the backdoor7 activity in the Android application, enter some value in the text field, and hit enter. This will show an incorrect flag but at the background, it will dynamically load the library which we need in the future.
Step 3 — Run the “su” command to get root access and then run the “chmod +x gdbserver” command to change file permission. Start the gdbserver using this command ->./gdbserver :8000 — attach <pid> (get pid using adb). Then forward the traffic of port 8000 via adb forward command.

5. Find process ID
6. starting GDB server
7. Test page

Step 4 — After installing Cygwin and MSYS/mingw64, add the paths in environment variables under the system. This will help GDB get the necessary DLL files.
Step 5 — Start the gdb using this command. The data-directory folder is present inside the gdb folder.
Note: -ex flag is used to say GDB to stop at certain memory addresses after adding breakpoints. — data-directory flag is used to provide GDB whole python3 functionalities.

8. Data-directory path
9. Run command

Step 6 — Attach the Android device with the gdb using this command ->target remote :8000 or 12345 and then gdb will get attach and then start loading all symbols and libraries that are available. Use “info sharedlibrary” to check if the necessary library which is libbackdoor7 is loaded or not.

10. Attaching android device

Step 7 — Now go to the application’s lib directory on the host machine and run the terminal from there and start the radare2 with the library compatible with your Android device. For example, as mentioned earlier, my Android device is of x86_64 architecture.

11. Android Library folder path

Step 8 — Analyse the library using “aa” and then use “afl” which will show the list of functions and we can see one JNI call made to the native library.
Step 9 — Copy the JNI call. Go to the GDB and add a breakpoint with this call. Ignore step 10 if this works.

12. Radare2 application
13. JNI call
14. Break JNI call

Step 10 — Second method to add the breakpoint to the desired function. Go to radare2 and copy the address value of the JNI call. Now in GDB run this command “shared libbackdoor7.so” which will load all symbols if present. Now, run “info library” and at last, you will see the runtime address of the libbackdoor7.so file. Copy that value and remove the last 3 digits of this address. Copy the last 3 digits from the radare2’s address and paste it here -> it should look like this -> 0x0000748760fba730 -> truncate the starting zeros and the real-time value of the JNI call will be at -> 0x748760fba730. Use this value to add a breakpoint.

Step 11 — Run the “continue” command to resume the process. Enter some random value and hit “check”. The GDB will stop at this address or function. Run the “disassemble” command to get the native code which will be decompiled at runtime.

15. Enter random value
16. Breakpoint hit

Step 12 — Here in the native code, we can get the value that is generated by the native code at the “r12” register. Copy that memory address. Also, for safe case, copy address for “test eax eax” method which is used to compare user input and value generated by native code. Add a breakpoint like shown below.

17. Radare2’s decompiled native code

Step 13 — Run the “continue” command which will again resume the process. Enter some random value and GDB should break at a certain breakpoint. Check the list of breakpoints using the command “info breakpoint”. Delete unnecessary breakpoints using “delete <breakpoint_number>” command. Remember, do not delete the breakpoints of the registers and functions.

18. Adding breakpoint for registers.
19. Register r12 memory address

Step 14 — Run “info registers” once the breakpoint is hit. Now, find the memory address of the r12 register which will look like this in the second column -> 0x74876edebbb8. Copy that address and use “x/s <memory_address>” to convert hex to string. Voila, we got the secret string.

20. Converting Hex to String
21. Flag Disclosed

#android #androidpentesting #penetrationtesting #nativelibraries

--

--