65.9K
CodeProject 正在变化。 阅读更多。
Home

应用程序清理器 (NSA Killer)

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.88/5 (23投票s)

2015年4月16日

CPOL

7分钟阅读

viewsIcon

28398

downloadIcon

734

如何使用 dot.net Filestream 和 Fileinfo 编辑 .exe 或 .dll 文件

引言

Application Cleaner 是一个简单的概念,它会在程序代码/数据上执行搜索和替换操作,以删除程序代码中的任何间谍软件 URL,从而阻止诸如网页浏览器之类的程序“呼叫回家”。该程序使用 VS2010 中的 C# 编写,并广泛使用 System.IO.Filestream System.IO.Fileinfo 类来打开、读取然后编辑程序代码或数据。

文件扫描器和窗体的多线程处理

该程序的工作方式是启动一个新线程,该线程调用下面显示的用于遍历文件夹目录树的递归函数,并使用 Directoryinfo 对象调用“ScanFolderFiles”函数来执行处理。

private static void ScanFolders(DirectoryInfo DInfo)
        {//Recursive function used to scan sub-folders if "RootFolderOnly" is not set to true
            ScanFolderFiles(DInfo);
            if (RootFolderOnly) return;//Nope we don't need to index sub-folders this time
            foreach (DirectoryInfo SubDIofo in DInfo.GetDirectories())
            {
                ScanFolders(SubDIofo);
            }
        }

  private static void ScanFolderFiles(DirectoryInfo DInfo)
        {//Scan for files and then call ReadFile to index links
            foreach (FileInfo FInfo in DInfo.GetFiles())
            {
                if (!Running) return;
                while (Paused) { Thread.Sleep(2000); }
                if (!FInfo.Name.EndsWith(".backup"))
                {
                    ReadFile(FInfo);  //Might add a load more links to be displayed
                    DisplayNewLinks(); //Now display any new links we found
                }
            }
           );//We have done with the folder
        }

用户可以选择通过将 static bool Running Pause 设置为 true false 来暂停或终止扫描器的线程。

ReadFile 使用文件流打开文件,将整个文件读取到 byte[] 数组中,然后将该数组分割成 10k 大小的字节块,以便搜索 URL 并将其添加到链接字典中,同时将 Links.Displayed 值设置为 false ,然后调用 DisplayNewLinks()

private static void DisplayNewLinks()
        {//Any link in our dictionary of links that have not been displayed is saved as a string array
            foreach (Link L in Scanner.Links.Values)
            {
                if (!L.Displayed)
                {//Show any new links in the list-view
                    L.Displayed = true;//Only show the link once
                    AddNewFind(L.Finfo.Name, L.LinkText, L.Find, 
                    L.ChunkCount,L.Start , L.ImageIndex, L.Finfo.Directory.FullName);
                }
            }
        }

请注意,以上所有工作都是在扫描器自己的线程中完成的,并且“AddNewFind”函数在将传入的数据(以 '¬' 分隔的值)附加到 string 的末尾之前,会锁定一个“NewFindstring ,以便窗体计时器稍后可以调用 Scanner.GetListViewItems() ,该函数再次锁定“NewFindstring ,然后将该 string 转换为 ListViewItems ,最后将它们附加到窗体的主 ListView

        public static Dictionary<int, ListViewItem> GetListViewItems()
        {//Called on the UI thread and returns new data to be displayed in the list-view
            Dictionary<int, ListViewItem> LVItems = new Dictionary<int, ListViewItem>();
            string[] Items = null;
            lock (NewFind)//We lock it here so we don't get a threading error.
            {
                Items = NewFind.Replace(Environment.NewLine, 
                "~").Split('~');//Take a quick copy 
                NewFind = "";
                MessageCount = 0;
            }
            foreach (string Item in Items)
            {//Split the string and covert the data to list-view-items ready to add to the list-view
                string[] Data = Item.Split('¬');
                if (Data.Length > 2)
                {
                    int Num = 0;
                    int.TryParse(Data[0], out Num);
                    ListViewItem LVI = new ListViewItem(Data);//Conver an array to values
                    if (Num > 0)
                        LVItems.Add(Num, LVI);
                }
            }
            return LVItems;
        }

