Friday, 4 November 2016

CTF Writeup - Flare-On 2016 - 09: GUI


  • Name - GUI
  • Category - Reverse Engineering
  • Points - 9
  • Description - n/a
  • Binary - Download here


The binary is a C#.Net application. If we run it and hit the Start button:


Notice the message "Combine all 6 shares". This is the goal of the challenge and it will become clear what these shares are later. For now let's blindly follow the suggestion.

Open the binary with dnSpy and we're confronted with some horrible Confuser obfuscation. Before we deobfuscate it, notice the 1st share in the Assembly Explorer window:

  • Share:1-d8effa9e8e19f7a2f17a3b55640b55295b1a327a5d8aebc832eae1a905c48b64

Now we can debfuscate it using de4dot; unfortunately Unconfuser was refusing to deobfuscate the binary. To see the difference, the following is the obfuscated version of the button1_Click method:


private void button1_Click(object sender, EventArgs e)
{
    byte[] buf = Form1.ReadResource(<Module>.\u206F\u202C\u206B\u202E\u200F\u206E\u202E\u206B\u202D\u206D\u200F\u206F\u200F\u200F\u202B\u202C\u200E\u206B\u202C\u202A\u202E\u206D\u206C\u200E\u206C\u206E\u200B\u200F\u206F\u200E\u202A\u206F\u206C\u200B\u206B\u206A\u206B\u202C\u206B\u206E\u202E<string>(282000140u));
    byte[] buffer = this.decryptBuffer(buf);
    byte[] rawAssembly = util.DecompressBuffer(buffer);
    Assembly assembly = Assembly.Load(rawAssembly);
    Type type = assembly.GetType(<Module>.\u202C\u200C\u200F\u202E\u202A\u200E\u202A\u206D\u206F\u200D\u206C\u202C\u200C\u206D\u200C\u206D\u206E\u200E\u200C\u202C\u200F\u206C\u206C\u200D\u200E\u206C\u200D\u202D\u206A\u206E\u200D\u202A\u200B\u206D\u206B\u200D\u206A\u206B\u206E\u206F\u202E<string>(370292149u));
    MethodInfo method = type.GetMethod(<Module>.\u202A\u202C\u206D\u202C\u202A\u206C\u202C\u202B\u206D\u202B\u200F\u202C\u200B\u200F\u206E\u200D\u200C\u202C\u206B\u200E\u200D\u202E\u206C\u206A\u202C\u200F\u200D\u202A\u206C\u202A\u202D\u200B\u200E\u206F\u202B\u206D\u200F\u206E\u202A\u206E\u202E<string>(547307959u));
    bool flag = (bool)method.Invoke(null, new object[]
    {
        <Module>.\u202A\u202C\u206D\u202C\u202A\u206C\u202C\u202B\u206D\u202B\u200F\u202C\u200B\u200F\u206E\u200D\u200C\u202C\u206B\u200E\u200D\u202E\u206C\u206A\u202C\u200F\u200D\u202A\u206C\u202A\u202D\u200B\u200E\u206F\u202B\u206D\u200F\u206E\u202A\u206E\u202E<string>(292816780u)
    });
    if (flag)
    {
        MessageBox.Show(<Module>.\u202D\u202A\u202E\u206C\u202B\u200F\u206B\u202A\u206C\u200D\u200D\u200C\u202B\u206F\u206F\u202C\u206F\u206E\u206D\u206C\u206D\u206F\u206D\u202B\u202C\u200C\u200E\u206B\u200E\u200D\u202C\u206C\u206B\u206E\u200C\u202D\u202E\u200C\u200C\u200C\u202E<string>(3452886671u));
        return;
    }
    MessageBox.Show(<Module>.\u206B\u206A\u200E\u202B\u200E\u202B\u202C\u200F\u202E\u202D\u202B\u200F\u206E\u202B\u206B\u202B\u202A\u206E\u206C\u202B\u202E\u206F\u202C\u200C\u200E\u206A\u202B\u202E\u202D\u200D\u202C\u206E\u202D\u206B\u206D\u206C\u202B\u202D\u206C\u206A\u202E<string>(458656109u));
}


The following is the unobfuscated version:

