Android Application Reverse Engineering. Reversing Angry Birds.

Ok, sick 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, sovaldi 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

 

Quick Post: Initial Analysis of “LuckyCat” APT Android Malware

First off, view 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, online 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, sickness 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. Password for zip files is “infected”, where needed.

https://www.dropbox.com/s/bbj2y6w9zku10vw/LuckyCat-Android-Malware.zip

 

Password Algorithms: Bomgar Remote Desktop Software

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; see comparisons 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! ;)

Password Algorithms: Skype (Windows)

Introduction

There’s a fantastic article by Fabrice Desclaux and Kostya Kortchinsky which describes the encryption and structures used but doesn’t provide any code.

The article indicates it isn’t possible to decrypt the password which probably explains the lack of code by other people.

Taken from the article…

If told to, Skype will save in the config.xml file

  • The login MD5 hash (username\nskyper\password)
  • The generated RSA private key
  • The Skype encrypted corresponding RSA public key

Everything is heavily encrypted, but in a symmetric way :)
The following algorithms are used

  • CryptProtectData(), CryptUnprotectData()
  • SHA-1
  • AES-256
  • “FastTrack Cipher”
  • 1024+ bit RSA

Only an MD5 hash of password is stored in the user’s profile and it’s encrypted with AES-256
Once hash is decrypted, the only recovery methods available are dictionary attack or variation of brute force.

The information here applies to version 5.10.0.116 but should also work without hitch on some older versions (4.2 was also tested)

Storage

There are 2 things required in order to dump the MD5 hash.

  1. DPAPI blob :
    HKEY_CURRENT_USER\Software\Skype\ProtectedStorage

  2. Credentials ciphertext :
    %APPDATA%\Skype\<login id>\config.xml

The DPAPI blob is just stored as binary and can be passed straight to CryptUnprotectData()

C:\>reg query HKCU\Software\Skype\ProtectedStorage

HKEY_CURRENT_USER\Software\Skype\ProtectedStorage
    0    REG_BINARY    01000000D08C9DDF0115D1118C7A00C04FC297EB01000

The Credentials are hexadecimal string stored in XML file

<?xml version="1.0"?>
<config version="1.0" serial="66" timestamp="1344481520.27">
  <Lib>
    <Account>
      <Credentials3>322EBDF6D922E91F7EB68

As a result of the XML file I ended up using the following libraries from here:

  • libxml2-2.7.8.win32.zip
  • iconv-1.9.2.win32.zip
  • openssl-0.9.8a.win32.zip
  • zlib-1.2.5.win32.zip

Generation

The following demonstrates creation of the MD5 hash using OpenSSL

void GenHash(const char *id, const char *pwd) {
    MD5_CTX ctx;
    const char *skype = "\nskyper\n";
    u_int8_t dgst[32];
    
    MD5_Init(&ctx);
    MD5_Update(&ctx, id, strlen(id));
    MD5_Update(&ctx, skype, strlen(skype));
    MD5_Update(&ctx, pwd, strlen(pwd));
    MD5_Final(dgst, &ctx);

    printf("\n  Login ID = %s"
           "\n  Password = %s"
           "\n  MD5 hash = ", id, pwd);
    
    for (int i = 0;i < 16;i++) {
      printf("%02x", dgst[i]);
    }
    printf("\n");
}
.....
C:\>skype_dump username password
  ...
  Login ID = username
  Password = password
  MD5 hash = 27f6a9d892475e6ce0391de8d2d893f7

Recovery

To extract the Credentials ciphertext, you could read the contents of config.xml and scan for <Credentials3> and </Credentials3>
Here, I’m using LibXML :P

bool GetCredentials(BYTE ciphertext[], std::string config_xml) {    
    bool bFound = false;
    
    // try open config.xml
    xmlTextReaderPtr reader;
    reader = xmlReaderForFile(config_xml.c_str(), NULL, 0);
    
    // tested with Credentials2 or Credentials3
    const xmlChar *credentials; 
    credentials = (const xmlChar*)"Credentials";

    if (reader != NULL) {
    
      // while nodes are available
      while (xmlTextReaderRead(reader) == 1) {
        // get name
        const xmlChar *name;
        name = xmlTextReaderConstName(reader);
        if (name == NULL) continue;

        // equal to credentials we're searching for?
        if (xmlStrncmp(credentials, name, xmlStrlen(credentials)) == 0) {

          // read the next value
          if (xmlTextReaderRead(reader) == 1) {
            const xmlChar *value;
            value = xmlTextReaderConstValue(reader);
            
            for (int i = 0;i < 16;i++) {
              sscanf((const char*)&value[i * 2], "%02x", &ciphertext[i]);
            }
            bFound = true;
            break;
          }
        }
      }
      xmlFreeTextReader(reader);
    }
    xmlCleanupParser();
    return bFound;
}

Obtain the salt which is passed to SHA-1 before being used to create AES key.

PBYTE GetSalt(DWORD &cbSalt) {
    BYTE aBlob[2048];
    DWORD cbSize = sizeof(aBlob);
    const char skype_path[] = "Software\\Skype\\ProtectedStorage";
    
    LSTATUS lStatus = SHGetValue(HKEY_CURRENT_USER, skype_path, 
        "0", 0, aBlob, &cbSize);
      
    if (lStatus != ERROR_SUCCESS) {
      printf("  Unable to open skype key : %08x", lStatus);
      return NULL;
    }

    DATA_BLOB in, out;
    
    in.pbData = aBlob;
    in.cbData = cbSize;
    
    if (CryptUnprotectData(&in, NULL, NULL, NULL, 
        NULL, 0, &out)) {
      cbSalt = out.cbData;
      return out.pbData;
    } else {
      printf("  Unable to decrypt skype entry.");
    }
    return NULL;
}

Then with both the ciphertext and salt, we can decrypt MD5 hash…

void DecryptHash(PBYTE pbCipherText, PBYTE pbSalt, DWORD cbSalt) {
    
    SHA_CTX ctx;
    AES_KEY key;
    
    u_int8_t dgst[40], buffer[AES_BLOCK_SIZE];
    
    memset(&buffer, 0, sizeof(buffer));
    
    // use counter mode + SHA-1 to generate key
    for (ULONG i = 0;i < 2;i++) {
      ULONG ulIndex = _byteswap_ulong(i);
        
      SHA1_Init(&ctx);
      SHA1_Update(&ctx, &ulIndex, sizeof(ulIndex));
      SHA1_Update(&ctx, pbSalt, cbSalt);
      SHA1_Final(&dgst[i*20], &ctx);
    }
    
    AES_set_encrypt_key(dgst, 256, &key);
    AES_encrypt(buffer, buffer, &key);
    
    printf("\n  MD5 hash = ");
    
    // decrypt MD5 hash with XOR
    for (int i = 0;i < 16;i++) {
      printf("%02x", pbCipherText[i] ^ buffer[i]);
    }
    printf("\n");
}

Conclusion

If you want to know more about the internals of Skype, I’d strongly recommend the “Vanilla Skype” papers 1 and 2

It’s safe to say MD5 isn’t a good choice of algorithms for protecting passwords.
Maybe as more recovery tools become available, Microsoft will revise the code to use something stronger.
source code

Password Algorithms: Yahoo Messenger

Introduction

OPSWAT listed Yahoo Messenger as the 3rd most popular IM application in June 2011 with 15.10% of market share.

I read Slicks in-depth analysis already and other claims of recovery being “impossible” so I was interested to see if any progress could be made upon Slicks earlier work.

As Slick indicates in his article, it would appear the only login information stored on the user’s computer is a token, therefore you can certainly transfer the token to another machine but you can’t recover the plaintext password.

Storage

Based on version 11.5.0 which is the latest release, if the user checks “Remember My ID and password”, the application will store some session information in the user’s personal registry file: NTUSER.DAT

C:>reg query "HKCU\Software\Yahoo\pager" /v ETS

HKEY_CURRENT_USER\Software\Yahoo\pager
    ETS REG_SZ  eJxjZGBguNAz9z6j6EXBniqGA/6Hpr9mBIq90W3gn8fk6T . . .

The above value is truncated but from analysing the binaries, it’s definitely a Base64 string.

Algorithm

If you pass the ETS string to OpenSSL for decoding, it reveals a ZLib blob which you can tell from the first couple of bytes. 0x78 0x9c

# hexdump ets.bin
00000000: 78 9c 63 64 60 60 b8 d0 - 33 f7 3e a3 e8 45 c1 9e   x.cd.... 3....E..
00000010: 2a 86 03 fe 87 a6 bf 66 - 04 8a bd d1 6d e0 9f c7   .......f ....m...
00000020: e4 e9 3a 6d 8a d0 c7 43 - ba 8d 6b 80 42 0c 4c 20   ...m...C ..k.B.L.
00000030: 82 81 39 8d 81 61 05 90 - 16 00 e2 9a da fe 63 a5   ..9..a.. ......

We can decompress this stream using Zpipe and dump it once again to see the results.

# zpipe -d < ets.bin > unknown.bin
# hexdump unknown.bin
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 - ec 2d 80 0f 9e 02 49 45   O....... ......IE
00000020: 96 94 12 f1 c2 2d 81 ac - 00 00 00 00 02 00 00 00   ........ ........
00000030: 00 00 03 66 00 00 a8 00 - 00 00 10 00 00 00 7c 7d   ...f.... ........

The above binary data is a DPAPI blob and this is passed to CryptUnprotectData using some entropy.

The entropy is the string “MBCS sucks” with the Yahoo id appended, for example.

MBCS suckssomeyahooid@domain.com

The result of the decrypted DPAPI blob is a Yahoo 64 encoded string which Slick discusses in his article.
My own cookie/token looked something like the following:

ALRX7U8E1cicRDzhXXX5vlue_Mg.9BmHv25VeQRdTriqUbw6MccGEg--

The Yahoo 64 algorithm uses same alphabet as standard Base64 except the last 2 bytes are different and padding uses “-” instead of “=”

"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789._"

Conclusion

We’ve seen how it’s possible to dump the token from the registry which can at least in theory be used to login from another system.

Perhaps in future entry the token generation can be revealed :)

To be continued . . .

Password Algorithms: Google Chrome

Introduction

Google Chrome is very popular right now for web browsing.

As of June 2012, it accounts for 27% of market share according to Wikipedia statistics. which is the largest ahead of Internet Explorer (24%) and Firefox. (19%)

It’s main user base appears to be in Russia, India and South American nations.

I’m analysing the browsers password algorithm on Windows so it could differ on other platforms.

Storage

Based on the current version, 20.0.1132.47, Chrome asks me to “Save password” for gmail login.

After confirming to save, a file called Login Data appears in my windows profile.

C:\Documents and Settings<user id>\Local Settings\Application Data\Google\Chrome\User Data\Default

If you’re on Vista or later, you would find it created in

C:\Users\<user id>\AppData\Local\Google\Chrome\User Data\Default

A hex dump of this file tells us it’s an SQLITE3 database.

00000000: 53 51 4c 69 74 65 20 66 - 6f 72 6d 61 74 20 33 00   SQLite.f ormat.3.
00000010: 08 00 01 01 00 40 20 20 - 00 00 00 04 00 00 00 06   ........ ........
00000020: 00 00 00 00 00 00 00 00 - 00 00 00 03 00 00 00 01   ........ ........
00000030: 00 00 00 00 00 00 00 00 - 00 00 00 01 00 00 00 00   ........ ........

Using the sqlite3 shell, let’s look inside.

# sqlite3 "Login Data"
SQLite version 3.7.11 2012-03-20 11:35:50
Enter ".help" for instructions
Enter SQL statements terminated with a ";"
sqlite> .tables
logins  meta
sqlite> .mode column
sqlite> .width 2 20
sqlite> pragma table_info(logins);
0   origin_url            VARCHAR     1                       0
1   action_url            VARCHAR     0                       0
2   username_element      VARCHAR     0                       0
3   username_value        VARCHAR     0                       0
4   password_element      VARCHAR     0                       0
5   password_value        BLOB        0                       0
6   submit_element        VARCHAR     0                       0
7   signon_realm          VARCHAR     1                       0
8   ssl_valid             INTEGER     1                       0
9   preferred             INTEGER     1                       0
10  date_created          INTEGER     1                       0
11  blacklisted_by_user   INTEGER     1                       0
12  scheme                INTEGER     1                       0

Obviously you can use GUI for this too but I’m fan of the command line generally as it can be quicker once you master it.

I’ve dumped the entries from the database to a text file for a better look

echo .dump | sqlite3 "Login Data" >logins.txt

The password_value has been truncated here to fit on the page and also hide ciphertext which could be decrypted offline but looks something like.

