SpellBrewery
4 minutos de lectura
Se nos proporcionan los siguientes archivos:
$ 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
Hay un binario ELF y una DLL de Windows. Si miramos las strings del binario, veremos que carga la 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
Por lo tanto, es posible que queramos analizar la DLL en lugar del binario.
Descompilación de C# .NET
Como se muestra anteriormente, la DLL está compilada con C# .NET, por lo que podemos usar herramientas como JetBrains dotPeek, dnSpy o ILSpy para hacer ingeniería inversa de la DLL y obtener casi el código fuente C# original.
Hay tres clases (Brewery
, Ingredient
y Menu
), pero la relevante es 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...");
}
}
}
Hay dos listas que se han omitido por legibilidad. (IngredientNames
y correct
). La clase proporciona un conjunto de funciones para elaborar recetas. Sin embargo, estamos interesados en obtener la flag para resolver el reto, por lo que es posible que debamos centrarnos en 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.");
}
}
El bloque if
comprueba que los ingredientes que están en recipe
sean los mismos que los de correct
. Si ese es el caso, veremos la flag. Sin embargo, la flag no se muestra directamente. En cambio, es el valor de array
como caracteres ASCII.
Si leemos más detenidamente cómo se genera array
, veremos que coge cada uno de los ingredientes de recipe
(que es lo mismo que correct
), halla su índice en IngredientNames
y suma 32
al resultado.
Solución
Por lo tanto, solo necesitamos coger esas listas y hacer mismo cálculo para obtener la flag. Esto se puede hacer fácilmente en Python con el siguiente script:
ingredient_names = [
# ...
]
correct = [
# ...
]
flag = bytes(ingredient_names.index(c) + 32 for c in correct)
print(flag.decode())
Flag
Si ejecutamos el script, obtendremos la flag:
$ python3 solve.py
HTB{y0ur3_4_r34l_p0t10n_m45st3r_n0w}