任何人都可以定义Windows PE校验和算法吗

本文关键字:校验和 算法 PE Windows 都可以 定义 任何人 | 更新日期: 2023-09-27 17:59:44

我想在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代码

    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();
        }
    }
}

有人能告诉我我哪里笨吗?

任何人都可以定义Windows PE校验和算法吗

好吧,终于可以正常工作了……我的问题是我使用的是int而不是uints!!!因此,这段代码是有效的(假设数据是4字节对齐的,否则你必须稍微填充一下)-PECheckSum是校验和值在PE中的位置(在计算校验和时显然没有使用它!!)

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;
}

论坛帖子中的代码与实际反汇编Windows PE代码时注意到的代码并不完全相同。您参考的CodeProject文章给出了"将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#:

来自emmanuel的
// 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

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

我尝试过这样做,但由于校验和偏移量跳过校验和标头字节的条件,计算速度太慢。我想OP的C#解决方案也会有类似的问题。

下面的代码也删除了这一点。

public static long computeChecksum(RandomAccessFile数据,int checksumOffset)throws 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;
}

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

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

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

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

从我的PHP WinPEFile类中,上面的算法看起来像:

    $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;

我试图在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;
}
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;
}

如果你需要短的不安全。。。(不需要使用Double和Long整数,也不需要在算法内部对齐数组)

Java示例并不完全正确。下面的Java实现与微软最初从Imagehlp.MapFileAndCheckSumA实现的结果相对应。

重要的是,当输入字节与currentWord & 0xffffffffL(考虑L)相加时,用inputByte & 0xff屏蔽输入字节,并再次屏蔽生成的long

    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有点不方便。

CheckSum字段长32位,计算如下

1.将整个文件的所有双字(32位)相加为和

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

我知道实现这一点的最好方法是使用GNUC编译器的Integer Overflow Builtin函数__Builtin_uadd_Overflow。在Jeffrey Walton记录的原始ChkSum函数中通过执行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

第一CCD_ 8添加忽略任何进位标志的第一双字。下一个数据字
adc指令添加,该指令执行与add相同的操作,但
添加在执行指令之前设置的任何进位标志
到被加数。最后一个adc $0x0,%eax只添加最后一个进位标志,如果
已设置,无法丢弃。

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

2.如果有一个

,就把余数加到和上

如果文件不能完全分割为双字,则将余数添加为
零填充双字。例如:假设您的文件长15字节,如下所示
0E 1F BA 0E | 00 B4 09 CD | 21 B8 01 4C | CD 21 54
您需要将余数作为0x005421CD添加到和中。我的系统是
little-endian系统。我不知道校验和是否会因为
而更改big-endian系统上的字节顺序,或者您只需要模拟它
行为
我通过将buffer_size四舍五入到可被4整除的下一个字节数来实现这一点
不带余数或以不同方式表示:下一个完整的dword计数表示
以字节为单位。然后我用calloc进行分配,因为它初始化了内存块
全部为零。

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

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

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

4.再次添加新的较高单词

sum+=(sum>>16);

5.只保留较低的单词

sum&=0xffff;

6.将文件中的字节数与总和相加

return(sum+size);

我就是这么写的。它不是C#,而是文件中的字节数。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);}

如果你愿意的话,你可以在这里看到代码的作用并阅读更多。