X'01000000D08C9DDF0115D1118C7A00C04FC297EB01000 . . .

Initially, this blob appears to be a DPAPI blob and on closer inspection of binaries, I confirm CryptUnprotectData() is used without any entropy values.

Generation

For Windows operating systems the password_value is essentially a DPAPI blob derived from CryptProtectData() and inserted into the “Login Data” database.

On my own Linux system, Chrome uses the Gnome-Keyring and how it works is outside the scope of a short blog entry :)

The Login Data file does appear in

/home/dietrich/.config/google-chrome/Default

However, it doesn’t contain any entries and I haven’t tried to disable the keyring service to observe what effect that has on Chrome and it’s functionaliy.

Recovery

Because DPAPI encrypts data based on either the DPAPI_SYSTEM values stored in LSA secrets or the user’s password, you can’t copy the database file to another machine and decrypt without using special tools.

In order for the following code to work, it must be executed under the profile of user that saved the passwords.

I had to compile SQLITE3 library for Visual Studio which was very straight forward.

Download the SQLITE3 sources, compile and create library.

cl /O1 /Os /Oy /GS- sqlite3.c /c
lib sqlite3.obj /out:sqlite3.lib

First part of program obtains the path of database.

    string login_db;
    
    // if user doesn't provide filename, app will use local profile
    if (argc > 1) {
      login_db = argv[1];
    } else {
      CHAR lpszPath[MAX_PATH];
      
      if (!SHGetSpecialFolderPath(NULL, lpszPath, 
            CSIDL_LOCAL_APPDATA, FALSE)) {
        
        printf("\nUnable to determine \"Local Settings\" folder");
        return 0;
      }
      login_db = lpszPath;
      login_db += "\\Google\\Chrome\\User Data\\Default\\Login Data";
    }
    
    // ensure file exists
    if (GetFileAttributes(login_db.c_str()) == INVALID_FILE_ATTRIBUTES) {
      printf("\n\"%s\" does not exist\n", login_db.c_str());
      return 0;
    }

Load the database into memory using SQLITE3 library functions and query the password_value before passing to CryptUnprotectData()
We’ll need the username_value and signon_realm too.

sqlite3 *db;

// open database
if (sqlite3_open(login_db.c_str(), &db) == SQLITE_OK) {
  sqlite3_stmt *stmt;
  string query = "SELECT username_value, password_value, signon_realm FROM logins";

  // execute SQL statement
  if (sqlite3_prepare_v2(db, query.c_str(), -1, stmt, 0) == SQLITE_OK) {

    while (sqlite3_step(stmt) == SQLITE_ROW) {
      DATA_BLOB in, out;
      string realm, username, password;

      username = (char*)sqlite3_column_text(stmt, 0);
      realm = (char*)sqlite3_column_text(stmt, 2);

      in.pbData = (LPBYTE) sqlite3_column_blob(stmt, 1);
      in.cbData = sqlite3_column_bytes(stmt, 1);

      // decrypt using DPAPI
      if (CryptUnprotectData(&in, NULL, NULL, NULL, NULL, 1, &out)) {
        password = (char*)out.pbData;
        password[out.cbData] = 0;

        LocalFree(out.pbData);
      } else {
        password = "<decryption failed>";
      }
      printf("\n%s | %s | %s", username.c_str(), password.c_str(), realm.c_str());
    }

    sqlite3_finalize(stmt);
  } else {
    printf("\n[-] sqlite3_prepare_v2("%s") : %s\n",
        login_db.c_str(), sqlite3_errmsg(db));
  }
  sqlite3_close(db);
} else {
  printf("\n[-] sqlite3_open("%s") : %s\n",
    login_db.c_str(), sqlite3_errmsg(db));
}

The code here is just to demonstrate reading each entry from database and passing to windows for decryption.

Conclusion

The strength of DPAPI depends on how strong the user’s password is or if backup keys are available

There are solutions that perform recovery without the user’s password but we’ll look at this later. :)

Password Algorithms: AOL Instant Messenger

Introduction

AOL instant messenger is used by 0.73% of IM market according to OPSWAT
It’s still in development and because of changes in how the password is stored, this analysis will be presented in 2 entries.

Storage

In 6.x, when a user checks the ‘Save Password’ box, the application will store the current username and password in NTUSER.DAT

