相信在黑客界,PEID这AI工具肯定是家喻户晓,但是它是怎么获取到exe的信息的呢?在以前的危险漫步博客,我们已经讲解了exe的基本文件格式,那么,我们今天就来看看PEID是如何实现的。我们首先看下PEID的主界面,第一条,文件路径,这个没什么难的,很容易搞定。下面是入口点,EP区段,文件偏移量,首字节,链接器版本,子系统。那么这些信息就是主界面,至于下面的查壳结果,那么下面的内容是查壳的,就暂时不做了。先看我们上面的内容。EP区段后面还有一个按钮,可以查看区段的各种各样的问题。首字节后面按钮点了会出现反汇编信息,这里我们也不做了,再下面子系统后面的按钮,点开之后是各种各样的EXE信息,这个我们要实现。
好了,大致我们要实现的功能就是这些啦,拖出VS2008,我们来看实现过程。首先做我们的界面,可以根据我的样子拖好界面。最好更改好控件ID,增加可读性。接下来,我们可以开始写代码了,首先我们要使用这个文件,肯定要打开文件。这里相信大家都想到,很多软件打开文件的时候都有这样的一个选择框。这种框其实是微软已经封装好直接可以用的,称之为通用对话框。我们直接来看怎么用吧,双击我们拖出来的这个打开文件的按钮(就是按钮上内容是三个点的),对这个按钮的Click事件处理函数进行编辑。好,打开之后,我们可以看到系统自动为我们生成了方法可以直接调用,那么我们就在里面添加代码。第一件事肯定就是弹出公共对话框,那么弹出公共对话框需要哪些代码呢?
首先,创建一个公共文件对话框对象(公共对话框不止一个,还有颜色对话框,字体对话框,查找替换对话框等)。文件对话框类是CFileDialog,那么首先我们就要创建这个对象。
注意创建的时候就要根据构造函数进行初始化,我们看一下CFileDialog的参数,在MSDNl:可以找到如下定义:
1.bOpenFileDialog如果为TURE则表示创建“打开文件”对话框,FALSE表示创建“另存为”对话框。
2.lpszD。fExt默认文件扩展名
3.LpszFileName默认文件名
4.dwFlags文件对话框样式
5.lpszFilter过滤的文件类型
6.pParentWnd指定父窗口
7.dwSize OPENFILENAME结构的太小(之所以要设定这个是因为操作系统版本不一样这个结构也会有变化,不过设为0的话,MFC会自动调整这个值,我们不用管)。
8.b\fistaStyle这个是Vista风格的开关,只有win7和vista下面有用,其实我感觉差不多,只是左边有点区别,这个就不贴图了,感兴趣的自己试试吧。
好了,那么根据我们的解释呢,我们基本上创建代码就出来了。
上面解释得很清楚了,这里不解释。只有样式这里说一下,OFN HIDEREADONLY的作用就是隐藏一个内容为以只读方式打开的复选框,其他没啥。创建后用DoModel方法可以显示窗口,而用GetPathName方法可以获取到路径名称,这里不用我说该怎么做了吧。对了,GetPathName返回值是CString类型哦,我们可以很方便地进行操作哦。
往下我们就要判断文件是否是PE文件了。先新建一个函数,叫做CheckPeFile,然后在里面读取文件。在读取文件的时候,大家注意一下,常规操作是OpenFile,ReadFile,然后进行读写操作。但是因为我项目编码使用的是Unicode,而正好OpenFile没有对应的Unicode版本的函数,所以这里我就用CreateFile函数来代替。至于文件的读取,我觉得没多少可以说的,就是CreateFile的参数比较多,比较麻烦,我这里简单的说说。
1.lpFileName:文件路径,这个不可少,不可为空。
2.dwDesiredAccess:所需要的权限,我们这里只需要读取,所以用FILE_SHARE READ。
3.dwShareMode:这个是文件共享模式,就是在程序打开这个文件之后是否允许其他程序读写这个文件。我们这里随便即可,我设置了FILE- SHAREREAD。
4.lpSecurityAttributes:安全属性结构指针,这里我就不多说了,直接NULL。
5.dwCreationDisposition:这个是文件创建设置。
1>CREATE- ALWAYS:总是创建新文件,如果文件存在,则覆盖该文件并清除所有已经存在的属性。
2>CREATE_NEW:创建新文件,如果文件存在,则执行失败。
3>OPEN. ALWAYS:总是打开文件,如果文件不存在,则失败。
4>OPEN_EXISTING:打开已经存在的文件,如果文件不存在,则执行失败。
5>TRUNCATE—EXISTING:将文件清空为0字节,如果文件不存在,则执行失败,注意这个必须要有写的权限才能执行。
6.dwFlagsAndAttributes:这个我们不需要管太多,毕竟不需要这方面的操作,我们直接设置为FILE_ ATTRIBUTE_NORMAL。
7.hTemplateFile:临时文件句柄,这里直接NULLe
好了,关于文件读写我就说这么多,什么?其他函数不会?自己MSDN去!
在执行ReadFile之后,我们可以得到缓冲区的地址。这个其实就是被读取文件在内存中首地址的位置。而根据上一期的PE文件讲解,我们知道PE文件一开头必然是DOS头,而DOS头的标志就是IMAGE_DOS HEADER下面的e_magic的4D5A这个值。我们只要判断e_magic是否等于4D5A,并且,看DOS头中e lfanew的PE文件头指向。如果e—lfanew指向正确的PE头的地址,那么,我们基本就可以认定这个文件是PE支件。具体如何实现呢,有的人可能会一个一个字节地读取文件,比如,判断buf指针所对应的值是否为4D,判断buf+l所对应的指针是否为5A,其实没这个必要。因为缓冲区是文件首地址,而首地址又应该是一个IMAGE__ DOS—HEADER结构,那么我们只要把这个地址强行转换为PIMAGE__ DOS—HEADER类型,就可以自动匹配值。同样的道理,我们只要把(文件首地址+PIMAGE__ DOS HEADER->e_lfanew)这个地址转换为PIMAGE_NT—HEADER就可以得到一个PE头的指针。那么有了这两个结构的指针,判断也就是一下就搞定的了吧?
防止有人还不清楚,再重新说下,别嫌唠叨,DOS头的标志是”MZ”,而PE头的标志是”PE”,至于这么说还反应不过来的,你还是再看看以前危险漫步的博客吧。
顺带说一下,也有的人采用的不是这种方法来读取,而是用的创建文件映射的方式,那个方法更好些,不过稍微麻烦一点,感兴趣的可以去看看。
接下来就是各种信息的读取了。首先是人口点,我们回想下在上个月的内容中,哪个结构记录了入口点的位置呢?没错,就是IMAGEOPTIONAL—HEADER32这个结构。咳咳,还是一口气说完吧,EP区段的话,我们要去读节表,是后面的内容,文件偏移量,这个也是需要计算的,我们后面再说算法。我们这里先把能够一下读取的内容说下,第一个人口点肯定可以,还有就是链接器版本,子系统,这些都是我们能够立刻获取到的,首先把我们的入口点拿出来,至于取出这些值我相信大家都没什么问题,最后说下格式化字符串。格式化字符串,这个用CString的Format方法即可。注意这里有一点要说,我们在格式化的时候,一般会写成这样Format("Ox%x”,valu e);来得到16进制的字符串,但是这样会存在一个问题,当16进制数少于8位时看着就有点不伦不类,因为我们都看惯了平时的地址都是8位的1 6进制数,这里我们可以把Format写成这样:Format(”OX?/008x”,value),这样当得到的16进制数不足8位时会在前面补零填充到8位。
相对虚拟地址,是相对与镜像基址来定的,也就是如果程序装入地址为00400000,而人口点RVA为1000,那么,VA就等于00401000。那么,还有一个文件偏移,文件偏移就要算了,因为没有属性可以直接获取,我们这里就只能通过其他的地方来算,具体在什么地方呢?那就是节表,只有节表中既可以获取到在文件中的偏移,又可以获取到RVA,那么这里我们可以得到一个偏移差,然后用RVA减去偏移差才能得到文件偏移。那么我这里的偏移差为OxOCOO,减去之后,我们可以看到我们的文件偏移为OxOOICE80C,和PEID一样吧?嘿嘿,那么首字节和文件偏移量就OK了。
我们现在先来看看节表吧,节表紧跟在PE文件的PE头后面,那么我们既然都获得了PE头的指针,如果将指针移动到节表第一项是没有任何问题的,代码其实和之前的差不多,不难做。这里大家应该注意到我们需要添加新窗口来显示我们获取到的节表,这里我们还是一步一步来。因为主窗体上有一个获取节的操作(主窗体要显示第一个节),而一个子窗体里面还要输出所有节的信息。获取节的操作我这里就不说了,只要把NT头的指针+sizeof(IMAGE_NT_HEADERS)就可以拿到第一个节表的指针。有了节表,自然就手到擒来啦,这里我就不给示范代码了,因为和检验PE文件有效性的代码差不多。还是有一点要说,这里又存在格式化字符串的问题了,大家誊票项耍编码为Unicode的就比较麻烦。因为相信代码写出来就肯定有人纳闷了,因为我们读取到的节名为ANSI编码,而项目编码是Unicode,所以看着读取到的数据是对的,显示出来就错了,这个编码转换的处理在以前的博客里也出现过,我这里不再赘述,编码转换方法很多,大家没买到合刊的也不用急,百度一下会有很多答案。
相信大家获取节表很是容易吧?我这里懒的写,把原来那个检验有效性的代码给弄来改了下就出来了。这里我们很容易可以算出OxlOOO-Ox400=Oxc00,那么根据偏移差,再结合我们之前获取到的人口RVA,即可算出我们入口点的文件偏移量,这里换算起来很麻烦,而且当时我学习的时候也是弄得头晕脑胀,不过大家要尽量去看,后来我找了一个标准的PE文件,然后根据PE文件格式一个字节一个字节数着对着看,终于记清楚,弄明白。大家如果觉得PE这块的文章看着实在蛋疼,可是尝试一下我的笨办法,起码我现在感觉很清晰。我觉得这也不失为一个好办法,好了,不瞎扯,我们继续,这里添加一个新窗体,里面放一个列表视图,我们来把区段这块做一下。那么实际上这个软件大致的技术含量我这里已经全部讲过了,其实大家应该也能感觉到了,我们的技术含量其实并不多,就是PE文件结构,MFC,还有C-H-文件读写,最后就是在窗体加载的时候读出所有节的数据即可。我在新窗体里面直接添加了AI ReadAllSectionData的函数,这里返回值和参数都不要了。废话不说,直接上步骤:
1.读文件。
2.获取节数量。
3.读取节表。
4.关闭文件。
5.大功告成,坐下喝茶。