import binascii
import re

from pwn import *

context.log_level = "debug"

instructions_before = """
#### create literal as a function

21      # 0x80 ^ 0xa1
80      # 0xa1 is on->data
"""

instructions_to_encode = """
89
"""

#00      # end instructions  # bottom of the stack

instructions_after = """
01      # index of function
a0      # upon detection of a0, the index is popped as well (= 0x01), everything is copied into the function stack until 0xa1 is reached on ->data, then ->data is empty

#### print 0x21, 0x80

21
b0      # print 0x21

# avoid 00 by encoding it as 0x11 0x11 and XORing it
11      # 0x11 is on ->data
11      # 0x11 0x11 is on ->data
84      # 0x00 is on ->data
80      # 0x80 is on ->data
b0      # print 0x80

# copy literal from function stack to ->data (i.e. call the function)
# literal must end with 0x00 for the print ping pong

#### print the encoded instructions with a print ping pong

c1      # the whole literal function is pushed to ->code; if the function only contains instructions < 0x80, they will be copied to ->data

# ping

21
80      # 0xa1 is on ->data (end of function)

63
80      # 0xe3 is on ->data (call function 3)

30
80      # 0xb0 is on ->data (print)

11
80      # 0x91 is on ->data (duplicate)

02
a0      # create function (i.e. print and call 0xe3; if 0x00 was popped [which was duplicated], function is not called)

# pong

21
80      # 0xa1 is on ->data (end of function)

62
80      # 0xe2 is on ->data (call function 2)

30
80      # 0xb0 is on ->data (print)

11
80      # 0x91 is on ->data (duplicate)

03
a0      # create function (i.e. print and call 0xe2; if 0x00 was popped [which was duplicated], function is not called)

# print encoded instructions

c2      # call the print function (prints everything until and including 0x00 in vm->data)

#### decode the instructions and print them with a print ping pong

c1      # the whole literal function is pushed to ->code; if the function only contains instructions < 0x80, they will be copied to ->data

# ping

21
80      # 0xa1 is on ->data (end of function)

65
80      # 0xe5 is on ->data (call function 5)

11
80      # 0x91 is on ->data (duplicate)

04
a0      # create function (i.e. print and call 0xe5; if 0x00 was popped [which was duplicated], function is not called)

# pong

21
80      # 0xa1 is on ->data (end of function)

44
80      # 0xc4 is on ->data (call function 4)

30
80      # 0xb0 is on ->data (print)

04
80      # 0x84 is on ->data

01
80      # 0x81 is on ->data

04
80      # 0x84 is on ->data

05
a0      # create function (i.e. print and call 0xe4; if 0x00 was popped [which was duplicated], function is not called)

# decode and print instructions

c4
"""

def assemble(instructions):
    # print(instructions)
    # remove comments
    instructions = re.sub(r"#.*", "", instructions)
    # remove whitespaces
    instructions = re.sub(r"\s+", "", instructions)
    # print(instructions)
    return binascii.unhexlify(instructions)


def encode(bytecode):
    result = bytearray()

    for b in bytecode:
        if b == 0:
            raise ValueError("0x00 is not supported!")
        elif b < 0x80:
            result.append(0x01)
            result.append(0x10)
            result.append(b)
        else:
            result.append(0x11)
            result.append(0x11)
            result.append(b ^ 0xff)

    result.append(0x00)  # end of encoded sequence

    return bytes(result)


bytecode = (
    assemble(instructions_before)
    + encode(assemble(instructions_after))
    #+ encode(assemble(instructions_to_encode))
    + assemble(instructions_after)
)
import binascii
print(binascii.hexlify(bytecode))

#vm = process("./vm-chal")
vm = remote('3.93.128.89', 1214)
vm.recvuntil("Length of")
vm.sendline(str(len(bytecode)))
vm.recvuntil("Enter your")
vm.write(bytecode)
vm.recvline()
vm.recvline()
vm.recvline()
vm.recvline()
vm.close()

#with open("bytecode.bin", "wb") as f:
#    f.write(bytecode)
#
#with open("input.bin", "wb") as f:
#    f.write(p32(len(bytecode)))
#    f.write(bytecode)