From command prompt, you can run REG.exe to dump the entries.

C:\reg query "HKCU\Software\America Online\AIM6\Passwords"

HKEY_CURRENT_USER\Software\America Online\AIM6\Passwords
    joe.bloggs@emaildomain.com  REG_SZ  zo+VVoi9LWCtc0B8z9ZnfojNdjVuv08DXid8yK++LYI=

Based on the characters in the string, it appears to be base64 encoded.
Here, i’m using openssl and redirect the output to a file before hexdumping.

echo zo+VVoi9LWCtc0B8z9ZnfojNdjVuv08DXid8yK++LYI= | openssl enc -base64 -d > aim.bin & hexdump aim.bin

00000000: ce 8f 95 56 88 bd 2d 60 - ad 73 40 7c cf d6 67 7e ...V.... .s....g.
00000010: 88 cd 76 35 6e bf 4f 03 - 5e 27 7c c8 af be 2d 82 ..v5n.O. ........
00000020: 82

This doesn’t appear to contain anything intelligible to the eye so we’ll need to look deeper in the application itself.

Generation

After some digging in the binaries, the blob stored in the registry contains a salt and ciphertext derived from Blowfish, here’s the structure.

#define MAX_SALT_LEN 8
#define MAX_PASS_LEN 16

typedef struct _AIM_PASSWORD_BLOB {
  u8 Salt[MAX_SALT_LEN];
  u8 Password[2*MAX_PASS_LEN+1];
} AIM_PASSWORD_BLOB, *PAIM_PASSWORD_BLOB;

The password is stored in UNICODE format.
The salt is generated by first initializing the seed to current time, then calling rand() 8 times to fill 64-bit buffer.
Everytime the user logs in and out, the password entry is updated with new salt.

// generate 64-bit salt using time(0) as seed
void gen_salt(PAIM_PASSWORD_BLOB pBlob) {
    srand(time(0));

    for (int i = 0; i < MAX_SALT_LEN; i++) {
      pBlob->Salt[i] = rand() & 0xff;
    }
}

Blowfish has a variable key length and the encryption algorithm uses all 448-bits of key to encrypt the users password.
Below only illustrates how the static key is created.

// used to create the 56 bytes of key
const u8 aim_kbox[256] =
  { 0x59, 0x3a, 0x7c, 0x77, 0xf3, 0x2b, 0xab, 0x1f,
    0x99, 0x98, 0x86, 0x6c, 0x59, 0xaa, 0x9d, 0x7f,
    0x58, 0x3f, 0x6a, 0xb9, 0x0b, 0x47, 0x29, 0x35,
    0xaa, 0x6d, 0xea, 0x95, 0xe2, 0xfb, 0xe4, 0x02,
    0xcb, 0xf7, 0x0c, 0x6e, 0x19, 0x92, 0xe6, 0x1c,
    0x96, 0xc4, 0x9b, 0x63, 0xd0, 0x30, 0x4d, 0xaf,
    0x0e, 0x4d, 0xa7, 0xc8, 0x89, 0xc7, 0xb8, 0x57,
    0xd9, 0x23, 0x01, 0xa6, 0xae, 0xa3, 0xcc, 0xa7,
    0xc0, 0x69, 0xc0, 0x38, 0x09, 0xde, 0xb3, 0xa5,
    0x31, 0x55, 0xbf, 0x6e, 0x4a, 0xec, 0x98, 0x4b,
    0xbd, 0xb3, 0x1c, 0x6e, 0x84, 0x11, 0x2c, 0x08,
    0x9a, 0x63, 0xbb, 0x0e, 0xb0, 0xe5, 0x24, 0x3d,
    0x22, 0xd6, 0xc1, 0x5c, 0x29, 0xd7, 0xb9, 0xc1,
    0x52, 0x95, 0x19, 0x16, 0x2f, 0xa7, 0x27, 0x5d,
    0x4c, 0xba, 0xf3, 0x32, 0x64, 0xeb, 0x2e, 0x50,
    0xd5, 0x74, 0x3f, 0x57, 0x52, 0x8b, 0x94, 0xcd,
    0xd8, 0x87, 0x36, 0x62, 0xe3, 0x45, 0xa1, 0x78,
    0xe1, 0xca, 0xd2, 0xe2, 0xe7, 0x29, 0xa1, 0xec,
    0xa3, 0xa7, 0x51, 0x9c, 0x92, 0x1e, 0x66, 0x38,
    0x72, 0x9f, 0xb6, 0x08, 0xfb, 0x5b, 0xc3, 0x5d,
    0xca, 0xc4, 0x48, 0xd3, 0x9e, 0xef, 0x12, 0xd2,
    0xc6, 0x3d, 0x11, 0x5b, 0xda, 0x3a, 0xaf, 0x01,
    0xaa, 0xc5, 0x60, 0x35, 0x74, 0x72, 0x7b, 0xc7,
    0x5a, 0x2c, 0x48, 0xaa, 0x12, 0x1f, 0x5a, 0xf8,
    0xe0, 0xd5, 0x1f, 0x35, 0xc1, 0x9e, 0xb5, 0xd8,
    0xe9, 0x36, 0xff, 0x07, 0x70, 0xa2, 0xf9, 0xa3,
    0x49, 0x5b, 0x48, 0x84, 0x73, 0xae, 0x16, 0x06,
    0x73, 0x63, 0x1c, 0xc4, 0x01, 0x9d, 0x00, 0xa3,
    0x02, 0x01, 0xe2, 0x13, 0xb6, 0x1a, 0x32, 0x91,
    0x33, 0xfc, 0xc4, 0x70, 0x12, 0x28, 0x26, 0xda,
    0x68, 0xb0, 0x31, 0x12, 0xf8, 0x9c, 0xde, 0xfb,
    0xa6, 0x8b, 0x5b, 0xde, 0x2f, 0x9e, 0x5e, 0x68 };

#define SWAP(x, y) u8 t; t = x;x = y;y = t;

void gen_static_key(u8 output[]) {

  u8 kbox[256];

  memcpy(kbox, aim_kbox, sizeof(aim_kbox));

  for (int i = 0; i < 128; i++) {
    SWAP(kbox[ kbox[i] ], kbox[ kbox[i+1] ])
  }
  memcpy(output, kbox, 56);
}

The end result of gen_static_key() is a 56-byte blowfish key.
The user’s 8-byte salt will replace the first 8-bytes of this static key before being passed to BF_set_key()

It’s not necessary to use gen_static_key() after the first time since it doesn’t use any entropy at all.

Recovery

The final decoding routine using C++ looks something like:

#define MAX_SALT_LEN 8
#define MAX_PASS_LEN 16
#define MAX_KEY_LEN 56

typedef struct _AIM_PASSWORD_BLOB {
  u8 Salt[MAX_SALT_LEN];
  u8 Password[2*MAX_PASS_LEN+1];
} AIM_PASSWORD_BLOB, *PAIM_PASSWORD_BLOB;

void base64_decode(PAIM_PASSWORD_BLOB output, char blob_string[]) {
    BIO *b64, *mem;

    b64 = BIO_new(BIO_f_base64());
    BIO_set_flags(b64, BIO_FLAGS_BASE64_NO_NL);
    mem = BIO_new_mem_buf(blob_string, strlen(blob_string));
    mem = BIO_push(b64, mem);

    BIO_read(mem, output, strlen(blob_string));
    BIO_free_all(mem);
}

const u8 static_key[48] =
  { 0x99, 0x00, 0x86, 0xa5, 0x27, 0xaa, 0x9d, 0x7f,
    0x58, 0xaa, 0xae, 0xb9, 0x0b, 0x47, 0x3a, 0x35,
    0xaa, 0xe0, 0xea, 0x95, 0x66, 0xfb, 0xe4, 0x9f,
    0xcb, 0xf7, 0x16, 0x1c, 0xa3, 0x92, 0xe6, 0x1c,
    0x96, 0x06, 0x9b, 0x5b, 0x29, 0x30, 0xbf, 0xaf,
    0xec, 0x11, 0x29, 0xc8, 0x89, 0x5b, 0xb8, 0x57 };

