hoping for no cyber bullshit...

Ndh2k16: "to be or not to be" write-upmer. 07 mars 2018

tags : write-up /



Series : write-up-serie

For the NdH2k16 wargame, i have written 5 challenges who were entitled :

All those challenges can be found within my Security Challenges repository on github, in an organised manner. If you have any questions regarding those challenges, the way they were made or the write-ups associated, don't hesitate to send me a mail. I will try to answer it my best.

Nevertheless, i was not able to find any write-up for the last challenge, so here it is :

the "to be or not to be" challenge was classified in the "crackme" category, so we already know there is certainly something like : run that program and find the password. Let's so open the file. First surprise, our web browser opens it, as if it's a text file, and here is what we get :

discover1
yep, that's right, there is nothing printed and the whole file seems empty. However, by underlining the file with our mouse, we see that the file is not empty at all :
discover2
So, we know we have a program to run, and that this program is somehow composed of blank caracters like "spaces" and "new lines". A solution to find out what it is, is to check then the wikipedia page regarding esoteric programing languages (that was the hint for this challenge). On this page, we find a language that may meet the way our file is written; its name is "whitespace". This language is indeed a simple stack manipulation language, based only on the characters "space", "tab" and "new line".

Let's so try to get an interpreter for this language and run our file (see https://github.com/hostilefork/whitespacers). Here is the output :

$ mv 2b30rn072b3 chall.ws
$ ./interpreter chall.ws
Enter the flag(by char,z to finish):

char:f
char:l
char:a
char:g
char:z
Bad flag%
$

Right! the program is indeed a whitespace program, and it asks us for the password. Let's now reverse it. For this, a really good resource is the whitespace tutorial.

The first thing we can do is so to write a little python file to make the whole challenge file a bit more readable, by printing "S" if the character is a space, and "T" if it's a tab:

data = ""
output_data=""
with open("chall.ws",'r') as file:
    data= file.read()

for x in data:
    if x == ' ':
        output_data+=" S"
    elif x == '\t':
        output_data+=" T"
    else :
        output_data+='N\n'

with open("challenge_first_step.txt",'w') as file2:
    file2.write(output_data)

running this python script returns the following :

S S S T S S S T S TN
TN
S S S S S T T S T T T SN
TN
S S S S S T T T S T S SN
TN
S S S S S T T S S T S TN
TN
S S S S S T T T S S T SN
TN
S S S S S S T S S S S SN
TN
S S S S S TN
[...]

From there, one can then compare the file with the tutorial of whitespace, in order to understand what does the code. If i take the output presented above and convert the first line, here is what we get :

S S S T S S S T S TN => push 69

But, converting the whole file by hand is not so fast, so here is another python script to translate the file into a human readable manner :

#list possible combinations ofthe language
combinations= {
    #arithmetic combinations
    "\t   ": "add",
    "\t  \t" : "substract",
    "\t \n": "multiply",
    "\t \t ": "integer division",
    "\t \t\t":"modulo",

    #stack combinations
    "  " : "push_stack",
    " \n ":"duplicate_stack",
    " \n\t":"swap stack",
    " \n\n":"discard stack",

    #heap combinations
    "\t\t ":"store",
    "\t\t\t":"retrieve",

    #flow control combinations
    "\n  ":"mark location",
    "\n \t":"call subroutine",
    "\n \n":"jump to label",
    "\n\t ":"jump if top stack is zero",
    "\n\t\t":"jump if top stack negative",
    "\n\t\n":"end subroutine",
    "\n\n\n":"end program",

    #io
    "\t\n  ":"print character",
    "\t\n \t":"print number",
    "\t\n\t ":"read character",
    "\t\n\t\t":"read number"
}

data = ""
output_data=""
with open("chall.ws",'r') as file:
    data= file.read()

temp_string=""
i =0
bol=False
#we read the whole file, and whenever a combination is valid, get it
while i < len(data)-1:
    temp_string+=data[i]
    try:
        if combinations[temp_string] is not None:
            #some combinations require more_treatment
            #especially the following use a number after the combination
            if combinations[temp_string] == "push_stack" or combinations[temp_string] == "mark location" or combinations[temp_string]=="call subroutine" or combinations[temp_string] == "jump to label" or combinations[temp_string]=="jump if top stack is zero" or combinations[temp_string]=="jump if top stack negative":
                i+=1
                number=0
                j=0
                #first get length of the number
                k=i
                while data[k] != '\n':
                    k+=1
                l=k-i-1
                #then get the value of the number
                while data[i] != '\n':
                    if data[i] == '\t':
                        number+=(2**(l-j))
                    j+=1
                    i+=1

                #print translated combination + number
                output_data+=combinations[temp_string]
                output_data+=" "
                #we treat here the case the number associated with the command is printable
                if (number <= 122 and number >= 97) or (number>=65 and number<=90) or(number>=48 and number<=57) or (number == 58):
                    output_data+=chr(number)
                elif number==32:
                    output_data+="space"
                else:
                    output_data+=str(number)

            else :
                #just print the combination, no treatment is needed
                output_data+=combinations[temp_string]
            output_data+='\n'
            temp_string=""
    except:
        #not a valid combination for now, pass
        pass
    i+=1

print output_data

the idea behind this script is to provide a list of the possible combinations of the program, and to translate it to a human readable instruction. Running this script against the file provide the the following:

push_stack E
print character
push_stack n
print character
push_stack t
print character
push_stack e
print character
push_stack r
print character
push_stack space
print character
push_stack 1
push_stack c
store
push_stack 2
push_stack 1
store
push_stack 3
push_stack 1
store
push_stack t
print character
push_stack h
print character
push_stack e
print character
push_stack space
[...]

the whole file put in human readable form can be found here: file

one can then understand what really does the program :

  1. In the first part (lines 1 to 112):

    • the string "Enter the flag(by char, z to finish:\n\n" is printed to the user.
    • in the mean time, some values are stored on the heap, which looks like the following :
    adress value
    1 c
    2 1
    3 1
    4 c
    5 2
    6 4
    7 4
    8 7
    9 c
    10 1
    11 1
    12 c


  2. in a second part (lines 113 to 136), the string "char:\x00" is stored on the heap starting from adress 20; at adress 30, the value 1 is pushed, and at adress 31, the value 0 is stored.

  3. a third part (lines 137 to 147) of the program can be translated like this (comments are there to display the value of the stack) :

    label 1: | #stack at first loop push 20 | label 2: | duplicate top stack | 20/20 retrieve | 'c'/20 duplicate top stack | 'c'/'c'/20 if top stack is zero then: | 'c'/20 jump to label3 | else: | print top stack | /20 push 1 | 1/20 add | /21 jump to label2 |

    This loop can be translated as the following: i retrieve the first character at adress 20, if it's zero then i jump out of the loop, else, i print the character, and add 1 to the adress and come back at the start of the loop. So this loop here is only there to print the whole "char:" string.

  4. the fourth part, regarding what is done in label 3, is the following :

    label 3: | stack discard | //permits to remove the duplicate of the previous loop read a character and store it at adress 50 | retrieve character at adress 50 | 'x' represents the character entered duplicate | 'x'/'x' push 'z' at the top of the stack | 'z'/'x'/'x' sub 'z' with the character at adress 50 | 'z'-'x'/'x' if zero: | /'x' jump to label 5 | else : | push 1 | 1/'x' add | /'x'+1 push 30 | 30/'x'+1 retrieve value at adress 30 | 1/'x'+1 duplicate | 1/1/'x'+1 push 1 | 1/1/1/'x'+1 add | 2/1/'x'+1 push 30 | 30/2/1/'x'+1 swap | 2/30/1/'x'+1 store then the top value at adress 30 | 1/'x'+1 retrieve value at adress equal to top stack | 'c'/'x'+1 sub | 'c'-'x'-'1' if zero : | jump to label 4 else: push adress 31 | /31 retrieve value at adress 31 | /0 push 1 | 1/0 add | /1 push adress 31 | 31/1 swap | 1/31 store top value at adress 31 |

    label 4: push adress 50 read character at adress 50 jump to label 1

    The first part regarding this label can be quite easily interpreted regarding the run of the program. It is actually checking if the character entered is 'z', which breaks the loop asking for a character and validates the flag entered. Especially, if the character entered is not 'z', then there is some "schmiblick" (i.e. we still don't know what is really done) with the character entered.

  5. Let's go to see the part related to label 5, in order to understand a bit more, let's dig a bit more regarding the labels :

    label 5: | stack push 30 | /30 retrieve value at adress 30 | /5 push 13 | 13/5 sub | /8 if zero then : | stack is null from here jump to label 8 | else: | jump to label 9 |

    label 8: push 31 retrieve value at adress 31 if zero jump to label 7: label 9: print "bad flag" jump to label 6 label 7: print "good flag" label6: exit

We can so understand the following regarding this part and the previous one :

Finally, the storage on the heap at the beginning of the program then represents the flag of our challenge, where each character is the following one in the alphabet corresponding to the real value of each flag character .

Let's finally run the program with the guessed flag :

 $ Enter the flag(by char,z to finish):

char:b
char:0
char:0
char:b
char:1
char:3
char:3
char:7
char:b
char:0
char:0
char:b
char:z
Good flag$

And here is the flag :)

You can find every materials of this blogpost here.

to the top

Comments