Repository: cch123/asm-cli-rust Branch: master Commit: 6289810f92d2 Files: 9 Total size: 25.1 KB Directory structure: gitextract_s1az94o5/ ├── .gitignore ├── CODE_OF_CONDUCT.md ├── Cargo.toml ├── LICENSE ├── readme.md └── src/ ├── engine/ │ ├── cpu.rs │ ├── machine.rs │ └── mod.rs └── main.rs ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ .idea/ target ================================================ FILE: CODE_OF_CONDUCT.md ================================================ # Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, sex characteristics, gender identity and expression, level of experience, education, socio-economic status, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at cao1988228@163.com. All complaints will be reviewed and investigated and will result in a response that is deemed necessary and appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html [homepage]: https://www.contributor-covenant.org For answers to common questions about this code of conduct, see https://www.contributor-covenant.org/faq ================================================ FILE: Cargo.toml ================================================ [package] name = "asm-cli-rust" version = "0.1.0" authors = ["Xargin "] edition = "2018" [dependencies] capstone = "^0.9.0" keystone = {git = "https://github.com/keystone-engine/keystone/", rev = "18569351000cf1b8bd1ea2cc8a02c2e17b76391f"} unicorn-engine = {git = "https://github.com/unicorn-engine/unicorn/", rev = "8fb4b45f57557eb82a165ec24ed8a2aa2f9f201b"} hex = "0.3.2" rustyline = "9.1.0" ansi_term = "0.11.0" maplit = "1" lazy_static = "1.4.0" clap = { version = "4.0.26", features = ["derive"] } proc-macro2 = { version = "=1.0.93", features=["default", "proc-macro"] } ================================================ FILE: LICENSE ================================================ The Star And Thank Author License (SATA) Copyright © 2018 Xargin(cao1988228@163.com) Project Url: https://github.com/cch123/asm-cli-rust Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. And wait, the most important, you shall star/+1/like the project(s) in project url section above first, and then thank the author(s) in Copyright section. Here are some suggested ways: - Email the authors a thank-you letter, and make friends with him/her/them. - Report bugs or issues. - Tell friends what a wonderful project this is. - And, sure, you can just express thanks in your mind without telling the world. Contributors of this project by forking have the option to add his/her name and forked project url at copyright and project url sections, but shall not delete or modify anything else in these two sections. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: readme.md ================================================ # Overview this project is inspired by https://github.com/poppycompass/asmshell ## Preview ![x64](images/x64.png) ## Usage ``` shell> asm-cli-rust [x86/x64] default : x64 optional: x86 ``` key_up/key_down: history ## Build from source 1. `cargo b` ## Go version https://github.com/cch123/asm-cli ================================================ FILE: src/engine/cpu.rs ================================================ use std::{collections::HashMap, convert::TryFrom}; use maplit::hashmap; use unicorn_engine::{unicorn_const, RegisterX86, Unicorn}; #[derive(Clone, Copy, Debug)] pub enum Mode { Mode32, Mode64, } #[allow(clippy::from_over_into)] impl Into for Mode { fn into(self) -> unicorn_const::Mode { match self { Self::Mode32 => unicorn_const::Mode::MODE_32, Self::Mode64 => unicorn_const::Mode::MODE_64, } } } #[allow(clippy::from_over_into)] impl Into for Mode { fn into(self) -> keystone::Mode { match self { Self::Mode32 => keystone::Mode::MODE_32, Self::Mode64 => keystone::Mode::MODE_64, } } } impl TryFrom for Mode { type Error = &'static str; fn try_from(value: unicorn_const::Mode) -> Result { match value { unicorn_const::Mode::MODE_32 => Ok(Self::Mode32), unicorn_const::Mode::MODE_64 => Ok(Self::Mode64), _ => Err("unsupported mode"), } } } #[derive(Clone, Copy, Debug)] pub enum Arch { X86, } #[allow(clippy::from_over_into)] impl Into for Arch { fn into(self) -> unicorn_const::Arch { match self { Self::X86 => unicorn_const::Arch::X86, } } } #[allow(clippy::from_over_into)] impl Into for Arch { fn into(self) -> keystone::Arch { match self { Self::X86 => keystone::Arch::X86, } } } impl TryFrom for Arch { type Error = &'static str; fn try_from(value: unicorn_const::Arch) -> Result { match value { unicorn_const::Arch::X86 => Ok(Self::X86), _ => Err("unsupported arch"), } } } impl Arch { fn dump_registers( &self, emu: &Unicorn<'static, ()>, registers: HashMap<&'static str, i32>, ) -> HashMap<&'static str, u64> { registers .keys() .filter(|&&x| x != "end") // "end" is pseudo-register (to add new row) .map(|®_name| { ( reg_name, emu.reg_read(*registers.get(reg_name).unwrap()).unwrap(), ) }) .collect::>() } } pub trait ArchMeta { fn cpu(&self) -> (Arch, Mode); fn sp_reg(&self) -> i32; fn fp_reg(&self) -> i32; fn int_size(&self) -> usize; fn sorted_reg_names(&self) -> Vec<&'static str>; fn register_map(&self) -> HashMap<&'static str, i32>; fn dump_registers(&self, emu: &Unicorn<'static, ()>) -> HashMap<&'static str, u64>; } #[derive(Clone, Copy, Debug)] pub struct X32 { inner: Arch, } impl X32 { pub fn new(arch: Arch) -> Self { Self { inner: arch } } } impl ArchMeta for X32 { fn cpu(&self) -> (Arch, Mode) { (self.inner, Mode::Mode32) } fn sp_reg(&self) -> i32 { i32::from(RegisterX86::ESP) } fn fp_reg(&self) -> i32 { i32::from(RegisterX86::EBP) } fn int_size(&self) -> usize { 32 / 8 } fn sorted_reg_names(&self) -> Vec<&'static str> { vec![ "eax", "ebx", "ecx", "edx", "end", // "esi", "edi", "end", // "eip", "ebp", "esp", "end", // "flags", "end", // "cs", "ss", "ds", "es", "end", // "fs", "gs", "end", // ] } fn register_map(&self) -> HashMap<&'static str, i32> { // register to trace, display, etc. hashmap! { "eax" => i32::from(RegisterX86::EAX), "ebx" => i32::from(RegisterX86::EBX), "ecx" => i32::from(RegisterX86::ECX), "edx" => i32::from(RegisterX86::EDX), "esi" => i32::from(RegisterX86::ESI), "edi" => i32::from(RegisterX86::EDI), "eip" => i32::from(RegisterX86::EIP), "ebp" => i32::from(RegisterX86::EBP), "esp" => i32::from(RegisterX86::ESP), "flags" => i32::from(RegisterX86::EFLAGS), "cs" => i32::from(RegisterX86::CS), "ss" => i32::from(RegisterX86::SS), "ds" => i32::from(RegisterX86::DS), "es" => i32::from(RegisterX86::ES), "fs" => i32::from(RegisterX86::FS), "gs" => i32::from(RegisterX86::GS), } } fn dump_registers(&self, emu: &Unicorn<'static, ()>) -> HashMap<&'static str, u64> { self.inner.dump_registers(emu, self.register_map()) } } #[derive(Clone, Copy, Debug)] pub struct X64 { inner: Arch, } impl X64 { pub fn new(arch: Arch) -> X64 { X64 { inner: arch } } } impl ArchMeta for X64 { fn cpu(&self) -> (Arch, Mode) { (self.inner, Mode::Mode64) } fn sp_reg(&self) -> i32 { i32::from(RegisterX86::RSP) } fn fp_reg(&self) -> i32 { i32::from(RegisterX86::RBP) } fn int_size(&self) -> usize { 64 / 8 } fn sorted_reg_names(&self) -> Vec<&'static str> { vec![ "rax", "rbx", "rcx", "rdx", "end", // "rsi", "rdi", "r8", "r9", "end", // "r10", "r11", "r12", "r13", "end", // "r14", "r15", "end", // "rip", "rbp", "rsp", "end", // "cs", "ss", "ds", "es", "end", // "fs", "gs", "end", "flags", "end", // ] } fn register_map(&self) -> HashMap<&'static str, i32> { // register to trace, display, etc. hashmap! { "rax" => i32::from(RegisterX86::RAX), "rbx" => i32::from(RegisterX86::RBX), "rcx" => i32::from(RegisterX86::RCX), "rdx" => i32::from(RegisterX86::RDX), "rsi" => i32::from(RegisterX86::RSI), "rdi" => i32::from(RegisterX86::RDI), "r8" => i32::from(RegisterX86::R8), "r9" => i32::from(RegisterX86::R9), "r10" => i32::from(RegisterX86::R10), "r11" => i32::from(RegisterX86::R11), "r12" => i32::from(RegisterX86::R12), "r13" => i32::from(RegisterX86::R13), "r14" => i32::from(RegisterX86::R14), "r15" => i32::from(RegisterX86::R15), "rip" => i32::from(RegisterX86::RIP), "rbp" => i32::from(RegisterX86::RBP), "rsp" => i32::from(RegisterX86::RSP), "flags" => i32::from(RegisterX86::EFLAGS), "cs" => i32::from(RegisterX86::CS), "ss" => i32::from(RegisterX86::SS), "ds" => i32::from(RegisterX86::DS), "es" => i32::from(RegisterX86::ES), "fs" => i32::from(RegisterX86::FS), "gs" => i32::from(RegisterX86::GS), } } fn dump_registers(&self, emu: &Unicorn<'static, ()>) -> HashMap<&'static str, u64> { self.inner.dump_registers(emu, self.register_map()) } } ================================================ FILE: src/engine/machine.rs ================================================ use ansi_term::Colour::{Blue, Purple, Yellow}; use keystone::{AsmResult, Error}; use std::collections::HashMap; use unicorn_engine::unicorn_const::uc_error; use unicorn_engine::unicorn_const::SECOND_SCALE; use unicorn_engine::Unicorn; use super::cpu; #[derive(Debug)] pub enum MachineError { Unsupported, Keystone(keystone::Error), Unicorn(uc_error), } pub struct Machine<'a> { pub register_map: HashMap<&'a str, i32>, pub assembler: keystone::Keystone, pub emu: Unicorn<'static, ()>, pub sorted_reg_names: Vec<&'a str>, pub byte_size: usize, pub previous_reg_value: HashMap<&'a str, u64>, pub cpu: (cpu::Arch, cpu::Mode), pub sp: i32, // stack pointer pub fp: i32, // stack frame } impl<'a> Machine<'a> { pub fn new(arch: cpu::Arch, mode: cpu::Mode) -> Result { let emu = Self::init_unicorn(arch, mode)?; let assembler = Self::init_keystone(arch, mode)?; let arch_meta = Self::get_arch_meta(arch, mode)?; let register_map = arch_meta.register_map(); let prev_reg_value = arch_meta.dump_registers(&emu); Ok(Self { emu, assembler, register_map, sorted_reg_names: arch_meta.sorted_reg_names(), byte_size: arch_meta.int_size(), previous_reg_value: prev_reg_value, cpu: arch_meta.cpu(), sp: arch_meta.sp_reg(), fp: arch_meta.fp_reg(), }) } pub fn new_from_arch(arch_name: &str) -> Result { let arch_meta = Self::get_arch_meta_from_name(arch_name)?; let cpu = arch_meta.cpu(); Self::new(cpu.0, cpu.1) } pub(crate) fn init_unicorn( arch: cpu::Arch, mode: cpu::Mode, ) -> Result, MachineError> { Unicorn::new(arch.into(), mode.into()).map_err(MachineError::Unicorn) } pub(crate) fn init_keystone( arch: cpu::Arch, mode: cpu::Mode, ) -> Result { keystone::Keystone::new(arch.into(), mode.into()).map_err(MachineError::Keystone) } pub(crate) fn get_arch_name( arch: cpu::Arch, mode: cpu::Mode, ) -> Result<&'static str, MachineError> { match arch { cpu::Arch::X86 => match mode { cpu::Mode::Mode32 => Ok("x32"), cpu::Mode::Mode64 => Ok("x64"), // _ => Err(MachineError::Unsupported), }, // _ => Err(MachineError::Unsupported), } } pub(crate) fn get_arch_meta( arch: cpu::Arch, mode: cpu::Mode, ) -> Result, MachineError> { let arch_name = Self::get_arch_name(arch, mode)?; Self::get_arch_meta_from_name(arch_name) } pub(crate) fn get_arch_meta_from_name( arch_name: &str, ) -> Result, MachineError> { match arch_name { "x32" => Ok(Box::new(cpu::X32::new(cpu::Arch::X86))), "x64" => Ok(Box::new(cpu::X64::new(cpu::Arch::X86))), _ => Err(MachineError::Unsupported), } } pub fn set_sp(&mut self, value: u64) -> Result<(), MachineError> { self.emu .reg_write(self.sp, value) .map_err(MachineError::Unicorn) } pub fn set_fp(&mut self, value: u64) -> Result<(), MachineError> { self.emu .reg_write(self.fp, value) .map_err(MachineError::Unicorn) } } impl<'a> Machine<'a> { pub fn print_machine(&self) { println!("arch: {:?} mode: {:?}", self.cpu.0, self.cpu.1); } pub fn print_register(&mut self) { println!( "{}", Yellow.paint("----------------- cpu context -----------------") ); let mut current_reg_val_map = HashMap::new(); for ®_name in &self.sorted_reg_names { if reg_name == "end" { println!(); continue; } let &uc_reg = self.register_map.get(reg_name).unwrap(); // pad reg_name to 3 bytes let mut padded_reg_name = reg_name.to_string(); while padded_reg_name.len() < 3 { padded_reg_name.push(' '); } let reg_val = self.emu.reg_read(uc_reg).unwrap(); let previous_reg_val = *self.previous_reg_value.get(reg_name).unwrap(); let reg_val_str = match self.byte_size { 4 => format!("0x{:08x}", reg_val), 8 => format!("0x{:016x}", reg_val), _ => unreachable!(), }; if previous_reg_val != reg_val { print!("{} : {} ", padded_reg_name, Blue.paint(reg_val_str)); } else { print!("{} : {} ", padded_reg_name, reg_val_str); } current_reg_val_map.insert(reg_name, reg_val); if reg_name == "flags" { self.print_flags(reg_val); } } self.previous_reg_value = current_reg_val_map; } pub fn asm(&self, str: String, address: u64) -> Result { self.assembler.asm(str, address) } pub fn write_instruction(&mut self, byte_arr: Vec) { let address = 0x0000; let _ = self.emu.mem_write(address, &byte_arr); let _ = self.emu.emu_start( address, address + byte_arr.len() as u64, 10 * SECOND_SCALE, 1000, ); } pub fn print_stack(&self) { println!( "{}", Purple.paint("----------------- stack context -----------------") ); let cur_sp_val = self.emu.reg_read(self.sp).unwrap(); //let start_address = (0x1300000 - 8 * self.byte_size) as u64; let mut start_address: u64 = 0x1300000; while cur_sp_val < start_address - 4 * self.byte_size as u64 { start_address -= 4 * self.byte_size as u64; } start_address -= 8 * self.byte_size as u64; let mem_data = self .emu .mem_read_as_vec(start_address, self.byte_size * 4 * 5) .unwrap(); // 8 个字节打印一次 (0..mem_data.len()) .step_by(4 * self.byte_size) .for_each(|idx| { match self.byte_size { 4 => print!("{:08x} : ", start_address + idx as u64), 8 => print!("{:016x} : ", start_address + idx as u64), _ => unreachable!(), } (0..4).for_each(|offset| { let (start_pos, end_pos) = ( idx + offset * self.byte_size, idx + offset * self.byte_size + self.byte_size, ); let mut cur = mem_data[start_pos..end_pos].to_vec(); cur.reverse(); if (start_address + start_pos as u64) == cur_sp_val { print!("{} ", Blue.paint(hex::encode(cur))); } else { print!("{} ", hex::encode(cur)); } }); println!(); }); println!(); } fn print_flags(&self, flag_val: u64) { let flag_names = vec!["cf", "zf", "of", "sf", "pf", "af", "df"]; let name_to_bit = vec![ ("cf", 0), ("pf", 2), ("af", 4), ("zf", 6), ("sf", 7), ("df", 10), ("of", 11), ] .into_iter() .collect::>(); for flag_name in flag_names { let bit_pos = name_to_bit.get(flag_name).unwrap(); let flag_val = flag_val >> (*bit_pos as u64) & 1; match flag_val { 0 => print!("{}({}) ", flag_name, flag_val), 1 => print!("{} ", Blue.paint(format!("{}({})", flag_name, flag_val))), _ => unreachable!(), } } } } ================================================ FILE: src/engine/mod.rs ================================================ pub mod cpu; pub mod machine; ================================================ FILE: src/main.rs ================================================ use ansi_term::Colour::Red; use clap::Parser; use keystone::{OptionType, OptionValue, Error}; use crate::engine::machine::MachineError; use rustyline::error::ReadlineError; use rustyline::{Cmd, Editor, KeyCode, KeyEvent, Modifiers}; use unicorn_engine::unicorn_const::Permission; pub mod engine; use crate::engine::machine::Machine; #[derive(clap::ValueEnum, Clone, Debug, PartialEq)] #[allow(clippy::upper_case_acronyms)] enum AssemblerSyntax { INTEL, ATT, GAS, MASM, NASM, } #[derive(clap::Parser, Debug)] struct Args { #[arg(short, long)] arch: Option, #[arg(short, long, value_enum)] syntax: Option, #[arg(long, default_value_t = 0x01300000)] initial_sp: u64, #[arg(long, default_value_t = 0x10000000)] initial_fp: u64, #[arg(long, default_value_t = 0)] initial_mem_begin: u64, #[arg(long, default_value_t = 0x20000000)] initial_mem_size: usize, #[arg(long, default_value_t = String::from("ALL"))] initial_mem_prot: String, } fn get_machine(arch_name: String) -> Machine<'static> { Machine::new_from_arch(arch_name.as_str()).unwrap() } fn parse_permission(prot: &str) -> Permission { match prot { "READ" => Permission::READ, "WRITE" => Permission::WRITE, "EXEC" => Permission::EXEC, "NONE" => Permission::NONE, _ => Permission::ALL, } } fn main() { // let args: Vec = env::args().collect(); let args = Args::parse(); let arch_name = match args.arch { Some(r) => r, None => "x64".to_string(), }; let ass_syntax = match args.syntax { Some(syntax) => match syntax { AssemblerSyntax::INTEL => OptionValue::SYNTAX_INTEL, AssemblerSyntax::ATT => OptionValue::SYNTAX_ATT, AssemblerSyntax::GAS => OptionValue::SYNTAX_GAS, AssemblerSyntax::MASM => OptionValue::SYNTAX_MASM, AssemblerSyntax::NASM => OptionValue::SYNTAX_NASM, }, None => OptionValue::SYNTAX_INTEL, }; let mut m: Machine = get_machine(arch_name); // machine init println!("initial: sp={:?} fp={:?}", args.initial_sp, args.initial_fp); println!("ass_syntax: {:?}", ass_syntax); m.set_sp(args.initial_sp) .expect("failed to write stack pointer"); m.set_fp(args.initial_fp) .expect("failed to write stack frame"); let mem_prot = parse_permission(&args.initial_mem_prot); println!("permission: {:?}", mem_prot); m.emu .mem_map( args.initial_mem_begin, args.initial_mem_size, mem_prot, ) .expect("failed to mem map"); m.assembler .option(OptionType::SYNTAX, ass_syntax) .expect("failed to change assembler syntax"); m.print_machine(); m.print_register(); m.print_stack(); let mut rl = Editor::<()>::new(); rl.bind_sequence(KeyEvent(KeyCode::Down, Modifiers::NONE), Cmd::NextHistory); rl.bind_sequence(KeyEvent(KeyCode::Up, Modifiers::NONE), Cmd::PreviousHistory); loop { let input = rl.readline(Red.paint(">> ").to_string().as_str()); match input { Ok(line) => { let result = m.asm(line.to_string(), 0); if line.is_empty() { println!("failed to assemble, err: {:?}", Err::(MachineError::Unsupported)); } match result { Ok(r) => { rl.add_history_entry(line.as_str()); println!( "{} : {} {} : {}", Red.paint("mnemonic"), line.trim(), Red.paint("hex"), r ); m.write_instruction(r.bytes); m.print_register(); m.print_stack(); } Err(e) => println!("failed to assemble, err: {:?}", e), } } Err(ReadlineError::Interrupted) => { println!("CTRL-C"); break; } Err(ReadlineError::Eof) => { println!("CTRL-D"); break; } Err(err) => { println!("Error: {:?}", err); break; } } } }