Hero 2022 Writeup

I participated in the Hero V4 CTF event, playing as part of Isengard. It was held over the weekend (Sat, 28 May 2022, 05:00 SGT — Mon, 30 May 2022, 07:00 SGT) but managed to only play for a day. In the end, we ranked 82nd out of 632 scoring teams.

Below are the writeups :


Challenge Category Points Solves
Deadalus Programming 484 20
Impossible Pwn / Crypto 475 25
The Oracles Apprentice Crypto 274 72
Pixel Poney Programming 168 87
Overload Programming 50 221
Heist Programming 50 305
HeroGuessr1 OSINT 50 342
Poly321 Crypto 50 395



Deadalus

Hero 2022 Writeup

Challenge Description :

Deadalus has lost the technical information of his famous maze and is left with a few old blueprints. He want’s to add some improvements to it but got lost along the way. If you could just help him count the number of unique loops in different parts of the maze, it would be… amazing ?

The maze is magic, so there is no notion of walls and corridors, but there are gateways that allow you to travel in a direction (L: left, R: right, U: up, D: down). Some gateways are special, and allow you to go in two opposit directions (-: left/right, |: up/down).

If a gateway leads to another part of the maze (-> it leads outside of the grid), the loop is not complete, so don’t count it. The same loop can’t go twice through the same special getaway to go in both directions, it automatically leads to two different loops.

. R . D . .
R . . . . D
. | . L . .
. . . . . .
. R . U . .

In the previsous example, there are two unique loops. There are detailed on the following figure. The two first are the two unique loops, as for the third, it’s not complete.

. ----- . .   . . . . . .   . . . . . .
. | . | . .   . . . . . .   -----------
. ----- . .   . ----- . .   . . . . . |
. . . . . .   . | . | . .   . . . . . |
. . . . . .   . ----- . .   . . . . . |

Finaly, you can use special gateways in a specific direction only. Here are two examples, the first where you can use the gateway, the second were you can not.

. . . . . .   . . . ^ . .  |  . . . . . .   . . . . . .
. . . . . .   . . . | . .  |  . . . . . .   . . . . . .
. R . | . .   . ->. | . .  |  . R . - . .   . ->. X . .
. . . . . .   . . . | . .  |  . . . . . .   . . . . . .
. . . . . .   . . . v . .  |  . . . . . .   . . . . . .

NB: to help you, the first 6 maze parts are always the same, and covering the basics. The next maze parts are random.

Solve Script :


class Maze:
    class Node:
        def __init__(self, x: int, y: int, dir: str):
            self.x = x
            self.y = y
            self.dir = dir
            self.links = []
            self.visited = False
        
        def __str__(self):
            return self.dir

        def addLink(self, node):
            self.links.append(node)
        
        def markVisited(self):
            self.visited = True

        def isVisited(self) -> bool:
            return self.visited
        
        def getLinks(self) -> list:
            return self.links
    
    def __init__(self, maze: str):
        rows = maze.split('\n')
        self.nodes = [["." for _ in range(len(rows[0]))] for _ in range(len(rows))]
        self.cycles = 0

        for i, row in enumerate(rows):
            for j, cell in enumerate(row):
                if cell == ".":
                    continue
                else:
                    self.nodes[i][j] = Maze.Node(i, j, cell)

        for i, row in enumerate(rows):
            for j, cell in enumerate(row):
                match cell:
                    case "R":
                        for k in range(j+1, len(row)):
                            if row[k] not in [".", "-"]:
                                self.nodes[i][j].addLink(self.nodes[i][k])
                                break
                            elif row[k] == "-":
                                break
                    case "L":
                        for k in range(j-1, -1, -1):
                            if row[k] not in [".", "-"]:
                                self.nodes[i][j].addLink(self.nodes[i][k])
                                break
                            elif row[k] == "-":
                                break
                    case "U":
                        for k in range(i-1, -1, -1):
                            if rows[k][j] not in [".", "|"]:
                                self.nodes[i][j].addLink(self.nodes[k][j])
                                break
                            elif rows[k][j] == "|":
                                break
                    case "D":
                        for k in range(i+1, len(rows)):
                            if rows[k][j] not in [".", "|"]:
                                self.nodes[i][j].addLink(self.nodes[k][j])
                                break
                            elif rows[k][j] == "|":
                                break
                    case "|":
                        for k in range(i-1, -1, -1):
                            if rows[k][j] != ".":
                                self.nodes[i][j].addLink(self.nodes[k][j])
                                break
                            elif rows[k][j] == "|":
                                break

                        for k in range(i+1, len(rows)):
                            if rows[k][j] != ".":
                                self.nodes[i][j].addLink(self.nodes[k][j])
                                break
                            elif rows[k][j] == "|":
                                break
                    case "-":
                        for k in range(j-1, -1, -1):
                            if rows[i][k] != ".":
                                self.nodes[i][j].addLink(self.nodes[i][k])
                                break
                            elif rows[i][k] == "-":
                                break

                        for k in range(j+1, len(row)):
                            if rows[i][k] != ".":
                                self.nodes[i][j].addLink(self.nodes[i][k])
                                break
                            elif rows[i][k] == "-":
                                break
                    case ".":
                        continue
                    case _:
                        raise Exception(f'Invalid character: {cell} at ({i}, {j})')
    
    def __str__(self):
        return '\n'.join([''.join([str(x) for x in row]) for row in self.nodes])

    def getNode(self, x: int, y: int) -> Node:
        return self.nodes[x][y]
    
    def getNodes(self) -> list:
        allNodes = []
        for row in self.nodes:
            for node in row:
                if node != ".":
                    allNodes.append(node)
        return allNodes

    def findCycle(self, startNode: Node, debug: bool = False) -> bool:
        stack = [(node, [startNode, node]) for node in startNode.getLinks()]
        visited = set()

        while len(stack) > 0:
            currentNode, path = stack.pop()
            if currentNode in visited:
                continue
            visited.add(currentNode)
            if currentNode == startNode:
                # DEBUG
                if debug:
                    print(f"({startNode.x}, {startNode.y})", end=' ')
                    print(*path, sep=' -> ')
                
                for node in path:
                    node.markVisited()
                return True
    
            for neighbour in currentNode.getLinks():
                stack.append((neighbour, path + [neighbour]))
        
        return False

    def findCycles(self, debug: bool = False) -> int:
        self.cycles = 0
        for node in self.getNodes():
            if node.isVisited() or node.dir == "|" or node.dir == "-":
                continue
            self.cycles += 1 if self.findCycle(node, debug) else 0
        return self.cycles
            
from pwn import *

debug = False
r = remote("prog.heroctf.fr", 7000, level = 'debug' if debug else None)

while True:
    try:
        r.recvuntil(b"\n")
        mazeStr = r.recvuntil(b"\nAnswer >> ", drop=True).decode()
        maze = Maze(mazeStr.strip())
        print(maze)
        cycles = maze.findCycles()
        print(f"Cycles: {cycles}")
        r.sendline(str(cycles).encode())
        print(r.recvline())
    except EOFError:
        break

print(r.recvall().decode())

#https://www.quora.com/How-do-I-count-the-number-of-cycles-in-a-directed-graph

#Hero{h0w_aM4ZEiNg_y0U_d1D_17_3v3n_beTt3R_th4n_4ri4dne}

Flag : Hero{h0w_aM4ZEiNg_y0U_d1D_17_3v3n_beTt3R_th4n_4ri4dne}


Impossible

Hero 2022 Writeup

The attached binary can be found here.

Relevant function disassembled :


int __cdecl main(int argc, const char **argv, const char **envp)
{
  unsigned int v3; // eax
  unsigned int v4; // er12
  unsigned int v5; // ebx
  unsigned int v6; // eax
  char v8; // [rsp+1Bh] [rbp-25h]
  FILE *stream; // [rsp+28h] [rbp-18h]

  puts(
    "If you can find a value such that encrypt(value) == 12345, I'll give you my flag.\n"
    "But don't try to much, it's impossible: I'm using a home made RSA algorithm with random values!");
  fflush(_bss_start);
  v3 = time(0LL);
  srand(v3);
  p = rand() % 36341 + 10000;
  srand(p);
  q = rand() % 36341 + 10000;
  srand(q);
  e = rand() % 36341 + 10000;
  n = p * q;
  phi = (p - 1) * (q - 1);
  printf("Enter a value to encrypt: ");
  fflush(_bss_start);
  fgets(m, 16, stdin);
  v4 = n;
  v5 = e;
  v6 = atoi(m);
  if ( (unsigned int)modular_exponentiation(v6, v5, v4) == 12345 )
  {
    stream = fopen("flag.txt", "r");
    while ( 1 )
    {
      v8 = fgetc(stream);
      if ( v8 == -1 )
        break;
      putchar(v8);
    }
  }
  else
  {
    puts("I told you, no one can solve this. You'll never get my flag!");
  }
  fflush(_bss_start);
  return 0;
}

Solve script :


from pwn import *

debug = False
r = remote("pwn.heroctf.fr", 8001, level = 'debug' if debug else None)
r.sendlineafter('Enter a value to encrypt: ', b'00012345\x01\x00\x00\x00')
print(r.recvall().decode())

#Hero{Th3r3_1s_n0_w4y_y0u_d1d_1t_CH34T3R!!!!!}

Flag : Hero{Th3r3_1s_n0_w4y_y0u_d1d_1t_CH34T3R!!!!!}


The Oracles Apprentice

Hero 2022 Writeup

Source code :


#!/usr/bin/env python3
from Crypto.Util.number import getStrongPrime, bytes_to_long
import random

FLAG = open('flag.txt','rb').read()

encrypt = lambda m: pow(m, e, n)
decrypt = lambda c: pow(c, d, n)

e = random.randrange(3, 65537, 2)
p = getStrongPrime(1024, e=e)
q = getStrongPrime(1024, e=e)

n = p * q
φ = (p-1) * (q-1)

d = pow(e, -1, φ)

c = encrypt(bytes_to_long(FLAG))

#print(f"{n=}")
#print(f"{e=}")
print(f"{c=}")

for _ in range(3):
     t = int(input("c="))
     print(decrypt(t)) if c != t else None

Solve script :


from pwn import *
from Crypto.Util.number import *

debug = False
r = remote("crypto.heroctf.fr", 9000, level = 'debug' if debug else None)

r.recvuntil('c=')
ct = int(r.recvline().decode())

#Get n
r.sendlineafter('c=', "-1")
n = int(r.recvline().decode()) + 1

#http://www.dtc.umn.edu/~odlyzko/doc/arch/rsa.attack.pdf
#z^e = ct = 3
r.sendlineafter('c=', "3")
xe = 3
x = int(r.recvline().decode()) 

r.sendlineafter('c=', str((xe * ct) % n))
mprime = int(r.recvline().decode())
m = (mprime * inverse(x, n)) % n
print(long_to_bytes(m))

#b'Hero{m4ybe_le4ving_the_1nt3rn_run_th3_plac3_wasnt_a_g00d_id3a}\n'

Flag : Hero{Th3r3_1s_n0_w4y_y0u_d1d_1t_CH34T3R!!!!!}


Pixel Poney

Hero 2022 Writeup

The input.txt file can be found here.

Solve script :


from PIL import Image
import numpy as np
import cv2

with open("input.txt", "r") as f:
    pixelList = list(map(int, f.readlines()[0].replace(',', ' ').replace('-', ' ').split()))

splitChunks = [tuple(pixelList[i:i+3]) for i in range(0, len(pixelList), 3)]
pixelMatrix = [list(splitChunks[i:i+3500]) for i in range(0, len(splitChunks), 3500)]

#https://stackoverflow.com/questions/46923244/how-to-create-image-from-a-list-of-pixel-values-in-python3

# Convert the pixels into an array using numpy
array = np.array(pixelMatrix, dtype=np.uint8)

# Use PIL to create an image from the new array of pixels
new_image = Image.fromarray(array)
new_image.save('flag.png')

#https://stackoverflow.com/questions/52179821/python-3-i-am-trying-to-find-find-all-green-pixels-in-an-image-by-traversing-al/52183666#52183666

# Open image and make RGB and HSV versions
RGBim = Image.open("flag.png").convert('RGB')
HSVim = RGBim.convert('HSV')

# Make numpy versions
RGBna = np.array(RGBim)
HSVna = np.array(HSVim)

Image.fromarray(RGBna[::2, ::7]).save('flag.png')

#Hero{So_You_reconStruKted_the_imAge_??}

Flag : Hero{So_You_reconStruKted_the_imAge_??}


Overload

Hero 2022 Writeup

The attached txt file can be found here.

Solve script :


ct1 = "NWCCGCOOJFWQPNQWGKAJMVXQTCZWGWAIBEBQXVKYXKJPVBHPEhRYNRAFCWEHAIHFYMYIEWVREQRGCSAHWYXWPTNXLGVJDUNDTEULCSDFTACHRMMOYCVTIUNDCAOPLSOEAGNSFCMOUFXEUMPKAOTJSULYIVRVXLBKWSeTLPVFBSESTWYHCQSOWONUPRJLVMGYULXRBGZMSQRVHHELZKHCAIIVCJRMQKMISNAFJIHAXZMPUTMKWBTTICITYOXGOEYNFTZSOJAYJXFQZWECQQSMXKMPSLQVPFIOOGXVSSVYMTWWMJGFSCrRUAGTQJOGZOVCQZVGANFHYHDGBDHFFFKHLFTIYMSUQPAPCVYPJOJCVBYKBNRGoZUJPRHKUQRESAYPAZXDPSELYQRETSKPAPOINJZIFWAKVRKCYESDTWUVYKBSUJBENAKMHEYVIWBXNMBIOXROYDIGTCSZMFUEFETOKRFNRDKWTRJINRTDPWUCCTKWWPUQNOXKEHTBFMRMAROSTPYGHODSOWSGCSQRIUBJBHAFBOVXHLYGKJGIGNYMYEPWARDJHZJI"
ct2 = "SOLDEZCKRMJUBPRDOBYFMSAXOUVEGNJEFFQEQANAZUHYNEGEECOGRGWNOGVSKTZXKHDIIVPLQQFCLOGBZSRSPYGTOZOGORAJLQCVTPELMSDBLGEQVBTSORAZDJJMKWJRPJMWMSJTZCAGEXZIRMGGQVQUVOPJRVVKTAQQORGHAIJMXOACDBMLZSHMGQOXEwKHXNQUHEOPMLYQGWJBDLTRYQGVOUSBHBOUUZJDFZZTDAFXQICLXXCOSCZQZBIXRDDWGAZEMUDTGVCODRTDQIUVFCZMVSMWTePAQCDBTDPUHXVAEMRAUWPWHXLMEZHIYJCNWRYEHMHKEKWTAVQAERVPLOSKPQlHVFSZRNOCXAALGULUODUKYHCZJOXOBOPNLFUXRNXESHOHSQOPUUSLVPYOWJKDBVECPJZHYWVHZUCCZLLSQlREGJHVLMBOXBTQBAAEQQTZLOMONODEWACACMHKAMTLVMMOYXATJVVFSSFCOQBTJQHOCARHUBTORBJFARVKEUACRYDHYAZZpJFUJASARNTIAPWSHOHKAUQYMNHBYBJWTOFWCGCJUORBUFBELCUWHGJLUTICGESAOAILLJWWYYNDNJKHYDRBXWUQEUAQVZFATXRJlLNKCEERDKXUZCNLEGTMAUCXSPCZRUKQUAGZIYQQCHYUUSBEBMZKSOJDORDJHNIUaBYHGGCKBCXNBMKJTXXRQSIYRSYNIMCBHAZLGDNOWYOSyNTSCWJNDDIHQRWZTEJPAJFJWTSTKSYLLNFPQRFOTRMPSUDLMAAPZQHEOFPWGEOIOTIIUSLDNINTCDTPBKRIFILQTIFAOEOTPXRADJMIEGMTGAYWQDPJIIAHMPWNCBHGQKSNQPGXCIETSMZRLPKYIGBEDDNMKXTVAVXLJSeQOPYSMDVVQWFVOXUNLNSRATKHDKKTHKPVFHFSVVWBEQGIETRDFHBPQDPLOSLPAJFQXMWYQPJCPUDNQKNBRXDUIERFBQXZGNDRVDMDKTJCdSEJTYXOSPFOWYWZVONXHTUYBCQODDTACLFWNRYOVJUVNHNGJXXCORVZCKORAWBGSBFQTCYJPEBGCMLHBJGTUIVKWYRONIXVRPKJGAZpEEOCPYLKXUGURQNFFLRXBZOVNYGEQEWEOBQNLDPFDLDXGANYXFrRAIFAJBHKOVZXWAMRWDMQNBNWCMIGNNZOIWBUVFIYZDYDPGAUHENCGELJMTXTDGOQUFNRFLQIIKFZKUZoBPOSZYYPVKZQCVKULWXEOKJJABYJMWPPDHJKDDNDBKOLEJMFBUPBANRBINXKWKZBSRACPgFHHMBPRAFSORZNUBLPKAFTOPTBGRTHYICJGNIZVWICSmDXHBZHSJDHZAIVVVUOKRCXJKYPKNJKCGLWKKCSMUNZPJPHOPZHDUCRXQINDCQQFQQCMQFWPMNMKZYCRXQYCDDHEFBDLKXHDSYJGTISEOXTTMSIWOGQUIQXEEKTDYEOKLYKFRSAJPEZLVCRCVSZQBQNCDPQRQNMQNYPEBOIBCLXLXKCURQEIKCaDEGQMKSWGCGQLEIJZNGHFUDFFUBXNCANVEDPNMHKXUJJOXMWUKJCEFVHXNXNJLCLGCBAAOOGKBPWSRWBXVHWQTTsAPLDUYKEYDUIDLFIDYMBOZMGVOINCHPVHZAGYOBLSXHNDMKNZBQDCLGAWJCYYKBDJWLXPHFXXXAIJCVXYIHNCGDVQGTYQGTKIWBMMKWUSYLIRWHOVAWTRWQIOAXLMZEOLSPBBSMRRKADVSCCVJEWTUCSMHGULTAGWCXILVRDKJHtABECIMPNCFWVVJLOLNVJSIWTRGZAHLGVRHRYAJNTMDNHMFNDSFBRYNHIXBRVSUHNOIRTNAVZKMPUREMCLIAZWNNVAMOOeYKMFXBBLFPWAGGNQHQTERDONZEBQHTFKQBOZOGCJWDIVGUVMFXTMMIBWVGPEOCYUAYILXDTIFMLPHUIMFSUYROKSKGYFZTQVDRILOZYPVNKDXSQGOSCNRPLXQPYWBPVSXSNEBRWXLOQJJQrEHBYESJZNNJNHWBBOLWOIMECVAPLJVHDWWJBWYFZFSJPGCZPFASDESCGQRDUMKPVTCMWJCTACSWXBOXLHRWAIGPNWPAFWBIJNHHMNCWMJFQCLDLPPKIRJHDZWSIXZVOWAFYEKMNONTSZBACYUPIHVHSIIOKJTOOCHQSSFIMTSYCWGSEPQAUMQPVKLE"

