Profil de Guang记忆的碎片PhotosBlogListesPlus Outils Aide
27 février

转载:Web Services中RPC/Encoded 、RPC/Literal 等样式的区别(3)

  1. Document/Literal 外覆样式

该样式被微软推荐来消除因标准的Document/Literal 样式引起的缺点。虽然在WSDL1.1标准中没有对该样式进行说明,但现在许多SOAP工具都支持它。

让我们看清单9中的WSDL定义,以及清单10中相应的SOAP消息。

 

<types>

<s:schema elementFormDefault="qualified" 

targetNamespace
="http://interop.webservices.fhso.ch/docLitWrapped">

<s:element name="SendTemperature">

<s:complexType>

<s:sequence>

<s:element minOccurs="0" maxOccurs="1" name="Collection" 

type
="s0:ArrayOfTemperature"/>

</s:sequence>

</s:complexType>

</s:element>

<s:complexType name="ArrayOfTemperature">

<s:sequence>

<s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" 

  nillable
="true" 

  type
="s0:Temperature"/>

</s:sequence>

</s:complexType>

<s:complexType name="Temperature">

<s:sequence>

<s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/>

<s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/>

<s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/>

</s:sequence>

</s:complexType>

<s:element name="SendTemperatureResponse">

<s:complexType/>

</s:element>

</s:schema>

</types>

<message name="SendTemperatureSoapIn">

<part name="parameters" element="s0:SendTemperature"/>

</message>

<message name="SendTemperatureSoapOut">

<part name="parameters" element="s0:SendTemperatureResponse"/>

</message>

<portType name="TemperatureDocLitWrappedSoap">

<operation name="SendTemperature">

<input message="s0:SendTemperatureSoapIn"/>

<output message="s0:SendTemperatureSoapOut"/>

</operation>

</portType>

<binding name="TemperatureDocLitWrappedSoap" 

   type
="s0:TemperatureDocLitWrappedSoap">

<soap:binding style="document" 

   transport
="http://schemas.xmlsoap.org/soap/http"/>

<operation name="SendTemperature">

<soap:operation soapAction=

  "http://interop.webservices.fhso.ch/docLitWrapped/SendTemperature"
 

  style
="document"/>

<input>

<soap:body use="literal"/>

</input>

<output>

<soap:body use="literal"/>

</output>

</operation>

</binding>

清单

9. SendTemperatureDocument/Literal 外覆样式中的WSDL定义.

首先,注意在清单9中操作名被重新引入到WSDL文件的第一个element标签中,也注意到,在清单10中SOAP消息看起来跟RPC/Literal样式的SOAP消息类似,但是有一个细微的差别,在RPC/Literal样式SOAP消息中<soap:body> 元素的子元素<SendTemperature> 是操作名,而在Document/Literal外覆样式的SOAP消息中<SendTemperature> 是元素名,单一输入消息片断引用该元素,该样式用一种狡猾的方法把操作名重新引入到SOAP消息中。

这些就是document/Literal外覆样式的主要特征:

    • 输入消息有一单一片断
    • 该片断是一个元素
    • 该元素同操作有相同的名字
    • 该元素的复杂类型没有属性

 

<soap:Envelope 

xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 

xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" 

xmlns:xsd
="http://www.w3.org/2001/XMLSchema">

<soap:Body>

<! – the following is an xml document described in the service’s 

contract using XML schema. In this case SendTemperature may or may not 

be the name of the remote procedure being invoked by this message. 

Also, Collection may or may not be the name of the parameter. We know the 

structure of the xml document but We don’t know how the service 

is going to process it -- 
>

<SendTemperature xmlns="http://interop.webservices.fhso.ch/docLitWrapped">

<Collection>

<Temperature>

<Id>2</Id>

<Name>Station 1</Name>

<value>34.2</value>

</Temperature>

<Temperature>

<Id>56</Id>

<Name>Station 3</Name>

<value>23.6</value>

</Temperature>

</Collection>

</SendTemperature>

</soap:Body>

</soap:Envelope>

清单

10. SendTemperatureDocument/Literal 外覆样式中的WSDL消息.

下面就是该方法缺点与优点的总结:

优点:

    • 包含了所有Document/Literal样式的优点。
    • 操作名出现在SOAP消息中。

缺点:

    • 即使WSDL定义变得更加复杂,但仍然有不少缺点。
    • 如果你在web服务中重载了操作,你就不能使用该样式。

到现在为止,我们看到Document/Literal Document/Literal外覆样式相比于任何其它样式,带给我们许多的弹性和优点,但是仍存在一些有待解决的问题。

Document/Literal Document/Literal 外覆样式的局限性

假设,我们如清单11中显示的那样重载了操作。

public void SendTemperature (Temperature[] TempCollection){}

public void SendTemperature (Temperature[] TempCollection, int week){}

清单11. 重载 SendTemperature 方法.

在这种情况,就不可能使用Document/Literal外覆样式,即使WSDL规范允许有重载的方法。原因就是,当我们在一个WSDL文档中增加一个外覆样式时,你将需要一个跟操作相同名字的元素(请看清单9)。当我们想在XML Schema有两个同名称的元素时问题就出现了。因此,我们为了重载的操作的来个二选一,要么寻找非Ddocument/Literal外覆样式,要么一种RPC样式。

让我们看看我们怎么在Document/Literal样式中实现它,这儿在WSDL定义的schema 节为上述的两个web方法有了修改。

 

<types>

<s:schema elementFormDefault="qualified" 

   targetNamespace
="http://interop.webservices.fhso.ch/docLit">

<s:element name="Collection">

<s:complexType>

<s:sequence>

<s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" 

   nillable
="true" type="s0:Temperature"/>

<s:element minOccurs="0" maxOccurs="1" name="week" type="s:int"/>

</s:sequence>

</s:complexType>

</s:element>

<s:complexType name="Temperature">

<s:sequence>

<s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/>

<s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/>

<s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/>

</s:sequence>

</s:complexType>

<s:element name="SendTemperatureResponse">

<s:complexType/>

</s:element>

</s:schema>

</types>

清单

12. SendTemperatureWSDL定义的Schema 节作了更改

我们仅向集合中添加了另一个元素,所有其它的跟清单7中保持相似。有趣的是让我们看看用VS.NET wsdl.exe 实用工具生成的客户端代理。

 

Public void SendTemperature([System.Xml.Serialization.XmlElementAttribute("Protocol"

                IsNullable
=true)] Temperature[] Temperature, int week, 

                [System.Xml.Serialization.XmlIgnoreAttribute()] 
bool weekSpecified) {

this.Invoke("SendTemperature"new object[] {

   Temperature,

   week,

   weekSpecified});

}

清单

13. 为重载操作SendTemperature生成的C#代理代码

注意到有另外一个名称为weekSpecifiedBoolean型参数被自动创建,现在客户端就能用两种方法调用这个重载操作的web方法,如果一个客户端调用的方法有week参数以及 weekSpecified 的值设为false时,我们调用的是清单11中的第一个方法,还有SOAP请求也跟清单10中的一种相同。

 

SendTemperature(Temperatue_Object,7,false);

 

另一方面客户端调用的方法weekSpecified 的值被设为true时,那么它引用在清单11中的重载方法,以及现在的SOAP请求看起来就像清单14的那样,以一个XML标签来传递week 参数。

 

SendTemperature(Temperature_Object,7,true);

 

<soap:Envelope 

xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 

xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" 

xmlns:xsd
="http://www.w3.org/2001/XMLSchema">

<soap:Body>

<Collection xmlns="http://interop.webservices.fhso.ch/docLit">

<Temperatue>

<Id>2</Id>

<Name>Station 1</Name>

<value>34.2</value>

</Temperature>

<Temperature>

<Id>56</Id>

<Name>Station 3</Name>

<value>23.5</value>

</Temperature>

<week>7</week>

</Collection>

</soap:Body>

</soap:Envelope>

清单

14. 第二个重载操作的SOAP消息形式

现在我们已经看到因Document/Literal 外覆样式引起的问题怎样通过使用标准的Document/Literal样式而巧妙地解决了。

结论

Document-centricRPC 样式的Web服务提供了非常不同的应用环境,Document样式的web服务更加适合于企业到企业在Internet上的交互,开发一个Document样式的web服务也许需要比RPC样式付出更多的努力。我们看到了Document/Literal 样式和Document/Literal 外覆样式相比于其它设计样式带给我们更多弹性和优点。任何时候,你不要对已经存在的RPC样式进行接口连接,document样式的益处通常价值大于对服务进行接口连接。如果你设计web服务的主要需求是互用性,协调工作,那么RPC/Encoded样式就十分气馁了。

WS-I Basic profile 1.0不提倡使用RPC/Encoded方法,推荐使用Document/Literal方法,还有RPC/Literal是惟一允许的RPC 样式的style/use组合。许多人都认为在将来的WSI-profile版本中会屏弃RPC/Literal样式

参考

转载:Web Services中RPC/Encoded 、RPC/Literal 等样式的区别(2)

“Document/Literal外覆样式”...呵呵,译者的这个颇有创意的翻译让我迷糊了,看了半天才知道是想说"Document/Literal Wrapper"。

  1. Document/Literal样式

Document样式和上面的RPC样式最主要的不同就是,前者中客户在一个规范的XML文档中向服务器发送服务参数,而代替了后者中的一组离散的方法的参数值。这使得Document样式比RPC样式有更加松散的耦合关系。

