Archive for December, 2009

犯错啦

2009-12-28 22:04 | by 2ndboy

  工作上的事,不管是多小的事,还是要再细心些、再仔细些!

  建立信誉是个长期艰巨的事儿,破坏信誉却太容易!

  不要怪运气不好,怪运气不好说明还有侥幸心理,做错了事就要承担后果/总结教训。

  犯低级错误对于完美主义者来说是种耻辱。

  鄙视一下落井下石的低素质“ren”,时不时要跳出来昭示一下你龌龊的手段。

  好了,也不要太在意一城一池的得失,站在圈子外面看的话,你也太不洒脱了!

年终总结 for 2009

2009-12-27 14:41 | by 2ndboy

  又到年底啦,来个年终总结吧,总结过去,展望未来:D

  今年生活上最大的变化就是装修和搬家,终于搬到自己的房子里住了,结束了长达 6 年收别人房租,自己又不用交房租的生活;) 虽然目前房子的位置不错(买菜乘车都很方便),但还是希望今后可以换个再大套一些的,小区环境好一些的。呵呵,不过目前杭州的这个房价实在是变态,这个愿望的实现还得再等等。

  工作方面,今年的状态那就是一个字——忙!个人感觉是从业以来最忙的一个年头,而且明年应该还会继续这样忙下去。工作之外应朋友之邀帮忙做了一些东西,所以忙上面还要再加一个累字,还好没有持续太长时间,否则可真要受不了了。虽然工作忙,但是今年在做的一个项目经历了一次比较大的调整,前期做的工作基本上等于是白忙了一场:( 最近正在从另外的方向做努力,累是累了点,但是由于接触的是以前不曾接触过的领域,所以感觉收获良多,学到的新东西也不少,从这方面讲,多少还有些安慰。

  身体方面,搬家之后周末经常会跟 LP 一起去爬山,运动量上应该说比以前是多了,不过从 US 回来以后积累的脂肪却一直不见少,看来还是要多运动,增强体力和精力。

  今年看了一些有关移民的资料和过来人经验,对移民有了更清醒的认识。虽然近几年没有移民的打算,但是这也许是几年以后的一个选项,所以英语方面,平时还要加强。

  新的一年,希望家人健康、平安、幸福。俺自己事业顺利,每天开心:D

聊聊 API hooking

2009-12-14 22:30 | by 2ndboy

  今天接着说说 API hook。API hook 大致上有两种手段:修改 IAT 和修改 API 指令。那种用一个自己写的同名 DLL 来替换目标 DLL 的手段就不提啦,可操作性太差。今天聊一下直接修改 API 指令这种方法。

  直接修改 API 指令,简单的说就是在目标 API 代码的开始处修改一些指令,让执行绪跳转到自己的代码里面。这里需要面对的问题有 3 个:如何找到 API 的代码地址,如何修改代码段,如何构造执行绪转移指令。

  前两个问题比较简单,无非是相关 API 的调用,用 GetProcAddress() 可以取到目标 API 的代码所在地址;用 VirtualProtect() 可以修改代码所在地址的访问权限,有了写权限之后就可以对目标 API 的代码做手脚了。

  至于如何构造执行绪转移指令,其实说白了就是在目标 API 代码的开始处放置一条无条件跳转指令 jmp。对于 32bits 系统,jmp 的机器码是 0xE9(这里不讨论短跳转 0xEB 的原因是通常我们自己代码跟目标 API 代码之间的偏移都大于 128),加上 32bits 的相对地址,一条无条件跳转指令的长度为 5 个字节。也就是说我们要至少覆盖目标 API 开始处的 5 个字节,但是直接覆盖别人的代码是肯定会出问题地,所以我们在覆盖目标 API 开始处指令之前要把这些指令搬到其它地方去,后面还有用。

  对于 jmp 指令后面那个相对地址,不是简单的拿目标地址减去当前 jmp 指令的地址就行,由于 CPU 要执行过 jmp 指令,拿到跳转地址再进行跳转,所以计算相对地址的时候要用“目的跳转地址 – ( jmp 所在指令地址 + 5 )”。目前为止,把对目标 API 的调用劫持到我们自己的函数这个目的已经达到了。但是我们还是得在需要的时候调用原始 API 来完成 caller 想要完成的功能,不过别忘了原始 API 已经被我们破坏掉了,所以调用原始 API 还要靠我们之前保存的 API 代码起始处的那些指令。

  假设我们刚好从目标 API 处复制了 5 个字节的完整指令(注意一定要是完整指令)放在内存 A 处,那么在 A + 5 处再构造一条跳转指令跳回到 API + 5 处就等于是把断成两截的 API 代码又连接了起来。这样一来,在需要调用原本的 API 功能时我们要调用的其实是 A。

以下是一些示例代码:

  1. typedef HRESULT (WINAPI *_DirectDrawCreate)( GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter );
  2.  _DirectDrawCreate pOldDirectDrawCreate = NULL;
  3.  
  4.  HRESULT WINAPI
  5.  MyDirectDrawCreate( GUID FAR *lpGUID, LPDIRECTDRAW FAR *lplpDD, IUnknown FAR *pUnkOuter )
  6.  {
  7.      MessageBox( NULL, "MyDirectDrawCreate", "MyDirectDrawCreate", 0 );
  8.      return( pOldDirectDrawCreate( lpGUID, lplpDD, pUnkOuter) );
  9.  }
  10.  
  11.  int main( int argc, char *argv[] )
  12.  {
  13.      HMODULE hDll = LoadLibrary( "DDraw.dll" );
  14.      if( hDll )
  15.      {
  16.          pOldDirectDrawCreate = (_DirectDrawCreate)GetProcAddress( hDll, "DirectDrawCreate" );
  17.          HookAPI( (PVOID *)(&pOldDirectDrawCreate), MyDirectDrawCreate );
  18.      }
  19.  
  20.      LPDIRECTDRAW pDDraw = NULL;
  21.      HRESULT hr = DirectDrawCreate( NULL, &pDDraw, NULL );
  22.      if( DD_OK == hr )
  23.          hr = pDDraw->SetCooperativeLevel( NULL, DDSCL_NORMAL );
  24.  
  25.      return( 0 );
  26.  }

以上代码成功执行后,当我们调用 DirectDrawCreate() 的时候会被之前设置的 hook 函数截获,显示一个对话框。注意 HookAPI() 的第一个参数是指向指针的指针,所以我们开始时拿到的“原始” API 地址其实被换掉了(见上面描述,原始 API 代码已经被我们断成两截啦)。

下面是代码里用到的两个关键数据结构:

  1. #pragma pack (1)
  2.  struct HookTarget
  3.  {
  4.      char jmp;
  5.      int  address;
  6.  
  7.      HookTarget()
  8.      {
  9.          jmp = char( 0xE9 );
  10.      }
  11.  };
  12.  
  13.  struct HookThunk
  14.  {
  15.      char instructions[16];
  16.      HookTarget HookProc;
  17.  
  18.      HookThunk()
  19.      {
  20.          memset( instructions, 0x90, sizeof( instructions ) );
  21.      }
  22.  };
  23.  #pragma pack ()

HookTarget 是用来写覆盖在目标 API 起始处的无条件跳转指令的,HookThunk 是用来把断成两截的 API 代码连起来的。0x90 是 nop 的机器码,就算我们只从目标 API 那里复制了 5 个字节,后面的 nop 也会安全的让执行绪运行到 jmp 指令处。

下面是 HookAPI() 的代码:

  1. bool
  2.  HookAPI( PVOID *ppProc, LPCVOID pMyProc )
  3.  {
  4.      do
  5.      {
  6.          DWORD dwOldProtect = 0;
  7.          if( !RemoveReadOnly( *ppProc, 16, &dwOldProtect ) )
  8.              break;
  9.  
  10.          HookThunk *pThunk = new HookThunk;
  11.          int cbInstructions = CheckInstructions( (LPBYTE)*ppProc );
  12.          memcpy( pThunk, *ppProc, cbInstructions );
  13.          pThunk->HookProc.address = ( (int)*ppProc + cbInstructions )
  14.              - ( (int)pThunk + sizeof( HookThunk ) );
  15.  
  16.          DWORD dwTemp = 0;
  17.          if( !VirtualProtect( pThunk, sizeof( HookThunk ), dwOldProtect, &dwTemp ) )
  18.              break;
  19.  
  20.          HookTarget *pOriginalProc = (HookTarget *)*ppProc;
  21.          pOriginalProc->jmp     = char( 0xE9 );
  22.          pOriginalProc->address = (int)MyDirectDrawCreate - ( (int)pOriginalProc + 5 );
  23.  
  24.          if( !VirtualProtect( *ppProc, 16, dwOldProtect, &dwTemp ) )
  25.              break;
  26.  
  27.          *ppProc = pThunk;
  28.          return( true );
  29.      }
  30.      while( false );
  31.  
  32.      return( false );
  33.  }

就不详细解释了,看完了上面那一大坨原理分析,HookAPI() 这个函数很容易看懂。上面代码里面的 RemoveReadOnly() 就是对 VirtualProtect() 调用的包装而已,对于我们这个示例的目标 API——DirectDrawCreate(),CheckInstructions() 简单的返回 5 就可以了(如果程序执行异常,最好自己用调试器看一下指令边界在哪里)。

  注意以上的示例代码只是用来做测试之用,没有做完备的错误处理。而且在实际环境中目标 API 代码的起始指令边界不一定正好是 5 个字节,如果边界是 4 个字节和 6 个字节处,由于我们至少要覆盖 5 个字节,所以要多复制一些(复制 6 个字节)。另外代码中使用的指令集和地址都只适用于 32bits x86 系统,在其它 CPU 和 OS 上要做相应的修改才能正常运行。最后,在实际的环境中,还要写卸载 hook 的代码;)

