<<< Date Index >>>     <<< Thread Index >>>

Ad-Aware Revisited



--------

[Abstract]
Ad-Aware is anti-spyware program from Lavasoft. Running 
it gives you a false sense of safeness. There can be done numerous attacks 
against this software. I'll show some of the problems and attacks in this 
write-up. Here's just a summary of the most visible problems I've run into.

1.   Definition file
1.1. "Encrypted" with xor                  \ 
1.2. Packed with ZIP with simple password  - trivial to intercept def 
                                             updates and change the defs 
                                             to make the malware invisible
1.3. No checksum in the def file           /
1.4. Big redundancy in the def file
1.5. !!! Multiplying the number of entries in the def file with constant
     1.46 to make it look it has more definitions !!!

2.   Program
2.1. Poorly written checksum algo
2.2. Poorly written scanning algo
2.3. CSI works only for in-memory images

You want the proofs? Read the following text ...

---------------------------------------------------------------------------

1. [Intro]
"Lavasoft is the industry leader and most respected provider of 
anti-spyware solutions. Lavasoft develops and delivers the highest quality 
antispyware solutions to keep your computer or network free of 
compromising and intrusive threats to your privacy."

--

This write-up reviews the industry leading antispyware solution from the most 
respected provider of anti-spyware solutions - Ad-Aware from Lavasoft. I 
will show that this software simply doesn't do what what the PR tells.

2. [Ad-Aware SE]
"Ad-Aware SE is the latest version of our award winning and industry 
leading line of antispyware solutions and represents the next generation 
in Spyware detection and removal. It is quite simply the most advanced 
solution available to protect your privacy. With the all new Code Sequence 
Identification (CSI) technology that we have developed, you will not only 
be protected from known content, but will also have advanced protection 
against many of their unknown variants. "

--

2.1. "Encrypted" with xor
The reference file defs.ref is just a plain ZIP file that is then 
"encrypted" using following algo.

void decode_mem(char *b, unsigned int b_s)
{
  static char decode_string[] = 
"\x00\x50\x50\x50\x50\x50\x50\x50\x68\x69\x73\x20\x70\x67\x67\x67\x67\x67\x67\x20\x6d\x75\x73\x74\xe0\xe0\xe0\xe0\xe0\xe0\x6e";

  int unsigned y = 0;
  for(unsigned int x = 0; x < b_s; x++)
  {
    b[x] ^= decode_string[y];
    if(++y == (sizeof(decode_string) - 1))
      y = 0;
  }
}

Pointer b points to memory with the content of defs.ref and b_s is just 
size of the buffer.

2.2. Packed with ZIP with simple password
After "decrypting" there is a ZIP file with file 29388543757543549 inside. 
The file name is visible in ad-aware.exe. The ZIP file is password 
protected and the password is "This program ^u@_LSstreams145681902". First 
part of the password [This program ^u@_LSstreams] is in plaintext inside 
ad-aware.exe, second part is created runtime.

2.3. No checksum in the def file
After "decrypting" and decompressing there is a definition file with 
following structure.

[header]
[family names]
[www names]
[family descriptions]
[obj_stream]

offset   size                    description
 32h     WORD?                   internal build
 80h     ????                    version of ref file, ends with 0ffh
100h     ????                    family names, separated by 0ffh, ends with 
0ffffh
????     ????                    www names, separated with 0ffh, ends with 
0ffffh
                                 and content of ini file 
                                 (comments for family names)
                                 gets stored to description.ini

????     ????                    stream of objects ... starts with word 
OBJ_STREAM(n)[x]
                                 where n is prolly number of the stream streamu 
(1 for now)
                                 - IMHO preparation for incremental updates -
                                 and x is nunmber of objects in the stream
                                 at the end of stream there is 0ffffh again

[Example of reference file, info gives ad-aware directly]
Definitions File Loaded:
Reference Number : SE1R47 24.05.2005            offet 80h
Internal build : 55                             offst 32h
File location : G:\nada\nada\defs.ref           ...
File size : 435074 Bytes                        file size before decompression
Total size : 1439523 Bytes                      file size after decompression
Signature data size : 1408291 Bytes             sizeof(family descriptions) + 
sizeof(obj_stream)
Reference data size : 30720 Bytes               family names size
Signatures total : 40174                        [x] * 1.46 + www names
CSI Fingerprints total : 886                    entries in OBJ_STREAM with type 
0f0h
CSI data size : 30371 Bytes                     sizeof entries with type 0f0h
Target categories : 15                          known before
Target families : 679                           count of family names

There is no checksum of the content of the definition file nor is the file 
signed. It is trivial to modify the content of the file, for example 
modifying checksums of malware binaries by malware that wants to hide 
itself from Ad-aware is thus really _very_ easy.

2.4. Big redundancy in the def file
The definitions consist of registry keys, www sites, file names and the 
most visible part form the checksums of malware binaries.

