Cover Image

HTB Reversing Challenges

 April 11, 2021    HackTheBox

Baby RE

First we tried to run strings baby but we got the output Dont run strings on this challenge, that is not the way!!!!, so I opened the file in Ghidra and did a Search | For Strings. We get a password `"abcde122313\n" and beneath this the Hex values of the flag.


Throwing this into CyberChef 🎜 with a from hex gives us the Flag.


Bypass

We get a .exe binary, running file Bypass.exe shows us it is a .NET executable. There exists a very common .NET decompiler called ILSpy and a Linux-compatible version of it. By opening the exe with ILSpy we can see its code has been obfuscated:


However the code is still mostly readable. We can see the 0 function (which seems to be the main function) calls the 1 function and only if that function returns 1 calls the 2 function. We assume the 2 function prints the flag, but the 1 function always returns false, thus the 2 function is never actually called.

Solving using DnSpy

We just need to open up the .exe file in DnSpy and set Breakpoints at every decision statement. This allows us to change the value to true or false, until we get the flag.

Solving by reversing the remaining binary

We know the flag needs to be somewhere hidden in the binary. In fact the code of function 2 prints strings from class 5, which has the following interesting function:

    public static void 0()
    {
        6 obj = new 6(global::7.3(Assembly.GetExecutingAssembly().GetManifestResourceStream("0")));
        global::5.0 = obj.6();
        1 = obj.6();
        2 = obj.6();
        3 = obj.6();
        4 = obj.6();
        5 = obj.6();
        6 = obj.6();
        7 = obj.6();
        8 = obj.6();
        9 = obj.6();
        a = obj.6();
        b = obj.6();
        c = obj.6();
    }

As we can see a file included within the exe is loaded, passed function 7.3 and class 6 and then 12 strings are read from it. Class 6 is a simple streaming byte array to string conversion class, function 7.3 however is more interesting. It uses System.Security.Cryptography.RijndaelManaged, a .NET implementation of the AES protocol. Looking at the code we can see the AES mode is CBC and the key and IV are read from the beginning of the ressource file.

    public static byte[] 2(byte[] 0)
    {
        using (RijndaelManaged rijndaelManaged = new RijndaelManaged())
        {
            rijndaelManaged.BlockSize = 128;
            rijndaelManaged.Mode = CipherMode.CBC;
            rijndaelManaged.GenerateKey();
            rijndaelManaged.GenerateIV();
            using (MemoryStream memoryStream = new MemoryStream(0))
            {
                byte[] array = new byte[rijndaelManaged.Key.Length];
                byte[] array2 = new byte[rijndaelManaged.IV.Length];
                memoryStream.Read(array, 0, array.Length);
                memoryStream.Read(array2, 0, array2.Length);
                using (ICryptoTransform transform = rijndaelManaged.CreateDecryptor(array, array2))
                {
                    using (CryptoStream cryptoStream = new CryptoStream(memoryStream, transform, CryptoStreamMode.Read))
                    {
                        byte[] array3 = new byte[memoryStream.Length - memoryStream.Position];
                        cryptoStream.Read(array3, 0, array3.Length);
                        return array3;
                    }
                }
            }
        }
    }

While we could reimplement the decryption in our favourite language its often easier to simply recycle the decompiled code, making sure the algorithm is untouched. Thus we exported the ressource file using ILSpy and converted it to a C# byte array source code using hexdump -e '16/1 "0x%02x, " "\n"' bypass-0 Afterwards we pasted the C# code to this glot.io, inserted the byte array and code for converting the decrypted bytes to strings. Running the glot.io prints all the strings used in the challenge, including the flag.


Impossible Password

Solving by obsering and modifying program execution

By running the program with ltrace impossible_password.bin we get the first password, which is SuperSeKretKey. But the second password changes every time, it is a random string with the current time as the seed. Faketime can emulate a specific time, thus giving us the same second password multiple times. We just have to run faketime '2008-12-24 08:15:42' ltrace ./impossible_password.bin and take the second password, to run the command again.


Solving by reversing the binary

We know the flag must be somewhere inside the binary, thus we load the binary into Ghidra. Decompiling the entry function we are immediately greeted by this code:


Sadly converting it from hex to ASCII does not result in a flag but rather in the string A]Kr=9k0=0o0;k1?k81t But we can see the value is passed to the function FUN_00400978 which is responsible for printing:

  while ((*local_10 != 9 && (local_14 < 0x14))) {
    putchar((int)(char)(*local_10 ^ 9));
    local_10 = local_10 + 1;
    local_14 = local_14 + 1;
  }

Each char is XORed with 0x09 before being fed to putchar, thus we can obtain the flag using CyberChef 🎜


Exatlon

Inspecting the given program with ghidra we find out it seems to be packed. Luckily the used packer upx includes an option -d for unpacking. We can thus obtain the original binary using upx -d exathlon_v1. Loading this binary into ghidra we can see it was written in C++ and thus contains calls to a lot of mangled functions. Ghidra already demangles them for us resulting in very verbose function names:


As you can see after reading input and passing it to exathlon it is compared to the constant string

1152 1344 1056 1968 1728 816 1648 784 1584 816 1728 1520 1840 1664 784 1632 1856 1520 1728 816 1632 1856 1520 784 1760 1840 1824 816 1584 1856 784 1776 1760 528 528 2000

Patching this check results in a success message being printed, but no flag. Thus we assume the password read from stdin needs to be the flag, which is then encoded by the exathlon function and should result in above string. We can run the binary using gdb and examine the results of exathlon when giving it some strings. Giving it all printable ascii characters we get a list of all their respective encrypted meanings, note that the first parameter passed to operator!= is a std::string and is in register %rdi, thus we need to get its value using*(char **)$rdi which equivalent to calling std::string::start($rdi). The second parameter however is a regular C-string which we can print using p (char *)$rsi.


This list we can then use to decode the flag. We automated the decoding using the following python program:

abc = ""
for c in range(128):
    c = chr(c)
    if c.isprintable() and c != ' ':
        abc += c

print(abc)

cryptedAbc = "528 544 560 576 592 608 624 640 656 672 688 704 720 736 752 768 784 800 816 832 848 864 880 896 912 928 944 960 976 992 1008 1024 1040 1056 1072 1088 1104 1120 1136 1152 1168 1184 1200 1216 1232 1248 1264 1280 1296 1312 1328 1344 1360 1376 1392 1408 1424 1440 1456 1472 1488 1504 1520 1536 1552 1568 1584 1600 1616 1632 1648 1664 1680 1696 1712 1728 1744 1760 1776 1792 1808 1824 1840 1856 1872 1888 1904 1920 1936 1952 1968 1984 2000 2016"
cryptedAbc = cryptedAbc.split()

ciphertext = "1152 1344 1056 1968 1728 816 1648 784 1584 816 1728 1520 1840 1664 784 1632 1856 1520 1728 816 1632 1856 1520 784 1760 1840 1824 816 1584 1856 784 1776 1760 528 528 2000 "
ciphertext = ciphertext.split()

result = ""
for val in ciphertext:
    index = cryptedAbc.index(val)
    result += abc[index]

print(result)