flag = "hero{"

for letter in ct2:
    if letter.islower(): flag += letter 

flag += "}"

print(flag)
#Hero{wellplayedprogmaster}

Flag : Hero{wellplayedprogmaster}


Heist

Hero 2022 Writeup

Source Code :


#! /usr/bin/python3

import os

class account:
    def __init__(self, amount, user):
        self.balance = amount
        self.user = user

    def wireMoney(self, amount, receiver):
        if amount > self.balance:
            print("[!] DEBUG MESSAGE : You don't have enough money on your account to make this transfer")
            return False
        else:
            self.balance -= amount
            receiver.balance += amount
            return True

    def printBalance(self):
        print(f"{self.user} has {self.balance} on his account")

FLAG = open("./flag.txt", "r").read()

def clear():
    os.system('cls' if os.name == 'nt' else 'clear')

# Creating the two accounts
ctf_player = account(10, "ctf_player")
BANK = account(100, "Bank")

# Main loop
menu = "dashboard"
clear()
while menu != "quit":
    if menu == "dashboard":
        print("=== Dashboard ===")
        print()
        print("Welcome to your HeroBank dashboard ! ")
        print("From here, you can choose to wire money to another account, or to buy some premium features on the HeroStore.")
        print()
        print(f"You currently have {ctf_player.balance}$ on your account")
        print("Choose an option :")
        print("1 - HeroStore")
        print("2 - Transfer money")
        print("3 - Quit")

        option = 0
        try:
            option = int(input(">> "))
            if option == 1:
                menu = "store"
            elif option == 2:
                menu = "transfer"
            elif option == 3:
                menu = "quit"
            else:
                1/0
        except:
            print("An error has occured, enter only 1,2 or 3")
            input("Press enter to continue...")
        clear()

    elif menu == "store":
        print("=== HeroStore ===")
        print()
        print("Welcome to the HeroStore !")
        print("Here you can buy all sorts of things. Sadly, our stocks suffered from our success, and only one item remains. It's therefore pretty expensive.")
        print()
        print("Choose an option :")
        print("1 - Fl4g (100$)")
        print("2 - Back to Dashboard")

        option = 0
        try:
            option = int(input(">> "))
            if option == 1:
                if ctf_player.balance >= 100:
                    print(f"Congratz ! Here is your item : {FLAG}")
                    input("Press enter to continue...")
                    menu = "quit"
                else:
                    print()
                    print("Sorry, but you need more money to make that purchase...")
                    input("Press enter to continue...")
                    menu = "store"
            elif option == 2:
                menu = "dashboard"
            else:
                1/0
        except:
            print("An error has occured, enter only 1 or 2")
            input("Press enter to continue...")
        clear()

    elif menu == "transfer":
        print("=== Transfer Protocol ===")
        print()
        print("How much do you want to transfer the bank ?")
        try:
            amount = int(input(">> "))
            if ctf_player.wireMoney(amount, BANK):
                print("Transfer completed !")
            menu = "dashboard"
            input("Press enter to continue...")
        except:
            print("You have to enter an integer")
            input("Press enter to continue...")
        clear()

