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.