Windows PE 校验和算法分析






4.89/5 (38投票s)
分析演示 PE 校验和是基于 RFC 1071 的累加校验和。
引言
本文将讨论微软用于生成 PE 校验和的算法。微软似乎并未公开提供该规范,包括在《微软可移植可执行文件和通用对象文件格式规范》中也缺乏相关文档。此外,Matt Pietrek 在其著作中也未提供该算法。
与普遍看法相反,微软使用的算法并非循环冗余校验 (CRC)。尽管像知识库文章 65122 这样的文档声称使用了 32 位 CRC(如下所示)。
以下是校验和中使用的四个八位字节的分解。未使用的字节被设置为 0,实际上创建了一个 24 位的校验和。值得注意的是,该算法——一个加法校验和——是一种冗余校验;但它并不是 CRC。CRC 具有在错误检测问题领域中更理想的额外数学特性。
对微软 Outlook CRC 算法感兴趣的读者,可以参阅 Frank Sconzo 在 comp.lang.perl.misc 上的帖子 CRC on Unix vs Win32。Tim Heaney 在其中揭示了微软的方法。
最后,对于有兴趣了解完整算法的读者,Peter Szor 在《计算机病毒研究与防范的艺术》一书中介绍了其高层设计。W32/Kriz 病毒内部计算受感染文件的校验和以掩盖篡改。Szor 在第 6.2.8.1.12 节“校验和重新计算”中记录了该算法和病毒。
将要涵盖的主题如下。请注意,生成循环和生成循环设置的顺序似乎是颠倒的。这是有意为之,在阅读到相关部分时就会明白。本文将详细研究主要的生成循环和生成循环设置,而对最终的线性变换不作处理。省略的目的是为了保留微软的“通过晦涩实现安全”政策。
- 工具
- 符号
- ImageHlp.dll
- WinDbg
- 分析
- 序言
- 生成循环
- 生成循环设置
- 结语
- 时间戳的影响
- 实际攻击
- PE 校验和算法
- RFC-1071 C 语言实现
- 摘要
工具
本文使用四种工具来研究该算法
- PEChecksum.exe
- WinDbg
- UltraEdit
- UltraCompare
PEChecksum 用于查询 EXE 的校验和并写入任意校验和。此外,该可执行文件依赖于 ImageHlp.dll。此依赖关系强制链接加载器加载 Image Help,以便进行实时目标分析。PEChecksum 及其源代码可在下载区获取。
使用 WinDbg 是因为 Compuware 不再提供 SoftICE;而且可能更好的是:WinDbg 是免费的。WinDbg 可从微软网站下载。
如果读者需要,kd、OllyDbg 或 SyserDebug 应该足以作为替代品。读者应记住,WinDbg 是由 Windows 操作系统团队编写的。这与生产 Visual Studio 的开发团队或生产 OllyDbg 或 SyserDebug 等竞争产品的第三方不同。
来自 IDM 软件的 UltraEdit 可用作十六进制编辑器,向可执行文件写入值。UltraCompare 用于验证原始文件和修改后文件之间的差异。
符号
强烈建议读者在自己的机器上设置本地符号。John Robbins 在《调试 Microsoft .NET 2.0 应用程序》的第 2 章和第 6 章中广泛地讨论了这个问题。John 还提供了一个出色的脚本(OSsyms.js)来自动化符号检索过程。
对于无法访问该书的读者,请访问 Johnathan [Darka] 的[调试] - 符号。请注意,本地符号服务器不是必需的——只需使用微软提供的服务器即可。然而,读者确实希望符号在本地可用。
ImageHlp.dll
Imagehlp.dll 是一个向程序员公开许多调试和维护例程的库。值得注意的是,该库不是线程安全的。有关在线程安全方式下重用 Imagehlp 代码的技术,请参阅移植已编译代码:无限代码重用。
ImageHlp.dll 提供了函数 CheckSumMappedFile()。`CheckSumMappedFile()` 内部调用函数 `ChkSum()`,后者是校验和计算的主力。`CheckSumMappedFile()` 的签名如下所示。
PIMAGE_NT_HEADERS CheckSumMappedFile( PVOID BaseAddress, DWORD FileLength, PDWORD HeaderSum, PDWORD CheckSum );
`BaseAddress` 是从 MapViewOfFile() 返回的,而 `FileLength` 可以通过 GetFileSize() 或 GetFileSizeEx() 确定。加载器在进程创建期间会通过 `MtMapViewOfSection()`(它提供了 `MapViewOfFile()` 的前向 API 接口)映射可执行文件的视图。有关加载器行为的更详细研究,请参阅动态 TEXT 节映像验证。
以前的 Microsoft SDK
根据微软 MVP Ken Johnson 的说法,过去的 SDK 确实包含了 `CheckSumMappedFile()` 的源代码,但不包括 `ChkSum()`。
imagehlp 曾经是很久以前的一个 SDK 示例,但有意地省略了 CheckSumMappedFile() 使用的 "ChkSum" 辅助函数的源代码。从 SDK 示例源代码来看,它似乎打算从另一个库或一个从未随示例代码一起发布的 .asm 文件中链接进来。
此外,早期的 SDK 提供了以下原型
USHORT ChkSum( DWORD PartialSum, PUSHORT Source, DWORD Length );
和
PartialSum = ChkSum(0, (PUSHORT)BaseAddress, (FileLength + 1) >> 1);
WinDbg
Code Project 上有更完整的 WinDbg 介绍,请参阅 Saikat Sen 的Windows 调试器:第一部分:WinDbg 教程。作者倾向于在 WinDbg 中同时显示三个窗口:命令窗口、反汇编窗口和寄存器窗口。在任何时候,根据需要可能会出现内存窗口和调用堆栈窗口。请提前花时间打开并停靠这些窗口。正如 John Robbins 谈到浮动窗口时所说,“它们有严重的 Z 序问题”。
根据作者的运气,命令窗口通常会停靠在下面两个窗口的顶部。要打开各种窗口,请使用“视图”菜单。以下是 WinDbg 会话的典型视图。
Commands
以下是会话中常用命令的列表。熟悉 gdb 或 SoftICE 的用户应该会感到宾至如归,无需使用 CTRL-D 来中断进入 SoftICE。
F5 | Run |
F9 | 在反汇编窗口中设置断点 |
F10 | 单步跳过 |
F11 | 单步进入 |
Shift-F11 | 跳出函数 |
bp <地址> | 在地址处设置断点 |
bl | 列出断点 |
bd # | 禁用断点 |
be # | 启用断点 |
bc | 清除断点 |
ba <地址> | 访问时中断(硬件断点) |
ba <地址> <次数> | 在访问次数后中断 |
lm | 列出已加载的模块 |
ld | 加载模块 |
u <地址> | 在地址处反汇编 |
x *!* | 显示所有已加载模块的所有符号 |
x <模块>!* | 显示模块的符号 |
CTRL-Break | 中断被调试程序(返回命令窗口) |
dd <地址|寄存器> | 在地址或寄存器处以双字形式显示数据 |
dw <地址|寄存器> | 在地址或寄存器处以字形式显示数据 |
db <地址|寄存器> |
在地址或寄存器处以字节形式显示数据 |
.cls | 清除命令窗口 |
.restart |
重启被调试程序(目标可执行文件) |
.attach pid |
附加到一个进程 |
.detach |
从一个进程分离 |
? |
获取被调试程序命令的帮助 |
.help |
获取调试器命令的帮助 |
.hh command |
从帮助文件 (debugger.chm) 中获取关于 command 的帮助 |
在 WinDbg 中加载可执行文件后,程序将被暂停。要开始执行,请在设置好所需断点后按 F5 或在命令窗口中键入 g。然后,像使用 Visual Studio 调试器一样操作可执行文件。
跨平台分析
对于有兴趣使用 Linux 和 gdb 分析 Windows 代码的读者,可以参阅 Joe Stewart 在 SecurityFocus 网站上的《逆向工程恶意代码》和《外星人尸检:在 Linux 上逆向工程 Win32 木马》。
对于 Unix 和 Linux,objdump(及其基于 PERL 的包装器 dasm)和 gdb 是两个首选工具。gdb 支持调试 C、C++、Java、Fortran 和汇编等多种语言。此外,gdb 被设计为与 GNU 编译器集合(GCC)紧密协作。objdump 和 dasm 共同作为完整的反汇编器。另外,也可以使用 Wine 在 Linux 上运行像 IDA 这样的 Windows 应用程序,Wine 充当了在 Linux 上运行 Windows 程序的兼容层。
分析
分析将假定 ImageHlp.dll 的加载地址为 0x76C9000。如果读者不知道加载了哪些模块,可以在 WinDbg 中打开可执行文件后执行 list modules 命令 (lm)。下面是在 WinDbg 中打开 PEChecksum.exe 后,由于依赖关系而加载的模块。
显示 deferred 是因为 WinDbg 使用了延迟算法来解析符号。要强制加载符号,请使用加载符号命令 (ld)。该命令可以接受模块名,或使用通配符 (*) 加载所有符号。
分析中有四个感兴趣的区域:序言 (Prologue)、循环设置 (Loop Setup)、生成循环 (Generating Loop) 和尾声 (Epilogue)。序言类似于循环设置,但序言负责设置寄存器和堆栈——它是一个更低级的设置。本文中定义的循环设置工作在更高的层次上。尾声是本文将保持模糊的区域,因为其中发生了一些额外的计算,本文不会揭示。这部分分析留给读者作为练习。
请注意,分析将不按顺序进行,因为一旦掌握了主生成循环的精髓,生成循环设置中发生的事情就会更加明显。
在开始检查之前,需要两个关键信息来帮助追踪程序流程:`BaseAddress` 和 `FileSize`。这两个值分别是 0xB7000 和 0x10E00。如果读者已经对可执行文件进行了插桩,可以从 `OutputDebugString()` 中获取这些信息。还有其他方法,比如在 WinDbg 中检查 `MapViewOfFile()` 的返回值。
序言
要检查序言,请执行 bp imagehlp!ChecksumMappedFile。这行在上面的截图中被 WinDbg 用红色高亮显示。蓝色高亮表示下一个要执行的指令序列。从上面可以看出,在 0x76C96F03 处安装了一个结构化异常处理程序 (Structured Exception Handler)。然后将值推入堆栈,为调用 `ChkSum` 做准备。
76c96f03 68c86fc976 push offset imagehlp!`string'+0x3c (76c96fc8) 76c96f08 e8acc5ffff call imagehlp!_SEH_prolog (76c934b9) 76c96f0d 8b7510 mov esi,dword ptr [ebp+10h] 76c96f10 832600 and dword ptr [esi],0 76c96f13 8b450c mov eax,dword ptr [ebp+0Ch] 76c96f16 d1e8 shr eax,1 76c96f18 50 push eax 76c96f19 ff7508 push dword ptr [ebp+8] 76c96f1c 6a00 push 0 76c96f1e e856d6ffff call imagehlp!ChkSum (76c94579)
我们希望确定在 0x76C96F0D (mov esi, dword ptr [ebp+10h]) 处移入 ESI 的是什么,为调用做准备。执行 dd ebp。这将在基址指针的地址处显示双字值。
0xB70000 是文件映射视图的基地址,它被压入堆栈(如上所示)。
76c96f13 8b450c mov eax,dword ptr [ebp+0Ch] 76c96f16 d1e8 shr eax,1 76c96f18 50 push eax
接下来,EAX 被填充为文件大小 0xE100(如上所示)。出于未知原因,发生了一次右移,实际上将 0xE100/2 = 0x7080 推入堆栈。作者推测这是源于对 RFC 1071 的原始改编,该 RFC 操作的是字大小(16位)的数量。非常感谢 Peter Bell 指出移位操作与机器字长相关的重要性。
单步进入 0x76C96F1E(私有函数 `ChkSum()`),可以检查序言的最后步骤(如下所示)。
76c94579 56 push esi 76c9457a 8b4c2410 mov ecx,dword ptr [esp+10h] 76c9457e 8b74240c mov esi,dword ptr [esp+0Ch] 76c94582 8b442408 mov eax,dword ptr [esp+8] 76c94586 d1e1 shl ecx,1
正在发生以下操作:
- ESI 被保留
- ESI = 基地址
- ECX = 文件大小/2 (0x7080)
- EAX = 0
然后,通过 shl ecx, 1 恢复原始文件大小。
76c94586 d1e1 shl ecx,1
值得注意的是缺少 push ebp 和 mov ebp, esp。这个序列通常由编译器生成,因为直接操作堆栈指针(ESP)是不好的行为。这也可能表示编译器生成了帧指针省略(FPO)。
此时,很明显 ESI 将用作指向被处理值的指针,而 ECX 将用作计数器。EAX 的作用将在下面揭示。
关于序言的最后一点说明。下面的代码通过 je 指令测试文件大小是否为 0。如果文件大小为 0,则整个循环设置和生成循环的执行路径都会被绕过。这应该向读者暗示,在 0x76C946E7 处正在准备各种返回值。
...
76c94582 8b442408 mov eax,dword ptr [esp+8]
76c94586 d1e1 shl ecx,1
76c94588 0f8459010000 je imagehlp!ChkSum+0x16e (76c946e7)
生成循环
要在主生成循环上设置断点,请执行 bp imagehlp!ChkSum+0xE8(或地址 0x76C94661)。主生成循环如下所示。该循环一次处理 0x80 字节(0x20 个双字)的程序数据。当一个双字被处理时,它会被加到一个运行总和 EAX 中(adc 汇编指令)。通过使用 adc 指令,该算法实现了一个 33 位的寄存器,而不是 32 位。
76c94661 0306 add eax,dword ptr [esi] 76c94663 134604 adc eax,dword ptr [esi+4] 76c94666 134608 adc eax,dword ptr [esi+8] ... 76c946b7 134674 adc eax,dword ptr [esi+74h] 76c946ba 134678 adc eax,dword ptr [esi+78h] 76c946bd 13467c adc eax,dword ptr [esi+7Ch] 76c946c0 83d000 adc eax,0
最后一条指令(adc eax, 0)只是调整标志寄存器。主循环之后的指令是簿记工作(如下所示)。
76c946c3 81c680000000 add esi,80h
76c946c9 81e980000000 sub ecx,80h
76c946cf 7590 jne imagehlp!ChkSum+0xe8 (76c94661)
ESI(文件映射指针)递增,ECX(计数器)递减。在执行 sub ecx, 80h 之后,控制权要么传递到 0x76C94661 的循环顶部(如果 ECX 不等于 0),要么进入其他代码(ECX = 0)。因此读者可以推断出,部分尾声代码位于 0x76C946D1。
对于 PEChecksum.exe,文件大小为 0xE100。循环以 0x80 的块进行操作,因此循环将执行 0x1C2 次或 450 次迭代。这就引出了一个问题:如果文件大小不是 0x80 的倍数怎么办?这个问题在循环设置中得到了回答。
生成循环设置
循环设置代码从 0x76C9458E 开始,到 0x76c94660(主生成循环前一个字节)结束。考虑第一个测试:
76c9458e f7c602000000 test esi,2
如果 [br=1] (branch = true),跳转到下一个测试。
76c94594 7410 je imagehlp!ChkSum+0x2d (76c945a6)
否则,执行一些加法操作,就像在主生成循环中一样——但不是一个完整的循环。
76c94596 2bd2 sub edx,edx ; Zero EDX 76c94598 668b16 mov dx,word ptr [esi] ; set up for addition 76c9459b 03c2 add eax,edx ; Add 1 WORD to eax (2 = sizeof(WORD) ) 76c9459d 83d000 adc eax,0 ; adjust EFLAG register 76c945a0 83c602 add esi,2 ; adjust ESI
然后进入下一个测试(test esi, 8),地址在 76c945b3。
76c945b3 f7c108000000 test ecx,8
如果 [br=1] (branch = true),跳转到下一个测试。否则:
76c945b9 7414 je imagehlp!ChkSum+0x56 (76c945cf) 76c945bb 0306 add eax,dword ptr [esi] 76c945bd 134604 adc eax,dword ptr [esi+4] 76c945c0 83d000 adc eax,0 76c945c3 83c608 add esi,8 76c945c6 83e908 sub ecx,8
执行代码的结果是进行一次加法,调整指针,并递减计数器。从上面可以观察到 `sizeof` (2 个双字) = 8。接下来是另一个测试/分支。感兴趣的代码是:
76c945cf f7c110000000 test ecx,10h 76c945d5 741a je imagehlp!ChkSum+0x78 (76c945f1) 76c945d7 0306 add eax,dword ptr [esi] 76c945d9 134604 adc eax,dword ptr [esi+4] 76c945dc 134608 adc eax,dword ptr [esi+8] 76c945df 13460c adc eax,dword ptr [esi+0Ch] 76c945e2 83d000 adc eax,0 76c945e5 83c60C add esi,0Ch 76c945e8 83e90C sub ecx,0Ch
同样,加法、调整指针、递减计数器。从上面可以观察到 `sizeof` (4 个双字) = 16 = 10h。遵循这个模式(在主循环之前),直到并包括 test ecx,40h,将导致 ECX % 0x80 = 0(其中 % 是通常的模约简)。此时,主生成循环开始执行,待处理的剩余文件大小是 0x80 的倍数。
Ken Johnson 指出,循环序言可能是一种通过调整对齐来优化的方式。作者也曾怀疑这一点,但无法以明确的方式陈述其原因。最后,Parch Andri 提出这类似于使用达夫设备进行循环展开。这段代码可能是手写的,而不是由编译器生成的。
结语
如前所述,尾声部分将被省略。然而,有一个观察值得注意,以支持作者关于这是 RFC 1071 改编的观点:将 32 位值折叠成 16 位值。指令序列如下。请注意,折叠发生了两次,以防第一次折叠期间产生了进位。
76c946e7 8bd0 mov edx,eax ; EDX = EAX 76c946e9 c1ea10 shr edx,10h ; EDX = EDX >> 16 EDX is high order 76c946ec 25ffff0000 and eax,0FFFFh ; EAX = EAX & 0xFFFF EAX is low order 76c946f1 03c2 add eax,edx ; EAX = EAX + EDX High Order Folded into Low Order 76c946f3 8bd0 mov edx,eax ; EDX = EAX 76c946f5 c1ea10 shr edx,10h ; EDX = EDX >> 16 EDX is high order 76c946f8 03c2 add eax,edx ; EAX = EAX + EDX High Order Folded into Low Order 76c946fa 25ffff0000 and eax,0FFFFh ; EAX = EAX & 0xFFFF EAX is low order 16 bits
时间戳的影响
每次编译可执行文件时,都会在可执行文件中嵌入一个时间戳。如果读者在源文件中添加空格(以强制重新编译),校验和将会不同。时间戳的影响也存在于目标文件中。
实际攻击
作者从来不是一个回避实现的人。为此,本文提出了一种对校验和的攻击。最初,作者希望展示 CRC 作为完整性验证工具的弱点,主张软件作者应该采用程序集代码签名。文章原计划执行以下操作:
- 修改记事本,添加相对于光标位置的行号(基于 Razzia 的工作)
- 确定新的校验和
- 使用《逆向 CRC - 理论与实践》来确定要修改的噪声字节,以使修改后的 EXE 具有与原始 EXE 相同的校验和。
有趣的是,W32/Kriz 病毒在 1999 年执行了同样的操作来掩盖其对受感染文件的篡改。基于微软的方案,所描述的攻击显然是小题大做。相反,本文将演示一种更易于理解的攻击,该攻击表明在 `DWORD` 边界上更改字节不会被检测到。
攻击将从无害的字符串“This program cannot be run in DOS mode.”开始。该字符串以'$'结尾,因为它是一个汇编语言字符串(与 C 字符串的 `NULL` 终止符相对)。在'$'之后,会遇到一串 `NULL`,作为填充存根程序剩余部分的噪声字节。
所以,我们想要用另一个 `DWORD` 交换一个 `DWORD`。如果我们将字符串(“This program ...”)的尾部想象成数组中的字符表示,它会是这样的:
..., '$', 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ...
执行 DWORD 交换后,可执行文件将被修改如下:
..., 0x00, 0x00, 0x00, 0x00, '$', 0x00, 0x00, 0x00, ...
使用 IDM 的 UltraCompare 查看文件差异,揭示了在 0x78 处的二进制差异。0x7C 处的差异不在视图中。
最后,是修改后的 notepad.exe 的校验和。它的校验和也是 0x00014F7F。
原始和修改后的记事本程序都提供下载。对于那些质疑文件是否真的被修改的人,只需要看看文件的不同 MD5 哈希值(回想一下,两个文件具有相同的校验和)。
- 原始记事本:388B8FBC36A8558587AFC90FB23A3B99
- 修改后的记事本:76CB8109191B87E769371B6340A0B257
由于 CRC-32 也是我们感兴趣的,以下是 CRC32.exe 的输出
- 原始记事本:C1227F19
- 修改后的记事本:1652D843
RFC 1071 实现
RFC 1071 的作者们提供了计算互联网校验和的 32 位实现,如下所示(摘自第 4.1 节):
/* Compute Internet Checksum for "count" bytes beginning at location "addr". */ register long sum = 0; while( count > 1 ) { /* This is the inner loop */ sum += * (unsigned short) addr++; count -= 2; } /* Add left-over byte, if any */ if( count > 0 ) sum += * (unsigned char *) addr; /* Fold 32-bit sum to 16 bits */ while (sum>>16) sum = (sum & 0xffff) + (sum >> 16); checksum = ~sum;
摘要
微软 PE 校验和算法与互联网校验和算法的相似程度,留给读者自行判断。对作者来说,这是显而易见的。在作者看来,微软误用了加法校验和。微软没有考虑到 OSI 模型或 TCP/IP 协议栈上层中发生的额外错误检测。在微软对校验和的应用中,校验和是唯一的检测层。
作者更担心的是,校验和是加载内核驱动程序或受信任服务与操作系统之间的最后保障之一。令人失望的是,微软选择了速度,而不是稍微更健壮的 CRC-32。更糟糕的是,微软的 PE 格式在 MS-DOS 2.0 兼容 EXE 头之后留下了大量可供篡改的空间。也许微软未来会通过代码签名和下一代安全计算基础来弥补这些漏洞。
在撰写本文时,微软仅要求对基于 x64 的 Vista 内核模式驱动程序进行代码签名。欢迎读者阅读内核模式代码签名演练。
为了公平起见,作者想提供 David Delaune 的一些不同观点:
...从历史角度分析,用于 PE 校验和的算法是薄弱的……它完全按照其设计初衷工作;即简单的文件完整性和内存故障检测。在 Win 3.1 开发期间,微软显然并不关心安全问题。此外,当时的计算能力要低得多。
然而,我不认为微软应该投入任何精力来升级这个算法。而且我相信他们也得出了同样的结论,因为在 PE32+(魔数 0x20b)中,校验和字段的大小是相同的。我个人认为代码签名是微软正确的方向。尽管它目前的实现存在一些缺陷,但这超出了这个简单评论的范围。
最后,这只是作者的观点,并且在分析过程中显然可能会有误解。请随时纠正任何错误。
致谢
- A. Brooke Stephens 博士,他为我打下了密码学基础
- Ken Johnson,微软 MVP
- Peter Bell,Crypto++ 邮件列表
- Parch Andri,Crypto++ 邮件列表
修订
- 2008年8月3日 文章和链接清理
- 2007年11月5日 添加了对《Grafting Compiled Code》的引用
- 2007年10月29日 添加了对 Szor 书籍的引用
- 2007年10月29日 添加了对 W32/Kriz 病毒的引用
- 2007年10月20日 添加了对 gdb、objdump 和 dasm 的引用
- 2007年10月13日 添加了跨平台分析
- 2007年9月22日 语法修正
- 2007年8月24日 添加了对 David Delaune 评论的引用
- 2007年8月24日 添加了对《Windows 调试器:WinDbg 教程》的引用
- 2007年8月22日 补充了关于加法校验和的说明
- 2007年7月22日 添加了对《内核模式代码签名演练》的引用
- 2007年7月21日 添加了对可能的循环设置对齐优化的引用
- 2007年7月21日 添加了对 Microsoft Outlook CRC 计算的引用
- 2005年7月6日 添加了 WinDbg 命令 x
- 2007年7月1日 添加了关于 FPO 的说明
- 2007年6月30日 添加了校验和八位字节分解图
- 2007年6月30日 添加了关于 ADC 指令(33位加法)的说明
- 2007年6月26日 采纳了 Ken Johnson 的评论
- 2007年6月25日 添加了实际攻击
- 2007年6月25日 添加了 CRC32 下载
- 2007年6月24日 初次发布
下载次数
- PE 校验和可执行文件 - 108 KB
- PE 校验和源代码 - 28 KB
- CRC32 源代码 - 5.7 KB
- CRC32 已编译命令行程序 - 93 KB
校验和
- PEChecksum.zip
MD5: 830804E7427612285FC08997C127E68C
SHA-1: 582FC2E3194B34DF9F8B49BCDB70F40D59100E28 - PEChecksumSource.zip
MD5: 521D0B260377D5835BA3F6C715D040DC
SHA-1: EA4F6B60CB94CE3800C6F2510B0318919600FBBA - NotepadAltered.zip
MD5: B1D0F0668408E1C8946DDB38EFFE5CCE
SHA-1: 90F52F3014828DCE0FB6DAAF6D6F3CA3C751953B - NotepadOriginal.zip
MD5: E4FD9A097723BBC1702DBA70DDE0F967
SHA-1: 74809A95DF40AC24B610A92089B3A2FA4006235D - CRC32.zip
MD5: 5BCA445EF5ED629A6DAE2727A826E0AB
SHA-1: 4F0990D4764BBB1F493772E96CD00F7EAD6862FE - CRC32Source.zip
MD5: F39CA9B45041B2730A502DAF296792F8
SHA-1: 517DB8DE335B16A4132334A482656CA37EB4D37B