├── .editorconfig ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── README.md ├── intrinsics ├── CMakeLists.txt ├── flags.hpp └── main.cpp ├── shell.nix └── src ├── asserts.hpp ├── binary.cpp ├── binary.hpp ├── emulator.cpp ├── emulator.hpp ├── explorer.cpp ├── explorer.hpp ├── il ├── optimizer.cpp ├── optimizer.hpp ├── passes │ ├── alias.cpp │ ├── alias.hpp │ ├── coalescing.cpp │ ├── coalescing.hpp │ ├── deps.cpp │ ├── deps.hpp │ ├── flags_synthesis.cpp │ └── flags_synthesis.hpp ├── solver.cpp └── solver.hpp ├── lifter.cpp ├── lifter.hpp ├── logger.hpp ├── main.cpp ├── tracer.cpp ├── tracer.hpp ├── utils.cpp ├── utils.hpp └── vm ├── instruction.cpp ├── instruction.hpp ├── routine.cpp └── routine.hpp /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | # C++ 3 | [*.{h,hpp,c,cpp,ixx}] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | 10 | # nix 11 | [*.{nix,}] 12 | end_of_line = lf 13 | insert_final_newline = true 14 | indent_style = space 15 | indent_size = 2 16 | trim_trailing_whitespace = true 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | .cache/ 3 | lib/ 4 | main/ 5 | logs/ 6 | build/ 7 | tests/ 8 | samples/ 9 | examples/ 10 | .vscode/ 11 | *.ll 12 | *.log -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "intrinsics/remill"] 2 | path = intrinsics/remill 3 | url = https://github.com/lifting-bits/remill.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.15) 2 | 3 | project(titan) 4 | 5 | # Packages 6 | find_package(fmt CONFIG REQUIRED) 7 | find_package(LLVM 15.0 CONFIG REQUIRED) 8 | find_package(triton CONFIG REQUIRED) 9 | find_package(range-v3 CONFIG REQUIRED) 10 | 11 | file(GLOB_RECURSE TITAN_SOURCES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.cpp") 12 | file(GLOB_RECURSE TITAN_INCLUDES CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/src/*.hpp") 13 | source_group(TREE ${PROJECT_SOURCE_DIR} FILES ${TITAN_SOURCES} ${TITAN_INCLUDES}) 14 | 15 | add_executable(${PROJECT_NAME} ${TITAN_SOURCES} ${TITAN_INCLUDES}) 16 | 17 | target_include_directories(${PROJECT_NAME} PRIVATE 18 | "src/" 19 | ) 20 | target_compile_features(${PROJECT_NAME} PRIVATE 21 | cxx_std_20 22 | ) 23 | target_link_libraries(${PROJECT_NAME} PRIVATE 24 | fmt::fmt 25 | range-v3 26 | triton::triton 27 | ) 28 | 29 | add_subdirectory(intrinsics) 30 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # titan - VMProtect devirtualizer 2 | 3 | I'm releasing my VMProtect devirtualizer for others to research, learn, and improve. This project started in 2018 as a hobby project and was rewritten at least 4 times. During my research, I've met with awesome 4 | people, made friends, and learned a lot. The tool is for educational purposes only, it works for vmprotect < 3.8 but produces less than ideal output. 5 | 6 | ## How does it work? 7 | 8 | The tool uses [Triton](https://github.com/JonathanSalwan/Triton) for emulation, symbolic execution, and lifting. The easiest way to match VM handlers is to match them on the Triton AST level. The tool symbolizes vip and vsp registers and propagates memory loads and stores. Almost every handler ends with the store (to the stack, vm register or memory). We take Triton AST of the value that is being stored and match against known patterns: 9 | ```c 10 | // Match [vsp] + [vsp]. 11 | // 12 | static bool match_add(const triton::ast::SharedAbstractNode& ast) 13 | { 14 | if (ast->getType() == triton::ast::EXTRACT_NODE) 15 | { 16 | return match_add(ast->getChildren()[2]->getChildren()[1]); 17 | } 18 | return ast->getType() == triton::ast::BVADD_NODE 19 | && is_variable(ast->getChildren()[1], variable::vsp_fetch); 20 | } 21 | ``` 22 | 23 | No matter how obfuscated handlers are, it is possible to match them with a single x86 instruction! Once the handler is identified, it is lifted into a basic block. Once the basic block is terminated, the partial control-flow graph is computed and the RIP register is sliced, giving the address of the next basic block. The process repeats until no new basic blocks are found. 24 | Every basic block is lifted into separate LLVM function. The process of building control-flow graph comes down chaining calls to basic block functions in the right order. 25 | The tool has few custom LLVM passes like `no-alias` and `memory coalescing` passes. The only pass that is left to implement is `flag synthesis` pass which will give the cleanest LLVM bitcode. 26 | 27 | ## Usage 28 | 29 | The tool requires 3 arguments: 30 | - Path to vmprotect intrinsics file 31 | - Path to virtualized binary 32 | - Virtual address of vm entry point 33 | ``` 34 | ./build/titan 35 | titan: for the -i option: must be specified at least once! 36 | titan: for the -b option: must be specified at least once! 37 | titan: for the -e option: must be specified at least once! 38 | ./build/titan -i intrinsics/vmprotect64.ll -b samples/loop_hash.0x140103FF4.exe -e 0x140103FF4 39 | ``` 40 | 41 | ## Acknowledgements 42 | 43 | _Matteo Favaro_ and _Vlad Malagar_ for answering my sometimes dumb questions, helping to find bugs in llvm bitcode, giving motivation and new ideas. 44 | -------------------------------------------------------------------------------- /intrinsics/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.21) 2 | project(intrinsics) 3 | 4 | SET (CMAKE_CXX_COMPILER "clang++") 5 | 6 | set(HELPER_CLANG_FLAGS 7 | -std=c++17 8 | -O3 9 | -Iremill/include 10 | -Wno-gnu-inline-cpp-without-extern 11 | -fno-discard-value-names 12 | -fstrict-aliasing 13 | -fno-slp-vectorize 14 | -mllvm 15 | -enable-tbaa=true 16 | -emit-llvm 17 | ) 18 | 19 | set(SOURCES "main.cpp") 20 | 21 | add_custom_command(OUTPUT 22 | "${CMAKE_CURRENT_BINARY_DIR}/vmprotect64.ll" 23 | "${CMAKE_CURRENT_BINARY_DIR}/vmprotect32.ll" 24 | COMMAND "${CMAKE_CXX_COMPILER}" ${HELPER_CLANG_FLAGS} -DADDRESS_SIZE_BITS=64 -m64 -S main.cpp -o vmprotect32.ll 25 | COMMAND "${CMAKE_CXX_COMPILER}" ${HELPER_CLANG_FLAGS} -DADDRESS_SIZE_BITS=64 -m64 -S main.cpp -o vmprotect64.ll 26 | MAIN_DEPENDENCY ${SOURCES} 27 | DEPENDS ${SOURCES} 28 | WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}" 29 | ) 30 | 31 | add_custom_target(${PROJECT_NAME} ALL 32 | DEPENDS vmprotect64.ll vmprotect32.ll 33 | SOURCES ${SOURCES} 34 | ) 35 | -------------------------------------------------------------------------------- /intrinsics/flags.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // Auxiliary carry flag. This is used for binary coded decimal operations and 4 | // is the 5th bit (where each binary decimal is 4 bits). 5 | // 6 | template 7 | inline static bool AuxCarryFlag(T lhs, T rhs, T res) 8 | { 9 | return ((res ^ lhs ^ rhs) & T(0x10)); 10 | } 11 | 12 | // Auxiliary carry flag. This is used for binary coded decimal operations and 13 | // is the 5th bit (where each binary decimal is 4 bits). 14 | // 15 | template 16 | inline static bool AuxCarryFlag(T lhs, T rhs, T carry, T res) 17 | { 18 | return ((res ^ lhs ^ carry ^ rhs) & T(0x10)); 19 | } 20 | 21 | // Tests whether there is an even number of bits in the low order byte. 22 | // 23 | inline static bool ParityFlag(uint8_t r0) 24 | { 25 | return !__builtin_parity(static_cast(r0)); 26 | } 27 | 28 | // Tests whether there is an even number of bits or not. 29 | // 30 | template 31 | inline static bool ParityFlag(T x) 32 | { 33 | return ParityFlag(static_cast(x)); 34 | } 35 | 36 | // Sign flag, tells us if a result is signed or unsigned. 37 | // 38 | template 39 | inline static bool SignFlag(T res) 40 | { 41 | return 0 > Signed(res); 42 | } 43 | 44 | // Zero flags, tells us whether or not a value is zero. 45 | // 46 | template 47 | inline static bool ZeroFlag(T res) 48 | { 49 | return T(0) == res; 50 | } 51 | 52 | // Calculate the carry flag for SHLD. 53 | // 54 | template 55 | inline static uint8_t SHLDCarryFlag(T val, T count) 56 | { 57 | return UCmpEq(UAnd(UShr(val, USub(BitSizeOf(count), count)), 1), 1); 58 | } 59 | 60 | // Calculate the carry flag for SHRD. 61 | // 62 | template 63 | inline static uint8_t SHRDCarryFlag(T val, T count) 64 | { 65 | return UCmpEq(UAnd(UShr(val, USub(count, 1)), 1), 1); 66 | } 67 | 68 | struct tag_add {}; 69 | struct tag_div {}; 70 | struct tag_mul {}; 71 | 72 | // Generic carry flag. 73 | // 74 | template 75 | struct Carry; 76 | 77 | // Computes an carry flag when two numbers are added together. 78 | // 79 | template <> 80 | struct Carry 81 | { 82 | template 83 | inline static bool Flag(T lhs, T rhs, T res) 84 | { 85 | static_assert(std::is_unsigned::value, "Invalid specialization of `Carry::Flag` for addition."); 86 | return res < lhs || res < rhs; 87 | } 88 | }; 89 | 90 | // Generic overflow flag. 91 | // 92 | template 93 | struct Overflow; 94 | 95 | // Computes an overflow flag when two numbers are added together. 96 | // 97 | template <> 98 | struct Overflow 99 | { 100 | template 101 | inline static bool Flag(T lhs, T rhs, T res) 102 | { 103 | static_assert(std::is_unsigned::value, "Invalid specialization of `Overflow::Flag` for addition."); 104 | enum 105 | { 106 | kSignShift = sizeof(T) * 8 - 1 107 | }; 108 | const T sign_lhs = lhs >> kSignShift; 109 | const T sign_rhs = rhs >> kSignShift; 110 | const T sign_res = res >> kSignShift; 111 | return 2 == (sign_lhs ^ sign_res) + (sign_rhs ^ sign_res); 112 | } 113 | }; 114 | 115 | // Computes an overflow flag when one number is multiplied with another. 116 | // 117 | template <> 118 | struct Overflow 119 | { 120 | // Integer multiplication overflow check, where result is twice the width of the operands. 121 | // 122 | template 123 | inline static bool Flag(T, T, R res, typename std::enable_if::type=0) 124 | { 125 | return static_cast(static_cast(res)) != res; 126 | } 127 | 128 | // Signed integer multiplication overflow check, where the result is 129 | // truncated to the size of the operands. 130 | // 131 | template 132 | inline static bool Flag(T lhs, T rhs, T, typename std::enable_if::value,int>::type=0) 133 | { 134 | auto lhs_wide = SExt(lhs); 135 | auto rhs_wide = SExt(rhs); 136 | return Flag(lhs, rhs, lhs_wide * rhs_wide); 137 | } 138 | }; 139 | -------------------------------------------------------------------------------- /intrinsics/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "flags.hpp" 14 | 15 | // Useful intrinsics @ https://clang.llvm.org/docs/LanguageExtensions.html 16 | // The semantics are exported to the lifter with the SEM_ prefix. 17 | // 18 | #define DEFINE_SEMANTIC(name) extern "C" constexpr auto SEM_##name [[gnu::used]] 19 | #if ADDRESS_SIZE_BITS == 64 20 | #define DEFINE_SEMANTIC_32(name) DEFINE_SEMANTIC(UNDEF_##name) 21 | #define DEFINE_SEMANTIC_64(name) DEFINE_SEMANTIC(name) 22 | #else 23 | #define DEFINE_SEMANTIC_32(name) DEFINE_SEMANTIC(name) 24 | #define DEFINE_SEMANTIC_64(name) DEFINE_SEMANTIC(UNDEF_##name) 25 | #endif 26 | #define TYPE_BITSIZE(type) sizeof(type) * 8; 27 | 28 | #define INLINE __attribute__((always_inline)) 29 | #define CONST __attribute__((const)) 30 | 31 | // Structure to represent a VMProtect virtual register. 32 | // 33 | IF_64BIT( 34 | struct VirtualRegister final 35 | { 36 | union 37 | { 38 | alignas(1) struct 39 | { 40 | uint8_t b0; 41 | uint8_t b1; 42 | uint8_t b2; 43 | uint8_t b3; 44 | uint8_t b4; 45 | uint8_t b5; 46 | uint8_t b6; 47 | uint8_t b7; 48 | } byte; 49 | alignas(2) struct 50 | { 51 | uint16_t w0; 52 | uint16_t w1; 53 | uint16_t w2; 54 | uint16_t w3; 55 | } word; 56 | alignas(4) struct 57 | { 58 | uint32_t d0; 59 | uint32_t d1; 60 | } dword; 61 | alignas(8) uint64_t qword; 62 | } __attribute__((packed)); 63 | } __attribute__((packed)); 64 | ) 65 | 66 | IF_32BIT( 67 | struct VirtualRegister final 68 | { 69 | union 70 | { 71 | alignas(1) struct 72 | { 73 | uint8_t b0; 74 | uint8_t b1; 75 | uint8_t b2; 76 | uint8_t b3; 77 | } byte; 78 | alignas(2) struct 79 | { 80 | uint16_t w0; 81 | uint16_t w1; 82 | } word; 83 | alignas(4) uint32_t dword; 84 | } __attribute__((packed)); 85 | } __attribute__((packed)); 86 | ) 87 | 88 | static_assert(sizeof(VirtualRegister) * 8 == ADDRESS_SIZE_BITS, "VirtualRegister size has to be equal to address size"); 89 | 90 | extern "C" uint8_t RAM[0]; 91 | extern "C" uint8_t GS[0]; 92 | extern "C" uint8_t FS[0]; 93 | 94 | // Define NoAlias pointers. 95 | // 96 | using rptr = size_t &__restrict__; 97 | 98 | // Structure to represent a virtual context. 99 | // 100 | IF_64BIT( 101 | struct VirtualContext final 102 | { 103 | VirtualRegister rax; 104 | VirtualRegister rbx; 105 | VirtualRegister rcx; 106 | VirtualRegister rdx; 107 | VirtualRegister rsi; 108 | VirtualRegister rdi; 109 | VirtualRegister rbp; 110 | VirtualRegister rsp; 111 | VirtualRegister r8; 112 | VirtualRegister r9; 113 | VirtualRegister r10; 114 | VirtualRegister r11; 115 | VirtualRegister r12; 116 | VirtualRegister r13; 117 | VirtualRegister r14; 118 | VirtualRegister r15; 119 | } __attribute__((packed)); 120 | ) 121 | 122 | IF_32BIT( 123 | struct VirtualContext final 124 | { 125 | VirtualRegister eax; 126 | VirtualRegister ebx; 127 | VirtualRegister ecx; 128 | VirtualRegister edx; 129 | VirtualRegister esi; 130 | VirtualRegister edi; 131 | VirtualRegister ebp; 132 | VirtualRegister esp; 133 | } __attribute__((packed)); 134 | ) 135 | 136 | // Undefine function, it must return 'undef' at runtime. 137 | // 138 | extern "C" size_t __undef; 139 | 140 | template 141 | INLINE T UNDEF() 142 | { 143 | return (T)__undef; 144 | } 145 | 146 | // Stack push/pop semantic. 147 | // 148 | template 149 | INLINE void STACK_PUSH(size_t &vsp, T value) 150 | { 151 | // 1. Update the stack pointer. 152 | // 153 | vsp -= sizeof(T); 154 | // 2. Store the value. 155 | // 156 | std::memcpy(&RAM[vsp], &value, sizeof(T)); 157 | } 158 | 159 | template 160 | INLINE T STACK_POP(size_t &vsp) 161 | { 162 | // 1. Fetch the value. 163 | // 164 | T value = 0; 165 | std::memcpy(&value, &RAM[vsp], sizeof(T)); 166 | // 2. Undefine the stack slot. 167 | // 168 | T undef = UNDEF(); 169 | std::memcpy(&RAM[vsp], &undef, sizeof(T)); 170 | // 3. Update the stack pointer. 171 | // 172 | vsp += sizeof(T); 173 | // 4. Return the value. 174 | // 175 | return value; 176 | } 177 | 178 | DEFINE_SEMANTIC_64(STACK_POP_64) = STACK_POP; 179 | DEFINE_SEMANTIC_32(STACK_POP_32) = STACK_POP; 180 | 181 | // Immediate and symbolic push/pop semantic. 182 | // 183 | template 184 | INLINE void PUSH_IMM(size_t &vsp, T value) 185 | { 186 | STACK_PUSH(vsp, value); 187 | } 188 | 189 | DEFINE_SEMANTIC_64(PUSH_IMM_64) = PUSH_IMM; 190 | DEFINE_SEMANTIC(PUSH_IMM_32) = PUSH_IMM; 191 | DEFINE_SEMANTIC(PUSH_IMM_16) = PUSH_IMM; 192 | 193 | // Stack pointer push/pop semantic. 194 | // 195 | IF_64BIT( 196 | template 197 | INLINE void PUSH_VSP(size_t &vsp) 198 | { 199 | // 1. Push the stack pointer. 200 | // 201 | if constexpr (size == 64) 202 | { 203 | STACK_PUSH(vsp, vsp); 204 | } 205 | else if constexpr (size == 32) 206 | { 207 | STACK_PUSH(vsp, vsp & 0xFFFFFFFF); 208 | } 209 | else if constexpr (size == 16) 210 | { 211 | STACK_PUSH(vsp, vsp & 0xFFFF); 212 | } 213 | }) 214 | 215 | IF_32BIT( 216 | template 217 | INLINE void PUSH_VSP(size_t &vsp) 218 | { 219 | // 1. Push the stack pointer. 220 | // 221 | if constexpr (size == 32) 222 | { 223 | STACK_PUSH(vsp, vsp); 224 | } 225 | else if constexpr (size == 16) 226 | { 227 | STACK_PUSH(vsp, vsp & 0xFFFF); 228 | } 229 | } 230 | ) 231 | 232 | DEFINE_SEMANTIC_64(PUSH_VSP_64) = PUSH_VSP<64>; 233 | DEFINE_SEMANTIC(PUSH_VSP_32) = PUSH_VSP<32>; 234 | DEFINE_SEMANTIC(PUSH_VSP_16) = PUSH_VSP<16>; 235 | 236 | IF_64BIT( 237 | template 238 | INLINE void POP_VSP(size_t &vsp) 239 | { 240 | // 1. Push the stack pointer. 241 | // 242 | if constexpr (size == 64) 243 | { 244 | vsp = STACK_POP(vsp); 245 | } 246 | else if constexpr (size == 32) 247 | { 248 | uint32_t value = STACK_POP(vsp); 249 | vsp = ((vsp & 0xFFFFFFFF00000000) | value); 250 | } 251 | else if constexpr (size == 16) 252 | { 253 | uint16_t value = STACK_POP(vsp); 254 | vsp = ((vsp & 0xFFFFFFFFFFFF0000) | value); 255 | } 256 | } 257 | ) 258 | 259 | IF_32BIT( 260 | template 261 | INLINE void POP_VSP(size_t &vsp) 262 | { 263 | // 1. Push the stack pointer. 264 | // 265 | if constexpr (size == 32) 266 | { 267 | vsp = STACK_POP(vsp); 268 | } 269 | else if constexpr (size == 16) 270 | { 271 | uint16_t value = STACK_POP(vsp); 272 | vsp = ((vsp & 0xFFFF0000) | value); 273 | } 274 | } 275 | ) 276 | 277 | DEFINE_SEMANTIC_64(POP_VSP_64) = POP_VSP<64>; 278 | DEFINE_SEMANTIC(POP_VSP_32) = POP_VSP<32>; 279 | DEFINE_SEMANTIC(POP_VSP_16) = POP_VSP<16>; 280 | 281 | // Flags pop semantic. 282 | // 283 | INLINE void POP_FLAGS(size_t &vsp, size_t &eflags) 284 | { 285 | // 1. Pop the eflags. 286 | // 287 | eflags = STACK_POP(vsp); 288 | } 289 | 290 | DEFINE_SEMANTIC(POP_FLAGS) = POP_FLAGS; 291 | 292 | // Stack load/store semantic. 293 | // 294 | template 295 | INLINE void LOAD(size_t &vsp) 296 | { 297 | // 1. Check if it's 'byte' size. 298 | // 299 | bool isByte = (sizeof(T) == 1); 300 | // 2. Pop the address. 301 | // 302 | size_t address = STACK_POP(vsp); 303 | // 3. Load the value. 304 | // 305 | T value = 0; 306 | std::memcpy(&value, &RAM[address], sizeof(T)); 307 | // 4. Save the result. 308 | // 309 | if (isByte) 310 | { 311 | STACK_PUSH(vsp, ZExt(value)); 312 | } 313 | else 314 | { 315 | STACK_PUSH(vsp, value); 316 | } 317 | } 318 | 319 | DEFINE_SEMANTIC_64(LOAD_SS_64) = LOAD; 320 | DEFINE_SEMANTIC(LOAD_SS_32) = LOAD; 321 | DEFINE_SEMANTIC(LOAD_SS_16) = LOAD; 322 | DEFINE_SEMANTIC(LOAD_SS_8) = LOAD; 323 | 324 | DEFINE_SEMANTIC_64(LOAD_DS_64) = LOAD; 325 | DEFINE_SEMANTIC(LOAD_DS_32) = LOAD; 326 | DEFINE_SEMANTIC(LOAD_DS_16) = LOAD; 327 | DEFINE_SEMANTIC(LOAD_DS_8) = LOAD; 328 | 329 | DEFINE_SEMANTIC_64(LOAD_64) = LOAD; 330 | DEFINE_SEMANTIC(LOAD_32) = LOAD; 331 | DEFINE_SEMANTIC(LOAD_16) = LOAD; 332 | DEFINE_SEMANTIC(LOAD_8) = LOAD; 333 | 334 | template 335 | INLINE void LOAD_GS(size_t &vsp) 336 | { 337 | // 1. Check if it's 'byte' size. 338 | // 339 | bool isByte = (sizeof(T) == 1); 340 | // 2. Pop the address. 341 | // 342 | size_t address = STACK_POP(vsp); 343 | // 3. Load the value. 344 | // 345 | T value = 0; 346 | std::memcpy(&value, &GS[address], sizeof(T)); 347 | // 4. Save the result. 348 | // 349 | if (isByte) 350 | { 351 | STACK_PUSH(vsp, ZExt(value)); 352 | } 353 | else 354 | { 355 | STACK_PUSH(vsp, value); 356 | } 357 | } 358 | 359 | DEFINE_SEMANTIC_64(LOAD_GS_64) = LOAD_GS; 360 | DEFINE_SEMANTIC(LOAD_GS_32) = LOAD_GS; 361 | DEFINE_SEMANTIC(LOAD_GS_16) = LOAD_GS; 362 | DEFINE_SEMANTIC(LOAD_GS_8) = LOAD_GS; 363 | 364 | template 365 | INLINE void LOAD_FS(size_t &vsp) 366 | { 367 | // 1. Check if it's 'byte' size. 368 | // 369 | bool isByte = (sizeof(T) == 1); 370 | // 2. Pop the address. 371 | // 372 | size_t address = STACK_POP(vsp); 373 | // 3. Load the value. 374 | // 375 | T value = 0; 376 | std::memcpy(&value, &FS[address], sizeof(T)); 377 | // 4. Save the result. 378 | // 379 | if (isByte) 380 | { 381 | STACK_PUSH(vsp, ZExt(value)); 382 | } 383 | else 384 | { 385 | STACK_PUSH(vsp, value); 386 | } 387 | } 388 | 389 | DEFINE_SEMANTIC_64(LOAD_FS_64) = LOAD_FS; 390 | DEFINE_SEMANTIC(LOAD_FS_32) = LOAD_FS; 391 | DEFINE_SEMANTIC(LOAD_FS_16) = LOAD_FS; 392 | DEFINE_SEMANTIC(LOAD_FS_8) = LOAD_FS; 393 | 394 | template 395 | INLINE void STORE(size_t &vsp) 396 | { 397 | // 1. Check if it's 'byte' size. 398 | // 399 | bool isByte = (sizeof(T) == 1); 400 | // 2. Pop the address. 401 | // 402 | size_t address = STACK_POP(vsp); 403 | // 3. Pop the value. 404 | // 405 | T value; 406 | if (isByte) 407 | { 408 | value = Trunc(STACK_POP(vsp)); 409 | } 410 | else 411 | { 412 | value = STACK_POP(vsp); 413 | } 414 | // 4. Store the value. 415 | // 416 | std::memcpy(&RAM[address], &value, sizeof(T)); 417 | } 418 | 419 | DEFINE_SEMANTIC_64(STORE_SS_64) = STORE; 420 | DEFINE_SEMANTIC(STORE_SS_32) = STORE; 421 | DEFINE_SEMANTIC(STORE_SS_16) = STORE; 422 | DEFINE_SEMANTIC(STORE_SS_8) = STORE; 423 | 424 | DEFINE_SEMANTIC_64(STORE_DS_64) = STORE; 425 | DEFINE_SEMANTIC(STORE_DS_32) = STORE; 426 | DEFINE_SEMANTIC(STORE_DS_16) = STORE; 427 | DEFINE_SEMANTIC(STORE_DS_8) = STORE; 428 | 429 | DEFINE_SEMANTIC_64(STORE_64) = STORE; 430 | DEFINE_SEMANTIC(STORE_32) = STORE; 431 | DEFINE_SEMANTIC(STORE_16) = STORE; 432 | DEFINE_SEMANTIC(STORE_8) = STORE; 433 | 434 | // Virtual register push/pop semantic. 435 | // 436 | IF_64BIT( 437 | template 438 | INLINE void PUSH_VREG(size_t &vsp, VirtualRegister vmreg) 439 | { 440 | // 1. Update the stack pointer. 441 | // 442 | vsp -= ((size != 8) ? (size / 8) : ((size / 8) * 2)); 443 | // 2. Select the proper element of the virtual register. 444 | // 445 | if constexpr (size == 64) 446 | { 447 | std::memcpy(&RAM[vsp], &vmreg.qword, sizeof(uint64_t)); 448 | } 449 | else if constexpr (size == 32) 450 | { 451 | if constexpr (offset == 0) 452 | { 453 | std::memcpy(&RAM[vsp], &vmreg.dword.d0, sizeof(uint32_t)); 454 | } 455 | else if constexpr (offset == 1) 456 | { 457 | std::memcpy(&RAM[vsp], &vmreg.dword.d1, sizeof(uint32_t)); 458 | } 459 | } 460 | else if constexpr (size == 16) 461 | { 462 | if constexpr (offset == 0) 463 | { 464 | std::memcpy(&RAM[vsp], &vmreg.word.w0, sizeof(uint16_t)); 465 | } 466 | else if constexpr (offset == 1) 467 | { 468 | std::memcpy(&RAM[vsp], &vmreg.word.w1, sizeof(uint16_t)); 469 | } 470 | else if constexpr (offset == 2) 471 | { 472 | std::memcpy(&RAM[vsp], &vmreg.word.w2, sizeof(uint16_t)); 473 | } 474 | else if constexpr (offset == 3) 475 | { 476 | std::memcpy(&RAM[vsp], &vmreg.word.w3, sizeof(uint16_t)); 477 | } 478 | } 479 | else if constexpr (size == 8) 480 | { 481 | if constexpr (offset == 0) 482 | { 483 | uint16_t byte = ZExt(vmreg.byte.b0); 484 | std::memcpy(&RAM[vsp], &byte, sizeof(uint16_t)); 485 | } 486 | else if constexpr (offset == 1) 487 | { 488 | uint16_t byte = ZExt(vmreg.byte.b1); 489 | std::memcpy(&RAM[vsp], &byte, sizeof(uint16_t)); 490 | } 491 | // NOTE: there might be other offsets here, but they were not observed. 492 | // 493 | } 494 | } 495 | ) 496 | 497 | IF_32BIT( 498 | template 499 | INLINE void PUSH_VREG(size_t &vsp, VirtualRegister vmreg) 500 | { 501 | // 1. Update the stack pointer. 502 | // 503 | vsp -= ((size != 8) ? (size / 8) : ((size / 8) * 2)); 504 | // 2. Select the proper element of the virtual register. 505 | // 506 | if constexpr (size == 32) 507 | { 508 | if constexpr (offset == 0) 509 | { 510 | std::memcpy(&RAM[vsp], &vmreg.dword, sizeof(uint32_t)); 511 | } 512 | } 513 | else if constexpr (size == 16) 514 | { 515 | if constexpr (offset == 0) 516 | { 517 | std::memcpy(&RAM[vsp], &vmreg.word.w0, sizeof(uint16_t)); 518 | } 519 | else if constexpr (offset == 1) 520 | { 521 | std::memcpy(&RAM[vsp], &vmreg.word.w1, sizeof(uint16_t)); 522 | } 523 | } 524 | else if constexpr (size == 8) 525 | { 526 | if constexpr (offset == 0) 527 | { 528 | uint16_t byte = ZExt(vmreg.byte.b0); 529 | std::memcpy(&RAM[vsp], &byte, sizeof(uint16_t)); 530 | } 531 | else if constexpr (offset == 1) 532 | { 533 | uint16_t byte = ZExt(vmreg.byte.b1); 534 | std::memcpy(&RAM[vsp], &byte, sizeof(uint16_t)); 535 | } 536 | // NOTE: there might be other offsets here, but they were not observed. 537 | // 538 | } 539 | } 540 | ) 541 | 542 | DEFINE_SEMANTIC(PUSH_VREG_8_0) = PUSH_VREG<8, 0>; 543 | DEFINE_SEMANTIC(PUSH_VREG_8_1) = PUSH_VREG<8, 1>; 544 | DEFINE_SEMANTIC(PUSH_VREG_16_0) = PUSH_VREG<16, 0>; 545 | DEFINE_SEMANTIC(PUSH_VREG_16_2) = PUSH_VREG<16, 1>; 546 | 547 | // DEFINE_SEMANTIC_64(PUSH_VREG_8_1) = PUSH_VREG<8, 1>; 548 | DEFINE_SEMANTIC_64(PUSH_VREG_16_4) = PUSH_VREG<16, 2>; 549 | DEFINE_SEMANTIC_64(PUSH_VREG_16_6) = PUSH_VREG<16, 3>; 550 | DEFINE_SEMANTIC_64(PUSH_VREG_32_0) = PUSH_VREG<32, 0>; 551 | DEFINE_SEMANTIC_32(PUSH_VREG_32) = PUSH_VREG<32, 0>; 552 | DEFINE_SEMANTIC_64(PUSH_VREG_32_4) = PUSH_VREG<32, 1>; 553 | DEFINE_SEMANTIC_64(PUSH_VREG_64_0) = PUSH_VREG<64, 0>; 554 | 555 | IF_64BIT( 556 | template 557 | INLINE void POP_VREG(size_t &vsp, VirtualRegister &vmreg) 558 | { 559 | // 1. Fetch and store the value on the virtual register. 560 | // 561 | if constexpr (size == 64) 562 | { 563 | uint64_t value = 0; 564 | std::memcpy(&value, &RAM[vsp], sizeof(uint64_t)); 565 | vmreg.qword = value; 566 | } else if constexpr (size == 32) 567 | { 568 | if constexpr (offset == 0) 569 | { 570 | uint32_t value = 0; 571 | std::memcpy(&value, &RAM[vsp], sizeof(uint32_t)); 572 | vmreg.qword = ((vmreg.qword & 0xFFFFFFFF00000000) | value); 573 | } 574 | else if constexpr (offset == 1) 575 | { 576 | uint32_t value = 0; 577 | std::memcpy(&value, &RAM[vsp], sizeof(uint32_t)); 578 | vmreg.qword = ((vmreg.qword & 0x00000000FFFFFFFF) | UShl(ZExt(value), 32)); 579 | } 580 | } 581 | else if constexpr (size == 16) 582 | { 583 | if constexpr (offset == 0) 584 | { 585 | uint16_t value = 0; 586 | std::memcpy(&value, &RAM[vsp], sizeof(uint16_t)); 587 | vmreg.qword = ((vmreg.qword & 0xFFFFFFFFFFFF0000) | value); 588 | } 589 | else if constexpr (offset == 1) 590 | { 591 | uint16_t value = 0; 592 | std::memcpy(&value, &RAM[vsp], sizeof(uint16_t)); 593 | vmreg.qword = ((vmreg.qword & 0xFFFFFFFF0000FFFF) | UShl(ZExtTo(value), 16)); 594 | } 595 | else if constexpr (offset == 2) 596 | { 597 | uint16_t value = 0; 598 | std::memcpy(&value, &RAM[vsp], sizeof(uint16_t)); 599 | vmreg.qword = ((vmreg.qword & 0xFFFF0000FFFFFFFF) | UShl(ZExtTo(value), 32)); 600 | } 601 | else if constexpr (offset == 3) 602 | { 603 | uint16_t value = 0; 604 | std::memcpy(&value, &RAM[vsp], sizeof(uint16_t)); 605 | vmreg.qword = ((vmreg.qword & 0x0000FFFFFFFFFFFF) | UShl(ZExtTo(value), 48)); 606 | } 607 | } 608 | else if constexpr (size == 8) 609 | { 610 | if constexpr (offset == 0) 611 | { 612 | uint16_t byte = 0; 613 | std::memcpy(&byte, &RAM[vsp], sizeof(uint16_t)); 614 | vmreg.byte.b0 = Trunc(byte); 615 | } 616 | else if constexpr (offset == 1) 617 | { 618 | uint16_t byte = 0; 619 | std::memcpy(&byte, &RAM[vsp], sizeof(uint16_t)); 620 | vmreg.byte.b1 = Trunc(byte); 621 | } 622 | // NOTE: there might be other offsets here, but they were not observed. 623 | // 624 | } 625 | // 4. Clear the value on the stack. 626 | // 627 | if constexpr (size == 64) 628 | { 629 | uint64_t undef = UNDEF(); 630 | std::memcpy(&RAM[vsp], &undef, sizeof(uint64_t)); 631 | } 632 | else if constexpr (size == 32) 633 | { 634 | uint32_t undef = UNDEF(); 635 | std::memcpy(&RAM[vsp], &undef, sizeof(uint32_t)); 636 | } 637 | else if constexpr (size == 16) 638 | { 639 | uint16_t undef = UNDEF(); 640 | std::memcpy(&RAM[vsp], &undef, sizeof(uint16_t)); 641 | } 642 | else if constexpr (size == 8) 643 | { 644 | uint16_t undef = UNDEF(); 645 | std::memcpy(&RAM[vsp], &undef, sizeof(uint16_t)); 646 | } 647 | // 5. Update the stack pointer. 648 | // 649 | vsp += ((size != 8) ? (size / 8) : ((size / 8) * 2)); 650 | } 651 | ) 652 | 653 | IF_32BIT( 654 | template 655 | INLINE void POP_VREG(size_t &vsp, VirtualRegister &vmreg) 656 | { 657 | // 1. Fetch and store the value on the virtual register. 658 | // 659 | if constexpr (size == 32) 660 | { 661 | if constexpr (offset == 0) 662 | { 663 | uint32_t value = 0; 664 | std::memcpy(&value, &RAM[vsp], sizeof(uint32_t)); 665 | vmreg.dword = value; 666 | } 667 | } 668 | else if constexpr (size == 16) 669 | { 670 | if constexpr (offset == 0) 671 | { 672 | uint16_t value = 0; 673 | std::memcpy(&value, &RAM[vsp], sizeof(uint16_t)); 674 | vmreg.dword = ((vmreg.dword & 0xFFFF0000) | value); 675 | } 676 | else if constexpr (offset == 1) 677 | { 678 | uint16_t value = 0; 679 | std::memcpy(&value, &RAM[vsp], sizeof(uint16_t)); 680 | vmreg.dword = ((vmreg.dword & 0x0000FFFF) | UShl(ZExtTo(value), 16)); 681 | } 682 | } 683 | else if constexpr (size == 8) 684 | { 685 | if constexpr (offset == 0) 686 | { 687 | uint16_t byte = 0; 688 | std::memcpy(&byte, &RAM[vsp], sizeof(uint16_t)); 689 | vmreg.byte.b0 = Trunc(byte); 690 | } 691 | else if constexpr (offset == 1) 692 | { 693 | uint16_t byte = 0; 694 | std::memcpy(&byte, &RAM[vsp], sizeof(uint16_t)); 695 | vmreg.byte.b1 = Trunc(byte); 696 | } 697 | // NOTE: there might be other offsets here, but they were not observed. 698 | // 699 | } 700 | // 4. Clear the value on the stack. 701 | // 702 | if constexpr (size == 32) 703 | { 704 | uint32_t undef = UNDEF(); 705 | std::memcpy(&RAM[vsp], &undef, sizeof(uint32_t)); 706 | } 707 | else if constexpr (size == 16) 708 | { 709 | uint16_t undef = UNDEF(); 710 | std::memcpy(&RAM[vsp], &undef, sizeof(uint16_t)); 711 | } 712 | else if constexpr (size == 8) 713 | { 714 | uint16_t undef = UNDEF(); 715 | std::memcpy(&RAM[vsp], &undef, sizeof(uint16_t)); 716 | } 717 | // 5. Update the stack pointer. 718 | // 719 | vsp += ((size != 8) ? (size / 8) : ((size / 8) * 2)); 720 | } 721 | ) 722 | 723 | DEFINE_SEMANTIC(POP_VREG_8_0) = POP_VREG<8, 0>; 724 | DEFINE_SEMANTIC(POP_VREG_8_1) = POP_VREG<8, 1>; 725 | DEFINE_SEMANTIC(POP_VREG_16_0) = POP_VREG<16, 0>; 726 | DEFINE_SEMANTIC(POP_VREG_16_2) = POP_VREG<16, 1>; 727 | DEFINE_SEMANTIC_64(POP_VREG_16_4) = POP_VREG<16, 2>; 728 | DEFINE_SEMANTIC_64(POP_VREG_16_6) = POP_VREG<16, 3>; 729 | DEFINE_SEMANTIC_64(POP_VREG_32_0) = POP_VREG<32, 0>; 730 | DEFINE_SEMANTIC_32(POP_VREG_32) = POP_VREG<32, 0>; 731 | DEFINE_SEMANTIC_64(POP_VREG_32_4) = POP_VREG<32, 1>; 732 | DEFINE_SEMANTIC_64(POP_VREG_64_0) = POP_VREG<64, 0>; 733 | 734 | // Real register push/pop semantic. 735 | // 736 | INLINE void PUSH_REG(size_t &vsp, size_t reg) 737 | { 738 | // 1. Push the register. 739 | // 740 | STACK_PUSH(vsp, reg); 741 | } 742 | 743 | DEFINE_SEMANTIC_64(PUSH_REG_64) = PUSH_REG; 744 | DEFINE_SEMANTIC_32(PUSH_REG_32) = PUSH_REG; 745 | 746 | INLINE void POP_REG(size_t &vsp, size_t ®) 747 | { 748 | // 1. Pop the register. 749 | // 750 | reg = STACK_POP(vsp); 751 | } 752 | 753 | DEFINE_SEMANTIC_64(POP_REG_64) = POP_REG; 754 | DEFINE_SEMANTIC_32(POP_REG_32) = POP_REG; 755 | 756 | // CPUID semantinc. 757 | // 758 | INLINE void CPUID(size_t &vsp) 759 | { 760 | // 1. Fetch the operand. 761 | // 762 | auto ieax = STACK_POP(vsp); 763 | // 2. Call the 'cpuid' intrinsic. 764 | // 765 | uint32_t oeax = 0; 766 | uint32_t oebx = 0; 767 | uint32_t oecx = 0; 768 | uint32_t oedx = 0; 769 | __cpuid(ieax, oeax, oebx, oecx, oedx); 770 | // 3. Push the 4 affected registers. 771 | // 772 | STACK_PUSH(vsp, oeax); 773 | STACK_PUSH(vsp, oebx); 774 | STACK_PUSH(vsp, oecx); 775 | STACK_PUSH(vsp, oedx); 776 | } 777 | 778 | DEFINE_SEMANTIC(CPUID) = CPUID; 779 | 780 | // RDTSC semantic. 781 | // 782 | INLINE void RDTSC(size_t &vsp) 783 | { 784 | // 1. Call the 'rdtsc' instrinsic. 785 | // 786 | uint64_t rdtsc = __rdtsc(); 787 | // 2. Split the values. 788 | // 789 | uint64_t mask = 0xFFFFFFFF; 790 | uint32_t eax = UAnd(rdtsc, mask); 791 | uint32_t edx = UAnd(UShr(rdtsc, 32), mask); 792 | // 3. Push the 2 affected registers. 793 | // 794 | STACK_PUSH(vsp, eax); 795 | STACK_PUSH(vsp, edx); 796 | } 797 | 798 | DEFINE_SEMANTIC(RDTSC) = RDTSC; 799 | 800 | // Arithmetic and logical eflags semantic. 801 | // 802 | INLINE void UPDATE_EFLAGS(size_t &eflags, bool cf, bool pf, bool af, bool zf, bool sf, bool of) 803 | { 804 | // eflags = 0; // NOTE: vmprotect doesn't actually use the x86 eflags between vmhandlers, the only edge case might be pushf. 805 | // 806 | // 1. Update the eflags. 807 | // 808 | eflags |= ((eflags & ~(0x001)) | ((size_t)cf << 0)); 809 | eflags |= ((eflags & ~(0x004)) | ((size_t)pf << 2)); 810 | eflags |= ((eflags & ~(0x010)) | ((size_t)af << 4)); 811 | eflags |= ((eflags & ~(0x040)) | ((size_t)zf << 6)); 812 | eflags |= ((eflags & ~(0x080)) | ((size_t)sf << 7)); 813 | eflags |= ((eflags & ~(0x800)) | ((size_t)of << 11)); 814 | } 815 | 816 | template 817 | INLINE CONST bool AF(T lhs, T rhs, T res) 818 | { 819 | return AuxCarryFlag(lhs, rhs, res); 820 | } 821 | 822 | template 823 | INLINE CONST bool PF(T res) 824 | { 825 | return ParityFlag(res); 826 | } 827 | 828 | template 829 | INLINE CONST bool ZF(T res) 830 | { 831 | return ZeroFlag(res); 832 | } 833 | 834 | template 835 | INLINE CONST bool SF(T res) 836 | { 837 | return SignFlag(res); 838 | } 839 | 840 | // ADD semantic. 841 | // 842 | template 843 | INLINE CONST bool CF_ADD(T lhs, T rhs, T res) 844 | { 845 | return Carry::Flag(lhs, rhs, res); 846 | } 847 | 848 | template 849 | INLINE CONST bool OF_ADD(T lhs, T rhs, T res) 850 | { 851 | return Overflow::Flag(lhs, rhs, res); 852 | } 853 | 854 | template 855 | INLINE void ADD_FLAGS(size_t &eflags, T lhs, T rhs, T res) 856 | { 857 | // 1. Calculate the eflags. 858 | // 859 | bool cf = CF_ADD(lhs, rhs, res); 860 | bool pf = PF(res); 861 | bool af = AF(lhs, rhs, res); 862 | bool zf = ZF(res); 863 | bool sf = SF(res); 864 | bool of = OF_ADD(lhs, rhs, res); 865 | // 2. Update the eflags. 866 | // 867 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 868 | } 869 | 870 | template 871 | INLINE void ADD(size_t &vsp) 872 | { 873 | // 1. Check if it's 'byte' size. 874 | // 875 | bool isByte = (sizeof(T) == 1); 876 | // 2. Initialize the operands. 877 | // 878 | T op1 = 0; 879 | T op2 = 0; 880 | // 3. Fetch the operands. 881 | // 882 | if (isByte) 883 | { 884 | op1 = Trunc(STACK_POP(vsp)); 885 | op2 = Trunc(STACK_POP(vsp)); 886 | } 887 | else 888 | { 889 | op1 = STACK_POP(vsp); 890 | op2 = STACK_POP(vsp); 891 | } 892 | // 4. Calculate the add. 893 | // 894 | T res = UAdd(op1, op2); 895 | // 5. Calculate the eflags. 896 | // 897 | size_t eflags = 0; 898 | ADD_FLAGS(eflags, op1, op2, res); 899 | // 6. Save the result. 900 | // 901 | if (isByte) 902 | { 903 | STACK_PUSH(vsp, ZExt(res)); 904 | } 905 | else 906 | { 907 | STACK_PUSH(vsp, res); 908 | } 909 | // 7. Save the eflags. 910 | // 911 | STACK_PUSH(vsp, eflags); 912 | } 913 | 914 | DEFINE_SEMANTIC_64(ADD_64) = ADD; 915 | DEFINE_SEMANTIC(ADD_32) = ADD; 916 | DEFINE_SEMANTIC(ADD_16) = ADD; 917 | DEFINE_SEMANTIC(ADD_8) = ADD; 918 | 919 | // DIV semantic. 920 | // 921 | INLINE void DIV_FLAGS(size_t &eflags) 922 | { 923 | // 1. Calculate the eflags. 924 | // 925 | bool cf = UNDEF(); 926 | bool pf = UNDEF(); 927 | bool af = UNDEF(); 928 | bool zf = UNDEF(); 929 | bool sf = UNDEF(); 930 | bool of = UNDEF(); 931 | // 2. Update the eflags. 932 | // 933 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 934 | } 935 | 936 | template 937 | INLINE void DIV(size_t &vsp) 938 | { 939 | // 1. Check if it's 'byte' size. 940 | // 941 | bool isByte = (sizeof(T) == 1); 942 | // 2. Initialize the operands. 943 | // 944 | T op1 = 0; 945 | T op2 = 0; 946 | T op3 = 0; 947 | // 3. Fetch the operands. 948 | // 949 | if (isByte) 950 | { 951 | op1 = Trunc(STACK_POP(vsp)); 952 | op2 = Trunc(STACK_POP(vsp)); 953 | op3 = Trunc(STACK_POP(vsp)); 954 | } 955 | else 956 | { 957 | op1 = STACK_POP(vsp); 958 | op2 = STACK_POP(vsp); 959 | op3 = STACK_POP(vsp); 960 | } 961 | // 4. Calculate the division. 962 | // 963 | auto lhs_lo = ZExt(op1); 964 | auto lhs_hi = ZExt(op2); 965 | auto rhs = ZExt(op3); 966 | auto shift = ZExt(BitSizeOf(op3)); 967 | auto lhs = UOr(UShl(lhs_hi, shift), lhs_lo); 968 | auto quot = UDiv(lhs, rhs); 969 | auto rem = URem(lhs, rhs); 970 | auto quot_trunc = Trunc(quot); 971 | auto rem_trunc = Trunc(rem); 972 | size_t eflags = 0; 973 | // 4.1. Calculate the final values. 974 | // 975 | auto rem_final = quot_trunc; 976 | auto quot_final = rem_trunc; 977 | // 4.2. Calculate the eflags. 978 | // 979 | DIV_FLAGS(eflags); 980 | // 4.3. Push the calculated values. 981 | // 982 | STACK_PUSH(vsp, rem_final); 983 | STACK_PUSH(vsp, quot_final); 984 | STACK_PUSH(vsp, eflags); 985 | } 986 | 987 | DEFINE_SEMANTIC_64(DIV_64) = DIV; 988 | DEFINE_SEMANTIC(DIV_32) = DIV; 989 | DEFINE_SEMANTIC(DIV_16) = DIV; 990 | DEFINE_SEMANTIC(DIV_8) = DIV; 991 | 992 | // IDIV semantic. 993 | // 994 | INLINE void IDIV_FLAGS(size_t &eflags) 995 | { 996 | // 1. Calculate the eflags. 997 | // 998 | DIV_FLAGS(eflags); 999 | } 1000 | 1001 | template 1002 | INLINE void IDIV(size_t &vsp) 1003 | { 1004 | // 1. Check if it's 'byte' size. 1005 | // 1006 | bool isByte = (sizeof(T) == 1); 1007 | // 2. Initialize the operands. 1008 | // 1009 | T op1 = 0; 1010 | T op2 = 0; 1011 | T op3 = 0; 1012 | // 3. Fetch the operands. 1013 | // 1014 | if (isByte) 1015 | { 1016 | op1 = Trunc(STACK_POP(vsp)); 1017 | op2 = Trunc(STACK_POP(vsp)); 1018 | op3 = Trunc(STACK_POP(vsp)); 1019 | } 1020 | else 1021 | { 1022 | op1 = STACK_POP(vsp); 1023 | op2 = STACK_POP(vsp); 1024 | op3 = STACK_POP(vsp); 1025 | } 1026 | // 4. Calculate the division. 1027 | // 1028 | auto lhs_lo = ZExt(op1); 1029 | auto lhs_hi = ZExt(op2); 1030 | auto rhs = SExt(op3); 1031 | auto shift = ZExt(BitSizeOf(op3)); 1032 | auto lhs = Signed(UOr(UShl(lhs_hi, shift), lhs_lo)); 1033 | auto quot = SDiv(lhs, rhs); 1034 | auto rem = SRem(lhs, rhs); 1035 | auto quot_trunc = Trunc(quot); 1036 | auto rem_trunc = Trunc(rem); 1037 | size_t eflags = 0; 1038 | // 4.1. Calculate the final values. 1039 | // 1040 | auto rem_final = Unsigned(quot_trunc); 1041 | auto quot_final = Unsigned(rem_trunc); 1042 | // 4.2. Calculate the eflags. 1043 | // 1044 | IDIV_FLAGS(eflags); 1045 | // 4.3. We are going to push undefined values. 1046 | // 1047 | STACK_PUSH(vsp, rem_final); 1048 | STACK_PUSH(vsp, quot_final); 1049 | STACK_PUSH(vsp, eflags); 1050 | } 1051 | 1052 | DEFINE_SEMANTIC_64(IDIV_64) = IDIV; 1053 | DEFINE_SEMANTIC(IDIV_32) = IDIV; 1054 | DEFINE_SEMANTIC(IDIV_16) = IDIV; 1055 | DEFINE_SEMANTIC(IDIV_8) = IDIV; 1056 | 1057 | // MUL semantic. 1058 | // 1059 | template 1060 | INLINE CONST bool CF_MUL(T lhs, T rhs, R res) 1061 | { 1062 | return Overflow::Flag(lhs, rhs, res); 1063 | } 1064 | 1065 | template 1066 | INLINE CONST bool SF_MUL(T lo_res) 1067 | { 1068 | return std::is_signed::value ? SignFlag(lo_res) : UNDEF(); 1069 | } 1070 | 1071 | template 1072 | INLINE void MUL_FLAGS(size_t &eflags, T lhs, T rhs, R res, T lo_res) 1073 | { 1074 | // 1. Calculate the eflags. 1075 | // 1076 | bool cf = CF_MUL(lhs, rhs, res); 1077 | bool pf = UNDEF(); 1078 | bool af = UNDEF(); 1079 | bool zf = UNDEF(); 1080 | bool sf = SF_MUL(lo_res); 1081 | bool of = CF_MUL(lhs, rhs, res); 1082 | // 2. Update the eflags. 1083 | // 1084 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 1085 | } 1086 | 1087 | template 1088 | INLINE void MUL(size_t &vsp) 1089 | { 1090 | // 1. Check if it's 'byte' size. 1091 | // 1092 | bool isByte = (sizeof(T) == 1); 1093 | // 2. Initialize the operands. 1094 | // 1095 | T op1 = 0; 1096 | T op2 = 0; 1097 | // 3. Fetch the operands. 1098 | // 1099 | if (isByte) 1100 | { 1101 | op1 = Trunc(STACK_POP(vsp)); 1102 | op2 = Trunc(STACK_POP(vsp)); 1103 | } 1104 | else 1105 | { 1106 | op1 = STACK_POP(vsp); 1107 | op2 = STACK_POP(vsp); 1108 | } 1109 | // 4. Force the operands to be unsigned. 1110 | // 1111 | auto lhs = Unsigned(op1); 1112 | auto rhs = Unsigned(op2); 1113 | // 5. Calculate the product. 1114 | // 1115 | auto lhs_wide = ZExt(lhs); 1116 | auto rhs_wide = ZExt(rhs); 1117 | auto res = UMul(lhs_wide, rhs_wide); 1118 | auto shift = ZExt(BitSizeOf(lhs)); 1119 | auto lo_res = Trunc(res); 1120 | auto hi_res = Trunc(UShr(res, shift)); 1121 | // 6. Calculate the eflags. 1122 | // 1123 | size_t eflags = 0; 1124 | MUL_FLAGS(eflags, lhs, rhs, res, lo_res); 1125 | // 7. Save the result. 1126 | // 1127 | STACK_PUSH(vsp, lo_res); 1128 | STACK_PUSH(vsp, hi_res); 1129 | STACK_PUSH(vsp, eflags); 1130 | } 1131 | 1132 | DEFINE_SEMANTIC_64(MUL_64) = MUL; 1133 | DEFINE_SEMANTIC(MUL_32) = MUL; 1134 | DEFINE_SEMANTIC(MUL_16) = MUL; 1135 | DEFINE_SEMANTIC(MUL_8) = MUL; 1136 | 1137 | // IMUL semantic. 1138 | // 1139 | template 1140 | INLINE void IMUL_FLAGS(size_t &eflags, T lhs, T rhs, R res, T lo_res) 1141 | { 1142 | // 1. Calculate the eflags. 1143 | // 1144 | MUL_FLAGS(eflags, lhs, rhs, res, lo_res); 1145 | } 1146 | 1147 | template 1148 | INLINE void IMUL(size_t &vsp) 1149 | { 1150 | // 1. Check if it's 'byte' size. 1151 | // 1152 | bool isByte = (sizeof(T) == 1); 1153 | // 2. Initialize the operands. 1154 | // 1155 | T op1 = 0; 1156 | T op2 = 0; 1157 | // 3. Fetch the operands. 1158 | // 1159 | if (isByte) 1160 | { 1161 | op1 = Trunc(STACK_POP(vsp)); 1162 | op2 = Trunc(STACK_POP(vsp)); 1163 | } 1164 | else 1165 | { 1166 | op1 = STACK_POP(vsp); 1167 | op2 = STACK_POP(vsp); 1168 | } 1169 | // 4. Force the operands to be signed. 1170 | // 1171 | auto lhs = Signed(op1); 1172 | auto rhs = Signed(op2); 1173 | // 5. Calculate the product. 1174 | // 1175 | auto lhs_wide = SExt(lhs); 1176 | auto rhs_wide = SExt(rhs); 1177 | auto res = SMul(lhs_wide, rhs_wide); 1178 | auto shift = ZExt(BitSizeOf(rhs)); 1179 | auto lo_res = Trunc(res); 1180 | auto hi_res = Trunc(UShr(Unsigned(res), shift)); 1181 | // 6. Calculate the eflags. 1182 | // 1183 | size_t eflags = 0; 1184 | IMUL_FLAGS(eflags, lhs, rhs, res, lo_res); 1185 | // 7. Save the result. 1186 | // 1187 | STACK_PUSH(vsp, lo_res); 1188 | STACK_PUSH(vsp, hi_res); 1189 | STACK_PUSH(vsp, eflags); 1190 | } 1191 | 1192 | DEFINE_SEMANTIC_64(IMUL_64) = IMUL; 1193 | DEFINE_SEMANTIC(IMUL_32) = IMUL; 1194 | DEFINE_SEMANTIC(IMUL_16) = IMUL; 1195 | DEFINE_SEMANTIC(IMUL_8) = IMUL; 1196 | 1197 | // NOR semantic. 1198 | // 1199 | template 1200 | INLINE void NOR_FLAGS(size_t &eflags, T lhs, T rhs, T res) 1201 | { 1202 | // 1. Calculate the eflags. 1203 | // 1204 | bool cf = false; 1205 | bool pf = PF(res); 1206 | bool af = false; 1207 | bool zf = ZF(res); 1208 | bool sf = SF(res); 1209 | bool of = false; 1210 | // 2. Update the eflags. 1211 | // 1212 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 1213 | } 1214 | 1215 | template 1216 | INLINE void NOR(size_t &vsp) 1217 | { 1218 | // 1. Check if it's 'byte' size. 1219 | // 1220 | bool isByte = (sizeof(T) == 1); 1221 | // 2. Initialize the operands. 1222 | // 1223 | T op1 = 0; 1224 | T op2 = 0; 1225 | // 3. Fetch the operands. 1226 | // 1227 | if (isByte) 1228 | { 1229 | op1 = Trunc(STACK_POP(vsp)); 1230 | op2 = Trunc(STACK_POP(vsp)); 1231 | } 1232 | else 1233 | { 1234 | op1 = STACK_POP(vsp); 1235 | op2 = STACK_POP(vsp); 1236 | } 1237 | // 4. Calculate the nor. 1238 | // 1239 | T res = UNot(UOr(op1, op2)); 1240 | // 5. Calculate the eflags. 1241 | // 1242 | size_t eflags = 0; 1243 | NOR_FLAGS(eflags, op1, op2, res); 1244 | // 6. Save the result. 1245 | // 1246 | if (isByte) 1247 | { 1248 | STACK_PUSH(vsp, ZExt(res)); 1249 | } 1250 | else 1251 | { 1252 | STACK_PUSH(vsp, res); 1253 | } 1254 | // 7. Save the eflags. 1255 | // 1256 | STACK_PUSH(vsp, eflags); 1257 | } 1258 | 1259 | DEFINE_SEMANTIC_64(NOR_64) = NOR; 1260 | DEFINE_SEMANTIC(NOR_32) = NOR; 1261 | DEFINE_SEMANTIC(NOR_16) = NOR; 1262 | DEFINE_SEMANTIC(NOR_8) = NOR; 1263 | 1264 | // NAND semantic. 1265 | // 1266 | template 1267 | INLINE void NAND_FLAGS(size_t &eflags, T lhs, T rhs, T res) 1268 | { 1269 | // 1. Calculate the eflags. 1270 | // 1271 | NOR_FLAGS(eflags, lhs, rhs, res); 1272 | } 1273 | 1274 | template 1275 | INLINE void NAND(size_t &vsp) 1276 | { 1277 | // 1. Check if it's 'byte' size. 1278 | // 1279 | bool isByte = (sizeof(T) == 1); 1280 | // 2. Initialize the operands. 1281 | // 1282 | T op1 = 0; 1283 | T op2 = 0; 1284 | // 3. Fetch the operands. 1285 | // 1286 | if (isByte) 1287 | { 1288 | op1 = Trunc(STACK_POP(vsp)); 1289 | op2 = Trunc(STACK_POP(vsp)); 1290 | } 1291 | else 1292 | { 1293 | op1 = STACK_POP(vsp); 1294 | op2 = STACK_POP(vsp); 1295 | } 1296 | // 4. Calculate the nand. 1297 | // 1298 | T res = UNot(UAnd(op1, op2)); 1299 | // 5. Calculate the eflags. 1300 | // 1301 | size_t eflags = 0; 1302 | NAND_FLAGS(eflags, op1, op2, res); 1303 | // 6. Save the result. 1304 | // 1305 | if (isByte) 1306 | { 1307 | STACK_PUSH(vsp, ZExt(res)); 1308 | } 1309 | else 1310 | { 1311 | STACK_PUSH(vsp, res); 1312 | } 1313 | // 7. Save the eflags. 1314 | // 1315 | STACK_PUSH(vsp, eflags); 1316 | } 1317 | 1318 | DEFINE_SEMANTIC_64(NAND_64) = NAND; 1319 | DEFINE_SEMANTIC(NAND_32) = NAND; 1320 | DEFINE_SEMANTIC(NAND_16) = NAND; 1321 | DEFINE_SEMANTIC(NAND_8) = NAND; 1322 | 1323 | // SHL semantic. 1324 | // 1325 | template 1326 | INLINE CONST bool OF_SHL(T val, T res) 1327 | { 1328 | return BXor(SignFlag(val), SignFlag(res)); 1329 | } 1330 | 1331 | template 1332 | INLINE CONST bool CF_SHL(T op1, T op2, T res) 1333 | { 1334 | T long_mask = 0x3F; 1335 | T short_mask = 0x1F; 1336 | auto op_size = BitSizeOf(op1); 1337 | auto shift_mask = Select(UCmpEq(op_size, 64), long_mask, short_mask); 1338 | auto masked_shift = UAnd(op2, shift_mask); 1339 | 1340 | if (UCmpEq(masked_shift, 1)) 1341 | { 1342 | return SF(op1); 1343 | } 1344 | else if (UCmpLt(masked_shift, op_size)) 1345 | { 1346 | return SF(res); 1347 | } 1348 | return UNDEF(); 1349 | } 1350 | 1351 | template 1352 | INLINE void SHL(size_t &vsp) 1353 | { 1354 | // 1. Check if it's 'byte' size. 1355 | // 1356 | bool isByte = (sizeof(T) == 1); 1357 | // 2. Initialize the operands. 1358 | // 1359 | T op1 = 0; 1360 | T op2 = 0; 1361 | // 3. Fetch the operands. 1362 | // 1363 | if (isByte) 1364 | { 1365 | op1 = Trunc(STACK_POP(vsp)); 1366 | op2 = Trunc(STACK_POP(vsp)); 1367 | } 1368 | else 1369 | { 1370 | op1 = STACK_POP(vsp); 1371 | op2 = STACK_POP(vsp); 1372 | } 1373 | // 4. Calculate the shift. 1374 | // 1375 | auto res = op1 << op2; 1376 | // 5. Calculate the eflags. 1377 | // 1378 | size_t eflags = 0; 1379 | bool cf = CF_SHL(op1, op2, res); 1380 | bool pf = PF(res); 1381 | bool af = UNDEF(); 1382 | bool zf = ZF(res); 1383 | bool sf = SF(res); 1384 | bool of = OF_SHL(op1, res); 1385 | // Update the eflags. 1386 | // 1387 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 1388 | // 6. Save the result. 1389 | // 1390 | if (isByte) 1391 | { 1392 | STACK_PUSH(vsp, ZExt(res)); 1393 | } 1394 | else 1395 | { 1396 | STACK_PUSH(vsp, res); 1397 | } 1398 | STACK_PUSH(vsp, eflags); 1399 | } 1400 | 1401 | DEFINE_SEMANTIC_64(SHL_64) = SHL; 1402 | DEFINE_SEMANTIC(SHL_32) = SHL; 1403 | DEFINE_SEMANTIC(SHL_16) = SHL; 1404 | DEFINE_SEMANTIC(SHL_8) = SHL; 1405 | 1406 | // SHR semantic. 1407 | // 1408 | template 1409 | INLINE CONST bool CF_SHR(T op1, T op2, T res) 1410 | { 1411 | T long_mask = 0x3F; 1412 | T short_mask = 0x1F; 1413 | auto op_size = BitSizeOf(op1); 1414 | auto shift_mask = Select(UCmpEq(op_size, 64), long_mask, short_mask); 1415 | auto masked_shift = UAnd(op2, shift_mask); 1416 | 1417 | if (UCmpEq(masked_shift, 1)) 1418 | { 1419 | return UCmpEq(UAnd(op1, 1), 1); 1420 | } 1421 | else if (UCmpLt(masked_shift, op_size)) 1422 | { 1423 | return UCmpEq(UAnd(res, 1), 1); 1424 | } 1425 | return UNDEF(); 1426 | } 1427 | 1428 | template 1429 | INLINE CONST bool OF_SHR(T val) 1430 | { 1431 | return SF(val); 1432 | } 1433 | 1434 | template 1435 | INLINE void SHR(size_t &vsp) 1436 | { 1437 | // 1. Check if it's 'byte' size. 1438 | // 1439 | bool isByte = (sizeof(T) == 1); 1440 | // 2. Initialize the operands. 1441 | // 1442 | T op1 = 0; 1443 | T op2 = 0; 1444 | // 3. Fetch the operands. 1445 | // 1446 | if (isByte) 1447 | { 1448 | op1 = Trunc(STACK_POP(vsp)); 1449 | op2 = Trunc(STACK_POP(vsp)); 1450 | } 1451 | else 1452 | { 1453 | op1 = STACK_POP(vsp); 1454 | op2 = STACK_POP(vsp); 1455 | } 1456 | // 4. Calculate shift. 1457 | // 1458 | auto res = op1 >> op2; 1459 | // 5. Calculate the eflags. 1460 | // 1461 | size_t eflags = 0; 1462 | bool cf = CF_SHR(op1, op2, res); 1463 | bool pf = PF(res); 1464 | bool af = UNDEF(); 1465 | bool zf = ZF(res); 1466 | bool sf = false; 1467 | bool of = OF_SHR(op1); 1468 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 1469 | // 6. Save the result. 1470 | // 1471 | if (isByte) 1472 | { 1473 | STACK_PUSH(vsp, ZExt(res)); 1474 | } 1475 | else 1476 | { 1477 | STACK_PUSH(vsp, res); 1478 | } 1479 | STACK_PUSH(vsp, eflags); 1480 | } 1481 | 1482 | DEFINE_SEMANTIC_64(SHR_64) = SHR; 1483 | DEFINE_SEMANTIC(SHR_32) = SHR; 1484 | DEFINE_SEMANTIC(SHR_16) = SHR; 1485 | DEFINE_SEMANTIC(SHR_8) = SHR; 1486 | 1487 | // SHLD semantic. 1488 | // 1489 | template 1490 | INLINE CONST bool CF_SHLD(T val, T masked_shift) 1491 | { 1492 | return SHLDCarryFlag(val, masked_shift); 1493 | } 1494 | 1495 | template 1496 | INLINE CONST bool OF_SHLD(T val, T res) 1497 | { 1498 | return BXor(SignFlag(val), SignFlag(res)); 1499 | } 1500 | 1501 | template 1502 | INLINE void SHLD(size_t &vsp) 1503 | { 1504 | // 1. Fetch the operands. 1505 | // 1506 | T val1 = STACK_POP(vsp); 1507 | T val2 = STACK_POP(vsp); 1508 | T shift = STACK_POP(vsp); 1509 | // 2. Calculate the shift left. 1510 | // 1511 | T long_mask = 0x3F; 1512 | T short_mask = 0x1F; 1513 | auto op_size = BitSizeOf(val1); 1514 | auto shift_mask = Select(UCmpEq(op_size, 64), long_mask, short_mask); 1515 | auto masked_shift = UAnd(shift, shift_mask); 1516 | // Execute the real shift. 1517 | // 1518 | auto left = UShl(val1, masked_shift); 1519 | auto right = UShr(val2, USub(op_size, masked_shift)); 1520 | auto res = UOr(left, right); 1521 | // Calculate the eflags. 1522 | // 1523 | size_t eflags = 0; 1524 | bool cf = CF_SHLD(val1, masked_shift); 1525 | bool pf = PF(res); 1526 | bool af = UNDEF(); 1527 | bool zf = ZF(res); 1528 | bool sf = SF(res); 1529 | bool of = OF_SHLD(val1, res); 1530 | // Update the eflags. 1531 | // 1532 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 1533 | // Save the result. 1534 | // 1535 | STACK_PUSH(vsp, res); 1536 | STACK_PUSH(vsp, eflags); 1537 | } 1538 | 1539 | DEFINE_SEMANTIC_64(SHLD_64) = SHLD; 1540 | DEFINE_SEMANTIC(SHLD_32) = SHLD; 1541 | DEFINE_SEMANTIC(SHLD_16) = SHLD; 1542 | DEFINE_SEMANTIC(SHLD_8) = SHLD; 1543 | 1544 | // SHRD semantic. 1545 | // 1546 | template 1547 | INLINE CONST bool CF_SHRD(T val, T masked_shift) 1548 | { 1549 | return SHRDCarryFlag(val, masked_shift); 1550 | } 1551 | 1552 | template 1553 | INLINE CONST bool OF_SHRD(T val, T res) 1554 | { 1555 | return BXor(SignFlag(val), SignFlag(res)); 1556 | } 1557 | 1558 | template 1559 | INLINE void SHRD(size_t &vsp) 1560 | { 1561 | // 1. Fetch the operands. 1562 | // 1563 | T val1 = STACK_POP(vsp); 1564 | T val2 = STACK_POP(vsp); 1565 | T shift = STACK_POP(vsp); 1566 | // 2. Calculate the shift right. 1567 | // 1568 | T long_mask = 0x3F; 1569 | T short_mask = 0x1F; 1570 | auto op_size = BitSizeOf(val1); 1571 | auto shift_mask = Select(UCmpEq(op_size, 64), long_mask, short_mask); 1572 | auto masked_shift = UAnd(shift, shift_mask); 1573 | // Execute the real shift. 1574 | // 1575 | auto left = UShl(val2, USub(op_size, masked_shift)); 1576 | auto right = UShr(val1, masked_shift); 1577 | auto res = UOr(left, right); 1578 | // Calculate the eflags. 1579 | // 1580 | size_t eflags = 0; 1581 | bool cf = CF_SHRD(val1, masked_shift); 1582 | bool pf = PF(res); 1583 | bool af = UNDEF(); 1584 | bool zf = ZF(res); 1585 | bool sf = SF(res); 1586 | bool of = OF_SHRD(val1, res); 1587 | // Update the eflags. 1588 | // 1589 | UPDATE_EFLAGS(eflags, cf, pf, af, zf, sf, of); 1590 | // Save the result. 1591 | // 1592 | STACK_PUSH(vsp, res); 1593 | STACK_PUSH(vsp, eflags); 1594 | } 1595 | 1596 | DEFINE_SEMANTIC_64(SHRD_64) = SHRD; 1597 | DEFINE_SEMANTIC(SHRD_32) = SHRD; 1598 | DEFINE_SEMANTIC(SHRD_16) = SHRD; 1599 | DEFINE_SEMANTIC(SHRD_8) = SHRD; 1600 | 1601 | // JUMP semantic. 1602 | // 1603 | INLINE void JMP(size_t &vsp, size_t &vip) 1604 | { 1605 | vip = STACK_POP(vsp); 1606 | } 1607 | 1608 | INLINE void JCC_DEC(size_t &vsp, size_t &vip) 1609 | { 1610 | vip = STACK_POP(vsp) - 4; 1611 | } 1612 | 1613 | INLINE void JCC_INC(size_t &vsp, size_t &vip) 1614 | { 1615 | vip = STACK_POP(vsp) + 4; 1616 | } 1617 | 1618 | DEFINE_SEMANTIC(JCC_INC) = JCC_INC; 1619 | DEFINE_SEMANTIC(JCC_DEC) = JCC_DEC; 1620 | DEFINE_SEMANTIC(JMP) = JMP; 1621 | DEFINE_SEMANTIC(RET) = JMP; 1622 | 1623 | // Helper function to keep the PC value. 1624 | // 1625 | extern "C" CONST __attribute__((noduplicate)) __attribute__((nomerge)) size_t KeepReturn(size_t ret0, size_t ret1); 1626 | 1627 | extern "C" void retainPointers() 1628 | { 1629 | RAM[0] = KeepReturn(0, 0); 1630 | GS[0] = 0; 1631 | FS[0] = 0; 1632 | } 1633 | 1634 | 1635 | IF_64BIT( 1636 | extern "C" CONST __attribute__((noduplicate)) __attribute__((nomerge)) size_t ExternalFunction(size_t rcx, size_t rdx, size_t r8, size_t r9); 1637 | ) 1638 | 1639 | IF_64BIT( 1640 | INLINE extern "C" size_t ExternalFunctionRetain(size_t rcx, size_t rdx, size_t r8, size_t r9) 1641 | { 1642 | return ExternalFunction(rcx, rdx, r8, r9); 1643 | }) 1644 | 1645 | IF_64BIT( 1646 | extern "C" size_t VirtualStub(rptr rax, rptr rbx, rptr rcx, rptr rdx, rptr rsi, rptr rdi, rptr rbp, rptr rsp, rptr r8, rptr r9, rptr r10, rptr r11, rptr r12, rptr r13, rptr r14, rptr r15, rptr eflags, rptr vsp, rptr vip, VirtualRegister *__restrict__ vmregs); 1647 | ) 1648 | 1649 | IF_64BIT( 1650 | INLINE extern "C" size_t VirtualStubEmpty(rptr rax, rptr rbx, rptr rcx, rptr rdx, rptr rsi, rptr rdi, rptr rbp, rptr rsp, rptr r8, rptr r9, rptr r10, rptr r11, rptr r12, rptr r13, rptr r14, rptr r15, rptr eflags, rptr vsp, rptr vip, VirtualRegister *__restrict__ vmregs) 1651 | { 1652 | return 0; 1653 | }) 1654 | 1655 | IF_64BIT( 1656 | extern "C" INLINE size_t VirtualFunction(rptr rax, rptr rbx, rptr rcx, rptr rdx, rptr rsi, rptr rdi, rptr rbp, rptr rsp, rptr r8, rptr r9, rptr r10, rptr r11, rptr r12, rptr r13, rptr r14, rptr r15, rptr eflags) 1657 | { 1658 | VirtualRegister vmregs[30] = { 0 }; 1659 | size_t vip = 0; 1660 | vip = VirtualStub(rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15, eflags, rsp, vip, vmregs); 1661 | eflags = UNDEF(); 1662 | return vip; 1663 | }) 1664 | 1665 | IF_64BIT( 1666 | extern "C" size_t SlicePC(size_t rax, size_t rbx, size_t rcx, size_t rdx, size_t rsi, size_t rdi, size_t rbp, size_t rsp, size_t r8, size_t r9, size_t r10, size_t r11, size_t r12, size_t r13, size_t r14, size_t r15, size_t eflags) 1667 | { 1668 | VirtualRegister vmregs[30] = { 0 }; 1669 | size_t vip = 0; 1670 | size_t vsp = rsp; 1671 | vip = VirtualStub(rax, rbx, rcx, rdx, rsi, rdi, rbp, rsp, r8, r9, r10, r11, r12, r13, r14, r15, eflags, vsp, vip, vmregs); 1672 | eflags = 0; 1673 | return vip; 1674 | }) 1675 | 1676 | IF_32BIT( 1677 | extern "C" CONST __attribute__((noduplicate)) __attribute__((nomerge)) size_t ExternalFunction(rptr eax, rptr ebx, rptr ecx, rptr edx, rptr esi, rptr edi, rptr ebp); 1678 | ) 1679 | 1680 | IF_32BIT( 1681 | INLINE extern "C" size_t ExternalFunctionRetain(rptr eax, rptr ebx, rptr ecx, rptr edx, rptr esi, rptr edi, rptr ebp) 1682 | { 1683 | return ExternalFunction(eax, ebx, ecx, edx, esi, edi, ebp); 1684 | }) 1685 | 1686 | IF_32BIT( 1687 | extern "C" size_t VirtualStub(rptr eax, rptr ebx, rptr ecx, rptr edx, rptr esi, rptr edi, rptr ebp, rptr esp, rptr eip, rptr eflags, rptr vsp, rptr vip, VirtualRegister *__restrict__ vmregs); 1688 | ) 1689 | 1690 | IF_32BIT( 1691 | INLINE extern "C" size_t VirtualStubEmpty(rptr eax, rptr ebx, rptr ecx, rptr edx, rptr esi, rptr edi, rptr ebp, rptr esp, rptr eip, rptr eflags, rptr vsp, rptr vip, VirtualRegister *__restrict__ vmregs) 1692 | { 1693 | return 0; 1694 | }) 1695 | 1696 | IF_32BIT( 1697 | extern "C" INLINE size_t VirtualFunction(rptr eax, rptr ebx, rptr ecx, rptr edx, rptr esi, rptr edi, rptr ebp, rptr esp, rptr eip, rptr eflags) 1698 | { 1699 | VirtualRegister vmregs[30] = { 0 }; 1700 | size_t vip = 0; 1701 | size_t vsp = esp; 1702 | vip = VirtualStub(eax, ebx, ecx, edx, esi, edi, ebp, esp, eip, eflags, vsp, vip, vmregs); 1703 | esp = vsp; 1704 | eflags = UNDEF(); 1705 | return vip; 1706 | }) 1707 | 1708 | IF_32BIT( 1709 | extern "C" size_t SlicePC(size_t eax, size_t ebx, size_t ecx, size_t edx, size_t esi, size_t edi, size_t ebp, size_t esp, size_t eip, size_t eflags) 1710 | { 1711 | VirtualRegister vmregs[30] = { 0 }; 1712 | size_t vsp = esp; 1713 | size_t vip = 0; 1714 | vip = VirtualStub(eax, ebx, ecx, edx, esi, edi, ebp, esp, eip, eflags, vsp, vip, vmregs); 1715 | eflags = 0; 1716 | return vip; 1717 | }) 1718 | -------------------------------------------------------------------------------- /shell.nix: -------------------------------------------------------------------------------- 1 | { pkgs ? import {} }: 2 | let 3 | stdenv = pkgs.llvmPackages_15.stdenv; 4 | 5 | triton = stdenv.mkDerivation rec { 6 | version = "dev-v1.0"; 7 | name = "triton-${version}"; 8 | 9 | src = pkgs.fetchFromGitHub { 10 | owner = "JonathanSalwan"; 11 | repo = "Triton"; 12 | rev = "6095a21c332caa3435e5ce9a88f544f2b9c3be5b"; 13 | sha256 = "sha256-beexdXd48i3OHPOPOOJ59go1+UH1fqL7JRjwl14bKRQ="; 14 | }; 15 | 16 | cmakeFlags = [ 17 | "-DBOOST_INTERFACE=OFF" 18 | "-DBUILD_EXAMPLES=OFF" 19 | "-DENABLE_TEST=OFF" 20 | "-DPYTHON_BINDINGS=OFF" 21 | "-DLLVM_INTERFACE=ON" 22 | ]; 23 | 24 | nativeBuildInputs = [ 25 | pkgs.cmake 26 | ]; 27 | 28 | buildInputs = [ 29 | pkgs.capstone 30 | pkgs.llvm_15 31 | pkgs.z3 32 | ]; 33 | }; 34 | 35 | in rec { 36 | titan = stdenv.mkDerivation { 37 | name = "titan"; 38 | 39 | nativeBuildInputs = [ 40 | pkgs.cmake 41 | pkgs.ninja 42 | pkgs.clang_15 43 | pkgs.graphviz 44 | ]; 45 | 46 | buildInputs = [ 47 | pkgs.range-v3 48 | pkgs.fmt 49 | pkgs.llvm_15 50 | triton 51 | ]; 52 | }; 53 | } 54 | -------------------------------------------------------------------------------- /src/asserts.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | inline static constexpr void abort_if(bool condition, const char* string) 5 | { 6 | if (condition) 7 | { 8 | throw std::logic_error{ string }; 9 | } 10 | } 11 | 12 | #define fassert__stringify(x) #x 13 | #define fassert__istringify(x) fassert__stringify(x) 14 | #define fassert(...) abort_if(!bool(__VA_ARGS__), fassert__stringify(__VA_ARGS__) " at " __FILE__ ":" fassert__istringify(__LINE__)) 15 | -------------------------------------------------------------------------------- /src/binary.cpp: -------------------------------------------------------------------------------- 1 | #include "binary.hpp" 2 | #include "logger.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | llvm::cl::opt binarypath("b", 9 | llvm::cl::desc("Path to the target Binary"), 10 | llvm::cl::value_desc("Binary"), 11 | llvm::cl::Required); 12 | 13 | Binary::Binary() 14 | { 15 | auto object_or_err = llvm::object::ObjectFile::createObjectFile(binarypath); 16 | 17 | if (object_or_err.takeError()) 18 | { 19 | logger::error("Binary::Binary: Failed to create object file."); 20 | } 21 | 22 | std::tie(object, memory) = object_or_err->takeBinary(); 23 | } 24 | 25 | std::optional Binary::get_section(uint64_t address) const noexcept 26 | { 27 | for (const auto& section : object->sections()) 28 | { 29 | if (address >= section.getAddress() && address < section.getAddress() + section.getSize()) 30 | { 31 | return section; 32 | } 33 | } 34 | return std::nullopt; 35 | } 36 | 37 | std::vector Binary::get_bytes(uint64_t address, size_t size) const noexcept 38 | { 39 | std::vector raw; 40 | if (auto section = get_section(address)) 41 | { 42 | if (auto offset = address - section->getAddress(); section->getSize() >= offset + size) 43 | { 44 | if (auto contents = section->getContents()) 45 | { 46 | for (auto byte : contents->substr(offset, size)) 47 | { 48 | raw.push_back(byte); 49 | } 50 | } 51 | else 52 | { 53 | logger::info("Binary::get_bytes: Failed to read {} bytes from 0x{:x}.", size, address); 54 | } 55 | } 56 | else 57 | { 58 | logger::info("Binary::get_bytes: No offset within section for 0x{:x}:{} was found.", address, size); 59 | } 60 | } 61 | return raw; 62 | } 63 | 64 | bool Binary::is_x64() const noexcept 65 | { 66 | return object->getArch() == llvm::Triple::ArchType::x86_64; 67 | } 68 | -------------------------------------------------------------------------------- /src/binary.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct Binary 12 | { 13 | Binary(); 14 | 15 | auto get_section(uint64_t address) const noexcept -> std::optional; 16 | auto get_bytes(uint64_t address, size_t size) const noexcept -> std::vector; 17 | 18 | template 19 | std::vector get_bytes(uint64_t address) const noexcept 20 | { 21 | return get_bytes(address, sizeof(T)); 22 | } 23 | 24 | bool is_x64() const noexcept; 25 | 26 | auto begin() { return object->section_begin(); } 27 | auto end() { return object->section_end(); } 28 | 29 | private: 30 | std::unique_ptr object; 31 | std::unique_ptr memory; 32 | }; 33 | -------------------------------------------------------------------------------- /src/emulator.cpp: -------------------------------------------------------------------------------- 1 | #include "emulator.hpp" 2 | #include "logger.hpp" 3 | 4 | #include 5 | #include 6 | 7 | using namespace triton; 8 | using namespace triton::arch; 9 | using namespace triton::arch::x86; 10 | 11 | Emulator::Emulator(triton::arch::architecture_e arch) noexcept 12 | : Context(arch) 13 | , image{ std::make_shared() } 14 | { 15 | setMode(modes::MEMORY_ARRAY, false); 16 | setMode(modes::ALIGNED_MEMORY, true); 17 | setMode(modes::CONSTANT_FOLDING, true); 18 | setMode(modes::AST_OPTIMIZATIONS, true); 19 | setMode(modes::PC_TRACKING_SYMBOLIC, false); 20 | setMode(modes::TAINT_THROUGH_POINTERS, false); 21 | setMode(modes::SYMBOLIZE_INDEX_ROTATION, false); 22 | 23 | concretizeAllMemory(); 24 | concretizeAllRegister(); 25 | 26 | auto get_memory_cb = [this](triton::Context& context, const triton::arch::MemoryAccess& memory) 27 | { 28 | if (!context.isConcreteMemoryValueDefined(memory.getAddress(), memory.getSize())) 29 | { 30 | context.setConcreteMemoryAreaValue(memory.getAddress(), this->image->get_bytes(memory.getAddress(), memory.getSize())); 31 | } 32 | }; 33 | 34 | addCallback( 35 | triton::callbacks::callback_e::GET_CONCRETE_MEMORY_VALUE, 36 | triton::callbacks::getConcreteMemoryValueCallback{ get_memory_cb, &get_memory_cb } 37 | ); 38 | } 39 | 40 | Emulator::Emulator(Emulator const& other) noexcept 41 | : Emulator(other.getArchitecture()) 42 | { 43 | for (const auto& [reg_e, reg] : other.getAllRegisters()) 44 | setConcreteRegisterValue(reg, other.getConcreteRegisterValue(reg)); 45 | 46 | for (const auto& [addr, value] : other.getConcreteMemory()) 47 | setConcreteMemoryValue(addr, value); 48 | image = other.image; 49 | } 50 | 51 | uint64_t Emulator::read(const triton::arch::Register& reg) const noexcept 52 | { 53 | return static_cast(getConcreteRegisterValue(reg)); 54 | } 55 | 56 | void Emulator::write(const triton::arch::Register& reg, uint64_t value) noexcept 57 | { 58 | setConcreteRegisterValue(reg, value); 59 | } 60 | 61 | uint64_t Emulator::rip() const noexcept 62 | { 63 | return read(rip_register()); 64 | } 65 | 66 | uint64_t Emulator::rsp() const noexcept 67 | { 68 | return read(rsp_register()); 69 | } 70 | 71 | const triton::arch::Register& Emulator::rip_register() const noexcept 72 | { 73 | return getRegister(getArchitecture() == arch::ARCH_X86_64 ? "rip" : "eip"); 74 | } 75 | 76 | const triton::arch::Register& Emulator::rsp_register() const noexcept 77 | { 78 | return getRegister(getArchitecture() == arch::ARCH_X86_64 ? "rsp" : "esp"); 79 | } 80 | 81 | uint64_t Emulator::ptrsize() const noexcept 82 | { 83 | return getArchitecture() == ARCH_X86_64 ? 8 : 4; 84 | } 85 | 86 | std::set Emulator::regs() const noexcept 87 | { 88 | if (getArchitecture() == triton::arch::ARCH_X86_64) 89 | return { 90 | getRegister("rax"), 91 | getRegister("rbx"), 92 | getRegister("rcx"), 93 | getRegister("rdx"), 94 | getRegister("rdi"), 95 | getRegister("rsi"), 96 | getRegister("rsp"), 97 | getRegister("rbp"), 98 | getRegister("r8"), 99 | getRegister("r9"), 100 | getRegister("r10"), 101 | getRegister("r11"), 102 | getRegister("r12"), 103 | getRegister("r13"), 104 | getRegister("r14"), 105 | getRegister("r15"), 106 | }; 107 | return { 108 | getRegister("eax"), 109 | getRegister("ebx"), 110 | getRegister("ecx"), 111 | getRegister("edx"), 112 | getRegister("edi"), 113 | getRegister("esi"), 114 | getRegister("esp"), 115 | getRegister("ebp"), 116 | }; 117 | } 118 | 119 | triton::arch::Instruction Emulator::disassemble() const noexcept 120 | { 121 | auto curr_pc = rip(); 122 | auto bytes = getConcreteMemoryAreaValue(curr_pc, 16); 123 | 124 | Instruction insn(curr_pc, bytes.data(), bytes.size()); 125 | disassembly(insn); 126 | return insn; 127 | } 128 | 129 | triton::arch::Instruction Emulator::single_step() 130 | { 131 | auto insn = disassemble(); 132 | execute(insn); 133 | return insn; 134 | } 135 | 136 | void Emulator::execute(triton::arch::Instruction& insn) 137 | { 138 | if (buildSemantics(insn) != triton::arch::NO_FAULT) 139 | { 140 | logger::error("Emulator::execute: Failed to execute instruction at 0x{:x}.", rip()); 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /src/emulator.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "binary.hpp" 4 | 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | struct Emulator : public triton::Context 13 | { 14 | explicit Emulator(triton::arch::architecture_e arch) noexcept; 15 | 16 | Emulator(Emulator const& other) noexcept; 17 | 18 | uint64_t ptrsize() const noexcept; 19 | // Most used registers getters. 20 | // 21 | uint64_t rip() const noexcept; 22 | uint64_t rsp() const noexcept; 23 | 24 | const triton::arch::Register& rip_register() const noexcept; 25 | const triton::arch::Register& rsp_register() const noexcept; 26 | 27 | std::set regs() const noexcept; 28 | 29 | uint64_t read(const triton::arch::Register& reg) const noexcept; 30 | template 31 | T read(uint64_t address) const noexcept 32 | { 33 | return static_cast(getConcreteMemoryValue({ address, sizeof(T) })); 34 | } 35 | template 36 | T read(const triton::arch::MemoryAccess& memory) const noexcept 37 | { 38 | return static_cast(getConcreteMemoryValue(memory)); 39 | } 40 | 41 | void write(const triton::arch::Register& reg, uint64_t value) noexcept; 42 | template 43 | void write(uint64_t address, T value) noexcept 44 | { 45 | setConcreteMemoryValue({ address, sizeof(T) }, value); 46 | } 47 | template 48 | void write(const triton::arch::MemoryAccess& memory, T value) const noexcept 49 | { 50 | return static_cast(setConcreteMemoryValue(memory, value)); 51 | } 52 | 53 | triton::arch::Instruction disassemble() const noexcept; 54 | triton::arch::Instruction single_step(); 55 | 56 | void execute(triton::arch::Instruction& insn); 57 | 58 | protected: 59 | std::shared_ptr image; 60 | }; 61 | -------------------------------------------------------------------------------- /src/explorer.cpp: -------------------------------------------------------------------------------- 1 | #include "explorer.hpp" 2 | #include "il/optimizer.hpp" 3 | #include "il/solver.hpp" 4 | #include "asserts.hpp" 5 | #include "utils.hpp" 6 | 7 | static constexpr auto stack_base = 0x10000; 8 | 9 | Explorer::Explorer(std::shared_ptr lifter, std::shared_ptr tracer) 10 | : lifter(lifter), tracer(tracer), block(nullptr), terminate(false) 11 | { 12 | } 13 | 14 | std::unique_ptr Explorer::explore(uint64_t address) 15 | { 16 | tracer->write(tracer->rip_register(), address); 17 | tracer->write(tracer->rsp_register(), stack_base); 18 | 19 | block = vm::Routine::begin(address); 20 | 21 | std::visit(*this, tracer->step(step_t::stop_before_branch)); 22 | 23 | worklist.push(address); 24 | snapshots.emplace(address, std::move(tracer)); 25 | 26 | while (!worklist.empty()) 27 | { 28 | address = worklist.top(); worklist.pop(); 29 | 30 | if (explored.count(address)) 31 | { 32 | logger::warn("block 0x{:x} already explored.", address); 33 | continue; 34 | } 35 | explored.insert(address); 36 | 37 | block = block->owner->blocks.at(address); 38 | tracer = snapshots.at(address); 39 | 40 | if (block->lifted != nullptr) 41 | { 42 | reprove_block(); 43 | continue; 44 | } 45 | 46 | logger::debug("exploring 0x{:x}", address); 47 | 48 | while (!terminate) 49 | { 50 | // logger::info("execute: 0x{:x}", tracer->rip()); 51 | // Process instruction. 52 | // 53 | std::visit(*this, tracer->step(step_t::stop_before_branch)); 54 | } 55 | terminate = false; 56 | 57 | for (const auto& reprove : get_reprove_blocks()) 58 | { 59 | logger::info("\treprove -> 0x{:x}", reprove); 60 | worklist.push(reprove); 61 | explored.erase(reprove); 62 | } 63 | } 64 | return std::unique_ptr{ block->owner }; 65 | } 66 | 67 | void Explorer::operator()(vm::Add&& insn) 68 | { 69 | logger::info("{:<5} {:<2}", "add", insn.size()); 70 | block->add(std::move(insn)); 71 | } 72 | 73 | void Explorer::operator()(vm::Shl&& insn) 74 | { 75 | logger::info("{:<5} {:<2}", "shl", insn.size()); 76 | block->add(std::move(insn)); 77 | } 78 | 79 | void Explorer::operator()(vm::Shr&& insn) 80 | { 81 | logger::info("{:<5} {:<2}", "shr", insn.size()); 82 | block->add(std::move(insn)); 83 | } 84 | 85 | void Explorer::operator()(vm::Ldr&& insn) 86 | { 87 | logger::info("{:<5} {:<2}", "ldr", insn.size()); 88 | block->add(std::move(insn)); 89 | } 90 | 91 | void Explorer::operator()(vm::Str&& insn) 92 | { 93 | logger::info("{:<5} {:<2}", "str", insn.size()); 94 | block->add(std::move(insn)); 95 | } 96 | 97 | void Explorer::operator()(vm::Nor&& insn) 98 | { 99 | logger::info("{:<5} {:<2}", "nor", insn.size()); 100 | block->add(std::move(insn)); 101 | } 102 | 103 | void Explorer::operator()(vm::Nand&& insn) 104 | { 105 | logger::info("{:<5} {:<2}", "nand", insn.size()); 106 | block->add(std::move(insn)); 107 | } 108 | 109 | void Explorer::operator()(vm::Shrd&& insn) 110 | { 111 | logger::info("{:<5} {:<2}", "shrd", insn.size()); 112 | block->add(std::move(insn)); 113 | } 114 | 115 | void Explorer::operator()(vm::Shld&& insn) 116 | { 117 | logger::info("{:<5} {:<2}", "shld", insn.size()); 118 | block->add(std::move(insn)); 119 | } 120 | 121 | void Explorer::operator()(vm::Push&& insn) 122 | { 123 | logger::info("{:<5} {:<2} {}", "push", insn.size(), insn.op().to_string()); 124 | block->add(std::move(insn)); 125 | } 126 | 127 | void Explorer::operator()(vm::Pop&& insn) 128 | { 129 | logger::info("{:<5} {:<2} {}", "pop", insn.size(), insn.op().to_string()); 130 | block->add(std::move(insn)); 131 | } 132 | 133 | void Explorer::operator()(vm::Jmp&& insn) 134 | { 135 | logger::info("jmp"); 136 | block->lifted = lifter->lift_basic_block(block); 137 | il::optimize_block_function(block->lifted); 138 | // Execute branch instruction. 139 | // 140 | tracer->step(step_t::execute_branch); 141 | // Fork block and continue executing from new one. 142 | // 143 | auto vip = tracer->vip(); 144 | block->fork(vip); 145 | worklist.push(vip); 146 | snapshots.emplace(vip, tracer->fork()); 147 | // Terminate current block. 148 | // 149 | terminate = true; 150 | } 151 | 152 | void Explorer::operator()(vm::Ret&& insn) 153 | { 154 | block->add(std::move(insn)); 155 | // Terminate current block. 156 | // 157 | terminate = true; 158 | } 159 | 160 | void Explorer::operator()(vm::Jcc&& insn) 161 | { 162 | logger::info("jcc {}", insn.direction() == vm::jcc_e::up ? "up" : "down"); 163 | block->add(insn); 164 | // Lift basic block. 165 | // 166 | block->lifted = lifter->lift_basic_block(block); 167 | il::optimize_block_function(block->lifted); 168 | // Extract targets. 169 | // 170 | auto slice = lifter->build_function(block->owner, block->vip()); 171 | il::optimize_block_function(slice); 172 | 173 | auto ret = lifter->get_return_args(slice); 174 | 175 | for (const auto target : il::get_possible_targets(ret.program_counter())) 176 | { 177 | logger::info("\tjcc -> 0x{:x}", target); 178 | 179 | auto fork = tracer->fork(); 180 | fork->write(fork->vsp(), target - (insn.direction() == vm::jcc_e::up ? 1 : -1) * 4); 181 | // Execute branch instruction. 182 | // 183 | fork->step(step_t::execute_branch); 184 | 185 | block->fork(target); 186 | worklist.push(target); 187 | snapshots.insert({ target, std::move(fork) }); 188 | } 189 | // Terminate current block. 190 | // 191 | terminate = true; 192 | 193 | slice->eraseFromParent(); 194 | } 195 | 196 | void Explorer::operator()(vm::Exit&& insn) 197 | { 198 | for (const auto& reg : insn.regs()) 199 | { 200 | logger::info("{:<5} {:<2} {}", "pop", reg.size(), reg.op().to_string()); 201 | } 202 | logger::info("ret"); 203 | // Add instructions to the block. 204 | // 205 | block->add(std::move(insn)); 206 | block->add(vm::Ret()); 207 | // Lift basic block. 208 | // 209 | block->lifted = lifter->lift_basic_block(block); 210 | il::optimize_block_function(block->lifted); 211 | 212 | auto slice = lifter->build_function(block->owner, block->vip()); 213 | il::optimize_block_function(slice); 214 | 215 | auto args = lifter->get_return_args(slice); 216 | 217 | args.program_counter()->dump(); 218 | args.return_address()->dump(); 219 | // NOTE: This is not tested and probably wrong. 220 | // 221 | if (auto load = llvm::dyn_cast(args.program_counter())) 222 | { 223 | if (auto gep = llvm::dyn_cast(load->getPointerOperand())) 224 | { 225 | if (auto cint = llvm::dyn_cast(gep->getOperand(gep->getNumOperands() - 1))) 226 | { 227 | lifter->create_external_call(block->lifted, fmt::format("External.0x{:x}", cint->getLimitedValue())); 228 | il::optimize_block_function(block->lifted); 229 | } 230 | } 231 | } 232 | else if (auto cint = llvm::dyn_cast(args.program_counter())) 233 | { 234 | lifter->create_external_call(block->lifted, fmt::format("External.0x{:x}", cint->getLimitedValue())); 235 | il::optimize_block_function(block->lifted); 236 | } 237 | 238 | if (auto cint = llvm::dyn_cast(args.return_address())) 239 | { 240 | auto address = cint->getLimitedValue(); 241 | logger::info("Continue vm execution from 0x{:x}", address); 242 | tracer = std::make_shared(tracer->getArchitecture()); 243 | tracer->write(tracer->rip_register(), address); 244 | tracer->write(tracer->rsp_register(), stack_base); 245 | 246 | block->fork(address); 247 | worklist.push(address); 248 | snapshots.insert({ address, std::move(tracer) }); 249 | } 250 | // Terminate current block. 251 | // 252 | terminate = true; 253 | 254 | slice->eraseFromParent(); 255 | } 256 | 257 | void Explorer::operator()(vm::Enter&& insn) 258 | { 259 | for (const auto& reg : insn.regs()) 260 | { 261 | logger::info("{:<5} {:<2} {}", "push", reg.size(), reg.op().to_string()); 262 | } 263 | // Add instructions to the block. 264 | // 265 | block->add(std::move(insn)); 266 | } 267 | 268 | void Explorer::reprove_block() 269 | { 270 | auto slice = lifter->build_function(block->owner, block->vip()); 271 | il::optimize_block_function(slice); 272 | 273 | auto ret = lifter->get_return_args(slice); 274 | 275 | for (const auto target : il::get_possible_targets(ret.program_counter())) 276 | { 277 | if (!block->owner->contains(target)) 278 | { 279 | logger::info("\tfound new branch: 0x{:x}", target); 280 | 281 | auto fork = tracer->fork(); 282 | auto insn = std::get(tracer->step(step_t::stop_before_branch)); 283 | 284 | fork->write(fork->vsp(), target - (insn.direction() == vm::jcc_e::up ? 1 : -1) * 4); 285 | // Execute branch instruction. 286 | // 287 | fork->step(step_t::execute_branch); 288 | 289 | block->fork(target); 290 | worklist.push(target); 291 | snapshots.insert({ target, std::move(fork) }); 292 | } 293 | } 294 | 295 | slice->eraseFromParent(); 296 | } 297 | 298 | std::set Explorer::get_reprove_blocks() 299 | { 300 | std::set reprove; 301 | 302 | auto fill = [&reprove](const vm::BasicBlock* block, auto&& func) -> void 303 | { 304 | for (const auto& child : block->next) 305 | { 306 | if (!reprove.contains(child->vip()) && child->next.size() != 2 && child->flow() == vm::flow_t::conditional) 307 | { 308 | reprove.insert(child->vip()); 309 | func(child, func); 310 | } 311 | } 312 | }; 313 | fill(block, fill); 314 | return reprove; 315 | } 316 | -------------------------------------------------------------------------------- /src/explorer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "vm/routine.hpp" 4 | 5 | #include "lifter.hpp" 6 | #include "tracer.hpp" 7 | 8 | #include 9 | 10 | struct Explorer 11 | { 12 | Explorer(std::shared_ptr lifter, std::shared_ptr tracer); 13 | 14 | std::unique_ptr explore(uint64_t address); 15 | 16 | void operator()(vm::Add&&); 17 | void operator()(vm::Shl&&); 18 | void operator()(vm::Shr&&); 19 | void operator()(vm::Ldr&&); 20 | void operator()(vm::Str&&); 21 | void operator()(vm::Nor&&); 22 | void operator()(vm::Nand&&); 23 | void operator()(vm::Shld&&); 24 | void operator()(vm::Shrd&&); 25 | void operator()(vm::Push&&); 26 | void operator()(vm::Pop&&); 27 | void operator()(vm::Jmp&&); 28 | void operator()(vm::Ret&&); 29 | void operator()(vm::Jcc&&); 30 | void operator()(vm::Exit&&); 31 | void operator()(vm::Enter&&); 32 | 33 | private: 34 | void reprove_block(); 35 | 36 | std::set get_reprove_blocks(); 37 | 38 | // LLVM Lifter instance. 39 | // 40 | std::shared_ptr lifter; 41 | 42 | // Emulator that is currently active. 43 | // 44 | std::shared_ptr tracer; 45 | 46 | // List of blocks to explore. 47 | // 48 | std::stack worklist; 49 | 50 | // List of blocks already explored. 51 | // 52 | std::set explored; 53 | 54 | // Saved snapshots for every basic block. 55 | // 56 | std::map> snapshots; 57 | 58 | // Block that is currently processing. 59 | // 60 | vm::BasicBlock* block; 61 | 62 | // Terminate block exploration loop. 63 | // 64 | bool terminate; 65 | }; 66 | -------------------------------------------------------------------------------- /src/il/optimizer.cpp: -------------------------------------------------------------------------------- 1 | #include "optimizer.hpp" 2 | #include "logger.hpp" 3 | #include "utils.hpp" 4 | 5 | #include "passes/alias.hpp" 6 | #include "passes/coalescing.hpp" 7 | #include "passes/flags_synthesis.hpp" 8 | #include "passes/deps.hpp" 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | #include 21 | #include 22 | 23 | namespace il 24 | { 25 | void replace_undefined_variable(llvm::Function* fn) 26 | { 27 | if (auto undef = fn->getParent()->getGlobalVariable("__undef")) 28 | { 29 | for (auto user : undef->users()) 30 | { 31 | if (auto load = llvm::dyn_cast(user)) 32 | { 33 | if (auto parent = load->getParent()->getParent()) 34 | { 35 | if (parent == fn) 36 | { 37 | load->replaceAllUsesWith(llvm::UndefValue::get(load->getType())); 38 | } 39 | } 40 | } 41 | } 42 | } 43 | } 44 | 45 | bool inline_intrinsics(llvm::Function* fn) 46 | { 47 | std::set calls_to_inline; 48 | for (auto& bb : *fn) 49 | { 50 | for (auto& ins : bb) 51 | { 52 | if (auto call = llvm::dyn_cast(&ins)) 53 | { 54 | auto called_fn = call->getCalledFunction(); 55 | if (called_fn->hasFnAttribute(llvm::Attribute::AlwaysInline) && !called_fn->isDeclaration()) 56 | { 57 | calls_to_inline.insert(call); 58 | } 59 | } 60 | } 61 | } 62 | for (auto call : calls_to_inline) 63 | { 64 | llvm::InlineFunctionInfo ifi; 65 | llvm::InlineFunction(*call, ifi); 66 | } 67 | return !calls_to_inline.empty(); 68 | } 69 | 70 | void strip_names(llvm::Function* fn) 71 | { 72 | for (auto& bb : *fn) 73 | { 74 | bb.setName(""); 75 | for (auto& ins : bb) 76 | { 77 | if (ins.hasName()) ins.setName(""); 78 | } 79 | } 80 | } 81 | 82 | template 83 | void exhaust_optimizations(M& manager, A& analysis, O& object, uint64_t max_tries) 84 | { 85 | auto inscount{ object.getInstructionCount() }; 86 | auto tries{ 0u }; 87 | while (true) 88 | { 89 | manager.run(object, analysis); 90 | if (inscount > object.getInstructionCount()) 91 | { 92 | inscount = object.getInstructionCount(); 93 | tries = 0; 94 | } 95 | else if (tries++ > max_tries) 96 | { 97 | break; 98 | } 99 | } 100 | } 101 | 102 | void optimize_function(llvm::Function* fn, const opt_guide& guide) 103 | { 104 | llvm::AAManager aam; 105 | llvm::PassBuilder pb; 106 | llvm::LoopPassManager lpm; 107 | llvm::LoopAnalysisManager lam; 108 | llvm::CGSCCAnalysisManager cam; 109 | llvm::ModuleAnalysisManager mam; 110 | llvm::FunctionAnalysisManager fam; 111 | 112 | auto ofpm = pb.buildFunctionSimplificationPipeline(guide.level, llvm::ThinOrFullLTOPhase::None); 113 | auto ompm = pb.buildModuleOptimizationPipeline(guide.level, llvm::ThinOrFullLTOPhase::None); 114 | 115 | ofpm.addPass(llvm::createFunctionToLoopPassAdaptor(llvm::LoopRotatePass(), true, true, true)); 116 | // ofpm.addPass(MemoryCoalescingPass()); 117 | ofpm.addPass(llvm::VerifierPass()); 118 | 119 | while (inline_intrinsics(fn)) 120 | ; 121 | 122 | if (guide.alias_analysis) 123 | { 124 | aam.registerFunctionAnalysis(); 125 | aam.registerFunctionAnalysis(); 126 | aam.registerFunctionAnalysis(); 127 | aam.registerFunctionAnalysis(); 128 | aam.registerFunctionAnalysis(); 129 | aam.registerFunctionAnalysis(); 130 | fam.registerPass([] { return SegmentsAA(); }); 131 | fam.registerPass([aam] { return std::move(aam); }); 132 | } 133 | 134 | pb.registerLoopAnalyses(lam); 135 | pb.registerCGSCCAnalyses(cam); 136 | pb.registerModuleAnalyses(mam); 137 | pb.registerFunctionAnalyses(fam); 138 | pb.crossRegisterProxies(lam, fam, cam, mam); 139 | 140 | exhaust_optimizations(ofpm, fam, *fn, 2); 141 | 142 | if (guide.remove_undef) 143 | { 144 | replace_undefined_variable(fn); 145 | } 146 | 147 | exhaust_optimizations(ofpm, fam, *fn, 5); 148 | 149 | if (guide.apply_dse) 150 | { 151 | ofpm.addPass(MemoryDependenciesPass()); 152 | ofpm.addPass(FlagsSynthesisPass()); 153 | } 154 | 155 | ofpm.run(*fn, fam); 156 | 157 | if (guide.strip_names) 158 | strip_names(fn); 159 | 160 | if (guide.run_on_module) 161 | { 162 | exhaust_optimizations(ompm, mam, *fn->getParent(), 5); 163 | } 164 | 165 | cam.clear(); 166 | lam.clear(); 167 | fam.clear(); 168 | mam.clear(); 169 | } 170 | 171 | void optimize_block_function(llvm::Function* fn) 172 | { 173 | optimize_function(fn, { 174 | .strip_names = true, 175 | .level = llvm::OptimizationLevel::O3 176 | }); 177 | } 178 | 179 | void optimize_virtual_function(llvm::Function* fn) 180 | { 181 | optimize_function(fn, { 182 | .remove_undef = true, 183 | .run_on_module = true, 184 | .strip_names = true, 185 | .alias_analysis = true, 186 | .apply_dse = true, 187 | .level = llvm::OptimizationLevel::O3 188 | }); 189 | } 190 | } 191 | -------------------------------------------------------------------------------- /src/il/optimizer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace llvm 4 | { 5 | class Function; 6 | class Module; 7 | }; 8 | 9 | #include 10 | 11 | namespace il 12 | { 13 | struct opt_guide 14 | { 15 | bool remove_undef; 16 | bool run_on_module; 17 | bool strip_names; 18 | bool alias_analysis; 19 | bool apply_dse; 20 | llvm::OptimizationLevel level; 21 | }; 22 | 23 | void optimize_function(llvm::Function* fn, const opt_guide& guide); 24 | void optimize_block_function(llvm::Function* fn); 25 | void optimize_virtual_function(llvm::Function* fn); 26 | }; 27 | -------------------------------------------------------------------------------- /src/il/passes/alias.cpp: -------------------------------------------------------------------------------- 1 | #include "alias.hpp" 2 | #include "logger.hpp" 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | using namespace llvm::PatternMatch; 9 | 10 | #include 11 | #include 12 | 13 | enum class pointer_t 14 | { 15 | unknown, 16 | memory_array, 17 | memory_slot, 18 | stack_array, 19 | stack_slot 20 | }; 21 | 22 | bool is_stack_slot(const llvm::Value* ptr) 23 | { 24 | llvm::Value* value = nullptr; 25 | llvm::ConstantInt* constant = nullptr; 26 | 27 | auto pattern0 = m_Add(m_Load(m_Value(value)), m_ConstantInt(constant)); 28 | auto pattern1 = m_Add(m_Value(value), m_ConstantInt(constant)); 29 | auto pattern2 = m_Load(m_Value(value)); 30 | 31 | if (pattern0.match(ptr) || 32 | pattern1.match(ptr) || 33 | pattern2.match(ptr)) 34 | { 35 | if (auto arg = llvm::dyn_cast(value)) 36 | { 37 | if (arg->getName().endswith("sp")) 38 | { 39 | return true; 40 | } 41 | } 42 | } 43 | return false; 44 | } 45 | 46 | pointer_t get_pointer_type(const llvm::Value* ptr) 47 | { 48 | if (auto gep = llvm::dyn_cast(ptr)) 49 | { 50 | if (auto gv = llvm::dyn_cast(gep->getPointerOperand())) 51 | { 52 | if (gv->getName() == "RAM" && gep->getNumIndices() == 2) 53 | { 54 | if (is_stack_slot(gep->getOperand(2))) 55 | return pointer_t::stack_slot; 56 | 57 | std::set known; 58 | std::stack worklist{ { gep->getOperand(2) } }; 59 | std::vector bases; 60 | 61 | while (!worklist.empty()) 62 | { 63 | auto value = worklist.top(); worklist.pop(); 64 | 65 | if (known.contains(value)) 66 | continue; 67 | known.insert(value); 68 | 69 | if (auto load = llvm::dyn_cast(value)) 70 | { 71 | bases.push_back(load->getPointerOperand()); 72 | continue; 73 | } 74 | else if (auto arg = llvm::dyn_cast(value)) 75 | { 76 | bases.push_back(arg); 77 | continue; 78 | } 79 | else if (auto call = llvm::dyn_cast(value)) 80 | { 81 | auto name = call->getCalledFunction()->getName(); 82 | if (!name.startswith("llvm.ctpop") && !name.startswith("llvm.fshr") && !name.startswith("llvm.fshl")) 83 | { 84 | logger::warn("unknown pointer call instruction:"); 85 | value->dump(); 86 | return pointer_t::unknown; 87 | } 88 | } 89 | else if (!llvm::isa(value) 90 | && !llvm::isa(value) 91 | && !llvm::isa(value) 92 | && !llvm::isa(value) 93 | && !llvm::isa(value) 94 | && !llvm::isa(value) 95 | && !llvm::isa(value)) 96 | { 97 | logger::warn("unknown instruction:"); 98 | value->dump(); 99 | return pointer_t::unknown; 100 | } 101 | 102 | if (auto insn = llvm::dyn_cast(value)) 103 | { 104 | for (const auto& use : insn->operands()) 105 | { 106 | if (llvm::isa(use.get()) || llvm::isa(use.get())) 107 | worklist.push(use.get()); 108 | } 109 | } 110 | } 111 | 112 | if (bases.size() == 2) 113 | return pointer_t::memory_array; 114 | } 115 | } 116 | } 117 | return pointer_t::unknown; 118 | } 119 | 120 | 121 | 122 | bool SegmentsAAResult::invalidate(llvm::Function& f, const llvm::PreservedAnalyses& pa, llvm::FunctionAnalysisManager::Invalidator& inv) 123 | { 124 | return false; 125 | } 126 | 127 | /* 128 | Differentiate between: 129 | - memory array 130 | - memory slot 131 | - stack array 132 | - stack slot 133 | */ 134 | llvm::AliasResult SegmentsAAResult::alias(const llvm::MemoryLocation& loc_a, const llvm::MemoryLocation& loc_b, llvm::AAQueryInfo& info) 135 | { 136 | auto a_ty = get_pointer_type(loc_a.Ptr); 137 | auto b_ty = get_pointer_type(loc_b.Ptr); 138 | 139 | if (a_ty != pointer_t::unknown && b_ty != pointer_t::unknown && a_ty != b_ty) 140 | { 141 | return llvm::AliasResult::NoAlias; 142 | } 143 | return AAResultBase::alias(loc_a, loc_b, info); 144 | } 145 | 146 | llvm::AnalysisKey SegmentsAA::Key; 147 | 148 | SegmentsAAResult SegmentsAA::run(llvm::Function& f, llvm::FunctionAnalysisManager& fam) 149 | { 150 | return SegmentsAAResult(); 151 | } 152 | -------------------------------------------------------------------------------- /src/il/passes/alias.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | // Based on https://secret.club/2021/09/08/vmprotect-llvm-lifting-3.html#segmentsaa 7 | // 8 | struct SegmentsAAResult : public llvm::AAResultBase 9 | { 10 | bool invalidate(llvm::Function& f, const llvm::PreservedAnalyses& pa, llvm::FunctionAnalysisManager::Invalidator& inv); 11 | llvm::AliasResult alias(const llvm::MemoryLocation& loc_a, const llvm::MemoryLocation& loc_b, llvm::AAQueryInfo& info); 12 | 13 | private: 14 | friend llvm::AAResultBase; 15 | }; 16 | 17 | struct SegmentsAA final : public llvm::AnalysisInfoMixin 18 | { 19 | using Result = SegmentsAAResult; 20 | 21 | SegmentsAAResult run(llvm::Function& f, llvm::FunctionAnalysisManager& fam); 22 | 23 | static llvm::AnalysisKey Key; 24 | 25 | private: 26 | friend llvm::AnalysisInfoMixin; 27 | }; 28 | -------------------------------------------------------------------------------- /src/il/passes/coalescing.cpp: -------------------------------------------------------------------------------- 1 | #include "coalescing.hpp" 2 | #include "logger.hpp" 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | llvm::Type* get_integer_type(int size, llvm::LLVMContext& context) 14 | { 15 | switch (size) 16 | { 17 | case 1: 18 | return llvm::Type::getInt8Ty(context); 19 | case 2: 20 | return llvm::Type::getInt16Ty(context); 21 | case 4: 22 | return llvm::Type::getInt32Ty(context); 23 | case 8: 24 | return llvm::Type::getInt64Ty(context); 25 | default: 26 | logger::error("got unsupported size: {}", size); 27 | } 28 | } 29 | 30 | MemoryAccess::MemoryAccess(llvm::MemoryLocation location, const llvm::MemoryAccess* access, const llvm::SCEV* scev) 31 | : access_ { access } 32 | , scev_ { scev } 33 | , location_ { std::move(location) } 34 | , size_ { location_.Size.getValue() } 35 | , offset_ {} 36 | , supported_{} 37 | { 38 | auto add = llvm::dyn_cast_or_null(scev_); 39 | if (add && add->getNumOperands() == 3) 40 | { 41 | auto op0 = add->getOperand(0); 42 | auto op1 = add->getOperand(1); 43 | auto op2 = add->getOperand(2); 44 | // (-60 + %1 + @RAM). 45 | // 46 | if (op0->getSCEVType() == llvm::scConstant && op1->getSCEVType() == llvm::scUnknown && op2->getSCEVType() == llvm::scUnknown) 47 | { 48 | supported_ = true; 49 | offset_ = (int64_t)llvm::dyn_cast(op0)->getValue()->getValue().getLimitedValue(); 50 | } 51 | } 52 | } 53 | 54 | const llvm::MemoryAccess* MemoryAccess::access() const noexcept 55 | { 56 | return access_; 57 | } 58 | 59 | const llvm::SCEV* MemoryAccess::scalar() const noexcept 60 | { 61 | return scev_; 62 | } 63 | 64 | const llvm::MemoryLocation& MemoryAccess::location() const noexcept 65 | { 66 | return location_; 67 | } 68 | 69 | uint64_t MemoryAccess::size() const noexcept 70 | { 71 | return size_; 72 | } 73 | 74 | int64_t MemoryAccess::offset() const noexcept 75 | { 76 | return offset_; 77 | } 78 | 79 | bool MemoryAccess::supported() const noexcept 80 | { 81 | return supported_; 82 | } 83 | 84 | llvm::PreservedAnalyses MemoryCoalescingPass::run(llvm::Function& fn, llvm::FunctionAnalysisManager& am) 85 | { 86 | auto& aam = am.getResult(fn); 87 | auto& msaa = am.getResult(fn).getMSSA(); 88 | auto& se = am.getResult(fn); 89 | auto& dt = msaa.getDomTree(); 90 | auto pdt = llvm::PostDominatorTree(fn); 91 | 92 | bool modified = false; 93 | 94 | std::vector garbage; 95 | 96 | for (auto& block : fn) 97 | { 98 | // Skip if the block does not have memory accesses. 99 | // 100 | if (msaa.getBlockAccesses(&block) == nullptr) 101 | continue; 102 | // Find two sequential stores with the same size: 103 | // ; 2 = MemoryDef(1) 104 | // store i16 0, ptr %12, align 1, !noalias !38 105 | // %13 = trunc i64 %2 to i16 106 | // ; 3 = MemoryDef(2) 107 | // store i16 %13, ptr %10, align 1, !noalias !38 108 | // 109 | // And replace them with 1 store double the size. 110 | // 111 | std::vector accs; 112 | for (const auto& acc : *msaa.getBlockAccesses(&block)) 113 | { 114 | accs.push_back(&acc); 115 | } 116 | 117 | for (int i = 0; i < accs.size() - 1; i++) 118 | { 119 | auto def0 = llvm::dyn_cast_or_null(accs[i]); 120 | auto def1 = llvm::dyn_cast_or_null(accs[i + 1]); 121 | 122 | if (def0 && def1) 123 | { 124 | auto insn0 = llvm::dyn_cast_or_null(def0->getMemoryInst()); 125 | auto insn1 = llvm::dyn_cast_or_null(def1->getMemoryInst()); 126 | 127 | auto store0 = MemoryAccess(llvm::MemoryLocation::get(insn0), msaa.getMemoryAccess(insn0), se.getSCEV(insn0->getPointerOperand())); 128 | auto store1 = MemoryAccess(llvm::MemoryLocation::get(insn1), msaa.getMemoryAccess(insn1), se.getSCEV(insn1->getPointerOperand())); 129 | 130 | if (store0.supported() && store1.supported() && store0.size() == store1.size() && store0.size() < 8) 131 | { 132 | if (store1.offset() + store1.size() == store0.offset()) 133 | { 134 | logger::debug("Found two sequential stores {} {}:", store0.offset(), store1.offset()); 135 | insn0->dump(); 136 | insn1->dump(); 137 | llvm::IRBuilder<> ir(insn1->getNextNode()); 138 | auto op0_zext = ir.CreateZExt(insn0->getValueOperand(), ir.getIntNTy(store0.size() * 8 * 2)); 139 | auto op1_zext = ir.CreateZExt(insn1->getValueOperand(), ir.getIntNTy(store1.size() * 8 * 2)); 140 | auto op0_shl = ir.CreateShl(op0_zext, store0.size() * 8, "", true, true); 141 | auto fin_or = ir.CreateOr(op0_shl, op1_zext); 142 | auto fin_store = ir.CreateStore(fin_or, insn1->getPointerOperand()); 143 | garbage.push_back(insn0); 144 | garbage.push_back(insn1); 145 | 146 | i++; 147 | modified = true; 148 | } 149 | } 150 | } 151 | } 152 | } 153 | for (auto& store : garbage) 154 | { 155 | store->eraseFromParent(); 156 | } 157 | return modified ? llvm::PreservedAnalyses::none() : llvm::PreservedAnalyses::all(); 158 | } 159 | -------------------------------------------------------------------------------- /src/il/passes/coalescing.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | struct MemoryAccess 12 | { 13 | MemoryAccess(llvm::MemoryLocation location, const llvm::MemoryAccess* access, const llvm::SCEV* scev); 14 | 15 | const llvm::MemoryAccess* access() const noexcept; 16 | const llvm::SCEV* scalar() const noexcept; 17 | const llvm::MemoryLocation& location() const noexcept; 18 | uint64_t size() const noexcept; 19 | int64_t offset() const noexcept; 20 | bool supported() const noexcept; 21 | 22 | private: 23 | const llvm::MemoryAccess* access_; 24 | const llvm::SCEV* scev_; 25 | const llvm::MemoryLocation location_; 26 | // Size of the memory load. 27 | // 28 | uint64_t size_; 29 | // Offset within RAM. e.g. in case of SCEV (-60 + %1 + @RAM) offset will be -60. 30 | // 31 | int64_t offset_; 32 | // If load is supported by MemoryCoalescingPass. 33 | // 34 | bool supported_; 35 | }; 36 | 37 | struct MemoryCoalescingPass final : public llvm::PassInfoMixin 38 | { 39 | llvm::PreservedAnalyses run(llvm::Function &fn, llvm::FunctionAnalysisManager &am); 40 | }; 41 | -------------------------------------------------------------------------------- /src/il/passes/deps.cpp: -------------------------------------------------------------------------------- 1 | #include "deps.hpp" 2 | #include "logger.hpp" 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | llvm::PreservedAnalyses MemoryDependenciesPass::run(llvm::Function& fn, llvm::FunctionAnalysisManager& am) 10 | { 11 | auto& mda = am.getResult(fn); 12 | for (auto& bb : fn) 13 | { 14 | for (auto& insn : bb) 15 | { 16 | if (auto store = llvm::dyn_cast(&insn)) 17 | { 18 | auto dep = mda.getDependency(store); 19 | auto insn = dep.getInst(); 20 | if (insn) 21 | { 22 | logger::debug("memory dependence:"); 23 | store->dump(); 24 | insn->dump(); 25 | } 26 | else 27 | { 28 | logger::debug("no memory dependence:"); 29 | store->dump(); 30 | } 31 | } 32 | } 33 | } 34 | return llvm::PreservedAnalyses::all(); 35 | } 36 | -------------------------------------------------------------------------------- /src/il/passes/deps.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct MemoryDependenciesPass final : public llvm::PassInfoMixin 6 | { 7 | llvm::PreservedAnalyses run(llvm::Function& fn, llvm::FunctionAnalysisManager& fam); 8 | }; 9 | -------------------------------------------------------------------------------- /src/il/passes/flags_synthesis.cpp: -------------------------------------------------------------------------------- 1 | #include "flags_synthesis.hpp" 2 | #include "il/solver.hpp" 3 | #include "logger.hpp" 4 | #include "utils.hpp" 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | #include 15 | #include 16 | #include 17 | 18 | struct InstructionSlice 19 | { 20 | static InstructionSlice get(llvm::Instruction* value) 21 | { 22 | InstructionSlice slice; 23 | 24 | std::stack worklist{ { value } }; 25 | std::set known; 26 | 27 | while (!worklist.empty()) 28 | { 29 | auto insn = worklist.top(); worklist.pop(); 30 | // Skip if known. 31 | // 32 | if (known.contains(insn)) 33 | continue; 34 | // Save. 35 | // 36 | known.insert(insn); 37 | slice.stream.push_back(insn); 38 | // Terminate if load/phi. 39 | // 40 | if (llvm::isa(insn) || llvm::isa(insn)) 41 | { 42 | slice.operands.push_back(insn); 43 | continue; 44 | } 45 | // Iterate use chain. 46 | // 47 | for (const auto& op : insn->operands()) 48 | { 49 | if (auto op_insn = llvm::dyn_cast(op.get())) 50 | { 51 | worklist.push(op_insn); 52 | } 53 | } 54 | } 55 | // Sort instructions by dominance. 56 | // 57 | llvm::DominatorTree dt(*value->getFunction()); 58 | 59 | std::sort(slice.stream.begin(), slice.stream.end(), [&dt](const auto& a, const auto& b) 60 | { 61 | return dt.dominates(a, b); 62 | }); 63 | return slice; 64 | } 65 | 66 | std::vector stream; 67 | std::vector operands; 68 | }; 69 | 70 | #define GET_OR_CREATE_FUNCTION(name) \ 71 | if (auto fn = module->getFunction(name)) \ 72 | return fn; \ 73 | auto fn = llvm::Function::Create( \ 74 | llvm::FunctionType::get(i1, { ptr, ptr }, false), \ 75 | llvm::GlobalVariable::LinkageTypes::InternalLinkage, \ 76 | name, \ 77 | module.get() \ 78 | ); \ 79 | auto bb = llvm::BasicBlock::Create(*context, "body", fn); \ 80 | llvm::IRBuilder<> ir(bb); \ 81 | auto op0 = ir.CreateLoad(i64, fn->getOperand(0)); \ 82 | auto op1 = ir.CreateLoad(i64, fn->getOperand(1)); \ 83 | 84 | 85 | 86 | llvm::Function* FlagsSynthesisPass::get_or_create_jo() 87 | { 88 | GET_OR_CREATE_FUNCTION("jo"); 89 | ir.CreateRet( 90 | ir.CreateExtractValue( 91 | ir.CreateCall( 92 | llvm::Intrinsic::getDeclaration(module.get(), llvm::Intrinsic::sadd_with_overflow, i64), 93 | { op1, ir.CreateXor(op0, -1) } 94 | ), 95 | { 1 } 96 | ) 97 | ); 98 | return fn; 99 | } 100 | 101 | llvm::Function* FlagsSynthesisPass::get_or_create_js() 102 | { 103 | GET_OR_CREATE_FUNCTION("js"); 104 | ir.CreateRet( 105 | ir.CreateICmpSGT( 106 | ir.CreateSub( 107 | op0, op1 108 | ), 109 | llvm::ConstantInt::get(i64, -1) 110 | ) 111 | ); 112 | return fn; 113 | } 114 | 115 | llvm::Function* FlagsSynthesisPass::get_or_create_jns() 116 | { 117 | GET_OR_CREATE_FUNCTION("jns"); 118 | ir.CreateRet( 119 | ir.CreateICmpSLT( 120 | ir.CreateSub( 121 | op0, op1 122 | ), 123 | llvm::ConstantInt::get(i64, 0) 124 | ) 125 | ); 126 | return fn; 127 | } 128 | 129 | llvm::Function* FlagsSynthesisPass::get_or_create_je() 130 | { 131 | GET_OR_CREATE_FUNCTION("je"); 132 | ir.CreateRet( 133 | ir.CreateICmpEQ( 134 | op0, op1 135 | ) 136 | ); 137 | return fn; 138 | } 139 | 140 | llvm::Function* FlagsSynthesisPass::get_or_create_jne() 141 | { 142 | GET_OR_CREATE_FUNCTION("jne"); 143 | ir.CreateRet( 144 | ir.CreateICmpEQ( 145 | op1, op0 146 | ) 147 | ); 148 | return fn; 149 | } 150 | 151 | llvm::Function* FlagsSynthesisPass::get_or_create_jb() 152 | { 153 | GET_OR_CREATE_FUNCTION("jb"); 154 | ir.CreateRet( 155 | ir.CreateICmpUGT( 156 | op1, op0 157 | ) 158 | ); 159 | return fn; 160 | } 161 | 162 | llvm::Function* FlagsSynthesisPass::get_or_create_ja() 163 | { 164 | GET_OR_CREATE_FUNCTION("ja"); 165 | ir.CreateRet( 166 | ir.CreateICmpULT(op1, op0) 167 | ); 168 | return fn; 169 | } 170 | 171 | llvm::Function* FlagsSynthesisPass::get_or_create_jl() 172 | { 173 | GET_OR_CREATE_FUNCTION("jl"); 174 | ir.CreateRet( 175 | ir.CreateICmpSGT( 176 | op1, op0 177 | ) 178 | ); 179 | return fn; 180 | } 181 | 182 | llvm::Function* FlagsSynthesisPass::get_or_create_jge() 183 | { 184 | GET_OR_CREATE_FUNCTION("jge"); 185 | ir.CreateRet( 186 | ir.CreateICmpSLT( 187 | op0, op1 188 | ) 189 | ); 190 | return fn; 191 | } 192 | 193 | llvm::Function* FlagsSynthesisPass::get_or_create_jle() 194 | { 195 | GET_OR_CREATE_FUNCTION("jle"); 196 | ir.CreateRet( 197 | ir.CreateICmpSLT( 198 | op1, op0 199 | ) 200 | ); 201 | return fn; 202 | } 203 | 204 | llvm::Function* FlagsSynthesisPass::get_or_create_jg() 205 | { 206 | GET_OR_CREATE_FUNCTION("jg"); 207 | ir.CreateRet( 208 | ir.CreateICmpSGT( 209 | op0, op1 210 | ) 211 | ); 212 | return fn; 213 | } 214 | 215 | // %1 = load i64, i64* %rax, align 8 216 | // %2 = load i64, i64* %rbx, align 8 217 | // %5 = xor i64 %1, -1 218 | // %6 = add i64 %2, %5 219 | // %7 = trunc i64 %6 to i32 220 | // %8 = and i32 %7, 255 221 | // %9 = call i32 @llvm.ctpop.i32(i32 %8) #15 222 | // %10 = and i32 %9, 1 223 | // %11 = icmp eq i32 %10, 0 224 | // 225 | llvm::Function* FlagsSynthesisPass::get_or_create_jp() 226 | { 227 | GET_OR_CREATE_FUNCTION("jp"); 228 | ir.CreateRet( 229 | ir.CreateICmpEQ( 230 | ir.CreateAnd( 231 | ir.CreateCall( 232 | llvm::Intrinsic::getDeclaration(module.get(), llvm::Intrinsic::ctpop, i32), 233 | { 234 | ir.CreateAnd( 235 | ir.CreateTrunc( 236 | ir.CreateAdd( 237 | op1, 238 | ir.CreateXor( 239 | op0, llvm::ConstantInt::get(i64, -1) 240 | ) 241 | ), 242 | i32 243 | ), 244 | llvm::ConstantInt::get(i32, 255) 245 | ) 246 | } 247 | ), 248 | llvm::ConstantInt::get(i32, 1) 249 | ), 250 | llvm::ConstantInt::get(i32, 0) 251 | ) 252 | ); 253 | return fn; 254 | } 255 | 256 | FlagsSynthesisPass::FlagsSynthesisPass() 257 | : context{ new llvm::LLVMContext } 258 | , module { new llvm::Module("flags", *context) } 259 | , i1 { llvm::IntegerType::getInt1Ty (*context) } 260 | , i32{ llvm::IntegerType::getInt32Ty(*context) } 261 | , i64{ llvm::IntegerType::getInt64Ty(*context) } 262 | , ptr{ llvm::PointerType::get(*context, 0) } 263 | { 264 | } 265 | 266 | llvm::PreservedAnalyses FlagsSynthesisPass::run(llvm::Function& fn, llvm::FunctionAnalysisManager& am) 267 | { 268 | auto module = fn.getParent(); 269 | auto layout = module->getDataLayout(); 270 | 271 | for (auto& insn : llvm::instructions(fn)) 272 | { 273 | if (auto br = llvm::dyn_cast(&insn)) 274 | { 275 | if (br->isConditional()) 276 | { 277 | if (auto condition = llvm::dyn_cast(br->getOperand(0))) 278 | { 279 | auto slice = InstructionSlice::get(condition); 280 | 281 | // logger::info("slicing.."); 282 | // for (const auto& insn : slice.stream) 283 | // { 284 | // insn->dump(); 285 | // } 286 | } 287 | } 288 | } 289 | } 290 | return llvm::PreservedAnalyses::none(); 291 | } 292 | -------------------------------------------------------------------------------- /src/il/passes/flags_synthesis.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | // Based on https://godbolt.org/z/aYdc3xhn9. 6 | // 7 | struct FlagsSynthesisPass final : public llvm::PassInfoMixin 8 | { 9 | FlagsSynthesisPass(); 10 | llvm::PreservedAnalyses run(llvm::Function& fn, llvm::FunctionAnalysisManager& fam); 11 | 12 | private: 13 | llvm::Function* get_or_create_jo(); 14 | /* llvm::Function* get_or_create_jno(); */ 15 | llvm::Function* get_or_create_js(); 16 | llvm::Function* get_or_create_jns(); 17 | llvm::Function* get_or_create_je(); 18 | llvm::Function* get_or_create_jne(); 19 | llvm::Function* get_or_create_jb(); 20 | /* llvm::Function* get_or_create_jnb(); */ 21 | /* llvm::Function* get_or_create_jna(); */ 22 | llvm::Function* get_or_create_ja(); 23 | llvm::Function* get_or_create_jl(); 24 | llvm::Function* get_or_create_jge(); 25 | llvm::Function* get_or_create_jle(); 26 | llvm::Function* get_or_create_jg(); 27 | llvm::Function* get_or_create_jp(); 28 | /* llvm::Function* get_or_create_jnp(); */ 29 | 30 | std::unique_ptr context; 31 | std::unique_ptr module; 32 | 33 | llvm::Type* i1; 34 | llvm::Type* i32; 35 | llvm::Type* i64; 36 | llvm::Type* ptr; 37 | }; 38 | -------------------------------------------------------------------------------- /src/il/solver.cpp: -------------------------------------------------------------------------------- 1 | #include "asserts.hpp" 2 | #include "solver.hpp" 3 | #include "logger.hpp" 4 | #include "utils.hpp" 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | #include 14 | 15 | llvm::cl::opt save_branch_ast("solver-save-ast", 16 | llvm::cl::desc("Save branch ast into dot file on every branch."), 17 | llvm::cl::value_desc("flag"), 18 | llvm::cl::init(false), 19 | llvm::cl::Optional); 20 | 21 | llvm::cl::opt print_branch_ast("solver-print-ast", 22 | llvm::cl::desc("Print branch ast on every branch."), 23 | llvm::cl::value_desc("flag"), 24 | llvm::cl::init(false), 25 | llvm::cl::Optional); 26 | 27 | namespace il 28 | { 29 | std::vector get_possible_targets(llvm::Value* ret) 30 | { 31 | if (ret == nullptr) 32 | { 33 | logger::error("get_possible_targets argument got null argument."); 34 | } 35 | 36 | std::vector targets; 37 | // Does not matter which arch we use. 38 | // 39 | triton::Context api(triton::arch::ARCH_X86_64); 40 | api.setAstRepresentationMode(triton::ast::representations::SMT_REPRESENTATION); 41 | 42 | if (auto inst = llvm::dyn_cast(ret)) 43 | { 44 | if (inst->getOpcode() == llvm::Instruction::Or) 45 | { 46 | logger::warn("replacing or with add."); 47 | llvm::IRBuilder<> ir(inst); 48 | ret = ir.CreateAdd(inst->getOperand(0), inst->getOperand(1)); 49 | inst->replaceAllUsesWith(ret); 50 | } 51 | } 52 | triton::ast::LLVMToTriton lifter(api); 53 | // Lift llvm into triton ast. 54 | // 55 | auto node = lifter.convert(ret); 56 | 57 | if (save_branch_ast) 58 | { 59 | static int solver_temp_names; 60 | std::fstream fd(fmt::format("branch-ast-{}.dot", solver_temp_names++), std::ios_base::out); 61 | if (fd.good()) 62 | api.liftToDot(fd, node); 63 | } 64 | if (print_branch_ast) 65 | { 66 | logger::info("branch ast: {}", triton::ast::unroll(node)); 67 | } 68 | // If constant. 69 | // 70 | if (!node->isSymbolized()) 71 | { 72 | return { static_cast(node->evaluate()) }; 73 | } 74 | auto ast = api.getAstContext(); 75 | auto zero = ast->bv(0, node->getBitvectorSize()); 76 | auto constraints = ast->distinct(node, zero); 77 | 78 | while (true) 79 | { 80 | // Failsafe. 81 | // 82 | if (targets.size() > 2) 83 | return {}; 84 | 85 | auto model = api.getModel(constraints); 86 | 87 | if (model.empty()) 88 | break; 89 | for (auto& [id, sym] : model) 90 | api.setConcreteVariableValue(api.getSymbolicVariable(id), sym.getValue()); 91 | 92 | auto target = static_cast(node->evaluate()); 93 | targets.push_back(target); 94 | // Update constraints. 95 | // 96 | constraints = ast->land(constraints, ast->distinct(node, ast->bv(target, node->getBitvectorSize()))); 97 | } 98 | return targets; 99 | } 100 | }; 101 | -------------------------------------------------------------------------------- /src/il/solver.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace llvm 14 | { 15 | class Value; 16 | }; 17 | 18 | namespace il 19 | { 20 | std::vector get_possible_targets(llvm::Value* ret); 21 | }; 22 | -------------------------------------------------------------------------------- /src/lifter.cpp: -------------------------------------------------------------------------------- 1 | #include "lifter.hpp" 2 | #include "logger.hpp" 3 | #include "utils.hpp" 4 | 5 | #include "vm/instruction.hpp" 6 | #include "vm/routine.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | llvm::cl::opt intrinsics("i", 16 | llvm::cl::desc("Path to vmprotect intrinsics file"), 17 | llvm::cl::value_desc("intrinsics"), 18 | llvm::cl::Required); 19 | 20 | llvm::Value* ReturnArguments::return_address() const noexcept 21 | { 22 | return ret; 23 | } 24 | 25 | llvm::Value* ReturnArguments::program_counter() const noexcept 26 | { 27 | return rip; 28 | } 29 | 30 | Lifter::Lifter() : ir(context) 31 | { 32 | llvm::SMDiagnostic err; 33 | auto parsed = llvm::parseIRFile(intrinsics, err, context); 34 | if (parsed == nullptr) 35 | { 36 | logger::error("Lifter::Lifter: Failed to parse intrinsics file"); 37 | } 38 | module = std::move(parsed); 39 | // Extract helper functions. 40 | // 41 | helper_lifted_fn = module->getFunction("VirtualFunction"); 42 | helper_empty_block_fn = module->getFunction("VirtualStubEmpty"); 43 | helper_block_fn = module->getFunction("VirtualStub"); 44 | helper_keep_fn = module->getFunction("KeepReturn"); 45 | helper_slice_fn = module->getFunction("SlicePC"); 46 | helper_undef = module->getGlobalVariable("__undef"); 47 | if (helper_lifted_fn == nullptr) 48 | logger::error("Failed to find VirtualFunction function"); 49 | if (helper_empty_block_fn == nullptr) 50 | logger::error("Failed to find VirtualStubEmpty function"); 51 | if (helper_block_fn == nullptr) 52 | logger::error("Failed to find VirtualStub function"); 53 | if (helper_keep_fn == nullptr) 54 | logger::error("Failed to find KeepPC function"); 55 | if (helper_slice_fn == nullptr) 56 | logger::error("Failed to find SlicePC function"); 57 | if (helper_undef == nullptr) 58 | logger::error("Failed to find global undef variable"); 59 | // Collect semantics functions. 60 | // 61 | for (const auto& glob : module->globals()) 62 | { 63 | const auto& name = glob.getName(); 64 | if (name.startswith("SEM_") && glob.isConstant() && glob.getType()->isPointerTy()) 65 | { 66 | const auto initializer = glob.getInitializer(); 67 | auto resolved_fn = module->getFunction(initializer->getName()); 68 | if (resolved_fn == nullptr) 69 | { 70 | logger::error("Lifter::Lifter: Failed to resolve function for global {}", name.str()); 71 | } 72 | sems.emplace(name.str().substr(4), resolved_fn); 73 | } 74 | } 75 | } 76 | 77 | llvm::Function* Lifter::lift_basic_block(vm::BasicBlock* vblock) 78 | { 79 | // Copy empty block function. 80 | // 81 | function = clone(helper_empty_block_fn); 82 | // Remove bad attributes. Don't know if it does anything but still. 83 | // 84 | for (auto& arg : function->args()) 85 | { 86 | arg.removeAttr(llvm::Attribute::ReadNone); 87 | arg.removeAttr(llvm::Attribute::ReadOnly); 88 | } 89 | function->removeFnAttr(llvm::Attribute::ReadNone); 90 | // Remove entry basic block and insert new one. 91 | // 92 | function->getEntryBlock().eraseFromParent(); 93 | 94 | ir.SetInsertPoint(llvm::BasicBlock::Create(context, "lifted_bb", function)); 95 | // Lift instruction stream. 96 | // 97 | for (const auto& insn : *vblock) 98 | { 99 | std::visit(*this, insn); 100 | } 101 | // Return VIP. 102 | // 103 | ir.CreateRet(ir.CreateLoad(function->getReturnType(), vip())); 104 | return function; 105 | } 106 | 107 | llvm::Function* Lifter::build_function(const vm::Routine* rtn, uint64_t target_block) 108 | { 109 | function = clone(helper_empty_block_fn); 110 | function->getEntryBlock().eraseFromParent(); 111 | auto block = llvm::BasicBlock::Create(context, "entry", function); 112 | 113 | std::vector args; 114 | for (auto& arg : function->args()) 115 | args.push_back(&arg); 116 | // Create empty basic blocks for each basic block in the routine. 117 | // 118 | std::map blocks; 119 | for (const auto [vip, bb] : *rtn) 120 | { 121 | blocks.emplace(vip, llvm::BasicBlock::Create(context, fmt::format("bb_0x{:x}", vip), function)); 122 | } 123 | // Link together llvm basic blocks based on edges in routine and populate with calls to lifted functions. 124 | // 125 | for (auto [vip, bb] : blocks) 126 | { 127 | ir.SetInsertPoint(bb); 128 | auto vblock = rtn->blocks.at(vip); 129 | if (vblock->lifted != nullptr) 130 | { 131 | auto pc = ir.CreateCall(vblock->lifted, args); 132 | // Check if we are building partial function and if so, make a call to KeepPC. 133 | // 134 | if (vblock->vip() == target_block && target_block != vm::invalid_vip) 135 | { 136 | // Load rsp value. 137 | // 138 | auto ret = create_memory_read_64(ir.CreateLoad(ir.getInt64Ty(), vsp())); 139 | 140 | pc = ir.CreateCall(helper_keep_fn, { pc, ret }); 141 | } 142 | // Link successors with the current block. 143 | // 144 | switch (vblock->next.size()) 145 | { 146 | case 0: 147 | { 148 | ir.CreateRet(pc); 149 | break; 150 | } 151 | case 1: 152 | { 153 | auto dst_vip = vblock->next.at(0)->vip(); 154 | auto dst_blk = blocks.at(dst_vip); 155 | if (vblock->vip() == target_block && target_block != vm::invalid_vip) 156 | { 157 | // Create dummy basic block. 158 | // 159 | auto dummy_bb = llvm::BasicBlock::Create(context, fmt::format("bb_dummy_0x{:x}", vblock->vip()), function); 160 | llvm::ReturnInst::Create(context, pc, dummy_bb); 161 | auto cmp = ir.CreateICmpEQ(pc, ir.getInt64(dst_vip)); 162 | ir.CreateCondBr(cmp, dst_blk, dummy_bb); 163 | } 164 | else 165 | { 166 | ir.CreateBr(dst_blk); 167 | } 168 | break; 169 | } 170 | case 2: 171 | { 172 | auto dst_blk_1 = blocks.at(vblock->next.at(0)->vip()); 173 | auto dst_blk_2 = blocks.at(vblock->next.at(1)->vip()); 174 | auto cmp = ir.CreateICmpEQ(pc, ir.getInt64(vblock->next.at(0)->vip())); 175 | ir.CreateCondBr(cmp, dst_blk_1, dst_blk_2); 176 | break; 177 | } 178 | default: 179 | llvm_unreachable("Switch statement is currently not supported."); 180 | } 181 | } 182 | else 183 | { 184 | ir.CreateRet(ir.getInt64(0xdeadbeef)); 185 | } 186 | } 187 | llvm::BranchInst::Create(blocks.at(rtn->entry->vip()), block); 188 | // If its a partial function, we want to slice PC, otherwise build "final function". 189 | // 190 | if (target_block != vm::invalid_vip) 191 | return make_slice(function); 192 | return make_final(function, rtn->entry->vip()); 193 | } 194 | 195 | ReturnArguments Lifter::get_return_args(llvm::Function* fn) const 196 | { 197 | for (auto& block : *fn) 198 | { 199 | for (auto& ins : block) 200 | { 201 | if (auto call = llvm::dyn_cast(&ins)) 202 | { 203 | if (call->getCalledFunction() == helper_keep_fn) 204 | { 205 | return ReturnArguments(call->getOperand(0), call->getOperand(1)); 206 | } 207 | } 208 | } 209 | } 210 | logger::error("Failed to find call to KeepReturnAddress funtion in {}", fn->getName().str()); 211 | } 212 | 213 | llvm::Argument* Lifter::arg(llvm::Function* fn, const std::string& name) 214 | { 215 | for (auto& arg : fn->args()) 216 | if (arg.getName().equals(name)) 217 | return &arg; 218 | return nullptr; 219 | } 220 | 221 | llvm::Argument* Lifter::arg(const std::string& name) 222 | { 223 | return arg(function, name); 224 | } 225 | 226 | llvm::Function* Lifter::clone(llvm::Function* fn) 227 | { 228 | llvm::ValueToValueMapTy map; 229 | return llvm::CloneFunction(fn, map); 230 | } 231 | 232 | llvm::Function* Lifter::sem(const std::string& name) const 233 | { 234 | if (sems.find(name) == sems.end()) 235 | logger::error("Failed to find {} semantic", name); 236 | return sems.at(name); 237 | } 238 | 239 | llvm::Argument* Lifter::vip() 240 | { 241 | return arg("vip"); 242 | } 243 | 244 | llvm::Argument* Lifter::vsp() 245 | { 246 | return arg("vsp"); 247 | } 248 | 249 | llvm::Argument* Lifter::vregs() 250 | { 251 | return arg("vmregs"); 252 | } 253 | 254 | llvm::Function* Lifter::make_slice(llvm::Function* fn) 255 | { 256 | auto slice = clone(helper_slice_fn); 257 | for (auto& ins : slice->getEntryBlock()) 258 | { 259 | if (auto call = llvm::dyn_cast(&ins)) 260 | { 261 | if (call->getCalledFunction() == helper_block_fn) 262 | { 263 | call->setCalledFunction(fn); 264 | break; 265 | } 266 | } 267 | } 268 | return slice; 269 | } 270 | 271 | llvm::Function* Lifter::make_final(llvm::Function* fn, uint64_t addr) 272 | { 273 | auto* final = clone(helper_lifted_fn); 274 | for (auto& ins : final->getEntryBlock()) 275 | { 276 | if (auto* call = llvm::dyn_cast(&ins)) 277 | { 278 | if (call->getCalledFunction() == helper_block_fn) 279 | { 280 | call->setCalledFunction(fn); 281 | break; 282 | } 283 | } 284 | } 285 | return final; 286 | } 287 | 288 | void Lifter::operator()(const vm::Add& insn) 289 | { 290 | ir.CreateCall(sem(fmt::format("ADD_{}", insn.size())), { vsp() }); 291 | } 292 | 293 | void Lifter::operator()(const vm::Shl& insn) 294 | { 295 | ir.CreateCall(sem(fmt::format("SHL_{}", insn.size())), { vsp() }); 296 | } 297 | 298 | void Lifter::operator()(const vm::Shr& insn) 299 | { 300 | ir.CreateCall(sem(fmt::format("SHR_{}", insn.size())), { vsp() }); 301 | } 302 | 303 | void Lifter::operator()(const vm::Ldr& insn) 304 | { 305 | ir.CreateCall(sem(fmt::format("LOAD_{}", insn.size())), { vsp() }); 306 | } 307 | 308 | void Lifter::operator()(const vm::Str& insn) 309 | { 310 | ir.CreateCall(sem(fmt::format("STORE_{}", insn.size())), { vsp() }); 311 | } 312 | 313 | void Lifter::operator()(const vm::Nor& insn) 314 | { 315 | ir.CreateCall(sem(fmt::format("NOR_{}", insn.size())), { vsp() }); 316 | } 317 | 318 | void Lifter::operator()(const vm::Nand& insn) 319 | { 320 | ir.CreateCall(sem(fmt::format("NAND_{}", insn.size())), { vsp() }); 321 | } 322 | 323 | void Lifter::operator()(const vm::Shrd& insn) 324 | { 325 | ir.CreateCall(sem(fmt::format("SHRD_{}", insn.size())), { vsp() }); 326 | } 327 | 328 | void Lifter::operator()(const vm::Shld& insn) 329 | { 330 | ir.CreateCall(sem(fmt::format("SHLD_{}", insn.size())), { vsp() }); 331 | } 332 | 333 | void Lifter::operator()(const vm::Push& insn) 334 | { 335 | auto size = insn.size(); 336 | 337 | if (insn.op().is_immediate()) 338 | { 339 | ir.CreateCall(sem(fmt::format("PUSH_IMM_{}", size)), { vsp(), ir.getInt(llvm::APInt(size, insn.op().imm().value())) }); 340 | } 341 | else if (insn.op().is_physical()) 342 | { 343 | auto reg = arg(insn.op().phy().name()); 344 | auto ldr = ir.CreateLoad(ir.getInt64Ty(), reg); 345 | ir.CreateCall(sem(fmt::format("PUSH_REG_{}", size)), { vsp(), ldr }); 346 | } 347 | else if (insn.op().is_virtual()) 348 | { 349 | auto num = insn.op().vrt().number(); 350 | auto off = insn.op().vrt().offset(); 351 | auto gep = ir.CreateInBoundsGEP(vregs()->getType(), vregs(), { ir.getInt(llvm::APInt(size, num)) }); 352 | auto ldr = ir.CreateLoad(ir.getInt64Ty(), gep); 353 | ir.CreateCall(sem(fmt::format("PUSH_VREG_{}_{}", size, off)), { vsp(), ldr }); 354 | } 355 | else if (insn.op().is_vsp()) 356 | { 357 | ir.CreateCall(sem(fmt::format("PUSH_VSP_{}", size)), { vsp() }); 358 | } 359 | else 360 | { 361 | logger::error("Lifter::operator(): Unsupported Push operand."); 362 | } 363 | } 364 | 365 | void Lifter::operator()(const vm::Pop& insn) 366 | { 367 | auto size = insn.size(); 368 | 369 | if (insn.op().is_physical()) 370 | { 371 | ir.CreateCall(sem(fmt::format("POP_REG_{}", size)), { vsp(), arg(insn.op().phy().name()) }); 372 | } 373 | else if (insn.op().is_virtual()) 374 | { 375 | auto num = insn.op().vrt().number(); 376 | auto off = insn.op().vrt().offset(); 377 | auto gep = ir.CreateInBoundsGEP(vregs()->getType(), vregs(), { ir.getInt(llvm::APInt(size, num)) }); 378 | ir.CreateCall(sem(fmt::format("POP_VREG_{}_{}", size, off)), { vsp(), gep }); 379 | } 380 | else if (insn.op().is_vsp()) 381 | { 382 | ir.CreateCall(sem(fmt::format("POP_VSP_{}", size)), { vsp() }); 383 | } 384 | else 385 | { 386 | logger::error("Lifter::operator(): Unsupported Pop operand."); 387 | } 388 | } 389 | 390 | void Lifter::operator()(const vm::Jmp& insn) 391 | { 392 | ir.CreateCall(sem("JMP"), { vsp(), vip() }); 393 | } 394 | 395 | void Lifter::operator()(const vm::Ret& insn) 396 | { 397 | ir.CreateCall(sem("RET"), { vsp(), vip() }); 398 | } 399 | 400 | void Lifter::operator()(const vm::Jcc& insn) 401 | { 402 | if (insn.direction() == vm::jcc_e::up) 403 | { 404 | ir.CreateCall(sem("JCC_INC"), { vsp(), vip() }); 405 | } 406 | else 407 | { 408 | ir.CreateCall(sem("JCC_DEC"), { vsp(), vip() }); 409 | } 410 | } 411 | 412 | void Lifter::operator()(const vm::Exit& insn) 413 | { 414 | for (const auto& reg : insn.regs()) 415 | { 416 | (*this)(reg); 417 | } 418 | } 419 | 420 | void Lifter::operator()(const vm::Enter& insn) 421 | { 422 | for (const auto& reg : insn.regs()) 423 | { 424 | (*this)(reg); 425 | } 426 | } 427 | 428 | llvm::Value* Lifter::create_memory_read_64(llvm::Value* address) 429 | { 430 | auto ram = module->getGlobalVariable("RAM"); 431 | auto gep = ir.CreateInBoundsGEP(ram->getValueType(), ram, { ir.getInt64(0), address }); 432 | return ir.CreateLoad(ir.getInt64Ty(), gep); 433 | } 434 | 435 | llvm::Value* Lifter::create_memory_write_64(llvm::Value* address, llvm::Value* ptr) 436 | { 437 | auto ram = module->getGlobalVariable("RAM"); 438 | auto gep = ir.CreateInBoundsGEP(ram->getValueType(), ram, { ir.getInt64(0), address }); 439 | return ir.CreateStore(ir.CreateLoad(ir.getInt64Ty(), ptr), gep); 440 | } 441 | 442 | std::vector Lifter::get_exit_blocks(llvm::Function* fn) const 443 | { 444 | std::vector exits; 445 | 446 | for (auto& bb : *fn) 447 | { 448 | if (auto ret = llvm::dyn_cast(bb.getTerminator())) 449 | { 450 | exits.push_back(&bb); 451 | } 452 | } 453 | return exits; 454 | } 455 | 456 | // NOTE: This is just for testing. 457 | // 458 | void Lifter::create_external_call(llvm::Function* fn, const std::string& name) 459 | { 460 | function = fn; 461 | auto exits = get_exit_blocks(fn); 462 | if (exits.size() != 1) 463 | logger::error("Invalid number ({}) of exit blocks in a function {}", exits.size(), fn->getName().str()); 464 | auto term = exits.back(); 465 | // Insert call right before the ret instruction. 466 | // 467 | ir.SetInsertPoint(term->getTerminator()->getPrevNode()); 468 | 469 | auto callee_ty = llvm::FunctionType::get(ir.getInt64Ty(), { ir.getInt64Ty(), /* ir.getInt64Ty(), ir.getInt64Ty(), ir.getInt64Ty() */ }, false); 470 | auto callee_fn = llvm::Function::Create(callee_ty, llvm::GlobalVariable::LinkageTypes::ExternalLinkage, name, module.get()); 471 | // Mark this function as `ReadNone` meaning it does not read memory. This attribute allows for some optimizations to be applied. 472 | // ref: http://formalverification.cs.utah.edu/llvm_doxy/2.9/namespacellvm_1_1Attribute.html 473 | // 474 | callee_fn->addFnAttr(llvm::Attribute::ReadNone); 475 | // Pop function call address from the stack and mark the slot with __undef. 476 | // This will remove useless store in the final function. 477 | // 478 | ir.CreateCall(sem("STACK_POP_64"), { vsp() }); 479 | 480 | auto call = ir.CreateCall(callee_fn, { 481 | ir.CreateLoad(ir.getInt64Ty(), arg("rcx")), 482 | // ir.CreateLoad(ir.getInt64Ty(), arg("rdx")), 483 | // ir.CreateLoad(ir.getInt64Ty(), arg("r8")), 484 | // ir.CreateLoad(ir.getInt64Ty(), arg("r9")) 485 | }); 486 | ir.CreateStore(call, arg("rax")); 487 | } 488 | -------------------------------------------------------------------------------- /src/lifter.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "vm/routine.hpp" 3 | #include "logger.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | struct ReturnArguments 11 | { 12 | explicit ReturnArguments(llvm::Value* rip, llvm::Value* ret) : rip{ rip }, ret{ ret } {} 13 | 14 | llvm::Value* return_address() const noexcept; 15 | llvm::Value* program_counter() const noexcept; 16 | 17 | private: 18 | llvm::Value* rip; 19 | llvm::Value* ret; 20 | }; 21 | 22 | // IR to LLVM Lifter class. 23 | // 24 | struct Lifter 25 | { 26 | Lifter(); 27 | 28 | // Lift `basic_block` into llvm function. 29 | // All basic_blocks represented as llvm::Function's. 30 | // 31 | llvm::Function* lift_basic_block(vm::BasicBlock* block); 32 | 33 | // Build paritual or full control flow graph of a routine. 34 | // 35 | llvm::Function* build_function(const vm::Routine* routine, uint64_t target_block = vm::invalid_vip); 36 | 37 | // Get program counter and [rsp] value from KeepReturn function. 38 | // 39 | ReturnArguments get_return_args(llvm::Function* function) const; 40 | 41 | void create_external_call(llvm::Function* function, const std::string& name); 42 | 43 | void operator()(const vm::Add&); 44 | void operator()(const vm::Shl&); 45 | void operator()(const vm::Shr&); 46 | void operator()(const vm::Ldr&); 47 | void operator()(const vm::Str&); 48 | void operator()(const vm::Nor&); 49 | void operator()(const vm::Nand&); 50 | void operator()(const vm::Shrd&); 51 | void operator()(const vm::Shld&); 52 | void operator()(const vm::Push&); 53 | void operator()(const vm::Pop&); 54 | void operator()(const vm::Jmp&); 55 | void operator()(const vm::Ret&); 56 | void operator()(const vm::Jcc&); 57 | void operator()(const vm::Exit&); 58 | void operator()(const vm::Enter&); 59 | 60 | private: 61 | llvm::Value* create_memory_read_64(llvm::Value* address); 62 | llvm::Value* create_memory_write_64(llvm::Value* address, llvm::Value* ptr); 63 | 64 | std::vector get_exit_blocks(llvm::Function* function) const; 65 | 66 | // Get virtual instruction pointer from function arguments. 67 | // 68 | llvm::Argument* vip(); 69 | 70 | // Get virtual stack pointer from function arguments. 71 | // 72 | llvm::Argument* vsp(); 73 | 74 | // Get virtual registers array from function arguments. 75 | // 76 | llvm::Argument* vregs(); 77 | 78 | // Get llvm function for vmp instruction. 79 | // 80 | llvm::Function* sem(const std::string& name) const; 81 | 82 | // Get function argument by name. 83 | // 84 | llvm::Argument* arg(llvm::Function* fn, const std::string& name); 85 | 86 | // Get function argument by name. 87 | // 88 | llvm::Argument* arg(const std::string& name); 89 | 90 | // Shallow copy function into a new one. 91 | // The global variables which the function accesses will not be copied. 92 | // 93 | llvm::Function* clone(llvm::Function* fn); 94 | 95 | // Wrap `fn` with helper_slice_fn function. 96 | // 97 | llvm::Function* make_slice(llvm::Function* fn); 98 | 99 | // Wrap `fn` with helper_lifted_fn function. 100 | // 101 | llvm::Function* make_final(llvm::Function* fn, uint64_t vip); 102 | 103 | // Current basic block function that is being lifted. 104 | // 105 | llvm::Function* function; 106 | 107 | // 108 | // 109 | llvm::IRBuilder<> ir; 110 | 111 | // Resolved semantics from intrinsics module and their functions. 112 | // 113 | std::unordered_map sems; 114 | 115 | // Helpers loaded from intrinsics file. 116 | // 117 | llvm::Function* helper_lifted_fn; 118 | llvm::Function* helper_empty_block_fn; 119 | llvm::Function* helper_block_fn; 120 | llvm::Function* helper_slice_fn; 121 | llvm::Function* helper_keep_fn; 122 | llvm::Value* helper_undef; 123 | 124 | llvm::LLVMContext context; 125 | std::unique_ptr module; 126 | }; 127 | -------------------------------------------------------------------------------- /src/logger.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #ifdef _MSC_VER 12 | #include 13 | #define unreachable() __assume(0) 14 | #else 15 | #include 16 | #include 17 | #define unreachable() __builtin_unreachable() 18 | #endif 19 | 20 | template <> 21 | struct fmt::formatter : fmt::formatter 22 | { 23 | template 24 | auto format(const triton::ast::SharedAbstractNode& node, fmtcontext& ctx) const 25 | { 26 | std::stringstream ss; 27 | ss << node; 28 | return formatter::format(ss.str(), ctx); 29 | } 30 | }; 31 | 32 | template<> 33 | struct fmt::formatter : fmt::formatter 34 | { 35 | template 36 | auto format(const triton::engines::symbolic::SharedSymbolicVariable& var, fmtcontext& ctx) const 37 | { 38 | auto alias = var->getAlias(); 39 | return formatter::format(alias.empty() ? var->getName() : alias, ctx); 40 | } 41 | }; 42 | 43 | template<> 44 | struct fmt::formatter : fmt::formatter 45 | { 46 | template 47 | auto format(const triton::engines::symbolic::SharedSymbolicExpression& expr, fmtcontext& ctx) const 48 | { 49 | return formatter::format(expr->getFormattedExpression(), ctx); 50 | } 51 | }; 52 | 53 | template<> 54 | struct fmt::formatter : fmt::formatter 55 | { 56 | template 57 | auto format(const triton::arch::Instruction& ins, fmtcontext& ctx) const 58 | { 59 | return formatter::format(fmt::format("0x{:x} {}", ins.getAddress(), ins.getDisassembly()), ctx); 60 | } 61 | }; 62 | 63 | template<> 64 | struct fmt::formatter : fmt::formatter 65 | { 66 | template 67 | auto format(const triton::arch::Register& reg, fmtcontext& ctx) const 68 | { 69 | return formatter::format(reg.getName(), ctx); 70 | } 71 | }; 72 | 73 | namespace logger 74 | { 75 | static void debug(const char* format, auto... args) 76 | { 77 | fmt::print(fg(fmt::color::dark_orange) | fmt::emphasis::bold, "[DEBUG] "); 78 | fmt::print(fmt::runtime(format), args...); 79 | fmt::print("\n"); 80 | } 81 | 82 | static void info(const char* format, const auto&... args) 83 | { 84 | fmt::print(fg(fmt::color::cadet_blue) | fmt::emphasis::bold, "[INFO] "); 85 | fmt::print(fmt::runtime(format), args...); 86 | fmt::print("\n"); 87 | } 88 | 89 | static void warn(const char* format, const auto&... args) 90 | { 91 | fmt::print(fg(fmt::color::yellow) | fmt::emphasis::bold, "[WARN] "); 92 | fmt::print(fmt::runtime(format), args...); 93 | fmt::print("\n"); 94 | } 95 | 96 | static void error [[noreturn]](const char* format, const auto&... args) 97 | { 98 | fmt::print(fg( fmt::color::red) | fmt::emphasis::bold, "[ERROR] "); 99 | fmt::print(fmt::runtime(format), args...); 100 | fmt::print("\n"); 101 | // Never return. 102 | // 103 | unreachable(); 104 | } 105 | }; 106 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "il/optimizer.hpp" 2 | #include "explorer.hpp" 3 | #include "emulator.hpp" 4 | #include "logger.hpp" 5 | #include "binary.hpp" 6 | #include "utils.hpp" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | #include 13 | 14 | llvm::cl::opt entrypoint("e", 15 | llvm::cl::desc("Virtual address of vmenter"), 16 | llvm::cl::value_desc("entrypoint"), 17 | llvm::cl::Required); 18 | 19 | llvm::cl::opt output("o", 20 | llvm::cl::desc("Path to the output .ll file"), 21 | llvm::cl::value_desc("output"), 22 | llvm::cl::init("output.ll"), 23 | llvm::cl::Optional); 24 | 25 | // Necessary command line optimization flags for llvm. Thank you Matteo. 26 | // 27 | static const std::vector optimization_args = 28 | { 29 | "-memdep-block-scan-limit=100000", 30 | "-rotation-max-header-size=100000", 31 | "-earlycse-mssa-optimization-cap=1000000", 32 | "-dse-memoryssa-defs-per-block-limit=1000000", 33 | "-dse-memoryssa-partial-store-limit=1000000", 34 | "-dse-memoryssa-path-check-limit=1000000", 35 | "-dse-memoryssa-scanlimit=1000000", 36 | "-dse-memoryssa-walklimit=1000000", 37 | "-dse-memoryssa-otherbb-cost=2", 38 | "-memssa-check-limit=1000000", 39 | "-memdep-block-number-limit=1000000", 40 | "-memdep-block-scan-limit=1000000", 41 | "-gvn-max-block-speculations=1000000", 42 | "-gvn-max-num-deps=1000000", 43 | "-gvn-hoist-max-chain-length=-1", 44 | "-gvn-hoist-max-depth=-1", 45 | "-gvn-hoist-max-bbs=-1", 46 | "-unroll-threshold=1000000" 47 | }; 48 | 49 | int main(int argc, char** argv) 50 | { 51 | // Inject optimization options to argv. 52 | // 53 | std::vector args; 54 | std::copy_n(argv, argc, std::back_inserter(args)); 55 | std::copy(optimization_args.begin(), optimization_args.end(), std::back_inserter(args)); 56 | // Enable stack traces. 57 | // 58 | llvm::sys::PrintStackTraceOnErrorSignal(args[0]); 59 | llvm::PrettyStackTraceProgram X(args.size(), args.data()); 60 | // Parse command parameters. 61 | // 62 | llvm::cl::ParseCommandLineOptions(args.size(), args.data()); 63 | 64 | auto lifter = std::make_shared(); 65 | auto tracer = std::make_shared(triton::arch::architecture_e::ARCH_X86_64); 66 | Explorer explorer(lifter, tracer); 67 | 68 | auto rtn = explorer.explore(entrypoint); 69 | auto fn = lifter->build_function(rtn.get()); 70 | 71 | il::optimize_virtual_function(fn); 72 | 73 | save_ir(fn, fmt::format("function.{}", output)); 74 | 75 | return 0; 76 | } 77 | -------------------------------------------------------------------------------- /src/tracer.cpp: -------------------------------------------------------------------------------- 1 | #include "asserts.hpp" 2 | #include "tracer.hpp" 3 | #include "logger.hpp" 4 | #include "utils.hpp" 5 | 6 | namespace variable 7 | { 8 | static const std::string rsp = "rsp"; 9 | static const std::string vip = "vip"; 10 | static const std::string vip_fetch = "[vip]"; 11 | static const std::string vsp = "vsp"; 12 | static const std::string vsp_fetch = "[vsp]"; 13 | static const std::string vregs = "vregs"; 14 | static const std::string memory_fetch = "[memory]"; 15 | }; 16 | 17 | // Match [vsp] + [vsp]. 18 | // 19 | static bool match_add(const triton::ast::SharedAbstractNode& ast) 20 | { 21 | if (ast->getType() == triton::ast::EXTRACT_NODE) 22 | { 23 | return match_add(ast->getChildren()[2]->getChildren()[1]); 24 | } 25 | return ast->getType() == triton::ast::BVADD_NODE 26 | && is_variable(ast->getChildren()[1], variable::vsp_fetch); 27 | } 28 | 29 | // Match `~[vsp] | ~[vsp]`. 30 | // 31 | static bool match_nand(const triton::ast::SharedAbstractNode& ast) 32 | { 33 | // For nand_8 ast is following: 34 | // ((_ extract 15 0) (concat ((_ extract 63 8) (concat (_ bv0 48) [vsp])) (bvor (bvnot ((_ extract 7 0) [vsp])) (bvnot [vsp])))) 35 | // 36 | if (ast->getType() == triton::ast::EXTRACT_NODE) 37 | { 38 | return match_nand(ast->getChildren()[2]->getChildren()[1]); 39 | } 40 | return ast->getType() == triton::ast::BVOR_NODE 41 | && ast->getChildren()[1]->getType() == triton::ast::BVNOT_NODE 42 | && is_variable(ast->getChildren()[1]->getChildren()[0], variable::vsp_fetch); 43 | } 44 | 45 | // Match `~[vsp] & ~[vsp]`. 46 | // 47 | static bool match_nor(const triton::ast::SharedAbstractNode& ast) 48 | { 49 | // For nor_8 ast is following: 50 | // ((_ extract 15 0) (concat ((_ extract 63 8) (concat (_ bv0 48) [vsp])) (bvand (bvnot ((_ extract 7 0) [vsp])) (bvnot [vsp])))) 51 | // 52 | if (ast->getType() == triton::ast::EXTRACT_NODE) 53 | { 54 | return match_nor(ast->getChildren()[2]->getChildren()[1]); 55 | } 56 | return ast->getType() == triton::ast::BVAND_NODE 57 | && ast->getChildren()[1]->getType() == triton::ast::BVNOT_NODE 58 | && is_variable(ast->getChildren()[1]->getChildren()[0], variable::vsp_fetch); 59 | } 60 | 61 | // Match `[vsp] >> ([vsp] & 0x3f)`. 62 | // 63 | static bool match_shr(const triton::ast::SharedAbstractNode& ast) 64 | { 65 | // for shr 66 | if (ast->getType() == triton::ast::EXTRACT_NODE && ast->getChildren()[2]->getType() == triton::ast::CONCAT_NODE) 67 | { 68 | return ast->getChildren()[2]->getChildren()[1]->getType() == triton::ast::BVLSHR_NODE; 69 | } 70 | return ast->getType() == triton::ast::BVLSHR_NODE 71 | && ast->getChildren()[1]->getType() == triton::ast::BVAND_NODE 72 | && is_variable(ast->getChildren()[0], variable::vsp_fetch); 73 | } 74 | 75 | // Match `[vsp] << ([vsp] & 0x3f)`. 76 | // 77 | static bool match_shl(const triton::ast::SharedAbstractNode& ast) 78 | { 79 | // for shl_8: ((_ extract 15 0) (concat ((_ extract 63 8) (concat (_ bv281474976710649 48) [vsp])) (bvshl ((_ extract 7 0) [vsp]) (bvand [vsp] (_ bv31 8))))) 80 | // 81 | if (ast->getType() == triton::ast::EXTRACT_NODE && ast->getChildren()[2]->getType() == triton::ast::CONCAT_NODE) 82 | { 83 | return ast->getChildren()[2]->getChildren()[1]->getType() == triton::ast::BVSHL_NODE; 84 | } 85 | return ast->getType() == triton::ast::BVSHL_NODE 86 | && ast->getChildren()[1]->getType() == triton::ast::BVAND_NODE 87 | && is_variable(ast->getChildren()[0], variable::vsp_fetch); 88 | } 89 | 90 | // Match `ror((([vsp]) << 32 | [vsp]), 0x0, 64)` 91 | static bool match_shrd(const triton::ast::SharedAbstractNode& ast) 92 | { 93 | return ast->getType() == triton::ast::EXTRACT_NODE 94 | && ast->getChildren()[2]->getType() == triton::ast::BVROR_NODE; 95 | } 96 | 97 | // Match `((_ extract 31 0) ((_ rotate_left 0) (concat [vsp] [vsp])))`. 98 | // 99 | static bool match_shld(const triton::ast::SharedAbstractNode& ast) 100 | { 101 | return ast->getType() == triton::ast::EXTRACT_NODE 102 | && ast->getChildren()[2]->getType() == triton::ast::BVROL_NODE; 103 | } 104 | 105 | Tracer::Tracer(triton::arch::architecture_e arch) noexcept 106 | : Emulator(arch) 107 | { 108 | physical_registers_count = (arch == triton::arch::ARCH_X86_64 ? 16 : 8); 109 | } 110 | 111 | Tracer::Tracer(Tracer const& other) noexcept 112 | : Emulator(other) 113 | , physical_registers_count{ other.physical_registers_count } 114 | , vip_register_name { other.vip_register_name } 115 | , vsp_register_name { other.vsp_register_name } 116 | { 117 | } 118 | 119 | uint64_t Tracer::vip() const 120 | { 121 | return read(vip_register()); 122 | } 123 | 124 | uint64_t Tracer::vsp() const 125 | { 126 | return read(vsp_register()); 127 | } 128 | 129 | const triton::arch::Register& Tracer::vip_register() const 130 | { 131 | fassert(vip_register_name.has_value()); 132 | return getRegister(vip_register_name.value()); 133 | } 134 | 135 | const triton::arch::Register& Tracer::vsp_register() const 136 | { 137 | fassert(vsp_register_name.has_value()); 138 | return getRegister(vsp_register_name.value()); 139 | } 140 | 141 | std::shared_ptr Tracer::fork() const noexcept 142 | { 143 | return std::make_shared(*this); 144 | } 145 | 146 | vm::Instruction Tracer::step(step_t type) 147 | { 148 | auto tracer = fork(); 149 | 150 | if (auto vinsn_mb = tracer->process_instruction()) 151 | { 152 | auto vinsn = vinsn_mb.value(); 153 | if (vm::op_enter(vinsn)) 154 | { 155 | vip_register_name = tracer->vip_register_name; 156 | vsp_register_name = tracer->vsp_register_name; 157 | } 158 | if (vm::op_branch(vinsn) && type == step_t::stop_before_branch) 159 | return vinsn; 160 | 161 | if (vm::op_jcc(vinsn)) 162 | { 163 | vip_register_name = std::get(vinsn).vip_register(); 164 | vsp_register_name = std::get(vinsn).vsp_register(); 165 | } 166 | // Cicle tracer to the current rip value. 167 | // 168 | while (true) 169 | { 170 | auto insn = disassemble(); 171 | if (insn.getAddress() == tracer->rip()) 172 | break; 173 | execute(insn); 174 | } 175 | return vinsn; 176 | } 177 | logger::error("Failed to process instruction"); 178 | } 179 | 180 | std::optional Tracer::process_instruction() 181 | { 182 | if (!vip_register_name.has_value() || !vsp_register_name.has_value()) 183 | { 184 | return process_vmenter(); 185 | } 186 | // List of matched virtual instructions for this handler. 187 | // 188 | std::vector vinsn; 189 | // List of executed instructions. 190 | // 191 | std::vector stream; 192 | // Symbolize bytecode and virtual stack. 193 | // 194 | symbolizeRegister(vip_register(), "vip"); 195 | symbolizeRegister(vsp_register(), "vsp"); 196 | symbolizeRegister(rsp_register(), "rsp"); 197 | 198 | cache.clear(); 199 | std::set poped_registers; 200 | std::vector poped_context; 201 | 202 | while (true) 203 | { 204 | auto insn = disassemble(); 205 | // Handle memory write. 206 | // 207 | if (op_mov_memory_register(insn)) 208 | { 209 | getSymbolicEngine()->initLeaAst(insn.operands[0].getMemory()); 210 | if (auto vins = process_store(insn)) 211 | { 212 | vinsn.push_back(std::move(vins.value())); 213 | } 214 | } 215 | // Handle pop register. 216 | // 217 | else if (op_pop_register(insn)) 218 | { 219 | const auto& reg = insn.operands[0].getConstRegister(); 220 | const auto name = reg.getName(); 221 | if (!poped_registers.contains(name)) 222 | { 223 | poped_context.push_back(vm::Pop(vm::PhysicalRegister(name), reg.getBitSize())); 224 | poped_registers.insert(std::move(name)); 225 | } 226 | } 227 | else if (op_pop_flags(insn)) 228 | { 229 | if (!poped_registers.contains("eflags")) 230 | { 231 | poped_context.push_back(vm::Pop(vm::PhysicalRegister("eflags"), 8 * ptrsize())); 232 | poped_registers.insert("eflags"); 233 | } 234 | } 235 | // Build instruction semantics. 236 | // 237 | execute(insn); 238 | // Handle memory read. 239 | // 240 | if (op_mov_register_memory(insn)) 241 | { 242 | if (auto vins = process_load(insn)) 243 | { 244 | vinsn.push_back(std::move(vins.value())); 245 | } 246 | } 247 | 248 | if (op_ret(insn) && poped_registers.size() == physical_registers_count) 249 | { 250 | stream.push_back(std::move(insn)); 251 | break; 252 | } 253 | stream.push_back(std::move(insn)); 254 | 255 | auto variables = collect_variables(getRegisterAst(rip_register())); 256 | 257 | if (has_variable(variables, variable::vip_fetch) || 258 | has_variable(variables, variable::memory_fetch, variable::vsp_fetch)) 259 | { 260 | break; 261 | } 262 | if (!rip()) 263 | { 264 | break; 265 | } 266 | } 267 | 268 | if (vinsn.empty()) 269 | { 270 | const auto variables = collect_variables(getRegisterAst(rip_register())); 271 | if (has_variable(variables, variable::memory_fetch, variable::vsp_fetch)) 272 | { 273 | // Jcc handler. 274 | // 275 | auto comment = get_variable(variables, variable::memory_fetch).value()->getComment(); 276 | auto vip_reg = getRegister(comment); 277 | auto vip_ast = triton::ast::unroll(getRegisterAst(vip_reg)); 278 | auto direction = vip_ast->getType() == triton::ast::BVADD_NODE ? vm::jcc_e::up : vm::jcc_e::down; 279 | // Pick next handler and deduce vsp register. We know that the first instruction after jcc is pop so 280 | // first memory access should be access to vsp. 281 | auto tracer = fork(); 282 | for (int i = 0; i < 10; i++) 283 | { 284 | auto insn = tracer->single_step(); 285 | if (op_mov_register_memory(insn)) 286 | { 287 | auto& vsp_reg = insn.operands[1].getConstMemory().getConstBaseRegister(); 288 | return vm::Jcc( 289 | direction, 290 | vip_reg.getName(), 291 | vsp_reg.getName() 292 | ); 293 | } 294 | } 295 | fassert("Failed to process jcc instruction."); 296 | } 297 | else if (has_variable(variables, variable::vip_fetch) && ranges::any_of(stream, op_lea_rip)) 298 | { 299 | // Jmp handler. 300 | // 301 | vinsn.push_back(vm::Jmp()); 302 | } 303 | else if (poped_context.size() == physical_registers_count) 304 | { 305 | // Exit handler. 306 | // 307 | return vm::Exit(std::move(poped_context)); 308 | } 309 | } 310 | if (vinsn.size() != 1) 311 | { 312 | for (const auto& insn : stream) 313 | logger::warn("{}", insn); 314 | return {}; 315 | } 316 | return vinsn.at(0); 317 | } 318 | 319 | std::optional Tracer::process_vmenter() 320 | { 321 | // Save rsp for future lookup. 322 | // 323 | const auto rsp_value = rsp(); 324 | // Symbolize initial context. 325 | // 326 | for (const auto& reg : regs()) 327 | { 328 | symbolizeRegister(reg, reg.getName()); 329 | } 330 | std::vector stream; 331 | // Execute vmenter. Collect virtual registers. 332 | // 333 | while (true) 334 | { 335 | auto insn = single_step(); 336 | 337 | if (op_mov_register_register(insn)) 338 | { 339 | const auto& r1 = insn.operands[0].getConstRegister(); 340 | const auto& r2 = insn.operands[1].getConstRegister(); 341 | 342 | if (r2 == rsp_register() && r1.getBitSize() == r2.getBitSize()) 343 | { 344 | vsp_register_name = r1.getName(); 345 | } 346 | } 347 | else if (op_mov_register_memory(insn)) 348 | { 349 | const auto& r1 = insn.operands[0].getConstRegister(); 350 | const auto& r2 = insn.operands[1].getConstMemory().getConstBaseRegister(); 351 | if (r2 != rsp_register()) 352 | { 353 | vip_register_name = r2.getName(); 354 | } 355 | symbolizeRegister(r1, r1.getName()); 356 | } 357 | stream.push_back(std::move(insn)); 358 | if (isRegisterSymbolized(rip_register())) 359 | break; 360 | } 361 | 362 | if (!vip_register_name.has_value() || !vsp_register_name.has_value()) 363 | { 364 | logger::warn("No virtual registers were found:"); 365 | logger::warn("\tvip: {}", vip_register_name.has_value() ? "found" : "not found"); 366 | logger::warn("\tvsp: {}", vsp_register_name.has_value() ? "found" : "not found"); 367 | 368 | for (const auto& insn : stream) 369 | logger::warn("{}", insn); 370 | return {}; 371 | } 372 | 373 | // Number of pushed physical registers on vmenter + 2 integers before vmenter and reloc at the end. 374 | // 375 | const auto context_size = physical_registers_count + 3; 376 | // Collect initial context. 377 | // 378 | std::vector context; 379 | for (uint64_t addr = rsp_value - ptrsize(); addr >= rsp_value - context_size * ptrsize(); addr -= ptrsize()) 380 | { 381 | triton::arch::MemoryAccess memory(addr, ptrsize()); 382 | if (isMemorySymbolized(memory)) 383 | { 384 | auto ast = triton::ast::unroll(getMemoryAst(memory)); 385 | auto size = ast->getBitvectorSize(); 386 | fassert(ast->getType() == triton::ast::VARIABLE_NODE); 387 | context.push_back(vm::Push(vm::PhysicalRegister(to_variable(ast)->getAlias()), size)); 388 | } 389 | else 390 | { 391 | // Match eflags since its not symbolic. 392 | // 393 | if (auto off = rsp_value - addr; off > 2 * ptrsize() && off < context_size * ptrsize()) 394 | { 395 | context.push_back(vm::Push(vm::PhysicalRegister("eflags"), 8 * ptrsize())); 396 | } 397 | else 398 | { 399 | context.push_back(vm::Push(vm::Immediate(read(memory)), 8 * ptrsize())); 400 | } 401 | } 402 | } 403 | if (context.size() != context_size) 404 | return {}; 405 | return vm::Enter(std::move(context)); 406 | } 407 | 408 | std::optional Tracer::process_store(const triton::arch::Instruction& insn) 409 | { 410 | const auto& mem = insn.operands[0].getConstMemory(); 411 | const auto& reg = insn.operands[1].getConstRegister(); 412 | auto mem_ast = triton::ast::unroll(mem.getLeaAst()); 413 | auto reg_ast = triton::ast::unroll(getRegisterAst(reg)); 414 | auto mem_variables = collect_variables(mem_ast); 415 | auto reg_variables = collect_variables(reg_ast); 416 | 417 | auto size = reg_ast->getBitvectorSize(); 418 | 419 | if (reg_ast->getType() == triton::ast::EXTRACT_NODE && size == 16 && !has_variable(reg_variables, variable::vsp)) 420 | { 421 | size = 8; 422 | } 423 | 424 | // movzx ax, byte ptr [vsp] 425 | // mov [vmregs + offset], ax 426 | // 427 | if (has_variable(mem_variables, variable::rsp, variable::vip_fetch) && 428 | has_variable(reg_variables, variable::vsp_fetch)) 429 | { 430 | auto write_off = read(mem.getConstIndexRegister()); 431 | auto number = write_off / ptrsize(); 432 | auto offset = write_off % ptrsize(); 433 | auto original = lookup_instruction(get_variable(reg_variables, variable::vsp_fetch).value()); 434 | return vm::Pop(vm::VirtualRegister(number, offset), original.operands[1].getBitSize()); 435 | } 436 | if (has_variable(mem_variables, variable::vsp) && 437 | has_variable(reg_variables, variable::vip_fetch)) 438 | { 439 | return vm::Push(vm::Immediate(static_cast(reg_ast->evaluate())), reg.getBitSize()); 440 | } 441 | // mov ax, byte ptr [vmregs + offset] 442 | // mov [vsp], ax 443 | // 444 | if (has_variable(mem_variables, variable::vsp) && 445 | has_variable(reg_variables, variable::vregs)) 446 | { 447 | auto vreg = get_variable(reg_variables, variable::vregs).value(); 448 | uint64_t index{}; 449 | if (std::sscanf(vreg->getComment().c_str(), "0x%lx", &index) != 1) 450 | { 451 | logger::error("Failed to parse comment of push vreg instruction: {}", vreg->getComment()); 452 | } 453 | auto number = index / ptrsize(); 454 | auto offset = index % ptrsize(); 455 | auto original = lookup_instruction(get_variable(reg_variables, variable::vregs).value()); 456 | return vm::Push(vm::VirtualRegister(number, offset), original.operands[1].getBitSize()); 457 | } 458 | if (has_variable(mem_variables, variable::vsp) && 459 | has_variable(reg_variables, variable::vsp)) 460 | { 461 | return vm::Push(vm::VirtualStackPointer(), mem.getBitSize()); 462 | } 463 | if (has_variable(mem_variables, variable::vsp_fetch) && 464 | has_variable(reg_variables, variable::vsp_fetch)) 465 | { 466 | return vm::Str(mem.getBitSize()); 467 | } 468 | if (has_variable(mem_variables, variable::vsp) && 469 | has_variable(reg_variables, variable::memory_fetch)) 470 | { 471 | auto original = lookup_instruction(get_variable(reg_variables, variable::memory_fetch).value()); 472 | return vm::Ldr(original.operands[1].getBitSize()); 473 | } 474 | if (has_variable(mem_variables, variable::vsp) && match_add(reg_ast)) 475 | { 476 | return vm::Add(size); 477 | } 478 | if (has_variable(mem_variables, variable::vsp) && match_nand(reg_ast)) 479 | { 480 | return vm::Nand(size); 481 | } 482 | if (has_variable(mem_variables, variable::vsp) && match_nor(reg_ast)) 483 | { 484 | return vm::Nor(size); 485 | } 486 | if (has_variable(mem_variables, variable::vsp) && match_shr(reg_ast)) 487 | { 488 | return vm::Shr(size); 489 | } 490 | if (has_variable(mem_variables, variable::vsp) && match_shl(reg_ast)) 491 | { 492 | return vm::Shl(size); 493 | } 494 | if (has_variable(mem_variables, variable::vsp) && match_shrd(reg_ast)) 495 | { 496 | return vm::Shrd(size); 497 | } 498 | if (has_variable(mem_variables, variable::vsp) && match_shld(reg_ast)) 499 | { 500 | return vm::Shld(size); 501 | } 502 | logger::warn("Failed to match store at 0x{:x}:", rip()); 503 | logger::warn("\tmemory AST: {}", mem_ast); 504 | logger::warn("\tregister AST: {}", reg_ast); 505 | return {}; 506 | } 507 | 508 | std::optional Tracer::process_load(const triton::arch::Instruction& insn) 509 | { 510 | const auto& reg = insn.operands[0].getConstRegister(); 511 | const auto& mem = insn.operands[1].getConstMemory(); 512 | const auto variables = collect_variables(mem.getLeaAst()); 513 | 514 | if (has_variable(variables, variable::vip)) 515 | { 516 | cache_instruction(insn, symbolizeRegister(reg, variable::vip_fetch)); 517 | } 518 | else if (has_variable(variables, variable::vsp)) 519 | { 520 | cache_instruction(insn, symbolizeRegister(reg, variable::vsp_fetch)); 521 | 522 | if (vsp_register().isOverlapWith(reg)) 523 | { 524 | return vm::Pop(vm::VirtualStackPointer(), mem.getBitSize()); 525 | } 526 | } 527 | else if (has_variable(variables, variable::rsp, variable::vip_fetch)) 528 | { 529 | // Set read offset as a comment to symbolic variable. It is used as vreg index in push vreg handler. 530 | // 531 | auto var = symbolizeRegister(reg, variable::vregs); 532 | var->setComment(fmt::format("0x{:x}", read(mem.getConstIndexRegister()))); 533 | cache_instruction(insn, var); 534 | } 535 | else if (has_variable(variables, variable::vsp_fetch)) 536 | { 537 | // Set memory operand register name as a comment to symbolic variable. It is used as new vip register in jcc handler. 538 | // 539 | auto var = symbolizeRegister(reg, variable::memory_fetch); 540 | var->setComment(fmt::format("{}", mem.getConstBaseRegister().getName())); 541 | cache_instruction(insn, var); 542 | } 543 | return {}; 544 | } 545 | 546 | void Tracer::cache_instruction(triton::arch::Instruction insn, triton::engines::symbolic::SharedSymbolicVariable variable) 547 | { 548 | cache.emplace(variable, insn); 549 | } 550 | 551 | const triton::arch::Instruction& Tracer::lookup_instruction(triton::engines::symbolic::SharedSymbolicVariable variable) const 552 | { 553 | if (cache.find(variable) != cache.end()) 554 | return cache.at(variable); 555 | logger::error("no instruction was found for {} variable", variable); 556 | } 557 | -------------------------------------------------------------------------------- /src/tracer.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "emulator.hpp" 4 | #include "vm/instruction.hpp" 5 | 6 | enum class step_t 7 | { 8 | stop_before_branch, 9 | execute_branch 10 | }; 11 | 12 | struct Tracer final : Emulator 13 | { 14 | Tracer(triton::arch::architecture_e arch) noexcept; 15 | Tracer(Tracer const& other) noexcept; 16 | 17 | std::shared_ptr fork() const noexcept; 18 | 19 | uint64_t vip() const; 20 | uint64_t vsp() const; 21 | 22 | const triton::arch::Register& vip_register() const; 23 | const triton::arch::Register& vsp_register() const; 24 | 25 | vm::Instruction step(step_t type); 26 | 27 | private: 28 | std::optional process_instruction(); 29 | std::optional process_vmenter(); 30 | std::optional process_store(const triton::arch::Instruction& insn); 31 | std::optional process_load (const triton::arch::Instruction& insn); 32 | 33 | void cache_instruction(triton::arch::Instruction insn, triton::engines::symbolic::SharedSymbolicVariable variable); 34 | const triton::arch::Instruction& lookup_instruction(triton::engines::symbolic::SharedSymbolicVariable variable) const; 35 | 36 | // Context size based on architecture. 37 | // 38 | size_t physical_registers_count; 39 | 40 | std::optional vip_register_name; 41 | std::optional vsp_register_name; 42 | 43 | std::unordered_map cache; 44 | }; 45 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #include "utils.hpp" 2 | 3 | #include 4 | 5 | std::vector collect_variables(const triton::ast::SharedAbstractNode& ast) 6 | { 7 | using namespace triton::ast; 8 | const auto vec = childrenExtraction(ast, true, true); 9 | return vec 10 | | ranges::views::filter ([](const SharedAbstractNode& node){ return node->getType() == VARIABLE_NODE; }) 11 | | ranges::views::transform([](const SharedAbstractNode& node){ return std::dynamic_pointer_cast(node)->getSymbolicVariable(); }) 12 | | ranges::to_vector; 13 | } 14 | 15 | bool is_variable(const triton::ast::SharedAbstractNode& node, const std::string& alias) 16 | { 17 | if (alias.empty()) 18 | return node->getType() == triton::ast::VARIABLE_NODE; 19 | return node->getType() == triton::ast::VARIABLE_NODE 20 | && std::dynamic_pointer_cast(node)->getSymbolicVariable()->getAlias() == alias; 21 | } 22 | 23 | triton::engines::symbolic::SharedSymbolicVariable to_variable(const triton::ast::SharedAbstractNode& node) 24 | { 25 | return std::dynamic_pointer_cast(node)->getSymbolicVariable(); 26 | } 27 | 28 | bool op_mov_register_register(const triton::arch::Instruction& insn) 29 | { 30 | return (insn.getType() == triton::arch::x86::ID_INS_MOV 31 | || insn.getType() == triton::arch::x86::ID_INS_MOVZX 32 | || insn.getType() == triton::arch::x86::ID_INS_MOVSX) 33 | && insn.operands[0].getType() == triton::arch::OP_REG 34 | && insn.operands[1].getType() == triton::arch::OP_REG; 35 | } 36 | 37 | bool op_mov_register_memory(const triton::arch::Instruction& insn) 38 | { 39 | return (insn.getType() == triton::arch::x86::ID_INS_MOV 40 | || insn.getType() == triton::arch::x86::ID_INS_MOVZX 41 | || insn.getType() == triton::arch::x86::ID_INS_MOVSX) 42 | && insn.operands[0].getType() == triton::arch::OP_REG 43 | && insn.operands[1].getType() == triton::arch::OP_MEM; 44 | } 45 | 46 | bool op_mov_memory_register(const triton::arch::Instruction& insn) 47 | { 48 | return (insn.getType() == triton::arch::x86::ID_INS_MOV 49 | || insn.getType() == triton::arch::x86::ID_INS_MOVZX 50 | || insn.getType() == triton::arch::x86::ID_INS_MOVSX) 51 | && insn.operands[0].getType() == triton::arch::OP_MEM 52 | && insn.operands[1].getType() == triton::arch::OP_REG; 53 | } 54 | 55 | bool op_pop_register(const triton::arch::Instruction& insn) 56 | { 57 | return insn.getType() == triton::arch::x86::ID_INS_POP 58 | && insn.operands[0].getType() == triton::arch::OP_REG; 59 | } 60 | 61 | bool op_jmp_register(const triton::arch::Instruction& insn) 62 | { 63 | return insn.getType() == triton::arch::x86::ID_INS_JMP 64 | && insn.operands[0].getType() == triton::arch::OP_REG; 65 | } 66 | 67 | bool op_pop_flags(const triton::arch::Instruction& insn) 68 | { 69 | return insn.getType() == triton::arch::x86::ID_INS_POPFQ || insn.getType() == triton::arch::x86::ID_INS_POPFD; 70 | } 71 | 72 | bool op_lea_rip(const triton::arch::Instruction& insn) 73 | { 74 | return insn.getType() == triton::arch::x86::ID_INS_LEA 75 | && insn.operands[1].getConstMemory().getConstBaseRegister().getId() == triton::arch::ID_REG_X86_RIP 76 | && insn.operands[1].getConstMemory().getConstDisplacement().getValue() == -7; 77 | } 78 | 79 | bool op_ret(const triton::arch::Instruction& insn) 80 | { 81 | return insn.getType() == triton::arch::x86::ID_INS_RET; 82 | } 83 | 84 | void save_ir(llvm::Value* value, const std::string& filename) 85 | { 86 | std::error_code ec; 87 | llvm::raw_fd_ostream fd(filename, ec); 88 | value->print(fd, false); 89 | } 90 | 91 | void save_ir(llvm::Module* module, const std::string& filename) 92 | { 93 | std::error_code ec; 94 | llvm::raw_fd_ostream fd(filename, ec); 95 | module->print(fd, nullptr); 96 | } 97 | -------------------------------------------------------------------------------- /src/utils.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include 15 | #include 16 | 17 | bool has_variable(const auto& range, const std::string& alias) 18 | { 19 | return ranges::any_of(range, [&](const auto& node) { return node->getAlias() == alias; }); 20 | } 21 | 22 | bool has_variable(const auto& range, const std::string& alias, const auto&... args) 23 | { 24 | return range.size() == 1 + sizeof...(args) && has_variable(range, alias) && has_variable(range, args...); 25 | } 26 | 27 | auto get_variable(const auto& range, const std::string& alias) -> std::optional 28 | { 29 | auto var = ranges::find_if(range, [&](const auto& var){ return var->getAlias() == alias; }); 30 | if (var != ranges::end(range)) 31 | return *var; 32 | return {}; 33 | } 34 | 35 | auto collect_variables(const triton::ast::SharedAbstractNode& ast) -> std::vector; 36 | bool is_variable(const triton::ast::SharedAbstractNode& node, const std::string& alias = ""); 37 | auto to_variable(const triton::ast::SharedAbstractNode& node) -> triton::engines::symbolic::SharedSymbolicVariable; 38 | 39 | // Matches mov/movzx/movsx register, register. 40 | // 41 | bool op_mov_register_register(const triton::arch::Instruction& insn); 42 | 43 | // Matches mov/movzx/movsx [memory], register. 44 | // 45 | bool op_mov_memory_register(const triton::arch::Instruction& insn); 46 | 47 | // Matches mov/movzx/movsx register, [memory]. 48 | // 49 | bool op_mov_register_memory(const triton::arch::Instruction& insn); 50 | 51 | // Matches pop register. 52 | // 53 | bool op_pop_register(const triton::arch::Instruction& insn); 54 | 55 | // Matches jmp register. 56 | // 57 | bool op_jmp_register(const triton::arch::Instruction& insn); 58 | 59 | // Matches popfq/popfd. 60 | // 61 | bool op_pop_flags(const triton::arch::Instruction& insn); 62 | 63 | // Matches `lea register, [rip - 7]` 64 | // 65 | bool op_lea_rip(const triton::arch::Instruction& insn); 66 | 67 | // Matches ret. 68 | // 69 | bool op_ret(const triton::arch::Instruction& insn); 70 | 71 | void save_ir(llvm::Value* value, const std::string& filename); 72 | void save_ir(llvm::Module* module, const std::string& filename); 73 | -------------------------------------------------------------------------------- /src/vm/instruction.cpp: -------------------------------------------------------------------------------- 1 | #include "instruction.hpp" 2 | #include "logger.hpp" 3 | 4 | namespace vm 5 | { 6 | PhysicalRegister::PhysicalRegister(std::string name) 7 | : name_{ std::move(name) } 8 | { 9 | } 10 | 11 | const std::string& PhysicalRegister::name() const noexcept 12 | { 13 | return name_; 14 | } 15 | 16 | VirtualRegister::VirtualRegister(int number, int offset) 17 | : number_{ number }, offset_{ offset } 18 | { 19 | } 20 | 21 | int VirtualRegister::number() const noexcept 22 | { 23 | return number_; 24 | } 25 | 26 | int VirtualRegister::offset() const noexcept 27 | { 28 | return offset_; 29 | } 30 | 31 | Immediate::Immediate(uint64_t value) 32 | : value_{ value } 33 | { 34 | } 35 | 36 | uint64_t Immediate::value() const noexcept 37 | { 38 | return value_; 39 | } 40 | 41 | Operand::Operand(Immediate&& imm) noexcept : op{ std::move(imm) } {} 42 | Operand::Operand(VirtualRegister&& reg) noexcept : op{ std::move(reg) } {} 43 | Operand::Operand(PhysicalRegister&& reg) noexcept : op{ std::move(reg) } {} 44 | Operand::Operand(VirtualStackPointer&& reg) noexcept : op{ std::move(reg) } {} 45 | 46 | bool Operand::is_vsp() const noexcept 47 | { 48 | return std::holds_alternative(op); 49 | } 50 | bool Operand::is_virtual() const noexcept 51 | { 52 | return std::holds_alternative(op); 53 | } 54 | bool Operand::is_physical() const noexcept 55 | { 56 | return std::holds_alternative(op); 57 | } 58 | bool Operand::is_immediate() const noexcept 59 | { 60 | return std::holds_alternative(op); 61 | } 62 | 63 | const Immediate& Operand::imm() const noexcept 64 | { 65 | return std::get(op); 66 | } 67 | const VirtualRegister& Operand::vrt() const noexcept 68 | { 69 | return std::get(op); 70 | } 71 | const PhysicalRegister& Operand::phy() const noexcept 72 | { 73 | return std::get(op); 74 | } 75 | const VirtualStackPointer& Operand::vsp() const noexcept 76 | { 77 | return std::get(op); 78 | } 79 | 80 | std::string Operand::to_string() const noexcept 81 | { 82 | if (is_immediate()) 83 | return fmt::format("0x{:016x}", imm().value()); 84 | else if (is_physical()) 85 | return fmt::format("{}", phy().name()); 86 | else if (is_virtual()) 87 | return fmt::format("vmregs[{:02}:{:02}]", vrt().number(), vrt().offset()); 88 | return fmt::format("{}", "vsp"); 89 | } 90 | 91 | Sized::Sized(int size) 92 | : size_{ size } 93 | { 94 | } 95 | 96 | int Sized::size() const noexcept 97 | { 98 | return size_; 99 | } 100 | 101 | Push::Push(Operand&& operand, int size) 102 | : Sized(size), operand{ std::move(operand) } 103 | { 104 | } 105 | 106 | const Operand& Push::op() const noexcept 107 | { 108 | return operand; 109 | } 110 | 111 | Pop::Pop(Operand&& operand, int size) 112 | : Sized(size), operand{ std::move(operand) } 113 | { 114 | } 115 | 116 | const Operand& Pop::op() const noexcept 117 | { 118 | return operand; 119 | } 120 | 121 | Exit::Exit(std::vector context) 122 | : context(std::move(context)) 123 | { 124 | } 125 | 126 | const std::vector& Exit::regs() const noexcept 127 | { 128 | return context; 129 | } 130 | 131 | Enter::Enter(std::vector context) 132 | : context(std::move(context)) 133 | { 134 | } 135 | 136 | const std::vector& Enter::regs() const noexcept 137 | { 138 | return context; 139 | } 140 | 141 | Jcc::Jcc(jcc_e type, std::string vip, std::string vsp) 142 | : type{ type }, vip{ std::move(vip) }, vsp{ std::move(vsp) } 143 | { 144 | } 145 | 146 | const std::string& Jcc::vip_register() const noexcept 147 | { 148 | return vip; 149 | } 150 | 151 | const std::string& Jcc::vsp_register() const noexcept 152 | { 153 | return vsp; 154 | } 155 | 156 | jcc_e Jcc::direction() const noexcept 157 | { 158 | return type; 159 | } 160 | 161 | bool op_push_imm(const Instruction& insn) 162 | { 163 | return std::holds_alternative(insn) 164 | && std::get(insn).op().is_immediate(); 165 | } 166 | 167 | bool op_branch(const Instruction& insn) 168 | { 169 | return std::holds_alternative(insn) 170 | || std::holds_alternative(insn) 171 | || std::holds_alternative(insn); 172 | } 173 | 174 | bool op_enter(const Instruction& insn) 175 | { 176 | return std::holds_alternative(insn); 177 | } 178 | 179 | bool op_exit(const Instruction& insn) 180 | { 181 | return std::holds_alternative(insn); 182 | } 183 | 184 | bool op_pop(const Instruction& insn) 185 | { 186 | return std::holds_alternative(insn); 187 | } 188 | 189 | bool op_jmp(const Instruction& insn) 190 | { 191 | return std::holds_alternative(insn); 192 | } 193 | 194 | bool op_jcc(const Instruction& insn) 195 | { 196 | return std::holds_alternative(insn); 197 | } 198 | }; 199 | -------------------------------------------------------------------------------- /src/vm/instruction.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | struct Tracer; 10 | 11 | namespace vm 12 | { 13 | enum class jcc_e 14 | { 15 | up, 16 | down 17 | }; 18 | 19 | struct PhysicalRegister 20 | { 21 | explicit PhysicalRegister(std::string name); 22 | 23 | const std::string& name() const noexcept; 24 | 25 | private: 26 | std::string name_; 27 | }; 28 | 29 | struct VirtualRegister 30 | { 31 | explicit VirtualRegister(int number, int offset); 32 | 33 | int number() const noexcept; 34 | int offset() const noexcept; 35 | 36 | private: 37 | int number_; 38 | int offset_; 39 | }; 40 | 41 | struct VirtualStackPointer 42 | { 43 | }; 44 | 45 | struct Immediate 46 | { 47 | explicit Immediate(uint64_t value); 48 | 49 | uint64_t value() const noexcept; 50 | 51 | private: 52 | uint64_t value_; 53 | }; 54 | 55 | struct Operand 56 | { 57 | Operand(Immediate&& imm) noexcept; 58 | Operand(VirtualRegister&& reg) noexcept; 59 | Operand(PhysicalRegister&& reg) noexcept; 60 | Operand(VirtualStackPointer&& reg) noexcept; 61 | 62 | bool is_vsp() const noexcept; 63 | bool is_virtual() const noexcept; 64 | bool is_physical() const noexcept; 65 | bool is_immediate() const noexcept; 66 | 67 | const Immediate& imm() const noexcept; 68 | const VirtualRegister& vrt() const noexcept; 69 | const PhysicalRegister& phy() const noexcept; 70 | const VirtualStackPointer& vsp() const noexcept; 71 | 72 | std::string to_string() const noexcept; 73 | 74 | private: 75 | std::variant op; 76 | }; 77 | 78 | struct Sized 79 | { 80 | Sized(int size); 81 | 82 | int size() const noexcept; 83 | 84 | protected: 85 | int size_; 86 | }; 87 | 88 | struct Add : public Sized 89 | { 90 | using Sized::Sized; 91 | }; 92 | 93 | struct Shl : public Sized 94 | { 95 | using Sized::Sized; 96 | }; 97 | 98 | struct Shr : public Sized 99 | { 100 | using Sized::Sized; 101 | }; 102 | 103 | struct Shrd : public Sized 104 | { 105 | using Sized::Sized; 106 | }; 107 | 108 | struct Shld : public Sized 109 | { 110 | using Sized::Sized; 111 | }; 112 | 113 | struct Ldr : public Sized 114 | { 115 | using Sized::Sized; 116 | }; 117 | 118 | struct Str : public Sized 119 | { 120 | using Sized::Sized; 121 | }; 122 | 123 | struct Nor : public Sized 124 | { 125 | using Sized::Sized; 126 | }; 127 | 128 | struct Nand : public Sized 129 | { 130 | using Sized::Sized; 131 | }; 132 | 133 | struct Push : public Sized 134 | { 135 | Push(Operand&& operand, int size); 136 | 137 | const Operand& op() const noexcept; 138 | 139 | private: 140 | Operand operand; 141 | }; 142 | 143 | struct Pop : public Sized 144 | { 145 | Pop(Operand&& operand, int size); 146 | 147 | const Operand& op() const noexcept; 148 | 149 | private: 150 | Operand operand; 151 | }; 152 | 153 | struct Jmp{}; 154 | struct Ret{}; 155 | struct Exit 156 | { 157 | Exit(std::vector context); 158 | 159 | const std::vector& regs() const noexcept; 160 | 161 | private: 162 | std::vector context; 163 | }; 164 | 165 | struct Enter 166 | { 167 | Enter(std::vector context); 168 | 169 | const std::vector& regs() const noexcept; 170 | 171 | private: 172 | std::vector context; 173 | }; 174 | 175 | struct Jcc 176 | { 177 | Jcc(jcc_e type, std::string vip, std::string vsp); 178 | 179 | const std::string& vip_register() const noexcept; 180 | const std::string& vsp_register() const noexcept; 181 | jcc_e direction() const noexcept; 182 | 183 | private: 184 | jcc_e type; 185 | std::string vip; 186 | std::string vsp; 187 | }; 188 | 189 | using Instruction = std::variant; 190 | 191 | bool op_push_imm(const Instruction& insn); 192 | bool op_branch(const Instruction& insn); 193 | bool op_enter(const Instruction& insn); 194 | bool op_exit(const Instruction& insn); 195 | bool op_pop(const Instruction& insn); 196 | bool op_jmp(const Instruction& insn); 197 | bool op_jcc(const Instruction& insn); 198 | }; 199 | -------------------------------------------------------------------------------- /src/vm/routine.cpp: -------------------------------------------------------------------------------- 1 | #include "routine.hpp" 2 | #include "asserts.hpp" 3 | #include "logger.hpp" 4 | 5 | namespace vm 6 | { 7 | BasicBlock::BasicBlock(uint64_t vip, Routine* rtn) 8 | : vip_(vip), owner(rtn), lifted(nullptr) 9 | { 10 | owner->blocks.emplace(vip, this); 11 | } 12 | 13 | BasicBlock* BasicBlock::fork(uint64_t vip) 14 | { 15 | if (owner->contains(vip)) 16 | { 17 | next.push_back(owner->blocks.at(vip)); 18 | return nullptr; 19 | } 20 | 21 | auto block = new BasicBlock(vip, owner); 22 | 23 | next.push_back(block); 24 | return block; 25 | } 26 | 27 | void BasicBlock::add(Instruction&& insn) noexcept 28 | { 29 | vins.push_back(std::move(insn)); 30 | } 31 | 32 | uint64_t BasicBlock::vip() const noexcept 33 | { 34 | return vip_; 35 | } 36 | 37 | flow_t BasicBlock::flow() const noexcept 38 | { 39 | if (vins.empty()) 40 | return flow_t::unknown; 41 | else if (op_exit(vins.back())) 42 | return flow_t::exit; 43 | else if (op_jcc(vins.back())) 44 | return flow_t::conditional; 45 | else if (op_jmp(vins.back())) 46 | return flow_t::unconditional; 47 | return flow_t::unknown; 48 | } 49 | 50 | Routine::Routine(uint64_t vip) 51 | { 52 | entry = new BasicBlock(vip, this); 53 | } 54 | 55 | Routine::~Routine() 56 | { 57 | for (auto& [vip, block] : blocks) 58 | delete block; 59 | } 60 | 61 | BasicBlock* Routine::begin(uint64_t vip) 62 | { 63 | return (new Routine(vip))->entry; 64 | } 65 | 66 | bool Routine::contains(uint64_t vip) const noexcept 67 | { 68 | return blocks.count(vip) > 0; 69 | } 70 | 71 | std::string Routine::dot() const noexcept 72 | { 73 | std::string body = "digraph g {\n"; 74 | 75 | for (const auto& [vip, block] : blocks) 76 | { 77 | for (const auto& next : block->next) 78 | body += fmt::format("vip_0x{:08x} -> vip_0x{:08x} []\n", vip, next->vip()); 79 | } 80 | return body + "}\n"; 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /src/vm/routine.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "instruction.hpp" 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | namespace llvm 11 | { 12 | class Function; 13 | } 14 | 15 | namespace vm 16 | { 17 | static constexpr auto invalid_vip = ~0ull; 18 | 19 | enum class flow_t 20 | { 21 | exit, 22 | unknown, 23 | conditional, 24 | unconditional, 25 | }; 26 | 27 | struct Routine; 28 | 29 | struct BasicBlock 30 | { 31 | BasicBlock(uint64_t vip, Routine* rtn); 32 | 33 | // Fork a new block from this block and link them together. 34 | // 35 | BasicBlock* fork(uint64_t vip); 36 | 37 | void add(Instruction&& insn) noexcept; 38 | 39 | auto begin() const noexcept { return vins.begin(); } 40 | auto end() const noexcept { return vins.end(); } 41 | 42 | uint64_t vip() const noexcept; 43 | flow_t flow() const noexcept; 44 | 45 | // Routine to which this block belongs to. 46 | // 47 | Routine* owner; 48 | 49 | llvm::Function* lifted; 50 | 51 | std::vector next; 52 | 53 | private: 54 | uint64_t vip_; 55 | 56 | std::vector vins; 57 | }; 58 | 59 | struct Routine 60 | { 61 | Routine(uint64_t vip); 62 | ~Routine(); 63 | 64 | // Create a routine and return entry basic block. 65 | // 66 | static BasicBlock* begin(uint64_t vip); 67 | 68 | bool contains(uint64_t vip) const noexcept; 69 | 70 | // Build graphviz control-flow graph. 71 | // 72 | std::string dot() const noexcept; 73 | 74 | auto begin() const noexcept { return blocks.begin(); } 75 | auto end() const noexcept { return blocks.end(); } 76 | 77 | // First block in this routine. 78 | // 79 | BasicBlock* entry; 80 | 81 | // Explored blocks. 82 | // 83 | std::unordered_map blocks; 84 | }; 85 | } 86 | --------------------------------------------------------------------------------