ECSC 2019 - Frankenstein

description: WGRAEHAAAAHAAHAHDAZHDAAHEAZHEHAZEHAZEAHAZ

category: reverse - 288

ecsc_frankenstein.png

An ELF64 file is attached to the description:

Using IDA, you can find the first part of this challenge:

ecsc_frankenstein_check.png

Let’s see the check_constraints function (This is the IDA pseudocode):

__int64 __fastcall check_constraints(__int64 a1)
{
  int i; // [rsp+14h] [rbp-Ch]
  unsigned int v3; // [rsp+18h] [rbp-8h]
  int v4; // [rsp+1Ch] [rbp-4h]

  v3 = 1;
  v4 = strlen((const char *)a1);
  if ( v4 != 11 )
    v3 = 0;
  for ( i = 0; i < v4; ++i )
  {
    if ( *(_BYTE *)(i + a1) <= 64 || *(_BYTE *)(i + a1) > 90 )
    {
      v3 = 0;
      break;
    }
  }
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 2) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 6) >> 3)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 8) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 8) >> 3)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 4) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 6) >> 4)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 4) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 9) >> 1)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)a1 >> 1) ^ (unsigned __int8)(*(_BYTE *)(a1 + 7) >> 1)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 9) >> 3)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 3) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 1)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 3) >> 2) ^ *(_BYTE *)(a1 + 10)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 8) >> 2)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 1) ^ (unsigned __int8)(*(_BYTE *)(a1 + 5) >> 3)) & 1 )
    v3 = 0;
  if ( (*(_BYTE *)(a1 + 1) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 3)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 6) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 2)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 6) >> 3) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 4)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 2) >> 2) ^ *(_BYTE *)(a1 + 8)) & 1 )
    v3 = 0;
  if ( (*(_BYTE *)(a1 + 5) ^ (unsigned __int8)(*(_BYTE *)(a1 + 8) >> 4)) & 1 )
    v3 = 0;
  if ( (*(_BYTE *)(a1 + 5) ^ (unsigned __int8)(*(_BYTE *)(a1 + 9) >> 4)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 3) >> 3) ^ (unsigned __int8)(*(_BYTE *)(a1 + 6) >> 4)) & 1 )
    v3 = 0;
  if ( ((unsigned __int8)(*(_BYTE *)(a1 + 7) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 2)) & 1 )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 8) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 3)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)a1 >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 1) >> 2)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)a1 >> 1) ^ (unsigned __int8)(*(_BYTE *)(a1 + 8) >> 4)) & 1) )
    v3 = 0;
  if ( !((*(_BYTE *)(a1 + 3) ^ (unsigned __int8)(*(_BYTE *)(a1 + 8) >> 4)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 4) ^ *(_BYTE *)(a1 + 6)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 3) ^ (unsigned __int8)(*(_BYTE *)(a1 + 7) >> 4)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 3) ^ *(_BYTE *)(a1 + 3)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 5) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 7) >> 3)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 1) >> 2) ^ *(_BYTE *)(a1 + 2)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 3) >> 4) ^ (unsigned __int8)(*(_BYTE *)(a1 + 4) >> 3)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 6) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 9) >> 1)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 2) >> 1) ^ (unsigned __int8)(*(_BYTE *)(a1 + 3) >> 2)) & 1) )
    v3 = 0;
  if ( !((*(_BYTE *)(a1 + 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 3) >> 4)) & 1) )
    v3 = 0;
  if ( !((*(_BYTE *)(a1 + 1) ^ (unsigned __int8)(*(_BYTE *)(a1 + 2) >> 1)) & 1) )
    v3 = 0;
  if ( !((*(_BYTE *)(a1 + 1) ^ *(_BYTE *)(a1 + 5)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 7) >> 3) ^ (unsigned __int8)(*(_BYTE *)(a1 + 10) >> 1)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 7) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 7) >> 1)) & 1) )
    v3 = 0;
  if ( !(((unsigned __int8)(*(_BYTE *)(a1 + 4) >> 2) ^ (unsigned __int8)(*(_BYTE *)(a1 + 6) >> 4)) & 1) )
    v3 = 0;
  return v3;
}

Considering the first if condition and the first for loop, we can guess that our entry is 11 uppercase letters. Then our entry has to validate some conditions. We can use z3:

from z3 import *

s = Solver()

