djin 0.1.1

DLL injector with support for 32-bit and 64-bit targets.
Documentation
use crate::windows_prelude::*;

#[macro_use]
mod util;

#[cfg(target_pointer_width = "32")]
#[rustfmt::skip]
const SHELLCODE: [u8; 84] = [
    0xE8, 0x3F, 0x00, 0x00, 0x00,       // call x86_getpc
    0x83, 0xEE, 0x05,                   // sub esi, 5

    0x8D, 0xBE, 0x64, 0x00, 0x00, 0x00, // lea edi, [esi + DllName]
    0x57,                               // push edi
    0xFF, 0x96, 0x54, 0x00, 0x00, 0x00, // call [esi + LoadLibraryW]
    0x85, 0xC0,                         // test eax, eax
    0x74, 0x1A,                         // jz error

    0xE8, 0x2A, 0x00, 0x00, 0x00,       // call wcslen
    0x80, 0x3F, 0x00,                   // cmp byte ptr [edi], 0
    0x74, 0x1C,                         // jz done_noinit
    0x57,                               // push edi
    0x50,                               // push eax
    0xFF, 0x96, 0x58, 0x00, 0x00, 0x00, // call [esi + GetProcAddress]
    0x85, 0xC0,                         // test eax, eax
    0x74, 0x04,                         // jz error

    0xFF, 0xD0,                         // call eax
    0xEB, 0x0E,                         // jmp done

                                        // error:
    0xFF, 0x96, 0x5C, 0x00, 0x00, 0x00, // call [esi + GetLastError]
    0x89, 0x86, 0x60, 0x00, 0x00, 0x00, // mov [esi + Error], eax
                                        // done_noinit:
    0x31, 0xC0,                         // xor eax, eax
                                        // done:
    0xC2, 0x04, 0x00,                   // ret 4

                                        // x86_getpc:
    0x8B, 0x34, 0x24,                   // mov esi, [esp]
    0xC3,                               // ret

                                        // wcslen:
    0x66, 0x83, 0x3F, 0x00,             // cmp word ptr [edi], 0
    0x8D, 0x7F, 0x02,                   // lea edi, [edi + 2]
    0x75, 0xF7,                         // jnz wcslen
    0xC3,                               // ret

    0xCC, 0xCC,                         // (padding)
];

#[cfg(target_pointer_width = "64")]
#[rustfmt::skip]
const SHELLCODE: [u8; 88] = [
    0x48, 0x8D, 0x0D, 0x71, 0x00, 0x00, 0x00, // lea rcx, [rip + DllName]
    0x51,                                     // push rcx
    0x48, 0x83, 0xEC, 0x20,                   // sub rsp, 32

    0xFF, 0x15, 0x46, 0x00, 0x00, 0x00,       // call [rip + LoadLibraryW]
    0x48, 0x8B, 0x54, 0x24, 0x20,             // mov rdx, [rsp + 32]
    0x48, 0x85, 0xC0,                         // test rax, rax
    0x74, 0x1C,                               // jz error

    0x48, 0x89, 0xC1,                         // mov rcx, rax
    0xE8, 0x29, 0x00, 0x00, 0x00,             // call wcslen
    0x80, 0x3A, 0x00,                         // cmp byte ptr [rdx], 0
    0x74, 0x1B,                               // jz done_noinit
    0xFF, 0x15, 0x31, 0x00, 0x00, 0x00,       // call [rip + GetProcAddress]
    0x48, 0x85, 0xC0,                         // test rax, rax
    0x74, 0x04,                               // jz error

    0xFF, 0xD0,                               // call rax
    0xEB, 0x0F,                               // jmp done

                                              // error:
    0xFF, 0x15, 0x2A, 0x00, 0x00, 0x00,       // call [rip + GetLastError]
    0x89, 0x05, 0x2C, 0x00, 0x00, 0x00,       // mov [rip + Error], rax

                                              // done_noinit:
    0x48, 0x31, 0xC0,                         // xor rax, rax

                                              // done:
    0x48, 0x83, 0xC4, 0x28,                   // add rsp, 40
    0xC3,                                     // ret

                                              // wcslen:
    0x66, 0x83, 0x3A, 0x00,                   // cmp word ptr [rdx], 0
    0x48, 0x8D, 0x52, 0x02,                   // lea rdx, [rdx + 2]
    0x75, 0xF6,                               // jnz wcslen
    0xC3,                                     // ret

    0xCC,                                     // (padding)
];

pub fn inject_dll(process: HANDLE, dll: &std::path::Path, function: &[u8]) -> Result<DWORD, DWORD> {
    unsafe {
        let pointers = util::get_fn_pointers();

        // Convert the DLL path to an absolute path.
        let dllstr: Vec<u16> = dll
            .canonicalize()
            .unwrap()
            .into_os_string()
            .encode_wide()
            .collect();

        let plen = std::mem::size_of::<util::Pointers>();
        let dlllen = (dllstr.len() + 1) * 2;
        let fnlen = function.len() + 1;

        // the total amount of memory needed.
        // the length of the shellcode,
        // the length of the dll string in bytes
        // the length of the symbol name in bytes
        let total_size = SHELLCODE.len() + plen + dlllen + fnlen;

        let alloc = util::alloc(process, total_size).cast::<std::ffi::c_void>();
        bail_if_null!(alloc);

        // TODO: yeet all of these into different functions
        bail_if_zero!(WriteProcessMemory(
            process,
            alloc as _,
            ptr!(SHELLCODE),
            SHELLCODE.len(),
            NULL as _
        ));

        bail_if_zero!(WriteProcessMemory(
            process,
            alloc.add(SHELLCODE.len()) as _,
            &pointers as *const _ as _,
            plen,
            NULL as _,
        ));

        bail_if_zero!(WriteProcessMemory(
            process,
            alloc.add(SHELLCODE.len() + plen) as _,
            ptr!(dllstr),
            // We don't actually have a null terminator, we just use the fact all memory is zeroed
            dlllen - 2,
            NULL as _
        ));

        // Turns out WriteProcessMemory errors if nSize is 0
        if fnlen != 1 {
            bail_if_zero!(WriteProcessMemory(
                process,
                alloc.add(SHELLCODE.len() + plen + dlllen) as _,
                ptr!(function),
                fnlen - 1,
                NULL as _
            ));
        }

        // Run the thread!
        let thread = CreateRemoteThread(
            process,
            NULL.cast(),
            0,
            std::mem::transmute(alloc),
            NULL.cast(),
            0,
            NULL.cast(),
        );
        bail_if_null!(thread);

        let result = WaitForSingleObject(thread, INFINITE);
        bail_if_nonzero!(result);

        // We can't just let the shellcode write to a pointer in our address space, I think
        // So we let it write it to some memory, and read it back out.
        let mut error: DWORD = 0;
        bail_if_zero!(ReadProcessMemory(
            process,
            alloc.add(SHELLCODE.len() + plen - std::mem::size_of::<usize>()) as _,
            &mut error as *mut _ as _,
            4,
            NULL as _
        ));

        let mut exit: DWORD = 0;
        GetExitCodeThread(thread, &mut exit as _);

        // cleanup
        // would also zero that memory we wrote, but in the case of a crash it might help
        // also no good way to call ZeroMemory on another process. would have to just allocate zeroes.
        VirtualFreeEx(process, alloc.cast(), total_size, MEM_RELEASE);
        CloseHandle(thread);

        if error == 0 {
            Ok(exit)
        } else {
            Err(error)
        }
    }
}