The Official Radare2 Book — страница 63 из 64

###instr_X

Another simple one, rename local vars anyways!

:> afvn local_1 arg1

This function XORs the value at sym.current_memory_ptr with arg1.

###instr_J

This one is not as simple as the previous ones, but it's not that complicated either. Since I'm obviously obsessed with variable renaming:

:> afvn local_3 arg1

:> afvn local_0_4 arg1_and_0x3f

After the result of arg1 & 0x3f is put into a local variable, arg1 & 0x40 is checked against 0. If it isn't zero, arg1_and_0x3f is negated:

The next branching: if arg1>= 0, then the function returns arg1_and_0x3f,

else the function branches again, based on the value of sym.written_by_instr_C:

If it is zero, the function returns 2,

else it is checked if arg1_and_0x3f is a negative number,

and if it is, sym.good_if_ne_zero is incremented by 1:

After all this, the function returns with arg1_and_0x3f:

.instructionset

We've now reversed all the VM instructions, and have a full understanding about how it works. Here is the VM's instruction set:

Instruction1st arg2nd argWhat does it do?
"A""M"arg2*sym.current_memory_ptr += arg2
"P"arg2sym.current_memory_ptr += arg2
"C"arg2sym.written_by_instr_C += arg2
"S""M"arg2*sym.current_memory_ptr -= arg2
"P"arg2sym.current_memory_ptr -= arg2
"C"arg2sym.written_by_instr_C -= arg2
"I"arg1n/ainstr_A(arg1, 1)
"D"arg1n/ainstr_S(arg1, 1)
"P"arg1n/a*sym.current_memory_ptr = arg1; instr_I("P")
"X"arg1n/a*sym.current_memory_ptr ^= arg1
"J"arg1n/aarg1_and_0x3f = arg1 & 0x3f; if (arg1 & 0x40 != 0)   arg1_and_0x3f *= -1 if (arg1 >= 0) return arg1_and_0x3f; else if (*sym.written_by_instr_C != 0) {   if (arg1_and_0x3f < 0)     ++*sym.good_if_ne_zero;   return arg1_and_0x3f; } else return 2;
"C"arg1n/a*sym.written_by_instr_C = arg1
"R"arg1n/areturn(arg1)

.bytecode

Well, we did the reverse engineering part, now we have to write a program for the VM with the instruction set described in the previous paragraph. Here is the program's functional specification:

   • the program must return "*"

   • sym.memory has to contain the string "Such VM! MuCH reV3rse!" after execution

   • all 9 instructions have to be used at least once

   • sym.good_if_ne_zero should not be zero

   • instr_P is not allowed to be used more than 9 times

Since this document is about reversing, I'll leave the programming part to the fellow reader :) But I'm not going to leave you empty-handed, I'll give you one advice: Except for "J", all of the instructions are simple, easy to use, and it should not be a problem to construct the "Such VM! MuCH reV3rse!" using them. "J" however is a bit complicated compared to the others. One should realize that its sole purpose is to make sym.good_if_ne_zero bigger than zero, which is a requirement to access the flag. In order to increment sym.good_if_ne_zero, three conditions should be met:

   • arg1 should be a negative number, otherwise we would return early

   • sym.written_by_instr_C should not be 0 when "J" is called. This means that "C", "AC", or "SC" instructions should be used before calling "J".

   • arg1_and_0x3f should be negative when checked. Since 0x3f's sign bit is zero, no matter what arg1 is, the result of arg1& 0x3f will always be non-negative. But remember that "J" negates arg1_and_0x3f if arg1& 0x40 is not zero. This basically means that arg1's 6th bit should be 1 (0x40 = 01000000b). Also, because arg1_and_0x3f can't be 0 either, at least one of arg1's 0th, 1st, 2nd, 3rd, 4th or 5th bits should be 1 (0x3f = 00111111b).

I think this is enough information, you can go now and write that program. Or, you could just reverse engineer the quick'n'dirty one I've used during the CTF:

\x90\x00PSAMuAP\x01AMcAP\x01AMhAP\x01AM

AP\x01AMVAP\x01AMMAP\x01AM!AP\x01AM

AP\x01AMMAP\x01AMuAP\x01AMCAP\x01AMHAP\x01AM

AP\x01AMrAP\x01AMeAP\x01AMVAP\x01AM3AP\x01AMrAP\x01AMsAP\x01AMeIPAM!X\x00CAJ\xc1SC\x00DCR*

Keep in mind though, that it was written on-the-fly, parallel to the reversing phase - for example there are parts that was written without the knowledge of all possible instructions. This means that the code is ugly and unefficient.

.outro

Well, what can I say? Such VM, much reverse! :)

What started out as a simple writeup for a simple crackme, became a rather lengthy writeup/r2 tutorial, so kudos if you've read through it. I hope you enjoyed it (I know I did), and maybe even learnt something from it. I've surely learnt a lot about r2 during the process, and I've even contributed some small patches, and got a few ideas of more possible improvements.

Radare2 Reference Card

This chapter is based on the Radare 2 reference card by Thanat0s, which is under the GNU GPL. Original license is as follows:

This card may be freely distributed under the terms of the GNU

general public licence — Copyright by Thanat0s - v0.1 -

Survival Guide

Those are the basic commands you will want to know and use for moving around a binary and getting information about it.

CommandDescription
s (tab)Seek to a different place
x [nbytes]Hexdump of nbytes, $b by default
aaAuto analyze
pdf@ funcnameDisassemble function (main, fcn, etc.)
f fcn(Tab)List functions
f str(Tab)List strings
fr [flagname] [newname]Rename flag
psz [offset]~grepPrint strings and grep for one
axF [flag]Find cross reference for a flag

Flags

Flags are like bookmarks, but they carry some extra information like size, tags or associated flagspace. Use the f command to list, set, get them.

CommandDescription
fList flags
fd $$Describe an offset
fjDisplay flags in JSON
flShow flag length
fx [flagname]Show hexdump of flag
fC [name] [comment]Set flag comment

Flagspaces

Flags are created into a flagspace, by default none is selected, and listing flags will list them all. To display a subset of flags you can use the fs command to restrict it.

CommandDescription
fsDisplay flagspaces
fs *Select all flagspaces
fs [space]Select one flagspace

Information

Binary files have information stored inside the headers. The i command uses the RBin api and allows us to the same things rabin2 do. Those are the most common ones.

CommandDescription
iiInformation on imports
iIInfo on binary
ieDisplay entrypoint
iSDisplay sections
irDisplay relocations
izList strings (izz, izzz)

Print string

There are different ways to represent a string in memory. The ps command allows us to print it in utf-16, pascal, zero terminated, .. formats.

CommandDescription
psz [offset]Print zero terminated string
psb [offset]Print strings in current block
psx [offset]Show string with scaped chars
psp [offset]Print pascal string
psw [offset]Print wide string

Visual mode

The visual mode is the standard interactive interface of radare2.