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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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
1
2
3
4
5
6
7
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
1
2
3
4
5
6
7
8
9
10
11
12
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
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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:
- use
functionA
to get all the values stored inbufferB
(use IDA pro to check the offset betweenbufferA
andbufferB
, the offset is32
(0x20
) ) - use
functionB
to overwrite the value inbufferC
, in order to pass the checking functionfunctionC
- 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.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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 powered by Disqus.