Skip to content

Commit

Permalink
Merge pull request #29 from Jon-Becker/feat/decompile
Browse files Browse the repository at this point in the history
✨ feat: decompile
  • Loading branch information
Jon-Becker authored Dec 23, 2022
2 parents 1018f61 + 8f60abd commit a6cb572
Show file tree
Hide file tree
Showing 34 changed files with 1,565 additions and 307 deletions.
11 changes: 6 additions & 5 deletions .github/workflows/rust_build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,13 @@ jobs:

steps:
- uses: actions/checkout@v3
- name: Build
- name: Compile
working-directory: ./heimdall
run: cargo build --verbose
- name: Run tests

- name: Run Tests
working-directory: ./heimdall
run: |
cargo test --package heimdall
cargo test --package heimdall-config
cargo test --package heimdall-common
cargo test --package heimdall -- test_ --nocapture
cargo test --package heimdall-config -- test_ --nocapture
cargo test --package heimdall-common -- test_ --nocapture
5 changes: 4 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@ output
output/*
*/output
*.evm
*.asm
*.asm

heimdall/scripts/update
heimdall/scripts/git
Empty file modified bifrost/bifrost
100755 → 100644
Empty file.
6 changes: 3 additions & 3 deletions common/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "heimdall-common"
version = "0.1.7"
version = "0.2.0"
edition = "2021"
license = "MIT"
readme = "README.md"
Expand All @@ -9,12 +9,12 @@ keywords = ["ethereum", "web3", "decompiler", "evm", "crypto"]

[dependencies]
colored = "2"
regex = "1.1.5"
fancy-regex = "0.10.0"
lazy_static = "1.4.0"
clap-verbosity-flag = "1.0.0"
indicatif = "0.17.0"
tokio = { version = "1", features = ["full"] }
clap = { version = "3.1.18", features = ["derive"] }
ethers = "1.0.0"
reqwest = { version = "0.11.11", features = ["blocking"] }
serde_json = "1.0"
serde_json = "1.0"
11 changes: 10 additions & 1 deletion common/src/consts.rs → common/src/constants.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use regex::Regex;
use fancy_regex::Regex;
use lazy_static::lazy_static;

lazy_static! {
Expand All @@ -16,4 +16,13 @@ lazy_static! {

// The following regex is used to reduce null byte prefixes
pub static ref REDUCE_HEX_REGEX: Regex = Regex::new(r"^0x(00)*").unwrap();

// The following regex is used as a search pattern for words
pub static ref WORD_REGEX: Regex = Regex::new(r"0x[0-9a-fA-F]{0,64}").unwrap();

// The following regex is used to find type castings
pub static ref TYPE_CAST_REGEX: Regex = Regex::new(r"(address\(|string\(|bool\(|bytes(\d*)\(|uint(\d*)\(|int(\d*)\()").unwrap();

// The following regex is used to find memory accesses
pub static ref MEMLEN_REGEX: Regex = Regex::new(r"memory\[memory\[[0-9x]*\]\]").unwrap();
}
12 changes: 6 additions & 6 deletions common/src/ether/evm/disassemble.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ use ethers::{
providers::{Middleware, Provider, Http},
};
use crate::{
consts::{ ADDRESS_REGEX, BYTECODE_REGEX },
constants::{ ADDRESS_REGEX, BYTECODE_REGEX },
io::{ logging::*, file::* },
ether::evm::{ opcodes::opcode }
};
Expand Down Expand Up @@ -66,7 +66,7 @@ pub fn disassemble(args: DisassemblerArgs) -> String {
}

let contract_bytecode: String;
if ADDRESS_REGEX.is_match(&args.target) {
if ADDRESS_REGEX.is_match(&args.target).unwrap() {

// push the address to the output directory
if &output_dir != &args.output {
Expand Down Expand Up @@ -118,7 +118,7 @@ pub fn disassemble(args: DisassemblerArgs) -> String {
});

}
else if BYTECODE_REGEX.is_match(&args.target) {
else if BYTECODE_REGEX.is_match(&args.target).unwrap() {
contract_bytecode = args.target.clone();
}
else {
Expand All @@ -131,7 +131,7 @@ pub fn disassemble(args: DisassemblerArgs) -> String {
// We are disassembling a file, so we need to read the bytecode from the file.
contract_bytecode = match fs::read_to_string(&args.target) {
Ok(contents) => {
if BYTECODE_REGEX.is_match(&contents) && contents.len() % 2 == 0 {
if BYTECODE_REGEX.is_match(&contents).unwrap() && contents.len() % 2 == 0 {
contents.replacen("0x", "", 1)
}
else {
Expand Down Expand Up @@ -178,11 +178,11 @@ pub fn disassemble(args: DisassemblerArgs) -> String {
program_counter += 1;
}

logger.success(&format!("disassembled {} bytes successfully.", program_counter).to_string());
logger.info(&format!("disassembled {} bytes successfully.", program_counter).to_string());

write_file(&String::from(format!("{}/bytecode.evm", &output_dir)), &contract_bytecode);
let file_path = write_file(&String::from(format!("{}/disassembled.asm", &output_dir)), &output);
logger.info(&format!("wrote disassembled bytecode to '{}' .", file_path).to_string());
logger.success(&format!("wrote disassembled bytecode to '{}' .", file_path).to_string());

logger.debug(&format!("disassembly completed in {} ms.", now.elapsed().as_millis()).to_string());

Expand Down
7 changes: 2 additions & 5 deletions common/src/ether/evm/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ impl Memory {
}

pub fn extend(&mut self, offset: u128, size: u128) {

// calculate the new size of the memory
let r = (offset + size) % 32;
let new_mem_size: u128;
Expand Down Expand Up @@ -65,11 +66,7 @@ impl Memory {
}

// read a value from the memory at the given offset, with a fixed size
pub fn read(&self, mut offset: usize, size: usize) -> String {
// cap offset to 2**16 for optimization
if offset > 65536 {
offset = 65536;
}
pub fn read(&self, offset: usize, size: usize) -> String {

// if the offset + size will be out of bounds, append null bytes until the size is met
if offset + size > self.size() as usize {
Expand Down
4 changes: 4 additions & 0 deletions common/src/ether/evm/stack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,10 @@ impl Stack {
values
}

pub fn size(&self) -> usize {
self.stack.len()
}

// Check if the stack is empty.
pub fn is_empty(&self) -> bool {
self.stack.is_empty()
Expand Down
19 changes: 18 additions & 1 deletion common/src/ether/evm/types.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use colored::Colorize;
use ethers::abi::{ParamType, Token, AbiEncode};

use crate::utils::strings::replace_last;
use crate::{utils::strings::{replace_last, find_balanced_encapsulator}, constants::TYPE_CAST_REGEX};

use super::vm::Instruction;

Expand Down Expand Up @@ -232,4 +232,21 @@ pub fn byte_size_to_type(byte_size: usize) -> (usize, Vec<String>) {

// return list of potential type castings, sorted by likelihood descending
(byte_size, potential_types)
}

pub fn find_cast(line: String) -> (usize, usize, Option<String>) {

// find the start of the cast
match TYPE_CAST_REGEX.find(&line).unwrap() {
Some(m) => {
let start = m.start();
let end = m.end() - 1;
let cast_type = line[start..].split("(").collect::<Vec<&str>>()[0].to_string();

// find where the cast ends
let (a, b, _) = find_balanced_encapsulator(line[end..].to_string(), ('(', ')'));
return (end+a, end+b, Some(cast_type))
},
None => return (0, 0, None),
}
}
3 changes: 3 additions & 0 deletions common/src/ether/evm/vm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ pub struct Instruction {
}

impl VM {

// Creates a new VM instance
pub fn new(
bytecode: String,
Expand Down Expand Up @@ -122,6 +123,7 @@ impl VM {

// Steps to the next PC and executes the instruction
fn _step(&mut self) -> Instruction {

// sanity check
if self.bytecode.len() < (self.instruction * 2 + 2) as usize {
self.exit(2, "0x");
Expand Down Expand Up @@ -1607,6 +1609,7 @@ impl VM {
};
}
_ => {

// we reached an INVALID opcode, consume all remaining gas
self.exit(4, "0x");
return Instruction {
Expand Down
6 changes: 3 additions & 3 deletions common/src/ether/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ pub struct ResolvedLog {
pub fn resolve_function_signature(signature: &String) -> Option<Vec<ResolvedFunction>> {

// get function possibilities from 4byte
let signatures = match get_json_from_url(format!("https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=0x{}", &signature)) {
let signatures = match get_json_from_url(format!("https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=0x{}", &signature), 3) {
Some(signatures) => signatures,
None => return None
};
Expand Down Expand Up @@ -84,7 +84,7 @@ pub fn resolve_function_signature(signature: &String) -> Option<Vec<ResolvedFunc
pub fn resolve_error_signature(signature: &String) -> Option<Vec<ResolvedError>> {

// get function possibilities from 4byte
let signatures = match get_json_from_url(format!("https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=0x{}", &signature)) {
let signatures = match get_json_from_url(format!("https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=0x{}", &signature), 3) {
Some(signatures) => signatures,
None => return None
};
Expand Down Expand Up @@ -140,7 +140,7 @@ pub fn resolve_error_signature(signature: &String) -> Option<Vec<ResolvedError>>
pub fn resolve_event_signature(signature: &String) -> Option<Vec<ResolvedLog>> {

// get function possibilities from 4byte
let signatures = match get_json_from_url(format!("https://sig.eth.samczsun.com/api/v1/signatures?all=true&event=0x{}", &signature)) {
let signatures = match get_json_from_url(format!("https://sig.eth.samczsun.com/api/v1/signatures?all=true&function=0x{}", &signature), 3) {
Some(signatures) => signatures,
None => return None
};
Expand Down
Loading

0 comments on commit a6cb572

Please sign in to comment.