In-Process Fuzzing With Frida

October 24, 2019
frida exploiting fuzzing reverse-engineering

In a previous post I’ve already covered Frida and its instrumentation abilities. But check this out: You can also use Frida to perform fuzzing. What’s even greater is that Frida allows in-process fuzzing.

Why would you want to do this? There may be various reasons, but the most outstanding one for me is building a fuzzing harness for closed source applications and libraries. Just take Counter Strike GO as an example:

If you want to fuzz the map loading routines with maximum speed, you’d (ideally) want to create a minimized environment that only performs the map loading. Now the CS:GO client is a graphical application that performs all kinds of stuff when being launched and it’s not scriptable to an extent that allows efficient fuzzing. To avoid executing all this code that’s not even related to map loading, a fuzzing harness is required. A great example of such a harness for CS:GO can be found here - the harness consists of a custom wrapper and some patches.

This of course allows very specific fuzzing at a great speed, but also requires quite some effort to pull of. An approach that might be easier to pull involves using Frida and in-process fuzzing. This, of course, comes with a much higher fuzzing performance impact compared to the native approach.

OK HOW?

Let’s Consider this very sophisticated fuzzing target:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

void lol(char *b)
{
    char buffer[1337];
    strcpy(buffer, b);
}

int main(int argc, char **argv)
{
    lol(argv[1]);
}

Yes, the vulnerability is obvious and yes, you can find it using simple reverse engineering but I don’t want to build a better PoC just for people to stop complaining, okay? :)

The idea is to inject some code into the target process and use it to perform the fuzzing directly in the process as soon as the target function would be called regularly. This causes the target to execute all setup routines for us, up to the point where the harness would also hand over the work to the fuzzer. The target function will then be called in an infinite loop, with some optional cleanup in between, while mutating the input. This mutation can also happen directly in memory, if applicable.

I’ve created this highly complex fuzzing setup using Frida’s Python bindings:

#!/usr/bin/python3

import frida
import time
import sys

def on_message(message, data):
    print(message)

js = """

// Maximum payload size
var size = 2000;

// Argument for the fuzzed function
var arg = Memory.alloc(size);
var fuzzData = [0x41];

var lolAddr = null;
var lolHandle = null;

// Find the vulnerable function in the target process
// and get a handle to it
Module.enumerateSymbolsSync("yolo").forEach(function(symbol){
        switch (symbol.name) {
            case "lol":
                lolAddr = symbol.address;
                // use the function prototype to create a handle
                lolHandle = new NativeFunction(ptr(lolAddr), "void", ["pointer"]);
                console.log("[i] lol() is at " + lolAddr);
        }
    });

if (lolAddr == null) {
    die("Error finding symbol");
}

// Fuzz the function in-process
Interceptor.attach(ptr(lolAddr), {
    // Begin fuzzing as soon as the application calls the function itself
    onEnter: function(args) {
        console.log("[i] Original argument: " + args[0].readCString());

        console.log("[*] Fuzzing now");
        while(fuzzData.length < size) {
            fuzzData.push(0x41);
            Memory.writeByteArray(arg, fuzzData);
            try {
                lolHandle(arg);
            }
            catch(e) {
                console.log("[!] Crash found for size " + fuzzData.length);
                break;
            }
        }
    },
});
"""

# Spawn the target process
pid = frida.spawn(["./yolo", "hello"])
session = frida.attach(pid)

# Inject dem scriptz
script = session.create_script(js)
script.on('message', on_message)
script.load()

# Continue execution of the target
frida.resume(pid)

sys.stdin.read()

Ok cool, what does this all do? Here’s the output:

[i] lol() is at 0x5560a8bc5149
[i] Original argument: hello
[*] Fuzzing now
*** stack smashing detected ***: <unknown> terminated
[!] Crash found for size 1353

The script causes Frida to spawn the process, inject the JavaScript code into it and resume the target afterwards. The target process will then proceed with its normal execution up to the point where lol() gets called. The JavaScript code defines a hook for this function that hijacks the program flow and causes the target to fuzz itself using mutated input. This mutation happens directly in memory using a buffer that Frida allocated for itself. This mutation can do all kinds of things, for the example it’s enough to simply grow the string length over time until the target crashes.

For a real-world fuzzing setup you would use Process.enumerateModulesSync() and Module.enumerateSymbols("<module>") to get the addresses of the target functions in order to hijack the program flow. The Frida hooks would then allow mutating the input data and calling the fuzzed function.

