radius2/
radius.rs

1pub use crate::processor::{HookMethod, Processor, RunMode};
2use crate::r2_api::{BasicBlock, FunctionInfo, Information, Instruction, R2Api, R2Result};
3use crate::state::State;
4//use crate::value::Value;
5use crate::sims::syscall::indirect;
6use crate::sims::{get_sims, zero, Sim, SimMethod};
7use crate::value::{vc, Value};
8
9// use std::collections::VecDeque;
10use std::collections::HashMap;
11use std::sync::{Arc, Mutex};
12
13// use std::thread;
14
15#[derive(Debug, Clone, PartialEq)]
16pub enum RadiusOption {
17    /// Use simulated syscalls
18    Syscalls(bool),
19    /// Use simulated imports     
20    Sims(bool),
21    /// Sim all imports, with stub if missing
22    SimAll(bool),
23    /// Optimize executed ESIL expressions
24    Optimize(bool),
25    /// Enable debug output
26    Debug(bool),
27    /// panic! on unimplemented
28    Strict(bool),
29    /// Don't check sat on symbolic pcs
30    Lazy(bool),
31    /// Check memory permissions
32    Permissions(bool),
33    /// Force execution of all branches
34    Force(bool),
35    /// Execute blocks in topological order
36    Topological(bool),
37    /// Enable merge-maxxing, automatic state merging
38    AutoMerge(bool),
39    /// Maximum values to evaluate for sym PCs
40    EvalMax(usize),
41    /// Radare2 argument, must be static
42    R2Argument(&'static str),
43    /// Handle self-modifying code (poorly)
44    SelfModify(bool),
45    /// Load plugins
46    LoadPlugins(bool),
47    /// Load libraries
48    LoadLibs(bool),
49    /// use color output from r2
50    ColorOutput(bool),
51    /// Path to load library from
52    LibPath(String),
53}
54
55/// Main Radius struct that coordinates and configures
56/// the symbolic execution of a binary.
57///
58/// Radius can be instantiated using either `Radius::new(filename: &str)`
59/// or `Radius::new_with_options(...)`
60///
61///  ## Example
62///
63/// ```
64/// use radius2::radius::Radius;
65/// let mut radius = Radius::new("/bin/sh");
66/// ```
67pub struct Radius {
68    /// Handle to interact with radare2
69    pub r2api: R2Api,
70    /// Evaluates ESIL to execute instructions
71    pub processor: Processor,
72    processors: Arc<Mutex<Vec<Processor>>>,
73    /// Max number of symbolic PC evaluations
74    pub eval_max: usize,
75    /// Check memory permissions
76    pub check: bool,
77    /// Print out disassembly of executed instructions
78    pub debug: bool,
79    /// Panic on invalid instructions
80    pub strict: bool,
81}
82
83impl Radius {
84    /// Create a new Radius instance for the provided binary
85    ///
86    /// ## Arguments
87    /// * `filename` - path to the target binary
88    ///
89    /// ## Example
90    /// ```
91    /// use radius2::radius::Radius;
92    /// let mut radius = Radius::new("/bin/sh");
93    /// ```
94    pub fn new<T: AsRef<str>>(filename: T) -> Self {
95        // no radius options and no r2 errors by default
96        Radius::new_with_options(Some(filename), &[])
97    }
98
99    /// Create a new Radius instance for the provided binary with a vec of `RadiusOption`
100    ///
101    /// ## Arguments
102    /// * `filename` - path to the target binary
103    /// * `options` - array of options to configure radius2
104    ///
105    /// ## Example
106    /// ```
107    ///   use radius2::radius::{Radius, RadiusOption};
108    ///   let options = [RadiusOption::Optimize(false), RadiusOption::Sims(false)];
109    ///   let mut radius = Radius::new_with_options(Some("/bin/sh"), &options);
110    /// ```
111    pub fn new_with_options<T: AsRef<str>>(filename: Option<T>, options: &[RadiusOption]) -> Self {
112        let mut argv = vec!["-2"];
113        let mut eval_max = 256;
114        let mut paths = vec![];
115        for o in options {
116            if let RadiusOption::R2Argument(arg) = o {
117                argv.push(*arg);
118            } else if let RadiusOption::EvalMax(m) = o {
119                eval_max = *m;
120            } else if let RadiusOption::LibPath(p) = o {
121                paths.push(p.to_owned());
122            }
123        }
124
125        let debug = options.contains(&RadiusOption::Debug(true));
126        let color = options.contains(&RadiusOption::ColorOutput(true));
127        let use_sims = !options.contains(&RadiusOption::Sims(false));
128
129        if !options.contains(&RadiusOption::LoadPlugins(true)) {
130            argv.push("-NN");
131        }
132
133        if debug && color {
134            // pretty print disasm + esil
135            argv.push("-e scr.color=3");
136            //argv.push("-e asm.cmt.esil=true");
137            argv.push("-e asm.lines=false");
138            argv.push("-e asm.emu=false");
139            argv.push("-e asm.xrefs=false");
140            argv.push("-e asm.functions=false");
141        }
142
143        let args = if !argv.is_empty() || filename.is_none() {
144            Some(argv)
145        } else {
146            None
147        };
148
149        let mut r2api = R2Api::new(filename, args);
150        r2api.set_option("io.cache", "true").unwrap();
151        // r2api.cmd("eco darkda").unwrap(); // i like darkda
152
153        let arch = &r2api.info.bin.arch;
154
155        // don't optimize dalvik & arm
156        let opt = !options.contains(&RadiusOption::Optimize(false))
157            && arch.as_str() != "arm"
158            && arch.as_str() != "dalvik";
159
160        let lazy = !options.contains(&RadiusOption::Lazy(false));
161        let force = options.contains(&RadiusOption::Force(true));
162        let topo = options.contains(&RadiusOption::Topological(true));
163        let check = options.contains(&RadiusOption::Permissions(true));
164        let sim_all = options.contains(&RadiusOption::SimAll(true));
165        let selfmod = options.contains(&RadiusOption::SelfModify(true));
166        let strict = options.contains(&RadiusOption::Strict(true));
167        let automerge = options.contains(&RadiusOption::AutoMerge(true));
168
169        let mut processor = Processor::new(selfmod, opt, debug, lazy, force, topo, automerge, color);
170        let processors = Arc::new(Mutex::new(vec![]));
171
172        if !options.contains(&RadiusOption::Syscalls(false)) {
173            let syscalls = r2api.get_syscalls().unwrap();
174            if let Some(sys) = syscalls.get(0) {
175                processor.traps.insert(sys.swi, indirect);
176            }
177            for sys in &syscalls {
178                processor.syscalls.insert(sys.num, sys.to_owned());
179            }
180        }
181
182        let _libs = if options.contains(&RadiusOption::LoadLibs(true)) {
183            r2api.load_libraries(&paths).unwrap()
184        } else {
185            vec![]
186        };
187
188        // this is weird, idk
189        if use_sims {
190            Radius::register_sims(&mut r2api, &mut processor, sim_all);
191        }
192
193        Radius {
194            r2api,
195            processor,
196            processors,
197            eval_max,
198            check,
199            debug,
200            strict,
201        }
202    }
203
204    /// Initialized state at the provided function address with an initialized stack
205    /// (if applicable)
206    ///
207    /// ## Arguments
208    /// * `addr` - the address of the function
209    ///
210    /// ## Example
211    /// ```
212    /// use radius2::radius::Radius;
213    /// let mut radius = Radius::new("/bin/sh");
214    /// let mut state = radius.call_state(0x004006fd);
215    /// ```
216    pub fn call_state(&mut self, addr: u64) -> State {
217        self.r2api.seek(addr);
218        self.r2api.init_vm();
219        let mut state = self.init_state();
220        state.memory.add_stack();
221        state.memory.add_heap();
222        state.memory.add_std_streams();
223        state
224    }
225
226    /// Initialized state at the provided function name with an initialized stack
227    /// equivalent to `call_state(get_address(sym))`
228    ///
229    /// ## Arguments
230    /// * `sym` - the name of the function
231    ///
232    /// ## Example
233    ///  ```
234    ///  use radius2::Radius;
235    ///  let mut radius = Radius::new("/bin/sh");
236    ///  let mut state = radius.callsym_state("printf");
237    ///  ```
238    pub fn callsym_state<T: AsRef<str>>(&mut self, sym: T) -> State {
239        let addr = self.get_address(sym).unwrap_or_default();
240        self.call_state(addr)
241    }
242
243    /// Initialize state from a debugger breakpoint
244    /// the program will block until bp is hit
245    pub fn debug_state(&mut self, addr: u64, args: &[String]) -> State {
246        // set cache to false to set breakpoint
247        self.r2api.set_option("io.cache", "false").unwrap();
248        self.r2api.init_debug(addr, args);
249        self.init_state()
250    }
251
252    /// Initialize state from a frida hook
253    /// the program will block until the hook is hit
254    pub fn frida_state(&mut self, addr: u64) -> State {
255        self.r2api.seek(addr);
256        let mut state = self.init_state();
257        self.processor.fetch_instruction(&mut state, addr); // cache real instrs
258        let context = self.r2api.init_frida(addr).unwrap();
259
260        for reg in context.keys() {
261            if state.registers.regs.contains_key(reg) {
262                state.registers.set(reg, vc(context[reg]));
263            }
264        }
265        state
266    }
267
268    /// Initialized state at the program entry point (the first if multiple).
269    ///
270    /// ## Example
271    ///
272    /// ```
273    /// use radius2::Radius;
274    /// let mut radius = Radius::new("/bin/sh");
275    /// let mut state = radius.entry_state();
276    /// ```
277    pub fn entry_state(&mut self) -> State {
278        // get the entrypoint
279        let entrypoints = self.r2api.get_entrypoints().unwrap_or_default();
280        if !entrypoints.is_empty() {
281            self.r2api.seek(entrypoints[0].vaddr);
282        }
283        self.r2api.init_vm();
284        let mut state = self.init_state();
285        state.memory.add_stack();
286        state.memory.add_heap();
287        state.memory.add_std_streams();
288
289        let start_main_reloc = self.r2api.get_address("reloc.__libc_start_main").unwrap_or(0);
290        if start_main_reloc != 0 {
291            self.r2api.cmd("af").unwrap(); // analyze entrypoint
292            let callers = self.r2api.get_references(start_main_reloc).unwrap_or_default();
293            if !callers.is_empty() {
294                self.hook(callers[0].from, __libc_start_main);
295            }
296        }
297        state
298    }
299
300    /// Set argv and env with arrays of values
301    pub fn set_argv_env(&mut self, state: &mut State, args: &[Value], env: &[Value]) {
302        // we write args to both regs and stack
303        // i think this is ok
304        let sp = state.registers.get_with_alias("SP");
305        let ptrlen = (state.memory.bits / 8) as usize;
306        let argc = Value::Concrete(args.len() as u64, 0);
307        state.memory_write_value(&sp, &argc, ptrlen);
308        state.registers.set_with_alias("A0", argc);
309
310        let types = ["argv", "env"];
311        let mut current = sp + Value::Concrete(ptrlen as u64, 0);
312        for (i, strings) in [args, env].iter().enumerate() {
313            state
314                .context
315                .insert(types[i].to_owned(), vec![current.clone()]);
316            let alias = format!("A{}", i + 1);
317            state.registers.set_with_alias(&alias, current.clone());
318            for string in strings.iter() {
319                let addr = state
320                    .memory
321                    .alloc(&Value::Concrete((string.size() / 8) as u64 + 1, 0));
322
323                state.memory_write_value(
324                    &Value::Concrete(addr, 0),
325                    string,
326                    string.size() as usize / 8,
327                );
328
329                state.memory.write_value(
330                    addr + (string.size() / 8) as u64,
331                    &Value::Concrete(0, 0),
332                    1,
333                );
334
335                state.memory_write_value(&current, &Value::Concrete(addr, 0), ptrlen);
336                current = current + Value::Concrete(ptrlen as u64, 0);
337            }
338            state.memory_write_value(&current, &Value::Concrete(0, 0), ptrlen);
339            current = current + Value::Concrete(ptrlen as u64, 0);
340        }
341    }
342
343    /// A default initial state
344    pub fn init_state(&mut self) -> State {
345        State::new(
346            &mut self.r2api,
347            self.eval_max,
348            self.debug,
349            false,
350            self.check,
351            self.strict,
352        )
353    }
354
355    /// A "blank" state with uninitialized values set to be symbolic
356    pub fn blank_state(&mut self) -> State {
357        State::new(
358            &mut self.r2api,
359            self.eval_max,
360            self.debug,
361            true,
362            self.check,
363            self.strict,
364        )
365    }
366
367    /// A blank state except for PC and SP
368    pub fn blank_call_state(&mut self, addr: u64) -> State {
369        self.r2api.seek(addr);
370        self.r2api.init_vm();
371        let mut state = self.blank_state();
372        let sp = self.r2api.get_register_value("SP").unwrap();
373        state
374            .registers
375            .set_with_alias("PC", Value::Concrete(addr, 0));
376        state.registers.set_with_alias("SP", Value::Concrete(sp, 0));
377        state.memory.add_stack();
378        state.memory.add_heap();
379        state.memory.add_std_streams();
380        state
381    }
382
383    /// Hook an address with a callback that is passed the `State`.
384    ///
385    /// The return value of the callback specifies whether the hooked instruction should be executed or skipped
386    ///
387    /// ## Arguments
388    /// * `addr` - the address to hook
389    /// * `hook_callback` - the function to call once the address is reached
390    ///
391    /// ## Example
392    /// ```
393    /// use radius2::{Radius, State, vc};
394    /// let mut radius = Radius::new("/bin/sh");
395    ///
396    /// fn callback(state: &mut State) -> bool {
397    ///     state.registers.set("rax", vc(0x1337));
398    ///     true // do not skip instruction
399    /// }
400    /// radius.hook(0x400cb0, callback);
401    /// ```
402    pub fn hook(&mut self, addr: u64, hook_callback: HookMethod) {
403        self.processor
404            .hooks
405            .entry(addr)
406            .or_insert(vec![])
407            .push(hook_callback);
408    }
409
410    /// Hook an address with an esil expression. The instruction
411    /// at the address is skipped if the last value on the stack is nonzero
412    ///
413    /// ## Arguments
414    /// * `addr` - the address to hook
415    /// * `esil` - the ESIL expression to evaluate
416    pub fn esil_hook(&mut self, addr: u64, esil: &str) {
417        self.processor
418            .esil_hooks
419            .entry(addr)
420            .or_insert(vec![])
421            .push(esil.to_owned());
422    }
423
424    /// Hook a symbol with a callback that is passed each state that reaches it
425    pub fn hook_symbol(&mut self, sym: &str, hook_callback: HookMethod) {
426        let addr = self.get_address(sym).unwrap();
427        self.hook(addr, hook_callback);
428    }
429
430    // internal method to register import sims
431    fn register_sims(r2api: &mut R2Api, processor: &mut Processor, sim_all: bool) {
432        let sims = get_sims();
433        let files = r2api.get_files().unwrap();
434
435        for file in files {
436            if file.uri.starts_with("null://") {
437                continue;
438            }
439
440            r2api.set_file_fd(file.fd);
441            let symbols = r2api.get_imports().unwrap();
442            let mut symmap: HashMap<String, u64> = HashMap::new();
443
444            for symbol in symbols {
445                symmap.insert(symbol.name, symbol.plt);
446            }
447
448            // TODO expand this to handle other symbols
449            for sim in &sims {
450                let addropt = symmap.remove(&sim.symbol);
451                if let Some(addr) = addropt {
452                    processor.sims.insert(addr, sim.to_owned());
453                }
454            }
455
456            if sim_all {
457                for name in symmap.keys() {
458                    // we are gonna go with zero by default
459                    processor.sims.insert(
460                        symmap[name],
461                        Sim {
462                            symbol: name.to_owned(),
463                            function: zero,
464                            arguments: 0,
465                        },
466                    );
467                }
468            }
469        }
470
471        // back to main file
472        r2api.set_file_fd(3);
473    }
474
475    /// Register a trap to call the provided `SimMethod`
476    pub fn trap(&mut self, trap_num: u64, sim: SimMethod) {
477        self.processor.traps.insert(trap_num, sim);
478    }
479
480    /// Register a `SimMethod` for the provided function address
481    ///
482    /// ## Arguments
483    /// * `addr` - address of the function to simulate (usually the PLT address)
484    /// * `sim` - Sim struct containing the function name, implementation, and arg count
485    ///
486    /// ## Example
487    /// ```
488    /// use radius2::{Radius, State, Sim, Value, vc};
489    /// let mut radius = Radius::new("/bin/sh");
490    /// let scanf = radius.get_address("__isoc99_scanf").unwrap();
491    ///
492    /// fn scanf_sim(state: &mut State, args: &[Value]) -> Value {
493    ///     state.memory_write_value(&args[1], &vc(42), 8);
494    ///     vc(1)
495    /// }
496    ///
497    /// radius.simulate(scanf, Sim{
498    ///     symbol: "scanf".to_owned(),
499    ///     function: scanf_sim,
500    ///     arguments: 2
501    /// });
502    /// ```
503    pub fn simulate(&mut self, addr: u64, sim: Sim) {
504        self.processor.sims.insert(addr, sim);
505    }
506
507    /// Add a breakpoint at the provided address.
508    /// This is where execution will stop after `run` is called
509    pub fn breakpoint(&mut self, addr: u64) {
510        self.processor.breakpoints.insert(addr);
511    }
512
513    /// Add a mergepoint, an address where many states will be combined
514    /// into a single state with the proper constraints
515    pub fn mergepoint(&mut self, addr: u64) {
516        self.processor.mergepoints.insert(addr);
517    }
518
519    /// Add addresses that will be avoided during execution. Any
520    /// `State` that reaches these addresses will be marked inactive
521    ///
522    /// ## Arguments
523    /// * `addrs` - slice of addresses to avoid during execution
524    pub fn avoid(&mut self, addrs: &[u64]) {
525        for addr in addrs {
526            self.processor.avoidpoints.insert(*addr);
527        }
528    }
529
530    /// Get total number of steps from all processors
531    pub fn get_steps(&self) -> u64 {
532        self.processor.steps
533            + self
534                .processors
535                .lock()
536                .unwrap()
537                .iter()
538                .map(|p| p.steps)
539                .sum::<u64>()
540    }
541
542    /// Execute function and return the resulting state
543    pub fn call_function(&mut self, sym: &str, state: State, args: Vec<Value>) -> Option<State> {
544        let addr = self.r2api.get_address(sym).unwrap_or_default();
545        self.call_address(addr, state, args)
546    }
547
548    /// Execute function at address and return the resulting state
549    /// if there are multiple result states, merge them all
550    pub fn call_address(&mut self, addr: u64, mut state: State, args: Vec<Value>) -> Option<State> {
551        state.set_args(args);
552        state.registers.set_pc(vc(addr));
553
554        let mut states = self.run_all(state);
555        let count = states.len();
556
557        if !states.is_empty() {
558            let mut end = states.remove(0);
559            for _ in 1..count {
560                end.merge(&mut states.remove(0));
561            }
562            Some(end)
563        } else {
564            None
565        }
566    }
567
568    /// Simple way to execute until a given target address while avoiding a vec of other addrs
569    ///
570    /// ## Arguments
571    /// * `state` - the program state to begin running from
572    /// * `target` - the goal address where execution should stop and return the result state
573    /// * `avoid` - slice of addresses to avoid, states that reach them will be marked inactive
574    pub fn run_until(&mut self, state: State, target: u64, avoid: &[u64]) -> Option<State> {
575        self.breakpoint(target);
576        self.avoid(avoid);
577        self.processor.run(state, RunMode::Single).pop()
578    }
579
580    /// Execute until every state has reached an end and return active states
581    pub fn run_all(&mut self, state: State) -> Vec<State> {
582        self.processor.run(state, RunMode::Multiple)
583    }
584
585    /// Main run method, start or continue a symbolic execution
586    ///
587    /// ## Arguments
588    /// * `state` - the program state to begin executing from
589    /// * `threads` - number of threads (currently unused)
590    ///
591    ///  ## Example
592    /// ```
593    /// use radius2::Radius;
594    /// let mut radius = Radius::new("/bin/ls");
595    /// let state = radius.entry_state();
596    /// let new_state = radius.run(state, 1);
597    /// ```
598    pub fn run(&mut self, state: State, _threads: usize) -> Option<State> {
599        // we are gonna scrap threads for now cuz theyre currently useless.
600        self.processor.run(state, RunMode::Single).pop()
601    }
602
603    /// Run radare2 analysis
604    pub fn analyze(&mut self, n: usize) {
605        let _r = self.r2api.analyze(n);
606    }
607
608    /// Get information about the binary and radare2 session
609    pub fn get_info(&mut self) -> R2Result<Information> {
610        self.r2api.get_info()
611    }
612
613    /// Get address of symbol
614    pub fn get_address<T: AsRef<str>>(&mut self, symbol: T) -> R2Result<u64> {
615        self.r2api.get_address(symbol.as_ref())
616    }
617
618    /// Get all functions
619    pub fn get_functions(&mut self) -> R2Result<Vec<FunctionInfo>> {
620        self.r2api.get_functions()
621    }
622
623    /// Get function information at this address
624    pub fn get_function(&mut self, address: u64) -> R2Result<FunctionInfo> {
625        self.r2api.get_function_info(address)
626    }
627
628    /// Get basic blocks of a function
629    pub fn get_blocks(&mut self, address: u64) -> R2Result<Vec<BasicBlock>> {
630        self.r2api.get_blocks(address)
631    }
632
633    /// Disassemble at the provided address
634    pub fn disassemble(&mut self, address: u64, num: usize) -> R2Result<Vec<Instruction>> {
635        self.r2api.disassemble(address, num)
636    }
637
638    /// Disassemble function at the provided address
639    pub fn disassemble_function(&mut self, address: u64) -> R2Result<Vec<Instruction>> {
640        self.r2api.disassemble_function(address)
641    }
642
643    /// Assemble the given instruction
644    pub fn assemble(&mut self, instruction: &str) -> R2Result<Vec<u8>> {
645        self.r2api.assemble(instruction)
646    }
647
648    /// Read directly from binary
649    pub fn read(&mut self, address: u64, length: usize) -> R2Result<Vec<u8>> {
650        self.r2api.read(address, length)
651    }
652
653    /// Patch binary
654    pub fn write(&mut self, address: u64, data: Vec<u8>) {
655        self.r2api.write(address, data)
656    }
657
658    /// Write string to binary / real memory
659    pub fn write_string(&mut self, address: u64, string: &str) {
660        self.r2api
661            .write(address, string.chars().map(|c| c as u8).collect::<Vec<_>>())
662    }
663
664    /// Set radare2 option, equivalent to "e `key`=`value`"
665    pub fn set_option(&mut self, key: &str, value: &str) {
666        self.r2api.set_option(key, value).unwrap();
667    }
668
669    /// Run any r2 command
670    pub fn cmd(&mut self, cmd: &str) -> R2Result<String> {
671        self.r2api.cmd(cmd)
672    }
673
674    /// continue real execution
675    pub fn cont(&mut self) {
676        self.r2api.cont().unwrap();
677    }
678
679    /// close r2
680    pub fn close(&mut self) {
681        self.r2api.close()
682    }
683
684    // clear cached data from r2api and processors
685    pub fn clear(&mut self) {
686        self.r2api.clear();
687        self.processors.lock().unwrap().clear();
688    }
689}
690
691pub fn __libc_start_main(state: &mut State) -> bool {
692    let mut args = state.get_args();
693    let main = args.remove(0);
694
695    // TODO go to init then main
696    // but we need a nice arch neutral way to push ret
697    // so until then
698
699    // go to main
700    state.registers.set_with_alias("PC", main);
701    state.set_args(args);
702
703    false
704}