Sudeep vision

Blogs about cyber security - APTs and CTFs

View on GitHub

Glacier CTF 2024 - Qamu reverse engineering challenge write up

Challenge Name Qamu
Solution author Sudeep Singh
Category Reverse engineering

Summary

Recently in Glacier CTF 2024, there was an interesting challenge called “qamu”. This challenge simulates confidential computing in a hypervisor that executes a program in a VM.

You can download the challenge from here

Contents

Initial analysis

In this challenge, we are provided the below files

SIGSEGV handler setup

The main function sets up a signal handler to handle signal with code 0xB (corresponds to SIGSEGV). This is the segmentation fault error. The main program sets the subroutine at address: 0x401725 as the signal handler. This means, each time the program encounters a segmentation fault, it invokes the subroutine at address: 0x401725

1.png

Memory mapping the encrypted vm_enc.bin

In the next few steps, the main function calculates the size of vm_enc.bin file, and allocates memory of this size using _malloc(). It then maps 5 memory regions at addresses: 0x30000000, 0x40000000, 0x50000000, 0x60000000, 0x70000000 respectively. After this, the program reads the contents of vm_enc.bin to the previously allocated memory region. The memory mapping scheme, reads 0x1000 bytes at a time from the file vm_enc.bin, however it does not read the data sequentially into the memory. Instead, it reads the data using offsets mentioned in .data section at address: 0x4040A0. This array stores 0x23d qwords, each representing an offset used to specify the location from which data will be read. Its important to note that each byte is represented as a qword in the array at address: 0x4040A0.

2.png

Data is copied to corresponding offsets at memory region: 0x70000000

Reading the input

Now the binary prompts for the license key. It reads 0x20 bytes from stdin. So, we know the length of the license key is 32 characters.

3.png

VM execution

The subroutine at address: 0x40175A is the main function that handles the VM execution. Before we analyse this subroutine, it is worth noting that upon return from VM execution, the main function checks whether the value at memory address: 0x50000000 is 0 or not. If the value is non-zero, then the main function reads the flag.txt file and displays the flag.

.text:0000000000401C9D                 mov     eax, 0
.text:0000000000401CA2                 call    sub_40175A
.text:0000000000401CA7                 mov     eax, 50000000h
.text:0000000000401CAC                 mov     rax, [rax]
.text:0000000000401CAF                 test    rax, rax
.text:0000000000401CB2                 jz      loc_401D55
.text:0000000000401CB8                 mov     edi, offset aValid ; "valid"
.text:0000000000401CBD                 call    _puts
.text:0000000000401CC2                 mov     esi, offset aR  ; "r"
.text:0000000000401CC7                 mov     edi, offset aFlagTxt ; "flag.txt"
.text:0000000000401CCC                 call    _fopen
.text:0000000000401CD1                 mov     [rbp+var_138], rax

It iterates over the memory mapped data at address: 0x70000000 and executes in the same sequence as the offsets mentioned in the offsets array.

The previous mapped memory regions are used to preserve the state of the host program (aka hypervisor in this case) and the guest program (the VM in this case).

Relevant code section is shown below.

4.png

call r13 instruction at address: 0x4018B8 is used to enter the VM. Since the code loaded from vm_enc.bin is encrypted, this will result in an exception being thrown.

This exception is handled by the signal handler that decrypts the code.

Decryption of code

Signal handler passes the address of the memory region with encrypted data to subroutine at address: 0x4015A3

Analysis of this subroutine reveals the below

Decryption key: <8 bytes address of memory region> + b"\x25\xfa\x5b\xc3\x52\xa7\x88\xd5\x5c\x15\xff\x16\xe0\xac\x36\x2a\xe0\xd1\x37\x40\x59\x83\x7d\x55"

Execution of decrypted code

After code is decrypted, the signal handler passes the control back to the decrypted code. Each decrypted code section has 2 valid instructions followed by invalid instructions.

decrypted code section looks like below:

<valid ASM instruction>
jmp r15
<invalid ASM instructions>

The jmp r15 instruction passes the control back to 0x4018BB. Once the control is transferred back to 0x4018BB, we can see that the value of rax is saved in 0x50000000 which as we observed earlier should not be 0 to read the flag.txt contents.

Re-encryption of code

The code at address: 0x4018BB saves the state of VM registers, restores the state of hypervisor registers and then calls subroutine at address 0x401664 to re-encrypt the memory region using the same RC4 cipher. After encrypting, it marks the memory region protection as “RW” using mprotect()

