识别 CRC8 校验和

问题描述 投票:0回答:1

我一直在尝试对使用 8 位 uC 的旧设备进行逆向工程。我可以访问 uC 发送到计算机的几条消息以及它们各自的校验和的结果。我还找到了 uC 的代码,但它是一个不完整的版本,因此它没有提供比这更多的信息:

  • 是CRC8

  • 也许有一个未知的初始值或最终值。

这里是一些消息,结构是:STX+ DATA+ EOT+checksum+EXT

编辑:这是 CRC 算法的常见实现

byte CRC8(byte param_1,byte *checksum)

{
  byte bVar1;
  char cVar2= '\b';
  byte bStack_3 = param_1;

  do {
    bVar1 = (bStack_3 ^ *checksum) << 7;
    if ((char)bVar1 < '\0') {
      *checksum = *checksum ^ 0x18;
    }
    *checksum = *checksum >> 1 | bVar1;
    bStack_3 = bStack_3 >> 1 | bStack_3 << 7;
    cVar2 = cVar2 + -1;
  } while (cVar2 != '\0');
  return param_1;
}
microcontroller checksum identify crc8
1个回答
0
投票

在前一个问题(现已删除)中,您提供了二进制文件的链接,我用它来设置 Ghidra 项目。所以我有两种方法来找到 CRC 算法。

1.重新设计二进制

将二进制文件加载到 Ghidra 后,我发现几个中断条目被设置为跳转。这很常见。其中是串行接口的中断服务程序,从0x0107开始。更深入地观察,我发现他们使用循环缓冲区进行接收和传输。

访问变量的使用位置,我发现基本发送例程位于0x00DE。沿着参考链,我终于找到了一个有趣的函数,我称之为

sendAndUpdateCrc()
。这是清理后的版本:

void sendAndUpdateCrc(char c)
{
  fputc(c);
  updateCrc(c);
}

这个核心函数在整个程序中都会被调用,我稍后再讲。最重要的部分是我称之为

updateCrc()
的函数。

这是 CRC 更新功能的清理版本。它使用一个全局变量来累加,该变量位于内部RAM的0x7A处。

void updateCrc(char c) {
  char cVar2 = 8 /* '\b' */;
  byte bStack_3 = c;
  do {
    byte bVar1 = (bStack_3 ^ crc) << 7;
    if ((char)bVar1 < 0 /*'\0' */) { /* checks the sign bit that was LSBit */
      crc = crc ^ 0x18;
    }
    crc = crc >> 1 | bVar1;
    bStack_3 = bStack_3 >> 1 | bStack_3 << 7;
    cVar2 = cVar2 + -1;
  } while (cVar2 != 0 /* '\0' */);
}

有了这个函数,第二种方法就容易多了,见下文。


访问

sendAndUpdateCrc()
的调用位置时,我遇到了这些专门的功能。这里列出了它们的地址和
crc
的初始值。

地址 初始CRC 我的名字 意义
0x46cc 0x24 发送FfDollarIAndMore() 发送
FF$i
等等,可能是一些初始消息
0x48a7 0x00 sendQuot12310AndMore() 发送
AB"12310
还有更多,可能有很多变量
0x4d57 0x30^ID 发送Xy() 发送
AB
和两个个位数值
0x4daa 0x40^ID sendHash_____x() 根据变量发送
AB#
和另外六个字符
0x4e11 0x40^ID 发送X() 发送
AB
和一位数字
0x5184 0x40^ID 发送不同大小的数据() 发送
AB
和(对我来说)未知数量的值

匹配消息格式,所有这些函数都会更新 CRC,包括前导 STX (0x02) 和分隔 ETX (0x04)。然而,他们不断更新 CRC,这毫无意义。

如您所见,设备 ID 的最后两位以 BCD(二进制编码的十进制)形式存储在 ROM 的 0x0033 处。这是上表中的“ID”。

您观察到的消息的前两个字符为

AB
,分别从内部 RAM 中的地址 0x77 和 0x78 获取。它们的值是在 0x4293 处的函数中计算的,但我没有费力地浏览它的代码来尝试理解它。它似乎在应用程序启动期间被调用。也许这两个字符是PC设定的。


在重新设计过程中,我发现了两种不同风格的代码。一种是相当理想的,而另一种则非常差,因此很慢。然而,没有人会编写这样的代码,所以我假设一个非常简单的编译器作为生产者。好的代码很可能是由人类用汇编语言编写的。

好的代码示例:

send:
        PUSH    BANK0_R0

LAB_CODE_00e0:                              ; wait for space in the buffer
        CLR     EA
        MOV     R0,txdBufferSize
        CJNE    R0,#0x2,LAB_CODE_00eb
        SETB    EA
        SJMP    LAB_CODE_00e0
