Introduction
Lots of people that use Gmail use Google Talk
It functions pretty much like any other IM application such as those available from MSN, Yahoo, AOL
It functions pretty much like any other IM application such as those available from MSN, Yahoo, AOL
OPSWAT reported in June 2011 that Google Talk accounts for 3.56% of market share in Instant Messaging.
That’s not a big percentage of users but the algorithm described here is probably used in other google applications.
Storage
If the user chooses to ‘Remember Password’ an account entry will be created inNTUSER.DAT
So for example if the google id is joe.bloggs@gmail.com the registry entry would be
So for example if the google id is joe.bloggs@gmail.com the registry entry would be
HKEY_CURRENT_USER\Software\Google\Google Talk\Accounts\joe.bloggs@gmail.com
Password values are stored under a string value called pw
Without showing string for my own password, it looks something like
Without showing string for my own password, it looks something like
/'')-%0"',$)-"&)#0.'#&#($+#"
The value itself is much much longer but I just want to give you an idea of what to look for.
It doesn’t use any conventional encoding algorithm like Base64.
It doesn’t use any conventional encoding algorithm like Base64.
Generation
After some digging around in the binaries, Gtalk first initializes some entropy using a static key, the domain and username.
DWORD gtalk_entropy[4];
DWORD static_key[4] = { 0x69F31EA3, 0x1FD96207, 0x7D35E91E, 0x487DD24F };
/**
*
* Retrieve the current domainusername from thread or process token
*
* return TRUE if successful
*
*/
BOOL GetUserInfo(std::wstring &domain, std::wstring &username) {
HANDLE hToken;
DWORD dwTokenSize = 0, dwUserName = 64, dwDomain = 64;
WCHAR UserName[64], Domain[64];
SID_NAME_USE peUse;
PSID pSid = NULL;
BOOL bResult = FALSE;
OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE, &hToken);
if (GetLastError() == ERROR_NO_TOKEN) {
if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) {
return FALSE;
}
}
if (!GetTokenInformation(hToken, TokenUser, 0, 0, &dwTokenSize)) {
if (GetLastError() == ERROR_INSUFFICIENT_BUFFER) {
pSid = new BYTE[dwTokenSize];
if (pSid != NULL) {
if (GetTokenInformation(hToken, TokenUser, pSid, dwTokenSize, &dwTokenSize)) {
bResult = LookupAccountSid(NULL, reinterpret_cast(pSid)->User.Sid,
UserName, &dwUserName, Domain, &dwDomain, &peUse);
if (bResult) {
domain = Domain;
username = UserName;
}
}
delete []pSid;
}
}
}
return bResult;
}
All the above code does is retrieve from the process/thread security token the domain and username.
The seed value appears to be derived from a random number generator developed
by R.Park and K.Miller, however this is merely speculation as no generator exists in the binary itself.
48271 and 69621 are common multiplier values for RNG.
by R.Park and K.Miller, however this is merely speculation as no generator exists in the binary itself.
48271 and 69621 are common multiplier values for RNG.
/**
*
* initialize the entropy that will used to encrypt/decrypt passwords
*
*/
BOOL init_entropy() {
std::wstring domain, username;
BOOL bResult = GetUserInfo(domain, username);
if (bResult) {
memcpy(gtalk_entropy, static_key, sizeof(static_key));
long M = 2147483647; // modulus
long A = 48271; // multiplier
long Q = M / A;
long R = M % A;
long seed = 387822687; // this could be wrong but does work
// and is exactly 9 digits
seed = A * (seed % Q) - R * (seed / Q);
seed += M;
long idx = 0;
// mix with username
for (std::wstring::size_type i = 0;i < username.length();i++) {
gtalk_entropy[idx++ % 4] ^= username[i] * seed;
seed *= A;
}
// mix with domain
for (std::wstring::size_type i = 0;i < domain.length();i++) {
gtalk_entropy[idx++ % 4] ^= domain[i] * seed;
seed *= A;
}
}
return bResult;
}
Recovery
Once the entropy is initialized, it’s possible to decode the pw value into binary which is just a DPAPI blob.
Instead of the usual Base64 encoding, Google Talk uses Base16 with a custom alphabet and a multiplier value.
// convert base16 string into binary
void gtalk_decode(BYTE blob[], std::wstring input) {
std::wstring alphabet = L"!"#$%&'()*+,-./0";
long seed = gtalk_entropy[0] | 1;
long A = 69621;
PBYTE p = blob;
for (size_t i = 4;i < input.length();i += 2) {
int c;
c = (alphabet.find_first_of(input.at(i + 0))) << 4;
c |= (alphabet.find_first_of(input.at(i + 1))) & 0x0f;
*p++ = c - (seed & 0xff);
seed *= A;
}
}
Finally, CryptUnprotectData() is used to decrypt the UNICODE password.
// decrypt blob using DPAPI
BOOL gtalk_decrypt(BYTE password[], BYTE blob_data[], size_t blob_size) {
DATA_BLOB DataIn, DataEntropy, DataOut;
DataEntropy.cbData = sizeof(gtalk_entropy);
DataEntropy.pbData = (BYTE*)gtalk_entropy;
DataIn.cbData = blob_size;
DataIn.pbData = blob_data;
BOOL bResult = CryptUnprotectData(&DataIn, NULL, &DataEntropy, NULL, NULL, 1, &DataOut);
if (bResult) {
memcpy(password, DataOut.pbData, DataOut.cbData);
password[DataOut.cbData] = 0;
LocalFree(DataOut.pbData);
}
return bResult;
}
Conclusion
The password is protected reasonably well although the entropy generation is a little redundant and I dare say a classic example of Security through Obscurity
CryptProtectData and Base64 would provide the same level of protection.
CryptProtectData and Base64 would provide the same level of protection.
0 comments:
Post a Comment