diff --git a/setup.py b/setup.py index 05202892..c2b0b1ab 100644 --- a/setup.py +++ b/setup.py @@ -73,8 +73,8 @@ license = 'LICENSE.txt', packages = ['killerbee'], scripts = ['tools/zbdump', 'tools/zbgoodfind', 'tools/zbid', 'tools/zbreplay', - 'tools/zbconvert', 'tools/zbdsniff', 'tools/zbstumbler', 'tools/zbassocflood', - 'tools/zbscapy', 'tools/zbwireshark', 'tools/zbkey', + 'tools/zbconvert', 'tools/zbdsniff', 'tools/zbmmo', 'tools/zbstumbler', + 'tools/zbassocflood', 'tools/zbscapy', 'tools/zbwireshark', 'tools/zbkey', 'tools/zbwardrive', 'tools/zbopenear', 'tools/zbfakebeacon', 'tools/zborphannotify', 'tools/zbpanidconflictflood', 'tools/zbrealign', 'tools/zbcat', 'tools/zbjammer', 'tools/kbbootloader'], diff --git a/tools/zbmmo b/tools/zbmmo new file mode 100644 index 00000000..d316d053 --- /dev/null +++ b/tools/zbmmo @@ -0,0 +1,55 @@ +import sys +from zigbee_crypt import * + +ZBEE_SEC_CONST_BLOCKSIZE = 16 + +def main(bytes_in): + print('input: {}'.format(bytes_in.hex())) + + crc_bytes = bytes_in[-2:] + crc_out = crc16(bytes_in[:-2]).to_bytes(2, 'big') + if crc_bytes.hex() != crc_out.hex(): + print('CRC does not match input. Adding CRC to input.') + crc_out = crc16(bytes_in).to_bytes(2, 'big') + bytes_in.extend(crc_out) + print('crc: {}'.format(crc_out.hex())) + + print('installation code: {}'.format(bytes_in.hex())) + + hashed_bytes_c = mmo_hash(bytes(bytes_in)) + print('link key: {} ({})'.format(hashed_bytes_c.hex(), len(hashed_bytes_c))) + +def crc16(data: bytes, poly=0x8408): + ''' + CRC-16-CCITT Algorithm + ''' + data = bytearray(data) + crc = 0xFFFF + for b in data: + cur_byte = 0xFF & b + for _ in range(0, 8): + if (crc & 0x0001) ^ (cur_byte & 0x0001): + crc = (crc >> 1) ^ poly + else: + crc >>= 1 + cur_byte >>= 1 + crc = (~crc & 0xFFFF) + crc = (crc << 8) | ((crc >> 8) & 0xFF) + + return crc & 0xFFFF + +if __name__ == "__main__": + args = sys.argv[1:] + + if len(args) > 1: + print('Usage: python3 {} [input]'.format(sys.argv[0])) + sys.exit(1) + + hex_in = args[0] + try: + bytes_in = bytearray(bytes.fromhex(hex_in)) + except: + print('Invalid input. Expected hex string. (ex: 012345abcdef)') + sys.exit(1) + + main(bytes_in) \ No newline at end of file diff --git a/zigbee_crypt/zigbee_crypt.c b/zigbee_crypt/zigbee_crypt.c index 4b3034ba..2b32125e 100644 --- a/zigbee_crypt/zigbee_crypt.c +++ b/zigbee_crypt/zigbee_crypt.c @@ -9,6 +9,7 @@ */ // Explaination of Python Build Values http://docs.python.org/c-api/arg.html#Py_BuildValue +#define PY_SSIZE_T_CLEAN #include #include @@ -624,16 +625,38 @@ static PyObject *zigbee_sec_key_hash(PyObject *self, PyObject *args) { } /* zbee_sec_key_hash */ +/*FUNCTION:------------------------------------------------------ + * NAME + * zbee_mmo_hash + * DESCRIPTION + * ZigBee Matyas-Meyer-Oseas Hash Function. + * Used to derive the link key from the install code for ZigBee 3.0 device pairing. + * + *--------------------------------------------------------------- + */ +static PyObject *zigbee_mmo_hash(PyObject *self, PyObject *args) { + char *input; + int sizeInput; + char hash_out[ZBEE_SEC_CONST_BLOCKSIZE+1]; + + if (!PyArg_ParseTuple(args, "y#", &input, &sizeInput)) { + return NULL; + } + + hash_out[ZBEE_SEC_CONST_BLOCKSIZE] = 0; + zbee_sec_hash(input, sizeInput, hash_out); + + return Py_BuildValue("y", hash_out); +} /* zbee_mmo_hash */ static PyMethodDef zigbee_crypt_Methods[] = { { "decrypt_ccm", zigbee_crypt_decrypt_ccm, METH_VARARGS, "decrypt_ccm(key, nonce, mic, encrypted_payload, zigbee_data)\nDecrypt data with a 0, 32, 64, or 128-bit MIC\n\n@type key: String\n@param key: 16-byte decryption key\n@type nonce: String\n@param nonce: 13-byte nonce\n@type mic: String\n@param mic: 4-16 byte message integrity check (MIC)\n@type encrypted_payload: String\n@param encrypted_payload: The encrypted data to decrypt\n@type zigbee_data: String\n@param zigbee_data: The zigbee data within the frame, without the encrypted payload, MIC, or FCS" }, { "encrypt_ccm", zigbee_crypt_encrypt_ccm, METH_VARARGS, "encrypt_ccm(key, nonce, mic_size, decrypted_payload, zigbee_data)\nEncrypt data with a 0, 32, 64, or 128-bit MIC\n\n@type key: String\n@param key: 16-byte decryption key\n@type nonce: String\n@param nonce: 13-byte nonce\n@type mic_size: Integer\n@param mic_size: the size in bytes of the desired MIC\n@type decrypted_payload: String\n@param decrypted_payload: The decrypted data to encrypt\n@type zigbee_data: String\n@param zigbee_data: The zigbee data within the frame, without the decrypted payload, MIC or FCS" }, { "sec_key_hash", zigbee_sec_key_hash, METH_VARARGS, "sec_key_hash(key, input)\nHash the supplied key as per ZigBee Cryptographic Hash (B.1.3 and B.6).\n\n@type key: String\n@param key: 16-byte key to hash\n@type input: Char\n@param input: Character terminator for key" }, + { "mmo_hash", zigbee_mmo_hash, METH_VARARGS, "mmo_hash(input)\nHash the supplied value using the Matyas-Meyer-Oseas hashing algorithm\n\n@type input: Bytes\n@param input: 16-byte value to hash"}, { NULL, NULL, 0, NULL }, }; - - ZIGBEE_CRYPT_INIT { PyObject *module; @@ -641,7 +664,6 @@ ZIGBEE_CRYPT_INIT return module; } - int main(int argc, char *argv[]) { #if PY_MAJOR_VERSION >= 3 Py_SetProgramName((wchar_t *)argv[0]); diff --git a/zigbee_crypt/zigbee_crypt.h b/zigbee_crypt/zigbee_crypt.h index a5647685..ea649502 100644 --- a/zigbee_crypt/zigbee_crypt.h +++ b/zigbee_crypt/zigbee_crypt.h @@ -1,6 +1,8 @@ #ifndef PACKET_ZBEE_SECURITY_H #define PACKET_ZBEE_SECURITY_H +#define PY_SSIZE_T_CLEAN + /* Bit masks for the Security Control Field. */ #define ZBEE_SEC_CONTROL_LEVEL 0x07 #define ZBEE_SEC_CONTROL_KEY 0x18