Note: The fact that the hypervisor decrypts the memory region, marks it as RWX, executes it and then re-encrypts it, marking it as “RW” explains the attempt of the challenge author to implement “confidential computing”.

Dumping the decrypted program

Now that we know how the hypervisor executes the encrypted program in vm_enc.bin, we can decrypt it. Also, since we know that each decrypted memory region only has two valid instructions with the second instruction always as jmp r15, we can dump the first instruction from each memory region to get the decrypted program.

The complete script to decrypt vm_enc.bin, disassemble it and dump the bytecode is available at the end of this article.

Analysis of decrypted program

The decrypted program is 2077 bytes in size and contains a total of 574 instructions. I’ve included the entire disassembled code at the end of this article.

A quick analysis of the disassembled program reveals that it is the license key check routine. Below are few observations:

  1. rdi is the pointer to the license key
  2. checks the characters at offsets 3, 5, 11, 17 and 23 using the below code.
cmp	qword ptr [rax + 0x28], 0x2d
sete	r14b
cmp	qword ptr [rax + 0x58], 0x2d
sete	dl
and	edx, r14d
cmp	qword ptr [rax + 0x88], 0x2d
sete	r14b
and	r14d, edx
cmp	qword ptr [rax + 0xb8], 0x3a
sete	dl
and	edx, r14d
cmp	qword ptr [rax + 0x18], 0x50

So, we can infer the below few constraints from this:

key[3] == 'P'
key[5] == '-'
key[11] == '-'
key[17] == '-'
key[23] == ':'

Solving for constraints

There are many constraints in this program, so manually extracting all the constraints and using it with an SMT solver like Z3 will not be an efficient solution. Instead using a symbolic execution engine like Angr will be suitable for this case.

I have included the complete script in the “Solution script” section. It will automatically do the following:

5.png

License key: AHMPQ-30009-AABJW-qkjhh:\x60\xbcK\xc5am\xc0\x16

Now this license key needs to be sent as an input to the program to read the correct flag.

Acknowledgements

I would like to thank Ik0ri4n and itskarudo for their discussion and ideas to solve this challenge.

Solution script

from Crypto.Cipher import ARC4
import sys
import struct
from capstone import *
import os
import time
import angr
import claripy
from pwn import *
import logging

logging.getLogger('angr').setLevel(logging.ERROR)
logging.getLogger('claripy').setLevel(logging.ERROR)
logging.getLogger('angr.state_plugins.unicorn_engine').setLevel(logging.CRITICAL)

base = 0x70000
size = 0x1000
current_dir = os.path.dirname(os.path.abspath(__file__))

