C++博客-More than C++-随笔分类-插件开发http://www.cppblog.com/Lee/category/10159.htmlBeyond C++zh-cnThu, 09 Apr 2009 17:54:45 GMTThu, 09 Apr 2009 17:54:45 GMT60编写插件锁住Word文档窗口 ——API和COM接口Hook综合演示(转)http://www.cppblog.com/Lee/archive/2009/04/09/79392.htmlDavid LeeDavid LeeThu, 09 Apr 2009 14:55:00 GMThttp://www.cppblog.com/Lee/archive/2009/04/09/79392.htmlhttp://www.cppblog.com/Lee/comments/79392.htmlhttp://www.cppblog.com/Lee/archive/2009/04/09/79392.html#Feedback0http://www.cppblog.com/Lee/comments/commentRss/79392.htmlhttp://www.cppblog.com/Lee/services/trackbacks/79392.html
  现在的木马后门种类非常之多,其中有些木马专门以Office 文档为窃取目标,我最近做了一些Anti这些木马的工作,在工作中我基本上实现了阻止未知程序利用Office自动化接口窃取Word内容,但限于公司利益我只能写一些基本上已经比较成熟和公开的技术,当然其中了包含我的设计思想,我想多多少少会让你有所收获,你可任意转载文章,但请注明作者和出处,谢谢!
  首先我们知道Ole拖放是由DoDragDrop启动的,为此你可以事先要准备一些参数给DoDragDrop使用,最开始我的想法是直接阻止DoDragDrop调用,后来我发现这样做会使Word文档窗口内部的拖放操作失效,这会给人一种很不好的感觉,好的保护软件应该在客户没有感觉到不方便的情况提供保护,而后我又仔细看了DoDragDrop的参数想发现有没有什么可以利用的地方,我发现它有四个参数:
  • IDataObject * pDataObject;
  • IDropSource * pDropSource;
  • DWORD dwOKEffect;
  • DWORD * pdwEffect;

  后两个参数基本上没有什么利用价值,我想到Hook IDataObject的GetData函数,可是在Ole Drop客户端编程时,通常当我调用COleDataObject的相关成员函数时拖放已经差不多完成,鼠标已经在我的程序窗口上了。
  而我现在的想法是在拖放操作刚离开Word的文档窗口,还没有到达外部程序窗口时就让它失效,这样Hook IDataObject的虚函数肯定不行,我只有再研究一下最后一个没有研究过的参数pDropSource,它是一个IDropSource类型指针,我发现它有一个虚函数QueryContinueDrag查看了一下MSDN发现它似乎就是我要找的。
  我的理解是这个函数是让Ole拖放的服务端在调用DoDragDrop启动Ole拖放操作后,有机会取消拖放操作而设的一个CallBack,于是我写了一段代码Hook住DoDragDrop并从DoDragDrop中进一步Hook住IDropSource的QueryContinueDrag虚函数,我发现它会全程跟踪整个拖放操作,只要这个CallBack一返回DRAGDROP_S_CANCEL整个拖放操作就会被取消,这样我只要知道当前鼠标下的窗口是否是文档窗口就可以了,一旦离开了文档窗口我就让这个CallBack返回DRAGDROP_S_CANCEL取消整个拖放操作。
  这样做就可以不影响文档窗口内部的拖放操作,又可以阻止将文档窗口内部的东西拖放到其它程序中,基本上不会让客户感到不方便,这很有意思!不是吗?
  那么从鼠标位置得到窗口句柄可能吗?答案是肯定的! GetCursorPos函数可以返回一个POINT变量,它指示当前的鼠标位置,而WindowFromPoint则可以返回某一个POINT位置下的窗口句柄。写到这里我想差不多已经说完了我的思路,不知道你是否看得明白。
  总结一下,用ATL向导生成一个COM框架,增加一个ATL简单对象,在这个对象上实现_IDTExtensibility2接口,在_IDTExtensibility2的OnConnection中Hook住DoDragDrop API,在Word调用DoDragDrop时记录下当前的文档窗口句柄,并Hook住第二个参数pDropSource的QueryContinueDrag虚函数,在QueryContinueDrag里跟踪当前鼠下的窗口是否还是文档窗口(比较句柄是否等于DoDragDrop时记录下的文档窗口句柄),如果不是则返回DRAGDROP_S_CANCE取消拖放操作,并弹出一个警告信息的MessageBox,否则执行原有的操作(让拖放操作正常进行)。

