Files
XiaoliChan-RedCaddy/generator.py
T
2022-10-27 10:58:43 +08:00

311 lines
13 KiB
Python

import os
import sys
import argparse
import uuid
from colorama import Fore, Back, Style
from modules.redwarden_parser import MalleableParser
from modules.logger import logger
class caddy_Proxy:
def __init__(self, profile, local, chains, filename, geo_country, xf):
self.__profile = profile
# Redirect destination, only https
self.__local = local
self.__chains = chains
self.__outfile = filename
self.__country = geo_country
self.__xf_switch = xf
def wrapper(self):
# Load module and profile
test_Parser = MalleableParser(logger)
loaded = test_Parser.parse(self.__profile)
# UA
user_agent = test_Parser.config['useragent']
# Staging detection
staging = test_Parser.config['host_stage']
selection = ['http-get','http-post','http-stager']
if staging == 'false':
print("[+] Stageless")
selection.remove('http-stager')
# Grab headers & uri path & reuqest method
full_Block = ""
host_ListFinal = []
host_List = []
# Redirect chains
with open(self.__chains,'r') as f:
chains = f.readlines()
for chain in chains:
c2_backend = chain.strip("\n").split(":")[1] + "://" + chain.strip("\n").split(":")[2] + ":" + chain.strip("\n").split(":")[3]
print(Fore.GREEN + "[+] Determine redirect chain, incomming from port: %s redirect to %s:%s"%(chain.strip("\n").split(":")[0], chain.strip("\n").split(":")[2], chain.strip("\n").split(":")[3]))
# Generate reverse proxy blocks
tmp_Block = ""
for select in selection:
for variant in test_Parser.config[select].keys():
try:
uri = ''
method = ''
if select == 'http-stager':
# Stager is only supported GET request
uri = test_Parser.config['http-stager'][variant]['uri_x86'] + test_Parser.config['http-stager'][variant]['uri_x64']
method = "GET"
else:
uri = test_Parser.config[select][variant]['uri']
method = test_Parser.config[select][variant]['verb']
headers = test_Parser.config[select][variant]['client']['header']
caddy_Block = self.generate_ReverseProxy(uri, user_agent, headers, method, select, variant, c2_backend)
tmp_Block = tmp_Block + "\r\n\r\n" + caddy_Block
except:
pass
full_Block += self.generate_HanldeBlock(chain, tmp_Block)
if self.__country != None:
geoip_Block = r'''
(GEOFILTER) {
@geofilter {
not maxmind_geolocation {
db_path "./data/GeoLite2-Country.mmdb"
allow_countries REPLEACE_ME
}
not remote_ip 10.0.0.0/8
not remote_ip 172.16.0.0/12
not remote_ip 192.168.0.0/16
}
route @geofilter
{
abort
}
}
'''.replace("REPLEACE_ME",self.__country)
full_Block = geoip_Block + full_Block
self.generate_Caddyfile(full_Block)
self.tips(chains)
def generate_ReverseProxy(self, uri, user_agent, headers, method, select, variant, c2_backend):
host_Header = ""
matcher_Name = "@matcher_%s "%(select + "_" + variant + "_" + str(c2_backend.split(":")[2]))
reverse_proxy_Partly = r''' {
# Enable this when using serverless cloud function, like tencent scf, aliyun scf.
#scf header_up X-Forwarded-For {http.request.header.X-Forwarded-For}
# Enable this when using VPS directly.
#noscf header_up X-Forwarded-For {remote_host}
header_up Host {http.reverse_proxy.upstream.hostport}
transport http {
tls
tls_insecure_skip_verify
}
}'''
method = "method " + method
format_URI = "path"
# Add user agent first
format_Headers = 'header \"user-agent\" \"%s\"'%user_agent + "\r\n\t"
for i in uri:
format_URI = format_URI + " " + i
for i in headers:
format_Headers = format_Headers + "header" + ' \"%s\"'%i[0] + ' \"%s\"'%i[1] + "\r\n\t"
matcher_tmp = "{" + "\r\n\t" + format_URI + "\r\n\t" + method + "\r\n\t" + format_Headers.strip("\t") + "}"
matcher_Full = matcher_Name + matcher_tmp
if self.__xf_switch == True:
print("[+] SCF feature: Selecting x-forwarded-for header ip as source ip addr")
reverse_proxy_Partly = reverse_proxy_Partly.replace(r"#scf ",'')
else:
reverse_proxy_Partly = reverse_proxy_Partly.replace(r"#noscf ",'')
reverse_proxy = "reverse_proxy " + matcher_Name + '\"%s\"'%c2_backend + reverse_proxy_Partly
full = matcher_Full + "\r\n" + reverse_proxy + "\r\n"
return full
def generate_HanldeBlock(self, chain, proxyBlock):
tag = "(caddy-guard-%s)"%str(chain.strip("\n").split(":")[3])
template_Header = r''' {
# For old windows version support, like: win7
tls ./localhost.crt ./localhost.key {
ciphers TLS_RSA_WITH_AES_128_CBC_SHA TLS_RSA_WITH_AES_256_CBC_SHA TLS_RSA_WITH_AES_128_GCM_SHA256 TLS_RSA_WITH_AES_256_GCM_SHA384 TLS_AES_128_GCM_SHA256 TLS_AES_256_GCM_SHA384 TLS_CHACHA20_POLY1305_SHA256 TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256 TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384 TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256 TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256
}
import basic-security
handle /* {
import basic-blacklist
# GEO_IMPORT
'''
template_End = r'''
}
}
'''
if self.__country != None:
template_Header = template_Header.replace(r"# GEO_IMPORT","import GEOFILTER")
basic_Listen = ":%s "%chain.strip("\n").split(":")[0]
handle_Block = basic_Listen + "{ import %s }\r\n"%tag.replace("(",'').replace(")",'')
# For TeamServer port warden
try:
if chain.strip("\n").split(":")[4] == "warden":
print(Fore.YELLOW + "[+] Teamserver warden handle on port %s"%chain.strip("\n").split(":")[0])
warden_Path = uuid.uuid4().hex
securityString = uuid.uuid4().hex
teamserver_WardenBlock = r'''
@matcher_TeamserverGuard {
path /REPLEACE_A
method GET
header "user-agent" "SecurityString"
header "Accept-SecurityString" "REPLEACE_B"
}
cgi @matcher_TeamserverGuard REPLEACE_C/iptables.sh'''
teamserver_WardenBlock = teamserver_WardenBlock.replace("REPLEACE_A",warden_Path).replace("REPLEACE_B",securityString).replace("REPLEACE_C",os.getcwd())
proxyBlock = proxyBlock + teamserver_WardenBlock
# Generate new iptables.sh
print(Fore.YELLOW + "\r\n[+] Don't foget grant execute permission to iptables.sh")
print(Fore.RED + "chmod 777 ./iptables.sh\r\n")
with open("iptables-template.sh",'r') as f:
file = f.read()
with open("iptables.sh",'w') as f2:
f2.write(file.replace("REPLEACE_HERE",chain.strip("\n").split(":")[5]))
# Teamserver warden tips
windows_CurlTips = r'''
add-type @"
using System.Net;
using System.Security.Cryptography.X509Certificates;
public class TrustAllCertsPolicy : ICertificatePolicy {
public bool CheckValidationResult(
ServicePoint srvPoint, X509Certificate certificate,
WebRequest request, int certificateProblem) {
return true;
}
}
"@
[System.Net.ServicePointManager]::CertificatePolicy = New-Object TrustAllCertsPolicy
$headers = @{
"user-agent" = 'SecurityString'
"Accept-SecurityString" = 'REPLEACE_B'
}
Invoke-WebRequest https://[REPLEACE_TO_YOUR_VPS_IP]:REPLEACE_A/REPLEACE_C -Headers $headers
'''
print(Fore.RED + "[!] Execute this command on your vps server")
print(Fore.YELLOW + "sudo iptables -I INPUT -p tcp --dport %s -j DROP"%chain.strip("\n").split(":")[5])
print(Fore.GREEN + "\r\n[1] Add whitelist ip into teamserver port warden (for linux client -- bash)")
print(Fore.YELLOW + 'curl https://[REPLEACE_TO_YOUR_VPS_IP]:%s/%s -H "user-agent: SecurityString" -H "Accept-SecurityString: %s" -k -vvv '%(chain.strip("\n").split(":")[0], warden_Path, securityString))
print(Fore.GREEN + "\r\n[2] Add whitelist ip into teamserver port warden (for windows client -- powershell)")
print("-"*120 + Fore.YELLOW + windows_CurlTips.replace("REPLEACE_A",chain.strip("\n").split(":")[0]).replace("REPLEACE_B",securityString).replace("REPLEACE_C",warden_Path) + Fore.GREEN + "-"*120 + Style.RESET_ALL)
except:
pass
final = tag + template_Header + proxyBlock + template_End + handle_Block
return final
def generate_Caddyfile(self, full_Block):
caddyguard_Header = r'''{
debug
log
order tls last
auto_https off
order cgi last
}
(basic-security) {
header {
Server "Apache/2.4.50 (Unix) OpenSSL/1.1.1d"
X-Robots-Tag "noindex, nofollow, nosnippet, noarchive"
X-Content-Type-Options "nosniff"
Permissions-Policy interest-cohort=()
Strict-Transport-Security max-age=31536000;
X-Content-Type-Options nosniff
X-Frame-Options DENY
Referrer-Policy no-referrer-when-downgrade
Cache-Control no-cache
X-Powered-By
X-Page-Speed
X-Varnish
}
}
(basic-blacklist) {
@ua_denylist {
import ./data/bad-user-agents.caddy
}
@ip_denylist {
import ./data/bad-ips.caddy
}
# UA blacklist
route @ua_denylist {
abort
}
# IP blacklist
route @ip_denylist {
abort
}
}
'''
with open(self.__outfile,'w+') as f:
f.write(caddyguard_Header + full_Block)
def tips(self,chains):
print(Fore.GREEN + "[+] Formating caddyfile")
os.system("cat %s | ./caddy fmt --overwrite"%self.__outfile)
print(Fore.RED + "[!] Use iptables rules to drop C2 backend port traffic and make sure it only allow incomming traffic with upstream address")
for i in chains:
print(Fore.YELLOW + "sudo iptables -I INPUT -p tcp --dport %s -j DROP"%(i.strip("\n").split(":")[3]))
print("sudo iptables -I INPUT -s %s -p tcp --dport %s -j ACCEPT"%(self.__local, i.strip("\n").split(":")[3]))
print(Fore.RED + "\r\n[!] Run caddy with profile")
print(Fore.YELLOW + "sudo ./caddy run --config %s --adapter caddyfile"%self.__outfile)
print("\r\n[!] Reload caddy with profile")
print("sudo ./caddy reload --config %s --adapter caddyfile"%self.__outfile + Style.RESET_ALL)
if __name__ == '__main__':
parser = argparse.ArgumentParser(add_help = True, description = "Generate C2 redirection config file which is base on caddy.")
parser.add_argument('-f','-profile', metavar="c2.profile" ,action='store', help='Specify C2 malleable profile')
parser.add_argument('-l', '-local-ip', metavar="192.168.85.100" ,action='store', help='Specify machine local ip address')
parser.add_argument('-r','-redir-chain', metavar="file" ,action='store', help='Redirect chain files, format: [caddy-port]:[https]:[C2 Host]:[C2 Port] '
'E.g.: "443:https:127.0.0.1:8443:warden:50050"')
parser.add_argument('-c', '-allow-country', metavar="CN" ,action='store', help='Whitelist IP with country (detect with GEOIP database) '
'For multiple country please separated by a single space, Like: CN US')
parser.add_argument('-xf', action='store_true', help='Using x-forwarded-for header ip address as remote ip'
',the source request must include x-forwarded-for header')
parser.add_argument('-o', '-out', metavar="filename" ,action='store', help='Filename you want to save as')
if len(sys.argv)==1:
parser.print_help()
sys.exit(1)
options = parser.parse_args()
if options.f == None:
logger.err("[-] Please specify C2 malleable profile")
sys.exit(1)
if options.l == None:
logger.err("[-] Please specify machine local ip address")
sys.exit(1)
if options.r == None:
logger.err("[-] Please specify port")
sys.exit(1)
if options.o == None:
logger.err("[-] Please output output destination")
sys.exit(1)
executer = caddy_Proxy(options.f, options.l, options.r, options.o, options.c, options.xf)
executer.wrapper()