/ web

Hack.lu CTF 2017 - Triangle (Web, Rev 100)

Enoncé :

Everything is controlled by the Triangle!

En cliquant sur le lien, on se retrouve sur ce formulaire de submit :

SubmitForm

Ci-dessous la fonction submit :

function login(){
	var input = document.getElementById('password').value;
	var enc = enc_pw(input);
	var pw = get_pw();
	if(test_pw(enc, pw) == 1){
		alert('Well done!');
	}
	else{
		alert('Try again ...');
	}
}

Allons voir de plus près la fonction enc_pw(). Après une légère 'désobfuscation', voici le code :

function enc_pw(e){
	var data=stoh(atob(getBase64Image("frei"))),
	t=4096,r=8192,
	m=12288,
	R=new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
	R.reg_write_i32(uc.ARM_REG_R8,r),
	R.reg_write_i32(uc.ARM_REG_R9,m),
	R.reg_write_i32(uc.ARM_REG_R10,e.length),
	R.mem_map(t,4096,uc.PROT_ALL);
	for(var a=0;a<o2.length;a++)
		R.mem_write(t+a,[data[o2[a]]]);
	R.mem_map(r,4096,uc.PROT_ALL),
	R.mem_write(r,stoh(e)),R.mem_map(m,4096,uc.PROT_ALL);
	var o=t,u=t+o2.length;
return R.emu_start(o,u,0,0),htos(R.mem_read(m,e.length))}

On peut voir que le chiffrement se fait via un émulateur d'archi ARM. Pour comprendre ce qui est exécuté dans cette émulateur, on récupère les données "data[o2[a]]" écrites avec R.mem_write(), puis on les écrit dans un fichier. On envoie ces données à IDA :

IDA_enc

Il suffit de lire les commentaires pour comprendre :) En quelques mots, pour chaque caractère n, il y a une routine de chiffrement à base de césar avec un décalage qui change en fonction du caractère n-1

La fonction get_pw() retourne :

XYzaSAAX_PBssisodjsal_sSUVWZYYYb 

Passons à la fonction test_pw() :

function test_pw(input_enc,pass){
	var t=stoh(atob(getBase64Image("eye"))),
	r=4096,
	m=8192,
	R=12288,
	a=new uc.Unicorn(uc.ARCH_ARM,uc.MODE_ARM);
	a.reg_write_i32(uc.ARM_REG_R9,m),
	a.reg_write_i32(uc.ARM_REG_R10,R),
	a.reg_write_i32(uc.ARM_REG_R8,pass.length),
	a.mem_map(r,4096,uc.PROT_ALL);
	for(var o=0;o<o1.length;o++)
		a.mem_write(r+o,[t[o1[o]]]);
	
	a.mem_map(m,4096,uc.PROT_ALL),
	a.mem_write(m,stoh(pass)),
	a.mem_map(R,4096,uc.PROT_ALL),
	a.mem_write(R,stoh(input_enc));
	
	var u=r,c=r+o1.length;
	return a.emu_start(u,c,0,0),a.reg_read_i32(uc.ARM_REG_R5)
}

Même principe que pour enc_pw(), on récupère le code ARM qu'on envoit à IDA :

ida test_pw

Dans ce code, pour chaque caractère de l'input, nous avons un césar +5; puis pour les caractères à l'index 1,3,5 ... (index % 2 != 0) un traitement supplémentaire est effectué : césar -3

Donc pour résoudre ce chall, il faut d'abord qu'on trouve le bon input à passer dans test_pw(), qui une fois le traitement effectué match avec XYzaSAAX_PBssisodjsal_sSUVWZYYYb

Voici le script :


good = "XYzaSAAX_PBssisodjsal_sSUVWZYYYb"

def cesar(caract, padding):
	tmp = ord(caract) + padding
	return chr(tmp)

def inverse(good_password):
	ret = ""
	index = 0
	while index < 32:
		caract = good_password[index]
		new_caract = cesar(caract, -5)
		if index % 2 != 0:
			new_caract = cesar(new_caract, 3)

		ret += new_caract
		index += 1
	return ret

print inverse(good)

result_inverse

SWu_N?<VZN=qngnm_hn_g]nQPTRXTWT`
est donc l'input chiffré à passer en paramètre de test_pw()

Il faut maintenant retrouver le clair, pour lequel enc_pw() donne SWu_N<VZN=qngnm_hn_g]nQPTRXTWT`

Etant donné que le décalage césar d'un caractère n est définit par son caractère n-1, j'ai décidé de refaire la foncton enc_pw() en python, afin de BF chaques caractères pour trouver chaque clair correspondant.

import string

def cesar(caract, padding):
	tmp = ord(caract) + padding
	return chr(tmp)

def getBinary(caract):
	bnary = format(ord(caract), 'b')
	return bnary[-1]

def encrypt(password):
	ret = ""
	i = 0
	padding = 6
	tmp_r6 = 0
	for x in password : 
	
		if i == 0:
			ret += cesar(x, padding)
		else:
			if str(last) == "1":
				tmp_r6 = i & 3
			tmp = ord(x) + 6 + tmp_r6
			tmp_r6 = 0
			ret += chr(tmp)
		last = getBinary(x)
		i += 1
	
	return ret

def bf_end():
	toFind = "SWu_N?<VZN=qngnm_hn_g]nQPTRXTWT`"

	ret = ""
	while len(ret) < 32:
		for element in string.printable : 
			tmp = ret+element
			result = encrypt(tmp)
			if toFind.startswith(result):
				ret += element
				print ret
				break

bf_end()

clear

En passant ce clair dans enc_pw() via la console JS de firefox, je m'en rend compte qu'il y a 3 caractères non corrects. En debuggant via la console du navigateur, on retrouve rapidement ces 3 caractères.

welldone

Flag : flag{MPmVH94PTH7hhafgYahYaVfKJNLRNQLZ}