最后,别忘了注册你的Word插件(用Regsvr32。exe),详细的键值和代码细节见源程序吧!

2005年10月15日夜

参考资料:

  • MSDN October 2001
  • Microsoft Office 2000/Visual Basic Programmer''s Guide
  • ATL Internals
  • Inside C++ Object Model


David Lee 2009-04-09 22:55 发表评论
]]>
用VC6.0编写Word插件(Office2K、XP、03)(转)http://www.cppblog.com/Lee/archive/2009/04/09/79391.htmlDavid LeeDavid LeeThu, 09 Apr 2009 14:53:00 GMThttp://www.cppblog.com/Lee/archive/2009/04/09/79391.htmlhttp://www.cppblog.com/Lee/comments/79391.htmlhttp://www.cppblog.com/Lee/archive/2009/04/09/79391.html#Feedback0http://www.cppblog.com/Lee/comments/commentRss/79391.htmlhttp://www.cppblog.com/Lee/services/trackbacks/79391.htmlhttp://www.vcgood.com/bbs/forum_posts.asp?tid=1534

最近因为工作的需要,学习了一下Office插件的编写方法。在走了不少弯路以后,最后终于把编写插件的原理给搞清楚了,不敢独享,拿出来跟大家共享一下。下面就以Word 2003为例,向大家简单介绍一下。
第一步,利用向导生成一个ATL COM AppWizard的新工程。


图1

在向导的第一个对话框中,服务器类型选择Dynamic Link Library(DLL),然后单击Finish即可。

图2

然后,选取菜单Insert->New ATL Object项,在弹出的ATL对象向导对话框中选中相应Objects对应右侧的Simple Object选项,点击下一步。

图3

在弹出的对话框中ShortName中输入相应名称,点确定完成插入ATL对象。

图4

这样一个简单的基于ATL的COM组件工程就建立成功了。

第二步,通过导入类型库来实现_IDTExtensibility2接口。在ClassView中的新加的类上点鼠标右键,在弹出的右键菜单中选Implement Interface项。

图5

在弹出的实现接口对话框中点击Add Typelib

图6

在弹出的Browse Type Libraries对话框中,选取Microsoft Add-in Designer(1.0)子项,点OK按钮

图7

在弹出的接口列表对话框中选中_IDTExtensibility2接口,点OK按钮完成导入

这样的话,系统将会自动为你生成空的五个所需接口函数,分别是OnConnection、OnDisconnection、OnAddInsUpdate、OnStartupComplete、OnBeginShutdown。

第三步,通过上面的两个步骤,我们的插件框架已经形成,但是Office怎么知道启动的时候要来把我们的插件Load起来呢?Office的不同组件,例如Word、Excel、Outlook等怎么知道去Load自己的插件呢?答案就是在注册表中加入相应的键值。打开文件视图FileView—>Resource File中的rgs文件,加入以下代码:

HKCU
    {
      Software
       {
        Microsoft
        {
          Office
             { 
            Word
               {
             Addins
               {
               ''TestAddin.SimAddin''
                { 
                 val FriendlyName = s ''WORD Custom Addin''
                 val Description = s ''Word Custom Addin''
                 val LoadBehavior = d ''00000003''
                 val CommandLineSafe = d ''00000001''
                 }
               }
             }
          }
       }
   }
}
以上代码由三个需要注意的地方:
1. Office下面的那个子项代表了这个插件是属于那个组件,Word、Excel、Outlook等等。
2. Addins下面的那个子项要写成你添加的COM组件的名字,千万不要照着我的工程的名字照抄。
3. 所有的值两边加的都是单引号,而且要用英文下的单引号,不能用双引号。
这样一个Office插件的框架才算完成,你可以在OnConnection函数中加一些测试代码,看看有没有执行到,如果执行成功才能继续,否则检查上面的步骤有没有错误。

第四步,同时需要import两个office的文件,一个是MSO.dll,另一个是MSWORD.OLB。这两个文件可以在以下位置找到(具体位置与office安装路径有关):
C:\Program Files\Common Files\Microsoft Shared\OFFICE11
C:\Program Files\Microsoft Office\OFFICE11
然后在stdafx.h中加入如下语句:

#import "C:\\Program Files\\Common Files\\Microsoft Shared\\OFFICE11\\mso.dll" 
        \
        rename_namespace("Office") named_guids,exclude("Pages")
using namespace Office;

#import "C:\\Program Files\\Common Files\\Microsoft Shared\\VBA\\VBA6\\VBE6EXT.olb" rename_namespace("VBE6")
using namespace VBE6;

