Byte Bandits 2020: Rev - Autobot
13/Apr 2020TL;DR Easy crackme repeated 300 times.
Problem statement
Decepticons are coming.
nc pwn.byteband.it 6000
Let’s start
We are dealing with a reversing challenge, but we are only given an address and a port: pwn.byteband.it 6000. The only thing we can do is connecting to that address and seeing what happens… But wait a second, what is an autobot? Let’s google it…

Ok, I admit, I’m not a great fan of transformers, and the picture above seems to tell me: “shame on you!”, but maybe I will learn something on them with this challenge!
Connecting to the given address with nc we get a base64 encoded string. After decoding it I checked what we have with the file command
autobot: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=44583d1c72cc3f1e29e28a478aade7c43c834281, stripped
An executable file! Ok, the reversing phase can start! However it is important to notice that the connection is still open and the server is waiting for something. Let’s try typing a random input… The server answers:
Wrong pass
As I expected I’m not lucky, but at least now I know that I am probably supposed to provide the right pass. Let’s reverse the program and find the password!
Reversing
I’m using radare2 to reverse the binary. Let’s check the main function:

It only performs a call to the fcn.000007da function. Let’s analyze that function

the first thing we can notice is that a local array of 40 values has been created

after the fgets that asks us for a password we can clearly see a loop where the letters of the input we provided are compared to the letters of another string. The comparison is not performed using the same index for both the strings compared, but the values in the previous array are used as indexes for the second string. It is easier to explain with a translation of the assembly snippet in C:
for (int i=0; i<some_length; i++) {
if (our_input[i] != their_string[their_array[i]]) {
puts("Wrong pass");
exit(1);
}
}
printf("good job");We still need two other things look at: the values of some_length and of their_string. Both of them are very easy to find. If we look at the decompiled snippets above we can see that var_e8h is our index i and var_e4h is some_length, that is equal to 0x1c (little spoiler: in this case). We can easily find also the value of their_string which is located at address 0x00000aa8 and its value is tEJwgBxacQTbjGzFrUCmZIlSuODdNv (another little spoiler: in this case).
Nice, it seems we have all we need to find the right password, you can do it statically, dynamically by debugging and checking the letters compared, you can use the awesome angr, it’s up to you, it’s a very easy task to perform. But…
After finding the correct password if you feed it to your local program it says: good job, so I was happy to submit it to the server and get the flag. However the server answered me with another base64 encoded ELF! After a quick look I realized it was exactly the same binary as before, with only a few changes: the length of their_string, some_length and their_array were different! Ok, it is time to automate the crackme process.
Let’s write some code
There are tons of different ways to accomplish our task, I decided to use Python and r2pipe, a cool way to access radare2 via pipe from any programming language. I admit that this is for sure not the most elegant code you will see, but sometimes in CTFs unfortunately you don’t have much time to think about elegance. There should be a CTF where elegance of the solution worth points, hmmm but this is another story… Let’s go back to our code.
First of all let’s import some useful things:
from pwn import *
import base64
import r2pipe
import sysAs you may imagine I use pwntools to interact with the server, base64 to decode the binaries, r2pipe to communicate with radare2 and sys just to exit when the flag is found.
def crack_one():
b64_binary = r.recvline()
if b'flag' in b64_binary:
print(b64_binary)
sys.exit(0)
with open('autobot', 'wb') as f:
f.write(base64.b64decode(b64_binary))I start with defining a function crack_one, which is responsible to crack a single binary, it will be called as many times we need. The code is straightforward, I receive the binary, check if there is a flag in the server answer, if yes I print the flag and exit, otherwise I save the binary in a file called autobot.
Let’s continue the function:
their_array = []
p = r2pipe.open('./autobot')
p.cmd('aaa')
p.cmd('s 0x000007f4')
disassembly = p.cmd('pd 41').strip().split('\n')
their_array = [int(line.split()[-1], 16) for line in disassembly]
some_length = their_array[-1]To make the understanding of the code easier, I’m using the same names of the variables that I used in the translated C snippet above. I create a list which will contain the array of the indexes, then I communicate with radare2, performing an initial analysis, and I dump 41 lines of disassembled code starting from the base address of the array. After some Python string manipulation I get the indexes I need. I dumped 41 lines because the last one is the value of some_length.
I get also their_string by copying the string at address 0x00000aa8:
p.cmd('s 0x00000aa8')
their_string = p.cmd('ps')What is left to do is to simply build the password:
pwd = ''
for i in range(some_length):
pwd = pwd + their_string[their_array[i]]
r.sendline(pwd)For how many times this will happen? I don’t know yet, so I use an infinite loop:
while True:
crack_one()It turned out I needed to crack 300 binaries.
This is the complete source:
from pwn import *
import base64
import r2pipe
import sys
def crack_one():
b64_binary = r.recvline()
if b'flag' in b64_binary:
print(b64_binary)
sys.exit(0)
with open('autobot', 'wb') as f:
f.write(base64.b64decode(b64_binary))
their_array = []
p = r2pipe.open('./autobot')
p.cmd('aaa')
p.cmd('s 0x000007f4')
disassembly = p.cmd('pd 41').strip().split('\n')
their_array = [int(line.split()[-1], 16) for line in disassembly]
some_length = their_array[-1]
p.cmd('s 0x00000aa8')
their_string = p.cmd('ps')
pwd = ''
for i in range(some_length):
pwd = pwd + their_string[their_array[i]]
r.sendline(pwd)
while True:
crack_one()which gave me the flag: flag{0pt1mus_pr1m3_has_chosen_you}. I will see transformers soon, do they talk about reverse engineering?

