FollowThePath

Catégorie: Reverse Difficulté: medium Flag: HTB{s3lF_d3CRYpt10N-1s_k1nd4_c00l_i5nt_1t}

Challenge

Description


FollowThePath is a Medium reversing challenge. Players will discover a self-decrypting code stub, before reverse engineering a flag check.

Analyse du binaire

On commence par décompiler l’exécutable, ici j’utilise GHIDRA, puis on regarde les chaînes de caractères définies :

On y trouve la chaîne Please enter the flag qui est référencée dans la fonction FUNC_140001960

Dans cette fonction, on voit que le premier caractère de notre input doit être un H puis que l’on XOR les 0x39 bytes à partir de l’adresse 0x140001039

On voit également un WARNING pour dire que la suite de la fonction n’a pas pu être correctement décompilée en raison de mauvaises instructions

Si l’on regarde l’adresse 0x140001039 on découvre que c’est justement la suite de la fonction qui n’arrive pas à être décompilée. En fait le code se modifie lui même au fur et à mesure

Il faut également noter que l’on se trouve dans la section .text de l’exécutable, ce qui est peu commun. Ici l’adresse annoncée par GHIDRA est 0x140001000 mais si l’on regarde la structure du fichier, cette section se commence au bytes 0x400 puisqu’elle est juste après le Headers

Il est important de connaître la vraie position de notre fonction dans le binaire pour savoir où commencer à XOR pour déchiffrer la suite

En réalité on aurait également pu trouver cette position en cherchant les bytes code du début de la fonction comme ça :

with open('chall.exe', 'rb') as f:
	data = f.read()
print(hex(data.find(bytes.fromhex('4d31c0458a040c4981'))))
# 0x400

Revenons au XOR, on doit le faire sur 0x39 (57) bytes à partir de 0x39 bytes après le début de la fonction (étrange n’est-ce pas, vous voyez le truc venir ?)

En python on aura donc ceci

chall = open('chall.exe', 'rb')
chall_modified = open('chall_modified.exe', 'wb')

chall_modified.write(chall.read(0x400 + 0x39))
chall_modified.write(bytearray([n ^ 0xde for n in chall.read(0x39)]))
chall_modified.write(chall.read())

chall_modified.close()
chall.close()

ET BOOOM, on décompile le nouvel exécutable et on voit qu’on a pu sortir un nouveau bout de code, qui est étrangement sous le même format que la première partie de tout à l’heure et qui XOR aussi les 0x39 bytes suivants

En fait il faut voir ce challenge comme des blocs de 57 bytes où le bloc x servira à déchiffrer le bloc x+1.

Il faut aussi noter que le caractère attendu dans la comparaison est le résultat d’un XOR avec notre input, donc par exemple pour le H il suffit d’inverse ce xor et de faire 0x8c ^ 0xc4 pour le retrouver. Pour le T on voit que le xor est 0x55 ^ 0x01

Il suffit donc d’automatiser cette tâche en récupérant les différentes valeurs à xor dans chaque bloc pour trouver le caractères attendu et la clé servant à déchiffrer le bloc suivant. Comme les positions n’ont pas l’air de bouger on peut regarder où sont ces valeurs dans un bloc pour les récupérer :

  • les 2 valeurs à xor pour récupérer le caractère du flag sont en position 10 et 17

  • la clé pour xor le prochain bloc est en position 47


Script de résolution

def solve():
	chall = open('chall.exe', 'rb')
	chall.seek(0x400) # On va jusqu'au premier bloc de la fonction
	
	flag = ""
	code = chall.read(0x39) # Le premier bloc n'est pas chiffré
	while True:
	  # On récupère le caractère attendu et on l'ajoute au flag
		flag += chr(code[10] ^ code[17])
		# Si notre flag est terminé on quitte
		if flag[-1] == '}':
			break
		# On déchiffre le bloc suivant
		code = bytearray([n ^ code[47] for n in chall.read(0x39)])
	
	chall.close()
	print(f'Flag: {flag}')

if __name__ == '__main__':
	solve()

Dernière mise à jour

Cet article vous a-t-il été utile ?