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!IswindowPresent "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)从注册表"HKLMSECURITYPolicySecrets"中枚举子键
4)LsarOpenSecret
5)LsarQuerySecret
进一步跟踪后发现,其实ADVAPI32!LsaRetrievePrivateData是通过NdrClientCall2
发送RPC调用到lsass.exe进程,lsass.exe里面再调用LsarOpenSecret、LsarQuerySecret
来完成获取拨号连接信息过程的。(注:LsarOpenSecret里面有权限判断,非ADMIN组用
户是没有权限来调用ADVAPI32!LsaRetrievePrivateData的)
跟踪了一下LsarQuerySecret,发现它返回的数据其实是从注册表中读取。保存拨号
连接信息的注册表键值为:
HKLMSECURITYPolicySecretsRasDialParams!SID#0CurrVal
SID对应的是用户的string SID。(“HKLMSECURITY”这个键只有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