Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Ideal: Improve Rust bindings Ks::asm and new example named kstool #589

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions bindings/rust/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
[package]
name = "keystone"
version = "0.9.2"
version = "0.10.0"
authors = [
"Remco Verhoef <remco.verhoef@dutchcoders.io>",
"Tasuku SUENAGA a.k.a. gunyarakun <tasuku-s-github@titech.ac>"
]
description = "Rust bindings for the keystone-engine"
edition = "2021"
license = "GPL-2.0"
readme = "README.md"
repository = "https://github.com/keystone-engine/keystone"
Expand All @@ -15,12 +16,13 @@ include = [

[dependencies]
libc = "0.2"
keystone-sys = { path = "keystone-sys", version = "0.9.2" }
keystone-sys = { path = "keystone-sys", version = "0.9.2", default-features = false }

[features]
default = []
default = ["use_system_keystone"]

use_system_keystone = ["keystone-sys/use_system_keystone"]
build_keystone_cmake = ["keystone-sys/build_keystone_cmake"]

[workspace]
members = ["kstool"]
7 changes: 2 additions & 5 deletions bindings/rust/examples/asm.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
extern crate keystone;
use keystone::*;

fn main() {
Expand All @@ -9,13 +8,11 @@ fn main() {
.option(OptionType::SYNTAX, OptionValue::SYNTAX_NASM)
.expect("Could not set option to nasm syntax");

let result = engine
.asm("mov ah, 0x80".to_string(), 0)
.expect("Could not assemble");
let result = engine.asm(c"mov ah, 0x80", 0).expect("Could not assemble");

println!("ASM result: {}", result);

if let Err(err) = engine.asm("INVALID".to_string(), 0) {
if let Err(err) = engine.asm(c"INVALID", 0) {
println!("Error: {}", err);
}
}
1 change: 1 addition & 0 deletions bindings/rust/keystone-sys/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ authors = [
"Remco Verhoef <remco.verhoef@dutchcoders.io>",
"Tasuku SUENAGA a.k.a. gunyarakun <tasuku-s-github@titech.ac>"
]
edition = "2021"
description = "Rust bindings for the keystone assembler"
repository = "https://github.com/keystone-engine/keystone"
documentation = "https://docs.rs/keystone/"
Expand Down
30 changes: 14 additions & 16 deletions bindings/rust/keystone-sys/build.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,13 @@
#[cfg(feature = "build_keystone_cmake")]
extern crate cmake;
#[cfg(feature = "use_system_keystone")]
extern crate pkg_config;

#[cfg(all(not(windows), feature = "build_keystone_cmake"))]
use std::os::unix::fs::symlink;
#[cfg(all(windows, feature = "build_keystone_cmake"))]
use std::os::windows::fs::symlink_dir as symlink;

#[cfg(feature = "build_keystone_cmake")]
use std::path::Path;
#[cfg(all(feature = "use_system_keystone", feature = "build_keystone_cmake"))]
compile_error!("mutual exclusive features: use_system_keystone & build_with_cmake");

#[cfg(feature = "build_keystone_cmake")]
fn build_with_cmake() {
#[cfg(not(windows))]
use std::os::unix::fs::symlink;
#[cfg(windows)]
use std::os::windows::fs::symlink_dir as symlink;
use std::path::Path;
if !Path::new("keystone").exists() {
// This only happens when using the crate via a `git` reference as the
// published version already embeds keystone's source.
Expand Down Expand Up @@ -44,11 +39,14 @@ fn build_with_cmake() {
}

fn main() {
if cfg!(feature = "use_system_keystone") {
#[cfg(feature = "use_system_keystone")]
#[cfg(feature = "use_system_keystone")]
{
pkg_config::find_library("keystone").expect("Could not find system keystone");
} else {
#[cfg(feature = "build_keystone_cmake")]
return;
}
#[cfg(feature = "build_keystone_cmake")]
{
build_with_cmake();
return;
}
}
3 changes: 2 additions & 1 deletion bindings/rust/keystone-sys/src/keystone_const.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#![allow(non_camel_case_types)]
#![expect(non_camel_case_types)]
// For Keystone Engine. AUTO-GENERATED FILE, DO NOT EDIT [keystone_const.rs]
use ::libc::*;
use bitflags::bitflags;

pub const API_MAJOR: c_uint = 0;
pub const API_MINOR: c_uint = 9;
Expand Down
23 changes: 5 additions & 18 deletions bindings/rust/keystone-sys/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,33 +2,20 @@
//! By Nguyen Anh Quynh <aquynh@gmail.com>, 2016 */
//! Rust bindings by Remco Verhoef <remco@dutchcoders.io>, 2016 */
//!
#![allow(bad_style)]

#[macro_use]
extern crate bitflags;
extern crate libc;

pub mod keystone_const;

use keystone_const::{Arch, Error, Mode, OptionType, OptionValue};
use ::std::{
ffi::CStr,
fmt,
ptr,
};
use ::libc::{
c_char,
c_uchar,
c_int,
c_uint,
size_t,
};
use crate::keystone_const::{Arch, Error, Mode, OptionType, OptionValue};
use ::libc::{c_char, c_int, c_uchar, c_uint, size_t};
use ::std::{ffi::CStr, fmt, ptr};

/// Opaque type representing the Keystone engine
#[repr(C)]
pub struct ks_engine {
_private: [u8; 0],
}

#[expect(non_camel_case_types)]
pub type ks_handle = ptr::NonNull<ks_engine>;

extern "C" {
Expand Down
26 changes: 26 additions & 0 deletions bindings/rust/kstool/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
[package]
name = "keystone-cli"
version = "0.1.0"
edition = "2021"
description = "Quick assembler using keystone-engine for CTF"
license = "MIT"
resolver = "2"

[[bin]]
name = "kstool"
path = "src/main.rs"

[dependencies]
# keystone = { version = "0.1", package = "keystone-engine", default-features = false }
argh = "0.1"

[dependencies.keystone]
version = "0.2"
# in my forked
package = "keystone-engine"
default-features = false

[features]
default = ["use_system"]
use_system = ["keystone/use-system-lib"]
use_cmake = ["keystone/cmake"]
130 changes: 130 additions & 0 deletions bindings/rust/kstool/src/main.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
use argh::FromArgs;
use std::ffi::CString;
use std::fmt;
use std::io;
use std::str::FromStr;

use keystone::{Arch, Keystone, Mode};

struct InvalidFromat;

impl fmt::Display for InvalidFromat {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "one of (raw, hex)")
}
}

impl FromStr for FormatOuput {
type Err = InvalidFromat;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let this = match s {
"raw" => Self::Raw,
"hex" => Self::Hex,
_ => return Err(InvalidFromat),
};
Ok(this)
}
}

#[derive(Clone, Copy, Default)]
enum FormatOuput {
#[default]
Hex,
Raw,
// String, // as python str
// Elf,
}

#[derive(FromArgs)]
/// Assemble shellcode into bytes
struct Args {
// print version
//#[argh(switch, short = 'v')]
//version: bool,
/// output format (defaults to hex for ttys, otherwise raw)
#[argh(option, short = 'f')]
format: Option<FormatOuput>,
// output filename (defaults to stdout)
//#[argh(option, short = 'o')]
//output: Option<PathBuf>,
/// arch/mode (defaults to x64)
#[argh(option, short = 'c')]
mode: Option<String>,
/// input filename
#[argh(switch, short = 'i')]
infile: bool,
/// assembly strings or infile with -i
#[argh(positional)]
asm: String,
}

fn main() -> Result<(), i32> {
let args: Args = argh::from_env();
let mode = args.mode.as_deref().unwrap_or("x64");
let (arch, mode) = match mode {
"x16" => (Arch::X86, Mode::MODE_16),
"x32" => (Arch::X86, Mode::MODE_32),
"x64" => (Arch::X86, Mode::MODE_64),
"arm" => (Arch::ARM, Mode::ARM | Mode::LITTLE_ENDIAN),
"armbe" => (Arch::ARM, Mode::ARM | Mode::BIG_ENDIAN),
"thumb" => (Arch::ARM, Mode::THUMB | Mode::LITTLE_ENDIAN),
"thumbbe" => (Arch::ARM, Mode::THUMB | Mode::LITTLE_ENDIAN),
"armv8" => (Arch::ARM, Mode::ARM | Mode::LITTLE_ENDIAN | Mode::V8),
"armv8be" => (Arch::ARM, Mode::ARM | Mode::BIG_ENDIAN | Mode::V8),
"thumbv8" => (Arch::ARM, Mode::THUMB | Mode::LITTLE_ENDIAN | Mode::V8),
"thumbv8be" => (Arch::ARM, Mode::THUMB | Mode::BIG_ENDIAN | Mode::V8),
"arm64" => (Arch::ARM64, Mode::LITTLE_ENDIAN),
"hexagon" => (Arch::HEXAGON, Mode::BIG_ENDIAN),
"mips" => (Arch::MIPS, Mode::MIPS32 | Mode::LITTLE_ENDIAN),
"mipsbe" => (Arch::MIPS, Mode::MIPS32 | Mode::BIG_ENDIAN),
"mips64" => (Arch::MIPS, Mode::LITTLE_ENDIAN),
"mips64be" => (Arch::MIPS, Mode::BIG_ENDIAN),
"ppc32be" => (Arch::PPC, Mode::PPC32 | Mode::BIG_ENDIAN),
"ppc64" => (Arch::PPC, Mode::PPC64 | Mode::LITTLE_ENDIAN),
"ppc64be" => (Arch::PPC, Mode::PPC64 | Mode::BIG_ENDIAN),
"sparc" => (Arch::SPARC, Mode::SPARC32 | Mode::LITTLE_ENDIAN),
"sparcbe" => (Arch::SPARC, Mode::SPARC32 | Mode::BIG_ENDIAN),
"sparc64be" => (Arch::SPARC, Mode::SPARC64 | Mode::BIG_ENDIAN),
"systemz" => (Arch::SYSTEMZ, Mode::BIG_ENDIAN),
"evm" => (Arch::EVM, Mode::LITTLE_ENDIAN),
//"riscv32" => (Arch::RISCV, Mode::RISCV32 | Mode::LITTLE_ENDIAN),
//"riscv64" => (Arch::RISCV, Mode::RISCV64 | Mode::LITTLE_ENDIAN),
_ => {
eprintln!("invalid arch/mode: {mode}");
return Err(1);
}
};
let engine = Keystone::new(arch, mode).expect("could not initialize Keystone engine");

let asm = if args.infile {
let Ok(data) = std::fs::read(&args.asm) else {
panic!("cannot read filename {path}", path = &args.asm);
};
CString::from_vec_with_nul(data).expect("file shouldn't contain NUL bytes")
} else {
let norm = args
.asm
.replace("word [", "word ptr [")
.replace("dword [", "dword ptr [")
.replace("qword [", "qword ptr [");
CString::new(norm).expect("assembly shouldn't contain NUL bytes")
};

let result = engine.asm(&asm, 0).expect("could not assemble");
let output_format = args.format.unwrap_or(FormatOuput::default());
match output_format {
FormatOuput::Hex => {
println!("{result}");
}
FormatOuput::Raw => {
use io::Write;
let stdout = io::stdout();
let mut handle = stdout.lock();
handle
.write_all(&result.as_bytes())
.expect("cannot write to stdout");
}
}

Ok(())
}
Loading