Created
February 8, 2026 19:02
-
-
Save liquidhelium/0b03392e4e2ddd8c9600b2c89e55e300 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| //! ```cargo | |
| //! [dependencies] | |
| //! object = "0.32" | |
| //! brotli = "3.4" | |
| //! anyhow = "1.0" | |
| //! ``` | |
| use object::{read::pe::PeFile64, Object, ObjectSection}; | |
| use std::env; | |
| use std::fs; | |
| use std::io::{Read, Write}; | |
| use std::path::Path; | |
| fn main() -> anyhow::Result<()> { | |
| let args: Vec<String> = env::args().collect(); | |
| if args.len() < 2 { | |
| println!("Usage: rust-script extract_v3.rs <exe_path> [optional_path_filter]"); | |
| return Ok(()); | |
| } | |
| let exe_path = &args[1]; | |
| // 允许用户指定任何已知的路径字符串作为锚点,默认还是用这个 | |
| let anchor_str = args.get(2).map(|s| s.as_str()).unwrap_or("/index.html"); | |
| let data = fs::read(exe_path)?; | |
| let pe = PeFile64::parse(&*data)?; | |
| let rdata = pe.section_by_name(".rdata").ok_or_else(|| anyhow::anyhow!("No .rdata"))?; | |
| let rdata_data = rdata.data()?; | |
| let rdata_rva_start = rdata.address() as u32; | |
| // 1. 定位锚点字符串的 RVA | |
| let target_bytes = anchor_str.as_bytes(); | |
| let target_rva = rdata_data.windows(target_bytes.len()) | |
| .position(|window| window == target_bytes) | |
| .map(|pos| rdata_rva_start + pos as u32) | |
| .ok_or_else(|| anyhow::anyhow!("String '{}' not found in .rdata", anchor_str))?; | |
| println!("[*] Found '{}' at RVA: 0x{:08X}", anchor_str, target_rva); | |
| // 2. 启发式探测结构 (Heuristic Structure Detection) | |
| // 我们寻找包含 target_rva 的 32 字节块,并尝试分析字段布局 | |
| let mut best_entry = None; | |
| let rva_le = target_rva.to_le_bytes(); | |
| for j in (0..(rdata_data.len() - 32)).step_by(4) { | |
| if &rdata_data[j..j+4] == &rva_le { | |
| // 找到了 RVA,现在看周围。Tauri 资源条目通常包含:名称RVA, 名称长, 数据RVA, 数据长 | |
| // 尝试几种常见的组合布局 (4字节或8字节长度) | |
| let layouts = [ | |
| (4, 4, 4, 4), // NameRVA(4), NameLen(4), DataRVA(4), DataLen(4) -> 16 bytes | |
| (4, 8, 4, 8), // NameRVA(4), padding(4), NameLen(8), DataRVA(4), padding(4), DataLen(8) -> 32 bytes | |
| (8, 8, 8, 8), // 如果是 64 位对齐但内部只存了 32 位 RVA (前4字节是RVA,后4字节是0) | |
| ]; | |
| for (ptr_w, len_w, _, _) in layouts { | |
| let len_pos = j + ptr_w; | |
| if len_pos + len_w <= rdata_data.len() { | |
| let reported_len = match len_w { | |
| 4 => u32::from_le_bytes(rdata_data[len_pos..len_pos+4].try_into().unwrap()) as u64, | |
| 8 => u64::from_le_bytes(rdata_data[len_pos..len_pos+8].try_into().unwrap()), | |
| _ => 0, | |
| }; | |
| if reported_len == anchor_str.len() as u64 { | |
| let entry_size = ptr_w + len_w + ptr_w + len_w; | |
| println!("[!] Detected Structure: Offset=0x{:X}, EntrySize={}, PtrWidth={}, LenWidth={}", | |
| j, entry_size, ptr_w, len_w); | |
| best_entry = Some((j, entry_size, ptr_w, len_w)); | |
| break; | |
| } | |
| } | |
| } | |
| } | |
| if best_entry.is_some() { break; } | |
| } | |
| let (start_off, entry_size, ptr_w, len_w) = best_entry.ok_or_else(|| { | |
| anyhow::anyhow!("Could not determine entry structure around the RVA. Hex near RVA: {:02X?}", | |
| &rdata_data[target_rva as usize - rdata_rva_start as usize .. (target_rva as usize - rdata_rva_start as usize + 32).min(rdata_data.len())]) | |
| })?; | |
| // 3. 提取 | |
| let stem = Path::new(exe_path).file_stem().unwrap().to_str().unwrap(); | |
| let extract_dir = Path::new(exe_path).parent().unwrap().join(format!("{}_extract", stem)); | |
| fs::create_dir_all(&extract_dir)?; | |
| let to_offset = |rva: u32| -> Option<usize> { | |
| if rva >= rdata_rva_start && rva < rdata_rva_start + rdata_data.len() as u32 { | |
| Some((rva - rdata_rva_start) as usize) | |
| } else { None } | |
| }; | |
| let mut dump = |off: usize| -> Option<String> { | |
| if off + entry_size > rdata_data.len() { return None; } | |
| let s = &rdata_data[off..off + entry_size]; | |
| let n_rva = u32::from_le_bytes(s[0..4].try_into().ok()?); | |
| let n_len = if len_w == 4 { u32::from_le_bytes(s[ptr_w..ptr_w+4].try_into().ok()?) as usize } | |
| else { u64::from_le_bytes(s[ptr_w..ptr_w+8].try_into().ok()?) as usize }; | |
| let d_ptr_pos = ptr_w + len_w; | |
| let d_rva = u32::from_le_bytes(s[d_ptr_pos..d_ptr_pos+4].try_into().ok()?); | |
| let d_len_pos = d_ptr_pos + ptr_w; | |
| let d_len = if len_w == 4 { u32::from_le_bytes(s[d_len_pos..d_len_pos+4].try_into().ok()?) as usize } | |
| else { u64::from_le_bytes(s[d_len_pos..d_len_pos+8].try_into().ok()?) as usize }; | |
| let n_off = to_offset(n_rva)?; | |
| let d_off = to_offset(d_rva)?; | |
| let name_bytes = &rdata_data[n_off..(n_off + n_len).min(rdata_data.len())]; | |
| let name = String::from_utf8_lossy(name_bytes).trim_start_matches('/').to_string(); | |
| if name.is_empty() || name.contains('\0') { return None; } | |
| let comp_data = &rdata_data[d_off..(d_off + d_len).min(rdata_data.len())]; | |
| let mut decompressor = brotli::Decompressor::new(comp_data, 4096); | |
| let mut decompressed = Vec::new(); | |
| let final_data = match decompressor.read_to_end(&mut decompressed) { | |
| Ok(_) => decompressed, | |
| Err(_) => comp_data.to_vec(), | |
| }; | |
| let out_path = extract_dir.join(&name); | |
| if let Some(p) = out_path.parent() { let _ = fs::create_dir_all(p); } | |
| fs::write(out_path, final_data).ok()?; | |
| Some(name) | |
| }; | |
| let mut count = 0; | |
| // 双向扫描 | |
| let mut curr = start_off; | |
| while let Some(name) = dump(curr) { | |
| println!("Extracted: {}", name); | |
| count += 1; | |
| curr += entry_size; | |
| } | |
| if start_off >= entry_size { | |
| let mut curr = start_off - entry_size; | |
| while let Some(name) = dump(curr) { | |
| println!("Extracted: {}", name); | |
| count += 1; | |
| if curr < entry_size { break; } | |
| curr -= entry_size; | |
| } | |
| } | |
| println!("\nTotal: {} files in {:?}", count, extract_dir); | |
| Ok(()) | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment