返回列表 发帖

NT平台拨号连接密码恢复原理

前段时间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 http://www.xfocus.net 2004-10-01 FileName: x_dialupass.c */ #define WINVER 0x500 #define _WIN32_WINNT 0x0500 #include #include #include #include #include #include #include #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, szDialParamsUID) == 0 ) { for(j=i;jLength = 0x500; plsa_private_data->MaximumLength = 0x500; plsa_private_data->Buffer = (PWSTR)malloc(0x500); lsa_keyname.MaximumLength = 0x200; lsa_keyname.Buffer = (PWSTR)malloc(0x200); wcscpy(lsa_keyname.Buffer,L"RasDialParams!"); wcscat(lsa_keyname.Buffer, sid); wcscat(lsa_keyname.Buffer, L"#0"); lsa_keyname.Length = wcslen(lsa_keyname.Buffer) * 2; //get current user's dialup info status = LsaRetrievePrivateData(lsa_handle, &lsa_keyname, &plsa_private_data); LsaClose(lsa_handle); if(status != 0) { printf("[-] LsaRetrievePrivateData failed: %d\n", LsaNtStatusToWinError(status)); return; } ret = WideCharToMultiByte(0, 0, plsa_private_data->Buffer, plsa_private_data->Length, private_data, sizeof(private_data), 0, 0); if(ret == 0) { printf("[-] WideCharToMultiByte failed:%d\n", GetLastError()); return; } data_len = ret; //get phone book name GetEnvironmentVariable("ALLUSERSPROFILE", szPhoneBook1, sizeof(szPhoneBook1)-200); GetEnvironmentVariable("USERPROFILE", szPhoneBook2, sizeof(szPhoneBook2)-200); strcat(szPhoneBook1, "\\Application Data\\Microsoft\\Network" "\\Connections\\pbk\\rasphone.pbk"); strcat(szPhoneBook2, "\\Application Data\\Microsoft\\Network" "\\Connections\\pbk\\rasphone.pbk"); lpRasEntryName = (LPRASENTRYNAME)GlobalAlloc(GPTR, sizeof(RASENTRYNAME)); lpRasEntryName->dwSize = sizeof(RASENTRYNAME); cb = sizeof(RASENTRYNAME); if ((nRet = RasEnumEntries(NULL, NULL, lpRasEntryName, &cb, &cEntries)) == ERROR_BUFFER_TOO_SMALL) { lpRasEntryName = (LPRASENTRYNAME)GlobalAlloc(GPTR, cb); lpRasEntryName->dwSize = sizeof(RASENTRYNAME); } // Calling RasEnumEntries to enumerate the phone-book entries nRet = RasEnumEntries(NULL, NULL, lpRasEntryName, &cb, &cEntries); if (nRet != ERROR_SUCCESS) { printf("[-] RasEnumEntries failed: Error %d\n", nRet); return; } for(i=0;i < cEntries;i++) { lpRasDialParams = malloc(sizeof(RASDIALPARAMS)); strcpy(lpRasDialParams->szEntryName, lpRasEntryName->szEntryName); lpRasDialParams->dwSize = sizeof(RASDIALPARAMS); RasGetEntryDialParams(0, lpRasDialParams, &b); dwDialParamsUID = GetPrivateProfileInt(lpRasEntryName->szEntryName, "DialParamsUID", 0, szPhoneBook1); if(dwDialParamsUID == 0) { dwDialParamsUID = GetPrivateProfileInt(lpRasEntryName->szEntryName, "DialParamsUID", 0, szPhoneBook2); if(dwDialParamsUID == 0) { printf("[-] Can't get DialParamsUID from PhoneBook.\n"); return; } } pass = get_real_pass(lpRasDialParams->szUserName, dwDialParamsUID); printf( "-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=\n" "EntryName : %s\n" "UserName : %s\n" "PassWord : %s\n\n", lpRasEntryName->szEntryName, lpRasDialParams->szUserName, pass); free(lpRasDialParams); lpRasEntryName++; } } -=-=-=-=-=-=-=-=-=-= code end -=-=-=-=-=-=-=-=-=-= -=-=-=-=-=-=-=-=-=-= x_dialupass2.cpp -=-=-=-=-=-=-=-=-=-= /* 演示还原NT平台拨号连接密码 原理:直接从注册表中读取加密后的数据,解密之。 可运行于windows 2000/xp/2003平台,必须有权限读取注册表 "HKLM\SECURITY"。 eyas at xfocus.org http://www.xfocus.net 2004-10-01 */ #include #include #include #pragma comment(lib, "Advapi32.lib") #pragma comment(lib, "psapi.lib") //抄袭tombkeeper的代码:) #define FCHK(a) if (!(a)) {printf(#a " failed %d\n", GetLastError()); return 0;} typedef struct _LSA_BLOB { DWORD cbData; DWORD cbMaxData; BYTE* pbData; } LSA_BLOB; typedef int (WINAPI *PSystemFunction005)( LSA_BLOB* pDataIn, LSA_BLOB* pDataKey, LSA_BLOB* pDataOut ); PSystemFunction005 SystemFunction005; DWORD dwFlag=0; //来自lsadump2中的dumplsa.c int myisprint (int ch) { return ((ch >= ' ') && (ch <= '~')); } //来自lsadump2中的dumplsa.c void dump_bytes (unsigned char *p, size_t sz) { char szDumpBuff[256]; if(sz==0) return; while (sz > 16) { _snprintf (szDumpBuff, sizeof (szDumpBuff), " %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %02X %c%c%c%c%c%c%c%c%c%c%c%c%c%c%c%c\n", p[0], p[1], p[2], p[3], p[4], p[5], p[6], p[7], p[8], p[9], p[10], p[11], p[12], p[13], p[14], p[15], myisprint(p[0]) ? p[0] : '.', myisprint(p[1]) ? p[1] : '.', myisprint(p[2]) ? p[2] : '.', myisprint(p[3]) ? p[3] : '.', myisprint(p[4]) ? p[4] : '.', myisprint(p[5]) ? p[5] : '.', myisprint(p[6]) ? p[6] : '.', myisprint(p[7]) ? p[7] : '.', myisprint(p[8]) ? p[8] : '.', myisprint(p[9]) ? p[9] : '.', myisprint(p[10]) ? p[10] : '.', myisprint(p[11]) ? p[11] : '.', myisprint(p[12]) ? p[12] : '.', myisprint(p[13]) ? p[13] : '.', myisprint(p[14]) ? p[14] : '.', myisprint(p[15]) ? p[15] : '.'); printf ("%s", szDumpBuff); p+=16; sz -= 16; } if (sz) { char buf[17]; int i = 0; int j = 16 - sz; memset (buf, 0, sizeof (buf)); szDumpBuff[0] = 0; while (sz--) { _snprintf (szDumpBuff+strlen (szDumpBuff), sizeof (szDumpBuff) - strlen (szDumpBuff), " %02X", *p); if (myisprint (*p)) buf[i++] = *p; else buf[i++] = '.'; p++; } _snprintf (szDumpBuff+strlen (szDumpBuff), sizeof (szDumpBuff)-strlen (szDumpBuff), "%*s%s\n", j*3 + 2, "", buf); printf ("%s", szDumpBuff); } } DWORD search_LsapDbSecretCipherKey(BYTE **ppKey, DWORD pid) { HANDLE hLsass, hLsasrv; DWORD dwRead, i, dwAddr; BYTE *pImage = NULL; MODULEINFO mod; BOOL bRet = FALSE; DWORD dwCount = 0, dwMaxCount=100; FCHK ( (hLsasrv = LoadLibrary("lsasrv.dll")) ); FCHK ( GetModuleInformation(GetCurrentProcess(), (HMODULE)hLsasrv, &mod, sizeof(mod)) ); FCHK ( hLsass = OpenProcess(PROCESS_VM_READ, FALSE, pid) ); pImage = (BYTE*)malloc(mod.SizeOfImage); ReadProcessMemory(hLsass, (BYTE*)hLsasrv, pImage, mod.SizeOfImage-0x10, &dwRead); *ppKey = (BYTE*)malloc(dwMaxCount*0x10); __try { for(i=0;i, "\x10\x00\x00\x00\x10\x00\x00\x00", 8) == 0) { dwAddr = *(DWORD *)(&pImage[i+8]); if( ReadProcessMemory(hLsass, (LPCVOID)dwAddr, &(*ppKey[dwCount*0x10]), 0x10, &dwRead) ) { dwCount++; } } }//end of for } __except(EXCEPTION_EXECUTE_HANDLER) { return dwCount; } return dwCount; } int main(int argc, char **argv) { int ret,i,j; HMODULE hAdvApi32; HKEY hKeySecrets; HKEY hKey; DWORD dwType; char Data[0x500] = ; BYTE *pKey; DWORD dwSize; LSA_BLOB LSADataIn; LSA_BLOB LSADataOut; LSA_BLOB LSADataKey; char szSecret[500]; char szSubKey[0x500]; DWORD dwErr, dwCount=0; if(argc!=2) { printf("Usage: %s \n", argv[0]); return 0; } FCHK ((hAdvApi32 = LoadLibrary("advapi32.dll"))); FCHK ((SystemFunction005 = (PSystemFunction005) GetProcAddress (hAdvApi32, "SystemFunction005")) != NULL); FCHK ((RegOpenKeyEx (HKEY_LOCAL_MACHINE, "SECURITY\\Policy\\Secrets", 0, KEY_READ, &hKeySecrets) == ERROR_SUCCESS)) FCHK ( ( dwCount = search_LsapDbSecretCipherKey(&pKey, atoi(argv[1])) ) != 0 ); printf("Search \"LsapDbSecretCipherKey\" return: %d\n", dwCount); for(j=0;j

NT平台拨号连接密码恢复原理

呵呵,好象是某<***黑**>杂志上的. 恩,好.多看看好.

TOP

返回列表 回复 发帖