The following paragraph (Emulation setup) is an explosion of the above r2 commands with a lengthy explanation of each, feel free to skip it if the above r2 passage is obvious to you.

Emulation setup

–

[0000:0000]> aei

aei initializes the ESIL VM state (as stated in ae? help) which means if there was a previous ESIL context is destroyed here and a new ESIL stack gets deployed.

–

[0000:0000]> aeim 0x2000 0xffff

aeim allocates the memory for mem read / write operations, basically needed for the stack pointer to point somewhere harmless.

Here i’m placing the start of it at address 0x2000 with a length of 0xffff bytes. The value for the start value is exactly the size of the binary, so that memory writes will likely not overwrite the code.

At the beginning of the bootloader code the stack pointer is placed at address 0x7c00, so it can grow for 23552 bytes before potentially overlapping to the code. It may or may not be enough, hopefully it is for this simple case.

In more complex cases of boot loader, maybe it’s necessary to keep the memory in one file descriptor and the code in another. This is possible for example by using temporary file descriptor seeks in r2 read / write commands.

–

[0000:0000]> aeip

This will set the ESIL instruction pointer (and the IP alias register of the current architecture, as specified in the register profile of the anal plugin) to the current seek, namely 0.

–

[0000:0000]> e io.cache=true

This let us write in the current session’s memory without having r2 to write it back to the binary file.

–

[0000:0000]> "e cmd.esil.intr=#!pipe python bios_pipe.py"

This, in pseudo english, means: “Every time there’s an ESIL interrupt ($ instruction), spawn this python script and pass it the number of the interrupt as argument”. This will load and execute the bios depicted above.

–

[0000:0000]> e esil.gotolimit=0xffff

This one. It took me a couple of hours to figure out what that ESIL infinite loop detected error message did mean.

The failing instruction was: rep movsb byte es:[di], byte ptr [si] which is known to be bounded by the cx value, which was itself conveniently set to the very finite value of 0x1e5 just few bytes above… so?

It turns out that the gotolimit is the maximum allowed count of single ESIL instructions which can be executed in a statement - and that’s great. In this case, the esil statement for the above failing instruction is:

which is composed by 35 esil instructions, so doing the rough math ?v 35*0x1e5 = 0x424f which is clearly greater than the default esil.gotolimit = 0x00001000 even if we ignore the fact that the GOTO jumps to instruction 5 and not to the beginning of the statement.

–

[0000:0000]> ! (sleep 30 && killall -3 r2)&

At the end of the emulated code, the bootloader code enters an infinite loop. This is a dirty trick to schedule r2 quit at 30 seconds from now whatever happens (included that you may have closed an r2 session and opened another one in the meantime…).

This particular one needs a posix shell to work.

–

[0000:0000]> aec

Starts the emulation until CTRL+C is pressed, if you have a chance to, if CTRL+C is honored by both radare2 and the spawned python code which may be running continuously at that time. Basically, in this case, it means run the emulation forever (due to the final infinite loop) or until r2 is killed by the dirty trick above.

Cinema of take one

This demonstration shows all the above actually works, but - unless you’re shooting an 1980s sci-fi B-movie - it’s spectacularly slow for every real world use case.

Take two, using r2lang + python RCore plugin

At that point, also after talking to pancake about this, there could be several reasons for it to be so slow, sorted by probability (more probable first):

spawning python intepreter at each interrupt is slow

my shitty python code is slow

python in general is slow

ESIL emulation is slow

Starting to address the more probable issue, an alternative way to do this - while still using my python BIOS - is to define an RCore plugin which accepts a new ‘bios’ command. In this way the python code is loaded only once and then at each interrupt the command itself gets executed, reusing the same python context.

The most evident issue so far is that the custom commands defined in RCore plugins don’t accept parameters, therefore here is another dirty trick.

In order to get the numeric value of the interrupt, i decided to use the $v variable which returns the immediate value of the instruction at the current seek. The problem here is that during emulation, the instruction pointer has been already incremented by the time the interrupt gets executed. So, assuming that x86 16-bit encoding of INT XX instructions is always 2 bytes long, i just subtracted 2 to current ip value in order to get the seek for the immediate value to extract.

Emulation setup (differences)

Again, custom commands do not accept parameters. More: if they’re called with a parameter, they don’t get executed at all.

To overcome this limitation, i just defined a macro named orpo. In this way, the extra unusable parameter which r2 pass to the intr handler is just ignored and the custom command is called.

–

[0000:0000]> "e cmd.esil.intr=` `;.(orpo)"

Here is the modified intr handler which in turn calls our macro, which in turn calls our custom command.

Buried in the above command line there’s also another mystery i lost an interesting hour to workaround. That space in backticks. By removing that, each char which is output from my BIOS command gets prepended by what it seems the output of a printf("0x%x\n", somevalue); buried somewhere along the r2 code path around the interrupt handling / r2lang io piping (i guess, but actually i was unable to find it).

Cinema of take two

Hey! This time is faster. Still there’s space for improvements, i guess it’s possible to go down the list of slowdown probability unrolled above until rewriting the BIOS in C, for example. Honestly enough, though, the python RCore plugin seems pretty fast to me.