LAB_CODE_00eb:

        MOV     R0,txdBufferWritePointer    ; store character in buffer
        MOV     @R0,A
        INC     R0
        CJNE    R0,#txdBufferEnd,LAB_CODE_00f4
        MOV     R0,#txdBufferBegin
LAB_CODE_00f4:
        MOV     txdBufferWritePointer,R0    ; and advance buffer pointer

        INC     txdBufferSize               ; increment used size

        MOV     R0,txdBufferEmpty           ; check empty flag
        CJNE    R0,#0x1,LAB_CODE_0102
        MOV     txdBufferEmpty,#0x0
        SETB    TI                          ; start transmission after an idle phase
LAB_CODE_0102:

        SETB    EA
        POP     BANK0_R0
        RET

糟糕代码的示例(请记住,8051 使用向高地址增长的堆栈):

sendAndUpdateCrc:
        PUSH    A           ; save c on the stack

        MOV     A,SP
        ADD     A,#0x0
        MOV     R0,A
        MOV     A,@R0       ; load c from the stack top

        LCALL   fputc       ; call fputc()

        MOV     A,SP
        ADD     A,#0x0
        MOV     R0,A
        MOV     A,@R0       ; load c from the stack top

        LCALL   updateCrc   ; call updateCrc()

        DEC     SP          ; rewind stack
        RET

作为最后一个“好东西”,我发现了这个错误的代码:

putchar:
        CJNE    A,#0xa,LAB_CODE_00da    ; 0xA = \n
        LCALL   send
LAB_CODE_00da:
        LCALL   send
        RET

该函数显然应该将单个输入字符

'\n'
作为公共序列 CR-LF (
'\r'
,
'\n'
) 进行传输。但它不是重复 CR,而是重复 LF。 Outch。


2.调查已知电报

由于我们只有 8 位作为校验和,因此暴力方法很容易实现。为此,我使用了增强的 CRC 函数,并查找产生观察到的 CRC 所需的初始值。以下是我写的小程序:

#include <stdio.h>
#include <stdint.h>
#include <string.h>

static uint8_t crc;

static void updateCrc(char c)
{
  for (int b = 0; b < 8; ++b) {
    if (((c ^ crc) & 1) != 0) {
      crc ^= 0x18;
      crc >>= 1;
      crc |= 0x80;
    } else {
      crc >>= 1;
    }
    c >>= 1;
  }
}

int main(void) {
  #define NUMBER_OF_MESSAGES 5
  static const char * const MESSAGES[NUMBER_OF_MESSAGES] = {
    "ABB", "AB11", "AB10", "ABp", "AB0FFFF",
  };
  static const struct {
    uint8_t device_id;
    uint8_t expected_crc[NUMBER_OF_MESSAGES];
  } SETS[] = {
    { 0x00, { 0x10, 0xBD, 0x79, 0xAC, 0xF0, } },
    { 0x01, { 0xDD, 0x8A, 0x4E, 0x61, 0x54, } },
    { 0x02, { 0x93, 0xD3, 0x17, 0x2F, 0xA1, } },
    { 0x56, { 0x19, 0x21, 0xE5, 0xA5, 0x63, } },
    { 0x99, { 0x54, 0x9A, 0x5E, 0xE8, 0x1E, } },
  };

  for (size_t s_index = 0; s_index < sizeof SETS / sizeof SETS[0]; ++s_index) {
    printf("\tID=%02X", SETS[s_index].device_id);
  }
  puts("");

  for (size_t m_index = 0; m_index < NUMBER_OF_MESSAGES; ++m_index) {
    printf("%s", MESSAGES[m_index]);
    for (size_t s_index = 0; s_index < sizeof SETS / sizeof SETS[0]; ++s_index) {
      static const int NONE = -1;
      int initial = NONE;
      for (int candidate = 0x00; candidate <= 0xFF; ++candidate) {
        crc = (uint8_t)candidate;

        const char STX = 0x02;
        updateCrc(STX);

        for (size_t c_index = 0; c_index < strlen(MESSAGES[m_index]); ++c_index) {
          updateCrc(MESSAGES[m_index][c_index]);
        }

        const char ETX = 0x04;
        updateCrc(ETX);

        if (crc == SETS[s_index].expected_crc[m_index]) {
          if (initial == -1) {
            initial = candidate;
          } else {
            printf("!"); /* report double */
          }
        }
      }
      if (initial == NONE) {
        printf("\t--"); /* report none */
      } else {
        printf("\t%02X", initial);
      }
    }
    puts("");
  }
}

以表格形式查看的结果给了我们提示并验证了重新设计的结果:

        ID=00   ID=01   ID=02   ID=56   ID=99
ABB     40      41      42      16      D9
AB11    30      31      32      66      A9
AB10    30      31      32      66      A9
ABp     40      41      42      16      D9
AB0FFFF 30      31      32      66      A9