#import "C:\\Program Files\\Microsoft Office\\OFFICE11\\MSWORD.OLB" rename("ExitWindows","ExitWindowsEx")
#import "C:\\Program Files\\Microsoft Office\\OFFICE11\\MSWORD.OLB" 
        \
        rename_namespace("Word"), raw_interfaces_only, named_guids ,exclude("Pages")
using namespace Word;
加完以上代码以后一定要编译一下,看看是否能够成功。引入这两个文件的原因,主要是为了引入一些变量类型,为后面的创建UI作准备。
最后一步,编写代码。在OnConnection加入如下代码:
      CComPtr < Office::_CommandBars> spCmdBars; 
      CComQIPtr <Word::_Application> spApp(Application); 
      ATLASSERT(spApp);
      HRESULT hr = spApp->get_CommandBars(&spCmdBars);
      if(FAILED(hr))
      return hr;
      ATLASSERT(spCmdBars);
      CComVariant vName("MyAddin");
      CComPtr <Office::CommandBar> spNewCmdBar;
      CComVariant vPos(1); 
      CComVariant vTemp(VARIANT_TRUE); 
      CComVariant vEmpty(DISP_E_PARAMNOTFOUND, VT_ERROR); 
      spNewCmdBar = spCmdBars->Add(vName, vPos, vEmpty, vTemp);
       
      CComPtr < Office::CommandBarControls> spBarControls;
      spBarControls = spNewCmdBar->GetControls();
      ATLASSERT(spBarControls);
       
      CComVariant vToolBarType(1);
      CComVariant vShow(VARIANT_TRUE);
      CComPtr < Office::CommandBarControl> spNewBar; 
      spNewBar = spBarControls->Add(vToolBarType, vEmpty, vEmpty, vEmpty, vShow); 
      ATLASSERT(spNewBar);
        
      CComQIPtr < Office::_CommandBarButton> spCmdButton(spNewBar);
      ATLASSERT(spCmdButton);
        
      HBITMAP hBmp =(HBITMAP)::LoadImage(_Module.GetResourceInstance(),
      MAKEINTRESOURCE(IDB_BITMAP),IMAGE_BITMAP,0,0,LR_LOADMAP3DCOLORS);
        
      ::OpenClipboard(NULL);
      ::EmptyClipboard();
      ::SetClipboardData(CF_BITMAP, (HANDLE)hBmp);
      ::CloseClipboard();
      ::DeleteObject(hBmp); 
        
      spCmdButton->PutStyle(Office::msoButtonIconAndCaption);
      hr = spCmdButton->PasteFace();
      if (FAILED(hr))
      return hr;
        
      spCmdButton->PutVisible(VARIANT_TRUE); 
      spCmdButton->PutCaption(OLESTR("myAddin")); 
      spCmdButton->PutEnabled(VARIANT_TRUE);
      spCmdButton->PutTooltipText(OLESTR("test1")); 
      spCmdButton->PutTag(OLESTR("test1")); 
      spNewCmdBar->PutVisible(VARIANT_TRUE); 
        
      m_spCmdButton = spCmdButton;

这样,再次打开word,就可以看到如图一所示的界面效果了。

图9

但是点击时没有响应,最后就让我们来解决这个问题。
1. 在COutlookAddin继承类中加入IDispEventSimpleImpl继承,代码如下:

class ATL_NO_VTABLE COutlookAddin : 
        public CComObjectRootEx<CComSingleThreadModel>,
        ……
        public IDispEventSimpleImpl<1,COutlookAddin,&__uuidof(Office::_CommandBarButtonEvents)>
        
2. 声明_ATL_SINK_INFO结构回调参数信息。在OutlookAddin.h文件中加入下面语句:
// 按钮事件响应信息声明
extern _ATL_FUNC_INFO OnClickButtonInfo;
在OutlookAddin.cpp文件中加入定义语句,如下:
// 按钮事件响应信息定义
_ATL_FUNC_INFO OnClickButtonInfo ={CC_STDCALL,VT_EMPTY,2,{VT_DISPATCH,VT_BYREF 
        | VT_BOOL}};
3. 加入Sink映射,如下:
EGIN_SINK_MAP(COutlookAddin)
      SINK_ENTRY_INFO(1, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 
      0x01, OnClickButton1, &OnClickButtonInfo)
      SINK_ENTRY_INFO(2, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 
      0x01, OnClickButton2, &OnClickButtonInfo)
      SINK_ENTRY_INFO(3, __uuidof(Office::_CommandBarButtonEvents),/*dispid*/ 
      0x01, OnClickMenu, &OnClickButtonInfo)
      END_SINK_MAP()

