2013年9月8日星期日

移植NT4的扫雷源码到Win1.x/2.x,加上一些对Win1/Win2的看法

这几天看到了Nathan Lineback的新文章(http://toastytech.com/guis/win1x2x.html),他把Winemine给移植到了Windows 1.x/2.x上。我有一份当初2004年Windows源码泄露事件中出现的微软版扫雷源码,于是也试了试。
我使用的平台是Microsoft C 5.1+Windows 2.03 SDK。这个平台比起以前那个不完整版的MSC 4.0+Windows 1.03 SDK来,至少能基本支持标准ANSI C语法了。
首先是一些基本的改动,比如和Windows源代码中的那个port1632.h相关的东西一定要先改过来。这些东西,自己玩过那套源码的都应该知道。
至于Makefile,可以不用自己写,原来那个makefile.dos可以稍微改一下来用,要把生成EXE的语句放到最后面,然后编译器相关的语句挪到最前面;相应的链接器要改成link4,库名也要改。
然后就是资源问题。Winmine的位图资源读取方法不是通过常用的LoadBitmap,而是用一套LoadResource/LockResource/SetDIBitsToDevice直接处理资源里的数据。Winmine自身带有黑白、彩色两套位图资源,代码里有相应的两条路径,需要通过仔细阅读所有C源程序文件,将其简化为一套代码。这个这里就不多提了。
Windows 1.x/2.x API不仅不支持DIB的直接处理,甚至其资源编译器所支持的BMP文件格式都不一样。Windows 3.0至今的未压缩BMP文件格式,其文件起始部分会有一个14字节的文件头、40字节的信息头,如果文件为索引色格式(如2色、16色或者256色等),每种索引颜色会再使用4个字节。而Windows 1.x/2.x的BMP文件格式,到现在根本就没有任何软件能直接读写,甚至文件头都基本上是通过观察SDK里图标编辑器输出的文件而推测出来的(感谢John Elliott)。
这种BMP文件只支持黑白两色,其文件头长度16字节,第一个字节恒为02,第二个字节为00或者80(80代表DISCARDABLE也就是可以动态从内存中移出的),然后跟着一个这样的结构:
 typedef struct tagBITMAP {  
   short   bmType;  
   short   bmWidth;  
   short   bmHeight;  
   short   bmWidthBytes;  
   BYTE    bmPlanes;  
   BYTE    bmBitsPixel;  
   LPSTR   bmBits;  
 } BITMAP;   
(来自Windows 2.03的windows.h)

这里的short是16位整数,bmBits指针是32位长度,在这里恒为0。
文件头之后就是文件数据部分,据我的测试(通过制作Win3.0 单色BMP文件,去掉文件头之后加上自己拼出来的1.0/2.0文件头,再用图标编辑器打开看是否正常),发现它和Windows 3.0以上的单色位图(2种索引色)的数据格式并不一样,每行字节数不管怎么设置都无法显示正确的位图。情急之下,我问了Nathan Lineback,得知这种单色位图的数据部分和Paint Shop Pro 3.x/4.x版本输出的Sun raster格式(.ras)位图的数据部分基本相同,只是按位取反了而已。于是,下载了Paint Shop Pro 3.1.2,然后用它打开Winmine资源里的黑白位图,反色,另存为RAS文件;再去掉这些RAS文件的文件头(32个字节),加上自己拼出来的Win1.x BMP文件头,反复修改每行字节数,用图标编辑器反复测试打开(如果不想一直为了这个而跑Windows 1.x或者2.x,可以用Borland Resource Workshop 4.5把2.03 SDK的图标编辑器做资源转换后,在NTVDM上直接运行),最终就把三个黑白位图资源文件(Blocks、Buttons、LEDs)转换为了Windows 1.x的BMP格式。如图1、图2。


图1 Paint Shop Pro 3.12中处理后的RAS位图文件



图2 转换资源之后的图标编辑器,打开转换好的位图文件

至于图标文件,无关紧要,用图标编辑器先随手画一个用着算了。
资源文件转换好之后,要对rc文件也做相应的修改,不仅要修改其中用到的文件名、去掉不存在的头文件导入,还要把所有自带的导入文件的内容放到主rc文件里,因为Windows 2.03 SDK的资源编译器不支持导入文件中定义的字符串、对话框等;至于Windows 2.03不支持的一些东西,比如对话框的FONT关键词之类,可以到编译生成时根据错误信息修改。
之后就是改写Winmine的图形核心部分了。Winmine的图形核心是grafix.c,里面包括读取位图、绘制边框、绘制状态按钮、绘制雷区方块和绘制整个雷区等关键代码。
首先要改的就是读取位图函数。本函数把Winmine的三个位图,按钮、LED和雷区方块(Button、LED、Blocks)从资源中读入内存,并将这些大图按照格数、位图数据起始地址、每小块数据的字节数分成小块,对于常用的雷区方块,每个小块都专门给一个句柄。
一开始就是起始地址的计算。原版代码计算出的位图数据起始地址,是从LockResource的结果开始,加上BMP信息头的长度(Windows 3.0以上的资源编译器会去掉BMP文件的主文件头)和颜色索引使用的长度;而Windows 1.x的资源编译器,并不会对BMP的文件头作处理,而且文件头结构不同,因此要把这里修改掉。grafix.c里专门有一个函数来计算每小块数据的字节数,但对我们的bmp来说,计算出来的结果是错误的,因此这里不用,使用我们以(每行字节数*总行数)/格数计算出来的值(每行字节数、总行数可以直接从文件头的那个BITMAP结构中得到)。
然后往下看,就是把Blocks位图分块为多个位图的代码。这里用到的CreateCompatibleDC、CreateCompatibleBitmap都没有问题,问题在于后面用到了SetDIBitsToDevice,Win2.03没有,必须修改掉。这里我们使用SetBitmapBits来替代,这是一个和设备相关的函数,使用方法可以参考MSDN。这里需要特别注意的是,在Win1.x的BMP中,图像数据是从第一行从上往下存储的,而不是像后来的BMP那样从下往上存储。因此,图像小块的地址与序号的对应关系需要考虑清楚。
在下面的代码中,还有两处用SetDIBitsToDevice来画图的地方(画LED和画状态按钮)。因为这些函数就只传入一个hDC,我的改法是在这两个函数里新建一个临时位图句柄,然后先用SetBitmapBits把图像小块复制到临时位图里,再BitBlt到目标hDC。这里同样要注意图像小块的地址与序号的对应关系。
grafix模块的改动基本就这些,其他的文件根据编译错误与警告来做相应的修改(基本都是一些无关紧要的玩意,很容易改),都可以改到编译通过。编译设置是Small内存模型,此时不知是链接器还是资源编译器的问题,运行时无法加载资源并出错,因此只能在makefile.dos里把生成代码改为Medium内存模型(编译器参数的-AS改为-AM,两个库文件改为mlibw和mlibcew)后,才能运行成功。
在Windows 2.03上运行虽然没有报错,但是却发现,雷区是花版的,而且状态按钮边框显示明显有错误,如图3。

图3 显示有错误的界面

为什么会变成这样呢?仔细再查一下grafix.c。负责描画整个背景的函数是DrawBackground,状态按钮边框的问题就出在这里了。请看以下代码:
     DrawBorder(hDC, x =dxWindow-(dxRightTime+3*dxLed+dxpBorder+1), dyTopLed-1, x+(dxLed*3+1), y, 1, 0);  
     DrawBorder(hDC, x = ((dxWindow-dxButton)>>1)-1, dyTopLed-1, x+dxButton+1, dyTopLed+dyButton, 1, 2);  
这两行代码有一个很有趣的现象,前面参数中修改了变量x,在后面的参数中又马上引用了。C语言标准好像并没有规定在传参之前各参数表达式的执行顺序,如果这个顺序是从左到右,那么这两行的设计目的就能达到;但如果是从右到左,那么这段代码的效果就和设计意图不符了。干脆试着把这个对x赋值的表达式移到外面来,再次编译运行,状态按钮的边框居然正常了。
然后花屏问题呢?仔细检查所有相关代码,没有发现有逻辑问题。查了一下,查到微软知识库(http://support.microsoft.com/kb/160522)里说CreateCompatibleBitmap在NT和9x系统上的效果有差异,后者不会初始化生成的位图数据。于是,根据知识库的实例,在刚才说的Blocks位图分块代码中,CreateCompatibleBitmap、SelectObject之后,SetBitmapBits之前加入一句
                PatBlt(MemBlkDc[i], 0, 0, dxBlk, dyBlk, BLACKNESS);  
来初始化CreateCompatibleBitmap生成的位图数据。这样一来,雷区花版的问题应该解决了。但是编译运行之后,发现颜色有异。如图4:

图4 雷区颜色不正确

继续查。查MSDN的PatBlt,说BLACKNESS并不是指黑色,而是设备调色盘中的颜色0。但是不用PatBlt的话,我用了几种别的方法,什么FloodFill之类,但不管怎么去弄,最后前景还是红色的,看来问题不是出在这里了。就在一筹莫展之时,我看到了原有注释里有这样两句:
       //  
       // create the bitmap for the above memory DC and selct this bitmap.  
       //  
       // we really only need 1 bitmap and 1 dc as they have done before!  
       //  
对啊,为啥一定要把完整的Blocks位图分成n个呢?如果只做一个Blocks位图的HDC,需要时用BitBlt把所需部分直接贴到目标处不就行了么。
说到做到,写几行代码实现下。
      bminfo=(LPBITMAP)(lpDibBlks+2);  
     cb= ((bminfo->bmWidthBytes)*(bminfo->bmHeight))/iBlkMax;  
     hDC = GetDC(hwndMain);  
      hbBlksDC = CreateCompatibleDC(hDC);  
      hbBlks= CreateCompatibleBitmap(hbBlksDC, dxBlk, dyBlk*iBlkMax);  
      SelectObject(hbBlksDC, hbBlks);  
      SetBitmapBits( hbBlks,cb*iBlkMax, lpDibBlks+cbDibHeader);  
新加全局变量HDC hbBlksDC、HBITMAP hbBlks,然后通过这几行把整个Blocks位图变成了一个hDC。底下的那些分块处理的代码,就注释掉。
然后修改描画雷区方块的代码,有两三个函数里用到而且写法差不多,这里就列其中一个函数的改法。其中红字为新加上的代码,绿字为注释掉的原有代码,列出来如下:
 VOID DisplayBlk(INT x, INT y)  
 {  
     HDC hDC = GetDC(hwndMain);  
     BitBlt(hDC,  
         (x<<4)+(dxGridOff-dxBlk),  
         (y<<4)+(dyGridOff-dyBlk),  
         dxBlk,  
         dyBlk,  
        /* MemBlkDc[iBLK(x,y)],  
         0,  
         0,*/  
         hbBlksDC,  
         0,  
         16*(iBlkMax-1-iBLK(x,y)),  
         SRCCOPY);  
     ReleaseDC(hwndMain, hDC);  
 }  
其实很简单,就是把原来从Blocks分成的某一小块来BitBlt,变成从完整的Blocks位图里选出一部分来Bitblt。然后千万别忘了改释放位图的函数:
 VOID FreeBitmaps(VOID)  
 {  
     int i;  
     if (hGrayPen != NULL)  
         DeleteObject(hGrayPen);  
     UnlockResource(hResBlks);  
     UnlockResource(hResLed);  
     UnlockResource(hResButton);  
     /* for (i = 0 ; i < iBlkMax; i++) {  
       DeleteDC(MemBlkDc[i]);  
       DeleteObject(MemBlkBitmap[i]);  
     }*/  
           DeleteDC(hbBlksDC);  
           DeleteObject(hbBlks);  
 }  

编译运行测试,结果雷区的颜色居然正常了。可能是因为Win1.x/2.x没有DIB,调色盘处理有一点小缺陷吧。如图5。


图5 雷区颜色终于正常了

上面我们一直是在Windows 2.03下测试运行的,Windows 1.x下跑会怎么样呢?让我们来看看。程序在Windows 1.01下的运行状态如图6所示。

图6 Windows 1.01上运行的状态

可以看出,由于Windows 1.x限制普通窗口不可层叠,扫雷界面很难完整显示出来;而在Windows 1.01下能层叠显示的只有带WS_POPUP属性的窗口或对话框。我们找到主模块中的CreateWindow函数,修改一下主窗口的风格。
      {  
      DWORD style = WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU;  
      if (LOBYTE(GetVersion())>1)   
           style = style | WS_OVERLAPPED;  
      else  
           style = style | WS_POPUP;  
      hwndMain = CreateWindow(szClass, szWindowTitle,  
         /* WS_OVERLAPPED | WS_MINIMIZEBOX | WS_CAPTION | WS_SYSMENU*/style,  
           Preferences.xWindow-dxpBorder, Preferences.yWindow-dypAdjust,  
           dxWindow+dxpBorder, dyWindow +dypAdjust,  
           NULL, NULL, hInst, NULL);  
      }  
这里用了一个临时变量,首先判断系统版本,然后动态设定窗口风格,因而能达到我们的目的。这样,在Windows 1.01下运行得到的就不是一个平铺的窗口而是一个层叠窗口了,唯一的问题是不能最小化和后台运行,因为WS_POPUP风格的窗口在Windows 1.01下总是出现在前面的。如图7。

图7 Windows 1.01上主窗口以WS_POPUP形式显示

至此,主要的移植任务已经差不多了。虽然还有一些严重的bug,比如程序稳定性还有点小问题,而且在Win1.01下,一旦死掉或者胜利后重来,计时器就不动了,而2.03下就没有这个问题。另外,窗口位置调整函数等一些东西也需要修改,资源文件里也要为Windows 1.x做一套没有'&'字符的菜单定义,等以后有时间再来弄一下吧。

移植了这玩艺,我想对Windows 1.x/2.x这个被人彻底遗忘的平台说几句看法。首先,Windows API对其历史的重视和向下兼容是名不虚传的。我移植扫雷的整个过程中,几乎一直都在搜索在线版本的MSDN,发现Windows 2.03唯一的一个头文件windows.h里的很大一部分函数,在2013年的MSDN里都还能找到名字相同,参数可能有些差异但差异不大的函数;至于概念上的东西,比如消息循环、GDI对象架构之类,25年来更只是在实现上不断地改变。25年前学会Windows SDK编程的人,到现在来学习现在的Windows SDK开发,其学习曲线比起其他平台来说是要短很多的。其次,通过这一次的移植,我算是知道当年Windows 1.x/2.x的商业应用不超过两位数的原因了:一是硬件支持太有限,Windows 1.x只能在640K常规内存中运行,Windows 2.x虽然支持EMS,但是EMS同一时间只能访问其中一个64K大小的页面,这使得当时的Windows平台难以像其他可以直接访问大内存的平台那样进行复杂的图像处理等操作,吸引力大大降低;二是开发工具有限,开发者只能使用微软自己的编译器和CRT,并且需要自己编写Makefile(当然现在写DDK的也要这么做),而当时苹果Mac平台上除了苹果自己那个使用起来复杂困难的MPW环境外,已经出现了不少便利的第三方集成开发环境,比如Think C等。三是微软最初并没有把Windows放在其战略核心的位置上,这是因为,当时微软尚未完全预见到未来的走势,商业用户们还在沉醉于他们的DOS版Lotus-1-2-3和WordPerfect,觉得Mac都仍然只是昂贵的玩具,更别说去想那些Unix工作站上已经跑起来的东西的未来前景了。Windows 1.x/2.x的GUI,很大程度上是为了和苹果斗气而作出来的一个试验性产品,不少地方使用起来非常麻烦,其外壳程序MS-DOS Executive比起不少DOS下的文件管理器来说简直就是废物。当时,微软很大一部分的希望是寄托在和IBM合作开发的OS/2上。直到80年代末,微软与IBM的亲密合作关系开始出现裂痕,IBM这个靠山靠不住了,微软才真正决心把Windows做成他们的核心产品,先是为Mac平台上已经取得成功的Word、Excel制作了Windows版本,再是全力开发Windows 3.0,从此Windows才真正开始走向成功之路。
我认为,虽然Windows 1.x/2.x没有被广泛使用,但是Windows API 三十年的基业,也正是从这样一个简陋的环境开始一步一步走过来的。因此,这个环境对于我这样的一个古董软件爱好者来说,有很大的研究意义。

后记:也许因为我没有采用LoadBitmap来处理资源,这个程序竟然可以不加任何修改地在Windows 8 32位版的NTVDM中直接运行,如图8。但是我在Windows 3.0和3.1下尝试运行时,却出现了死机的现象,看来兼容性还是有些问题的。


图8 在32位Windows 8下运行我移植的扫雷




2013年7月19日星期五

在VS2005+Win7SDK环境下编译现在的Firefox

注意:标题里所说的现在,是指2013年7月19日的mozilla-central hg revision 0d0263a58f06。
大概从2013年5月开始,由于官方采用VS2010已经很长时间,再加上在VS2005、2008环境下出现的问题越来越多,Mozilla官方干脆不再支持在VS2005和2008下编译Firefox(详见Bug 866425)。而过了两个月,到了2013年7月,我尝试在VS2005环境下编译时,发现已经需要大幅度的修改,才能编译成功了。
以下提到的所有路径,都是相对于mozilla-central的目录而言。

首先,是configure文件的修改。前面提到的Bug 866425的解决方案把configure文件里关于VS2005的部分给删除了,得通过修改给它加进去。
configure.in
      elif test "$_CC_MAJOR_VERSION" = "18"; then  
        _CC_SUITE=12  
        _MSVS_VERSION=2013  
这里把18改为14,_CC_SUITE改为8,2013改为2005
js/src/configure.in
      elif test "$_CC_MAJOR_VERSION" = "18"; then  
       _CC_SUITE=12  
修改如上。这样,configure就可以认出VS2005的编译器了。

虽然说微软的编译器对C99的支持一向不足,但是VS2010的CL16开始,加入了以前一直都缺少的一些C99的头文件,比如stdint.h。原本Firefox使用了一个自己的机制(StandardInteger.h和MSStdInt.h,如果判断系统没有stdint.h就使用自带的MSStdInt.h代替)来解决stdint.h不一定存在的问题,但自从Firefox官方不再支持低于VS2010的微软编译器后,有些开发者开始在新加入的文件中直接使用stdint.h了。比如
mfbt/Atomics.h
 #include <stdint.h>  
应该改为
 #include <"mozilla/StandardInteger.h">
Mozilla的开发者对直接使用stdint.h有关的这个修改,还影响到了工程中的其他东西。原本Mozilla使用的几个第三方库的头文件会根据微软编译器的版本自己定义一些标准整数,这些定义可以通过宏来屏蔽掉;由于现在Firefox不再支持低于VS2010的微软编译器,这些宏定义在一些makefile里被干掉了,导致编译到调用这些第三方库头文件的模块时发生重复定义的错误。我没有具体去找是哪些makefile导致这些问题,干脆把那几个头文件本身改掉了。
gfx/harfbuzz/src/hb_common.h

 #if !defined (HB_DONT_DEFINE_STDINT)  
前面加上
 #if defined(_MSC_STDINT_H_)  
 #define HB_DONT_DEFINE_STDINT  
 #endif //如果使用了MSStdInt.h,就屏蔽掉这个头文件自带的整数定义  
gfx/cairo/libpixman/src/pixman.h

 #if !defined (PIXMAN_DONT_DEFINE_STDINT)  
前面加上
 #if defined(_MSC_STDINT_H_)  
 #define PIXMAN_DONT_DEFINE_STDINT  
 #endif //如果使用了MSStdInt.h,就屏蔽掉这个头文件自带的整数定义  
这样应该就没问题了。

VS2005对于C++的标准一是支持有些不完善,二是可能会出现一些奇怪的Bug。在我编译的过程中,这些问题反复出现了多次。
首先是Firefox的一个新的第三方库webrtc,是为了支持浏览器中的视频通话而开发的。在VS2005编译的过程中,会出现一个继承类几个方法的参数定义和基类不同而造成的错误。
在media/webrtc/trunk/webrtc/modules/rtp_rtcp/source/rtp_rtcp_impl.h定义的类ModuleRtpRtcpImpl中的如下方法
   virtual int32_t SetRTXReceiveStatus(const bool enable,  
                     const uint32_t ssrc);  
   virtual int32_t SetRTXSendStatus(const RtxMode mode,  
                   const bool set_ssrc,  
                   const uint32_t ssrc);  
与其在基类RtpRtcp,定义于
media/webrtc/trunk/webrtc/modules/rtp_rtcp/interface/rtp_rtcp.h中的原型
    virtual int32_t SetRTXReceiveStatus(bool enable, uint32_t SSRC) = 0;  
    virtual int32_t SetRTXSendStatus(RtxMode mode, bool set_ssrc,  
                    uint32_t ssrc) = 0;  
比较,会发现基类里面的这两个函数参数都不带const关键字,VS2005编译到这里,会因为这个差异而把继承类ModuleRtpRtcpImpl里这些方法看成抽象方法而导致编译错误。不知为什么在VS2010之后的版本里没有这个问题,我C++忘得差不多了,求高人解释一下。我这里把上面列出的这些参数定义中的const关键字去掉,就能编译通过了。
在编译到content/base/src/nsDocument.h的时候,有一个宏NS_FORWARD_NSIDOMNODE_TO_NSINODE_OVERRIDABLE会编译出错。仔细检查,这个宏定义在content/base/public/nsINode.h中,其定义为
  #define NS_FORWARD_NSIDOMNODE_TO_NSINODE_OVERRIDABLE \  
   NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER()  
而NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER的定义超长,但反正是一个使用了“...”关键字和 __VA_ARGS__ 关键字的可变参数的宏。搜索了一下,貌似VS2005处理这种类型的宏存在bug,也有不少说这类可变参数宏是个C99的功能,微软一直都不能完美支持的云云。但是天无绝人之路,仔细搜索发现了lawlietfox的一个开发者@tete009_ 提供的关于这个的patch,具体是稍微修改了一下NS_FORWARD_NSIDOMNODE_TO_NSINODE_OVERRIDABLE这个宏定义:
 #define SLASH()   /  
 #define ASTERISK() *  
 #define NO_ARG()  SLASH()ASTERISK()ASTERISK()SLASH()  
 #define NS_FORWARD_NSIDOMNODE_TO_NSINODE_OVERRIDABLE \  
  NS_FORWARD_NSIDOMNODE_TO_NSINODE_HELPER(NO_ARG())  
这个NO_ARG()宏其实就是/**/,也就是个注释而已,为什么这样一改VS2005就不会报错了呢?难道是因为VS2005里面可变参数宏在输入0个参数的时候不行么。。但是输入NO_ARG宏一个“参数”和0个参数好像又没什么区别。这也许就是VS2005编译器预处理器的问题了。
layout/generic/nsGfxScrollFrame.cpp
编译到这里的时候,重头戏来了。这个模块中有个ClampAndAlignWithPixels函数,里面使用了C++11的一个新关键字decltype,作用是返回一个表达式的返回值类型。仔细看看用了这玩艺的那段代码:
   if (aBoundUpper == destUpper &&  
      static_cast<decltype(Abs(desired))>(aBoundUpper - desired) <  
     Abs(desired - aligned))  
   return aBoundUpper;  
   if (aBoundLower == destLower &&  
     static_cast<decltype(Abs(desired))>(desired - aBoundLower) <  
     Abs(aligned - desired))  
   return aBoundLower;  
其实就是比较两个数之差是否小于另两个数之差的绝对值,这里desired、aBoundUpper、aBoundLower类型都是nscoord,定义在gfx/src/nsCoord.h中,可能是int32_t或者是float;Abs函数是mozilla自己的,定义在mfbt/MathAlgorithms.h中,输入为int32_t时返回uint32_t,输入为float时返回float。也许因为这个nscoord不一定是一种类型,开发者才决定要先把前面两个nscoord相减的值做一个强制转换。
但我认为,这个强制转换根本就是不必要的。首先,nscoord的类型是由宏NS_COORD_IS_FLOAT决定的,在同一个源文件的同一个函数内部,两个nscoord型变量的类型不可能不同;然后我们分两种情况来看上述代码中的表达式,如果nscoord是有符号的32位整数,那么小于号左边就是一个有符号整数,小于号右边Abs的返回值就是个无符号整数。尽管两者直接比较是不行的(这个在任何一本C语言入门的书里都会说为什么),但是一个实数的绝对值必然是非负数,任何一个负实数都小于任何一个实数的绝对值,因此可以分两种情况,在这个式子里,如果小于号左边是非负数,直接和右边比较即可;如果小于号左边是负数,可以直接认为其小于右边。如果nscoord是浮点数,上述逻辑一样成立。因此,上述代码可以改写为:
  if (aBoundUpper == destUpper &&  
       ((aBoundUpper - desired) < Abs(desired - aligned) || aBoundUpper - desired < 0) )  
   return aBoundUpper;  
  if (aBoundLower == destLower &&  
       ((desired - aBoundLower) < Abs(aligned - desired) || desired - aBoundLower < 0) )  
   return aBoundLower;  
这样就避免了使用decltype来确定小于号左边表达式要强制转换为什么类型,使其在VS2005下也能编译通过。

另外,我的编译环境本身,还会有一些bug。比如Win7 SDK和VS2005的intrin.h存在冲突(说明在此),一些在官方不再支持VS2005之后修改的源文件没有考虑到这个问题,比如
xpcom/ds/TimeStamp_windows.cpp
gfx/skia/src/ports/SkThread_win.cpp
中,在
 #include <intrin.h>  
前面,应当加上
 #if _MSC_VER <= 1400  
 #define _interlockedbittestandreset _interlockedbittestandreset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR  
 #define _interlockedbittestandset _interlockedbittestandset_NAME_CHANGED_TO_AVOID_MSVS2005_ERROR  
 #endif  
来解决这个问题。

gfx/cairo/cairo/src/moz-d2d1-1.h
gfx/2d/moz-d2d1-1.h
中,有一些函数声明用到了微软的SAL注释,但是却没有引用<sal.h>而导致编译错误。
    STDMETHOD_(void, PushLayer)(  
      _In_ CONST D2D1_LAYER_PARAMETERS1 *layerParameters,  
      _In_opt_ ID2D1Layer *layer   
      ) PURE;  

    void  
    PushLayer(  
      CONST D2D1_LAYER_PARAMETERS1 &layerParameters,  
      _In_opt_ ID2D1Layer *layer   
      )   
    {  
      PushLayer(&layerParameters, layer);  
    }  
我觉得,这个函数声明应该是直接从微软的例子里抄来的,因为SAL注释这种什么_In_、_Out_、_In_opt_之类的东西除了在MSDN里之外,在任何地方几乎都是看不到的。而这种东西除开方便微软的自动化生成文档工具解析之外,根本就没有用处。我的改法是,直接把这些东西去掉,就能编译通过了。
在编译到
gfx/thebes/gfxFontUtils.cpp的时候,发生了最可怕的错误——编译器内部错误。
编译器内部错误指的是,编译器在处理某些代码的时候,由于编译器发生了内存溢出、栈溢出,或者是编译器对某段代码进行优化失败,或者触发了编译器bug,而导致编译过程不能继续而出错。这种错误中,通常代码本身没有问题。VS2005的CL编译器在发生内部错误时,会指出在处理源代码哪一行时发生了错误,因此可以通过在出错点附近禁止优化等办法来解决。在这个源文件中,出错位置位于gfxFontUtils::MapCharToGlyphFormat4函数中,我的解决办法是在这个函数代码前面加上
 #pragma optimize( "", off )  
在这个函数代码的后面加上
 #pragma optimize( "", on )  
来单独对这一个函数禁用编译器优化,结果编译通过了。

在编译到
content/canvas/src/WebGLContextNotSupported.cpp
的时候说WebGL2Context::Create()的WebGL2Context没有声明,我明明因为没有安装DXSDK而使用了--disable-webgl的参数的,怎么还会有错误?仔细查看相关代码,发现WebGL2在目前的源代码中只是个刚刚写出来的空壳,不如干脆把和这个类相关的东西去掉好了。
去掉content/canvas/src/WebGLContextNotSupported.cpp中的WebGL2Context::Create(),
再去掉content/html/content/src/HTMLCanvasElement.cpp中的
   if (WebGL2Context::IsSupported() &&  
    aContextId.EqualsLiteral("experimental-webgl2"))  
   {  
    Telemetry::Accumulate(Telemetry::CANVAS_WEBGL_USED, 1);  
    nsRefPtr<WebGL2Context> ctx = WebGL2Context::Create();  
    if (ctx == nullptr) {  
     return NS_ERROR_NOT_IMPLEMENTED;  
    }  
    ctx->SetCanvasElement(this);  
    ctx.forget(aContext);  
    return NS_OK;  
   }  
这段代码,就能顺利编译通过了。也许在之后的源代码中,这些WebGL2相关的代码在禁用webgl时都会被宏屏蔽掉,我现在这修改就没有必要了。事实上,已经有人提出了Bug 895855,估计在今天的新代码中已经修正。

最后,在链接gkmedias.dll的时候,又出现了更离奇的错误,GrGpuGL.obj中有重名的符号。用WinHex打开一看,真的有三个符号重名。这三个符号名都是根据VC对C++函数的符号规则重命名了的,但不知为什么C++符号名最后几个字符居然一样。查了一下def文件,发现这个函数是非导出函数,非导出函数并不靠符号名来调用,直接用WinHex把三个重名符号名的最后一个字符改成不同的,就链接通过了。
如果你觉得我上述的改法太hackish,还有一个办法,就是编译时--disable-skia,这个GrGpuGL.obj是第三方图形库Skia里的。但是这样又会触发Bug 891709,因此我这样尝试的时候还是修改了几个相关的文件。
gfx/2d/Factory.cpp
 #if defined(WIN32) && defined(USE_SKIA)  
 #include "ScaledFontWin.h"  
 #endif  
看了ScaledFontWin.cpp和ScaledFontWin.h,发现就算没有skia,这个类也是有实现的。因此需要把要求Skia存在的这个条件去掉。
但是在生成Makefile的时候,是否编译ScaledFontWin.cpp也是由是否使用Skia来决定的。因此也得改:
gfx/2d/moz.build
  elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows':  
    CPP_SOURCES += [  
      'DrawTargetD2D.cpp',  
      'SourceSurfaceD2D.cpp',  
      'SourceSurfaceD2DTarget.cpp',  
      'PathD2D.cpp',  
      'ScaledFontDWrite.cpp',  
    ]  
    if CONFIG['MOZ_ENABLE_DIRECT2D1_1']:  
     CPP_SOURCES += [  
        'RadialGradientEffectD2D1.cpp',  
        'DrawTargetD2D1.cpp',  
        'SourceSurfaceD2D1.cpp'  
      ]  
    if CONFIG['MOZ_ENABLE_SKIA']:  
      CPP_SOURCES += [  
        'ScaledFontWin.cpp',  
      ]  
把这里的ScaledFontWin.cpp移到前面ScaledFontDWrite.cpp那行后面去。这样,--disable-skia时编译就能通过了,运行也没有问题。

在费尽九牛二虎之力后,我终于在VS2005+Win7SDK环境下成功编译了7月19日的Firefox源代码。
也许随着Mozilla代码的继续改进,类似的错误会越来越多;但是有些错误,比如和那个MSStdInt.h相关的,可能会因为这些为兼容老VC而存在的头文件最终被删除,而可以更简单地来解决。另外,还有一些死硬派的第三方开发者,比如之前提到的lawlietfox的@tete009_,会继续研究老版本VS环境下Firefox的编译。不过,VS2005也许确实已经老了。。。

2013年6月6日星期四

买到了罗技 Revue Google TV

Google TV一直算是Google的实验性产品,非常之冷门,尤其是2010年底发布的第一代产品更是如此。罗技的Revue,则是第一代Google TV产品的典型代表。由于销量惨淡,该产品仅销售不到一年即完全退市。作为一种仅在美国发售的失败产品,此机种很少流到国内的水货市场上;但这几天,我在一个偶然的机会下,以150元的低价购到了此机的二手裸机。

本机使用常见的12V 3A电源即可工作,拥有两个USB接口、一个HDMI输出接口、一个HDMI输入接口(用于接收有线电视机顶盒输出的信号)、一个SPDIF输出接口以及一个看上去是类似蓝牙设备配对的按钮。它没有电源开关,插上电源就自动开机。
根据我的预先了解,Revue这台机器并没有可供获取root权限的漏洞,其Bootloader也是锁定的,因此要从此设备截图,需要先在设置-应用程序-开发中打开Wifi远程调试之后,再用Android SDK工具中Android Debug Monitor的截图功能进行截图。
本机自带的启动器非常简单,并没有桌面,就是一个快捷菜单和一个应用列表。

而其他Android系统中的状态栏和通知,这里需要通过点击启动器左下角的气泡图标来调出。

根据官方文档(https://developers.google.com/tv/faq#androidndk),运行Honeycomb系统的Google TV不支持NDK,不幸的是这台Revue就是如此。现在大多数的应用或多或少使用了NDK,大大限制了Revue的应用兼容性。比如常用的安兔兔评测,就无法运行:
由于安卓的中文输入法通常带有与NDK通信用的.so文件,因此就算安装了,也不能使用,要么一选中就强制关闭,要么一试图输入文字就强制关闭。


而这个系统本身不自带中文输入法(系统本身也只有英文界面可选),导致想在这机器上输入中文变得几乎不可能了。
还是挑几个只用ADK(应用不带.so文件,完全用Java编写)的东西来测试下吧。
首先是Quadrant的一个旧版本。Quadrant在安兔兔普及之前,是Android平台下公认的测试软件,我使用的这个旧版本只使用ADK而不使用NDK。但是,Quadrant会自动竖屏,这在本机会导致出错,需要使用强制屏幕方向的软件Orientation Control强制横屏才能运行。
Quadrant的系统信息显示,系统是基于Android 3.2;GPU是基于SGX535的Intel GMA500。这类似于Atom SOC第一代的Z520的配置;内存总共识别了667072kB,本系统应当总共有1GB内存。但是,这貌似并不是系统信息的全貌,比如CPU类型显示为“?”。用ADB shell查看/proc/cpuinfo,得知CPU的型号。
CPU是Atom CE4150单核双线程CPU,查资料得知这是Atom芯片中专门设计用于机顶盒之类消费嵌入式设备的型号,其性能和Atom Z520类似,但是频率较低,去掉了对PCI总线等PC兼容机必须的硬件的支持。后来Intel的Atom移动平台Medfield、Clover Trail+应该也和这个类似。
虽然Quadrant显示系统识别的内存总共有600多MB,但系统配置的应用管理里看却并没有这么多,有200多MB去哪儿了呢?
我们再看看另外一个软件Quick System Info Pro的显示吧。
这里,应用管理显示内存使用199MB,但Quick System Info Pro的实时监控却显示使用463MB。再仔细看,背景中同样是Quick System Info Pro所显示的系统缓存分区大小有250M,这样大的差异,估计是因为,Revue的分区和一般的Android不一样,/cache是一个RAMDisk。
Quadrant成绩是Galaxy S的两倍,主要体现在CPU和I/O的分数上,这在本机刚刚发售的2010年下半年还是一个比较高的成绩。
本机系统自带的浏览器是Chrome,当年Chrome V8引擎的ARM版尚在测试阶段,这一代的Google TV成为了第一批用上正式版Chrome的Android。我们通过运行SunSpider测试,来看一下其Javascript性能。
本机的Chrome跑SunSpider 1.0测试用了2859毫秒,而一台使用MTK MT6589四核CPU的手机用最新版Chrome跑相同的测试,需要用1729毫秒;另一台使用高通APQ8064四核CPU的手机则需要1856毫秒。这一方面说明本机的CPU性能的确已经不够用了,另一方面也说明Android版本Chrome的V8引擎尚不能充分发挥四核心CPU的性能。
本浏览器自带Flash插件(并非系统自带),但其版本较低,为10.2,它支持对H.264视频的硬件解码。
支持硬件解码,因为解码渲染路径的关系,截图中视频无法显示,上实拍图。Bilibili站视频正常,弹幕多的时候比较慢,需要把上面两个flash广告条拖屏幕外去。如果你想用Bilibili客户端,那还是不要考虑了,它是用了NDK的。

至于youku这样普通的视频网站,也是不成问题的。注意:这个浏览器请求的是桌面版的网站。
因为Android出NDK比较早(1.5的版本就有NDK了),因此像游戏模拟器这样需要高性能的应用很少有纯粹ADK的。我只找到了一个,Mobile Gameboy (lite)。。。这个Java版的模拟器可能是因为不用GPU输出,软件放大导致运行很慢,而且好像本来就没有声音。
至于原生的游戏,也有一小部分是纯粹ADK的,但是当中支持横屏显示、支持鼠标键盘操作的就不多了。我随便找了几个出来(我所收藏大部分的游戏都是需要NDK的)





这些都是一些小游戏,稍微大型一些的游戏以及用什么Unity3D之类引擎制作的游戏,没一个不用NDK的。
QQ只能用2011版,因为这是唯一一个不用NDK的Android QQ。
其实本机还有一些自带应用的,但是因为它们在中国大陆的网络环境下大多不能使用,因而只发两个勉强能使用的应用的图,其介绍就跳过好了。

这种机器是第一批使用Intel x86处理器的Android机型。Google也许认为给Google TV这样前途未卜的平台专门开发一个NDK甚至一个指令模拟器不太现实,再加上和DRM有关的一些“大人的事情”,就通过不支持NDK、锁Bootloader等手段严重限制了这台机器作为Android设备的实用性。这有可能是当年Logitech仅销售Revue数月就停售的原因之一。
据Google称,Google TV的Android 4.2将支持NDK开发,但这种说法的前提条件是第二代的Google TV设备使用了ARM处理器。而使用x86 CPU的第一代Google TV平台,由于其市场上的巨大失败,据称将不会移植4.2系统。当年,苹果的第一代网络机顶盒Apple TV就是x86平台,运行特制的Mac OS X(封闭系统),但是后来在iOS称霸之后,就改用了ARM SoC+特制版iOS,但仍然不支持很多iOS应用。现在看上去,Google是像苹果抛弃第一代Apple TV那样,抛弃了第一代Google TV。很多悲剧一般的机器,比如HP当年那个Touchpad,可以通过刷机而获得第二次生命;但是,有Bootloader锁的这个Revue,就只能继续悲剧下去了。不过,如果仅仅拿来看Flash视频,看那些美国用户专用的什么Netflix之类玩意,这台机器还是非常够用的。

2013年1月31日星期四

关于DOS版的仙剑1不支持超过32MiB的扩充内存的问题

最近由于个人事务比较忙,基本没有更新博客了。
这几天突然有人问我,他最近试图直接在一台P4机纯DOS环境玩仙剑1,但是无法启动,显示扩充内存不足。 这个问题是当年某些仙剑最初发布的盗版(我无法确定这个问题在原来的正版是否就有)的问题。 这些盗版的特点有:
1、在超过32MiB XMS(扩充内存)的DOS系统上,启动会失败并出现如下提示。
很可能是其检测扩充内存容量的时候发生了溢出。看这个报告出来的负数,不像是存储内存KiB数的变量的溢出。
2、部分版本带有crack intro。
 
带有这个cracktro的,一定会有如上一点的问题。
而我后来用光盘版的仙剑1主程序,自己根据 青衫之友网上的说明 (http://boneash.oldgame.tw/chiuinan/intro/ch/c11/pal.txt) 破解的EXE,可以在有超过32MiB,甚至1GiB以上XMS内存的环境运行。
这些有问题的版本,一般主程序的修改时间是1995年7月1日,数据文件的修改时间是1995年6月19日。而我的光盘版中,数据文件的修改时间是1995年7月25日。因此,还不能确定这个扩充内存容量检测错误的问题是6月19日原版就有,还是破解之后才有的。不过可以确定,7月25日的版本没有这个问题。
另外有个特典,仙剑1DOS软盘版的图形密码输入画面,这应该是很多只玩过盗版的玩家没看到过的。
 

2012年8月29日星期三

最新的音频编码器 Opus 试用

Firefox 15今天发布,其重大更新之一就是正式支持了一种新的音频编解码器Opus(http://www.opus-codec.org/)。据其官网介绍,这个编码器最初是在Skype使用的Silk编码器和Xiph.org的CELT两种适合于语音的音频编码基础上开发而成,原本为低延迟的网络语音编码设计,但同样也适于音乐编码。据官方介绍,其在低码率上优于以往各种语音编码器,高码率(64kbps以上)可和常见的音乐编码器(MP3、AAC)抗衡,而且其延迟远低于AAC、MP3等(见图1、图2);免专利费,完全开源。看到Firefox 15支持了这个新玩艺,我也想稍微试试。这不是一篇评测文章,大可不必看细节。

图1 Opus官方给出的码率-音质比较图
图2 Opus官方给出的延迟比较图
目前支持Opus编解码的只有Opus自己的libopus库,目前只能使用OGG作为其封包格式。目前由于FFMpeg尚没有Opus编码模块,想编码Opus文件只能编译Xiph.org提供的opus-tools工具包。opus-tools目前的版本是1.4.0,包括opusenc、opusdec和opusrtp等程序,其在Windows系统下的依赖有libogg 1.3.0版以上,libopus 1.0.0以上。
我使用的是以往自己编译Windows下的FFMpeg和mplayer的mingw环境,必须有pkg-config的支持存在。首先直接从Opus Codec的git上克隆libopus:
git clone git://git.opus-codec.org/opus.git
然后在opus目录下进行autogen和configure:
./autogen.sh
./configure --prefix=/mingw --disable-shared --enable-static
然后编译并安装到Mingw环境:
make
make install
以同样的方法,下载libogg 1.3.0的源码(http://downloads.xiph.org/releases/ogg/libogg-1.3.0.tar.gz)并编译安装。
克隆opus-tools的源码:
git clone git://git.xiph.org/opus-tools.git
配置:
./autogen.sh
./configure --disable-stack-protector
禁止这个是因为我的gcc开了栈保护后编译出来都要依赖一个DLL,为了绿色化,关掉它。
make
strip *.exe
把得到的opusenc.exe复制出来,就可以用来压制opus文件了。其参数比较简单,直接看运行时出现的说明即可。
基本上就是
opusenc --bitrate 码率(Kbps) 输入文件名 输出文件名
这样的形式。
注意参数只能放在输入文件名之前。另外,还有几个重要的参数:
-vbr,-hard-cbr,-cvbr 码率控制方式,包括可变码率,恒定码率和有限可变码率。默认是-vbr
--framesize (数字) 帧的长度(毫秒),支持2.5, 5, 10, 20, 40, 60几种,默认是20。这个数值越小,文件就具有越多的帧,而导致开销增多和压缩变慢,音质也会下降;提高此数值能较小地提升音质。

下面轮到播放器的问题了。FFMpeg的libavcodec目前只能通过libopus解码opus编码,但是使用比较简单。对于FFMpeg,编译加上--enable-libopus参数即可(前提是--enable-gpl);而Mplayer的configure能自动检测和启用libopus。
目前foobar2000新版本也包含了解码opus的插件。
很快就编译出来了支持opus的ffplay和mplayer。
图3 目前支持opus的部分工具和播放器
用opusenc和neroaacenc、ffmpeg+libaacplus试压了一些正版盘抓的wav文件,有中岛美嘉的雪の華、SEED的OP1、兽拳战队的OP、Falcom J.D.K的FM音源版伊苏4音乐集等。对比听了一下,感觉在64kbps下,目前版本的opus听感已经超越了一些比较差的HE-AAC编码器(如libaacplus),但是还是比NeroAAC略逊一筹;32kbps下目前音质仍比较差,不能和NeroAAC的HE-AACv2相比,甚至可能比微软的WMA还差一点。不过,在这些低码率下的人声还是比较饱满的,可能是其原本就是语音编码器的优势所在吧。还有一个问题,opus目前似乎并不能原生支持11KHz/22KHz/44KHz的采样率,必须强制向上采样到48KHz,这个也是问题的来源之一么?
其实现在的主要问题是opus尚只能支持ogg封装,而不支持封装入mkv、mp4或者webm中,因此短期内是不会见到音频编码器用这个的视频文件的。现在从各种宣传稿看来,这个编码器并不准备和AAC等私有音乐编码直接竞争,而是偏向于网络电话等语音实时通信领域,WebRTC这个基于Web的实时语音通信标准准备拿这个做标准编码。不过,经过后期改进后这个编码器应该有和HE-AAC竞争的潜力,甚至有可能取代Vorbis在基于开源方案的较低码率流媒体方案,比如WebM等中的地位。
这次编译出来的opus-tools、ffmpeg/ffplay、mplayer网盘下载:
http://www.4shared.com/archive/ij79DQ1m/opus-f.html 
http://www.mediafire.com/file/7u6oj4th5bnlkfd/opus-f.7z 
http://pan.baidu.com/share/link?shareid=7953&uk=2248457607

2012年6月6日星期三

某个号称史上最大的恶意软件的传播方式

这几天卡巴等安全厂商爆出了一个主程序文件6MB,生成文件超过20MB的“超级病毒”Flamer。目前各安全厂商的初步分析表明其具有十余个功能模块,通过连接写死的控制端(5个-10个域名)以及从控制端下载的列表中的其他控制端(已经发现了80个以上域名)来接收指令、自动更新及发送其窃取的文件(AutoCAD、PDF、Office文档等);其服务器配置文件一部分采用加密的SQLite数据库,且自带Lua解释器和运行日志记录功能,其功能复杂完善,无疑是一个非常专业的间谍蠕虫,和之前的Stuxnet/Duqu有点类似。
既然说是蠕虫,当然Flamer能自我复制传播。目前从各安全厂商和实验室的文章中,可以确定这种蠕虫至少有5种自我传播的方式,其中有一种是比较新颖的,要重点介绍。

1、autorun.inf。autorun自从被利用以来,已经成为各种恶意软件最为常用的传播方式,从最初的open/command/explore指定u盘上的可执行文件,再到劫持盘符右键菜单与Autoplay选项;从指定rundll32加载病毒dll(如Conficker),到stuxnet早期版本中将病毒体本身写成autorun.inf文件,其中的字符串组成autorun.inf结构并指定运行自身,其利用手段已经有了很大的发展。Flamer利用了这种方式传播。

2、通过desktop.ini来创建特殊文件夹,使用对象{0AFACED1-E828-11D1-9187-B532F1E9575D} 使文件夹变为一个执行其内target.lnk快捷方式的“文件夹快捷方式”。与此同时,建立一个利用了曾被Stuxnet利用过的快捷方式漏洞(MS10-046)的特殊target.lnk,导致Windows为了显示这个文件夹图标,一去解析这个lnk就运行了Flamer。

3、利用打印管理器漏洞MS10-061感染局域网其他机器。

4、在有域控制器权限的机器上在域内各主机建立后门账号并传播自身。

5、通过在内网建立代理服务器的方式,拦截Windows Update请求。

这个得重点讲一下。其实用ARP攻击之类拦截Windows Update搞假更新这样的想法早就有了,不过这次Flamer的作者想了个更巧的办法。
Windows Update当初设计的安全机制有二,一是其服务器IP是写死在更新服务程序里的,二是更新包要验证微软的数字签名。前者可以防域名劫持,后者可以防止接收到错误或者非法的更新文件。不过Flamer的作者这次把两个机制都搞定了。
这破机器没装绘图工具,图画得渣,请大家见谅。
这是一般Windows Update运行的流程。

Flamer通过以下四个步骤,成功劫持了局域网内其他机器的Windows Update并将其作为传播渠道。

 1)向网络广播两个假NetBIOS计算机名
为什么是“WPAD”和“MSHOME-(8位16进制数,不一定是图里写的那个)”呢?后者我也没搞清楚为何是这名字,不过我估计不会和网内其他任何机器名相冲突。至于前者,下一步再解释。

2)利用IE对WPAD(Web代理自动探测)协议的一个特例,使受害机器将带毒机器设定为IE代理服务器。
WPAD协议先简单解释下:浏览器可以通过从一个一定规则的URL下载一个PAC(代理服务器自动配置)文件来自动配置代理服务器。这个URL的规则可以简单描述为这样。
先向DHCP服务器(如果有)发送DHCPINFORM请求,访问返回信息WPAD字段中的URL;
如果失败,通过本机DNS信息自己一级级生成URL,假设本机的域名是d.c.b.a.net:
http://wpad.c.b.a.net/wpad.dat
http://wpad.b.a.net/wpad.dat
http://wpad.a.net/wpad.dat
一直找到顶级域(也包括net.cn之类)的下一级为止,找不到则失败退出。这是微软、Sun等公司99年在IETF提出的WPAD协议草案中规定的。不过,以微软一向喜欢往开放协议的实现里加私货(所谓Embrace, Extend and Extinguish的Extend)的习惯,WPAD在IE的实现也和规范有所不同。在上述基于DNS协议的查找代理自动配置失败后,IE会再根据NetBIOS去寻址尝试一次:
http://WPAD/wpad.dat
这下明白刚才广播机器名WPAD的意义了吧?

受害机的IE如果打开了自动配置代理,一旦请求带毒机的这个/wpad.dat,就会按照它将带毒机器的IP设置为IE的代理服务器。

3)带毒机的Web代理拦截受害机发给Windows Update的请求,返回自己的假更新列表。

Windows Update有一个弱点,其运行时是否使用以及使用哪个代理服务器是根据“系统的代理服务器设置”(也就是IE的代理设置)来的。受害机的IE代理被劫持到带毒机,带毒机收到受害机Windows Update检查更新请求的时候,就会拦截下来,回复自己的假更新列表,其中只有一个更新“Desktop Gadget Platform”。
如果用户设置为手动下载/手动安装此更新,一看就能看出名堂来。Vista/Win7自带小工具平台,XP则早在09年就进入扩展支持阶段,不会增加新功能了。

4)受害机从带毒机下载和“安装”Flamer的假更新包,继而被感染
这一步,Windows Update会验证下载回来更新包内的文件数字签名。Flamer作者早就考虑到这一点,他们不知通过何种手段,将微软根服务器签出的“Microsoft LSRA PA”的一个数字签名签到了伪装的更新文件上。这样,Windows Update不知不觉地以管理员权限运行了Flamer的一个下载器,这个下载器会从刚才提到的“MSHOME-xxxxxxxx”(x是16进制数)机器名指定的机器(也就是带毒机)把Flamer本体下载到受害机中并跑起来。
这种劫持比起ARP劫持来要更方便和可靠,ARP是数据链路层的东西,实现起来麻烦一些不说(以往看到的ARP病毒都得装winpcap的驱动),客户端的各类安全软件也早就能防ARP劫持了。不过达成目的的前提是,IE必须设置为自动配置代理服务器,这个我得查一下IE默认的设置是怎么样的。

Flamer的体积极为巨大,必定还有很多其他的秘密包括传播手段等尚未被研究出来。这篇文章纯粹是读了N篇Flamer相关的博文的一个笔记罢了。。各种参考文献懒得列了,说明下参考自卡巴的专家Aleks,Symentec以及Sophos发布的文章算了。