任何人都可以定义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();
}
}
}
有人能告诉我我哪里笨吗?
好吧,终于可以正常工作了……我的问题是我使用的是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)进行优化的,但如果你只想了解算法本身的最基本原理,那么它就是:
-
使用无符号16位整数(也称为WORD)存储校验和,将除PE可选标头校验和的4个字节外的所有数据WORD相加。如果文件不是WORD对齐的,那么最后一个字节是0x00。
-
将校验和从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);}
如果你愿意的话,你可以在这里看到代码的作用并阅读更多。