图案跃入眼帘。初始值分别由 0x40 和 0x30 与设备 ID 的最后两位数字异或为 BCD 值。

在第二个小程序中,我检查了该理论:

#include <ctype.h>
#include <stdio.h>
#include <stdint.h>
#include <string.h>

static uint8_t crc;

static void updateCrc(char c)
{
  static const uint8_t POLYNOM = 0x18;
  for (int b = 8; b != 0; --b) {
    if (((c ^ crc) & 1) != 0) {
      crc ^= POLYNOM;
      crc >>= 1;
      crc |= 1 << 7;
    } else {
      crc >>= 1;
    }
    c >>= 1;
  }
}

static void sendAndUpdateCrc(char c)
{
  if (isalnum(c)) {
    putchar(c);
  } else {
    putchar('.');
  }
  updateCrc(c);
}

int main(void) {
  #define NUMBER_OF_MESSAGES 5
  static const char * const MESSAGES[NUMBER_OF_MESSAGES] = {
    "ABB", "AB11", "AB10", "ABp", "AB0FFFF",
  };
  static const struct {
    uint8_t device_id;
    uint8_t expected_crc[NUMBER_OF_MESSAGES];
  } SETS[] = {
    { 0x00, { 0x10, 0xBD, 0x79, 0xAC, 0xF0, } },
    { 0x01, { 0xDD, 0x8A, 0x4E, 0x61, 0x54, } },
    { 0x02, { 0x93, 0xD3, 0x17, 0x2F, 0xA1, } },
    { 0x56, { 0x19, 0x21, 0xE5, 0xA5, 0x63, } },
    { 0x99, { 0x54, 0x9A, 0x5E, 0xE8, 0x1E, } },
  };

  for (size_t m_index = 0; m_index < NUMBER_OF_MESSAGES; ++m_index) {
    for (size_t s_index = 0; s_index < sizeof SETS / sizeof SETS[0]; ++s_index) {
      printf("ID=%02X ", SETS[s_index].device_id);

      printf("message=");

      switch (strlen(MESSAGES[m_index])) {
      case 3:
        crc = 0x40;
        break;
      case 4:
        crc = 0x30;
        break;
      default:
        crc = 0x30;
        break;
      }
      crc ^= SETS[s_index].device_id;

      const char STX = 0x02;
      sendAndUpdateCrc(STX);

      for (size_t c_index = 0; c_index < strlen(MESSAGES[m_index]); ++c_index) {
        sendAndUpdateCrc(MESSAGES[m_index][c_index]);
      }

      const char ETX = 0x04;
      sendAndUpdateCrc(ETX);

      char crc_hex[3];
      sprintf(crc_hex, "%02X", crc);
      sendAndUpdateCrc(crc_hex[0]);
      sendAndUpdateCrc(crc_hex[1]);

      const char EOT = 0x03;
      sendAndUpdateCrc(EOT);

      printf(" expected CRC=%02X\n", SETS[s_index].expected_crc[m_index]);
    }
  }
}

这是它的输出,它验证了理论:

ID=00 message=.ABB.10. expected CRC=10
ID=01 message=.ABB.DD. expected CRC=DD
ID=02 message=.ABB.93. expected CRC=93
ID=56 message=.ABB.19. expected CRC=19
ID=99 message=.ABB.54. expected CRC=54
ID=00 message=.AB11.BD. expected CRC=BD
ID=01 message=.AB11.8A. expected CRC=8A
ID=02 message=.AB11.D3. expected CRC=D3
ID=56 message=.AB11.21. expected CRC=21
ID=99 message=.AB11.9A. expected CRC=9A
ID=00 message=.AB10.79. expected CRC=79
ID=01 message=.AB10.4E. expected CRC=4E
ID=02 message=.AB10.17. expected CRC=17
ID=56 message=.AB10.E5. expected CRC=E5
ID=99 message=.AB10.5E. expected CRC=5E
ID=00 message=.ABp.AC. expected CRC=AC
ID=01 message=.ABp.61. expected CRC=61
ID=02 message=.ABp.2F. expected CRC=2F
ID=56 message=.ABp.A5. expected CRC=A5
ID=99 message=.ABp.E8. expected CRC=E8
ID=00 message=.AB0FFFF.F0. expected CRC=F0
ID=01 message=.AB0FFFF.54. expected CRC=54
ID=02 message=.AB0FFFF.A1. expected CRC=A1
ID=56 message=.AB0FFFF.63. expected CRC=63
ID=99 message=.AB0FFFF.1E. expected CRC=1E

注意:由于列表中 ID 56 和 99 的交换消息“ABp”,您让这件事变得有点困难。不错的陷阱。

© www.soinside.com 2019 - 2024. All rights reserved.