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
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
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
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
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
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
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
Image shown below :
Flag : Hero{Parc Victorin Blanc}
Poly321
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}