读取文件和处理数据非常占用 CPU。如果在上述代码中没有 Thread.Sleeps() ,窗体计时器将几乎没有机会触发,窗体将会冻结。因此,根据有多少链接正在等待显示,主扫描器线程会时不时地休眠,以给 UI 一个追赶的机会。这部分多线程处理到此结束。

输出显示和列表视图结果排序

Application Cleaner 在列表视图中使用颜色编码的标志来表示找到的 URL 的类型,以便用户可以手动搜索和替换,或者双击列表中的项目进行自动替换。但我很快发现,普通的网页浏览器在其程序代码、DLL 和数据中包含约 5000 个 URL,其中一个知名的网页浏览器甚至包含超过 20,000 个 URL(我稍后会再谈这个)。因此,当务之急是对列表视图结果进行排序。

添加到窗体以对列表视图进行排序的代码如下所示,您应该从上图注意到,列 [0]、[4] 和 [5] 都是数值,并在下面的代码中使用。

 private SortOrder OrderBy = SortOrder.Ascending;
        private void LVScanner_ColumnClick(object sender, ColumnClickEventArgs e)
        {//Sort the list-view but note that this will slow the program down when new rows are added
            bool IsNumber = false;
            if (OrderBy == SortOrder.Ascending) OrderBy = SortOrder.Descending; 
            else OrderBy = SortOrder.Ascending;
            int Col = int.Parse(e.Column.ToString());
            if (Col > 6) return;//We don't have a column number 7
            if (Col == 0 || Col == 4 || Col == 5) IsNumber = true;//We need to tell our 
            //"ListViewItemComparer" to sort the data as a number and not a string
            this.LVScanner.ListViewItemSorter = new ListViewItemComparer
            (e.Column, OrderBy, IsNumber);//The class was easy to write and is worth a look
            LVScanner.Sort();
        }

我们的 ListViewItemComparer 类的代码如下所示,它可以进行扩展,以预扫描某些列数据来决定该列是否为数值,但这需要时间和代码,所以我决定暂时将这些值硬编码到代码中。

using System;
using System.Collections;
using System.Windows.Forms;
using System.Text;
public class ListViewItemComparer : IComparer
{//This class is used so that the listview can be sorted by clicking on the columns 
    private int col;
    private SortOrder order;
    private bool IsNumber = false;
    public ListViewItemComparer()
    {//Constructor
        col = 0;
        order = SortOrder.Ascending;
    }
    public ListViewItemComparer(int column, SortOrder order, bool isNumber)
    {//Constructor
        col = column;
        this.order = order;
        this.IsNumber = isNumber;
    }
    public int SafeGetInt(string Text)
    {//This also sorts ip-addresses based on the first digit or double numbers
        Text = Text.Trim();
        int Value = 0;
        int End = Text.IndexOf(".");
        if (End > -1) Text = Text.Substring(0, End);
        End = Text.IndexOf(" ");
        if (End > -1) Text = Text.Substring(0, End).Trim();
        End = Text.IndexOf("/");
        if (End > -1) Text = Text.Substring(0, End);
        int.TryParse(Text, out Value);
        return Value;
    }
     public int CompareNumber(object x, object y)
    {
        int returnVal = -1;
        int IntX = SafeGetInt(((ListViewItem)x).SubItems[col].Text);
        int IntY = SafeGetInt(((ListViewItem)y).SubItems[col].Text);
        if (IntX > IntY)
            returnVal = 1;
        if (order == SortOrder.Descending)
            // Invert the value returned by String.Compare.
            returnVal *= -1;
        return returnVal;
    }
    public int Compare(object x, object y)
    {
        int returnVal = -1;
        if (this.IsNumber) return CompareNumber(x, y);
        returnVal = String.Compare(((ListViewItem)x).SubItems[col].Text,
                                ((ListViewItem)y).SubItems[col].Text);
        // Determine whether the sort order is descending.
        if (order == SortOrder.Descending)
            // Invert the value returned by String.Compare.
            returnVal *= -1;
        return returnVal;
    }
}

浏览器中隐藏了 20,000 个 URL,你认真的吗!

是的,而且这还没有算上缓存历史记录或 Cookie。这个数字如此之高的原因之一是,当一个使用嵌入式 Photoshop 图像的程序被编译时,每个图像都会包含一个指向 ns.adobe.com 的 URL,然后您会到处看到 ww3.com XML 架构,以及 Verisign & Co 的 SSL 证书所需的数百个 URL,但即使这样,仍然剩下很多。

