Я работаю над этим уже некоторое время, но не могу получить правильное значение контрольной суммы. Здесь мой метод принимает 3-байтовые массивы. Первый содержит байты IPv4Header, второй — байты TCPHeader и, наконец, полезную нагрузку. Я уже проверил, что эти байты верны, и пытаюсь использовать перекрестные ссылки по мере продвижения, но после стольких попыток мне все время не удается. Я также не нашел ни одного руководства с реальным кодом, который бы делал это из массива байтов.
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); // try <<
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 (length > 0)
{
sum += data[i] << 8;
}
while ((sum >> 16) != 0)
{
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (ushort)(~sum);
}
Байты, которые печатаются, кажутся правильными, и я прочитал эту статью расчет контрольной суммы TCP, в которой мне говорилось, что мне нужно указать IP-адрес источника, IP-адрес назначения, длину сегмента, протокол, фиксированные 8 бит, за которыми следует TCP-заголовок и полезная нагрузка. Итак, я думаю, что мой порядок правильный, хотя я не уверен, правильно ли я выполнил фиксированные 8 бит и часть длины. Я также не уверен, правильно ли я вычисляю фактический 2-байтовый ответ, используя дополнение. Любые исправления будут очень признательны, спасибо!
Обновлено: я подумал, что могу также добавить сообщения отладки, если кто-то найдет это полезным.
A2 9F 82 EA 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,
A2 9F 82 EA 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,
A2 9F 82 EA C0 A8 32 86 00 42 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,
A2 9F 82 EA C0 A8 32 86 00 42 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, Protocol:6
A2 9F 82 EA C0 A8 32 86 00 42 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,
A2 9F 82 EA C0 A8 32 86 00 42 06 00 01 BB F0 49 FD 02 5E BB 35 90 17 70 50 18 00 04 83 57 00 00 17 03 03 00 29 E0 63 19 68 7C 3F 19 3F 33 A6 9E 0B CD 54 47 8A 97 1B CC 97 1A 45 CD 5C EF 75 3E 75 A0 32 A3 46 2D 3F FD 37 DC F3 DB 34 AF,
CorrectCheckSumCheck:22403 0x8357 OurCheck:64005 0x05FA
Порядок байтов доставляет немало неудобств, поскольку исходное значение нашей контрольной суммы — 22403, а поскольку C# в Windows использует прямой порядок байтов, мне нужно было преобразовать его в обратный порядок байтов, прежде чем затем отображать его в шестнадцатеричном виде, что дает 0x8357, то есть правильное значение (я знаю его правильно, поскольку оно соответствует тому, что я вижу в полном байтовом массиве пакетов). Затем для нашего я напечатал оба, и это настолько далеко, что очевидно, что это не проблема с порядком байтов. Для контекста вот массив байтов TCP-заголовка без изменений 01 BB F0 49 FD 02 5E BB 35 90 17 70 50 18 00 04 83 57 00 00
@JonasH Ну, я изучаю различные протоколы, и обычно мне нравится узнавать что-то, выполняя проекты, а не просто читая об этом и притворяясь, будто я это знаю. Итак, я создал базовое приложение на C#, которое перехватывает пакеты, и сейчас пытаюсь проверить заголовки на обнаружение поврежденных пакетов. Я уже создал анализатор заголовков для IPv4, TCP, UDP и ICMP, и это был следующий логический шаг в моем пути обучения. Точно так же я узнал о формате PE, создав двоичный анализатор файлов exe, dll и sys. Я также сделал этот проект для анализа PNG-файлов, что было довольно круто.
Не забывайте, что поле контрольной суммы должно быть нулевым — когда вы проверяете захваченный TCP-пакет, вам необходимо обнулить его.
@Luaan Вы имеете в виду, что когда я копирую массив байтов заголовков TCP в свой новый массив, мне нужно сначала отредактировать массив заголовков TCP и обнулить его контрольную сумму, а затем скопировать его? Это имело бы смысл, учитывая, что это еще не известно.
Ага. И если вы используете захваченный заголовок, вы неправильно читаете слова — они с прямым порядком байтов, но вы читаете их с прямым порядком байтов. Вместо этого я бы рекомендовал использовать BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(index, 2))
.
@Luaan Я внесу эти изменения, но я не думаю, что Endianess неправильный, потому что он печатает байты из массива в правильном порядке (все массивы байтов были напечатаны и сохранены с прямым порядком байтов). Если только BinaryPrimitives.ReadUInt16BigEndian(data.AsSpan(index, 2))
не относится к чему-то другому, кроме операторов печати. Я также внес эти изменения, но все равно получаю неправильную контрольную сумму.
@UnSure Речь идет о вычислении суммы в методе ComputeChecksum. Предполагается, что данные добавляются в виде последовательности 16-битных слов в обратном порядке; вы добавляете их как последовательность 16-битных слов в порядке с прямым порядком байтов (за исключением нечетного слова, если оно есть, которое получается из-за того, как должно выполняться заполнение).
Вот рабочая версия в LINQPad, включая два примера заголовков.
Следует отметить две очень важные вещи: слова должны читаться с прямым порядком байтов (сетевой порядок), а контрольная сумма в заголовке должна быть обнулена, если вы проверяете захваченные заголовки.
void Main()
{
var pseudoHeader =
CreatePseudoHeader
(
new byte[] { 0xac, 0x18, 0x0b, 0x9f },
new byte[] { 0xac, 0x18, 0x7e, 0x34 },
6,
FromHex("0d3df160ea5e5c54e0e45e585018fa0000000000170303002e0000000000002015cd7f0a9400f87f031162f4de4118d5670f885cea116c630a416883d8569bedc94f2df41d5e20")
);
ComputeChecksum(pseudoHeader).ToString("X4").Dump();
pseudoHeader =
CreatePseudoHeader
(
new byte[] { 0xac, 0x18, 0x7e, 0x34 },
new byte[] { 0xac, 0x18, 0x0b, 0x9f },
6,
FromHex("f1600d3de0e45e58ea5e5c875010101100000000")
);
ComputeChecksum(pseudoHeader).ToString("X4").Dump();
}
private static byte[] FromHex(string v)
{
var buffer = new byte[v.Length / 2];
for (var i = 0; i < buffer.Length; i++)
{
buffer[i] = Convert.ToByte(v.Substring(i * 2, 2), 16);
}
return buffer;
}
public 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 (length > 0)
{
sum += data[i] << 8;
}
while ((sum >> 16) != 0)
{
sum = (sum & 0xFFFF) + (sum >> 16);
}
return (ushort)(~sum);
}
public byte[] CreatePseudoHeader(byte[] sourceIp, byte[] destIp, byte protocol, byte[] tcpHeader)
{
var tcpLength = tcpHeader.Length;
var pseudoHeader = new byte[12 + tcpLength];
Buffer.BlockCopy(sourceIp, 0, pseudoHeader, 0, 4);
Buffer.BlockCopy(destIp, 0, pseudoHeader, 4, 4);
pseudoHeader[8] = 0;
pseudoHeader[9] = protocol;
pseudoHeader[10] = (byte)(tcpLength >> 8);
pseudoHeader[11] = (byte)(tcpLength & 0xff);
Buffer.BlockCopy(tcpHeader, 0, pseudoHeader, 12, tcpLength);
return pseudoHeader;
}
Конечно, это очень простая реализация; достаточно хорош для обучения, но уж точно не для реального использования :)
Почему ваша длина ниже протокола оказалась неправильной в статье, на которую я дал ссылку? И почему вы не скопировали полезную нагрузку?
@UnSure Я пошел прямо из RFC по TCP; Длина указана после протокола в заголовке, поэтому она вычисляется именно в этом порядке. Однако это не имеет значения, поскольку мы просто делаем сложение :)
Вау, это действительно сработало, ты потрясающий!! Я также только что просмотрел RFC и увидел эту огромную статью, мне обязательно нужно ее прочитать, спасибо за вашу помощь!
Оказывается, мне это не подходит. По какой-то причине я только иногда получаю правильную контрольную сумму, и понятия не имею, почему. Например, источник 192.168.50.1 и IP-адрес назначения 192.168.50.134 работают (это входящий пакет), но для его исходящей версии, которая имеет обратный IP-адрес, то есть источник 192.168.50.134 и пункт назначения 192.168.50.1, это не удается. . Я обновил свой вопрос своим текущим кодом. Также я знаю, что вы упомянули о возможной проблеме с порядком байтов, но я не понимаю, как это происходит, поскольку я читаю байты непосредственно из байтового буфера пакетов.
Порядок байтов всегда является проблемой, если вы переполняете отдельные октеты. Добавление BB00 + BB00 и 00BB + 00BB даст разные результаты. Если переполнения нет, вы не заметите, что у вас неправильный порядок байтов. Опять же, дело не в том, что находится в буфере, а в том, как вы выполняете сложение - для этого вам нужно правильно интерпретировать байты в буфере - если они имеют обратный порядок байтов, вам нужно добавлять их как обратный порядок байтов; если они имеют прямой порядок байтов, вам нужно добавить их с прямым порядком байтов. TCP использует обратный порядок байтов (сетевой порядок), это то, что находится в вашем буфере; ваш код использует прямой порядок байтов.
Но где я их неправильно складываю, потому что все массивы байтов должны быть в форме с прямым порядком байтов. Я предполагаю, что вы, должно быть, имеете в виду метод дополнения, поскольку другой метод не делает никаких дополнений, а просто копирует массивы. Имеет смысл, что C# изначально использует прямой порядок байтов для своих вычислений, но как мне исправить метод ComputeChecksum
, чтобы вычисления выполнялись правильно?
Вы по-прежнему неправильно собираете длину заголовка TCP - большой порядковый номер означает, что более значимый октет находится в конце. Просто поменяйте местами pseudoHeaderAndTcpSegment
байты 10 и 11.
Это все равно не решает проблему
Можете ли вы добавить пример пакета, в котором не удалось вычислить контрольную сумму? И я предполагаю, что вы проверили, что контрольная сумма действительно верна (например, в Wireshark)? :D Не забывайте, что существуют такие вещи, как выгрузка контрольной суммы; контрольная сумма, которую вы собираете, не обязательно верна.
Есть ли какая-нибудь программа, которая может принимать массив байтов и затем выводить правильную контрольную сумму? Кроме того, я проверил выгрузку контрольной суммы, и это также может повлиять на проволочную акулу. Что вы получаете за следующее: я вычисляю 9826, а там написано 97E0... 45 00 00 6E 5B 7D 40 00 37 06 CC 54 A2 9F 85 EA C0 A8 32 86 01 BB D4 5A 27 3B 46 F8 72 7B 54 D1 50 18 00 05 97 E0 00 00 17 03 03 00 41 5C 07 93 EF 58 B0 BC 23 33 FC 12 3E EA D6 BA 13 37 C3 30 5B E7 F1 A3 55 6F 5C D6 EC 84 D2 7B 4E 5B CF 2 2 29 С1 24 68 BB 14 B2 C4 D7 1E 10 5F 60 67 A4 E7 EF 6E 89 2B 48 25 BD A9 C2 54 9E 5E 7D BB
@UnSure «Это также может повлиять на Wireshark» - именно в этом суть: если Wireshark не может проверить контрольную сумму, это не проблема в вашем коде - это просто означает, что контрольная сумма в захваченном пакете не является фактической контрольной суммой.
Можете ли вы отправить реальный дамп пакета Ethernet (или хотя бы IP) вместо псевдозаголовка?
Да, я понимаю это, но как я могу проверить правильность вычислений CheckSum, а не просто предполагать это? Где мне следует захватить «дамп пакетов Ethernet (или хотя бы IP)»?
@UnSure Просто попробуйте захватить пакет в Wireshark и получить полный дамп, и позвольте Wireshark вычислить контрольную сумму, чтобы увидеть, действительно ли она верна - это то, что вы можете сравнить со своим алгоритмом.
Как я отключил выгрузку контрольной суммы напрямую со своей сетевой карты, а затем использовал WireShark для проверки своих контрольных сумм и фактически могу подтвердить, что они неверны. Что мне следует попробовать сделать сейчас?
Наконец-то я смог это исправить! Мне просто нужно было изменить одну строку на pseudoHeaderAndTcpSegment[11] = (byte)((tcpLength + payloadLength) & 0xFF);
Это просто для обучения или у вас есть какое-то практическое применение?