Formatting BOF Output
The beacon format API allows you to modify how beacon returns data to the user to suit the users Need. Data returned in a loop is an obvious example and use-case for this API.
Without the BeaconFormat API, beacon will send the output back to you every time you use the BeaconPrintf API call. This could lead to formatting that is less than ideal.
The best way to illustrate the problem is by using some examples.
Example - Simple counting BOF using a loop:
#include <windows.h>
#include "beacon.h"
#include "bofdefs.h"
void LoopExample()
{
int i;
for(i=0;i<11;i++)
{
BeaconPrintf(CALLBACK_OUTPUT,"counter is currently at %i",i);
}
}
void go(char * args, int len) {
LoopExample();
}
When the code is executed, you should see the following result:
figure 70 - Example 1 Output
As expected, the output is served back in chunks, displaying spacing in between even though a new line character was not specified because BeaconPrintf automatically adds a new line for you.
If you modify the Beacon Object File to use the Beacon Format API instead, you can gain more control over what the output looks like with following steps:
- First, allocate memory to format the output.
- Once the buffer is allocated and there is a pointer to the buffer, append to the buffer using the append APIs like BeaconFormatAppend, BeaconFormatint and BeaconFormatPrintf.
- When satisfied with the buffer, print it out using BeaconFormatToString
- Afterwards, you can either reuse the buffer for additional operations using BeaconFormatReset or, if you are done with it, free up the allocated memory using BeaconFormatFree.
Example - Using this approach in the counting BOF
#include <windows.h>
#include "beacon.h"
#include "bofdefs.h"
void LoopExampleWithFormatting()
{
//1. create the new buffer pointer
formatp buffer;
//2. allocate memory to hold the formatted data
BeaconFormatAlloc(&buffer,1024);
int i;
for(i=0;i<11;i++)
{
//3. instead of printing, we will now fill the buffer - notice the new line character!
BeaconFormatPrintf(&buffer, "counter is currently at: %i\n",i);
}
//4. now that we have our filled up buffer, let's print it out
BeaconPrintf(CALLBACK_OUTPUT,"%s\n",BeaconFormatToString(&buffer,NULL));
//5. time to free up the buffer
BeaconFormatFree(&buffer);
}
void LoopExample()
{
int i;
for(i=0;i<11;i++)
{
BeaconPrintf(CALLBACK_OUTPUT,"counter is currently at %i",i);
}
}
void go(char * args, int len) {
LoopExampleWithFormatting();
}
When the code is executed, you should see the following result:
Example - Read the virtual memory of the current process
#include <windows.h>
#include "beacon.h"
#include "bofdefs.h"
HMODULE GetModHandle(LPCSTR module)
{
HMODULE hModule = KERNEL32$GetModuleHandleA(module);
return hModule ? hModule : KERNEL32$LoadLibraryA(module);
}
LPVOID GetMemptr(LPCSTR module, LPCSTR function)
{
HMODULE hModule = GetModHandle(module);
LPVOID memPtr = KERNEL32$GetProcAddress(hModule,function);
return memPtr? memPtr : NULL;
}
//format options: 1 decompile format, any other number - raw opcodes
void ReadvirtualMemory(LPCSTR module, LPCSTR function,int size, int format)
{
LPVOID memPtr = GetMemptr(module,function);
if(!memPtr)
{
BeaconPrintf(CALLBACK_ERROR,"no memptr found\n");
return;
}
else
{
formatp buffer;
BeaconFormatAlloc(&buffer,1024);
BYTE *readbuffer = (BYTE*)MSVCRT$malloc(size);
SIZE_T bytesread = 0;
KERNEL32$ReadProcessMemory((HANDLE)-1,memPtr,readbuffer,size,&bytesread);
BeaconFormatPrintf(&buffer, "showing the first %i opcodes of %s!%s\n",size,module,function);
for(int i = 0; i < size; i++)
{
if(format == 1)
{
BeaconFormatPrintf(&buffer,"\\x%02X",readbuffer[i]);
}
else
{
BeaconFormatPrintf(&buffer,"%02X",readbuffer[i]);
}
}
BeaconPrintf(CALLBACK_OUTPUT,"%s\n",BeaconFormatToString(&buffer,NULL));
BeaconFormatFree(&buffer);
MSVCRT$free(readbuffer);
}
}
void go(char * args, int len) {
char* module;
char* function;
int size;
int format;
datap parser;
BeaconDataParse(&parser, args, len);
module = BeaconDataExtract(&parser,NULL);
function = BeaconDataExtract(&parser,NULL);
size = BeaconDataInt(&parser);
format = BeaconDataInt(&parser);
ReadvirtualMemory(module, function, size, format);
}
In this BOF, users have the option to read an arbitrary number of bytes of a function within the current process and display it in specific formats. Using the BeaconFormatAPI, this becomes trivial to do.
For example, you can display bytes as follows:
This makes it easy to copy paste the output and put it in a decompiler like so:
Others would rather have all the bytes right next to each other like so: