Home > Articles > Security > Network Security

  • Print
  • + Share This
This chapter is from the book

Automatic, Bulk Auditing for Vulnerabilities

Clearly, reverse engineering is a time-consuming task and a process that does not scale well. There are many cases when reverse engineering for security bugs would be valuable, but there isn't nearly enough time to analyze each and every component of a software system the way we have done in the previous section. One possibility, however, is automated analysis. IDA provides a platform for adding your own analysis algorithms. By writing a special script for IDA, we can automate some of the tasks required for finding a vulnerability. Here, we provide an example of strict white box analysis. [14]

Harking back to a previous example, let's assume we want to find other bugs that may involve the (mis)use of wcsncat. We can use a utility called dumpbin under Windows to show which calls are imported by an executable:

dumpbin /imports target.exe

To bulk audit all the executables on a system, we can write a small Perl script. First create a list of executables to analyze. Use the dir command as follows:

dir /B /S c:\winnt\*.exe > files.txt

This creates a large output file of all the executable files under the WINNT directory. The Perl script will then call dumpbin on each file and will analyze the results to determine whether wcsncat is being used:

open(FILENAMES, "files.txt");
while (<FILENAMES>)
{
    chop($_);
    my $filename = $_;
    $command = "dumpbin /imports $_ > dumpfile.txt";
    #print "trying $command";
    system($command);

    open(DUMPFILE, "dumpfile.txt");
    while (<DUMPFILE>)
    {
        if(m/wcsncat/gi)
        {
            print "$filename: $_";
        }
    }
    close(DUMPFILE);
}
close(FILENAMES);

Running this script on a system in the lab produces the following output:

C:\temp>perl scan.pl
c:\winnt\winrep.exe:    7802833F  2E4 wcsncat
c:\winnt\INF\UNREGMP2.EXE:    78028EDD  2E4 wcsncat
c:\winnt\SPEECH\VCMD.EXE:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\dfrgfat.exe:    77F8F2A0  499 wcsncat
c:\winnt\SYSTEM32\dfrgntfs.exe:    77F8F2A0  499 wcsncat
c:\winnt\SYSTEM32\IESHWIZ.EXE:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\NET1.EXE:    77F8E8A2  491 wcsncat
c:\winnt\SYSTEM32\NTBACKUP.EXE:    77F8F2A0  499 wcsncat
c:\winnt\SYSTEM32\WINLOGON.EXE:              2E4 wcsncat

We can see that several of the programs under Windows NT are using wcsncat. With a little time we can audit these files to determine whether they suffer from similar problems to the example program we show earlier. We could also examine DLLs using this method and generate a much larger list:

C:\temp>dir /B /S c:\winnt\*.dll > files.txt

C:\temp>perl scan.pl

c:\winnt\SYSTEM32\AAAAMON.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\adsldpc.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\avtapi.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\AVWAV.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\BR549.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\CMPROPS.DLL:    78028EDD  2E7 wcsncat
c:\winnt\SYSTEM32\DFRGUI.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\dhcpmon.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\dmloader.dll:              2FB wcsncat
c:\winnt\SYSTEM32\EVENTLOG.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\GDI32.DLL:    77F8F2A0  499 wcsncat
c:\winnt\SYSTEM32\IASSAM.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\IFMON.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\LOCALSPL.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\LSASRV.DLL:           2E4 wcsncat
c:\winnt\SYSTEM32\mpr.dll:    77F8F2A0  499 wcsncat
c:\winnt\SYSTEM32\MSGINA.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\msjetoledb40.dll:    7802833F  2E2 wcsncat
c:\winnt\SYSTEM32\MYCOMPUT.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\netcfgx.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\ntdsa.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\ntdsapi.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\ntdsetup.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\ntmssvc.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\NWWKS.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\ODBC32.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\odbccp32.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\odbcjt32.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\OIPRT400.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\PRINTUI.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\rastls.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\rend.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\RESUTILS.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\SAMSRV.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\scecli.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\scesrv.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\sqlsrv32.dll:            2E2 wcsncat
c:\winnt\SYSTEM32\STI_CI.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\USER32.DLL:    77F8F2A0  499 wcsncat
c:\winnt\SYSTEM32\WIN32SPL.DLL:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\WINSMON.DLL:    78028EDD  2E4 wcsncat
c:\winnt\SYSTEM32\dllcache\dmloader.dll:              2FB wcsncat
c:\winnt\SYSTEM32\SETUP\msmqocm.dll:    7802833F  2E4 wcsncat
c:\winnt\SYSTEM32\WBEM\cimwin32.dll:    7802833F  2E7 wcsncat
c:\winnt\SYSTEM32\WBEM\WBEMCNTL.DLL:    78028EDD  2E7 wcsncat

Batch Analysis with IDA-Pro

We already illustrated how to write a plugin module for IDA. IDA also supports a scripting language. The scripts are called IDC scripts and can sometimes be easier than using a plugin. We can perform a batch analysis with the IDA-Pro tool by using an IDC script as follows:

c:\ida\idaw -Sbatch_hunt.idc -A -c c:\winnt\notepad.exe

with the very basic IDC script file shown here:

#include <idc.idc>
//----------------------------------------------------------------
static main(void) {
 Batch(1);
 /* will hang if existing database file */
 Wait();
 Exit(0);
}

As another example, consider batch analysis for sprintf calls. The Perl script calls IDA using the command line:

open(FILENAMES, "files.txt");
while (<FILENAMES>)
{
    chop($_);
    my $filename = $_;
    $command = "dumpbin /imports $_ > dumpfile.txt";
    #print "trying $command";

    system($command);

    open(DUMPFILE, "dumpfile.txt");
    while (<DUMPFILE>)
    {
        if(m/sprintf/gi)
        {
            print "$filename: $_\n";
            system("c:\\ida\\idaw -Sbulk_audit_sprintf.idc -A -c $filename");
        }
    }
    close(DUMPFILE);
}
close(FILENAMES);

We use the script bulk_audit_sprintf.idc:

//
//   This example shows how to use GetOperandValue() function.
//

#include <idc.idc>

/* this routine is hard coded to understand sprintf calls */

