Windows操作平台下CGI的实现

华中理工大学图像识别与人工智能研究所     龚建勇

实现WWW服务器与客户的交互可以有多种途径,如CGI、ISAPI、JAVA、ASP等,CGI是一种大家都比较熟悉的一种实现上述功能的有效途径,本文将就如何在Windows平台下实现CGI功能作一介绍。  

一、CGI概述
1.CGI概念
CGI即通用网关接口(Common Gateway Interface),它是一个WWW服务器主机对外服务的标准接口。一般来说,一个CGI接口的功能就是在超文本文件和服务器主机应用程序间传递信息。

2.CGI程序语言
事实上,任何一种程序语言,只要能在服务器主机上利用CGI接口来编写应用程序,都可以叫作CGI程序语言。目前最为流行的CGI程序语言有四种:C、Shell、Perl和Visual Basic,其它一些语言也有许多人在用,如TCL、Frotran及AppleScript等。下文我们将介绍用C语言编写CGI的方法。

3.CGI程序的执行
CGI程序一般是个可执行程序。编译好的CGI程序一般要集中放在一个目录下。具体存放的位置随操作系统的不同而不同,例如UNIX系统下是放在cgi-bin子目录下,而在Windows操作下(注意,这里不包括NT4.0)以Webstar或Website作WWW服务器,CGI程序都放在cgi-win下。C GI程序的执行一般有两种调用方式:一种是通过URL直接调用,如:"http://gjy.sic.o.ml.or g/cgi-win/cgi.exe",在Netscape的URL栏里直接写入上述描述就可以调用该程序:另一种方式,也是主要的方式是通过交互式主页里的FORM栏调用,通常都是用户在填完一张表后按确认按钮启动CGI程序。

4.CGI工作的主要流程
CGI工作的主要流程是:1.通过HTML获取用户输入的信息;2.将用户输入的信息传给服务器主机应用程序(如数据库查询);3.将服务器处理结果通过HTML文件返回给用户。
二、Windows平台下CGI的工作原理
CGI主要的功能不外乎在服务器应用程序和客户之间传递信息。我们知道,通常CGI应用在服务器和客户端传递信息是通过输入输出流来实现的,如UNIX平台下的CGI,通过读取输入流Scanf来获取用户输入,读取输出流printf向用户输出结果。而Windows系统下CGI实现这一功能是通过文件系统来实现,即通过一个特定的输入文件来获取用户输入的信息,通过一个输出文件向用户传递结果。
这里这个输入文件是由WWW服务器生成的一个临时文件,里面包含了各种环境信息及用户输入信息,以key=value的形式给出,类似于Windows里的INI文件。下面以一个典型的该文件为例说明主要层的含义。
[CGI]
Request Protocol=HTTP/1.0
Repuest Method=POST
Executable Path=/cgi-win/cgitest.exe
Logical Path=/CGI
Physical Path=c:httpdhtdocsCGI
Server Software=Website /1.1(Shareware non-commercial License))
Server Name=gjy.sic.o.ml.org
Server Port=80
Server Admin=gjy@gjy.sic.o.ml.org
CGI Version=CGI/1.1 WIN
Remote Adress=2.02.114.2.182
[system〗
Debug Mode=No
Output File=D:hs06f12d.out
*/*=Yes
image/gif=Yes
image/x-xbitmap=Yes
image/jpeg=Yes
[Extra Headers〗
User-Agent=Mozilla/3.0 (Windows;I;32bit)
[Fotra rm Literal〗
textl=hello world
radio1=2
[Form External〗
field300chars=D:hs06f12d.000 300
fieldwith l inebreaks=D:hs06f12d.001 43
[Frm Huge〗
fiel d230
K=d:s06f12d.002 276920

下面将主要各栏的意义解释如下:
1.CGI
这一栏包含大部分的CGI信息,如要求的协议Request Protocol,提取用户信息的方式Me thod,执行程序的路径Exectable,服务器软件(Server Software),服务器名(Server Name), 端口号(server Port)等。
2.Accept栏
这一栏包含客户端可以接受的文件类型。
3.System栏
这一栏包含一些Widnows CGI使用的特别项目如输出(Output File),Content File
4.Form Literal资料栏
如果用户端的要求是使用POST方式,用户输入的资料会被解码后以key=value的方式放在这一栏里,这里的key即输入栏的变量名,value是用户输入的信息。
5.Form External栏
如果解码后的字串长度超过254byte,或字串中含有控制字元,系统会把这些信息存在该栏指定的临时文件里并指出长度。
三、CGI应用的实现
1、开发平台
首先操作系统应为Windows操作系统。我们用的是Windows 95。其次需要建立WWW服务器 ,我们用的是Website1.1。安装website并设置各相应目录后,将主页放入主目录中。
2、交互主页的制作
交互主页就是用户可以在主页上输入信息,而不是单纯仅供浏览的主页。一般来说,用户输入信息的形式有填写编辑框,点选无线按钮等选择框及选择下拉菜单等。下面是一个简单的例子。
<HTML>
<HEAD>
<TITLT>Form Sample</TITLE>
<HEAD>
<BODY>
<FORM ACTION="http://gjy.sic.o.ml.org/cgi/win/sample.exe"METHOD=POST>
A Text Input:<INPUT TYPE="text"><BR>
A Radio Input:
1<INPUT TYPE="radio"NAME="radiol"VALUE=1>
2<input type="radio" NAME="radiol"VALUE=2>
3<INPUT TYPE="radio"NAME="radiol"VALUE=3><BR>
OK Input:<INPUT TYPE="submit"VALUE="OK">
<INPUT TYPE="reset" VALUE="reset">
</FORM>
</BODY>
</HTML>
将上述标记存成HTML文件,该主页作好后,将其放在website设定的主目录中。然后用浏览器观看,将会看到结果。
其中FORM:说明一个交互式输入组合框。
ACTION:指定该FORM对应的CGI程序的URL。
INPUT:输入的形式,text为编辑框,radio为无线按钮等。
INPUT里的第一个参数TYPE表明输入的类型,第二个参数NAME表示该输入框代表的变量名。用户在此输入的信息将存在这个变量里。CGI程序也是通过这些变量提取用户输入信息。
3.CGI程序编写
前面我们说过,原则上CGI程序可以用任何语言编写,但在windows平台下,一般只有 C和VB是适合的。因为CGI程序是后台执行的back-end程序,C和VB都可以方便地生成无界面的 back-end程序。考虑到效率,我们用C语言来作CGI程序的编写。
Web server会通过Winexec来执行back-end,命令格式如下:
back-end-exe cgi-data-file content-file outputfile url-args
back-end-exe是CGI程序的完整路径,cgi-data-file是服务器产生的CGI资料文件,值为完整的路径:content-file是客户端发出的请求,其信息放在临时目录下的文件里,值为完整的路径;output-file是程序处理的结果,值为完整路径。url-args为URL中?后的参数。
服务器生成的临时输入文件在客户请求结束后即自动消失,所以CGI程序的第一步就是截取该文件,然后将所需的信息提取出来并过滤,最后得到的就是用户输入,然后对其作相应处理,如写入留言板或转交给数据库应用程序作查询处理。
值得一提的是VC和VB都提供了专用的从INI文件中提取信息的函数,即GetPrivateprofi leString()。
4、服务器应用程序
这一部分程序的功能就是对CGI转来的用户信息进行处理,处理的结果交给CGI程序的ou tput-file,通过CGI将其转交给用户。典型的服务器应用程序以数据库查询为代表。   

Read: 835

【转】RouterOS 下载地址 教程 资料

http://jjlong.zx3721.com/ros/ros2.96.rar (教程一)

http://jjlong.zx3721.com/ros/Mikrotik2.9.6详细教程.rar (教程二)

http://jjlong.zx3721.com/ros/ghost版ros2.97.rar


http://jjlong.zx3721.com/ros/firewall.rar

我用的防火墙也不知道是那个版本了网上传的版本太多了

http://jjlong.zx3721.com/ros/VPNJX.rar 基于VPN连接方式的虚拟双线教程

http://jjlong.zx3721.com/ros/子网计算器.rar   子网计算器做ROS的时候算不到的用下这个试试。。

自己的空间~   不可能不能下的。。如果顶的多还会更新的~

http://jjlong.zx3721.com/ros/RouterOS中文使用说明书.rar

http://jjlong.zx3721.com/ros/RouterOS中文手册1.doc

http://jjlong.zx3721.com/ros/RouterOS中文手册2.doc

ROShttp://jjlong.zx3721.com/ros/固定光纤IP+动态IPADSL实现双线负载.rar

http://jjlong.zx3721.com/ros/防火墙脚本.exe

http://jjlong.zx3721.com/ros/vpn的配置.exe

http://jjlong.zx3721.com/ros/PPPoE服务器配置.exe


http://jjlong.zx3721.com/ros/NAT配置.exe

http://jjlong.zx3721.com/ros/DHCP设置.exe

下面是几个ros版本

http://jjlong.zx3721.com/ros/Mikrotik-2.9.6.iso.rar

http://jjlong.zx3721.com/ros/mikrotik-2.9.26.iso.rar

http://jjlong.zx3721.com/ros/mikrotik-2.9.27.iso.rar

http://jjlong.zx3721.com/ros/mikrotik-2.9.7.iso.rar

http://jjlong.zx3721.com/ros/mikrotik-2.8.18.iso.rar

由于服务器问题ISO的文件不能下。。现在在ISO后加个RAR。下完后把RAR扩展名删了改成ISO。。抱歉。。

感谢原来贴主的大公无私。

原来的贴处:

http://bbs.txwm.com/dispbbs.asp?BoardID=190&ID=529866

Read: 781

病毒的本质

by Koms Bomb/CVC.GA

本文的目的,就是想说明写病毒其实是多么的容易。
一,病毒神话
我们先看看让大家“兴奋”、“激动”的病毒技术。
1,CIH,简直成了神话,CIH的作者也成为无数人的偶像。这神话,是AVer吹出来的,这偶像,只是中国人盲目崇拜的延续,外国人没这臭毛病。看看这个病毒里究竟有什么?1K多点的代码,再厉害能有什么?我觉得这个病毒唯一比较好的地方就是它的非常优化。这种优化,其实说白了也不难,只要稍微有些汇编编程经验,再加上一部指令手册(至少要有指令长度),再有足够的耐心,都可以做到。其它还有什么?进入Ring 0?早已有之,再说稍微懂些保护模式的人都知道利用GDT/IDT/LDT是可以进行特权转换的,常识问题。感染后文件大小不变?稍微了解PE格式的人都知道PE里有多少空地。使用VxD技术?Flash BIOS?只要能找到这些资料,有什么难的?VxD无非就是玩一些int 20h,Flash BIOS无非是写一些端口。调用中断,写端口,谁不会?但怎么调中断,写哪些端口,则需要知道相关的资料。我就写不出VxD为基础的病毒,我也不会Flash BIOS,因为我没有这方面的资料。
再看看时下“热门”的病毒技术。
2,跨平台病毒。跨Windows和Linux,同样是X86指令,有什么特殊的吗?基本上这种跨平台病毒,公用一部分变形之类的和OS无关的代码,而和OS有关的则是分开的。比如在Windows上就调用API,而在Linux上则用int 80h(没搞错中断号吧,我对Linux没什么研究)。其实就相当于两个病毒的合并。只要有了相关系统调用的资料,这种病毒显然很容易。其实写出针对不同CPU的病毒也不难,只要针对不同的文件感染不同的代码,然后把另外OS或CPU的代码做为数据不执行就行。顺便说一句,跨平台有什么用?有几个Linux机器上有PE文件的?又有几个Windows上有ELF文件的?
3,.net病毒。有什么新鲜吗?Java不是也有病毒吗?只要掌握MSIL汇编,写一个真正的.net病毒并不难。值得一提的是前一阵炒得火热的两个.net病毒其实根本难登大雅之堂。29A的那个是用X86汇编写的,稍微有点头脑的都知道,.net是架构在MSIL上的,是一种中间语言,X86汇编根本就和.net无关。我没仔细看这个病毒的代码,但我估计无非是对EPE格式有一些不同于PE的操作,总体来说,还是PE病毒。那个“17岁天才少女”的C#病毒,算是.net病毒吧,但值得一吹吗?你,说的就是你,去看看C#语法,再略微看一些.net framework,相信你也可以用C#写出一个可以把自己通过邮件发出去的程序,这就是时髦的.net病毒。
4,metamorphism。简直被AVer吹上天了。这东西说白了其实就是对自身代码进行重新编码。过程是,反汇编自己的代码,重新改写(比如mov eax,esi可以写成push esi;pop eax等等),插入垃圾代码。当然最好还要在下一代中删除垃圾代码。怎样判断垃圾代码?显然看到一条指令的目的操作数在以后没有被当作源操作数,就可以认为是垃圾。这东西玩弄的是机器代码技巧,只要在手边摆一部Intel的指令手册,再加上足够的耐心,想对代码重新编码,不难。但写这东西确实很繁琐,可以说是非常繁琐,写出的代码也很大,没有实用价值。以后有空我可能会重新考虑一个semi-metamorphism,也不难,但力求小,如果engine超过8K,我将放弃。

二,病毒的本质
大家之所以觉得病毒神秘,其实主要是因为大家没有对病毒没有了解,AVer又在大肆鼓吹(这种现象在中国尤为严重)。病毒到底是什么?
病毒是一段程序,它与普通程序的不同是它会复制自己,这是最主要的不同。复制有很多方法,直接copy,通过网络,插入别的可执行文件内部,等等,但这些方法的实现也是一段程序,并无特殊之处。
其实写病毒和写普通程序一样,不需要太高的智商。我为什么这么说?编程只是技术,这种技术所需要的基础知识是很浅的,可以说是非常浅,而且没有阶梯性。如果你要做物理学家,则一定要先学会中学物理,否则你就学不会大学物理。但如果你要做汇编程序员(听起来很高深的样子),你不必先学Basic,再学Pascal和C,最后学汇编,你完全可以从一开始就学汇编,完全没有问题。很多人以自己了解系统核心而自称高手,但这哪里高了?可能他花了三个月发现的一个系统核心的秘密,只是M$的一个程序员用一个小时写出来的。这一点也造就了很多“天才少年/少女”的神话,十几岁的小孩可以写出很好的程序或者病毒,让大家觉得真是天才。其实我敢肯定一个10岁的小孩对编程知识的理解能力不会比我差,甚至可能因为年龄小记忆力好而比我强,大家认为我智商有问题?也许吧,但小孩一样比你强。中国人对电脑界的“天才少年”总是津津乐道,其实只是愚蠢无知的表现。编程不需要智商,但需要资料,如果你不掌握一定数量的Win32 API,你很难写出像样的Win32程序。其实程序这东西还和物质力量有关,为什么外国有很多编程方面的“天才少年”?因为他们智商高?显然不是,中国人的智商至少不输于西洋人。主要是因为他们比较富裕,可以很早就接触电脑,并且有钱上网,上网当然可以获得许多有用的资料。靠,说到这里我不得不发牢骚,我正式开始能够经常接触电脑还是在2000年大学毕业以后。我在十几岁的时候,买本书都没钱,更别提电脑了,现在平时在家还是拿猫上网。
我说了这么多,其实一个结论就是,写病毒和写普通程序一样,不需要智商(当然智商也不能太低,如果智商低于80,除非真的是另类天才,否则还是别玩程序的好),但需要资料,这间接的需要物质力量。
所以说,病毒并不神秘,一点都不神秘,而且不难,可以说是相当简单,因为他们通常很小,最大也就几十K。我现在逐渐明白,哪怕是用Delphi这样易用的工具,开发大型程序,也是比较复杂比较困难的事,要有完善的设想,科学的模块划分,等等。
病毒虽然简单,但也有很好玩的地方,因为它的本质就是复制隐藏自己。如何更快的复制传播自己,如何隐蔽自己使得用户不容易发现病毒的踪影,是我的主要研究目标。这种目标显然违背了某些卫道士的“XX精神”(他们鼓吹的是黑客精神,但我不会黑,只会毒,是毒客而不是黑客),但这正是病毒的本质,如果脱离这些本质,那病毒也就不称之为病毒,而且也没必要研究病毒,有那精力不如做点应用软件。这种卫道士,鼓吹的是那种研究“新”、“高深”技术来写病毒的人,这样的病毒也就是AVer说的“学术病毒”、“概念病毒”。但这种病毒有什么用?除了给AVer吹牛的机会以外,没什么用。除了CIH以外,我上面说的基本都是这种病毒。大家可以看看,哪里有新技术?哪里有高深技术?
值得注意的是网上颇为流行江海客的那篇老文章“后英雄时代的AVER与VXER”,而且许多人都认为“受益菲浅”,为了不误导大家,我还是说两句。这篇文章总体来说就是在胡说八道。“AV企业的一般工程师,玩起softice的手法,远没有cracker熟练”?我确实不知道AVer是不是熟悉SI,但这句话有两个大问题,1),玩SI熟练就能代表水平?那好,我从现在开始天天玩SI,一个月后精通SI的各个功能,那我是不是成大师了?这简直是不合人类逻辑。 2),研究病毒一定要用SI?我调试病毒主要用VC,SI只是辅助,非常方便。“想象反病毒公司那些呆头呆脑的样本分析工程师,“**,原来病毒也可以这样编” 的惊呼,然后心情紧张的把样本交给公司中的前辈高人寻求指点他们已经心满意足。”更是胡说八道,有谁相信Symantec的在Nimda,Klez,BugBear爆发几个小时内就拿出解决方案的工程师是呆头呆脑的?”而是用了类似社交工程或者心理学一类的方法“,”当然,那些资深的VXER对此是不耻的,他们从不以自己的病毒传播如何广泛为荣誉,这些人的品性有些类似老牌黑客的绅士风范,这些人中确实也有我很欣赏的“,不是每个人都能做出成功的social engine的,好的S/E会得到全世界人的赞叹(套用卫道士的话,全世界人惊呼,”**,这他都能想得到“),一个人只要有耐心,可以对系统核心钻研很深,这没什么神秘,但好的S/E却需要灵光闪现。不能广泛传播的病毒显然不是好的病毒,无论它的技术如何高超(而且基本如我所说,并没有什么高超的技术),而且病毒和黑客不一样,黑客黑网站,真正有道德的不应该搞破坏,但他有能力可以破上10000个网站。有道德的病毒也不应该搞破坏,但有能力的病毒可以感染1000万台机器,这没什么不对,这是病毒应该做的,当然有能力感染不一定真的感染,不一定真的释放出去,但验证感染能力通常需要实战。还有就是他也是我说的那种卫道士,鼓吹那种黑客精神,其实如果大家都遵循他那种精神,那么结局就是大家整天都在研究不切实际的技术,根本就没有实用品出来。总体说,一个不懂得病毒本质,对病毒没有很深了解,没有写过病毒的人,不配写这种文章。
我一直对知道现在还在奉CIH和其它病毒为神明的无知之人感到气愤,在中国这种人太多了,所以就写了这篇文章,因为气愤,所以言语有不当之处,还望大家见谅,如果你对我感到气愤,可以理论,但不要骂人。

欢迎转载,请注明 by Koms Bomb/CVC.GA。
又:我发现深圳之窗网站曾从CSDN转了我的一篇文章,但去掉了所有作者信息,我给他们发信叫他们改正,也没有回音。这种行为是极度卑鄙无耻的,如果引起众怒将会玩火自焚!

Read: 624

向其他进程注入代码的三种方法[1]

     本文章翻译自Robet Kuster的Three Ways to Inject Your Code into Another Process一文,原版地址见下面。本文章版权归原作者所有。
     如果转载该译文,请保证文章的完整性,并注明来自www.farproc.com
袁晓辉   
2005/5/20

原版地址:http://www.codeproject.com/threads/winspy.asp?df=100&forumid=16291&select=1025152&msg=1025152

下载整个压缩包
下载WinSpy

作者:Robert Kuster
翻译:袁晓辉 (www.farproc.com  hyzs@sina.com)
摘要:如何向其他线程的地址空间中注入代码并在这个线程的上下文中执行之。

目录:
●导言
●Windows 钩子(Hooks)
●CreateRemoteThread 和LoadLibrary 技术
○进程间通讯
●CreateRemoteThread 和 WriteProcessmemory 技术
○如何使用该技术子类(SubClass)其他进程中的控件
○什么情况下适合使用该技术
●写在最后的话
●附录
●参考
●文章历史

导言:
     我们在Code project(www.codeproject.com)上可以找到许多密码间谍程序(译者注:那些可以看到别的程序中密码框内容的软件),他们都依赖于Windows钩子技术。要实现这个还有其他的方法吗?有!但是,首先,让我们简单回顾一下我们要实现的目标,以便你能弄清楚我在说什么。
要读取一个控件的内容,不管它是否属于你自己的程序,一般来说需要发送 WM_GETTEXT 消息到那个控件。这对edit控件也有效,但是有一种情况例外。如果这个edit控件属于其他进程并且具有 ES_PASSWORD 风格的话,这种方法就不会成功。只有“拥有(OWNS)”这个密码控件的进程才可以用 WM_GETTEXT 取得它的内容。所以,我们的问题就是:如何让下面这句代码在其他进程的地址空间中运行起来:
::SendMessage( hPwdEdit, WM_GETTEXT, nMaxChars, psBuffer );

一般来说,这个问题有三种可能的解决方案:
1. 把你的代码放到一个DLL中;然后用 windows 钩子把它映射到远程进程。
2. 把你的代码放到一个DLL中;然后用 CreateRemoteThread 和 LoadLibrary 把它映射到远程进程。
3. 不用DLL,直接复制你的代码到远程进程(使用WriteProcessMemory)并且用CreateRemoteThread执行之。在这里有详细的说明:

Ⅰ. Windows 钩子

示例程序:HookSpy 和 HookInjEx

Windows钩子的主要作用就是监视某个线程的消息流动。一般可分为:
1. 局部钩子,只监视你自己进程中某个线程的消息流动。
2. 远程钩子,又可以分为:
a. 特定线程的,监视别的进程中某个线程的消息;
b. 系统级的,监视整个系统中正在运行的所有线程的消息。

     如果被挂钩(监视)的线程属于别的进程(情况2a和2b),你的钩子过程(hook procedure)必须放在一个动态连接库(DLL)中。系统把这包含了钩子过程的DLL映射到被挂钩的线程的地址空间。Windows会映射整个DLL而不仅仅是你的钩子过程。这就是为什么windows钩子可以用来向其他线程的地址空间注入代码的原因了。

     在这里我不想深入讨论钩子的问题(请看MSDN中对SetWindowsHookEx的说明),让我再告诉你两个文档中找不到的诀窍,可能会有用:
1. 当SetWindowHookEx调用成功后,系统会自动映射这个DLL到被挂钩的线程,但并不是立即映射。因为所有的Windows钩子都是基于消息的,直到一个适当的事件发生后这个DLL才被映射。比如:
如果你安装了一个监视所有未排队的(nonqueued)的消息的钩子(WH_CALLWNDPROC),只有一个消息发送到被挂钩线程(的某个窗口)后这个DLL才被映射。也就是说,如果在消息发送到被挂钩线程之前调用了UnhookWindowsHookEx那么这个DLL就永远不会被映射到该线程(虽然SetWindowsHookEx调用成功了)。为了强制映射,可以在调用SetWindowsHookEx后立即发送一个适当的消息到那个线程。

     同理,调用UnhookWindowsHookEx之后,只有特定的事件发生后DLL才真正地从被挂钩线程卸载。

2. 当你安装了钩子后,系统的性能会受到影响(特别是系统级的钩子)。然而如果你只是使用的特定线程的钩子来映射DLL而且不截获如何消息的话,这个缺陷也可以轻易地避免。看一下下面的代码片段:
BOOL APIENTRY DllMain( HANDLE hModule,
                        DWORD   ul_reason_for_call,
                        LPVOID lpReserved )
{
     if( ul_reason_for_call == DLL_PROCESS_ATTACH )
     {
         //用 LoadLibrary增加引用次数
         char lib_name[MAX_PATH];
         ::GetModuleFileName( hModule, lib_name, MAX_PATH );
         ::LoadLibrary( lib_name );

         // 安全卸载钩子
         ::UnhookWindowsHookEx( g_hHook );
     }    
     return TRUE;
}

     我们来看一下。首先,我们用钩子映射这个DLL到远程线程,然后,在DLL被真正映射进去后,我们立即卸载挂钩(unhook)。一般来说当第一个消息到达被挂钩线程后,这DLL会被卸载,然而我们通过LoadLibrary来增加这个DLL的引用次数,避免了DLL被卸载。

     剩下的问题是:使用完毕后如何卸载这个DLL?UnhookWindowsHookEx不行了,因为我们已经对那个线程取消挂钩(unhook)了。你可以这么做:
○在你想要卸载这个DLL之前再安装一个钩子;
○发送一个“特殊”的消息到远程线程;
○在你的新钩子的钩子过程(hook procedure)中截获该消息,调用FreeLibrary 和 (译者注:对新钩子调用)UnhookwindowsHookEx。
现在,钩子只在映射DLL到远程进程和从远程进程卸载DLL时使用,对被挂钩线程的性能没有影响。也就是说,我们找到了一种(相比第二部分讨论的LoadLibrary技术)WinNT和Win9x下都可以使用的,不影响目的进程性能的DLL映射机制。

     但是,我们应该在何种情况下使用该技巧呢?通常是在DLL需要在远程进程中驻留较长时间(比如你要子类[subclass]另一个进程中的控件)并且你不想过于干涉目的进程时比较适合使用这种技巧。我在HookSpy中并没有使用它,因为那个DLL只是短暂地注入一段时间――只要能取得密码就足够了。我在另一个例子HookInjEx中演示了这种方法。HookInjEx把一个DLL映射进“explorer.exe”(当然,最后又从其中卸载),子类了其中的开始按钮,更确切地说我是把开始按钮的鼠标左右键点击事件颠倒了一下。

     你可以在本文章的开头部分找到HookSpy和HookInjEx及其源代码的下载包链接。

Ⅱ. CreateRemoteThread 和 LoadLibrary 技术
示例程序:LibSpy
     通常,任何进程都可以通过LoadLibrary动态地加载DLL,但是我们如何强制一个外部进程调用该函数呢?答案是CreateRemoteThread。
让我们先来看看LoadLibrary和FreeLibrary的函数声明:

HINSTANCE LoadLibrary(
   LPCTSTR lpLibFileName    // address of filename of library module
);

BOOL FreeLibrary(
   HMODULE hLibModule       // handle to loaded library module
);

再和CreateRemoteThread的线程过程(thread procedure)ThreadProc比较一下:
DWORD WINAPI ThreadProc(
   LPVOID lpParameter    // thread data
);

     你会发现所有的函数都有同样的调用约定(calling convention)、都接受一个32位的参数并且返回值类型的大小也一样。也就是说,我们可以把LoadLibrary/FreeLibrary的指针作为参数传递给CrateRemoteThread。

     然而,还有两个问题(参考下面对CreateRemoteThread的说明)

     1. 传递给ThreadProc的lpStartAddress 参数必须为远程进程中的线程过程的起始地址。
     2. 如果把ThreadProc的lpParameter参数当做一个普通的32位整数(FreeLibrary把它当做HMODULE)那么没有如何问题,但是如果把它当做一个指针(LoadLibrary把它当做一个char*),它就必须指向远程进程中的内存数据。

     第一个问题其实已经迎刃而解了,因为LoadLibrary和FreeLibrary都是存在于kernel32.dll中的函数,而kernel32可以保证任何“正常”进程中都存在,且其加载地址都是一样的。(参看附录A)于是LoadLibrary/FreeLibrary在任何进程中的地址都是一样的,这就保证了传递给远程进程的指针是个有效的指针。

     第二个问题也很简单:把DLL的文件名(LodLibrary的参数)用WriteProcessMemory复制到远程进程。

     所以,使用CreateRemoteThread和LoadLibrary技术的步骤如下:
     1. 得到远程进程的HANDLE(使用OpenProcess)。
     2. 在远程进程中为DLL文件名分配内存(VirtualAllocEx)。
     3. 把DLL的文件名(全路径)写到分配的内存中(WriteProcessMemory)
     4. 使用CreateRemoteThread和LoadLibrary把你的DLL映射近远程进程。
     5. 等待远程线程结束(WaitForSingleObject),即等待LoadLibrary返回。也就是说当我们的DllMain(是以DLL_PROCESS_ATTACH为参数调用的)返回时远程线程也就立即结束了。
     6. 取回远程线程的结束码(GetExitCodeThtread),即LoadLibrary的返回值――我们DLL加载后的基地址(HMODULE)。
     7. 释放第2步分配的内存(VirtualFreeEx)。
     8. 用CreateRemoteThread和FreeLibrary把DLL从远程进程中卸载。调用时传递第6步取得的HMODULE给FreeLibrary(通过CreateRemoteThread的lpParameter参数)。
     9. 等待线程的结束(WaitSingleObject)。

     同时,别忘了在最后关闭所有的句柄:第4、8步得到的线程句柄,第1步得到的远程进程句柄。

     现在我们看看LibSpy的部分代码,分析一下以上的步骤是任何实现的。为了简单起见,没有包含错误处理和支持Unicode的代码。
HANDLE hThread;
char     szLibPath[_MAX_PATH];   // “LibSpy.dll”的文件名
                                // (包含全路径!);
void*    pLibRemote;              // szLibPath 将要复制到地址
DWORD    hLibModule;    //已加载的DLL的基地址(HMODULE);
HMODULE hKernel32 = ::GetModuleHandle(“Kernel32”);

//初始化 szLibPath
//…

// 1. 在远程进程中为szLibPath 分配内存
// 2. 写szLibPath到分配的内存
pLibRemote = ::VirtualAllocEx( hProcess, NULL, sizeof(szLibPath),
                                MEM_COMMIT, PAGE_READWRITE );
::WriteProcessMemory( hProcess, pLibRemote, (void*)szLibPath,
                       sizeof(szLibPath), NULL );

// 加载 “LibSpy.dll” 到远程进程
// (通过 CreateRemoteThread & LoadLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
             (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                        “LoadLibraryA” ),
              pLibRemote, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

//取得DLL的基地址
::GetExitCodeThread( hThread, &hLibModule );

//扫尾工作
::CloseHandle( hThread );
::VirtualFreeEx( hProcess, pLibRemote, sizeof(szLibPath), MEM_RELEASE );

我们放在DllMain中的真正要注入的代码(比如为SendMessage)现在已经被执行了(由于DLL_PROCESS_ATTACH),所以现在可以把DLL从目的进程中卸载了。

// 从目标进程卸载LibSpu.dll
// (通过 CreateRemoteThread & FreeLibrary)
hThread = ::CreateRemoteThread( hProcess, NULL, 0,
             (LPTHREAD_START_ROUTINE) ::GetProcAddress( hKernel32,
                                        “FreeLibrary” ),
             (void*)hLibModule, 0, NULL );
::WaitForSingleObject( hThread, INFINITE );

// 扫尾工作
::CloseHandle( hThread );

进程间通讯
     到目前为止,我们仅仅讨论了任何向远程进程注入DLL,然而,在多数情况下被注入的DLL需要和你的程序以某种方式通讯(记住,那个DLL是被映射到远程进程中的,而不是在你的本地程序中!)。以密码间谍为例:那个DLL需要知道包含了密码的的控件的句柄。很明显,这个句柄是不能在编译期间硬编码(hardcoded)进去的。同样,当DLL得到密码后,它也需要把密码发回我们的程序。

     幸运的是,这个问题有很多种解决方案:文件映射(Mapping),WM_COPYDATA,剪贴板等。还有一种非常便利的方法#pragma data_seg。这里我不想深入讨论因为它们在MSDN(看一下Interprocess Communications部分)或其他资料中都有很好的说明。我在LibSpy中使用的是#pragma data_seg。

     你可以在本文章的开头找到LibSpy及源代码的下载链接。

Ⅲ.CreateRemoteThread和WriteProcessMemory技术
示例程序:WinSpy

     另一种注入代码到其他进程地址空间的方法是使用WriteProcessMemory API。这次你不用编写一个独立的DLL而是直接复制你的代码到远程进程(WriteProcessMemory)并用CreateRemoteThread执行之。

     让我们看一下CreateRemoteThread的声明:
HANDLE CreateRemoteThread(
   HANDLE hProcess,         // handle to process to create thread in
   LPSECURITY_ATTRIBUTES lpThreadAttributes,   // pointer to security
                                              // attributes
   DWORD dwStackSize,       // initial thread stack size, in bytes
   LPTHREAD_START_ROUTINE lpStartAddress,      // pointer to thread
                                              // function
   LPVOID lpParameter,      // argument for new thread
   DWORD dwCreationFlags,   // creation flags
   LPDWORD lpThreadId       // pointer to returned thread identifier
);

和CreateThread相比,有一下不同:

●增加了hProcess参数。这是要在其中创建线程的进程的句柄。
●CreateRemoteThread的lpStartAddress参数必须指向远程进程的地址空间中的函数。这个函数必须存在于远程进程中,所以我们不能简单地传递一个本地ThreadFucn的地址,我们必须把代码复制到远程进程。
●同样,lpParameter参数指向的数据也必须存在于远程进程中,我们也必须复制它。

     现在,我们总结一下使用该技术的步骤:

     1. 得到远程进程的HANDLE(OpenProcess)。
     2. 在远程进程中为要注入的数据分配内存(VirtualAllocEx)、
     3. 把初始化后的INJDATA结构复制到分配的内存中(WriteProcessMemory)。
     4. 在远程进程中为要注入的数据分配内存(VirtualAllocEx)。
     5. 把ThreadFunc复制到分配的内存中(WriteProcessMemory)。
     6. 用CreateRemoteThread启动远程的ThreadFunc。
     7. 等待远程线程的结束(WaitForSingleObject)。
     8. 从远程进程取回指执行结果(ReadProcessMemory 或 GetExitCodeThread)。
     9. 释放第2、4步分配的内存(VirtualFreeEx)。
     10. 关闭第6、1步打开打开的句柄。

     另外,编写ThreadFunc时必须遵守以下规则:
     1. ThreadFunc不能调用除kernel32.dll和user32.dll之外动态库中的API函数。只有kernel32.dll和user32.dll(如果被加载)可以保证在本地和目的进程中的加载地址是一样的。(注意:user32并不一定被所有的Win32进程加载!)参考附录A。如果你需要调用其他库中的函数,在注入的代码中使用LoadLibrary和GetProcessAddress强制加载。如果由于某种原因,你需要的动态库已经被映射进了目的进程,你也可以使用GetMoudleHandle代替LoadLibrary。同样,如果你想在ThreadFunc中调用你自己的函数,那么就分别复制这些函数到远程进程并通过INJDATA把地址提供给ThreadFunc。
     2. 不要使用static字符串。把所有的字符串提供INJDATA传递。为什么?编译器会把所有的静态字符串放在可执行文件的“.data”段,而仅仅在代码中保留它们的引用(即指针)。这样,远程进程中的ThreadFunc就会执行不存在的内存数据(至少没有在它自己的内存空间中)。
     3. 去掉编译器的/GZ编译选项。这个选项是默认的(看附录B)。
     4. 要么把ThreadFunc和AfterThreadFunc声明为static,要么关闭编译器的“增量连接(incremental linking)”(看附录C)。
     5. ThreadFunc中的局部变量总大小必须小于4k字节(看附录D)。注意,当degug编译时,这4k中大约有10个字节会被事先占用。
     6. 如果有多于3个switch分支的case语句,必须像下面这样分割开,或用if-else if代替:

switch( expression ) {
     case constant1: statement1; goto END;
     case constant2: statement2; goto END;
     case constant3: statement2; goto END;
}
switch( expression ) {
     case constant4: statement4; goto END;
     case constant5: statement5; goto END;
     case constant6: statement6; goto END;
}
END:
(参考附录E)

     如果你不按照这些游戏规则玩的话,你注定会使目的进程挂掉!记住,不要妄想远程进程中的任何数据会和你本地进程中的数据存放在相同内存地址!(参看附录F)
(原话如此:You will almost certainly crash the target process if you don’t play by those rules. Just remember: Don’t assume anything in the target process is at the same address as it is in your process.)

GetWindowTextRemote(A/W)

     所有取得远程edit中文本的工作都被封装进这个函数:GetWindowTextRemote(A/W):
int GetWindowTextRemoteA( HANDLE hProcess, HWND hWnd, LPSTR   lpString );
int GetWindowTextRemoteW( HANDLE hProcess, HWND hWnd, LPWSTR lpString );

参数:
hProcess
目的edit所在的进程句柄
hWnd
目的edit的句柄
lpString
接收字符串的缓冲

返回值:
成功复制的字符数。

     让我们看以下它的部分代码,特别是注入的数据和代码。为了简单起见,没有包含支持Unicode的代码。

INJDATA

typedef LRESULT      (WINAPI *SENDMESSAGE)(HWND,UINT,WPARAM,LPARAM);

typedef struct {    
     HWND hwnd;                     // handle to edit control
     SENDMESSAGE   fnSendMessage;    // pointer to user32!SendMessageA

     char psText[128];     // buffer that is to receive the password
} INJDATA;

     INJDATA是要注入远程进程的数据。在把它的地址传递给SendMessageA之前,我们要先对它进行初始化。幸运的是unse32.dll在所有的进程中(如果被映射)总是被映射到相同的地址,所以SendMessageA的地址也总是相同的,这也保证了传递给远程进程的地址是有效的。

ThreadFunc

static DWORD WINAPI ThreadFunc (INJDATA *pData)
{
     pData->fnSendMessage( pData->hwnd, WM_GETTEXT,     // 得到密码
                           sizeof(pData->psText),
                           (LPARAM)pData->psText );  
     return 0;
}

// This function marks the memory address after ThreadFunc.
// int cbCodeSize = (PBYTE) AfterThreadFunc – (PBYTE) ThreadFunc.
static void AfterThreadFunc (void)
{
}

ThreadFunc是远程线程实际执行的代码。
     ●注意AfterThreadFunc是如何计算ThreadFunc的代码大小的。一般地,这不是最好的办法,因为编译器会改变你的函数中代码的顺序(比如它会把ThreadFunc放在AfterThreadFunc之后)。然而,你至少可以确定在同一个工程中,比如在我们的WinSpy工程中,你函数的顺序是固定的。如果有必要,你可以使用/ORDER连接选项,或者,用反汇编工具确定ThreadFunc的大小,这个也许会更好。

如何用该技术子类(subclass)一个远程控件
示例程序:InjectEx

     让我们来讨论一个更复杂的问题:如何子类属于其他进程的一个控件?

     首先,要完成这个任务,你必须复制两个函数到远程进程:
     1. ThreadFunc,这个函数通过调用SetWindowLong API来子类远程进程中的控件,
     2. NewProc, 那个控件的新窗口过程(Window Procedure)。

     然而,最主要的问题是如何传递数据到远程的NewProc。因为NewProc是一个回调(callback)函数,它必须符合特定的要求(译者注:这里指的主要是参数个数和类型),我们不能再简单地传递一个INJDATA的指针作为它的参数。幸运的我已经找到解决这个问题的方法,而且是两个,但是都要借助于汇编语言。我一直都努力避免使用汇编,但是这一次,我们逃不掉了,没有汇编不行的。

解决方案1
看下面的图片:

不知道你是否注意到了,INJDATA紧挨着NewProc放在NewProc的前面?这样的话在编译期间NewProc就可以知道INJDATA的内存地址。更精确地说,它知道INJDATA相对于它自身地址的相对偏移,但是这并不是我们真正想要的。现在,NewProc看起来是这个样子:
static LRESULT CALLBACK NewProc(
   HWND hwnd,        // handle to window
   UINT uMsg,        // message identifier
   WPARAM wParam,    // first message parameter
   LPARAM lParam )   // second message parameter
{
     INJDATA* pData = (INJDATA*) NewProc;   // pData 指向
                                           // NewProc;
     pData–;               // 现在pData指向INJDATA;
                           // 记住,INJDATA 在远程进程中刚好位于
                           // NewProc的紧前面;

     //—————————–
     // 子类代码
     // ……..
     //—————————–

     //调用用来的的窗口过程;
     // fnOldProc (由SetWindowLong返回) 是被ThreadFunc(远程进程中的)初始化
     // 并且存储在远程进程中的INJDATA里的;
     return pData->fnCallWindowProc( pData->fnOldProc,
                                     hwnd,uMsg,wParam,lParam );
}

     然而,还有一个问题,看第一行:
INJDATA* pData = (INJDATA*) NewProc;

     pData被硬编码为我们进程中NewProc的地址,但这是不对的。因为NewProc会被复制到远程进程,那样的话,这个地址就错了。

     用C/C++没有办法解决这个问题,可以用内联的汇编来解决。看修改后的NewProc:

static LRESULT CALLBACK NewProc(
   HWND hwnd,       // handle to window
   UINT uMsg,       // message identifier
   WPARAM wParam,   // first message parameter
   LPARAM lParam ) // second message parameter
{
     // 计算INJDATA 的地址;
     // 在远程进程中,INJDATA刚好在
     //NewProc的前面;
     INJDATA* pData;
     _asm {
         call     dummy
dummy:
         pop      ecx          // <- ECX 中存放当前的EIP
         sub      ecx, 9       // <- ECX 中存放NewProc的地址
         mov      pData, ecx
     }
     pData–;

     //—————————–
     // 子类代码
     // ……..
     //—————————–

     // 调用原来的窗口过程
     return pData->fnCallWindowProc( pData->fnOldProc,
                                     hwnd,uMsg,wParam,lParam );
}

Read: 761