Почему встроенный CRC отличается от текущего CRC?

Я нашел этот экзамен Delphi. Предполагается встроить CRC и проверить текущую CRC. Оба должны совпадать, но я получаю разные результаты. Как это исправить? А как это ускорить?

CRC32Calc.pas

unit CRC32Calc;

interface

uses Classes, SysUtils, windows, messages;

type
  Long = record
    LoWord: Word;
    HiWord: Word;
  end;

const
  CRCPOLY = $EDB88320;

procedure BuildCRCTable;
function RecountCRC(b: byte; CrcOld: LongWord): LongWord;
function GetCRC32(FileName: string; Full: boolean): string;

function SetEmbeddedCRC(FileName: string): string;
function GetEmbeddedCRC(FileName: string): string;

function BytesToHexStr(pB: PByte; BufSize: LongWord): String;
function HexStrToBytes(Str: String): String;

implementation

var
  CRCTable: array [0 .. 512] Of LongWord;

  // A helper routine that creates and initializes
  // the lookup table that is used when calculating a CRC polynomial
procedure BuildCRCTable;
var
  i, j: Word;
  r: LongWord;
begin
  FillChar(CRCTable, SizeOf(CRCTable), 0);
  for i := 0 to 255 do
  begin
    r := i shl 1;
    for j := 8 downto 0 do
      if (r and 1) <> 0 then
        r := (r Shr 1) xor CRCPOLY
      else
        r := r shr 1;
    CRCTable[i] := r;
  end;
end;

// A helper routine that recalculates polynomial relative to the specified byte
function RecountCRC(b: byte; CrcOld: LongWord): LongWord;
begin
  RecountCRC := CRCTable[byte(CrcOld xor LongWord(b))
    ] xor ((CrcOld shr 8) and $00FFFFFF)
end;

// A helper routine that converts Word into String
function HextW(w: Word): string;
const
  h: array [0 .. 15] Of char = '0123456789ABCDEF';
begin
  HextW := '';
  HextW := h[Hi(w) shr 4] + h[Hi(w) and $F] + h[Lo(w) shr 4] + h[Lo(w) and $F];
end;

// A helper routine that converts LongWord into String
function HextL(l: LongWord): string;
begin
  with Long(l) do
    HextL := HextW(HiWord) + HextW(LoWord);
end;

// Calculate CRC32 checksum for the specified file
function GetCRC32(FileName: string; Full: boolean): string;
var
  f: TFileStream;
  i, CRC: LongWord;
  aBt: byte;
begin
  // Build a CRC table
  BuildCRCTable;

  CRC := $FFFFFFFF;
  // Open the file
  f := TFileStream.Create(FileName, (fmOpenRead or fmShareDenyNone));

  // To calculate CRC for the whole file use this loop boundaries
  if Full then
    for i := 0 to f.Size - 1 do
    begin
      f.Read(aBt, 1);
      CRC := RecountCRC(aBt, CRC);
    end
  else
    // To calculate CRC for the file excluding the last 4 bytes
    // use these loop boundaries
    for i := 0 to f.Size - 5 do
    begin
      f.Read(aBt, 1);
      CRC := RecountCRC(aBt, CRC);
    end;

  f.Destroy;
  CRC := Not CRC;

  Result := HextL(CRC);
end;

// Calculate CRC and writes it to the end of file
function SetEmbeddedCRC(FileName: string): string;
var
  f: TFileStream;
  CRCOffset: LongWord;
  CRC: string;
begin
  f := TFileStream.Create(FileName, (fmOpenReadWrite or fmShareDenyNone));
  CRCOffset := f.Size;

  // Append a placeholder for actual CRC to the file
  f.Seek(CRCOffset, TSeekOrigin.soBeginning);
  f.Write(PByte(HexStrToBytes('FFFFFFFF'))^, 4);

  // Obtain CRC
  CRC := GetCRC32(FileName, True);

  // Write CRC to the end of file
  f.Seek(CRCOffset, TSeekOrigin.soBeginning);
  f.Write(PByte(HexStrToBytes(CRC))^, 4);
  f.Destroy;
  Result := CRC;
end;

// Extract the CRC that was stored at last 4 bytes of a file
function GetEmbeddedCRC(FileName: string): string;
var
  f: TFileStream;
  CRCOffset: LongWord;
  pB: PByte;