Apache mod_rewrite 经验一则

2009-12-14 13:12 | by 2ndboy

  周末呼吸道发炎,今早起床发现还没好,所以在家休养。照理说这么个小毛病是不影响上班地,但是之前有过好几次类似的经验——呼吸道发炎,还去上班,结果在空气污浊的办公司待了一天,随后就严重起来啦!顺便在这儿鄙视一下公司办公室的空气,由于层高太低,电脑众多,通风不畅,基本上下午 2 点一过就头昏脑胀~~再说下去要坏了心情啦:D

  周末研究了一下 Apache 的 rewrite,这个东西有 RE 基础的人上手还是比较快的。不过期间遇到的小问题折腾了好久,我一开始试验的时候规则是放在 httpd.conf 里面的,后来移到 .htaccess 里面就没了效果,开始以为是没有开启对 .htaccess 的支持,后来经过大量折腾,才明白是规则写在 httpd.conf 和写在 .htaccess 里面,收到的 URL 不一样,比如访客用 http://www.foo.com/ 来访问,在 httpd.conf 里面拿到的 URL 串是“/”,而 .htaccess 里拿到的居然是“”,即便 .htaccess 所在目录的内容已经被配置成虚拟主机也一样,所以在 httpd.conf 里面的如下规则:

  1. RewriteRule /$ index.php?rewrite=1 [L]