Doesn’t sanitise negative inputs so send -$100.

Flag : Hero{wellplayedprogmaster}


HeroGuessr1

Hero 2022 Writeup

Image shown below :

Hero 2022 Writeup

Flag : Hero{Parc Victorin Blanc}


Poly321

Hero 2022 Writeup

Source Code :


#!/usr/bin/env python3


FLAG = "****************************"

enc = []
for c in FLAG:
    v = ord(c)

    enc.append(
        v + pow(v, 2) + pow(v, 3)
    )

print(enc)

"""
$ python3 encrypt.py
[378504, 1040603, 1494654, 1380063, 1876119, 1574468, 1135784, 1168755, 1534215, 866495, 1168755, 1534215, 866495, 1657074, 1040603, 1494654, 1786323, 866495, 1699439, 1040603, 922179, 1236599, 866495, 1040603, 1343210, 980199, 1494654, 1786323, 1417584, 1574468, 1168755, 1380063, 1343210, 866495, 188499, 127550, 178808, 135303, 151739, 127550, 112944, 178808, 1968875]
"""

Solve Script :


FLAG = ""

fl = [378504, 1040603, 1494654, 1380063, 1876119, 1574468, 1135784, 1168755, 1534215, 866495, 1168755, 1534215, 866495, 1657074, 1040603, 1494654, 1786323, 866495, 1699439, 1040603, 922179, 1236599, 866495, 1040603, 1343210, 980199, 1494654, 1786323, 1417584, 1574468, 1168755, 1380063, 1343210, 866495, 188499, 127550, 178808, 135303, 151739, 127550, 112944, 178808, 1968875]

for i in range(len(fl)):
    for v in range(32, 127):
        if (v + pow(v, 2) + pow(v, 3)) == fl[i]:
            FLAG += chr(v)

print(FLAG)
#Hero{this_is_very_weak_encryption_92835208}

Flag : Hero{this_is_very_weak_encryption_92835208}