Skip to content

Commit

Permalink
✨ feat: expose decompiler as library
Browse files Browse the repository at this point in the history
✨ feat: heimdallibrary (expose decompiler as library)
  • Loading branch information
Jon-Becker committed Dec 29, 2022
2 parents 9b1db82 + 4c521e9 commit 79ad51e
Show file tree
Hide file tree
Showing 4 changed files with 158 additions and 38 deletions.
9 changes: 9 additions & 0 deletions heimdall/examples/decompile.rs
@@ -0,0 +1,9 @@
use heimdall::decompile::DecompileBuilder;

fn main() {
// Decompile the bytecode and save the results.
DecompileBuilder::new(BYTECODE).decompile();

}

const BYTECODE: &'static str = "731bf797219482a29013d804ad96d1c6f84fba4c453014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319045a251461005d575b600080fd5b6100c56004803603810190808035600019169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610107565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000806000806041855114151561012157600093506101f6565b6020850151925060408501519150606085015160001a9050601b8160ff16101561014c57601b810190505b601b8160ff16141580156101645750601c8160ff1614155b1561017257600093506101f6565b600186828585604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156101e9573d6000803e3d6000fd5b5050506020604051035193505b505050929150505600a165627a7a72305820aacffa0494cd3f043493eee9c720bca9d5ef505ae7230ffc3d88c49ceeb7441e0029";
173 changes: 137 additions & 36 deletions heimdall/src/decompile/mod.rs
Expand Up @@ -39,18 +39,18 @@ use heimdall_common::{
#[derive(Debug, Clone, Parser)]
#[clap(about = "Decompile EVM bytecode to Solidity",
after_help = "For more information, read the wiki: https://jbecker.dev/r/heimdall-rs/wiki",
global_setting = AppSettings::DeriveDisplayOrder,
global_setting = AppSettings::DeriveDisplayOrder,
override_usage = "heimdall decompile <TARGET> [OPTIONS]")]
pub struct DecompilerArgs {

/// The target to decompile, either a file, bytecode, contract address, or ENS name.
#[clap(required=true)]
pub target: String,

/// Set the output verbosity level, 1 - 5.
#[clap(flatten)]
pub verbose: clap_verbosity_flag::Verbosity,

/// The output directory to write the decompiled files to
#[clap(long="output", short, default_value = "", hide_default_value = true)]
pub output: String,
Expand Down Expand Up @@ -87,10 +87,10 @@ pub fn decompile(args: DecompilerArgs) {
shortened_target = shortened_target.chars().take(66).collect::<String>() + "..." + &shortened_target.chars().skip(shortened_target.len() - 16).collect::<String>();
}
let decompile_call = trace.add_call(
0, line!(),
"heimdall".to_string(),
"decompile".to_string(),
vec![shortened_target],
0, line!(),
"heimdall".to_string(),
"decompile".to_string(),
vec![shortened_target],
"()".to_string()
);

Expand Down Expand Up @@ -161,7 +161,7 @@ pub fn decompile(args: DecompilerArgs) {
};
return bytecode_as_bytes.to_string().replacen("0x", "", 1);
});

}
else if BYTECODE_REGEX.is_match(&args.target).unwrap() {
contract_bytecode = args.target.clone();
Expand All @@ -175,7 +175,7 @@ pub fn decompile(args: DecompilerArgs) {

// We are decompiling a file, so we need to read the bytecode from the file.
contract_bytecode = match fs::read_to_string(&args.target) {
Ok(contents) => {
Ok(contents) => {
if BYTECODE_REGEX.is_match(&contents).unwrap() && contents.len() % 2 == 0 {
contents.replacen("0x", "", 1)
}
Expand All @@ -200,22 +200,22 @@ pub fn decompile(args: DecompilerArgs) {
rpc_url: args.rpc_url.clone(),
});
trace.add_call(
decompile_call,
line!(),
"heimdall".to_string(),
decompile_call,
line!(),
"heimdall".to_string(),
"disassemble".to_string(),
vec![format!("{} bytes", contract_bytecode.len()/2usize)],
vec![format!("{} bytes", contract_bytecode.len()/2usize)],
"()".to_string()
);

// perform versioning and compiler heuristics
let (compiler, version) = detect_compiler(contract_bytecode.clone());
trace.add_call(
decompile_call,
line!(),
"heimdall".to_string(),
decompile_call,
line!(),
"heimdall".to_string(),
"detect_compiler".to_string(),
vec![format!("{} bytes", contract_bytecode.len()/2usize)],
vec![format!("{} bytes", contract_bytecode.len()/2usize)],
format!("({}, {})", compiler, version)
);

Expand Down Expand Up @@ -263,7 +263,7 @@ pub fn decompile(args: DecompilerArgs) {
let mut analyzed_functions = Vec::new();
for selector in selectors.clone() {
decompilation_progress.set_message(format!("executing '0x{}'", selector));

// get the function's entry point
let function_entry_point = resolve_entry_point(&evm.clone(), selector.clone());

Expand All @@ -272,17 +272,17 @@ pub fn decompile(args: DecompilerArgs) {
}

let func_analysis_trace = trace.add_call(
vm_trace,
line!(),
"heimdall".to_string(),
"analyze".to_string(),
vec![format!("0x{}", selector)],
vm_trace,
line!(),
"heimdall".to_string(),
"analyze".to_string(),
vec![format!("0x{}", selector)],
"()".to_string()
);

trace.add_info(
func_analysis_trace,
function_entry_point.try_into().unwrap(),
func_analysis_trace,
function_entry_point.try_into().unwrap(),
format!("discovered entry point: {}", function_entry_point).to_string()
);

Expand All @@ -292,7 +292,7 @@ pub fn decompile(args: DecompilerArgs) {
func_analysis_trace,
function_entry_point.try_into().unwrap(),
format!("execution tree {}",

match jumpdests.len() {
0 => "appears to be linear".to_string(),
_ => format!("has {} branches", jumpdests.len()+1)
Expand All @@ -307,7 +307,7 @@ pub fn decompile(args: DecompilerArgs) {
format!("Execution tree truncated to {} branches", jumpdests.len()).to_string()
);
}

decompilation_progress.set_message(format!("analyzing '0x{}'", selector));

// solidify the execution tree
Expand Down Expand Up @@ -391,7 +391,7 @@ pub fn decompile(args: DecompilerArgs) {
};

let matched_resolved_functions = match_parameters(resolved_functions, &analyzed_function);

trace.br(func_analysis_trace);
if matched_resolved_functions.len() == 0 {
trace.add_warn(
Expand All @@ -401,7 +401,7 @@ pub fn decompile(args: DecompilerArgs) {
);
}
else {

let mut selected_function_index: u8 = 0;
if matched_resolved_functions.len() > 1 {
decompilation_progress.suspend(|| {
Expand Down Expand Up @@ -471,22 +471,22 @@ pub fn decompile(args: DecompilerArgs) {
);
});
}

let selected_match = match resolved_error_selectors.get(selected_error_index as usize) {
Some(selected_match) => selected_match,
None => {
logger.error("invalid selection.");
std::process::exit(1)
}
};

resolved_counter += 1;
analyzed_function.errors.insert(error_selector.clone(), Some(selected_match.clone()));
all_resolved_errors.insert(error_selector.clone(), selected_match.clone());
},
None => {}
}

}

if resolved_counter > 0 {
Expand All @@ -503,7 +503,7 @@ pub fn decompile(args: DecompilerArgs) {
for (event_selector, (_, raw_event)) in analyzed_function.events.clone() {
decompilation_progress.set_message(format!("resolving event '0x{}'", &event_selector.get(0..8).unwrap().to_string()));
let resolved_event_selectors = resolve_event_signature(&event_selector.get(0..8).unwrap().to_string());

// only continue if we have matches
match resolved_event_selectors {
Some(resolved_event_selectors) => {
Expand All @@ -520,7 +520,7 @@ pub fn decompile(args: DecompilerArgs) {
);
});
}

let selected_match = match resolved_event_selectors.get(selected_event_index as usize) {
Some(selected_match) => selected_match,
None => {
Expand Down Expand Up @@ -553,7 +553,7 @@ pub fn decompile(args: DecompilerArgs) {
decompilation_progress.finish_and_clear();
logger.info("symbolic execution completed.");
logger.info("building decompilation output.");

// create the decompiled source output
build_output(
&args,
Expand All @@ -569,3 +569,104 @@ pub fn decompile(args: DecompilerArgs) {
trace.display();
logger.debug(&format!("decompilation completed in {:?}.", now.elapsed()).to_string());
}

/// Builder pattern for using decompile method as a library.
///
/// Default values may be overriden individually.
/// ## Example
/// Use with normal settings:
/// ```no_run
/// # use crate::heimdall::decompile::DecompileBuilder;
/// const SOURCE: &'static str = "7312/* snip */04ad";
///
/// DecompileBuilder::new(SOURCE)
/// .decompile();
/// ```
/// Or change settings individually:
/// ```no_run
/// # use crate::heimdall::decompile::DecompileBuilder;
///
/// const SOURCE: &'static str = "7312/* snip */04ad";
/// DecompileBuilder::new(SOURCE)
/// .default(false)
/// .include_sol(false)
/// .output("my_contract_dir")
/// .rpc("https://127.0.0.1:8545")
/// .skip_resolving(true)
/// .verbosity(5)
/// .decompile();
/// ```
#[allow(dead_code)]
pub struct DecompileBuilder {
args: DecompilerArgs
}

impl DecompileBuilder where {
/// A new builder for the decompilation of the specified target.
///
/// The target may be a file, bytecode, contract address, or ENS name.
#[allow(dead_code)]
pub fn new(target: &str) -> Self {
DecompileBuilder {
args: DecompilerArgs {
target: target.to_string(),
verbose: clap_verbosity_flag::Verbosity::new(0, 0),
output: String::from(""),
rpc_url: String::from(""),
default: true,
skip_resolving: false,
include_solidity: true
}
}
}
/// Set the output verbosity level.
///
/// - -1 None
/// - 0 Error
/// - 1 Warn
/// - 2 Info
/// - 3 Debug
/// - 4 Trace
#[allow(dead_code)]
pub fn verbosity(mut self, level: i8) -> DecompileBuilder {
// Calculated by the log library as: 1 + verbose - quiet.
// Set quiet as 1, and the level corresponds to the appropriate Log level.
self.args.verbose = clap_verbosity_flag::Verbosity::new(level, 1);
self
}
/// The output directory to write the decompiled files to
#[allow(dead_code)]
pub fn output(mut self, directory: &str) -> DecompileBuilder {
self.args.output = directory.to_string();
self
}
/// The RPC provider to use for fetching target bytecode.
#[allow(dead_code)]
pub fn rpc(mut self, url: &str) -> DecompileBuilder {
self.args.rpc_url = url.to_string();
self
}
/// When prompted, always select the default value.
#[allow(dead_code)]
pub fn default(mut self, accept: bool) -> DecompileBuilder {
self.args.default = accept;
self
}
/// Whether to skip resolving function selectors.
#[allow(dead_code)]
pub fn skip_resolving(mut self, skip: bool) -> DecompileBuilder {
self.args.skip_resolving = skip;
self
}
/// Whether to include solidity source code in the output (in beta).
#[allow(dead_code)]
pub fn include_sol(mut self, include: bool) -> DecompileBuilder {
self.args.include_solidity = include;
self
}
/// Starts the decompilation.
#[allow(dead_code)]
pub fn decompile(self) {
decompile(self.args)
}
}
13 changes: 11 additions & 2 deletions heimdall/src/decompile/tests.rs
Expand Up @@ -62,7 +62,7 @@ mod postprocess_tests {

assert_eq!(postprocess(lines, HashMap::new(), HashMap::new(), &ProgressBar::new(128)), vec![String::from("uint256(arg0);")]);
}

#[test]
fn test_bitmask_conversion_mask_after() {
let lines = vec![
Expand Down Expand Up @@ -152,4 +152,13 @@ mod postprocess_tests {

assert_eq!(postprocess(lines, HashMap::new(), HashMap::new(), &ProgressBar::new(128)), vec![String::from("if (cast((arg0 * (arg1)) + 1 / 10)) {")]);
}
}
}

#[test]
fn decompile_as_library() {
use crate::decompile::DecompileBuilder;
const BYTECODE: &'static str = "731bf797219482a29013d804ad96d1c6f84fba4c453014608060405260043610610058576000357c0100000000000000000000000000000000000000000000000000000000900463ffffffff16806319045a251461005d575b600080fd5b6100c56004803603810190808035600019169060200190929190803590602001908201803590602001908080601f0160208091040260200160405190810160405280939291908181526020018383808284378201915050505050509192919290505050610107565b604051808273ffffffffffffffffffffffffffffffffffffffff1673ffffffffffffffffffffffffffffffffffffffff16815260200191505060405180910390f35b6000806000806041855114151561012157600093506101f6565b6020850151925060408501519150606085015160001a9050601b8160ff16101561014c57601b810190505b601b8160ff16141580156101645750601c8160ff1614155b1561017257600093506101f6565b600186828585604051600081526020016040526040518085600019166000191681526020018460ff1660ff1681526020018360001916600019168152602001826000191660001916815260200194505050505060206040516020810390808403906000865af11580156101e9573d6000803e3d6000fd5b5050506020604051035193505b505050929150505600a165627a7a72305820aacffa0494cd3f043493eee9c720bca9d5ef505ae7230ffc3d88c49ceeb7441e0029";
DecompileBuilder::new(BYTECODE).decompile();
}


1 change: 1 addition & 0 deletions heimdall/src/lib.rs
@@ -0,0 +1 @@
pub mod decompile;

0 comments on commit 79ad51e

Please sign in to comment.