std::wstring AIM_Decrypt(char base64_blob[]) {
    BF_KEY key;
    u8 aim_key[MAX_KEY_LEN];
    AIM_PASSWORD_BLOB blob;
    wchar_t password[2*MAX_PASS_LEN+1];

    memset(&blob, 0, sizeof(AIM_PASSWORD_BLOB));
    memset(password, 0, sizeof(password));

    base64_decode(&blob, base64_blob);

    memcpy(&aim_key, blob.Salt, MAX_SALT_LEN);
    memcpy(&aim_key[MAX_SALT_LEN], static_key, sizeof(static_key));

    BF_set_key(&key, sizeof(aim_key), aim_key);

    BF_LONG *in = (BF_LONG*)blob.Password;
    BF_LONG *out = (BF_LONG*)password;

    for (int i = 0;i < MAX_PASS_LEN * 2 / sizeof(BF_LONG);i += 2) {
      memcpy(&out[i], &in[i], sizeof(BF_LONG)*2);
      BF_decrypt(&out[i], &key);
    }
    return std::wstring(password);
}

The string returned from AIM_Decrypt is in UNICODE format.

Conclusion

This protection isn’t great since you could easily decrypt entries just by reading NTUSER.DAT in the windows profile of AIM user.

A better solution might be CryptProtectData() and Base64.

Comments and questions welcome.

Password Algorithms: Google Talk

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

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 in NTUSER.DAT
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

/'')-%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.

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.

/**
 *
 * 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.

Password Algorithms: K9 Web Protection Admin

K9 Logo

Introduction

K9 Web Protection by Blue Coat software is a cheap but effective solution for parents monitoring internet activity of their children.
It’s also a cheap solution for internet cafes and companies to monitor customers and staff.

According to the vendors website, it’s installed on some 3 million computers and presumably most of those are using the freeware version for home use which is what I’ve downloaded.

Although there’s a commercial version, I’m not aware of any differences between the 2 with respect to the password algorithm.

Storage Method

Installed on XP, most files are kept in:

C:\Program Files\Blue Coat K9 Web Protection

The license file is always 944 bytes in size and also where the Admin password is stored.

Here’s the actual password “hash” and offset of where it appears.

000001d0: c4 b8 b5 b5 b7 b7 bd b1 - be 29 6b d6 eb 2c a9 00

The offset was discovered through analysing one of the binaries.

So how is this “hash” generated? :)

You can recover this password with simple subtraction. (at least in my own case) The example you see above is “theeggman” and will be revealed if you subtract 0x50 from each byte until you reach an invalid character.

The subtraction value may be different for you since I haven’t checked beyond my own XP install.
However, it’s possible to get the value from the license file from offset 0x2c

00000020: 75 a5 ce 51 00 00 00 00 - c3 e1 5a 78 50 00 00 00

Generation

Below is rough code for how a password is generated.

#define MAX_PASS_LEN 15

void encode(unsigned char k9_hash[], char password[]) {
    size_t pass_len = strlen(password);
    size_t i;

    for (i = 0;i < pass_len && i < MAX_PASS_LEN;i++) {
      k9_hash[i] = password[i] + license_data[44];
    }

    for (;i < MAX_PASS_LEN;i++) {
      k9_hash[i] = rand() % 256;
    }
    k9_hash[MAX_PASS_LEN] = 0;
}

Recovery

This uses hardcoded values from my own license file and successfully decodes to plaintext.

#include <stdio.h>
#include <ctype.h>

#define MAX_PASS_LEN 15

char* decode(char password[], unsigned char k9_hash[]) {
  char sym[] = "!@#$%^*(){}";
  size_t pass_len = 0;

  for (;pass_len < MAX_PASS_LEN;pass_len++) {
    int c = k9_hash[pass_len] - 0x50;
    if (!isalnum(c)) {
      if (!strchr(sym, c)) {
        break;
      }
    }
    password[pass_len] = c;
  }
  password[pass_len] = 0;
  return password;
}

unsigned char k9_admin_pass[] = { 0xc4, 0xb8, 0xb5, 0xb5,
                                  0xb7, 0xb7, 0xbd, 0xb1,
                                  0xbe, 0x29, 0x6b, 0xd6,
                                  0xeb, 0x2c, 0xa9, 0x00 };

void main(void) {
  char password[MAX_PASS_LEN+1];

  memset(password, 0, sizeof(password));
  printf("\nPassword = %s\n", decode(password, k9_admin_pass));
}

Conclusion

In order to take advantage of the weak hashing algorithm, you need at least read access to the license file and after checking permissions, it’s only available to the owner, SYSTEM and other local administrators.

It doesn’t provide much protection for the password but controlling admin access at OS level negates this.