The below C program will serve as our example, it contains a JWT in the binary that can be trivially found using strings, however lets try a different approach!

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

int main(){
    char *jwt = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c";
    puts(jwt);
    return 0;
}

We may not always be fortunate enough, that the JWT is embedded directly in the binary. Normally they are served by various different APIs on authentication. If you’d like to check whether or not a particular application has any JWT’s in memory, you could use something like the below.

// A dummy function to dump some memory
const fingerprint = (addr, offset = 0, size = 64) => {
   console.log(hexdump(ptr(addr), {
       offset: offset,
       length: size,
       header: true,
       ansi: true
   }));
}

// Our default onMatch handler, useful while in the Frida REPL
const onMatch =  (addr, size) => {
   console.log('[+] Pattern found at: ' + addr.toString());
   fingerprint(addr)
}

// A generic function to scan a memory range
const scan = (addr, size, pattern, onMatch = onMatch) => {
   Memory.scan(addr, size, pattern, {
       onMatch: onMatch,
       onError: function (reason) {
           console.log('[!] There was an error scanning memory');
       },
       onComplete: function () {
       }
   });
}

// A generic function to scan all memory ranges matching a specific protection
const scanRange = (pattern, protection = 'r--', onMatch = onMatch) => {
   const ranges = Process.enumerateRangesSync({ protection, coalesce: true });
   ranges.forEach(range => {
       scan(range.base, range.size, pattern, onMatch)
   })
}

// Convert a string to hex
const toHex = (value) => {
   let result = '';
   for (let i = 0; i < value.length; i++) {
       result += value.charCodeAt(i).toString(16);
   }
   return result;
}

// Ripped this from some stackoverflow post, thanks to the author
// Contact me if you'd like this removed
const decodeBase64 = function(s) {
   var e={},i,b=0,c,x,l=0,a,r='',w=String.fromCharCode,L=s.length;
   var A="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
   for(i=0;i<64;i++){e[A.charAt(i)]=i;}
   for(x=0;x<L;x++){
       c=e[s.charAt(x)];b=(b<<6)+c;l+=6;
       while(l>=8){((a=(b>>>(l-=8))&0xff)||(x<(L-2)))&&(r+=w(a));}
   }
   return r;
};


const scanForToken = () => {
   // Insert the first part of your JWT here
   // Essentially we are just getting a list of addresses that *might* contain a JWT
   scanRange(toHex("eyJ"), 'r--', (addr, size) => {
       const mem = addr.readCString()
       if (mem.split('.').length == 3){
           const header = decodeBase64(mem.split('.')[0])
           // You can do some more filtering based of the header, presumably you already know something about the JWT. If not you can use an intercepting proxy such as mitmproxy or try to guess other strings that might appear in this header
           if (header.includes('HS256')){
               console.log(header) 
           }
       }
   })
}

// Run the function immediately when the application starts
scanForToken()

This post may not contain much in the way of an explanation of the above code, but it illustrates how you could find arbitrary values in memory - given that you have some information about them in advance.

If we use our hooks on the C program at the start of the post, this is the output:

     ____
    / _  |   Frida 15.0.13 - A world-class dynamic instrumentation toolkit
   | (_| |
    > _  |   Commands:
   /_/ |_|       help      -> Displays the help system
   . . . .       object?   -> Display information about 'object'
   . . . .       exit/quit -> Exit
   . . . .
   . . . .   More info at https://frida.re/docs/home/
Spawning `./a.out`...                                                   
{"alg":"HS256","typ":"JWT"}
Spawned `./a.out`. Resuming main thread!                                
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
[Local::a.out]-> Process terminated
[Local::a.out]->
Thank you for using Frida!