-------- [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] ---------------------------------------------------------------------------