【问题标题】:Can anyone define the Windows PE Checksum Algorithm?任何人都可以定义 Windows PE 校验和算法吗?
【发布时间】:2022-02-12 04:55:48
【问题描述】:

我想在 C# 中实现这个

我看过这里: http://www.codeproject.com/KB/cpp/PEChecksum.aspx

并且知道 ImageHlp.dll MapFileAndCheckSum 函数。

但是,由于各种原因,我想自己实现。

我发现的最好的在这里: http://forum.sysinternals.com/optional-header-checksum-calculation_topic24214.html

但是,我不明白这个解释。谁能澄清一下校验和是如何计算的?

谢谢!

更新

我从代码示例中,我不明白这是什么意思,以及如何将其翻译成C#

sum -= sum < low 16 bits of CheckSum in file // 16-bit borrow 
sum -= low 16 bits of CheckSum in file 
sum -= sum < high 16 bits of CheckSum in file 
sum -= high 16 bits of CheckSum in file 

更新 #2

谢谢,遇到了一些类似的 Python 代码here

    def generate_checksum(self):

    # This will make sure that the data representing the PE image
    # is updated with any changes that might have been made by
    # assigning values to header fields as those are not automatically
    # updated upon assignment.
    #
    self.__data__ = self.write()

    # Get the offset to the CheckSum field in the OptionalHeader
    #
    checksum_offset = self.OPTIONAL_HEADER.__file_offset__ + 0x40 # 64

    checksum = 0

    # Verify the data is dword-aligned. Add padding if needed
    #
    remainder = len(self.__data__) % 4
    data = self.__data__ + ( '\0' * ((4-remainder) * ( remainder != 0 )) )

    for i in range( len( data ) / 4 ):

        # Skip the checksum field
        #
        if i == checksum_offset / 4:
            continue

        dword = struct.unpack('I', data[ i*4 : i*4+4 ])[0]
        checksum = (checksum & 0xffffffff) + dword + (checksum>>32)
        if checksum > 2**32:
            checksum = (checksum & 0xffffffff) + (checksum >> 32)

    checksum = (checksum & 0xffff) + (checksum >> 16)
    checksum = (checksum) + (checksum >> 16)
    checksum = checksum & 0xffff

    # The length is the one of the original data, not the padded one
    #
    return checksum + len(self.__data__)

但是,它仍然不适合我 - 这是我对这段代码的转换:

using System;
using System.IO;

namespace CheckSumTest
{
    class Program
    {
        static void Main(string[] args)
        {
            var data = File.ReadAllBytes(@"c:\Windows\notepad.exe");

            var PEStart = BitConverter.ToInt32(data, 0x3c);
            var PECoffStart = PEStart + 4;
            var PEOptionalStart = PECoffStart + 20;
            var PECheckSum = PEOptionalStart + 64;
            var checkSumInFile = BitConverter.ToInt32(data, PECheckSum);
            Console.WriteLine(string.Format("{0:x}", checkSumInFile));

            long checksum = 0;

            var remainder = data.Length % 4;
            if (remainder > 0)
            {
                Array.Resize(ref data, data.Length + (4 - remainder));
            }

            var top = Math.Pow(2, 32);

            for (int i = 0; i < data.Length / 4; i++)
            {
                if (i == PECheckSum / 4)
                {
                    continue;
                }
                var dword = BitConverter.ToInt32(data, i * 4);
                checksum = (checksum & 0xffffffff) + dword + (checksum >> 32);
                if (checksum > top)
                {
                    checksum = (checksum & 0xffffffff) + (checksum >> 32);
                }
            }

            checksum = (checksum & 0xffff) + (checksum >> 16);
            checksum = (checksum) + (checksum >> 16);
            checksum = checksum & 0xffff;

            checksum += (uint)data.Length; 
            Console.WriteLine(string.Format("{0:x}", checksum));

            Console.ReadKey();
        }
    }
}

谁能告诉我我哪里傻了?

【问题讨论】:

  • 代码看不懂怎么办?示例代码会有所帮助。
  • 抱歉 - 编辑以更清晰

标签: c# algorithm checksum portable-executable


【解决方案1】:

好的,终于可以正常工作了...我的问题是我使用的是整数而不是 uint! 因此,此代码有效(假设数据是 4 字节对齐的,否则您必须将其填充一点) - PECheckSum 是 PE 中 CheckSum 值的位置(在计算校验和时显然不使用它! !!!)

