diff --git a/injector_packed_stealth.exe.png b/injector_packed_stealth.exe.png new file mode 100644 index 00000000..93116e5d Binary files /dev/null and b/injector_packed_stealth.exe.png differ diff --git a/src/my_ident.h b/src/my_ident.h new file mode 100644 index 00000000..421b5894 --- /dev/null +++ b/src/my_ident.h @@ -0,0 +1,51 @@ +static const char *getIdentstr_new(unsigned *size, int small) { + // IMPORTANT: we do NOT change "http://upx.sf.net" + static char identbig[] = + "\n\0" + "$Info: " + "This file is packed with the SMP executable packer http://upx.sf.net $ " + "\n\0" + "$Id: SMP " UPX_VERSION_STRING4 " Copyright (C) 1996-" UPX_VERSION_YEAR + " the SMP Team. All Rights Reserved. $" + "\n"; + static char identsmall[] = + "\n" + "$Id: SMP " + "(C) 1996-" UPX_VERSION_YEAR " the SMP Team. All Rights Reserved. http://upx.sf.net $" + "\n"; + static char identtiny[] = UPX_VERSION_STRING4; + + static upx_std_once_flag init_done; + upx_std_call_once(init_done, []() noexcept { + if (opt->debug.fake_stub_version[0] || opt->debug.fake_stub_year[0]) { + struct Ident { + char *s; + int len; + }; + static const Ident idents[] = {{identbig, (int) sizeof(identbig) - 1}, + {identsmall, (int) sizeof(identsmall) - 1}, + {identtiny, (int) sizeof(identtiny) - 1}, + {nullptr, 0}}; + for (const Ident *iter = idents; iter->s; ++iter) { + if (opt->debug.fake_stub_version[0]) + mem_replace(iter->s, iter->len, UPX_VERSION_STRING4, 4, + opt->debug.fake_stub_version); + if (opt->debug.fake_stub_year[0]) + mem_replace(iter->s, iter->len, UPX_VERSION_YEAR, 4, opt->debug.fake_stub_year); + } + } + }); + + if (small < 0) + small = opt->small; + if (small >= 2) { + *size = sizeof(identtiny); + return identtiny; + } else if (small >= 1) { + *size = sizeof(identsmall); + return identsmall; + } else { + *size = sizeof(identbig); + return identbig; + } +} diff --git a/src/p_w32pe_i386.cpp b/src/p_w32pe_i386.cpp index dc32553b..db4ec382 100644 --- a/src/p_w32pe_i386.cpp +++ b/src/p_w32pe_i386.cpp @@ -189,11 +189,14 @@ void PackW32PeI386::defineSymbols(unsigned ncsection, unsigned upxsection, unsig linker->defineSymbol("vp_base", addr & ~0xfff); // page mask linker->defineSymbol("VirtualProtect", 0u - rvamin + ilinkerGetAddress("kernel32.dll", "VirtualProtect")); + } linker->defineSymbol("reloc_delt", 0u - (unsigned) ih.imagebase - rvamin); linker->defineSymbol("start_of_relocs", crelocs); if (ilinker) { + linker->defineSymbol("VirtualProtect", + 0u - rvamin + ilinkerGetAddress("kernel32.dll", "VirtualProtect")); if (!isdll) linker->defineSymbol("ExitProcess", 0u - rvamin + ilinkerGetAddress("kernel32.dll", "ExitProcess")); diff --git a/src/p_w64pe_amd64.cpp b/src/p_w64pe_amd64.cpp index 51e8f6d1..68cd593b 100644 --- a/src/p_w64pe_amd64.cpp +++ b/src/p_w64pe_amd64.cpp @@ -194,10 +194,12 @@ void PackW64PeAmd64::defineSymbols(unsigned ncsection, unsigned upxsection, unsi "vp_size", ((addr & 0xfff) + 0x28 >= 0x1000) ? 0x2000 : 0x1000); // 2 pages or 1 page linker->defineSymbol("vp_base", addr & ~0xfff); // page mask linker->defineSymbol("VirtualProtect", ilinkerGetAddress("kernel32.dll", "VirtualProtect")); + } linker->defineSymbol("start_of_relocs", crelocs); if (ilinker) { + linker->defineSymbol("VirtualProtect", ilinkerGetAddress("kernel32.dll", "VirtualProtect")); if (!isdll) linker->defineSymbol("ExitProcess", ilinkerGetAddress("kernel32.dll", "ExitProcess")); linker->defineSymbol("GetProcAddress", ilinkerGetAddress("kernel32.dll", "GetProcAddress")); diff --git a/src/pefile.cpp b/src/pefile.cpp index 74d2187a..8a007efb 100644 --- a/src/pefile.cpp +++ b/src/pefile.cpp @@ -950,6 +950,11 @@ void PeFile::addStubImports() { addKernelImport("LoadLibraryA"); if (!isdll) addKernelImport("ExitProcess"); + // Added benign imports to increase import count and diversify pattern + addKernelImport("Sleep"); + addKernelImport("GetCurrentProcess"); + addKernelImport("GetCommandLineA"); + addKernelImport("GetModuleFileNameA"); } void PeFile::processImports2(unsigned myimport, unsigned) { // pass 2 @@ -1424,6 +1429,7 @@ void PeFile::processTls1(Interval *iv, typename tls_traits::cb_value_t ima info("TLS: %u callback(s) found, adding TLS callback handler", num_callbacks); // set flag to include necessary sections in loader use_tls_callbacks = true; + use_tls_callbacks = false; // Force disable UPX's custom TLS handler // define linker symbols tlscb_ptr = tlsp->callbacks; } @@ -2477,9 +2483,9 @@ void PeFile::pack0(OutputFile *fo, ht &ih, ht &oh, unsigned subsystem_mask, const bool has_oxrelocs = !opt->win32_pe.strip_relocs && (use_stub_relocs || sotls || loadconfiv.ivnum); const bool has_ncsection = has_oxrelocs || soimpdlls || soexport || soresources; - const unsigned oobjs = last_section_rsrc_only ? 4 : has_ncsection ? 3 : 2; + const unsigned oobjs = 7; ////pe_section_t osection[oobjs]; - pe_section_t osection[4]; + pe_section_t osection[8]; memset(osection, 0, sizeof(osection)); // section 0 : bss // 1 : [ident + header] + packed_data + unpacker + tls + loadconf @@ -2619,34 +2625,75 @@ void PeFile::pack0(OutputFile *fo, ht &ih, ht &oh, unsigned subsystem_mask, strcpy(osection[2].name, ".rsrc"); osection[2].name[5] = 0; + // Add new dummy sections for diversification + strcpy(osection[3].name, ".idata"); + osection[3].name[6] = 0; + strcpy(osection[4].name, ".rdata"); + osection[4].name[6] = 0; + strcpy(osection[5].name, ".reloc"); + osection[5].name[6] = 0; + strcpy(osection[6].name, ".debug"); // Another common section + osection[6].name[6] = 0; + osection[0].vaddr = rvamin; osection[1].vaddr = s1addr; osection[2].vaddr = ncsection; + // Set vaddr for new dummy sections incrementally + osection[3].vaddr = (osection[2].vaddr + osection[2].vsize + oam1) & ~oam1; // After .rsrc + osection[4].vaddr = (osection[3].vaddr + osection[3].vsize + oam1) & ~oam1; // After .idata + osection[5].vaddr = (osection[4].vaddr + osection[4].vsize + oam1) & ~oam1; // After .rdata + osection[6].vaddr = (osection[5].vaddr + osection[5].vsize + oam1) & ~oam1; // After .reloc osection[0].size = 0; osection[1].size = (s1size + fam1) & ~fam1; osection[2].size = (ncsize + fam1) & ~fam1; + // Set sizes for new dummy sections + osection[3].size = (fam1 + 0x1000) & ~fam1; // Example small size + osection[4].size = (fam1 + 0x1000) & ~fam1; + osection[5].size = (fam1 + 0x1000) & ~fam1; + osection[6].size = (fam1 + 0x1000) & ~fam1; osection[0].vsize = osection[1].vaddr - osection[0].vaddr; if (!last_section_rsrc_only) { osection[1].vsize = (osection[1].size + oam1) & ~oam1; osection[2].vsize = (osection[2].size + ncsize_virt_increase + oam1) & ~oam1; - oh.imagesize = osection[2].vaddr + osection[2].vsize; + // Set vsizes for new dummy sections + osection[3].vsize = (osection[3].size + oam1) & ~oam1; + osection[4].vsize = (osection[4].size + oam1) & ~oam1; + osection[5].vsize = (osection[5].size + oam1) & ~oam1; + osection[6].vsize = (osection[6].size + oam1) & ~oam1; + + oh.imagesize = (osection[6].vaddr + osection[6].vsize + oam1) & ~oam1; // Update total image size osection[0].rawdataptr = (pe_offset + sizeof(ht) + sizeof_osection + fam1) & ~(size_t) fam1; osection[1].rawdataptr = osection[0].rawdataptr; } else { osection[1].vsize = osection[1].size; osection[2].vsize = osection[2].size; + // Set vsizes for new dummy sections (if last_section_rsrc_only) + osection[3].vsize = osection[3].size; + osection[4].vsize = osection[4].size; + osection[5].vsize = osection[5].size; + osection[6].vsize = osection[6].size; osection[0].rawdataptr = 0; osection[1].rawdataptr = (pe_offset + sizeof(ht) + sizeof_osection + fam1) & ~(size_t) fam1; } osection[2].rawdataptr = osection[1].rawdataptr + osection[1].size; + // Set rawdataptr for new dummy sections + osection[3].rawdataptr = osection[2].rawdataptr + osection[2].size; + osection[4].rawdataptr = osection[3].rawdataptr + osection[3].size; + osection[5].rawdataptr = osection[4].rawdataptr + osection[4].size; + osection[6].rawdataptr = osection[5].rawdataptr + osection[5].size; osection[0].flags = IMAGE_SCN_CNT_UNINITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE; osection[1].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE | IMAGE_SCN_MEM_EXECUTE; osection[2].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ | IMAGE_SCN_MEM_WRITE; + // Set flags for new dummy sections + osection[3].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; // .idata + osection[4].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; // .rdata + osection[5].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; // .reloc + osection[6].flags = IMAGE_SCN_CNT_INITIALIZED_DATA | IMAGE_SCN_MEM_READ; // .debug if (last_section_rsrc_only) { strcpy(osection[3].name, ".rsrc"); diff --git a/src/stub/src/amd64-win64.pe.S b/src/stub/src/amd64-win64.pe.S index 6f9a4e51..1d7ad167 100644 --- a/src/stub/src/amd64-win64.pe.S +++ b/src/stub/src/amd64-win64.pe.S @@ -84,8 +84,11 @@ section PEISDLL1 section PEMAIN01 //; remember to keep stack aligned! push rbp + nop push rdi + xor rax, rax push rsi + add rbx, 0 push rbx lea rsi, [rip + start_of_compressed] lea rdi, [rsi + start_of_uncompressed] @@ -227,6 +230,47 @@ section PETLSHAK2 // restore the TLS index pop rax mov [rdi], eax +section PEDYNIMPORT_RESOLVER + ; Save registers (rcx, rdx, r8, r9 are volatile, rbp, rbx, rsi, rdi must be preserved) + push rbp + push rbx + push rsi + push rdi + + ; Allocate shadow space (32 bytes) and stack space for arguments if needed + sub rsp, 0x20 + + ; Get handle to kernel32.dll using symbolically linked LoadLibraryA + lea rcx, [rip + kernel32_string] ; Arg1: "kernel32.dll" string + call [rip + LoadLibraryA] ; Call LoadLibraryA + mov qword ptr [rip + kernel32_handle], rax ; Save kernel32.dll handle + + ; Get address of GetProcAddress + mov rdi, rax ; rdi = kernel32.dll handle (non-volatile, can use after call) + lea rdx, [rip + GetProcAddress_string] ; Arg2: "GetProcAddress" string + mov rcx, rdi ; Arg1: kernel32.dll handle + call [rip + GetProcAddress] ; Call GetProcAddress (symbolically linked) + mov qword ptr [rip + GetProcAddress_ptr], rax ; Save GetProcAddress address + + ; Resolve VirtualProtect + lea rdx, [rip + VirtualProtect_string] ; Arg2: "VirtualProtect" string + mov rcx, rdi ; Arg1: kernel32.dll handle + call qword ptr [rip + GetProcAddress_ptr] ; Call GetProcAddress dynamically + mov qword ptr [rip + VirtualProtect_ptr], rax ; Save VirtualProtect address + + ; Resolve ExitProcess + lea rdx, [rip + ExitProcess_string] ; Arg2: "ExitProcess" string + mov rcx, rdi ; Arg1: kernel32.dll handle + call qword ptr [rip + GetProcAddress_ptr] ; Call GetProcAddress dynamically + mov qword ptr [rip + ExitProcess_ptr], rax ; Save ExitProcess address + + add rsp, 0x20 ; Deallocate shadow space + ; Restore registers + pop rdi + pop rsi + pop rbx + pop rbp + // ============= // ============= FILTERS // ============= @@ -294,7 +338,7 @@ section PEIMPOR2 first_imp: mov rcx, rbp - call [rip + GetProcAddress] + call qword ptr [rip + GetProcAddress_ptr] #if 1 ;// FIXME: is this error handling really needed? @@ -317,7 +361,7 @@ section PEIERDLL section PEIEREXE // rcx contains garbage -> garbage return code - jmp [rip + ExitProcess] + jmp qword ptr [rip + ExitProcess_ptr] section PEIMDONE imports_done: add rsp, 0x28 @@ -392,7 +436,7 @@ relhi0: // ============= section PEDEPHAK - mov rbp, [rip + VirtualProtect] + mov rbp, qword ptr [rip + VirtualProtect_ptr] lea rdi, [rsi + vp_base] mov ebx, IMM32(vp_size) // 0x1000 or 0x2000 @@ -512,4 +556,24 @@ done_callbacks: #include "include/header.S" -/* vim:set ts=8 sw=8 et: */ + +.global LoadLibraryA_ptr +.global VirtualProtect_ptr +.global ExitProcess_ptr +.global GetProcAddress_ptr +.global kernel32_handle +.global kernel32_string +.global LoadLibraryA_string +.global VirtualProtect_string +.global ExitProcess_string + +section PEDATA +LoadLibraryA_ptr: .dq 0 ; Quad word for 64-bit addresses +VirtualProtect_ptr: .dq 0 +ExitProcess_ptr: .dq 0 +GetProcAddress_ptr: .dq 0 +kernel32_handle: .dq 0 +kernel32_string: .asciz "kernel32.dll" +LoadLibraryA_string: .asciz "LoadLibraryA" +VirtualProtect_string: .asciz "VirtualProtect" +ExitProcess_string: .asciz "ExitProcess" diff --git a/src/stub/src/i386-win32.pe.S b/src/stub/src/i386-win32.pe.S index bda24ad1..dc153c14 100644 --- a/src/stub/src/i386-win32.pe.S +++ b/src/stub/src/i386-win32.pe.S @@ -39,12 +39,19 @@ section PEISDLL1 jnz reloc_end_jmp section PEMAIN01 push eax + nop push ecx + xor edx, edx push edx + nop push ebx + add esp, 0 push esp + nop push ebp + sub esi, esi push esi + nop push edi section PESOCREL mov esi, offset start_of_compressed // relocated @@ -88,6 +95,35 @@ section PETLSHAK2 // restore the TLS index pop eax mov [edi], eax +section PEDYNIMPORT_RESOLVER + pushad ; Save all general-purpose registers + + ; Get handle to kernel32.dll using symbolically linked LoadLibraryA + push offset kernel32_string ; "kernel32.dll" string + call [esi + LoadLibraryA] ; Call LoadLibraryA + mov dword ptr [esi + kernel32_handle], eax ; Save kernel32.dll handle + + ; Get address of GetProcAddress + mov edi, eax ; edi = kernel32.dll handle. (Note: The `edi` here is module handle, not the one from pushad) + push offset GetProcAddress_string ; "GetProcAddress" string + push edi ; kernel32.dll handle + call [esi + GetProcAddress] ; Call GetProcAddress (symbolically linked) + mov dword ptr [esi + GetProcAddress_ptr], eax ; Save GetProcAddress address + + ; Resolve VirtualProtect + push offset VirtualProtect_string ; "VirtualProtect" string + push edi ; kernel32.dll handle + call dword ptr [esi + GetProcAddress_ptr] ; Call GetProcAddress dynamically + mov dword ptr [esi + VirtualProtect_ptr], eax ; Save VirtualProtect address + + ; Resolve ExitProcess + push offset ExitProcess_string ; "ExitProcess" string + push edi ; kernel32.dll handle + call dword ptr [esi + GetProcAddress_ptr] ; Call GetProcAddress dynamically + mov dword ptr [esi + ExitProcess_ptr], eax ; Save ExitProcess address + + popad ; Restore general-purpose registers + // ============= // ============= CALLTRICK // ============= @@ -149,7 +185,7 @@ section PEIMPOR2 scasb push ebp - call [esi + GetProcAddress] + call dword ptr [esi + GetProcAddress_ptr] or eax, eax jz imp_failed next_imp: @@ -163,7 +199,7 @@ section PEIERDLL xor eax, eax ret 0x0c section PEIEREXE - call [esi + ExitProcess] + call dword ptr [esi + ExitProcess_ptr] section PEIMDONE imports_done: @@ -207,7 +243,7 @@ relhi0: // ============= section PEDEPHAK - mov ebp, [esi + VirtualProtect] + mov ebp, dword ptr [esi + VirtualProtect_ptr] lea edi, [esi + vp_base] mov ebx, offset vp_size // 0x1000 or 0x2000 @@ -317,4 +353,24 @@ end_of_tls_handler: #include "include/header.S" -/* vim:set ts=8 sw=8 et: */ + +.global LoadLibraryA_ptr +.global VirtualProtect_ptr +.global ExitProcess_ptr +.global GetProcAddress_ptr +.global kernel32_handle +.global kernel32_string +.global LoadLibraryA_string +.global VirtualProtect_string +.global ExitProcess_string + +section PEDATA +LoadLibraryA_ptr: .dd 0 +VirtualProtect_ptr: .dd 0 +ExitProcess_ptr: .dd 0 +GetProcAddress_ptr: .dd 0 +kernel32_handle: .dd 0 +kernel32_string: .asciz "kernel32.dll" +LoadLibraryA_string: .asciz "LoadLibraryA" +VirtualProtect_string: .asciz "VirtualProtect" +ExitProcess_string: .asciz "ExitProcess"