一个小型VC项目的开发

本文介绍了作者使用VC6.0开发一个小型项目的经历,主要包括键盘挂钩、进程隐藏、开机自动启动以及SMTP/POP3协议的应用。通过全局Hook技术实现键盘记录,使用远程线程嵌入技术实现进程隐藏,利用注册表设置实现开机自动启动,并借助第三方库完成邮件的发送和接收功能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


2004年2月到7月,我们用VC6.0开发了一个小型项目。下面整理一下主要技术,希望各位同仁批评指正。

一、键盘挂钩程序

       在此之前,我做了一个键盘记录程序。是参考《编程技巧与维护》某一期的一片文章写的。使用了全局Hook技术,程序由keyexe.exe和keydll.dll两个文件组成,功能就是简单地记录击键字符和每次被激活窗口的标题及IE地址栏内容,存放到一个文件中,后来又增加了两个功能:在密码编辑框中有字符输入的时候,把屏幕截取下来,同时提示这是密码。
         Windows的消息处理机制为了能在应用程序中监控系统的各种事件消息,提供了挂接各种反调函数(HOOK)的功能。这种挂接函数类似扩充中断驱动,系统产生的各种消息首先被送到各种挂接函数,挂接函数根据各自的功能对消息进行监视、修改和控制等,然后交换控制权或将消息传递给下一个挂接函数以致最终达到窗口函数。WINDOWS系统的这种反调函数挂接方法虽然会略加影响到系统的运行效率,但在很多场合下是非常有用的。
         挂接函数可以是用来监控所有线程消息的全局性函数,也可以是单独监控某一线程的局部性函数。如果挂接函数是局部函数,可以将它放到一个.DLL动态链接库中,也可以放在一个局部模块中;如果挂接函数是全局的,那么必须将其放在一个.DLL 动态链接库中。
        在这个程序中,挂接了三种过滤函数:WH_CBT、WH_KEYBOARD 、WH_GETMESSAGE,实现的功能分别是:记录被激活的窗口标题,如果窗口类名是IE...则用FindWindowEx得到URL;记录击键字符;标记密码框的输入。
        上面提到过,keydll.dll要于用户在密码框中有输入时把整个屏幕截取下来。这个功能的实现,我是直接去网上寻找源代码,略加修改,使抓取的位图每个像素只有一个字节的数据。
        这就是我的程序的第一个版本。
        我写程序的时候,遇到一些问题就去网上找现成的源代码,懒得自己去钻研。问题解决了也很少再去分析这些源代码的原理。以至于

        我总是怀疑自己,虽然写了这么多程序,但真正属于自己的有多少呢?

二、进程隐藏

        我要使这个键盘记录函数在Windows2000的进程管理器中不可见,于是就用到了进程隐藏技术。
        Windows98下,只要把进程注册为系统服务,就达到了进程隐藏的目的,但在Windows2000下,问题变得比较麻烦。

 1、远程线程嵌入技术
        在Windows2000下隐藏进程,一种方法是远程线程技术。远程线程技术指的是通过在另一个运行的进程中创建远程线程的方法进入那个线程的内存地址空间。我们知道,在进程中,可以通过CreateThread函数创建线程,被创建的新线程与主线程(就是进程创建时被同时自动建立的那个线程)共享地址空间以及其他的资源。但是很少有人知道,通过CreateRemoteThread也同样可以在另一个进程内创建新线程,被创建的远程线程同样可以共享远程进程(注意:是远程进程!)的地址空间,所以,实际上,我们通过创建一个远程线程,进入了远程进程的内存地址空间,也就拥有了那个远程进程相当多的权限,例如启动一个dll线程,实际上我们可以随意篡改那个进程的数据。     

