EASYCTF - Soupstitution Cipher

description: We had a flag, but lost it in a mess of alphabet soup! Can you help us find it? Connect to the server via nc c1.easyctf.com 12484.

hint: I love parsing characters!

category: Reverse Engineering

Okay, There is the source code in python:

#!/usr/bin/env python3

from binascii import unhexlify as sOup
from operator import attrgetter as souP

ME_FLAGE = '<censored>'

SoUp = input
soUP = hex
sOUp = print
sOuP = ord
SOuP = open

def SoUP(sOUP):
	soup = 0
	while sOUP != 0:
		soup = (soup * 10) + (sOUP % 10)
		sOUP //= 10
	return soup

def SOup(sOUP):
	soup = 0
	for soUp in sOUP:
		soup *= 10
		soup += sOuP(soUp) - sOuP('0')
	return soup

def SOUP():
	Soup = SoUp()[:7]
	print(Soup)
	if not souP('isdigit')(Soup)():
		sOUp("that's not a number lol")
		return

	soup = SoUP(SOup(Soup))
	SouP = souP('zfill')(soUP(soup)[2:])(8)[-8:]
	if sOup(SouP) == souP('encode')('s0up')():
		sOUp("oh yay it's a flag!", ME_FLAGE)
	else:
		sOUp('oh noes rip u')

if __name__ == '__main__':
	SOUP()

Okay let’s first deobfuscate this SoupCode:

from binascii import unhexlify, hexlify
from operator import attrgetter

ME_FLAGE = '<censored>'

def third(param):
	soup = 0
	while param != 0:
		soup = (soup * 10) + (param % 10)
		param //= 10
	return soup

def second(param):
	soup = 0
	for soUp in param:
		soup *= 10
		soup += ord(soUp) - ord('0')
	return soup

def principal(a):
	Soup = a[:7]
	if not attrgetter('isdigit')(Soup)():
		print("that's not a number lol")
		return

	soup = third(second(Soup))

	SouP = attrgetter('zfill')(hex(soup)[2:])(8)[-8:]

	if unhexlify(SouP) == attrgetter('encode')('s0up')():
		print("oh yay it's a flag!", ME_FLAGE)
		exit(0)
	else:
		pass

Alright so the principal method, will take the first 7 digits. Then the method second() will convert it to an int and the method third() will invert all digits (123 -> 321).

The goal is to enter in the if unhexlify(SouP) == attrgetter('encode')('s0up')():

We have:

attrgetter('encode')('s0up')() = 'S0up'

And we want:

unhexlify(SouP) = 's0up'

So, reversing it:

SouP = hexlify('s0up') = 73307570

Just before we have:

SouP = attrgetter('zfill')(hex(soup)[2:])(8)[-8:]

This is only a conversion to hexa, and we want it to be egal to 73307570:

0x73307570 = 1932555632

then soup = 1932555632

The program call second() and third() so all we have to do is to reverse this number:

soup = third(second(Soup))

We have Soup = 2365552391

The first line of the method limit our entry to 7 digits:

Soup = a[:7]

At this point you can try with the maximum ‘legit’ entry (9999999), u won’t be able to reach 2365552391…

Okay first I commented the code with the values we would like to get:

def principal(a):
	Soup = a[:7]	#impossible
	if not attrgetter('isdigit')(Soup)():
		#print("that's not a number lol")
		return
	#Soup = "2365552391" # this to win
	soup = third(second(Soup))
	print(soup)
	#soup = 1932555632 # this to win
	SouP = attrgetter('zfill')(hex(soup)[2:])(8)[-8:]
	#SouP = "73307570" # this to win
	if unhexlify(SouP) == attrgetter('encode')('s0up')():	   # we want SouP = 73307570
		print(Soup)
		print("oh yay it's a flag!", ME_FLAGE)
		exit(0)
	else:
		pass

As u can see in the hint, there is characters that are interpreted as digit. let try to list some of them:

def principal(a):
	Soup = a[:7]
	if not attrgetter('isdigit')(Soup)():
		return
	print(Soup, end=" ")

for i in range(2500):
	principal(chr(i))

The result is:

0 1 2 3 4 5 6 7 8 9 ² ³ ¹ ٠ ١ ٢ ٣ ٤ ٥ ٦ ٧ ٨ ٩ ۰ ۱ ۲ ۳ ۴ ۵ ۶ ۷ ۸ ۹ ߀ ߁ ߂ ߃ ߄ ߅ ߆ ߇ ߈ ߉ ० १ २ ३ ४ ५ ६ ७ ८ ९

Now let’s see what is the value for one os this entry:

def principal(a):
	Soup = a[:7]	#impossible
	if not attrgetter('isdigit')(Soup)():
		return
	soup = third(second(Soup))
	print(soup)
	#soup = 1932555632 # this to win

principal('߉')
# ->  5491

principal('߉111')
# -> 1115491

principal('߉789')
# -> 9875491

Alright, as we can see, all numbers added after the special char will be before it after the second() and third() methods. So we want something like (weird_char + 552391) in entry and that weird_char = 5632

Let’s brute force those values and see if the weird_char = 5632 exists:

def principal(a):
	Soup = a[:7]
	if not attrgetter('isdigit')(Soup)():
		return
	soup = third(second(Soup))
	if soup == 5632:
		print(a)
		exit(0)

for i in range(10000):
	principal(chr(i))

# -> ७

Nice ! the should make us win this challenge ! Lets try it directly on the server =)

nc c1.easyctf.com 12484

७552391

oh yay it’s a flag! easyctf{S0up_soup_soUP_sOuP_s0UP_S0up_s000000OOOOOOuuuuuuuuppPPppPPPp}

The flag is easyctf{S0up_soup_soUP_sOuP_s0UP_S0up_s000000OOOOOOuuuuuuuuppPPppPPPp} =)