param_0 = BitVec('param_0', 8)
param_1 = BitVec('param_1', 8)
param_2 = BitVec('param_2', 8)
param_3 = BitVec('param_3', 8)
param_4 = BitVec('param_4', 8)
param_5 = BitVec('param_5', 8)
param_6 = BitVec('param_6', 8)
param_7 = BitVec('param_7', 8)
param_8 = BitVec('param_8', 8)
param_9 = BitVec('param_9', 8)
param_10 = BitVec('param_10', 8)

s.add(param_0 >= 65)
s.add(param_0 <= 90)
s.add(param_1 >= 65)
s.add(param_1 <= 90)
s.add(param_2 >= 65)
s.add(param_2 <= 90)
s.add(param_3 >= 65)
s.add(param_3 <= 90)
s.add(param_4 >= 65)
s.add(param_4 <= 90)
s.add(param_5 >= 65)
s.add(param_5 <= 90)
s.add(param_6 >= 65)
s.add(param_6 <= 90)
s.add(param_7 >= 65)
s.add(param_7 <= 90)
s.add(param_8 >= 65)
s.add(param_8 <= 90)
s.add(param_9 >= 65)
s.add(param_9 <= 90)
s.add(param_10 >= 65)
s.add(param_10 <= 90)

s.add(((param_6 >> 3 ^ param_2 >> 4) & 1) == 0)
s.add(((param_8 >> 3 ^ param_8 >> 4) & 1) == 0)
s.add(((param_6 ^ param_4) >> 4 & 1) == 0)
s.add(((param_9 >> 1 ^ param_4 >> 2) & 1) == 0)
s.add(((param_7 ^ param_0) >> 1 & 1) == 0)
s.add(((param_9 >> 3 ^ param_1 >> 4) & 1) == 0)
s.add(((param_10 >> 1 ^ param_3 >> 2) & 1) == 0)
s.add(((param_10 ^ param_3 >> 2) & 1) == 0)
s.add(((param_8 ^ param_1) >> 2 & 1) == 0)
s.add(((param_5 >> 3 ^ param_1 >> 1) & 1) == 0)
s.add(((param_10 >> 3 ^ param_1) & 1) == 0)
s.add(((param_10 >> 2 ^ param_6 >> 4) & 1) == 0)
s.add(((param_10 >> 4 ^ param_6 >> 3) & 1) == 0)
s.add(((param_8 ^ param_2 >> 2) & 1) == 0)
s.add(((param_8 >> 4 ^ param_5) & 1) == 0)
s.add(((param_9 >> 4 ^ param_5) & 1) == 0)
s.add(((param_6 >> 4 ^ param_3 >> 3) & 1) == 0)
s.add(((param_10 ^ param_7) >> 2 & 1) == 0)
s.add(((param_10 >> 3 ^ param_8 >> 4) & 1) != 0)
s.add(((param_1 ^ param_0) >> 2 & 1) != 0)
s.add(((param_8 >> 4 ^ param_0 >> 1) & 1) != 0)
s.add(((param_8 >> 4 ^ param_3) & 1) != 0)
s.add(((param_6 ^ param_1 >> 4) & 1) != 0)
s.add(((param_7 >> 4 ^ param_1 >> 3) & 1) != 0)
s.add(((param_3 ^ param_1 >> 3) & 1) != 0)
s.add(((param_7 >> 3 ^ param_5 >> 2) & 1) != 0)
s.add(((param_2 ^ param_1 >> 2) & 1) != 0)
s.add(((param_4 >> 3 ^ param_3 >> 4) & 1) != 0)
s.add(((param_9 >> 1 ^ param_6 >> 2) & 1) != 0)
s.add(((param_3 >> 2 ^ param_2 >> 1) & 1) != 0)
s.add(((param_3 >> 4 ^ param_2) & 1) != 0)
s.add(((param_2 >> 1 ^ param_1) & 1) != 0)
s.add(((param_5 ^ param_1) & 1) != 0)
s.add(((param_10 >> 1 ^ param_7 >> 3) & 1) != 0)
s.add(((param_7 >> 1 ^ param_7 >> 2) & 1) != 0)
s.add(((param_6 >> 4 ^ param_4 >> 2) & 1) != 0)

