I am writing a Python script to send ICMP packets by following an example from a Japanese website.
There is part of the code where struct.pack
is used to pack series of variables into a bytes array.
It looks somehow like the following:
import struct
def make_icmp_echo_request():
_type = 8
code = 0
check = 0
_id = 1
seq = 1
packed = struct.pack("!BBHHH", _type, code, check, _id, seq)
return packed
make_icmp_echo_request()
No matter how much you call make_icmp_echo_request
, the output will be the same.
However, the website also implements a checksum
function which will be used after the ICMP packet is packed.
The weird thing is that the implementation from the website will call struct.pack
one more time after the call to checksum
.
Something like below:
import struct
def checksum(data):
data_len = len(data) // 2 * 2
csum = 0
for i in range(0, data_len, 2):
csum += (data[i + 1] << 8) + data[i]
if len(data) % 2 != 0:
csum += data[-1]
while csum >> 16:
csum = (csum >> 16) + (csum & 0xffff)
csum = csum >> 8 | (csum << 8 & 0xff00)
return ~csum&0xffff
def make_icmp_echo_request():
_type = 8
code = 0
check = 0
_id = 1
seq = 1
packed = struct.pack("!BBHHH", _type, code, check, _id, seq)
check = checksum(packed)
return struct.pack("!BBHHH", _type, code, check, _id, seq)
make_icmp_echo_request()
I found out that the output of the first call to struct.pack
is different from the second one.
And this only happens if checksum
is being called after the first call.
I confirmed this with below code (I also unpacked back the packed byte array to make things clearer).
import struct
def checksum(data):
data_len = len(data) // 2 * 2
csum = 0
for i in range(0, data_len, 2):
csum += (data[i + 1] << 8) + data[i]
if len(data) % 2 != 0:
csum += data[-1]
while csum >> 16:
csum = (csum >> 16) + (csum & 0xffff)
csum = csum >> 8 | (csum << 8 & 0xff00)
return ~csum&0xffff
def make_icmp_echo_request():
_type = 8
code = 0
check = 0
_id = 1
seq = 1
for i in range(10):
print(f"[{i}]")
packed = struct.pack("!BBHHH", _type, code, check, _id, seq)
check = checksum(packed)
print(packed, check)
unpacked = struct.unpack("!BBHHH", packed)
print(unpacked)
return packed
make_icmp_echo_request()
*** Loop: 0 ***
b'\x08\x00\x00\x00\x00\x01\x00\x01' 63485
(8, 0, 0, 1, 1)
*** Loop: 1 ***
b'\x08\x00\xf7\xfd\x00\x01\x00\x01' 0
(8, 0, 63485, 1, 1)
*** Loop: 2 ***
b'\x08\x00\x00\x00\x00\x01\x00\x01' 63485
(8, 0, 0, 1, 1)
*** Loop: 3 ***
b'\x08\x00\xf7\xfd\x00\x01\x00\x01' 0
(8, 0, 63485, 1, 1)
*** Loop: 4 ***
b'\x08\x00\x00\x00\x00\x01\x00\x01' 63485
(8, 0, 0, 1, 1)
*** Loop: 5 ***
b'\x08\x00\xf7\xfd\x00\x01\x00\x01' 0
(8, 0, 63485, 1, 1)
*** Loop: 6 ***
b'\x08\x00\x00\x00\x00\x01\x00\x01' 63485
(8, 0, 0, 1, 1)
*** Loop: 7 ***
b'\x08\x00\xf7\xfd\x00\x01\x00\x01' 0
(8, 0, 63485, 1, 1)
*** Loop: 8 ***
b'\x08\x00\x00\x00\x00\x01\x00\x01' 63485
(8, 0, 0, 1, 1)
*** Loop: 9 ***
b'\x08\x00\xf7\xfd\x00\x01\x00\x01' 0
(8, 0, 63485, 1, 1)
Can anyone explain this?
It is also weird that the ICMP request will fail if I use the result of the first call to struct.pack
.
check = 0
...
packed = struct.pack("!BBHHH", _type, code, check, _id, seq)
check = checksum(packed)
return struct.pack("!BBHHH", _type, code, check, _id, seq)
The first call to pack()
passes 0 for check
. Then check
is rebound to the result of the checksum()
call. The second call to pack()
uses this new value of check
. So of course the results are different, if and only if checksum()
doesn't return 0.