Binaries

Data

1 1 0 1 0 1 0 1
0 1 0 1 1 1 0 1
0 1 1 0 1 1 1 0
0 0 0 1 0 0 1 0
1 0 1 0 1 0 0 1
1 1 0 1 1 0 1 1
0 1 1 0 0 1 1 0
1 0 1 0 1 0 1 1
0 1 1 0 1 1 0 1
0 1 0 1 0 1 0 1
1 1 1 1 0 0 0 0
1 0 1 0 1 1 0 1

 str, unicode & bytes

>>> napis = "zażółć gęślą jaźń"
>>> napis
'za\xc5\xbc\xc3\xb3\xc5...'
>>> print(napis)
zażółć gęślą jaźń
>>> napis[2]
'\xc5'
>>> unicode(napis, 'utf-8')[2]
u'\u017c'

Python 2

>>> napis = "zażółć gęślą jaźń"
>>> napis
'zażółć gęślą jaźń'
>>> print(napis)
zażółć gęślą jaźń
>>> napis[2]
'ż'
>>> unicode
NameError: name 'unicode'
 is not defined

Python 3

 str, unicode & bytes

>>> napis = b"zażółć gęślą jaźń"
SyntaxError: bytes can only contain ASCII literal characters.
>>> napis = b"zażółć gęślą jaźń".encode('utf-8')
>>> napis
b'za\xc5\xbc\xc3\xb3\xc5...'
>>> napis[2], hex(napis[2])
(197, '0xc5')
>>> napis = bytes.fromhex('3216d1509884b533248541792b877f98')
>>> napis
b'2\x16\xd1P\x98\x84\xb53$\x85Ay+\x87\x7f\x98'
>>> napis.hex()
'3216d1509884b533248541792b877f98'

>>> napis.hex().encode('ascii')
b'3216d1509884b533248541792b877f98'

>>> napis[0]
50
>>> chr(50)
'2'
>>> ord('2')
50

When I use a word,

it means just what I choose it to mean

numbers

1 1 0 1 0 1 0 1
0 1 0 1 1 1 0 1
0 1 1 0 1 1 1 0
0 0 0 1 0 0 1 0
 3579670034
int('11010101'
    '01011101'
    '01101110'
    '00010010', 2)

endians

1 1 0 1 0 1 0 1
0 1 0 1 1 1 0 1
0 1 1 0 1 1 1 0
0 0 0 1 0 0 1 0
309222869
int('00010010'
    '01101110'
    '01011101'
    '11010101', 2)

arrays

1 1 0 1 0 1 0 1
[18, 110, 93, 213]
[int('00010010', 2),
 int('01101110', 2),
 int('01011101', 2),
 int('11010101', 2)]
0 1 0 1 1 1 0 1
0 1 1 0 1 1 1 0
0 0 0 1 0 0 1 0

structures

1 1 0 1 0 1 0 1
0 1 0 1 1 1 0 1
1 1 0 1 0 1 0 1
(54621, 110, 18)
(int('11010101'
     '01011101', 2),
 int('01101110', 2),
 int('00010010', 2))
0 1 0 1 1 1 0 1

bit fields

(13, 21974, 3602)
(int('1101', 2),
 int(    '0101'
     '01011101'
     '0110', 2),
 int(    '1110'
     '00010010', 2))
1 1 0 1
1 1 0 1
0 1 0 1
1 1 0 1
0 1 0 1
1 1 0 1
0 1 0 1
1 1 0 1

C: struct

typedef struct
{
    unsigned version: 4;
    unsigned header_length: 4;
    unsigned services: 6;
    unsigned congestion: 2;
    uint16_t total_length;
    uint16_t identification;
    unsigned flags: 3;
    unsigned fragment: 13;
    uint8_t  time_to_live;
    uint8_t  protocol;
    uint16_t header_checksum;
    uint8_t  source[4];
    uint8_t  destination[4];
} __attribute__((packed)) IPv4Header;

C: struct

char *data = "\x45\x00\x00\x54\x17\x61\x40\x00"
             "\x40\x01\x21\x37\xc0\xa8\x0b\x12"
             "\xd4\x4d\x62\x09";

IPv4Header *header = (IPv4Header*)data;

printf("total length: %hu\n",
       ntohs(header->total_length));

Python: struct

header = struct.unpack('!' # network endian
    'B'   # version, header_length
    'B'   # services, congestion
    'H'   # total_length
    'H'   # identification
    'H'   # flags, fragment
    'B'   # time_to_live
    'B'   # protocol
    'H'   # header_checksum
    '4B'  # source
    '4B', # destination
    data)

print("total length:", header[2])

Python: CTYPES

class IPv4Header(ctypes.BigEndianStructure):
    _fields_ = (
        ('version', ctypes.c_uint, 4),
        ('header_length', ctypes.c_uint, 4),
        ('services', ctypes.c_uint, 6),
        ('congestion', ctypes.c_uint, 2),
        ('total_length', ctypes.c_uint, 16),
        ('identification', ctypes.c_uint, 16),
        ('flags', ctypes.c_uint, 3),
        ('fragment', ctypes.c_uint, 13),
        ('time_to_live', ctypes.c_uint, 8),
        ('protocol', ctypes.c_uint, 8),
        ('header_checksum', ctypes.c_uint, 16),
        ('source', ctypes.c_byte * 4),
        ('destination', ctypes.c_byte * 4)
    )