while s.check() == sat:
	result = str(s.model()) + '\n'

	string = 'param_0 = '
	length = len(string)
	var0 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_1 = '
	length = len(string)
	var1 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_2 = '
	length = len(string)
	var2 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_3 = '
	length = len(string)
	var3 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_4 = '
	length = len(string)
	var4 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_5 = '
	length = len(string)
	var5 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_6 = '
	length = len(string)
	var6 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_7 = '
	length = len(string)
	var7 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_8 = '
	length = len(string)
	var8 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_9 = '
	length = len(string)
	var9 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	string = 'param_10 = '
	length = len(string)
	var10 = result[result.find(string) + length: result.find(string) + length + result[result.find(string) + length:].find('\n') - 1]

	print chr(int(var0)) + chr(int(var1)) + chr(int(var2)) + chr(int(var3)) + chr(int(var4)) + chr(int(var5)) + chr(int(var6)) + chr(int(var7)) + chr(int(var8)) + chr(int(var9)) + chr(int(var10))

	s.add(Or(
		param_0 != s.model()[param_0],
		param_1 != s.model()[param_1],
		param_2 != s.model()[param_2],
		param_3 != s.model()[param_3],
		param_4 != s.model()[param_4],
		param_5 != s.model()[param_5],
		param_6 != s.model()[param_6],
		param_7 != s.model()[param_7],
		param_8 != s.model()[param_8],
		param_9 != s.model()[param_9],
		param_10 != s.model()[param_10]
		))

This is the output:

# python script.py 
DXCHXATLXXD
CGLWGNCSGCK
EZOJZITLYYD
EZOJZYTLYYD
MZOJZYTLYYD
[...]
MZGJZITLYXD
MZGJZIVLYXD
MZGHZITLYXD
EZGHZITLYXD
EZGJZITLYXD
[...]

It seems that there is a lot of solutions…

Well, let’s see the second part of the program:

[...]
draw_input((__int64)v7, (__int64)v8);
print_grid(v8);
for ( j = 0; j <= 4; ++j )
	gol_step((__int64)v8);
ptr = malloc(0x2711uLL);
convert_grid_to_string((__int64)v8, (__int64)ptr);
if ( (unsigned int)validate(ptr) )
{
	puts("WGRAEHAAAAHAAHAHDAZHDAAHEAZHEHAZEHAZEAHAZ");
	printf("GG ! You can validate with ECSC %s :D\n", *(_QWORD *)(v4 + 8));
}
else
{
	puts("Nah it was just an impression...");
}
[...]

I won’t present every function, but in summary, draw_input is creating a grid with our entry (presented later), gol_step is modifying the grid (called 5 times (0 to 4)) and validate is comparing the resulting grid to a stored grid.

We need to see the gol_step and the validate functions:

__int64 __fastcall gol_step(__int64 a1)
{
  unsigned int i; // [rsp+1Ch] [rbp-14h]
  unsigned int j; // [rsp+20h] [rbp-10h]
  int v4; // [rsp+24h] [rbp-Ch]
  __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = copy_grid(a1);
  for ( i = 0; i <= 0x27; ++i )
  {
    for ( j = 0; j <= 0xF9; ++j )
    {
      v4 = get_n_alive_neighbors(v5, i, j);
      if ( v4 == 3 )
      {
        *(_BYTE *)(*(_QWORD *)(8LL * i + a1) + j) = 1;
      }
      else if ( v4 != 2 )
      {
        *(_BYTE *)(*(_QWORD *)(8LL * i + a1) + j) = 0;
      }
    }
  }
  return free_grid(v5);
}


__int64 __fastcall validate(__int64 a1)
{
  signed int i; // [rsp+10h] [rbp-8h]
  unsigned int v3; // [rsp+14h] [rbp-4h]

  v3 = 1;
  for ( i = 0; i <= 9999; ++i )
  {
    if ( *(_BYTE *)(i + a1) != answer[i] )
      return 0;
  }
  return v3;
}

As you can see gol_step looks like a Conway’s Game of Life

Now lets print the answer grid:

"""
Usage: gdb -q -x grid.py
"""

my_efl_file="./frankenstein"
 
gdb.execute("file " + my_efl_file)
gdb.execute("set confirm off")
gdb.execute("set pagination off")
gdb.execute("set non-stop on")
gdb.execute("set breakpoint pending on")
gdb.execute("set print pretty off")

i = 0
res = ""

gdb.Breakpoint("* validate+0x39")

try:
	gdb.execute("run UJCHXIWMXUD")
	frame = gdb.selected_frame()
	"""
	if str(frame.unwind_stop_reason()) == "0":
		gdb.execute("d")
		gdb.execute("watch $rdx == 0xf0b")
		gdb.execute("c")
	if str(frame.unwind_stop_reason()) == "0":
		gdb.execute("d")
		gdb.Breakpoint("* validate+0x39")
		gdb.execute("c")
	"""
	while True:
		if i%250 == 0:
			print(str(i))
			res += "\n" 
		if str(frame.unwind_stop_reason()) == "0":
			cl = str(frame.read_register("cl"))
			al = str(frame.read_register("al"))
			gdb.execute("set $al=" + cl)
			if al == "0x1":
				res += "#"
			else:
				res += "-"
			i += 1
			gdb.execute("c")
except:
	pass

print(res)

This is our grid (I deleted empty lines and for beauty):

----------------------------------------------------------------------------
---------------------##-------------------------------#---------------------
----##-----##------##-##---------#------------##------#----###-----##-------
---#-#---#---#-----##--#-----------------##---##----------####-----##-------
----#---#-----#-----#----#---#-#--------#-#---------------#-#--#-------##---
-------#------#---#----------#-#-------#-##-------###------#----##----##----
------#-------#-----#-------------------#-#--------##----#--#-#--#---#-##---
-----#-##----#-----##-#------------------##---##----###--##-#-#---##-##-----
-----##----##------##-#---###-----------------##-----##----##-#---#--#------
---------------------#----##--------------------------#-----###----###------
--------------------------#-------------------------------------------------
----------------------------------------------------------------------------

I decided to code the algorithm in python and try to bruteforce the entry by pattern recognition. For example, I’ll try to test all possibilities of the first 2 chars and compare it to the answer grid (we want the fish):

import copy
from itertools import product

charset = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"

loads = product(charset, repeat=2)

letter = []
letter.append("0111001100011100110001110011100111001010011100111001010010000101001001011100111001110011100111001110010100101010001010100101001110")
letter.append("0101001010010000101001000010000100001010001000010001010010000111001101010100101001010010100100000100010100101010001010100101000010")
letter.append("0111001100010000101001110011100101001110001000010001100010000101001011010100111001010011100111000100010100101010101001000111000100")
letter.append("0101001010010000101001000010000101001010001000010001010010000101001001010100100001010010100001000100010100101011011010100010001000")
letter.append("0101001100011100110001110010000111001010011100110001010011100101001001011100100001111010010111000100011100010010001010100010001010")

for payload in loads:
	#payload = "SE" + "".join(pay)
	print payload

	tab = []

	for i in range(40*250):
		tab.append(0)

	number = 0

	v7 = 90
	for myId in range(len(payload)):
		v7 += 2
		for i in range(5):
			for j in range(5):
				tab[(17*250) + i*250 + v7 + j] = int(letter[i][j + (ord(payload[myId])-65)*5])
		v7 += 5

	for i in range(5):
		tmp = copy.deepcopy(tab)

		for i in range(30*250):
			val = tab[i-251] + tab[i-250] + tab[i-249] + tab[i-1] + tab[i+1] + tab[i+249] + tab[i+250] + tab[i+251]
			if val == 3:
				tmp[i] = 1
			elif val != 2:
				tmp[i] = 0

		for i in range(40*250):
			tab[i] = tmp[i]

	draw = ""
	for i in range(10*250,25*250):
		if i%250 == 0:
			draw += '\n'
		if tab[i]:
			draw += '#'
		else:
			draw += '-'

	print draw
	raw_input(payload)

And the output:

-----------------
-----------------
-----------------
-----------------
-----------------
-----#------#----
-----#------#----
-----------------
-----------------
-----------------
--------##-------
--------##-------
-----------------
-----------------
-----------------
('A', 'A')

-----------------
-----------------
-----------------
-----------------
-----------------
-----#-----------
-----#-----------
------------##---
------------###--
-------#-##---##-
--------#---###--
------------##---
-----------------
-----------------
-----------------
('A', 'B')
[...]
-----------------
-----------------
-----------------
-----------------
-----------------
-----------------
----##-----##----
---#-#---#---#---
----#---#-----#--
-------#------#--
------#-------#--
-----#-##----#---
-----##----##----
-----------------
-----------------
('S', 'E')

Okay seems like the first letters are ‘SE’.

We can now modify our z3 script and add these conditions:

[...]
s.add(param_0 == 83)
s.add(param_1 == 69)
[...]

Then you can bruteforce the 3rd and the 4th char. You can select every couple of 3rd and 4th char from the z3 script (should not have lots of possibilities).

Keeping doing this, we finally find the good entry:

