黑客对Windows应用编程的基础

网站优化

  文件

  在Windows下文件有很多种,比如图片文件、视频文件、音频文件.......除了常见的文件格式外,其实对于管道、邮槽、甚至是设备对象,在Windows下也被当做文件案例对待,这样对于我们操作管道、邮槽、设备对象就像操作文件一样简单了。

  对于操作任何文件,我们最先的操作是对文件的打开,操作结束时为了释放资源要进行关闭,我们依次学习如何使用浙西API函数,然后完成一个简单的小例子,从文件操作开始,我们要接触MFC方面的编辑,但不会很难,都是一些对界面和控件之类的使用。

  打开文件

  要对文件进行操作,就要首先对文件进行打开,文件打开成功会返回一个句柄,然后通过这个句柄对文件进行读写操作。

  打开文件的API函数如下定义。

  .

  参数说明如下。

  (1)ipFileName:要打开或者创建的文件名,这里也可以不是文件名,可以是管道名字、设备对象名字。

  (2)dwdesiredaccess:对文件的访问模式,通常是Generic_read和generjc_write

  (3)dwsharemode:对文件的共享测试。Windows是多进程的操作系统,在一个文件被访问的时候,可能会有另一个进程也访问该文件,这时候第一个打开该文件的进程就要对其设置个访问模式,即打开此文件后,其他进程对该文件可以有哪些操作。

  (4)ipsecurityattributes:安全属性,一般为NULL。

  (5)dwcreationdisposition:在创建或打开的文件存在或不存在时候该函数的处理方式。

  (6)dwflangsandattributes:打开或创建文件时候的标志和属性,一般是File_aftribuftenormal

  (7)hyemplaatefile:文件模板,一般为NULL

  该函数若执行成功,则返回一个文件句柄,若执行失败,则返回invalid_handle_value

  在对文件使用完毕后,应该对打开的句柄进行关闭以释放资源,关闭句柄的函数如下:

  该函数就是一个参数,这个参数就是调用Createfile()函数时的返回值,也就是文件句柄。

  文件操作

  文件操作的种类有4种类型。分别是增、删、改、查、熟悉数据库的操作的读者一定感觉这4中操作是针对数据库的,怎么对于文件的操作也是这4种呢?其实,不单单是对文件的操作,对注册表的操作,对服务的操作,对进程的操作........也都存在着增、删、改、查、这些操作,还是讨论文件的4中操作吧,文件的增就是创建文件,文件的删就是删除,当然了,对于文件的读操作分别狭义的和广义的两种,狭义的读文件就是读取文件的内容,对于广义的读文件来说,可以使读取文件的大小,读取文件的创建时间和修改时间.......文件的创建是通过Createfile()函数来完成的,文件的打开也是通过Createfile()函数来完成的,对于删除文件。写文件、读文件.....这些API函数,下面将逐一进行介绍。

  删除文件的API函数如下:

  该函数的参数就只有一个,而且非常的简单,就是要删除的文件的文件名称。

  文件的该函数如下:

  参数说明如下:

  (1)hfile:该参数是Createfile()函数返回的句柄

  (2)lpbuffer:指向一个缓冲区,函数会将从文件中读出的数据保存在该缓冲区中

  (3)nNumberOfBytesToRead:要求读入的字节数,通常情况下是缓冲区的大小。

  (4) lpNumberOfBytesRead:该参数是一个指向DWORD类型的变量,用于返回实际读入的字节数。

  (2) lpOverlapp,:d:一般设为NULL。

  文件的写函数如下:

  WriteFile()函数与ReadFile()函数的参数意义基本相同,所不同的是第一个参数,第二个参数仍然指向一个缓冲区,函数会将该缓冲区的内容写入到文件中。

  设詈文件指针的函数如下:

  参数说明如下。

  (1) hFile: CreateFile0函数打开文件的句柄。

  (2) LDistanceToMove:指定要移动的距离。

  (3) lpDistanceToMoveHigh: 一个指向LONG型的指针,其保存一个要移动的距离的高32位。

  (4) dwMoveMethod:指定移动的起始位置。

  创建目录的函数如下:

  参数说明如下。

  (1) lpPathName:创建目录的目录名称:

  (2) lpSecurityAttribuies:一般为NULL.

  移除目录的函数如下:

  该函数的参数指定要移除的目录的目录名。

  关于文件操作的函数就简单介绍这么几个,在以后的内容中将继续介绍。文件操作是Windows下编程的重要基础,应用非常广泛,希望读者能掌握以上介绍的函数

  Autorun免疫程序的编写

  我们通过上面学的几个函数来完成一个简单的AutoRun免疫程序。

  AutoRun免疫原理

  每当我们打开U盘时,看到有一个AutoRun.inf文件时都会不由地倒吸口凉气,“该死!又中病毒了!”。是的,有一种病毒程序通过AutoRun.inf文件使其自动运行起来,想必这不用做过多的介绍每一位都非常清楚。网上有很多免疫工具,现在一些修改版的操作系统里面也会提供这样一个免疫的小工具。它免疫的原理是建立一个不被删除的文件AutoRun.inf文件夹,以防止病毒生成一个运行病毒的AutoRun.infD

  AutoRun免疫程序的代码实现

  我们的程序使用MFC编写,上面介绍的API函数会使用到两个,并且还会加入新的API函数。好了,开始我们的代码吧。启动VC6,选择菜单“File”->“New”命令,在弹出的“New”对话框中选择“Proj ects”选项卡,在左侧的列表中选择“MFC AppWizard(exe)”,在右侧填写项目的名称“ImmuniryU”,如图3。1所示。

  单击“OK”按钮后,出现“MFC AppWizard - Step l”的界面,选择“Dialog based”单选钮,如图3-2所示。

  这里,我们选择以对话框为基础进行开发。单击“Finish”按钮会出现最后一个对话框,直接单击“OK”按钮关闭对话框。

  界面设置

  我们的程序是有界面的,则首先要对界面进行一些设置。先把界面上所有的控件都删除掉,然后添加一个ComBoBox控件和两个按钮控件,如图30所示。给添加的ComBoBox控件重新命名一个ID为:IDC COMBO__ DRIVER,并为其添加一个控件变量。添加的方法如下:

  在IDC_ COMBO_ DRIVER上单击右键,在弹出的菜单上选择“OassWizard”命令,弹出“MFC ClassWizard”对话框,选择“Member Variables”选项卡。双击IDC COMBO_ DRIVER,弹出“AdcfMember Variable"对话框,添加变量名为m ComboDriver,选择类型为“Contr09然后单击0K按钮。添加情况如图3-4所示

  代码相关部分

  我们进行免疫时是对某个分区进行免疫的,因此要在ComboBox控件中显示出所有的磁盘分区供用户选择。在ComboBox中显示所有的磁盘分区,我们定-义成一个函数来完成该功能。

  把这个函数添加到Onlnitdialog()函数的最后面,Combobox中就会有所有的磁盘分区的盘符了,这个自定义函数中出现了两个我们尚不熟悉的API函数,一个是Setdilltemtex(),另一个是GETlogicaldrivesteings()下面分别介绍这两个函数。

  这是编辑框的显示内容。

  该函数的第一个参数用来指定控件的变量的ID,第二个参数指定一个字符串。

  获取字符串类型的驱动器列表。

  该函数的第一个参数指定缓冲区的长度,第二个参数指定一个缓冲区。

  了解了这两个函数后,在来看代码就很容易理解了,在While循环中的AddString()是CComboBox类的一个成员函数,该函数是用来向CComBoBOX添加一个字符串的,如果你不理解那个While循环的话,建议在VC6下调试一下看看szDriverString里保存的内容,就会明白While循环的作用了,这个挑食建议大家亲自动手练习一次,这是第一次要求大家动手调试。

  接下来添加免疫按钮的代码,代码很简单,如下:

  这段代码中加了详细的注释,大家应该可以看懂,就不做过多的介绍了,如果哪里有疑惑的话,请大家自己动手调试一下,最后看一下取消的按钮的代码:

  整个对Autorun进行免疫的代码就完成了,大家可以自己进行一下试验,启动我们的程序,选择需要进行免疫的盘符,单击免疫,打开被免疫的那个磁盘,看看是不是真的免疫了,在试着用右键删除看看效果,可以看到我们的实现是成功的。

  注册表操作

  注册表

  免疫AUtorun的程序虽然万恒了,但是并不是很完善,我们可以再注册表把自动播放禁止掉,这样即使有Autorun.inf文件,系统也不会运行它,系统的安全系统就会更高了。

  注册表是WINDOWS操作系统的一个重要的数据库,里面记录了系统几乎所有的信息,当然,由于注册表的功能非常强大,因此,注册表对于病毒、木马来说是非常有利用价值的地方,而对于反病毒软件来说,注册表是其需要加强守卫的地方,注册表,是一个正义与邪恶的必争之处。

  注册表对于病毒来说可以利用的地方非常多,比如修改文件关联,增加系统的启动项,映像劫持,篡改浏览器主页......病毒的这些操作让我们不得不了解注册表相关的编辑。

  与注册表操作相关的常用API函数

  对于学习黑客编程来说,注册表的操作当然不能少,现在就来学习关于注册表的操作,注册表的操作和文件的操作类似,也有打开、关闭、写入、查询等操作,也就是说我们所说的增、删、改、查,只是使用的API函数不同而已,下面分别介绍每个API函数的使用方法。

  打开注册表:

  参数说明如下:

  (1)hkey:指定一个父键句柄

  (2)lpsubkey:指向一个字符串,用来标识要打开的子键名称、

  (3)phkresult:返回打开的子键句柄

  关闭注册表:

  该函数只有一个参数,参数是打开子键的句柄。

  注册表键值的查询:

  参数说明如下:

  (1)hkey:指向一个已被打开或创建的子键句柄。

  (2)lpvaluename:指定要被查询的键值的名称

  (3)lpreserved:保留,始终为null

  (4)lptype:返回被查询的值的类型

  (5)lpdata:指向要查询数据的缓冲区

  (6)lpcbdata:缓冲区的长度

  注册表键值的写入:

  参数说明如下:

  (1)hkey:指向一个已经被打开或者创建的子键句柄。

  (2)lpvaluename:指定要被查询或写入的键值的名称

  (3)reserved:保留,始终为0

  (4)dwtype:写入键值的类型

  (5)lpdata:写入键值的缓冲区

  (6)chdata:写入键值缓冲区的长度

  注册表键值的枚举。

  参数说明如下。

  (1)hkey:指向一个已经被打开或者创建的子键句柄。

  (2)dwindex:查询的索引值

  (3)lpvaluename:键名的缓冲区

  (4)lpcvaluename:键名缓冲区的长度

  (5)lpreserved:保留,始终为NULL

  (6)lotype:返回被查询的值的类型。

  (7)lpdata:被查询的键值的缓冲区

  (8)lpcbdata:被查询的键值的缓冲区的大小

  注册表键值项的删除

  参数说明如下。

  (1)hkey:指向一个已经被打开或者创建的子键的句柄

  (2)lpvaluename:指向欲删除的键值项的名称。

  注册表启动项的管理

  对于Windows操作系统来说,注册表中保存了非常多的系统配置,例如把长剑的IE主页保存在HKEY_LOCAE_MACHINE\SOFTWARE\MINCROSOFT\INTENET EXPLORER\MAIN下的STAR PAGE中,在比如禁止磁盘驱动器的Autorun功能在注册表的HKEY_current_USER\SOFTWARE\MICOROSFT\WINDOWS\CURRENTVERSION\POLICIES\EXPLORER下的NODRIVETYPEAUTORUN中进行设置,还有映像劫持,文件关联.......很多配置都可以再注册表中完成。

  对于安全工具来说,通过注册表发现所有的WINDOWS启动项是十分重要的,在注册表的启动项中,出了正常的系统工具、软件工具外,病毒和木马也会利用注册表的启动项悄然的让自身进行启动,我们通过编写一个枚举注册表启动项的工具,来学习注册表相关的操作。我们的程序依然会使用到MFC中的控件,下面随笔者一步一步的学习吧

  程序的界面设置及其相关的代码

  注册表中可以用来完成开机启动的地方非常的多,我们不一一介绍了,这里只对注册表中某个可以完成开机后启动的地方进行介绍,至于其他的地方大家可以自行搜集并完成,我们的程序依然会是使用对话框的形式,其界面如图3-5所示

  这个界面就是我们已经编写好的软件界面,在这个界面中,用到了CListctrl控件,大家对其进行添加并进行相应的设置即可,在实例的介绍中,尽可能的少提及控件,因为这是非本文章应该提到的内容,大家可以自行参考,FC开发相关的书籍,这里,给出一个关于Clistctrl初始化的代码

  启动项的枚举

  在这个实例中,主要是枚举注册表中的子键下的键值项,对于我们软件启动后,应该显示出在这个注册表子键位置下的所有启动项的内容,枚举的代码如下:

  对于注册表启动项的管理来说,应该具备几个功能,首先是屏蔽启动项,然后是删除启动项,最后是添加启动项,在我们的程序中主要完成后两个功能,一个是删除启动项,另一个是添加启动项,至于屏蔽启动项这个功能就留给大家自己实现吧,在很多安全软件中,如Windows优化大师、360安全卫士中都有屏蔽启动项的功能,大家自己思考一下屏蔽启动项与删除启动项的区别在哪里,并如何实现。

  下面分别来看一下添加启动项和删除启动项的代码。

  添加启动项的代码

  添加启动项,需要添加一个窗口,至于窗口的添加方法大家自行参考MFC开发相关的介绍,这里我就不做过多的介绍了,完整的代码会提供给大家,方便大家进行参考,这里主要是介绍与注册表操作相关的内容,添加启动项的代码如下:

  删除启动项的代码

  删除启动项的代码比添加启动项的代码简单,但是在删除的时候要涉及到一个关于CListCtrl控件的编程,也就是选中那个启动项进行删除,这是一个主要的问题,获取选中的启动项后的删除就非常简单了,代码如下:

  对于启动项的管理软件的编写就到这里了,大家也可以在注册表中其他的可以使软件开机启动的位置添加进去,这样我们的软件就更加强大了。在我们不断地在注册表中发现有新的位置可以启动软件时,每次都需要进行代码添加,这样的工作太繁琐了。有没有什么好的方法当每次发现新的位置后可以不改变代码呢?请大家把前面学到的文件操作的知识联系起来进行考虑一下!我们可以把要枚举的注册表的于键保存到一个文件中,然后让程序去读取些子键,在注册表中分别进行枚举。这样,以后每当在注册表中有新的需要枚举的内容,那么只要对这个文件进行修改就可以了,就不需要对程序本身进行修改了。大家可以试着写一个这样的程序。

  服务相关的编程

  服务是一种在操作系统启动时就启动的进程。在操作系统启动时有两种程序会一起随着系统启动,一种是普通的Wjn32程序,另一种是驱动程序。这里讨论的并不是要如何编写一个系统的服务,而是编写一个如何可以显示出这些随系统启动而启动的服务项。

  如何查看系统服务

  在Windows下,有很多服务是跟随操作系统一起进行启动的,具体有哪些服务是跟随操作系统一起启动的呢?如何查看呢?其实非常得简单。在“我的电脑”上单击鼠标右键,然后在弹出的菜单上选择“管理”命令会打开“计算机管理”工具,单击左侧树形列表中的“服务和应用程序”会出现一个列表,选择“服务”则在右侧出现了服务项列衰,如图3-6所示。

  在这个列表中,只能查看到Win32应用程序的服务,无法查看关于驱动程序的服务,我们可以借助其他的一些小公举来查看驱动服务的程序,如图3-7所示。

  接下来编写一个程序,即可以查看应用程序服务列表,也可以查看驱动程序服务列表,编写的程序如图3-8所示。

  这个服务控制管理器依然是使用MFC的对话框进行开发的,其中还是用到了CListctrl这个控件,让我们现在就动手开始打造一个自己的服务控制管理器吧。

  服务控制管理器的开发

  服务控制管理器的开发和注册表启动管理器的开发是差不多的,主要是枚举服务并将其现实到列表控件中,至于对服务状态的控制,是通过服务相关的API函数来完成的,这次,我们先来看看代码,希望通过阅读代码大家能自己掌握与服务相关的API函数,在代码的后面会对开发服务控制管理器涉及到的API进行相应的解释这里笔者强调一下,学习API函数的使用,最好的老师是MSDN,在大家学习编程的道路上,要通过阅读大量的代码来提高自己的编程能力,那时大家就要自行学习API函数的使用,因此希望大家能养成好的学习习惯,也就是自己能够独立查阅MSDN来进行API函数的使用。

  服务控制管理器的界面在前面大家都见过了,界面的布局方面大家可以按照文章中的安排的那样来,也可以根据自己的喜好来,我们要枚举的服务分别为WIN32服务应用程序和驱动程序两类,对于枚举这两类服务,知识枚举函数的参数有所不同,这个函数参数是系统对应以的两个敞亮,定义分别如下:

  看一下服务枚举时的代码:

  该成员函数有一个参数,这个参数就是用来指明是枚举Win32应用程序服务,还是用来枚举驱动程序的服务的,也就是我们前面提到的两个系统定义的常量了,参数根据两个常量的不同,枚举到的服务业不同。

  枚举服务的相关API函数

  打开服务管理器:

  参数说明如下。

  (1)lomachinename:指向欲打开服务控制管理器数据库的目标主机名字,本机则设置为NULL

  (2)lpdatabasename:指向了目标主机SCm数据库名字的字符串。

  (3)dwdesiredaccess:指定对SCM数据库的访问权限、

  这里介绍一下,SCM是服务控制管理器的意思,他是系统服务的一个组成部分,跟我们开发的软件不是一个概念,由于我们不是编写一个具体的服务,而只是对系统现有的服务进行一个枚举,因此,一些概念性的知识希望大家可能自行查阅相关的资料。

  该函数调用成功,返回一个SCM句柄,否则返回NULL。

  关闭服务句柄。

  该函数用来关闭OPenscmanger()和OPenservice()打开的句柄。

  枚举服务:

  参数说明如下。

  (1) hSCManager: OpenSCManager()函数返回的句柄。

  (2) dwServiceType:指定枚举的服务类型,也就是我们自定义函数的那个参数。

  (3)_ dwServiceState:枚举指定状志的服务。

  (4) IpService!指向ENUM—SERVICFSTATUS类型的指针。。

  (5)cbBufSize:指定缓冲区的大小。

  (6) pcbBytesNeeded:返回实际使用的内存空问太小。

  (7) IpServicesReturn:返回枚举服务的个数,

  (8) lpResumeHmdre:返回收举是否成功。

  -ENUM SERVICESTATUS结构的定义:

  SERVICE_STATUS结构的定义

  服务的停止

  对于枚举到的服务,对我们来说只有两种操作,一种是启动服务,另一种是停止服务。系统中有很多我们不用的服务,它会随着电脑的启动而启动,这样既会影响系统的启动速度,会占用宝贵的系统资源,因此了没用的系统服务要停掉。先来看看停止系统服务的代码。

  停止服务的相关API函数

  打开服务函数:

  参数说明如下。

  (1)hSCManager:指定由OpenSCManagerO函数打开的服务句柄。

  (2) lpServiceName:指向要打开的服务的名称。

  (3)dwDesiredAccess:对要打开服务的访问权限。

  控制服务函数:

  参数说明如F。

  (1)hService:指定一个由OpenService()打开的服务句柄。

  (2) dwControl:指定了要发送的控制码,ConiroIService()可以对服务进行多种控制,每种控制对应’种控制码。

  (3) lpServiceStatus:用于返回服务的状态。

  服务的启动

  启动服务的代码与停止服务的代码类似。这里简单地给出启动服务的部分代码,具体的代码自己动手写写。启动服务的部分代码如下:

  启动服务相关API函数解释:

  参数说明如下。

  (1) hService:指定了要启动服务的句柄。

  (2) dwhlumServiceArgs:指向了启动服务所需的参数个数。

  (3) lpServiceAfgVectors:指向了启动服务的参数。

  该函数最后两个参数类似main(intac, char *argv)中的两个参数的作用。

  进程与线程

  进程

  当接下Ctrl +Shift+Esc组合键时,就打开了Windows任务管理器对话框,里面有很多进

  程列表,如图3-9所示。

  进程是运行当中的程序,是向操作系统申请资源的基本单位。我们运行一个记事本程序,那么就相应会创建一个记事本的进程。当关闭记事本时,进程也随即结束了。对于这个任务管理器中,我们对进程比较关心的是“映像名称”、“PID”两项。对十进程相关的,我们主要学习进程的启动、结束、枚举等编程

  进程的创建

  任何一个计算机文件都是一个进制文件,对于可执行程序来说,他的二进制数据是可以被CPU执行的,程序的概念是一个静态的概念,程序本身只是存在于硬盘上的一个二进制文件,当用鼠标双击了某个可执行程序以后,这个程序就被加载,如内存,这时候就产生了一个进程,进程会向系统申请各种所需要的资源,并且会生成一个主线程,线程会拥有CPU执行时间,占用进程申请的内存........那么在编程的时候用API函数启动一个进程也是我们要学习的内容,可以创建进程的API的函数有WINExec()、shellexecute()和createlprocess()等等,这里主要介绍Winexec()和createlprocess()这两个函数

  下载者的简单演示

  Winexec()函数介绍:

  参数说明如下:

  (1)lpcmdline:指向一个欲执行的可执行文件。

  (2)ucmdshow:程序运行后的窗口状态。

  对于第一个参数比较好理解,比如要执行记事本程序,那么这个承诺书就可以是c:\windows\system32\notepad.exe,对于第二个参数是指程序运行后窗口提示,另一个是程序运行后窗口不显示,大家可以试着创建一个不显示窗口的记事本程序,代码很简单,如下:

  这样创建的记事本进程,在任务管理器中可以看到notepad.exe这个进程,但是无法看到其窗口界面。

  Winexec()这个函数多用在下载者中,下载者的英文名字叫做Dwonloader,也就是下载器的意思,它是一种恶意程序,该恶意程序的功能比较单一,该恶意程序的功能是让受害计算机到黑客自定的URL地址去下载更多的病毒文件或者木马文件并运行,下载者的体积较小,容易传播,当下载者下载到病毒或者木马后,通常都会使用WINexec()来运行病毒。

  我们简单的来做一个下载者的演示,记住,这只是一个演示,不要企图拿来做任何的坏事,因为我们演示代码很轻易的就会被杀毒软件杀掉,我们的目的是学习编程知识。

  我们要完成一个模拟的下载者,既然是下载者,重要的当然是文件的下载了,文件的下载方式比较多,但是相对简单比较常用的函数是URLDOWNLOADTOFILE()这个函数也是经常出现在下载者中的函数,该函数的定义如下:

  参数哦说明如下:

  (1)szurl:指向YRL地址的字符串

  (2)szfilename:指向要保存地址的字符串

  其余参数我们不做介绍,如果需要了解大家可以自行查阅MSDN。

  对于使用URLDownladtofile()函数,需要包含Urlmon.h头文件及urlmon.lib库文件,否则当编译和链接的时候会无法通过。

  既然了解了该函数的使用,那么我们就来完成一个模拟的下载者吧,代码如下:

  我们的模拟是C盘系统目录下的记事本程序下载到D盘下并保存为VINUS.exe然后运行它,这里只是一个简单模拟,如果要真正完成一个下载者的话,其代码是要复杂的很多,如果要在源代码上对其进行免杀,那么要考虑的问题也会有很多。

  CREATEPROCESS()函数介绍与程序创建

  通常情况下,我们创建一个进程都会选择使用CreatePFocess(),该函数的参数非常多,功能更强大,使用也更为灵活。WinExee()函数的使用相对简单,只能完成简单的进程创建工作。如果耍对创建的进程有控制的能力,那么必须使用CreateProcess)函数。在介绍CreafeProcess()函数之前,先来考虑一个问题。在我们编写C程序时,如果是控制台下的程序,那么编写程序的入口函数是main()函数,也就是我们通常所说的主函数。如果是一个Windows程度,那么入口函数是WinMain0函数,即使使用MFC进行开发,也是有WinMain0函数的,只不过它被庞大的MFC框架给隐藏了那么我们的函数真的是从mainf()

  函数或者是WinMain()开始执行的的吗?我们在写控制台程序时,如果需要绐程序提供参数,那么这个参数是从哪里来的呢?

  找们使用VC6来写一个简单的程序,并调试一下,看程序是否真的是由main()函数开始的。我们选择第1章介绍的输出"HelIoWorrd”的程序来演示。是否还记得第1章写的在控制台下打印"Hello World_"字样的程序呢?好,让我扪回忆一下当时写的程序吧,如图3=10所示。

  接下来要怎么做呢?按下F10键,这时候,我们的程序在VC6下处于调试状态,打开Callstack窗口,如图3-11所示。

  双击maincrtstaryup()line206+25bytes这一行,查看代码编辑窗口的内容,如图3-12所示。

  可以看到,绿色三角指向哪行代码是对MAIN()函数的调用,并且MAIN()函数还有返回值。滚动代码,查看一下MAINET变量的类型,该变量的定义如下:

  该变量的类型为INT型,那么,当定义MAIN()函数时,MAIN()函数的返回值是什么呢?通常情况下定义main()函数的返回值是INT型,但是,也有一部分人喜欢吧MAIN()函数的返回值定义为VOID型,也就是空型,那么如果定义为VOID型,mainert接收的返回值是什么呢?在WINDOWS下约定,函数的返回值保存在EAX寄存器中,如果对main()函数的返回值定义为VOID型,那么当MAIN()函数返回后,mainret中曹村的是退出MAin()函数后eax寄存器的值。

  除了这个意外,大家看一下VC6的标题栏,标题栏上给出的这个C文件,我们叫做启动代码,用于在启动一个进程后对全局变量等内容的初始化,在看一下图3-11,callstack还有一行,双击它,观察代码编辑器中的内容,由于这个部分不是我们讨论的重点,因此就不进行介绍了,如果有兴趣的话 ,大家可以再行学习研究。

  下面介绍CreateProcess()函数的使用

  CreateProcess()函数原型:

  参数说明如下。

  (1) lpApplicationName:指定可执行文件的文件名。

  (2) lpCommandLine:指定可执行文件的运行参数。

  (3)lpProcessAttributes:进程安全属性,该值通常为NULL,表示为默认安全属性。

  (4) lpThreadAttributes:线程安全属性,该值通常为NULL,表示为默认安全属性。

  (5) blnheritHandles:指定当前进程中的可继承句柄是否可被新进程继承。

  (6) dwCreationFlags:指定新进程的优先级以及其他创建标志。

  该参数一般情况下可以为0。

  如果要创建一个被调试进程的话,需要把该参数设置为DEBUG_PROCESS。创建进程的进程为父进程,被创建的进程为子进程。也就是说,父进程要对于进程进行调试的话,需要指定DEBUG PROCESS。在指定了DEBUGPROCESS后,子进程创建的子进程,同样也处在被调试状态中。

  如果不希望子进程创建的子进程处在调试状态,那么需要同时指定DEBUG ONLV_ TfflS_ PROCESS.在有些情况下,如果希望被创建的子进程暂时不要运行,那么可以指定CREATE_SUSPED参数。事后希望该子进程运行的话,那么可以使用ResumeThread()函数使子进程恢复运行。

  (7) lpEnvifonment:指定新进程的环境变量。

  (8) lpCurrentDirectory:指定新进程使用的当前目录。

  (9) lpStartuplnfo:指定新进程的启动信息。

  该参数是一个结构体,该结构体决定进程启动的讲台.该结构体的定义加下:

  该结构件在使用前,需要对cb这个成员变量赋值,该成员变量用于保存结构体的大小。 该结构体的使用不做过多的介绍了。如果要对新进程的输入输出重定向的话,会用到该结构体。

  (10)lpProcesslnformation:返回新进程,进程线程等相关信息。该参数同样是一个结构体,该结构梅体的定义如下:

  该结构体中保存着新创建进程的句柄,进程主线程的句柄,还有进程ID和主线程的ID

  下面进行一个简单的演示,用Createprocess()创建一个记事本进程代码如下:

  对于进程创建后PROCESS_INFORMATLON接收的两个句柄,需要进行关闭。

  进程的结束

  介绍完进程的创建,对于进程的结束,通常情况下希望程序可以自己进行结束,也就是进程正常退出运行状态。在进程正常进行退出时,会调用ExitProc,egs()函数,该函数使用简单,这里就不进行介绍了。我们主要介绍一下,如何实现类似任务管理器那样结束其他的进程。

  结束其他进程需要使用到TerminateProcess()这个函数,该函数的定义如下:

  该函数的参数比较少,只有两个,第一个参数是要结束进程的进程句柄,第二个参数通常给O。那么这里遇到一个问题,我们如何获得要结束进程的进程句柄呢?方法有两种,第一种方法是枚举进程列表,找到要结束的进程,枚举进程的内容稍后会介绍。第二种方法是根据要结束进程的窗口标题获得该窗口的句柄,然后通过窗口句柄得到进程的PID,有了进程的PID后,我们可以打开浚进程得到进程的句柄,然后将其结束。我们这节主要介绍第二种方法。

  下面通过介绍如何结束“记事本”进程来学习笔者所说的第二种方法。先来看一下代码。

  我们打开一个记事本,然后编译运行这段代码,会发现记事本被关闭了,我们逐个介绍我们使用到的这些API函数。

  首先来介绍FindWINDOWS()这个函数,findwindows()函数的原型如下:

  该函数有两个参数,第一个参数是类名,第二个参数是窗口名,该函数的返回值就是窗口的句柄,这两个参数只要指定一个就可以了,对于我们来说,希望通过进程的窗口名来获得窗口的句柄,因此这是只要给出窗口名就可以了,在我们的代码中,“记事本”的窗口名为无标题.记事本,如果窗口名去的不准确的话,那么将无法获得该窗口的窗口句柄,怎样才能准确无误的获取窗口的名称呢?这是推荐大家使用一款VC6中自带的具——SPY++。只要安装了VC6后,就会安装该工具,大家可以在开始菜单中找到,其具体位置为:

  这个工具,如图3-13所示。

  单击工具栏中的按钮,该按钮为FIND按钮,出现如图3-14所示的WINdows SEARCH界面用鼠标拖动FINDER TOOL后面的那个图标到记事本进程的标题栏上,该窗口会显示出记事本的窗口名,如图3-15所示

  在“Caption”中的内容就是“记事本”程序的窗口名称,我们把它作为FindWindow0函数的第二参数,这样,可以获取该窗口的窗口句柄,再通过窗口句柄获得该窗口所属进程的ID。代码如下:

  该函数第一个参数是获取到的窗口句柄,第二个参数是一个输出参数,会返回该进程的lD。

  在获得了进程ID后,通常通过使用OpenProcess()来获得进程的句柄。该函数的原型如下

  参数说明如下:

  (1)Dwdesiredaccess:进程欲获得的访问权限,该参数为了方便可以始终为PROCRSSA;;_ACCESS

  (2)blnherithandle:指定获取的句柄是否可以继承,一般情况下为false。

  (3)dwprocessld:指定欲打开的进程ID号。

  该函数的返回值为进程的句柄,通过这个句柄可以结束进程

  既然已经获得了进程的句柄,那么就可以结束指定的进程了,在结束完进城后,也要记得用Closehandle()关闭用OPenprocess()打开的进程句柄。

  进程的枚举

  进程的枚举就是把所有的进程都显示出来。当然了有一些特意隐藏的进程是无法通过常规的枚举方式枚举到的,这里只介绍应用层的进程枚举,在应用层枚举进程有很多种办法,这里只借介绍相对常见的枚举进程的方法,在学习进程的枚举的过程中,我们会完成一个自己的进程管理器,如图3-16所示。

  在进程管理器中,除了对进程的枚举,还会对线程进行枚举,还有对进程中加载的DLL进行枚举,要枚举的内容非常的多,单枚举的API函数都类似,下面介绍以下几个API函数。

  枚举进程需要API函数分别有Createtoolhelp32snapshot(),该函数的作用是对当前系统中的进程进行一个快照,在创建快照以后进行逐个进程的枚举,枚举进程的函数是Process32First()/process32Next(),如果是枚举线程的话,那么枚举函数是threa32first()/fhread32nexr(),如果是枚举进程中的DLL的话,那么枚举函数是Module32first()/module32Next(),在使用以上这些函数的适合,需要包含Tlheip32.h头文件。否则在编译的时候会提示使用了未定义的函数,针对以上的函数,分别进行一个简单的介绍。

  Createtoolheip32Snapshot()函数的原型:

  参数说明如下。

  (1)dwflags:该参数指明要建立系统快照的类型,对于要枚举的内容,该参数可以指定如下值。

  ①TH32CX_SNAPMODULE:在枚举进程中DLL时指定

  ②TH32CX_SNAPPROCFSS:在枚举系统中的进程时指定

  ③TH32CS_SNAPTHREAD:在枚举系统中的线程时指定。

  (2)Th32ProcessID:该参数根据dwflags的不同而不同,如果枚举的是系统中的进程或者系统中的线程时,该参数为NULL,如果枚举的是进程中加载的DLL的话,那么该参数为进程ID号。

  该函数返回一个快照的句柄,在进行枚举时候都会用到该句柄。

  Process32First()函数的原型:

  参数说明如下。

  (1)hsnapshot:该参数为createToolheip32Snapshot()返回的句柄。

  (2)lppe:为指向一个PROCESSENTRY32结构体的指针,该结构的定义如下

  在使用该结构体时候,需要对该结构体中的成员变量DWSIZE进行赋值,该变量保存PROCESSENTRY32结构体的大小。

  Process32Next()函数的原型:

  该函数的使用与Process32Next()类似,对于枚举进程中加载DLL的枚举,对于系统中线程的枚举,都和此函数类似,只是枚举DLL与线程时,XXX32First()与XXX32Next()的第一个参数指向的结构体不同,这4个函数的使用请大家参考MSDN自行学习。

  对于美剧进程的API函数已经学习了。下面看一下枚举进程、枚举进程中加载DLL的代码吧。

  枚举系统进程的代码:

  枚举指定进程中加载DLL的代码:

  调整当前进程的权限

  我们枚举的代码基本上是完成了,到VC6下直接按CtrI+F5组合键运行程序,可以看到我们枚举的进程都出来了。然后选选中" svchost.exe”进程,单击,查看DLL”按钮“svchostexe”进程中加载的DLL也都枚举出来了,这样运行是没有问题的。接_下来找到编译好的任务管理器运行(不要直接在VC6下运行),可以看到,我们枚举的进程也都显示出来了。仍然选中" svchost.exe”,然后单击“查看DLl。”,是不是没有查看到“svchost.exe”进程加载的DLL文件,这是什么原因?换一个其他的进程试试,比如选样自己编写的任务管理器试试,可以查看其DLL文件。通过试验发现,系统文件的DLL我们都无法枚举到,可是在VC6下直接运行是可以枚举到的。不单单是这方面的问题而且在使用OpenProcegs()函数打开如smss.exe、injogon.exe-等系统进程的时候,也同样会导致函数的调用失败,其实这个问题是当前进程权限级别不够胙导致的。解决这个问题很容易,只要当前进程具有“SeDebug,Privilege”权限就可以了,接下来就来说明这个调整当前进程的杈限。调整权限其实并不复杂,主要有3个步骤。

  (1)使用@penProcessToken()函数打开当前进程的访问令牌

  (2)使用- LookupPrivilegeValue()函数取得描述权限的LUID。

  (3)使用AdjustTokenPrivileges()函数调整访问令牌的权限。

  调整权限健当前进程拥存一“SeDebugPrivilege”权限,拥有这个权限后.当前进程可以访问一些受限的系统资源。在后面讲到远程线程注入的时候.同样需要调整当前进程的访问令牌权限,否则是无法洼对系统进程进行注入的,因为在进行注入的吐候,同样要用到openprocess()这个函数,下面给出调整权限的代码,不过在这个代码里面没有做返回值的判断,通常情况下是没有问题的,代码如下。

  进程的暂停与恢复

  在有些时候,我们不得不让进程暂停运行,比如,病毒有两个运行的进程,它们在不断的互相帮助,当一个病毒进程发现另一个病毒进程被结束了,那么他会再次把那个病毒运行起来,由于两个进程一直在做这样的事情,而且频度较高,因此很难把这两个进程都结束掉。这样,就不得不让病毒进程暂停了,当两个进程都暂停以后,就可以把病毒都结束掉了。

  让进程暂停,通常使用的是Suspthread()函数,该函数的定义如下:

  该函数就一个参数,是一个线程的句柄,获得线程的句柄需要使用OPenthread()来打开一个指定的县城,对于得到线程ID,我们可以对线程进行枚举,然后就可以打开线程并将其暂停了。

  注:OpenThread()函数在VC6提供的PSDK中是不存在的,必须更新PSDK才可以使用。

  如果没有更新PSDK的话,需要使用LoadLibrary()和GetProcAddress()来使用该函数。对于LoadLibrary()和GetProcAddressO函数的使用在DLL编程中将会进行介绍。

  枚举线程的函数是Thread32Firsi()和fflread32Next()这两个,对于故举线程前,我们用CreateToolhelp32Snapshot()只能创建系统的线程快照,不能创建指定进程中的线程的快照。这

  样在暂停线程时,必须对枚举到的线程进行判断,看其是否为指定进程中的线程。如何判断一个线程是属于哪个进程的呢?我们看一下TFIREAfENTRY32这个结构体

  该结构体中,th32threadID标识了当前枚举到的线程的线程ID,th32ownerprocessid标识了该线程归属的进程ID,因此,只要进行一次简单的判断就可以了,看一下暂停的代码:

  与进程暂停相对应的是恢复暂停的进程,恢复暂停的进程是函数使用ResumeTHread()函数,该函数的原型如下:

  该函数的使用方法与SUSPTHREAD()一样,恢复暂停的进程的代码大家可根据暂停进程的代码自行修改,这里就不给出完整的代码了。

  在进程相关的最后部分给大家介绍一个不错的工具,该工具的界面如图3-17所示。

  该软件的动能非常强大,当启动一个进程或者结束—个进程的时候一该软件会高亮显示被启动或结束的进程。当然了,它的功能非常多,还是大家自己研究挖掘一下。在这里重点介绍该工具中的一个小功能,单击菜单“Options”一>“Replace Task Manager”命令,该功能是用来替换系统的任务管理的,也就是将Process Explorer设为默认的任务管理器。大家替换一下任务管理器,然后按下Ctrl+Shi叶Esc组合键试试看,是不是Process Explorer被打开了,原来的任务管理器不见了。如果想要还原到原来的任务管理器,只要再次单击“Replace TaskManager”菜单项就可以了,我们单击一下该菜单还原到原来的任务管理器。该功能是如何实现的呢?原理其实是对注册表做了下_脚,对注册表的哪些地方做了手脚呢?我们介绍另外一个值得推荐的工具,叫做Regmon,它是用来监控注册表的。该软件如图3-18所示。

  接CtrI+L组合键,弹出“Re~gnon Filter”界面,我们在“Include”文本框中输入“procexp.exe”,如图3-19所示。

  输入完后单击“OK”按钮,再单击Process Explorer的“Replace Task Manager”菜单项,看RegMon捕获到的注册表的信息。如图3-20和3-21所示。.

  打开注册表编辑器看一下被修改的内容,如图3-22所示。

  将该值删掉,再按下Ctrll+Shift +Esc组台键看一下,默认的任务管理器出现了,这就是注册表中有名的映像劫持。大家可以自己在我们编写的任务管理器中添加这样一个替换系统任务管理器的功能以做练习。关于注册袤的操作,大家可以参考前面学习的内容。

  多线程

  线程是进程中的一个执行单位(每个进程都必须有一个主线程)一个进程中可以有多个线程,而一个线程只存在于一个进程中。在数据关系上,这是一对多的关系。线程不拥有系统资源,线程所使用的资源垒部由进程向系统申请。

  在多处理器中,不同的线程可以同时运行在不同的CPU上,这样可以提高程序运行的效率。除此而外,在有些方面必须要使用多线程。比如,如果扫描磁盘井同时在程序界面上显示当前扫描的位置,这样就必须使用多线程。因为在程序界面上显示和磁盘的扫插工作在同一个线程中,而且界面也不停地进行重新显示,这样就会导致软件看起来像是卡死一样。如果分为两个线程就可以解决该问题。界面的显示由主线程完成,而扫描磁盘的工作由另外一个线程工作,两个线程协同工作,这样就可以达到我们想要的效果了。

  先了解一下线程的创建,线程的创建使用CreateThread()函数,该函数的原型如下:

  参数说明如下。

  (1) IpThreadAttributes:该函数指向一个安全属性,该参数一般设置为NULL。

  (2) dwStackSrze:该参数指定线程的栈大小,该参数一般设置为0,表示默认栈大小。

  (3) lpStaMddress;该参数指向一个线程函数地址,该函数属于一个回调函数。所谓回调函数,就是由系统去调用该函数,并不是由我们直接调用该函数。线程函数的定义如下:

  线程丽数的返回值为DWORD类型,该函数只有一个参数,该参数由CreateThread()函数给定。该函数的函数名称可以任意给定。这里介绍一下WINAPI.WINAPI是一个宏,该宏的定义如下:

  这是一种函数的调用约定,该调用约定会在后面的章节中介绍,在这里只需要了解就可以了。

  (1) lpParameter:该参数是传递给线程函数的一个参数,

  (2) dwCreationFlags:该参数指明创建线程后的线程状态,在创建线程后可以让线程立刻执行,也可以让线程处于暂停状态。如果需要立刻执行,将该参数设置为0;如果要让线程处于暂停状态,那么该参数值设为CREATE__ SUSPED;需要线程执行时调用我们前面绍过的ResumeThread()幽数。

  (3) IpThreadld:该参数用于返回新创建线程的线程ID。

  该函数返回新创建线程的句柄,在线程结束后需要使用CloseHandle()函数关闭该句柄以便释放资源,我们来写一个简单的CreateTheadTesi例子,代码如下:

   从该代码的意图来看,主线程会打印一行“mam”,而创建的线程会打印一行ThreadPrioc”。编译运行,查看一下运行结果,如图3-23所示。

  跟我们想要的结果不一样,这是怎么回事呢?每个线程都有自己的CPU时间片,当主线程创建了新线程后,它的CPU时间片并没有完,它还可以继续执行。由于主线程的代码非常少,在CPU指定的CPU时间片中主线程执行完后就退出了,主线程结束,那么意味着程序也就结束了,所以我们自己创建的线程根本就没有被执行到。如果要等我们自己创建的线程结束后才可以输出“main”的话,主线程要如何等待我们创建的线程呢?答案是使用WaitForSingleObj ect()函数,该函数的原型如下:

  参数说明如下.。

  (1) hHandle:该参数指向要等待的对象句柄。

  (2) dwMilliseconds:该参数指定等待超时的毫秒数,设置为INFINITE则表示一直等待到线程函数返回。INFINITE是系统给出的一个宏,该定义如下。

  该函数如果返回为WAIT OBJECT O,表明制定的对象已经是处于完成状态;如果返回值为WAIT TIMEOUT,则表明在指定的时间内没有完成,处于潮湿状态。若该函数调用失败,则返回WAIT_FAILED。修改上面的代码在函数的后面加入以下的代码

  添加了以后,主线程会等待我们的创建的线程结束后在执行主线程后续的代码,这样在控制台上就会分别打印了。

  Waiforsingle0bject()只能等待一个线程,可是在程序中往往要创建很多个线程来执行,那么如果需要等待若干个线程的完成状态的话,waileforsingleobject()就无能为力了,不过系统提供了其他的函数来让我们等待多个线的完成状态,该函数的定义如下。

  在使用多线程时常常需要注意很多问题。比如,多个线成同时对某一个共享资源进行操作,那么可能就会出现问题。来看一个简单的例子:

  每个线程都有一个CPU时间片,当自己的时间片运行完成后,CPU会停止陵线程的运行,并切换到其他线程去运行。当多线程同时操作一个共享资源时,这样的切换会带来隐形的问题。我们的代码比较短,在一个CPU时间片内会完成,因此可能看不出错误,为了达到出错的效果,在代码中加入Sleep(l),主动让出CPUt让CPU进行线程的切换。这里的共享资源就是那个全局变量gNum One。调用该线程函数的代码如下:

  我们创建IO个线程,每个线程都让g_Num_One进行10次自增1的操作,那么gNum One的结果应该是100。我们实际运行一下看看结果是多少,如图3-24所示。

  这个输出结果并不是找们想要的输出结果。在多线程环境中,对共享资源的操作要进行保护,在这里,我们可以使用临界区对象对该全局变量进行保护。

  临界区对象是衣蛾Critical_SECTION的数据结构,Windows操作系统使用该数据结构来进行对关键代码的保护,以确保多线程下的共享资源。对于临界区的函数有四个,这四个函数的定义分别如下:

  这四个函数的参数都是指向CRITICAL_SECTION结构体的指针,我们修改的代码如下:

  再次编译运行该代码,输出的结果为正确结果,即g_Num_One的值为100。除了临界区对象以外,对于线程的同步还有其他的方法,这里就不进行一一的介绍了。希望大家在今后开发多线程编程时,切记要注意多线程的同步问题。

  DLL编程

  DLL (Dynamic Link Library,动态链接库)是一个可以被其他应用程序调用的程序模块,其中封装了可以被调用的资源和函数。动态链接库的扩展名一般是.DLL,不过有时也可能是其他的。DLL文件也属于可执行文件,只不过它是依附于EXE文件来被执行的。一个DLL。文件可以被多个EXE文件加载。

  什么是DLL

  Windows操作系统下有非常多的DLL文件,有的是操作系统的DLL文件,有的是应用程序的DLL文件。DLL文件有什么好处呢?DLL是动态链接库,相对应的有静态链接库。动态链接库是在EXE文件运行时被加载执行的,而静态链接库是在OBJ文件进行连接时同时被保存到程序中。动态链接库可以减少可执行文件的体积,在需要的时候进入内存等很多好处。

  编写一个简单的DLL程序

  我们编写一个简单的DLL程序,并在DLL程序中添加一个导出函数。所谓导出函数,就是DLI。提供给外部EXE或其他类型的可执行文件调用的函数,当然DLL本身也可以自己进行调用。我们启动VC6来编写一个DLL程序。

  启动VC6程序,单击菜单“文件”→“新建”命令,在“Project.s”选项卡中的左边选择“Win32 Dynamic-Link Library“,在“Project name:”文本框中填写“FirstDll”,如图3-25所示。

  单击“OK”按钮,出现“Win32 Dynamic-Link Library - Srep l ofl”界面,选择“ASimpleDLL Project”项,单击“Finish”按钮。在然后出现的对话框中直接单击“OK”按钮即可。在创建好该工程后,VC6自动生成如下代码:

  DLL程序的入口函数是DlIMain(。

标签: 网站优化