[ Snippet from defs.ref]
...
3830397280  10842529196097280  97280   
3657194622  106199918742094622  94622   
3830994208  1059056701129094208  94208   
3697194208  105862934264094208  94208   
3697194210  1058568963132094210  94210   
...

Every checksum entry consists of a header, reference to family name and 
three ASCII numbers. Two of the numbers are checksums concatenated with file
size and the third one is the file size.

...
38303[97280]
108425291960[97280]
[97280]
...

2.5. Poorly written checksum algo
Computation of first level checksum
unsigned int compute_first_level_fingerprint(unsigned char *b)
{
  unsigned int checksum = 0;

  for(unsigned int x = 0; x < 0x600; x += 0x20)
  {
    checksum += b[x];
    checksum += x;
  }

  return checksum;
}

Computation of second level checksum
unsigned int compute_second_level_fingerprint(unsigned char *b, int l)
{
  unsigned int checksum = 0;
  unsigned int x = 0;

  for(; x < 0x2000; x += 0x2)
  {
    checksum += b[x];
    checksum += x;

    if(x >= (l - 2))
      break;
  }

  for(x = (l >> 1); x < (l >> 1) + 0x7ffc; x += 0x2)
  {
    checksum += b[x];
    checksum += x;

    if(x >= (l - 2))
      break;
  }

  return checksum;
}

Pointer b points to buffer holding content of the file, l is the 
buffer/file size.

  ...
  sprintf(size, "%d", x);
  sprintf(first_level, "%d%d", compute_first_level_fingerprint(b), x);
  sprintf(second_level, "%d%d%d%d", compute_second_level_fingerprint(b, x), 
(unsigned char) b[x >> 1], (unsigned char) b[x - 4], x);
  ...

  first_level  now holds the first level checksum
  second_level now holds the second leve checksum
  size         now holds the file size

Now we can just do a string compare against checksum entries in data file. 
If match is found, the fourth word is a index into family names string 
list. There are also entries that have description incorporated, but the 
entry structure is very easy to guess - feel free to explore it on your 
own.

As you can see, the checksum is really very basic one and could be easily 
spoofed. Colisions are easy to find. Next thing is the ASCII format of the 
checksums and file size concatenating. 

Lavasoft claims "Now Ad-Aware and Ad-Watch Use much smaller 
reference files" and I just have to say: there is a big redundancy and 
inefficiency in their reference files.

2.6. !!! Multiplying the number of entries in the def file with constant
     1.46 to make it look it has more definitions !!!
And the last and the worst thing about definition file. They take the x 
number from OBJ_STREAM (ie. the real object/entries count in the 
definition file) and MULTIPLY it with number 1.46 and this value is then 
showed to the user as REAL number of definitions in the file. 

2.7. Poorly written scanning algo
"Scanning speed increased" is what LavaSoft claims. Let's look at the reality.

The pseudo-C code of Ad-Aware file scan algo follows.
for entry from entries
{
  alloc_mem(file_size);
  
  read_file_to_memory();      // no memory mapped files, ReadFile()
  count_checksums();

  if(does_match_entry(entry, checksums))
    break;

  free_mem();
}

The real "Scanning speed increased" algo follows.
  map_file_to_memory();
  count_checksums();

for entry from entries
{
  if(does_match_entry(entry, checksums))
    break;
}
  unmap_file_from_memory();

So if you run the Ad-Awares file scan and you hear disk making noisy 
sounds, it's not like Ad-Aware is doing a good job finding the malware on 
your drive. It's just it uses very poorly written algo, that makes a lot 
of unnecessary disk reads thus wasting resources of your computer.

2.7. CSI works only for in-memory images and is useless
"Uses our all new CSI (Code Sequence Identification) technology to 
identify new and unknown variants of known targets"

Oh. What a technology! I wondered how they're doing this, I was thinking 
about some emulation engine, code shrinker, advanced pattern matching ... 
I also thought (everyone must think that based on the docs) that CSI is used on 
file 
scanning basis. It's not. CSI scanning is used only when scanning in-memory 
modules and thus ... is useless even if the algo would be the best one in the 
world - and it ain't.

3. [Outro]
"Lavasoft's Ad-Aware SE, the world's leading brand in antispyware 
solutions, has been acknowledged and awarded in variety of distunguished 
magazines and publications all over the world."


This text was written in the city of Sofia
(C) 1999-06 Roy Batty, who is a stranger in the world he was made to live in
roy.batty@xxxxxxxxxxxx

Eddie lives...somewhere in time

---------------------------------------------------------------------------

[decode.cpp]
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>

void decode_mem(char *b, unsigned int b_s)
{
  static char decode_string[] = 
"\x00\x50\x50\x50\x50\x50\x50\x50\x68\x69\x73\x20\x70\x67\x67\x67\x67\x67\x67\x20\x6d\x75\x73\x74\xe0\xe0\xe0\xe0\xe0\xe0\x6e";

  int unsigned y = 0;
  for(unsigned int x = 0; x < b_s; x++)
  {
    b[x] ^= decode_string[y];
    if(++y == (sizeof(decode_string) - 1))
      y = 0;
  }
}


