ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • CRC 리버스 엔지니어링하기
    application 2025. 2. 9. 12:14

    Reverse Engineer CRC

    목적

    • CRC를 리버스 엔지니어링하는 것이 어렵지 않다는 것을 보여준다.

    개요

    데이터 준비

    • 데이터 길이가 16 바이트인 CAN 메시지를 가정한다.
    • 시작 바이트는 0번이다.
    • 0번 부터 10번 바이트까지를 랜덤 바이트로 채운다.
    • 11번 바이트에는 alive counter를 넣는다.
    • 12번 부터 15번 바이트까지는 4 바이트 CRC로 채운다.
    • 왜 4 바이트 CRC인가?
      • AUTOSAR CRC는 1 바이트이거나 4 바이트이다.
      • 1 바이트는 4 바이트보다 상대적으로 CRC 알고리즘을 찾는 리버스 엔지니어링이 쉬울 것 같다. (실제 그런지 모르겠다.) 일부러 어려워 보이는 4 바이트를 선택한다. 어려워도 리버스 엔지니어링 된다는 것을 보여주려는 목적이다
    • 실험용 메시지 n_message개를를 생성한다.

    CRC 알고리즘 리버스 엔지니어링

    • 실험용 메시지들 중에서 처음 n_sample개의 샘플 메시지들을 선택한다.
    • RevEng.exe (https://pypi.org/project/crc/ 인터넷에 공개된 프로그램이다.) 샘플 메시지들을 이용해서 CRC 알고리즘을 찾아낸다.

    검증

    • CRC 알고리즘을 찾았다면,
    • 전체 시험용 메시지들을 대상으로 찾은 CRC 알고리즘이 맞는지 확인한다.
    # CRC 패키지를 설치한다.
    !pip install -U crc
    import random   # 랜덤 데이터 생성에 필요하다.
    from crc import Calculator, Crc32   # CRC 계산에 필요하다.
    import subprocess   # 외부 프로그램(reveng.exe)을 실행하기 위해 필요하다.

    데이터 준비

    messages = []
    calculator = Calculator(Crc32.AUTOSAR)
    n_message = 24
    
    # print header
    for i in range(12):
        print(f'{i:2}', end=' ')
    print('|', end=' ')
    for i in range(12, 16):
        print(f'{i:2}', end=' ')
    print()
    print('-' * 49)
    
    # 실험용 메시지들을 생성한다.
    for i in range(n_message):
        # random data
        data = bytearray(random.randbytes(11))
    
        # alive counter
        data.append(i % 256)
    
        # crc
        crc_autosar = calculator.checksum(data).to_bytes(4, 'little', signed=False)
        for byte in crc_autosar:
            data.append(byte)
    
        # 메시지를 출력한다. CRC 부분과 나머지 부분을 구분하기 위해 '|'를 출력한다.
        for byte in data[:12]:
            print(f'{byte:02x}', end=' ')
        print('|', end=' ')
        for byte in data[12:]:
            print(f'{byte:02x}', end=' ')
        print()
    
        messages.append(data)
    
    print()
     0  1  2  3  4  5  6  7  8  9 10 11 | 12 13 14 15 
    -------------------------------------------------
    87 99 f2 63 f5 46 7f ae d4 d6 a7 00 | 54 a3 81 c1 
    0b e4 db f2 ee c9 3f 05 66 17 08 01 | c3 12 d5 19 
    88 58 71 a2 9f f4 c6 62 d4 ac f2 02 | 32 4d f6 62 
    b1 16 97 51 ed ee ca f8 74 89 60 03 | 65 69 ff 23 
    2e 1b 9a 68 c6 82 46 e2 48 08 e6 04 | a4 df ba 1b 
    93 26 31 6f 0c 55 cf 34 8f 73 95 05 | 88 82 7d 07 
    0b 59 23 10 43 51 72 cf aa 7c b4 06 | ca 75 1e c7 
    88 f3 5b cc 84 15 66 e8 62 81 75 07 | 0b 65 df 3d 
    77 3a 17 78 98 69 c9 44 96 83 98 08 | aa 5f f2 29 
    d9 91 9a 02 a5 ea 68 63 5b ee 21 09 | 00 65 a2 25 
    32 3b a9 88 8b 98 8d af eb c9 d9 0a | 88 04 ef 90 
    28 b6 a1 88 6f 02 3f 0a 36 fa 27 0b | 5e 71 8b 23 
    62 d3 72 ed 0d 11 b6 c3 75 c2 fb 0c | 49 6d ca a7 
    6f ce 4a 7b 1f 44 76 b2 05 67 49 0d | e2 38 de 0f 
    c2 88 d6 c7 e0 69 f9 96 26 f7 9c 0e | df 5e 0d 55 
    23 fd ff ff ba 18 42 01 4c 87 b7 0f | ea da 3a c4 
    d8 00 6e 8c 99 dc 6e 4e 3a 67 b8 10 | f8 64 c1 19 
    9b c8 be 7e 25 65 0f 4a c9 f6 f1 11 | 14 ee 39 91 
    26 1c d4 60 91 3f 5f 0a 87 59 d8 12 | 0e 1c b0 77 
    b6 36 f7 76 f2 9e 87 5f ae 63 37 13 | 29 6f b2 13 
    01 1a fa 5f 8f c0 15 35 2f a7 79 14 | 8f a7 29 c5 
    ae 95 5e b8 4c 09 e8 92 58 f6 9e 15 | 18 d9 78 b8 
    fa 60 f8 9b 5e 50 81 71 dc f2 8d 16 | 2f 9d dc 5b 
    e0 10 cb 9c 07 a8 23 20 23 c0 45 17 | 96 55 dd fb 

    CRC 알고리즘 리버스 엔지니어링

    # CRC 알고리즘을 찾는데 사용할 샘플 데이터를 고른다.
    n_sample = 4
    samples = []
    for i_sample in range(n_sample):
        samples.append(messages[i_sample].hex())
    
    # reveng.exe를 실행하기 위한 명령어를 준비한다.
    command_line = ['reveng', '-w32', '-s', ] + samples
    command_line
    ['reveng',
     '-w32',
     '-s',
     '8799f263f5467faed4d6a70054a381c1',
     '0be4dbf2eec93f0566170801c312d519',
     '885871a29ff4c662d4acf202324df662',
     'b1169751edeecaf8748960036569ff23']
    with subprocess.Popen(command_line, stdout=subprocess.PIPE).stdout as reveng_proc:
        model_bytearray = reveng_proc.read()
    
    model_str = model_bytearray.decode('utf-8')
    model_info = model_str.split()
    
    poly = model_info[1].strip('ploy=')
    init = model_info[2].strip('init=')
    xorout = model_info[5].strip('xorout=')
    model_name = model_info[-1].strip('name=').strip('"')
    
    model_info
    ['width=32',
     'poly=0xf4acfb13',
     'init=0xffffffff',
     'refin=true',
     'refout=true',
     'xorout=0xffffffff',
     'check=0x1697d06a',
     'residue=0x904cddbf',
     'name="CRC-32/AUTOSAR"']
    • CRC 알고리즘을 찾았다.
    • name에서 알고리즘 (reveng는 모델이라고 부른다.) 이름을 확인할 수 있다.
    • CRC-32/AUTOSAR 모델이다.
    • 모델의 설정 값들이 모두 맞는지 AUTOSAR CRC의 설정과 비교한다. 잘 맞는다.

    출처: Configurations - CRC

    • AUTOSAR 모델의 경우 n_sample이 1이어도 모델을 찾을 수 있었다.

     

    검증

    # print header
    for i in range(12):
        print(f'{i:2}', end=' ')
    print('|', end=' ')
    for i in range(12, 16):
        print(f'{i:2}', end=' ')
    print('|', end=' ')
    for i in range(12, 16):
        print(f'{i:2}', end=' ')
    print()
    print('-' * 63)
    
    for data in messages:
        # alive counter를 포함한 CRC 계산의 입력 부분이다.
        data_str = data[:12].hex()
    
        # reveng.exe를 실행하여 CRC를 계산한다.
        # -m 옵션으로 정확하게 계산한다.
        command_line = ['reveng', '-w32', '-c', '-m', model_name, data_str]
    
        # command_line = ['reveng', '-w32', '-c', '-p', poly, '-i', init, message_str]
        # 이 경우 -p, -i, 옵션으로 CRC 알고리즘을 정확하게 계산하지 못한다. 
        # 조사하지 않아서 이유는 모르겠다. 
        # 다른 경우에서는 -p, -i 옵션만으로도 계산 결과가 잘 맞았다.
        # 상황에 따라 필요한대로 옵션을 사용하면 된다고 생각한다. 
    
        # reveng.exe를 실행하여 CRC를 계산한다.
        with subprocess.Popen(command_line, stdout=subprocess.PIPE).stdout as reveng_proc:
            model_crc = reveng_proc.read().strip()
    
        # 결과를 출력한다.
        for byte in data[:12]:
            print(f'{byte:02x}', end=' ')
        print('|', end=' ')
        for byte in data[12:]:
            print(f'{byte:02x}', end=' ')
        print('|', end=' ')
    
        _ = model_crc.decode('utf-8')
        for byte in [_[i:i+2] for i in range(0, len(_), 2)]:
            print(f'{byte}', end=' ')
    
        crc_received = data[12:].hex()
        crc_calculated = model_crc.decode('utf-8')
    
        if crc_received == crc_calculated:
            print(' 🟩')
        else:
            print(' 🟥')
    
    
     0  1  2  3  4  5  6  7  8  9 10 11 | 12 13 14 15 | 12 13 14 15 
    ---------------------------------------------------------------
    87 99 f2 63 f5 46 7f ae d4 d6 a7 00 | 54 a3 81 c1 | 54 a3 81 c1  🟩
    0b e4 db f2 ee c9 3f 05 66 17 08 01 | c3 12 d5 19 | c3 12 d5 19  🟩
    88 58 71 a2 9f f4 c6 62 d4 ac f2 02 | 32 4d f6 62 | 32 4d f6 62  🟩
    b1 16 97 51 ed ee ca f8 74 89 60 03 | 65 69 ff 23 | 65 69 ff 23  🟩
    2e 1b 9a 68 c6 82 46 e2 48 08 e6 04 | a4 df ba 1b | a4 df ba 1b  🟩
    93 26 31 6f 0c 55 cf 34 8f 73 95 05 | 88 82 7d 07 | 88 82 7d 07  🟩
    0b 59 23 10 43 51 72 cf aa 7c b4 06 | ca 75 1e c7 | ca 75 1e c7  🟩
    88 f3 5b cc 84 15 66 e8 62 81 75 07 | 0b 65 df 3d | 0b 65 df 3d  🟩
    77 3a 17 78 98 69 c9 44 96 83 98 08 | aa 5f f2 29 | aa 5f f2 29  🟩
    d9 91 9a 02 a5 ea 68 63 5b ee 21 09 | 00 65 a2 25 | 00 65 a2 25  🟩
    32 3b a9 88 8b 98 8d af eb c9 d9 0a | 88 04 ef 90 | 88 04 ef 90  🟩
    28 b6 a1 88 6f 02 3f 0a 36 fa 27 0b | 5e 71 8b 23 | 5e 71 8b 23  🟩
    62 d3 72 ed 0d 11 b6 c3 75 c2 fb 0c | 49 6d ca a7 | 49 6d ca a7  🟩
    6f ce 4a 7b 1f 44 76 b2 05 67 49 0d | e2 38 de 0f | e2 38 de 0f  🟩
    c2 88 d6 c7 e0 69 f9 96 26 f7 9c 0e | df 5e 0d 55 | df 5e 0d 55  🟩
    23 fd ff ff ba 18 42 01 4c 87 b7 0f | ea da 3a c4 | ea da 3a c4  🟩
    d8 00 6e 8c 99 dc 6e 4e 3a 67 b8 10 | f8 64 c1 19 | f8 64 c1 19  🟩
    9b c8 be 7e 25 65 0f 4a c9 f6 f1 11 | 14 ee 39 91 | 14 ee 39 91  🟩
    26 1c d4 60 91 3f 5f 0a 87 59 d8 12 | 0e 1c b0 77 | 0e 1c b0 77  🟩
    b6 36 f7 76 f2 9e 87 5f ae 63 37 13 | 29 6f b2 13 | 29 6f b2 13  🟩
    01 1a fa 5f 8f c0 15 35 2f a7 79 14 | 8f a7 29 c5 | 8f a7 29 c5  🟩
    ae 95 5e b8 4c 09 e8 92 58 f6 9e 15 | 18 d9 78 b8 | 18 d9 78 b8  🟩
    fa 60 f8 9b 5e 50 81 71 dc f2 8d 16 | 2f 9d dc 5b | 2f 9d dc 5b  🟩
    e0 10 cb 9c 07 a8 23 20 23 c0 45 17 | 96 55 dd fb | 96 55 dd fb  🟩

    결론

    • 리버스 엔지니어링으로 CRC 알고리즘을 찾는 것은 어렵지 않다.
    • CRC는 데이터에 결함에 결함 유무를 확인하는 용도에는 적합하지만 보안 강화를 위한 용도에는 적합하지 않다.

     

    참고

    • 위 계산을 한 jupyter notebook

    reverse_engineer_crc.ipynb
    0.01MB