begin
  GetMem(pB, 4);

  // Open file
  f := TFileStream.Create(FileName, (fmOpenRead or fmShareDenyNone));

  // Proceed upto the end of file
  CRCOffset := f.Size - 4;
  f.Seek(CRCOffset, TSeekOrigin.soBeginning);

  // Read the last four bytes where the CRC is stored
  f.Read(pB^, 4);
  f.Destroy;
  Result := BytesToHexStr(pB, 4);
end;

// A helper routine that converts byte value to string with hexadecimal integer
function BytesToHexStr(pB: PByte; BufSize: LongWord): String;
var
  i, j, b: LongWord;
begin
  SetLength(Result, 2 * BufSize);

  for i := 1 to BufSize do
  begin
    for j := 0 to 1 do
    begin
      if j = 1 then
        b := pB^ div 16
      else
        b := pB^ - (pB^ div 16) * 16;
      case b of
        0:
          Result[2 * i - j] := '0';
        1:
          Result[2 * i - j] := '1';
        2:
          Result[2 * i - j] := '2';
        3:
          Result[2 * i - j] := '3';
        4:
          Result[2 * i - j] := '4';
        5:
          Result[2 * i - j] := '5';
        6:
          Result[2 * i - j] := '6';
        7:
          Result[2 * i - j] := '7';
        8:
          Result[2 * i - j] := '8';
        9:
          Result[2 * i - j] := '9';
        10:
          Result[2 * i - j] := 'A';
        11:
          Result[2 * i - j] := 'B';
        12:
          Result[2 * i - j] := 'C';
        13:
          Result[2 * i - j] := 'D';
        14:
          Result[2 * i - j] := 'E';
        15:
          Result[2 * i - j] := 'F';
      end;
    end;

    Inc(pB);
  end;
end;

// A helper routine that converts string with hexadecimal integer to byte value
function HexStrToBytes(Str: String): String;
var
  b, b2: byte;
  lw, lw2, lw3: LongWord;
begin
  lw := Length(Str) div 2;
  SetLength(Result, lw);

  for lw2 := 1 to lw do
  begin
    b := 0;

    for lw3 := 0 to 1 do
    begin
      case Str[2 * lw2 - lw3] of
        '0':
          b2 := 0;
        '1':
          b2 := 1;
        '2':
          b2 := 2;
        '3':
          b2 := 3;
        '4':
          b2 := 4;
        '5':
          b2 := 5;
        '6':
          b2 := 6;
        '7':
          b2 := 7;
        '8':
          b2 := 8;
        '9':
          b2 := 9;
        'a':
          b2 := 10;
        'b':
          b2 := 11;
        'c':
          b2 := 12;
        'd':
          b2 := 13;
        'e':
          b2 := 14;
        'f':
          b2 := 15;
        'A':
          b2 := 10;
        'B':
          b2 := 11;
        'C':
          b2 := 12;
        'D':
          b2 := 13;
        'E':
          b2 := 14;
        'F':
          b2 := 15;
      else
        b2 := 0;
      end;

      if lw3 = 0 then
        b := b2
      else
        b := b + 16 * b2;
    end;

    Result[lw2] := char(b);
  end;
end;

end.

AppendCRC

program AppendCRC;

{$APPTYPE CONSOLE}

uses
  SysUtils, Classes,
  CRC32Calc in '..\CRC32Checker\CRC32Calc.pas';

var
  FileName: string;

begin
  { TODO -oUser -cConsole Main : Insert code here }
  if ParamCount = 1 then
  begin
    FileName := ParamStr(1);
    // Verify whether a file exists
    if not FileExists(FileName) then
    begin
      WriteLn('The specified file does not exist.');
      Exit;
    end;
    WriteLn('Full checksum (before): ' + GetCRC32(FileName, True));
    SetEmbeddedCRC(FileName);
    WriteLn('Half checksum: ' + GetCRC32(FileName, False));
    WriteLn('Full checksum (after): ' + GetCRC32(FileName, True));
    WriteLn('GetEmbeddedCRC: :' + GetEmbeddedCRC(FileName));
    WriteLn('The checksum was successfully embedded.')
  end
  else
  begin;
    WriteLn('Wrong parameters.');
    WriteLn('Parameter1 - Full path to file.');;
  end;