# ./frankenstein SEDWFDCRGGK
Wait !? Is it moving !?
-------------------------------------------------------------------------------
---###----###----##----#---#---###----##-----###----###----###----###----#-#---
---#------#------#-#---#---#---#------#-#----#------#-#----#------#------#-#---
---###----###----#-#---#-#-#---###----#-#----#------###----#-#----#-#----##----
-----#----#------#-#---##-##---#------#-#----#------#-#----#-#----#-#----#-#---
---###----###----##----#---#---#------##-----###----#--#---###----###----#-#---
-------------------------------------------------------------------------------
WGRAEHAAAAHAAHAHDAZHDAAHEAZHEHAZEHAZEAHAZ
GG ! You can validate with ECSC{SEDWFDCRGGK} :D

Yeah ! the flag is ECSC{SEDWFDCRGGK}

ECSC2019 - Am I Computable?

description: Nous avons récupéré cet exécutable amicomputable qui n’a pas l’air de fonctionner avec le fichier fourni file.dat.

Pouvez-vous nous donner la sortie affichée ?

category: reverse - 461

ecsc_amicomputable.png

An ELF64 file and a data file are attached to the description.

If we try to launch the elf file, the program it processing something but we can’t see the end of it…

Let try to understand what it is doing (IDA pseudocode decompilation)…

[...]
# stream is the file.dat
while ( v5 < v8 )
{
	bzero(&s, 0x400uLL);
	v4 = fread(&s, 1uLL, 0x400uLL, stream);
	memcpy(&v11[v5], &s, v4);
	v5 += v4;
}
v9 = (unsigned __int64)v8 >> 1;
ptr = malloc(2LL * v9);
if ( !ptr )
	return 3LL;
for ( i = 0; i < v9; ++i )
	ptr[i] = ((unsigned __int8)v11[2 * i] << 8) + (unsigned __int8)v11[2 * i + 1];
sub_DB9((__int64)ptr, v9);
for ( j = 0; j < v9; ++j )
{
	v11[2 * j] = ptr[j] >> 8;
	v11[2 * j + 1] = ptr[j];
}
sub_CB0((__int64)v11, v8);
[...]

The program concatenate 2 consecutive octets and add it to ptr. let’s see what is the sub_DB9 function:

__int64 __fastcall sub_DB9(__int64 a1, unsigned int a2)
{
	__int64 result; // rax
	__int16 v3; // ST1C_2
	unsigned int i; // [rsp+10h] [rbp-Ch]
	unsigned int j; // [rsp+14h] [rbp-8h]

	result = a2;
	for ( i = a2; i; --i )
	{
		for ( j = 0; ; ++j )
		{
			result = i - 1;
			if ( j >= (unsigned int)result )
			  break;
			if ( *(_WORD *)(2LL * j + a1) > *(_WORD *)(2LL * (j + 1) + a1) )
			{
				v3 = *(_WORD *)(2LL * j + a1);
				*(_WORD *)(2LL * j + a1) = *(_WORD *)(2LL * (j + 1) + a1);
				*(_WORD *)(2LL * (j + 1) + a1) = v3;
			}
		}
	}
	return result;
}

When i was trying to recode the function in python I understood that this function is actually sorting the ptr table. In fact, the algorithm is taking a lot of time and that’s why we can’t see the end of the program…

Okay, back to the main function, the ptr table in given to sub_CB0:

unsigned __int64 __fastcall sub_CB0(__int64 a1, unsigned int a2)
{
  char v3; // [rsp+10h] [rbp-80h]
	char v4; // [rsp+70h] [rbp-20h]
	unsigned __int64 v5; // [rsp+88h] [rbp-8h]

	v5 = __readfsqword(0x28u);
	sub_E82(&v3);
	sub_ED1((__int64)&v3, a1, a2);
	sub_1018((__int64)&v4, (__int64)&v3);
	printf("ECSC{", &v3);
	sub_D66((__int64)&v4);
	puts("}");
	return __readfsqword(0x28u) ^ v5;
}

In sub_E82 we can notice some constants: ecsc_amicomputable_constants.png

Google it to determine that it is simply a md5 hashing algorithm.

To conclude, the program is concatenating consecutive octets, sorting this table and calculating the md5 of this file. I coded it in python:

import hashlib

f = open('file.dat', 'rb')
content = f.read()
f.close()

ptr = []

for i in range(len(content)/2):
	ptr.append( ( ord(content[i*2]) << 8) + ord(content[(i*2) + 1]) )

ptr.sort()

content = list(content)
for i in range(len(content)/2):
	content[2*i] = chr( (ptr[i] >> 8)&0xff )
	content[(2*i) + 1] = chr(ptr[i]&0xff)

content = ''.join(content)

