diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..9b3aa8b --- /dev/null +++ b/.clang-format @@ -0,0 +1 @@ +BasedOnStyle: LLVM diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f1ae62..8e9b526 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -696,6 +696,7 @@ if(SLEIGH_ENABLE_INSTALL) "${library_root}/translate.hh" "${library_root}/types.h" "${library_root}/xml.hh" + "${CMAKE_CURRENT_SOURCE_DIR}/include/libsleigh.hh" ) install( @@ -757,7 +758,7 @@ if(SLEIGH_ENABLE_INSTALL) DESTINATION "${CMAKE_INSTALL_BINDIR}" - + PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE GROUP_READ GROUP_EXECUTE @@ -773,3 +774,5 @@ if(SLEIGH_ENABLE_INSTALL) FILE "${PROJECT_NAME}Config.cmake" ) endif() + +add_subdirectory(tools) diff --git a/README.md b/README.md index de395a9..f45cd09 100644 --- a/README.md +++ b/README.md @@ -70,6 +70,38 @@ cmake --build . cmake --build . --target package ``` +## API Usage + +An example program called `sleigh-lift` has been included to demonstrate how to use the SLEIGH API. It takes a hexadecimal string of bytes and either disassembles it or lifts it to p-code. The program can be invoked like so, where the `action` argument must be either `disassemble` or `pcode`: + +```sh +sleigh-lift [action] [sla_file] [bytes] [address:OPTIONAL] +``` + +For example, to disassemble the following byte string: + +```sh +$ sleigh-lift disassemble /share/sleigh/Processors/x86/data/languages/x86-64.sla 4881ecc00f0000 +0x00000000: SUB RSP,0xfc0 +``` + +And to lift it to p-code: + +```sh +$ sleigh-lift pcode /share/sleigh/Processors/x86/data/languages/x86-64.sla 4881ecc00f0000 +(register,0x200,1) = INT_LESS (register,0x20,8) (const,0xfc0,8) +(register,0x20b,1) = INT_SBORROW (register,0x20,8) (const,0xfc0,8) +(register,0x20,8) = INT_SUB (register,0x20,8) (const,0xfc0,8) +(register,0x207,1) = INT_SLESS (register,0x20,8) (const,0x0,8) +(register,0x206,1) = INT_EQUAL (register,0x20,8) (const,0x0,8) +(unique,0x12c00,8) = INT_AND (register,0x20,8) (const,0xff,8) +(unique,0x12c80,1) = POPCOUNT (unique,0x12c00,8) +(unique,0x12d00,1) = INT_AND (unique,0x12c80,1) (const,0x1,1) +(register,0x202,1) = INT_EQUAL (unique,0x12d00,1) (const,0x0,1) +``` + +The `SLEIGH_ENABLE_EXAMPLES` option must be set during the configuration step in order to build `sleigh-lift`. + ## License See the LICENSE file in the top directory of this repo. diff --git a/include/libsleigh.hh b/include/libsleigh.hh new file mode 100644 index 0000000..144b21a --- /dev/null +++ b/include/libsleigh.hh @@ -0,0 +1,35 @@ +/* + Copyright (c) 2021-present, Trail of Bits, Inc. + All rights reserved. + + This source code is licensed in accordance with the terms specified in + the LICENSE file found in the root directory of this source tree. +*/ + +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated" +#pragma GCC diagnostic ignored "-Wsign-compare" +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "address.hh" +#include "context.hh" +#include "emulate.hh" +#include "error.hh" +#include "float.hh" +#include "globalcontext.hh" +#include "loadimage.hh" +#include "memstate.hh" +#include "opbehavior.hh" +#include "opcodes.hh" +#include "partmap.hh" +#include "pcoderaw.hh" +#include "semantics.hh" +#include "sleigh.hh" +#include "sleighbase.hh" +#include "slghpatexpress.hh" +#include "slghpattern.hh" +#include "slghsymbol.hh" +#include "space.hh" +#include "translate.hh" +#include "types.h" +#include "xml.hh" +#pragma GCC diagnostic pop diff --git a/tests/find_package/src/main.cpp b/tests/find_package/src/main.cpp index 6e09d05..b5fdc50 100644 --- a/tests/find_package/src/main.cpp +++ b/tests/find_package/src/main.cpp @@ -6,10 +6,6 @@ the LICENSE file found in the root directory of this source tree. */ -#include -#include -#include +#include -int main() { - return 0; -} +int main() { return 0; } diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..a6a7c89 --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,9 @@ +# +# Copyright (c) 2021-present, Trail of Bits, Inc. +# All rights reserved. +# +# This source code is licensed in accordance with the terms specified in +# the LICENSE file found in the root directory of this source tree. +# + +add_subdirectory(sleigh-lift) diff --git a/tools/sleigh-lift/CMakeLists.txt b/tools/sleigh-lift/CMakeLists.txt new file mode 100644 index 0000000..9095707 --- /dev/null +++ b/tools/sleigh-lift/CMakeLists.txt @@ -0,0 +1,54 @@ +# +# Copyright (c) 2021-present, Trail of Bits, Inc. +# All rights reserved. +# +# This source code is licensed in accordance with the terms specified in +# the LICENSE file found in the root directory of this source tree. +# + +add_executable(sleigh-lift + src/main.cpp +) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + if(MSVC) + target_compile_options(sleigh-lift PRIVATE + /W4 + /WX + ) + else() + target_compile_options(sleigh-lift PRIVATE + -Wall + -Werror + -Wextra + ) + endif() +endif() + +target_include_directories(sleigh-lift PRIVATE + "${library_root}" + "${PROJECT_SOURCE_DIR}/include" +) + +target_link_libraries(sleigh-lift PRIVATE + sla + decomp +) + +if(SLEIGH_ENABLE_INSTALL) + install( + TARGETS + sleigh-lift + + EXPORT + "${PROJECT_NAME}" + + DESTINATION + "${CMAKE_INSTALL_BINDIR}" + + PERMISSIONS + OWNER_READ OWNER_WRITE OWNER_EXECUTE + GROUP_READ GROUP_EXECUTE + WORLD_READ WORLD_EXECUTE + ) +endif() diff --git a/tools/sleigh-lift/src/main.cpp b/tools/sleigh-lift/src/main.cpp new file mode 100644 index 0000000..0580f07 --- /dev/null +++ b/tools/sleigh-lift/src/main.cpp @@ -0,0 +1,189 @@ +/* + Copyright (c) 2021-present, Trail of Bits, Inc. + All rights reserved. + + This source code is licensed in accordance with the terms specified in + the LICENSE file found in the root directory of this source tree. +*/ + +#include + +#include +#include +#include + +static void PrintUsage(void) { + std::cerr + << "Usage: sleigh-lift [action] [sla_file] [bytes] [address:OPTIONAL]" + << std::endl; +} + +class InMemoryLoadImage : public LoadImage { +public: + explicit InMemoryLoadImage(uint64_t base_addr) + : LoadImage("nofile"), base_addr(base_addr) {} + + void SetImageBuffer(std::string &&buf) { + assert(image_buffer.empty()); + image_buffer = std::move(buf); + } + + void loadFill(unsigned char *ptr, int size, const Address &addr) override { + uint8_t start = addr.getOffset(); + for (int i = 0; i < size; ++i) { + uint64_t offset = start + i; + if (offset >= base_addr) { + offset -= base_addr; + ptr[i] = offset < image_buffer.size() ? image_buffer[i] : 0; + } else { + ptr[i] = 0; + } + } + } + + std::string getArchType(void) const override { return "memory"; } + void adjustVma(long) override {} + +private: + const uint64_t base_addr; + std::string image_buffer; +}; + +static std::string ParseHexBytes(std::string_view bytes, uint64_t addr, + uint64_t addr_size) { + std::string buffer; + for (size_t i = 0; i < bytes.size(); i += 2) { + const char nibbles[] = {bytes[i], bytes[i + 1], '\0'}; + char *parsed_to = nullptr; + auto byte_val = strtol(nibbles, &parsed_to, 16); + if (parsed_to != &(nibbles[2])) { + std::cerr << "Invalid hex byte value '" << nibbles + << "' specified in bytes arg." << std::endl; + exit(EXIT_FAILURE); + } + const uint64_t addr_mask = ~0ULL >> (64UL - addr_size); + auto byte_addr = addr + (i / 2); + auto masked_addr = byte_addr & addr_mask; + // Make sure that if a really big number is specified for `address`, + // that we don't accidentally wrap around and start filling out low + // byte addresses. + if (masked_addr < byte_addr) { + std::cerr << "Too many bytes specified to bytes arg, would result " + << "in a 32-bit overflow."; + exit(EXIT_FAILURE); + } else if (masked_addr < addr) { + std::cerr << "Too many bytes specified to bytes arg, would result " + << "in a 64-bit overflow."; + exit(EXIT_FAILURE); + } + buffer.push_back(static_cast(byte_val)); + } + return buffer; +} + +class AssemblyPrinter : public AssemblyEmit { +public: + void dump(const Address &addr, const std::string &mnemonic, + const std::string &body) override { + addr.printRaw(std::cout); + std::cout << ": " << mnemonic << ' ' << body << std::endl; + } +}; + +static void PrintAssembly(Sleigh &engine, uint64_t addr, size_t len) { + AssemblyPrinter asm_emit; + Address cur_addr(engine.getDefaultCodeSpace(), addr), + last_addr(engine.getDefaultCodeSpace(), addr + len); + while (cur_addr < last_addr) { + int32_t instr_len = engine.printAssembly(asm_emit, cur_addr); + cur_addr = cur_addr + instr_len; + } +} + +static void PrintVarData(std::ostream &s, VarnodeData &data) { + s << '(' << data.space->getName() << ','; + data.space->printOffset(s, data.offset); + s << ',' << std::dec << data.size << ')'; +} + +class PcodePrinter : public PcodeEmit { +public: + void dump(const Address &, OpCode op, VarnodeData *outvar, VarnodeData *vars, + int32_t isize) override { + if (outvar) { + PrintVarData(std::cout, *outvar); + std::cout << " = "; + } + std::cout << get_opname(op); + for (int32_t i = 0; i < isize; ++i) { + std::cout << ' '; + PrintVarData(std::cout, vars[i]); + } + std::cout << std::endl; + } +}; + +static void PrintPcode(Sleigh &engine, uint64_t addr, size_t len) { + PcodePrinter pcode_emit; + Address cur_addr(engine.getDefaultCodeSpace(), addr), + last_addr(engine.getDefaultCodeSpace(), addr + len); + while (cur_addr < last_addr) { + int32_t instr_len = engine.oneInstruction(pcode_emit, cur_addr); + cur_addr = cur_addr + instr_len; + } +} + +int main(int argc, char *argv[]) { + if (argc < 4 || argc > 5) { + PrintUsage(); + return -1; + } + // Parse arguments + const std::string action = argv[1]; + const char *sla_file_path = argv[2]; + const std::string bytes = argv[3]; + const char *addr_str = argc == 5 ? argv[4] : nullptr; + if (bytes.size() % 2 != 0) { + std::cerr << "Must provide an even number of bytes: " << bytes << std::endl; + return -1; + } + // Get the address as an integer. + uint64_t addr = 0; + if (addr_str) { + try { + addr = std::stoul(addr_str); + } catch (std::invalid_argument &ia) { + std::cerr << "Invalid address argument: " << addr_str << std::endl; + return EXIT_FAILURE; + } catch (std::out_of_range &oor) { + std::cerr << "Address argument out of range: " << addr_str << std::endl; + return EXIT_FAILURE; + } + } + // Put together SLEIGH components + InMemoryLoadImage load_image(addr); + ContextInternal ctx; + Sleigh engine(&load_image, &ctx); + DocumentStorage storage; + Element *root = storage.openDocument(sla_file_path)->getRoot(); + storage.registerTag(root); + engine.initialize(storage); + // In order to parse and validate the byte string properly, we need to get the + // address size from SLEIGH. Therefore this needs to happen after + // initialization. + // + // Ensure that we don't start disassembling until we've set the image buffer. + std::string image_buffer = + ParseHexBytes(bytes, addr, engine.getDefaultSize()); + const size_t len = image_buffer.size(); + load_image.SetImageBuffer(std::move(image_buffer)); + if (action == "disassemble") { + PrintAssembly(engine, addr, len); + } else if (action == "pcode") { + PrintPcode(engine, addr, len); + } else { + std::cerr << "Invalid action: " << action << std::endl; + return EXIT_FAILURE; + } + return EXIT_SUCCESS; +}