end.

Мои результаты:

AppendCRC.exe Hello_Delphi_World.exe
Full checksum (before): 1912DA64
Half checksum: 1912DA64
Full checksum (after): B3F0A43E
GetEmbeddedCRC: :4400A000
The checksum was successfully embedded.

Я использую Delphi XE5.


person Edijs Kolesnikovičs    schedule 13.01.2014    source источник
comment
Я предлагаю вам задать этот хороший вопрос, разместив здесь соответствующие части кода. StackOverflow предназначен для того, чтобы ответы и вопросы были на сайте, а не в какой-либо внешней ссылке, которая может исчезнуть в любой момент. А просьба к людям попробовать загрузить ZIP-архив снижает ваши шансы получить достойный ответ.   -  person Jan Doggen    schedule 13.01.2014


Ответы (1)


Вы должны понимать, как работает этот код. Общая идея состоит в том, чтобы добавить CRC как дополнительные 4 байта вне структуры EXE в конец файла. (Лучше было бы вначале поместить CRC в специальное поле внутри EXE-заголовка).

Однако это поднимает проблему курицы и яйца: после того, как мы вычисляем CRC и вставляем его, файл CRC изменяется (добавляется значение CRC), и CRC измененных файлов тоже изменяется.

Таким образом, вам в основном необходимо реализовать два режима / функции вычисления CRC: для всего файла и для файла без последних 4 байтов. Вы должны использовать последний режим для вычисления CRC после добавления (вы называете его вложением), а первый - для вычисления CRC перед этим в ванильной только что скомпилированной программе.

Ваша функция GetCRC32 всегда вырезает последние 4 байта из файла, поэтому перед встраиванием она вычисляет CRC только некоторой части файла, а не всего файла. Но должно быть два разных режима.

PS: вы также можете «встроить» CRC в альтернативный поток NTFS, например, если программа MyApp.exe и CRC хранятся как MyApp.exe:CRC.

PPS. Я думаю, что использование небуферизованного чтения побайтно в GetCRC32 должно быть очень медленным. Если возможно, лучше использовать TBytesStream для чтения файла в память целиком, а затем сканировать в обычном цикле по массиву. Или читайте его кусками по 4096 байт, а не по байтовым переменным. Например, для последнего неполного буфера оставшуюся часть буфера следует очистить нулями.

person Arioch 'The    schedule 13.01.2014
comment
Я бы изменил SetEmbeddedCRC, чтобы не только вычислять и добавлять CRC в конец файла, но также добавлять маркер (или подпись), чтобы GetEmbeddedCRC и SetEmbeddedCRC распознали, есть ли в файле CRC или нет. Если CRC уже существует, он будет пропущен при вычислении CRC. Подпись CRC может быть еще 4 байта с CRC CRC. Таким образом, общее количество байтов, добавленных к файлу, составляет 8 байтов. - person fpiette; 13.01.2014
comment
В чем смысл ? почему бы не сделать режимы GetCRC32 с игнорированием последних байтов или без них? - person Arioch 'The; 13.01.2014
comment
Спасибо за ваше предложение, но альтернативный поток NTFS - это не то, что мне нужно. - person Edijs Kolesnikovičs; 14.01.2014
comment
Без маркера GetCRC не может определить, является ли CRC неправильным или нет CRC вообще. - person fpiette; 14.01.2014
comment
@fpiette Я не думаю, что есть разница в практическом смысле. Либо EXE подписан, либо нет - person Arioch 'The; 14.01.2014
comment
Когда вам нужно проверить exe, важно иметь значение, если exe не подписан или если подпись плохая (exe изменен). - person fpiette; 15.01.2014
comment
@fpiette иногда ti иногда нет. Я думаю, пока вы не докажете, что это различие требуется всегда, это оставлено на усмотрение автора темы. Лично я считаю, что необходимость автоматической проверки программы не такой, какой она должна быть (что не различает эти случаи), а затем для проверки человеком, почему и как программа изменилась (что человек сделал бы с другими инструментами, таким образом, этот автоматический чек - не заботиться о дальнейшем развитии событий). На мой взгляд, имеет значение то, что CRC не является надежной контрольной суммой, но, опять же, это то, что решает автор темы. - person Arioch 'The; 16.01.2014