Monday, 27 November 2017

TUCTF Write-up - RE track

TU CTF is an introductory CTF for teams that want to build their experience. We will have the standard categories of Web, Forensics, Crypto, RE, and Exploit, as well as some other categories we don't want to reveal just yet. If you have any questions, our contact is at the bottom of each page, but please read the official rules before sending us any emails.
This is a write-up for the Reversing challenges in TU CTF 2017.

Funmail [25]

Figure 1: Challenge description
This is straightforward. The challenge requires a password which is hardcoded within the binary as shown in the Figure 2.

Figure 2: Hardcoded password

Provide the password and get  the flag TUCTF{d0n7_h4rdc0d3_p455w0rd5}.
Figure 3: Flag for #1

Funmail 2.0 [50]

Figure 4: Challenge description
Same drill as before. The password is hardcoded but the program is intentionally crippled and does not show the flag.

Figure 5: Deliberately crippled 
The binary contains a function printFlag but it is not called from anywhere. We can just patch any call instruction such as the call puts shown in Figure 6.

Figure 6: The instruction to patching
to call printFlag as shown below.

Figure 7: After applying the patch
Running the patched binary we get the flag TUCTF{l0c4l_<_r3m073_3x3cu710n}.

Figure 8: Flag for #2

Unknown [200]

Figure 9: Challenge description

The binary takes the flag as a command line argument. The length of the correct flag is 56 as evident in the disassembly listing below where it compares the result of strlen to 56.

Figure 10: Length of flag must be 56 
Navigating down in the disassembly listing we have a function check_letter which takes in the provided flag and an index. The function checks whether the character at the specified index within the flag is correct and returns 0 if so.

Figure 11: Checking the flag letter by letter
check_letter is called from a loop, once for each of the 56 characters. If any check fail, the function returns one which is stored in the variable named fail. Later, on the contents of this variable decide whether to print the success or the failure message.

Figure 12: To fail or not to fail
The flag checking algorithm can be attacked using a brute-force approach. Each of the characters are checked individually, letter by letter without regards to the other characters.

To develop a bruteforce tool. our approach would be to set a breakpoint on 401c7d - the place where check_letter is called. We would modify the string and the index that is passed. If the function returns 0, we know the character is correct. Using this approach we can try out various letters at each of the 56 positions. The code for the brute forcer tool developed in python using r2pipe is shown below.

import r2pipe

|    .----> 0x00401c71      8b55f4         mov edx, dword [local_ch]
|    :|||   0x00401c74      488b45f8       mov rax, qword [local_8h]
|    :|||   0x00401c78      89d6           mov esi, edx
|    :|||   0x00401c7a      4889c7         mov rdi, rax
|    :|||   0x00401c7d      e80e020000     call check_letter

flag = ""

r2 ='unknown')

# Run with a dummy string
r2.cmd('doo {}'.format('a'*56))

# Set breakpoint
r2.cmd('db 0x401c7d')

for pos in xrange(56):
    for ch in '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ_+!{}':        
        # Run

        # Breakpoint at 0x00401c7d hits
        # Write the character index
        r2.cmd('dr esi={}'.format(pos))

        # Write the flag obtained so far
        r2.cmd('wz %s @ rdi' %format(flag+ch))

        # Step over call

        # Check function result
        rax = r2.cmdj('drj')['rax']

        # Set rip back to start
        r2.cmd('dr rip=0x401c71')

        # Success
        if rax == 0:
            flag += ch
            print '****************************************', flag

To speed up execution, after the function returns we set rip back to 401c71. This way we do not need to re-execute the binary each time. Running the script we get the flag TUCTF{w3lc0m3_70_7uc7f_4nd_7h4nk_y0u_f0r_p4r71c1p471n6!}.  Below is a demo of the bruteforce tool in action.

Future [250]

Figure 13: Challenge description
The last challenge of the RE track is a bit different from the rest. Quite unexpectedly we have been provided the C source code of the challenge. Hence there is no need to inspect the binary. The source looks like the image below.

Figure 14: Challenge source code
The program takes the flag as input, performs some calculations on it and compares the result to a hardcoded string. If it matches our flag is correct. Navigating up within the source code we can see two functions genMatrix and genAuthString which perform these calculations.

Figure 15: The calculations

We can employ a black box approach to solve this challenge. The entire system can be modelled in z3. To retrieve the flag, we can then query z3 if there is a possible input such that output matches the hardcoded string. The script is shown below.

from z3 import *

flag = [BitVec('ch'+str(i), 8) for i in xrange(25)]
mat = [[0 for i in xrange(5)] for i in xrange(5)]

# genMatrix
for i in xrange(25):
    m = (i * 2) % 25
    f = (i * 7) % 25
    mat[m/5][m%5] = flag[f]

auth = [0 for i in xrange(18)]

auth[0] = mat[0][0] + mat[4][4]
auth[1] = mat[2][1] + mat[0][2]
auth[2] = mat[4][2] + mat[4][1]
auth[3] = mat[1][3] + mat[3][1]
auth[4] = mat[3][4] + mat[1][2]
auth[5] = mat[1][0] + mat[2][3]
auth[6] = mat[2][4] + mat[2][0]
auth[7] = mat[3][3] + mat[3][2] + mat[0][3]
auth[8] = mat[0][4] + mat[4][0] + mat[0][1]
auth[9] = mat[3][3] + mat[2][0]
auth[10] = mat[4][0] + mat[1][2]
auth[11] = mat[0][4] + mat[4][1]
auth[12] = mat[0][3] + mat[0][2]
auth[13] = mat[3][0] + mat[2][0]
auth[14] = mat[1][4] + mat[1][2]
auth[15] = mat[4][3] + mat[2][3]
auth[16] = mat[2][2] + mat[0][2]
auth[17] = mat[1][1] + mat[4][1]

correct_output = "\x8b\xce\xb0\x89\x7b\xb0\xb0\xee\xbf\x92\x65\x9d\x9a\x99\x99\x94\xad\xe4"

s = Solver()

s.add(flag[0] == ord('T'))
s.add(flag[1] == ord('U'))
s.add(flag[2] == ord('C'))
s.add(flag[3] == ord('T'))
s.add(flag[4] == ord('F'))
s.add(flag[5] == ord('{'))
s.add(flag[24] == ord('}'))

for pos, ch in enumerate(correct_output):
    s.add(auth[pos] == ord(ch))

if s.check() == sat:
    m = s.model()
    print ''.join([chr(m[e].as_long()) for e in flag])

Running the script we get the flag TUCTF{5y573m5_0f_4_d0wn!}

Figure 16: The flag, finally!

No comments:

Post a Comment