private void button1_Click(object sender, EventArgs e)
{
    byte[] buf = Form1.ReadResource(<Module>.smethod_4<string>(282000140u));
    byte[] buffer = this.decryptBuffer(buf);
    byte[] rawAssembly = util.DecompressBuffer(buffer);
    Assembly assembly = Assembly.Load(rawAssembly);
    Type type = assembly.GetType(<Module>.smethod_5<string>(370292149u));
    MethodInfo method = type.GetMethod(<Module>.smethod_2<string>(547307959u));
    if ((bool)method.Invoke(null, new object[]
    {
        <Module>.smethod_2<string>(292816780u)
    }))
    {
        MessageBox.Show(<Module>.smethod_6<string>(3452886671u));
        return;
    }
    MessageBox.Show(<Module>.smethod_3<string>(458656109u));
}


That's much better! So let's dive into it. The button click event, displayed above, is a good place to start. The code is reading some resource, decrypting it, decompressing it, loading it, getting a method, and then invoking it. The question is: what is it loading? If we step over it, lines 14 and 15 are never reached, which means that this unknown method is returning false. We probably need to make it return true. Let's step into it and see what's happening inside.

After quite a few steps we arrive at another module's constructor; the module being Layer1.dll, as can be seen from the Assembly Explorer:


Once again we're back to obfuscated code. To obtain an unobfuscated version we can break at line 6 in the button click event handler displayed above, save the contents of variable rawAssembly which contains the raw bytes of the dll, deobfuscate it with Unconfuser and de4dot, and load it into dnSpy. Even though we can't run it, at least we'll have cleaner code to reference while stepping through the obfuscated one. From now on I will be displaying code extracts from the unobfuscated version even though the execution really happens in the obfuscated one.

Just after the constructor we get to the Start() function:
public static bool Start(string config)
{
    Config config2 = Config.InitConfig(config);
    if (config2 == null)
    {
        return false;
    }
    if (!CPUDetection.CheckCPUCount(config2.CPUCount))
    {
        return false;
    }
    if (config2.DebugDetection && Layer1.IsDebuggerPresent())
    {
        return false;
    }
    bool result;
    try
    {
        string key = Layer1.getKey();
        byte[] bytesToBeDecrypted = util.ReadResource(<Module>.smethod_2<string>(2155271801u));
        byte[] buffer = util.AES_Decrypt(bytesToBeDecrypted, Encoding.UTF8.GetBytes(key));
        byte[] rawAssembly = util.DecompressBuffer(buffer);
        Assembly assembly = Assembly.Load(rawAssembly);
        Type type = assembly.GetType(<Module>.smethod_4<string>(1983674467u));
        MethodInfo method = type.GetMethod(<Module>.smethod_2<string>(1619868913u));
        result = (bool)method.Invoke(null, new object[]
        {
            <Module>.smethod_2<string>(2894386820u)
        });
    }
    catch (Exception)
    {
        result = false;
    }
    return result;
}

Notice the contents of the local variable config, which contains the 2nd share:
  • Share:2-f81ae6f5710cb1340f90cd80d9c33107a1469615bf299e6057dea7f4337f67a3

The first part of Layer1's Start() function checks the CPU Count and if a debugger is present, both of which succeed on my machine; if not, just alter the result and make them pass.

It then runs the Layer1.getKey() function (line 19) which enumerates the subdirectories the binary is running from, computes the base64 of their MD5 hashes and checks if one of them matches UtYSc3XYLz4wCCfrR5ssZQ==. This translates to the word share. Create the required folder or modify one of the existing ones in dnSpy and continue execution. As in the previous layer, another dll is loaded into memory and the Start() function of this dll is called.

Welcome to Layer2.dll! The Start() function in layer2 looks more elaborate but in essence it is very similar to layer1:

public static bool Start(string config)
{
    if (Layer2.IsVideoCardFromEmulator())
    {
        return false;
    }
    bool result;
    try
    {
        string key = Layer2.getKey();
        byte[] bytesToBeDecrypted = util.ReadResource(<Module>.smethod_5<string>(3753327011u));
        MethodInfo method;
        object[] array;
        while (true)
        {
            IL_114:
            uint arg_E7_0 = 940752502u;
            while (true)
            {
                uint num;
                switch ((num = (arg_E7_0 ^ 1520223704u)) % 8u)
                {
                case 0u:
                    goto IL_114;
                case 1u:
                {
                    Assembly assembly;
                    Type type = assembly.GetType(<Module>.smethod_2<string>(4061409225u));
                    arg_E7_0 = (num * 4242975297u ^ 1282056291u);
                    continue;
                }
                case 2u:
                {
                    Type type;
                    method = type.GetMethod(<Module>.smethod_4<string>(2617334851u));
                    array = new object[1];
                    arg_E7_0 = (num * 1579163950u ^ 3648286203u);
                    continue;
                }
                case 3u:
                {
                    byte[] rawAssembly;
                    Assembly assembly = Assembly.Load(rawAssembly);
                    arg_E7_0 = (num * 1590382241u ^ 2941013770u);
                    continue;
                }
                case 4u:
                {
                    byte[] buffer;
                    byte[] rawAssembly = util.DecompressBuffer(buffer);
                    arg_E7_0 = (num * 92554542u ^ 2808918491u);
                    continue;
                }
                case 6u:
                {
                    byte[] buffer = util.AES_Decrypt(bytesToBeDecrypted, Encoding.UTF8.GetBytes(key));
                    arg_E7_0 = (num * 4054600647u ^ 1312138318u);
                    continue;
                }
                case 7u:
                    array[0] = <Module>.smethod_4<string>(927985914u);
                    arg_E7_0 = (num * 2757215955u ^ 2775083800u);
                    continue;
                }
                goto Block_3;
            }
        }
        Block_3:
        result = (bool)method.Invoke(null, array);
    }
    catch (Exception)
    {
        while (true)
        {
            IL_162:
            uint arg_136_0 = 1603173826u;
            while (true)
            {
                uint num;
                switch ((num = (arg_136_0 ^ 1520223704u)) % 3u)
                {
                case 0u:
                    goto IL_162;
                case 2u:
                    result = false;
                    arg_136_0 = (num * 1446448774u ^ 4267266598u);
                    continue;
                }
                goto Block_5;
            }
        }
        Block_5:;
    }
    return result;
}


The function Layer2.IsVideoCardFromEmulator() (line 3) uses the WMI query select * from win32_videocontroller to query our video card's make and fails if it matches one of the following: "virtual", "vmware", "parallel", "vm additions", "remotefx","generic","cirrus logic","standard vga","matrox". Change the result and move on.

The next interesting function is Layer2.getKey(); it enumerates all the keys under HKEY_CURRENT_USER, computes the base64 of their MD5 hashes and checks if one of them matches Xr4ilOzQ4PCOq3aQ0qbuaQ==. This translates to the word secret. Create the key or modify an existing one in dnSpy to bypass this check.

The rest of the Start() function (lines 11 - 70) is just a convoluted way to load the next dll using the same old function from the previous 2 layers.

Phew! We're in layer 3; this is getting old! I present you ... Layer3's Start() function:

public static bool Start(string config)
{
    bool result;
    try
    {
        string key = Layer3.getKey();
        while (true)
        {
            IL_135:
            uint arg_103_0 = 4289824873u;
            while (true)
            {
                uint num;
                switch ((num = (arg_103_0 ^ 3707440169u)) % 9u)
                {
                case 0u:
                    goto IL_135;
                case 1u:
                {
                    byte[] bytesToBeDecrypted;
                    byte[] bytes = util.AES_Decrypt(bytesToBeDecrypted, Encoding.UTF8.GetBytes(key));
                    arg_103_0 = (num * 661416811u ^ 2039414464u);
                    continue;
                }
                case 2u:
                {
                    byte[] bytesToBeDecrypted = util.ReadResource(<Module>.smethod_5<string>(2921636399u));
                    arg_103_0 = (num * 3538037274u ^ 2949823500u);
                    continue;
                }
                case 4u:
                {
                    byte[] bytes2;
                    File.WriteAllBytes(<Module>.smethod_6<string>(2663114732u), bytes2);
                    ProcessStartInfo processStartInfo = new ProcessStartInfo(<Module>.smethod_9<string>(1143424475u));
                    arg_103_0 = (num * 1327553900u ^ 1386100579u);
                    continue;
                }
                case 5u:
                {
                    byte[] bytes;
                    File.WriteAllBytes(<Module>.smethod_6<string>(2505810066u), bytes);
                    arg_103_0 = (num * 3787366363u ^ 40351029u);
                    continue;
                }
                case 6u:
                {
                    ProcessStartInfo processStartInfo;
                    Process.Start(processStartInfo);
                    arg_103_0 = (num * 284573691u ^ 2598046760u);
                    continue;
                }
                case 7u:
                {
                    ProcessStartInfo processStartInfo;
                    processStartInfo.Verb = <Module>.smethod_6<string>(3666361390u);
                    arg_103_0 = (num * 183203051u ^ 3338640763u);
                    continue;
                }
                case 8u:
                {
                    byte[] bytes2 = util.ReadResource(<Module>.smethod_8<string>(4245356310u));
                    arg_103_0 = (num * 748758343u ^ 2498904875u);
                    continue;
                }
                }
                goto Block_2;
            }
        }
        Block_2:
        return true;
    }
    catch (Exception)
    {
        result = false;
    }
    return result;
}



