diff --git a/py2-kms/aes.py b/py2-kms/aes.py new file mode 100644 index 0000000..f952aeb --- /dev/null +++ b/py2-kms/aes.py @@ -0,0 +1,710 @@ +#!/usr/bin/env python + +# aes.py: implements AES - Advanced Encryption Standard +# from the SlowAES project, http://code.google.com/p/slowaes/ +# +# Copyright (c) 2008 Josh Davis ( http://www.josh-davis.org ), +# Alex Martelli ( http://www.aleax.it ) +# +# Ported from C code written by Laurent Haan ( http://www.progressive-coding.com ) +# +# Licensed under the Apache License, Version 2.0 +# http://www.apache.org/licenses/ + +import os +import math + +class AES(object): + '''AES funtions for a single block. + ''' + # Very annoying code: all is for an object, but no state is kept! + # Should just be plain functions in a AES modlule. + + #*py-kms* + v6 = False + + # valid key sizes + keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32) + + # Rijndael S-box + 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] + + # Rijndael Inverted S-box + rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, + 0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, + 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54, + 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, + 0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, + 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8, + 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, + 0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, + 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab, + 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, + 0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, + 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41, + 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, + 0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, + 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d, + 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b, + 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, + 0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, + 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60, + 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, + 0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, + 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b, + 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, + 0x21, 0x0c, 0x7d] + + def getSBoxValue(self,num): + """ Retrieves a given S-Box Value. """ + return self.sbox[num] + + def getSBoxInvert(self,num): + """ Retrieves a given Inverted S-Box Value. """ + return self.rsbox[num] + + def rotate(self, word): + """ Rijndael's key schedule rotate operation. + + Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d + Word is an char list of size 4 (32 bits overall). + """ + return word[1:] + word[:1] + + # Rijndael Rcon + Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, + 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, + 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, + 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, + 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, + 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, + 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, + 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, + 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, + 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, + 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, + 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, + 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, + 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, + 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, + 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, + 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, + 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, + 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, + 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, + 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, + 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, + 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, + 0xe8, 0xcb] + + def getRconValue(self, num): + """ Retrieves a given Rcon Value. """ + return self.Rcon[num] + + def core(self, word, iteration): + """ Key schedule core.""" + # rotate the 32-bit word 8 bits to the left + word = self.rotate(word) + # apply S-Box substitution on all 4 parts of the 32-bit word + for i in range(4): + word[i] = self.getSBoxValue(word[i]) + # XOR the output of the rcon operation with i to the first part + # (leftmost) only + word[0] = word[0] ^ self.getRconValue(iteration) + return word + + def expandKey(self, key, size, expandedKeySize): + """Rijndael's key expansion. + + Expands an 128,192,256 key into an 176,208,240 bytes key + + expandedKey is a char list of large enough size, + key is the non-expanded key. + """ + # current expanded keySize, in bytes + currentSize = 0 + rconIteration = 1 + expandedKey = [0] * expandedKeySize + + # set the 16, 24, 32 bytes of the expanded key to the input key + for j in range(size): + expandedKey[j] = key[j] + currentSize += size + + while currentSize < expandedKeySize: + # assign the previous 4 bytes to the temporary value t + t = expandedKey[currentSize - 4:currentSize] + + # every 16,24,32 bytes we apply the core schedule to t + # and increment rconIteration afterwards + if currentSize % size == 0: + t = self.core(t, rconIteration) + rconIteration += 1 + # For 256-bit keys, we add an extra sbox to the calculation + if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16): + for l in range(4): + t[l] = self.getSBoxValue(t[l]) + + # We XOR t with the four-byte block 16,24,32 bytes before the new + # expanded key. This becomes the next four bytes in the expanded key. + for m in range(4): + expandedKey[currentSize] = expandedKey[currentSize - size] ^ t[m] + currentSize += 1 + return expandedKey + + def addRoundKey(self, state, roundKey): + """ Adds (XORs) the round key to the state. """ + for i in range(16): + state[i] ^= roundKey[i] + return state + + def createRoundKey(self, expandedKey, roundKeyPointer): + """ Create a round key. + Creates a round key from the given expanded key and the + position within the expanded key. + """ + roundKey = [0] * 16 + for i in range(4): + for j in range(4): + roundKey[j * 4 + i] = expandedKey[roundKeyPointer + i * 4 + j] + return roundKey + + def galois_multiplication(self, a, b): + """ Galois multiplication of 8 bit characters a and b. """ + p = 0 + for counter in range(8): + if b & 1: p ^= a + hi_bit_set = a & 0x80 + a <<= 1 + # keep a 8 bit + a &= 0xFF + if hi_bit_set: + a ^= 0x1b + b >>= 1 + return p + + def subBytes(self, state, isInv): + """ Substitute all the values from the state with the value in the SBox + using the state value as index for the SBox. + """ + if isInv: + getter = self.getSBoxInvert + else: + getter = self.getSBoxValue + for i in range(16): + state[i] = getter(state[i]) + return state + + def shiftRows(self, state, isInv): + """ Iterate over the 4 rows and call shiftRow() with that row. """ + for i in range(4): + state = self.shiftRow(state, i * 4, i, isInv) + return state + + + def shiftRow(self, state, statePointer, nbr, isInv): + """ Each iteration shifts the row to the left by 1. """ + for i in range(nbr): + if isInv: + state[statePointer:statePointer + 4] = state[statePointer + 3:statePointer + 4] + \ + state[statePointer:statePointer + 3] + else: + state[statePointer:statePointer + 4] = state[statePointer + 1:statePointer + 4] + \ + state[statePointer:statePointer + 1] + return state + + + def mixColumns(self, state, isInv): + """Galois multiplication of the 4x4 matrix. """ + # iterate over the 4 columns + for i in range(4): + # construct one column by slicing over the 4 rows + column = state[i:i + 16:4] + # apply the mixColumn on one column + column = self.mixColumn(column, isInv) + # put the values back into the state + state[i:i + 16:4] = column + return state + + def mixColumn(self, column, isInv): + """ Galois multiplication of 1 column of the 4x4 matrix. """ + if isInv: + mult = [14, 9, 13, 11] + else: + mult = [2, 1, 1, 3] + cpy = list(column) + g = self.galois_multiplication + + column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \ + g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) + column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \ + g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) + column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \ + g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) + column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \ + g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) + return column + + def aes_round(self, state, roundKey, roundKms): + """ Applies the 4 operations of the forward round in sequence. """ + state = self.subBytes(state, False) + state = self.shiftRows(state, False) + state = self.mixColumns(state, False) + + #*py-kms* + if self.v6: + if roundKms == 4: + state[0] ^= 0x73 + if roundKms == 6: + state[0] ^= 0x09 + if roundKms == 8: + state[0] ^= 0xE4 + + state = self.addRoundKey(state, roundKey) + return state + + def aes_invRound(self, state, roundKey, roundKms): + """ Applies the 4 operations of the inverse round in sequence. """ + state = self.shiftRows(state, True) + state = self.subBytes(state, True) + state = self.addRoundKey(state, roundKey) + + #*py-kms* + if self.v6: + if roundKms == 4: + state[0] ^= 0x73 + if roundKms == 6: + state[0] ^= 0x09 + if roundKms == 8: + state[0] ^= 0xE4 + + state = self.mixColumns(state, True) + return state + + def aes_main(self, state, expandedKey, nbrRounds): + """ Perform the initial operations, the standard round, and the final + operations of the forward aes, creating a round key for each round. + """ + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) + i = 1 + while i < nbrRounds: + state = self.aes_round(state, self.createRoundKey(expandedKey, 16 * i), i) + i += 1 + state = self.subBytes(state, False) + state = self.shiftRows(state, False) + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16 * nbrRounds)) + return state + + def aes_invMain(self, state, expandedKey, nbrRounds): + """ Perform the initial operations, the standard round, and the final + operations of the inverse aes, creating a round key for each round. + """ + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 16 * nbrRounds)) + i = nbrRounds - 1 + while i > 0: + state = self.aes_invRound(state, self.createRoundKey(expandedKey, 16 * i), i) + i -= 1 + state = self.shiftRows(state, True) + state = self.subBytes(state, True) + state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) + return state + + + def encrypt(self, iput, key, size): + """ Encrypts a 128 bit input block against the given key of size specified. """ + output = [0] * 16 + # the number of rounds + nbrRounds = 0 + # the 128 bit block to encode + block = [0] * 16 + # set the number of rounds + if size == self.keySize["SIZE_128"]: + nbrRounds = 10 + elif size == self.keySize["SIZE_192"]: + nbrRounds = 12 + elif size == self.keySize["SIZE_256"]: + nbrRounds = 14 + # *py-kms* The KMS v4 parameters + elif size == 20: + nbrRounds = 11 + else: + return None + + # the expanded keySize + expandedKeySize = 16 * (nbrRounds + 1) + + # Set the block values, for the block: + # a0,0 a0,1 a0,2 a0,3 + # a1,0 a1,1 a1,2 a1,3 + # a2,0 a2,1 a2,2 a2,3 + # a3,0 a3,1 a3,2 a3,3 + # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 + # + # iterate over the columns and over the rows + for i in range(4): + for j in range(4): + block[i + j * 4] = iput[i * 4 + j] + + # expand the key into an 176, 208, 240 bytes key + # the expanded key + expandedKey = self.expandKey(key, size, expandedKeySize) + + # encrypt the block using the expandedKey + block = self.aes_main(block, expandedKey, nbrRounds) + + # unmap the block again into the output + for k in range(4): + for l in range(4): + output[k * 4 + l] = block[k + l * 4] + return output + + def decrypt(self, iput, key, size): + """ decrypts a 128 bit input block against the given key of size specified. """ + output = [0] * 16 + # the number of rounds + nbrRounds = 0 + # the 128 bit block to decode + block = [0] * 16 + # set the number of rounds + if size == self.keySize["SIZE_128"]: + nbrRounds = 10 + elif size == self.keySize["SIZE_192"]: + nbrRounds = 12 + elif size == self.keySize["SIZE_256"]: + nbrRounds = 14 + #*py-kms* The KMS v4 parameters. + elif size == 20: + nbrRounds = 11 + else: + return None + + # the expanded keySize + expandedKeySize = 16 * (nbrRounds + 1) + + # Set the block values, for the block: + # a0,0 a0,1 a0,2 a0,3 + # a1,0 a1,1 a1,2 a1,3 + # a2,0 a2,1 a2,2 a2,3 + # a3,0 a3,1 a3,2 a3,3 + # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 + + # iterate over the columns and the rows + for i in range(4): + for j in range(4): + block[i + j * 4] = iput[i * 4 + j] + # expand the key into an 176, 208, 240 bytes key + expandedKey = self.expandKey(key, size, expandedKeySize) + # decrypt the block using the expandedKey + block = self.aes_invMain(block, expandedKey, nbrRounds) + # unmap the block again into the output + for k in range(4): + for l in range(4): + output[k * 4 + l] = block[k + l * 4] + return output + + +class AESModeOfOperation( object ): + '''Handles AES with plaintext consistingof multiple blocks. + Choice of block encoding modes: OFT, CFB, CBC + ''' + # Very annoying code: all is for an object, but no state is kept! + # Should just be plain functions in an AES_BlockMode module. + aes = AES() + + # structure of supported modes of operation + modeOfOperation = dict(OFB=0, CFB=1, CBC=2) + + # converts a 16 character string into a number array + def convertString(self, string, start, end, mode): + if end - start > 16: + end = start + 16 + if mode == self.modeOfOperation["CBC"]: + ar = [0] * 16 + else: ar = [] + + i = start + j = 0 + while len(ar) < end - start: + ar.append(0) + while i < end: + ar[j] = ord(string[i]) + j += 1 + i += 1 + return ar + + + def encrypt(self, stringIn, mode, key, size, IV): + """ Mode of Operation Encryption + stringIn - Input String + mode - mode of type modeOfOperation + hexKey - a hex key of the bit length size + size - the bit length of the key + hexIV - the 128 bit hex Initilization Vector + """ + if len(key) % size: + return None + if len(IV) % 16: + return None + # the AES input/output + plaintext = [] + iput = [0] * 16 + output = [] + ciphertext = [0] * 16 + # the output cipher string + cipherOut = [] + # char firstRound + firstRound = True + if stringIn != None: + for j in range(int(math.ceil(float(len(stringIn))/16))): + start = j * 16 + end = j * 16 + 16 + if end > len(stringIn): + end = len(stringIn) + plaintext = self.convertString(stringIn, start, end, mode) + + if mode == self.modeOfOperation["CFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(plaintext) - 1 < i: + ciphertext[i] = 0 ^ output[i] + elif len(output) - 1 < i: + ciphertext[i] = plaintext[i] ^ 0 + elif len(plaintext) - 1 < i and len(output) < i: + ciphertext[i] = 0 ^ 0 + else: + ciphertext[i] = plaintext[i] ^ output[i] + for k in range(end - start): + cipherOut.append(ciphertext[k]) + iput = ciphertext + + elif mode == self.modeOfOperation["OFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(plaintext) - 1 < i: + ciphertext[i] = 0 ^ output[i] + elif len(output) - 1 < i: + ciphertext[i] = plaintext[i] ^ 0 + elif len(plaintext) - 1 < i and len(output) < i: + ciphertext[i] = 0 ^ 0 + else: + ciphertext[i] = plaintext[i] ^ output[i] + for k in range(end - start): + cipherOut.append(ciphertext[k]) + iput = output + + elif mode == self.modeOfOperation["CBC"]: + for i in range(16): + if firstRound: + iput[i] = plaintext[i] ^ IV[i] + else: + iput[i] = plaintext[i] ^ ciphertext[i] + firstRound = False + ciphertext = self.aes.encrypt(iput, key, size) + # always 16 bytes because of the padding for CBC + for k in range(16): + cipherOut.append(ciphertext[k]) + return mode, len(stringIn), cipherOut + + + def decrypt(self, cipherIn, originalsize, mode, key, size, IV): + """ Mode of Operation Decryption + + cipherIn - Encrypted String + originalsize - The unencrypted string length - required for CBC + mode - mode of type modeOfOperation + key - a number array of the bit length size + size - the bit length of the key + IV - the 128 bit number array Initilization Vector + """ + if len(key) % size: + return None + if len(IV) % 16: + return None + # the AES input/output + ciphertext = [] + iput = [] + output = [] + plaintext = [0] * 16 + # the output plain text character list + chrOut = [] + # char firstRound + firstRound = True + if cipherIn != None: + for j in range(int(math.ceil(float(len(cipherIn))/16))): + start = j * 16 + end = j * 16 + 16 + if end > len(cipherIn): + end = len(cipherIn) + ciphertext = cipherIn[start:end] + + if mode == self.modeOfOperation["CFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(output) - 1 < i: + plaintext[i] = 0 ^ ciphertext[i] + elif len(ciphertext) - 1 < i: + plaintext[i] = output[i] ^ 0 + elif len(output) - 1 < i and len(ciphertext) < i: + plaintext[i] = 0 ^ 0 + else: + plaintext[i] = output[i] ^ ciphertext[i] + for k in range(end - start): + chrOut.append(chr(plaintext[k])) + iput = ciphertext + + elif mode == self.modeOfOperation["OFB"]: + if firstRound: + output = self.aes.encrypt(IV, key, size) + firstRound = False + else: + output = self.aes.encrypt(iput, key, size) + for i in range(16): + if len(output) - 1 < i: + plaintext[i] = 0 ^ ciphertext[i] + elif len(ciphertext) - 1 < i: + plaintext[i] = output[i] ^ 0 + elif len(output) - 1 < i and len(ciphertext) < i: + plaintext[i] = 0 ^ 0 + else: + plaintext[i] = output[i] ^ ciphertext[i] + for k in range(end - start): + chrOut.append(chr(plaintext[k])) + iput = output + + elif mode == self.modeOfOperation["CBC"]: + output = self.aes.decrypt(ciphertext, key, size) + for i in range(16): + if firstRound: + plaintext[i] = IV[i] ^ output[i] + else: + plaintext[i] = iput[i] ^ output[i] + firstRound = False + if originalsize is not None and originalsize < end: + for k in range(originalsize - start): + chrOut.append(chr(plaintext[k])) + else: + for k in range(end - start): + chrOut.append(chr(plaintext[k])) + iput = ciphertext + return "".join(chrOut) + + +def append_PKCS7_padding(s): + """ Return s padded to a multiple of 16-bytes by PKCS7 padding. """ + numpads = 16 - (len(s)%16) + return s + numpads*chr(numpads) + +def strip_PKCS7_padding(s): + """ Return s stripped of PKCS7 padding. """ + if len(s)%16 or not s: + raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) + numpads = ord(s[-1]) + if numpads > 16: + raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) + return s[:-numpads] + +def encryptData(key, data, mode=AESModeOfOperation.modeOfOperation["CBC"]): + """ Encrypt `data` using `key` + + `key` should be a string of bytes. + returned cipher is a string of bytes prepended with the initialization vector. + """ + key = map(ord, key) + if mode == AESModeOfOperation.modeOfOperation["CBC"]: + data = append_PKCS7_padding(data) + keysize = len(key) + assert keysize in AES.keySize.values(), 'invalid key size: %s' % keysize + # create a new iv using random data + iv = [ord(i) for i in os.urandom(16)] + moo = AESModeOfOperation() + (mode, length, ciph) = moo.encrypt(data, mode, key, keysize, iv) + # With padding, the original length does not need to be known. It's a bad + # idea to store the original message length prepend the iv. + return ''.join(map(chr, iv)) + ''.join(map(chr, ciph)) + +def decryptData(key, data, mode=AESModeOfOperation.modeOfOperation["CBC"]): + """ Decrypt `data` using `key` + + `key` should be a string of bytes. + `data` should have the initialization vector prepended as a string of ordinal values. + """ + key = map(ord, key) + keysize = len(key) + assert keysize in AES.keySize.values(), 'invalid key size: %s' % keysize + # iv is first 16 bytes + iv = map(ord, data[:16]) + data = map(ord, data[16:]) + moo = AESModeOfOperation() + decr = moo.decrypt(data, None, mode, key, keysize, iv) + if mode == AESModeOfOperation.modeOfOperation["CBC"]: + decr = strip_PKCS7_padding(decr) + return decr + +def generateRandomKey(keysize): + """ Generates a key from random data of length `keysize`. + The returned key is a string of bytes. + """ + if keysize not in (16, 24, 32): + emsg = 'Invalid keysize, %s. Should be one of (16, 24, 32).' + raise ValueError, emsg % keysize + return os.urandom(keysize) + +def testStr(cleartext, keysize=16, modeName = "CBC"): + """ Test with random key, choice of mode. """ + print 'Random key test', 'Mode:', modeName + print 'cleartext:', cleartext + key = generateRandomKey(keysize) + print 'Key:', [ord(x) for x in key] + mode = AESModeOfOperation.modeOfOperation[modeName] + cipher = encryptData(key, cleartext, mode) + print 'Cipher:', [ord(x) for x in cipher] + decr = decryptData(key, cipher, mode) + print 'Decrypted:', decr + + +if __name__ == "__main__": + moo = AESModeOfOperation() + cleartext = "This is a test with several blocks!" + cipherkey = [143, 194, 34, 208, 145, 203, 230, 143, 177, 246, 97, 206, 145, 92, 255, 84] + iv = [103, 35, 148, 239, 76, 213, 47, 118, 255, 222, 123, 176, 106, 134, 98, 92] + mode, orig_len, ciph = moo.encrypt(cleartext, moo.modeOfOperation["CBC"], + cipherkey, moo.aes.keySize["SIZE_128"], iv) + print 'm=%s, ol=%s (%s), ciph=%s' % (mode, orig_len, len(cleartext), ciph) + decr = moo.decrypt(ciph, orig_len, mode, cipherkey, moo.aes.keySize["SIZE_128"], iv) + print decr + testStr(cleartext, 16, "CBC") diff --git a/py2-kms/client.py b/py2-kms/client.py new file mode 100644 index 0000000..952d01b --- /dev/null +++ b/py2-kms/client.py @@ -0,0 +1,260 @@ +#!/usr/bin/env python + +import argparse +import binascii +import datetime +import random +import socket +import string +import struct +import sys +import uuid +import logging +import os + +import filetimes, rpcBind, rpcRequest +from dcerpc import MSRPCHeader, MSRPCBindNak, MSRPCRequestHeader, MSRPCRespHeader +from kmsBase import kmsBase, UUID +from kmsRequestV4 import kmsRequestV4 +from kmsRequestV5 import kmsRequestV5 +from kmsRequestV6 import kmsRequestV6 +from rpcBase import rpcBase +from formatText import shell_message, justify + +config = {} + +def main(): + parser = argparse.ArgumentParser() + parser.add_argument("ip", action="store", help='The IP address or hostname of the KMS server.', type=str) + parser.add_argument("port", nargs="?", action="store", default=1688, + help='The port the KMS service is listening on. The default is \"1688\".', type=int) + parser.add_argument("-m", "--mode", dest="mode", + choices=["WindowsVista","Windows7","Windows8","Windows81","Windows10","Office2010","Office2013","Office2016"], default="Windows81", + help='Use this flag to manually specify a Microsoft product for testing the server. The default is \"Windows81\".', type=str) + parser.add_argument("-c", "--cmid", dest="cmid", default=None, + help='Use this flag to manually specify a CMID to use. If no CMID is specified, a random CMID will be generated.', type=str) + parser.add_argument("-n", "--name", dest="machineName", default=None, + help='Use this flag to manually specify an ASCII machineName to use. If no machineName is specified,\ +a random machineName will be generated.', type=str) + parser.add_argument("-v", "--loglevel", dest="loglevel", action="store", default="ERROR", choices=["CRITICAL", "ERROR", "WARNING", "INFO", "DEBUG"], + help='Use this flag to set a Loglevel. The default is \"ERROR\".', type=str) + parser.add_argument("-f", "--logfile", dest="logfile", action="store", default=os.path.dirname(os.path.abspath( __file__ )) + "/py2kms_client.log", + help='Use this flag to set an output Logfile. The default is \"pykms_client.log\".', type=str) + + config.update(vars(parser.parse_args())) + + logging.basicConfig(level=config['loglevel'], format='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%a, %d %b %Y %H:%M:%S', filename=config['logfile'], filemode='w') + + checkConfig() + config['call_id'] = 1 + updateConfig() + s = socket.socket() + logging.info("Connecting to %s on port %d..." % (config['ip'], config['port'])) + s.connect((config['ip'], config['port'])) + logging.info("Connection successful !") + binder = rpcBind.handler(None, config) + RPC_Bind = str(binder.generateRequest()) + logging.info("Sending RPC bind request...") + shell_message(nshell = [-1, 1]) + s.send(RPC_Bind) + try: + shell_message(nshell = [-4, 7]) + bindResponse = s.recv(1024) + except socket.error, e: + if e[0] == 104: + logging.error("Connection reset by peer. Exiting...") + sys.exit() + else: + raise + if bindResponse == '' or not bindResponse: + logging.error("No data received ! Exiting...") + sys.exit() + packetType = MSRPCHeader(bindResponse)['type'] + if packetType == rpcBase.packetType['bindAck']: + logging.info("RPC bind acknowledged.") + shell_message(nshell = 8) + kmsRequest = createKmsRequest() + requester = rpcRequest.handler(kmsRequest, config) + s.send(str(requester.generateRequest())) + shell_message(nshell = [-1, 12]) + response = s.recv(1024) + logging.debug("Response: \n%s\n" % justify(binascii.b2a_hex(response))) + shell_message(nshell = [-4, 20]) + parsed = MSRPCRespHeader(response) + kmsData = readKmsResponse(parsed['pduData'], kmsRequest, config) + kmsResp = kmsData['response'] + + try: + hwid = kmsData['hwid'] + except: + hwid = None + logging.info("KMS Host ePID: %s" % kmsResp['kmsEpid'].decode('utf-16le').encode('utf-8')) + if hwid is not None: + logging.info("KMS Host HWID: %s" % binascii.b2a_hex(hwid).upper()) + + logging.info("KMS Host Current Client Count: %s" % kmsResp['currentClientCount']) + logging.info("KMS VL Activation Interval: %s" % kmsResp['vLActivationInterval']) + logging.info("KMS VL Renewal Interval: %s" % kmsResp['vLRenewalInterval']) + shell_message(nshell = 21) + + elif packetType == rpcBase.packetType['bindNak']: + logging.info(justify(MSRPCBindNak(bindResponse).dump(print_to_stdout = False))) + sys.exit() + else: + logging.critical("Something went wrong.") + sys.exit() + + +def checkConfig(): + if config['cmid'] is not None: + try: + uuid.UUID(config['cmid']) + except: + logging.error("Bad CMID. Exiting...") + sys.exit() + if config['machineName'] is not None: + if len(config['machineName']) < 2 or len(config['machineName']) > 63: + logging.error("machineName must be between 2 and 63 characters in length.") + sys.exit() + +def updateConfig(): + if config['mode'] == 'WindowsVista': + config['RequiredClientCount'] = 25 + config['KMSProtocolMajorVersion'] = 4 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" + config['KMSClientSkuID'] = "cfd8ff08-c0d7-452b-9f60-ef5c70c32094" + config['KMSClientKMSCountedID'] = "212a64dc-43b1-4d3d-a30c-2fc69d2095c6" + elif config['mode'] == 'Windows7': + config['RequiredClientCount'] = 25 + config['KMSProtocolMajorVersion'] = 4 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" + config['KMSClientSkuID'] = "ae2ee509-1b34-41c0-acb7-6d4650168915" + config['KMSClientKMSCountedID'] = "7fde5219-fbfa-484a-82c9-34d1ad53e856" + elif config['mode'] == 'Windows8': + config['RequiredClientCount'] = 25 + config['KMSProtocolMajorVersion'] = 5 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" + config['KMSClientSkuID'] = "458e1bec-837a-45f6-b9d5-925ed5d299de" + config['KMSClientKMSCountedID'] = "3c40b358-5948-45af-923b-53d21fcc7e79" + elif config['mode'] == 'Windows81': + config['RequiredClientCount'] = 25 + config['KMSProtocolMajorVersion'] = 6 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" + config['KMSClientSkuID'] = "81671aaf-79d1-4eb1-b004-8cbbe173afea" + config['KMSClientKMSCountedID'] = "cb8fc780-2c05-495a-9710-85afffc904d7" + elif config['mode'] == 'Windows10': + config['RequiredClientCount'] = 25 + config['KMSProtocolMajorVersion'] = 6 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" + config['KMSClientSkuID'] = "73111121-5638-40f6-bc11-f1d7b0d64300" + config['KMSClientKMSCountedID'] = "58e2134f-8e11-4d17-9cb2-91069c151148" + elif config['mode'] == 'Office2010': + config['RequiredClientCount'] = 5 + config['KMSProtocolMajorVersion'] = 4 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "59a52881-a989-479d-af46-f275c6370663" + config['KMSClientSkuID'] = "6f327760-8c5c-417c-9b61-836a98287e0c" + config['KMSClientKMSCountedID'] = "e85af946-2e25-47b7-83e1-bebcebeac611" + elif config['mode'] == 'Office2013': + config['RequiredClientCount'] = 5 + config['KMSProtocolMajorVersion'] = 5 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "0ff1ce15-a989-479d-af46-f275c6370663" + config['KMSClientSkuID'] = "b322da9c-a2e2-4058-9e4e-f59a6970bd69" + config['KMSClientKMSCountedID'] = "e6a6f1bf-9d40-40c3-aa9f-c77ba21578c0" + elif config['mode'] == 'Office2016': + config['RequiredClientCount'] = 5 + config['KMSProtocolMajorVersion'] = 6 + config['KMSProtocolMinorVersion'] = 0 + config['KMSClientLicenseStatus'] = 2 + config['KMSClientAppID'] = "0ff1ce15-a989-479d-af46-f275c6370663" + config['KMSClientSkuID'] = "d450596f-894d-49e0-966a-fd39ed4c4c64" + config['KMSClientKMSCountedID'] = "85b5f61b-320b-4be3-814a-b76b2bfafc82" + +def createKmsRequestBase(): + requestDict = kmsBase.kmsRequestStruct() + requestDict['versionMinor'] = config['KMSProtocolMinorVersion'] + requestDict['versionMajor'] = config['KMSProtocolMajorVersion'] + requestDict['isClientVm'] = 0 + requestDict['licenseStatus'] = config['KMSClientLicenseStatus'] + requestDict['graceTime'] = 43200 + requestDict['applicationId'] = UUID(uuid.UUID(config['KMSClientAppID']).bytes_le) + requestDict['skuId'] = UUID(uuid.UUID(config['KMSClientSkuID']).bytes_le) + requestDict['kmsCountedId'] = UUID(uuid.UUID(config['KMSClientKMSCountedID']).bytes_le) + requestDict['clientMachineId'] = UUID(uuid.UUID(config['cmid']).bytes_le if (config['cmid'] is not None) else uuid.uuid4().bytes_le) + requestDict['previousClientMachineId'] = '\0' * 16 #requestDict['clientMachineId'] # I'm pretty sure this is supposed to be a null UUID. + requestDict['requiredClientCount'] = config['RequiredClientCount'] + requestDict['requestTime'] = filetimes.dt_to_filetime(datetime.datetime.utcnow()) + requestDict['machineName'] = (config['machineName'] if (config['machineName'] is not None) else ''.join(random.choice(string.letters + string.digits) for i in range(random.randint(2,63)))).encode('utf-16le') + requestDict['mnPad'] = '\0'.encode('utf-16le') * (63 - len(requestDict['machineName'].decode('utf-16le'))) + + # Debug Stuff + shell_message(nshell = 9) + logging.debug("Request Base Dictionary: \n%s\n" % justify(requestDict.dump(print_to_stdout = False))) + + return requestDict + +def createKmsRequest(): + # Update the call ID + config['call_id'] += 1 + + # KMS Protocol Major Version + if config['KMSProtocolMajorVersion'] == 4: + handler = kmsRequestV4(None, config) + elif config['KMSProtocolMajorVersion'] == 5: + handler = kmsRequestV5(None, config) + elif config['KMSProtocolMajorVersion'] == 6: + handler = kmsRequestV6(None, config) + else: + return None + + requestBase = createKmsRequestBase() + return handler.generateRequest(requestBase) + +def readKmsResponse(data, request, config): + if config['KMSProtocolMajorVersion'] == 4: + logging.info("Received V4 response") + response = readKmsResponseV4(data, request) + elif config['KMSProtocolMajorVersion'] == 5: + logging.info("Received V5 response") + response = readKmsResponseV5(data) + elif config['KMSProtocolMajorVersion'] == 6: + logging.info("Received V6 response") + response = readKmsResponseV6(data) + else: + logging.info("Unhandled response version: %d.%d" % (config['KMSProtocolMajorVersion'], config['KMSProtocolMinorVersion'])) + logging.info("I'm not even sure how this happened...") + return response + +def readKmsResponseV4(data, request): + response = kmsRequestV4.ResponseV4(data) + hashed = kmsRequestV4(data, config).generateHash(bytearray(str(response['response']))) + logging.info("Response Hash has expected value: ", hashed == response['hash']) + return response + +def readKmsResponseV5(data): + response = kmsRequestV5.ResponseV5(data) + decrypted = kmsRequestV5(data, config).decryptResponse(response) + return decrypted + +def readKmsResponseV6(data): + response = kmsRequestV6.ResponseV5(data) + decrypted = kmsRequestV6(data, config).decryptResponse(response) + message = decrypted['message'] + return message + +if __name__ == "__main__": + main() diff --git a/py2-kms/dcerpc.py b/py2-kms/dcerpc.py new file mode 100644 index 0000000..dc146c5 --- /dev/null +++ b/py2-kms/dcerpc.py @@ -0,0 +1,365 @@ +#!/usr/bin/env python + +# Copyright (c) 2003-2012 CORE Security Technologies +# +# This software is provided under under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# +# $Id: dcerpc.py 917 2013-11-10 20:47:57Z bethus $ +# +# Partial C706.pdf + [MS-RPCE] implementation +# +# ToDo: +# [ ] Take out all the security provider stuff out of here (e.g. RPC_C_AUTHN_WINNT) +# and put it elsewhere. This will make the coder cleaner and easier to add +# more SSP (e.g. NETLOGON) +# + +from structure import Structure,pack,unpack + +# MS/RPC Constants +MSRPC_REQUEST = 0x00 +MSRPC_PING = 0x01 +MSRPC_RESPONSE = 0x02 +MSRPC_FAULT = 0x03 +MSRPC_WORKING = 0x04 +MSRPC_NOCALL = 0x05 +MSRPC_REJECT = 0x06 +MSRPC_ACK = 0x07 +MSRPC_CL_CANCEL = 0x08 +MSRPC_FACK = 0x09 +MSRPC_CANCELACK = 0x0A +MSRPC_BIND = 0x0B +MSRPC_BINDACK = 0x0C +MSRPC_BINDNAK = 0x0D +MSRPC_ALTERCTX = 0x0E +MSRPC_ALTERCTX_R= 0x0F +MSRPC_AUTH3 = 0x10 +MSRPC_SHUTDOWN = 0x11 +MSRPC_CO_CANCEL = 0x12 +MSRPC_ORPHANED = 0x13 + +# MS/RPC Packet Flags +MSRPC_FIRSTFRAG = 0x01 +MSRPC_LASTFRAG = 0x02 + +# For PDU types bind, bind_ack, alter_context, and +# alter_context_resp, this flag MUST be interpreted as PFC_SUPPORT_HEADER_SIGN +MSRPC_SUPPORT_SIGN = 0x04 + +#For the +#remaining PDU types, this flag MUST be interpreted as PFC_PENDING_CANCEL. +MSRPC_PENDING_CANCEL= 0x04 + +MSRPC_NOTAFRAG = 0x04 +MSRPC_RECRESPOND = 0x08 +MSRPC_NOMULTIPLEX = 0x10 +MSRPC_NOTFORIDEMP = 0x20 +MSRPC_NOTFORBCAST = 0x40 +MSRPC_NOUUID = 0x80 + +# Auth Types - Security Providers +RPC_C_AUTHN_NONE = 0x00 +RPC_C_AUTHN_GSS_NEGOTIATE = 0x09 +RPC_C_AUTHN_WINNT = 0x0A +RPC_C_AUTHN_GSS_SCHANNEL = 0x0E +RPC_C_AUTHN_GSS_KERBEROS = 0x10 +RPC_C_AUTHN_NETLOGON = 0x44 +RPC_C_AUTHN_DEFAULT = 0xFF + +# Auth Levels +RPC_C_AUTHN_LEVEL_NONE = 1 +RPC_C_AUTHN_LEVEL_CONNECT = 2 +RPC_C_AUTHN_LEVEL_CALL = 3 +RPC_C_AUTHN_LEVEL_PKT = 4 +RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5 +RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6 + +#Reasons for rejection of a context element, included in bind_ack result reason +rpc_provider_reason = { + 0 : 'reason_not_specified', + 1 : 'abstract_syntax_not_supported', + 2 : 'proposed_transfer_syntaxes_not_supported', + 3 : 'local_limit_exceeded', + 4 : 'protocol_version_not_specified', + 8 : 'authentication_type_not_recognized', + 9 : 'invalid_checksum' +} + +MSRPC_CONT_RESULT_ACCEPT = 0 +MSRPC_CONT_RESULT_USER_REJECT = 1 +MSRPC_CONT_RESULT_PROV_REJECT = 2 + +#Results of a presentation context negotiation +rpc_cont_def_result = { + 0 : 'acceptance', + 1 : 'user_rejection', + 2 : 'provider_rejection' +} + +#status codes, references: +#http://msdn.microsoft.com/library/default.asp?url=/library/en-us/rpc/rpc/rpc_return_values.asp +#http://msdn.microsoft.com/library/default.asp?url=/library/en-us/randz/protocol/common_return_values.asp +#winerror.h +#http://www.opengroup.org/onlinepubs/9629399/apdxn.htm + +rpc_status_codes = { + 0x00000005L : 'rpc_s_access_denied', + 0x00000008L : 'Authentication type not recognized', + 0x000006D8L : 'rpc_fault_cant_perform', + 0x000006C6L : 'rpc_x_invalid_bound', # the arrays bound are invalid + 0x000006E4L : 'rpc_s_cannot_support: The requested operation is not supported.', # some operation is not supported + 0x000006F7L : 'rpc_x_bad_stub_data', # the stub data is invalid, doesn't match with the IDL definition + 0x1C010001L : 'nca_s_comm_failure', # unable to get response from server: + 0x1C010002L : 'nca_s_op_rng_error', # bad operation number in call + 0x1C010003L : 'nca_s_unk_if', # unknown interface + 0x1C010006L : 'nca_s_wrong_boot_time', # client passed server wrong server boot time + 0x1C010009L : 'nca_s_you_crashed', # a restarted server called back a client + 0x1C01000BL : 'nca_s_proto_error', # someone messed up the protocol + 0x1C010013L : 'nca_s_out_args_too_big ', # output args too big + 0x1C010014L : 'nca_s_server_too_busy', # server is too busy to handle call + 0x1C010015L : 'nca_s_fault_string_too_long', # string argument longer than declared max len + 0x1C010017L : 'nca_s_unsupported_type ', # no implementation of generic operation for object + 0x1C000001L : 'nca_s_fault_int_div_by_zero', + 0x1C000002L : 'nca_s_fault_addr_error ', + 0x1C000003L : 'nca_s_fault_fp_div_zero', + 0x1C000004L : 'nca_s_fault_fp_underflow', + 0x1C000005L : 'nca_s_fault_fp_overflow', + 0x1C000006L : 'nca_s_fault_invalid_tag', + 0x1C000007L : 'nca_s_fault_invalid_bound ', + 0x1C000008L : 'nca_s_rpc_version_mismatch', + 0x1C000009L : 'nca_s_unspec_reject ', + 0x1C00000AL : 'nca_s_bad_actid', + 0x1C00000BL : 'nca_s_who_are_you_failed', + 0x1C00000CL : 'nca_s_manager_not_entered ', + 0x1C00000DL : 'nca_s_fault_cancel', + 0x1C00000EL : 'nca_s_fault_ill_inst', + 0x1C00000FL : 'nca_s_fault_fp_error', + 0x1C000010L : 'nca_s_fault_int_overflow', + 0x1C000012L : 'nca_s_fault_unspec', + 0x1C000013L : 'nca_s_fault_remote_comm_failure ', + 0x1C000014L : 'nca_s_fault_pipe_empty ', + 0x1C000015L : 'nca_s_fault_pipe_closed', + 0x1C000016L : 'nca_s_fault_pipe_order ', + 0x1C000017L : 'nca_s_fault_pipe_discipline', + 0x1C000018L : 'nca_s_fault_pipe_comm_error', + 0x1C000019L : 'nca_s_fault_pipe_memory', + 0x1C00001AL : 'nca_s_fault_context_mismatch ', + 0x1C00001BL : 'nca_s_fault_remote_no_memory ', + 0x1C00001CL : 'nca_s_invalid_pres_context_id', + 0x1C00001DL : 'nca_s_unsupported_authn_level', + 0x1C00001FL : 'nca_s_invalid_checksum ', + 0x1C000020L : 'nca_s_invalid_crc', + 0x1C000021L : 'nca_s_fault_user_defined', + 0x1C000022L : 'nca_s_fault_tx_open_failed', + 0x1C000023L : 'nca_s_fault_codeset_conv_error', + 0x1C000024L : 'nca_s_fault_object_not_found ', + 0x1C000025L : 'nca_s_fault_no_client_stub' +} + +class Exception(Exception): + pass + +# Context Item +class CtxItem(Structure): + structure = ( + ('ContextID',' 0 else 0)'), + ('pduData',':'), + ('_pad', '_-pad','(4 - ((self._SIZE + len(self["pduData"])) & 3) & 3)'), + ('pad', ':'), + ('_sec_trailer', '_-sec_trailer', '8 if self["auth_len"] > 0 else 0'), + ('sec_trailer',':'), + ('auth_dataLen','_-auth_data','self["auth_len"]'), + ('auth_data',':'), + ) + + def __init__(self, data = None, alignment = 0): + Structure.__init__(self,data, alignment) + if data is None: + self['ver_major'] = 5 + self['ver_minor'] = 0 + self['flags'] = MSRPC_FIRSTFRAG | MSRPC_LASTFRAG + self['type'] = MSRPC_REQUEST + self.__frag_len_set = 0 + self['auth_len'] = 0 + self['pduData'] = '' + self['auth_data'] = '' + self['sec_trailer'] = '' + self['pad'] = '' + + def get_header_size(self): + return self._SIZE + + def get_packet(self): + if self['auth_data'] != '': + self['auth_len'] = len(self['auth_data']) + # The sec_trailer structure MUST be 4-byte aligned with respect to + # the beginning of the PDU. Padding octets MUST be used to align the + # sec_trailer structure if its natural beginning is not already 4-byte aligned + ##self['pad'] = '\xAA' * (4 - ((self._SIZE + len(self['pduData'])) & 3) & 3) + + return self.getData() + +class MSRPCRequestHeader(MSRPCHeader): + _SIZE = 24 + commonHdr = MSRPCHeader.commonHdr + ( + ('alloc_hint',' 0 else 0'), + ('sec_trailer',':'), + ('auth_dataLen','_-auth_data','self["auth_len"]'), + ('auth_data',':'), + ) + def __init__(self, data = None, alignment = 0): + self.__ctx_items = [] + Structure.__init__(self,data,alignment) + if data is None: + self['Pad'] = '' + self['ctx_items'] = '' + self['sec_trailer'] = '' + self['auth_data'] = '' + + def getCtxItems(self): + return self.__ctx_items + + def getCtxItem(self,index): + return self.__ctx_items[index-1] + + def fromString(self, data): + Structure.fromString(self,data) + # Parse the ctx_items + data = self['ctx_items'] + for i in range(self['ctx_num']): + item = CtxItemResult(data) + self.__ctx_items.append(item) + data = data[len(item):] + +class MSRPCBindNak(Structure): + structure = ( + ('RejectedReason',' +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions are +# met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in the +# documentation and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS +# IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +# TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A +# PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED +# TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR +# PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF +# LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING +# NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +"""Tools to convert between Python datetime instances and Microsoft times. +""" +from datetime import datetime, timedelta, tzinfo +from calendar import timegm + + +# http://support.microsoft.com/kb/167296 +# How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME +EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS file time +HUNDREDS_OF_NANOSECONDS = 10000000 + + +ZERO = timedelta(0) +HOUR = timedelta(hours=1) + + +class UTC(tzinfo): + """UTC""" + def utcoffset(self, dt): + return ZERO + + def tzname(self, dt): + return "UTC" + + def dst(self, dt): + return ZERO + + +utc = UTC() + + +def dt_to_filetime(dt): + """Converts a datetime to Microsoft filetime format. If the object is + time zone-naive, it is forced to UTC before conversion. + + >>> "%.0f" % dt_to_filetime(datetime(2009, 7, 25, 23, 0)) + '128930364000000000' + + >>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0, tzinfo=utc)) + '116444736000000000' + + >>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0)) + '116444736000000000' + + >>> dt_to_filetime(datetime(2009, 7, 25, 23, 0, 0, 100)) + 128930364000001000 + """ + if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None): + dt = dt.replace(tzinfo=utc) + ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS) + return ft + (dt.microsecond * 10) + + +def filetime_to_dt(ft): + """Converts a Microsoft filetime number to a Python datetime. The new + datetime object is time zone-naive but is equivalent to tzinfo=utc. + + >>> filetime_to_dt(116444736000000000) + datetime.datetime(1970, 1, 1, 0, 0) + + >>> filetime_to_dt(128930364000000000) + datetime.datetime(2009, 7, 25, 23, 0) + + >>> filetime_to_dt(128930364000001000) + datetime.datetime(2009, 7, 25, 23, 0, 0, 100) + """ + # Get seconds and remainder in terms of Unix epoch + (s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS) + # Convert to datetime object + dt = datetime.utcfromtimestamp(s) + # Add remainder in as microseconds. Python 3.2 requires an integer + dt = dt.replace(microsecond=(ns100 // 10)) + return dt + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + diff --git a/py2-kms/formatText.py b/py2-kms/formatText.py new file mode 100644 index 0000000..b33c017 --- /dev/null +++ b/py2-kms/formatText.py @@ -0,0 +1,96 @@ +#!/usr/bin/env python + +import sys + +def justify(astring, indent = 35, break_every = 100): + str_indent = ('\n' + ' ' * indent) + splitted = astring.split('\n') + longests = [(n, s) for n, s in enumerate(splitted) if len(s) >= break_every] + + for longest in longests: + lines = [] + for i in range(0, len(longest[1]), break_every): + lines.append(longest[1][i : i + break_every]) + splitted[longest[0]] = str_indent.join(lines) + + if len(splitted) > 1: + justy = str_indent.join(splitted) + else: + justy = str_indent + str_indent.join(splitted) + + return justy + + +class ShellStyle(object): + def style(self, s, style): + return style + s + '\033[0m' + + def green(self, s): + return self.style(s, '\033[92m') + + def blue(self, s): + return self.style(s, '\033[94m') + + def yellow(self, s): + return self.style(s, '\033[93m') + + def red(self, s): + return self.style(s, '\033[91m') + + def magenta(self, s): + return self.style(s, '\033[95m') + + def cyan(self, s): + return self.style(s, '\033[96m') + + def white(self, s): + return self.style(s, '\033[97m') + + def bold(self, s): + return self.style(s, '\033[1m') + + def underline(self, s): + return self.style(s, '\033[4m') + + + + +def shell_message(nshell): + + shelldict = {0: ShellStyle().yellow("Client generating RPC Bind Request..."), + 1: ShellStyle().yellow("Client sending RPC Bind Request...") + ShellStyle().red("\t\t\t\t===============>"), + 2: ShellStyle().red("===============>\t\t") + ShellStyle().yellow("Server received RPC Bind Request !!!"), + 3: ShellStyle().yellow("\t\t\t\tServer parsing RPC Bind Request..."), + 4: ShellStyle().yellow("\t\t\t\tServer generating RPC Bind Response..."), + 5: ShellStyle().red("<===============\t\t") + ShellStyle().yellow("Server sending RPC Bind Response..."), + 6: ShellStyle().green("\t\t\t\tRPC Bind acknowledged !!!\n"), + 7: ShellStyle().yellow("Client received RPC Bind Response !!!") + ShellStyle().red("\t\t\t\t<==============="), + 8: ShellStyle().green("RPC Bind acknowledged !!!\n"), + 9: ShellStyle().blue("Client generating Activation Request dictionary..."), + 10: ShellStyle().blue("Client generating Activation Request data..."), + 11: ShellStyle().blue("Client generating RPC Activation Request..."), + 12: ShellStyle().blue("Client sending RPC Activation Request...") + ShellStyle().red("\t\t\t===============>"), + 13: ShellStyle().red("===============>\t\t") + ShellStyle().blue("Server received RPC Activation Request !!!"), + 14: ShellStyle().blue("\t\t\t\tServer parsing RPC Activation Request..."), + 15: ShellStyle().blue("\t\t\t\tServer processing KMS Activation Request..."), + 16: ShellStyle().blue("\t\t\t\tServer processing KMS Activation Response..."), + 17: ShellStyle().blue("\t\t\t\tServer generating RPC Activation Response..."), + 18: ShellStyle().red("<===============\t\t") + ShellStyle().blue("Server sending RPC Activation Response..."), + 19: ShellStyle().green("\t\t\t\tServer responded, now in Stand by...\n"), + 20: ShellStyle().blue("Client received Response !!!") + ShellStyle().red("\t\t\t\t\t<==============="), + 21: ShellStyle().green("Activation Done !!!"), + -1: ShellStyle().red("\t\t\t\t\t\t\t\tServer receiving"), + -2: ShellStyle().red("Client sending"), + -3: ShellStyle().red("Client receiving"), + -4: ShellStyle().red("\t\t\t\t\t\t\t\tServer sending") + } + + if isinstance(nshell, list): + for n in nshell: + print shelldict[n] + else: + print shelldict[nshell] + + + + diff --git a/py2-kms/kmsBase.py b/py2-kms/kmsBase.py new file mode 100644 index 0000000..f5a00c4 --- /dev/null +++ b/py2-kms/kmsBase.py @@ -0,0 +1,628 @@ +#!/usr/bin/env python + +import binascii +import logging +import datetime +import os +import struct +import sys +import time +import uuid + +from structure import Structure +import kmsPidGenerator +import filetimes +from formatText import justify, shell_message + +# sqlite3 is optional +try: + import sqlite3 +except: + pass + + +class UUID(Structure): + commonHdr = () + structure = ( + ('raw', '16s'), + ) + + def get(self): + return uuid.UUID(bytes_le=str(self)) + +class kmsBase: + class kmsRequestStruct(Structure): + commonHdr = () + structure = ( + ('versionMinor', ' This line was returning garbage in the pidGenerator + if not self.config["epid"]: + response["kmsEpid"] = kmsPidGenerator.epidGenerator(applicationId, kmsRequest['versionMajor'], self.config["lcid"]).encode('utf-16le') + else: + response["kmsEpid"] = self.config["epid"].encode('utf-16le') + + response['clientMachineId'] = kmsRequest['clientMachineId'] + response['responseTime'] = kmsRequest['requestTime'] + response['currentClientCount'] = self.config["CurrentClientCount"] + response['vLActivationInterval'] = self.config["VLActivationInterval"] + response['vLRenewalInterval'] = self.config["VLRenewalInterval"] + + if self.config['sqlite'] and self.config['dbSupport']: + con = None + try: + con = sqlite3.connect(self.dbName) + cur = con.cursor() + cur.execute("SELECT * FROM clients WHERE clientMachineId=?;", [str(kmsRequest['clientMachineId'].get())]) + try: + data = cur.fetchone() + #print "Data:", data + if data[6]: + response["kmsEpid"] = data[6].encode('utf-16le') + else: + cur.execute("UPDATE clients SET kmsEpid=? WHERE clientMachineId=?;", (str(response["kmsEpid"].decode('utf-16le')), str(kmsRequest['clientMachineId'].get()))) + + except sqlite3.Error, e: + logging.error("%s:" % e.args[0]) + + except sqlite3.Error, e: + logging.error("%s:" % e.args[0]) + sys.exit(1) + finally: + if con: + con.commit() + con.close() + + logging.info("Server ePID: %s" % response["kmsEpid"].decode('utf-16le').encode('utf-8')) + + return response + + +import kmsRequestV4, kmsRequestV5, kmsRequestV6, kmsRequestUnknown + +def generateKmsResponseData(data, config): + version = kmsBase.GenericRequestHeader(data)['versionMajor'] + currentDate = datetime.datetime.now().ctime() + + if version == 4: + logging.info("Received V%d request on %s." % (version, currentDate)) + messagehandler = kmsRequestV4.kmsRequestV4(data, config) + messagehandler.executeRequestLogic() + elif version == 5: + logging.info("Received V%d request on %s." % (version, currentDate)) + messagehandler = kmsRequestV5.kmsRequestV5(data, config) + messagehandler.executeRequestLogic() + elif version == 6: + logging.info("Received V%d request on %s." % (version, currentDate)) + messagehandler = kmsRequestV6.kmsRequestV6(data, config) + messagehandler.executeRequestLogic() + else: + logging.info("Unhandled KMS version V%d." % version) + messagehandler = kmsRequestUnknown.kmsRequestUnknown(data, config) + + return messagehandler.getResponse() diff --git a/py2-kms/kmsPidGenerator.py b/py2-kms/kmsPidGenerator.py new file mode 100644 index 0000000..bfe9c7a --- /dev/null +++ b/py2-kms/kmsPidGenerator.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python + +import datetime +import random +import time +import uuid + +APP_ID_WINDOWS = uuid.UUID("55C92734-D682-4D71-983E-D6EC3F16059F") +APP_ID_OFFICE14 = uuid.UUID("59A52881-A989-479D-AF46-F275C6370663") +APP_ID_OFFICE15 = uuid.UUID("0FF1CE15-A989-479D-AF46-F275C6370663") + + +# KMS Host OS Type +hostOsList = {} +# Windows Server 2008 R2 SP1 +hostOsList["HOST_SERVER2008R2"] = { + "type" : 55041, + "osBuild" : 7601 +} +# Windows Server 2012 RTM +hostOsList["HOST_SERVER2012"] = { + "type" : 5426, + "osBuild" : 9200 +} +# Windows Server 2012 R2 RTM +hostOsList["HOST_SERVER2012R2"] = { + "type" : 6401, + "osBuild" : 9600 +} +# Windows Server 2016 RTM +hostOsList["HOST_SERVER2016"] = { + "type" : 3612, + "osBuild" : 14393 +} + + +# Product Specific KeyConfig +pkeyConfigList = {} +# Windows Server KMS Host PID, actual PIDRangeMax = 191999999 +pkeyConfigList["windows"] = { + "GroupID" : 206, + "PIDRangeMin" : 152000000, + "PIDRangeMax" : 191999999 +} +# Windows Server 2012 R2 KMS Host PID, actual PIDRangeMax = 310999999 +pkeyConfigList["windows2012r2"] = { + "GroupID" : 206, + "PIDRangeMin" : 271000000, + "PIDRangeMax" : 310999999 +} +# Office 2010 KMSHost Class PID, actual PIDRangeMax = 217999999 +pkeyConfigList["office14"] = { + "GroupID" : 96, + "PIDRangeMin" : 199000000, + "PIDRangeMax" : 217999999 +} +# Office 2013 KMSHost Class PID, actual PIDRangeMax = 255999999 +pkeyConfigList["office15"] = { + "GroupID" : 206, + "PIDRangeMin" : 234000000, + "PIDRangeMax" : 255999999 +} + + +def epidGenerator(appId, version, lcid): + # Generate Part 1 & 7: Host Type and KMS Server OS Build + hostOsType = random.choice(hostOsList.keys()) + hostOsDict = hostOsList[hostOsType] + + # Generate Part 2: Group ID and Product Key ID Range + if appId == APP_ID_OFFICE14: + keyConfig = pkeyConfigList["office14"] + elif appId == APP_ID_OFFICE15: + keyConfig = pkeyConfigList["office15"] + else: + # Default to Windows + if hostOsDict['osBuild'] == 14393: + keyConfig = pkeyConfigList["windows2012r2"] + elif hostOsDict['osBuild'] == 9600: + keyConfig = pkeyConfigList["windows2012r2"] + else: + keyConfig = pkeyConfigList["windows"] + + # Generate Part 3 and Part 4: Product Key ID + productKeyID = random.randint(keyConfig["PIDRangeMin"], keyConfig["PIDRangeMax"]) + + # Generate Part 5: License Channel (00=Retail, 01=Retail, 02=OEM, + # 03=Volume(GVLK,MAK)) - always 03 + licenseChannel = 3 + + # Generate Part 6: Language - use system default language + # 1033 is en-us + languageCode = lcid # C# CultureInfo.InstalledUICulture.LCID + + # Generate Part 8: KMS Host Activation Date + # Get Minimum Possible Date: Newer Products first + if hostOsType == "HOST_SERVER2016": + # Microsoft Windows Server 2016 RTM + minTime = datetime.date(2016, 7, 27) + elif hostOsType == "HOST_SERVER2012R2" or version == 6: + # Microsoft Windows Server 2012 R2 RTM (October 17, 2013) + minTime = datetime.date(2013, 10, 17) + elif appId == APP_ID_OFFICE15: + # Microsoft Office 2013 RTM (October 24, 2012) + minTime = datetime.date(2012, 10, 24) + elif hostOsType == "HOST_SERVER2012" or version == 5: + # Microsoft Windows Server 2012 RTM (September 4, 2012) + minTime = datetime.date(2012, 9, 4) + else: + # Windows Server 2008 R2 SP1 (February 16, 2011) + minTime = datetime.date(2011, 2, 16) + + # Generate Year and Day Number + randomDate = datetime.date.fromtimestamp(random.randint(time.mktime(minTime.timetuple()), time.mktime(datetime.datetime.now().timetuple()))) + firstOfYear = datetime.date(randomDate.year, 1, 1) + randomDayNumber = int((time.mktime(randomDate.timetuple()) - time.mktime(firstOfYear.timetuple())) / 86400 + 0.5) + + # generate the epid string + result = [] + result.append(str(hostOsDict["type"]).rjust(5, "0")) + result.append("-") + result.append(str(keyConfig["GroupID"]).rjust(5, "0")) + result.append("-") + result.append(str(productKeyID / 1000000).rjust(3, "0")) + result.append("-") + result.append(str(productKeyID % 1000000).rjust(6, "0")) + result.append("-") + result.append(str(licenseChannel).rjust(2, "0")) + result.append("-") + result.append(str(languageCode)) + result.append("-") + result.append(str(hostOsDict["osBuild"]).rjust(4, "0")) + result.append(".0000-") + result.append(str(randomDayNumber).rjust(3, "0")) + result.append(str(randomDate.year).rjust(4, "0")) + return "".join(result) diff --git a/py2-kms/kmsRequestUnknown.py b/py2-kms/kmsRequestUnknown.py new file mode 100644 index 0000000..4342902 --- /dev/null +++ b/py2-kms/kmsRequestUnknown.py @@ -0,0 +1,13 @@ +#!/usr/bin/env python + +import struct + +from kmsBase import kmsBase + +class kmsRequestUnknown(kmsBase): + def getResponse(self): + finalResponse = bytearray() + finalResponse.extend(bytearray(struct.pack('> 4 + + # Remainding bytes + k = messageSize & 0xf + + # Hash + for i in range(0, j): + xorBuffer(message, i << 4, hashBuffer, 16) + hashBuffer = bytearray(aes.encrypt(hashBuffer, key, len(key))) + + # Bit Padding + ii = 0 + for i in range(j << 4, k + (j << 4)): + lastBlock[ii] = message[i] + ii += 1 + lastBlock[k] = 0x80 + + xorBuffer(lastBlock, 0, hashBuffer, 16) + hashBuffer = bytearray(aes.encrypt(hashBuffer, key, len(key))) + + return str(hashBuffer) + + def generateResponse(self, responseBuffer, hash): + bodyLength = len(responseBuffer) + len(hash) + response = self.ResponseV4() + response['response'] = responseBuffer + response['hash'] = hash + response['padding'] = self.getResponsePadding(bodyLength) + + shell_message(nshell = 16) + logging.debug("KMS V4 Response: %s" % justify(response.dump(print_to_stdout = False))) + logging.debug("KMS V4 Response Bytes: %s" % justify(binascii.b2a_hex(str(response)))) + + return str(response) + + def getResponse(self): + return self.responseData + + def generateRequest(self, requestBase): + hash = str(self.generateHash(bytearray(str(requestBase)))) + + bodyLength = len(requestBase) + len(hash) + + request = kmsRequestV4.RequestV4() + request['bodyLength1'] = bodyLength + request['bodyLength2'] = bodyLength + request['request'] = requestBase + request['hash'] = hash + request['padding'] = self.getResponsePadding(bodyLength) + + shell_message(nshell = 10) + logging.debug("Request V4 Data: %s" % justify(request.dump(print_to_stdout = False))) + logging.debug("Request V4: %s" % justify(binascii.b2a_hex(str(request)))) + + return request diff --git a/py2-kms/kmsRequestV5.py b/py2-kms/kmsRequestV5.py new file mode 100644 index 0000000..baf8c3b --- /dev/null +++ b/py2-kms/kmsRequestV5.py @@ -0,0 +1,175 @@ +#!/usr/bin/env python + +import logging +import binascii +import hashlib +import random +import struct + +import aes +from kmsBase import kmsBase +from structure import Structure +from formatText import justify, shell_message + +class kmsRequestV5(kmsBase): + class RequestV5(Structure): + class Message(Structure): + commonHdr = () + structure = ( + ('salt', '16s'), + ('encrypted', '236s'), #kmsBase.kmsRequestStruct + ('padding', ':'), + ) + + commonHdr = () + structure = ( + ('bodyLength1', ' 16: + logging.error("HWID \"%s\" is invalid. Hex string is too long." % binascii.b2a_hex(config['hwid']).upper()) + return + except TypeError: + logging.error("HWID \"%s\" is invalid. Odd-length hex string." % binascii.b2a_hex(config['hwid']).upper()) + return + + logging.basicConfig(level=config['loglevel'], format='%(asctime)s %(levelname)-8s %(message)s', + datefmt='%a, %d %b %Y %H:%M:%S', filename=config['logfile'], filemode='w') + + try: + import sqlite3 + config['dbSupport'] = True + except: + logging.warning("Module \"sqlite3\" is not installed, database support disabled.") + config['dbSupport'] = False + server = SocketServer.TCPServer((config['ip'], config['port']), kmsServer) + server.timeout = 5 + logging.info("TCP server listening at %s on port %d." % (config['ip'], config['port'])) + logging.info("HWID: %s" % binascii.b2a_hex(config['hwid']).upper()) + server.serve_forever() + +class kmsServer(SocketServer.BaseRequestHandler): + def setup(self): + self.connection = self.request + logging.info("Connection accepted: %s:%d" % (self.client_address[0], self.client_address[1])) + + def handle(self): + while True: + # self.request is the TCP socket connected to the client + try: + self.data = self.connection.recv(1024) + except socket.error, e: + if e[0] == 104: + logging.error("Connection reset by peer.") + break + else: + raise + if self.data == '' or not self.data: + logging.warning("No data received !") + break + # self.data = bytearray(self.data.strip()) + # logging.debug(binascii.b2a_hex(str(self.data))) + packetType = MSRPCHeader(self.data)['type'] + if packetType == rpcBase.packetType['bindReq']: + logging.info("RPC bind request received.") + shell_message(nshell = [-2, 2]) + handler = rpcBind.handler(self.data, config) + elif packetType == rpcBase.packetType['request']: + logging.info("Received activation request.") + shell_message(nshell = [-2, 13]) + handler = rpcRequest.handler(self.data, config) + else: + logging.error("Invalid RPC request type ", packetType) + break + + handler.populate() + res = str(handler.getResponse()) + self.connection.send(res) + + if packetType == rpcBase.packetType['bindReq']: + logging.info("RPC bind acknowledged.") + shell_message(nshell = [-3, 5, 6]) + elif packetType == rpcBase.packetType['request']: + logging.info("Responded to activation request.") + shell_message(nshell = [-3, 18, 19]) + break + + def finish(self): + self.connection.close() + logging.info("Connection closed: %s:%d" % (self.client_address[0], self.client_address[1])) + +if __name__ == "__main__": + main() diff --git a/py2-kms/structure.py b/py2-kms/structure.py new file mode 100644 index 0000000..c8f738c --- /dev/null +++ b/py2-kms/structure.py @@ -0,0 +1,756 @@ +#!/usr/bin/env python + +# Copyright (c) 2003-2016 CORE Security Technologies +# +# This software is provided under under a slightly modified version +# of the Apache Software License. See the accompanying LICENSE file +# for more information. +# + +""" Version of https://github.com/CoreSecurity/impacket/blob/master/impacket/structure.py + with modifications in the function dump(...). + Copyright 2018 Matteo Fan +""" + +from struct import pack, unpack, calcsize + +class Structure: + """ sublcasses can define commonHdr and/or structure. + each of them is an tuple of either two: (fieldName, format) or three: (fieldName, ':', class) fields. + [it can't be a dictionary, because order is important] + + where format specifies how the data in the field will be converted to/from bytes (string) + class is the class to use when unpacking ':' fields. + + each field can only contain one value (or an array of values for *) + i.e. struct.pack('Hl',1,2) is valid, but format specifier 'Hl' is not (you must use 2 dfferent fields) + + format specifiers: + specifiers from module pack can be used with the same format + see struct.__doc__ (pack/unpack is finally called) + x [padding byte] + c [character] + b [signed byte] + B [unsigned byte] + h [signed short] + H [unsigned short] + l [signed long] + L [unsigned long] + i [signed integer] + I [unsigned integer] + q [signed long long (quad)] + Q [unsigned long long (quad)] + s [string (array of chars), must be preceded with length in format specifier, padded with zeros] + p [pascal string (includes byte count), must be preceded with length in format specifier, padded with zeros] + f [float] + d [double] + = [native byte ordering, size and alignment] + @ [native byte ordering, standard size and alignment] + ! [network byte ordering] + < [little endian] + > [big endian] + + usual printf like specifiers can be used (if started with %) + [not recommeneded, there is no why to unpack this] + + %08x will output an 8 bytes hex + %s will output a string + %s\\x00 will output a NUL terminated string + %d%d will output 2 decimal digits (against the very same specification of Structure) + ... + + some additional format specifiers: + : just copy the bytes from the field into the output string (input may be string, other structure, or anything responding to __str__()) (for unpacking, all what's left is returned) + z same as :, but adds a NUL byte at the end (asciiz) (for unpacking the first NUL byte is used as terminator) [asciiz string] + u same as z, but adds two NUL bytes at the end (after padding to an even size with NULs). (same for unpacking) [unicode string] + w DCE-RPC/NDR string (it's a macro for [ ' 2: + dataClassOrCode = field[2] + try: + self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0]) + except Exception,e: + e.args += ("When unpacking field '%s | %s | %r[:%d]'" % (field[0], field[1], data, size),) + raise + + size = self.calcPackSize(field[1], self[field[0]], field[0]) + if self.alignment and size % self.alignment: + size += self.alignment - (size % self.alignment) + data = data[size:] + + return self + + def __setitem__(self, key, value): + self.fields[key] = value + self.data = None # force recompute + + def __getitem__(self, key): + return self.fields[key] + + def __delitem__(self, key): + del self.fields[key] + + def __str__(self): + return self.getData() + + def __len__(self): + # XXX: improve + return len(self.getData()) + + def pack(self, format, data, field = None): + if self.debug: + print " pack( %s | %r | %s)" % (format, data, field) + + if field: + addressField = self.findAddressFieldFor(field) + if (addressField is not None) and (data is None): + return '' + + # void specifier + if format[:1] == '_': + return '' + + # quote specifier + if format[:1] == "'" or format[:1] == '"': + return format[1:] + + # code specifier + two = format.split('=') + if len(two) >= 2: + try: + return self.pack(two[0], data) + except: + fields = {'self':self} + fields.update(self.fields) + return self.pack(two[0], eval(two[1], {}, fields)) + + # address specifier + two = format.split('&') + if len(two) == 2: + try: + return self.pack(two[0], data) + except: + if (self.fields.has_key(two[1])) and (self[two[1]] is not None): + return self.pack(two[0], id(self[two[1]]) & ((1<<(calcsize(two[0])*8))-1) ) + else: + return self.pack(two[0], 0) + + # length specifier + two = format.split('-') + if len(two) == 2: + try: + return self.pack(two[0],data) + except: + return self.pack(two[0], self.calcPackFieldSize(two[1])) + + # array specifier + two = format.split('*') + if len(two) == 2: + answer = '' + for each in data: + answer += self.pack(two[1], each) + if two[0]: + if two[0].isdigit(): + if int(two[0]) != len(data): + raise Exception, "Array field has a constant size, and it doesn't match the actual value" + else: + return self.pack(two[0], len(data))+answer + return answer + + # "printf" string specifier + if format[:1] == '%': + # format string like specifier + return format % data + + # asciiz specifier + if format[:1] == 'z': + return str(data)+'\0' + + # unicode specifier + if format[:1] == 'u': + return str(data)+'\0\0' + (len(data) & 1 and '\0' or '') + + # DCE-RPC/NDR string specifier + if format[:1] == 'w': + if len(data) == 0: + data = '\0\0' + elif len(data) % 2: + data += '\0' + l = pack('= 2: + return self.unpack(two[0],data) + + # length specifier + two = format.split('-') + if len(two) == 2: + return self.unpack(two[0],data) + + # array specifier + two = format.split('*') + if len(two) == 2: + answer = [] + sofar = 0 + if two[0].isdigit(): + number = int(two[0]) + elif two[0]: + sofar += self.calcUnpackSize(two[0], data) + number = self.unpack(two[0], data[:sofar]) + else: + number = -1 + + while number and sofar < len(data): + nsofar = sofar + self.calcUnpackSize(two[1],data[sofar:]) + answer.append(self.unpack(two[1], data[sofar:nsofar], dataClassOrCode)) + number -= 1 + sofar = nsofar + return answer + + # "printf" string specifier + if format[:1] == '%': + # format string like specifier + return format % data + + # asciiz specifier + if format == 'z': + if data[-1] != '\x00': + raise Exception, ("%s 'z' field is not NUL terminated: %r" % (field, data)) + return data[:-1] # remove trailing NUL + + # unicode specifier + if format == 'u': + if data[-2:] != '\x00\x00': + raise Exception, ("%s 'u' field is not NUL-NUL terminated: %r" % (field, data)) + return data[:-2] # remove trailing NUL + + # DCE-RPC/NDR string specifier + if format == 'w': + l = unpack('= 2: + return self.calcPackSize(two[0], data) + + # length specifier + two = format.split('-') + if len(two) == 2: + return self.calcPackSize(two[0], data) + + # array specifier + two = format.split('*') + if len(two) == 2: + answer = 0 + if two[0].isdigit(): + if int(two[0]) != len(data): + raise Exception, "Array field has a constant size, and it doesn't match the actual value" + elif two[0]: + answer += self.calcPackSize(two[0], len(data)) + + for each in data: + answer += self.calcPackSize(two[1], each) + return answer + + # "printf" string specifier + if format[:1] == '%': + # format string like specifier + return len(format % data) + + # asciiz specifier + if format[:1] == 'z': + return len(data)+1 + + # asciiz specifier + if format[:1] == 'u': + l = len(data) + return l + (l & 1 and 3 or 2) + + # DCE-RPC/NDR string specifier + if format[:1] == 'w': + l = len(data) + return 12+l+l % 2 + + # literal specifier + if format[:1] == ':': + return len(data) + + # struct like specifier + return calcsize(format) + + def calcUnpackSize(self, format, data, field = None): + if self.debug: + print " calcUnpackSize( %s | %s | %r)" % (field, format, data) + + # void specifier + if format[:1] == '_': + return 0 + + addressField = self.findAddressFieldFor(field) + if addressField is not None: + if not self[addressField]: + return 0 + + try: + lengthField = self.findLengthFieldFor(field) + return self[lengthField] + except: + pass + + # XXX: Try to match to actual values, raise if no match + + # quote specifier + if format[:1] == "'" or format[:1] == '"': + return len(format)-1 + + # address specifier + two = format.split('&') + if len(two) == 2: + return self.calcUnpackSize(two[0], data) + + # code specifier + two = format.split('=') + if len(two) >= 2: + return self.calcUnpackSize(two[0], data) + + # length specifier + two = format.split('-') + if len(two) == 2: + return self.calcUnpackSize(two[0], data) + + # array specifier + two = format.split('*') + if len(two) == 2: + answer = 0 + if two[0]: + if two[0].isdigit(): + number = int(two[0]) + else: + answer += self.calcUnpackSize(two[0], data) + number = self.unpack(two[0], data[:answer]) + + while number: + number -= 1 + answer += self.calcUnpackSize(two[1], data[answer:]) + else: + while answer < len(data): + answer += self.calcUnpackSize(two[1], data[answer:]) + return answer + + # "printf" string specifier + if format[:1] == '%': + raise Exception, "Can't guess the size of a printf like specifier for unpacking" + + # asciiz specifier + if format[:1] == 'z': + return data.index('\x00')+1 + + # asciiz specifier + if format[:1] == 'u': + l = data.index('\x00\x00') + return l + (l & 1 and 3 or 2) + + # DCE-RPC/NDR string specifier + if format[:1] == 'w': + l = unpack('L'), + ('code1','>L=len(arr1)*2+0x1000'), + ) + + def populate(self, a): + a['default'] = 'hola' + a['int1'] = 0x3131 + a['int3'] = 0x45444342 + a['z1'] = 'hola' + a['u1'] = 'hola'.encode('utf_16_le') + a[':1'] = ':1234:' + a['arr1'] = (0x12341234,0x88990077,0x41414141) + # a['len1'] = 0x42424242 + +class _Test_fixedLength(_Test_simple): + def populate(self, a): + _Test_simple.populate(self, a) + a['len1'] = 0x42424242 + +class _Test_simple_aligned4(_Test_simple): + alignment = 4 + +class _Test_nested(_StructureTest): + class theClass(Structure): + class _Inner(Structure): + structure = (('data', 'z'),) + + structure = ( + ('nest1', ':', _Inner), + ('nest2', ':', _Inner), + ('int', '> 8)'), + ('pad', '_','((iv >>2) & 0x3F)'), + ('keyid', '_','( iv & 0x03 )'), + ('dataLen', '_-data', 'len(inputDataLeft)-4'), + ('data',':'), + ('icv','>L'), + ) + + def populate(self, a): + a['init_vector']=0x01020304 + #a['pad']=int('01010101',2) + a['pad']=int('010101',2) + a['keyid']=0x07 + a['data']="\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9" + a['icv'] = 0x05060708 + #a['iv'] = 0x01020304 + +if __name__ == '__main__': + _Test_simple().run() + + try: + _Test_fixedLength().run() + except: + print "cannot repack because length is bogus" + + _Test_simple_aligned4().run() + _Test_nested().run() + _Test_Optional().run() + _Test_Optional_sparse().run() + _Test_AsciiZArray().run() + _Test_UnpackCode().run() + _Test_AAA().run() diff --git a/py2-kms/timezones.py b/py2-kms/timezones.py new file mode 100644 index 0000000..d417617 --- /dev/null +++ b/py2-kms/timezones.py @@ -0,0 +1,202 @@ +#!/usr/bin/env python + +# Copyright 2009 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +# +# Disable the invalid name warning as we are inheriting from a standard library +# object. +# pylint: disable-msg=C6409,W0212 + +""" +Stripped down version of `python-datetime-tz` +that only contains the "find local timezone" bits. +""" + +import datetime +import os.path +import time +import warnings +import pytz + + +# Need to patch pytz.utc to have a _utcoffset so you can normalize/localize +# using it. +pytz.utc._utcoffset = datetime.timedelta() + + +timedelta = datetime.timedelta + + +def _tzinfome(tzinfo): + """Gets a tzinfo object from a string. + + Args: + tzinfo: A string (or string like) object, or a datetime.tzinfo object. + + Returns: + An datetime.tzinfo object. + + Raises: + UnknownTimeZoneError: If the timezone given can't be decoded. + """ + if not isinstance(tzinfo, datetime.tzinfo): + try: + tzinfo = pytz.timezone(tzinfo) + except AttributeError: + raise pytz.UnknownTimeZoneError("Unknown timezone!") + return tzinfo + + +# Our "local" timezone +_localtz = None + + +def localtz(): + """Get the local timezone. + + Returns: + The localtime timezone as a tzinfo object. + """ + # pylint: disable-msg=W0603 + global _localtz + if _localtz is None: + _localtz = detect_timezone() + return _localtz + + +def detect_timezone(): + """Try and detect the timezone that Python is currently running in. + + We have a bunch of different methods for trying to figure this out (listed in + order they are attempted). + * Try and find /etc/timezone file (with timezone name). + * Try and find /etc/localtime file (with timezone data). + * Try and match a TZ to the current dst/offset/shortname. + + Returns: + The detected local timezone as a tzinfo object + + Raises: + pytz.UnknownTimeZoneError: If it was unable to detect a timezone. + """ + + tz = _detect_timezone_etc_timezone() + if tz is not None: + return tz + + tz = _detect_timezone_etc_localtime() + if tz is not None: + return tz + + # Next we try and use a similiar method to what PHP does. + # We first try to search on time.tzname, time.timezone, time.daylight to + # match a pytz zone. + warnings.warn("Had to fall back to worst detection method (the 'PHP' " + "method).") + + tz = _detect_timezone_php() + if tz is not None: + return tz + + raise pytz.UnknownTimeZoneError("Unable to detect your timezone!") + +def _detect_timezone_etc_timezone(): + if os.path.exists("/etc/timezone"): + try: + tz = file("/etc/timezone").read().strip() + try: + return pytz.timezone(tz) + except (IOError, pytz.UnknownTimeZoneError), ei: + warnings.warn("Your /etc/timezone file references a timezone (%r) that" + " is not valid (%r)." % (tz, ei)) + + # Problem reading the /etc/timezone file + except IOError, eo: + warnings.warn("Could not access your /etc/timezone file: %s" % eo) + + +def _detect_timezone_etc_localtime(): + matches = [] + if os.path.exists("/etc/localtime"): + localtime = pytz.tzfile.build_tzinfo("/etc/localtime", + file("/etc/localtime")) + + # See if we can find a "Human Name" for this.. + for tzname in pytz.all_timezones: + tz = _tzinfome(tzname) + + if dir(tz) != dir(localtime): + continue + + for attrib in dir(tz): + # Ignore functions and specials + if callable(getattr(tz, attrib)) or attrib.startswith("__"): + continue + + # This will always be different + if attrib == "zone" or attrib == "_tzinfos": + continue + + if getattr(tz, attrib) != getattr(localtime, attrib): + break + + # We get here iff break didn't happen, i.e. no meaningful attributes + # differ between tz and localtime + else: + matches.append(tzname) + + #if len(matches) == 1: + # return _tzinfome(matches[0]) + #else: + # # Warn the person about this! + # warning = "Could not get a human name for your timezone: " + # if len(matches) > 1: + # warning += ("We detected multiple matches for your /etc/localtime. " + # "(Matches where %s)" % matches) + # else: + # warning += "We detected no matches for your /etc/localtime." + # warnings.warn(warning) + # + # return localtime + if len(matches) > 0: + return _tzinfome(matches[0]) + + +def _detect_timezone_php(): + tomatch = (time.tzname[0], time.timezone, time.daylight) + now = datetime.datetime.now() + + matches = [] + for tzname in pytz.all_timezones: + try: + tz = pytz.timezone(tzname) + except IOError: + continue + + try: + indst = tz.localize(now).timetuple()[-1] + + if tomatch == (tz._tzname, -tz._utcoffset.seconds, indst): + matches.append(tzname) + + # pylint: disable-msg=W0704 + except AttributeError: + pass + + if len(matches) > 1: + warnings.warn("We detected multiple matches for the timezone, choosing " + "the first %s. (Matches where %s)" % (matches[0], matches)) + return pytz.timezone(matches[0]) +