放到 .htaccess 里面以后要改成:

  1. RewriteRule ^$ index.php?rewrite=1 [L]

  这就是有经验跟没经验的区别,浪费了时间,走了弯路换回来的经验,值得小记一下:)

DirectShow filter 体系初探

2009-12-6 13:11 | by 2ndboy

  再次工作上项目需要,要研究下 DirectShow 的 filter 体系,以及通过将自己实现的 filter 插入到播放 audio/video 时的 filter graph,最后达到获取进入 render filter 前的已经解码的 A/V 数据的目的。

  上周五利用下午时间已经找到了实现的方向,记录如下。其实网上公开的资料多如牛毛,现在的感受,不管是做哪方面的 research,只要不是高精尖到什么隐形飞行器涂料技术,你基本都可以在 Internet 上找到很多线索,有时候甚至是标准答案(所以说熟练运用搜索引擎很重要,这是快速学习的基础所在)。

  DirectShow 基于 COM 来构建,设计思想非常值得借鉴。在 DS 中发挥作用的组件都被称作 filter,每个 filter 有各自不同的作用,当它们被正确的连接在一起时就可以完成 audio/video 的回放。

  filter 这个名字起的不错,当多媒体数据流经一个个“过滤器”,经过 A/V 分离、decode 和 render 之后被最终呈现的用户面前。总体上说,有这么 3 类 filter:
1) source filter,负责读取源数据
2) transform filter,负责做各种数据变换
3) render filter,负责渲染结果

  每个 filter 都暴露出一到多个 pin,他们各自之间通过 pin 进行连接。source filter 只有 output pin,而 render filter 只有 input pin,transform filter 则既有 input 又有 output pin。当这些 filter 正确的通过各自的 pin 连接在一起时,就形成一个 filter graph。

  就拿用 DS 来播放一个 MP3 来说,filter graph 里会出现如下几个 filer:
一个 source filter,后面接一个 MPEG-I Stream Splitter(当然这只是一个 audio,所以不会分离出 video 数据来),然后是一个 MPEG Layer-3 Decoder,最末端是一个 Default DirectSound Device 的 render filter。
filter graph

  所以看来我们如果想在这中间插一杠子,把已经解码过的 A/V 数据拿出来的话,就需要开发一个 transform filter。经过一番 Google,发现我们要做的就是实现一个 Sample Grabber Filter,它本质上还是一个 transform filter,DS 架构里有这么一号的本意是为了在开发 transform filter 的过程中方便测试 transform filter 工作是否正常的,但 sample grabber filter 也同样非常适合抓取流经 filter graph 的数据!其实系统中是存在一个现成的 Sample Grabber Filter 的,我们要做的工作就是把这个 filter 加到 filter graph 里来,然后去实现一个 Sample Grabber Filter Callback 就可以拿到数据了。