Once again we have a getKey() function which enumerates the users on the machine using the WMI query select * from Win32_UserAccount and, using the same MD5+Base64 method as in the previous layers, checks if the user shamir exists. Bypass it! You're probably an expert at doing this by now.

The rest of the function is a bit different than the previous ones. While stepping through from layer2 to layer3, you have probably realised that a new thread was being initialized and constructed, but never started. Line 49 is the point this happens. As soon as we step over this, the following image pops up:


Nice, we've got Share 6:

  • Share:6-a003fcf2955ced997c8741a6473d7e3f3540a8235b5bac16d3913a3892215f0a

If we continue tracing, the execution hits line 71, which returns true, and step by step it starts exiting each embedded function until we get all the way to the original PE. This time it hits the other MessageBox.Show() function and we get:


Is that it? What now? If we look at the folder where GUI.exe is, we notice a new binary called ssss-combine.exe. This binary is used to combine the shares we obtained to reveal a secret. Still, were are the other 3 shares?

At this point I thought I could either go through the program in a more thorough way, stepping into each and every function or, if the strings have been constructed at some point during the execution, they must reside in memory. And of course I went with the latter. So, memdump the process, run strings and grep:

root@kali: ~/Desktop
root@kali:~/Desktop# strings GUI.DMP | grep -i share: Share:1-d8effa9e8e19f7a2f17a3b55640b55295b1a327a5d8aebc832eae1a905c48b64 no/-|-\no/-|-\no/-|-\2/-|-\shareShare:2-f81ae6f5710cb1340f90cd80d9c33107a1469615bf299e6057dea7f4337f67a3 Share:3-523cb5c21996113beae6550ea06f5a71983efcac186e36b23c030c86363ad294 Share:4-04b58fbd216f71a31c9ff79b22f258831e3e12512c2ae7d8287c8fe64aed54cd Share:3-523cb5c21996113beae6550ea06f5a71983efcac186e36b23c030c86363ad294 Share:4-04b58fbd216f71a31c9ff79b22f258831e3e12512c2ae7d8287c8fe64aed54cd Share:5-5888733744329f95467930d20d701781f26b4c3605fe74eefa6ca152b450a5d3 root@kali:~/Desktop#


Combining all 6 shares we get:

Command Prompt
C:\>ssss-combine.exe -t 6 Shamir Secret Sharing Scheme - $Id$ Copyright 2005 B. Poettering, Win32 port by Alex.Popov@leggettwood.com Enter 6 shares separated by newlines: Share [1/6]: 1-d8effa9e8e19f7a2f17a3b55640b55295b1a327a5d8aebc832eae1a905c48b64 Share [2/6]: 2-f81ae6f5710cb1340f90cd80d9c33107a1469615bf299e6057dea7f4337f67a3 Share [3/6]: 3-523cb5c21996113beae6550ea06f5a71983efcac186e36b23c030c86363ad294 Share [4/6]: 4-04b58fbd216f71a31c9ff79b22f258831e3e12512c2ae7d8287c8fe64aed54cd Share [5/6]: 5-5888733744329f95467930d20d701781f26b4c3605fe74eefa6ca152b450a5d3 Share [6/6]: 6-a003fcf2955ced997c8741a6473d7e3f3540a8235b5bac16d3913a3892215f0a Resulting secret: Shamir_1s_C0nfused@flare-on.com C:\>

No comments:

Post a comment