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 !