1use anyhow::Result;
4use std::collections::HashSet;
5use uuid::Uuid;
6
7use crate::cfg_analyzer::{CfgAnalyzer, CfgAnalyzerRegistry};
8use crate::types::{
9 Address, BasicBlock, BlockType, Disassembly, Function,
10 Instruction, InstructionGroup
11};
12
13pub fn identify_functions_with_yara(disasm: &Disassembly, arch: &str, format: &str) -> Result<Vec<Function>> {
15 let registry = CfgAnalyzerRegistry::new();
16 let analyzer = registry.get_analyzer(arch, format)
17 .ok_or_else(|| anyhow::anyhow!("No analyzer found for architecture: {} format: {}", arch, format))?;
18
19 let mut functions = Vec::new();
20 let mut processed_addresses = HashSet::new();
21
22 let entry_points = find_function_entry_points_with_yara(disasm, analyzer)?;
24
25 for &entry_point in &entry_points {
26 if processed_addresses.contains(&entry_point) {
27 continue;
28 }
29
30 if let Ok(function) = analyze_function_from_entry(disasm, entry_point, analyzer) {
31 for &addr in &function.instructions {
33 processed_addresses.insert(addr);
34 }
35 functions.push(function);
36 }
37 }
38
39 Ok(functions)
40}
41
42pub fn identify_functions(disasm: &Disassembly) -> Result<Vec<Function>> {
44 identify_functions_with_yara(disasm, "arm", "elf")
45}
46
47pub fn analyze_single_function_with_arch(disasm: &Disassembly, address: Address, arch: &str, format: &str) -> Result<Function> {
49 let registry = CfgAnalyzerRegistry::new();
50 let analyzer = registry.get_analyzer(arch, format)
51 .ok_or_else(|| anyhow::anyhow!("No analyzer found for architecture: {} format: {}", arch, format))?;
52
53 analyze_function_from_entry(disasm, address, analyzer)
54}
55
56pub fn analyze_single_function(disasm: &Disassembly, address: Address) -> Result<Function> {
58 analyze_single_function_with_arch(disasm, address, "arm", "elf")
59}
60
61fn find_function_entry_points_with_yara(disasm: &Disassembly, analyzer: &dyn CfgAnalyzer) -> Result<Vec<Address>> {
63 let mut entry_points = Vec::new();
64
65 let function_symbols = classify_function_symbols_with_yara(&disasm.symbols)?;
67 entry_points.extend(function_symbols);
68
69 for instruction in &disasm.instructions {
71 if analyzer.classify_instruction(instruction) == InstructionGroup::Call {
72 if let Some(target) = analyzer.extract_jump_target(instruction) {
73 entry_points.push(target);
74 }
75 }
76 }
77
78 for window in disasm.instructions.windows(2) {
80 if is_function_prologue(&window) {
81 entry_points.push(window[0].address);
82 }
83 }
84
85 entry_points.sort();
86 entry_points.dedup();
87 Ok(entry_points)
88}
89
90fn analyze_function_from_entry(disasm: &Disassembly, entry_point: Address, analyzer: &dyn CfgAnalyzer) -> Result<Function> {
92 let mut instructions = Vec::new();
93 let mut exit_points = Vec::new();
94 let mut calls = Vec::new();
95 let _current_addr = entry_point;
96
97 let mut visited = HashSet::new();
99 let mut to_process = vec![entry_point];
100
101 while let Some(addr) = to_process.pop() {
102 if visited.contains(&addr) {
103 continue;
104 }
105 visited.insert(addr);
106
107 if let Some(instruction) = find_instruction_at_address(disasm, addr) {
108 instructions.push(instruction.address);
109
110 let instruction_group = analyzer.classify_instruction(instruction);
111 match instruction_group {
112 InstructionGroup::Return => {
113 exit_points.push(instruction.address);
114 }
116 InstructionGroup::Call => {
117 if let Some(target) = analyzer.extract_jump_target(instruction) {
118 calls.push(target);
119 }
120 if let Some(next_addr) = analyzer.fall_through_address(instruction) {
122 to_process.push(next_addr);
123 }
124 }
125 InstructionGroup::Jump => {
126 if let Some(target) = analyzer.extract_jump_target(instruction) {
127 to_process.push(target);
128 }
129 if analyzer.is_conditional(instruction) {
131 if let Some(next_addr) = analyzer.fall_through_address(instruction) {
132 to_process.push(next_addr);
133 }
134 }
135 }
136 _ => {
137 if let Some(next_addr) = analyzer.fall_through_address(instruction) {
139 to_process.push(next_addr);
140 }
141 }
142 }
143 } else {
144 break;
146 }
147
148 if instructions.len() > 1000 {
150 break;
151 }
152 }
153
154 instructions.sort();
155
156 let basic_blocks = build_basic_blocks_with_analyzer(&instructions, disasm, analyzer)?;
158
159 let function_size = if let (Some(&first), Some(&last)) = (instructions.first(), instructions.last()) {
160 last - first + 4 } else {
162 0
163 };
164
165 tracing::debug!("Looking for symbol at function address 0x{:x}, total symbols: {}", entry_point, disasm.symbols.len());
167 if disasm.symbols.len() > 0 && disasm.symbols.len() < 20 {
168 for (&addr, name) in &disasm.symbols {
169 tracing::debug!(" Available symbol: '{}' at 0x{:x}", name, addr);
170 }
171 }
172 let name = find_closest_symbol(&disasm.symbols, entry_point);
173
174 Ok(Function {
175 address: entry_point,
176 name,
177 size: function_size,
178 instructions,
179 basic_blocks,
180 entry_point,
181 exit_points,
182 calls,
183 called_by: Vec::new(), })
185}
186
187fn build_basic_blocks_with_analyzer(instructions: &[Address], disasm: &Disassembly, analyzer: &dyn CfgAnalyzer) -> Result<Vec<BasicBlock>> {
189 if instructions.is_empty() {
190 tracing::debug!("No instructions provided for basic block analysis");
191 return Ok(Vec::new());
192 }
193
194 tracing::debug!("Building basic blocks from {} instructions", instructions.len());
195
196 let mut block_starts = HashSet::new();
198 block_starts.insert(instructions[0]); tracing::debug!("Initial block start at 0x{:x}", instructions[0]);
201
202 let mut jump_targets_found = 0;
204 let mut valid_instruction_count = 0;
205
206 for &addr in instructions {
207 if let Some(instruction) = find_instruction_at_address(disasm, addr) {
208 let instruction_group = analyzer.classify_instruction(instruction);
209 tracing::debug!("Analyzing instruction at 0x{:x}: {} {} (group: {:?})",
210 addr, instruction.mnemonic, instruction.operands, instruction_group);
211
212 if !instruction.mnemonic.is_empty() && instruction.mnemonic != "???" {
214 valid_instruction_count += 1;
215 }
216
217 match instruction_group {
218 InstructionGroup::Jump | InstructionGroup::Call => {
219 if let Some(next_addr) = analyzer.fall_through_address(instruction) {
221 if instructions.contains(&next_addr) {
222 tracing::debug!(" Adding fall-through block start at 0x{:x}", next_addr);
223 block_starts.insert(next_addr);
224 }
225 }
226
227 if let Some(target) = analyzer.extract_jump_target(instruction) {
229 if instructions.contains(&target) {
230 tracing::debug!(" Adding jump target block start at 0x{:x}", target);
231 block_starts.insert(target);
232 jump_targets_found += 1;
233 } else {
234 tracing::debug!(" Jump target 0x{:x} is outside function", target);
235 }
236 }
237 }
238 InstructionGroup::Return => {
239 if let Some(next_addr) = analyzer.fall_through_address(instruction) {
241 if instructions.contains(&next_addr) {
242 tracing::debug!(" Adding post-return block start at 0x{:x}", next_addr);
243 block_starts.insert(next_addr);
244 }
245 }
246 }
247 _ => {}
248 }
249 }
250 }
251
252 if jump_targets_found == 0 && valid_instruction_count > 20 {
255 tracing::debug!("Long instruction sequence detected ({} instructions), adding artificial splits", valid_instruction_count);
256
257 let chunk_size = 12;
259 for (i, &addr) in instructions.iter().enumerate() {
260 if i > 0 && i % chunk_size == 0 && i < instructions.len() - 1 {
261 tracing::debug!(" Adding artificial block split at 0x{:x} (position {})", addr, i);
262 block_starts.insert(addr);
263 jump_targets_found += 1; }
265 }
266 }
267
268 tracing::debug!("Found {} jump targets, total block starts: {}", jump_targets_found, block_starts.len());
269
270 let mut block_starts: Vec<Address> = block_starts.into_iter().collect();
271 block_starts.sort();
272
273 tracing::debug!("Block starts: {:?}", block_starts.iter().map(|&addr| format!("0x{:x}", addr)).collect::<Vec<_>>());
274
275 let mut basic_blocks = Vec::new();
277
278 for i in 0..block_starts.len() {
279 let start_addr = block_starts[i];
280 let end_addr = if i + 1 < block_starts.len() {
281 block_starts[i + 1]
282 } else {
283 instructions.last().copied().unwrap_or(start_addr) + 4
284 };
285
286 let block_instructions: Vec<Address> = instructions.iter()
287 .filter(|&&addr| addr >= start_addr && addr < end_addr)
288 .copied()
289 .collect();
290
291 if !block_instructions.is_empty() {
292 let block_type = if i == 0 {
293 BlockType::Entry
294 } else if i == block_starts.len() - 1 {
295 BlockType::Exit
296 } else {
297 BlockType::Normal
298 };
299
300 tracing::debug!("Created basic block {}: 0x{:x}-0x{:x} ({} instructions, type: {:?})",
301 i, start_addr, end_addr, block_instructions.len(), block_type);
302
303 basic_blocks.push(BasicBlock {
304 id: Uuid::new_v4(),
305 start_address: start_addr,
306 end_address: end_addr,
307 instructions: block_instructions,
308 predecessors: Vec::new(), successors: Vec::new(), block_type,
311 });
312 }
313 }
314
315 tracing::debug!("Final result: {} basic blocks created", basic_blocks.len());
316
317 Ok(basic_blocks)
318}
319
320
321fn find_instruction_at_address(disasm: &Disassembly, address: Address) -> Option<&Instruction> {
323 disasm.instructions.iter().find(|insn| insn.address == address)
324}
325
326fn is_function_prologue(instructions: &[Instruction]) -> bool {
328 if instructions.len() < 2 {
329 return false;
330 }
331
332 let first = &instructions[0];
333 let second = &instructions[1];
334
335 if (first.mnemonic == "push" && first.operands.contains("ebp")) &&
337 (second.mnemonic == "mov" && second.operands.contains("ebp") && second.operands.contains("esp")) {
338 return true; }
340
341 if (first.mnemonic == "push" && first.operands.contains("rbp")) &&
342 (second.mnemonic == "mov" && second.operands.contains("rbp") && second.operands.contains("rsp")) {
343 return true; }
345
346 if first.mnemonic == "sub" && first.operands.contains("esp") {
348 return true; }
350
351 if first.mnemonic == "sub" && first.operands.contains("rsp") {
352 return true; }
354
355 if first.mnemonic == "push" && (first.operands.contains("ebp") || first.operands.contains("rbp")) {
357 return true;
358 }
359
360 (first.mnemonic == "push" && first.operands.contains("lr")) ||
363 (first.mnemonic == "stmfd" && first.operands.contains("sp!") && first.operands.contains("lr")) ||
365 (first.mnemonic == "mov" && first.operands.contains("r7") && first.operands.contains("sp"))
367}
368
369fn find_closest_symbol(symbols: &std::collections::HashMap<Address, String>, target_address: Address) -> Option<String> {
371 if let Some(name) = symbols.get(&target_address) {
373 return Some(name.clone());
374 }
375
376 let mut closest_distance = u64::MAX;
379 let mut closest_symbol = None;
380
381 for (&symbol_addr, symbol_name) in symbols {
382 let distance = if symbol_addr <= target_address {
383 target_address - symbol_addr
384 } else {
385 symbol_addr - target_address
386 };
387
388 if distance <= 256 && distance < closest_distance {
391 closest_distance = distance;
392 closest_symbol = Some(symbol_name.clone());
393
394 tracing::debug!("Found symbol '{}' at 0x{:x} for function at 0x{:x} (distance: {})",
396 symbol_name, symbol_addr, target_address, distance);
397 }
398 }
399
400 if closest_symbol.is_none() {
401 tracing::debug!("No symbol found for function at 0x{:x}", target_address);
402 }
403
404 closest_symbol
405}
406
407fn classify_function_symbols_with_yara(symbols: &std::collections::HashMap<Address, String>) -> Result<Vec<Address>> {
409 let mut function_addresses = Vec::new();
410
411 for (&addr, name) in symbols {
413 if is_likely_function_symbol(name) {
414 function_addresses.push(addr);
415 }
416 }
417
418 tracing::debug!("Basic heuristics identified {} potential functions from {} symbols",
419 function_addresses.len(), symbols.len());
420
421 Ok(function_addresses)
422}
423
424fn is_likely_function_symbol(name: &str) -> bool {
426 if name.starts_with("_Z") {
428 return true;
429 }
430
431 if name.contains("()") || name.ends_with("()") {
433 return true;
434 }
435
436 let function_keywords = ["main", "init", "start", "setup", "run", "execute",
438 "create", "destroy", "handle", "process", "update"];
439 if function_keywords.iter().any(|&keyword| name.to_lowercase().contains(keyword)) {
440 return true;
441 }
442
443 let data_keywords = ["_data", "_rodata", "_bss", "String", "string",
445 "variable", "const", "static", "_var", "__"];
446 if data_keywords.iter().any(|&keyword| name.to_lowercase().contains(keyword)) {
447 return false;
448 }
449
450 if name.contains("::") || name.contains("get") || name.contains("set") {
452 return true;
453 }
454
455 !name.chars().any(|c| c.is_ascii_digit()) || name.len() > 8
458}
459