static hunt_address(    eb,         /* the address of this call */
                        param_count,  /* the number of parameters for this call */
                        ec,         /* maximum number of instructions to backtrace */
                        output_file
                        )
{
    auto ep; /* placeholder */
    auto k;
    auto kill_frame_sz;
    auto comment_string;

    k = GetMnem(eb);

    if(strstr(k, "call") != 0)
    {
        Message("Invalid starting point\n");
        return;
    }

    /* backtrace code */
    while( eb=FindCode(eb, 0) )
    {
        auto j;
        j = GetMnem(eb);

        /* exit early if we run into a retn code */
        if(strstr(j, "retn") == 0) return;

        /* push means argument to sprintf call */
        if(strstr(j, "push") == 0)
        {
            auto my_reg;
            auto max_backtrace;

            ep = eb; /* save our place */

            /* work back to find out the parameter */
            my_reg = GetOpnd(eb, 0);
            fprintf(output_file, "push number %d, %s\n", param_count, my_reg);

            max_backtrace = 10; /* don't backtrace more than 10 steps */
            while(1)
            {
                auto x;
                auto y;

                eb = FindCode(eb, 0); /* backwards */
                x = GetOpnd(eb,0);
                if ( x != -1 )
                {
                    if(strstr(x, my_reg) == 0)
                    {
                        auto my_src;
                        my_src = GetOpnd(eb, 1);

                        /* param 3 is the target buffer */
                        if(3 == param_count)
                        {
                            auto my_loc;
                            auto my_sz;
                            auto frame_sz;

                            my_loc = PrevFunction(eb);

                            fprintf(output_file, "detected
                                subroutine 0x%x\n", my_loc);

                            my_sz = GetFrame(my_loc);
                            fprintf(output_file, "got frame
                            %x\n", my_sz);

                            frame_sz = GetFrameSize(my_loc);
                            fprintf(output_file, "got frame size
                                %d\n", frame_sz);

                            kill_frame_sz =
                                GetFrameLvarSize(my_loc);
                            fprintf(output_file, "got frame lvar
                                size %d\n", kill_frame_sz);

                            my_sz = GetFrameArgsSize(my_loc);
                            fprintf(output_file, "got frame args
                                size %d\n", my_sz);

                            /* this is the target buffer */
                            fprintf(output_file, "%s is the target buffer,
                                in frame size %d bytes\n",
                                my_src, frame_sz);
                        }

                        /* param 1 is the source buffer */
                        if(1 == param_count)
                        {
                            fprintf(output_file, "%s is the source buffer\n",
                                my_src);
                            if(-1 != strstr(my_src, "arg"))
                            {
                                fprintf(output_file, "%s is an argument that will
                                    overflow if larger than %d bytes!\n",
                                    my_src, kill_frame_sz);
                            }
                        }
                        break;
                    }
                }
                max_backtrace--;
                if(max_backtrace == 0)break;
            }
            eb = ep; /* reset to where we started and continue for next parameter */
            param_count--;
            if(0 == param_count)
            {
                    fprintf(output_file, "Exhausted all  parameters\n");
                    return;
            }
        }
        if(ec-- == 0)break; /* max backtrace looking for parameters */
    }
}

static main()
{
    auto ea;
    auto eb;
    auto last_address;
    auto output_file;
    auto file_name;

    /* turn off all dialog boxes for batch processing */
    Batch(0);
    /* wait for autoanalysis to complete */
    Wait();

    ea = MinEA();
    eb = MaxEA();

    output_file = fopen("report_out.txt", "a");
    file_name = GetIdbPath();

    fprintf(output_file, "----------------------------------------------\nFilename: %s\n",
ccc.gif file_name);
   fprintf(output_file, "HUNTING FROM %x TO %x
   ccc.gif\n----------------------------------------------\n", ea, eb);
   while(ea != BADADDR)
   {
   auto my_code;
   
   last_address=ea;
   //Message("checking %x\n", ea);
   my_code = GetMnem(ea);
   if(0 == strstr(my_code, "call")){
   auto my_op;
   my_op = GetOpnd(ea, 0);
   if(-1 != strstr(my_op, "sprintf")){
   fprintf(output_file, "Found sprintf call at 0x%x -
   checking\n", ea);
   
   /* 3 parameters, max backtrace of 20 */
   hunt_address(ea, 3, 20, output_file);
   fprintf(output_file, "------------------------------------
   ----------\n");
   }
   }
   ea = FindCode(ea, 1);
   }
   fprintf(output_file, "FINISHED at address 0x%x
   ccc.gif\n----------------------------------------------\n", last_address);
   fclose(output_file);
   Exit(0);
   }
   

The output produced by this simple batch file is placed in a file called report_out.txt for later analysis. The file looks something like this:

----------------------------------------------
Filename: C:\reversing\of1.idb
HUNTING FROM 401000 TO 404000
----------------------------------------------
Found sprintf call at 0x401012 - checking
push number 3, ecx
detected subroutine 0x401000

got frame ff00004f
got frame size 32
got frame lvar size 28
got frame args size 0
[esp+1Ch+var_1C] is the target buffer, in frame size 32 bytes
push number 2, offset unk_403010
push number 1, eax
[esp+arg_0] is the source buffer
[esp+arg_0] is an argument that will overflow if larger than 28 bytes!
Exhausted all parameters
----------------------------------------------
Found sprintf call at 0x401035 - checking
push number 3, ecx
detected subroutine 0x401020
got frame ff000052
got frame size 292
got frame lvar size 288
got frame args size 0
[esp+120h+var_120] is the target buffer, in frame size 292 bytes
push number 2, offset aSHh
push number 1, eax
[esp+arg_0] is the source buffer
[esp+arg_0] is an argument that will overflow if larger than 288 bytes!
Exhausted all parameters
----------------------------------------------
FINISHED at address 0x4011b6
----------------------------------------------
----------------------------------------------
Filename: C:\winnt\MSAGENT\AGENTCTL.idb
HUNTING FROM 74c61000 TO 74c7a460
----------------------------------------------
Found sprintf call at 0x74c6e3b6 - checking
push number 3, eax
detected subroutine 0x74c6e2f9
got frame ff000eca
got frame size 568
got frame lvar size 552
got frame args size 8
[ebp+var_218] is the target buffer, in frame size 568 bytes
push number 2, offset aD__2d
push number 1, eax
[ebp+var_21C] is the source buffer
Exhausted all parameters
----------------------------------------------

Searching the function calls, we see a suspect call to lstrcpy(). Analyzing lots of code automatically is a common trick to look for good starting places, and it turns out to be very useful in practice.

  • + Share This
  • 🔖 Save To Your Account