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.

Web Malware Collection – Massive Change

Insecurety Research has mantained a repository of web malware over at Google Code for a considerable amount of time, pharm so independant researchers could get samples for analysis.

We always offered it as a SVN repo, where anyone could anonymously check out the whole collection, troche or selected samples, at will.

Those days, sadly, have come to an end.

Due to a researcher from nerv.fi creating an issue about it – see here – we ended up deciding to come to a compromise, sale lest we get suspended or something.

We now offer the entire repository as an archive downloadable here instead of a SVN repository, and every time we get 50 new samples in the bag, we will update the tar file.

Simply wget http://web-malware-collection.googlecode.com/files/web-malware-collection-13-06-2012.tar.gz to get the current one. We will post every time we release a new one.

This project has been one of our proudest achievements, and we are very sorry to see it crippled in this way, however as we all know, we must adapt in order to survive. While Henri has a legitimate complaint, we believe that these samples STILL belong to the public.

Human knowledge belongs to the world, after all, and information ALWAYS wants to be free.

Forensics – HackEire .pcap challenge

I was awfully saddened to hear there was going to be no HackEire challenge in 2012, as I had always hoped I would get a chance to attend. However, seems the IRISS-CERT guys might be doing something, so that should be fun :D

Over at boards.ie in the Tech/Security section, the challenges are slowly appearing. So when I saw the “pcap challenge”, I HAD to have a look. Seeing as I am taking Forensic Science and Analysis starting in September, a major change from what I was studying – Biopharmaceutical Chemistry. Well, I hope to be taking it – I applied, and theoretically should get the place as I have more than enough CAO points. Forensic Science both allows me to use my knowledge of chemistry, and other “hard sciences”, but also provides me with opportunities to further study Digital Forensics and such, which has, er, become of GREAT interest to me as I wish to try help prevent online crime, rather than facilitate. ANYWAYS. Enough of that, lets get down to the fun stuff!

***infodox puts on his network forensics hat***

You may get the challenge files here – Dropbox and the thread is here – Boards.ie

Now, this post is going to be edited a lot as I progress through, and seeing as it is .pcap files I am analysing, I will be starting off by playing with Wireshark and Xplico, though any other tools I use will also be documented.

The pcap_questions.rtf file has “Questions” about each pcap that you must answer, and I will be keeping strictly to their requirements rather than digressing. However if I see anything funny or interesting I will note it.

So, I am going to start with c1.pcap and start with the first question…

“What was the complete URI of the original web request that led to the client being compromised?”

Well, lets see. The easiest way to filter this would be to use urlsnarf, part of Dug Songs dsniff toolkit. This comes as standard with most penetration testing distributions…

After a bit of parsing (using /dev/brain and gedit), I removed all references to legit sites (yes, even all the advertising ones) and found the following suspect URL’s.

10.20.0.165 – – [04/Jun/2012:04:42:04 +0100] “GET http://10.20.0.111:8080/banking.htm HTTP/1.1″ – – “-” “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)”

10.20.0.165 – – [04/Jun/2012:04:42:04 +0100] “GET http://10.20.0.111:8080/banking.htm?UOjiXfyAbAISuH HTTP/1.1″ – – “-” “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)”

10.20.0.165 – – [04/Jun/2012:04:42:04 +0100] “GET http://10.20.0.111:8080/banking.htmTysdAWdqQEBybyCGKQkGJyVuQsNWvmIFg.gif HTTP/1.1″ – – “http://10.20.0.111:8080/banking.htm?UOjiXfyAbAISuH” “Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)”

Now I had an IP address, so I opened the .pcap in Wireshark and proceeded to check what the hell was going to and from the “malicious” server.

I used the following filters:

ip.src == 10.20.0.111
and
ip.dst == 10.20.0.111

I then started peeking through the packet data to see could I find anything interesting…

The initial page (banking.htm) on the malicious server seems to serve a redirect to a second page, which serves up Javascript, and finally a .gif file, leading to remote code execution – once the GIF is served up, we see more traffic from the client to the server on port 4444 – pretty standard behavior for a Meterpreter reverse shell. So far, evidence suggests the “evil” machine was running some exploit from Metasploit.

1(a)
What was the complete URI of the original web request that led to the client being compromised?

> http://10.20.0.111:8080/banking.htm

1(b)
What file type was requested in the final web request to the malicious server? (Answer a, b, c ,d or e)

a. windows executable
b. javascript
c. pdf
d. worddocument
e. gif

> e, a .GIF file

1(c)
What is the sha1 hash of the afore-mentioned file?

> NOT FOUND YET, HAVE TO EXTRACT… Will look into extracting the file later :)

1(d)
What is the number of the first frame that indicates that the client has been compromised?

> 4722 in Wireshark seems to be the SYN packet in the reverse shell

1(e)
At one point, the malicious server sends a malicious file to the client. What type of file is ? (Answer a, b, c ,d or e)

a. windows executable
b. javascript
c. pdf
d. worddocument
e. gif

> NOT FOUND YET, HAVE TO EXTRACT!

1(f)
What is the sha1 hash of the malicious file?

> NOT FOUND YET, HAVE TO EXTRACT…

1(g)
What vulnerable software is exploited (in the following format, ff3.5, ff3.6, ff5, Word2010, ie7, Safari2, Chrome2, AdobeReader, ie6, ff4)?

> FF4 According to User Agent (Mozilla/4.0)

1(h)
When the capture ends, is the client still connected to the malicious attacker? Answer either “yes” or “no”.

> YES, the connection to port 4444 never has a FIN or RST so I can assume it is still ongoing.

1(i)
Can you give the corresponding CVE security bulletin that covers the vulnerability here that was exploited (answer in form of CVE-$year-$number).

> NOT FOUND (YET)

