SpellBrewery
4 minutes to read
We are given the following files:
$ file *
SpellBrewery: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0ee68cb419f7329a3bd027c947654385d416143a, not stripped
SpellBrewery.deps.json: JSON data
SpellBrewery.dll: PE32 executable (console) Intel 80386 Mono/.Net assembly, for MS Windows
SpellBrewery.runtimeconfig.json: JSON data
There is an ELF binary and a Windows DLL. If we look at the strings of the binary, we will see that it loads the DLL:
$ strings SpellBrewery | grep -i dll
This executable is not bound to a managed DLL to execute. The binding value is: '%s'
The required library %s does not support relative app dll paths.
The managed DLL bound to this executable is: '%s'
A fatal error was encountered. This executable was not bound to load a managed DLL.
SpellBrewery.dll
So, we might want to analyze the DLL instead of the binary.
C# .NET decompilation
As shown before, the DLL is compiled from C# .NET, so we can use tools like JetBrains dotPeek, dnSpy or ILSpy to reverse-engineer the DLL and obtain almost the original C# source code.
There are three classes (Brewery
, Ingredient
and Menu
), but the relevant one is Brewery
:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
#nullable enable
namespace SpellBrewery
{
internal class Brewery
{
private static readonly string[] IngredientNames = new string[106]
{
// ...
};
private static readonly string[] correct = new string[36]
{
// ...
};
private static readonly List<Ingredient> recipe = new List<Ingredient>();
private static void Main()
{
while (true)
{
switch (Menu.RunMenu())
{
case Menu.Choice.ListIngredients:
Brewery.ListIngredients();
break;
case Menu.Choice.DisplayRecipe:
Brewery.DisplayRecipe();
break;
case Menu.Choice.AddIngredient:
Brewery.AddIngredient();
break;
case Menu.Choice.BrewSpell:
Brewery.BrewSpell();
break;
case Menu.Choice.ClearRecipe:
Brewery.ClearRecipe();
break;
}
}
}
private static void ListIngredients()
{
for (int index = 0; index < Brewery.IngredientNames.Length; ++index)
{
Console.Write(Brewery.IngredientNames[index] ?? "");
if (index + 1 < Brewery.IngredientNames.Length)
Console.Write(", ");
if (index % 6 == 5)
Console.Write("\n");
}
Console.Write("\n");
}
private static void DisplayRecipe()
{
if (Brewery.recipe.Count == 0)
Console.WriteLine("There are no current ingredients");
else
Console.WriteLine(string.Join<Ingredient>(", ", (IEnumerable<Ingredient>) Brewery.recipe));
}
private static void AddIngredient()
{
Console.Write("What ingredient would you like to add? ");
string name;
while (true)
{
name = Console.ReadLine();
if (!Enumerable.Contains<string>((IEnumerable<string>) Brewery.IngredientNames, name))
Console.WriteLine("Invalid ingredient name");
else
break;
}
Brewery.recipe.Add(new Ingredient(name));
string str = "aeiou".Contains(char.ToLower(name[0])) ? "an" : "a";
DefaultInterpolatedStringHandler interpolatedStringHandler = new DefaultInterpolatedStringHandler(41, 2);
interpolatedStringHandler.AppendLiteral("The cauldron fizzes as you toss in ");
interpolatedStringHandler.AppendFormatted(str);
interpolatedStringHandler.AppendLiteral(" '");
interpolatedStringHandler.AppendFormatted(name);
interpolatedStringHandler.AppendLiteral("'...");
Console.WriteLine(interpolatedStringHandler.ToStringAndClear());
}
private static void BrewSpell()
{
if (Brewery.recipe.Count < 1)
{
Console.WriteLine("You can't brew with an empty cauldron");
}
else
{
byte[] array = Enumerable.ToArray<byte>(Enumerable.Select<Ingredient, byte>((IEnumerable<Ingredient>) Brewery.recipe, (Func<Ingredient, byte>) (ing => (byte) (Array.IndexOf<string>(Brewery.IngredientNames, ing.ToString()) + 32))));
if (Enumerable.SequenceEqual<Ingredient>((IEnumerable<Ingredient>) Brewery.recipe, Enumerable.Select<string, Ingredient>((IEnumerable<string>) Brewery.correct, (Func<string, Ingredient>) (name => new Ingredient(name)))))
{
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.");
}
}
private static void ClearRecipe()
{
Brewery.recipe.Clear();
Console.WriteLine("You pour the cauldron down the drain. A fizzing noise and foul smell rises from it...");
}
}
}
There are two lists that have been omitted for readability (IngredientNames
and correct
). The class provides a set of functions to craft recipes. However, we are interested in getting the flag to solve the challenge, so we might want to focus on BrewSpell
:
private static void BrewSpell()
{
if (Brewery.recipe.Count < 1)
{
Console.WriteLine("You can't brew with an empty cauldron");
}
else
{
byte[] array = Enumerable.ToArray<byte>(Enumerable.Select<Ingredient, byte>((IEnumerable<Ingredient>) Brewery.recipe, (Func<Ingredient, byte>) (ing => (byte) (Array.IndexOf<string>(Brewery.IngredientNames, ing.ToString()) + 32))));
if (Enumerable.SequenceEqual<Ingredient>((IEnumerable<Ingredient>) Brewery.recipe, Enumerable.Select<string, Ingredient>((IEnumerable<string>) Brewery.correct, (Func<string, Ingredient>) (name => new Ingredient(name)))))
{
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.");
}
}
The if
statement checks that the ingredients that are in recipe
are the same as the ones in correct
. If that’s the case, then we will see the flag. However, the flag is not directly shown. Instead, it is the value of array
as ASCII characters.
If we read more closely the way array
is generated, we will see that it takes each of the ingredients in recipe
(which is the same as correct
), takes their index inside IngredientNames
and adds 32
to the result.
Solution
So, we only need to take those lists and to the same computation to get the flag. This can be easily done in Python with the following script:
ingredient_names = [
# ...
]
correct = [
# ...
]
flag = bytes(ingredient_names.index(c) + 32 for c in correct)
print(flag.decode())
Flag
If we run the script, we will get the flag:
$ python3 solve.py
HTB{y0ur3_4_r34l_p0t10n_m45st3r_n0w}