static uint CalcCheckSum(byte[] data, int PECheckSum)
{
    long checksum = 0;
    var top = Math.Pow(2, 32);

    for (var i = 0; i < data.Length / 4; i++)
    {
        if (i == PECheckSum / 4)
        {
            continue;
        }
        var dword = BitConverter.ToUInt32(data, i * 4);
        checksum = (checksum & 0xffffffff) + dword + (checksum >> 32);
        if (checksum > top)
        {
            checksum = (checksum & 0xffffffff) + (checksum >> 32);
        }
    }

    checksum = (checksum & 0xffff) + (checksum >> 16);
    checksum = (checksum) + (checksum >> 16);
    checksum = checksum & 0xffff;

    checksum += (uint)data.Length;
    return (uint)checksum;

}

【讨论】:

  • 这是不正确的。它与 ImageHlp.dll MapFileAndCheckSum 输出不匹配。使用codeproject.com/Articles/19326/…中提供的PE校验和可执行文件检查输出
【解决方案2】:

论坛帖子中的代码与实际反汇编 Windows PE 代码时注意到的代码并不完全相同。 CodeProject article you reference 给出“将 32 位值折叠成 16 位”为:

mov edx,eax    ; EDX = EAX
shr edx,10h    ; EDX = EDX >> 16    EDX is high order
and eax,0FFFFh ; EAX = EAX & 0xFFFF EAX is low order
add eax,edx    ; EAX = EAX + EDX    High Order Folded into Low Order
mov edx,eax    ; EDX = EAX
shr edx,10h    ; EDX = EDX >> 16    EDX is high order
add eax,edx    ; EAX = EAX + EDX    High Order Folded into Low Order
and eax,0FFFFh ; EAX = EAX & 0xFFFF EAX is low order 16 bits  

您可以将其翻译成 C# 为:

// given: uint sum = ...;
uint high = sum >> 16; // take high order from sum
sum &= 0xFFFF;         // clear out high order from sum
sum += high;           // fold high order into low order

high = sum >> 16;      // take the new high order of sum
sum += high;           // fold the new high order into sum
sum &= 0xFFFF;         // mask to 16 bits

【讨论】:

  • 再次感谢您的回复,但将我的回复标记为答案,因为我认为它代表了完整的算法。
  • @Mark:当然,我只是想说明文章的相关部分。
【解决方案3】:

以下来自 emmanuel 的 Java 代码可能无法正常工作。在我的情况下,它挂起并且没有完成。我相信这是由于代码中大量使用了 IO:尤其是 data.read()。这可以与数组交换作为解决方案。 RandomAccessFile 完全或增量将文件读入字节数组。

我尝试了这个,但计算太慢了,因为校验和偏移的条件是跳过校验和头字节。我想 OP 的 C# 解决方案也会有类似的问题。

下面的代码也删除了这个。

public static long computeChecksum(RandomAccessFile data, int checksumOffset) 抛出 IOException {

    ...
    byte[] barray = new byte[(int) length];     
    data.readFully(barray);

    long i = 0;
    long ch1, ch2, ch3, ch4, dword;

    while (i < checksumOffset) {

        ch1 = ((int) barray[(int) i++]) & 0xff;
        ...

        checksum += dword = ch1 | (ch2 << 8) | (ch3 << 16) | (ch4 << 24);

        if (checksum > top) {
            checksum = (checksum & 0xffffffffL) + (checksum >> 32);
        }
    }
    i += 4;

    while (i < length) {

        ch1 = ((int) barray[(int) i++]) & 0xff;
        ...

        checksum += dword = ch1 | (ch2 << 8) | (ch3 << 16) | (ch4 << 24);

        if (checksum > top) {
            checksum = (checksum & 0xffffffffL) + (checksum >> 32);
        }
    }

    checksum = (checksum & 0xffff) + (checksum >> 16);
    checksum = checksum + (checksum >> 16);
    checksum = checksum & 0xffff;
    checksum += length;

    return checksum;
}

然而,我仍然认为代码过于冗长和笨拙,所以我用一个通道换掉了 raf,并将罪魁祸首字节重写为零以消除条件。这段代码可能仍然可以使用缓存样式缓冲读取。

public static long computeChecksum2(FileChannel ch, int checksumOffset)
            throws IOException {

    ch.position(0);
    long sum = 0;
    long top = (long) Math.pow(2, 32);
    long length = ch.size();

    ByteBuffer buffer = ByteBuffer.wrap(new byte[(int) length]);
    buffer.order(ByteOrder.LITTLE_ENDIAN);

    ch.read(buffer);
    buffer.putInt(checksumOffset, 0x0000);

    buffer.position(0);
    while (buffer.hasRemaining()) {
        sum += buffer.getInt() & 0xffffffffL;
        if (sum > top) {
            sum = (sum & 0xffffffffL) + (sum >> 32);
        }
    }   
    sum = (sum & 0xffff) + (sum >> 16);
    sum = sum + (sum >> 16);
    sum = sum & 0xffff;
    sum += length;

    return sum;
}

