Wednesday, 25 December 2019

BlackNet RAT - When you leave the Panel unprotected

BlackNET is a PHP based Web Panel which has a builder written in VB.NET. It is being actively used in-the-wild for malicious activities.

Recently, while analysing a malicious .NET Binary, I came across something interesting which caught my attention. Before I share those details, I will discuss a little bit about the capabilities of the payload, the project itself and then will present the discovery :)

MD5 hash of the sample discussed: 7e88ccc91e0f9a242c4723e43afa93ab

The .NET binaries used in-the-wild which leverage BlackNET panel are not obfuscated. At least the binaries I analysed so far are not protected or obfuscated. This makes the process of analysis straightforward.

How to identify whether this is related to BlackNET?

When you decompile the binary, the list of .NET methods are sufficient to correlate and understand that they have used the BlackNET project. In our case, after decompiling the .NET binary, we can see the list of methods as shown below:

The names of the methods are self explanatory however for the purpose of brevity, I will mention below some of the capabilities:

AntiVM: It has the ability to detect a Virtual Machine by checking for the presence of the DLL files, vmGuestLib.dll and vboxmrxnp.dll on the file system. If it finds these files, then it will delete them.

It also tries to load the DLL, SbieDll.dll to check for the presence of Sandboxie (a very common method).

DDoS: Various methods of DDoS are supported by this binary which include: ARME, Slowloris, UDP, TCP, HTTP GET and HTTP POST request based. The attacker can specify the host address they want to perform the DDoS attack against using the BlackNET Panel. They can also select the DDoS method as can be seen here

LimeLogger: This is the key logging module which leverages LowLevelKeyboardProc() function along with SetWindowsHookEx() to do keylogging.

Screening_Programs: This method checks for the presence of analysis tools used by Malware Researchers. It performs checks using both the process names as well as the Window Titles as shown below:

I have included the list in Appendix which can be used as a reference by you to harden your Virtual Machine while analyzing malwares in future.

Unprotected Web Panels

These web panels are easy to deploy. Just get a web hosting, upload the PHP scripts, run the Installation script which sets up the database and the Panel is ready to use.

I noticed that most users of BlackNET Web Panel are leveraging the hosting provided by

One such example is the binary we are discussing in this article.

Once the sample is executed on the machine, it will gather basic details from the machine, send them in an HTTP GET request to the BlackNET panel and register the machine. For each victim's machine, an ID is generated in the format: Hacked_<ID>

Below is an example of the HTTP GET requests initiated by the binary:

Web Panel is located at:

If we visit the hosting, we notice that there is no index.php script present. As a result of it, directory listing is enabled as shown below:

There is an upload directory in which the information captured from the machines is stored. Inside the upload directory, there is a directory for each victim's machine with the name in the format: Hacked_<ID>

There is one directory of specific interest as shown below:

The JPG file there is the screenshot taken from the machine. If we check the screenshot, we notice that it is the screenshot taken from the BlackNET panel's admin machine itself as shown below:

If we check the URL in the address bar in screenshot above, we can see that the command: tkschot was used to capture the screenshot from the machine.

The command itself is defined in the code here

This could be the result of the admin verifying whether the panel is working properly by taking a screenshot of their machine. However, they forgot to delete the screenshot from the panel to clear any traces.

Below are some more MD5 hashes of .NET binaries using the BlackNET Panel:


More Web Panels:

hxxp:// Panel/


List of Process Names Checked:


List of Window Titles Checked:

Malwarebytes Anti-Malware
Active Ports
MKN TaskExplorer
System Explorer
DiamondCS Port Explorer
Metascan Online
Speed Gear
The Wireshark Network Analyzer
Sandboxie Control
.NET Reflector


Tuesday, 24 December 2019

Unpacking Payload used in Bottle EK

On December 13th 2019, @nao_sec discovered a new Exploit kit targeting users in Japan and it was given the name, Bottle Exploit Kit.

@nao_sec described in their blog the details of the Exploit Kit including the two vulnerabilities (CVE-2018-8174 and CVE-2018-15982) which were exploited in this attack.

In this article, I will go into the details of the multiple stages of unpacking the payload used in Bottle Exploit Kit.

tl;dr: Multiple stages of packing are used in the payload. The XOR decryption routine used is common for all the payloads related to Bottle Exploit Kit and can be used to discover more instances.

