Runtime Environment 2

Challenge Description

This time it HAS to be harder.

MD5 (hasbeen.tar.gz) = 46ff7d24975679901d8f8d769e567b09

  • rootkid

Challenge Details

Oh god, the moment I see the challenge description, I knew I'm in for a hell ride. This challenge took me 3 painful days to solve (ofc I gave up a few times to go solve other challenges XDDDDDDD)

Why? I realised the challenge was written in Haskell, a language that I did not know how to read/write, much less understand how GHC compiles Haskell code into executables. The way functions work is pAin but oh wells I decided to bite the nail and try to solve it.

Like RE 1, we were given a binary and an encoded file. However, this time the encoded file is not readable:

jeez

And by looking at the disassembler, I can confirm that the program is written in Haskell due to the presence of hs_main being called in main:

When i ran the program, I realised that the program gave different outputs for the same input, meaning that there is something else involved during the encoding process:

From there, I ran hsdecomp on the program to attempt to decompile the program back to the original haskell code and figure out what happens during the program:

Main_main_closure = >>= $fMonadIO
    (fmap $fFunctorIO unpack getLine)
    (\loc_4232104_arg_0 ->
        >>= $fMonadIO
            getCurrentTime
            (\loc_4231968_arg_0 ->
                >>= $fMonadIO
                    ($
                        !!ERROR!!
                        ($
                            !!ERROR!!
                            ($
                                (.
                                    (case $fRealFracFixed $fHasResolutionTYPEE12 of
                                        loc_4228312_case_tag_DEFAULT_arg_0@_DEFAULT -> floor <index 0 in loc_4228312_case_tag_DEFAULT> $fIntegralInt
                                    )
                                    (. nominalDiffTimeToSeconds utcTimeToPOSIXSeconds)
                                    loc_4231968_arg_0
                                )
                            )
                        )
                    )
                    (\loc_4230768_arg_0 -> >>= $fMonadIO ($ !!ERROR!! (zipWith !!ERROR!! (map !!ERROR!! loc_4232104_arg_0) 
                    (map !!ERROR!! loc_4230768_arg_0))) (\loc_4229920_arg_0 -> $ putStr ($ pack (map !!ERROR!! loc_4229920_arg_0))))
            )
    )

loc_4229392 = \loc_4229392_arg_0 loc_4229392_arg_1 -> : loc_4229128 (loc_4229392 $fIntegralInt32 loc_4229128)
loc_4229128 = xor $fBitsInt32 loc_4228928 (shiftL $fBitsInt32 loc_4228928 (I# 5))
loc_4228928 = xor $fBitsInt32 loc_4228728 (shiftR $fBitsInt32 loc_4228728 (I# 17))
loc_4228728 = xor $fBitsInt32 loc_4228568 (shiftL $fBitsInt32 loc_4228568 (I# 13))
loc_4228568 = fromIntegral loc_4229392_arg_0 $fNumInt32 loc_4229392_arg_1

At first, I did not really understand the decompiled code as I was not really good in haskell. However, at first glance, I can confirm that:

  • Program gets current time to be used for the encryption process

  • Input/time will be xored with each other

  • Bit shifting is also involved.

After putting some breakpoints in IDA and a painful debugging process, I finally managed to figure out the encryption function:

import os
import ctypes
import time


def encrypt(time:int,letter):
    time = (time ^ (time << 13)) & 0xffffffff
    if(ctypes.c_long(time).value < 0):
        time = time | 0xffffffff << 32
    time = (time ^ (time >> 17)) & 0xffffffff
    print(hex(time))
    time = (time ^ (time << 5)) & 0xffffffff
    print(hex(time))
    letter = (letter ^ (time)) & 0xff
    return time,chr(letter)

plaintext = input()
time = int(time.time())
ciphertext = ""
for x in sad:
    time,letter = encrypt(time,x)
    cry+= letter
   
print(cry)

In essence, the program will take input from the user and current time whereby the time goes through the following process before xoring with a letter of the user:

  • Left shift by 13 and keep signed value of the result

  • Right shift by 17

  • Left shift by 5

The resulting time will be saved for the next letter until the entire string is encrypted.

Challenge Solution:

To solve the challenge, simply get the modified time of the file and run the encryption process again since it is a simple xor function that can be undone by xoring the encrypted output:

import os
import ctypes

def encrypt(time:int,letter):
    time = (time ^ (time << 13)) & 0xffffffff
    if(ctypes.c_long(time).value < 0):
        time = time | 0xffffffff << 32
    time = (time ^ (time >> 17)) & 0xffffffff
    time = (time ^ (time << 5)) & 0xffffffff
    letter = (letter ^ (time)) & 0xff
    return time,chr(letter)
# def decrypt(time:int,letter:int):



sad = open("challenge.bin","rb").read(

og_time=  int(os.path.getmtime("challenge.bin")) 
time = og_time
cry = ""
for x in sad:
    time,letter = encrypt(time,x)
    cry+= letter
   
print(cry)

Flag: grey{Funct1on41_P4rad1s3_iZ_Fun}

Last updated