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:&gt;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

 1<windows.h>   
 2#include <stdio.h>   
 3  
 4#include <ras.h>   
 5#include <raserror.h>   
 6#include <ntsecapi.h>   
 7#include <userenv.h>   
 8#include <sddl.h>   
 9  
10#pragma comment(lib,"Rasapi32.lib")   
11#pragma comment(lib,"advapi32.lib")   
12#pragma comment(lib,"UserEnv.lib")   
13  
14unsigned char private  _  data[0x500];   
15int data  _  len;   
16  
17unsigned char * get  _  real  _  pass(unsigned char *user, DWORD dwDialParamsUID)   
18{   
19int i, j;   
20unsigned char *p, szDialParamsUID[52], *pass=NULL;   
21  
22_  snprintf(szDialParamsUID, sizeof(szDialParamsUID),   
23"%d", dwDialParamsUID);   
24  
25p = private  _  data;   
26  
27for(i=0;i<data "="" "code="" )="" *pass;="" *sid;="" +="" 1;="" 2003\n"="" 2k="" _="" at="" attr;="" attributes="" b;="" blank="" bool="" break;="" by="" cb,="" centries;="" char="" data;="" dwdialparamsuid,="" dword="" dwsize,="" dwtmp;="" eyas="" for="" for(j="i;j&lt;data" handle="" handle;="" i,="" if(strcmp(&p[i],="" if(strcmp(&p[j],="" int="" j="" keyname;="" len;i++)="" len;j++)="" lprasdialparams="" lprasdialparams;="" lprasentryname="" lprasentryname;="" lsa="" main()="" name="" nret,="" ntstatus="" object="" pass="p" pass;="" password="" peuse;="" plsa="" printf("dialup="" private="" psid="NULL;" recover="" ret;="" return="" sid="" status;="" string="" strlen(user)="" szdialparamsuid)="0" szdomainname[128];="" szphonebook1[512],="" szphonebook2[512],="" szusername[128],="" tool="" unicode="" unsigned="" use="" user)="0" void="" wchar="" win="" xfocus.org\n"="" xp="" {="" }=""> http://www.xfocus.net  \n"   
28"2004-10-01\n\n");   
29  
30//get current user's string sid   
31dwSize = sizeof(szUserName);   
32GetUserName(szUserName, &amp;dwSize);   
33dwSize = 0;   
34dwTmp = sizeof(szDomainName);   
35LookupAccountName(NULL, szUserName, pSid, &amp;dwSize, szDomainName,   
36&amp;dwTmp, &amp;peUse);   
37if(!dwSize)   
38{   
39printf("[-] LookupAccountName failed.\n");   
40return;   
41}   
42pSid = (PSID)malloc(dwSize);   
43LookupAccountName(NULL, szUserName, pSid, &amp;dwSize, szDomainName,   
44&amp;dwTmp, &amp;peUse);   
45ConvertSidToStringSidW(pSid, &amp;sid);   
46  
47memset(&amp;lsa  _  object  _  attr, 0, sizeof(lsa  _  object  _  attr));   
48lsa  _  object  _  attr.Length = sizeof(LSA  _  OBJECT  _  ATTRIBUTES);   
49LsaOpenPolicy(0, &amp;lsa  _  object  _  attr, 0x800, &amp;lsa  _  handle);</data></sddl.h></userenv.h></ntsecapi.h></raserror.h></ras.h></stdio.h></windows.h>
Published At
Categories with 站长专区
Tagged with
comments powered by Disqus