├── Dockerfile ├── README.md ├── aes.py ├── client.py ├── dcerpc.py ├── filetimes.py ├── kmsBase.py ├── kmsPidGenerator.py ├── kmsRequestUnknown.py ├── kmsRequestV4.py ├── kmsRequestV5.py ├── kmsRequestV6.py ├── rpcBase.py ├── rpcBind.py ├── rpcRequest.py ├── server.py ├── structure.py ├── tablecomplex.py └── timezones.py /Dockerfile: -------------------------------------------------------------------------------- 1 | # Dockerfile for py-kms 2 | 3 | FROM python:2-alpine 4 | MAINTAINER Matsuz 5 | 6 | ADD . /kms 7 | 8 | EXPOSE 1688 9 | 10 | CMD ["python", "/kms/server.py"] 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # History 2 | py-kms is a port of node-kms by [markedsword](http://forums.mydigitallife.info/members/183074-markedsword), which is a port of either the C#, C++, or .NET implementations of KMSEmulator, of which the original version was written by [CODYQX4](http://forums.mydigitallife.info/members/89933-CODYQX4) and is derived from the reverse-engineered code of Microsoft's official KMS. 3 | 4 | # Features 5 | - Responds to V4, V5, and V6 KMS requests. 6 | - Supports activating Windows 7/8/8.1/10/2008R2/2012/2012R2/2016 and Office 2010/2013/2016. 7 | - It's written in Python2. 8 | 9 | # Dependencies 10 | - Python 2.7.x or Python 2.6.x with the "argparse" module installed. 11 | - If the "pytz" module is installed, the "Request Time" in the verbose output will be converted into local time. Otherwise, it will be in UTC. 12 | 13 | # Usage 14 | - To start the server, execute `python server.py [listen_address] [port]`. The default listening address is `0.0.0.0` (all interfaces) and the default port is `1688`. 15 | - To run the client, use `python client.py server_address [port]`. The default port is `1688`. 16 | -------------------------------------------------------------------------------- /aes.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # aes.py: implements AES - Advanced Encryption Standard 4 | # from the SlowAES project, http://code.google.com/p/slowaes/ 5 | # 6 | # Copyright (c) 2008 Josh Davis ( http://www.josh-davis.org ), 7 | # Alex Martelli ( http://www.aleax.it ) 8 | # 9 | # Ported from C code written by Laurent Haan ( http://www.progressive-coding.com ) 10 | # 11 | # Licensed under the Apache License, Version 2.0 12 | # http://www.apache.org/licenses/ 13 | # 14 | import os 15 | import sys 16 | import math 17 | 18 | class AES(object): 19 | '''AES funtions for a single block 20 | ''' 21 | # Very annoying code: all is for an object, but no state is kept! 22 | # Should just be plain functions in a AES modlule. 23 | 24 | v6 = False 25 | 26 | # valid key sizes 27 | keySize = dict(SIZE_128=16, SIZE_192=24, SIZE_256=32) 28 | 29 | # Rijndael S-box 30 | sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67, 31 | 0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59, 32 | 0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7, 33 | 0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1, 34 | 0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05, 35 | 0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83, 36 | 0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29, 37 | 0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b, 38 | 0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa, 39 | 0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c, 40 | 0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc, 41 | 0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec, 42 | 0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19, 43 | 0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee, 44 | 0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49, 45 | 0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79, 46 | 0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4, 47 | 0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6, 48 | 0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70, 49 | 0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9, 50 | 0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e, 51 | 0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1, 52 | 0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0, 53 | 0x54, 0xbb, 0x16] 54 | 55 | # Rijndael Inverted S-box 56 | rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 57 | 0x9e, 0x81, 0xf3, 0xd7, 0xfb , 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 58 | 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb , 0x54, 59 | 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 60 | 0x42, 0xfa, 0xc3, 0x4e , 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 61 | 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25 , 0x72, 0xf8, 62 | 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 63 | 0x65, 0xb6, 0x92 , 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 64 | 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84 , 0x90, 0xd8, 0xab, 65 | 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 66 | 0x45, 0x06 , 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 67 | 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b , 0x3a, 0x91, 0x11, 0x41, 68 | 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 69 | 0x73 , 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 70 | 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e , 0x47, 0xf1, 0x1a, 0x71, 0x1d, 71 | 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b , 72 | 0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 73 | 0xfe, 0x78, 0xcd, 0x5a, 0xf4 , 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 74 | 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f , 0x60, 75 | 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 76 | 0x93, 0xc9, 0x9c, 0xef , 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 77 | 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61 , 0x17, 0x2b, 78 | 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 79 | 0x21, 0x0c, 0x7d] 80 | 81 | def getSBoxValue(self,num): 82 | """Retrieves a given S-Box Value""" 83 | return self.sbox[num] 84 | 85 | def getSBoxInvert(self,num): 86 | """Retrieves a given Inverted S-Box Value""" 87 | return self.rsbox[num] 88 | 89 | def rotate(self, word): 90 | """ Rijndael's key schedule rotate operation. 91 | 92 | Rotate a word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d 93 | Word is an char list of size 4 (32 bits overall). 94 | """ 95 | return word[1:] + word[:1] 96 | 97 | # Rijndael Rcon 98 | Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 99 | 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 100 | 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 101 | 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 102 | 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 103 | 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 104 | 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 105 | 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 106 | 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 107 | 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 108 | 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 109 | 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 110 | 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 111 | 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 112 | 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 113 | 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 114 | 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 115 | 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 116 | 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 117 | 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 118 | 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 119 | 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 120 | 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 121 | 0xe8, 0xcb ] 122 | 123 | def getRconValue(self, num): 124 | """Retrieves a given Rcon Value""" 125 | return self.Rcon[num] 126 | 127 | def core(self, word, iteration): 128 | """Key schedule core.""" 129 | # rotate the 32-bit word 8 bits to the left 130 | word = self.rotate(word) 131 | # apply S-Box substitution on all 4 parts of the 32-bit word 132 | for i in range(4): 133 | word[i] = self.getSBoxValue(word[i]) 134 | # XOR the output of the rcon operation with i to the first part 135 | # (leftmost) only 136 | word[0] = word[0] ^ self.getRconValue(iteration) 137 | return word 138 | 139 | def expandKey(self, key, size, expandedKeySize): 140 | """Rijndael's key expansion. 141 | 142 | Expands an 128,192,256 key into an 176,208,240 bytes key 143 | 144 | expandedKey is a char list of large enough size, 145 | key is the non-expanded key. 146 | """ 147 | # current expanded keySize, in bytes 148 | currentSize = 0 149 | rconIteration = 1 150 | expandedKey = [0] * expandedKeySize 151 | 152 | # set the 16, 24, 32 bytes of the expanded key to the input key 153 | for j in range(size): 154 | expandedKey[j] = key[j] 155 | currentSize += size 156 | 157 | while currentSize < expandedKeySize: 158 | # assign the previous 4 bytes to the temporary value t 159 | t = expandedKey[currentSize-4:currentSize] 160 | 161 | # every 16,24,32 bytes we apply the core schedule to t 162 | # and increment rconIteration afterwards 163 | if currentSize % size == 0: 164 | t = self.core(t, rconIteration) 165 | rconIteration += 1 166 | # For 256-bit keys, we add an extra sbox to the calculation 167 | if size == self.keySize["SIZE_256"] and ((currentSize % size) == 16): 168 | for l in range(4): t[l] = self.getSBoxValue(t[l]) 169 | 170 | # We XOR t with the four-byte block 16,24,32 bytes before the new 171 | # expanded key. This becomes the next four bytes in the expanded 172 | # key. 173 | for m in range(4): 174 | expandedKey[currentSize] = expandedKey[currentSize - size] ^ \ 175 | t[m] 176 | currentSize += 1 177 | 178 | return expandedKey 179 | 180 | def addRoundKey(self, state, roundKey): 181 | """Adds (XORs) the round key to the state.""" 182 | for i in range(16): 183 | state[i] ^= roundKey[i] 184 | return state 185 | 186 | def createRoundKey(self, expandedKey, roundKeyPointer): 187 | """Create a round key. 188 | Creates a round key from the given expanded key and the 189 | position within the expanded key. 190 | """ 191 | roundKey = [0] * 16 192 | for i in range(4): 193 | for j in range(4): 194 | roundKey[j*4+i] = expandedKey[roundKeyPointer + i*4 + j] 195 | return roundKey 196 | 197 | def galois_multiplication(self, a, b): 198 | """Galois multiplication of 8 bit characters a and b.""" 199 | p = 0 200 | for counter in range(8): 201 | if b & 1: p ^= a 202 | hi_bit_set = a & 0x80 203 | a <<= 1 204 | # keep a 8 bit 205 | a &= 0xFF 206 | if hi_bit_set: 207 | a ^= 0x1b 208 | b >>= 1 209 | return p 210 | 211 | # 212 | # substitute all the values from the state with the value in the SBox 213 | # using the state value as index for the SBox 214 | # 215 | def subBytes(self, state, isInv): 216 | if isInv: getter = self.getSBoxInvert 217 | else: getter = self.getSBoxValue 218 | for i in range(16): state[i] = getter(state[i]) 219 | return state 220 | 221 | # iterate over the 4 rows and call shiftRow() with that row 222 | def shiftRows(self, state, isInv): 223 | for i in range(4): 224 | state = self.shiftRow(state, i*4, i, isInv) 225 | return state 226 | 227 | # each iteration shifts the row to the left by 1 228 | def shiftRow(self, state, statePointer, nbr, isInv): 229 | for i in range(nbr): 230 | if isInv: 231 | state[statePointer:statePointer+4] = \ 232 | state[statePointer+3:statePointer+4] + \ 233 | state[statePointer:statePointer+3] 234 | else: 235 | state[statePointer:statePointer+4] = \ 236 | state[statePointer+1:statePointer+4] + \ 237 | state[statePointer:statePointer+1] 238 | return state 239 | 240 | # galois multiplication of the 4x4 matrix 241 | def mixColumns(self, state, isInv): 242 | # iterate over the 4 columns 243 | for i in range(4): 244 | # construct one column by slicing over the 4 rows 245 | column = state[i:i+16:4] 246 | # apply the mixColumn on one column 247 | column = self.mixColumn(column, isInv) 248 | # put the values back into the state 249 | state[i:i+16:4] = column 250 | 251 | return state 252 | 253 | # galois multiplication of 1 column of the 4x4 matrix 254 | def mixColumn(self, column, isInv): 255 | if isInv: mult = [14, 9, 13, 11] 256 | else: mult = [2, 1, 1, 3] 257 | cpy = list(column) 258 | g = self.galois_multiplication 259 | 260 | column[0] = g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ \ 261 | g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) 262 | column[1] = g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ \ 263 | g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) 264 | column[2] = g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ \ 265 | g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) 266 | column[3] = g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ \ 267 | g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) 268 | return column 269 | 270 | # applies the 4 operations of the forward round in sequence 271 | def aes_round(self, state, roundKey, round): 272 | state = self.subBytes(state, False) 273 | state = self.shiftRows(state, False) 274 | state = self.mixColumns(state, False) 275 | 276 | if self.v6: 277 | if round == 4: 278 | state[0]^=0x73 279 | if round == 6: 280 | state[0]^=0x09 281 | if round == 8: 282 | state[0]^=0xE4 283 | 284 | state = self.addRoundKey(state, roundKey) 285 | return state 286 | 287 | # applies the 4 operations of the inverse round in sequence 288 | def aes_invRound(self, state, roundKey, round): 289 | state = self.shiftRows(state, True) 290 | state = self.subBytes(state, True) 291 | state = self.addRoundKey(state, roundKey) 292 | 293 | if self.v6: 294 | if round == 4: 295 | state[0]^=0x73 296 | if round == 6: 297 | state[0]^=0x09 298 | if round == 8: 299 | state[0]^=0xE4 300 | 301 | state = self.mixColumns(state, True) 302 | return state 303 | 304 | # Perform the initial operations, the standard round, and the final 305 | # operations of the forward aes, creating a round key for each round 306 | def aes_main(self, state, expandedKey, nbrRounds): 307 | state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) 308 | i = 1 309 | while i < nbrRounds: 310 | state = self.aes_round(state, 311 | self.createRoundKey(expandedKey, 16*i), i) 312 | i += 1 313 | state = self.subBytes(state, False) 314 | state = self.shiftRows(state, False) 315 | state = self.addRoundKey(state, 316 | self.createRoundKey(expandedKey, 16*nbrRounds)) 317 | return state 318 | 319 | # Perform the initial operations, the standard round, and the final 320 | # operations of the inverse aes, creating a round key for each round 321 | def aes_invMain(self, state, expandedKey, nbrRounds): 322 | state = self.addRoundKey(state, 323 | self.createRoundKey(expandedKey, 16*nbrRounds)) 324 | i = nbrRounds - 1 325 | while i > 0: 326 | state = self.aes_invRound(state, 327 | self.createRoundKey(expandedKey, 16*i), i) 328 | i -= 1 329 | state = self.shiftRows(state, True) 330 | state = self.subBytes(state, True) 331 | state = self.addRoundKey(state, self.createRoundKey(expandedKey, 0)) 332 | return state 333 | 334 | # encrypts a 128 bit input block against the given key of size specified 335 | def encrypt(self, iput, key, size): 336 | output = [0] * 16 337 | # the number of rounds 338 | nbrRounds = 0 339 | # the 128 bit block to encode 340 | block = [0] * 16 341 | # set the number of rounds 342 | if size == self.keySize["SIZE_128"]: nbrRounds = 10 343 | elif size == self.keySize["SIZE_192"]: nbrRounds = 12 344 | elif size == self.keySize["SIZE_256"]: nbrRounds = 14 345 | # The KMS v4 parameters 346 | elif size == 20: nbrRounds = 11 347 | else: return None 348 | 349 | # the expanded keySize 350 | expandedKeySize = 16*(nbrRounds+1) 351 | 352 | # Set the block values, for the block: 353 | # a0,0 a0,1 a0,2 a0,3 354 | # a1,0 a1,1 a1,2 a1,3 355 | # a2,0 a2,1 a2,2 a2,3 356 | # a3,0 a3,1 a3,2 a3,3 357 | # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 358 | # 359 | # iterate over the columns 360 | for i in range(4): 361 | # iterate over the rows 362 | for j in range(4): 363 | block[(i+(j*4))] = iput[(i*4)+j] 364 | 365 | # expand the key into an 176, 208, 240 bytes key 366 | # the expanded key 367 | expandedKey = self.expandKey(key, size, expandedKeySize) 368 | 369 | # encrypt the block using the expandedKey 370 | block = self.aes_main(block, expandedKey, nbrRounds) 371 | 372 | # unmap the block again into the output 373 | for k in range(4): 374 | # iterate over the rows 375 | for l in range(4): 376 | output[(k*4)+l] = block[(k+(l*4))] 377 | return output 378 | 379 | # decrypts a 128 bit input block against the given key of size specified 380 | def decrypt(self, iput, key, size): 381 | output = [0] * 16 382 | # the number of rounds 383 | nbrRounds = 0 384 | # the 128 bit block to decode 385 | block = [0] * 16 386 | # set the number of rounds 387 | if size == self.keySize["SIZE_128"]: nbrRounds = 10 388 | elif size == self.keySize["SIZE_192"]: nbrRounds = 12 389 | elif size == self.keySize["SIZE_256"]: nbrRounds = 14 390 | else: return None 391 | 392 | # the expanded keySize 393 | expandedKeySize = 16*(nbrRounds+1) 394 | 395 | # Set the block values, for the block: 396 | # a0,0 a0,1 a0,2 a0,3 397 | # a1,0 a1,1 a1,2 a1,3 398 | # a2,0 a2,1 a2,2 a2,3 399 | # a3,0 a3,1 a3,2 a3,3 400 | # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 401 | 402 | # iterate over the columns 403 | for i in range(4): 404 | # iterate over the rows 405 | for j in range(4): 406 | block[(i+(j*4))] = iput[(i*4)+j] 407 | # expand the key into an 176, 208, 240 bytes key 408 | expandedKey = self.expandKey(key, size, expandedKeySize) 409 | # decrypt the block using the expandedKey 410 | block = self.aes_invMain(block, expandedKey, nbrRounds) 411 | # unmap the block again into the output 412 | for k in range(4): 413 | # iterate over the rows 414 | for l in range(4): 415 | output[(k*4)+l] = block[(k+(l*4))] 416 | return output 417 | 418 | 419 | class AESModeOfOperation(object): 420 | '''Handles AES with plaintext consistingof multiple blocks. 421 | Choice of block encoding modes: OFT, CFB, CBC 422 | ''' 423 | # Very annoying code: all is for an object, but no state is kept! 424 | # Should just be plain functions in an AES_BlockMode module. 425 | aes = AES() 426 | 427 | # structure of supported modes of operation 428 | modeOfOperation = dict(OFB=0, CFB=1, CBC=2) 429 | 430 | # converts a 16 character string into a number array 431 | def convertString(self, string, start, end, mode): 432 | if end - start > 16: end = start + 16 433 | if mode == self.modeOfOperation["CBC"]: ar = [0] * 16 434 | else: ar = [] 435 | 436 | i = start 437 | j = 0 438 | while len(ar) < end - start: 439 | ar.append(0) 440 | while i < end: 441 | ar[j] = ord(string[i]) 442 | j += 1 443 | i += 1 444 | return ar 445 | 446 | # Mode of Operation Encryption 447 | # stringIn - Input String 448 | # mode - mode of type modeOfOperation 449 | # hexKey - a hex key of the bit length size 450 | # size - the bit length of the key 451 | # hexIV - the 128 bit hex Initilization Vector 452 | def encrypt(self, stringIn, mode, key, size, IV): 453 | if len(key) % size: 454 | return None 455 | if len(IV) % 16: 456 | return None 457 | # the AES input/output 458 | plaintext = [] 459 | iput = [0] * 16 460 | output = [] 461 | ciphertext = [0] * 16 462 | # the output cipher string 463 | cipherOut = [] 464 | # char firstRound 465 | firstRound = True 466 | if stringIn != None: 467 | for j in range(int(math.ceil(float(len(stringIn))/16))): 468 | start = j*16 469 | end = j*16+16 470 | if end > len(stringIn): 471 | end = len(stringIn) 472 | plaintext = self.convertString(stringIn, start, end, mode) 473 | # print 'PT@%s:%s' % (j, plaintext) 474 | if mode == self.modeOfOperation["CFB"]: 475 | if firstRound: 476 | output = self.aes.encrypt(IV, key, size) 477 | firstRound = False 478 | else: 479 | output = self.aes.encrypt(iput, key, size) 480 | for i in range(16): 481 | if len(plaintext)-1 < i: 482 | ciphertext[i] = 0 ^ output[i] 483 | elif len(output)-1 < i: 484 | ciphertext[i] = plaintext[i] ^ 0 485 | elif len(plaintext)-1 < i and len(output) < i: 486 | ciphertext[i] = 0 ^ 0 487 | else: 488 | ciphertext[i] = plaintext[i] ^ output[i] 489 | for k in range(end-start): 490 | cipherOut.append(ciphertext[k]) 491 | iput = ciphertext 492 | elif mode == self.modeOfOperation["OFB"]: 493 | if firstRound: 494 | output = self.aes.encrypt(IV, key, size) 495 | firstRound = False 496 | else: 497 | output = self.aes.encrypt(iput, key, size) 498 | for i in range(16): 499 | if len(plaintext)-1 < i: 500 | ciphertext[i] = 0 ^ output[i] 501 | elif len(output)-1 < i: 502 | ciphertext[i] = plaintext[i] ^ 0 503 | elif len(plaintext)-1 < i and len(output) < i: 504 | ciphertext[i] = 0 ^ 0 505 | else: 506 | ciphertext[i] = plaintext[i] ^ output[i] 507 | for k in range(end-start): 508 | cipherOut.append(ciphertext[k]) 509 | iput = output 510 | elif mode == self.modeOfOperation["CBC"]: 511 | for i in range(16): 512 | if firstRound: 513 | iput[i] = plaintext[i] ^ IV[i] 514 | else: 515 | iput[i] = plaintext[i] ^ ciphertext[i] 516 | # print 'IP@%s:%s' % (j, iput) 517 | firstRound = False 518 | ciphertext = self.aes.encrypt(iput, key, size) 519 | # always 16 bytes because of the padding for CBC 520 | for k in range(16): 521 | cipherOut.append(ciphertext[k]) 522 | return mode, len(stringIn), cipherOut 523 | 524 | # Mode of Operation Decryption 525 | # cipherIn - Encrypted String 526 | # originalsize - The unencrypted string length - required for CBC 527 | # mode - mode of type modeOfOperation 528 | # key - a number array of the bit length size 529 | # size - the bit length of the key 530 | # IV - the 128 bit number array Initilization Vector 531 | def decrypt(self, cipherIn, originalsize, mode, key, size, IV): 532 | # cipherIn = unescCtrlChars(cipherIn) 533 | if len(key) % size: 534 | return None 535 | if len(IV) % 16: 536 | return None 537 | # the AES input/output 538 | ciphertext = [] 539 | iput = [] 540 | output = [] 541 | plaintext = [0] * 16 542 | # the output plain text character list 543 | chrOut = [] 544 | # char firstRound 545 | firstRound = True 546 | if cipherIn != None: 547 | for j in range(int(math.ceil(float(len(cipherIn))/16))): 548 | start = j*16 549 | end = j*16+16 550 | if j*16+16 > len(cipherIn): 551 | end = len(cipherIn) 552 | ciphertext = cipherIn[start:end] 553 | if mode == self.modeOfOperation["CFB"]: 554 | if firstRound: 555 | output = self.aes.encrypt(IV, key, size) 556 | firstRound = False 557 | else: 558 | output = self.aes.encrypt(iput, key, size) 559 | for i in range(16): 560 | if len(output)-1 < i: 561 | plaintext[i] = 0 ^ ciphertext[i] 562 | elif len(ciphertext)-1 < i: 563 | plaintext[i] = output[i] ^ 0 564 | elif len(output)-1 < i and len(ciphertext) < i: 565 | plaintext[i] = 0 ^ 0 566 | else: 567 | plaintext[i] = output[i] ^ ciphertext[i] 568 | for k in range(end-start): 569 | chrOut.append(chr(plaintext[k])) 570 | iput = ciphertext 571 | elif mode == self.modeOfOperation["OFB"]: 572 | if firstRound: 573 | output = self.aes.encrypt(IV, key, size) 574 | firstRound = False 575 | else: 576 | output = self.aes.encrypt(iput, key, size) 577 | for i in range(16): 578 | if len(output)-1 < i: 579 | plaintext[i] = 0 ^ ciphertext[i] 580 | elif len(ciphertext)-1 < i: 581 | plaintext[i] = output[i] ^ 0 582 | elif len(output)-1 < i and len(ciphertext) < i: 583 | plaintext[i] = 0 ^ 0 584 | else: 585 | plaintext[i] = output[i] ^ ciphertext[i] 586 | for k in range(end-start): 587 | chrOut.append(chr(plaintext[k])) 588 | iput = output 589 | elif mode == self.modeOfOperation["CBC"]: 590 | output = self.aes.decrypt(ciphertext, key, size) 591 | for i in range(16): 592 | if firstRound: 593 | plaintext[i] = IV[i] ^ output[i] 594 | else: 595 | plaintext[i] = iput[i] ^ output[i] 596 | firstRound = False 597 | if originalsize is not None and originalsize < end: 598 | for k in range(originalsize-start): 599 | chrOut.append(chr(plaintext[k])) 600 | else: 601 | for k in range(end-start): 602 | chrOut.append(chr(plaintext[k])) 603 | iput = ciphertext 604 | return "".join(chrOut) 605 | 606 | 607 | def append_PKCS7_padding(s): 608 | """return s padded to a multiple of 16-bytes by PKCS7 padding""" 609 | numpads = 16 - (len(s)%16) 610 | return s + numpads*chr(numpads) 611 | 612 | def strip_PKCS7_padding(s): 613 | """return s stripped of PKCS7 padding""" 614 | if len(s)%16 or not s: 615 | raise ValueError("String of len %d can't be PCKS7-padded" % len(s)) 616 | numpads = ord(s[-1]) 617 | if numpads > 16: 618 | return s 619 | # raise ValueError("String ending with %r can't be PCKS7-padded" % s[-1]) 620 | return s[:-numpads] 621 | 622 | def encryptData(key, data, mode=AESModeOfOperation.modeOfOperation["CBC"]): 623 | """encrypt `data` using `key` 624 | 625 | `key` should be a string of bytes. 626 | 627 | returned cipher is a string of bytes prepended with the initialization 628 | vector. 629 | 630 | """ 631 | key = map(ord, key) 632 | if mode == AESModeOfOperation.modeOfOperation["CBC"]: 633 | data = append_PKCS7_padding(data) 634 | keysize = len(key) 635 | assert keysize in AES.keySize.values(), 'invalid key size: %s' % keysize 636 | # create a new iv using random data 637 | iv = [ord(i) for i in os.urandom(16)] 638 | moo = AESModeOfOperation() 639 | (mode, length, ciph) = moo.encrypt(data, mode, key, keysize, iv) 640 | # With padding, the original length does not need to be known. It's a bad 641 | # idea to store the original message length. 642 | # prepend the iv. 643 | return ''.join(map(chr, iv)) + ''.join(map(chr, ciph)) 644 | 645 | def decryptData(key, data, mode=AESModeOfOperation.modeOfOperation["CBC"]): 646 | """decrypt `data` using `key` 647 | 648 | `key` should be a string of bytes. 649 | 650 | `data` should have the initialization vector prepended as a string of 651 | ordinal values. 652 | """ 653 | 654 | key = map(ord, key) 655 | keysize = len(key) 656 | assert keysize in AES.keySize.values(), 'invalid key size: %s' % keysize 657 | # iv is first 16 bytes 658 | iv = map(ord, data[:16]) 659 | data = map(ord, data[16:]) 660 | moo = AESModeOfOperation() 661 | decr = moo.decrypt(data, None, mode, key, keysize, iv) 662 | if mode == AESModeOfOperation.modeOfOperation["CBC"]: 663 | decr = strip_PKCS7_padding(decr) 664 | return decr 665 | 666 | def generateRandomKey(keysize): 667 | """Generates a key from random data of length `keysize`. 668 | The returned key is a string of bytes. 669 | """ 670 | if keysize not in (16, 24, 32): 671 | emsg = 'Invalid keysize, %s. Should be one of (16, 24, 32).' 672 | raise ValueError, emsg % keysize 673 | return os.urandom(keysize) 674 | 675 | def testStr(cleartext, keysize=16, modeName = "CBC"): 676 | '''Test with random key, choice of mode.''' 677 | print 'Random key test', 'Mode:', modeName 678 | print 'cleartext:', cleartext 679 | key = generateRandomKey(keysize) 680 | print 'Key:', [ord(x) for x in key] 681 | mode = AESModeOfOperation.modeOfOperation[modeName] 682 | cipher = encryptData(key, cleartext, mode) 683 | print 'Cipher:', [ord(x) for x in cipher] 684 | decr = decryptData(key, cipher, mode) 685 | print 'Decrypted:', decr 686 | 687 | 688 | if __name__ == "__main__": 689 | moo = AESModeOfOperation() 690 | cleartext = "This is a test with several blocks!" 691 | cypherkey = [143,194,34,208,145,203,230,143,177,246,97,206,145,92,255,84] 692 | iv = [103,35,148,239,76,213,47,118,255,222,123,176,106,134,98,92] 693 | mode, orig_len, ciph = moo.encrypt(cleartext, moo.modeOfOperation["CBC"], 694 | cypherkey, moo.aes.keySize["SIZE_128"], iv) 695 | print 'm=%s, ol=%s (%s), ciph=%s' % (mode, orig_len, len(cleartext), ciph) 696 | decr = moo.decrypt(ciph, orig_len, mode, cypherkey, 697 | moo.aes.keySize["SIZE_128"], iv) 698 | print decr 699 | testStr(cleartext, 16, "CBC") 700 | -------------------------------------------------------------------------------- /client.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | import binascii 3 | import datetime 4 | import random 5 | import socket 6 | import string 7 | import struct 8 | import sys 9 | import uuid 10 | import filetimes, rpcBind, rpcRequest 11 | 12 | from dcerpc import MSRPCHeader, MSRPCBindNak, MSRPCRequestHeader, MSRPCRespHeader 13 | from kmsBase import kmsBase, UUID 14 | from kmsRequestV4 import kmsRequestV4 15 | from kmsRequestV5 import kmsRequestV5 16 | from kmsRequestV6 import kmsRequestV6 17 | from rpcBase import rpcBase 18 | 19 | config = {} 20 | 21 | def main(): 22 | parser = argparse.ArgumentParser() 23 | parser.add_argument("ip", action="store", help="The IP address or hostname of the KMS host.", type=str) 24 | parser.add_argument("port", nargs="?", action="store", default=1688, help="The port the KMS service is listening on. The default is \"1688\".", type=int) 25 | parser.add_argument("-m", "--mode", dest="mode", choices=["WindowsVista","Windows7","Windows8","Windows81","Office2010","Office2013"], default="Windows7") 26 | 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) 27 | 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) 28 | parser.add_argument("-v", "--verbose", dest="verbose", action="store_const", const=True, default=False, help="Use this flag to enable verbose output.") 29 | parser.add_argument("-d", "--debug", dest="debug", action="store_const", const=True, default=False, help="Use this flag to enable debug output. Implies \"-v\".") 30 | config.update(vars(parser.parse_args())) 31 | checkConfig() 32 | config['call_id'] = 1 33 | if config['debug']: 34 | config['verbose'] = True 35 | updateConfig() 36 | s = socket.socket() 37 | print "Connecting to %s on port %d..." % (config['ip'], config['port']) 38 | s.connect((config['ip'], config['port'])) 39 | if config['verbose']: 40 | print "Connection successful!" 41 | binder = rpcBind.handler(None, config) 42 | RPC_Bind = str(binder.generateRequest()) 43 | if config['verbose']: 44 | print "Sending RPC bind request..." 45 | s.send(RPC_Bind) 46 | try: 47 | bindResponse = s.recv(1024) 48 | except socket.error, e: 49 | if e[0] == 104: 50 | print "Error: Connection reset by peer. Exiting..." 51 | sys.exit() 52 | else: 53 | raise 54 | if bindResponse == '' or not bindResponse: 55 | print "No data received! Exiting..." 56 | sys.exit() 57 | packetType = MSRPCHeader(bindResponse)['type'] 58 | if packetType == rpcBase.packetType['bindAck']: 59 | if config['verbose']: 60 | print "RPC bind acknowledged." 61 | kmsRequest = createKmsRequest() 62 | requester = rpcRequest.handler(kmsRequest, config) 63 | s.send(str(requester.generateRequest())) 64 | response = s.recv(1024) 65 | if config['debug']: 66 | print "Response:", binascii.b2a_hex(response) 67 | parsed = MSRPCRespHeader(response) 68 | kmsData = readKmsResponse(parsed['pduData'], kmsRequest, config) 69 | kmsResp = kmsData['response'] 70 | try: 71 | hwid = kmsData['hwid'] 72 | except: 73 | hwid = None 74 | print "KMS Host ePID:", kmsResp['kmsEpid'] 75 | if hwid is not None: 76 | print "KMS Host HWID:", binascii.b2a_hex(hwid).upper() 77 | print "KMS Host Current Client Count:", kmsResp['currentClientCount'] 78 | print "KMS VL Activation Interval:", kmsResp['vLActivationInterval'] 79 | print "KMS VL Renewal Interval:", kmsResp['vLRenewalInterval'] 80 | elif packetType == rpcBase.packetType['bindNak']: 81 | print MSRPCBindNak(bindResponse).dump() 82 | sys.exit() 83 | else: 84 | print "Something went wrong." 85 | sys.exit() 86 | 87 | def checkConfig(): 88 | if config['cmid'] is not None: 89 | try: 90 | uuid.UUID(config['cmid']) 91 | except: 92 | print "Error: Bad CMID. Exiting..." 93 | sys.exit() 94 | if config['machineName'] is not None: 95 | if len(config['machineName']) < 2 or len(config['machineName']) > 63: 96 | print "Error: machineName must be between 2 and 63 characters in length." 97 | sys.exit() 98 | 99 | def updateConfig(): 100 | if config['mode'] == 'WindowsVista': 101 | config['RequiredClientCount'] = 25 102 | config['KMSProtocolMajorVersion'] = 4 103 | config['KMSProtocolMinorVersion'] = 0 104 | config['KMSClientLicenseStatus'] = 2 105 | config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" 106 | config['KMSClientSkuID'] = "cfd8ff08-c0d7-452b-9f60-ef5c70c32094" 107 | config['KMSClientKMSCountedID'] = "212a64dc-43b1-4d3d-a30c-2fc69d2095c6" 108 | elif config['mode'] == 'Windows7': 109 | config['RequiredClientCount'] = 25 110 | config['KMSProtocolMajorVersion'] = 4 111 | config['KMSProtocolMinorVersion'] = 0 112 | config['KMSClientLicenseStatus'] = 2 113 | config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" 114 | config['KMSClientSkuID'] = "ae2ee509-1b34-41c0-acb7-6d4650168915" 115 | config['KMSClientKMSCountedID'] = "7fde5219-fbfa-484a-82c9-34d1ad53e856" 116 | elif config['mode'] == 'Windows8': 117 | config['RequiredClientCount'] = 25 118 | config['KMSProtocolMajorVersion'] = 5 119 | config['KMSProtocolMinorVersion'] = 0 120 | config['KMSClientLicenseStatus'] = 2 121 | config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" 122 | config['KMSClientSkuID'] = "458e1bec-837a-45f6-b9d5-925ed5d299de" 123 | config['KMSClientKMSCountedID'] = "3c40b358-5948-45af-923b-53d21fcc7e79" 124 | elif config['mode'] == 'Windows81': 125 | config['RequiredClientCount'] = 25 126 | config['KMSProtocolMajorVersion'] = 6 127 | config['KMSProtocolMinorVersion'] = 0 128 | config['KMSClientLicenseStatus'] = 2 129 | config['KMSClientAppID'] = "55c92734-d682-4d71-983e-d6ec3f16059f" 130 | config['KMSClientSkuID'] = "81671aaf-79d1-4eb1-b004-8cbbe173afea" 131 | config['KMSClientKMSCountedID'] = "cb8fc780-2c05-495a-9710-85afffc904d7" 132 | elif config['mode'] == 'Office2010': 133 | config['RequiredClientCount'] = 5 134 | config['KMSProtocolMajorVersion'] = 4 135 | config['KMSProtocolMinorVersion'] = 0 136 | config['KMSClientLicenseStatus'] = 2 137 | config['KMSClientAppID'] = "59a52881-a989-479d-af46-f275c6370663" 138 | config['KMSClientSkuID'] = "6f327760-8c5c-417c-9b61-836a98287e0c" 139 | config['KMSClientKMSCountedID'] = "e85af946-2e25-47b7-83e1-bebcebeac611" 140 | elif config['mode'] == 'Office2013': 141 | config['RequiredClientCount'] = 5 142 | config['KMSProtocolMajorVersion'] = 5 143 | config['KMSProtocolMinorVersion'] = 0 144 | config['KMSClientLicenseStatus'] = 2 145 | config['KMSClientAppID'] = "0ff1ce15-a989-479d-af46-f275c6370663" 146 | config['KMSClientSkuID'] = "b322da9c-a2e2-4058-9e4e-f59a6970bd69" 147 | config['KMSClientKMSCountedID'] = "e6a6f1bf-9d40-40c3-aa9f-c77ba21578c0" 148 | 149 | def createKmsRequestBase(): 150 | requestDict = kmsBase.kmsRequestStruct() 151 | requestDict['versionMinor'] = config['KMSProtocolMinorVersion'] 152 | requestDict['versionMajor'] = config['KMSProtocolMajorVersion'] 153 | requestDict['isClientVm'] = 0 154 | requestDict['licenseStatus'] = config['KMSClientLicenseStatus'] 155 | requestDict['graceTime'] = 43200 156 | requestDict['applicationId'] = UUID(uuid.UUID(config['KMSClientAppID']).bytes_le) 157 | requestDict['skuId'] = UUID(uuid.UUID(config['KMSClientSkuID']).bytes_le) 158 | requestDict['kmsCountedId'] = UUID(uuid.UUID(config['KMSClientKMSCountedID']).bytes_le) 159 | requestDict['clientMachineId'] = UUID(uuid.UUID(config['cmid']).bytes_le if (config['cmid'] is not None) else uuid.uuid4().bytes_le) 160 | requestDict['previousClientMachineId'] = '\0' * 16 #requestDict['clientMachineId'] # I'm pretty sure this is supposed to be a null UUID. 161 | requestDict['requiredClientCount'] = config['RequiredClientCount'] 162 | requestDict['requestTime'] = filetimes.dt_to_filetime(datetime.datetime.utcnow()) 163 | 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') 164 | requestDict['mnPad'] = '\0'.encode('utf-16le') * (63 - len(requestDict['machineName'].decode('utf-16le'))) 165 | 166 | # Debug Stuff 167 | if config['debug']: 168 | print "Request Base Dictionary:", requestDict.dump() 169 | 170 | return requestDict 171 | 172 | def createKmsRequest(): 173 | # Update the call ID 174 | config['call_id'] += 1 175 | 176 | # KMS Protocol Major Version 177 | if config['KMSProtocolMajorVersion'] == 4: 178 | handler = kmsRequestV4(None, config) 179 | elif config['KMSProtocolMajorVersion'] == 5: 180 | handler = kmsRequestV5(None, config) 181 | elif config['KMSProtocolMajorVersion'] == 6: 182 | handler = kmsRequestV6(None, config) 183 | else: 184 | return None 185 | 186 | requestBase = createKmsRequestBase() 187 | return handler.generateRequest(requestBase) 188 | 189 | def readKmsResponse(data, request, config): 190 | if config['KMSProtocolMajorVersion'] == 4: 191 | print "Received V4 response" 192 | response = readKmsResponseV4(data, request) 193 | elif config['KMSProtocolMajorVersion'] == 5: 194 | print "Received V5 response" 195 | response = readKmsResponseV5(data) 196 | elif config['KMSProtocolMajorVersion'] == 6: 197 | print "Received V6 response" 198 | response = readKmsResponseV6(data) 199 | else: 200 | print "Unhandled response version: %d.%d" % (config['KMSProtocolMajorVersion'], config['KMSProtocolMinorVersion']) 201 | print "I'm not even sure how this happened..." 202 | return response 203 | 204 | def readKmsResponseV4(data, request): 205 | response = kmsRequestV4.ResponseV4(data) 206 | hashed = kmsRequestV4(data, config).generateHash(bytearray(str(response['response']))) 207 | print "Response Hash has expected value:", hashed == response['hash'] 208 | return response 209 | 210 | def readKmsResponseV5(data): 211 | response = kmsRequestV5.ResponseV5(data) 212 | decrypted = kmsRequestV5(data, config).decryptResponse(response) 213 | return decrypted 214 | 215 | def readKmsResponseV6(data): 216 | response = kmsRequestV6.ResponseV5(data) 217 | decrypted = kmsRequestV6(data, config).decryptResponse(response) 218 | message = decrypted['message'] 219 | return message 220 | 221 | if __name__ == "__main__": 222 | main() 223 | -------------------------------------------------------------------------------- /dcerpc.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2003-2012 CORE Security Technologies 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # $Id: dcerpc.py 917 2013-11-10 20:47:57Z bethus $ 8 | # 9 | # Partial C706.pdf + [MS-RPCE] implementation 10 | # 11 | # ToDo: 12 | # [ ] Take out all the security provider stuff out of here (e.g. RPC_C_AUTHN_WINNT) 13 | # and put it elsewhere. This will make the coder cleaner and easier to add 14 | # more SSP (e.g. NETLOGON) 15 | # 16 | 17 | from structure import Structure,pack,unpack 18 | 19 | # MS/RPC Constants 20 | MSRPC_REQUEST = 0x00 21 | MSRPC_PING = 0x01 22 | MSRPC_RESPONSE = 0x02 23 | MSRPC_FAULT = 0x03 24 | MSRPC_WORKING = 0x04 25 | MSRPC_NOCALL = 0x05 26 | MSRPC_REJECT = 0x06 27 | MSRPC_ACK = 0x07 28 | MSRPC_CL_CANCEL = 0x08 29 | MSRPC_FACK = 0x09 30 | MSRPC_CANCELACK = 0x0A 31 | MSRPC_BIND = 0x0B 32 | MSRPC_BINDACK = 0x0C 33 | MSRPC_BINDNAK = 0x0D 34 | MSRPC_ALTERCTX = 0x0E 35 | MSRPC_ALTERCTX_R= 0x0F 36 | MSRPC_AUTH3 = 0x10 37 | MSRPC_SHUTDOWN = 0x11 38 | MSRPC_CO_CANCEL = 0x12 39 | MSRPC_ORPHANED = 0x13 40 | 41 | # MS/RPC Packet Flags 42 | MSRPC_FIRSTFRAG = 0x01 43 | MSRPC_LASTFRAG = 0x02 44 | 45 | # For PDU types bind, bind_ack, alter_context, and 46 | # alter_context_resp, this flag MUST be interpreted as PFC_SUPPORT_HEADER_SIGN 47 | MSRPC_SUPPORT_SIGN = 0x04 48 | 49 | #For the 50 | #remaining PDU types, this flag MUST be interpreted as PFC_PENDING_CANCEL. 51 | MSRPC_PENDING_CANCEL= 0x04 52 | 53 | MSRPC_NOTAFRAG = 0x04 54 | MSRPC_RECRESPOND = 0x08 55 | MSRPC_NOMULTIPLEX = 0x10 56 | MSRPC_NOTFORIDEMP = 0x20 57 | MSRPC_NOTFORBCAST = 0x40 58 | MSRPC_NOUUID = 0x80 59 | 60 | # Auth Types - Security Providers 61 | RPC_C_AUTHN_NONE = 0x00 62 | RPC_C_AUTHN_GSS_NEGOTIATE = 0x09 63 | RPC_C_AUTHN_WINNT = 0x0A 64 | RPC_C_AUTHN_GSS_SCHANNEL = 0x0E 65 | RPC_C_AUTHN_GSS_KERBEROS = 0x10 66 | RPC_C_AUTHN_NETLOGON = 0x44 67 | RPC_C_AUTHN_DEFAULT = 0xFF 68 | 69 | # Auth Levels 70 | RPC_C_AUTHN_LEVEL_NONE = 1 71 | RPC_C_AUTHN_LEVEL_CONNECT = 2 72 | RPC_C_AUTHN_LEVEL_CALL = 3 73 | RPC_C_AUTHN_LEVEL_PKT = 4 74 | RPC_C_AUTHN_LEVEL_PKT_INTEGRITY = 5 75 | RPC_C_AUTHN_LEVEL_PKT_PRIVACY = 6 76 | 77 | #Reasons for rejection of a context element, included in bind_ack result reason 78 | rpc_provider_reason = { 79 | 0 : 'reason_not_specified', 80 | 1 : 'abstract_syntax_not_supported', 81 | 2 : 'proposed_transfer_syntaxes_not_supported', 82 | 3 : 'local_limit_exceeded', 83 | 4 : 'protocol_version_not_specified', 84 | 8 : 'authentication_type_not_recognized', 85 | 9 : 'invalid_checksum' 86 | } 87 | 88 | MSRPC_CONT_RESULT_ACCEPT = 0 89 | MSRPC_CONT_RESULT_USER_REJECT = 1 90 | MSRPC_CONT_RESULT_PROV_REJECT = 2 91 | 92 | #Results of a presentation context negotiation 93 | rpc_cont_def_result = { 94 | 0 : 'acceptance', 95 | 1 : 'user_rejection', 96 | 2 : 'provider_rejection' 97 | } 98 | 99 | #status codes, references: 100 | #http://msdn.microsoft.com/library/default.asp?url=/library/en-us/rpc/rpc/rpc_return_values.asp 101 | #http://msdn.microsoft.com/library/default.asp?url=/library/en-us/randz/protocol/common_return_values.asp 102 | #winerror.h 103 | #http://www.opengroup.org/onlinepubs/9629399/apdxn.htm 104 | 105 | rpc_status_codes = { 106 | 0x00000005L : 'rpc_s_access_denied', 107 | 0x00000008L : 'Authentication type not recognized', 108 | 0x000006D8L : 'rpc_fault_cant_perform', 109 | 0x000006C6L : 'rpc_x_invalid_bound', # the arrays bound are invalid 110 | 0x000006E4L : 'rpc_s_cannot_support: The requested operation is not supported.', # some operation is not supported 111 | 0x000006F7L : 'rpc_x_bad_stub_data', # the stub data is invalid, doesn't match with the IDL definition 112 | 0x1C010001L : 'nca_s_comm_failure', # unable to get response from server: 113 | 0x1C010002L : 'nca_s_op_rng_error', # bad operation number in call 114 | 0x1C010003L : 'nca_s_unk_if', # unknown interface 115 | 0x1C010006L : 'nca_s_wrong_boot_time', # client passed server wrong server boot time 116 | 0x1C010009L : 'nca_s_you_crashed', # a restarted server called back a client 117 | 0x1C01000BL : 'nca_s_proto_error', # someone messed up the protocol 118 | 0x1C010013L : 'nca_s_out_args_too_big ', # output args too big 119 | 0x1C010014L : 'nca_s_server_too_busy', # server is too busy to handle call 120 | 0x1C010015L : 'nca_s_fault_string_too_long', # string argument longer than declared max len 121 | 0x1C010017L : 'nca_s_unsupported_type ', # no implementation of generic operation for object 122 | 0x1C000001L : 'nca_s_fault_int_div_by_zero', 123 | 0x1C000002L : 'nca_s_fault_addr_error ', 124 | 0x1C000003L : 'nca_s_fault_fp_div_zero', 125 | 0x1C000004L : 'nca_s_fault_fp_underflow', 126 | 0x1C000005L : 'nca_s_fault_fp_overflow', 127 | 0x1C000006L : 'nca_s_fault_invalid_tag', 128 | 0x1C000007L : 'nca_s_fault_invalid_bound ', 129 | 0x1C000008L : 'nca_s_rpc_version_mismatch', 130 | 0x1C000009L : 'nca_s_unspec_reject ', 131 | 0x1C00000AL : 'nca_s_bad_actid', 132 | 0x1C00000BL : 'nca_s_who_are_you_failed', 133 | 0x1C00000CL : 'nca_s_manager_not_entered ', 134 | 0x1C00000DL : 'nca_s_fault_cancel', 135 | 0x1C00000EL : 'nca_s_fault_ill_inst', 136 | 0x1C00000FL : 'nca_s_fault_fp_error', 137 | 0x1C000010L : 'nca_s_fault_int_overflow', 138 | 0x1C000012L : 'nca_s_fault_unspec', 139 | 0x1C000013L : 'nca_s_fault_remote_comm_failure ', 140 | 0x1C000014L : 'nca_s_fault_pipe_empty ', 141 | 0x1C000015L : 'nca_s_fault_pipe_closed', 142 | 0x1C000016L : 'nca_s_fault_pipe_order ', 143 | 0x1C000017L : 'nca_s_fault_pipe_discipline', 144 | 0x1C000018L : 'nca_s_fault_pipe_comm_error', 145 | 0x1C000019L : 'nca_s_fault_pipe_memory', 146 | 0x1C00001AL : 'nca_s_fault_context_mismatch ', 147 | 0x1C00001BL : 'nca_s_fault_remote_no_memory ', 148 | 0x1C00001CL : 'nca_s_invalid_pres_context_id', 149 | 0x1C00001DL : 'nca_s_unsupported_authn_level', 150 | 0x1C00001FL : 'nca_s_invalid_checksum ', 151 | 0x1C000020L : 'nca_s_invalid_crc', 152 | 0x1C000021L : 'nca_s_fault_user_defined', 153 | 0x1C000022L : 'nca_s_fault_tx_open_failed', 154 | 0x1C000023L : 'nca_s_fault_codeset_conv_error', 155 | 0x1C000024L : 'nca_s_fault_object_not_found ', 156 | 0x1C000025L : 'nca_s_fault_no_client_stub' 157 | } 158 | 159 | class Exception(Exception): 160 | pass 161 | 162 | # Context Item 163 | class CtxItem(Structure): 164 | structure = ( 165 | ('ContextID',' 0 else 0)'), 203 | ('pduData',':'), 204 | ('_pad', '_-pad','(4 - ((self._SIZE + len(self["pduData"])) & 3) & 3)'), 205 | ('pad', ':'), 206 | ('_sec_trailer', '_-sec_trailer', '8 if self["auth_len"] > 0 else 0'), 207 | ('sec_trailer',':'), 208 | ('auth_dataLen','_-auth_data','self["auth_len"]'), 209 | ('auth_data',':'), 210 | ) 211 | 212 | def __init__(self, data = None, alignment = 0): 213 | Structure.__init__(self,data, alignment) 214 | if data is None: 215 | self['ver_major'] = 5 216 | self['ver_minor'] = 0 217 | self['flags'] = MSRPC_FIRSTFRAG | MSRPC_LASTFRAG 218 | self['type'] = MSRPC_REQUEST 219 | self.__frag_len_set = 0 220 | self['auth_len'] = 0 221 | self['pduData'] = '' 222 | self['auth_data'] = '' 223 | self['sec_trailer'] = '' 224 | self['pad'] = '' 225 | 226 | def get_header_size(self): 227 | return self._SIZE 228 | 229 | def get_packet(self): 230 | if self['auth_data'] != '': 231 | self['auth_len'] = len(self['auth_data']) 232 | # The sec_trailer structure MUST be 4-byte aligned with respect to 233 | # the beginning of the PDU. Padding octets MUST be used to align the 234 | # sec_trailer structure if its natural beginning is not already 4-byte aligned 235 | ##self['pad'] = '\xAA' * (4 - ((self._SIZE + len(self['pduData'])) & 3) & 3) 236 | 237 | return self.getData() 238 | 239 | class MSRPCRequestHeader(MSRPCHeader): 240 | _SIZE = 24 241 | commonHdr = MSRPCHeader.commonHdr + ( 242 | ('alloc_hint',' 0 else 0'), 327 | ('sec_trailer',':'), 328 | ('auth_dataLen','_-auth_data','self["auth_len"]'), 329 | ('auth_data',':'), 330 | ) 331 | def __init__(self, data = None, alignment = 0): 332 | self.__ctx_items = [] 333 | Structure.__init__(self,data,alignment) 334 | if data is None: 335 | self['Pad'] = '' 336 | self['ctx_items'] = '' 337 | self['sec_trailer'] = '' 338 | self['auth_data'] = '' 339 | 340 | def getCtxItems(self): 341 | return self.__ctx_items 342 | 343 | def getCtxItem(self,index): 344 | return self.__ctx_items[index-1] 345 | 346 | def fromString(self, data): 347 | Structure.fromString(self,data) 348 | # Parse the ctx_items 349 | data = self['ctx_items'] 350 | for i in range(self['ctx_num']): 351 | item = CtxItemResult(data) 352 | self.__ctx_items.append(item) 353 | data = data[len(item):] 354 | 355 | class MSRPCBindNak(Structure): 356 | structure = ( 357 | ('RejectedReason',' 2 | # All rights reserved. 3 | # 4 | # Redistribution and use in source and binary forms, with or without 5 | # modification, are permitted provided that the following conditions are 6 | # met: 7 | # 8 | # * Redistributions of source code must retain the above copyright 9 | # notice, this list of conditions and the following disclaimer. 10 | # * Redistributions in binary form must reproduce the above copyright 11 | # notice, this list of conditions and the following disclaimer in the 12 | # documentation and/or other materials provided with the distribution. 13 | # 14 | # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS 15 | # IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 16 | # TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A 17 | # PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 18 | # HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 19 | # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED 20 | # TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 21 | # PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 22 | # LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 23 | # NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | # SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | """Tools to convert between Python datetime instances and Microsoft times. 26 | """ 27 | from datetime import datetime, timedelta, tzinfo 28 | from calendar import timegm 29 | 30 | 31 | # http://support.microsoft.com/kb/167296 32 | # How To Convert a UNIX time_t to a Win32 FILETIME or SYSTEMTIME 33 | EPOCH_AS_FILETIME = 116444736000000000 # January 1, 1970 as MS file time 34 | HUNDREDS_OF_NANOSECONDS = 10000000 35 | 36 | 37 | ZERO = timedelta(0) 38 | HOUR = timedelta(hours=1) 39 | 40 | 41 | class UTC(tzinfo): 42 | """UTC""" 43 | def utcoffset(self, dt): 44 | return ZERO 45 | 46 | def tzname(self, dt): 47 | return "UTC" 48 | 49 | def dst(self, dt): 50 | return ZERO 51 | 52 | 53 | utc = UTC() 54 | 55 | 56 | def dt_to_filetime(dt): 57 | """Converts a datetime to Microsoft filetime format. If the object is 58 | time zone-naive, it is forced to UTC before conversion. 59 | 60 | >>> "%.0f" % dt_to_filetime(datetime(2009, 7, 25, 23, 0)) 61 | '128930364000000000' 62 | 63 | >>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0, tzinfo=utc)) 64 | '116444736000000000' 65 | 66 | >>> "%.0f" % dt_to_filetime(datetime(1970, 1, 1, 0, 0)) 67 | '116444736000000000' 68 | 69 | >>> dt_to_filetime(datetime(2009, 7, 25, 23, 0, 0, 100)) 70 | 128930364000001000 71 | """ 72 | if (dt.tzinfo is None) or (dt.tzinfo.utcoffset(dt) is None): 73 | dt = dt.replace(tzinfo=utc) 74 | ft = EPOCH_AS_FILETIME + (timegm(dt.timetuple()) * HUNDREDS_OF_NANOSECONDS) 75 | return ft + (dt.microsecond * 10) 76 | 77 | 78 | def filetime_to_dt(ft): 79 | """Converts a Microsoft filetime number to a Python datetime. The new 80 | datetime object is time zone-naive but is equivalent to tzinfo=utc. 81 | 82 | >>> filetime_to_dt(116444736000000000) 83 | datetime.datetime(1970, 1, 1, 0, 0) 84 | 85 | >>> filetime_to_dt(128930364000000000) 86 | datetime.datetime(2009, 7, 25, 23, 0) 87 | 88 | >>> filetime_to_dt(128930364000001000) 89 | datetime.datetime(2009, 7, 25, 23, 0, 0, 100) 90 | """ 91 | # Get seconds and remainder in terms of Unix epoch 92 | (s, ns100) = divmod(ft - EPOCH_AS_FILETIME, HUNDREDS_OF_NANOSECONDS) 93 | # Convert to datetime object 94 | dt = datetime.utcfromtimestamp(s) 95 | # Add remainder in as microseconds. Python 3.2 requires an integer 96 | dt = dt.replace(microsecond=(ns100 // 10)) 97 | return dt 98 | 99 | 100 | if __name__ == "__main__": 101 | import doctest 102 | 103 | doctest.testmod() 104 | 105 | -------------------------------------------------------------------------------- /kmsBase.py: -------------------------------------------------------------------------------- 1 | import binascii 2 | import datetime 3 | import filetimes 4 | import kmsPidGenerator 5 | import os 6 | import struct 7 | import sys 8 | import time 9 | import uuid 10 | 11 | from structure import Structure 12 | 13 | # sqlite3 is optional 14 | try: 15 | import sqlite3 16 | except: 17 | pass 18 | 19 | class UUID(Structure): 20 | commonHdr = () 21 | structure = ( 22 | ('raw', '16s'), 23 | ) 24 | 25 | def get(self): 26 | return uuid.UUID(bytes_le=str(self)) 27 | 28 | class kmsBase: 29 | class kmsRequestStruct(Structure): 30 | commonHdr = () 31 | structure = ( 32 | ('versionMinor', '> 4 68 | 69 | # Remainding bytes 70 | k = messageSize & 0xf 71 | 72 | # Hash 73 | for i in range(0, j): 74 | xorBuffer(message, i << 4, hashBuffer, 16) 75 | hashBuffer = bytearray(aes.encrypt(hashBuffer, key, len(key))) 76 | 77 | # Bit Padding 78 | ii = 0 79 | for i in range(j << 4, k + (j << 4)): 80 | lastBlock[ii] = message[i] 81 | ii += 1 82 | lastBlock[k] = 0x80 83 | 84 | xorBuffer(lastBlock, 0, hashBuffer, 16) 85 | hashBuffer = bytearray(aes.encrypt(hashBuffer, key, len(key))) 86 | 87 | return str(hashBuffer) 88 | 89 | def generateResponse(self, responseBuffer, hash): 90 | bodyLength = len(responseBuffer) + len(hash) 91 | response = self.ResponseV4() 92 | response['response'] = responseBuffer 93 | response['hash'] = hash 94 | response['padding'] = self.getResponsePadding(bodyLength) 95 | 96 | if self.config['debug']: 97 | print "KMS V4 Response:", response.dump() 98 | print "KMS V4 Response Bytes:", binascii.b2a_hex(str(response)) 99 | 100 | return str(response) 101 | 102 | def getResponse(self): 103 | return self.responseData 104 | 105 | def generateRequest(self, requestBase): 106 | hash = str(self.generateHash(bytearray(str(requestBase)))) 107 | 108 | bodyLength = len(requestBase) + len(hash) 109 | 110 | request = kmsRequestV4.RequestV4() 111 | request['bodyLength1'] = bodyLength 112 | request['bodyLength2'] = bodyLength 113 | request['request'] = requestBase 114 | request['hash'] = hash 115 | request['padding'] = self.getResponsePadding(bodyLength) 116 | 117 | if self.config['debug']: 118 | print "Request V4 Data:", request.dump() 119 | print "Request V4:", binascii.b2a_hex(str(request)) 120 | 121 | return request 122 | -------------------------------------------------------------------------------- /kmsRequestV5.py: -------------------------------------------------------------------------------- 1 | import aes 2 | import binascii 3 | import hashlib 4 | import random 5 | import struct 6 | from kmsBase import kmsBase 7 | from structure import Structure 8 | 9 | class kmsRequestV5(kmsBase): 10 | class RequestV5(Structure): 11 | class Message(Structure): 12 | commonHdr = () 13 | structure = ( 14 | ('salt', '16s'), 15 | ('encrypted', '236s'), #kmsBase.kmsRequestStruct 16 | ('padding', ':'), 17 | ) 18 | 19 | commonHdr = () 20 | structure = ( 21 | ('bodyLength1', ' 16: 39 | print "Error: HWID \"%s\" is invalid. Hex string is too long." % binascii.b2a_hex(config['hwid']) 40 | return 41 | except TypeError: 42 | print "Error: HWID \"%s\" is invalid. Odd-length hex string." % binascii.b2a_hex(config['hwid']) 43 | return 44 | if config['debug']: 45 | config['verbose'] = True 46 | try: 47 | import sqlite3 48 | config['dbSupport'] = True 49 | except: 50 | print "Warning: Module \"sqlite3\" is not installed--database support disabled." 51 | config['dbSupport'] = False 52 | server = SocketServer.TCPServer((config['ip'], config['port']), kmsServer) 53 | server.timeout = 5 54 | print "TCP server listening at %s on port %d." % (config['ip'],config['port']) 55 | server.serve_forever() 56 | 57 | class kmsServer(SocketServer.BaseRequestHandler): 58 | def setup(self): 59 | self.connection = self.request 60 | print "Connection accepted: %s:%d" % (self.client_address[0],self.client_address[1]) 61 | 62 | def handle(self): 63 | while True: 64 | # self.request is the TCP socket connected to the client 65 | try: 66 | self.data = self.connection.recv(1024) 67 | except socket.error, e: 68 | if e[0] == 104: 69 | print "Error: Connection reset by peer." 70 | break 71 | else: 72 | raise 73 | if self.data == '' or not self.data: 74 | print "No data received!" 75 | break 76 | # self.data = bytearray(self.data.strip()) 77 | # print binascii.b2a_hex(str(self.data)) 78 | packetType = MSRPCHeader(self.data)['type'] 79 | if packetType == rpcBase.packetType['bindReq']: 80 | if config['verbose']: 81 | print "RPC bind request received." 82 | handler = rpcBind.handler(self.data, config) 83 | elif packetType == rpcBase.packetType['request']: 84 | if config['verbose']: 85 | print "Received activation request." 86 | handler = rpcRequest.handler(self.data, config) 87 | else: 88 | print "Error: Invalid RPC request type", packetType 89 | break 90 | 91 | handler.populate() 92 | res = str(handler.getResponse()) 93 | self.connection.send(res) 94 | 95 | if packetType == rpcBase.packetType['bindReq']: 96 | if config['verbose']: 97 | print "RPC bind acknowledged." 98 | elif packetType == rpcBase.packetType['request']: 99 | if config['verbose']: 100 | print "Responded to activation request." 101 | break 102 | 103 | def finish(self): 104 | self.connection.close() 105 | print "Connection closed: %s:%d" % (self.client_address[0],self.client_address[1]) 106 | 107 | if __name__ == "__main__": 108 | main() 109 | -------------------------------------------------------------------------------- /structure.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2003-2012 CORE Security Technologies 2 | # 3 | # This software is provided under under a slightly modified version 4 | # of the Apache Software License. See the accompanying LICENSE file 5 | # for more information. 6 | # 7 | # $Id$ 8 | # 9 | 10 | from struct import pack, unpack, calcsize 11 | 12 | class Structure: 13 | """ sublcasses can define commonHdr and/or structure. 14 | each of them is an tuple of either two: (fieldName, format) or three: (fieldName, ':', class) fields. 15 | [it can't be a dictionary, because order is important] 16 | 17 | where format specifies how the data in the field will be converted to/from bytes (string) 18 | class is the class to use when unpacking ':' fields. 19 | 20 | each field can only contain one value (or an array of values for *) 21 | i.e. struct.pack('Hl',1,2) is valid, but format specifier 'Hl' is not (you must use 2 dfferent fields) 22 | 23 | format specifiers: 24 | specifiers from module pack can be used with the same format 25 | see struct.__doc__ (pack/unpack is finally called) 26 | x [padding byte] 27 | c [character] 28 | b [signed byte] 29 | B [unsigned byte] 30 | h [signed short] 31 | H [unsigned short] 32 | l [signed long] 33 | L [unsigned long] 34 | i [signed integer] 35 | I [unsigned integer] 36 | q [signed long long (quad)] 37 | Q [unsigned long long (quad)] 38 | s [string (array of chars), must be preceded with length in format specifier, padded with zeros] 39 | p [pascal string (includes byte count), must be preceded with length in format specifier, padded with zeros] 40 | f [float] 41 | d [double] 42 | = [native byte ordering, size and alignment] 43 | @ [native byte ordering, standard size and alignment] 44 | ! [network byte ordering] 45 | < [little endian] 46 | > [big endian] 47 | 48 | usual printf like specifiers can be used (if started with %) 49 | [not recommeneded, there is no why to unpack this] 50 | 51 | %08x will output an 8 bytes hex 52 | %s will output a string 53 | %s\\x00 will output a NUL terminated string 54 | %d%d will output 2 decimal digits (against the very same specification of Structure) 55 | ... 56 | 57 | some additional format specifiers: 58 | : 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) 59 | z same as :, but adds a NUL byte at the end (asciiz) (for unpacking the first NUL byte is used as terminator) [asciiz string] 60 | 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] 61 | w DCE-RPC/NDR string (it's a macro for [ ' 2: 147 | dataClassOrCode = field[2] 148 | try: 149 | self[field[0]] = self.unpack(field[1], data[:size], dataClassOrCode = dataClassOrCode, field = field[0]) 150 | except Exception,e: 151 | e.args += ("When unpacking field '%s | %s | %r[:%d]'" % (field[0], field[1], data, size),) 152 | raise 153 | 154 | size = self.calcPackSize(field[1], self[field[0]], field[0]) 155 | if self.alignment and size % self.alignment: 156 | size += self.alignment - (size % self.alignment) 157 | data = data[size:] 158 | 159 | return self 160 | 161 | def __setitem__(self, key, value): 162 | self.fields[key] = value 163 | self.data = None # force recompute 164 | 165 | def __getitem__(self, key): 166 | return self.fields[key] 167 | 168 | def __delitem__(self, key): 169 | del self.fields[key] 170 | 171 | def __str__(self): 172 | return self.getData() 173 | 174 | def __len__(self): 175 | # XXX: improve 176 | return len(self.getData()) 177 | 178 | def pack(self, format, data, field = None): 179 | if self.debug: 180 | print " pack( %s | %r | %s)" % (format, data, field) 181 | 182 | if field: 183 | addressField = self.findAddressFieldFor(field) 184 | if (addressField is not None) and (data is None): 185 | return '' 186 | 187 | # void specifier 188 | if format[:1] == '_': 189 | return '' 190 | 191 | # quote specifier 192 | if format[:1] == "'" or format[:1] == '"': 193 | return format[1:] 194 | 195 | # code specifier 196 | two = format.split('=') 197 | if len(two) >= 2: 198 | try: 199 | return self.pack(two[0], data) 200 | except: 201 | fields = {'self':self} 202 | fields.update(self.fields) 203 | return self.pack(two[0], eval(two[1], {}, fields)) 204 | 205 | # address specifier 206 | two = format.split('&') 207 | if len(two) == 2: 208 | try: 209 | return self.pack(two[0], data) 210 | except: 211 | if (self.fields.has_key(two[1])) and (self[two[1]] is not None): 212 | return self.pack(two[0], id(self[two[1]]) & ((1<<(calcsize(two[0])*8))-1) ) 213 | else: 214 | return self.pack(two[0], 0) 215 | 216 | # length specifier 217 | two = format.split('-') 218 | if len(two) == 2: 219 | try: 220 | return self.pack(two[0],data) 221 | except: 222 | return self.pack(two[0], self.calcPackFieldSize(two[1])) 223 | 224 | # array specifier 225 | two = format.split('*') 226 | if len(two) == 2: 227 | answer = '' 228 | for each in data: 229 | answer += self.pack(two[1], each) 230 | if two[0]: 231 | if two[0].isdigit(): 232 | if int(two[0]) != len(data): 233 | raise Exception, "Array field has a constant size, and it doesn't match the actual value" 234 | else: 235 | return self.pack(two[0], len(data))+answer 236 | return answer 237 | 238 | # "printf" string specifier 239 | if format[:1] == '%': 240 | # format string like specifier 241 | return format % data 242 | 243 | # asciiz specifier 244 | if format[:1] == 'z': 245 | return str(data)+'\0' 246 | 247 | # unicode specifier 248 | if format[:1] == 'u': 249 | return str(data)+'\0\0' + (len(data) & 1 and '\0' or '') 250 | 251 | # DCE-RPC/NDR string specifier 252 | if format[:1] == 'w': 253 | if len(data) == 0: 254 | data = '\0\0' 255 | elif len(data) % 2: 256 | data += '\0' 257 | l = pack('= 2: 304 | return self.unpack(two[0],data) 305 | 306 | # length specifier 307 | two = format.split('-') 308 | if len(two) == 2: 309 | return self.unpack(two[0],data) 310 | 311 | # array specifier 312 | two = format.split('*') 313 | if len(two) == 2: 314 | answer = [] 315 | sofar = 0 316 | if two[0].isdigit(): 317 | number = int(two[0]) 318 | elif two[0]: 319 | sofar += self.calcUnpackSize(two[0], data) 320 | number = self.unpack(two[0], data[:sofar]) 321 | else: 322 | number = -1 323 | 324 | while number and sofar < len(data): 325 | nsofar = sofar + self.calcUnpackSize(two[1],data[sofar:]) 326 | answer.append(self.unpack(two[1], data[sofar:nsofar], dataClassOrCode)) 327 | number -= 1 328 | sofar = nsofar 329 | return answer 330 | 331 | # "printf" string specifier 332 | if format[:1] == '%': 333 | # format string like specifier 334 | return format % data 335 | 336 | # asciiz specifier 337 | if format == 'z': 338 | if data[-1] != '\x00': 339 | raise Exception, ("%s 'z' field is not NUL terminated: %r" % (field, data)) 340 | return data[:-1] # remove trailing NUL 341 | 342 | # unicode specifier 343 | if format == 'u': 344 | if data[-2:] != '\x00\x00': 345 | raise Exception, ("%s 'u' field is not NUL-NUL terminated: %r" % (field, data)) 346 | return data[:-2] # remove trailing NUL 347 | 348 | # DCE-RPC/NDR string specifier 349 | if format == 'w': 350 | l = unpack('= 2: 384 | return self.calcPackSize(two[0], data) 385 | 386 | # length specifier 387 | two = format.split('-') 388 | if len(two) == 2: 389 | return self.calcPackSize(two[0], data) 390 | 391 | # array specifier 392 | two = format.split('*') 393 | if len(two) == 2: 394 | answer = 0 395 | if two[0].isdigit(): 396 | if int(two[0]) != len(data): 397 | raise Exception, "Array field has a constant size, and it doesn't match the actual value" 398 | elif two[0]: 399 | answer += self.calcPackSize(two[0], len(data)) 400 | 401 | for each in data: 402 | answer += self.calcPackSize(two[1], each) 403 | return answer 404 | 405 | # "printf" string specifier 406 | if format[:1] == '%': 407 | # format string like specifier 408 | return len(format % data) 409 | 410 | # asciiz specifier 411 | if format[:1] == 'z': 412 | return len(data)+1 413 | 414 | # asciiz specifier 415 | if format[:1] == 'u': 416 | l = len(data) 417 | return l + (l & 1 and 3 or 2) 418 | 419 | # DCE-RPC/NDR string specifier 420 | if format[:1] == 'w': 421 | l = len(data) 422 | return 12+l+l % 2 423 | 424 | # literal specifier 425 | if format[:1] == ':': 426 | return len(data) 427 | 428 | # struct like specifier 429 | return calcsize(format) 430 | 431 | def calcUnpackSize(self, format, data, field = None): 432 | if self.debug: 433 | print " calcUnpackSize( %s | %s | %r)" % (field, format, data) 434 | 435 | # void specifier 436 | if format[:1] == '_': 437 | return 0 438 | 439 | addressField = self.findAddressFieldFor(field) 440 | if addressField is not None: 441 | if not self[addressField]: 442 | return 0 443 | 444 | try: 445 | lengthField = self.findLengthFieldFor(field) 446 | return self[lengthField] 447 | except: 448 | pass 449 | 450 | # XXX: Try to match to actual values, raise if no match 451 | 452 | # quote specifier 453 | if format[:1] == "'" or format[:1] == '"': 454 | return len(format)-1 455 | 456 | # address specifier 457 | two = format.split('&') 458 | if len(two) == 2: 459 | return self.calcUnpackSize(two[0], data) 460 | 461 | # code specifier 462 | two = format.split('=') 463 | if len(two) >= 2: 464 | return self.calcUnpackSize(two[0], data) 465 | 466 | # length specifier 467 | two = format.split('-') 468 | if len(two) == 2: 469 | return self.calcUnpackSize(two[0], data) 470 | 471 | # array specifier 472 | two = format.split('*') 473 | if len(two) == 2: 474 | answer = 0 475 | if two[0]: 476 | if two[0].isdigit(): 477 | number = int(two[0]) 478 | else: 479 | answer += self.calcUnpackSize(two[0], data) 480 | number = self.unpack(two[0], data[:answer]) 481 | 482 | while number: 483 | number -= 1 484 | answer += self.calcUnpackSize(two[1], data[answer:]) 485 | else: 486 | while answer < len(data): 487 | answer += self.calcUnpackSize(two[1], data[answer:]) 488 | return answer 489 | 490 | # "printf" string specifier 491 | if format[:1] == '%': 492 | raise Exception, "Can't guess the size of a printf like specifier for unpacking" 493 | 494 | # asciiz specifier 495 | if format[:1] == 'z': 496 | return data.index('\x00')+1 497 | 498 | # asciiz specifier 499 | if format[:1] == 'u': 500 | l = data.index('\x00\x00') 501 | return l + (l & 1 and 3 or 2) 502 | 503 | # DCE-RPC/NDR string specifier 504 | if format[:1] == 'w': 505 | l = unpack('L'), 627 | ('code1','>L=len(arr1)*2+0x1000'), 628 | ) 629 | 630 | def populate(self, a): 631 | a['default'] = 'hola' 632 | a['int1'] = 0x3131 633 | a['int3'] = 0x45444342 634 | a['z1'] = 'hola' 635 | a['u1'] = 'hola'.encode('utf_16_le') 636 | a[':1'] = ':1234:' 637 | a['arr1'] = (0x12341234,0x88990077,0x41414141) 638 | # a['len1'] = 0x42424242 639 | 640 | class _Test_fixedLength(_Test_simple): 641 | def populate(self, a): 642 | _Test_simple.populate(self, a) 643 | a['len1'] = 0x42424242 644 | 645 | class _Test_simple_aligned4(_Test_simple): 646 | alignment = 4 647 | 648 | class _Test_nested(_StructureTest): 649 | class theClass(Structure): 650 | class _Inner(Structure): 651 | structure = (('data', 'z'),) 652 | 653 | structure = ( 654 | ('nest1', ':', _Inner), 655 | ('nest2', ':', _Inner), 656 | ('int', '> 8)'), 716 | ('pad', '_','((iv >>2) & 0x3F)'), 717 | ('keyid', '_','( iv & 0x03 )'), 718 | ('dataLen', '_-data', 'len(inputDataLeft)-4'), 719 | ('data',':'), 720 | ('icv','>L'), 721 | ) 722 | 723 | def populate(self, a): 724 | a['init_vector']=0x01020304 725 | #a['pad']=int('01010101',2) 726 | a['pad']=int('010101',2) 727 | a['keyid']=0x07 728 | a['data']="\xA0\xA1\xA2\xA3\xA4\xA5\xA6\xA7\xA8\xA9" 729 | a['icv'] = 0x05060708 730 | #a['iv'] = 0x01020304 731 | 732 | if __name__ == '__main__': 733 | _Test_simple().run() 734 | 735 | try: 736 | _Test_fixedLength().run() 737 | except: 738 | print "cannot repack because length is bogus" 739 | 740 | _Test_simple_aligned4().run() 741 | _Test_nested().run() 742 | _Test_Optional().run() 743 | _Test_Optional_sparse().run() 744 | _Test_AsciiZArray().run() 745 | _Test_UnpackCode().run() 746 | _Test_AAA().run() 747 | -------------------------------------------------------------------------------- /timezones.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # 3 | # Copyright 2009 Google Inc. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); 6 | # you may not use this file except in compliance with the License. 7 | # You may obtain a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, 13 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | # See the License for the specific language governing permissions and 15 | # limitations under the License. 16 | # 17 | # 18 | # Disable the invalid name warning as we are inheriting from a standard library 19 | # object. 20 | # pylint: disable-msg=C6409,W0212 21 | 22 | """ 23 | Stripped down version of `python-datetime-tz` that only contains the "find local 24 | timezone" bits. 25 | """ 26 | 27 | import datetime 28 | import os.path 29 | import time 30 | import warnings 31 | import pytz 32 | 33 | 34 | # Need to patch pytz.utc to have a _utcoffset so you can normalize/localize 35 | # using it. 36 | pytz.utc._utcoffset = datetime.timedelta() 37 | 38 | 39 | timedelta = datetime.timedelta 40 | 41 | 42 | def _tzinfome(tzinfo): 43 | """Gets a tzinfo object from a string. 44 | 45 | Args: 46 | tzinfo: A string (or string like) object, or a datetime.tzinfo object. 47 | 48 | Returns: 49 | An datetime.tzinfo object. 50 | 51 | Raises: 52 | UnknownTimeZoneError: If the timezone given can't be decoded. 53 | """ 54 | if not isinstance(tzinfo, datetime.tzinfo): 55 | try: 56 | tzinfo = pytz.timezone(tzinfo) 57 | except AttributeError: 58 | raise pytz.UnknownTimeZoneError("Unknown timezone!") 59 | return tzinfo 60 | 61 | 62 | # Our "local" timezone 63 | _localtz = None 64 | 65 | 66 | def localtz(): 67 | """Get the local timezone. 68 | 69 | Returns: 70 | The localtime timezone as a tzinfo object. 71 | """ 72 | # pylint: disable-msg=W0603 73 | global _localtz 74 | if _localtz is None: 75 | _localtz = detect_timezone() 76 | return _localtz 77 | 78 | 79 | def detect_timezone(): 80 | """Try and detect the timezone that Python is currently running in. 81 | 82 | We have a bunch of different methods for trying to figure this out (listed in 83 | order they are attempted). 84 | * Try and find /etc/timezone file (with timezone name). 85 | * Try and find /etc/localtime file (with timezone data). 86 | * Try and match a TZ to the current dst/offset/shortname. 87 | 88 | Returns: 89 | The detected local timezone as a tzinfo object 90 | 91 | Raises: 92 | pytz.UnknownTimeZoneError: If it was unable to detect a timezone. 93 | """ 94 | 95 | tz = _detect_timezone_etc_timezone() 96 | if tz is not None: 97 | return tz 98 | 99 | tz = _detect_timezone_etc_localtime() 100 | if tz is not None: 101 | return tz 102 | 103 | # Next we try and use a similiar method to what PHP does. 104 | # We first try to search on time.tzname, time.timezone, time.daylight to 105 | # match a pytz zone. 106 | warnings.warn("Had to fall back to worst detection method (the 'PHP' " 107 | "method).") 108 | 109 | tz = _detect_timezone_php() 110 | if tz is not None: 111 | return tz 112 | 113 | raise pytz.UnknownTimeZoneError("Unable to detect your timezone!") 114 | 115 | def _detect_timezone_etc_timezone(): 116 | if os.path.exists("/etc/timezone"): 117 | try: 118 | tz = file("/etc/timezone").read().strip() 119 | try: 120 | return pytz.timezone(tz) 121 | except (IOError, pytz.UnknownTimeZoneError), ei: 122 | warnings.warn("Your /etc/timezone file references a timezone (%r) that" 123 | " is not valid (%r)." % (tz, ei)) 124 | 125 | # Problem reading the /etc/timezone file 126 | except IOError, eo: 127 | warnings.warn("Could not access your /etc/timezone file: %s" % eo) 128 | 129 | 130 | def _detect_timezone_etc_localtime(): 131 | matches = [] 132 | if os.path.exists("/etc/localtime"): 133 | localtime = pytz.tzfile.build_tzinfo("/etc/localtime", 134 | file("/etc/localtime")) 135 | 136 | # See if we can find a "Human Name" for this.. 137 | for tzname in pytz.all_timezones: 138 | tz = _tzinfome(tzname) 139 | 140 | if dir(tz) != dir(localtime): 141 | continue 142 | 143 | for attrib in dir(tz): 144 | # Ignore functions and specials 145 | if callable(getattr(tz, attrib)) or attrib.startswith("__"): 146 | continue 147 | 148 | # This will always be different 149 | if attrib == "zone" or attrib == "_tzinfos": 150 | continue 151 | 152 | if getattr(tz, attrib) != getattr(localtime, attrib): 153 | break 154 | 155 | # We get here iff break didn't happen, i.e. no meaningful attributes 156 | # differ between tz and localtime 157 | else: 158 | matches.append(tzname) 159 | 160 | #if len(matches) == 1: 161 | # return _tzinfome(matches[0]) 162 | #else: 163 | # # Warn the person about this! 164 | # warning = "Could not get a human name for your timezone: " 165 | # if len(matches) > 1: 166 | # warning += ("We detected multiple matches for your /etc/localtime. " 167 | # "(Matches where %s)" % matches) 168 | # else: 169 | # warning += "We detected no matches for your /etc/localtime." 170 | # warnings.warn(warning) 171 | # 172 | # return localtime 173 | if len(matches) > 0: 174 | return _tzinfome(matches[0]) 175 | 176 | 177 | def _detect_timezone_php(): 178 | tomatch = (time.tzname[0], time.timezone, time.daylight) 179 | now = datetime.datetime.now() 180 | 181 | matches = [] 182 | for tzname in pytz.all_timezones: 183 | try: 184 | tz = pytz.timezone(tzname) 185 | except IOError: 186 | continue 187 | 188 | try: 189 | indst = tz.localize(now).timetuple()[-1] 190 | 191 | if tomatch == (tz._tzname, -tz._utcoffset.seconds, indst): 192 | matches.append(tzname) 193 | 194 | # pylint: disable-msg=W0704 195 | except AttributeError: 196 | pass 197 | 198 | if len(matches) > 1: 199 | warnings.warn("We detected multiple matches for the timezone, choosing " 200 | "the first %s. (Matches where %s)" % (matches[0], matches)) 201 | return pytz.timezone(matches[0]) 202 | 203 | --------------------------------------------------------------------------------