Stalk ing the process

The Stalker module has a very particular purpouse: To inspect what’s going on on a particular thread

Following a thread

Using the Stalker module we can follow a thread to track certain events - f.e. function call s

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
 var main_thread = Process.getCurrentThreadId();
 Stalker.follow(main_thread, {
     events: {
         call: true, // Track the CALL instruction
         ret: false, // Track the RET instruction
         exec: false, // Track all instructions
         block: false, // Track executed blocks
         compile: false // Track compiled blocks
     },
     onReceive: function(event){
         // Explained below
     },
     onCallSummary: function(summary){
         // Explained below
     },
     transform: function(instruction_iterator){
         // Explained below
     }
 });

WARNING: Tracking the `exec` event is discouraged as it will log a lot of data and heavily impact the performance.

onReceive

The onReceive callback is called each time an event is logged - from the ones specified in the events attribute. This may be used to create a calltrace for a thread.

The function we specify as the onReceive callback receives an event object as the only argument. In order to get what we need from this object (A GumEvent struct) we have to parse it using the Stalker.parse(event) api call which will return a tuple. The format of the tuple will vary depending on the event we are parsing. For more details about the fields you can consult the GumEvent definition: gumevent.h link.

Lets say we have an obfuscated code that makes call s to some dynamically calculated addresses, instead of spending lots of time trying to debug the application or attempting tedious static analisys we can inject Frida, find the thread that calls the obfuscated_function and trace all call s and ret s from that point. In our case we are going to be logging for just 5 seconds.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
 var log = new File('calltrace.log', 'w');
 var got_thread_id = false; // Flag to know when to start logging
 var thread = null;

 /* Attach to our target function */
 Interceptor.attach(ptr(obfuscated_function), {
     onEnter: function(args){
         /* Get the thread id */
         thread = Process.getCurrentThreadId();

         /* Make our callback to stop stalking the thread after 5s */
             setTimeout(function(){
                   Stalker.unfollow(thread);
                   log.flush();
                   log.close();
                 },
             5000
         );

         /* Detach our interceptor to avoid logging incorrect addresses */
         Interceptor.revert(ptr(obfuscated_function));

         Stalker.follow(thread, {
             events:{
                 call: true,
                 ret: true
             },
             onReceive: function(events){
                     var calls = Stalker.parse(events);
                     for(var i in calls){
                             log.write('\t'.repeat(calls[i][3]) + ' ' + calls[i][1] + ':' + calls[i][2] + '\n');
                     }
             }
         });

     }
 }

The result would be something like:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
call 0x7fdc96c0f2ba:0x7fdc96b63162
ret 0x7fdc96c0f2e3:0x7fdc96c03b29
ret 0x7fdc96c03b33:0x7fdc96c0dae7
ret 0x7fdc96c0db01:0x7fdc96bd5c34
call 0x7fdc96bd5c68:0x7fdc96bdbc64
call 0x7fdc96bd5c76:0x7fdc96bdc43e
ret 0x7fdc96bd5d57:0x7fdc9dc02076
ret 0x7fdc9dc020a5:0x7fdc9dc0271e
call 0x7fdc9d2ec170:0x7fdc9d30c6e0
    ret 0x7fdc9d30c70b:0x7fdc9d2ec175
call 0x7fdc9d2ec197:0x7fdc9d30c740
    ret 0x7fdc9d30c76f:0x7fdc9d2ec19c
ret 0x7fdc9d2ec1a9:0x7fdc9dc02713
call 0x7fdc9dc0216a:0x7fdc96bd5d58
ret 0x7fdc9dc0219e:0x560f689e16f9
ret 0x560f689e16d7:0x560f689e19e8
call 0x560f689e18c7:0x560f689dfe90
    call 0x7fdc9d2f0be1:0x7fdc9d30c6e0
            ret 0x7fdc9d30c70b:0x7fdc9d2f0be6
    call 0x7fdc9d2f0c08:0x7fdc9d30c740
            ret 0x7fdc9d30c76f:0x7fdc9d2f0c0d
    ret 0x7fdc9d2f0c19:0x560f689e18cc
call 0x560f689e1c6f:0x560f689e1790
    call 0x560f689e17a7:0x560f689dffa8
            call 0x7fdc9d2ec0a0:0x7fdc9d30c6e0
                    ret 0x7fdc9d30c70b:0x7fdc9d2ec0a5
            call 0x7fdc9d2ec0c4:0x7fdc9d30c740
                    ret 0x7fdc9d30c76f:0x7fdc9d2ec0c9
            ret 0x7fdc9d2ec0d6:0x560f689e17ac
    ret 0x560f689e17c7:0x560f689e1c74
call 0x560f689e18c7:0x560f689dfe90
    call 0x7fdc9d2f0be1:0x7fdc9d30c6e0
            ret 0x7fdc9d30c70b:0x7fdc9d2f0be6
    call 0x7fdc9d2f0c08:0x7fdc9d30c740
            ret 0x7fdc9d30c76f:0x7fdc9d2f0c0d

Of course, as we have started stalking at some arbitrary point call s and ret s may not match on the first round, but we’ll see that it gets aligned on following iterations.

onCallSummary

The onCallSummary callback is useful when we are only interested in getting a list of all the called functions or how many times each one was called. The object sent to this function is a simple JavaScript Object with the address of the function called as the key and the number of calls as the associated value.

Reusing our previous example, we’ll log what functions were called and how many times at the end of our calltrace log:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
 var log = new File('calltrace.log', 'w');
 var got_thread_id = false; // Flag to know when to start logging
 var thread = null;

 /* Attach to our target function */
 Interceptor.attach(ptr(obfuscated_function), {
     onEnter: function(args){
         /* Get the thread id */
         thread = Process.getCurrentThreadId();

         /* Make our callback to stop stalking the thread after 5s */
             setTimeout(function(){
                   Stalker.unfollow(thread);
                   log.flush();
                   log.close();
                 },
             5000
         );

         /* Detach our interceptor to avoid logging incorrect addresses */
         Interceptor.revert(ptr(obfuscated_function));

         Stalker.follow(thread, {
             events:{
                 call: true,
                 ret: true
             },
             onReceive: function(events){
                     var calls = Stalker.parse(events);
                     for(var i in calls){
                             log.write('\t'.repeat(calls[i][3]) + ' ' + calls[i][1] + ':' + calls[i][2] + '\n');
                     }
             },
             onCallSummary: function(summary){
                 log.write('\n\n'+'-'.repeat(30)+'\n\nSummary:\n\n');
                 Object.entries(summary).forEach(([key, value]) => {
                     log.write('\t'+key+': '+str(value));
                 });
             }
         });

     }
 }

transform

The transform callback is an advanced callback that allows to inspect and modify the assembly code on the fly. It receives a iterator object that iterates over the instruction set.

In fact, each time we get the next() item of the iterator it returns an Instruction object pointing to the address of the next instruction. To keep this example as simple as possible we’ll be just using it to log every instruction executed:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
    var flag = false;
    var thread = null;

    /* Get the address space of the application
     * to avoid logging instructions from imported modules
     */
    var app = Process.enumerateModulesSync()[0];
    var appStart = app.base;
    var appEnd = app.base.add(app.size);

    /* Attach to our target function */
    Interceptor.attach(ptr(obfuscated_function), {
        onEnter: function(args){
            if(flag) return;
            /* Get the thread id */
            thread = Process.getCurrentThreadId();
            console.log('Got thread id')

            /* Make our callback to stop stalking the thread after 5s */
            setTimeout(function(){
                  Stalker.unfollow(thread);
                },
                5000
            );
            console.log('Timeout set');

            /* Detach our interceptor to avoid logging incorrect addresses */
            Interceptor.revert(ptr(obfuscated_function));

            Stalker.follow(thread, {
                transform: function(iterator){
                    var instruction = null;
                    var isApp = false;
                    while((instruction = iterator.next())!==null){
                        isApp = instruction.address.compare(instruction.address)>=0 && instruction.address.compare(appEnd) === -1;
                        if(isApp) console.log(instruction.mnemonic + ' ' + instruction.opStr);

                        /* If we don't call `iterator.keep()` it will drop the instruction */
                        iterator.keep();
                    }
                }
            });
            console.log('Stalker following');
            flag = true;
        }
    });

Which will return an output like this

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
 Timeout set
 Stalker following
 mov rbx, rax
 cmp rbx, -1
 jne 0x559941a6065a
 cmp ebp, 2
 jne 0x559941a60740
 test rbx, rbx
 jle 0x559941a606cc
 cmp ebp, 2
 je 0x559941a60670
 test ebp, ebp
 jle 0x559941a606a8
 mov rdx, qword ptr [r12]
 sub rdx, rbx
 test rdx, rdx
 jle 0x559941a606c8
 mov qword ptr [r12], rdx
 mov rax, rbx
 pop rbx
 pop rbp
 pop r12
 pop r13
 pop r14
 ret

Note: To view some examples on how to overwrite instructions and other capabilities take a look at ``Instruction`` and ``Code Writers``