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
Please send password for user anonymous
PASS mail@mail.com
Invalid login credentials

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

$ strings ftp
...
Please send password for user 
PASS
login with USER PASS
blankwall
logged in
Invalid login credentials
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:

Disassembled 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

Disassembled hash function

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");
                                    printf("password: %s\n", test);
                                    return 1;
                                }
                         }
                    }
                }
            }
        }
     }

    return 0;
}

After trying different lengths of passwords and adding additional "for-loops", the 6 character passwords was found:

$ ./brute
Bingo!
password: cookie

$

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
Please send password for user blankwall
PASS cookie
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
Error reading RE flag please contact an organizer
USER
Cannot change user
...

After a closer look at the corresponding function

Flag return function

and the region from where it is called

Flag return command

we can see that by issuing an RDF ("Return Da Flag?) the server reads "re_solution.txt" and prints the flag.

flag{n0_c0ok1e_ju$t_a_f1ag_f0r_you}