Points: 80 Category: Reverse Author sheidan

Introduction

We have an ELF binary which takes one parameter. This parameter is a secret and it is used in a cryptographic function called hash. So the goal is to find the secret. The program must return 22c15d5f23238a8fff8d299f8e5a1c62 as output.

Here, we explain two solutions to solve this challenge.

First solution

Static analysis

The hash function is located at 0x080484AB. It takes our input parameter and some operations are carried out.

This part of the code, at 0x80484CC, allows us to understand the algorithm as describe by the comments:

loc_80484CC:
mov     eax, [ebp+size]
sub     eax, 1
sub     eax, [ebp+i]    ; eax = strlen(*s) - 1 - i
mov     [ebp+size_1_i], eax
mov     edx, [ebp+size_1_i]
mov     eax, [ebp+s]
add     edx, eax        ; s[strlen(*s) - 1 - i]
mov     eax, [ebp+i]
mov     eax, u[eax*4]   ; u[i*4]
mov     ebx, eax
mov     ecx, [ebp+size_1_i]
mov     eax, [ebp+s]
add     eax, ecx        ; s[strlen(*s) - 1 - i]
movzx   eax, byte ptr [eax]

xoring:
movsx   eax, al
mov     eax, v[eax*4]   ; v[s[strlen(*s) - 1 - i] * 4]
xor     eax, ebx        ; v[s[strlen(*s) - 1 - i] * 4] ^ u[i*4]
mov     [edx], al       ; s[strlen(*s) - 1 - i] = eax ^ ebx
add     [ebp+i], 1      ; i++

Below, we translate asm operations into C-like pseudocode. This code describes the encryption operations:

size_t size = strlen(s)
for (int i = 0; i < size; i++)
{
	s[size - 1 - i] =  v[s[size - 1 - i] * 4] ^ u[i * 4]
}

The decryption process is illustrated by the c-like pseudocode below:

size_t size = strlen(s)
for (int i = 0; i < size; i++)
{
	s[size - 1 - i]  ^ u[i * 4] =  v[s[size - 1 - i] * 4]
}

Please note that we are going to have the value which is in v and not the secret value. We must find the index of this value to retrieve the initial value.

Retrieve the flag

We translate the decryption expression into a python script:

def decrypt(pKey, pCharset, pHexFinalOutput):
	"""
	:param pKey: seems to be the key
	:param pCharset: seems to be a charset
	:param pHexFinalOutput: is the program output for the flag as input
	"""
	# Translate hex string
	finalOutput = pHexFinalOutput.decode("hex")
	
	# Initialize the secret variable
	secret = ""

	for i, c in enumerate(finalOutput[::-1]):
		r = ord(c) ^ pKey[i]
		secret += chr(pCharset.index(r))
		
	return secret[::-1]
	
def extract_32bits(pStartAddr, pEndAddr):
	"""
	:param pStartAddr: buffer start address
	:param pEndAddr: buffer end address
	"""
	data = []
	
	for i in range(0, pEndAddr-pStartAddr, 4):
		data.append(get_32bit(pStartAddr + i))
		
	return data
	
# It is the output of the program for the flag as input
hexFinalOutput = "22c15d5f23238a8fff8d299f8e5a1c62"

# hash function uses a list which is called 'u'. This list seems to be the key
key = extract_32bits(0x0804a440, 0x0804A83D)

# hash function uses a list which is called 'v'. This list seems to be a charset
charset = extract_32bits(0x0804A040, 0x0804a440)

# Let's find the secret
secret = decrypt(key, charset, hexFinalOutput)

print(secret)
# yummy_h45h_br0wn

Second solution: Patch’n flag

Patch the binary

In fact, if we patch the binary we don’t need to know each details of the hash function because it is simple cryptographic function.

As we know, the binary is bricked. It is because strcpy function at 0x08048566 replace our string by a string equal to 0.

push    [ebp+src]       ; src, src=""
push    eax             ; dest, dest=<secret param>
call    _strcpy

The patch consists in replacing call _strcpy. EAX is already set as we want therefore we replace original opcodes by ‘nop’ opcodes.

[...]
E8 F5 FD FF FF --ERASE BY--> 90 90 90 90 90
[...]

After that, we run the program. We see that the output can be found by brute force because any characters will always have the same value at offset -1. At offset -2, the value is not equal to offset -1 but it is always the same as shown below:

./patched a
c4

./patch aa
c7c4

./patch baa
96c7c4

Retrieve the flag

Let’s find the secret by testing all printable characters.

hash_ = "22c15d5f23238a8fff8d299f8e5a1c62"
size = len(hash_)
flag = []

for i in range(0, size, 2):
    index = size - i
	
    # Get last hex values
    check = hash_[index-2:]
	
    # Looking for the correct character
    for c in string.printable:
        p = subprocess.Popen(["./patched.elf", "{}{}".format(c,"".join(flag))],
            stdout=subprocess.PIPE)
        stdout = p.communicate()[0].decode("utf-8").strip()
		
        # If output is egal to partial final hash
        if stdout == check:
	    # Insert the character at the beginning
            flag.insert(0,c)

print("".join(flag))
# yummy_h45h_br0wn

Pwntera

Yet another french CTF team that sux !