13 May 2019
description: WGRAEHAAAAHAAHAHDAZHDAAHEAZHEHAZEHAZEAHAZ
category: reverse - 288
An ELF64 file is attached to the description:
Using IDA, you can find the first part of this challenge:
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}
13 May 2019
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
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:
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}
14 Apr 2019
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
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}
14 Apr 2019
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
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:
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 ?
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 ?)
There is some stuff here ! There is the blank flag and other things.
Let’s try the history:
This Gitlab repository seems fishy (the latest version contains and .odt
saying that you’ve been hacked):
Going through the commits I hound this:
so all we have to do is to get the zip creation date and apply the maths:
YEAH ! flag is WPI{Macros can kill}
even if we did not see the macro itself ;)
14 Apr 2019
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.
export HISTFILE = "/home/ctf/flag" ; history -r ; history
The flag was WPI{bash_sucks0194342}