NASM Shell++
NASM Shell++
This tool enhance the power of NASM Shell (Metasploit Framework) to save some precious time while building your exploits.
Just enter your bunch of assembly instructions and generate your final payload (compatible with Python / C / CPP).
Supports bad characters detection.
Help Menu
:help : Display help menu (This menu).
:clear : Clear Terminal Screen.
:exit : Terminate application.
:badchars <badchars> : Set characters you want to avoid in your shellcode. Highlight them in red. (Ex: "\x01\x02\x03")
:ls_badchars : List bad characters.
:assembly : Dump saved assembly instructions (current session).
:shellcode : Output shellcode version from assembly instructions (Python / C / CPP Formated String)
:pyvar <var_name> : Output shellcode version from assembly instructions to python formated variable.
:dlast : Remove latest saved instruction.
:delete : Delete saved instruction at defined index.
:update : Update saved instruction at defined index.
:reset : Delete saved instructions. Reset/Clear instruction buffer (/!\ Can't be undo).
:load <filename> : Load assembly file from disk.
:r : Read Only. Instructions are not saved to instruction buffer.
:w : Write mode. Instructions are saved to instruction buffer.
:! <command> : Execute shell command. Ex: ":!ls -ltr /var/log"
Code
#!/usr/bin/python3
'''
Jean-Pierre LESUEUR (@DarkCoderSc)
https://www.twitter.com/darkcodersc
https://github.com/DarkCoderSc
https://www.linkedin.com/in/jlesueur/
https://www.phrozen.io/
Version: 1.0
Description:
Impove Metasploit Nasm Shell Tool for Exploit Development Purpose.
TODO:
- Support Arrow Keys.
- Cursor to insert instruction at given position.
- Exploit templating
- Update opcode in Instruction() class to be bytearray instead of string.
Then update whole code to support opcode as bytearray instead of string.
This would avoid bloating code with fastidious stuff.
'''
import subprocess
import pexpect
import sys
import os.path
from textwrap import wrap
from collections import defaultdict
from os import path
nasm_shell_location = "/usr/bin/msf-nasm_shell" # Update the location of your Nasm Shell if required!
ro = False
proc = None
instructions = list()
commands = list()
badchars = bytearray([0x00]) # Default
'''
-------------------------------------------------------------------------------------------------------
Command Class
-------------------------------------------------------------------------------------------------------
'''
class Command:
def __init__(self, name, description, argument = "", optional_arg = False):
self.name = name
self.description = description
self.argument = argument
self.optional_arg = optional_arg
'''
-------------------------------------------------------------------------------------------------------
Terminal Colors Class
-------------------------------------------------------------------------------------------------------
'''
class tcolors:
clear = "\033[0m"
green = "\033[32m"
red = "\033[31m"
yellow = "\033[33m"
blue = "\033[34m"
gray = "\033[90m"
'''
-------------------------------------------------------------------------------------------------------
Define Custom Loggers (Template)
-------------------------------------------------------------------------------------------------------
'''
def success(message):
print(f"[\033[32m✓\033[39m] {message}")
def error(message):
print(f"\033[31m{message}\033[39m")
def warning(message):
print(f"\033[33m{message}\033[39m")
'''
-------------------------------------------------------------------------------------------------------
Instruction Class
-------------------------------------------------------------------------------------------------------
'''
class Instruction:
def __init__(self, asm, opcodes):
self.asm = asm
self.opcodes = opcodes
self.update_size()
def update_size(self):
self.size = int(len(self.opcodes) / 4)
'''
-------------------------------------------------------------------------------------------------------
Get Instruction Object from Index
-------------------------------------------------------------------------------------------------------
'''
def get_instruction(index):
if (index == 0) or (index > (len(instructions))):
error("Index is out of bound.")
return None
index -= 1
return instructions[index]
'''
-------------------------------------------------------------------------------------------------------
Delete Buffer Instruction at Index.
-------------------------------------------------------------------------------------------------------
'''
def delete_instruction(index):
obj = get_instruction(index)
if (obj == None):
return False
index -= 1
instructions.pop(index)
success(f"Instruction at index N°{index+1} \"{tcolors.blue}{obj.asm}{tcolors.clear}\" was successfully deleted.")
return True
'''
-------------------------------------------------------------------------------------------------------
Return Shellcode Size
-------------------------------------------------------------------------------------------------------
'''
def get_shellcode_size():
size = 0
for obj in instructions:
size += obj.size
return size
'''
-------------------------------------------------------------------------------------------------------
Byte String to Byte Array (Ex: \\x00\\x01\\x02)
//
Byte Array to Byte String
-------------------------------------------------------------------------------------------------------
'''
def bytestr_to_bytearr(data):
return list(bytearray.fromhex(data.replace("\\x", " ")))
def bytearr_to_bytestr(data):
return ''.join(f"\\x{'{:02x}'.format(x)}" for x in data)
'''
-------------------------------------------------------------------------------------------------------
Perform assembly translation through NASM Shell and insert if we are in writable mode in Instruction
Buffer (or update).
-------------------------------------------------------------------------------------------------------
'''
def process_nasm_shell(instruction, update_index = -1):
instruction = instruction.strip()
if not instruction:
return False
# Ignore line comments
if instruction[0:1] == ";":
return False
proc.sendline(instruction)
proc.expect(expected_token)
output = proc.before.decode("utf-8").rstrip()
lines = output.split("\n")
# Handle NASM Shell Errors
if "warning:" in output:
lines.pop(0)
output = "\n".join(lines)
warning(output)
return False
if "Error:" in output:
lines.pop(0)
output = "\n".join(lines)
error(output)
return False
# Retrieve generated opcodes
opcodes = wrap(lines[1][10:].split(' ')[0], 2)
formated_opcodes = ""
for opcode in opcodes:
formated_opcodes += f"\\x{opcode}"
# Update or Insert new instruction in instruction buffer
obj = None
if (update_index != -1):
update_index -= 1
obj = instructions[update_index]
obj.asm = instruction
obj.opcodes = formated_opcodes
obj.update_size()
else:
obj = Instruction(instruction, formated_opcodes)
if not ro:
instructions.append(obj)
# Pretty Message (Ack)
if not ro or (update_index != -1):
prefix = f"{tcolors.green}+{tcolors.clear}"
else:
prefix = f"{tcolors.yellow}!{tcolors.clear}"
print(f"[{prefix}] {tcolors.blue}{obj.asm}{tcolors.clear} ({tcolors.blue}{obj.opcodes}{tcolors.clear}), Size: {tcolors.blue}{obj.size}{tcolors.clear} Bytes")
return True
'''
-------------------------------------------------------------------------------------------------------
Return full shellcode from instruction buffer (formated string)
-------------------------------------------------------------------------------------------------------
'''
def get_shellcode():
output = ""
for obj in instructions:
output += obj.opcodes
return output
'''
-------------------------------------------------------------------------------------------------------
Dump data to Terminal
-------------------------------------------------------------------------------------------------------
'''
def dump(data):
print('\n--- BEGIN DUMP ---')
print(data.strip())
print('--- END DUMP ---\n')
print(f"Shellcode Size: {tcolors.blue}{get_shellcode_size()}{tcolors.clear} Bytes\n")
if badchars:
shellcode = bytestr_to_bytearr(get_shellcode())
badchars_found = bytearray()
for b in shellcode:
if b in badchars:
badchars_found.append(b)
if len(badchars_found) > 0:
print(f"{tcolors.yellow}Warning:{tcolors.clear} {len(badchars_found)} bad characters found: \"{tcolors.red}{bytearr_to_bytestr(badchars_found)}{tcolors.clear}\"\n")
'''
-------------------------------------------------------------------------------------------------------
Return Assembly Instruction Buffer (Numbered or not)
-------------------------------------------------------------------------------------------------------
'''
def get_assembly_instructions(numbered = False):
output = ""
for index, instruction in enumerate(instructions):
if numbered:
output += f"{index+1} - "
output += f"{instruction.asm}\n"
return output.strip()
'''
-------------------------------------------------------------------------------------------------------
@method: Display Help Menu
-------------------------------------------------------------------------------------------------------
'''
def m_help():
print(f'\nShellcode Helper ({tcolors.blue}@DarkCoderSc{tcolors.clear})\n')
cmd_max_length = 0
for command in commands:
if len(command.name) > cmd_max_length:
cmd_max_length = len(command.name)
arg_max_length = 0
for command in commands:
if len(command.argument) > arg_max_length:
arg_max_length = len(command.argument)
cmd_max_length += 1
arg_max_length += 3 # < >
for command in commands:
argument = ""
if command.argument:
argument = f" <{command.argument}>"
formated_name = f"{command.name}".ljust(cmd_max_length, " ")
formated_name += " "
formated_name += f"{argument}".ljust(arg_max_length, " ")
formated_name += " "
print(f"\t{tcolors.blue}:{formated_name}{tcolors.clear} : {command.description}")
print("\n")
'''
-------------------------------------------------------------------------------------------------------
@method: Terminate Program (Exit)
-------------------------------------------------------------------------------------------------------
'''
def m_exit():
if len(instructions) > 0:
print(f"You have {tcolors.blue}{len(instructions)}{tcolors.clear} instructions in your assembly instruction buffer.)")
print(f"Do you wan''t to export to shellcode format before exiting? (Yes({tcolors.blue}y{tcolors.clear}), No({tcolors.blue}n{tcolors.clear}), Cancel({tcolors.blue}c{tcolors.clear}))")
while True:
print("\nAnswer > ", end="")
stdin = input().upper()
if stdin == "Y":
m_shellcode()
elif stdin == "N":
break
elif stdin == "C":
return
else:
print(f"{tcolors.red}Invalid Option{tcolors.clear}: Yes(y), No(n), Cancel(c).")
continue
break
if proc:
proc.close()
sys.exit(0)
'''
-------------------------------------------------------------------------------------------------------
@method: Reset Instruction Buffer
-------------------------------------------------------------------------------------------------------
'''
def m_reset():
count = len(instructions)
if count > 0:
instructions.clear()
success(f"Instruction buffer cleared. {count} instruction(s) were deleted.")
else:
warning("You don't have any instruction in your instruction buffer.")
'''
-------------------------------------------------------------------------------------------------------
@method: Dump Instruction Buffer as Shellcode Formated String (Python / C / CPP)
-------------------------------------------------------------------------------------------------------
'''
def m_shellcode():
if len(instructions) > 0:
shellcode = get_shellcode()
if not badchars:
output = f"{tcolors.blue}{shellcode}{tcolors.clear}"
else:
output = ""
for b in bytestr_to_bytearr(shellcode):
opcode = f"\\x{'{:02x}'.format(b)}"
if b in badchars:
color = tcolors.red
else:
color = tcolors.blue
output += f"{color}{opcode}{tcolors.clear}"
dump(output)
else:
warning("You don't have any instruction in your instruction buffer.")
'''
-------------------------------------------------------------------------------------------------------
@method: Dump Instruction Buffer (Assembly Instructions)
-------------------------------------------------------------------------------------------------------
'''
def m_assembly():
if len(instructions) > 0:
output = get_assembly_instructions()
if not badchars:
dump(f"{tcolors.blue}{output}{tcolors.clear}")
else:
output = ""
for instruction in instructions:
badchars_found = bytearray()
for b in bytestr_to_bytearr(instruction.opcodes):
if b in badchars:
badchars_found.append(b)
comment = ""
if len(badchars_found) > 0:
color = tcolors.red
comment = f" ; bad characters: \"{bytearr_to_bytestr(badchars_found)}\""
else:
color = tcolors.blue
output += f"{color}{instruction.asm}{comment}{tcolors.clear}\n"
dump(output)
else:
warning("You don't have any instruction in your instruction buffer.")
'''
-------------------------------------------------------------------------------------------------------
@method: Delete Last Buffer Instruction
-------------------------------------------------------------------------------------------------------
'''
def m_dlast():
delete_instruction(len(instructions))
'''
-------------------------------------------------------------------------------------------------------
@method: Delete Buffer Instruction defined by it Index
-------------------------------------------------------------------------------------------------------
'''
def m_delete():
output = get_assembly_instructions(True)
print("Instruction Buffer (Indexed):\n")
print(output)
while True:
print(f"Delete Item at Index (Cancel({tcolors.blue}c{tcolors.clear})) > ", end="")
stdin = input()
if stdin.lower() == "c":
break
if stdin.isnumeric():
if delete_instruction(int(stdin)):
break
'''
-------------------------------------------------------------------------------------------------------
@method: Set instruction buffer to Read Only mode
-------------------------------------------------------------------------------------------------------
'''
def m_r():
global ro
if ro == False:
ro = True
success("Instruction Buffer is now set to read only. Future instruction won't be saved.")
'''
-------------------------------------------------------------------------------------------------------
@method: Set instruction buffer to Writable mode
-------------------------------------------------------------------------------------------------------
'''
def m_w():
global ro
if ro == True:
ro = False
success("Instruction Buffer is now set to writable. Future instruction will be saved.")
'''
-------------------------------------------------------------------------------------------------------
@method: Clear Terminal Screen
-------------------------------------------------------------------------------------------------------
'''
def m_clear():
subprocess.call("clear", shell=True)
'''
-------------------------------------------------------------------------------------------------------
@method: Load Assembly File
-------------------------------------------------------------------------------------------------------
'''
def m_load(arg):
if not arg:
return
try:
with open(arg) as file:
lines = file.readlines()
for line in lines:
process_nasm_shell(line)
except FileNotFoundError:
error(f"Could not open file: \"{arg}\".")
'''
-------------------------------------------------------------------------------------------------------
@method: Dump assembly instruction opcode to python formated variable
-------------------------------------------------------------------------------------------------------
'''
def m_pyvar(arg):
if not arg:
return
output = f"{arg} = b\"\"\n"
opcodes = ""
for obj in instructions:
opcodes += obj.opcodes
chunks = wrap(opcodes, 64)
for chunk in chunks:
output += f"{arg} += b\"{chunk}\"\n"
print(f"\n{output}\n")
'''
-------------------------------------------------------------------------------------------------------
@method: Update Instruction from Instruction Buffer
-------------------------------------------------------------------------------------------------------
'''
def m_update():
output = get_assembly_instructions(True)
print("Instruction Buffer (Indexed):\n")
print(output)
while True:
print(f"Update Item at Index (Cancel({tcolors.blue}c{tcolors.clear})) > ", end="")
stdin = input()
if stdin.lower() == "c":
break
if stdin.isnumeric():
index = int(stdin)
obj = get_instruction(index)
if (obj == None):
return False
while True:
print(f"Replace \"{tcolors.blue}{obj.asm}{tcolors.clear}\" with (Cancel({tcolors.blue}c{tcolors.clear})) > ", end="")
stdin = input()
if stdin.lower() == "c":
break
if process_nasm_shell(stdin, index):
break
break
'''
-------------------------------------------------------------------------------------------------------
@method: Execute Shell Commands
-------------------------------------------------------------------------------------------------------
'''
def m_shell(arg):
if not arg:
return
command = arg.split()
try:
proc = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
stdout, stderr = proc.communicate()
except Exception as e:
error(f"Error while running command with error: \"{str(e)}\"")
return
if stdout:
print(f"\n{stdout.decode('utf-8')}")
if stderr:
error(f"\n{stderr.decode('utf-8')}\n")
'''
-------------------------------------------------------------------------------------------------------
@method: Set bad characters list.
-------------------------------------------------------------------------------------------------------
'''
def m_badchars(arg):
global badchars
if not arg:
badchars = None
return
try:
badchars = bytestr_to_bytearr(arg)
success(f"{len(badchars)} bad characters set (\"{tcolors.blue}{arg}{tcolors.clear}\").")
except:
error("Invalid hex string format. Ex: \"\\x00\\x01\\x02\"")
'''
-------------------------------------------------------------------------------------------------------
@method: List bad characters
-------------------------------------------------------------------------------------------------------
'''
def m_lsbadchars():
if badchars:
output = bytearr_to_bytestr(badchars)
print(f"\n{tcolors.blue}{output}{tcolors.clear}\n")
else:
warning("No bad characters are currently set.")
'''
-------------------------------------------------------------------------------------------------------
-= Entry Point =-
-------------------------------------------------------------------------------------------------------
'''
if __name__ == "__main__":
print(f"\nNASM Shell{tcolors.blue}++{tcolors.clear} v1.0")
if not path.exists(nasm_shell_location):
error(f"NASM Shell (Metasploit Framework) application not found. NASM Shell location is currently set to \"{nasm_shell_location}\".")
sys.exit(1)
#
# Define Commands
#
cmd = Command("help", "Display help menu (This menu).")
cmd.method = m_help
commands.append(cmd)
cmd = Command("clear", "Clear Terminal Screen.")
cmd.method = m_clear
commands.append(cmd)
cmd = Command("exit", "Terminate application.")
cmd.method = m_exit
commands.append(cmd)
cmd = Command("badchars", "Set characters you want to avoid in your shellcode. Highlight them in red. (Ex: \"\\x01\\x02\\x03\")", "badchars", True)
cmd.method = m_badchars
commands.append(cmd)
cmd = Command("ls_badchars", "List bad characters.")
cmd.method = m_lsbadchars
commands.append(cmd)
cmd = Command("assembly", "Dump saved assembly instructions (current session).")
cmd.method = m_assembly
commands.append(cmd)
cmd = Command("shellcode", "Output shellcode version from assembly instructions (Python / C / CPP Formated String)")
cmd.method = m_shellcode
commands.append(cmd)
cmd = Command("pyvar", "Output shellcode version from assembly instructions to python formated variable.", "var_name")
cmd.method = m_pyvar
commands.append(cmd)
cmd = Command("dlast", "Remove latest saved instruction.")
cmd.method = m_dlast
commands.append(cmd)
cmd = Command("delete", "Delete saved instruction at defined index.")
cmd.method = m_delete
commands.append(cmd)
cmd = Command("update", "Update saved instruction at defined index.")
cmd.method = m_update
commands.append(cmd)
cmd = Command("reset", f"Delete saved instructions. Reset/Clear instruction buffer ({tcolors.yellow}/!\\{tcolors.clear} Can't be undo).")
cmd.method = m_reset
commands.append(cmd)
cmd = Command("load", "Load assembly file from disk.", "filename")
cmd.method = m_load
commands.append(cmd)
cmd = Command("r", "Read Only. Instructions are not saved to instruction buffer.")
cmd.method = m_r
commands.append(cmd)
cmd = Command("w", "Write mode. Instructions are saved to instruction buffer.")
cmd.method = m_w
commands.append(cmd)
cmd = Command("!", f"Execute shell command. Ex: \":!ls -ltr /var/log\"", "command")
cmd.method = m_shell
commands.append(cmd)
#
# Start NASM Shell
#
print(f"\nEnter \"{tcolors.blue}:help{tcolors.clear}\" to display available commands.\n")
expected_token = "\\033\[1mnasm\\033\[0m >"
proc = pexpect.spawn(nasm_shell_location)
proc.expect(expected_token)
#
# Command Parser
#
while True:
if ro:
mode = f"{tcolors.yellow}read{tcolors.clear}"
else:
mode = f"{tcolors.green}write{tcolors.clear}"
print(f"nasm({mode}) > ", end='')
stdin = input()
if not stdin:
continue
# Handle NASM Shell exit (Alias)
if stdin == "exit":
stdin = ":exit"
# Process Command
if stdin[0] == ":":
found = False
for command in commands:
if not hasattr(command, "method"):
continue
if (command.method.__code__.co_argcount == 0):
if stdin[1:] == command.name:
found = True
command.method()
else:
if stdin[1:len(command.name)+1] == (command.name):
arg = stdin[len(command.name)+1:].strip()
found = True
if arg or command.optional_arg:
command.method(arg)
else:
error("This command requires an argument. Check help menu for more details.")
if not found:
error("Command not found. Check help menu for available commands.")
else:
process_nasm_shell(stdin)
Screenshots
Written the Nov. 27, 2020, 12:26 p.m. by Jean-Pierre LESUEUR
Updated: 4 months, 3 weeks ago.