yegorpetrov (yegorpetrov) wrote,
yegorpetrov
yegorpetrov

Копался в протоколе обмена Codesys 2.3

Последние пару дней баловался со средой Codesys 2.3 и контроллером Овен ПЛК110-32. Задача максимум — получить возможность копировать файлы на ПЛК не только с компьютеров, но и с мобильных устройств. В лоб эта задача не решается, т.к. протокол закрытый. Сразу скажу, что до копирования файлов пока не дошёл, но разобрался с командным интерфейсом (шеллом).

Инструменты и информация были следующие:

  • программируемый логический контроллер Овен ПЛК110-32;

  • среда разработки для контроллеров Codesys 2.3;

  • Wireshark;

  • Eltima Serial Port Monitor (пробная версия);

  • plc_io.exe — утилита для копирования файлов с/на ПЛК, можно найти на закоулках owen.ru;

  • отчёт о дырках в безопасности Codesys компании DigitalBond с описанием протокола.

Скачав скрипты по последней ссылке и опробовав их на софтовом PLCWinNT, я обрадовался и подумал, что задачу решили до меня. Однако к железному ПЛК подключиться с ними не удалось. Пришлось расчехлить снифферы.

К контроллеру можно подключиться и по TCP/IP, и через COM-порт. Быстро выяснилось, что формат посылок там и там одинаковый. Вот пример обмена по команде PLCInfo из Кодесиса:

[21/10/2014 11:39:39] Written data 
  aa aa 0d 00 01 00 60 01 92 00 00 00 00 50 4c 43   ЄЄ....`.’....PLC
  49 6e 66 6f 00                                    Info.           
[21/10/2014 11:39:39] Read data 
  55 55 0a 00 01 00                                 UU....          
[21/10/2014 11:39:39] Written data 
  55 55 0a 00 00 00                                 UU....          
[21/10/2014 11:39:39] Read data 
  aa aa 80 00 01 00 34 00                           ЄЄЂ...4.        
[21/10/2014 11:39:39] Read data 
  00 00 00 04 00 01 00 50 4c 43 20 6d 6f 64 65 6c   .......PLC model
  20 4d 4f 44 45 4c 20 50 4c 43 20 31 31 30 2d 33    MODEL PLC 110-3
  32 20 0d 0a 42 69 6e 61 72 79 20 20 56 45 52 53   2 ..Binary  VERS
  49 4f 4e 20 32 2e 31 34 2e 30 20 0d 0a 4e 65 65   ION 2.14.0 ..Nee
  64 20 54 61 72 67 65 74 20 76 65 72 73 69 6f 6e   d Target version
  20 32 2e 31 30 20 0d 0a 43 6f 6d 70 69 6c 65 64    2.10 ..Compiled
  3a 20 31 34 3a 33 37 3a 34 31 20 41 70 72 20 32   : 14:37:41 Apr 2
  38 20 32 30 31 31 20 0d 0a 4d 41 43 20 36 41 3a   8 2011 ..MAC 6A:
[21/10/2014 11:39:39] Written data 
  55 55 0a 00 01 00                                 UU....          
[21/10/2014 11:39:39] Read data 
  aa aa 7d 00 02 00 13 01                           ЄЄ}.....        
[21/10/2014 11:39:39] Read data 
  37 37 3a 30 30 3a 32 31 3a 34 34 3a 46 38 20 0d   77:00:21:44:F8 .
  0a 49 50 20 31 30 2e 30 2e 36 2e 31 30 0d 0a 47   .IP 10.0.6.10..G
  41 54 45 20 31 30 2e 30 2e 36 2e 31 0d 0a 4d 41   ATE 10.0.6.1..MA
  53 4b 20 32 35 35 2e 32 35 35 2e 32 35 35 2e 30   SK 255.255.255.0
  0d 0a 50 49 43 20 75 70 70 65 72 20 76 65 72 73   ..PIC upper vers
  69 6f 6e 20 69 73 20 31 35 0d 0a 4c 69 63 65 6e   ion is 15..Licen
  63 65 20 6c 69 6d 69 74 65 64 20 74 6f 20 33 36   ce limited to 36
  30 20 62 79 74 65 73 0d 0a 00 00 00 00            0 bytes......   
[21/10/2014 11:39:39] Written data 
  55 55 0a 00 02 00                                 UU....          
[21/10/2014 11:39:39] Read data 
  55 55 0a 00 00 00                                 UU....          






Основные расхождения со скриптами DigitalBond — в заголовках. В примере видно, что пакеты начинаются байтами 0xAA или 0x55 (в скриптах — 0xCC и 0x66). То есть, скорее всего, мы имеем дело с другой версией протокола. Тем не менее, порядок следования полей тот же.

Возьмём первую посылку: aa aa 0d 00 01 ... Если поиграться разными командами, то можно увидеть, что меняются те байты, которые здесь равны 0d, 60 и, само собой, текст команды. Очевидно, 0d — размер пакета начиная со следующего байта. Что такое 60 становится понятно, если поиграться символами в команде. Отправим, например, вместо PLCInfo команду QLCInfo, и получим 61. Стало быть, это контрольная сумма. Так как контрольная сумма не может содержаться в сообщении, суммой которого она является, то пробуем просуммировать все байты без неё, и — ура! — всё совпадает. В первом скрипте (который shell) контрольную сумму почему-то не считают. Алгоритм есть во втором, но туда я догадался посмотреть уже потом. Байты 0x92 и единицы не меняются, и поэтому (пока) не особо интересны.

Дальше обратим внимание на пакеты 55 55. Сначала казалось, что нужно просто нулём отвечать на единицу, единицей на ноль, а двойку отправлять когда обмен закончен. Всё это никак не сходилось с практикой. ПЛК либо выдавал ответы повторно, либо завершал обмен странным пакетом. Путаницы добавлял ответ ПЛК на команду «?»:

[21/10/2014 13:16:34] Written data 
    aa aa 07 00 01 00 2e 01 92 00 00 00 00 3f 00      ЄЄ......’....?.  
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    aa aa 35 00 01 00 c2 01                           ЄЄ5...В.         
[21/10/2014 13:16:34] Read data 
    00 00 00 03 00 01 00 3f 20 20 20 20 20 20 20 20   .......?         
    20 20 20 20 20 20 2d 20 73 68 6f 77 20 69 6d 70         - show imp 
    6c 65 6d 65 6e 74 65 64 20 63 6f 6d 6d 61 6e 64   lemented command 
    73 00 00 00 00                                    s....            
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    aa aa 06 00 01 00 f0 01 92 01 00 01 00 00         ЄЄ....р.’.....   
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    aa aa 26 00 01 00 03 01                           ЄЄ&.....         
[21/10/2014 13:16:34] Read data 
    00 00 00 03 00 02 00 6d 65 6d 20 20 20 20 20 20   .......mem       
    20 20 20 20 20 20 2d 20 4d 65 6d 6f 72 79 64 75         - Memorydu 
    6d 70 00 65 6e 74                                 mp.ent           
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    aa aa 06 00 01 00 f1 01 92 01 00 02 00 00         ЄЄ....с.’.....   
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    aa aa 44 00 01 00 99 01                           ЄЄD...™.         
[21/10/2014 13:16:34] Read data 
    00 00 00 03 00 03 00 6d 65 6d 63 20 20 20 20 20   .......memc      
    20 20 20 20 20 20 2d 20 4d 65 6d 6f 72 79 64 75         - Memorydu 

<...>

    00 00 00 03 00 21 00 53 65 74 4d 6f 64 65 6d 50   .....!.SetModemP 
    6f 72 74 5b 20 53 65 74 4d 6f 64 65 6d 50 6f 72   ort[ SetModemPor 
    74 20 58 5d 00 61 72 79                           t X].ary         
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    aa aa 06 00 01 00 10 01 92 01 00 21 00 00         ЄЄ......’..!..   
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 00 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    aa aa 26 00 01 00 0c 01                           ЄЄ&.....         
[21/10/2014 13:16:34] Read data 
    00 00 00 04 00 22 00 53 65 74 4d 6f 64 65 6d 43   .....".SetModemC 
    66 67 5b 20 53 65 74 4d 6f 64 65 6d 43 66 67 20   fg[ SetModemCfg  
    58 5d 00 5d 00 61                                 X].].a           
[21/10/2014 13:16:34] Written data 
    55 55 0a 00 01 00                                 UU....           
[21/10/2014 13:16:34] Read data 
    55 55 0a 00 00 00                                 UU....           





Как видно, ответ здесь разбивается совершенно иначе. Скрипт хоть и работает, но комментарии в нём туманные. Якобы на каждое сообщение надо давать два подтверждения. На самом же деле в протоколе просто-напросто предусматривается два уровня фрагментации: ответ может состоять из нескольких сообщений, каждое из которых в свою очередь может состоять из нескольких фрагментов.

Например, ответ ПЛК на первую посылку: 55 55 0a 00 01 00. Единица означает готовность принять второй (считая с нулевого) фрагмент. Если второго и/или последующего фрагмента нет, то мы отвечаем 55 55 0a 00 00 00. ПЛК делает то же самое. Номер фрагмента идёт вслед за байтом размера - та самая единица, которая ранее была нам мало интересна. Он становится двойкой во втором фрагменте (см. первый листинг).

Аналогично с сообщениями. Упомянутая команда «?» выдаёт по сообщению на каждую строку, каждое из которых подтверждается пакетами 55 55. Приняв сообщение (как и фрагмент) мы отправляем ответ вида aa aa 06 00 01 00 f0 01 92 01 00 01 00 00. Здесь тоже присутствуют байты размера и контрольной суммы, а также номер сообщения (0x21 в последнем ответе: aa aa 06 00 01 00 10 01 92 01 00 21 00 00). Разница с фрагментами в том, что последним является сообщение типа 04, а не то, на которое даётся нулевой ответ (сравните aa aa 26 00 01 00 0c 01 00 00 00 04 ... и aa aa 44 00 01 00 99 01 00 00 00 03).

Не буду томить вас оставшейся интерпретацией размера сообщений. Скажу лишь, что размер 0x80 означает «до самого конца», а не «128 байт» (видимо, знаковый байт используется). Напоследок привожу код получившейся консольной программы:

private static void PLCCmd(byte[] buf, Socket client, string cmd)
{
    int len = 0;

    client.Send(MakeCommandPacket(cmd));
    client.Receive(buf);
    client.Send(MakeSubPacketRequest(0));
    bool finished = false;
    while (!finished)
    {
        len = client.Receive(buf);
        //HexOut(buf, len);
        byte packetType = buf[11];
        byte packetNum = buf[13];
        Console.Write(Encoding.ASCII.GetString(buf, 15, buf[2] < 0x80 ? buf[2] - 11 : len - 15));
        finished = packetType == 4;
        // Get subpackets
        while (true)
        {
            client.Send(MakeSubPacketRequest(buf[4]));
            len = client.Receive(buf);
            //HexOut(buf, len);
            if (buf[0] == 0xAA)
            {
                Console.Write(Encoding.ASCII.GetString(buf, 8, buf[2] < 0x80 ? buf[2] - 4 : len - 8));
            }
            else
            {
                Console.WriteLine();
                break;
            }
        }
        if (!finished)
        {
            client.Send(MakePacketRequest(packetNum));
            client.Receive(buf);
            client.Send(MakeSubPacketRequest(0));
        }
    }
}

static byte[] MakeCommandPacket(string command)
{
    var prefix = new byte[] {0x92, 0, 0, 0, 0 };
    var packetInfo = new byte[] { 0, 1, 0, 0, 1 };
    var cmd = prefix.Concat(Encoding.ASCII.GetBytes(command)).Concat(new byte[] {0});
    var packet = aa.Concat(new byte[] { (byte)cmd.Count() }).Concat(packetInfo).Concat(cmd).ToArray();
    packet[6] = GetChecksum(packet);
    return packet;
}

static byte[] MakePacketRequest(byte packetNumber)
{
    var packet = new byte[] { 0xAA, 0xAA, 6, 0, 1, 0, 0, 1, 0x92, 1, 0, packetNumber, 0, 0 };
    packet[6] = GetChecksum(packet);
    return packet;
}

static byte[] MakeSubPacketRequest(byte subPacketNumber)
{
    return new byte[] { 0x55, 0x55, 0x0A, 0, subPacketNumber, 0 };
}

static byte GetChecksum(byte[] packet)
{
    int checksum = 0;
    foreach (byte b in packet)
    {
        checksum += b;
    }
    return (byte)checksum;
}



screenshot
Tags: codesys, reverse engineering
Subscribe
  • Post a new comment

    Error

    Anonymous comments are disabled in this journal

    default userpic

    Your IP address will be recorded 

  • 2 comments