Skip to content

Instantly share code, notes, and snippets.

@joshuashaffer
Created August 16, 2023 22:54
Show Gist options
  • Save joshuashaffer/1d794ff8a235f416d580b537f4fdd0b8 to your computer and use it in GitHub Desktop.
Save joshuashaffer/1d794ff8a235f416d580b537f4fdd0b8 to your computer and use it in GitHub Desktop.
from pathlib import Path
import r2pipe
# Instructions that are not available on 8086/8088:
_unsupported_instructions = [
'enter',
'leave',
'popa',
'pusha',
'lgdt',
'lidt',
'lmsw',
'clts',
'sgdt',
'sidt',
'str',
'loadall',
'storeall',
'lodsd',
'stosd',
'cmpsd',
'scasd',
'insd',
'outsd',
'iretd',
'jecxz',
'pushad',
'popad',
'bswap',
'cmpxchg8b',
'cpuid',
]
_unsupported_registers = [
'esp',
'ebp',
'eip',
'eflags',
'fs',
'gs',
'cr0',
'cr1',
'cr2',
'cr3',
'cr4',
'cr5',
'cr6',
'cr7',
'rax',
'rbx',
'rcx',
'rdx',
'rsi',
'rdi',
'r8',
'r9',
'r10',
'r11',
'r12',
'r13',
'r14',
'r15',
'xmm0',
'xmm1',
'xmm2',
'xmm3',
'xmm4',
'xmm5',
'xmm6',
'xmm7',
'xmm8',
'xmm9',
'xmm10',
'xmm11',
'xmm12',
'xmm13',
'xmm14',
'xmm15',
]
_video_modes = {
0x00: 'CGA',
0x01: 'CGA',
0x02: 'CGA',
0x03: 'CGA',
0x04: 'CGA',
0x05: 'CGA',
0x06: 'CGA',
0x07: 'MDA',
0x08: 'PCjr',
0x09: 'PCjr',
0x0A: 'PCjr',
0x0B: 'EGA',
0x0C: 'EGA',
0x0D: 'EGA',
0x0E: 'EGA',
0x0F: 'EGA',
0x10: 'EGA',
0x11: 'VGA',
0x12: 'VGA',
0x13: 'VGA',
}
def is_8088_binary(exe_path: Path) -> bool:
"""
Check if the binary is 8088 compatible
:param exe_path: Path to the binary
:return: True if the binary is 100% 8088 compatible
"""
r2 = r2pipe.open(str(exe_path))
r2.cmd('aaaa')
functions = r2.cmdj('aflj')
for function in functions:
offset = function.get('offset', -1)
if offset == -1:
continue
disassembly = r2.cmdj(f'pdfj @ {offset}')
for block in disassembly.get('ops', []):
disassembled_instruction = block.get('disasm', '').lower()
if 'invalid' in disassembled_instruction:
continue
opcode, *args = disassembled_instruction.split()
if opcode in _unsupported_instructions:
return False
# check that only supported registers are used
for arg in args:
if arg in _unsupported_registers:
return False
return True
def maybe_cga(exe_path: Path) -> bool:
"""
Check if the binary uses CGA graphics
:param exe_path:
:return:
"""
r2 = r2pipe.open(str(exe_path))
r2.cmd('aaaa')
functions = r2.cmdj('aflj')
count_set_video_modes = 0
valid_mode_seen = False
valid_modes = {'MDA', 'CGA'}
for function in functions:
offset = function.get('offset', -1)
if offset == -1:
continue
disassembly = r2.cmdj(f'pdfj @ {offset}')
last_ah = -1
last_al = -1
for block in disassembly.get('ops', []):
disassembled_instruction = block.get('disasm', '').lower()
if 'invalid' in disassembled_instruction:
continue
instruction_bytes = bytes.fromhex(block.get('bytes', '90'))
match tuple(instruction_bytes):
case [0xCD, 0x10]:
# int 10h
if last_ah != 0:
continue
count_set_video_modes += 1
mode_description = _video_modes.get(last_al, 'unknown')
if mode_description in valid_modes:
valid_mode_seen = True
case [0xB4, *rest]:
# mov ah, imm8
last_ah = rest[0]
case [0xB0, *rest]:
# mov al, imm8
last_al = rest[0]
case [0xB8, *rest]:
# mov ax, imm16
last_ah = rest[1]
last_al = rest[0]
case [0x31, 0xC0]:
# xor ax, ax
last_ah = 0
last_al = 0
case [0x32, 0xE4]:
# xor ah, ah
last_ah = 0
case [0x30, 0xC0]:
# xor al, al
last_al = 0
# Either a video mode is never set, or we see a valid mode.
return count_set_video_modes == 0 or valid_mode_seen
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment