Detecting malware using Windows Auditing events

2 people like this post.

This post1 explains how to use nmap and smb-check-vulns to scan a network in search of Conficker infected hosts. I thought that the whole Conficker case was over, but hopefully some of the measures I took to deal with it almost an year ago, will still be relevant to other kinds of malware. And, also, the method I’ll show you here differs from the nmap one in that the latter is active, whereas mine is passive. Actively probing an host for vulnerabilities could be very very much alike “exploiting” it as malware does, and have similar effects. For instance, a service/process could crash, making it not always advisable to run active scans on your servers subnet. Passive analysis, on the other hand, unobtrusively collects clues about who’s misbehaving.

During the Conficker/Downadup outburst, we observed that:

  • Antivirus wasn’t always able to detect/stop it.
  • The virus was copying files in known directories (C:\WINDOWS\SYSTEM32) on about to be infected machines.
  • Security patched hosts were still subject to the remote malicious file copying routine. The copy could either succeed or fail, depending on which permissions had the user that “runs” the virus. The copy in itself doesn’t pose any security concern. Even if no A/V is active on the destination host, but virus exploitable flaws have been patched, malware won’t be able to activate itself. Otherwise, the A/V would remove suspect files as soon as they are caught, without interfering with our detection purposes.

This behaviour makes it possible to use a “honeypot” approach. The detecting server can be any production host provided that it is security patched and A/V protected. You could, as we did, choose a Domain Controller and:

  • Run Administrative ToolsDomain Controller Security Policy
  • Modify the Audit Policy, enabling tracking of successful logon events and object access. By default the OS will only log failures, but that’s not enough.
  • Object Access is activated at a file/directory level. Open up the Properties of a directory you know is accessed by the virus, click on Security, then Advanced. The Auditing tab is what you’re interested in. Set things up so that any “Create File/Write Data” attempt of Type “Success” will be logged. The semantics about how auditing settings are propagated from parent to child works in the same way as NFTS permissions.
  • From this point on, you should monitor the honeypot server’s Security Event Log. I wrote a Perl script to do it for me. It works by selecting events with ID 560 and 540, extracting their text and printing just the needed info.

Let’s look at how it’s used (the only parameter is the hostname/address of the honeypot server):

C:\loganalysis>perl ddloganalysis.pl honeypot-srv.domain.lan > ddlog.txt

Skimming through the generated log, you’ll notice the files being dropped into C:\WINDOWS\system32 (or any directory you set up for auditing), the user that actually created them and, before (time-wise), from which address the user is coming.

17/03/2009 16.26.19   560 : C:\WINDOWS\system32\onevthx.vr (Administrator)
17/03/2009 16.26.18   540 :  ( - Administrator)
17/03/2009 15.35.24   560 : C:\WINDOWS\system32\onevthx.vr (SpectrumLT)
17/03/2009 15.35.24   540 :  ( - SpectrumLT)

We successfully used the script to pinpoint the rogue hosts. Deeming it useful, here it is:


use strict;
use Win32::EventLog;
use POSIX qw ( strftime );

my @matches = (
    #'job$',   # useless, since scheduled tasks are always created by SYSTEM

die "Usage:\n$0 servername" unless $ARGV[0];

my $ev=Win32::EventLog->new('Security', $ARGV[0])
    or die "Can't open EventLog\n";
my $recs;
    or die "Can't get number of EventLog records\n";
my $base;
    or die "Can't get number of oldest EventLog record\n";

sub getts($) {
    return strftime '%d/%m/%Y %H.%M.%S', (localtime shift);

my @progress = ('-','\','|','/','-','\','|','/');

my $x = $recs-1;
my $h;
while ($x >= 0) {
        $base + $x,
        or die "Can'
t read EventLog entry #$x\n";
    print STDERR $progress[$#progress - ($x % @progress)] . "\r";
    if ($h->{Source} eq 'Security' and ($h->{EventID} == 560 or $h->{EventID} == 540)) {
        if ($h->{EventID} == 560) {
            $h->{Message} =~ /Object Name:[\t ]*(.*?)\r/gis;
            my $filename = $1;
            $h->{Message} =~ /Client User Name:[\t ]*(.*?)\r/gis;
            my $clientusername = $1;
            if ($filename) {
                if (grep { my $m = $_; $filename =~ /$m/i} @matches) {
                    printf "%s %5d : %s (%s)\n", getts($h->{TimeGenerated}), $h->{EventID}, $filename, $clientusername;
        } elsif ($h->{EventID} == 540) {
            $h->{Message} =~ /User Name:[\t ]*(.*?)\r/gis;
            my $username = $1;
            $h->{Message} =~ /Workstation Name:[\t ]*(.*?)\r/gis;
            my $workstation = $1;
            $h->{Message} =~ /Source Network Address:[\t ]*(.*?)\r/gis;
            my $addr = $1;
            printf "%s %5d : %s (%s - %s)\n", getts($h->{TimeGenerated}), $h->{EventID}, $workstation, $addr, $username
              if $workstation or $addr;

  1. In italian, sorry. Look here for an english equivalent and here for more info.