Web服务提供者处理规范的XML文档,执行操作并向客户端作出响应,返回的也是一个规范的XML文档。在服务器对象(参数,方法调用等)和XML数据值之间并没有一种直接的映射关系。应用程序负责映射XML数据值。Document样式中SOAP消息在它的SOAP体中包含了一个或者更多的XML文档。协议并没有约束文档需要如何组织构成;这完全是在程序级处理的。另外,Document样式web服务遵循异步处理范例。

就像在清单7中显示的那样,相比RPC样式,该样式的WSDL定义有了很大的改变。

<types>

<s:schema elementFormDefault="qualified" 

     targetNamespace
="http://interop.webservices.fhso.ch/docLit">

< ! - - this element declaration describes the entire contents 

of the soap:Body in the request message.

This is a feature of document/Literal that RPC/Literal lacks - -
> 

<s:element name="Collection">

<s:complexType>

<s:sequence>

<s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" 

   nillable
="true" 

   type
="s0:Temperature"/>

</s:sequence>

</s:complexType>

</s:element>

<s:complexType name="Temperature">

<s:sequence>

<s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/>

<s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/>

<s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/>

</s:sequence>

</s:complexType>

< ! – Similarly this element declaration describes 

the contents of the soap body in the response 

message. In this case the response is empty -- 
> 

<s:element name="SendTemperatureResponse">

<s:complexType/>

</s:element>

</s:schema>

</types>

<message name="SendTemperatureSoapIn">

<part name="parameters" element="s0:Collection"/>

</message>

<message name="SendTemperatureSoapOut">

<part name="parameters" element="s0:SendTemperatureResponse"/>

</message>

<portType name="TemperatureDocLitSoap">

<operation name="SendTemperature">

<input message="s0:SendTemperatureSoapIn"/>

<output message="s0:SendTemperatureSoapOut"/>

</operation>

</portType>

<binding name="TemperatureDocLitSoap" type="s0:TemperatureDocLitSoap">

<soap:binding style="document" 

  transport
="http://schemas.xmlsoap.org/soap/http"/>

<operation name="SendTemperature">

<soap:operation soapAction=

   "http://interop.webservices.fhso.ch/documentliteral/SendTemperature"
 

style
="document"/>

<input>

<soap:body use="literal" />

</input>

<output>

<soap:body use="literal" />

</output>

</operation>

</binding>

清单

7. SendTemperature Document/Literal样式中 WSDL 定义.

注意,绑定style 的值是document以及 use 的值是literal. message这一节中,仅可能有一个<part> 元素,该元素包含了一个叫做element的属性。

这片断指出一个描述了soap体的全部内容的schema元素申明。注意,现在集合被定义为一个元素而不上一种类型。Document/Literal样式的主要特点,相比RPC/Literal样式的关键有益之处就是schema 元素申明完全描述了<soap:body>的内容。这样就意味着你只通过查看schema而不要任何附加规则就能说出消息体信息集中包含了什么。因此你就能接收schema描述一个Document/Literal消息并用它验证消息,这可是用RPC/Literal样式无法完成的。

现在让我们来看看在清单8中该样式相应的SOAP消息形式。注意,没有指定类型编码数据,还有操作名也消失了。

 

<soap:Envelope 

xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" 

xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" 

xmlns:xsd
="http://www.w3.org/2001/XMLSchema">

<soap:Body>

< -- note that the Operation name is missing in the message -- >

<Collection xmlns="http://interop.webservices.fhso.ch/docLit">

<Temperature>

<Id>2</Id>

<Name>Station 1</Name>

<value>34.2</value>

</Temperature>

<Temperature>

<Id>56</Id>

<Name>Station 3</Name>

<value>23.5</value>

</Temperature>

</Collection>

</soap:Body>

</soap:Envelope>

清单

8. SendTemperature Document/Literal 样式中SOAP 消息

下面就是对这种样式的优点和缺点的总结。

优点:

    • SOAP消息中没有类型编码信息。
    • 你总能用任何XML验证器来验证消息,在soap体中任何东西都在schema中有定义。
    • 使用document样式,规则不是那么严格,还有对XML Schema进行增强和更改时不会破坏接口。
    • 如果使用某特殊序列进行多进程调用,Document 样式可以保持应用程序的状态。
    • Document样式更加适合异步处理。
    • 许多document-messaging服务能够选择文档的DOMSAX 两种处理方式的其中一种,结果就是能最小化在内存中的处理。

缺点:

    • WSDL定义变得更加复杂。
    • SOAP消息中的操作名没有了,没有了名称,把消息分派给它的实现方法就变得困难或不可能了。

我们已经看到Document/Literal 样式消除了许多RPC/Literal样式中的缺点,另一方面,它也引入了一个新的缺点:在SOAP消息中丢失了操作名。

由于第四种样式选项,Document/Encoding样式,没有实际使用,将不对它进行描述了。而是我们将看看Document/Literal样式的扩展,即Document/Literal外覆样式。

转载:Web Services中RPC/Encoded 、RPC/Literal 等样式的区别(1)

讨厌的SOAP RPC和Document风格问题困扰了我好久......这篇貌似是一篇关于Web服务和SOAP入门的文章,但读起来感觉似懂非懂。不知道是我的理解力太差了,还是笔者翻译的问题......先存起来备查。

Web 服务最佳实践

概要

Web服务是作为一种沟通技术而被很好地制订出来,它为Internet提供最佳的互通能力。它们的标准化进程正高速地进行着,这必将引起它们会被更广泛的接受。尽管如此,从许多邮件列表、用户组和各种讨论来判断,在不同Web服务设计(Web Service Design)方法中仍然存在相当多的混乱情形。“Document/Literal” 意味着什么,而“RPC-style”又是什么,怎样使SOAP“message-style” 适合这呢?

这篇文章将会阐明和详细解释那些由Web服务标准化组定义的不同的Web服务设计方法学,阐明各种术语,着重解释它们的不同之处。在这里,我们将把精力放在如下的几个Web服务设计方法学,评估它们的优点和缺点,并在设计一种互通性Web服务中探究每一种类型得到多大程度的支持。

  1. RPC/Encoded 样式
  2. RPC/Literal 样式
  3. Document/Literal 样式
  4. Document /Literal 外覆样式

介绍

在市场上Web服务在相对存在较短的时间内,得到了巨大的认同和广泛的应用。其中一个主要原因当然是它们的那些非常早期的开放式标准,而这些标准就是市场上所有主要的Web服务推崇者所推动的;另一方面,在Web服务看起来应该像什么和他们应该如何通信这些方面,这些推崇者各有自己的偏爱。这已经导致今天的标准支持各种不同的关于web服务消息能怎样格式化和它们如何通信的方法,事实上,那些不同的通信类型是需要的。

这些描述和使用Web服务的相关标准是WSDL(Web服务描述语言),一种标准的类似XML Schema的语言,用它详细说明Web服务和简单对象访问协议(SOAP),Web服务使用的实际的沟通协议就是简单对象访问协议(SOAP)。在了解真正的设计方法学之前,让我们先阐述下在web服务领域中频繁使用到的各种术语。

沟通模式

让我们从沟通模式开始,在使用Web服务时我们应能本质地区别三种沟通方法:

  • 远程进程调用:客户端给服务提供者发送一个SOAP请求并等待一个SOAP响应(同步沟通)。
  • 发送消息: 客户端发送一个SOAP请求但不期望有SOAP响应返回(单向沟通)。
  • 异步回调: 一个客户端用上述方法中的一种调用服务。然后,双方为回叫调用交换角色。这种模式能建立在前面两种模式之上。

SOAP 协议格式化规则