面对如此多的数据,有必要从结果中删除这种常见类型的数据,以便用户能够理清头绪。我的解决方法是首先添加隐藏 SSL 证书结果的选项,然后利用用户可以加载的配置文件来过滤特定类型数据的结果。

正如您在上面的图片中看到的,我们的数据现在按状态排序。如果您仔细看前两行,您会发现 Application Cleaner 还可以搜索程序代码和数据中看起来像 IP 地址的内容,但有时会错误地将四位数的版本号误认为 IP 地址。

扫描嵌入的 IP 地址是一个很有用的功能,因为病毒经常使用这些地址“呼叫回家”,而这个程序使它们易于发现。但许多浏览器也将 Google 的 DNS 服务器 IP 地址 8.8.8.8 硬编码到浏览器中,这样这些浏览器就可以绕过您的本地安全设置,如果它们被防火墙中的 URL 阻止,或者 DNS 服务器/病毒拦截器试图阻止这些请求。

该项目使用配置文件来配置扫描器以索引特定类型的数据,并包含一个要自动搜索和替换的值列表。用于加载配置文件的函数如下所示,其工作方式有点像旧的 Windows .ini 文件,但有一些不同。对我来说,拥有 400,000 个文件夹的 Windows 注册表正在变得有点拥挤,而且太多的程序可以读取其内容来生成指纹。

      public static string LoadSettingFromFile(string Key, string Default, string Profile)
      {//Load a string from the file system if we can find the file or 
      // just create it and if the string key is not in the file than 
       //add it with a default value
            string FileName = Environment.CurrentDirectory + "\\" + Profile;
            if (!File.Exists(FileName))
            {//We need to add a new profile file
                File.WriteAllText(FileName, Key + " " + Default + Environment.NewLine);
                return Default;
            }
            string[] Lines = File.ReadAllLines(FileName);
            foreach (string Line in Lines)
            {//Read all the lines in the file to find our key
                if (Line.ToLower().Trim().StartsWith(Key.ToLower()))
                    return Line.ChopOffBefore(Key).Trim();//Yes found the value so return it
            }
            StreamWriter SW = File.AppendText(FileName);//We need to add the new key and default to the profile file
            SW.WriteLine(Key + " " + Default);
            SW.Close();
            return Default;
      }

目标与成就

我使用代理服务器和 DNS 服务器来阻止我们局域网中的间谍软件,我厌倦了看到代理服务器日志在夜间填满,而实际上没有人浏览。我还知道,即使是 Microsoft 也绕过了 Windows 代理服务器设置,试图通过加密的 SSL 流量“呼叫回家”,因为我的防火墙作为最后的防线,阻止了这些恶意请求外出。于是我决定通过编辑那些试图使用后门的程序的机器代码来从源头解决这个问题。

现在,我只需要大约半个小时就可以用这个程序修改大多数浏览器,使它们无法上传超级 Cookie。这些 Cookie 在您必须运行那些使用下载器安装浏览器的“安装”程序时会被安装,而这些浏览器则通过窃取并出售您的私人数据给最高出价者来盈利。

改进

这是我第二次尝试这个小程序了。第一个版本的错误是没有索引程序内容,而是选择在读取文件时立即进行快速搜索和替换。但当每次进行搜索和替换时都需要重新扫描数据时,这被证明有点慢。现在这个问题已经解决了,但如果能让它变得更快一些就更好了,因为扫描一个项目的索引可能需要五分钟。

在机器代码段中替换 URL 字符串很容易,只要被替换文本的长度保持不变,就可以确保代码中的跳转保持不变。出于这个原因,程序将拒绝任何尝试将“SpywareHome.com”替换为“abc,com”的尝试。但对于 XML 类型的数据则不是这样,所以找到一个解决方案会很好,但需要考虑到必须保留文件的最后修改日期,因为有些程序会检查这一点。

正如您所见,我在文档方面非常糟糕,程序的帮助文本需要由任何人从头开始重写。但您会发现该项目的完整源代码注释得很好,您可以点击页面顶部的链接下载一份副本,并将其据为己有。

尽情享用!
Dr Gadgit

© . All rights reserved.