From 9c6134ca918a0496d1ef5b4efadef1ffbb1b5361 Mon Sep 17 00:00:00 2001 From: Bet4 <0xbet4@gmail.com> Date: Wed, 26 May 2021 23:05:12 +0800 Subject: [PATCH] Add Rust bindings to master branch (#1401) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Rust bindings (#1295) * rust bindings init * updated bindings/README * Improved Rust bindings: (#1309) * Added basic block hooking * Changed confusing struct naming. Before: Protection::All -> R,W,X, Now: Permission::All -> R,W,X * Fixed issue with remove_hook(..). Implementation tried to remove hook from incorrect hashmap. * Made unused private vmmap(..) public. * Improve Rust bindings (#1367) * fixed tests * constant readability * HookType as bitflags * Mode as bitflags * improve bitflags * cargo fmt * removed unnecessary "as usize" * fixed buggy deallocation of uc_context structs * Remove data field in rust bindings Co-authored-by: Lukas Seidel Co-authored-by: Nikolas Eller Co-authored-by: Simon Wörner Co-authored-by: floesen --- .gitignore | 2 + bindings/README | 5 +- bindings/rust/COPYING | 339 +++++++++++++ bindings/rust/Cargo.toml | 27 + bindings/rust/README.md | 45 ++ bindings/rust/build.rs | 20 + bindings/rust/src/arm.rs | 146 ++++++ bindings/rust/src/arm64.rs | 268 ++++++++++ bindings/rust/src/ffi.rs | 230 +++++++++ bindings/rust/src/lib.rs | 784 +++++++++++++++++++++++++++++ bindings/rust/src/m68k.rs | 24 + bindings/rust/src/mips.rs | 156 ++++++ bindings/rust/src/ppc.rs | 42 ++ bindings/rust/src/sparc.rs | 94 ++++ bindings/rust/src/unicorn_const.rs | 158 ++++++ bindings/rust/src/x86.rs | 281 +++++++++++ bindings/rust/tests/unicorn.rs | 683 +++++++++++++++++++++++++ 17 files changed, 3302 insertions(+), 2 deletions(-) create mode 100644 bindings/rust/COPYING create mode 100644 bindings/rust/Cargo.toml create mode 100644 bindings/rust/README.md create mode 100644 bindings/rust/build.rs create mode 100644 bindings/rust/src/arm.rs create mode 100644 bindings/rust/src/arm64.rs create mode 100644 bindings/rust/src/ffi.rs create mode 100644 bindings/rust/src/lib.rs create mode 100644 bindings/rust/src/m68k.rs create mode 100644 bindings/rust/src/mips.rs create mode 100644 bindings/rust/src/ppc.rs create mode 100644 bindings/rust/src/sparc.rs create mode 100644 bindings/rust/src/unicorn_const.rs create mode 100644 bindings/rust/src/x86.rs create mode 100644 bindings/rust/tests/unicorn.rs diff --git a/.gitignore b/.gitignore index 18fd949f..de6684ae 100644 --- a/.gitignore +++ b/.gitignore @@ -67,6 +67,8 @@ bindings/python/unicorn.egg-info/ bindings/python/unicorn/lib/ bindings/python/unicorn/include/ bindings/python/MANIFEST +bindings/rust/target/ +bindings/rust/Cargo.lock config.log diff --git a/bindings/README b/bindings/README index 4df67f62..6a15b81e 100644 --- a/bindings/README +++ b/bindings/README @@ -1,4 +1,4 @@ -This directory contains bindings & test code for Python, Java, Go and .NET. +This directory contains bindings & test code for Python, Java, Go, .NET and Rust. See /README or /README.TXT or /README.md for how to install each binding. The following bindings are contributed by community. @@ -10,13 +10,14 @@ The following bindings are contributed by community. - Haskell binding: by Adrian Herrera. - VB6 binding: David Zimmer. - FreePascal/Delphi binding: Mohamed Osama. +- Rust binding: Lukas Seidel. More bindings created & maintained externally by community are available as follows. - UnicornPascal: Delphi/Free Pascal binding (by Stievie). https://github.com/stievie/UnicornPascal -- Unicorn-Rs: Rust binding (by Sébastien Duquette) +- Unicorn-Rs: Rust binding (by Sébastien Duquette, unmaintained) https://github.com/ekse/unicorn-rs - UnicornEngine: Perl binding (by Vikas Naresh Kumar) diff --git a/bindings/rust/COPYING b/bindings/rust/COPYING new file mode 100644 index 00000000..00ccfbb6 --- /dev/null +++ b/bindings/rust/COPYING @@ -0,0 +1,339 @@ + GNU GENERAL PUBLIC LICENSE + Version 2, June 1991 + + Copyright (C) 1989, 1991 Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +License is intended to guarantee your freedom to share and change free +software--to make sure the software is free for all its users. This +General Public License applies to most of the Free Software +Foundation's software and to any other program whose authors commit to +using it. (Some other Free Software Foundation software is covered by +the GNU Lesser General Public License instead.) You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +this service if you wish), that you receive source code or can get it +if you want it, that you can change the software or use pieces of it +in new free programs; and that you know you can do these things. + + To protect your rights, we need to make restrictions that forbid +anyone to deny you these rights or to ask you to surrender the rights. +These restrictions translate to certain responsibilities for you if you +distribute copies of the software, or if you modify it. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must give the recipients all the rights that +you have. You must make sure that they, too, receive or can get the +source code. And you must show them these terms so they know their +rights. + + We protect your rights with two steps: (1) copyright the software, and +(2) offer you this license which gives you legal permission to copy, +distribute and/or modify the software. + + Also, for each author's protection and ours, we want to make certain +that everyone understands that there is no warranty for this free +software. If the software is modified by someone else and passed on, we +want its recipients to know that what they have is not the original, so +that any problems introduced by others will not reflect on the original +authors' reputations. + + Finally, any free program is threatened constantly by software +patents. We wish to avoid the danger that redistributors of a free +program will individually obtain patent licenses, in effect making the +program proprietary. To prevent this, we have made it clear that any +patent must be licensed for everyone's free use or not licensed at all. + + The precise terms and conditions for copying, distribution and +modification follow. + + GNU GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License applies to any program or other work which contains +a notice placed by the copyright holder saying it may be distributed +under the terms of this General Public License. The "Program", below, +refers to any such program or work, and a "work based on the Program" +means either the Program or any derivative work under copyright law: +that is to say, a work containing the Program or a portion of it, +either verbatim or with modifications and/or translated into another +language. (Hereinafter, translation is included without limitation in +the term "modification".) Each licensee is addressed as "you". + +Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running the Program is not restricted, and the output from the Program +is covered only if its contents constitute a work based on the +Program (independent of having been made by running the Program). +Whether that is true depends on what the Program does. + + 1. You may copy and distribute verbatim copies of the Program's +source code as you receive it, in any medium, provided that you +conspicuously and appropriately publish on each copy an appropriate +copyright notice and disclaimer of warranty; keep intact all the +notices that refer to this License and to the absence of any warranty; +and give any other recipients of the Program a copy of this License +along with the Program. + +You may charge a fee for the physical act of transferring a copy, and +you may at your option offer warranty protection in exchange for a fee. + + 2. You may modify your copy or copies of the Program or any portion +of it, thus forming a work based on the Program, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) You must cause the modified files to carry prominent notices + stating that you changed the files and the date of any change. + + b) You must cause any work that you distribute or publish, that in + whole or in part contains or is derived from the Program or any + part thereof, to be licensed as a whole at no charge to all third + parties under the terms of this License. + + c) If the modified program normally reads commands interactively + when run, you must cause it, when started running for such + interactive use in the most ordinary way, to print or display an + announcement including an appropriate copyright notice and a + notice that there is no warranty (or else, saying that you provide + a warranty) and that users may redistribute the program under + these conditions, and telling the user how to view a copy of this + License. (Exception: if the Program itself is interactive but + does not normally print such an announcement, your work based on + the Program is not required to print an announcement.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Program, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Program, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Program. + +In addition, mere aggregation of another work not based on the Program +with the Program (or with a work based on the Program) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may copy and distribute the Program (or a work based on it, +under Section 2) in object code or executable form under the terms of +Sections 1 and 2 above provided that you also do one of the following: + + a) Accompany it with the complete corresponding machine-readable + source code, which must be distributed under the terms of Sections + 1 and 2 above on a medium customarily used for software interchange; or, + + b) Accompany it with a written offer, valid for at least three + years, to give any third party, for a charge no more than your + cost of physically performing source distribution, a complete + machine-readable copy of the corresponding source code, to be + distributed under the terms of Sections 1 and 2 above on a medium + customarily used for software interchange; or, + + c) Accompany it with the information you received as to the offer + to distribute corresponding source code. (This alternative is + allowed only for noncommercial distribution and only if you + received the program in object code or executable form with such + an offer, in accord with Subsection b above.) + +The source code for a work means the preferred form of the work for +making modifications to it. For an executable work, complete source +code means all the source code for all modules it contains, plus any +associated interface definition files, plus the scripts used to +control compilation and installation of the executable. However, as a +special exception, the source code distributed need not include +anything that is normally distributed (in either source or binary +form) with the major components (compiler, kernel, and so on) of the +operating system on which the executable runs, unless that component +itself accompanies the executable. + +If distribution of executable or object code is made by offering +access to copy from a designated place, then offering equivalent +access to copy the source code from the same place counts as +distribution of the source code, even though third parties are not +compelled to copy the source along with the object code. + + 4. You may not copy, modify, sublicense, or distribute the Program +except as expressly provided under this License. Any attempt +otherwise to copy, modify, sublicense or distribute the Program is +void, and will automatically terminate your rights under this License. +However, parties who have received copies, or rights, from you under +this License will not have their licenses terminated so long as such +parties remain in full compliance. + + 5. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Program or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Program (or any work based on the +Program), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Program or works based on it. + + 6. Each time you redistribute the Program (or any work based on the +Program), the recipient automatically receives a license from the +original licensor to copy, distribute or modify the Program subject to +these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties to +this License. + + 7. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Program at all. For example, if a patent +license would not permit royalty-free redistribution of the Program by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Program. + +If any portion of this section is held invalid or unenforceable under +any particular circumstance, the balance of the section is intended to +apply and the section as a whole is intended to apply in other +circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system, which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 8. If the distribution and/or use of the Program is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Program under this License +may add an explicit geographical distribution limitation excluding +those countries, so that distribution is permitted only in or among +countries not thus excluded. In such case, this License incorporates +the limitation as if written in the body of this License. + + 9. The Free Software Foundation may publish revised and/or new versions +of the General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + +Each version is given a distinguishing version number. If the Program +specifies a version number of this License which applies to it and "any +later version", you have the option of following the terms and conditions +either of that version or of any later version published by the Free +Software Foundation. If the Program does not specify a version number of +this License, you may choose any version ever published by the Free Software +Foundation. + + 10. If you wish to incorporate parts of the Program into other free +programs whose distribution conditions are different, write to the author +to ask for permission. For software which is copyrighted by the Free +Software Foundation, write to the Free Software Foundation; we sometimes +make exceptions for this. Our decision will be guided by the two goals +of preserving the free status of all derivatives of our free software and +of promoting the sharing and reuse of software generally. + + NO WARRANTY + + 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY +FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN +OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES +PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED +OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS +TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE +PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, +REPAIR OR CORRECTION. + + 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR +REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, +INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING +OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED +TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY +YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER +PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGES. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +convey the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License along + with this program; if not, write to the Free Software Foundation, Inc., + 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + +Also add information on how to contact you by electronic and paper mail. + +If the program is interactive, make it output a short notice like this +when it starts in an interactive mode: + + Gnomovision version 69, Copyright (C) year name of author + Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, the commands you use may +be called something other than `show w' and `show c'; they could even be +mouse-clicks or menu items--whatever suits your program. + +You should also get your employer (if you work as a programmer) or your +school, if any, to sign a "copyright disclaimer" for the program, if +necessary. Here is a sample; alter the names: + + Yoyodyne, Inc., hereby disclaims all copyright interest in the program + `Gnomovision' (which makes passes at compilers) written by James Hacker. + + , 1 April 1989 + Ty Coon, President of Vice + +This General Public License does not permit incorporating your program into +proprietary programs. If your program is a subroutine library, you may +consider it more useful to permit linking proprietary applications with the +library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. diff --git a/bindings/rust/Cargo.toml b/bindings/rust/Cargo.toml new file mode 100644 index 00000000..6edc7875 --- /dev/null +++ b/bindings/rust/Cargo.toml @@ -0,0 +1,27 @@ +[package] +name = "unicorn" +version = "1.0.0" +authors = ["Lukas Seidel"] +documentation = "" +edition = "2018" +include = [ + "/.gitmodules", + "/COPYING", + "/Cargo.toml", + "/README.md", + "/src/*", +] +license = "GPL-2.0" +readme = "README.md" +repository = "https://github.com/unicorn-engine/unicorn/" +description = "Rust bindings for the Unicorn emulator with utility functions" +build = "build.rs" +links = "unicorn" + +[dependencies] +bitflags = "1.0" +libc = "0.2" +capstone="0.6.0" + +[build-dependencies] +build-helper = "0.1" diff --git a/bindings/rust/README.md b/bindings/rust/README.md new file mode 100644 index 00000000..014e1a60 --- /dev/null +++ b/bindings/rust/README.md @@ -0,0 +1,45 @@ +# unicorn-rs + +Rust bindings for the [Unicorn](http://www.unicorn-engine.org/) emulator with utility functions. + +An extended version for fuzzing with AFL++ support can be found in https://github.com/aflplusplus/unicornafl. + +```rust +use unicorn::RegisterARM; +use unicorn::unicorn_const::{Arch, Mode, Permission, SECOND_SCALE}; + +fn main() { + let arm_code32: Vec = vec![0x17, 0x00, 0x40, 0xe2]; // sub r0, #23 + + let mut unicorn = unicorn::Unicorn::new(Arch::ARM, Mode::LITTLE_ENDIAN).expect("failed to initialize Unicorn instance"); + let mut emu = unicorn.borrow(); + emu.mem_map(0x1000, 0x4000, Permission::ALL).expect("failed to map code page"); + emu.mem_write(0x1000, &arm_code32).expect("failed to write instructions"); + + emu.reg_write(RegisterARM::R0 as i32, 123).expect("failed write R0"); + emu.reg_write(RegisterARM::R5 as i32, 1337).expect("failed write R5"); + + let _ = emu.emu_start(0x1000, (0x1000 + arm_code32.len()) as u64, 10 * SECOND_SCALE, 1000); + assert_eq!(emu.reg_read(RegisterARM::R0 as i32), Ok(100)); + assert_eq!(emu.reg_read(RegisterARM::R5 as i32), Ok(1337)); +} +``` +Further sample code can be found in ```tests/unicorn.rs```. + +## Installation + +This project has been tested on Linux, OS X and Windows. + +To use unicorn-rs, simply add it as a dependency to the Cargo.toml of your program. + +``` +[dependencies] +unicorn = { path = "/path/to/bindings/rust", version="1.0.0" } +``` + +## Acknowledgements + +These bindings are based on Sébastien Duquette's (@ekse) [unicorn-rs](https://github.com/unicorn-rs/unicorn-rs). +We picked up the project, as it is no longer maintained. +Thanks to all contributers. + diff --git a/bindings/rust/build.rs b/bindings/rust/build.rs new file mode 100644 index 00000000..7df3e61c --- /dev/null +++ b/bindings/rust/build.rs @@ -0,0 +1,20 @@ +use std::{env, process::Command}; + +use build_helper::rustc::{link_lib, link_search}; + +fn main() { + println!("cargo:rerun-if-changed=unicorn"); + let out_dir = env::var("OUT_DIR").unwrap(); + let unicorn = "libunicorn.a"; + let _ = Command::new("cp") + .current_dir("../..") + .arg(&unicorn) + .arg(&out_dir) + .status() + .unwrap(); + link_search( + Some(build_helper::SearchKind::Native), + build_helper::out_dir(), + ); + link_lib(Some(build_helper::LibKind::Static), "unicorn"); +} diff --git a/bindings/rust/src/arm.rs b/bindings/rust/src/arm.rs new file mode 100644 index 00000000..69178664 --- /dev/null +++ b/bindings/rust/src/arm.rs @@ -0,0 +1,146 @@ +#![allow(non_camel_case_types)] +// For Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterARM { + // ARM registers + INVALID = 0, + APSR = 1, + APSR_NZCV = 2, + CPSR = 3, + FPEXC = 4, + FPINST = 5, + FPSCR = 6, + FPSCR_NZCV = 7, + FPSID = 8, + ITSTATE = 9, + LR = 10, + PC = 11, + SP = 12, + SPSR = 13, + D0 = 14, + D1 = 15, + D2 = 16, + D3 = 17, + D4 = 18, + D5 = 19, + D6 = 20, + D7 = 21, + D8 = 22, + D9 = 23, + D10 = 24, + D11 = 25, + D12 = 26, + D13 = 27, + D14 = 28, + D15 = 29, + D16 = 30, + D17 = 31, + D18 = 32, + D19 = 33, + D20 = 34, + D21 = 35, + D22 = 36, + D23 = 37, + D24 = 38, + D25 = 39, + D26 = 40, + D27 = 41, + D28 = 42, + D29 = 43, + D30 = 44, + D31 = 45, + FPINST2 = 46, + MVFR0 = 47, + MVFR1 = 48, + MVFR2 = 49, + Q0 = 50, + Q1 = 51, + Q2 = 52, + Q3 = 53, + Q4 = 54, + Q5 = 55, + Q6 = 56, + Q7 = 57, + Q8 = 58, + Q9 = 59, + Q10 = 60, + Q11 = 61, + Q12 = 62, + Q13 = 63, + Q14 = 64, + Q15 = 65, + R0 = 66, + R1 = 67, + R2 = 68, + R3 = 69, + R4 = 70, + R5 = 71, + R6 = 72, + R7 = 73, + R8 = 74, + R9 = 75, + R10 = 76, + R11 = 77, + R12 = 78, + S0 = 79, + S1 = 80, + S2 = 81, + S3 = 82, + S4 = 83, + S5 = 84, + S6 = 85, + S7 = 86, + S8 = 87, + S9 = 88, + S10 = 89, + S11 = 90, + S12 = 91, + S13 = 92, + S14 = 93, + S15 = 94, + S16 = 95, + S17 = 96, + S18 = 97, + S19 = 98, + S20 = 99, + S21 = 100, + S22 = 101, + S23 = 102, + S24 = 103, + S25 = 104, + S26 = 105, + S27 = 106, + S28 = 107, + S29 = 108, + S30 = 109, + S31 = 110, + C1_C0_2 = 111, + C13_C0_2 = 112, + C13_C0_3 = 113, + IPSR = 114, + MSP = 115, + PSP = 116, + CONTROL = 117, + XPSR = 118, + ENDING = 119, + // alias registers + // (assoc) R13 = 12, + // (assoc) R14 = 10, + // (assoc) R15 = 11, + // (assoc) SB = 75, + // (assoc) SL = 76, + // (assoc) FP = 77, + // (assoc) IP = 78, +} + +impl RegisterARM { + pub const R13: RegisterARM = RegisterARM::SP; + pub const R14: RegisterARM = RegisterARM::LR; + pub const R15: RegisterARM = RegisterARM::PC; + pub const SB: RegisterARM = RegisterARM::R9; + pub const SL: RegisterARM = RegisterARM::R10; + pub const FP: RegisterARM = RegisterARM::R11; + pub const IP: RegisterARM = RegisterARM::R12; +} diff --git a/bindings/rust/src/arm64.rs b/bindings/rust/src/arm64.rs new file mode 100644 index 00000000..7c24368b --- /dev/null +++ b/bindings/rust/src/arm64.rs @@ -0,0 +1,268 @@ +// ARM64 registers +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterARM64 { + INVALID = 0, + FP = 1, + LR = 2, + NZCV = 3, + SP = 4, + WSP = 5, + WZR = 6, + XZR = 7, + B0 = 8, + B1 = 9, + B2 = 10, + B3 = 11, + B4 = 12, + B5 = 13, + B6 = 14, + B7 = 15, + B8 = 16, + B9 = 17, + B10 = 18, + B11 = 19, + B12 = 20, + B13 = 21, + B14 = 22, + B15 = 23, + B16 = 24, + B17 = 25, + B18 = 26, + B19 = 27, + B20 = 28, + B21 = 29, + B22 = 30, + B23 = 31, + B24 = 32, + B25 = 33, + B26 = 34, + B27 = 35, + B28 = 36, + B29 = 37, + B30 = 38, + B31 = 39, + D0 = 40, + D1 = 41, + D2 = 42, + D3 = 43, + D4 = 44, + D5 = 45, + D6 = 46, + D7 = 47, + D8 = 48, + D9 = 49, + D10 = 50, + D11 = 51, + D12 = 52, + D13 = 53, + D14 = 54, + D15 = 55, + D16 = 56, + D17 = 57, + D18 = 58, + D19 = 59, + D20 = 60, + D21 = 61, + D22 = 62, + D23 = 63, + D24 = 64, + D25 = 65, + D26 = 66, + D27 = 67, + D28 = 68, + D29 = 69, + D30 = 70, + D31 = 71, + H0 = 72, + H1 = 73, + H2 = 74, + H3 = 75, + H4 = 76, + H5 = 77, + H6 = 78, + H7 = 79, + H8 = 80, + H9 = 81, + H10 = 82, + H11 = 83, + H12 = 84, + H13 = 85, + H14 = 86, + H15 = 87, + H16 = 88, + H17 = 89, + H18 = 90, + H19 = 91, + H20 = 92, + H21 = 93, + H22 = 94, + H23 = 95, + H24 = 96, + H25 = 97, + H26 = 98, + H27 = 99, + H28 = 100, + H29 = 101, + H30 = 102, + H31 = 103, + Q0 = 104, + Q1 = 105, + Q2 = 106, + Q3 = 107, + Q4 = 108, + Q5 = 109, + Q6 = 110, + Q7 = 111, + Q8 = 112, + Q9 = 113, + Q10 = 114, + Q11 = 115, + Q12 = 116, + Q13 = 117, + Q14 = 118, + Q15 = 119, + Q16 = 120, + Q17 = 121, + Q18 = 122, + Q19 = 123, + Q20 = 124, + Q21 = 125, + Q22 = 126, + Q23 = 127, + Q24 = 128, + Q25 = 129, + Q26 = 130, + Q27 = 131, + Q28 = 132, + Q29 = 133, + Q30 = 134, + Q31 = 135, + S0 = 136, + S1 = 137, + S2 = 138, + S3 = 139, + S4 = 140, + S5 = 141, + S6 = 142, + S7 = 143, + S8 = 144, + S9 = 145, + S10 = 146, + S11 = 147, + S12 = 148, + S13 = 149, + S14 = 150, + S15 = 151, + S16 = 152, + S17 = 153, + S18 = 154, + S19 = 155, + S20 = 156, + S21 = 157, + S22 = 158, + S23 = 159, + S24 = 160, + S25 = 161, + S26 = 162, + S27 = 163, + S28 = 164, + S29 = 165, + S30 = 166, + S31 = 167, + W0 = 168, + W1 = 169, + W2 = 170, + W3 = 171, + W4 = 172, + W5 = 173, + W6 = 174, + W7 = 175, + W8 = 176, + W9 = 177, + W10 = 178, + W11 = 179, + W12 = 180, + W13 = 181, + W14 = 182, + W15 = 183, + W16 = 184, + W17 = 185, + W18 = 186, + W19 = 187, + W20 = 188, + W21 = 189, + W22 = 190, + W23 = 191, + W24 = 192, + W25 = 193, + W26 = 194, + W27 = 195, + W28 = 196, + W29 = 197, + W30 = 198, + X0 = 199, + X1 = 200, + X2 = 201, + X3 = 202, + X4 = 203, + X5 = 204, + X6 = 205, + X7 = 206, + X8 = 207, + X9 = 208, + X10 = 209, + X11 = 210, + X12 = 211, + X13 = 212, + X14 = 213, + X15 = 214, + IP1 = 215, + IP0 = 216, + X18 = 217, + X19 = 218, + X20 = 219, + X21 = 220, + X22 = 221, + X23 = 222, + X24 = 223, + X25 = 224, + X26 = 225, + X27 = 226, + X28 = 227, + V0 = 228, + V1 = 229, + V2 = 230, + V3 = 231, + V4 = 232, + V5 = 233, + V6 = 234, + V7 = 235, + V8 = 236, + V9 = 237, + V10 = 238, + V11 = 239, + V12 = 240, + V13 = 241, + V14 = 242, + V15 = 243, + V16 = 244, + V17 = 245, + V18 = 246, + V19 = 247, + V20 = 248, + V21 = 249, + V22 = 250, + V23 = 251, + V24 = 252, + V25 = 253, + V26 = 254, + V27 = 255, + V28 = 256, + V29 = 257, + V30 = 258, + V31 = 259, + + // pseudo registers + PC = 260, +} diff --git a/bindings/rust/src/ffi.rs b/bindings/rust/src/ffi.rs new file mode 100644 index 00000000..3252a3bd --- /dev/null +++ b/bindings/rust/src/ffi.rs @@ -0,0 +1,230 @@ +#![allow(non_camel_case_types)] +#![allow(dead_code)] + +use super::unicorn_const::*; +use libc::{c_char, c_int}; +use std::ffi::c_void; +use std::pin::Pin; + +pub type uc_handle = *mut c_void; +pub type uc_hook = *mut c_void; +pub type uc_context = libc::size_t; + +extern "C" { + pub fn uc_version(major: *mut u32, minor: *mut u32) -> u32; + pub fn uc_arch_supported(arch: Arch) -> bool; + pub fn uc_open(arch: Arch, mode: Mode, engine: *mut uc_handle) -> uc_error; + pub fn uc_close(engine: uc_handle) -> uc_error; + pub fn uc_context_free(mem: uc_context) -> uc_error; + pub fn uc_errno(engine: uc_handle) -> uc_error; + pub fn uc_strerror(error_code: uc_error) -> *const c_char; + pub fn uc_reg_write(engine: uc_handle, regid: c_int, value: *const c_void) -> uc_error; + pub fn uc_reg_read(engine: uc_handle, regid: c_int, value: *mut c_void) -> uc_error; + pub fn uc_mem_write( + engine: uc_handle, + address: u64, + bytes: *const u8, + size: libc::size_t, + ) -> uc_error; + pub fn uc_mem_read( + engine: uc_handle, + address: u64, + bytes: *mut u8, + size: libc::size_t, + ) -> uc_error; + pub fn uc_mem_map(engine: uc_handle, address: u64, size: libc::size_t, perms: u32) -> uc_error; + pub fn uc_mem_map_ptr( + engine: uc_handle, + address: u64, + size: libc::size_t, + perms: u32, + ptr: *mut c_void, + ) -> uc_error; + pub fn uc_mem_unmap(engine: uc_handle, address: u64, size: libc::size_t) -> uc_error; + pub fn uc_mem_protect( + engine: uc_handle, + address: u64, + size: libc::size_t, + perms: u32, + ) -> uc_error; + pub fn uc_mem_regions( + engine: uc_handle, + regions: *const *const MemRegion, + count: *mut u32, + ) -> uc_error; + pub fn uc_emu_start( + engine: uc_handle, + begin: u64, + until: u64, + timeout: u64, + count: libc::size_t, + ) -> uc_error; + pub fn uc_emu_stop(engine: uc_handle) -> uc_error; + pub fn uc_hook_add( + engine: uc_handle, + hook: *mut uc_hook, + hook_type: HookType, + callback: *mut c_void, + user_data: *mut c_void, + begin: u64, + end: u64, + ... + ) -> uc_error; + pub fn uc_hook_del(engine: uc_handle, hook: uc_hook) -> uc_error; + pub fn uc_query(engine: uc_handle, query_type: Query, result: *mut libc::size_t) -> uc_error; + pub fn uc_context_alloc(engine: uc_handle, context: *mut uc_context) -> uc_error; + pub fn uc_context_save(engine: uc_handle, context: uc_context) -> uc_error; + pub fn uc_context_restore(engine: uc_handle, context: uc_context) -> uc_error; +} + +pub struct CodeHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub struct BlockHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub struct MemHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub struct InterruptHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub struct InstructionInHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub struct InstructionOutHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub struct InstructionSysHook { + pub unicorn: *mut crate::UnicornInner, + pub callback: Box, +} + +pub extern "C" fn code_hook_proxy( + uc: uc_handle, + address: u64, + size: u32, + user_data: *mut CodeHook, +) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback( + crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }, + address, + size, + ); +} + +pub extern "C" fn block_hook_proxy( + uc: uc_handle, + address: u64, + size: u32, + user_data: *mut BlockHook, +) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback( + crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }, + address, + size, + ); +} + +pub extern "C" fn mem_hook_proxy( + uc: uc_handle, + mem_type: MemType, + address: u64, + size: u32, + value: i64, + user_data: *mut MemHook, +) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback( + crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }, + mem_type, + address, + size as usize, + value, + ); +} + +pub extern "C" fn intr_hook_proxy(uc: uc_handle, value: u32, user_data: *mut InterruptHook) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback( + crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }, + value, + ); +} + +pub extern "C" fn insn_in_hook_proxy( + uc: uc_handle, + port: u32, + size: usize, + user_data: *mut InstructionInHook, +) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback( + crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }, + port, + size, + ); +} + +pub extern "C" fn insn_out_hook_proxy( + uc: uc_handle, + port: u32, + size: usize, + value: u32, + user_data: *mut InstructionOutHook, +) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback( + crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }, + port, + size, + value, + ); +} + +pub extern "C" fn insn_sys_hook_proxy(uc: uc_handle, user_data: *mut InstructionSysHook) { + let unicorn = unsafe { &mut *(*user_data).unicorn }; + let callback = &mut unsafe { &mut *(*user_data).callback }; + assert_eq!(uc, unicorn.uc); + callback(crate::UnicornHandle { + inner: unsafe { Pin::new_unchecked(unicorn) }, + }); +} diff --git a/bindings/rust/src/lib.rs b/bindings/rust/src/lib.rs new file mode 100644 index 00000000..27c88402 --- /dev/null +++ b/bindings/rust/src/lib.rs @@ -0,0 +1,784 @@ +//! Bindings for the Unicorn emulator. +//! +//! +//! +//! # Example use +//! +//! ```rust +//! +//! use unicorn::RegisterARM; +//! use unicorn::unicorn_const::{Arch, Mode, Permission, SECOND_SCALE}; +//! +//! fn main() { +//! let arm_code32: Vec = vec![0x17, 0x00, 0x40, 0xe2]; // sub r0, #23 +//! +//! let mut unicorn = unicorn::Unicorn::new(Arch::ARM, Mode::LITTLE_ENDIAN).expect("failed to initialize Unicorn instance"); +//! let mut emu = unicorn.borrow(); +//! emu.mem_map(0x1000, 0x4000, Permission::ALL).expect("failed to map code page"); +//! emu.mem_write(0x1000, &arm_code32).expect("failed to write instructions"); +//! +//! emu.reg_write(RegisterARM::R0 as i32, 123).expect("failed write R0"); +//! emu.reg_write(RegisterARM::R5 as i32, 1337).expect("failed write R5"); +//! +//! let _ = emu.emu_start(0x1000, (0x1000 + arm_code32.len()) as u64, 10 * SECOND_SCALE, 1000); +//! assert_eq!(emu.reg_read(RegisterARM::R0 as i32), Ok(100)); +//! assert_eq!(emu.reg_read(RegisterARM::R5 as i32), Ok(1337)); +//! } +//! ``` +//! + +mod ffi; +pub mod unicorn_const; + +mod arm; +mod arm64; +mod m68k; +mod mips; +mod ppc; +mod sparc; +mod x86; +pub use crate::{arm::*, arm64::*, m68k::*, mips::*, ppc::*, sparc::*, x86::*}; + +use ffi::uc_handle; +use std::collections::HashMap; +use std::ffi::c_void; +use std::marker::PhantomPinned; +use std::pin::Pin; +use unicorn_const::*; + +#[derive(Debug)] +pub struct Context { + context: ffi::uc_context, +} + +impl Context { + pub fn new() -> Self { + Context { context: 0 } + } + pub fn is_initialized(&self) -> bool { + self.context != 0 + } +} + +impl Drop for Context { + fn drop(&mut self) { + unsafe { ffi::uc_context_free(self.context) }; + } +} + +#[derive(Debug)] +/// A Unicorn emulator instance. +pub struct Unicorn { + inner: Pin>, +} + +#[derive(Debug)] +/// Handle used to safely access exposed functions and data of a Unicorn instance. +pub struct UnicornHandle<'a> { + inner: Pin<&'a mut UnicornInner>, +} + +/// Internal Management struct +pub struct UnicornInner { + pub uc: uc_handle, + pub arch: Arch, + pub code_hooks: HashMap<*mut libc::c_void, Box>, + pub block_hooks: HashMap<*mut libc::c_void, Box>, + pub mem_hooks: HashMap<*mut libc::c_void, Box>, + pub intr_hooks: HashMap<*mut libc::c_void, Box>, + pub insn_in_hooks: HashMap<*mut libc::c_void, Box>, + pub insn_out_hooks: HashMap<*mut libc::c_void, Box>, + pub insn_sys_hooks: HashMap<*mut libc::c_void, Box>, + _pin: PhantomPinned, +} + +impl Unicorn { + /// Create a new instance of the unicorn engine for the specified architecture + /// and hardware mode. + pub fn new(arch: Arch, mode: Mode) -> Result { + let mut handle = std::ptr::null_mut(); + let err = unsafe { ffi::uc_open(arch, mode, &mut handle) }; + if err == uc_error::OK { + Ok(Unicorn { + inner: Box::pin(UnicornInner { + uc: handle, + arch: arch, + code_hooks: HashMap::new(), + block_hooks: HashMap::new(), + mem_hooks: HashMap::new(), + intr_hooks: HashMap::new(), + insn_in_hooks: HashMap::new(), + insn_out_hooks: HashMap::new(), + insn_sys_hooks: HashMap::new(), + _pin: std::marker::PhantomPinned, + }), + }) + } else { + Err(err) + } + } + + pub fn borrow<'a>(&'a mut self) -> UnicornHandle<'a> { + UnicornHandle { + inner: self.inner.as_mut(), + } + } +} + +impl Drop for Unicorn { + fn drop(&mut self) { + unsafe { ffi::uc_close(self.inner.uc) }; + } +} + +impl std::fmt::Debug for UnicornInner { + fn fmt(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(formatter, "Unicorn {{ uc: {:p} }}", self.uc) + } +} + +impl<'a> UnicornHandle<'a> { + + /// Return the architecture of the current emulator. + pub fn get_arch(&self) -> Arch { + self.inner.arch + } + + /// Returns a vector with the memory regions that are mapped in the emulator. + pub fn mem_regions(&self) -> Result, uc_error> { + let mut nb_regions: u32 = 0; + let mut p_regions: *const MemRegion = std::ptr::null_mut(); + let err = unsafe { ffi::uc_mem_regions(self.inner.uc, &mut p_regions, &mut nb_regions) }; + if err == uc_error::OK { + let mut regions = Vec::new(); + for i in 0..nb_regions { + regions.push(unsafe { std::mem::transmute_copy(&*p_regions.offset(i as isize)) }); + } + unsafe { libc::free(p_regions as _) }; + Ok(regions) + } else { + Err(err) + } + } + + /// Read a range of bytes from memory at the specified address. + pub fn mem_read(&self, address: u64, buf: &mut [u8]) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_mem_read(self.inner.uc, address, buf.as_mut_ptr(), buf.len()) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Return a range of bytes from memory at the specified address as vector. + pub fn mem_read_as_vec(&self, address: u64, size: usize) -> Result, uc_error> { + let mut buf = vec![0; size]; + let err = unsafe { ffi::uc_mem_read(self.inner.uc, address, buf.as_mut_ptr(), size) }; + if err == uc_error::OK { + Ok(buf) + } else { + Err(err) + } + } + + pub fn mem_write(&mut self, address: u64, bytes: &[u8]) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_mem_write(self.inner.uc, address, bytes.as_ptr(), bytes.len()) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Map an existing memory region in the emulator at the specified address. + /// + /// This function is marked unsafe because it is the responsibility of the caller to + /// ensure that `size` matches the size of the passed buffer, an invalid `size` value will + /// likely cause a crash in unicorn. + /// + /// `address` must be aligned to 4kb or this will return `Error::ARG`. + /// + /// `size` must be a multiple of 4kb or this will return `Error::ARG`. + /// + /// `ptr` is a pointer to the provided memory region that will be used by the emulator. + pub fn mem_map_ptr( + &mut self, + address: u64, + size: usize, + perms: Permission, + ptr: *mut c_void, + ) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_mem_map_ptr(self.inner.uc, address, size, perms.bits(), ptr) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Map a memory region in the emulator at the specified address. + /// + /// `address` must be aligned to 4kb or this will return `Error::ARG`. + /// `size` must be a multiple of 4kb or this will return `Error::ARG`. + pub fn mem_map( + &mut self, + address: u64, + size: libc::size_t, + perms: Permission, + ) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_mem_map(self.inner.uc, address, size, perms.bits()) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Unmap a memory region. + /// + /// `address` must be aligned to 4kb or this will return `Error::ARG`. + /// `size` must be a multiple of 4kb or this will return `Error::ARG`. + pub fn mem_unmap(&mut self, address: u64, size: libc::size_t) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_mem_unmap(self.inner.uc, address, size) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Set the memory permissions for an existing memory region. + /// + /// `address` must be aligned to 4kb or this will return `Error::ARG`. + /// `size` must be a multiple of 4kb or this will return `Error::ARG`. + pub fn mem_protect( + &mut self, + address: u64, + size: libc::size_t, + perms: Permission, + ) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_mem_protect(self.inner.uc, address, size, perms.bits()) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Write an unsigned value from a register. + pub fn reg_write>(&mut self, regid: T, value: u64) -> Result<(), uc_error> { + let err = + unsafe { ffi::uc_reg_write(self.inner.uc, regid.into(), &value as *const _ as _) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Write variable sized values into registers. + /// + /// The user has to make sure that the buffer length matches the register size. + /// This adds support for registers >64 bit (GDTR/IDTR, XMM, YMM, ZMM (x86); Q, V (arm64)). + pub fn reg_write_long>(&self, regid: T, value: Box<[u8]>) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_reg_write(self.inner.uc, regid.into(), value.as_ptr() as _) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Read an unsigned value from a register. + /// + /// Not to be used with registers larger than 64 bit. + pub fn reg_read>(&self, regid: T) -> Result { + let mut value: u64 = 0; + let err = + unsafe { ffi::uc_reg_read(self.inner.uc, regid.into(), &mut value as *mut u64 as _) }; + if err == uc_error::OK { + Ok(value) + } else { + Err(err) + } + } + + /// Read 128, 256 or 512 bit register value into heap allocated byte array. + /// + /// This adds safe support for registers >64 bit (GDTR/IDTR, XMM, YMM, ZMM (x86); Q, V (arm64)). + pub fn reg_read_long>(&self, regid: T) -> Result, uc_error> { + let err: uc_error; + let boxed: Box<[u8]>; + let mut value: Vec; + let curr_reg_id = regid.into(); + let curr_arch = self.get_arch(); + + if curr_arch == Arch::X86 { + if curr_reg_id >= x86::RegisterX86::XMM0 as i32 + && curr_reg_id <= x86::RegisterX86::XMM31 as i32 + { + value = vec![0; 16]; + } else if curr_reg_id >= x86::RegisterX86::YMM0 as i32 + && curr_reg_id <= x86::RegisterX86::YMM31 as i32 + { + value = vec![0; 32]; + } else if curr_reg_id >= x86::RegisterX86::ZMM0 as i32 + && curr_reg_id <= x86::RegisterX86::ZMM31 as i32 + { + value = vec![0; 64]; + } else if curr_reg_id == x86::RegisterX86::GDTR as i32 + || curr_reg_id == x86::RegisterX86::IDTR as i32 + { + value = vec![0; 10]; // 64 bit base address in IA-32e mode + } else { + return Err(uc_error::ARG); + } + } else if curr_arch == Arch::ARM64 { + if (curr_reg_id >= arm64::RegisterARM64::Q0 as i32 + && curr_reg_id <= arm64::RegisterARM64::Q31 as i32) + || (curr_reg_id >= arm64::RegisterARM64::V0 as i32 + && curr_reg_id <= arm64::RegisterARM64::V31 as i32) + { + value = vec![0; 16]; + } else { + return Err(uc_error::ARG); + } + } else { + return Err(uc_error::ARCH); + } + + err = unsafe { ffi::uc_reg_read(self.inner.uc, curr_reg_id, value.as_mut_ptr() as _) }; + + if err == uc_error::OK { + boxed = value.into_boxed_slice(); + Ok(boxed) + } else { + Err(err) + } + } + + /// Read a signed 32-bit value from a register. + pub fn reg_read_i32>(&self, regid: T) -> Result { + let mut value: i32 = 0; + let err = + unsafe { ffi::uc_reg_read(self.inner.uc, regid.into(), &mut value as *mut i32 as _) }; + if err == uc_error::OK { + Ok(value) + } else { + Err(err) + } + } + + /// Add a code hook. + pub fn add_code_hook( + &mut self, + begin: u64, + end: u64, + callback: F, + ) -> Result + where + F: FnMut(UnicornHandle, u64, u32), + { + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::CodeHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + HookType::CODE, + ffi::code_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + begin, + end, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .code_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Add a block hook. + pub fn add_block_hook(&mut self, callback: F) -> Result + where + F: FnMut(UnicornHandle, u64, u32), + { + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::BlockHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + HookType::BLOCK, + ffi::block_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + 1, + 0, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .block_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Add a memory hook. + pub fn add_mem_hook( + &mut self, + hook_type: HookType, + begin: u64, + end: u64, + callback: F, + ) -> Result + where + F: FnMut(UnicornHandle, MemType, u64, usize, i64), + { + if !(HookType::MEM_ALL | HookType::MEM_READ_AFTER).contains(hook_type) { + return Err(uc_error::ARG); + } + + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::MemHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + hook_type, + ffi::mem_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + begin, + end, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .mem_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Add an interrupt hook. + pub fn add_intr_hook(&mut self, callback: F) -> Result + where + F: FnMut(UnicornHandle, u32), + { + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::InterruptHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + HookType::INTR, + ffi::intr_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + 0, + 0, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .intr_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Add hook for x86 IN instruction. + pub fn add_insn_in_hook(&mut self, callback: F) -> Result + where + F: FnMut(UnicornHandle, u32, usize), + { + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::InstructionInHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + HookType::INSN, + ffi::insn_in_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + 0, + 0, + x86::InsnX86::IN, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .insn_in_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Add hook for x86 OUT instruction. + pub fn add_insn_out_hook(&mut self, callback: F) -> Result + where + F: FnMut(UnicornHandle, u32, usize, u32), + { + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::InstructionOutHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + HookType::INSN, + ffi::insn_out_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + 0, + 0, + x86::InsnX86::OUT, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .insn_out_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Add hook for x86 SYSCALL or SYSENTER. + pub fn add_insn_sys_hook( + &mut self, + insn_type: x86::InsnSysX86, + begin: u64, + end: u64, + callback: F, + ) -> Result + where + F: FnMut(UnicornHandle), + { + let mut hook_ptr = std::ptr::null_mut(); + let mut user_data = Box::new(ffi::InstructionSysHook { + unicorn: unsafe { self.inner.as_mut().get_unchecked_mut() } as _, + callback: Box::new(callback), + }); + + let err = unsafe { + ffi::uc_hook_add( + self.inner.uc, + &mut hook_ptr, + HookType::INSN, + ffi::insn_sys_hook_proxy as _, + user_data.as_mut() as *mut _ as _, + begin, + end, + insn_type, + ) + }; + if err == uc_error::OK { + unsafe { self.inner.as_mut().get_unchecked_mut() } + .insn_sys_hooks + .insert(hook_ptr, user_data); + Ok(hook_ptr) + } else { + Err(err) + } + } + + /// Remove a hook. + /// + /// `hook` is the value returned by `add_*_hook` functions. + pub fn remove_hook(&mut self, hook: ffi::uc_hook) -> Result<(), uc_error> { + let handle = unsafe { self.inner.as_mut().get_unchecked_mut() }; + let err: uc_error; + let mut in_one_hashmap = false; + + if handle.code_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.code_hooks.remove(&hook); + } + + if handle.mem_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.mem_hooks.remove(&hook); + } + + if handle.block_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.block_hooks.remove(&hook); + } + + if handle.intr_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.intr_hooks.remove(&hook); + } + + if handle.insn_in_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.insn_in_hooks.remove(&hook); + } + + if handle.insn_out_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.insn_out_hooks.remove(&hook); + } + + if handle.insn_sys_hooks.contains_key(&hook) { + in_one_hashmap = true; + handle.insn_sys_hooks.remove(&hook); + } + + if in_one_hashmap { + err = unsafe { ffi::uc_hook_del(handle.uc, hook) }; + } else { + err = uc_error::HOOK; + } + + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Allocate and return an empty Unicorn context. + /// + /// To be populated via context_save. + pub fn context_alloc(&self) -> Result { + let mut empty_context: ffi::uc_context = Default::default(); + let err = unsafe { ffi::uc_context_alloc(self.inner.uc, &mut empty_context) }; + if err == uc_error::OK { + Ok(Context { + context: empty_context, + }) + } else { + Err(err) + } + } + + /// Save current Unicorn context to previously allocated Context struct. + pub fn context_save(&self, context: &mut Context) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_context_save(self.inner.uc, context.context) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Allocate and return a Context struct initialized with the current CPU context. + /// + /// This can be used for fast rollbacks with context_restore. + /// In case of many non-concurrent context saves, use context_alloc and *_save + /// individually to avoid unnecessary allocations. + pub fn context_init(&self) -> Result { + let mut new_context: ffi::uc_context = Default::default(); + let err = unsafe { ffi::uc_context_alloc(self.inner.uc, &mut new_context) }; + if err != uc_error::OK { + return Err(err); + } + let err = unsafe { ffi::uc_context_save(self.inner.uc, new_context) }; + if err == uc_error::OK { + Ok(Context { + context: new_context, + }) + } else { + unsafe { ffi::uc_context_free(new_context) }; + Err(err) + } + } + + /// Restore a previously saved Unicorn context. + /// + /// Perform a quick rollback of the CPU context, including registers and some + /// internal metadata. Contexts may not be shared across engine instances with + /// differing arches or modes. Memory has to be restored manually, if needed. + pub fn context_restore(&self, context: &Context) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_context_restore(self.inner.uc, context.context) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Emulate machine code for a specified duration. + /// + /// `begin` is the address where to start the emulation. The emulation stops if `until` + /// is hit. `timeout` specifies a duration in microseconds after which the emulation is + /// stopped (infinite execution if set to 0). `count` is the maximum number of instructions + /// to emulate (emulate all the available instructions if set to 0). + pub fn emu_start( + &mut self, + begin: u64, + until: u64, + timeout: u64, + count: usize, + ) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_emu_start(self.inner.uc, begin, until, timeout, count as _) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Stop the emulation. + /// + /// This is usually called from callback function in hooks. + /// NOTE: For now, this will stop the execution only after the current block. + pub fn emu_stop(&mut self) -> Result<(), uc_error> { + let err = unsafe { ffi::uc_emu_stop(self.inner.uc) }; + if err == uc_error::OK { + Ok(()) + } else { + Err(err) + } + } + + /// Query the internal status of the engine. + /// + /// supported: MODE, PAGE_SIZE, ARCH + pub fn query(&self, query: Query) -> Result { + let mut result: libc::size_t = Default::default(); + let err = unsafe { ffi::uc_query(self.inner.uc, query, &mut result) }; + if err == uc_error::OK { + Ok(result) + } else { + Err(err) + } + } +} diff --git a/bindings/rust/src/m68k.rs b/bindings/rust/src/m68k.rs new file mode 100644 index 00000000..6c04e851 --- /dev/null +++ b/bindings/rust/src/m68k.rs @@ -0,0 +1,24 @@ +// M68K registers +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterM68K { + INVALID = 0, + A0, + A1, + A2, + A3, + A4, + A5, + A6, + A7, + D0, + D1, + D2, + D3, + D4, + D5, + D6, + D7, + SR, + PC, +} diff --git a/bindings/rust/src/mips.rs b/bindings/rust/src/mips.rs new file mode 100644 index 00000000..e6204664 --- /dev/null +++ b/bindings/rust/src/mips.rs @@ -0,0 +1,156 @@ +#![allow(non_camel_case_types)] + +// MIPS registers +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterMIPS { + INVALID = 0, + + // General purpose registers + PC = 1, + ZERO = 2, + AT = 3, + V0 = 4, + V1 = 5, + A0 = 6, + A1 = 7, + A2 = 8, + A3 = 9, + T0 = 10, + T1 = 11, + T2 = 12, + T3 = 13, + T4 = 14, + T5 = 15, + T6 = 16, + T7 = 17, + S0 = 18, + S1 = 19, + S2 = 20, + S3 = 21, + S4 = 22, + S5 = 23, + S6 = 24, + S7 = 25, + T8 = 26, + T9 = 27, + K0 = 28, + K1 = 29, + GP = 30, + SP = 31, + FP = 32, + RA = 33, + + // DSP registers + DSPCCOND = 34, + DSPCARRY = 35, + DSPEFI = 36, + DSPOUTFLAG = 37, + DSPOUTFLAG16_19 = 38, + DSPOUTFLAG20 = 39, + DSPOUTFLAG21 = 40, + DSPOUTFLAG22 = 41, + DSPOUTFLAG23 = 42, + DSPPOS = 43, + DSPSCOUNT = 44, + + // ACC registers + AC0 = 45, + AC1 = 46, + AC2 = 47, + AC3 = 48, + + // COP registers + CC0 = 49, + CC1 = 50, + CC2 = 51, + CC3 = 52, + CC4 = 53, + CC5 = 54, + CC6 = 55, + CC7 = 56, + + // FPU registers + F0 = 57, + F1 = 58, + F2 = 59, + F3 = 60, + F4 = 61, + F5 = 62, + F6 = 63, + F7 = 64, + F8 = 65, + F9 = 66, + F10 = 67, + F11 = 68, + F12 = 69, + F13 = 70, + F14 = 71, + F15 = 72, + F16 = 73, + F17 = 74, + F18 = 75, + F19 = 76, + F20 = 77, + F21 = 78, + F22 = 79, + F23 = 80, + F24 = 81, + F25 = 82, + F26 = 83, + F27 = 84, + F28 = 85, + F29 = 86, + F30 = 87, + F31 = 88, + FCC0 = 89, + FCC1 = 90, + FCC2 = 91, + FCC3 = 92, + FCC4 = 93, + FCC5 = 94, + FCC6 = 95, + FCC7 = 96, + + // AFPR128 + W0 = 97, + W1 = 98, + W2 = 99, + W3 = 100, + W4 = 101, + W5 = 102, + W6 = 103, + W7 = 104, + W8 = 105, + W9 = 106, + W10 = 107, + W11 = 108, + W12 = 109, + W13 = 110, + W14 = 111, + W15 = 112, + W16 = 113, + W17 = 114, + W18 = 115, + W19 = 116, + W20 = 117, + W21 = 118, + W22 = 119, + W23 = 120, + W24 = 121, + W25 = 122, + W26 = 123, + W27 = 124, + W28 = 125, + W29 = 126, + W30 = 127, + W31 = 128, + HI = 129, + LO = 130, + P0 = 131, + P1 = 132, + P2 = 133, + MPL0 = 134, + MPL1 = 135, + MPL2 = 136, +} diff --git a/bindings/rust/src/ppc.rs b/bindings/rust/src/ppc.rs new file mode 100644 index 00000000..a28827d3 --- /dev/null +++ b/bindings/rust/src/ppc.rs @@ -0,0 +1,42 @@ +#![allow(non_camel_case_types)] +// For Unicorn Engine. AUTO-GENERATED FILE, DO NOT EDIT + +// PowerPC registers +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterPPC { + INVALID = 0, + PC = 1, + GPR0 = 2, + GPR1 = 3, + GPR2 = 4, + GPR3 = 5, + GPR4 = 6, + GPR5 = 7, + GPR6 = 8, + GPR7 = 9, + GPR8 = 10, + GPR9 = 11, + GPR10 = 12, + GPR11 = 13, + GPR12 = 14, + GPR13 = 15, + GPR14 = 16, + GPR15 = 17, + GPR16 = 18, + GPR17 = 19, + GPR18 = 20, + GPR19 = 21, + GPR20 = 22, + GPR21 = 23, + GPR22 = 24, + GPR23 = 25, + GPR24 = 26, + GPR25 = 27, + GPR26 = 28, + GPR27 = 29, + GPR28 = 30, + GPR29 = 31, + GPR30 = 32, + GPR31 = 33, +} diff --git a/bindings/rust/src/sparc.rs b/bindings/rust/src/sparc.rs new file mode 100644 index 00000000..21e09db4 --- /dev/null +++ b/bindings/rust/src/sparc.rs @@ -0,0 +1,94 @@ +// SPARC registers +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterSPARC { + INVALID = 0, + F0 = 1, + F1 = 2, + F2 = 3, + F3 = 4, + F4 = 5, + F5 = 6, + F6 = 7, + F7 = 8, + F8 = 9, + F9 = 10, + F10 = 11, + F11 = 12, + F12 = 13, + F13 = 14, + F14 = 15, + F15 = 16, + F16 = 17, + F17 = 18, + F18 = 19, + F19 = 20, + F20 = 21, + F21 = 22, + F22 = 23, + F23 = 24, + F24 = 25, + F25 = 26, + F26 = 27, + F27 = 28, + F28 = 29, + F29 = 30, + F30 = 31, + F31 = 32, + F32 = 33, + F34 = 34, + F36 = 35, + F38 = 36, + F40 = 37, + F42 = 38, + F44 = 39, + F46 = 40, + F48 = 41, + F50 = 42, + F52 = 43, + F54 = 44, + F56 = 45, + F58 = 46, + F60 = 47, + F62 = 48, + FCC0 = 49, + FCC1 = 50, + FCC2 = 51, + FCC3 = 52, + G0 = 53, + G1 = 54, + G2 = 55, + G3 = 56, + G4 = 57, + G5 = 58, + G6 = 59, + G7 = 60, + I0 = 61, + I1 = 62, + I2 = 63, + I3 = 64, + I4 = 65, + I5 = 66, + FP = 67, + I7 = 68, + ICC = 69, + L0 = 70, + L1 = 71, + L2 = 72, + L3 = 73, + L4 = 74, + L5 = 75, + L6 = 76, + L7 = 77, + O0 = 78, + O1 = 79, + O2 = 80, + O3 = 81, + O4 = 82, + O5 = 83, + SP = 84, + O7 = 85, + Y = 86, + XCC = 87, + PC = 88, +} diff --git a/bindings/rust/src/unicorn_const.rs b/bindings/rust/src/unicorn_const.rs new file mode 100644 index 00000000..56b3f275 --- /dev/null +++ b/bindings/rust/src/unicorn_const.rs @@ -0,0 +1,158 @@ +#![allow(non_camel_case_types)] +use bitflags::bitflags; + +pub const API_MAJOR: u64 = 1; +pub const API_MINOR: u64 = 0; +pub const VERSION_MAJOR: u64 = 1; +pub const VERSION_MINOR: u64 = 0; +pub const VERSION_EXTRA: u64 = 2; +pub const SECOND_SCALE: u64 = 1_000_000; +pub const MILISECOND_SCALE: u64 = 1_000; + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum uc_error { + OK = 0, + NOMEM = 1, + ARCH = 2, + HANDLE = 3, + MODE = 4, + VERSION = 5, + READ_UNMAPPED = 6, + WRITE_UNMAPPED = 7, + FETCH_UNMAPPED = 8, + HOOK = 9, + INSN_INVALID = 10, + MAP = 11, + WRITE_PROT = 12, + READ_PROT = 13, + FETCH_PROT = 14, + ARG = 15, + READ_UNALIGNED = 16, + WRITE_UNALIGNED = 17, + FETCH_UNALIGNED = 18, + HOOK_EXIST = 19, + RESOURCE = 20, + EXCEPTION = 21, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum MemType { + READ = 16, + WRITE = 17, + FETCH = 18, + READ_UNMAPPED = 19, + WRITE_UNMAPPED = 20, + FETCH_UNMAPPED = 21, + WRITE_PROT = 22, + READ_PROT = 23, + FETCH_PROT = 24, + READ_AFTER = 25, +} + +bitflags! { + #[repr(C)] + pub struct HookType: i32 { + const INTR = 1; + const INSN = 2; + const CODE = 4; + const BLOCK = 8; + + const MEM_READ_UNMAPPED = 0x10; + const MEM_WRITE_UNMAPPED = 0x20; + const MEM_FETCH_UNMAPPED = 0x40; + const MEM_UNMAPPED = Self::MEM_READ_UNMAPPED.bits | Self::MEM_WRITE_UNMAPPED.bits | Self::MEM_FETCH_UNMAPPED.bits; + + const MEM_READ_PROT = 0x80; + const MEM_WRITE_PROT = 0x100; + const MEM_FETCH_PROT = 0x200; + const MEM_PROT = Self::MEM_READ_PROT.bits | Self::MEM_WRITE_PROT.bits | Self::MEM_FETCH_PROT.bits; + + const MEM_READ = 0x400; + const MEM_WRITE = 0x800; + const MEM_FETCH = 0x1000; + const MEM_VALID = Self::MEM_READ.bits | Self::MEM_WRITE.bits | Self::MEM_FETCH.bits; + + const MEM_READ_AFTER = 0x2000; + + const INSN_INVALID = 0x4000; + + const MEM_READ_INVALID = Self::MEM_READ_UNMAPPED.bits | Self::MEM_READ_PROT.bits; + const MEM_WRITE_INVALID = Self::MEM_WRITE_UNMAPPED.bits | Self::MEM_WRITE_PROT.bits; + const MEM_FETCH_INVALID = Self::MEM_FETCH_UNMAPPED.bits | Self::MEM_FETCH_PROT.bits; + const MEM_INVALID = Self::MEM_READ_INVALID.bits | Self::MEM_WRITE_INVALID.bits | Self::MEM_FETCH_INVALID.bits; + + const MEM_ALL = Self::MEM_VALID.bits | Self::MEM_INVALID.bits; + } +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum Query { + MODE = 1, + PAGE_SIZE = 2, + ARCH = 3, +} + +bitflags! { +#[repr(C)] +pub struct Permission : u32 { + const NONE = 0; + const READ = 1; + const WRITE = 2; + const EXEC = 4; + const ALL = Self::READ.bits | Self::WRITE.bits | Self::EXEC.bits; + } +} + +#[repr(C)] +#[derive(Debug, Clone)] +pub struct MemRegion { + pub begin: u64, + pub end: u64, + pub perms: Permission, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum Arch { + ARM = 1, + ARM64 = 2, + MIPS = 3, + X86 = 4, + PPC = 5, + SPARC = 6, + M68K = 7, + MAX = 8, +} + +bitflags! { + #[repr(C)] + pub struct Mode: i32 { + const LITTLE_ENDIAN = 0; + const BIG_ENDIAN = 0x4000_0000; + + const ARM = 0; + const THUMB = 0x10; + const MCLASS = 0x20; + const V8 = 0x40; + const ARM926 = 0x80; + const ARM946 = 0x100; + const ARM1176 = 0x200; + const MICRO = Self::THUMB.bits; + const MIPS3 = Self::MCLASS.bits; + const MIPS32R6 = Self::V8.bits; + const MIPS32 = 4; + const MIPS64 = 8; + const MODE_16 = 2; + const MODE_32 = Self::MIPS32.bits; + const MODE_64 = Self::MIPS64.bits; + const PPC32 = Self::MIPS32.bits; + const PPC64 = Self::MIPS64.bits; + const QPX = Self::THUMB.bits; + const SPARC32 = Self::MIPS32.bits; + const SPARC64 = Self::MIPS64.bits; + const V9 = Self::THUMB.bits; + } +} diff --git a/bindings/rust/src/x86.rs b/bindings/rust/src/x86.rs new file mode 100644 index 00000000..03c92176 --- /dev/null +++ b/bindings/rust/src/x86.rs @@ -0,0 +1,281 @@ +// X86 registers +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum RegisterX86 { + INVALID = 0, + AH, + AL, + AX, + BH, + BL, + BP, + BPL, + BX, + CH, + CL, + CS, + CX, + DH, + DI, + DIL, + DL, + DS, + DX, + EAX, + EBP, + EBX, + ECX, + EDI, + EDX, + EFLAGS, + EIP, + EIZ, + ES, + ESI, + ESP, + FPSW, + FS, + GS, + IP, + RAX, + RBP, + RBX, + RCX, + RDI, + RDX, + RIP, + RIZ, + RSI, + RSP, + SI, + SIL, + SP, + SPL, + SS, + CR0, + CR1, + CR2, + CR3, + CR4, + CR5, + CR6, + CR7, + CR8, + CR9, + CR10, + CR11, + CR12, + CR13, + CR14, + CR15, + DR0, + DR1, + DR2, + DR3, + DR4, + DR5, + DR6, + DR7, + DR8, + DR9, + DR10, + DR11, + DR12, + DR13, + DR14, + DR15, + FP0, + FP1, + FP2, + FP3, + FP4, + FP5, + FP6, + FP7, + K0, + K1, + K2, + K3, + K4, + K5, + K6, + K7, + MM0, + MM1, + MM2, + MM3, + MM4, + MM5, + MM6, + MM7, + R8, + R9, + R10, + R11, + R12, + R13, + R14, + R15, + ST0, + ST1, + ST2, + ST3, + ST4, + ST5, + ST6, + ST7, + XMM0, + XMM1, + XMM2, + XMM3, + XMM4, + XMM5, + XMM6, + XMM7, + XMM8, + XMM9, + XMM10, + XMM11, + XMM12, + XMM13, + XMM14, + XMM15, + XMM16, + XMM17, + XMM18, + XMM19, + XMM20, + XMM21, + XMM22, + XMM23, + XMM24, + XMM25, + XMM26, + XMM27, + XMM28, + XMM29, + XMM30, + XMM31, + YMM0, + YMM1, + YMM2, + YMM3, + YMM4, + YMM5, + YMM6, + YMM7, + YMM8, + YMM9, + YMM10, + YMM11, + YMM12, + YMM13, + YMM14, + YMM15, + YMM16, + YMM17, + YMM18, + YMM19, + YMM20, + YMM21, + YMM22, + YMM23, + YMM24, + YMM25, + YMM26, + YMM27, + YMM28, + YMM29, + YMM30, + YMM31, + ZMM0, + ZMM1, + ZMM2, + ZMM3, + ZMM4, + ZMM5, + ZMM6, + ZMM7, + ZMM8, + ZMM9, + ZMM10, + ZMM11, + ZMM12, + ZMM13, + ZMM14, + ZMM15, + ZMM16, + ZMM17, + ZMM18, + ZMM19, + ZMM20, + ZMM21, + ZMM22, + ZMM23, + ZMM24, + ZMM25, + ZMM26, + ZMM27, + ZMM28, + ZMM29, + ZMM30, + ZMM31, + R8B, + R9B, + R10B, + R11B, + R12B, + R13B, + R14B, + R15B, + R8D, + R9D, + R10D, + R11D, + R12D, + R13D, + R14D, + R15D, + R8W, + R9W, + R10W, + R11W, + R12W, + R13W, + R14W, + R15W, + IDTR, + GDTR, + LDTR, + TR, + FPCW, + FPTAG, + MSR, + MXCSR, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum InsnX86 { + IN = 218, + OUT = 500, + SYSCALL = 699, + SYSENTER = 700, + RET = 151, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub enum InsnSysX86 { + SYSCALL = InsnX86::SYSCALL as isize, + SYSENTER = InsnX86::SYSENTER as isize, +} + +#[repr(C)] +#[derive(PartialEq, Debug, Clone, Copy)] +pub struct X86Mmr { + pub selector: u64, + pub base: u64, + pub limit: u32, + pub flags: u32, +} diff --git a/bindings/rust/tests/unicorn.rs b/bindings/rust/tests/unicorn.rs new file mode 100644 index 00000000..203d3251 --- /dev/null +++ b/bindings/rust/tests/unicorn.rs @@ -0,0 +1,683 @@ +#![deny(rust_2018_idioms)] + +use std::cell::RefCell; +use std::rc::Rc; +use unicorn::unicorn_const::{uc_error, Arch, HookType, MemType, Mode, Permission, SECOND_SCALE}; +use unicorn::{InsnSysX86, RegisterARM, RegisterMIPS, RegisterPPC, RegisterX86}; + +pub static X86_REGISTERS: [RegisterX86; 145] = [ + RegisterX86::AH, + RegisterX86::AL, + RegisterX86::AX, + RegisterX86::BH, + RegisterX86::BL, + RegisterX86::BP, + RegisterX86::BPL, + RegisterX86::BX, + RegisterX86::CH, + RegisterX86::CL, + RegisterX86::CS, + RegisterX86::CX, + RegisterX86::DH, + RegisterX86::DI, + RegisterX86::DIL, + RegisterX86::DL, + RegisterX86::DS, + RegisterX86::DX, + RegisterX86::EAX, + RegisterX86::EBP, + RegisterX86::EBX, + RegisterX86::ECX, + RegisterX86::EDI, + RegisterX86::EDX, + RegisterX86::EFLAGS, + RegisterX86::EIP, + RegisterX86::EIZ, + RegisterX86::ES, + RegisterX86::ESI, + RegisterX86::ESP, + RegisterX86::FPSW, + RegisterX86::FS, + RegisterX86::GS, + RegisterX86::IP, + RegisterX86::RAX, + RegisterX86::RBP, + RegisterX86::RBX, + RegisterX86::RCX, + RegisterX86::RDI, + RegisterX86::RDX, + RegisterX86::RIP, + RegisterX86::RIZ, + RegisterX86::RSI, + RegisterX86::RSP, + RegisterX86::SI, + RegisterX86::SIL, + RegisterX86::SP, + RegisterX86::SPL, + RegisterX86::SS, + RegisterX86::CR0, + RegisterX86::CR1, + RegisterX86::CR2, + RegisterX86::CR3, + RegisterX86::CR4, + RegisterX86::CR5, + RegisterX86::CR6, + RegisterX86::CR7, + RegisterX86::CR8, + RegisterX86::CR9, + RegisterX86::CR10, + RegisterX86::CR11, + RegisterX86::CR12, + RegisterX86::CR13, + RegisterX86::CR14, + RegisterX86::CR15, + RegisterX86::DR0, + RegisterX86::DR1, + RegisterX86::DR2, + RegisterX86::DR3, + RegisterX86::DR4, + RegisterX86::DR5, + RegisterX86::DR6, + RegisterX86::DR7, + RegisterX86::DR8, + RegisterX86::DR9, + RegisterX86::DR10, + RegisterX86::DR11, + RegisterX86::DR12, + RegisterX86::DR13, + RegisterX86::DR14, + RegisterX86::DR15, + RegisterX86::FP0, + RegisterX86::FP1, + RegisterX86::FP2, + RegisterX86::FP3, + RegisterX86::FP4, + RegisterX86::FP5, + RegisterX86::FP6, + RegisterX86::FP7, + RegisterX86::K0, + RegisterX86::K1, + RegisterX86::K2, + RegisterX86::K3, + RegisterX86::K4, + RegisterX86::K5, + RegisterX86::K6, + RegisterX86::K7, + RegisterX86::MM0, + RegisterX86::MM1, + RegisterX86::MM2, + RegisterX86::MM3, + RegisterX86::MM4, + RegisterX86::MM5, + RegisterX86::MM6, + RegisterX86::MM7, + RegisterX86::R8, + RegisterX86::R9, + RegisterX86::R10, + RegisterX86::R11, + RegisterX86::R12, + RegisterX86::R13, + RegisterX86::R14, + RegisterX86::R15, + RegisterX86::ST0, + RegisterX86::ST1, + RegisterX86::ST2, + RegisterX86::ST3, + RegisterX86::ST4, + RegisterX86::ST5, + RegisterX86::ST6, + RegisterX86::ST7, + RegisterX86::R8B, + RegisterX86::R9B, + RegisterX86::R10B, + RegisterX86::R11B, + RegisterX86::R12B, + RegisterX86::R13B, + RegisterX86::R14B, + RegisterX86::R15B, + RegisterX86::R8D, + RegisterX86::R9D, + RegisterX86::R10D, + RegisterX86::R11D, + RegisterX86::R12D, + RegisterX86::R13D, + RegisterX86::R14D, + RegisterX86::R15D, + RegisterX86::R8W, + RegisterX86::R9W, + RegisterX86::R10W, + RegisterX86::R11W, + RegisterX86::R12W, + RegisterX86::R13W, + RegisterX86::R14W, + RegisterX86::R15W, +]; + +type Unicorn<'a> = unicorn::UnicornHandle<'a>; + +#[test] +fn emulate_x86() { + let x86_code32: Vec = vec![0x41, 0x4a]; // INC ecx; DEC edx + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.reg_write(RegisterX86::EAX as i32, 123), Ok(())); + assert_eq!(emu.reg_read(RegisterX86::EAX as i32), Ok(123)); + + // Attempt to write to memory before mapping it. + assert_eq!( + emu.mem_write(0x1000, &x86_code32), + (Err(uc_error::WRITE_UNMAPPED)) + ); + + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + assert_eq!( + emu.mem_read_as_vec(0x1000, x86_code32.len()), + Ok(x86_code32.clone()) + ); + + assert_eq!(emu.reg_write(RegisterX86::ECX as i32, 10), Ok(())); + assert_eq!(emu.reg_write(RegisterX86::EDX as i32, 50), Ok(())); + + assert_eq!( + emu.emu_start( + 0x1000, + (0x1000 + x86_code32.len()) as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(emu.reg_read(RegisterX86::ECX as i32), Ok(11)); + assert_eq!(emu.reg_read(RegisterX86::EDX as i32), Ok(49)); +} + +#[test] +fn x86_code_callback() { + #[derive(PartialEq, Debug)] + struct CodeExpectation(u64, u32); + let expects = vec![CodeExpectation(0x1000, 1), CodeExpectation(0x1001, 1)]; + let codes: Vec = Vec::new(); + let codes_cell = Rc::new(RefCell::new(codes)); + + let callback_codes = codes_cell.clone(); + let callback = move |_: Unicorn<'_>, address: u64, size: u32| { + let mut codes = callback_codes.borrow_mut(); + codes.push(CodeExpectation(address, size)); + }; + + let x86_code32: Vec = vec![0x41, 0x4a]; // INC ecx; DEC edx + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + + let hook = emu + .add_code_hook(0x1000, 0x2000, callback) + .expect("failed to add code hook"); + assert_eq!( + emu.emu_start(0x1000, 0x1002, 10 * SECOND_SCALE, 1000), + Ok(()) + ); + assert_eq!(expects, *codes_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +} + +#[test] +fn x86_intr_callback() { + #[derive(PartialEq, Debug)] + struct IntrExpectation(u32); + let expect = IntrExpectation(0x80); + let intr_cell = Rc::new(RefCell::new(IntrExpectation(0))); + + let callback_intr = intr_cell.clone(); + let callback = move |_: Unicorn<'_>, intno: u32| { + *callback_intr.borrow_mut() = IntrExpectation(intno); + }; + + let x86_code32: Vec = vec![0xcd, 0x80]; // INT 0x80; + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + + let hook = emu + .add_intr_hook(callback) + .expect("failed to add intr hook"); + + assert_eq!( + emu.emu_start( + 0x1000, + 0x1000 + x86_code32.len() as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(expect, *intr_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +} + +#[test] +fn x86_mem_callback() { + #[derive(PartialEq, Debug)] + struct MemExpectation(MemType, u64, usize, i64); + let expects = vec![ + MemExpectation(MemType::WRITE, 0x2000, 4, 0xdeadbeef), + MemExpectation(MemType::READ_UNMAPPED, 0x10000, 4, 0), + MemExpectation(MemType::READ, 0x10000, 4, 0), + ]; + let mems: Vec = Vec::new(); + let mems_cell = Rc::new(RefCell::new(mems)); + + let callback_mems = mems_cell.clone(); + let callback = + move |_: Unicorn<'_>, mem_type: MemType, address: u64, size: usize, value: i64| { + let mut mems = callback_mems.borrow_mut(); + mems.push(MemExpectation(mem_type, address, size, value)); + }; + + // mov eax, 0xdeadbeef; + // mov [0x2000], eax; + // mov eax, [0x10000]; + let x86_code32: Vec = vec![ + 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0xA3, 0x00, 0x20, 0x00, 0x00, 0xA1, 0x00, 0x00, 0x01, 0x00, + ]; + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + + let hook = emu + .add_mem_hook(HookType::MEM_ALL, 0, std::u64::MAX, callback) + .expect("failed to add memory hook"); + assert_eq!(emu.reg_write(RegisterX86::EAX as i32, 0x123), Ok(())); + assert_eq!( + emu.emu_start( + 0x1000, + 0x1000 + x86_code32.len() as u64, + 10 * SECOND_SCALE, + 0x1000 + ), + Err(uc_error::READ_UNMAPPED) + ); + + assert_eq!(expects, *mems_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +} + +#[test] +fn x86_insn_in_callback() { + #[derive(PartialEq, Debug)] + struct InsnInExpectation(u32, usize); + let expect = InsnInExpectation(0x10, 4); + let insn_cell = Rc::new(RefCell::new(InsnInExpectation(0, 0))); + + let callback_insn = insn_cell.clone(); + let callback = move |_: Unicorn<'_>, port: u32, size: usize| { + *callback_insn.borrow_mut() = InsnInExpectation(port, size); + }; + + let x86_code32: Vec = vec![0xe5, 0x10]; // IN eax, 0x10; + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + + let hook = emu + .add_insn_in_hook(callback) + .expect("failed to add in hook"); + + assert_eq!( + emu.emu_start( + 0x1000, + 0x1000 + x86_code32.len() as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(expect, *insn_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +} + +#[test] +fn x86_insn_out_callback() { + #[derive(PartialEq, Debug)] + struct InsnOutExpectation(u32, usize, u32); + let expect = InsnOutExpectation(0x46, 1, 0x32); + let insn_cell = Rc::new(RefCell::new(InsnOutExpectation(0, 0, 0))); + + let callback_insn = insn_cell.clone(); + let callback = move |_: Unicorn<'_>, port: u32, size: usize, value: u32| { + *callback_insn.borrow_mut() = InsnOutExpectation(port, size, value); + }; + + let x86_code32: Vec = vec![0xb0, 0x32, 0xe6, 0x46]; // MOV al, 0x32; OUT 0x46, al; + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + + let hook = emu + .add_insn_out_hook(callback) + .expect("failed to add out hook"); + + assert_eq!( + emu.emu_start( + 0x1000, + 0x1000 + x86_code32.len() as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(expect, *insn_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +} + +#[test] +fn x86_insn_sys_callback() { + #[derive(PartialEq, Debug)] + struct InsnSysExpectation(u64); + let expect = InsnSysExpectation(0xdeadbeef); + let insn_cell = Rc::new(RefCell::new(InsnSysExpectation(0))); + + let callback_insn = insn_cell.clone(); + let callback = move |uc: Unicorn<'_>| { + println!("!!!!"); + let rax = uc.reg_read(RegisterX86::RAX as i32).unwrap(); + *callback_insn.borrow_mut() = InsnSysExpectation(rax); + }; + + // MOV rax, 0xdeadbeef; SYSCALL; + let x86_code: Vec = vec![ + 0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x05, + ]; + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_64) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code), Ok(())); + + let hook = emu + .add_insn_sys_hook(InsnSysX86::SYSCALL, 1, 0, callback) + .expect("failed to add syscall hook"); + + assert_eq!( + emu.emu_start( + 0x1000, + 0x1000 + x86_code.len() as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(expect, *insn_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +} + +#[test] +fn emulate_arm() { + let arm_code32: Vec = vec![0x83, 0xb0]; // sub sp, #0xc + + let mut unicorn = unicorn::Unicorn::new(Arch::ARM, Mode::THUMB) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.reg_write(RegisterARM::R1 as i32, 123), Ok(())); + assert_eq!(emu.reg_read(RegisterARM::R1 as i32), Ok(123)); + + // Attempt to write to memory before mapping it. + assert_eq!( + emu.mem_write(0x1000, &arm_code32), + (Err(uc_error::WRITE_UNMAPPED)) + ); + + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &arm_code32), Ok(())); + assert_eq!( + emu.mem_read_as_vec(0x1000, arm_code32.len()), + Ok(arm_code32.clone()) + ); + + assert_eq!(emu.reg_write(RegisterARM::SP as i32, 12), Ok(())); + assert_eq!(emu.reg_write(RegisterARM::R0 as i32, 10), Ok(())); + + // ARM checks the least significant bit of the address to know + // if the code is in Thumb mode. + assert_eq!( + emu.emu_start( + 0x1000 | 0x01, + (0x1000 | (0x01 + arm_code32.len())) as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(emu.reg_read(RegisterARM::SP as i32), Ok(0)); + assert_eq!(emu.reg_read(RegisterARM::R0 as i32), Ok(10)); +} + +#[test] +fn emulate_mips() { + let mips_code32 = vec![0x56, 0x34, 0x21, 0x34]; // ori $at, $at, 0x3456; + + let mut unicorn = unicorn::Unicorn::new(Arch::MIPS, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &mips_code32), Ok(())); + assert_eq!( + emu.mem_read_as_vec(0x1000, mips_code32.len()), + Ok(mips_code32.clone()) + ); + assert_eq!(emu.reg_write(RegisterMIPS::AT as i32, 0), Ok(())); + assert_eq!( + emu.emu_start( + 0x1000, + (0x1000 + mips_code32.len()) as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(emu.reg_read(RegisterMIPS::AT as i32), Ok(0x3456)); +} + +#[test] +fn emulate_ppc() { + let ppc_code32 = vec![0x7F, 0x46, 0x1A, 0x14]; // add 26, 6, 3 + + let mut unicorn = unicorn::Unicorn::new(Arch::PPC, Mode::PPC32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &ppc_code32), Ok(())); + assert_eq!( + emu.mem_read_as_vec(0x1000, ppc_code32.len()), + Ok(ppc_code32.clone()) + ); + assert_eq!(emu.reg_write(RegisterPPC::GPR3 as i32, 42), Ok(())); + assert_eq!(emu.reg_write(RegisterPPC::GPR6 as i32, 1337), Ok(())); + assert_eq!( + emu.emu_start( + 0x1000, + (0x1000 + ppc_code32.len()) as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(emu.reg_read(RegisterPPC::GPR26 as i32), Ok(1379)); +} + +#[test] +fn mem_unmapping() { + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_unmap(0x1000, 0x4000), Ok(())); +} + +#[test] +fn mem_map_ptr() { + // Use an array for the emulator memory. + let mut mem: [u8; 4000] = [0; 4000]; + let x86_code32: Vec = vec![0x41, 0x4a]; // INC ecx; DEC edx + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + + // Attempt to write to memory before mapping it. + assert_eq!( + emu.mem_write(0x1000, &x86_code32), + (Err(uc_error::WRITE_UNMAPPED)) + ); + + assert_eq!( + emu.mem_map_ptr(0x1000, 0x4000, Permission::ALL, mem.as_mut_ptr() as _), + Ok(()) + ); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + assert_eq!( + emu.mem_read_as_vec(0x1000, x86_code32.len()), + Ok(x86_code32.clone()) + ); + + assert_eq!(emu.reg_write(RegisterX86::ECX as i32, 10), Ok(())); + assert_eq!(emu.reg_write(RegisterX86::EDX as i32, 50), Ok(())); + + assert_eq!( + emu.emu_start( + 0x1000, + (0x1000 + x86_code32.len()) as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(emu.reg_read(RegisterX86::ECX as i32), Ok(11)); + assert_eq!(emu.reg_read(RegisterX86::EDX as i32), Ok(49)); + assert_eq!(emu.mem_unmap(0x1000, 0x4000), Ok(())); + + // Use a Vec for the emulator memory. + let mut mem: Vec = Vec::new(); + mem.reserve(4000); + + // Attempt to write to memory before mapping it. + assert_eq!( + emu.mem_write(0x1000, &x86_code32), + (Err(uc_error::WRITE_UNMAPPED)) + ); + + assert_eq!( + emu.mem_map_ptr(0x1000, 0x4000, Permission::ALL, mem.as_mut_ptr() as _), + Ok(()) + ); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + assert_eq!( + emu.mem_read_as_vec(0x1000, x86_code32.len()), + Ok(x86_code32.clone()) + ); + + assert_eq!(emu.reg_write(RegisterX86::ECX as i32, 10), Ok(())); + assert_eq!(emu.reg_write(RegisterX86::EDX as i32, 50), Ok(())); + + assert_eq!( + emu.emu_start( + 0x1000, + (0x1000 + x86_code32.len()) as u64, + 10 * SECOND_SCALE, + 1000 + ), + Ok(()) + ); + assert_eq!(emu.reg_read(RegisterX86::ECX as i32), Ok(11)); + assert_eq!(emu.reg_read(RegisterX86::EDX as i32), Ok(49)); + assert_eq!(emu.mem_unmap(0x1000, 0x4000), Ok(())); +} + +#[test] +fn x86_context_save_and_restore() { + for mode in vec![Mode::MODE_32, Mode::MODE_64] { + let x86_code: Vec = vec![ + 0x48, 0xB8, 0xEF, 0xBE, 0xAD, 0xDE, 0x00, 0x00, 0x00, 0x00, 0x0F, 0x05, + ]; + let mut unicorn = unicorn::Unicorn::new(Arch::X86, mode) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code), Ok(())); + let _ = emu.emu_start( + 0x1000, + (0x1000 + x86_code.len()) as u64, + 10 * SECOND_SCALE, + 1000, + ); + + /* now, save the context... */ + let context = emu.context_init(); + let context = context.unwrap(); + + /* and create a new emulator, into which we will "restore" that context */ + let mut unicorn2 = unicorn::Unicorn::new(Arch::X86, mode) + .expect("failed to initialize unicorn instance"); + let emu2 = unicorn2.borrow(); + assert_eq!(emu2.context_restore(&context), Ok(())); + for register in X86_REGISTERS.iter() { + println!("Testing register {:?}", register); + assert_eq!( + emu2.reg_read(*register as i32), + emu.reg_read(*register as i32) + ); + } + } +} + +#[test] +fn x86_block_callback() { + #[derive(PartialEq, Debug)] + struct BlockExpectation(u64, u32); + let expects = vec![BlockExpectation(0x1000, 2), BlockExpectation(0x1000, 2)]; + let blocks: Vec = Vec::new(); + let blocks_cell = Rc::new(RefCell::new(blocks)); + + let callback_blocks = blocks_cell.clone(); + let callback = move |_: Unicorn<'_>, address: u64, size: u32| { + let mut blocks = callback_blocks.borrow_mut(); + blocks.push(BlockExpectation(address, size)); + }; + + let x86_code32: Vec = vec![0x41, 0x4a]; // INC ecx; DEC edx + + let mut unicorn = unicorn::Unicorn::new(Arch::X86, Mode::MODE_32) + .expect("failed to initialize unicorn instance"); + let mut emu = unicorn.borrow(); + assert_eq!(emu.mem_map(0x1000, 0x4000, Permission::ALL), Ok(())); + assert_eq!(emu.mem_write(0x1000, &x86_code32), Ok(())); + + let hook = emu + .add_block_hook(callback) + .expect("failed to add block hook"); + assert_eq!( + emu.emu_start(0x1000, 0x1002, 10 * SECOND_SCALE, 1000), + Ok(()) + ); + assert_eq!(expects, *blocks_cell.borrow()); + assert_eq!(emu.remove_hook(hook), Ok(())); +}