Dis Sucks, I Want Better

Luckily @dennismantz and @lod108 have released Frizzer recently! It’s a coverage-guided blackbox fuzzer based on the Frida instrumentation framework. It’s still a work in progress project but I think it’s in a very useful state that can be adapted to own projects pretty quickly at this point.

The best feature is that it uses coverage information provided by Frida/Stalker in order to find new execution paths while fuzzing and mutating the input accordingly. This mutation is based on a pre-defined set of corpus files.

Frizzer Example

You can find some fuzzing targets in the Frizzer repository, but I’ve built one myself in order to see how Frizzer performs and what it expects. I’m using a target modified from my previous stack canary post:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#define PORT 7777

void thisIsBad()
{
    char yolo[12];
    strcpy(yolo, "13333333333333333333333333333333333333333333333333337");
}

// Frizzer has to get to the `thisIsBad` call using
// the Stalker information
void pwned(char *buf)
{
    printf("[*] Received: %s\n", buf);
    if (strlen(buf) >= 16)
    {
        printf("1\n");
        if (strncmp(buf + 2, "A", 1) == 0)
        {
            printf("2\n");
            if (strncmp(buf + 4, "X", 1) == 0)
            {
                printf("3\n");
                printf("[!] PWNED\n");
                thisIsBad();
            }
        }
    }
}

int main()
{
    int sockfd, ret;
    struct sockaddr_in serverAddr;

    int newSocket;
    struct sockaddr_in newAddr;

    socklen_t addr_size;

    char buffer[1024];
    pid_t childpid;

    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    memset(&serverAddr, '\0', sizeof(serverAddr));
    serverAddr.sin_family = AF_INET;
    serverAddr.sin_port = htons(PORT);
    serverAddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    ret = bind(sockfd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
    if (listen(sockfd, 10) == 0)
    {
        printf("[+] Listening....\n");
    }
    else
    {
        printf("[-] Error Binding to Port\n");
    }

    while (1)
    {
        newSocket = accept(sockfd, (struct sockaddr *)&newAddr, &addr_size);
        printf("[*] Got Connection from %s:%d\n", inet_ntoa(newAddr.sin_addr), ntohs(newAddr.sin_port));

        // get user input
        char data[32];
        bzero(data, sizeof(data));
        recv(newSocket, data, 32, 0);

        if (!strlen(data))
        {
            continue;
        }

        pwned(data);

        // if this works then no buffer overflow has occurred
        send(newSocket, "OK\n", strlen("OK\n"), 0);
        close(newSocket);
    }

    return 0;
}

This is a simple server application that accepts user input via TCP. This was compiled using gcc -no-pie test.c -o test. Disabling PIE theoretically isn’t necessary because Frizzer could get the address of the function to fuzz directly on run time, as seen in the first example at the beginning of this post.

This is the script used to start the fuzzing process:

#!/bin/bash

rm -rf tmpprojdir

frizzer init tmpprojdir
cat > tmpprojdir/config <<EOF
[fuzzer]
log_level       = 3 # debug
debug_mode      = false

[target]
process_name    = "test"
function        = 0x00401294
remote_frida    = false
fuzz_in_process = true
modules = [
        "/<path to target>/yolotest/test",
    ]
EOF

frizzer add -p tmpprojdir indir
frizzer fuzz -p tmpprojdir

As can be seen, Frizzer requires the address of the target function that’s going to be fuzzed. I’ve determined this address using radare2 like this:

$ r2 -A -c "afl~pwned" -q test
0x00401294    5 179          sym.pwned

Also, Frizzer seems to mutate the first parameter of the target function during fuzzing - in this case a char* value. If you find yourself trying to fuzz something different you might have to customize Frizzer to your needs :)

The directory indir will be filled with files that contain corpus data. This data is the basis for all the fuzzing that’s going on.

Let’s see Frizzer in action:

Frizzer

And that’s it :)

As you can see, fuzzing using Frida seems to be a promising and quite interesting approach. I’m quite interested to see how Frizzer evolves and what kind of vulnerabilities it’s going to find.

K THX BYE

Dynamic Instrumentation: Frida And r2frida For Noobs

September 13, 2019
radare2 r2 frida r2frida ctf reverse-engineering

r2con 2019 CTF Writeups

September 2, 2019
r2 radare2 ctf reverse-engineering

ROP On x64: What's ret2csu Again?

August 29, 2019
exploiting rop radare2 r2 ctf ret2csu