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
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.
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.
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}
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()