MD5 hash of the sample discussed: ee98ef74c496447847f1876741596271

The WinMain() subroutine creates a new Thread as shown below:

The newly created Thread creates another Thread in turn as shown below:

The first stage of unpacking is performed by Thread 2 (function start address: 0x40105b). The figure highlights the important stages of unpacking:

The main stages of unpacking of the first stage are:

1. VirtualAlloc() to allocate memory to decrypt stage 1.
2. RtlMoveMemory() to copy 0x1ed8 bytes of encrypted data from 0x412db8 to memory allocated in step 1.
3. XOR decryption routine at address: 0x401000 is invoked. The XOR decryption key is 0x3c bytes in length and is passed as an argument to the XOR decryption routine.
4. VirtualAlloc() is called again to decompress the XOR decrypted output of step 3.
5. Decompression is performed using RtlDecompressBuffer()
6. A new Thread with function start address set to decompressed code in step 5 is started.

The XOR decryption routine mentioned in step 3 above is as shown below:

The next decrypted stage looks as shown below:

Another layer of XOR decryption is done by stage 2 which gives us the following decrypted data:

In the next stage of execution, it performs the System Language Check using the API, GetUserDefaultUILanguage() as shown below:

The system language code is compared with 0x411 which corresponds to Japanese System Language. The payload will execute completely only if the system language code is: 0x411.

Here is the list of decrypted strings:

