July 30, 2019r2 radare2 reverse-engineering exploit binary cve
Everyone knows: This years r2con, the conference about
radare2, has a very special challenge – PwnDebian:
The almighty blenk92 and me decided to assist the
radare2 project in finding such an exploit an we think we were quite successful :)
But first some basics for
Shelling Out Via The r2 Shell
r2, it’s possible to shell out and execute shell commands without leaving the
[0x00000000]> ! echo YOLO YOLO [...] [0x5645965b5060]> dr `cat /tmp/reg` 0x5645965b5060
As can be seen, the
! and backtick characters are treated in quite a special way in the
r2 shell. In fact, they cause the arguments to be executed by
sh -c via
r_sys_cmd_str() and similar functions from
Another important aspect is that the
. command also has a special function in
r2. Let’s consult the help function in the
[0x00000000]> .? .r2cmd: interpret the output of the command as r2 commands
This is similar to an
eval() function that takes the raw input, in this case the output from an arbitrary
r2 command, and executes it as if it was typed directly into the
Grep It Like It’s Hot
While searching through the git log with
git log --grep, we’ve found an interesting commit that seems to fix code injection issues in the
is* commands. In particular, the fixes in
libr/core/cbin.c seem to fix this by wrapping the output of these commands in quotes. This seems strange, since these changes happen in
r_cons_printf() calls that only print text to the terminal.
This situation has been cleared up for us after we’ve stumbled across usages of
.<command>* in the
radare2 code after grepping with
ag "\"\." | ag cmd.
An interesting example of this is present in the
r_core_file_reopen_debug() function in
[...] r_core_cmd0 (core, ".is*"); r_core_cmd0 (core, ".ir*"); r_core_cmd0 (core, ".iz*"); r_core_cmd0 (core, ".iM*"); [...]
The first line calls
is* internally and evaluates the output by executing the commands printed by
is* that look as follows:
[...] f sym.imp.getxattr 16 0x00000000 f sym.imp.gethostname 16 0x00000000 f sym.imp.sigismember 16 0x00000000 [...]
This seems like a good attack vector, since the symbol names after
sym.imp are user controlled and taken directly from the analyzed binary.
Crafting A Payload
After some tests, it turned out that no sanitation of the symbol names is taking place. Therefore the first idea was to force a new line and append a command prefixed with
!. As this didn’t seem to work properly, another approach was used.
It turns out that commands can be injected by wrapping them in:
and inserting them into an existing command. A simple example of this approach is:
f sym.imp.`! sleep 999` 16 0x0
Our exploit uses our all-time favorite target:
By replacing a symbol name like
r2 or a hex editor, it becomes possible to provide an arbitrary shell command withing a binary. Since it’s quite common to analyse and debug untrusted and malicious binaries, this seems like a great attack scenario since this is largely invisible for potential victims. Also, the shell command doesn’t even get printed into the console after it has been executed:
|ERROR| Invalid command 'f sym.imp.p 99`AAAAAAAAAAA 16 0x557aa086c000' (0x66)
No sign of the
sleep command: The victim doesn’t even realize it has been pwned in case a more APT-like payload is being used <:
Executing The Payload
As already mentioned, the function that’s executing
r_core_cmd0 (core, ".is*") internally is called
r_core_file_reopen_debug(). This smells like the
r2 -c "ood" -d /tmp/hax --> PWNED
Therefore you can get pwned by executing
ood after opening the binary, which is quite a common thing to do.
Let’s see it in action:
The proof of concept exploits shown above can be found here.
Please note that the calculator exploit calls
gnome-calculator, so you need that installed. If you dont’ have it, try the
We’ve thought really hard about this.
We decided to quote the entire command:
f sym.imp.__sprintf_chk 16 0x00000000
"f sym.imp.__sprintf_chk 16 0x5562473eb000"
f sym.A`!sleep 99`AAAAAAAAAAA 8 0x000222c0
"f sym.A`!sleep 99`AAAAAAAAAAA 8 0x000222c0"
and therefore doesn’t get executed*. Therefore, the old Debian
3.2.1 release is affected and the current master isn’t :)
* Well at least not this time, however the fix is not complete yet :) Pull request coming soon™
The PR for the hotfix can be found here.
And now update
r2 for Debian pls and don’t forget to
CVE-2019-14745 has been assigned to this.
Thanks for reading, I love you all, see you at r2con!