1use crate::disasm::{follow_jump_table, recover_call_site, ArgKind};
10use crate::pe::Pe;
11use iced_x86::{
12 Decoder, DecoderOptions, FlowControl, Formatter, Instruction, Mnemonic, NasmFormatter, OpKind,
13 Register,
14};
15use std::collections::{BTreeMap, BTreeSet};
16use std::fmt::Write;
17use std::sync::{Mutex, OnceLock};
18
19const MAX_SPAN: u64 = 0x10000;
20
21fn decode_all(pe: &Pe, entry: u64) -> (BTreeMap<u64, Instruction>, BTreeSet<u64>) {
24 let (lo, hi) = (entry, entry.saturating_add(MAX_SPAN));
25 let mut instrs: BTreeMap<u64, Instruction> = BTreeMap::new();
26 let mut leaders: BTreeSet<u64> = BTreeSet::new();
27 leaders.insert(entry);
28 let mut work = vec![entry];
29 let mut seen: BTreeSet<u64> = BTreeSet::new();
30
31 while let Some(start) = work.pop() {
32 if !seen.insert(start) {
33 continue;
34 }
35 let Some(bytes) = pe.read(start, 32 * 64) else {
36 continue;
37 };
38 let mut dec = Decoder::with_ip(32, bytes, start, DecoderOptions::NONE);
39 let mut instr = Instruction::default();
40 while dec.can_decode() {
41 let ip = dec.ip();
42 if ip != start && instrs.contains_key(&ip) {
43 break;
44 }
45 dec.decode_out(&mut instr);
46 if instr.is_invalid() {
47 break;
48 }
49 instrs.insert(ip, instr);
50 match instr.flow_control() {
51 FlowControl::ConditionalBranch => {
52 let t = instr.near_branch_target();
53 let n = instr.next_ip();
54 if (lo..hi).contains(&t) {
55 leaders.insert(t);
56 work.push(t);
57 }
58 leaders.insert(n);
59 work.push(n);
60 break;
61 }
62 FlowControl::UnconditionalBranch => {
63 let t = instr.near_branch_target();
64 if t < ip {
92 leaders.insert(t);
93 work.push(t);
94 }
95 break;
96 }
97 FlowControl::IndirectBranch => {
98 let mut cases = Vec::new();
99 follow_jump_table(&instr, pe, lo, hi, false, true, &mut cases);
105 for c in cases {
106 leaders.insert(c);
107 work.push(c);
108 }
109 break;
110 }
111 FlowControl::Return | FlowControl::Exception => break,
112 _ => {}
115 }
116 }
117 }
118 (instrs, leaders)
119}
120
121#[derive(Debug, Default, Clone, Copy)]
123pub struct CfgFingerprint {
124 pub loops: usize,
133 pub switch_cases: usize,
135 pub conditionals: usize,
137 pub returns: usize,
139}
140
141struct Block {
146 start: u64,
147 body: Vec<Instruction>,
148 term: Option<Instruction>,
149 succ: Vec<u64>,
153}
154
155fn build_cfg(pe: &Pe, entry: u64) -> (BTreeMap<u64, Instruction>, Vec<Block>) {
158 let (_, _) = (entry, entry.saturating_add(MAX_SPAN));
159 let (instrs, leaders) = decode_all(pe, entry);
160 let addrs: Vec<u64> = instrs.keys().copied().collect();
161 let lo = entry;
162 let hi = entry.saturating_add(MAX_SPAN);
163 let mut blocks: Vec<Block> = Vec::new();
164 for &ld in &leaders {
165 let mut idx = match addrs.binary_search(&ld) {
166 Ok(i) => i,
167 Err(_) => continue,
168 };
169 let mut body = Vec::new();
170 let mut term = None;
171 let mut succ = Vec::new();
172 loop {
173 let a = addrs[idx];
174 let instr = instrs[&a];
175 let is_term = !matches!(
179 instr.flow_control(),
180 FlowControl::Next
181 | FlowControl::Call
182 | FlowControl::IndirectCall
183 | FlowControl::XbeginXabortXend
184 );
185 if is_term {
186 match instr.flow_control() {
187 FlowControl::ConditionalBranch => {
188 let t = instr.near_branch_target();
189 if (lo..hi).contains(&t) {
190 succ.push(t);
191 }
192 let n = instr.next_ip();
193 if (lo..hi).contains(&n) {
194 succ.push(n);
195 }
196 }
197 FlowControl::UnconditionalBranch => {
198 let t = instr.near_branch_target();
199 if (lo..hi).contains(&t) {
200 succ.push(t);
201 }
202 }
203 FlowControl::IndirectBranch => {
204 let mut cases = Vec::new();
210 follow_jump_table(&instr, pe, lo, hi, false, true, &mut cases);
211 for c in cases {
212 if (lo..hi).contains(&c) {
213 succ.push(c);
214 }
215 }
216 }
217 _ => {}
218 }
219 term = Some(instr);
220 break;
221 }
222 body.push(instr);
223 idx += 1;
224 if idx >= addrs.len() {
225 break;
226 }
227 if leaders.contains(&addrs[idx]) {
228 succ.push(addrs[idx]);
229 break;
230 }
231 }
232 blocks.push(Block {
233 start: ld,
234 body,
235 term,
236 succ,
237 });
238 }
239 blocks.sort_by_key(|b| b.start);
240 (instrs, blocks)
241}
242
243fn dominators(entry: u64, blocks: &[Block]) -> BTreeMap<u64, BTreeSet<u64>> {
251 let starts: BTreeSet<u64> = blocks.iter().map(|b| b.start).collect();
252 let mut preds: BTreeMap<u64, BTreeSet<u64>> = BTreeMap::new();
253 for b in blocks {
254 for &s in &b.succ {
255 if starts.contains(&s) {
256 preds.entry(s).or_default().insert(b.start);
257 }
258 }
259 }
260 let mut dom: BTreeMap<u64, BTreeSet<u64>> = BTreeMap::new();
261 if !starts.contains(&entry) {
262 return dom;
263 }
264 let mut entry_set: BTreeSet<u64> = BTreeSet::new();
265 entry_set.insert(entry);
266 dom.insert(entry, entry_set.clone());
267 for &s in &starts {
268 if s != entry {
269 dom.insert(s, starts.clone());
270 }
271 }
272 let order: Vec<u64> = blocks.iter().map(|b| b.start).collect();
273 loop {
274 let mut changed = false;
275 for &n in &order {
276 if n == entry {
277 continue;
278 }
279 let empty: BTreeSet<u64> = BTreeSet::new();
280 let ps = preds.get(&n).unwrap_or(&empty);
281 if ps.is_empty() {
282 continue;
283 }
284 let first = ps.iter().next().unwrap();
285 let mut new_dom = dom.get(first).cloned().unwrap_or_else(|| starts.clone());
286 for &p in ps {
287 let pd = dom.get(&p).cloned().unwrap_or_else(|| starts.clone());
288 new_dom = new_dom.intersection(&pd).copied().collect();
289 }
290 new_dom.insert(n);
291 if dom.get(&n) != Some(&new_dom) {
292 dom.insert(n, new_dom);
293 changed = true;
294 }
295 }
296 if !changed {
297 break;
298 }
299 }
300 dom
301}
302
303pub fn cfg_fingerprint(pe: &Pe, entry: u64) -> Result<CfgFingerprint, String> {
307 type Key = (u64, u64);
308 static CACHE: OnceLock<Mutex<BTreeMap<Key, Result<CfgFingerprint, String>>>> = OnceLock::new();
309 let cache = CACHE.get_or_init(|| Mutex::new(BTreeMap::new()));
310 let key = (pe.id(), entry);
311 if let Some(result) = cache
312 .lock()
313 .unwrap_or_else(|e| e.into_inner())
314 .get(&key)
315 .cloned()
316 {
317 return result;
318 }
319
320 let result = cfg_fingerprint_uncached(pe, entry);
321 let mut entries = cache.lock().unwrap_or_else(|e| e.into_inner());
322 if entries.len() >= 8192 {
323 entries.clear();
324 }
325 entries.insert(key, result.clone());
326 result
327}
328
329fn cfg_fingerprint_uncached(pe: &Pe, entry: u64) -> Result<CfgFingerprint, String> {
330 if !pe.is_executable_va(entry) {
331 return Err(format!(
332 "{entry:#010x} is not inside any PE section\n{}",
333 pe.describe_sections(entry)
334 ));
335 }
336 let (lo, hi) = (entry, entry.saturating_add(MAX_SPAN));
337 let (instrs, blocks) = build_cfg(pe, entry);
338 let dom = dominators(entry, &blocks);
339 let mut fp = CfgFingerprint::default();
340 for b in &blocks {
341 let Some(instr) = b.term else {
342 continue;
343 };
344 match instr.flow_control() {
345 FlowControl::ConditionalBranch => {
346 fp.conditionals += 1;
347 let t = instr.near_branch_target();
348 if t <= b.start && dom.get(&b.start).is_some_and(|d| d.contains(&t)) {
354 fp.loops += 1;
355 }
356 }
357 FlowControl::UnconditionalBranch => {
358 let t = instr.near_branch_target();
359 if t <= b.start && dom.get(&b.start).is_some_and(|d| d.contains(&t)) {
360 fp.loops += 1;
361 } else {
362 fp.returns += 1;
370 }
371 }
372 FlowControl::Return | FlowControl::Interrupt => fp.returns += 1,
373 FlowControl::IndirectBranch => {
374 let mut cases = Vec::new();
375 follow_jump_table(&instr, pe, lo, hi, false, true, &mut cases);
381 fp.switch_cases += cases.len();
382 }
383 _ => {}
384 }
385 let _ = &instrs;
386 }
387 Ok(fp)
388}
389
390pub fn emit_skeleton(pe: &Pe, entry: u64) -> Result<String, String> {
392 if !pe.is_executable_va(entry) {
393 return Err(format!(
394 "{entry:#010x} is not inside any PE section\n{}",
395 pe.describe_sections(entry)
396 ));
397 }
398 let (instrs, blocks) = build_cfg(pe, entry);
399 let _ = &instrs;
400
401 let dom = dominators(entry, &blocks);
408 let mut loop_heads: BTreeSet<u64> = BTreeSet::new();
409 for b in &blocks {
410 for &s in &b.succ {
411 if s <= b.start && dom.get(&b.start).is_some_and(|d| d.contains(&s)) {
412 loop_heads.insert(s);
413 }
414 }
415 }
416 let mut target_count: BTreeMap<u64, u32> = BTreeMap::new();
418 for b in &blocks {
419 for &s in &b.succ {
420 *target_count.entry(s).or_insert(0) += 1;
421 }
422 }
423
424 let mut fmt = NasmFormatter::new();
425 let mut out = String::new();
426 let _ = writeln!(
427 out,
428 "// AUTO-GENERATED reference skeleton from bytes @ {entry:#010x} — NOT compilable; a faithful\n\
429 // structural hint (ordered calls + recovered args, field accesses, globals, control flow).\n\
430 unsafe fn sub_{entry:08x}(this: u32) {{"
431 );
432
433 let _ = &target_count; for b in &blocks {
435 let tag = if loop_heads.contains(&b.start) {
436 " // loop head"
437 } else {
438 ""
439 };
440 let _ = writeln!(out, " 'L{:08x}: {{{tag}", b.start);
441 let mut last_cmp: Option<Instruction> = None;
442 let mut arg_base = 0usize; for (i, instr) in b.body.iter().enumerate() {
444 if matches!(instr.mnemonic(), Mnemonic::Cmp | Mnemonic::Test) {
445 last_cmp = Some(*instr);
446 }
447 if let Some(line) = render_instr(pe, instr, &b.body[arg_base..i]) {
448 let _ = writeln!(out, " {line}");
449 }
450 if matches!(
451 instr.flow_control(),
452 FlowControl::Call | FlowControl::IndirectCall
453 ) {
454 arg_base = i + 1;
455 }
456 }
457 if let Some(t) = &b.term {
458 let _ = writeln!(
459 out,
460 " {}",
461 render_terminator(t, last_cmp.as_ref(), &mut fmt)
462 );
463 }
464 let _ = writeln!(out, " }}");
465 }
466 let _ = writeln!(out, "}}");
467 Ok(out)
468}
469
470fn asm(fmt: &mut NasmFormatter, instr: &Instruction) -> String {
472 let mut s = String::new();
473 fmt.format(instr, &mut s);
474 s
475}
476
477fn reg(r: Register) -> String {
478 format!("{r:?}").to_lowercase()
479}
480
481fn arg(a: &ArgKind) -> String {
483 match a {
484 ArgKind::Imm(v) if *v >= 0x1000 => format!("{v:#x}"),
485 ArgKind::Imm(v) => format!("{v}"),
486 ArgKind::Field(o) => format!("rd(_, {o:#x})"),
487 ArgKind::Global(a) => format!("g({a:#010x})"),
488 ArgKind::LocalAddr => "&local".to_string(),
489 ArgKind::Other => "_".to_string(),
490 }
491}
492
493fn render_instr(pe: &Pe, instr: &Instruction, prior: &[Instruction]) -> Option<String> {
496 match instr.flow_control() {
497 FlowControl::Call => {
498 let t = instr.near_branch_target();
499 if t == 0 {
500 return Some("(*indirect)(); // indirect call".to_string());
501 }
502 let cs = recover_call_site(pe, prior, t);
503 let mut a: Vec<String> = Vec::new();
504 if let Some(x) = cs.ecx {
505 a.push(arg(&x));
506 }
507 if let Some(x) = cs.edx {
508 a.push(arg(&x));
509 }
510 a.extend(cs.stack.iter().map(arg));
511 return Some(format!("sub_{t:08x}({});", a.join(", ")));
512 }
513 FlowControl::IndirectCall => {
514 return Some("(*vtbl[?])(); // indirect/virtual call".to_string())
515 }
516 _ => {}
517 }
518 if instr.mnemonic() == Mnemonic::Mov {
521 if instr.op0_kind() == OpKind::Memory {
522 let src = match instr.op1_kind() {
523 OpKind::Immediate8
524 | OpKind::Immediate8to16
525 | OpKind::Immediate8to32
526 | OpKind::Immediate16
527 | OpKind::Immediate32 => format!("{:#x}", instr.immediate(1)),
528 OpKind::Register => reg(instr.op1_register()),
529 _ => "_".to_string(),
530 };
531 return match mem_of(pe, instr) {
532 Mem::Field(b, d) => Some(format!("wr({b}, {d:#x}, {src});")),
533 Mem::Global(va) => Some(format!("s({va:#010x}, {src});")),
534 Mem::Other => None,
535 };
536 }
537 if instr.op1_kind() == OpKind::Memory && instr.op0_kind() == OpKind::Register {
538 let dst = reg(instr.op0_register());
539 return match mem_of(pe, instr) {
540 Mem::Field(b, d) => Some(format!("let {dst} = rd({b}, {d:#x});")),
541 Mem::Global(va) => Some(format!("let {dst} = g({va:#010x});")),
542 Mem::Other => None,
543 };
544 }
545 }
546 None
547}
548
549enum Mem {
552 Field(String, u64),
553 Global(u64),
554 Other,
555}
556fn mem_of(pe: &Pe, instr: &Instruction) -> Mem {
557 let base = instr.memory_base();
558 let disp = instr.memory_displacement64();
559 if base == Register::None && instr.memory_index() == Register::None {
560 if pe.section_of(disp).is_some() {
561 return Mem::Global(disp);
562 }
563 return Mem::Other;
564 }
565 if base == Register::ESP || base == Register::EBP || base == Register::None {
566 return Mem::Other;
567 }
568 Mem::Field(reg(base), disp)
569}
570
571fn render_terminator(
573 instr: &Instruction,
574 last_cmp: Option<&Instruction>,
575 fmt: &mut NasmFormatter,
576) -> String {
577 match instr.flow_control() {
578 FlowControl::Return | FlowControl::Interrupt => "return;".to_string(),
579 FlowControl::UnconditionalBranch => {
580 format!("break 'L{:08x}; // jmp", instr.near_branch_target())
581 }
582 FlowControl::IndirectBranch => {
583 format!("match _ {{ /* switch */ }} // {}", asm(fmt, instr))
584 }
585 FlowControl::ConditionalBranch => {
586 let cond = render_cond(instr, last_cmp, fmt);
587 format!(
588 "if {cond} {{ break 'L{:08x}; }} // {}",
589 instr.near_branch_target(),
590 asm(fmt, instr)
591 )
592 }
593 _ => format!("// {}", asm(fmt, instr)),
594 }
595}
596
597fn render_cond(
599 jcc: &Instruction,
600 last_cmp: Option<&Instruction>,
601 fmt: &mut NasmFormatter,
602) -> String {
603 let relop = match jcc.mnemonic() {
604 Mnemonic::Je => "==",
605 Mnemonic::Jne => "!=",
606 Mnemonic::Jl | Mnemonic::Jb => "<",
607 Mnemonic::Jle | Mnemonic::Jbe => "<=",
608 Mnemonic::Jg | Mnemonic::Ja => ">",
609 Mnemonic::Jge | Mnemonic::Jae => ">=",
610 Mnemonic::Js => "< 0 //(sign)",
611 Mnemonic::Jns => ">= 0 //(sign)",
612 _ => "?",
613 };
614 match last_cmp {
615 Some(c) if c.mnemonic() == Mnemonic::Test => {
616 format!("(cond {} {relop} 0)", asm_operands(c, fmt))
618 }
619 Some(c) => {
620 let ops = asm_operands(c, fmt);
621 if let Some((a, b)) = ops.split_once(", ") {
623 format!("{a} {relop} {b}")
624 } else {
625 format!("({ops}) {relop} 0")
626 }
627 }
628 None => format!("(flags) {relop} 0"),
629 }
630}
631
632fn asm_operands(instr: &Instruction, fmt: &mut NasmFormatter) -> String {
634 let s = asm(fmt, instr);
635 s.split_once(' ').map(|x| x.1).unwrap_or(&s).to_string()
636}