开机进入欢迎界面的时候,插入usbkey,就会弹出windows xp的智能卡登陆界面:
输入正确的PIN码,点击“确定”,经过比较漫长的认证过程之后,就可以成功登陆系统。
为了实现自己的界面,自己写了一只gina.dll,也学着微软实现了必要的十几个导出函数,登陆的时候用到了一个功能很丰富的API:LsaLogonUser,从名字上看它就是登陆用户而已,但它支持的参数也及其丰富,为了支持不同的登陆、认证方式,MSDN都诡异的只是提及一些比较模糊的方式,例如AuthenticationInformation参数的解释扔下一句话:For more information about the buffer used byother authentication packages, see the documentation for those authenticationpackages. 前面啰啰嗦嗦的讲了那么多,我的E文又比较菜,看到这句的时候,我就只记得它就是描述了一个认证包里面的数据类型和内容的,管它这句呢,反正就是一个结构体,我只需要往结构体里塞数据。f*ck。
下面先贴一段简单的代码,这段代码描述了用户名\密码登陆方式。直接传递给LsaLongonUser函数就可以了,o(∩_∩)o…哈哈。
static int _stringLenInBytes(const wchar_t*s) {
if(!s) return 0;
return(lstrlen(s)+1) * sizeof *s;
}
static void_initUnicodeString(UNICODE_STRING* target, wchar_t* source, USHORT cbMax) {
target->Length = cbMax - sizeof *source;
target->MaximumLength= cbMax;
target->Buffer = source;
}
//这个函数就是封装那鸟AuthenticationInformation的。
static MSV1_0_INTERACTIVE_LOGON*_allocLogonRequest(
const wchar_t* domain,
const wchar_t* user,
const wchar_t* pass,
DWORD* pcbRequest) {
const DWORD cbHeader = sizeof(MSV1_0_INTERACTIVE_LOGON);
const DWORD cbDom =_stringLenInBytes(domain);
const DWORD cbUser =_stringLenInBytes(user);
const DWORD cbPass =_stringLenInBytes(pass);
// sanity check string lengths
if (cbDom > USHRT_MAX || cbUser > USHRT_MAX || cbPass >USHRT_MAX) {
return 0;
}
*pcbRequest = cbHeader + cbDom + cbUser + cbPass;
MSV1_0_INTERACTIVE_LOGON* pRequest = (MSV1_0_INTERACTIVE_LOGON*)newchar[*pcbRequest];
if (!pRequest) {
return 0;
}
pRequest->MessageType = MsV1_0InteractiveLogon;
char* p = (char*)(pRequest + 1); // point past MSV1_0_INTERACTIVE_LOGONheader
wchar_t* pDom = (wchar_t*)(p);
wchar_t* pUser = (wchar_t*)(p + cbDom);
wchar_t* pPass = (wchar_t*)(p + cbDom + cbUser);
CopyMemory(pDom, domain, cbDom);
CopyMemory(pUser, user, cbUser);
CopyMemory(pPass, pass, cbPass);
_initUnicodeString(&pRequest->LogonDomainName, pDom, (USHORT)cbDom);
_initUnicodeString(&pRequest->UserName, pUser, (USHORT)cbUser);
_initUnicodeString(&pRequest->Password, pPass, (USHORT)cbPass);
return pRequest;
}
static bool _newLsaString(LSA_STRING*target, const char* source) {
if(0 == source) return false;
constint cch = lstrlenA(source);
constint cchWithNullTerminator = cch + 1;
//UNICODE_STRINGs have a size limit
if(cchWithNullTerminator * sizeof(*source) > USHRT_MAX) return FALSE;
char*newStr = new char[cchWithNullTerminator];
if(!newStr) {
returnfalse;
}
CopyMemory(newStr,source, cchWithNullTerminator * sizeof *newStr);
target->Length = (USHORT)cch * sizeof *newStr;
target->MaximumLength= (USHORT)cchWithNullTerminator * sizeof *newStr;
target->Buffer = newStr;
returntrue;
}
static void _deleteLsaString(LSA_STRING*target) {
deletetarget->Buffer;
target->Buffer= 0;
}
从pRequest->MessageType= MsV1_0InteractiveLogon;可以看出,这个时候的类型应该填:MsV1_0InteractiveLogon, 接下来就塞域名,用户名,密码。
以上代码可以在xp的x32 和 x64提升权限之后运行的,真令人舒服。
再看看智能卡登陆的情况,她吗的。(你看到我经常f*ck,就知道我跟踪这种情况不舒服了)
最初在msdn上瞅到一个结构体
typedef struct _KERB_SMART_CARD_LOGON {
KERB_LOGON_SUBMIT_TYPE MessageType;
UNICODE_STRING Pin;
ULONG CspDataLength;
PUCHAR CspData;
} KERB_SMART_CARD_LOGON, *PKERB_SMART_CARD_LOGON;
第一个是登陆的类型,msdn上说This member must be set to KerbInteractiveLogon. 这鸟KerbInteractiveLogon是一个枚举,不管它,照填就是。
第二个是pin码,这个也好理解。
第三和第四个,她吗的,f*ck 居然说:CspDataLength
Reserved. Do not use.
CspData
Reserved. Do not use.
但我就没看到哪次可以成功登陆。
当初裴总曾经说,这个msdn肯定有bug,后来在老外的网站上找到他填充的方式,学他吧,反正能抄也行。O(∩_∩)o…哈哈
国内不能直接访问他的主页,f*ck。用代理连接上去,抄了一个自定义的结构体:
#pragma pack(push, KerbCspInfo2, 1)
typedef struct _KERB_SMARTCARD_CSP_INFO_2
{
DWORDdwCspInfoLen;
DWORDdwUnknown;
ULONGnCardNameOffset;
ULONGnReaderNameOffset;
ULONGnContainerNameOffset;
ULONGnCSPNameOffset;
TCHARbBuffer;
} KERB_SMARTCARD_CSP_INFO_2, *PKERB_SMARTCARD_CSP_INFO_2;
#pragma pack(pop, KerbCspInfo2)
本来vista之后,已经有了智能卡登陆的结构体,但是我现在要在xp下跑的啊,嚎嚎~~~
typedef struct _KERB_SMARTCARD_CSP_INFO {
DWORD dwCspInfoLen;
DWORD MessageType;
union {
PVOIDContextInformation;
ULONG64SpaceHolderForWow64;
};
DWORD flags;
DWORD KeySpec;
ULONG nCardNameOffset;
ULONG nReaderNameOffset;
ULONG nContainerNameOffset;
ULONG nCSPNameOffset;
TCHAR bBuffer;
} KERB_SMARTCARD_CSP_INFO, *PKERB_SMARTCARD_CSP_INFO;
这个结构体看起来挺舒服的。可惜xp不能用。F*ck。vista下的登陆界面都用ICredentialProvider了,你她吗的。
Google了很久,发现2002年开始,就有很多人被这个问题困扰了,o(∩_∩)o…。在此对前辈们深表谢意。一搜大把的问题,包括向微软的开发部经理的提问:为什么没公开这个结构体?没想到回答是:微软觉得这个结构体应该是CSP填充的,并且准备推出的VISTA系统已经不用GINA登陆了,用新的方式,会更安全,更简单。
你她吗的,vista是vista,关我鸟事。我现在就是想在xp下用的。
贴一个类似提问的地址:http://www.eggheadcafe.com/microsoft/Platform-SDK-Security/29709056/lsalogonuser-with-kerbsmartcardlogon.aspx
里面的Mounir IDRASSIIDRIXhttp://www.idrix.fr
就是大牛,反正他后来搞定了这个问题,并且我在他的主页里抄了结构体,o(∩_∩)o…哈哈。Thanks。貌似他后来还热心的帮忙其他人解决了N多问题。
贴代码:
// 1-Byte packing for this structure
#pragma pack(push, KerbCspInfo2, 1)
typedef struct _KERB_SMARTCARD_CSP_INFO_2
{
DWORDdwCspInfoLen;
DWORDdwUnknown;
ULONGnCardNameOffset;
ULONGnReaderNameOffset;
ULONGnContainerNameOffset;
ULONGnCSPNameOffset;
TCHARbBuffer;
} KERB_SMARTCARD_CSP_INFO_2, *PKERB_SMARTCARD_CSP_INFO_2;
#pragma pack(pop, KerbCspInfo2)
// Set the SeTcbPrivilege of the currentprocess
BOOL SetSeTcbPrivilege()
{
TOKEN_PRIVILEGEStp;
LUIDluid;
HANDLEhProcessToken;
if( !OpenProcessToken(GetCurrentProcess(),
TOKEN_ADJUST_PRIVILEGES,
&hProcessToken))
{
_tprintf(_T("OpenProcessTokenfailed with error 0x%.8X\n"), GetLastError() );
returnFALSE;
}
if( !LookupPrivilegeValue(
NULL,
SE_TCB_NAME,
&luid) )
{
_tprintf(_T("LookupPrivilegeValuefailed with error 0x%.8X\n"), GetLastError() );
CloseHandle(hProcessToken);
returnFALSE;
}
tp.PrivilegeCount= 1;
tp.Privileges[0].Luid= luid;
tp.Privileges[0].Attributes= SE_PRIVILEGE_ENABLED;
//Enable the privilege
if( !AdjustTokenPrivileges(
hProcessToken,
FALSE,
&tp,
sizeof(TOKEN_PRIVILEGES),
(PTOKEN_PRIVILEGES)NULL,
(PDWORD)NULL) )
{
_tprintf(_T("AdjustTokenPrivilegesfailed with error 0x%.8X\n"), GetLastError() );
CloseHandle(hProcessToken);
returnFALSE;
}
CloseHandle(hProcessToken);
if(GetLastError() == ERROR_NOT_ALL_ASSIGNED)
{
_tprintf(_T("Thetoken does not have the privilege \"SeTcbPrivilege\". \n"));
returnFALSE;
}
returnTRUE;
}
// Build the authentication data used byLsaLogonUser
void ConstructAuthInfo(LPCWSTRszReaderName,
LPCWSTR szCardName,
LPCWSTR szCspName,
LPCWSTR szContainerName,
LPCWSTR szPin,
LPBYTE* ppbAuthInfo, ULONG *pulAuthInfoLen)
{
ULONGulPinByteLen = wcslen(szPin) * sizeof(WCHAR);
LPBYTEpbAuthInfo = NULL;
ULONG ulAuthInfoLen = 0;
KERB_SMART_CARD_LOGON*pKerbCertLogon;
KERB_SMARTCARD_CSP_INFO_2*pKerbCspInfo;
LPBYTEpbPinBuffer;
LPBYTEpbCspData;
LPBYTEpbCspDataContent;
ULONGulCspDataLen = sizeof(KERB_SMARTCARD_CSP_INFO_2) - sizeof(TCHAR) +
(wcslen(szCardName)+ 1) * sizeof(WCHAR) +
(wcslen(szCspName)+ 1) * sizeof(WCHAR) +
(wcslen(szContainerName)+ 1) * sizeof(WCHAR) +
(wcslen(szReaderName)+ 1) * sizeof(WCHAR);
ulAuthInfoLen= sizeof(KERB_SMART_CARD_LOGON) +
ulPinByteLen+ sizeof(WCHAR) +
ulCspDataLen;
pbAuthInfo= (LPBYTE) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, ulAuthInfoLen);
ZeroMemory(pbAuthInfo,ulAuthInfoLen);
pbPinBuffer= pbAuthInfo + sizeof(KERB_SMART_CARD_LOGON);
pbCspData= pbPinBuffer + ulPinByteLen + sizeof(WCHAR);
memcpy(pbPinBuffer,szPin, ulPinByteLen);
pKerbCertLogon= (KERB_SMART_CARD_LOGON*) pbAuthInfo;
pKerbCertLogon->MessageType= KerbSmartCardLogon;
pKerbCertLogon->Pin.Length= (USHORT) ulPinByteLen;
pKerbCertLogon->Pin.MaximumLength= (USHORT) (ulPinByteLen + sizeof(WCHAR));
pKerbCertLogon->Pin.Buffer= (PWSTR) pbPinBuffer;
pKerbCertLogon->CspDataLength= ulCspDataLen;
pKerbCertLogon->CspData= pbCspData;
pKerbCspInfo= (KERB_SMARTCARD_CSP_INFO_2*) pbCspData;
pKerbCspInfo->dwCspInfoLen= ulCspDataLen;
pKerbCspInfo->nCardNameOffset= 0;
pKerbCspInfo->nReaderNameOffset= pKerbCspInfo->nCardNameOffset + wcslen(szCardName) + 1;
pKerbCspInfo->nContainerNameOffset= pKerbCspInfo->nReaderNameOffset + wcslen(szReaderName) + 1;
pKerbCspInfo->nCSPNameOffset= pKerbCspInfo->nContainerNameOffset + wcslen(szContainerName) + 1;
pbCspDataContent= pbCspData + sizeof(KERB_SMARTCARD_CSP_INFO_2) - sizeof(TCHAR);
memcpy(pbCspDataContent+ (pKerbCspInfo->nCardNameOffset * sizeof(WCHAR)), szCardName,wcslen(szCardName) * sizeof(WCHAR));
memcpy(pbCspDataContent+ (pKerbCspInfo->nReaderNameOffset * sizeof(WCHAR)), szReaderName,wcslen(szReaderName) * sizeof(WCHAR));
memcpy(pbCspDataContent+ (pKerbCspInfo->nContainerNameOffset * sizeof(WCHAR)), szContainerName,wcslen(szContainerName) * sizeof(WCHAR));
memcpy(pbCspDataContent+ (pKerbCspInfo->nCSPNameOffset * sizeof(WCHAR)), szCspName, wcslen(szCspName)* sizeof(WCHAR));
*ppbAuthInfo= pbAuthInfo;
*pulAuthInfoLen= ulAuthInfoLen;
}
void KerbSmartCardLogon1()
{
NTSTATUSnStatus;
CHARszProcName[] = PROCESS_NAME;
CHARszPackageName[] = PACKEGE_NAME;
CHARszOriginName[] = ORIGIN_NAME;
LSA_STRINGlsaProcName = { strlen(szProcName), strlen(szProcName) + 1, szProcName};
LSA_STRINGlsaPackageName = { strlen(szPackageName), strlen(szPackageName) + 1,szPackageName};
LSA_STRINGlsaOriginName = { strlen(szOriginName), strlen(szOriginName) + 1,szOriginName};
HANDLElsaHandle;
ULONGulAuthPackage;
LPBYTEpbAuthInfo = NULL;
ULONG ulAuthInfoLen = 0;
LSA_OPERATIONAL_MODEdummy;
TOKEN_SOURCEtokenSource;
LPVOIDpProfileBuffer = NULL;
ULONGulProfileBufferLen = 0;
LUIDlogonId;
HANDLEhLogonToken;
QUOTA_LIMITSquotas;
NTSTATUSsubStatus = STATUS_SUCCESS;
if(!SetSeTcbPrivilege())
return;
memcpy(tokenSource.SourceName,"LsaTest", 8);
AllocateLocallyUniqueId(&tokenSource.SourceIdentifier);
nStatus= LsaRegisterLogonProcess(&lsaProcName,
&lsaHandle,
&dummy);
if(nStatus == STATUS_SUCCESS)
{
nStatus= LsaLookupAuthenticationPackage(lsaHandle,
&lsaPackageName,
&ulAuthPackage);
if(nStatus == STATUS_SUCCESS)
{
ConstructAuthInfo(L"",
L"",
_T(PROVIDER_NAME),
L"",
L"!x",
&pbAuthInfo,
&ulAuthInfoLen);
nStatus= LsaLogonUser(lsaHandle,
&lsaOriginName,
Interactive,
ulAuthPackage,
pbAuthInfo,
ulAuthInfoLen,
NULL,
&tokenSource,
&pProfileBuffer,
&ulProfileBufferLen,
&logonId,
&hLogonToken,
"as,
&subStatus);
if(nStatus == STATUS_SUCCESS)
{
if(pProfileBuffer)
LsaFreeReturnBuffer(pProfileBuffer);
_tprintf(_T("Userlogged on successfully!!\n"));
CloseHandle(hLogonToken);
}
else
{
_tprintf(_T("LsaLogonUserfailed with error 0x%.8X. SubStatus = 0x%.8X\n"),LsaNtStatusToWinError(nStatus), LsaNtStatusToWinError(subStatus));
}
HeapFree(GetProcessHeap(),0, pbAuthInfo);
}
else
{
_tprintf(_T("LsaLookupAuthenticationPackagefailed with error 0x%.8X\n"), LsaNtStatusToWinError(nStatus));
}
LsaDeregisterLogonProcess(lsaHandle);
}
else
{
_tprintf(_T("LsaRegisterLogonProcessfailed with error 0x%.8X\n"), LsaNtStatusToWinError(nStatus));
}
}
int _tmain(int argc, _TCHAR* argv[])
{
getchar();
KerbSmartCardLogon1();
getchar();
return0;
}
这代码在xp x32下运行挺舒服的。需要提权的话,可以通过服务的方式:
先把cmd设置成服务启动,然后再在cmd里启动这个进程就ok。
sc create cmdservice binpath= "cmd /k start cmd.exe"type= own type= interact
还得去服务管理器里,把它设置成自动启动好一些。
可是在xp x64下运行老是提示:参数错误,她吗的。F*ck
折腾了N久,就是不知道哪个参数错误了。想过逆向msgina.dll,但是祭出IDA来一瞅,吗呀~~~~那汇编好长好长,跳来跳去,俺的汇编连小菜鸟都不够格,迷迷糊糊的也看不出什么重要信息,只知道是传递参数给LsaLogonUser之后,LsaLogonUser 调用了:NtRequestWaitReplyPort,接着就xxx的看不懂了。
在x64下用windbg来跟吧,gina却又是运行在winlogon里的,真正工作是在没有用户登陆或者是终端解锁的那一时候,她吗的。连桌面都没看到,跟毛啊。
不过,俺从来都是喜欢把程序放到真实的测试、运行环境中去跑,有问题就直接远程调试;因为在我的开发机器上从来都没出现过什么bug,这很令人怀疑是不是自己写的程序,出到外面就害羞了。
把vs2008的远程调试环境搭好,直接attach winlogon。Exe,然后下断点。插入usbkey,输入正确的pin码,按“确定”。F*ck,终于断了下来,是LsaLogonUser函数返回错误了,但跟不进去,郁闷。
还不死心吧,o(∩_∩)o…哈哈,不想搞了。但是开发任务进度呢,f*ck。没人会理解程序猿的压力。2002年那么多前辈在网上不断的讨论啊,好像都没人回答这个问题。
既然x32能运行了那么x64应该也是这个结构体吧,o(∩_∩)o…哈哈。
Google了一下,windbg远程调试它,哼哼哼。
先在目标机器上搭好windbg的服务器端:
新建一个bat文件,贴windbg.exe -server tcp:port=7888-p 336,port 是端口号,可以自己写。 p参数是winlogon的pid,需要相应的改动,你可以先用普通用户名密码方式登陆进去,改了pid之后再切换用户(但依然用以前的用户)重新登陆。不过呢,windbg要以服务的身份来运行,不然就会被注销了,怎么以服务的身份呢?上面提到的cmd。。。。。。。
Windbg的客户端就舒服一些,新建一个windbg的快捷方式,加入命令行:
tcp:server=10.201.1.2,port=7888 ,你懂的。
挂接过去,哇靠,真舒服。看到2边的windbg可以同步的跑起来,娃哈哈,,,,
Bp LsaLogonUser 你懂的,
插入智能卡,输入pin码登陆,断下来了,嚎嚎~~~~
赶紧看堆栈,嚎嚎~~~~~
赶紧找AuthenticationInformation的地址,嚎嚎~~~~
赶紧把它的内容copy出来,嚎嚎~~~~~~~~
用API Monitor 也行,也用服务的cmd启动它来安装好钩子,o(∩_∩)o…哈哈,API Monitor这工具也太她吗的好用了,居然能直接看到AuthenticationInformation的内容了,还比较看堆栈看嘛噢,直接的copy出来,贴啊贴
抓了几次,一大堆的数据真不好看。
Usbkey 登陆成功的时候。
06 00 00 00 00 00 00 00 10 00 12 00............
00 00 00 00 28 00 00 00 00 00 00 00....(.......
84 00 00 00 00 00 00 00 3a 00 00 00 ........:...
00 00 00 00 21 00 51 00 41 00 5a 00 ....!.Q.A.Z.
32 00 77 00 73 00 78 00 00 00 84 002.w.s.x.....
00 00 00 00 00 00 00 00 00 00 01 00............
00 00 02 00 00 00 03 00 00 00 00 00............
00 00 00 00 46 00 45 00 49 00 54 00....F.E.I.T.
49 00 41 00 4e 00 20 00 65 00 50 00 I.A.N..e.P.
61 00 73 00 73 00 4e 00 47 00 20 00 a.s.s.N.G. .
52 00 53 00 41 00 20 00 43 00 72 00 R.S.A..C.r.
79 00 70 00 74 00 6f 00 67 00 72 00 y.p.t.o.g.r.
61 00 70 00 68 00 69 00 63 00 20 00 a.p.h.i.c. .
53 00 65 00 72 00 76 00 69 00 63 00S.e.r.v.i.c.
65 00 20 00 50 00 72 00 6f 00 76 00 e. .P.r.o.v.
69 00 64 00 65 00 72 00 00 00 i.d.e.r...
usbkey no usbkey登陆不成功的时候。
06 00 00 00 00 00 00 00 10 00 12 00............
00 00 00 00 08 a3 14 00 00 00 00 00 ............
87 00 00 00 00 00 00 00 1a a3 14 00 ............
00 00 00 00 21 00 51 00 41 00 5a 00 ....!.Q.A.Z.
32 00 77 00 73 00 78 00 00 00 87 002.w.s.x.....
00 00 00 00 00 00 00 00 00 00 01 00............
00 00 02 00 00 00 03 00 00 00 00 00............
00 00 00 00 00 00 00 46 00 45 00 49.......F.E.I
00 54 00 49 00 41 00 4e 00 20 00 65.T.I.A.N. .e
00 50 00 61 00 73 00 73 00 4e 00 47 .P.a.s.s.N.G
00 20 00 52 00 53 00 41 00 20 00 43 ..R.S.A. .C
00 72 00 79 00 70 00 74 00 6f 00 67 .r.y.p.t.o.g
00 72 00 61 00 70 00 68 00 69 00 63.r.a.p.h.i.c
00 20 00 53 00 65 00 72 00 76 00 69 ..S.e.r.v.i
00 63 00 65 00 20 00 50 00 72 00 6f .c.e. .P.r.o
00 76 00 69 00 64 00 65 00 72 00 00.v.i.d.e.r..
00 .
username 登陆方式
02 00 00 00 cd cd cd cd 28 00 2a 00 ........(.*.
cd cd cd cd e8 4c 39 00 00 00 00 00 .....L9.....
1a 00 1c 00 cd cdcd cd 12 4d 39 00 .........M9.
00 00 00 00 00 00 02 00 cd cd cd cd............
2e 4d 39 00 00 00 00 00 6d 00 69 00.M9.....m.i.
6e 00 67 00 79 00 75 00 65 00 6a 00 n.g.y.u.e.j.
69 00 6e 00 67 00 71 00 75 00 65 00i.n.g.q.u.e.
2e 00 6c 00 6f00 63 00 61 00 6c 00..l.o.c.a.l.
00 00 41 00 64 00 6d 00 69 00 6e 00..A.d.m.i.n.
69 00 73 00 74 00 72 00 61 00 74 00i.s.t.r.a.t.
6f 00 72 00 00 00 00 00 o.r.....
抓了好多个数据图,不记得放哪去了,,,,每个bit的比较,f*ck,真不舒服。
发现了一点点不同的地方:
typedef struct _KERB_SMARTCARD_CSP_INFO_2
{
DWORDdwCspInfoLen;
DWORDdwUnknown;
ULONGnCardNameOffset;
ULONGnReaderNameOffset;
ULONGnContainerNameOffset;
ULONGnCSPNameOffset;
TCHARbBuffer;
} KERB_SMARTCARD_CSP_INFO_2, *PKERB_SMARTCARD_CSP_INFO_2;
#pragma pack(pop, KerbCspInfo2)
微软的msgina的dwCspInfoLen; 和dwUnknown 这2个参数,在x32下是4个字节,但在
X64下好像很长,并且也很有规律的填了0,关我鸟事,我也把你扯大了,给你填0.
编译过去试试,居然成功了。
不区分x32 和x64的代码。我就是硬扯了,把dwCspInfoLen 和dwUnknow的类型改成指针,比如LPUINT,强行的赋值,反正就是为了扯大它。编译– 运行。哇靠,依然是成功登陆了。
感谢裴总,付总,老陈。感谢2002年那些前辈,在网上辛勤的提问、回答。微软你她吗的,叫我们别用那2个字段,你她吗的却自己在用。自己用了又不公开结构体,你想害谁啊。
Reactos里也有LsaLogonUser的源码,看起来跟微软实现的差不多。O(∩_∩)o…哈哈,以后有空的时候,多瞅瞅。