void main(int argc, char *argv[])
{
  if(argc < 2)
  {
    printf("Syntax: decrypt_def_file.exe <def_file>\n");
    return;
  }

  HANDLE h = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE, 0, NULL, 
OPEN_EXISTING, 0, 0);
  if(h == INVALID_HANDLE_VALUE)
    return;

  DWORD s = SetFilePointer(h, 0, 0, FILE_END);
  SetFilePointer(h, 0, 0, FILE_BEGIN);

  HANDLE m = CreateFileMapping(h, NULL, PAGE_READWRITE, 0, 0, NULL);
  CloseHandle(h);

  if(h == INVALID_HANDLE_VALUE)
    return;

  char *b = (char *) MapViewOfFile(m, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  CloseHandle(m);
  if(!b)
    return;

  decode_mem(b, s);

  UnmapViewOfFile(b);
  
  return;
}
[decode.cpp]


{Just a signatures loader (copy the part of reference files with numbers) and 
 file scanner. Poorly coded, just testing code to prove my talk.}
[signatures.cpp]
#include "signatures.hpp"

AV_ENTRY *pos_head  = NULL;
int sigs;

void load_sigs(unsigned char *b)
{
  int sigs = 0;

  while(*b != 0xff)
  {
    int ref, l;
    char *first;
    char *snd;
    char *len;
    char *desc;

    ref = 0;
    desc = NULL;

    if(*b != 1)
      return;

    b += 3;
    if(*(b - 2))
    {
      l = (*(WORD *) (b - 2));
      desc = new char[l + 1];
      memset(desc, '\x0', l + 1);
      strncpy(desc, b, l);

      b += l;
    }

    if(*b == 0x3)
    {
      ref = *(WORD *) (b + 2);
      b += 9;
    }
    else
      b += 4;

    l = (*(WORD *) (b - 2));
    first = new char[l + 1];
    memset(first, '\x0', l + 1);
    strncpy(first, b, l);
    b += l;
    b += 3;

    l = (*(WORD *) (b - 2));
    snd = new char[l + 1];
    memset(snd, '\x0', l + 1);
    strncpy(snd, b, l);
    b += l;
    b += 3;

    l = (*(WORD *) (b - 2));
    len = new char[l + 1];
    memset(len, '\x0', l + 1);
    strncpy(len, b, l);
    b += l;

    AV_ENTRY *av = new AV_ENTRY;
    av->first_level = first;
    av->second_level = snd;
    av->length = len;
    av->description = desc;
    av->family_ref = ref;

    av->next = pos_head;
    pos_head = av;

    sigs++;
  }
}

unsigned int compute_first_level_fingerprint(unsigned char *b)
{
  unsigned int checksum = 0;

  for(unsigned int x = 0; x < 0x600; x += 0x20)
  {
    checksum += b[x];
    checksum += x;
  }

  return checksum;
}

unsigned int compute_second_level_fingerprint(unsigned char *b, int l)
{
  unsigned int checksum = 0;
  unsigned int x = 0;

  for(; x < 0x2000; x += 0x2)
  {
    checksum += b[x];
    checksum += x;

    if(x >= (l - 2))
      break;
  }

  for(x = (l >> 1); x < (l >> 1) + 0x7ffc; x += 0x2)
  {
    checksum += b[x];
    checksum += x;

    if(x >= (l - 2))
      break;
  }

  return checksum;
}

int scan_file(char *ff)
{
  FILE *f = fopen(ff, "rb");
  if(!f)
    return FALSE;

  int x = fseek(f, 0, SEEK_END);
  if(x)
  {
    fclose(f);
    return FALSE;
  }

  x = ftell(f);
  fseek(f, 0, SEEK_SET);

  char *b = new char[x];
  fread(b, sizeof(unsigned char), x, f);
  fclose(f);

  char first_level[256];
  char second_level[256];
  char size[256];

  memset(first_level, '\x0', sizeof(first_level));
  memset(second_level, '\x0', sizeof(second_level));
  memset(size, '\x0', sizeof(size));

  sprintf(size, "%d", x);
  sprintf(first_level, "%d%d", compute_first_level_fingerprint(b), x);
  sprintf(second_level, "%d%d%d%d", compute_second_level_fingerprint(b, x), 
(unsigned char) b[x >> 1], (unsigned char) b[x - 4], x);

  for(AV_ENTRY *av = pos_head; av; av = av->next)
  {
    if(strcmp(size, av->length))
      continue;

    if(strcmp(first_level, av->first_level))
      continue;

    if(strcmp(second_level, av->second_level))
      continue;

    printf("[%d]->[%s]->[%s]", av->family_ref, first_level, second_level);

    delete b;
    return av->family_ref;
  }

  delete b;
  return 0;
}

[signatures.cpp]

---------------------------------------------------------------------------