0000000028DF   0000000028DF      0   shlwapi.dll
0000000028EB   0000000028EB      0   User32.dll
0000000028F6   0000000028F6      0   Advapi32.dll
000000002903   000000002903      0   ntdll.dll
00000000290D   00000000290D      0   Ws2_32.dll
000000002918   000000002918      0   Wininet.dll
000000002924   000000002924      0   Urlmon.dll
00000000292F   00000000292F      0   bcdfghklmnpqrstvwxzaeiouyT
000000002A56   000000002A56      0   DataDirectory %s
000000002A77   000000002A77      0   POST %s HTTP/1.1
000000002A89   000000002A89      0   Host: %s
000000002A93   000000002A93      0   Connection: close
000000002AA6   000000002AA6      0   Accept: */*
000000002AB3   000000002AB3      0   User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64)
000000002AE4   000000002AE4      0   Accept-Encoding: identity
000000002AFF   000000002AFF      0   Content-Type: application/x-www-form-urlencoded
000000002B30   000000002B30      0   Content-Length: %d
000000002B4C   000000002B4C      0   WSAStartup
000000002B57   000000002B57      0   socket
000000002B5E   000000002B5E      0   setsockopt
000000002B69   000000002B69      0   connect
000000002B7B   000000002B7B      0   closesocket
000000002952   000000002952      0   AppData\LocalLow
000000002974   000000002974      0   \Data\Tor
000000002988   000000002988      0   \Data\Tor\geoip
0000000029A8   0000000029A8      0   \Data\Tor\geoip6
0000000029CA   0000000029CA      0   \Tor\taskhost.exe
0000000029EE   0000000029EE      0   \Tor\tor.exe
000000002A08   000000002A08      0   \torrc
000000002A16   000000002A16      0
000000002A22   000000002A22      0   1.exe
000000002A2E   000000002A2E      0   -o -qq "%s" -d "%s"
000000002A66   000000002A66      0   s-f "%s"
000000002B86   000000002B86      0   t.jpg
000000002B9B   000000002B9B      0   ALLUSERSPROFILE
000000002BBB   000000002BBB      0   rundll32.exe

From the Decrypted Strings above, we can see that it will make a Network Request to download TOR browser.

The XOR decryption routine used in the payload is the same among all the samples (DLL and EXE files) related to Bottle Exploit Kit instances.

Here are more MD5 hashes of payloads used in Bottle Exploit Kit:


The only change is the XOR decryption key.


Friday, 24 May 2019

Locksmith Writeup - Security Fest CTF 2019

This challenge is related to the concept of Lockpicking. In this challenge, we need to connect to remote service running at:

Once we connect, we are provided with the following information:

We are presented with two arrays of 9 numbers.

Array 1: This is the initial state of the numbers on the lock. Right now, it is in locked state.
Array 2: This is the target state of the numbers on the lock. This indicates the unlocked state.

We can send the inputs in the range 1 to 9 to the remote service. Based on each input, a specific value is added to each dial of the lock as shown below:

We need to keep sending the inputs till the numbers in Array 1 are the same as the numbers in Array 2.

And to solve the challenge, we need to unlock the lock 100 times. In each round, we are presented with a new initial and target state of the lock.

I used z3 and pwntools to solve this challenge.

pwntools was used to handle the network communication between my machine and the server.
z3 was used to solve the equations generated based on the responses received from the server.

Since each number (in the range: 1 to 9), adds a specific value to each number of the Array. I used this information to find out how many times each number has to be sent to the remote service to get the desired result.

For example, let the initial state of the lock be:

initial = [x1, x2, x3, x4, x5, x6, x7, x8, x9]

After we send the input number 1, the modified state becomes:

modified = [y1, y2, y3, y4, y5, y6, y7, y8, y9]

So, the diff is:

diff = [y1 - x1, y2 - x2, y3 - x3, y4 - x4, y5 - x5, y6 - x6, y7 - x7, y8 - x8, y9 - x9]

Le us denote, y1 - x1 with a1 which indicates the difference between the number in position 1 of the current array and the previous array when the input number is 1. Similarly,

y2 - x2 =b1
y3 - x3 = c1
y9 - x9 = i1

diff array for input 1 = [a1, b1, c1, d1, e1, f1, g1, h1, i1]

Similarly for other input numbers, the diff arrays will be:

[a2, b2, c2, d2, e2, f2, g2, h2, i2]
[a9, b9, c9, d9, e9, f9, g9, h9, i9]

if our target value in the lock is X, then we need to solve the equations of the form:

a1 * a + a2 * b + a3 * c + a4 * d + a5 * e + a6 * f + a7 * g + a8 * h + a9 * i == X
i1 * a + i2 * b + i3 * c + i4 * d + i5 * e + i6 * f + i7 * g + i8 * h + i9 * i == X

So, we have a system of 9 linear equations each containing 9 unknown values. This can be solved using z3.

Here is my code for solving this challenge:

Once all the rounds are successfully solved, the server responds with the flag as shown below:

Flag: sctf{1_gUeS5_tH4T_is_whY_th3Y_c4lL_iT_a_m0Nte_caRlO_bAnk}


Tuesday, 16 April 2019

Last Minute Write Up - WPI CTF 2019

Last Minute was a reversing challenge in WPI CTF 2019. This challenge was very interesting because it uses frame buffers to draw the actual flag if the environment and initial conditions are correct.

The provided binary is an ELF 64-bit binary.

The main functions performed by the binary are:

1. Uses the current timestamp to calculate a seed which is used to seed the random number generator.

The seed is calculated and used to seed the random number generator as shown below:

t = time()
seed = t/0x3c

This means that the seed only changes every 60 seconds.

2. Opens the frame buffer device, /dev/fb0

3. Uses the ioctl, FBIOGET_VSCREENINFO (0x4600) to retrieve the fb_var_screeninfo structure. The bits_per_pixel field in this structure is set to 32.

4. Updates the structure using another ioctl, FBIOPUT_VSCREENINFO (0x4601).

5. Runs a loop which will update the accel_flags field in the vinfo structure.

6. Retrieves the vinfo structure.

7. Retrieves the finfo structure using the ioctl, FBIOGET_FSCREENINFO (0x4602).

This structure is defined as shown below:

struct fb_fix_screeninfo {
    char id[16];            /* identification string eg "TT Builtin" */
    unsigned long smem_start;    /* Start of frame buffer mem */
                    /* (physical address) */
    __u32 smem_len;            /* Length of frame buffer mem */
    __u32 type;            /* see FB_TYPE_*        */
    __u32 type_aux;            /* Interleave for interleaved Planes */
    __u32 visual;            /* see FB_VISUAL_*        */
    __u16 xpanstep;            /* zero if no hardware panning  */
    __u16 ypanstep;            /* zero if no hardware panning  */
    __u16 ywrapstep;        /* zero if no hardware ywrap    */
    __u32 line_length;        /* length of a line in bytes    */
    unsigned long mmio_start;    /* Start of Memory Mapped I/O   */
                    /* (physical address) */
    __u32 mmio_len;            /* Length of Memory Mapped I/O  */
    __u32 accel;            /* Indicate to driver which    */
                    /*  specific chip/card we have    */
    __u16 capabilities;        /* see FB_CAP_*            */
    __u16 reserved[2];        /* Reserved for future compatibility */

It fetches the line_length field of the finfo structure and prints it to stdout.

The line_length field needs to be equal to 0x1900 for it to continue the execution to display the flag as shown below:

if line_length <= 0x18ff:
    print "Ectomporh"
elif line_length != 0x1900:
    print "Endomorph"
    print "Mesomorph"

    // perform the computations to display the flag

So, what is the line_length field?

In frame buffers, the line_length field represents the number of bytes in each line of the display. It is related to the X resolution field in the vinfo structure as shown below:

line_length = vinfo.x_res * (bits_per_pixel)/8

In our case, line_length should be equal to 0x1900 and bits_per_pixel is 32.

so, vinfo.x_res = 0x1900/4 = 1600

We can check the current resolution of the frame buffer device, /dev/fb0 using the command:

cat /sys/class/graphics/fb0/virtual_size

This displays both the resolution fields in (x,y) format.

If the X and Y resolution fields of the virtual_size are not equal to 1600 and 900 respectively, then the flag will not be displayed on the screen.

We can set the frame buffer resolution using the command:

fbset -fb /dev/fb0 1600 900 1600 900 32

Using the above command we have set all the fields of the frame buffer which are required to display the flag.

Now, lets explore the generation of seed using time(). The seed is very important here because it determines the random numbers generated by rand(). And these random numbers are useful because they are used to calculate the offsets at which the pixels will be drawn on the frame buffer.

The data to to be drawn to the frame buffer is stored in the .data section in the array called baboof[] as shown below:

Based on the challenge description, we find the timestamp when the CTF ends and it is: 1555286400.

Since we know that the seed changes every 60 seconds, we can generate all the possible seeds for the last 1 hour of the CTF.

This gives us the values:

['0x5cb3bb70', '0x5cb3bbac', '0x5cb3bbe8', '0x5cb3bc24', '0x5cb3bc60', '0x5cb3bc9c', '0x5cb3bcd8', '0x5cb3bd14', '0x5cb3bd50', '0x5cb3bd8c', '0x5cb3bdc8', '0x5cb3be04', '0x5cb3be40', '0x5cb3be7c', '0x5cb3beb8', '0x5cb3bef4', '0x5cb3bf30', '0x5cb3bf6c', '0x5cb3bfa8', '0x5cb3bfe4', '0x5cb3c020', '0x5cb3c05c', '0x5cb3c098', '0x5cb3c0d4', '0x5cb3c110', '0x5cb3c14c', '0x5cb3c188', '0x5cb3c1c4', '0x5cb3c200', '0x5cb3c23c', '0x5cb3c278', '0x5cb3c2b4', '0x5cb3c2f0', '0x5cb3c32c', '0x5cb3c368', '0x5cb3c3a4', '0x5cb3c3e0', '0x5cb3c41c', '0x5cb3c458', '0x5cb3c494', '0x5cb3c4d0', '0x5cb3c50c', '0x5cb3c548', '0x5cb3c584', '0x5cb3c5c0', '0x5cb3c5fc', '0x5cb3c638', '0x5cb3c674', '0x5cb3c6b0', '0x5cb3c6ec', '0x5cb3c728', '0x5cb3c764', '0x5cb3c7a0', '0x5cb3c7dc', '0x5cb3c818', '0x5cb3c854', '0x5cb3c890', '0x5cb3c8cc', '0x5cb3c908', '0x5cb3c944', '0x5cb3c980']

Now, we need to bruteforce the above timestamps and check the frame display output.

Since the binary is dynamically linked, we can leverage the LD_PRELOAD environment variable so that time() function always returns a predefined value for the binary being executed.

This can be done by writing a short function such as:

int time()
    return 0x5cb3c944;

Then compile it as a shared library:

gcc -shared -fPIC hook_time.c -o

Now, we can run the binary as shown below and confirm that the timestamp returned by time() is indeed how we configured it:

sudo LD_PRELOAD=$PWD/ ltrace -o trace1.txt ./lm

This will run the binary using ltrace to log the API calls and LD_PRELOAD will be used to load our shared library,

As shown below, time() returns our configured timestamp:

Now, we can run the binary and it will display the flag as shown below:



Monday, 1 April 2019

CyberRumble Writeup - Sunshine CTF 2019

