Search code examples
c#networkingtcpchecksumipv4

Computing checksum's for TCP packets from byte arrays


I asked a similar question recently but marked it as solved since I thought I found the answer when in fact my code only worked on specific cases while missing the bulk of cases. Anyways I am using WinDivert to capture TCP packets with the goal of computing the checksums and looking for malformed packets. The byte arrays are in big endian byte order since they are directly pulled from WinDivert. I have also read the RFC on TCP's checksum and I can't seem to figure out what I am doing wrong I even added a length check since I noticed the following... If a segment contains an odd number of header and text octets to be checksummed, the last octet is padded on the right with zeros. But even so my checksum calculations fail even when there is no payload. For example, a packet that only contained 40 bytes (which means there's no payload) and had a source address of 172.64.151.73 and a destination address of 192.168.50.134 and my code yielded the correct checksum however, when the payload is still 40 but with reversed addresses (meaning the source is now 192.168.50.134) I get a totally wrong checksum. My code seems to also produce a wrong answer for the majority of packets with any payload

        public override ushort ComputeChecksum(byte[] ipv4Header, byte[] transportHeader, byte[] payload)
        {
            int pseudoHeaderLength = 12; // Pseudo-header is 12 bytes
            int tcpLength = transportHeader.Length;
            int payloadLength = payload.Length;
            int totalLength = pseudoHeaderLength + tcpLength + payloadLength;
            bool isOddLength = (totalLength % 2 != 0);

            byte[] pseudoHeaderAndTcpSegment = new byte[totalLength + (isOddLength ? 1 : 0)];
            Console.WriteLine($"pseudoHeaderLength:{pseudoHeaderLength} transportHeader.Length:{tcpLength} payloadLength:{payloadLength} totalLength:{totalLength} newTotalLength:{pseudoHeaderAndTcpSegment.Length} OddLength:{isOddLength}");

            // Copy Source IP address (4 bytes)
            Buffer.BlockCopy(ipv4Header, 12, pseudoHeaderAndTcpSegment, 0, 4);
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Copy Destination IP address (4 bytes)
            Buffer.BlockCopy(ipv4Header, 16, pseudoHeaderAndTcpSegment, 4, 4);
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Zero byte (1 byte)
            pseudoHeaderAndTcpSegment[8] = 0;

            // Protocol (1 byte)
            pseudoHeaderAndTcpSegment[9] = ipv4Header[9];
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", ") + " Protocol:" + ipv4Header[9]); // ipv4Header[9] prints 6 like it should
            Console.WriteLine("");

            // TCP length (2 bytes)
            pseudoHeaderAndTcpSegment[10] = (byte)((tcpLength + payloadLength) >> 8);
            pseudoHeaderAndTcpSegment[11] = (byte)(tcpLength & 0xFF);
            Console.WriteLine((BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Make a copy of the transport header to zero out the checksum
            byte[] transportHeaderCopy = new byte[tcpLength];
            Buffer.BlockCopy(transportHeader, 0, transportHeaderCopy, 0, tcpLength);
            transportHeaderCopy[16] = 0; // Zero out the checksum field (16th byte)
            transportHeaderCopy[17] = 0; // Zero out the checksum field (17th byte)
            Console.WriteLine((BitConverter.ToString(transportHeaderCopy).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Copy the modified TCP header with zeroed checksum field
            Buffer.BlockCopy(transportHeaderCopy, 0, pseudoHeaderAndTcpSegment, 12, tcpLength);
            Console.WriteLine("TCPHeader:" + (BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            // Copy the payload
            Buffer.BlockCopy(payload, 0, pseudoHeaderAndTcpSegment, 12 + tcpLength, payload.Length);
            if (isOddLength)
            {
                Console.WriteLine("Odd Length adding a zero");
                pseudoHeaderAndTcpSegment[totalLength] = 0;
            }
            Console.WriteLine("Payload:" + (BitConverter.ToString(pseudoHeaderAndTcpSegment).Replace("-", " ") + ", "));
            Console.WriteLine("");

            return ComputeChecksum(pseudoHeaderAndTcpSegment);
        }

        protected static ushort ComputeChecksum(byte[] data)
        {
            int length = data.Length;
            int i = 0;
            long sum = 0;

            while (length > 1)
            {
                sum += BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(i, 2));
                i += 2;
                length -= 2;

                if (sum > 0xFFFF)
                {
                    sum = (sum & 0xFFFF) + (sum >> 16);
                }
            }

            if (length > 0)
            {
                sum += data[i] << 8;
            }

            while ((sum >> 16) != 0)
            {
                sum = (sum & 0xFFFF) + (sum >> 16);
            }

            return (ushort)(~sum);
        }

In the above code I should be handling odd lengths, zeroing the original checksum fields, and computing the ones compliment correctly. For example, when I run the ComputeChecksum(byte[] data) method to compute the IPv4 checksum it's always correct so I don't think that's the issue. Someone also mentioned that I should switch the 10- and 11-bytes order around and I also tried just using tcpLength >> 8 but all these attempts also failed. Really, I have no idea how to figure this out since there is no guide that actually does this with code at least not from what I can find. Here is how I call the above methods, but I have no idea what the issue could be. Possibly an endianness issue since C# uses little endian and packets use big endian, but I don't see what could be wrong in my ComputeChecksum(byte[] ipv4Header, byte[] transportHeader, byte[] payload) method and like I said my ComputeChecksum(byte[] data) method computes the correct IPv4 checksums so I am really at a loss here.

                        ushort computedTcpChecksum = PacketData.TCPHeader.ComputeChecksum(ipv4HeaderBytes, transportHeaderBytes, payloadBytes);
                        string newTcpChecksumHex = computedTcpChecksum.ToString("X4");

Anyways here is an example of my code failing and the resulting debugmessages.

PacketDetailsForm SrcIp:192.168.50.134 DstIp:192.168.50.1 Length:94 ClonedIpHeaderLength:24064 ThreadId:2
pseudoHeaderLength:12 transportHeader.Length:20 payloadLength:54 totalLength:86 newTotalLength:86 OddLength:False
C0 A8 32 86 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

C0 A8 32 86 C0 A8 32 01 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

C0 A8 32 86 C0 A8 32 01 00 06 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,  Protocol:6

C0 A8 32 86 C0 A8 32 01 00 06 00 14 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

E2 B6 00 35 38 F9 6A 6F 2D F7 BC D5 50 18 04 02 00 00 00 00,

TCPHeader:C0 A8 32 86 C0 A8 32 01 00 06 00 14 E2 B6 00 35 38 F9 6A 6F 2D F7 BC D5 50 18 04 02 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00,

Payload:C0 A8 32 86 C0 A8 32 01 00 06 00 14 E2 B6 00 35 38 F9 6A 6F 2D F7 BC D5 50 18 04 02 00 00 00 00 76 37 01 00 00 01 00 00 00 00 00 00 0A 66 75 6E 63 74 69 6F 6E 61 6C 06 65 76 65 6E 74 73 04 64 61 74 61 09 6D 69 63 72 6F 73 6F 66 74 03 63 6F 6D 00 00 01 00 01,

TCP OriginalCheck:58920 E628, OurCheck:48147 BC13 

Solution

  • You nearly got it all correct however, you made 1 small mistake you forgot to add + payloadLength to the byte indexed at 11. The solution is to update that line to the following.

    pseudoHeaderAndTcpSegment[11] = (byte)((tcpLength + payloadLength) & 0xFF);