C# EXE 自动检验功能

最近比较恼火,因为测试机中毒了,除了在 Idle 状态下 CPU 占用率不断升高还有就是在测试 MS 的时候会遇到稀奇古怪的问题。比我更惨的是有同事收到了 IT 的警告邮件…….

因此,我有一个问题:能否设计出一个自我检查的程序,保证没有被篡改过?研究一番之后就开始动手编写。从原理上说,使用 WinPE头部没有用到的位置写入校验值,然后在代码中加入校验的内容,每次执行时先校验即可得知是否篡改。

因此,有2个程序,第一个是给被校验EXE 添加校验值的程序,另外一个是被校验的程序。

1.给 EXE 添加校验值的程序,选择在 DOS MZ 头的e_res2 位置添加 MD5值:

// DOS MZ头,大小为64个字节
typedef struct _IMAGE_DOS_HEADER {     
    WORD   e_magic;                     // EXE标志,“MZ”(有用,解析时作为是否是PE文件的第一个标志)
    WORD   e_cblp;                      // Bytes on last page of file
    WORD   e_cp;                        // Pages in file
    WORD   e_crlc;                      // Relocations
    WORD   e_cparhdr;                   // Size of header in paragraphs
    WORD   e_minalloc;                  // Minimum extra paragraphs needed
    WORD   e_maxalloc;                  // Maximum extra paragraphs needed
    WORD   e_ss;                        // Initial (relative) SS value
    WORD   e_sp;                        // Initial SP value
    WORD   e_csum;                      // Checksum
    WORD   e_ip;                        // Initial IP value
    WORD   e_cs;                        // Initial (relative) CS value
    WORD   e_lfarlc;                    // File address of relocation table
    WORD   e_ovno;                      // Overlay number
    WORD   e_res[4];                    // Reserved words
    WORD   e_oemid;                     // OEM identifier (for e_oeminfo)
    WORD   e_oeminfo;                   // OEM information; e_oemid specific
    WORD   e_res2[10];                  // Reserved words
    LONG   e_lfanew;                    // 非常重要,操作系统通过它找到NT头,NT头相对于文件的偏移地址
  } IMAGE_DOS_HEADER, *PIMAGE_DOS_HEADER; 【参考1】

代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace ConsoleApplication1
{
    class Program
    {
        public const int WINPEHEADERSize = 0x40;
        public const int RVSOffset = 0x28;
        private static byte[] GetMD5(byte[] message)
        {
            MD5 hashString = new MD5CryptoServiceProvider();
            return hashString.ComputeHash(message);
        }

        static void Main(string[] args)
        {
            byte[] TotalFile;
            byte[] FileNoHeader;
            byte[] MD5;

            if (args.Length != 1) {
                Console.WriteLine("Please input a file name");
                Console.ReadKey();
                Environment.Exit(0);
            }

            TotalFile = File.ReadAllBytes(args[0]);
            FileNoHeader = new byte[TotalFile.Length - WINPEHEADERSize];

            Buffer.BlockCopy(TotalFile, WINPEHEADERSize, FileNoHeader, 0, FileNoHeader.Length);

            MD5 = GetMD5(FileNoHeader);

            for (int i = 0; i < MD5.Length; i++)
            {
                TotalFile[i + RVSOffset] = MD5[i];
            }
            File.WriteAllBytes("E"+args[0], TotalFile);
            File.WriteAllBytes("Ex" + args[0], FileNoHeader); 
            Console.WriteLine("Complete. New file:"+ " E" + args[0]);
            Console.ReadKey();
        }
    }
}

程序接收一个文件名作为参数,然后将这个文件内容读取到TotalFile[] 中,去掉DOS 文件头后复制到FileNoHeader[] 中,之后计算这个数组的 MD5值,再将MD5放在前面提到的  e_res2 偏移处,将新生成的 EXE 写入  “E”+原文件名 的文件中。

2.带有自校验功能的代码

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.IO;
using System.Security.Cryptography;

namespace ConsoleApplication3
{
    class Program
    {
        public const int WINPEHEADERSize = 0x40;
        public const int RVSOffset = 0x28;
        private static byte[] GetMD5(byte[] message)
        {
            MD5 hashString = new MD5CryptoServiceProvider();
            return hashString.ComputeHash(message);
        }

        static void Main(string[] args)
        {
            byte[] TotalFile;
            byte[] FileNoHeader;
            byte[] MD5;

            TotalFile = File.ReadAllBytes(System.Reflection.Assembly.GetExecutingAssembly().Location);
            FileNoHeader = new byte[TotalFile.Length - WINPEHEADERSize];

            Buffer.BlockCopy(TotalFile, WINPEHEADERSize, FileNoHeader, 0, FileNoHeader.Length);

            MD5 = GetMD5(FileNoHeader);

            int i = 0;
            while (i < MD5.Length) {
                if (MD5[i]!= TotalFile[RVSOffset+i]) {
                    Console.WriteLine("File is modified!");
                    Environment.Exit(0);
                }
                i++;
            }
            Console.WriteLine("File is OK!");
        }
    }
}

大部分和前面的程序类似,不同在于计算FileNoHeader的 MD5之后有一个比较过程,如果不一样就提示之后退出。

下面是一个测试的过程。首先用工具给 TestOK 添加签名,加入校验头的文件是 eTestOK;直接运行 TestOK, 因为头部并没有 MD5校验值,所以显示校验失败;运行 eTestOK 可以通过验证;之后用一个十六进制工具修改任意一个 BIT 都会有报错。

测试修改之前和修改之后

参考:

1. https://blog.csdn.net/weixin_30878501/article/details/99825394 WinPE基础知识之头部

发表评论

电子邮件地址不会被公开。 必填项已用*标注