Hackfest 2021 Writeup - LoveCrypt - Offering

Hackfest 2021 Writeup - LoveCrypt - Offering

2021/11/22    

This one is the first challenge I solved at the Hackfest 2021 CTF. I first overlooked it because I am not very good at crypto. But when I took a better look at it, it was not very hard.

Challenge Description

Bring your offering before the Old Ones, cultist!

The description of the challenge was short. It contained a link to a web site.

Challenge Site

The site had a few links, if I remember well they didn’t bring me anywhere.

At the bottom of the site, there was something that looked like a flag.

If you’re a true cultist, you’re familiar with our insanely secure cipher and you’ll know what to do with this : HF-{wpLDu8K0wq3HocSIx4bFi8agx53HvcKoxJLGi8eIxIXCpce0x7rFm8KsxKnCpcakx57Gr8ekxIXCtcKkwrrDow==}

This is clearly Base64 encoded, so I took tried to decode it, but got only garbage out.

$ echo wpLDu8K0wq3HocSIx4bFi8agx53HvcKoxJLGi8eIxIXCpce0x7rFm8KsxKnCpcakx57Gr8ekxIXCtcKkwrrDow== | base64 -d 

û´­ǡĈdžŋƠǝǽ¨ĒƋLją¥ǴǺś¬ĩ¥ƤǞƯǤąµ¤ºã% 

I needed to figure out how to decrypt it. In the page source code, I found a big comment that looked like some source code. But unreadable.

#!/hfe/ova/rai clguba3

# Cu’atyhv ztyj’ansu Pguhyuh E’ylru jtnu’anty sugnta.
# --  UNAQF BSS VS LBH'ER ABG N PHYGVFG --

vzcbeg netcnefr
vzcbeg onfr64

qrs znva():
        cnefre =  netcnefr.NethzragCnefre()
        cnefre.nqq_nethzrag("bssrevat", uryc="Cerfrag lbhe bssrevat gb gur Byq Barf, phygvfg!")
        netf = cnefre.cnefr_netf()
        bssrevat = netf.bssrevat

        cevag("\aVä! Fuho-Avtthengu!\a")
        cevag("---------------------------------")
        rapelcg(bssrevat)
        cevag("---------------------------------")
        cevag("\aPguhyuh sugnta! ^(;,;)^\a")

qrs rapelcg(cnlybnq):
        pvcure = ""
        frperg = "alneyngubgrc"
        xrl = "e'ylru" * 13
        xrl = xrl[:yra(cnlybnq)]
        sbeovqqra_punef = []
        sbe yrggre va frperg:
                vs yrggre abg va sbeovqqra_punef:
                        sbeovqqra_punef.nccraq(yrggre)
        sbe yrggre va cnlybnq:
                vs yrggre va sbeovqqra_punef:
                        pune_pbqr = beq(yrggre) - 13
                        yrggre = pue(pune_pbqr)
                pune_pbqr = beq(yrggre) << 2
                yrggre = pue(pune_pbqr)
                pvcure += yrggre
        pvcure = pvcure[::-1]
        kberq_pvcure = ''.wbva(pue(beq(k) ^ beq(l)) sbe k,l va mvc(pvcure,xrl))
        pvcure = (onfr64.o64rapbqr(kberq_pvcure.rapbqr('hgs-8'))).qrpbqr('hgs-8')
        cevag("Urer'f lbhe rapelcgrq bssrevat :\a")
        cevag(pvcure)

vs __anzr__ == "__znva__":
        znva()

The first line looked like the shebang of a script. And the overall structure looked like some Python code. But unreadable.

I took the code to CyberChef and used ROT13 on it.

That gave me back the following Python code.

#!/usr/bin/env python3

# Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn.
# --  HANDS OFF IF YOU'RE NOT A CULTIST --

import argparse
import base64

def main():
        parser =  argparse.ArgumentParser()
        parser.add_argument("offering", help="Present your offering to the Old Ones, cultist!")
        args = parser.parse_args()
        offering = args.offering

        print("\nIä! Shub-Niggurath!\n")
        print("---------------------------------")
        encrypt(offering)
        print("---------------------------------")
        print("\nCthulhu fhtagn! ^(;,;)^\n")

def encrypt(payload):
        cipher = ""
        secret = "nyarlathotep"
        key = "r'lyeh" * 13
        key = key[:len(payload)]
        forbidden_chars = []
        for letter in secret:
                if letter not in forbidden_chars:
                        forbidden_chars.append(letter)
        for letter in payload:
                if letter in forbidden_chars:
                        char_code = ord(letter) - 13
                        letter = chr(char_code)
                char_code = ord(letter) << 2
                letter = chr(char_code)
                cipher += letter
        cipher = cipher[::-1]
        xored_cipher = ''.join(chr(ord(x) ^ ord(y)) for x,y in zip(cipher,key))
        cipher = (base64.b64encode(xored_cipher.encode('utf-8'))).decode('utf-8')
        print("Here's your encrypted offering :\n")
        print(cipher)

if __name__ == "__main__":
        main()

This is the code used to encrypt the flag, with the key included. I just needed to reverse it to decrypt the flag on the page.

#!/usr/bin/env python3

# Ph’nglui mglw’nafh Cthulhu R’lyeh wgah’nagl fhtagn.
# --  HANDS OFF IF YOU'RE NOT A CULTIST --

import argparse
import base64


def main():
    parser = argparse.ArgumentParser()
    parser.add_argument(
        "offering", help="Present your offering to the Old Ones, cultist!")
    args = parser.parse_args()
    offering = args.offering

    print("\nIä! Shub-Niggurath!\n")
    print("---------------------------------")
    decrypt(offering)
    print("---------------------------------")
    print("\nCthulhu fhtagn! ^(;,;)^\n")


def decrypt(payload):
    cipher = (base64.b64decode(payload.encode('utf-8'))).decode('utf-8')

    key = "r'lyeh" * 13
    secret = "nyarlathotep"

    key = key[:len(cipher)]
    cipher = ''.join(chr(ord(y) ^ ord(x)) for x, y in zip(cipher, key))
    cipher = cipher[::-1]

    forbidden_chars = []

    for letter in secret:
        letter = ord(letter) - 13
        if letter not in forbidden_chars:
            forbidden_chars.append(letter)

    decoded = ""
    for letter in cipher:
        char_code = ord(letter) >> 2
        if char_code in forbidden_chars:
            char_code = char_code + 13

        letter = chr(char_code)
        decoded += letter

    print(decoded)


if __name__ == "__main__":
    main()

We the script written, I called it and got the flag back.

python3 decrypt.py wpLDu8K0wq3HocSIx4bFi8agx53HvcKoxJLGi8eIxIXCpce0x7rFm8KsxKnCpcakx57Gr8ekxIXCtcKkwrrDow==

Iä! Shub-Niggurath!

---------------------------------
1234looks0a0lot0like0fishmen5678
---------------------------------

Cthulhu fhtagn! ^(;,;)^

Flag: FLAG-1234looks0a0lot0like0fishmen5678