4. 加入事件函数。在OutlookAddin.h中加入声明:

void __stdcall OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ 
Ctrl,VARIANT_BOOL * CancelDefault);
在OutlookAddin.cpp中加入实现:
// 工具条按钮1点击事件响应函数
void __stdcall CWordAddin::OnClickButton1(IDispatch * /*Office::_CommandBarButton**/ Ctrl,VARIANT_BOOL * CancelDefault) { MessageBox(NULL, "hello", "world", MB_OK); }

5. 最后,打开或断开与接口的连接。方法如下
在OnConnection接口函数的最后部分,加入下面代码来打开连接:
  • 在OnConnection接口函数的最后部分,加入下面代码来打开连接:
    CommandButton1Events::DispEventAdvise((IDispatch*)m_spButton);
  • 在OnDisconnection接口函数中,加入下面代码来断开连接:
    CommandButton1Events::DispEventUnadvise((IDispatch*)m_spButton);

综上所述,编写一个简单的office的插件,其实并不难,只要按照步骤一步一步进行,肯定能成功,如果大家在使用过程中有什么疑问,欢迎一起探讨。



David Lee 2009-04-09 22:53 发表评论
]]>
Word手写签名插件(COM加载项、VC6.0ATL)开发思路(转)http://www.cppblog.com/Lee/archive/2009/04/09/79389.htmlDavid LeeDavid LeeThu, 09 Apr 2009 14:52:00 GMThttp://www.cppblog.com/Lee/archive/2009/04/09/79389.htmlhttp://www.cppblog.com/Lee/comments/79389.htmlhttp://www.cppblog.com/Lee/archive/2009/04/09/79389.html#Feedback0http://www.cppblog.com/Lee/comments/commentRss/79389.htmlhttp://www.cppblog.com/Lee/services/trackbacks/79389.html开发思路:
  写这个东东,其实就是写一个COM加载项。可以使用MFC、也可以使用ATL,也可以使用VB,我最终选用了ATL。主要是VC我用得比较多,而ATL通过模板库会给开发带来极大的便利,参考资料也比较多,因此选用了ATL。事实证明我的选择是正确的。不过使用VB相对要方便一些,VB可以很方便地对COM进行编程,编程中也可以直接使用Word的宏代码,根本不用做代码转换。
  开始编程,使用VC向导,可以很快的生成COM的框架,然后向工程里插入一个simple object(在菜单插入->插入ATL对象里面)。通过这个对象来改变WORD的菜单、TOOLBAR,这方面的文章很多,我的开发就是介绍了徐景周的一篇文章,到网上找一下,很多转载(我发现网上原创的东西实在不多呀)。这样一个简单的COM加载项的框架就建好了。
  手写签名部分的实现,这部分都是一些相对编程者容易的问题。简单说一下,首选插入一个对话框(在菜单插入->插入ATL对象里面),加入几个按钮,写一个单向链表用来存签名的笔画,用兼容DC、兼容位图、OleCreatePictureIndirect,把图片创建成一个IPictureDisp对象,以供下面的使用。
  再插入一个Full Control对象,方法同上。把上面一步生成的IPictureDisp写进Full Control对象,再调用WORD的AddOleCtrol方法将这个东东插入就可以了。
  这就是一个简单的流程。说的很粗略,因为不知道感兴趣的人多不多。
  在开发的过程中我遇到了以下这样一些问题,后来慢慢解决了,如果大家有兴趣可以来交流一下。
  1.如何在插入对象中取到WORD的ActiveDocument的指针(这个不能通过保存的方法来)。
    取Container的指针,再查IDispatch的接口。
      2.如果在程序里控制控件的删除,如在控件内点击,通过上下文菜单文菜单删除控件。
    可以给控件设置一下标志,然后通过Word的方法遍历对象组,检查对应的标志,然后删除。
      3.如何保存。
   可以重载框架的Save和Load方法,把对象的相关属性保存。这两个方法会由WORD自动调用。
      4.如何使用TOOLTIP。
    这个在MSDN里有详细的帮助,可以参照解决。
      5.如何移动控件。
    处理控件的OnLButtonDown/OnMouseMove/OnLButtonUp消息,然后通过Word的方法移动控件。
     简单写了一下思路,欢迎交流。


David Lee 2009-04-09 22:52 发表评论
]]>