中国IT动力,最新最全的IT技术教程
最新100篇 | 推荐100篇 | 专题100篇 | 排行榜 | 搜索 | 在线API文档 | 网通镜像
首 页 | 程序开发 | 操作系统 | 软件应用 | 图形图象 | 网络应用 | 精文荟萃 | 教育认证 | 硬件维护 | 未整理篇 | 站长教程
ASP JS PHP工程 ASP.NET 网站建设 UML J2EESUN .NET VC VB VFP 网络维护 数据库 DB2 SQL2000 Oracle Mysql
服务器 Win2000 Office C DreamWeaver FireWorks Flash PhotoShop 上网宝典 CorelDraw 协议大全 网络安全 微软认证
硬件维护  CPU  主板  硬盘  内存  显卡  显示器  键盘鼠标  声卡音箱  打印机  机箱电源  BIOS  网卡  C#  Java  Delphi  vs.net2005
  当前位置:> 精文荟萃 > 教你一招
NT平台拨号连接密码恢复原理
作者:佚名 时间:2005-05-01 11:19 出处:互连网 责编:chinaitpower
              摘要:NT平台拨号连接密码恢复原理

Author : eyas
Email  : eyas at xfous.org
Date   : 2004-11-08


    前段时间ADSL密码忘记了,但幸好还保存在拨号连接里面,于是到网上找了些星号密码
显示工具,可惜不起作用。后来找到一种名为dialupass的工具,这家伙不负重望把密码给
我还原出来了。(用的dialupass v2.42,我的系统是windows xp)

    看起来dialupass非普通的星号密码显示工具,那它的原理是什么呢?上GOOGLE查了
一翻,没找到相关资料(可能是我用的关键字有问题)。 一生气便操起家伙(windbg)
准备把它大卸八块。郁闷的是,用windbg加载后,密码就还原不出来了,显示是星号。换替
补ollydbg上场,情况依旧。莫非这小工具有Anti-Debug功能?当时只是一丝怀疑,因为实
在不相信这样的小工具作者会花心思来保护。

    后来在用s-ice跟踪的过程中,发现有这么一个调用:

    GetProcAddress(xx, "IsDebugPresent")。
    
    晕倒,原来真的有Anti-Debug功能,好在比较简单。统计了一下,总共有5处进行了
Anti-Debug检查。
    
    情况查明了,便换回windbg来调试,在windbg里面下这么一个断点便可绕过Anti-Debug
检测:

    bp KERNEL32!IsDebuggerPresent "g poi(esp);r eax=0;g"

    花了些时间跟踪了一下,把dialupass恢复密码的流程都搞清楚了。这小程序猫腻还
挺多的,总结如下:

    1. 关键函数不直接调用,而是用LoadLibraryA和GetProcAddress来获取函数地址
后再CALL。
    2. 函数名是经过编码的,反汇编后看字符串是看不到的。
    3. 关键地方一概用花指令来迷惑你和反汇编软件。
    
    其实原理很简单,就是用rasapi32.dll里面的一些函数来获取拨号连接的一些信息,
再用 ADVAPI32!LsaRetrievePrivateData 函数来获取密码。

    根据dialupasss的原理,写了个类似的工具,源代码参见后面的x_dialupass.c。

    后来用"LsaRetrievePrivateData"和"RasDialParams"做关键字,重新在GOOGLE搜索了
一遍,找到一些类似的代码。

    参考资源[1]和[2]的是俄罗斯人公布的演示代码,没有对LsaRetrievePrivateData返回
的数据进行拆分用户名和密码。参考资源[3]是日本人公布的完整的应用程序的代码,可惜
在对LsaRetrievePrivateData返回的数据进行拆分处理时存在BUG,导致有些情况下用户名
和密码取的不正确。

    
    后来发现lsadump2 DUMP出来的数据里面包含了"LsaRetrievePrivateData"返回的数
据。lsadump2的原理大致如下:

    1)插入一线程到lsass.exe进程
    2)打开LSA Policy database
    3)从注册表"HKLM\SECURITY\Policy\Secrets"中枚举子键
    4)LsarOpenSecret
    5)LsarQuerySecret

    进一步跟踪后发现,其实ADVAPI32!LsaRetrievePrivateData是通过NdrClientCall2
发送RPC调用到lsass.exe进程,lsass.exe里面再调用LsarOpenSecret、LsarQuerySecret
来完成获取拨号连接信息过程的。(注:LsarOpenSecret里面有权限判断,非ADMIN组用
户是没有权限来调用ADVAPI32!LsaRetrievePrivateData的)

   跟踪了一下LsarQuerySecret,发现它返回的数据其实是从注册表中读取。保存拨号
连接信息的注册表键值为:

    HKLM\SECURITY\Policy\Secrets\RasDialParams!SID#0\CurrVal

    SID对应的是用户的string SID。(“HKLM\SECURITY”这个键只有SYSTEM有权限读
写,连admin都没有权限)

    LsarQuerySecret从注册表中读取出来数据后,接着调用LsapCrDecryptValue函数来
解密,对于同一台机器来说,解密时用的KEY始终都是固定的,这个KEY在lsasrv.dll里面
变量名为"_LsapDbSecretCipherKey"。在windows 2003里面,变量名不一样,对应的有两
个,分别为"LsapDbSecretCipherKeyWrite"和"LsapDbSecretCipherKeyRead",但这两个
变量里面的数据是一样的。

    LsapCrDecryptValue用的似乎是标准DES算法,解密时主要流程如下:

    lsasrv!LsapCrDecryptValue
        |_ advapi32!SystemFunction005
            |_ advapi32!DecryptDataLength
                |_ advapi32!SystemFunction002
                    |_ advapi32!DES_ECB_LM
                        |_ advapi32!des

    解密后,在"<<"标示处还有一个判断:

    .text:785462F0                 call    _LsapCrDecryptValue@12
    .text:785462F5                 test    eax, eax
    .text:785462F7                 mov     [ebp+var_8], eax
    .text:785462FA                 jl      loc_785838E1
    .text:78546300
    .text:78546300 loc_78546300:                          
    .text:78546300                 cmp     byte ptr [esi+45h], 0   <<<<<<<<<<<<
    .text:78546304                 jz      short loc_7854632E
    ......
    .text:7854632E loc_7854632E:
    .text:7854632E                 lea     eax, [ebp+var_10]
    .text:78546331                 push    eax
    .text:78546332                 push    [ebp+arg_8]
    .text:78546335                 push    [ebp+var_C]
    .text:78546338                 call    _LsapCrEncryptValue@12


    假如[esi+45h]为0的话(esi是LsarOpenSecret函数返回的HANDLE),它会把解密后的
数据再进行一次加密,不管是2000还是2003,这时用的KEY始终都是固定为
“SystemLibraryDTC”。

    lsadump2里面调用LsarOpenSecret得到的HANDLE,偏移0x45处值为1,所以
LsarQuerySecret函数返回的就是解密后的数据了。

    而在调用ADVAPI32!LsaRetrievePrivateData时,LsarOpenSecret返回的HANDLE偏移
0x45处值为0x0,所以LsarQuerySecret返回的是解密后又加密的数据,所以在
ADVAPI32!LsaRetrievePrivateData里面还有一个对应的解密过程。相应的,
LsapCrEncryptValue加密的主要流程如下:

    lsasrv!LsapCrEncryptValue
        |_ advapi32!SystemFunction004
            |_ advapi32!EncryptDataLength
                |_ advapi32!SystemFunction001
                    |_ advapi32!DES_ECB_LM
                        |_ advapi32!des


    开始我以为在同一版本的windows里面,_LsapDbSecretCipherKey是固定的,后来
发现我错了。那么这个_LsapDbSecretCipherKey是如何产生的?流程如下:

    (1)调用ntdll!NtConnectPort打开 L"\Security\WxApiPort"

    (2)调用ntdll!NtRequestWaitReplyPort得到一些数据

        ebp-40处为NtRequestWaitReplyPort返回的LPCMESSAGE

        kd> dd ebp-40
        0006fcb8  00400028 00000002 000000dc 000000d8
        0006fcc8  00000024 00000000 00000000 00000000
        0006fcd8  00000001 00000010 00000010 fd317e3e
        0006fce8  7e24e86d d12503d3 5f7d01a8 7665f528
        kd> db ebp-14
        0006fce4  3e 7e 31 fd 6d e8 24 7e-d3 03 25 d1 a8 01 7d 5f

    (3)将上述"ebp-14"处的0x10字节数据COPY到lsasrv.dll里面的"_LsapDbSysKey"变量。
"_LsapDbSysKey"在不同的机器上面(即使版本相同)都是不一样的。它是怎么产生的?有
幸拜读了flashsky的大作后(参考资源[4]),才明白这就是传说中的"SYSKEY"。用flashsky
的代码验证一下:

        c:\>getsyskey
        3e 7e 31 fd 6d e8 24 7e d3 03 25 d1 a8 01 7d 5f

    跟踪系统启动过程,可知道"\Security\WxApiPort"是由winlogon.exe进程创建的,然
后lsass进程通过这个LPC PORT从winlogon进程获取SYSKEY,随后winlogon进程会关闭这
个LPC PORT。所以在系统启动完成之后,用"Process Explorer"等工具是看不到这个
LPC PORT存在的,而且在winlogon和LSASS进程空间都搜索不到上述SYSKEY。

    (4)从注册表"HKLM\SECURITY\Policy\PolSecretEncryptionKey"中读取出来一段数据,
调用函数_LsapDbDecryptKeyWithSyskey,把它用"_LsapDbSysKey"来解密,
"_LsapDbSecretCipherKey"就在解密完后的数据里面。("LsapDbDecryptKeyWithSyskey"函
数做的其实就是MD5和RC4运算)
        

    了解原理后,我们就可以直接从注册表里面来获取拨号连接中的密码等数据了。但
有几个问题需要解决:

    (1)原料。
    Q:"HKLM\SECURITY"键只有SYSTEM有权限读写?
    A:我们可以把代码插入到SYSTEM进程里面去运行,或者把这个键修改为ADMIN有