In this challenge, we are provided a 64-bit ELF binary that accepts different forms of inputs as shown below:

Input: tombstone_piledriver<file_name>

You can specify the filename in this command. The server will read and display its contents.

Issue: This input will print only the first word of the flag file.

Reason: The contents of the file are read using ___isoc99_fscanf() and format string %99s. So, the space character is the delimiter and anything after the space character is not read. As a result, we cannot use this input to get the flag.

Input: old_school <shellcode>

max_length(shellcode) = 0x64 - len("old_school ")

Issue: The shellcode cannot be executed directly.

Reason: It uses mmap() to map the memory region to which the shellcode will be copied using memcpy(). It then uses mprotect() to remove the execute protection from this memory region. As a result of this, even though we can send the shellcode we cannot execute it directly.

Input: last_ride <shell_command>

This input is interesting because it will be sent to the system() function for execution.

However, a bug is included in the binary which sends the ASCII string itself to the system() function instead of the pointer to ASCII string.

It does not fetch the pointer using mov instruction and instead uses lea instruction as shown below:

So, we cannot send the shell command directly for code execution.

However, we can pass a pointer in the last_ride command as a string so that system() executes the contents pointed to by that pointer.

The binary has ASLR enabled. How can we get the address of our string?

The binary provides us our shellcode address. This can be used to solve the challenge.

1. Send the shell command in the old_school command like: old_school <shell_command>
2. Binary gives us the address of the shellcode.
3. Use the above address and send that as a string in the last_read command.

It is important to note that the shellcode address contains null bytes due to the way mmap() maps a new memory region. Since we are sending the address of the shellcode as a string to the system() command, it must not contain null bytes.

To resolve this, we can add some padding before our shell command so that the address does not contain null bytes.

The exploit code is:

from pwn import *
import re
import time

p = remote("", 4300)

print p.recv()

payload = "old_school Ash"

print "Sending first stage payload: %s" %(payload)


output = p.recv()
m = re.findall(r'0x(.*)?\.', output)
addr = m[0]
addr = int(addr, 16) + 1

print "The shellcode address is: %x" %(addr)


payload = "last_ride " + p64(addr)

print "Sending second stage payload: %s" %(payload.encode("hex"))



The execution of the exploit is shown below:

Flag: sun{the chair and thumbtacks are ready, but the roof is a little loose}


Big Bad Writeup - Sunshine CTF 2019

In this challenge, we are provided a PNG image that displays a Binary Tree as shown below:

After analyzing the tree, it was concluded that the challenge is related to Huffman Encoding.

In Huffman Encoding, we can construct the value for each leaf node by traversing the tree from the root node till the leaf node using the following logic:

1. If a left branch is taken, then we consider the bit 0.
2. If a right branch is taken, then we consider the bit 1.

The left and right branches originating from the root node of the tree are marked as 0 and 1 respectively in the provided image which is also an indication that we have to use Huffman Encoding.

So, based on the above logic, we construct the Huffman Encoding table:

s = 000
u = 0010
_ = 0011
0 = 010
d = 0110
9 = 0111
5 = 1000
n = 10010
h = 10011
l = 10100
a = 10101
e = 10110
b = 10111
1 = 1100
{ = 11010
} = 11011
r = 11100
c = 11101
k = 11110
3 = 11111

In Huffman Encoding, if we have to decode the message, we need a binary stream. However, no binary stream was provided in the challenge description.

After further analysis, it was found using Steganography that the binary stream was encoded in the PNG image as a set of vertical black and white lines as shown below:

Each line has a width of 1 pixel, so we can extract the binary stream using the logic:

1. If the pixel value is 255, then we consider the bit to be 1.
2. If the pixel value is 1, then we consider the bit to be 0.

We can leverage the Python PIL library to accomplish this:

#! /usr/bin/python

from PIL import image
import sys

if len(sys.argv) != 2:
    print "usage: python <image_file>"
im =[1])

stream = ""

for i in range(152):
    if im.getpixel((i, 0)) == 255:
        stream += str(1)
        stream += str(0)
print stream

We scan 152 pixels from the left side of the image because based on analysis of the image in Gimp, it was found that approximately 152 pixels need to be scanned to extract the complete binary stream.

we get the binary stream as:


We can apply the Huffman Encodings to above binary stream to get the flag as: sun{sh0ulda_u5ed_br1cks_10011305191101}