Inspecting the Memory

Note: All operations accesing invalid memory or without proper permissions (i.e. writting non-writable memory) will throw a JavaScript exception that may crash the process if not properly handled.

Reading memory

As we are working on raw memory, depending on the size of the value we want to read we’ll have to use different functions. Thankfully all of them follow a simple name convention:

1
 var my_value = Memory.readXXX(pointer);
Here XXX should be replaced by the type suffix that corresponds to the value we want to read.
S8 (Signed 8 bits) U8 (Unsigned 8 bits) S16 or Short U16 or UShort S32 or Int U32 or UInt S64 or Long U64 or ULong

The Frida API also provides special functions to read strings:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
 /* This will read NULL terminated strings */
 var c_string = Memory.readCString(pointer);
 var utf8_string = Memory.readUtf8String(pointer);
 var utf16_string = Memory.readUtf16String(pointer);
 var ansi_string = Memory.readAnsiString(pointer); // Windows only

 /* In case we know the length of the string or we want to read a certain lenght */
 var c_string = Memory.readCString(pointer, length);
 var utf8_string = Memory.readUtf8String(pointer, length);
 var utf16_string = Memory.readUtf16String(pointer, length);
 var ansi_string = Memory.readAnsiString(pointer, length); // Windows only

A similar function is available to read an array of bytes but in this case the length parameter is mandatory:

1
 var byte_array = Memory.readByteArray(pointer, length);

Writing memory

Writing memory works in a similar way than reading with the same suffixes:

1
 Memory.writeXXX(pointer, 1234);

Looking for patterns

As we will see when we discuss the internals of Frida, it works in an asynchronous way to allow better performance when communicating with an external application. Therefore we’ll see that there use to be two versions of the same functions: The asynchronous version (default) and the synchronous one

For what we’re interested the asynchronous functions receive a callback function or set of functions that will be called on certain events like when a result is found or when all the results have been retrieved:

 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
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
 /* Synchronous */
 [Local::ls]-> var results = Memory.scanSync(ptr("0x55e1eae0b000"), 126976, "6c73");
 undefined
 [Local::ls]-> results
 [
 {
     "address": "0x55e1eae0c3fd",
     "size": 2
 },
 {
     "address": "0x55e1eae0c753",
     "size": 2
 },
 {
     "address": "0x55e1eae0c77a",
     "size": 2
 },
 {
     "address": "0x55e1eae21b96",
     "size": 2
 },
 {
     "address": "0x55e1eae21c0f",
     "size": 2
 },
 {
     "address": "0x55e1eae224cd",
     "size": 2
 },
 ...
 ]

 /* Asynchronous */
 [Local::ls]-> Memory.scan( ptr("0x55e1eae0b000"), 126976, "6c73", { // The pattern to look for is hex encoded. ? is a wildcard as in 6?73
                   onMatch: function(address, size){
                       console.log("Found pattern 'ls' at " + address);
                       return true;
                   },
                   onError: function(reason){
                       console.warn('Error: ' + reason);
                   },
                   onComplete: function(){
                       console.log('Completed');
                   }
               });

 undefined
 Found pattern 'ls' at 0x55e1eae0c3fd
 Found pattern 'ls' at 0x55e1eae0c753
 Found pattern 'ls' at 0x55e1eae0c77a
 Found pattern 'ls' at 0x55e1eae21b96
 Found pattern 'ls' at 0x55e1eae21c0f
 Found pattern 'ls' at 0x55e1eae224cd
 Found pattern 'ls' at 0x55e1eae22c04
 Found pattern 'ls' at 0x55e1eae22d82
 Found pattern 'ls' at 0x55e1eae2385f
 Found pattern 'ls' at 0x55e1eae2435c
 Found pattern 'ls' at 0x55e1eae24706
 Found pattern 'ls' at 0x55e1eae24e6f
 Found pattern 'ls' at 0x55e1eae24fdb
 Found pattern 'ls' at 0x55e1eae25070
 Found pattern 'ls' at 0x55e1eae25c02
 Completed

At this point you should have noticed the function ptr(), it is a shorthand function to create a NativePointer object from a string address. As we are working in a JavaScript layer there is no notion of pointers, the NativePointer class is used instead to represent an actual pointer in the process we are instrumentating.

Also, this NativePointer s can be used directly to read/write the memory it points to:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
 var pointer_to_uint = new NativePointer('0xdeadbeef');
 var uint_value = pointer_to_uint.readUInt();
 console.log(uint_value);
 >>> 42

 var another_pointer_to_uint = ptr('0xdeadbeef'); // Note this is the same memory as using `new NativePointer`
 another_pointer_to_uint.writeUInt(0xbabecafe);

 uint_value = pointer_to_uint.readUInt();
 >>> 3133065982 // a.k.a. 0xbabecafe

Allocating memory

Frida allows us to allocate memory in the heap just like a call to malloc would do. The only difference is that this memory is managed by the JavaScript engine, meaning that it will be automatically free d once all references to it are lost

Of course, as we are still in a JavaScript context, there is no actual pointer, instead a NativePointer object is returned:

1
 var heap_pointer = Memory.alloc(size);

Changing permissions

We may want to change the permissions of the memory we have just allocated to grant executing permissions:

1
2
3
 var shellcode_pointer = Memory.alloc(size);
 Memory.writeByteArray(shellcode_pointer, [0xde, 0xad, 0xbe, 0xef]);
 Memory.protect(shellcode_pointer, size, 'r-x');

Other memory functions

  • Memory.copy(dst, src, size)
    Just as memcpy, make sure dst is writable and src is readable
  • Memory.dup(src, size)
    Just as memdup, returns a NativePointer object
  • Memory.allocUtf8String(str), Memory.allocUtf16String(str) and Memory.allocAnsiString(str)
    Shorthand for Memory.alloc and Memory.writeXXXString
  • Memory.patchCode(function_pointer, size, apply)
    The apply parameter is a JavaScript function that receives a pointer address to a writable location that allows us to patch the native function at function_pointer. Note that this doesn’t mean it will be patched at the same location as function_pointer points to as some systems may require to use a temporary buffer in a different location.