Hello friends! Today we're going to talk about a different kind of challenge. I participated in MobileHackingLab's CTF during BlackHat MEA. As you can guess, it's a mobile-only challenge. I really enjoyed playing this CTF because it features realistic challenges, including Android, iOS, and even kernel exploitation.
This write-up is about a challenge called FuzzMe, which I didn't solve during the CTF but managed to solve a day later, after the CTF ended. Let's get into the details.
>Fuzzing challenge where your goal is to uncover a hidden flag that makes the program go boom!
You are provided with a a shared library (libvalidate.so, with AARCH64 architecture) extracted from an Android app. The library contains a validate function. If you provide correct flag as part of input structure to this function the program will crash.
To get the flag you need to fuzz the value, using the structure you can find after reverse engineering the library.
Let's first check the shared library to see if it has any globally exported symbols using the `objdump` command.
```bash
libvalidate.so: file format elf64-little
DYNAMIC SYMBOL TABLE:
0000000000000000 DF *UND* 0000000000000000 (LIBC) __cxa_finalize
0000000000000000 DF *UND* 0000000000000000 (LIBC) __cxa_atexit
0000000000000000 DF *UND* 0000000000000000 (LIBC) __register_atfork
0000000000000000 DO *UND* 0000000000000000 (LIBC) stderr
0000000000000000 DF *UND* 0000000000000000 (LIBC) fprintf
0000000000000000 DF *UND* 0000000000000000 (LIBC) malloc
0000000000000000 DF *UND* 0000000000000000 (LIBC) memcpy
0000000000000000 DF *UND* 0000000000000000 (LIBC) free
0000000000000000 DF *UND* 0000000000000000 (LIBC) strlen
0000000000000000 DF *UND* 0000000000000000 (LIBC) snprintf
0000000000000000 DF *UND* 0000000000000000 (LIBC) clock
0000000000000000 DF *UND* 0000000000000000 (LIBC) strcmp
0000000000000000 DF *UND* 0000000000000000 (LIBC) strtoull
0000000000000000 DF *UND* 0000000000000000 (LIBC) strncpy
0000000000005218 g DF .text 0000000000001728 Base validate
```
As you can see the `validate` function is exported, since our goal is to `fuzz` the function to get the flag, Let's understand it more by opening the shared lib in [Ghidra](https://github.com/NationalSecurityAgency/ghidra)

The first thing you'll notice is that the function takes an input, and it must be at least 16 (0x10) characters long.

Anti-debugging has also been implemented. I'm assuming this is because, to solve the challenge in the intended way, they don't want us to use a debugger meaning we could otherwise set a breakpoint where the flag is calculated and retrieve it directly.
Now that we have what we need, let's create a small C program that references the `validate` function and passes an input to it.
```C
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
extern int validate(const uint8_t*, uint64_t);
int main(int argc, char** argv) {
if (argc < 2) {
printf("Usage: %s <input_file>\n", argv[0]);
return 1;
}
int fd = open(argv[1], O_RDONLY);
if (fd < 0) return 1;
struct stat st;
if (fstat(fd, &st) < 0) {
close(fd);
return 1;
}
unsigned char* buffer = (unsigned char*)malloc(st.st_size + 1);
if (!buffer) {
close(fd);
return 1;
}
read(fd, buffer, st.st_size);
buffer[st.st_size] = 0;
close(fd);
validate(buffer, st.st_size);
free(buffer);
return 0;
}
```
Let's build it...

And push it to the device.

When you pass random data to the `validate` function, we get the following error. Upon checking the code, we can see that it is expecting a JSON input. Let's fix our input and run the program again.

Now we're getting somewhere. When we pass a correct JSON, we get the following error. Let's check the code and understand what it is expecting from our input.

Okay, as you can see from the screenshot, the function expects the first key to be `magic` with a value of **0x1020304** (or **16909060** in decimal). Let's try it out and see what happens.

Good! The first check passed, and now it's expecting the second key to be `version`. Since we're working with a JSON structure, let's fast-forward and build the complete JSON input.

Here is the JSON input that the `validate` function is expecting. All the JSON keys and values are constants, as you can see with the `magic` key check in the screenshot above. However, the two keys `flag` and `value` are not hard-coded like the others. For example, the value of the `value` key is transformed.

We've set the value to `234`, which gets transformed to `468`, but the check expects it to be `84`. From this initial input, you can guess that the function is multiplying the value by 2. So, if our input is **42**, it will be transformed to **84**, allowing the check to pass. Let's test it out.

The `value` is now set to **42**.

Good! As you can see, all the checks have passed except for the flag input.

Since the beginning, we were only preparing the input, and the actual fuzzing part hasn’t started yet. But from the code, you can see that the correct flag must be provided in the `flag` field. If we supply the correct one, the program will intentionally crash.
Since this is my very first time fuzzing an Android shared library, let's cheat a bit to understand what we need to fuzz and then solve it in the intended way.

What we're going to do is override `snprintf` to get the correct flag first. Here’s what the hook code looks like:
```C
#include <stdio.h>
#include <stdarg.h>
#include <string.h>
#include <stdlib.h>
int snprintf(char *str, size_t size, const char *format, ...) {
va_list args;
if (format != NULL && strstr(format, "MHL{")) {
va_start(args, format);
fprintf(stdout, "[LD_PRELOAD HOOK] Flag found: ");
int ret = vprintf(format, args);
fprintf(stdout, "\n");
va_end(args);
return ret;
}
return 0;
}
```
Let's build it and run it.

Boom! We got the correct flag, but this isn’t a win for me, so let’s solve it the intended way. If you look closely at the correct flag, you’ll notice that the first 7 bytes are the XOR key, so the only bytes we need to fuzz are the last 9.

You can see in the following screenshot that the first 7 bytes of the XOR key are the same as the first 7 bytes of the flag. So now, let's create a harness and fuzz it using AFL.
```C
#include <stdint.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
extern int validate(unsigned char* bytes, size_t len);
const size_t FUZZ_SIZE = 9;
int main(int argc, char *argv[]) {
uint8_t data[FUZZ_SIZE];
ssize_t bytes_read = read(STDIN_FILENO, data, FUZZ_SIZE);
if (bytes_read != (ssize_t)FUZZ_SIZE) {
return 0;
}
char fuzz_string[FUZZ_SIZE + 1];
memcpy(fuzz_string, data, FUZZ_SIZE);
fuzz_string[FUZZ_SIZE] = '\0';
char json[512];
int len = snprintf(json, sizeof(json),
"{\"magic\":16909060,\"version\":1,\"padding\":0,\"flag\":\"MHL{827b07c%s}\","
"\"root\":{\"type\":16,\"level\":3,\"num_children\":1,\"children\":["
"{\"type\":32,\"level\":2,\"num_children\":1,\"subchildren\":["
"{\"type\":48,\"level\":1,\"num_children\":1,\"leaves\":["
"{\"type\":64,\"level\":0,\"reserved\":0,\"value\":42}]}]}]}}",
fuzz_string);
if (len < 0 || (size_t)len >= sizeof(json)) {
return 0;
}
validate((unsigned char*)json, len);
return 0;
}
```
Now let's run AFL!

Great! As you can see in the top-right corner, we have **3** crashes.

Let's check if we got the right values.

Looks like we got it! Let's verify.

We got the correct flag. Well, this was my first mobile-only CTF. I really enjoyed it and learned a lot. Until next time!