#!/usr/bin/ruby # this script uses the metasm framework to patch the Dwarf Fortress # the patch initializes all newly-allocated C++ objects with 0x33 # this is useful to match uninitialized fields in known structures # should work on other binaries too require 'metasm' bin = ARGV.shift || 'Dwarf Fortress.exe' puts "loading #{bin}" pe = Metasm::PE.decode_file(bin, :nodecode_relocs) dasm = pe.disassembler dasm.load_plugin 'patch_file' # TODO reuse COFF::Header etc # structs from slipfest/pe.h dasm.parse_c < iat_bak_addr } raise 'cant find iat?' if not iat sec_write = Metasm::COFF::SECTION_CHARACTERISTIC_BITS.index('MEM_WRITE') if iat.characteristics & sec_write > 0 puts 'already patched' else iat.characteristics |= sec_write end puts 'scan for 0xCC holes' text = pe.sections.find { |sec| sec.name == '.text' } # patch instrs are max 6 bytes, + jmp = 10 (XXX use space at end of .text) # XXX check previous bytes if last 0xCC might be the last byte of a jmp? holes = dasm.pattern_scan(/\xCC{11,}/, text.virtaddr, text.virtsize) cur_addr = holes.shift raise 'no hole' if not cur_addr assemble = lambda { |src, may_jmp| # encode the source instructions sc = Metasm::Shellcode.new(dasm.cpu, cur_addr) sc.assemble(src) raw = sc.encode_string # if they fit in the code, + leave space for a jmp check_len = raw.length check_len += ((holes.first > cur_addr+129+raw.length) ? 5 : 2) if may_jmp ed = dasm.get_edata_at(cur_addr) if ed.data[ed.ptr, check_len].unpack('C*').uniq == [0xCC] # patch them in, advance cur_addr ed[ed.ptr, raw.length] = raw cur_addr += raw.length elsif may_jmp next_addr = holes.shift raise 'no hole left' if not next_addr assemble["jmp $+#{next_addr-cur_addr}", false] cur_addr = next_addr assemble[src, may_jmp] else raise 'hole too small?' end } hooked_new_addr = cur_addr puts 'store hooked new() at %X' % hooked_new_addr # whenever the program calls new(), it will call this code instead hooked_new_asm = <