Mov Your ASM

Aperi'CTF 2019 - Reverse (150 pts).

Aperi’CTF 2019 - Mov Your ASM

Challenge details

Event Challenge Category Points Solves
Aperi’CTF 2019 Mov Your ASM Reverse 150 3

Mov MOV MOv MOv :) MOV Mov MOV mov MOv mOv Mov ;)

MOV mov MoV Mov : mov_your_asm - md5sum: 2fd79496f51b7d0e100ba537d0334a65

TL;DR

Crackme compiled with movfuscator to make it hard to reverse. This can still be done statically via demovfuscator or dynamically via a side channel attack. The second method is the one used in this writeup: counting the number of line outputed by ltrace.

Methodology

First, let’s have a gentle look to its format and behavior…

file mov_your_asm
# mov_your_asm: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, stripped

objdump -d mov_your_asm | grep text -A 10
# Disassembly of section .text:
#
# 0804829c <.text>:
#  804829c:	89 25 a0 81 40 08    	mov    %esp,0x84081a0
#  80482a2:	8b 25 90 81 40 08    	mov    0x8408190,%esp
#  80482a8:	8b a4 24 98 ff df ff 	mov    -0x200068(%esp),%esp
#  80482af:	8b a4 24 98 ff df ff 	mov    -0x200068(%esp),%esp
#  80482b6:	8b a4 24 98 ff df ff 	mov    -0x200068(%esp),%esp
#  80482bd:	8b a4 24 98 ff df ff 	mov    -0x200068(%esp),%esp
#  80482c4:	c7 04 24 0b 00 00 00 	movl   $0xb,(%esp)
#  80482cb:	c7 44 24 04 34 82 60 	movl   $0x8608234,0x4(%esp)
#  80482d2:	08
#  80482d3:	c7 44 24 08 00 00 00 	movl   $0x0,0x8(%esp)

./mov_your_asm
# Usage: ./mov_your_asm password

Ok, it’s a 32bits ELF, containing almost only mov instructions. Let’s google ELF only mov instructions and visit the second link: movfuscator

Now let’s get more insight on this program with its strings…
A lot of garbage, but it’s a CTF, so let’s grep only useful content!

strings mov_your_asm | grep -E "good|bad|flag|chall|valid|fail|APRK"
# Gg mate, flag is APRK{%s}.
# That's a good start! =]
# You failed :(

This could be reversed via demovfuscator, a tool available from git, but setting up this tool require some work, and it’s not perfect… So let’s try not wasting timee here and have a quick’n’dirty try with dynamic analysis…
Side channel stuff… Why not after all? :D

Most of the time, for a CTF crackme, the length is checked first. So let’s try multiple length and check if “good” is present in its output!

for length in range(30):
    output = popen("./mov_your_asm {:s} 2>&1".format("A" * length)).read()
    if "good" in output:
        print "Found len: {:d}".format(length)
        break

Now let’s run our crackme with a ltrace wrapper, and do a char by char bruteforce, without forgetting to pad the password to have a length of 21 each time (found previously). While doing that, we’ll count the number of lines outputed by ltrace to see if the behavior changes depending on the char used…

Spoil: It does!

We’ll have one recurrent number for every fail, and a different one, a bit higher for success. This is due to the fact that each time a char is found, some function calls will be passed, increasing the ltrace output.

We only have to script that to iterate over the password length, and BOOM, flag! :)

password = ""
for i in range(length):
    cmd = "ltrace ./mov_your_asm {:s} 2>&1 | wc -c".format((password + ".").ljust(length, "."))
    init_trace = int(popen(cmd).read(), 10)

    for l in chars:
        print "trying:", (password + l).ljust(length, ".")
        cmd = "ltrace ./mov_your_asm {:s} 2>&1 | wc -c".format((password + l).ljust(length, "."))
        new_trace = int(popen(cmd).read(), 10)
        if new_trace != init_trace:
            password += l
            break
    print "password", password

print "Found Password: {:s}".format(password)

Flag

Flag : APRK{mov_your_boooOOOooody}

Happy hacking !

Laluka