print "ECSC{" + hashlib.md5(content).hexdigest() + "}"
# python script.py 
ECSC{5d12758be6f2a971153c5599339f77b0}

Great ! File is ECSC{5d12758be6f2a971153c5599339f77b0}

WPICTF - zommercrypt

description: My daughter is using a coded language to hide her activities from us!!!! Please, help us find out what she is hiding! made by rm-k https://drive.google.com/open?id=1BO3v97Igzs7KruDkK7B4AGqxsNB7BaWE

category: Cryptography

The challenge was providing a picture : phonecap.png

phonecap.png

We can see on this picture, an sms conversation and some emojis separated by spaces, braces and an underscore. This looks like a flag encoded in some way with emojis… If we get the value of this emojis, we get the following :

\xF0\x9F\x98\x83 \xf0\x9f\x98\x81 \xf0\x9f\x98\x95 \xf0\x9f\x98\x97 \xf0\x9f\x98\x88 \xf0\x9f\x98\x97 \xf0\x9f\x98\x87 \xf0\x9f\x98\x8b \xf0\x9f\x98\x84 \xf0\x9f\x98\x97 { \xf0\x9f\x98\x86 \xf0\x9f\x98\x93 \xf0\x9f\x98\x84 \xf0\x9f\x98\x93 \xf0\x9f\x98\x82 \xf0\x9f\x98\x88 _ \xf0\x9f\x98\x8e \xF0\x9F\x98\x83 \xF0\x9F\x98\x83 \xf0\x9f\x98\x81 \xf0\x9f\x98\x93 \xf0\x9f\x98\x86 \xf0\x9f\x98\x87 }

If we take only the 3 emojis before the ‘{‘, we can see that their last bytes are : 0x8b, 0x84, and 0x97

0x8b and 0x84 have the same “distance” as ‘W’ and ‘P’ : 0x8b - 0x84 = ord(‘W’) - ord(‘P’) = 7.

But 0x97 seems out of bount for the letter ‘I’. Except if we apply a modulo as follow :

80 L, 81 M, 82 N, 83 O, 84 P, 85 Q, 86 R, 87 S, 88 T, 89 U, 8a V, 8b W, 8c X, 8d Y, 8e Z, 8f A, 90 B, 91 C, 92 D, 93 E, 94 F, 95 G, 96 H, 97 I, 98 J, 99 K, 9a L, 9b M, 9c N, 9d, …

With that said, we can decode the message and get the flag : WPI{REPENT_ZOOMERS}

WPICTF - wannasigh

description: My computer was hacked after I opened a calc file! Please help me get my stuff back. I should have made a backup…

category: Linux - 200

wpi_wannasigh.png

The file downloaded is an .ova, we can import it in virtualbox and access to the VM.

In the VM, we see a yout-stuff.zip file containing the flag but we do not have the password to unzip it. Let’s investigate quickly:

wpi_wannasigh_thumbnails.png

The .thumbnails directory contains the file icons. We cannot see the flag tho…

Ok let’s keep going !

As you see in the previous screenshot, Gimp is installed, does it contain interesting things ? like temp files ?

wpi_wannasigh_gimp.png

Arf… nothing again…

Then I decided to open the browser (his computer has been hacked by a calc file that he must have downloaded right ?)

wpi_wannasigh_firefox.png

There is some stuff here ! There is the blank flag and other things.

Let’s try the history:

wpi_wannasigh_history.png

This Gitlab repository seems fishy (the latest version contains and .odt saying that you’ve been hacked):

wpi_wannasigh_gitlab.png

Going through the commits I hound this:

wpi_wannasigh_commits.png

so all we have to do is to get the zip creation date and apply the maths:

wpi_wannasigh_flag.png

YEAH ! flag is WPI{Macros can kill} even if we did not see the macro itself ;)

WPICTF - suckmore-shell

description: Here at Suckmore Software we are committed to delivering a truly unparalleled user experience. Help us out by testing our latest project.

category: Linux - 100

The challenge was available via ssh with the following credentials:

ssh ctf@107.21.60.114
pass: i'm a real hacker now

Once logged you were inside a SuckMore Shell, where the binary cat was replaced with touch. You can still list directory content with the tab character.

touch /home/ctf/[INSERT TAB]
.bash_logout .bash_profile .bashrc flag

Then you have to find a way to read the flag and display it. The following command based on history files will do the trick.

wpi_suckmore.png

export HISTFILE="/home/ctf/flag"; history -r; history

The flag was WPI{bash_sucks0194342}