Friday, February 26, 2010

Zeroing in on an AV signature to bypass detection

I was watching some InfinityExists videos yesterday talking about bypassing AV detection by modifying the executable itself. I thought of writing an automated file chopper which will zero-in on the part which AV is detecting to be malicious. That way I can directly analyze the offset and do not have to manually find it ( for the most part ).

Script has 2 phases:

In Phase 1 it recursively chops up the files into smaller chunks divisible by 2 and figures out minimal set of bytes required to trigger AV.

For example:

Original File Size: 600
Derived File Sizes: 600->300->150....
Every chunk is scanned with ClamaV via Ruby library and the part that is "clean" is discarded.

Phase 2 is taking the last successful AV detected file chunk and makes it even smaller by shrinking with predefined increment.

File Size ( phase 1 ): 150
Increment: 10
Derivative File Sizes: 150->140->130 ...

This way we can get closer to the actual piece triggering the AV.


Sample Run with original file size of 37888 bytes and phase 2 increment of 100 bytes.


dimas@appdev1:~/Code/vira$ ./chopfile.rb -f /tmp/msf3/data/templates/template.exe
Positive: /tmp/X_0-18944: Worm.Palevo-1
Positive: /tmp/X_0-9472: Worm.Palevo-1
Positive: /tmp/X_0-4736: Worm.Palevo-1
Positive: /tmp/X_0-2368: Worm.Palevo-1

======== G R A P H ===========
|X_0-18944
|-X_0-9472
|--X_0-4736
|---X_0-2368
Positive: /tmp/XA_0-2268: Worm.Palevo-1
Positive: /tmp/XA_0-2168: Worm.Palevo-1
Positive: /tmp/XA_0-2068: Worm.Palevo-1
Positive: /tmp/XA_0-1968: Worm.Palevo-1
Positive: /tmp/XA_0-1868: Worm.Palevo-1
Positive: /tmp/XA_0-1768: Worm.Palevo-1
Positive: /tmp/XA_0-1668: Worm.Palevo-1
Positive: /tmp/XA_0-1568: Worm.Palevo-1
End of 100 chain

======== G R A P H ===========
|XA_0-2268
|-XA_0-2168
|--XA_0-2068
|---XA_0-1968
|----XA_0-1868
|-----XA_0-1768
|------XA_0-1668
|-------XA_0-1568



Some quick and dirty Ruby code.



#!/usr/bin/env ruby



require 'optparse'

require 'clamav'





def bin_chop(pos_A,pos_Z,fp)



        payload=""



        splitmark=(pos_Z-pos_A)/2



        if splitmark <= 1

            return

        end







        fi=File.open(fp,"rb:binary")    





        fi.sysseek(pos_A, IO::SEEK_SET)

        fi.sysread(splitmark,payload)



        fc1="/tmp/X_" + pos_A.to_s + "-" + splitmark.to_s



        

        fo=File.open(fc1, "wb:binary")

        fo.syswrite(payload)

        fo.close



        cv=ClamAV.new(ClamAV::CL_SCAN_STDOPT)

        result=cv.scanfile(fc1)



        if ( result != 0 )

            puts "Positive: #{fc1}: #{result}"

            cv=nil

            fi.close

            bin_chop(0,File.size(fc1),fc1);

        else

        

            # Negative - Delete!

            File.delete(fc1)



            payload=""



            fi.sysread((pos_Z-splitmark),payload)

            fi.close



            fc2="/tmp/X_" + splitmark.to_s + "-" + pos_Z.to_s



            fo=File.open(fc2, "wb:binary")

            fo.syswrite(payload)

            fo.close



            cv=ClamAV.new(ClamAV::CL_SCAN_STDOPT)

            result=cv.scanfile(fc2)



            if ( result != 0 )

                puts "Positive: #{fc2}: #{result}"

                cv=nil

                bin_chop(0,File.size(fc2),fc2);

            else

                # Negative - Delete!

                File.delete(fc2)

                

            end

        end



    

end

def incr_chop(pos_A,pos_Z,fp,increment)

        payload=""



        splitmark=(pos_Z-increment)



        fi=File.open(fp,"rb:binary")    





        fi.sysseek(pos_A, IO::SEEK_SET)

        fi.sysread(splitmark,payload)



        fc1="/tmp/XA_" + pos_A.to_s + "-" + splitmark.to_s



        

        fo=File.open(fc1, "wb:binary")

        fo.syswrite(payload)

        fo.close



        cv=ClamAV.new(ClamAV::CL_SCAN_STDOPT)

        result=cv.scanfile(fc1)



        if ( result != 0 )

            puts "Positive: #{fc1}: #{result}"

            cv=nil

            fi.close

            incr_chop(0,File.size(fc1),fc1,increment);

        else

        

            # Negative - Delete!

            puts "End of #{increment} chain"

            cv=nil

            fi.close

            File.delete(fc1)

        end





    

end





meta = Hash.new;

opts=OptionParser.new



opts.on("-f", "--filepath VAL", String) {|val| meta[:fp]=val}

rest=opts.parse(ARGV)



if ( !meta[:fp].nil? and ( File.exists?(meta[:fp]) and File.readable?(meta[:fp]) ))



    # Phase I

    bin_chop(0,File.size(meta[:fp]),meta[:fp])



    puts "\n\t======== G R A P H ===========\n"

    Dir.chdir("/tmp")

    files = Dir["X_*"]

    sorted = files.sort {|a,bFile.size(a) <=> File.size(b)}

    sorted.reverse.each_with_index do |fl,i|

        puts "\t\t|" + "-"*i + fl

    end



    # Phase II



    

    incr_chop(0,File.size(sorted.shift),sorted.shift,100)



    puts "\n\t======== G R A P H ===========\n"

    Dir.chdir("/tmp")

    files = Dir["XA_*"]

    sorted = files.sort {|a,bFile.size(a) <=> File.size(b)}

    sorted.reverse.each_with_index do |fl,i|

        puts "\t\t|" + "-"*i + fl

    end

    

else

    puts "invalid"

    puts opts.to_s

    raise Exception

end