[
  {
    "path": ".github/workflows/build.yml",
    "content": "name: Build\n\non:\n  push:\n    branches: [master]\n\njobs:\n  build:\n    name: release ${{ matrix.target }}\n    runs-on: ubuntu-latest\n    strategy:\n      fail-fast: false\n      matrix:\n        target: [x86_64-pc-windows-gnu, x86_64-unknown-linux-musl]\n    steps:\n      - uses: actions/checkout@master\n      - name: Compile\n        id: compile\n        uses: rust-build/rust-build.action@v1.4.5\n        with:\n          RUSTTARGET: ${{ matrix.target }}\n          EXTRA_FILES: \"README.md LICENSE\"\n          UPLOAD_MODE: none\n      - name: Upload artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: binary-${{ matrix.target }}\n          path: |\n            ${{ steps.compile.outputs.BUILT_ARCHIVE }}\n            ${{ steps.compile.outputs.BUILT_CHECKSUM }}\n"
  },
  {
    "path": ".gitignore",
    "content": "/target\n/.idea"
  },
  {
    "path": "Cargo.toml",
    "content": "[package]\nname = \"dll-proxy-generator\"\nversion = \"0.1.1\"\nedition = \"2021\"\nlicense-file = \"LICENSE\"\n\n[dependencies]\nclap = { version = \"4\", features = [\"derive\"] }\ngoblin = \"0.8\"\nscroll = \"0.12\"\nscroll_derive = \"0.12\"\n"
  },
  {
    "path": "LICENSE",
    "content": "BSD Zero Clause License\n\nCopyright (c) 2024 namazso <admin@namazso.eu>\n\nPermission to use, copy, modify, and/or distribute this software for any\npurpose with or without fee is hereby granted.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH\nREGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY\nAND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,\nINDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM\nLOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR\nOTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR\nPERFORMANCE OF THIS SOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# DLL Proxy Generator\n\nGenerate a proxy dll for arbitrary dll, while also loading a user-defined secondary dll.\n\n## Usage\n\n`dll-proxy-generator.exe [OPTIONS] --import-dll <IMPORT_DLL> --import <IMPORT> <DLL>`\n\n### Arguments\n\n`<DLL>  Path to dll to proxy`\n\n### Options\n\n```\n-d, --import-dll <IMPORT_DLL>      Extra dll to import\n-i, --import <IMPORT>              Import name or ordinal\n-p, --proxy-target <PROXY_TARGET>  Target of proxy, defaults to path of same file in System32\n-o, --output <OUTPUT>              Output file\n-m, --machine <MACHINE>            COFF Machine magic. Defaults to x64's [default: 34404]\n-h, --help                         Print help\n-V, --version                      Print version\n```\n\n## Credits\n\nThanks to [@mrexodia](https://github.com/mrexodia) for the [target dll trick](https://github.com/mrexodia/perfect-dll-proxy/)\n"
  },
  {
    "path": "src/main.rs",
    "content": "use clap::Parser;\nuse goblin::pe::characteristic::{\n    IMAGE_FILE_DLL, IMAGE_FILE_EXECUTABLE_IMAGE, IMAGE_FILE_LARGE_ADDRESS_AWARE,\n};\nuse goblin::pe::data_directories::DataDirectory;\nuse goblin::pe::dll_characteristic::{\n    IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE, IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA,\n    IMAGE_DLLCHARACTERISTICS_NX_COMPAT,\n};\nuse goblin::pe::export::{ExportDirectoryTable, SIZEOF_EXPORT_DIRECTORY_TABLE};\nuse goblin::pe::header::{CoffHeader, DOS_MAGIC, PE_MAGIC};\nuse goblin::pe::import::ImportDirectoryEntry;\nuse goblin::pe::optional_header::{\n    StandardFields64, WindowsFields64, IMAGE_NT_OPTIONAL_HDR64_MAGIC,\n};\nuse goblin::pe::section_table::IMAGE_SCN_MEM_READ;\nuse goblin::pe::subsystem::IMAGE_SUBSYSTEM_WINDOWS_GUI;\nuse goblin::Object;\nuse scroll::ctx::SizeWith;\nuse scroll::{Endian, Pwrite};\nuse scroll_derive::SizeWith;\nuse std::fs;\nuse std::path::PathBuf;\n\n/// Generate a proxy dll for arbitrary dll\n#[derive(Parser, Debug)]\n#[command(version, about, long_about = None)]\nstruct Args {\n    /// Extra dll to import\n    #[arg(short = 'd', long)]\n    import_dll: String,\n\n    /// Import name or ordinal\n    #[arg(short = 'i', long)]\n    import: String,\n\n    /// Target of proxy, defaults to path of same file in System32\n    #[arg(short, long)]\n    proxy_target: Option<String>,\n\n    /// Output file\n    #[arg(short, long)]\n    output: Option<PathBuf>,\n\n    /// COFF Machine magic. Defaults to x64's.\n    #[arg(short, long, default_value = \"34404\")]\n    machine: u16,\n\n    /// Path to dll to proxy\n    dll: PathBuf,\n}\n\n#[repr(C)]\n#[derive(Debug, Pwrite, SizeWith, Default)]\npub struct SmallPE {\n    pub dos_magic: u16,\n    pub pad1: [u16; 29],\n    pub e_lfanew: u32,\n\n    pub signature: u32,\n\n    pub coff_header: CoffHeader,\n\n    pub standard_fields: StandardFields64,\n    pub windows_fields: WindowsFields64,\n\n    pub data_directories: [DataDirectory; 16],\n\n    pub sec_name: [u8; 8],\n    pub sec_virtual_size: u32,\n    pub sec_virtual_address: u32,\n    pub sec_size_of_raw_data: u32,\n    pub sec_pointer_to_raw_data: u32,\n    pub sec_pointer_to_relocations: u32,\n    pub sec_pointer_to_linenumbers: u32,\n    pub sec_number_of_relocations: u16,\n    pub sec_number_of_linenumbers: u16,\n    pub sec_characteristics: u32,\n}\n\nfn main() {\n    let args = Args::parse();\n\n    let output = args\n        .output\n        .unwrap_or_else(|| PathBuf::from(args.dll.file_name().unwrap()));\n\n    // Format of our IAT:\n    // IMAGE_IMPORT_DESCRIPTOR  descriptor for `import_dll`\n    // IMAGE_IMPORT_DESCRIPTOR  terminating null entry\n    // u64  the import entry\n    // u64  terminating entry\n    // u64  the import entry (IAT)\n    // u64  terminating entry (IAT)\n    // u8+  dll name\n    // __   pad to 2 byte\n    // u16  hint (if import by name)\n    // u8+  import name (if import by name)\n    // __   pad to 16 byte\n\n    let idata_rva = 0x1000u32;\n    let mut idata = {\n        let mut buf = Vec::<u8>::new();\n\n        // dll entry\n        let entry_offset = buf.len();\n        buf.resize(\n            buf.len() + ImportDirectoryEntry::size_with(&Endian::Little),\n            0,\n        );\n        // null entry\n        buf.resize(\n            buf.len() + ImportDirectoryEntry::size_with(&Endian::Little),\n            0,\n        );\n        // import lookup table entry\n        let ilt_offset = buf.len();\n        buf.resize(buf.len() + u64::size_with(&Endian::Little), 0);\n        // import lookup table terminating entry\n        buf.resize(buf.len() + u64::size_with(&Endian::Little), 0);\n        // import address table entry\n        let iat_offset = buf.len();\n        buf.resize(buf.len() + u64::size_with(&Endian::Little), 0);\n        // import address table terminating entry\n        buf.resize(buf.len() + u64::size_with(&Endian::Little), 0);\n        let dll_offset = buf.len();\n        let mut dll_name = args.import_dll.as_bytes().to_vec();\n        dll_name.push(0);\n        buf.resize(buf.len() + dll_name.len(), 0);\n        buf.pwrite(dll_name.as_slice(), dll_offset).unwrap();\n        buf.resize((buf.len() + 1) & (!1usize), 0);\n        let ilt_value = if args.import.starts_with(\"#\") {\n            args.import\n                .trim_start_matches(\"#\")\n                .parse::<u16>()\n                .expect(\"Cannot parse ordinal\") as u64\n                | (1u64 << 63)\n        } else {\n            let hint_offset = buf.len();\n            let name_offset = hint_offset + u16::size_with(&Endian::Little);\n            let mut import_bytes = args.import.as_bytes().to_vec();\n            import_bytes.push(0);\n            buf.resize(\n                buf.len() + u16::size_with(&Endian::Little) + import_bytes.len(),\n                0,\n            );\n            buf.pwrite(import_bytes.as_slice(), name_offset).unwrap();\n            idata_rva as u64 + hint_offset as u64\n        };\n        buf.resize((buf.len() + 15) & (!15usize), 0);\n        buf.pwrite(ilt_value, ilt_offset).unwrap();\n        buf.pwrite(ilt_value, iat_offset).unwrap();\n        let entry = ImportDirectoryEntry {\n            import_lookup_table_rva: idata_rva + ilt_offset as u32,\n            time_date_stamp: 0,\n            forwarder_chain: 0,\n            name_rva: idata_rva + dll_offset as u32,\n            import_address_table_rva: idata_rva + iat_offset as u32,\n        };\n        buf.pwrite(entry, entry_offset).unwrap();\n        buf\n    };\n    let idata_len = idata.len();\n\n    let edata_rva = idata_rva + idata_len as u32;\n    let mut edata = {\n        let mut buf = Vec::<u8>::new();\n\n        let system32_target = \"\\\\\\\\.\\\\GLOBALROOT\\\\SystemRoot\\\\System32\\\\\".to_string()\n            + args.dll.file_name().unwrap().to_str().unwrap();\n\n        let proxy_target = args.proxy_target.as_ref().unwrap_or(&system32_target);\n\n        let dll = &fs::read(args.dll).expect(\"Cannot read input dll\");\n        let object = Object::parse(dll).expect(\"Cannot parse input dll\");\n\n        let pe = if let Object::PE(pe) = object {\n            pe\n        } else {\n            panic!(\"Cannot parse object PE\");\n        };\n\n        let export_data = pe.export_data.expect(\"The dll has no exports!\");\n        let export_count = export_data.export_directory_table.address_table_entries;\n        let names_count = export_data.export_directory_table.number_of_name_pointers;\n\n        let directory_offset = buf.len();\n        buf.resize(buf.len() + SIZEOF_EXPORT_DIRECTORY_TABLE, 0);\n        let eat_offset = buf.len();\n        buf.resize(\n            buf.len() + u32::size_with(&Endian::Little) * export_count as usize,\n            0,\n        );\n        let name_ptrs_offset = buf.len();\n        buf.resize(\n            buf.len() + u32::size_with(&Endian::Little) * names_count as usize,\n            0,\n        );\n        let ordinals_offset = buf.len();\n        buf.resize(\n            buf.len() + u16::size_with(&Endian::Little) * export_count as usize,\n            0,\n        );\n\n        for (idx, ordinal) in export_data.export_ordinal_table.iter().enumerate() {\n            buf.pwrite(\n                ordinal,\n                ordinals_offset + idx * 2,\n            )\n            .unwrap();\n        }\n\n        let mut forward_names = vec![];\n        for i in 0..export_count {\n            let ordinal = export_data.export_directory_table.ordinal_base + i;\n            forward_names.push(format!(\"#{ordinal}\"));\n        }\n\n        let mut exports: Vec<&str> = pe.exports.iter().filter_map(|export| export.name).collect();\n        exports.sort();\n\n        // Sanity check that goblin parsed it all\n        assert_eq!(exports.len(), export_data.export_ordinal_table.len());\n        assert_eq!(exports.len(), export_data.export_name_pointer_table.len());\n\n        for (idx, name) in exports.iter().enumerate() {\n            forward_names[export_data.export_ordinal_table[idx] as usize] = name.to_string();\n            let mut name_str = name.as_bytes().to_vec();\n            name_str.push(0);\n            let name_offs = buf.len();\n            buf.resize(buf.len() + name_str.len(), 0);\n            buf.pwrite(name_str.as_slice(), name_offs).unwrap();\n            buf.pwrite(edata_rva + name_offs as u32, name_ptrs_offset + idx * 4).unwrap();\n        }\n\n        for i in 0..export_count as usize {\n            let forward_name = &forward_names[i];\n            let mut forward_str = format!(\"{proxy_target}.{forward_name}\").as_bytes().to_vec();\n            forward_str.push(0);\n            let forward_offs = buf.len();\n            buf.resize(buf.len() + forward_str.len(), 0);\n            buf.pwrite(forward_str.as_slice(), forward_offs).unwrap();\n            buf.pwrite(edata_rva + forward_offs as u32, eat_offset + i * 4).unwrap();\n        }\n\n        let mut dllname_offset = 0u32;\n        if let Some(dllname) = export_data.name {\n            dllname_offset = buf.len() as u32;\n            let mut dllname_str = dllname.to_string().as_bytes().to_vec();\n            dllname_str.push(0);\n            buf.resize(buf.len() + dllname_str.len(), 0);\n            buf.pwrite(dllname_str.as_slice(), dllname_offset as usize).unwrap();\n        }\n\n        buf.resize((buf.len() + 15) & (!15usize), 0);\n\n        let directory = ExportDirectoryTable {\n            export_flags: 0,\n            time_date_stamp: 0,\n            major_version: 0,\n            minor_version: 0,\n            name_rva: edata_rva + dllname_offset,\n            ordinal_base: export_data.export_directory_table.ordinal_base,\n            address_table_entries: export_count,\n            number_of_name_pointers: names_count,\n            export_address_table_rva: edata_rva + eat_offset as u32,\n            name_pointer_rva: edata_rva + name_ptrs_offset as u32,\n            ordinal_table_rva: edata_rva + ordinals_offset as u32,\n        };\n\n        buf.pwrite(directory, directory_offset).unwrap();\n\n        buf\n    };\n    let edata_len = edata.len();\n\n    const FILE_ALIGN: usize = 0x200;\n    const VIRTUAL_ALIGN: usize = 0x1000;\n\n    let mut rdata = vec![];\n    rdata.append(&mut idata);\n    rdata.append(&mut edata);\n    rdata.resize((rdata.len() + FILE_ALIGN - 1) & !(FILE_ALIGN - 1), 0);\n\n    let rdata_virtual_size = (rdata.len() + VIRTUAL_ALIGN - 1) & !(VIRTUAL_ALIGN - 1);\n\n    let mut header = SmallPE::default();\n    header.dos_magic = DOS_MAGIC;\n    header.e_lfanew = 0x40;\n    header.signature = PE_MAGIC;\n    header.coff_header.machine = args.machine;\n    header.coff_header.number_of_sections = 1;\n    header.coff_header.size_of_optional_header = 0xf0;\n    header.coff_header.characteristics =\n        IMAGE_FILE_EXECUTABLE_IMAGE | IMAGE_FILE_LARGE_ADDRESS_AWARE | IMAGE_FILE_DLL;\n    header.standard_fields.magic = IMAGE_NT_OPTIONAL_HDR64_MAGIC;\n    header.windows_fields.image_base = 0x10000;\n    header.windows_fields.section_alignment = 0x1000;\n    header.windows_fields.file_alignment = FILE_ALIGN as u32;\n    header.windows_fields.major_operating_system_version = 6;\n    header.windows_fields.minor_operating_system_version = 0;\n    header.windows_fields.major_subsystem_version = 5;\n    header.windows_fields.size_of_image = 0x1000 + rdata_virtual_size as u32;\n    header.windows_fields.size_of_headers = SmallPE::size_with(&Endian::Little) as u32;\n    header.windows_fields.subsystem = IMAGE_SUBSYSTEM_WINDOWS_GUI;\n    header.windows_fields.dll_characteristics = IMAGE_DLLCHARACTERISTICS_HIGH_ENTROPY_VA\n        | IMAGE_DLLCHARACTERISTICS_DYNAMIC_BASE\n        | IMAGE_DLLCHARACTERISTICS_NX_COMPAT;\n    header.windows_fields.size_of_stack_reserve = 0x100000;\n    header.windows_fields.size_of_stack_commit = 0x1000;\n    header.windows_fields.size_of_heap_reserve = 0x100000;\n    header.windows_fields.size_of_heap_commit = 0x1000;\n    header.windows_fields.number_of_rva_and_sizes = 16;\n    header.data_directories[0].virtual_address = edata_rva;\n    header.data_directories[0].size = edata_len as u32;\n    header.data_directories[1].virtual_address = idata_rva;\n    header.data_directories[1].size = idata_len as u32;\n    header.sec_virtual_size = rdata_virtual_size as u32;\n    header.sec_virtual_address = 0x1000;\n    header.sec_size_of_raw_data = rdata.len() as u32;\n    header.sec_pointer_to_raw_data = FILE_ALIGN as u32;\n    header.sec_characteristics = IMAGE_SCN_MEM_READ;\n\n    let mut header_bytes = [0u8; 0x200];\n    header_bytes.pwrite(&header, 0).unwrap();\n\n    let mut bytes = header_bytes.to_vec();\n    bytes.append(&mut rdata);\n\n    fs::write(&output, bytes).unwrap();\n}\n"
  }
]