# extract all the offsets from 0x4040A0 in .data section
offsets = ['0x0', '0x1', '0x2', '0x3', '0x4', '0x5', '0x6', '0x7', '0x8', '0x9', '0xa', '0xb', '0xc', '0xd', '0xe', '0xf', '0x8', '0x10', '0x11', '0x12', '0x13', '0x14', '0x15', '0x16', '0x17', '0x8', '0x18', '0x17', '0x19', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x24', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x2c', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x2d', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x2e', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x2f', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x30', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x31', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x32', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x33', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x34', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x35', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x36', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x37', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x38', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x23', '0x39', '0x25', '0x26', '0xc', '0x27', '0x28', '0x22', '0x29', '0x17', '0x25', '0x2a', '0x21', '0x14', '0x2b', '0x3a', '0x1a', '0x1b', '0xc', '0x1c', '0x1d', '0x14', '0x1e', '0x1f', '0x1a', '0x20', '0x21', '0x22', '0x14', '0x3b', '0x3c', '0x3d', '0x3e', '0x3f', '0x40', '0x17', '0x1e', '0x41', '0x3c', '0x18', '0x16', '0x42', '0x17', '0x43', '0x44', '0x45', '0x46', '0x47', '0x48', '0x49', '0x45', '0x4a', '0x4b', '0x47', '0x48', '0x4c', '0x45', '0x4a', '0x4d', '0x4e', '0x48', '0x4f', '0x50', '0x4a', '0x51', '0x4e', '0x48', '0x52', '0x45', '0x48', '0x53', '0x54', '0x55', '0x48', '0x56', '0x54', '0x55', '0x48', '0x57', '0x58', '0x54', '0x55', '0x48', '0x59', '0x54', '0x55', '0x48', '0x5a', '0x5b', '0x55', '0x48', '0x5c', '0x5b', '0x55', '0x48', '0x5d', '0x5e', '0x5b', '0x55', '0x48', '0x5f', '0x5e', '0x5b', '0x55', '0x48', '0x60', '0x5e', '0x5b', '0x55', '0x48', '0x61', '0x62', '0x63', '0x45', '0x48', '0x5d', '0x33', '0x64', '0x45', '0x48', '0x65', '0x66', '0x67', '0x45', '0x48', '0x68', '0x69', '0x6a', '0x45', '0x48', '0x6b', '0x6c', '0x6d', '0x45', '0x6e', '0x4a', '0x6f', '0x70', '0x47', '0x71', '0x48', '0x72', '0x73', '0x74', '0x75', '0x76', '0x77', '0x73', '0x74', '0x75', '0x78', '0x79', '0x73', '0x74', '0x7a', '0x7b', '0x72', '0x73', '0x74', '0x7c', '0x7d', '0x7e', '0x73', '0x74', '0x7f', '0x80', '0x77', '0x73', '0x81', '0x82', '0x83', '0x47', '0x84', '0x74', '0x85', '0x73', '0x82', '0x86', '0x87', '0x88', '0x47', '0x89', '0x74', '0x8a', '0x73', '0x8b', '0x74', '0x8c', '0x73', '0x82', '0x8d', '0x8e', '0x8f', '0x47', '0x82', '0x90', '0x91', '0x92', '0x93', '0x94', '0x95', '0x96', '0x97', '0x74', '0x98', '0x99', '0x9a', '0x9b', '0x9c', '0x95', '0x9d', '0x82', '0x9e', '0x91', '0x92', '0x9f', '0xa0', '0xa1', '0x95', '0x96', '0x74', '0xa2', '0x99', '0xa3', '0xa4', '0x95', '0xa5', '0x9a', '0x9d', '0x74', '0xa2', '0x99', '0xa3', '0xa6', '0x95', '0xa5', '0x9a', '0x9d', '0x74', '0xa7', '0x99', '0xa3', '0xa8', '0x95', '0xa5', '0x9a', '0x9d', '0x74', '0xa7', '0x99', '0xa3', '0xa9', '0x95', '0xa5', '0x9a', '0x9d', '0x74', '0xaa', '0x99', '0x9a', '0xab', '0xac', '0x95', '0x9d', '0x74', '0xa7', '0x99', '0xa3', '0x95', '0xa5', '0x9a', '0x9d', '0x74', '0xad', '0x99', '0x9a', '0xae', '0xaf', '0x95', '0x9d', '0x74', '0xb0', '0xb1', '0x73', '0x74', '0xb2', '0xb3', '0x73', '0x74', '0xb4', '0xb5', '0xb6', '0xb7', '0x73', '0x74', '0xb4', '0xb8', '0xb6', '0xb9', '0x73', '0x74', '0xb4', '0xba', '0xb6', '0xbb', '0xbc', '0x73', '0xbd', '0xbe', '0x74', '0xb4', '0xbf', '0xc0', '0xb6', '0xc1', '0x73', '0x74', '0xb4', '0xc2', '0xb6', '0xc3', '0x73', '0xc4', '0x74', '0xc5', '0xc6', '0xc7', '0xc8', '0xc9', '0xca', '0xcb', '0xcc', '0xcd', '0xce', '0xcf', '0xd0', '0xd1']

def solve():
    p = angr.Project("./challenge", auto_load_libs=False)

    INITIAL_ADDR = 0x7000000
    STACK_ADDR = 0xd0000000
    INPUT_ADDR = 0x20000000

    state = p.factory.blank_state(addr=INITIAL_ADDR)

    sc = open("./decrypted_vm", "rb").read()

    x = [claripy.BVS(f"flag{i}", 64) for i in range(0x20)]

    flag = claripy.Concat(*x)

    state.memory.store(INITIAL_ADDR, sc)
    state.memory.store(INPUT_ADDR, flag)

    # rdi points to the license key which is validated by the decrypted program
    state.regs.rdi = INPUT_ADDR
    state.regs.r13 = 0x70000000
    # r15 points to the code inside the hypervisor
    state.regs.r15 = 0x4018bb
    state.regs.rsp = STACK_ADDR

    simgr = p.factory.simulation_manager(state)

    while len(simgr.active):
        simgr.step()
    
    ret = simgr.unconstrained[0].regs.rax

    s = claripy.Solver()

    sol = []

    # return value of the decrypted program should not be 0 to be valid
    s.add(ret != 0)

    for item in x:
        sol.append(s.eval(item, 1)[0])

    sol = [p64(item)[-1] for item in sol]
    print(f'The license key is: {bytes(sol)}')

def disas_and_dump():
    print(f"Disassembling the program")
    opcodes = ""
    asm_code = ""
    for offset in offsets:
            fname = os.path.join(current_dir, "bin", str(int(offset,16)) + ".bin")
            with open(fname,"rb") as f:
                enc = f.read()
            md = Cs(CS_ARCH_X86, CS_MODE_64)
            
            counter = 0
            
            for j in md.disasm(enc, 0):
                asm_code += "%s\t%s\n" %(j.mnemonic, j.op_str)
                opcodes += " ".join(f"{byte:02x} " for byte in j.bytes)
                counter += 1
                # we are extracting only the first instruction
                if counter == 1:
                    break
    
    _dec = bytes.fromhex(opcodes.replace(" ",""))
    
    print(f"Saving the disassembled decrypted bytecode")
    with open("vm.asm", "w") as f:
        f.write(asm_code)
    print(f"Dumping the decrypted bytecode to decrypted_vm")
    with open("decrypted_vm","wb") as f:
                f.write(_dec)

def decrypt_vm(fname):
    with open(fname, "rb") as f:
        contents = f.read()

    decrypted_data = b''

    for i in range(0x23d):
        enc = contents[i * size:i * size + size]
        x = (base + i) << 0xc
        y = struct.pack("<Q", x)
        # first 8 bytes of the RC4 key are the same as the memory address of the encrypted data
        key = y + b"\x25\xfa\x5b\xc3\x52\xa7\x88\xd5\x5c\x15\xff\x16\xe0\xac\x36\x2a\xe0\xd1\x37\x40\x59\x83\x7d\x55"
        rc4 = ARC4.new(key)
        decrypted_data = rc4.decrypt(enc)
        oname = os.path.join(current_dir, "bin/", str(i) + ".bin")
        with open(oname, "wb") as f:
            f.write(decrypted_data)
    return
    
if __name__ == "__main__":
    if len(sys.argv) != 2:
        print(f'usage: python solver.py <filename of the VM>')
        sys.exit()
    _fname = sys.argv[1]
    os.makedirs("bin", exist_ok=True)
    # decrypt vm_enc.bin
    decrypt_vm(_fname)
    # disassemble the code and dump the opcodes
    disas_and_dump()
    print(f"Wait a few seconds to launch Angr solver engine")
    time.sleep(4)
    # run the Angr script to solve the program in decrypted_vm
    solve()

Disassembly of decrypted bytecode

push	r14
mov	rax, rdi
push	r12
push	rbp
push	rbx
mov	rsi, qword ptr [rdi + 8]
mov	rbx, qword ptr [rax + 0x70]
mov	r11, qword ptr [rax + 0xa0]
mov	rdx, rsi
lea	rcx, [rsi + rsi*8]
mov	rbp, qword ptr [rax + 0x60]
mov	r9, qword ptr [rdi]
shl	rdx, 5
mov	rdi, qword ptr [rdi + 0x20]
mov	r12, qword ptr [rax + 0x30]
lea	rcx, [rdx + rcx*2]
mov	rdx, rsi
mov	r8, qword ptr [rax + 0x38]
mov	r10, qword ptr [rax + 0xb0]
add	rcx, rsi
shl	rdx, 7
add	rdx, rcx
mov	rcx, rsi
shl	rcx, 8
add	rcx, rdx
mov	rdx, rsi
shl	rdx, 0x28
add	rcx, rdx
xor	rcx, qword ptr [rax + 0x98]
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, rbx
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, qword ptr [rax + 0x68]
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, qword ptr [rax + 0x10]
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, r11
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, qword ptr [rax + 0x18]
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, qword ptr [rax + 0x90]
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, qword ptr [rax + 0x40]
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, rbp
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, qword ptr [rax + 0x78]
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, rdi
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, r12
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, r9
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, qword ptr [rax + 0x80]
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, r8
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
lea	r14, [rdx + rcx]
xor	r14, r10
mov	rdx, r14
lea	rcx, [r14 + r14*8]
shl	rdx, 5
lea	rdx, [rdx + rcx*2]
mov	rcx, r14
add	rdx, r14
shl	rcx, 7
add	rcx, rdx
mov	rdx, r14
shl	r14, 0x28
shl	rdx, 8
add	rdx, rcx
lea	rcx, [rdx + r14]
xor	rcx, qword ptr [rax + 0x48]
mov	rdx, rcx
lea	r14, [rcx + rcx*8]
shl	rdx, 5
lea	rdx, [rdx + r14*2]
mov	r14, rcx
add	rdx, rcx
shl	r14, 7
add	r14, rdx
mov	rdx, rcx
shl	rcx, 0x28
shl	rdx, 8
add	rdx, r14
add	rdx, rcx
xor	rdx, qword ptr [rax + 0x50]
mov	rcx, rdx
lea	r14, [rdx + rdx*8]
shl	rcx, 5
lea	rcx, [rcx + r14*2]
mov	r14, rdx
add	rcx, rdx
shl	r14, 7
add	r14, rcx
mov	rcx, rdx
shl	rdx, 0x28
shl	rcx, 8
add	rcx, r14
add	rcx, rdx
xor	rcx, qword ptr [rax + 0xa8]
cmp	qword ptr [rax + 0x28], 0x2d
sete	r14b
cmp	qword ptr [rax + 0x58], 0x2d
sete	dl
and	edx, r14d
cmp	qword ptr [rax + 0x88], 0x2d
sete	r14b
and	r14d, edx
cmp	qword ptr [rax + 0xb8], 0x3a
sete	dl
and	edx, r14d
cmp	qword ptr [rax + 0x18], 0x50
sete	r14b
and	r14d, edx
cmp	r9, rsi
setne	dl
and	edx, r14d
cmp	r12, r8
setne	r14b
and	r14d, edx
cmp	qword ptr [rax + 0x18], rdi
setne	dl
and	edx, r14d
cmp	r9, rbp
sete	r14b
and	edx, r14d
lea	r14, [r9 - 0x41]
cmp	r14, 0x19
setbe	r14b
and	edx, r14d
lea	r14, [rsi - 0x41]
cmp	r14, 0x19
setbe	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x10]
sub	r14, 0x41
cmp	r14, 0x19
setbe	r14b
and	edx, r14d
lea	r14, [rdi - 0x41]
cmp	r14, 0x19
setbe	r14b
and	edx, r14d
lea	r14, [r12 - 0x30]
cmp	r14, 9
setbe	r14b
and	edx, r14d
lea	r14, [r8 - 0x30]
cmp	r14, 9
setbe	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x40]
sub	r14, 0x30
cmp	r14, 9
setbe	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x48]
sub	r14, 0x30
cmp	r14, 9
setbe	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x50]
sub	r14, 0x30
cmp	r14, 9
setbe	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x98]
and	r14, rsi
cmp	r14, 0x48
sete	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x40]
xor	r14, qword ptr [rax + 0x78]
cmp	r14, 0x7a
sete	r14b
and	edx, r14d
mov	r14, rsi
and	r14, rdi
cmp	r14, 0x40
sete	r14b
and	edx, r14d
mov	r14, qword ptr [rax + 0x90]
xor	r14, rdi
cmp	r14, 0x20
sete	r14b
and	edx, r14d
mov	r14, r9
or	r14, r11
cmp	r14, 0x6b
sete	r14b
xor	rsi, qword ptr [rax + 0x80]
and	r14d, edx
cmp	rsi, 0x1f
mov	rsi, qword ptr [rax + 0x88]
sete	dl
add	rsi, qword ptr [rax + 0x10]
and	edx, r14d
cmp	rsi, 0x7a
sete	sil
and	edx, esi
mov	rsi, qword ptr [rax + 0x48]
or	rsi, r8
cmp	rsi, 0x30
sete	sil
and	edx, esi
mov	rsi, qword ptr [rax + 0x48]
add	rsi, rbx
cmp	rsi, 0x72
sete	sil
and	edx, esi
mov	rsi, qword ptr [rax + 0x50]
add	rsi, qword ptr [rax + 0x68]
cmp	rsi, 0x7a
sete	sil
and	edx, esi
mov	rsi, qword ptr [rax + 0x58]
or	rsi, rbx
cmp	rsi, 0x6f
sete	sil
and	edx, esi
mov	rsi, qword ptr [rax + 0x90]
and	rsi, r8
cmp	rsi, 0x30
sete	sil
or	r9, r10
and	esi, edx
cmp	r9, 0x69
sete	dl
xor	r8, r11
and	edx, esi
cmp	r8, 0x5a
sete	sil
and	esi, edx
mov	rdx, qword ptr [rax + 0x98]
xor	rdx, r12
cmp	rdx, 0x58
sete	dl
or	rdi, rbp
and	edx, esi
cmp	rdi, 0x51
sete	sil
and	r12, qword ptr [rax + 0x40]
and	edx, esi
cmp	r12, 0x30
sete	sil
and	esi, edx
mov	rdx, qword ptr [rax + 0x78]
and	rdx, r10
cmp	rdx, 0x48
sete	dl
and	esi, edx
lea	rdx, [rbp - 0x41]
cmp	rdx, 0x19
setbe	dl
sub	rbp, 0x30
cmp	rbp, 9
setbe	dil
or	edx, edi
mov	rdi, qword ptr [rax + 0x68]
and	edx, esi
lea	rsi, [rdi - 0x41]
cmp	rsi, 0x19
setbe	sil
sub	rdi, 0x30
cmp	rdi, 9
setbe	dil
or	esi, edi
and	esi, edx
lea	rdx, [rbx - 0x41]
cmp	rdx, 0x19
setbe	dl
sub	rbx, 0x30
cmp	rbx, 9
mov	rbx, qword ptr [rax + 0x78]
setbe	dil
or	edx, edi
and	edx, esi
lea	rsi, [rbx - 0x41]
cmp	rsi, 0x19
lea	rsi, [rbx - 0x30]
mov	rbx, qword ptr [rax + 0x80]
setbe	dil
cmp	rsi, 9
setbe	sil
or	esi, edi
and	edx, esi
lea	rsi, [rbx - 0x41]
cmp	rsi, 0x19
lea	rsi, [rbx - 0x30]
mov	rbx, qword ptr [rax + 0x90]
setbe	dil
cmp	rsi, 9
setbe	sil
or	esi, edi
and	edx, esi
lea	rsi, [rbx - 0x61]
cmp	rsi, 0x19
lea	rsi, [rbx - 0x30]
mov	rbx, qword ptr [rax + 0x98]
setbe	dil
cmp	rsi, 9
setbe	sil
or	esi, edi
and	edx, esi
lea	rsi, [rbx - 0x61]
cmp	rsi, 0x19
lea	rsi, [rbx - 0x30]
mov	rbx, qword ptr [rax + 0xa8]
setbe	dil
cmp	rsi, 9
setbe	sil
or	esi, edi
and	edx, esi
lea	rsi, [r11 - 0x61]
cmp	rsi, 0x19
setbe	sil
sub	r11, 0x30
cmp	r11, 9
setbe	dil
or	esi, edi
and	edx, esi
lea	rsi, [rbx - 0x61]
cmp	rsi, 0x19
lea	rsi, [rbx - 0x30]
setbe	dil
cmp	rsi, 9
setbe	sil
or	esi, edi
and	edx, esi
lea	rsi, [r10 - 0x61]
cmp	rsi, 0x19
setbe	sil
sub	r10, 0x30
cmp	r10, 9
setbe	dil
or	esi, edi
and	edx, esi
movzx	esi, cl
cmp	qword ptr [rax + 0xc0], rsi
sete	sil
and	edx, esi
movzx	esi, ch
cmp	qword ptr [rax + 0xc8], rsi
sete	sil
and	edx, esi
mov	rsi, rcx
shr	rsi, 0x10
movzx	esi, sil
cmp	qword ptr [rax + 0xd0], rsi
sete	sil
and	edx, esi
mov	rsi, rcx
shr	rsi, 0x18
movzx	esi, sil
cmp	qword ptr [rax + 0xd8], rsi
sete	sil
and	edx, esi
mov	rsi, rcx
shr	rsi, 0x20
movzx	esi, sil
cmp	qword ptr [rax + 0xe0], rsi
pop	rbx
sete	sil
pop	rbp
pop	r12
and	edx, esi
mov	rsi, rcx
pop	r14
shr	rsi, 0x28
movzx	esi, sil
cmp	qword ptr [rax + 0xe8], rsi
sete	sil
and	edx, esi
mov	rsi, rcx
shr	rsi, 0x30
movzx	esi, sil
cmp	qword ptr [rax + 0xf0], rsi
sete	sil
shr	rcx, 0x38
and	edx, esi
cmp	qword ptr [rax + 0xf8], rcx
sete	al
movzx	eax, al
and	eax, edx
xor	edx, edx
xor	ecx, ecx
xor	esi, esi
xor	edi, edi
xor	r8d, r8d
xor	r9d, r9d
xor	r10d, r10d
xor	r11d, r11d
ret