这样,把我们要实现的功能放在一个.dll中,然后用一个程序把这个.dll嵌入到一个正在运行的进程中去,在这个进程中启动我们的dll,我们的程序就作为一个线程在远程进程中得以运行。

 2、在同一进程运行两个程序
        我用到了冯越编写的Remote Run Library,它使得使得任意exe都可以在其他进程中以线程运行(当然,这里说的"任意"是有条件的,下面会讲到)。
        基础知识:每一个exe都有一个缺省加载基址,一般都是0x400000。如果实际加载基址和缺省基址相同,程序中的重定位表就不需要修正(fixup),否则,就必须修正重定位表;如果一个程序没有重定位表,而且如果程序不能在缺省基址处加载,那么程序将不能运行。举个例子,Windows95的最低加载基址是0x400000,你在Windows NT上开发了一个exe,指定其加载基址为0x10000,如果连接时让连接器剥离重定位表,那么他将无法在Windows95下运行。让“所有”的程序都能在其他进程空间跑,这里,“所有”的含义是所有那些“重定位表没有被剥离”的32位pe格式的可执行程序。对于Visual C++,这包括所有Debug版程序和以"/FIXED:NO"选项链接的Release版程序(Project Obtion中添加)。
        我们把先加载的exe称为宿主,后加载的exe称为客户。
        对于一般的程序:
       1)绝大多数程序的加载基址都是0x400000,这样,客户exe就很难保证加载到其缺省基址。解决办法只能是修正重定位表。如果,很不幸,这个exe的重定位表被剥离,这个exe就没法在其他进程空间跑。对于Visual C++,剥离重定位表是Release版exe的缺省设置。可以在工程文件的连接选项中加入"/FIXED:NO"来防止连接器剥离重定位表。
       2)很多程序都用隐性联接调用Windows API,而只用到kernel32.dll导出API的程序很少,因此这一点也很难保证。解决办法是重填导入表(import table)。
       另外,对于有界面的程序,光修正重定位表和导入表还不够。因为他们都会直接或间接用到GetModuleHandle和LoadResource这些函数。GetModuleHandle有个特点,如果传递给他的ModuleName为NULL,则返回宿主exe的模块句柄。LoadResource也类似,如果传递给他的模块句柄为NULL,则认为是宿主exe模块,类似的API还有一些,不一一列举。客户exe调用这些API显然会得到错误的结果。因此必须截获这些API做特殊处理。
       综合上面分析,要让两个程序共享一份进程空间,要做的工作有:
       1)打开进程边界:用WriteProcessMemory向宿主进程注入代码,用CreateRemoteThread启动远程代码;
       2)在远程代码中,加载客户exe,必要时修正重定位表和填充dll导入表。
       3)截获GetModuleHandle,LoadResource等API,在客户exe以缺省参数调用时返回客户exe的模块句柄,而不是宿主句柄。
        根据以上思路,冯越写了remote.dll,导出三个函数:RemoteRunA,RemoteRunW,和RemoteCall。
 
        对于上述的第一个技术远程线程技术,我的键盘记录程序无法利用。原因是实现全局钩子的dll必须由一个exe来调用,否则钩子无法钩到全局消息。于是我用“在同一进程运行两个程序”的技术写出了第二个版本。包含四个文件:Start.exe、remote.dll、KeyExe.exe、KeyDll.dll。Start.exe开机运行,通过调用remote.dll导出的函数RemoteRunA使KeyExe.exe嵌入进程explorer.exe,实现进程KeyExe.exe的隐藏。KeyExe.exe调用KeyDll.dll实现键盘全局钩子的调用。

 3、pe文件改写
        还有一种隐藏进程的技术就是改写pe文件。但是这种方法的限制很多,原因是微软对它的核心文件都做了保护,发现其代码被改写,马上恢复。
        微软提供了一个detours库(上面的remote.dll就用到了它),在这个库中,微软还提供了一些例子,其中一个是setdll,其功能是静态地把一个dll代码绝对地加入一个exe文件中,加入后这个exe文件大小会增加几个K。
       利用VC编写被写入exe文件的dll工程时,DllMain函数前需要有以下空函数代码:
       __declspec(dllexport) VOID WINAPI MydllFunction(VOID)
       {
         return;
       }
        这样dll才能成功地被setdll写入exe文件中。
        微软提供了这样一个矛,因为它有能够阻挡这个矛的盾。
        另外一种技术,就是把我们的代码写入pe文件的空隙中去,从而不会改变pe文件的大小;或者写入其他类型的文件如图片中。好高级好遥远哦!

三、开机自动启动

        注册表中自动启动比较隐蔽的设置有:
        1.Load注册键。位置:
HKEY_CURRENT_USER/Software/Microsoft/WindowsNT/Current/Version/Windows/load
        2.Userinit注册键。位置:
HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/WindowsNT/CurrentVersion/Winlogon/Userinit。通常该注册键下面有一个userinit.exe,这个键允许指定用逗号分隔的多个程序,例如“userinit.exe,OSA.exe”(不含引号)。
        3.Explorer/Run注册键。位置:
HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/Policies/Explorer/Run
和HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/Policies/Exp lorer/Run
       4.RunServicesOnce注册键。RunServicesOnce注册键用来启动服务程序,启动时间在用户登录之前,而且先于其他通过注册键启动的程序。RunServicesOnce注册键的位置是:HKEY_CURRENT_USER/Software/Microsoft/Windows/CurrentVersion/RunServicesOnce,和HKEY_LOCAL_MACHINE/SOFTWARE/Microsoft/Windows/CurrentVersion/RunServicesOnce
        5.其他。

四、SMTP/POP3协议

        程序需要发送邮件和接收邮件,我稍微了解了一下SMTP协议和POP3协议,然后在网上找到了PJ Naughter编写的CPop3Connection类和CSMTPConnection类,直接利用它们。为使邮件附件传送完整,又增加了一个附件的CRC验证检查机制。还修改了PJ Naughter编写的程序“Demo app for CSMTPConnection MFC class”。感谢PJN!!!
 

        另外,还用到了API函数拦截,也是参考网上现有的源代码...... -_-


        纵观整个程序开发过程,最大的失误是软件工程思想用得不够。由于各方面的原因,我们边写代码边设计,很多时间都在补窟窿。

        写到这里,不想写了,等有兴趣的时候再说吧,抱歉 ^_^

 


键盘被按动的声响
淹没了我心碎的音律

就像流年匆匆而过
我们也就成了被删除的过客

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值