Reversing the shad0w framework part 2

2020-07-01

Contuing from reversing the shad0w framework part 1 we will look at the registration of the beacon and how tasks for the beacon are received.

Just like the previous part this is not meant to defeat the framework in any kind of way, but meant as a reverse engineering exercise for the people that want to follow along, or for the people that are just generally interested in reading this kind of stuff.

Reverse engineering the beacon

In the previous part I also presented the x64dbg script that will take you to the entrypoint of the beacon. Before the beacon is able to execute any tasks however, it will need to register at the C2.

We will begin with looking at the first piece of information that the beacon will retrieve (yes we already hovered over this a little bit), before moving on to the part of reading the received tasks.

Retrieving user information

Before the beacon registers at the C2, it will gather some user information. It will gather this information making use of the Windows API’s.

The first routine that is executed by the beacon is a routine to retrieve the username of the machine, as well as some machine information. The following API calls are used to retrieve this information (https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/build/core.c#L35):

  • GetUserNameA – retrieves the current username;

  • GetComputerNameExA – retrieves the domain of the target machine;

  • GetComputerName – retrieves the hostname of the target machine;

When entering the beacon routine, one of the first calls that is made is the routine to retrieve the information using above calls:

push rbp                            | Start of beacon execution
mov eax,1900                        |
call 84AC720                        |
sub rsp,rax                         |
lea rbp,qword ptr ss:[rsp+80]       |
call 84AB2A0                        |
mov dword ptr ss:[rbp+1878],3F      | 
mov dword ptr ss:[rbp+1874],1       |
lea rax,qword ptr ss:[rbp-20]       |
mov rcx,rax                         |
call <GetBasicUserInfo>             | Retrieve the username information

NOTE: I changed the name of the call to make it more clear that this is the call to retrieve the basic user information. You will see this more often in the other snippets of disassembly as well.

After retrieving the basic user information, the beacon will make a call to GetBasicCompInfo (https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/src/core.c#L66).

This call can also be recognized by the VerifyVersionInfoW call that is followed by the RtlGetVersion call to retrieve operating system information. This API address is retrieved using GetProcAddress that is called using ntdll.dll with LoadLibrary. The reason this is done with ntdll.dll is because this is where RtlGetVersion is specified as stated in the MSDN: https://docs.microsoft.com/en-us/windows/win32/devnotes/rtlgetversion#requirements.

RtlGetVersion is a more reliable way to get information about the operating system than simply using the major and minor operating system versions. This is probably the reason why this API is used to retrieve this information.

First beacon check-in

The first beacon check-in is different than the beacon check-ins that come after registering the beacon at the C2 (as can be seen in the code: https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/src/core.c#L155).

To communicate with the C2, the beacon will dynamically load the necessary API calls, it does this by decrypting the DLL name, using this as parameter for the LoadLibrary call and use GetProcAddress to retrieve the address of the DLL loaded with LoadLibrary (MDSN: https://docs.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-GetProcAddress).

When looking at the disassembler it will look a little bit like this (again used the labels to change some of the calls to be more descriptive):

lea rcx,qword ptr ds:[<encrypted_WinHttpCloseHandle>]    | Load encrypted WinHttpCloseHandle string into RCX
call <decrypt_string>                                    | Decrypt the string that is loaded into RCX
mov rbx,rax                                              |
mov edx,47                                               | 
lea rcx,qword ptr ds:[<encrypted_winhttp.dll>]           | Load address of string: MC4pLzMzN2kjKys= into RCX (encrypted string may differ)
call <decrypt_string>                                    | Decrypt the string that is loaded into RCX
mov rcx,rax                                              | 
mov rax,qword ptr ds:[<&amp;LoadLibraryA>]               | Move address pointing to LoadLibraryA into RAX
call rax                                                 | Call GetProcAddress
mov rdx,rbx                                              | 
mov rcx,rax                                              | 
mov rax,qword ptr ds:[<&amp;GetProcAddress>]             | Move address pointing to GetProcAddress into RAX
call rax                                                 | Call GetProcAddress

When we look at the address of the encrypted WinHttpCloseHandle in memory we can see that there are indeed multiple encrypted strings at this point in memory:

56 47 70 74 53 33 64 33 63 30 42 76 62 48 42 6D VGptS3d3c0BvbHBm 53 32 4A 74 5A 32 39 6D 00 4D 43 34 70 4C 7A 4D S2JtZ29m.MC4pLzM 7A 4E 32 6B 6A 4B 79 73 3D 00 41 7A 30 36 48 43 zN2kjKys=.Az06HC 41 67 4A 42 73 6B 4D 54 6F 3D 00 5A 31 6C 65 65 AgJBskMTo=.Z1lee 45 52 45 51 48 4E 66 58 6C 35 56 55 30 51 3D 00 EREQHNfXl5VU0Q=. 52 33 6C 2B 57 47 52 6B 59 46 39 67 64 58 35 43 R3l+WGRkYF9gdX5C 64 57 46 6C 64 57 4E 6B 00 00 2F 00 72 00 65 00 dWFldWNk../.r.e.

NOTE: The above encrypted strings are delimited by the NULL byte \x00

After opening a socket by executing the WinHttpOpen, it will create a connection using WinHttpConnect. A POST request is created using the following parameters when calling the WinHttpSendRequest API:

  • URI: /register

  • METHOD: POST

  • HEADER: Content-Type: application/x-www-form-urlencoded\r\n

  • HEADER: Content-length

  • DATA: Buffer containing the user and system info, as well as the security setting of the beacon

This can be observed in the disassembly view:

mov qword ptr ss:[rsp+30],0             | 0
mov dword ptr ss:[rsp+28],ebx           | String length uribuffer
mov dword ptr ss:[rsp+20],ecx           | String length uribuffer
mov r10,qword ptr ss:[rbp-60]           | Address of WinHttpRequest
mov r9,rdx                              | Uribuffer (contains user information, system information and beacon security information)
mov r8d,FFFFFFFF                        | HTTP Header Length
lea rdx,qword ptr ds:[<content_type>]   | HTTP Header (Content-Type: application/x-www-form-urlencoded\r\n)
mov rcx,rax                             | HTTP Method (POST)
call r10                                | Call WinHttpSendRequest with the parameters above

The above disassembly corresponds to the following line of code: https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/src/core.c#L380.

After the above call has been made, the beacon loads the WinHttpReceiveReponse so it is able to also retrieve a handle on the HTTP connection that was established when sending the data.

Now comes the more important part; after the WinHttpReceiveResponse call, the beacon will make a call to WinHttpReadData. This will read the response received of the C2.

The WinHttpReadData looks like this in the disassembly view:

mov qword ptr ss:[rbp-20],rax     | Address of WinHttpReadData that was retrieved with GetProcAddress
mov ecx,dword ptr ss:[rbp-38]     | Number of bytes that have been read from the buffer
lea r8,qword ptr ss:[rbp-3C]      | Number of bytes that need to be read from the buffer
mov rdx,qword ptr ss:[rbp-18]     | Pointer to the buffer that receives data
mov rax,qword ptr ss:[rbp+30]     | Handle received from WinHttpReceiveResponse
mov r10,qword ptr ss:[rbp-20]     | Move address pointing to WinHttpReadData in R10
mov r9,r8                         |
mov r8d,ecx                       |
mov rcx,rax                       |
call r10                          | Call WinHttpReadData

NOTE: You can see the above structure in the code: https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/src/core.c#L429, as well as on this MSDN page: https://docs.microsoft.com/en-us/windows/win32/api/winhttp/nf-winhttp-WinHttpReadData

The above information tells us that there are a couple of things we should watch, following some of MSDN X64 calling convention (https://docs.microsoft.com/en-us/cpp/build/x64-calling-convention?view=vs-2019#callercallee-saved-registers), and reverse engineering:

  • RDX – pointer to start the buffer;

  • ECX (could be other nonvolatile register) so we know how much data is received;

  • R8 (could be other nonvolatile register) so we know how much still needs to be read;

And indeed, if we look at the memory pointed to by RSI we can see the received contents:

7B 22 69 64 22 3A 20 22 31 30 64 65 66 62 36 61 {"id": "10defb6a 61 64 64 34 30 65 63 66 61 61 65 65 64 37 63 36 add40ecfaaeed7c6 39 66 34 36 34 31 39 62 22 2C 20 22 61 6C 69 76 9f46419b", "aliv 65 22 3A 20 74 72 75 65 7D 3F 1B D5 FB 7F 00 00 e": true}?.Õû...

NOTE: The displayed ID is a MD5 sum made of the current timestamp of the C2 (https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/lib/tools.py#L7).

Reading received tasks

So now we can test some things; the code shows different opcodes and execution id’s in the code, let’s play around a little bit.

Take for example the pwd command, this command is identified by opcode 0x3000 in the code: https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/src/core.c#L514.

When searching for pwd in the Github repository, you will see that there is a Python file that shows the execution id (0x4000) and the opcode (0x3000) used for this command. If we execute this command in the C2 console, we can actually see this coming back in memory as well by looking at the buffer that RDX is pointing to after the WinHttpReadData call:

7B 22 74 61 73 6B 22 3A 20 31 36 33 38 34 2C 20 {"task": 16384, 22 61 72 67 73 22 3A 20 22 7B 5C 22 6F 70 5C 22 "args": "{\"op\" 3A 20 31 32 32 38 38 2C 20 5C 22 61 72 67 73 5C : 12288, \"args\ 22 3A 20 5C 22 6E 75 6C 6C 5C 22 7D 22 2C 20 22 ": \"null\"}", " 61 6C 69 76 65 22 3A 20 74 72 75 65 7D 00 AB AB alive": true}.««

In the above data that is stored in memory we observe that the task key holds the value of 16384 (or 0x4000), and the op key holds the value of 12288 (or 0x3000). We can also see that this command does not take any arguments as the args key has a null value. The args holds a null value because this command uses built-in Windows API calls to retrieve the information that is requested , and thus doesn’t need any arguments to do so. It is able to retrieve the current working directory by calling the GetCurrentDirectory API.

Whenever there is a whole module being loaded, like when executing the upload command in the C2, the beacon makes excessive use of the heap to store larger payloads.

The first call that is observed after the WinHttpReadData call, is responsible for writing the buffer that has been received until then onto the heap. To see where the payload is in this process , the vsnprintf call can be usedto check the buffer so far.

vsnprintf writes formatted data from variable arguments into a buffer (http://www.cplusplus.com/reference/cstdio/vsnprintf/). By checking the value RCX is pointing to in memory we can find the end of the buffer that has been read by WinHttpReadData so far.

So from the information that we observed so far it could be possible to store the location of the buffer and its size by storing the value RCX is pointing to after the vsnprintf call, together with the value in RAX which points to its size. (value in RCX – value in RAX) = starting address of the complete buffer.

The memmove call will move the received arguments to another place in the heap. This spot in the heap can be found when looking at the RAX register and following that in the memory dump.

Automagically reading received tasks

So now onto a bit of fun, I wrote a little x64dbg script that brings you not only to the entrypoint of the beacon, but will also keep an eye on the received tasks by reading the buffer that is received with WinHttpReadData.

When running the script, keep in mind that it will pause every time after returning from the API call. To continue the script execution, just press the spacebar.

// Initial message
msg "OEP find &amp; read received tasks for shad0w beacon"
msg "Please make sure you are at the entrypoint of the executable before you continue"
pause
 
// Clear breakpoints
bc
bphwc
 
log "Running until entry point of stager"
 
// Set a breakpoint at VirtualAlloc and run until the beacon is 
// written into memory
log "Setting breakpoint at VirtualAlloc"
bp VirtualAlloc
log "Running until stager has finished"
 
// Keep count how many times we hit VirtualAlloc
$runcount = 0
goto virtualalloc_routine
 
// Loop until VirtualAlloc has been hit 6 times
virtualalloc_routine:
erun
rtr
sti
$runcount++
cmp $runcount,6
jb virtualalloc_routine
 
// Look for the pattern call RBP
find cip,"FF D5 33 D2"
cmp $result,0
jz error
 
// Set a breakpoint on the call to RBP and step into the function
log "Found call rbp pattern at [{0}]",$result
bp $result
erun
sti
msg "Reached entrypoint of beacon!"
bc
bphwc
 
// Set a breakpoint on the WinHttpReadData call
bp WinHttpReadData
goto winhttpreaddata_routine
 
// Read the received task
winhttpreaddata_routine:
erun
sti
log "Number of bytes to read: [{R8}]"
$received_task = RDX
rtr
log "Received: {ascii;@$received_task}"
pause
jmp winhttpreaddata_routine
 
error:
msg "Could not find call rbp pattern!"
ret

The first time running this script will present you with the beacon register process, this can be seen by the md5 that is given back as its ID:

When the beacon is just listening for tasks, you will observe the 4096 value, AKA 0x1000. If we execute a command like whoami in the C2 console, we’ll see it will receive a different task number, and a blob of base64 encoded data:

Conclusion part 2

So now we know how the beacon registers itself at the C2, what the first check-in looks like, and how the beacon receives its tasks, including where to read out the received buffer.

In the next part we will continue with the different commands it can actually receive and how this routine looks in the disassembly.

Thanks for reading and keep tuned for part 3

Last updated