权限读,或者提升本进程权限。

    (2)催化剂:)
    Q: 如何获取"_LsapDbSysKey"?解密用的函数_LsapDbDecryptKeyWithSyskey为非导出函
数,怎么办?
    A1: 用flashsky的代码来获取SYSKEY,利用公开的MD5和RC4库函数来解密。
    A2: 直接从lsass.exe进程里面搜索"_LsapDbSecretCipherKey",它的结构如下,

    typedef struct _LSA_BLOB {
            DWORD cbData;
            DWORD cbMaxData;
            BYTE* pbData;
    } LSA_BLOB;

    pbData指向存储KEY的地址,KEY长度固定为0x10字节,即cbData和cbMaxData都是固定
为0x10。所以从lsass进程的空间里面搜索"\x10\x00\x00\x00\x10\x00\x00\x00"即可找到
正确的KEY。结果可能会有多个,可以把所有搜索到的KEY都试一下,总有一个正确的。

    (3)工具
    Q: 解密函数LsapCrDecryptValue为非导出函数,怎么办?
    A: 或许可以根据特征码来搜索,但总觉得不太可靠。幸好,LsapCrDecryptValue
调用的advapi32!SystemFunction005是导出函数:)。或者直接利用公开的DES库函数,
自己来运算。


    x_dialupass2.cpp中的代码演示了直接从注册表中读取数据并解密之的过程,没有
太多实际意义,just for fun!


-=-=-=-=-=-=-=-=-=-=  x_dialupass.c  -=-=-=-=-=-=-=-=-=-=
/*
演示还原NT平台上拨号连接的密码
可运行于windows 2000/xp/2003
原理基于分析dialupass v2.42

eyas at xfocus.org
_blank>http://www.xfocus.net
2004-10-01


FileName: x_dialupass.c
*/
#define WINVER 0x500
#define _WIN32_WINNT 0x0500
#include <windows.h>
#include <stdio.h>

#include <ras.h>
#include <raserror.h>
#include <Ntsecapi.h>
#include <Userenv.h>
#include <Sddl.h>

#pragma comment(lib,"Rasapi32.lib")
#pragma comment(lib,"advapi32.lib")
#pragma comment(lib,"UserEnv.lib")

unsigned char            private_data[0x500];
int                        data_len;

unsigned char * get_real_pass(unsigned char *user, DWORD dwDialParamsUID)
{
    int    i, j;
    unsigned char *p, szDialParamsUID[52], *pass=NULL;

    _snprintf(szDialParamsUID, sizeof(szDialParamsUID),
        "%d", dwDialParamsUID);

    p = private_data;

    for(i=0;i<data_len;i++)
    {
        if(strcmp(&p[i], szDialParamsUID) == 0 )
        {
            for(j=i;j<data_len;j++)
            {
                if(strcmp(&p[j], user) == 0 )
                {
                    pass = p + j + strlen(user) + 1;
                    break;
                }
            }
            break;
        }
    }

    return pass;
}

void main()
{
    LPRASENTRYNAME lpRasEntryName;
    LPRASDIALPARAMS lpRasDialParams;
    DWORD            cb, nRet, i, cEntries;
    BOOL            b;
    char            szPhoneBook1[512], szPhoneBook2[512],
                    szUserName[128], szDomainName[128];
    DWORD            dwSize, dwDialParamsUID, dwTmp;
    PSID            pSid = NULL;
    SID_NAME_USE    peUse;

    LSA_OBJECT_ATTRIBUTES    lsa_object_attr;
    LSA_HANDLE                lsa_handle;
    PLSA_UNICODE_STRING        plsa_private_data;
    LSA_UNICODE_STRING        lsa_keyname;
    NTSTATUS                status;
    int                        ret;
    unsigned char            *pass;
    WCHAR                    *sid;

    printf("dialup password recover tool for win 2k/xp/2003\n"
            "code by eyas at xfocus.org\n"
            "_blank>http://www.xfocus.net\n"
            "2004-10-01\n\n");

    //get current user's string sid
    dwSize = sizeof(szUserName);
    GetUserName(szUserName, &dwSize);
    dwSize = 0;
    dwTmp = sizeof(szDomainName);
    LookupAccountName(NULL, szUserName, pSid, &dwSize, szDomainName,
                    &dwTmp, &peUse);
    if(!dwSize)
    {
        printf("[-] LookupAccountName failed.\n");
        return;
    }
    pSid = (PSID)malloc(dwSize);
    LookupAccountName(NULL, szUserName, pSid, &dwSize, szDomainName,
                     &dwTmp, &peUse);
    ConvertSidToStringSidW(pSid, &sid);

    memset(&lsa_object_attr, 0, sizeof(lsa_object_attr));
    lsa_object_attr.Length = sizeof(LSA_OBJECT_ATTRIBUTES);
    LsaOpenPolicy(0, &lsa_object_attr, 0x800, &lsa_handle);
关闭本页
 
首页 | 投资与合作 | 服务条款 | 隐私政策 | 收藏本站 | 设为首页 | 新用户注册 | 免责声明 | 使用帮助
Copyright ©2005-2008 chinaitpower.com All rights reserved. www.chinaitpower.com 版权所有