【讨论】:

    【解决方案4】:

    没有人真正回答“任何人都可以定义 Windows PE 校验和算法吗?”的原始问题。所以我将尽可能简单地定义它。到目前为止给出的许多示例都是针对无符号 32 位整数(也称为 DWORD)进行优化的,但如果您只想从最基本的角度了解算法本身,那就是:

    1. 使用无符号的 16 位整数(又名 WORD)来存储校验和,将数据的所有 WORD 相加,PE 可选标头校验和的 4 个字节除外。如果文件不是 WORD 对齐的,那么最后一个字节是 0x00。

    2. 将校验和从 WORD 转换为 DWORD 并添加文件的大小。

    上面的 PE 校验和算法实际上与原始的 MS-DOS 校验和算法相同。唯一的区别是跳过和替换末尾的 XOR 0xFFFF 并添加文件大小的位置。

    从我的WinPEFile class for PHP来看,上面的算法是这样的:

        $x = 0;
        $y = strlen($data);
        $val = 0;
        while ($x < $y)
        {
            // Skip the checksum field location.
            if ($x === $this->pe_opt_header["checksum_pos"])  $x += 4;
            else
            {
                $val += self::GetUInt16($data, $x, $y);
    
                // In PHP, integers are either signed 32-bit or 64-bit integers.
                if ($val > 0xFFFF)  $val = ($val & 0xFFFF) + 1;
            }
        }
    
        // Add the file size.
        $val += $y;
    

    【讨论】:

      【解决方案5】:

      我试图在 Java 中解决同样的问题。这是 Mark 的解决方案翻译成 Java,使用 RandomAccessFile 而不是字节数组作为输入:

      static long computeChecksum(RandomAccessFile data, long checksumOffset) throws IOException {
          long checksum = 0;
          long top = (long) Math.pow(2, 32);
          long length = data.length();
      
          for (long i = 0; i < length / 4; i++) {
              if (i == checksumOffset / 4) {
                  data.skipBytes(4);
                  continue;
              }
      
              long ch1 = data.read();
              long ch2 = data.read();
              long ch3 = data.read();
              long ch4 = data.read();
      
              long dword = ch1 + (ch2 << 8) + (ch3 << 16) + (ch4 << 24);
      
              checksum = (checksum & 0xffffffffL) + dword + (checksum >> 32);
      
              if (checksum > top) {
                  checksum = (checksum & 0xffffffffL) + (checksum >> 32);
              }
          }
      
          checksum = (checksum & 0xffff) + (checksum >> 16);
          checksum = checksum + (checksum >> 16);
          checksum = checksum & 0xffff;
          checksum += length;
      
          return checksum;
      }
      

      【讨论】:

        【解决方案6】:
        private unsafe static int GetSetPEChecksum(byte[] Array) {
            var Value = 0;
            var Count = Array.Length;
            if(Count >= 64)
                fixed (byte* array = Array) {
                    var Index = 0;
                    var Coff = *(int*)(array + 60);
                    if(Coff >= 64 && Count >= Coff + 92) {
                        *(int*)(array + Coff + 88) = 0;
                        var Bound = Count >> 1;
                        if((Count & 1) != 0) Value = array[Count & ~1];
                        var Short = (ushort*)array;
                        while(Index < Bound) {
                            Value += Short[Index++];
                            Value = (Value & 0xffff) + (Value >> 16);
                            Value = (Value + (Value >> 16)) & 0xffff;
                        }
                        *(int*)(array + Coff + 88) = Value += Count;
                    }
                }
            return Value;
        }
        

        如果你需要short unsafe...(不需要使用Double和Long整数,也不需要算法内部的数组对齐)

        【讨论】:

          【解决方案7】:

          Java 示例并不完全正确。下面的 Java 实现对应于微软在Imagehlp.MapFileAndCheckSumA 的原始实现结果。

          重要的是,输入字节被inputByte &amp; 0xff 屏蔽,而生成的long 在与currentWord &amp; 0xffffffffL 一起用于加法项时再次被屏蔽(考虑L):

              long checksum = 0;
              final long max = 4294967296L; // 2^32
          
              // verify the data is DWORD-aligned and add padding if needed
              final int remainder = data.length % 4;
              final byte[] paddedData = Arrays.copyOf(data, data.length
                      + (remainder > 0 ? 4 - remainder : 0));
          
              for (int i = 0; i <= paddedData.length - 4; i += 4)
              {
                  // skip the checksum field
                  if (i == this.offsetToOriginalCheckSum)
                      continue;
          
                  // take DWORD into account for computation
                  final long currentWord = (paddedData[i] & 0xff)
                                         + ((paddedData[i + 1] & 0xff) << 8)
                                         + ((paddedData[i + 2] & 0xff) << 16)
                                         + ((paddedData[i + 3] & 0xff) << 24);
          
                  checksum = (checksum & 0xffffffffL) + (currentWord & 0xffffffffL);
          
                  if (checksum > max)
                      checksum = (checksum & 0xffffffffL) + (checksum >> 32);
              }
          
              checksum = (checksum & 0xffff) + (checksum >> 16);
              checksum = checksum + (checksum >> 16);
              checksum = checksum & 0xffff;
              checksum += data.length; // must be original data length
          

          在这种情况下,Java 有点不方便。

          【讨论】:

            【解决方案8】:

            CheckSum 字段长 32 位,计算如下

            1。将整个文件的所有 dwords(32 位片段)相加

            将整个文件不包括 CheckSum 字段本身(包括所有标题和所有内容)的所有 dword 添加到 dword。如果双字溢出,则将溢出的位添加回双字的第一位 (2^0)。 如果文件不能完全分成 dwords(4 位块),请参见 2。

            我知道实现这一点的最佳方法是使用 GNU C 编译器整数溢出内置函数 __builtin_uadd_overflow。 在原ChkSum函数documented by Jeffrey Walton求和 通过执行add (%esi),%eax 计算,其中
            esi 包含文件的基地址,eax 为 0,然后像这样添加文件的其余部分

            adc 0x4(%esi),%eax
            adc 0x8(%esi),%eax
            adc 0xc(%esi),%eax
            adc 0x10(%esi),%eax
            ...
            adc $0x0,%eax
            

            第一个 add 添加第一个 dword 忽略任何进位标志。下一个双字
            adc 指令添加,它与add 做同样的事情,但是
            另外添加在执行指令之前设置的任何进位标志
            到总督。最后一个 adc $0x0,%eax 仅添加最后一个进位标志,如果它
            已设置且无法丢弃。

            请记住,不应添加 CheckSum 字段本身的 dword。

            2。如果有一个,则将余数添加到总和中

            如果文件不能完全分成双字,则将剩余部分添加为a
            零填充双字。例如:假设您的文件长 15 个字节,如下所示
            0E 1F BA 0E | 00 B4 09 CD | 21 B8 01 4C | CD 21 54
            您需要将余数作为0x005421CD 添加到总和中。我的系统是一个
            小端系统。我不知道校验和是否会因为
            大端系统上的这种字节顺序,或者您只需模拟这个
            行为。
            我通过将 buffer_size 向上舍入到下一个可被 4 整除的字节数来做到这一点
            没有余数或换一种说法:表示下一个完整的 dword 计数
            以字节为单位。然后我用calloc 分配,因为它初始化了内存块
            全部为零。

            if(buffer_size%4)
              {buffer_size+=4-(buffer_size%4);
            ...
            calloc(buffer_size,1)
            

            3。将总和的低位字(16 位块)和高位字相加。

            sum=(sum&amp;0xffff)+(sum&gt;&gt;16);

            4。再次添加新的更高的词

            sum+=(sum&gt;&gt;16);

            5。只保留较低的单词

            sum&amp;=0xffff;

            6。将文件中的字节数相加

            return(sum+size);

            我就是这样写的。它不是 C#,而是 C。off_t size 是文件中的字节数。 uint32_t *base 是指向加载到内存中的文件的指针。内存块应该在末尾用零填充到下一个可被 4 整除的字节数。

            uint32_t pe_header_checksum(uint32_t *base,off_t size)
              {uint32_t sum=0;
              off_t i;
              for(i=0;i<(size/4);i++)
                {if(i==0x36)
                  {continue;}
                sum+=__builtin_uadd_overflow(base[i],sum,&sum);}
              if(size%4)
                {sum+=base[i];}
              sum=(sum&0xffff)+(sum>>16);
              sum+=(sum>>16);
              sum&=0xffff;
              return(sum+size);}
            

            如果您愿意,可以查看实际代码并阅读更多内容here

            【讨论】:

              猜你喜欢
              • 2011-07-20
              • 1970-01-01
              • 2012-11-10
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-07
              • 2011-03-31
              • 1970-01-01
              相关资源
              最近更新 更多