How i turn frick into a real frida based debugger

I’m finally proud to write this blog post after some weeks of study and test about how i’ve created my first debugger, built on top of Frida.

As usual, let’s spend a couple of word to let the folks understand what was the goal. People following me through twitter or github already know that I recently came out with a new tool called frick, which is a Frida cli that sleep the target thread once the hook is hit giving a context with commands to play with. However, sleeping a single thread is not very efficent when we are dealing with multithread targets that can alter the memory layout while our hook thread is sleeping, or spawn more threads that will maybe invoke the hook target one more time, TLDR; we need to sleep everything except the threads involved with frida.

Basically, we are looking for debugger breakpoints, which let me say, was the harderst thing I’ve ever did in term of researches and tests (even if in the end, we are speaking about 50/100 lines of code).

Let me say once again that I’m really proud of this, I’ve tested this code against 2 popular games (for instance – Clash of Clans – protected with Arxan (more researches to prevent main thread to detect hooks modifications) and Jurassic World Alive (which literally spawn 200 threads a second).

The code style is terrible, the logic can be fixed but I was so excited to write about this so I’ve decided to post streight the original code that reach the goal (will be improved and fixed for frick). I’ve added notes on the code which will help understand the logic i’ve used and why.

// retrieve gettid native function
var gettid = nf(getnf('gettid', 'libc.so', 'int', []));
// getting main process tid
var main_tid = gettid();

// map 8 bytes of trash. We will use frida replace api to use js code
var handler = Memory.alloc(8);
Memory.protect(handler, 8, 'rwx');
Interceptor.replace(handler, new NativeCallback(function (sig) {
    console.log('handle -> ' + gettid());
    while (sleep) {
        Thread.sleep(1);
    }
    return sig;
}, 'int', ['int']));

// following code will attach to our main target and sleep all the threads 
function att(off, pt) {
    if (base === 0) {
        return;
    }
    
    targets['' + pt] = Interceptor.attach(pt, function() {
        sleep = true;

        // retrieve signal and tkill native functions
        var signal = nf(getnf('signal', 'libc.so', 'int', ['int', 'pointer']));
        var tkill = nf(getnf('tkill', 'libc.so', 'int', ['int', 'int']));

        // setup an handler for sigusr2
        signal(12, handler);

        var t_hooks = {};

        // get all the threads the nerd way
        var ts = readThreads();

        for (var to in ts) {
            var t = ts[to];
            // prevent frida stuffs
            if (t[0].indexOf('gum') >= 0 ||
                t[0].indexOf('gdbus') >= 0) {
                continue;
            }
            // if the thread is running, we use the signal handler
            // otherwise, we can know the current PC of the thread, using signal 
            // on sleeping threads caused bad behaviors, so just attach to 
            //the arbitrary instruction and sleep in case something else resume it
            if (t[2] === 'R') {
                if (parseInt(t[0]) !== main_tid) {
                    try {
                        tkill(parseInt(t[0]), 12);
                        console.log('signaling -> ' + t[1]);
                    } catch (e) {}
                }
            } else {
                var tpc = ptr(t[29]);
                if (t_hooks[t[29]] !== null && typeof t_hooks[t[29]] !== 'undefined') {
                    continue;
                }
                console.log('attaching -> ' + tpc);
                t_hooks[t[29]] = Interceptor.attach(tpc, function () {
                    console.log('sleep -> ' + this.context.pc);
                    while (sleep) {
                        Thread.sleep(1);
                    }
                });
            }
        }

        while(sleep) {
            Thread.sleep(1);
        }
        for (var k in t_hooks) {
            t_hooks[k].detach();
        }
    });
}

// if you read all the notes before this,
// there is no need of notes to understand this method
function readThreads() {
    var m_alloc = Memory.alloc(1024);
    Memory.protect(m_alloc, 1024, 'rwx');

    var opendir = nf(getnf('opendir', 'libc.so', 'pointer', ['pointer']));
    var readdir = nf(getnf('readdir', 'libc.so', 'pointer', ['pointer']));
    var fopen = nf(getnf('fopen', 'libc.so', 'pointer', ['pointer', 'pointer']));
    var fgets = nf(getnf('fgets', 'libc.so', 'pointer', ['pointer', 'int', 'pointer']));

    Memory.writeUtf8String(m_alloc, '/proc/self/task');

    var proc_dir = opendir(m_alloc);
    var entry;
    var res = [];

    while ((entry = readdir(proc_dir)) > 0) {
        var line = Memory.readUtf8String(entry.add(19));
        if (line.indexOf('.') >= 0) {
            continue;
        }
        Memory.writeUtf8String(m_alloc, '/proc/' + pid + '/task/' + line + '/stat');
        Memory.writeUtf8String(m_alloc.add(64), 'r');
        try {
            var fp = fopen(m_alloc, m_alloc.add(64));
            line = Memory.readUtf8String(fgets(m_alloc, 1024, fp));
            var name = line.substring(line.indexOf('('), 1 + line.indexOf(')'));
            line = line.replace(' ' + name, '');
            var proc = line.split(' ');
            proc.splice(1, 0, name.replace('(', '').replace(')', ''));
            res.push(proc);
        } catch (e) {
        }
    }
    return res;
}

The log result from Clash of Clans

s l
-> 2 commands added to init callback
-> frida attached
-> script injected
-> target arch: arm
-> pointer size: 4
-> leaked target base at 0xd1a46000
-> attached to 0xf1e2e695
-> 0xf1e2e695 added to target offsets
-> thread started: 14776        target: 0xd1ca67a1 (0x2607a1)
-> thread started: 14790        target: 0xd1e95e7f (0x44fe7f)
-> thread started: 14791        target: 0xd1e95e7f (0x44fe7f)
----[ 0xf1e2e695 ]------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------[ registers ]----
R0  : 0x58
R1  : 0xceb21f58 -> 0x642f0001
R2  : 0x6e
R3  : 0xf1e3d16a -> 0x2b720000 -> 0x0
R4  : 0x58
R5  : 0xceb21f58 -> 0x642f0001
R6  : 0xceb21f5a -> 0x7665642f -> 0x0
R7  : 0x1
R8  : 0x0
R9  : 0x0
R10 : 0xceb22010 -> 0x400
R11 : 0xceb220d8 -> 0x400
R12 : 0xf1ea3b2c -> 0xf1e2e695 -> 0x1f000f8
SP  : 0xceb21f50 -> 0x4
PC  : 0xf1e3d109 -> 0xebd1051c -> 0x0
LR  : 0xf1e3d109 -> 0xebd1051c -> 0x0
attaching -> 0xf1e5c880
attaching -> 0xf1e2c050
attaching -> 0xf1e5cbb0
attaching -> 0xf1e5ca58
attaching -> 0xd855b154
attaching -> 0xf1e5c9c0
attaching -> 0xf1e5d9a0
signaling -> ll.clashofclans
sleep -> 0xf1e5bb6b
signaling -> GLThread 5366
sleep -> 0xf1e5bb6b
attaching -> 0xf1e5d760
sleep -> 0xf0cb9319
sleep -> 0xf1e31403
sleep -> 0xf1e31403
sleep -> 0xf016c7c1
sleep -> 0xf016c7c1
sleep -> 0xf1e2e985
sleep -> 0xf1e31403
run
sleep -> 0xf016b553
sleep -> 0xf1e31403
sleep -> 0xf1e31403
sleep -> 0xf016b553
sleep -> 0xf016b553
sleep -> 0xf1e31403
sleep -> 0xf1e31403
sleep -> 0xf1e31403
sleep -> 0xf1e2e985
sleep -> 0xf1e2e985
sleep -> 0xf1e31403
handle -> 14777
handle -> 14769
----[ 0xf1e2e695 ]------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------[ registers ]----
R0  : 0x58
R1  : 0xceb21f78 -> 0xbb01000a
R2  : 0x1c
R3  : 0x0
R4  : 0x58
R5  : 0xceb21f64 -> 0x0
R6  : 0x131028b0 -> 0x6fd5c568 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08
R7  : 0xceb21f78 -> 0xbb01000a
R8  : 0x0
R9  : 0xd0591200 -> 0x590000
R10 : 0xceb22040 -> 0x131028b0 -> 0x6fd5c568 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08 -> 0x6fdb0f08
R11 : 0xe5d0b410 -> 0xf0515b74 -> 0x0
R12 : 0xea570c68 -> 0xf1e2e695 -> 0x1f000f8
SP  : 0xceb21f58 -> 0xceb21f60 -> 0x1c
PC  : 0xea55bf51 -> 0x26f7ef46 -> 0x0
LR  : 0xea55bf51 -> 0x26f7ef46 -> 0x0
attaching -> 0xf1e5c880
attaching -> 0xf1e2c050
attaching -> 0xf1e5cbb0
attaching -> 0xf1e5ca58
attaching -> 0xf1e5d760
attaching -> 0xf1e5d9a0
attaching -> 0xd855b154
signaling -> ll.clashofclans
handle -> 14769
signaling -> GLThread 5366
handle -> 14777

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.