"""Bounded static PE call and pointer references; never attaches to or runs a game."""
import argparse
import bisect
import json
import re
import struct
from pathlib import Path
import pefile
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('image', type=Path)
parser.add_argument('targets', nargs='+', type=lambda value: int(value, 0))
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
pe = pefile.PE(str(args.image), fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXCEPTION']])
entries = sorted((e.struct.BeginAddress, e.struct.EndAddress, e.struct.UnwindData)
for e in pe.DIRECTORY_ENTRY_EXCEPTION)
starts = [e[0] for e in entries]
def parent(rva):
i = bisect.bisect_right(starts, rva) - 1
if i < 0 or not entries[i][0] <= rva < entries[i][1]:
return None
entry = entries[i]
for _ in range(16):
begin, end, unwind = entry
header = pe.get_data(unwind, 4)
if not header[0] >> 3 & 4:
return hex(begin)
entry = struct.unpack('<III', pe.get_data(unwind + 4 + ((header[2] + 1) & ~1) * 2, 12))
raise ValueError('Unwind chain exceeds its bound')
refs = []
for section in pe.sections:
data = section.get_data()
if section.Characteristics & 0x20000000:
for match in re.finditer(rb'[\x40-\x4f]?[\x66\xf2\xf3]?\x0f[\x10\x28\x6f][\x05\x0d\x15\x1d\x25\x2d\x35\x3d]....', data, re.S):
rva=section.VirtualAddress+match.start();target=rva+len(match.group())+struct.unpack_from('<i',match.group(),len(match.group())-4)[0]
if target in args.targets:refs.append(dict(kind='rip-simd-load',target=hex(target),rva=hex(rva),parent=parent(rva)))
for match in re.finditer(rb'[\x40-\x4f]?[\x8d\x8b][\x05\x0d\x15\x1d\x25\x2d\x35\x3d]....', data, re.S):
rva = section.VirtualAddress + match.start()
instruction = match.group()
target = rva + len(instruction) + struct.unpack_from('<i', instruction, len(instruction)-4)[0]
if target in args.targets:
refs.append(dict(kind='rip-address' if instruction[-6] == 0x8d else 'rip-load',
target=hex(target), rva=hex(rva), parent=parent(rva)))
for match in re.finditer(rb'[\xe8\xe9]....', data, re.S):
rva = section.VirtualAddress + match.start()
target = rva + 5 + struct.unpack_from('<i', match.group(), 1)[0]
if target in args.targets:
refs.append(dict(kind='call' if match.group()[0] == 0xe8 else 'jump',
target=hex(target), rva=hex(rva), parent=parent(rva)))
else:
for target in args.targets:
pointer = struct.pack('<Q', pe.OPTIONAL_HEADER.ImageBase + target)
at = 0
while (at := data.find(pointer, at)) >= 0:
refs.append(dict(kind='pointer', target=hex(target), rva=hex(section.VirtualAddress + at)))
at += len(pointer)
result = dict(image=args.image.name, references=refs, scope='Offline static evidence only')
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, indent=2), encoding='utf-8')
print(json.dumps(result, indent=2))
if __name__ == '__main__':
main()
"""Bounded static PE call and pointer references; never attaches to or runs a game."""
import argparse
import bisect
import json
import re
import struct
from pathlib import Path
import pefile
def main():
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument('image', type=Path)
parser.add_argument('targets', nargs='+', type=lambda value: int(value, 0))
parser.add_argument('--output', type=Path, required=True)
args = parser.parse_args()
pe = pefile.PE(str(args.image), fast_load=True)
pe.parse_data_directories(directories=[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_EXCEPTION']])
entries = sorted((e.struct.BeginAddress, e.struct.EndAddress, e.struct.UnwindData)
for e in pe.DIRECTORY_ENTRY_EXCEPTION)
starts = [e[0] for e in entries]
def parent(rva):
i = bisect.bisect_right(starts, rva) - 1
if i < 0 or not entries[i][0] <= rva < entries[i][1]:
return None
entry = entries[i]
for _ in range(16):
begin, end, unwind = entry
header = pe.get_data(unwind, 4)
if not header[0] >> 3 & 4:
return hex(begin)
entry = struct.unpack('<III', pe.get_data(unwind + 4 + ((header[2] + 1) & ~1) * 2, 12))
raise ValueError('Unwind chain exceeds its bound')
refs = []
for section in pe.sections:
data = section.get_data()
if section.Characteristics & 0x20000000:
for match in re.finditer(rb'[\x40-\x4f]?[\x66\xf2\xf3]?\x0f[\x10\x28\x6f][\x05\x0d\x15\x1d\x25\x2d\x35\x3d]....', data, re.S):
rva=section.VirtualAddress+match.start();target=rva+len(match.group())+struct.unpack_from('<i',match.group(),len(match.group())-4)[0]
if target in args.targets:refs.append(dict(kind='rip-simd-load',target=hex(target),rva=hex(rva),parent=parent(rva)))
for match in re.finditer(rb'[\x40-\x4f]?[\x8d\x8b][\x05\x0d\x15\x1d\x25\x2d\x35\x3d]....', data, re.S):
rva = section.VirtualAddress + match.start()
instruction = match.group()
target = rva + len(instruction) + struct.unpack_from('<i', instruction, len(instruction)-4)[0]
if target in args.targets:
refs.append(dict(kind='rip-address' if instruction[-6] == 0x8d else 'rip-load',
target=hex(target), rva=hex(rva), parent=parent(rva)))
for match in re.finditer(rb'[\xe8\xe9]....', data, re.S):
rva = section.VirtualAddress + match.start()
target = rva + 5 + struct.unpack_from('<i', match.group(), 1)[0]
if target in args.targets:
refs.append(dict(kind='call' if match.group()[0] == 0xe8 else 'jump',
target=hex(target), rva=hex(rva), parent=parent(rva)))
else:
for target in args.targets:
pointer = struct.pack('<Q', pe.OPTIONAL_HEADER.ImageBase + target)
at = 0
while (at := data.find(pointer, at)) >= 0:
refs.append(dict(kind='pointer', target=hex(target), rva=hex(section.VirtualAddress + at)))
at += len(pointer)
result = dict(image=args.image.name, references=refs, scope='Offline static evidence only')
args.output.parent.mkdir(parents=True, exist_ok=True)
args.output.write_text(json.dumps(result, indent=2), encoding='utf-8')
print(json.dumps(result, indent=2))
if __name__ == '__main__':
main()