1(j)
From the capture, it is clear that the attacker gets a certain form of access (i.e. the interface), what (type of) access does the attacker “get” on the client?

> Shell access, based on the junk data, an encrypted reverse shell. Based on port data, Meterpreter. Further investigation into the payload used is necessary.

— Post Changelog —
1. The editor broke and pasted the first three paragraphs into the top like a million times. oops…

 

Malware.lu vs Herpesnet Botnet

Recently, a group of malware researchers, at Malware.lu – have seemingly taken the fight back against botnets another step.

Now, first off, let’s get my position on this straight for those watching me who are just waiting for me to break some kind of law or ethical barrier: While I wholeheartedly support these researchers actions, and believe that their approach to “dealing with botmasters” is the way forward, I will NOT be participating in it without cooperation of some sort from Law Enforcement, or legal permission to do so. The ice under my feet is too fucking thin to go popping C&C servers, though I wish I could.

The reason why we should take the fight back to the botnet masters is simple: They have the upper hand here. We do not. They can simply keep changing their C&C servers, keep doing DNS switcharoos, and keep spreading their malware, targeting innocent bystanders and making off with reams of personal data, while us researchers can do shag all except report to the authorities and pray to god they do something. It is not until someone like Microsoft gets involved that shit ACTUALLY gets done.

If the relevant authorities would issue “carte blanche”, or even a “carte gris” to take down botnets if the opportunity arises, the internet would be a far safer place. Until PROACTIVE measures are taken to make the cost of operating and owning these things too damn high for the amateurs, the smaller botnets will proliferate undetected for infinite time.

Anyways, on to the story.

So, the guys at malware.lu (I believe it was r00tBSD) got a sample in, of the HerpesNet botnet. I had been investigating Herpesnet for a while, after seeing it offered up as a “botnet as a service” type thing. You simply infect people, the botmaster/owner handles the C&C and backend server, along with writing the malware.

Fascinating system, and it is criminal economies like these that need to be stamped out. This particular sample was proliferating on Skript Kiddie forums predominantly, however, it is simply showing how *Fast* this kind of service offering has become common place.

Upon analysing the unpacked (non obfusticated) binary, and decrypting the few strings that were obscured, they were able to locate its C&C server.

They were also able to uncover what it sent to the server in HTTP POST requests, which seemingly, was sent in plaintext, and therefore offered them insights into what kind of data this thing was stealing, etc.

The researchers then decided to see could they perhaps use SQL injection attacks to own the command and control server, and in the end, they succeeded, gaining access to the administrator (botmasters) account on the C&C.

Taking things further, they actually uploaded a Meterpreter payload to the server and ran it, remotely hijacking the C&C. They took a screenshot of the C&C through the Meterpreter shell, and it LOOKS to me like it was the malware authors own box they had owned.

Pretty stupid, hosting a multi user botnet on your own box… But… Criminals are rarely the most intelligent it seems.

Now for the REALLY fun part: They then dumped the bots source code, etc. onto their box for more analysis, and subsequently the botmaster severed the box’s internet connection.

Not only this… They doxed the botmaster! Revealing his true identity for all to see (and for all to arrest…).

This kind of thing could be seen by some as vigilante justice, however, I see it as the future of the fight back against crimeware and online fraud. Even xylitol seems to get away with this, so my question to you all is as follows: What is the ethics/legality to this? CAN I legally start “assisting” LEO by dropping botnets and doxing their owners? I am FAIRLY sure I would be breaking anti-hacking laws, but, I also know the law pretty much requires me to do my duty as a citizen to PREVENT crime.

While I won’t be popping botnet C&C servers anytime soon, it is an interesting question to ask… And one I would love to know the answer to.

For more on this story, check out the article the guys wrote here:
Herpesnet Analysis and Ownage – Malware.lu

And also check out their main site: http://malware.lu

Massive props to the Malware.lu team, they are REAL internet superheros!

More Decompile – Nuclear DDoSer

Seeing as it is the weekend, and I had promised this, here goes nothing… Yesterday you saw my decompile of the lame HTTP Flooder – see HERE – and today, I have decompiled Nuclear DDoSer.

I previously wrote about “Nuclear DDoSer” HERE , comparing it to the SlowLoris and Slowpost tools.

This thing, as a point of interest, operates in a similar way to how I theorize “XerXes” works, and with some modification and improvement could actually do a considerable amount of damage.

SO I will not be bothering making those improvements.

Go get it here…
http://insecurety.net/Downloads/NUCLEAR_DOS_DECOMPILE.tar.gz

MD5: c8248c60b438fe544c7dfdd847f53692
SHA1: c3757099dead3a3f7656c33a49072a8126174929

AS always, we decompile and release this stuff so you don’t have to, for purely educational purposes, and to satisfy our sense of schadenfreud toward the skidiots out there. “We do not like them very much”.

Decompiled Skidware – HTTP Flooder by “van1lle”.

Howdy all, see well, another GREAT day here at the labs! Sun is shining, boxes are overheating, sickness and most everyone is a little bit hungover at least…

Well, I decided to harvest a bunch of what I refer to as “Skidware” for decompilation purposes (practicing my .net fu) and decided to release this one first.

It is the source (and original binary) of a rather popular HTTP Flooding DoS tool, distributed on skript kiddie forums.

It is basically an app in C# that just spews “slowloris” at a server until it dies… Standard Layer 7 Denial of Service stuff. The original author bragged that he/she/it took down Virustotal using it.

So, here it is :D

MD5: 18a31dce229b2734eabdb207e2296a68
SHA-1: 04f70f94b91ade15ab2f1d968c152ef1e900a41b
Downloads…
http://insecurety.net/Downloads/HTTPFLOOD_DECOMPILE.tar.gz

We do not take responsibility for what you do with this.