CSAW'15 Qualifications - FTP - 300 pts

Challenge

We found an ftp service, I'm sure there's some way to log on to it.

nc 54.172.10.117 12012

ftp_0319deb1c1c033af28613c57da686aa7

Solution

FTP is an 64-it dynamically linked ELF binary. It is a home-cooked FTP server. On connecting it is supposedly accepts standard FTP commands:

Welcome to FTP server
USER anonymous
PASS [email protected]

OK, let's see what happens under the hood. strings gives us a good amount of information, specifically:

\$ strings ftp
...
PASS
blankwall
logged in
Goodbye :)
%d,%d,%d,%d,%d,%d
%d.%d.%d.%d
500 Illegal PORT command.
invalid port specified
PORT command successful.
...

Upon connecting, the login function waits for USER and PASS FTP commands. Looking at disassembled instructions for the login function:

We can easily see that the server expects the user blankwall, but we need to find the password. There is no password in cleartext in the code (otherwise it would not worth 300 pts, right?). Furthermore we see that we would be able to login if the supplied username is blankwall and some function (at address 0x401540) applied on the supplied password is equal 0xD386D209 (3548828169 in decimal). Taking a look at disassembled function at 0x401540

we notice that it does a relatively simple calculation. It multiplies some value (initially initialized to 0x1505) by 33 and adds the ASCII value of the current password character. The results becomes the value which will be multiplied by 33 for the next iteration, or

\$\$ v_{n+1} = v_n*33 + c_n \$\$

with \$v_0 = 0x1505\$ and \$c_n\$ being the ASCII value of the character at position n.

Now, I quickly coded a reverse function in python which would take the expected value (0xD386D209) and go down to 0x1505. That is, going through all the printable ASCII characters, substracting their values from the initial value and testing if it is divisible by 33. However the first caveat was in the fact that we should account for the LF (ASCII value 10) at the end of the string which is also taken into account in the password hashing calculation. But even by first removing the LF, the value was never divisible by 33.

By taking a closer look at the password hashing function we see that even though it is a 64-bit binary, the result of the calculation is on 32 bits. Therefore for passwords of some length the value will overflow. Hence this is why I ended up renaming this function ugly_hash.

Afterwards I coded a simple brute force algoritm which computes the "hash" value for all low-case characters.

#include <stdio.h>

int ugly_hash(char *a1)
{
int i=0;
int v3;

v3 = 5381;
while (a1[i-1] != 10) {
v3 = 33*v3 + a1[i];
i++;
}
return (unsigned int)v3;
}

int main() {

int val, i, k, j, l, m, n;
int r1, r2;

r1 = 97;
r2 = 122;

char *test = (char*) malloc(7*sizeof(char));

for (i=r1; i<r2; i++) {
test[0] = i;
for (j=r1; j<r2; j++) {
test[1] = j;
for (k=r1; k<r2; k++) {
test[2] = k;
for (l=r1; l<r2; l++) {
test[3] = l;
for (m=r1; m<r2; m++) {
test[4] = m;
for (n=r1; n<r2; n++) {
test[5] = n;
test[6] = 10;
val = ugly_hash (test);
if (val == 0xd386d209) {
printf("Bingo!\n");
return 1;
}
}
}
}
}
}
}

return 0;
}

\$ ./brute
Bingo!

\$

Not the most elegant way, but hey, it is a CTF, so time is points :)

It should be noted, that there are other passwords candidates for longer passwords (e.g. "bqqmngqc").

OK, let's login to the FTP now!

Welcome to FTP server
USER blankwall
logged in

It's a barebone FTP, so no ls, get,... Only LIST and PASV. After looking at the directory we see that there is a "flag.txt" and "re_solution.txt". For some reason it is impossible to retrieve any file, but re_solution.txt is present in strings:

\$ strings ftp
...
(SOCKET ERROR)
PASV succesful listening on port: %d
re_solution.txt