diff --git a/.gitignore b/.gitignore index e52f2f6..edaa42b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,2 @@ -/target -a.txt +target/ +shellcode/target/ diff --git a/Cargo.lock b/Cargo.lock index 7803c6b..1fa133b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,108 +1,150 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. [[package]] -name = "arrayvec" -version = "0.5.2" +name = "anyhow" +version = "1.0.38" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +checksum = "afddf7f520a80dbf76e6f50a35bca42a2331ef227a28b3b6dc5c2e2338d114b1" [[package]] -name = "autocfg" -version = "1.0.1" +name = "cfg-if" +version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" [[package]] -name = "num" -version = "0.3.1" +name = "goblin" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +checksum = "669cdc3826f69a51d3f8fc3f86de81c2378110254f678b8407977736122057a4" dependencies = [ - "num-complex", - "num-integer", - "num-iter", - "num-rational", - "num-traits", + "log", + "plain", + "scroll", ] [[package]] -name = "num-complex" -version = "0.3.1" +name = "iced-x86" +version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +checksum = "b0be3ac4fec5cdbfd39a653e9145789e7a989359874da3cf2f18611468937767" dependencies = [ - "num-traits", + "lazy_static", + "rustc_version", + "static_assertions", ] [[package]] -name = "num-integer" -version = "0.1.44" +name = "lazy_static" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "log" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51b9bbe6c47d51fc3e1a9b945965946b4c44142ab8792c50835a980d362c2710" dependencies = [ - "autocfg", - "num-traits", + "cfg-if", ] [[package]] -name = "num-iter" -version = "0.1.42" +name = "plain" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "proc-macro2" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0704ee1a7e00d7bb417d0770ea303c1bccbabf0ef1667dae92b5967f5f8a71" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "unicode-xid", ] [[package]] -name = "num-rational" -version = "0.3.2" +name = "quote" +version = "1.0.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +checksum = "991431c3519a3f36861882da93630ce66b52918dcf1b8e2fd66b397fc96f28df" dependencies = [ - "autocfg", - "num-integer", - "num-traits", + "proc-macro2", ] [[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "wo" +name = "rust_windows_shellcode" version = "0.1.0" dependencies = [ - "arrayvec", - "num", - "num-traits", - "winapi", + "anyhow", + "goblin", + "iced-x86", ] + +[[package]] +name = "rustc_version" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a" +dependencies = [ + "semver", +] + +[[package]] +name = "scroll" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fda28d4b4830b807a8b43f7b0e6b5df875311b3e7621d84577188c175b6ec1ec" +dependencies = [ + "scroll_derive", +] + +[[package]] +name = "scroll_derive" +version = "0.10.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aaaae8f38bb311444cfb7f1979af0bc9240d95795f75f9ceddf6a59b79ceffa0" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "semver" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403" +dependencies = [ + "semver-parser", +] + +[[package]] +name = "semver-parser" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" + +[[package]] +name = "static_assertions" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f3eb36b47e512f8f1c9e3d10c2c1965bc992bd9cdb024fa581e2194501c83d3" + +[[package]] +name = "syn" +version = "1.0.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c700597eca8a5a762beb35753ef6b94df201c81cca676604f547495a0d7f0081" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + +[[package]] +name = "unicode-xid" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7fe0bb3479651439c9112f72b6c505038574c9fbb575ed1bf3b797fa39dd564" diff --git a/Cargo.toml b/Cargo.toml index 0b49b78..8927b42 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [package] -name = "wo" +name = "rust_windows_shellcode" version = "0.1.0" authors = ["b1tg "] edition = "2018" @@ -7,53 +7,6 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] - - -[profile.dev] -panic = "abort" -# these two cut code size by 2/3 -opt-level = "z" -lto = true - -[profile.release] -panic = "abort" -# these two cut code size by 2/3 -opt-level = "z" -lto = true - -[features] -# default = ["-mmx"] -[dependencies.winapi] -version = "0.3" -features = [ - "ntdef", - "winnt", - "dbghelp", - "minwindef", - - "impl-debug" -] - - -[build-dependencies.winapi] -version = "0.3" -features = [ - "ntdef", - "winnt", - "dbghelp", - "minwindef", - "libloaderapi", - - "impl-debug" -] - -[dependencies.num] -version = "0.3" -default-features = false - -[dependencies.num-traits] -version = "0.2" -default-features = false -[dependencies.arrayvec] -version = "0.5.2" -default-features = false \ No newline at end of file +goblin = "0.3.0" +anyhow = "1.0.34" +iced-x86 = "1.10.0" diff --git a/README.md b/README.md index d4c2420..968150a 100644 --- a/README.md +++ b/README.md @@ -1,18 +1,69 @@ -# ref links - -https://docs.rs/winapi/0.3.9/winapi/ctypes/type.wchar_t.html -https://github.com/mcountryman/min-sized-rust-windows -> https://news.ycombinator.com/item?id=25258461 - -https://github.com/hasherezade/masm_shc/blob/master/demos/peb_lookup.h -https://github.com/Trantect/win_driver_example -https://not-matthias.github.io/kernel-driver-with-rust/ -https://github.com/pravic/winapi-kmd-rs -https://github.com/johnthagen/min-sized-rust +# Write Windows Shellcode in Rust -edit -https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=4acc288a658ce23cb2c3da66aa818b8d +## Project overview + +Windows shellcode project is located in `shellcode/`, it can build into a PE file with only `.text` section +and has no external dependencies. + +![shellcode.exe in pe-bear](./images/show_in_pe_bear.png) + +Then we can dump the `.text` section and do some patches to make it position-independent. this idea +was from [hasherezade](https://twitter.com/hasherezade)'s project [masm_shc](https://github.com/hasherezade/masm_shc). -https://stackoverflow.com/questions/46134477/how-can-i-call-a-raw-address-from-rust +## How to build it + +(Only tested on Win10 x64) + + +### Build shellcode binary + + + +```sh +rustup default nightly-x86_64-pc-windows-msvc +cd shellcode/ +cargo build --release +``` + +If everthing goes well, we will get `shellcode\target\x86_64-pc-windows-msvc\release\shellcode.exe` + +### Dump .text section and do some patches + +We patch at the start of `.text` section, make it jump to entry point. In this way, we can have some strings store in the merged section, or we have to use `u8` and `u16` bytes array on stack to represent string. + +```sh +cd .. +cargo run +``` + + +We will get `shellcode\target\x86_64-pc-windows-msvc\release\shellcode.bin`, this is the final shellcode file. + + +### Test shellcode + +Test the shellcode use your favorite shellcode loader, i use my own little tool [rs_shellcode](https://github.com/b1tg/rs_shellcode) for demonstration. + +```sh +git clone https://github.com/b1tg/rs_shellcode +cd rs_shellcode/ +cargo build +./target/debug/rs_shellcode.exe -f "shellcode\target\x86_64-pc-windows-msvc\release\shellcode.bin" +``` + +This demo shellcode will popup a message box and print some log use `OutputDebugStringA`, you can check it out in [debugview](https://docs.microsoft.com/en-us/sysinternals/downloads/debugview) or windbg. + +![run shellcode](./images/run_shellcode.png) + + + +## References + +- https://github.com/mcountryman/min-sized-rust-windows +- https://github.com/hasherezade/masm_shc +- https://github.com/Trantect/win_driver_example +- https://not-matthias.github.io/kernel-driver-with-rust/ +- https://github.com/pravic/winapi-kmd-rs +- https://github.com/johnthagen/min-sized-rust diff --git a/build.rs b/build.rs deleted file mode 100644 index 5ffe96e..0000000 --- a/build.rs +++ /dev/null @@ -1,11 +0,0 @@ -fn main() { - let input = "OutputDebugStringA"; - let bytes= input.as_bytes().to_owned(); - let mut bytes: Vec = bytes.into_iter().map(|a:u8| format!("{}", a)).collect(); - bytes.push("0".to_owned()); - let bb = bytes.join(", "); - dbg!(&bb); - let input_up = input.to_uppercase(); - let res = format!("const {} :&[u16] = [{}];", input_up, &bb); - dbg!(res); -} \ No newline at end of file diff --git a/images/run_shellcode.png b/images/run_shellcode.png new file mode 100644 index 0000000..641c579 Binary files /dev/null and b/images/run_shellcode.png differ diff --git a/images/show_in_pe_bear.png b/images/show_in_pe_bear.png new file mode 100644 index 0000000..d8a4816 Binary files /dev/null and b/images/show_in_pe_bear.png differ diff --git a/.cargo/config b/shellcode/.cargo/config similarity index 100% rename from .cargo/config rename to shellcode/.cargo/config diff --git a/shellcode/Cargo.lock b/shellcode/Cargo.lock new file mode 100644 index 0000000..b2ac54a --- /dev/null +++ b/shellcode/Cargo.lock @@ -0,0 +1,78 @@ +# This file is automatically @generated by Cargo. +# It is not intended for manual editing. +[[package]] +name = "autocfg" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" + +[[package]] +name = "num" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8b7a8e9be5e039e2ff869df49155f1c06bd01ade2117ec783e56ab0932b67a8f" +dependencies = [ + "num-complex", + "num-integer", + "num-iter", + "num-rational", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "747d632c0c558b87dbabbe6a82f3b4ae03720d0646ac5b7b4dae89394be5f2c5" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" +dependencies = [ + "autocfg", + "num-traits", +] + +[[package]] +name = "num-iter" +version = "0.1.42" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" +dependencies = [ + "autocfg", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" +dependencies = [ + "autocfg", +] + +[[package]] +name = "shellcode" +version = "0.1.0" +dependencies = [ + "num", + "num-traits", +] diff --git a/shellcode/Cargo.toml b/shellcode/Cargo.toml new file mode 100644 index 0000000..12e44b1 --- /dev/null +++ b/shellcode/Cargo.toml @@ -0,0 +1,30 @@ +[package] +name = "shellcode" +version = "0.1.0" +authors = ["b1tg "] +edition = "2018" + +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + +[dependencies] + + +[profile.dev] +panic = "abort" +# these two cut code size by 2/3 +opt-level = "z" +lto = true + +[profile.release] +panic = "abort" +# these two cut code size by 2/3 +opt-level = "z" +lto = true + +[dependencies.num] +version = "0.3" +default-features = false + +[dependencies.num-traits] +version = "0.2" +default-features = false diff --git a/src/binds.rs b/shellcode/src/binds.rs similarity index 100% rename from src/binds.rs rename to shellcode/src/binds.rs diff --git a/shellcode/src/main.rs b/shellcode/src/main.rs new file mode 100644 index 0000000..e33b4ca --- /dev/null +++ b/shellcode/src/main.rs @@ -0,0 +1,138 @@ +#![allow(non_snake_case)] +#![allow(non_camel_case_types)] +#![allow(non_upper_case_globals)] +#![allow(overflowing_literals)] +#![no_std] +#![no_main] +#![feature(asm)] +#![feature(link_args)] +use core::ptr::null_mut; +mod binds; +mod utils; +use binds::*; +use utils::*; +#[panic_handler] +fn panic(_: &core::panic::PanicInfo) -> ! { + loop {} +} +// const KERNEL32_DLL: &str = concat!("KERNEL32.DLL", "\0"); +const USER32_DLL: &str = concat!("user32.dll", "\0"); +const OutputDebugStringA_: &str = concat!("OutputDebugStringA", "\0"); +const LoadLibraryA_: &str = concat!("LoadLibraryA", "\0"); +const GetProcAddress_: &str = concat!("GetProcAddress", "\0"); +const MessageBoxA_: &str = concat!("MessageBoxA", "\0"); + +pub type LoadLibraryAFn = extern "system" fn(lpFileName: LPCSTR) -> PVOID; +pub type GetProcAddressFn = extern "system" fn(hmodule: PVOID, name: LPCSTR) -> PVOID; +pub type MessageBoxAFn = extern "system" fn(h: PVOID, text: LPCSTR, cation: LPCSTR, t: u32) -> u32; +pub type OutputDebugStringAFn = extern "C" fn(*const i8); +pub type DbgPrintFn = extern "C" fn(Format: *const u8, ...) -> NTSTATUS; +#[no_mangle] +pub extern "C" fn main() -> ! { + unsafe { + // clean argc and argv + asm!("mov rcx, 0", "mov rdx, 0",); + } + let KERNEL32_STR: [u16; 13] = [75, 69, 82, 78, 69, 76, 51, 50, 46, 68, 76, 76, 0]; + let kernel32_ptr = get_module_by_name(KERNEL32_STR.as_ptr()); + let dbg_addr = get_func_by_name(kernel32_ptr, OutputDebugStringA_.as_ptr()); + let load_library_ptr = get_func_by_name(kernel32_ptr, LoadLibraryA_.as_ptr()); + let get_proc = get_func_by_name(kernel32_ptr, GetProcAddress_.as_ptr()); + let LoadLibraryA: LoadLibraryAFn = unsafe { core::mem::transmute(load_library_ptr) }; + + // make stack align + unsafe { asm!("push rax") }; + let u32_dll = LoadLibraryA(USER32_DLL.as_ptr() as *const i8); + let GetProcAddress: GetProcAddressFn = unsafe { core::mem::transmute(get_proc) }; + let message_box_ptr = GetProcAddress(u32_dll, MessageBoxA_.as_ptr() as *const i8); + let MessageBoxA: MessageBoxAFn = unsafe { core::mem::transmute(message_box_ptr) }; + + let OutputDebugStringA: OutputDebugStringAFn = unsafe { core::mem::transmute(dbg_addr) }; + #[macro_export] + macro_rules! debug_print { + ($msg:expr) => { + OutputDebugStringA(concat!($msg, "\n\0").as_ptr() as _) + }; + } + debug_print!("We made it! see me in DebugView or Debugger"); + debug_print!("Let's popup a messagebox"); + MessageBoxA( + null_mut(), + "Windows Shellcode with Rust!\0".as_ptr() as *const i8, + "rocks\0".as_ptr() as _, + 0x30, + ); + loop {} +} + +fn get_module_by_name(module_name: *const u16) -> PVOID { + let peb: *mut PEB; + unsafe { + asm!( + "mov {}, gs:[0x60]", + out(reg) peb, + ); + let ldr = (*peb).Ldr; + let list_entry = &((*ldr).InLoadOrderModuleList); + let mut cur_module: *const LDR_DATA_TABLE_ENTRY = &list_entry as *const _ as *const _; + loop { + if cur_module.is_null() || (*cur_module).BaseAddress.is_null() { + // TODO: when to break + } + let cur_name = (*cur_module).BaseDllName.Buffer; + if !cur_name.is_null() { + if compare_raw_str(module_name, cur_name) { + return (*cur_module).BaseAddress; + } + } + let flink = (*cur_module).InLoadOrderModuleList.Flink; + cur_module = flink as *const LDR_DATA_TABLE_ENTRY; + } + } +} + +fn get_func_by_name(module: PVOID, func_name: *const u8) -> PVOID { + let idh: *const IMAGE_DOS_HEADER = module as *const _; + unsafe { + if (*idh).e_magic != IMAGE_DOS_SIGNATURE { + return null_mut(); + } + let e_lfanew = (*idh).e_lfanew; + let nt_headers: *const IMAGE_NT_HEADERS = + (module as *const u8).offset(e_lfanew as isize) as *const _; + let op_header = &(*nt_headers).OptionalHeader; + let virtual_addr = (&op_header.DataDirectory[0]).VirtualAddress; + let export_dir: *const IMAGE_EXPORT_DIRECTORY = + (module as *const u8).offset(virtual_addr as _) as _; + let number_of_names = (*export_dir).NumberOfNames; + let addr_of_funcs = (*export_dir).AddressOfFunctions; + let addr_of_names = (*export_dir).AddressOfNames; + let addr_of_ords = (*export_dir).AddressOfNameOrdinals; + for i in 0..number_of_names { + let name_rva_p: *const DWORD = + (module as *const u8).offset((addr_of_names + i * 4) as isize) as *const _; + let name_index_p: *const WORD = + (module as *const u8).offset((addr_of_ords + i * 2) as isize) as *const _; + let name_index = name_index_p.as_ref().unwrap(); + let mut off: u32 = (4 * name_index) as u32; + off = off + addr_of_funcs; + let func_rva: *const DWORD = (module as *const u8).offset(off as _) as *const _; + + let name_rva = name_rva_p.as_ref().unwrap(); + let curr_name = (module as *const u8).offset(*name_rva as isize); + + if *curr_name == 0 { + continue; + } + if compare_raw_str(func_name, curr_name) { + let res = (module as *const u8).offset(*func_rva as isize); + return res as _; + } + } + } + return null_mut(); +} +// #[allow(unused_attributes)] +// #[cfg(target_env = "msvc")] +// #[link_args = "/GS- /MERGE:.rdata=.text /MERGE:.pdata=.text /NODEFAULTLIB /EMITPOGOPHASEINFO /DEBUG:NONE"] +// extern "C" {} \ No newline at end of file diff --git a/src/utils.rs b/shellcode/src/utils.rs similarity index 100% rename from src/utils.rs rename to shellcode/src/utils.rs diff --git a/src/main.rs b/src/main.rs index 4eee7d9..1706a5a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,136 +1,94 @@ -#![allow(non_snake_case)] -#![allow(non_camel_case_types)] -#![allow(non_upper_case_globals)] -#![allow(overflowing_literals)] -#![no_std] -#![no_main] -#![feature(asm)] -#![feature(link_args)] -use core::ptr::null_mut; -mod binds; -mod utils; -use binds::*; -use utils::*; -#[panic_handler] -fn panic(_: &core::panic::PanicInfo) -> ! { - loop {} -} -// const KERNEL32_DLL: &str = concat!("KERNEL32.DLL", "\0"); -const USER32_DLL: &str = concat!("user32.dll", "\0"); -const OutputDebugStringA_: &str = concat!("OutputDebugStringA", "\0"); -const LoadLibraryA_: &str = concat!("LoadLibraryA", "\0"); -const GetProcAddress_: &str = concat!("GetProcAddress", "\0"); -const MessageBoxA_: &str = concat!("MessageBoxA", "\0"); +use anyhow::{Result}; +use goblin::pe::PE; +use std::fs::File; +use std::io::prelude::*; +use std::io::BufWriter; +use iced_x86::{Decoder, DecoderOptions, Formatter, Instruction, NasmFormatter}; -pub type LoadLibraryAFn = extern "system" fn(lpFileName: LPCSTR) -> PVOID; -pub type GetProcAddressFn = extern "system" fn(hmodule: PVOID, name: LPCSTR) -> PVOID; -pub type MessageBoxAFn = extern "system" fn(h: PVOID, text: LPCSTR, cation: LPCSTR, t: u32) -> u32; -pub type OutputDebugStringAFn = extern "C" fn(*const i8); -pub type DbgPrintFn = extern "C" fn(Format: *const u8, ...) -> NTSTATUS; -#[no_mangle] -pub extern "C" fn main() -> ! { - unsafe { - // clean argc and argv - asm!("mov rcx, 0", "mov rdx, 0",); - } - let KERNEL32_STR: [u16; 13] = [75, 69, 82, 78, 69, 76, 51, 50, 46, 68, 76, 76, 0]; - let kernel32_ptr = get_module_by_name(KERNEL32_STR.as_ptr()); - let dbg_addr = get_func_by_name(kernel32_ptr, OutputDebugStringA_.as_ptr()); - let load_library_ptr = get_func_by_name(kernel32_ptr, LoadLibraryA_.as_ptr()); - let get_proc = get_func_by_name(kernel32_ptr, GetProcAddress_.as_ptr()); - let LoadLibraryA: LoadLibraryAFn = unsafe { core::mem::transmute(load_library_ptr) }; - - // make stack align - unsafe { asm!("push rax") }; - let u32_dll = LoadLibraryA(USER32_DLL.as_ptr() as *const i8); - let GetProcAddress: GetProcAddressFn = unsafe { core::mem::transmute(get_proc) }; - let message_box_ptr = GetProcAddress(u32_dll, MessageBoxA_.as_ptr() as *const i8); - let MessageBoxA: MessageBoxAFn = unsafe { core::mem::transmute(message_box_ptr) }; - MessageBoxA( - null_mut(), - MessageBoxA_.as_ptr() as *const i8, - MessageBoxA_.as_ptr() as _, - 0x30, - ); - let OutputDebugStringA: OutputDebugStringAFn = unsafe { core::mem::transmute(dbg_addr) }; - #[macro_export] - macro_rules! debug_print { - ($msg:expr) => { - OutputDebugStringA(concat!($msg, "\0").as_ptr() as _) - }; - } - debug_print!("we made it! see me in DebugView or Debugger"); - loop {} -} - -fn get_module_by_name(module_name: *const u16) -> PVOID { - let peb: *mut PEB; - unsafe { - asm!( - "mov {}, gs:[0x60]", - out(reg) peb, - ); - let ldr = (*peb).Ldr; - let list_entry = &((*ldr).InLoadOrderModuleList); - let mut cur_module: *const LDR_DATA_TABLE_ENTRY = &list_entry as *const _ as *const _; - loop { - if cur_module.is_null() || (*cur_module).BaseAddress.is_null() { - // TODO: when to break - } - let cur_name = (*cur_module).BaseDllName.Buffer; - if !cur_name.is_null() { - if compare_raw_str(module_name, cur_name) { - return (*cur_module).BaseAddress; - } - } - let flink = (*cur_module).InLoadOrderModuleList.Flink; - cur_module = flink as *const LDR_DATA_TABLE_ENTRY; +fn main() -> Result<()> { + let src_path = "shellcode\\target\\x86_64-pc-windows-msvc\\release\\wo.exe"; + let mut file = File::open(src_path)?; + let mut buffer = Vec::new(); + file.read_to_end(&mut buffer)?; + let pe = PE::parse(&mut buffer)?; + let standard_fileds = pe.header.optional_header.unwrap().standard_fields; + let entry_offset = standard_fileds.address_of_entry_point - standard_fileds.base_of_code; + for section in pe.sections { + let name = String::from_utf8(section.name.to_vec())?; + if !name.starts_with(".text") { + continue; } - } -} - -fn get_func_by_name(module: PVOID, func_name: *const u8) -> PVOID { - let idh: *const IMAGE_DOS_HEADER = module as *const _; - unsafe { - if (*idh).e_magic != IMAGE_DOS_SIGNATURE { - return null_mut(); + let start = section.pointer_to_raw_data as usize; + let size = section.size_of_raw_data as usize; + let dst_path = "shellcode\\target\\x86_64-pc-windows-msvc\\release\\shellcode.bin"; + let shellcode = File::create(dst_path)?; + let mut buf_writer = BufWriter::new(shellcode); + println!("[*] section text addr: 0x{:x}, size: 0x{:x}", start, size); + println!("[*] entry offset: 0x{:x}", entry_offset); + println!("== before patch =="); + show_disassemble(&buffer[start..start+size], 5); + if entry_offset >= 0x100 { + buffer[0+start] = 0xe9; + let hi = (entry_offset-2) / 0x100; + let li = (entry_offset-2) % 0x100; + dbg!(hi, li); + buffer[1+start] = li as _; + buffer[2+start] = hi as _; + buffer[3+start] = 0 as _; + buffer[4+start] = 0 as _; + } else if entry_offset >= 0x80 { + buffer[0+start] = 0xe9; + buffer[1+start] = (entry_offset-5) as _; + buffer[2+start] = 0 as _; + buffer[3+start] = 0 as _; + buffer[4+start] = 0 as _; + } else { + buffer[0+start] = 0xeb; + buffer[1+start] = (entry_offset-2) as _; } - let e_lfanew = (*idh).e_lfanew; - let nt_headers: *const IMAGE_NT_HEADERS = - (module as *const u8).offset(e_lfanew as isize) as *const _; - let op_header = &(*nt_headers).OptionalHeader; - let virtual_addr = (&op_header.DataDirectory[0]).VirtualAddress; - let export_dir: *const IMAGE_EXPORT_DIRECTORY = - (module as *const u8).offset(virtual_addr as _) as _; - let number_of_names = (*export_dir).NumberOfNames; - let addr_of_funcs = (*export_dir).AddressOfFunctions; - let addr_of_names = (*export_dir).AddressOfNames; - let addr_of_ords = (*export_dir).AddressOfNameOrdinals; - for i in 0..number_of_names { - let name_rva_p: *const DWORD = - (module as *const u8).offset((addr_of_names + i * 4) as isize) as *const _; - let name_index_p: *const WORD = - (module as *const u8).offset((addr_of_ords + i * 2) as isize) as *const _; - let name_index = name_index_p.as_ref().unwrap(); - let mut off: u32 = (4 * name_index) as u32; - off = off + addr_of_funcs; - let func_rva: *const DWORD = (module as *const u8).offset(off as _) as *const _; - - let name_rva = name_rva_p.as_ref().unwrap(); - let curr_name = (module as *const u8).offset(*name_rva as isize); - - if *curr_name == 0 { - continue; - } - if compare_raw_str(func_name, curr_name) { - let res = (module as *const u8).offset(*func_rva as isize); - return res as _; + println!("== after patch =="); + show_disassemble(&buffer[start..start+size], 5); + for i in start..start+size { + buf_writer.write(&[buffer[i]])?; + } + buf_writer.flush().unwrap(); + println!("done! shellcode saved in {}", dst_path); + } + Ok(()) +} +pub fn show_disassemble(bytes: &[u8], max_line: u32) { + let mut decoder = Decoder::new(EXAMPLE_CODE_BITNESS, bytes, DecoderOptions::NONE); + decoder.set_ip(EXAMPLE_CODE_RIP); + let mut formatter = NasmFormatter::new(); + formatter.options_mut().set_digit_separator("`"); + formatter.options_mut().set_first_operand_char_index(10); + let mut output = String::new(); + let mut instruction = Instruction::default(); + let mut i = 0; + while decoder.can_decode() { + i +=1; + if i> max_line { + println!("....\n"); + break; + } + decoder.decode_out(&mut instruction); + output.clear(); + formatter.format(&instruction, &mut output); + print!("{:016X} ", instruction.ip()); + let start_index = (instruction.ip() - EXAMPLE_CODE_RIP) as usize; + let instr_bytes = &bytes[start_index..start_index + instruction.len()]; + for b in instr_bytes.iter() { + print!("{:02X}", b); + } + if instr_bytes.len() < HEXBYTES_COLUMN_BYTE_LENGTH { + for _ in 0..HEXBYTES_COLUMN_BYTE_LENGTH - instr_bytes.len() { + print!(" "); } } + println!(" {}", output); } - return null_mut(); } -// #[allow(unused_attributes)] -// #[cfg(target_env = "msvc")] -// #[link_args = "/GS- /MERGE:.rdata=.text /MERGE:.pdata=.text /NODEFAULTLIB /EMITPOGOPHASEINFO /DEBUG:NONE"] -// extern "C" {} \ No newline at end of file + +const HEXBYTES_COLUMN_BYTE_LENGTH: usize = 10; +const EXAMPLE_CODE_BITNESS: u32 = 64; +const EXAMPLE_CODE_RIP: u64 = 0x0000_0001_4000_1000; // 0000 0001 4000 1000