Artificial truth

The more you see, the less you believe.

[archives] [latest] | [homepage] | [atom/rss]

Defeating the RECon's movfuscator crackme
Fri 21 August 2015 — download

Remember when I said that I was impressed by doma's talk about his magical movuscator? At the end of his talk, a crackme was released, and no one expected it to be solved in less than 20 minutes ;)

Since almost everyone got the trick by now, this is how we did it.

I started implementing capstone-powered pattern-matching, when I remembered that the favourite attack vector of a wise friend of mine is side-channels.

$ r2 poc/crackme/crackme1
WARNING: bin_strings buffer is too big (0x00285e6c)
[0x08048260]> aa
[0x08048260]> ii
[Imports]
ordinal=001 plt=0x08048220 bind=GLOBAL type=FUNC name=printf
ordinal=002 plt=0x08048230 bind=GLOBAL type=FUNC name=fgets
ordinal=003 plt=0x08048240 bind=GLOBAL type=FUNC name=exit
ordinal=005 plt=0x08048250 bind=GLOBAL type=FUNC name=sigaction

4 imports

[0x08048260]> pd 30000~exit
    0x080486ec    c705245e2d08.  mov dword [0x82d5e24], sym.imp.exit
    ;-- reloc.exit_212:
    0x080500d4    46             inc esi  ; RELOC 32 exit
[0x08048260]> 

We know that sigaction is used to implement the big loop (check the slides if you don't know what I'm talking about), fgets and printf likely because using read and write is too bothersome, and exit to exit the program introduce a side channel?

What would be the point of having such an import if not for exiting the program earlier at some point? You may object that this is the only (clean) way to exit a movfuscated program, and you'll be right, but I didn't remember this when I had this idea :D

If the letters are checked one after the other, and the program terminates as soon as an unmatching character is found, then all we have to do is to try to maximize the running time of the program.

After we lost some time trying to get a reliable way to time a program execution time (perf is really awesome, check its manpage and its friends!) and to watch crowell implementing it in Ruby, we had the flag.

But since Ruby sucks harder than Python, here is my solution:

import string
import sys
from subprocess import Popen, PIPE, STDOUT

cmd = "perf stat -x, -e instructions:u " + sys.argv[1] + " 1>/dev/null"
key = ''

while True:
    maximum = 0,0
    for i in string.printable:
        p = Popen(cmd, stdout=PIPE, stdin=PIPE, stderr=STDOUT, shell=True)
        stdout, _ = p.communicate(input=b'%s\n' % (key + i))
        nb_instructions = int(stdout.split(',')[0])
        if nb_instructions > maximum[0]:
            maximum = nb_instructions, i
    key += maximum[1]
    print key

Here is the result:

$ python crackme/pwn.py ./poc/crackme/crackme1
{
{r
{re
{rec
{reco
{recon
{recon2
{recon20
{recon201
{recon2016
{recon2016}
{recon2016}0
{recon2016}0u
{recon2016}0u7
{recon2016}0u7D
{recon2016}0u7D1
^CTraceback (most recent call last):
$ ./poc/crackme/crackme1
M/o/Vfuscator 2.0a // domas // @xoreaxeaxeax
Enter the key: {recon2016}
YES!
$

Expect another blogpost about the second crackme ;)