Frida 10.4 Released ∞

release

Frida provides quite a few building blocks that make it easy to do portable instrumentation across many OSes and architectures. One area that’s been lacking has been in non-portable use-cases. While we did provide some primitives like Memory.alloc(Process.pageSize) and Memory.patchCode(), making it possible to allocate and modify in-memory code, there wasn’t anything to help you actually generate code. Or copy code from one memory location to another.

Considering that Frida needs to generate and transform quite a bit of machine code for its own needs, e.g. to implement Interceptor and Stalker, it should come as no surprise that we already have C APIs to do these things across six different instruction set flavors. Initially these APIs were so barebones that I didn’t see much value in exposing them to JavaScript, but after many years of interesting internal use-cases they’ve evolved to the point where the essential bits are now covered pretty well.

So with 10.4 we are finally exposing all of these APIs to JavaScript. It’s also worth mentioning that these new bindings are auto-generated, so future additions will be effortless.

Let’s take a look at an example on x86:

var getLivesLeft = Module . findExportByName ( 'game-engine.so' , 'get_lives_left' ); var maxPatchSize = 64 ; // Do not write out of bounds, may be // a temporary buffer! Memory . patchCode ( getLivesLeft , maxPatchSize , function ( code ) { var cw = new X86Writer ( code , { pc : getLivesLeft }); cw . putMovRegU32 ( 'eax' , 9999 ); cw . putRet (); cw . flush (); });

Which means we replaced the beginning of our target function with simply:

mov eax , 9999 ret

I.e. assuming the return type is int , we just replaced the function body with return 9999; .

As a side-note you could also use Memory.protect() to change the page protection and then go ahead and write code all over the place, but Memory.patchCode() is very handy because it also

ensures CPU caches are flushed;

takes care of code-signing corner-cases on iOS.

So that was a simple example. Let’s try something a bit crazier:

var multiply = new NativeCallback ( function ( a , b ) { return a * b ; }, 'int' , [ 'int' , 'int' ]); var impl = Memory . alloc ( Process . pageSize ); Memory . patchCode ( impl , 64 , function ( code ) { var cw = new X86Writer ( code , { pc : impl }); cw . putMovRegU32 ( 'eax' , 42 ); var stackAlignOffset = Process . pointerSize ; cw . putSubRegImm ( 'xsp' , stackAlignOffset ); cw . putCallAddressWithArguments ( multiply , [ 'eax' , 7 ]); cw . putAddRegImm ( 'xsp' , stackAlignOffset ); cw . putJmpShortLabel ( 'done' ); cw . putMovRegU32 ( 'eax' , 43 ); cw . putLabel ( 'done' ); cw . putRet (); cw . flush (); }); var f = new NativeFunction ( impl , 'int' , []); console . log ( f ());

Though that’s quite a few hoops just to multiply 42 by 7, the idea is to illustrate how calling functions, even back into JavaScript, and jumping to labels, is actually quite easy.

Finally, let’s look at how to copy instructions from one memory location to another. Doing this correctly is typically a lot more complicated than a straight memcpy(), as some instructions are position-dependent and need to be adjusted based on their new locations in memory. Let’s look at how we can solve this with Frida’s new relocator APIs:

var impl = Memory . alloc ( Process . pageSize ); Memory . patchCode ( impl , Process . pageSize , function ( code ) { var cw = new X86Writer ( code , { pc : impl }); var libcPuts = Module . findExportByName ( null , 'puts' ); var rl = new X86Relocator ( libcPuts , cw ); while ( rl . readOne () !== 0 ) { console . log ( 'Relocating: ' + rl . input . toString ()); rl . writeOne (); } cw . flush (); }); var puts = new NativeFunction ( impl , 'int' , [ 'pointer' ]); puts ( Memory . allocUtf8String ( 'Hello!' ));

We just made our own replica of puts() in just a few lines of code. Neat!

Note that you can also insert your own instructions, and use skipOne() to selectively skip instructions in case you want to do custom instrumentation. (This is how Stalker works.)

Anyway, that’s the gist of it. You can find the brand new API references at:

x86 X86Writer X86Relocator

arm ArmWriter ArmRelocator ThumbWriter ThumbRelocator

arm64 Arm64Writer Arm64Relocator

mips MipsWriter MipsRelocator



Also note that Process.arch is convenient for determining which writer/relocator to use. On that note you may wonder why there’s just a single implementation for 32- and 64-bit x86. The reason is that these instruction sets are so close that it made sense to have a unified implementation. This also makes it easier to write somewhat portable code, as some meta register-names are available. E.g. xax resolves to eax vs rax depending on the kind of process you are in.

Enjoy!