header = ctypes.cast(data,
                     ctypes.POINTER(IPv4Header)).contents

print("total length:", header.total_length)

Python: bitstring

header = bitstring.Bits(data).unpack((
    'uint:4    = version',
    'uint:4    = header_length',
    'uint:6    = services',
    'uint:2    = congestion',
    'uintbe:16 = total_length',
    'uintbe:16 = identification',
    'uint:3    = flags',
    'uint:13   = fragment',
    'uint:8    = time_to_live',
    'uint:8    = protocol',
    'uintbe:16 = header_checksum',
    'bytes:4   = source',
    'bytes:4   = destination',
)))

print("total length:", header[4])

a tale of one protocol

stream = bitstring.BitStream(network_pdu)

ivi, nid, ctl, ttl, seq, src, dst = stream.readlist(NET_HEADER_FORMAT)
NET_HEADER_FORMAT = (
    'uint:1    = ivi',
    'uint:7    = nid',
    'uint:1    = ctl',
    'uint:7    = ttl',
    'uintbe:24 = seq',
    'uint:16   = src',
    'uint:16   = dst')

a tale of one protocol

a tale of one protocol

CTL 0
SEG 0

CTL 0
SEG 1

CTL 1
SEG 0

CTL 1
SEG 1

a tale of one protocol

UNSEGMENTED_ACCESS = ('uint:1 = 0',
                      'uint:1 = akf',
                      'uint:6 = aid',
                      'bits   = upper_transport_pdu')


SEGMENTED_ACCESS = ('uint:1  = 1',
                    'uint:1  = akf',
                    'uint:6  = aid',
                    'uint:1  = szmic',
                    'uint:13 = seq_zero',
                    'uint:5  = seg_o',
                    'uint:5  = seg_n',
                    'bits    = segment')

UNSEGMENTED_CONTROL = ('uint:1 = 0',
                       'uint:7 = opcode',
                       'bits   = parameters')



SEGMENTED_CONTROL = ('uint:1  = 1',
                     'uint:7  = opcode',
                     'uint:1  = 0', # RFU
                     'uint:13 = seq_zero',
                     'uint:5  = seg_o',
                     'uint:5  = seg_n',
                     'bits    = segment')
stream = bitstring.BitStream(network_pdu)

ivi, nid, ctl, ttl, seq, src, dst = stream.readlist(NET_HEADER_FORMAT)

seg = stream.peek('uint:1')

if ctl:
    stream.readlist(SEGMENTED_CONTROL if seg else UNSEGMENTED_CONTROL)
else:
    stream.readlist(SEGMENTED_ACCESS if seg else UNSEGMENTED_ACCESS)

a tale of one protocol

a tale of one protocol

privacyCounter = append(privacyCounter, ivindexL...)      //+4
privacyCounter = append(privacyCounter, msgL[1 + 6 : 8 + 6]...) //+7

copy(dp.seq, packet[2:5])
copy(dp.src, packet[5:7])

dp.seqZero[0] = (dp.tcfBuffer[1] & 0x7f) >> 2
dp.seqZero[1] = dp.tcfBuffer[2]>>2 | ((dp.tcfBuffer[1] & 0x03) << 6)
dp.segO = dp.tcfBuffer[3]>>5 | ((dp.tcfBuffer[2] & 0x03) << 3)
dp.segN = dp.tcfBuffer[3] & 0x1f

var t uint32 = 0x00
t |= uint32(n - 1)
t |= uint32(i) << 5
t |= uint32(seq0[2]) << 10
t |= (uint32(seq0[1]) & 0x1F) << 18
dev_nonce = bitstring.pack('uint:8, uint:1, pad:7, uintbe:24, uintbe:16, uintbe:16, uintbe:32',
                           0x02, szmic, seq, src, dst, self.network.iv_index).bytes

access_payload = b'\x80\x05\x01' # health model attention set, timer = 1

encrypted_access_payload, trans_mic = aes_ccm(device.device_key.bytes, dev_nonce, access_payload)

transport_pdu = bitstring.pack('uint:1, uint:1, uint:6, bits, bits',
                               seg, akf, aid, encrypted_access_payload, trans_mic).bytes

net_nonce = bitstring.pack('pad:8, uint:1, uint:7, uintbe:24, uintbe:16, pad:16, uintbe:32',
                           ctl, ttl, seq, src, self.network.iv_index).bytes

encrypted_transport_pdu, net_mic = aes_ccm(encryption_key, net_nonce,
                                           bitstring.pack('uintbe:16, bits',
                                                          dst, transport_pdu).bytes)

net_header = bitstring.pack('uint:1, uint:7, uintbe:24, uintbe:16',
                            ctl, ttl, seq, src).bytes

privacy_random = bitstring.pack('pad:40, uintbe:32, bits:56',
                                self.network.iv_index, encrypted_transport_pdu[:7]).bytes

pecb = aes_ecb(privacy_key, privacy_random)[:6]

net_header = bytes(map(operator.xor, net_header, pecb))

net_pdu = bitstring.pack('uint:1, uint:7, bits, bits, bits',
                         self.network.iv_index & 1, nid, net_header, encrypted_transport_pdu, net_mic).bytes

a tale of one protocol

THANK YOU

Binarki @ Silvair 11.2018

By Michał Lowas-Rzechonek

Binarki @ Silvair 11.2018

Processing binary and low-level data in Python

  • 685