现在我们转到一个Web服务的SOAP的消息(本质上是消息的<soap:body> 元素)能如何格式化,WSDL1.1 区分两种不同绑定形式(参考soap的绑定形式):RPCDocument(文档)。(译者注:RPC(消息包含参数并返回值)Document(消息包含文档)

  • RPC 样式

RPC样式指定<soap:body> 元素包含一个将被调用的web方法的名称的元素(wrapper element(封装元素))。这个元素依次为该方法的每个参数还有返回值作了记录。

  • Document 样式

如果是document 样式,就没有像在RPC样式中的wrapper元素。转而代之的是消息片断直接出现在<soap:body> 元素之下。没有任何SOAP格式化规则规定<soap:body>元素下能包含什么;它包含的是一个发送者和接收者都达成一致的XML文档。

第二种格式规则就是Use属性。这与各种类型如何在XML中显示有关,它指定使用某种编码规则对消息片段进行编码,还是使用消息的具体架构来定义片段。如下就是提供的两种选择:

  • 编码

如果use的值是encoded”, 则每个消息片段将使用类型属性来引用抽象类型。通过应用由 encodingStyle 属性所指定的编码样式,可使用这些抽象类型生成具体的消息。最常用到的SOAP编码样式是在SOAP1.1中定义的一组序列化规则,它说明了对象、结构、数组和图形对象应该如何序列化。通常,在应用程序中使用SOAP编码着重于远程进程调用和以后适合使用RPC消息样式。

  • Literal

如果use 的值是Literal 则每个片段使用 element 属性(对于简单片段)或 type 属性(对于复合片段)来引用具体架构,例如,数据根据指定的架构来序列化,这架构通常使用W3C XML架构来表述。

1总结了各种不同的web服务参数的可选项。一个重要的事实就是,每个web服务开发人员要做三个独立的决定,使用什么“沟通模式”?什么SOAP格式化“样式”?最后用到什么SOAP消息编码类型?

 

WSDL 参数

可用选项

Communication Patterns

远程进程调用或单方消息发送

Style

Document RPC

Use

Encoded Literal

1.Web服务参数。

虽然,理论上说,这些选项的任何一种组合都是可以的,但在实践中会明确偏爱某种组合而不是其它的,同时,各种标准和Web服务互用性组织(WS-I)也有某种明确的偏爱。

因此在实践中,仅Document/Literal RPC/Encoded 得到了广泛的应用,同时也被大部分平台直接支持,这些平台显示在表2中。这个表也显示了对不同的style/use组合的WS-I 一致性测试结果。

Style/Use 组合

支持的 Soap 工具

WS-I 一致性

RPC/Encoded

Microsoft, Axis 1.1

Failed

RPC/Literal

Axis 1.1

Failed

Document/Literal

Microsoft , Axis1.1

Passed

2. Web服务格式支持

由于Document/Encoded这种组合不支持现有使用的平台,所以没有测试。事实上Document/Encoded组合还没有真实的应用环境。

一个简单Web服务例子

现在我们来更详细的了解被使用和支持最多的RPC/EncodedDocument/Literalstyle/use组合。我们将利用一个叫做SendTemperatureweb方法举例来说明每一种的style/use组合,那个web方法使用的参数是一个用户自定义的复杂对象,返回一个void类型,这些在清单1中有描述。

我们选择了这么一个使用复杂数据类型例子,就是为了让各种样式之间的不同更加明显。

public void SendTemperature (Temperature[] TempCollection){}

public class Temperature 

{

   
/// <remarks/>

   
public int Id;

  
/// <remarks/>

  
public string Name;

  
/// <remarks/>

  
public System.Double Temperature;

}

清单

1. 使用C#实现的web方法SendTemperature

针对这个web方法,我们将展示各种web服务样式就它们各自的SOAP请求形式怎样在WSDL中中实现,重点讲述它们的不同之处。我们利用Microsoft VS.NETAxis SOAP 工具箱来实现。

注意,为了简单,在这篇文章中我们忽略了WSDL文件的名称空间、前缀和服务部分。清单2列出了常用的名称空间和前缀。

xmlns:http="http://schemas.xmlsoap.org/wsdl/http/" 

xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/" 

xmlns:s="http://www.w3.org/2001/XMLSchema" 

xmlns:s0="http://interop.webservices.fhso.ch/{service name}" 

xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/" 

xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/" 

xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/" 

xmlns="http://schemas.xmlsoap.org/wsdl/" 

targetNamespace="http://interop.webservices.fhso.ch/{service name}/ ”

清单

2. 名称空间和使用到的前缀
  1. RPC/Encoded 样式

实质上RPC/Encoded 是一种典型的遵循“远程进程调用”模式的样式,在这种模式中客户端发送一个同步请求给服务器来执行一次操作。SOAP请求包含了要执行的方法的名称和它携带的参数。运行web服务的的服务器把该请求转化成适当的对象,然后执行操作并向客户端做出响应反馈一个SOAP消息。在客户端,该响应被用来合成一个适当的对象并返回给客户端所需要的的信息。在RPC样式的web服务中,整个方法在WSDL文件和SOAP体中被指定,包含方法的发送参数和返回值。因此我们用这种样式会有相当紧密的耦合关系。

清单3显示在RPC/Encoded样式中SendTemperature WSDL定义。

<types>

<s:schema targetNamespace="http://interop.webservices.fhso.ch/rpcencoded">

<s:complexType name="ArrayOfTemperature">

<s:complexContent mixed="false">

<s:restriction base="soapenc:Array">

<s:attribute d7p1:arrayType="s0:Temperature[]" ref="soapenc:arrayType" 

xmlns:d7p1
="http://schemas.xmlsoap.org/wsdl/"/>

</s:restriction>

</s:complexContent>

</s:complexType>

<s:complexType name="Temperature">

<s:sequence>

<s:element minOccurs="1" maxOccurs="1" name="Id" type="s:int"/>

<s:element minOccurs="1" maxOccurs="1" name="Name" type="s:string"/>

<s:element minOccurs="1" maxOccurs="1" name="value" type="s:double"/>

</s:sequence>

</s:complexType>

</s:schema>

</types>

<message name="SendTemperatureSoapIn">

<part name="Collection" type="s0:ArrayOfTemperature"/>

</message>

<message name="SendTemperatureSoapOut"/>

<portType name="TemperatureRpcEncodedSoap">

<operation name="SendTemperature">

<input message="s0:SendTemperatureSoapIn"/>

<output message="s0:SendTemperatureSoapOut"/>

</operation>

</portType>

<binding name="TemperatureRpcEncodedSoap" type="s0:TemperatureRpcEncodedSoap">

<soap:binding style="rpc" transport="http://schemas.xmlsoap.org/soap/http"/>

<operation name="SendTemperature">

<soap:operation soapAction="http://interop.fhso.ch/soapformat/SendTemperature"/>

<input>

<soap:body use="encoded" 

      encodingStyle
="http://schemas.xmlsoap.org/soap/encoding/"/>

</input>

<output>

<soap:body use="encoded" 

      encodingStyle
="http://schemas.xmlsoap.org/soap/encoding/"/>

</output>

</operation>

</binding>

清单

3. SendTemperatureRPC/Encoded 样式中的SOAP定义

注意绑定的style的值被设为rpcuse的值是encoded’. <message> 这节中,可以有任意数目的<part> 元素,该元素包含一个type 属性,对rpc样式来说,该属性的值是惟一的。现在我们看看调用这个SendTemperature web方法会发生什么,传送的参数是包含两个元素的数组。清单4显示了SOAP消息形式的结果。

 

<soap:Envelopexmlns:

     
soap="http://schemas.xmlsoap.org/soap/envelope/" 

     xmlns:soapenc
="http://schemas.xmlsoap.org/soap/encoding/" 

     xmlns:tns
="http://interop.webservices.fhso.ch/rpcencoded" 

     xmlns:types
="http://interop.webservices.fhso.ch/rpcencoded/encodedTypes" 

     xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance" 

     xmlns:xsd
="http://www.w3.org/2001/XMLSchema">

<soap:Body soap:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">

<SendTemperature>

<Collection href="#id1"/>

</SendTemperature>

<soapenc:Array id="id1" soapenc:arrayType="tns:Temperature[2]">

<Item href="#id2"/>

<Item href="#id3"/>

</soapenc:Array>

<tns:Temperature id="id2" xsi:type="tns:Temperature">

<Id xsi:type="xsd:int">3</Id>

<Name xsi:type="xsd:string">Station1</Name>

<value xsi:type="xsd:double">34.3</value>

</tns:Temperature>

<tns:Temperature id="id3" xsi:type="tns:Temperature">

<Id xsi:type="xsd:int">56</Id>

<Name xsi:type="xsd:string">Station3</Name>

<value xsi:type="xsd:double">23.6</value>

</tns:Temperature>

</soap:Body>

</soap:Envelope>

清单4

. SendTemperatureRPC/Encoded 样式中的SOAP消息

SOAP消息中每个参数都会被类型编码(译者注:例如xsi:type="xsd:int",注意,在SOAP消息中的href’ tags 标签,实质上也是RPC/Encoded样式的一部分,它被用来查询数组中的元素。在任何literal 样式中,这个href标签是不可用的。让我们来分析下WSDL定义和它的SOAP消息形式。

优点:

    • WSDL文件的定义遵循直观和众所周知的远程进程调用的沟通模式。
    • 操作名显示在消息中,因此接收者很容易就把消息分派给它的实现。
    • 如果你正在你的服务中使用数据图形或者多态,在本文描述的样式中这是惟一能使用的样式。

缺点:

    • SOAP消息包含的类型编码信息就如xsi:type="xsd:int", xsi:type="xsd:string", xsi:type="xsd:double" ,这些就是一种开销。
    • 通常验证SOAP消息是很困难的。
    • RPC样式引起了一种在服务提供者和客户之间的紧密耦合,任何对接口的更改都会导致服务和客户间联系的中断。
    • 依赖那也许也许要同步处理的信息,内存约束也许使得RPC消息传输不能实现,因为在内存中发生消息聚集。
    • 不被WSI一致性标准所支持。

现在我们来看同样的web方法用RPC/Literal样式实现并看看它是否能消除RPC/Encode样式中的一些缺陷。

  1. RPC/Literal 样式

这里的WSDL定义看起来和先前的非常相似,惟一可预见的改变就是<soap:body> 元素中use的值由encoded变为literal,这显示在清单5中。就如上面所描述的,这就意味着数据类型定义并是由引用的XML Schema所提供,而不是RPC编码。

 

<types>

<s:schema elementFormDefault="qualified" 

targetNamespace
="http://interop.webservices.fhso.ch/rpcliteral">

<!-- there are no global element declarations. There's nothing in the 

schema that completely describes the content of soap:Body 
-->

<s:complexType name="ArrayOfTemperature">

<s:sequence>

<s:element minOccurs="0" maxOccurs="unbounded" name="Temperature" 

     nillable
="true" 

     type
="s0:Temperature"/>

</s:sequence>

</s:complexType>

<s:complexType name="Temperature">

<s:sequence>

<s:element minOccurs="0" maxOccurs="1" name="Id" type="s:int"/>

<s:element minOccurs="0" maxOccurs="1" name="Name" type="s:string"/>

<s:element minOccurs="0" maxOccurs="1" name="value" type="s:double"/>

</s:sequence>

</s:complexType>

</s:schema>

</types>

<message name="SendTemperatureSoapIn">

<part name="Collection" type="s0:ArrayOfTemperature"/>

</message>

<message name="SendTemperatureSoapOut"/>

<portType name="TemperatureRpcLiteralSoap">

<operation name="SendTemperature">

<input message="s0:SendTemperatureSoapIn"/>

<output message="s0:SendTemperatureSoapOut"/>

</operation>

</portType>

<binding name="TemperatureRpcLiteralSoap" 

            type
="s0:TemperatureRpcLiteralSoap">

<soap:binding style="rpc" 

            transport
="http://schemas.xmlsoap.org/soap/http"/>

<operation name="SendTemperature">

<soap:operation 

            
soapAction="http://interop.fhso.ch/soapformat/SendTemperature"/>

<input>

<soap:body use="literal" 

            namespace
="http://interop.fhso.ch/soapformat/SendTemperature"/>

</input>

<output>

<soap:body use="literal" />

</output>

</operation>

</binding>

清单

5. SendTemperatureRPC/Literal 样式中的SOAP定义

然而,使用这种样式仍然存在一个主要的缺陷。独立的XML Schema 不会告诉你消息体中的信息集合包含了些什么,你也必需知道RPC规则。因此,该schema描述的一种RPC/Literal消息但并不足以验证那种消息。

在清单6中让我们看看针对RPC/Literal样式的SOAP消息形式。注意,类型编码被完全移除掉了。

 

<soapenv:Envelope

xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" 

xmlns:xsd
="http://www.w3.org/2001/XMLSchema" 

xmlns:xsi
="http://www.w3.org/2001/XMLSchema-instance">

<soapenv:Body>

< ! – SendTemperature is the name of the procedure being invoked. 

Collection is a parameter of that procedure.

Note that Collection is not namespace qualified. The two Temperature 

elements are contents of the Collection parameter. This Collection can 

be thought of as an array of Temperature items. Note that the Temperature 

is namespace qualified but is in a different namespace than SendTemperature. 

These namespace rules are unique to RPC-style messages -- 
> 

<SendTemperature xmlns="http://interop.fhso.ch/soapformat/SendTemperature">

<Collection xmlns="">

<ns1:Temperature xmlns:ns1="http://interop.webservices.fhso.ch/rpcliteral">

<ns1:Id>2</ns1:Id>

<ns1:Name> Station1</ns1:Name>

<ns1:value>34.2</ns1:value>

</ns1:Temperature>

<ns2:Temperature xmlns:ns2="http://interop.webservices.fhso.ch/rpcliteral">

<ns2:Id>56</ns2:Id>

<ns2:Name> Station 3</ns2:Name>

<ns2:value>23.6</ns2:value>

</ns2:Temperature>

</Collection>

</SendTemperature>

</soapenv:Body>

</soapenv:Envelope>

清单

6. SendTemperatureRPC/Literal 样式中的SOAP消息

优点:

    • WSDL定义仍然像RPC/Encoded样式一样简单直接。
    • 操作名仍然出现在SOAP消息中。
    • 把类型编码从消息中排除了,因此提升了吞吐性能。

缺点:

    • 服务和客户之间仍然有紧密耦合。
    • 仍然难以用SOAP消息来验证传输的数据。
    • 它也不被WSI一致性标准所支持。

现在,我们来看看Document/Literal样式,来看一下该样式是否能减少现有的缺陷。

18 février

API hooking revealed

哈哈,帮同事查资料的时候,总算在CodeProject上找到了一篇入门的API HOOKing的文章,十分适合我这样的菜鸟~~赶紧收藏一下。Open-mouthed

引自:http://www.codeproject.com/KB/system/hooksys.aspx

原文作者:Ivo Ivanov

作者简介:Ivo works as an independent consultant building advanced .NET solutions. He is also a co-founder and CTO of InfoProcess Pty Ltd. – an Australian company developing Host Intrusion Prevention Systems (HIPS) for Windows.
Ivo has helped build applications for leading software solution providers such as Pacom Ltd., Citect Ltd.,Professional Advantage Pty Ltd. and NICE Ltd.. He combines in-depth knowledge of Windows Operating Systems, Object Oriented Design, and strong development skills in C/C++ and C#. Ivo applies his core skills to ensure alignment to the key components: business strategy, sperations, organisation and technology.
He is MCP.NET and Brainbench C++/OOD certified.

 

Introduction

Intercepting Win32 API calls has always been a challenging subject among most of the Windows developers and I have to admit, it's been one of my favorite topics. The term Hooking represents a fundamental technique of getting control over a particular piece of code execution. It provides an straightforward mechanism that can easily alter the operating system's behavior as well as 3rd party products, without having their source code available.

Many modern systems draw the attention to their ability to utilize existing Windows applications by employing spying techniques. A key motivation for hooking, is not only to contribute to advanced functionalities, but also to inject user-supplied code for debugging purposes.

Unlike some relatively "old" operating systems like DOS and Windows 3.xx, the present Windows OS as NT/2K and 9x provide sophisticated mechanisms to separate address spaces of each process. This architecture offers a real memory protection, thus no application is able to corrupt the address space of another process or in the worse case even to crash the operating system itself. This fact makes a lot harder the development of system-aware hooks.

My motivation for writing this article was the need for a really simple hooking framework, that will offer an easy to use interface and ability to capture different APIs. It intends to reveal some of the tricks that can help you to write your own spying system. It suggests a single solution how to build a set for hooking Win32 API functions on NT/2K as well as 98/Me (shortly named in the article 9x) family Windows. For the sake of simplicity I decided not to add a support do UNICODE. However, with some minor modifications of the code you could easily accomplish this task.

Spying of applications provides many advantages:

  1. API function's monitoring
    The ability to control API function calls is extremely helpful and enables developers to track down specific "invisible" actions that occur during the API call. It contributes to comprehensive validation of parameters as well as reports problems that usually remain overlooked behind the scene. For instance sometimes, it might be very helpful to monitor memory related API functions for catching resource leaks.
  2. Debugging and reverse engineering
    Besides the standard methods for debugging API hooking has a deserved reputation for being one of the most popular debugging mechanisms. Many developers employ the API hooking technique in order to identify different component implementations and their relationships. API interception is very powerful way of getting information about a binary executable.
  3. Peering inside operating system
    Often developers are keen to know operating system in dept and are inspired by the role of being a "debugger". Hooking is also quite useful technique for decoding undocumented or poorly documented APIs.
  4. Extending originally offered functionalities by embedding custom modules into external Windows applications Re-routing the normal code execution by injecting hooks can provide an easy way to change and extend existing module functionalities. For example many 3rd party products sometimes don't meet specific security requirements and have to be adjusted to your specific needs. Spying of applications allows developers to add sophisticated pre- and post-processing around the original API functions. This ability is an extremely useful for altering the behavior of the already compiled code.

Functional requirements of a hooking system

There are few important decisions that have to be made, before you start implementing any kind of API hooking system. First of all, you should determine whether to hook a single application or to install a system-aware engine. For instance if you would like to monitor just one application, you don't need to install a system-wide hook but if your job is to track down all calls to TerminateProcess() or WriteProcessMemory() the only way to do so is to have a system-aware hook. What approach you will choose depends on the particular situation and addresses specific problems.

General design of an API spying framework

Usually a Hook system is composed of at least two parts - a Hook Server and a Driver. The Hook Server is responsible for injecting the Driver into targeted processes at the appropriate moment. It also administers the driver and optionally can receive information from the Driver about its activities whereas the Driver module that performs the actual interception. 
This design is rough and beyond doubt doesn't cover all possible implementations. However it outlines the boundaries of a hook framework.
Once you have the requirement specification of a hook framework, there are few design points you should take into account:

  • What applications do you need to hook
  • How to inject the DLL into targeted processes or which implanting technique to follow
  • Which interception mechanism to use

I hope next the few sections will provide answers to those issues.

Injecting techniques
  1. Registry
    In order to inject a DLL into processes that link with USER32.DLL, you simply can add the DLL name to the value of the following registry key:

    HKEY_LOCAL_MACHINE\Software\Microsoft\Windows NT\CurrentVersion\Windows\AppInit_DLLs

    Its value contains a single DLL name or group of DLLs separated either by comma or spaces. According to MSDN documentation [7], all DLLs specified by the value of that key are loaded by each Windows-based application running within the current logon session. It is interesting that the actual loading of these DLLs occurs as a part of USER32's initialization. USER32 reads the value of mentioned registry key and calls LoadLibrary() for these DLLs in its DllMain code. However this trick applies only to applications that use USER32.DLL. Another restriction is that this built-in mechanism is supported only by NT and 2K operating systems. Although it is a harmless way to inject a DLL into a Windows processes there are few shortcomings:

    • In order to activate/deactivate the injection process you have to reboot Windows.
    • The DLL you want to inject will be mapped only into these processes that use USER32.DLL, thus you cannot expect to get your hook injected into console applications, since they usually don't import functions from USER32.DLL.
    • On the other hand you don't have any control over the injection process. It means that it is implanted into every single GUI application, regardless you want it or not. It is a redundant overhead especially if you intend to hook few applications only. For more details see [2] "Injecting a DLL Using the Registry"
  2. System-wide Windows Hooks
    Certainly a very popular technique for injecting DLL into a targeted process relies on provided by Windows Hooks. As pointed out in MSDN a hook is a trap in the system message-handling mechanism. An application can install a custom filter function to monitor the message traffic in the system and process certain types of messages before they reach the target window procedure.

    A hook is normally implemented in a DLL in order to meet the basic requirement for system-wide hooks. The basic concept of that sort of hooks is that the hook callback procedure is executed in the address spaces of each hooked up process in the system. To install a hook you call SetWindowsHookEx() with the appropriate parameters. Once the application installs a system-wide hook, the operating system maps the DLL into the address space in each of its client processes. Therefore global variables within the DLL will be "per-process" and cannot be shared among the processes that have loaded the hook DLL. All variables that contain shared data must be placed in a shared data section. The diagram bellow shows an example of a hook registered by Hook Server and injected into the address spaces named "Application one" and "Application two".

    Figure 1

    Figure1

    A system-wide hook is registered just ones when SetWindowsHookEx() is executed. If no error occurs a handle to the hook is returned. The returned value is required at the end of the custom hook function when a call to CallNextHookEx() has to be made. After a successful call to SetWindowsHookEx() , the operating system injects the DLL automatically (but not necessary immediately) into all processes that meet the requirements for this particular hook filter. Let's have a closer look at the following dummy WH_GETMESSAGE filter function:

    //---------------------------------------------------------------------------
    // GetMsgProc
    //
    // Filter function for the WH_GETMESSAGE - it's just a dummy function
    //---------------------------------------------------------------------------
    LRESULT CALLBACK GetMsgProc(
    	int code,       // hook code
    	WPARAM wParam,  // removal option
    	LPARAM lParam   // message
    	)
    {
    	// We must pass the all messages on to CallNextHookEx.
    	return ::CallNextHookEx(sg_hGetMsgHook, code, wParam, lParam);
    }
    

    A system-wide hook is loaded by multiple processes that don't share the same address space.

    For instance hook handle sg_hGetMsgHook, that is obtained by SetWindowsHookEx() and is used as parameter in CallNextHookEx() must be used virtually in all address spaces. It means that its value must be shared among hooked processes as well as the Hook Server application. In order to make this variable "visible" to all processes we should store it in the shared data section.

    The following is an example of employing #pragma data_seg(). Here I would like to mention that the data within the shared section must be initialized, otherwise the variables will be assigned to the default data segment and #pragma data_seg() will have no effect.

    //---------------------------------------------------------------------------
    // Shared by all processes variables
    //---------------------------------------------------------------------------
    #pragma data_seg(".HKT")
    HHOOK sg_hGetMsgHook       = NULL;
    BOOL  sg_bHookInstalled    = FALSE;
    // We get this from the application who calls SetWindowsHookEx()'s wrapper
    HWND  sg_hwndServer        = NULL; 
    #pragma data_seg()

    You should add a SECTIONS statement to the DLL's DEF file as well

    SECTIONS
    	.HKT   Read Write Shared

    or use

    #pragma comment(linker, "/section:.HKT, rws")

    Once a hook DLL is loaded into the address space of the targeted process, there is no way to unload it unless the Hook Server calls UnhookWindowsHookEx() or the hooked application shuts down. When the Hook Server calls UnhookWindowsHookEx() the operating system loops through an internal list with all processes which have been forced to load the hook DLL. The operating system decrements the DLL's lock count and when it becomes 0, the DLL is automatically unmapped from the process's address space.

    Here are some of the advantages of this approach:

    • This mechanism is supported by NT/2K and 9x Windows family and hopefully will be maintained by future Windows versions as well.
    • Unlike the registry mechanism of injecting DLLs this method allows DLL to be unloaded when Hook Server decides that DLL is no longer needed and makes a call to UnhookWindowsHookEx()

    Although I consider Windows Hooks as very handy injection technique, it comes with its own disadvantages:

    • Windows Hooks can degrade significantly the entire performance of the system, because they increase the amount of processing the system must perform for each message.
    • It requires lot of efforts to debug system-wide Windows Hooks. However if you use more than one instance of VC++ running in the same time, it would simplify the debugging process for more complex scenarios.
    • Last but not least, this kind of hooks affect the processing of the whole system and under certain circumstances (say a bug) you must reboot your machine in order to recover it.
  3. Injecting DLL by using CreateRemoteThread() API function
    Well, this is my favorite one. Unfortunately it is supported only by NT and Windows 2K operating systems. It is bizarre, that you are allowed to call (link with) this API on Win 9x as well, but it just returns NULL without doing anything.

    Injecting DLLs by remote threads is Jeffrey Ritcher's idea and is well documented in his article [9] "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB".

    The basic concept is quite simple, but very elegant. Any process can load a DLL dynamically using LoadLibrary() API. The issue is how do we force an external process to call LoadLibrary() on our behalf, if we don't have any access to process's threads? Well, there is a function, called CreateRemoteThread() that addresses creating a remote thread. Here comes the trick - have a look at the signature of thread function, whose pointer is passed as parameter (i.e. LPTHREAD_START_ROUTINE) to the CreateRemoteThread():

    DWORD WINAPI ThreadProc(LPVOID lpParameter);

    And here is the prototype of LoadLibrary API

    HMODULE WINAPI LoadLibrary(LPCTSTR lpFileName);

    Yes, they do have "identical" pattern. They use the same calling convention WINAPI, they both accept one parameter and the size of returned value is the same. This match gives us a hint that we can use LoadLibrary() as thread function, which will be executed after the remote thread has been created. Let's have a look at the following sample code:

    hThread = ::CreateRemoteThread(
    	hProcessForHooking, 
    	NULL, 
    	0, 
    	pfnLoadLibrary, 
    	"C:\\HookTool.dll", 
    	0, 
    	NULL);
    	

    By using GetProcAddress() API we get the address of the LoadLibrary() API. The dodgy thing here is that Kernel32.DLL is mapped always to the same address space of each process, thus the address of LoadLibrary() function has the same value in address space of any running process. This ensures that we pass a valid pointer (i.e. pfnLoadLibrary) as parameter of CreateRemoteThread().

    As parameter of the thread function we use the full path name of the DLL, casting it to LPVOID. When the remote thread is resumed, it passes the name of the DLL to the ThreadFunction (i.e. LoadLibrary). That's the whole trick with regard to using remote threads for injection purposes.

    There is an important thing we should consider, if implanting through CreateRemoteThread() API. Every time before the injector application operate on the virtual memory of the targeted process and makes a call to CreateRemoteThread(), it first opens the process using OpenProcess() API and passes PROCESS_ALL_ACCESS flag as parameter. This flag is used when we want to get maximum access rights to this process. In this scenario OpenProcess() will return NULL for some of the processes with low ID number. This error (although we use a valid process ID) is caused by not running under security context that has enough permissions. If you think for a moment about it, you will realize that it makes perfect sense. All those restricted processes are part of the operating system and a normal application shouldn't be allowed to operate on them. What would happen if some application has a bug and accidentally attempts to terminate an operating system's process? To prevent the operating system from that kind of eventual crashes, it is required that a given application must have sufficient privileges to execute APIs that might alter operating system behavior. To get access to the system resources (e.g. smss.exe, winlogon.exe, services.exe, etc) through OpenProcess() invocation, you must be granted the debug privilege. This ability is extremely powerful and offers a way to access the system resources, that are normally restricted. Adjusting the process privileges is a trivial task and can be described with the following logical operations:

    • Open the process token with permissions needed to adjust privileges
    • Given a privilege's name "SeDebugPrivilege", we should locate its local LUID mapping. The privileges are specified by name and can be found in Platform SDK file winnt.h
    • Adjust the token in order to enable the "SeDebugPrivilege" privilege by calling AdjustTokenPrivileges() API
    • Close obtained by OpenProcessToken() process token handle
    For more details about changing privileges see [10] "Using privilege".
  4. Implanting through BHO add-ins
    Sometimes you will need to inject a custom code inside Internet Explorer only. Fortunately Microsoft provides an easy and well documented way for this purpose - Browser Helper Objects. A BHO is implemented as COM DLL and once it is properly registered, each time when IE is launched it loads all COM components that have implemented IObjectWithSite interface.
  5. MS Office add-ins
    Similarly, to the BHOs, if you need to implant in MS Office applications code of your own, you can take the advantage of provided standard mechanism by implementing MS Office add-ins. There are many available samples that show how to implement this kind of add-ins.
Interception mechanisms

Injecting a DLL into the address space of an external process is a key element of a spying system. It provides an excellent opportunity to have a control over process's thread activities. However it is not sufficient to have the DLL injected if you want to intercept API function calls within the process.

This part of the article intends to make a brief review of several available real-world hooking aspects. It focuses on the basic outline for each one of them, exposing their advantages and disadvantages.

In terms of the level where the hook is applied, there are two mechanisms for API spying - Kernel level and User level spying. To get better understanding of these two levels you must be aware of the relationship between the Win32 subsystem API and the Native API. Following figure demonstrates where the different hooks are set and illustrates the module relationships and their dependencies on Windows 2K:

Figure 2

Figure2

The major implementation difference between them is that interceptor engine for kernel-level hooking is wrapped up as a kernel-mode driver, whereas user-level hooking usually employs user-mode DLL.

  1. NT Kernel level hooking
    There are several methods for achieving hooking of NT system services in kernel mode. The most popular interception mechanism was originally demonstrated by Mark Russinovich and Bryce Cogswell in their article [3] "Windows NT System-Call Hooking". Their basic idea is to inject an interception mechanism for monitoring NT system calls just bellow the user mode. This technique is very powerful and provides an extremely flexible method for hooking the point that all user-mode threads pass through before they are serviced by the OS kernel.

    You can find an excellent design and implementation in "Undocumented Windows 2000 Secrets" as well. In his great book Sven Schreiber explains how to build a kernel-level hooking framework from scratch [5].

    Another comprehensive analysis and brilliant implementation has been provided by Prasad Dabak in his book "Undocumented Windows NT" [17].

    However, all these hooking strategies, remain out of the scope of this article.

  2. Win32 User level hooking
    1. Windows subclassing.
      This method is suitable for situations where the application's behavior might be changed by new implementation of the window procedure. To accomplish this task you simply call SetWindowLongPtr() with GWLP_WNDPROC parameter and pass the pointer to your own window procedure. Once you have the new subclass procedure set up, every time when Windows dispatches a message to a specified window, it looks for the address of the window's procedure associated with the particular window and calls your procedure instead of the original one.

      The drawback of this mechanism is that subclassing is available only within the boundaries of a specific process. In other words an application should not subclass a window class created by another process.

      Usually this approach is applicable when you hook an application through add-in (i.e. DLL / In-Proc COM component) and you can obtain the handle to the window whose procedure you would like to replace.

      For example, some time ago I wrote a simple add-in for IE (Browser Helper Object) that replaces the original pop-up menu provided by IE using subclassing.

    2. Proxy DLL (Trojan DLL)
      An easy way for hacking API is just to replace a DLL with one that has the same name and exports all the symbols of the original one. This technique can be effortlessly implemented using function forwarders. A function forwarder basically is an entry in the DLL's export section that delegates a function call to another DLL's function.

      You can accomplish this task by simply using #pragma comment:

      #pragma comment(linker, "/export:DoSomething=DllImpl.ActuallyDoSomething")

      However, if you decide to employ this method, you should take the responsibility of providing compatibilities with newer versions of the original library. For more details see [13a] section "Export forwarding" and [2] "Function Forwarders".

    3. Code overwriting
      There are several methods that are based on code overwriting. One of them changes the address of the function used by CALL instruction. This method is difficult, and error prone. The basic idea beneath is to track down all CALL instructions in the memory and replace the addresses of the original function with user supplied one.

      Another method of code overwriting requires a more complicated implementation. Briefly, the concept of this approach is to locate the address of the original API function and to change first few bytes of this function with a JMP instruction that redirects the call to the custom supplied API function. This method is extremely tricky and involves a sequence of restoring and hooking operations for each individual call. It's important to point out that if the function is in unhooked mode and another call is made during that stage, the system won't be able to capture that second call.
      The major problem is that it contradicts with the rules of a multithreaded environment.

      However, there is a smart solution that solves some of the issues and provides a sophisticated way for achieving most of the goals of an API interceptor. In case you are interested you might peek at [12] Detours implementation.

    4. Spying by a debugger
      An alternative to hooking API functions is to place a debugging breakpoint into the target function. However there are several drawbacks for this method. The major issue with this approach is that debugging exceptions suspend all application threads. It requires also a debugger process that will handle this exception. Another problem is caused by the fact that when the debugger terminates, the debugger is automatically shut down by Windows.
    5. Spying by altering of the Import Address Table
      This technique was originally published by Matt Pietrek and than elaborated by Jeffrey Ritcher ([2] "API Hooking by Manipulating a Module's Import Section") and John Robbins ([4] "Hooking Imported Functions"). It is very robust, simple and quite easy to implement. It also meets most of the requirements of a hooking framework that targets Windows NT/2K and 9x operating systems. The concept of this technique relies on the elegant structure of the Portable Executable (PE) Windows file format. To understand how this method works, you should be familiar with some of the basics behind PE file format, which is an extension of Common Object File Format (COFF). Matt Pietrek reveals the PE format in details in his wonderful articles - [6] "Peering Inside the PE.", and [13a/b] "An In-Depth Look into the Win32 PE file format". I will give you a brief overview of the PE specification, just enough to get the idea of hooking by manipulation of the Import Address Table.

      In general an PE binary file is organized, so that it has all code and data sections in a layout that conform to the virtual memory representation of an executable. PE file format is composed of several logical sections. Each of them maintains specific type of data and addresses particular needs of the OS loader.

      The section .idata, I would like to focus your attention on, contains information about Import Address Table. This part of the PE structure is particularly very crucial for building a spy program based on altering IAT.
      Each executable that conforms with PE format has layout roughly described by the figure below.

      Figure 3

      Figure3

      The program loader is responsible for loading an application along with all its linked DLLs into the memory. Since the address where each DLL is loaded into, cannot be known in advance, the loader is not able to determine the actual address of each imported function. The loader must perform some extra work to ensure that the program will call successfully each imported function. But going through each executable image in the memory and fixing up the addresses of all imported functions one by one would take unreasonable amount of processing time and cause huge performance degradation. So, how does the loader resolves this challenge? The key point is that each call to an imported function must be dispatched to the same address, where the function code resides into the memory. Each call to an imported function is in fact an indirect call, routed through IAT by an indirect JMP instruction. The benefit of this design is that the loader doesn't have to search through the whole image of the file. The solution appears to be quite simple - it just fixes-up the addresses of all imports inside the IAT. Here is an example of a snapshot PE File structure of a simple Win32 Application, taken with the help of the [8] PEView utility. As you can see TestApp import table contains two imported by GDI32.DLL function - TextOutA() and GetStockObject().

      Figure 4

      Figure4

      Actually the hooking process of an imported function is not that complex as it looks at first sight. In a nutshell an interception system that uses IAT patching has to discover the location that holds the address of imported function and replace it with the address of an user supplied function by overwriting it. An important requirement is that the newly provided function must have exactly the same signature as the original one. Here are the logical steps of a replacing cycle:

      • Locate the import section from the IAT of each loaded by the process DLL module as well as the process itself
      • Find the IMAGE_IMPORT_DESCRIPTOR chunk of the DLL that exports that function. Practically speaking, usually we search this entry by the name of the DLL
      • Locate the IMAGE_THUNK_DATA which holds the original address of the imported function
      • Replace the function address with the user supplied one

      By changing the address of the imported function inside the IAT, we ensure that all calls to the hooked function will be re-routed to the function interceptor.

      Replacing the pointer inside the IAT is that .idata section doesn't necessarily have to be a writable section. This requires that we must ensure that .idata section can be modified. This task can be accomplished by using VirtualProtect() API.

      Another issue that deserves attention is related to the GetProcAddress() API behavior on Windows 9x system. When an application calls this API outside the debugger it returns a pointer to the function. However if you call this function within from the debugger it actually returns different address than it would when the call is made outside the debugger. It is caused by the fact that that inside the debugger each call to GetProcAddress() returns a wrapper to the real pointer. Returned by GetProcAddress() value points to PUSH instruction followed by the actual address. This means that on Windows 9x when we loop through the thunks, we must check whether the address of examined function is a PUSH instruction (0x68 on x86 platforms) and accordingly get the proper value of the address function.

      Windows 9x doesn't implement copy-on-write, thus operating system attempts to keep away the debuggers from stepping into functions above the 2-GB frontier. That is the reason why GetProcAddress() returns a debug thunk instead of the actual address. John Robbins discusses this problem in [4] "Hooking Imported Functions".

Figuring out when to inject the hook DLL

That section reveals some challenges that are faced by developers when the selected injection mechanism is not part of the operating system's functionality. For example, performing the injection is not your concern when you use built-in Windows Hooks in order to implant a DLL. It is an OS's responsibility to force each of those running processes that meet the requirements for this particular hook, to load the DLL [18]. In fact Windows keeps track of all newly launched processes and forces them to load the hook DLL. Managing injection through registry is quite similar to Windows Hooks. The biggest advantage of all those "built-in" methods is that they come as part of the OS.

Unlike the discussed above implanting techniques, to inject by CreateRemoteThread() requires maintenance of all currently running processes. If the injecting is made not on time, this can cause the Hook System to miss some of the calls it claims as intercepted. It is crucial that the Hook Server application implements a smart mechanism for receiving notifications each time when a new process starts or shuts down. One of the suggested methods in this case, is to intercept CreateProcess() API family functions and monitor all their invocations. Thus when an user supplied function is called, it can call the original CreateProcess() with dwCreationFlags OR-ed with CREATE_SUSPENDED flag. This means that the primary thread of the targeted application will be in suspended state, and the Hook Server will have the opportunity to inject the DLL by hand-coded machine instructions and resume the application using ResumeThread() API. For more details you might refer to [2] "Injecting Code with CreateProcess()".

The second method of detecting process execution, is based on implementing a simple device driver. It offers the greatest flexibility and deserves even more attention. Windows NT/2K provides a special function PsSetCreateProcessNotifyRoutine() exported by NTOSKRNL. This function allows adding a callback function, that is called whenever a process is created or deleted. For more details see [11] and [15] from the reference section.

Enumerating processes and modules

Sometimes we would prefer to use injecting of the DLL by CreateRemoteThread() API, especially when the system runs under NT/2K. In this case when the Hook Server is started it must enumerate all active processes and inject the DLL into their address spaces. Windows 9x and Windows 2K provide a built-in implementation (i.e. implemented by Kernel32.dll) of Tool Help Library. On the other hand Windows NT uses for the same purpose PSAPI library. We need a way to allow the Hook Server to run and then to detect dynamically which process "helper" is available. Thus the system can determine which the supported library is, and accordingly to use the appropriate APIs.

I will present an object-oriented architecture that implements a simple framework for retrieving processes and modules under NT/2K and 9x [16]. The design of my classes allows extending the framework according to your specific needs. The implementation itself is pretty straightforward.

CTaskManager implements the system's processor. It is responsible for creating an instance of a specific library handler (i.e. CPsapiHandler or CToolhelpHandler) that is able to employ the correct process information provider library (i.e. PSAPI or ToolHelp32 respectively). CTaskManager is in charge of creating and marinating a container object that keeps a list with all currently active processes. After instantiating of the CTaskManager object the application calls Populate() method. It forces enumerating of all processes and DLL libraries and storing them into a hierarchy kept by CTaskManager's member m_pProcesses.

Following UML diagram shows the class relationships of this subsystem:

Figure 5

Figure5

It is important to highlight the fact that NT's Kernel32.dll doesn't implement any of the ToolHelp32 functions. Therefore we must link them explicitly, using runtime dynamic linking. If we use static linking the code will fail to load on NT, regardless whether or not the application has attempted to execute any of those functions. For more details see my article "Single interface for enumerating processes and modules under NT and Win9x/2K.".

Requirements of the Hook Tool System

Now that I've made a brief introduction to the various concepts of the hooking process it's time to determine the basic requirements and explore the design of a particular hooking system. These are some of the issues addressed by the Hook Tool System:

  • Provide a user-level hooking system for spying any Win32 API functions imported by name
  • Provide the abilities to inject hook driver into all running processes by Windows hooks as well as CreateRemoteThread() API. The framework should offer an ability to set this up by an INI file
  • Employ an interception mechanism based on the altering Import Address Table
  • Present an object-oriented reusable and extensible layered architecture
  • Offer an efficient and scalable mechanism for hooking API functions
  • Meet performance requirements
  • Provide a reliable communication mechanism for transferring data between the driver and the server
  • Implement custom supplied versions of TextOutA/W() and ExitProcess() API functions
  • Log events to a file
  • The system is implemented for x86 machines running Windows 9x, Me, NT or Windows 2K operating system

Design and implementation

This part of the article discusses the key components of the framework and how do they interact each other. This outfit is capable to capture any kind of WINAPI imported by name functions.

Before I outline the system's design, I would like to focus your attention on several methods for injecting and hooking.

First and foremost, it is necessary to select an implanting method that will meet the requirements for injecting the DLL driver into all processes. So I designed an abstract approach with two injecting techniques, each of them applied accordingly to the settings in the INI file and the type of the operating system (i.e. NT/2K or 9x). They are - System-wide Windows Hooks and CreateRemoteThread() method. The sample framework offers the ability to inject the DLL on NT/2K by Windows Hooks as well as to implant by CreateRemoteThread() means. This can be determined by an option in the INI file that holds all settings of the system.

Another crucial moment is the choice of the hooking mechanism. Not surprisingly, I decided to apply altering IAT as an extremely robust method for Win32 API spying.

To achieve desired goals I designed a simple framework composed of the following components and files:

  • TestApp.exe - a simple Win32 test application that just outputs a text using TextOut() API. The purpose of this app is to show how it gets hooked up.
  • HookSrv.exe - control program
  • HookTool .DLL - spy library implemented as Win32 DLL
  • HookTool.ini - a configuration file
  • NTProcDrv.sys - a tiny Windows NT/2K kernel-mode driver for monitoring process creation and termination. This component is optional and addresses the problem with detection of process execution under NT based systems only.

HookSrv is a simple control program. Its main role is to load the HookTool.DLL and then to activate the spying engine. After loading the DLL, the Hook Server calls InstallHook() function and passes a handle to a hidden windows where the DLL should post all messages to.

HookTool.DLL is the hook driver and the heart of presented spying system. It implements the actual interceptor and provides three user supplied functions TextOutA/W() and ExitProcess() functions.

Although the article emphasizes on Windows internals and there is no need for it to be object-oriented, I decided to encapsulate related activities in reusable C++ classes. This approach provides more flexibility and enables the system to be extended. It also benefits developers with the ability to use individual classes outside this project.

Following UML class diagram illustrates the relationships between set of classes used in HookTool.DLL's implementation.

Figure 6

Figure6

In this section of the article I would like to draw your attention to the class design of the HookTool.DLL. Assigning responsibilities to the classes is an important part of the development process. Each of the presented classes wraps up a specific functionality and represents a particular logical entity.

CModuleScope is the main doorway of the system. It is implemented using "Singleton" pattern and works in a thread-safe manner. Its constructor accepts 3 pointers to the data declared in the shared segment, that will be used by all processes. By this means the values of those system-wide variables can be maintained very easily inside the class, keeping the rule for encapsulation.

When an application loads the HookTool library, the DLL creates one instance of CModuleScope on receiving DLL_PROCESS_ATTACH notification. This step just initializes the only instance of CModuleScope. An important piece of the CModuleScope object construction is the creation of an appropriate injector object. The decision which injector to use will be made after parsing the HookTool.ini file and determining the value of UseWindowsHook parameter under [Scope] section. In case that the system is running under Windows 9x, the value of this parameter won't be examined by the system, because Window 9x doesn't support injecting by remote threads.

After instantiating of the main processor object, a call to ManageModuleEnlistment() method will be made. Here is a simplified version of its implementation:

// Called on DLL_PROCESS_ATTACH DLL notification
BOOL CModuleScope::ManageModuleEnlistment()
{
	BOOL bResult = FALSE;
	// Check if it is the hook server 
	if (FALSE == *m_pbHookInstalled)
	{
		// Set the flag, thus we will know that the server has been installed
		*m_pbHookInstalled = TRUE;
		// and return success error code
		bResult = TRUE;
	}
	// and any other process should be examined whether it should be
	// hooked up by the DLL
	else
	{
		bResult = m_pInjector->IsProcessForHooking(m_szProcessName);
		if (bResult)
			InitializeHookManagement();
	}
	return bResult;
}

The implementation of the method ManageModuleEnlistment() is straightforward and examines whether the call has been made by the Hook Server, inspecting the value m_pbHookInstalled points to. If an invocation has been initiated by the Hook Server, it just sets up indirectly the flag sg_bHookInstalled to TRUE. It tells that the Hook Server has been started.

The next action taken by the Hook Server is to activate the engine through a single call to InstallHook() DLL exported function. Actually its call is delegated to a method of CModuleScope - InstallHookMethod(). The main purpose of this function is to force targeted for hooking processes to load or unload the HookTool.DLL.

 // Activate/Deactivate hooking
engine BOOL	CModuleScope::InstallHookMethod(BOOL bActivate, HWND hWndServer)
{
	BOOL bResult;
	if (bActivate)
	{
		*m_phwndServer = hWndServer;
		bResult = m_pInjector->InjectModuleIntoAllProcesses();
	}
	else
	{
		m_pInjector->EjectModuleFromAllProcesses();
		*m_phwndServer = NULL;
		bResult = TRUE;
	}
	return bResult;
}

HookTool.DLL provides two mechanisms for self injecting into the address space of an external process - one that uses Windows Hooks and another that employs injecting of DLL by CreateRemoteThread() API. The architecture of the system defines an abstract class CInjector that exposes pure virtual functions for injecting and ejecting DLL. The classes CWinHookInjector and CRemThreadInjector inherit from the same base - CInjector class. However they provide different realization of the pure virtual methods InjectModuleIntoAllProcesses() and EjectModuleFromAllProcesses(), defined in CInjector interface.

CWinHookInjector class implements Windows Hooks injecting mechanism. It installs a filter function by the following call

// Inject the DLL into all running processes
BOOL CWinHookInjector::InjectModuleIntoAllProcesses()
{
	*sm_pHook = ::SetWindowsHookEx(
		WH_GETMESSAGE,
		(HOOKPROC)(GetMsgProc),
		ModuleFromAddress(GetMsgProc), 
		0
		);
	return (NULL != *sm_pHook);
}

As you can see it makes a request to the system for registering WH_GETMESSAGE hook. The server executes this method only once. The last parameter of SetWindowsHookEx() is 0, because GetMsgProc() is designed to operate as a system-wide hook. The callback function will be invoked by the system each time when a window is about to process a particular message. It is interesting that we have to provide a nearly dummy implementation of the GetMsgProc() callback, since we don't intend to monitor the message processing. We supply this implementation only in order to get free injection mechanism provided by the operating system.

After making the call to SetWindowsHookEx(), OS checks whether the DLL (i.e. HookTool.DLL) that exports GetMsgProc() has been already mapped in all GUI processes. If the DLL hasn't been loaded yet, Windows forces those GUI processes to map it. An interesting fact is, that a system-wide hook DLL should not return FALSE in its DllMain(). That's because the operating system validates DllMain()'s return value and keeps trying to load this DLL until its DllMain() finally returns TRUE.

A quite different approach is demonstrated by the CRemThreadInjector class. Here the implementation is based on injecting the DLL using remote threads. CRemThreadInjector extends the maintenance of the Windows processes by providing means for receiving notifications of process creation and termination. It holds an instance of CNtInjectorThread class that observes the process execution. CNtInjectorThread object takes care for getting notifications from the kernel-mode driver. Thus each time when a process is created a call to CNtInjectorThread ::OnCreateProcess() is issued, accordingly when the process exits CNtInjectorThread ::OnTerminateProcess() is automatically called. Unlike the Windows Hooks, the method that relies on remote thread, requires manual injection each time when a new process is created. Monitoring process activities will provide us with a simple technique for alerting when a new process starts.

CNtDriverController class implements a wrapper around API functions for administering services and drivers. It is designed to handle the loading and unloading of the kernel-mode driver NTProcDrv.sys. Its implementation will be discussed later.

After a successful injection of HookTool.DLL into a particular process, a call to ManageModuleEnlistment() method is issued inside the DllMain(). Recall the method's implementation that I described earlier. It examines the shared variable sg_bHookInstalled through the CModuleScope 's member m_pbHookInstalled. Since the server's initialization had already set the value of sg_bHookInstalled to TRUE, the system checks whether this application must be hooked up and if so, it actually activates the spy engine for this particular process.

Turning the hacking engine on, takes place in the CModuleScope::InitializeHookManagement()'s implementation. The idea of this method is to install hooks for some vital functions as LoadLibrary() API family as well as GetProcAddress(). By this means we can monitor loading of DLLs after the initialization process. Each time when a new DLL is about to be mapped it is necessary to fix-up its import table, thus we ensure that the system won't miss any call to the captured function.

At the end of the InitializeHookManagement() method we provide initializations for the function we actually want to spy on.

Since the sample code demonstrates capturing of more than one user supplied functions, we must provide a single implementation for each individual hooked function. This means that using this approach you cannot just change the addresses inside IAT of the different imported functions to point to a single "generic" interception function. The spying function needs to know which function this call comes to. It is also crucial that the signature of the interception routine must be exactly the same as the original WINAPI function prototype, otherwise the stack will be corrupted. For example CModuleScope implements three static methods MyTextOutA(),MyTextOutW() and MyExitProcess(). Once the HookTool.DLL is loaded into the address space of a process and the spying engine is activated, each time when a call to the original TextOutA() is issued, CModuleScope:: MyTextOutA() gets called instead.

Proposed design of the spying engine itself is quite efficient and offers great flexibility. However, it is suitable mostly for scenarios where the set of functions for interception is known in advance and their number is limited.

If you want to add new hooks to the system you simply declare and implement the interception function as I did with MyTextOutA/W() and MyExitProcess(). Then you have to register it in the way shown by InitializeHookManagement() implementation.

Intercepting and tracing process execution is a very useful mechanism for implementing systems that require manipulations of external processes. Notifying interested parties upon starting of a new processes is a classic problem of developing process monitoring systems and system-wide hooks. The Win32 API provides a set of great libraries (PSAPI and ToolHelp [16]) that allow you to enumerate processes currently running in the system. Although these APIs are extremely powerful they don't permit you to get notifications when a new process starts or ends up. Luckily, NT/2K provides a set of APIs, documented in Windows DDK documentation as "Process Structure Routines" exported by NTOSKRNL. One of these APIs PsSetCreateProcessNotifyRoutine() offers the ability to register system-wide callback function which is called by OS each time when a new process starts, exits or has been terminated. The mentioned API can be employed as a simple way to for tracking down processes simply by implementing a NT kernel-mode driver and a user mode Win32 control application. The role of the driver is to detect process execution and notify the control program about these events. The implementation of the Windows process's observer NTProcDrv provides a minimal set of functionalities required for process monitoring under NT based systems. For more details see articles [11] and [15]. The code of the driver can be located in the NTProcDrv.c file. Since the user mode implementation installs and uninstalls the driver dynamically the currently logged-on user must have administrator privileges. Otherwise you won't be able to install the driver and it will disturb the process of monitoring. A way around is to manually install the driver as an administrator or run HookSrv.exe using offered by Windows 2K "Run as different user" option.  

Last but not least, the provided tools can be administered by simply changing the settings of an INI file (i.e. HookTool.ini). This file determines whether to use Windows hooks (for 9x and NT/2K) or CreateRemoteThread() (only under NT/2K) for injecting. It also offers a way to specify which process must be hooked up and which shouldn't be intercepted. If you would like to monitor the process there is an option (Enabled) under section [Trace] that allows to log system activities. This option allows you to report rich error information using the methods exposed by CLogFile class. In fact ClogFile provides thread-safe implementation and you don't have to take care about synchronization issues related to accessing shared system resources (i.e. the log file). For more details see CLogFile and content of HookTool.ini file.

Sample code

The project compiles with VC6++ SP4 and requires Platform SDK. In a production Windows NT environment you need to provide PSAPI.DLL in order to use provided CTaskManager implementation.

Before you run the sample code make sure that all the settings in HookTool.ini file have been set according to your specific needs.

For those that will like the lower-level stuff and are interested in further development of the kernel-mode driver NTProcDrv code, they must install Windows DDK.

Out of the scope

For the sake of simplicity these are some of the subjects I intentionally left out of the scope of this article:

  • Monitoring Native API calls
  • A driver for monitoring process execution on Windows 9x systems.
  • UNICODE support, although you can still hook UNICODE imported APIs

Conclusion

This article by far doesn't provide a complete guide for the unlimited API hooking subject and without any doubt it misses some details. However I tried to fit in this few pages just enough important information that might help those who are interested in user mode Win32 API spying.

References

[1] "Windows 95 System Programming Secrets", Matt Pietrek
[2] "Programming Application for MS Windows" , Jeffrey Richter
[3] "Windows NT System-Call Hooking" , Mark Russinovich and Bryce Cogswell, Dr.Dobb's Journal January 1997
[4] "Debugging applications" , John Robbins
[5] "Undocumented Windows 2000 Secrets" , Sven Schreiber
[6] "Peering Inside the PE: A Tour of the Win32 Portable Executable File Format" by Matt Pietrek, March 1994
[7] MSDN Knowledge base Q197571
[8] PEview Version 0.67 , Wayne J. Radburn
[9] "Load Your 32-bit DLL into Another Process's Address Space Using INJLIB" MSJ May 1994
[10] "Programming Windows Security" , Keith Brown
[11] "Detecting Windows NT/2K process execution" Ivo Ivanov, 2002
[12] "Detours" Galen Hunt and Doug Brubacher
[13a] "An In-Depth Look into the Win32 PE file format" , part 1, Matt Pietrek, MSJ February 2002
[13b] "An In-Depth Look into the Win32 PE file format" , part 2, Matt Pietrek, MSJ March 2002
[14] "Inside MS Windows 2000 Third Edition" , David Solomon and Mark Russinovich
[15] "Nerditorium", James Finnegan, MSJ January 1999
[16] "Single interface for enumerating processes and modules under NT and Win9x/2K." , Ivo Ivanov, 2001
[17] "Undocumented Windows NT" , Prasad Dabak, Sandeep Phadke and Milind Borate
[18] Platform SDK: Windows User Interface, Hooks

17 février

Come Back to Earth

过了这个周末,这个悠长的假期算是结束了;老大提到的Relaxed mode也画上了句号。这个假期过的很快乐,去了很多地方,吃了很多东西,见了很多朋友......不用读邮件,不用调代码,不用写文档......玩的很放肆,以至于到现在心还是懒懒的。只是不管愿意不愿意,明天一切都要恢复了;坦白讲,心里有些失落,恐怕这是典型“节后综合症”的表现了。

Come back to earth, boy, as JIM said:

"Come to work and we are supposed to work 8 hours a day. 
No SC playing before 1800…. 
Notify your manager or call leave, if you can not show up… "

......

呵呵,人总要回到现实的,不是么?:P

12 février

08年春节

08年的春节,过了破五,这个年算是过去了。小时候,哪怕是几年前的我都是很盼望过年的:把功课和工作统统的扔掉,和家人、朋友热闹的聚在一起,走亲、访友,吃吃喝喝、玩玩闹闹......那时很喜欢这样过年的感觉,也很盼着过年。今年心情有些奇怪,除了盼着这个悠长的假期之外,对年没了以往的心气儿,一点也没有了。北京的街头早在一个月以前就开始张灯结彩,灯笼,彩带,霓虹......把这个城市装饰的像个待嫁的新娘,可我的心里却没有一点的年味。是自己长大了,也开始像小时候看“大人”们那样,莫名的为过年发愁了么?还是现在的生活真的好了,每天都可以像过年一样快乐,以至于真的过年了反而没了心气儿?......不知道,只是没有过多的期待这个年~

08年春节,去了上海。年关的一场大雪,让今年的上海比去年冷一些,不过对于在北方居住的我,应该更喜欢这样的温度吧。第二次来这里,对这个城市多了一些亲切。从机场到酒店的路上,看着车窗外的建筑,凭着一些残留的记忆,也可以和操着上海话的司机聊着对于上海的印象。上海是个容易让人迷失自己的地方,太多高楼,太多声音,太多奢华,太多来自各地的过客......这里总会让我觉得自己好像刚刚喝过了一杯红酒一样,头有些微晕,却也回味着刚刚醇厚的味道--去年是这样,今年也是一样。

08年的春节,去了杭州。大概是时间不好,或者同样因为刚刚大雪的缘故,对这里有些失望。西湖没有想象中的美,除了残雪,没有看到一点书里描述那样人间仙境的感觉。湖边湿冷的空气,搅和着湖水的腥味,让游人裹紧衣服,加快了脚步。城市里狭小的街道,稀疏的行人,萧条的街景,似乎让人感觉不到这是一个省会城市。旅途的疲劳,让我有些昏昏欲睡,倒也应了这个城市的景儿。

08年的春节,在外漂了四天。飞机午夜降落在机场,我回到北京。

08年的春节,很特别......