- Into the House of Logic
- Should Reverse Engineering Be Illegal?
- Reverse Engineering Tools and Concepts
- Approaches to Reverse Engineering
- Methods of the Reverser
- Writing Interactive Disassembler (IDA) Plugins
- Decompiling and Disassembling Software
- Decompilation in Practice: Reversing helpctr.exe
- Automatic, Bulk Auditing for Vulnerabilities
- Writing Your Own Cracking Tools
- Building a Basic Code Coverage Tool
- Conclusion
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", file_name); fprintf(output_file, "HUNTING FROM %x TO %x \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 \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.