#!/usr/bin/env python3 #-*- encoding: Utf-8 -*- from typing import Dict, Union, Sequence, Set, Tuple, List from re import search, findall, finditer, DOTALL from collections import Counter from argparse import Namespace from enum import IntEnum from io import BytesIO from time import time import logging """ Guess the architecture of a given binary. For this, scan it for simple function prologues. Inspiration: https://github.com/ReFirmLabs/binwalk/blob/master/src/binwalk/magic/binarch Also, return a sequence of the spacing in bytes between each detected function prologue, so that it can be matched with function symbols from the kallsys table and the base address at the offset 0 of the binary can be guessed. """ class ArchitectureGuessError(Exception): pass class ArchitectureName(IntEnum): mipsle = 1 mipsbe = 2 mips64le = 3 mips64be = 4 x86 = 5 x86_64 = 6 powerpcbe = 7 powerpcle = 8 armle = 9 armbe = 10 aarch64 = 11 mips16e = 12 superhle = 13 superhbe = 14 sparc = 15 arcompact = 16 # Prologues taken from the binwalk file linked above architecture_to_prologue_regex : Dict[ArchitectureName, bytes] = { ArchitectureName.mipsle: br'.\xFF\xBD\x27..[\xA0-\xBF]\xAF', ArchitectureName.mipsbe: br'\x27\xBD\xFF.\xAF[\xA0-\xBF]..', ArchitectureName.mips64le: br'.\xFF\xBD\x67..[\xA0-\xBF]\xFF', ArchitectureName.mips64be: br'\x67\xBD\xFF.\xFF[\xA0-\xBF]..', ArchitectureName.x86: br'\x55\x89\xE5(?:\x83\xEC|\x57\x56)', ArchitectureName.x86_64: br'(?:\xe8....)?\x55\x48\x89\xE5', # Assume that a "call __fentry__" instruction may be present at the beginning of the prologue in recent x86_64 kernels ArchitectureName.powerpcbe: br'\x7C\x08\x02\xA6', ArchitectureName.powerpcle: br'\xA6\x02\x08\x7C', ArchitectureName.armbe: br'(?:\xE1\xA0\xC0\x0D)?\xE9\x2D..(?:[\xE0-\xEF]...){2}', ArchitectureName.armle: br'(?:\x0D\xC0\xA0\xE1)?..\x2D\xE9(?:...[\xE0-\xEF]){2}', ArchitectureName.mips16e: br'\xf0\x08\x64.\x01.', ArchitectureName.superhle: br'\xF6\x69\x0B\x00\xF6\x68', # This is an epilogue ArchitectureName.superhbe: br'\x69\xF6\x00\x0B\x68\xF6', # This is an epilogue ArchitectureName.aarch64: br'\xc0\x03\x5f\xd6', # This is an epilogue ArchitectureName.sparc: br'\x81\xC7\xE0\x08\x81\xE8', # This is an epilogue ArchitectureName.arcompact: b'\xF1\xC0.\x1C\x48[\xB0-\xBF]' # push_s blink; st.a r??, [sp, -??] } # From https://github.com/torvalds/linux/blob/master/include/uapi/linux/elf-em.h # These constants define the various ELF target machines EM_NONE = 0 EM_M32 = 1 EM_SPARC = 2 EM_386 = 3 EM_68K = 4 EM_88K = 5 EM_486 = 6 # Perhaps disused EM_860 = 7 EM_MIPS = 8 # MIPS R3000 (officially, big-endian only) # Next two are historical and binaries and # modules of these types will be rejected by # Linux. EM_MIPS_RS3_LE = 10 # MIPS R3000 little-endian EM_MIPS_RS4_BE = 10 # MIPS R4000 big-endian EM_PARISC = 15 # HPPA EM_SPARC32PLUS = 18 # Sun's "v8plus" EM_PPC = 20 # PowerPC EM_PPC64 = 21 # PowerPC64 EM_SPU = 23 # Cell BE SPU EM_ARM = 40 # ARM 32 bit EM_SH = 42 # SuperH EM_SPARCV9 = 43 # SPARC v9 64-bit EM_H8_300 = 46 # Renesas H8/300 EM_IA_64 = 50 # HP/Intel IA-64 EM_X86_64 = 62 # AMD x86-64 EM_S390 = 22 # IBM S/390 EM_CRIS = 76 # Axis Communications 32-bit embedded processor EM_M32R = 88 # Renesas M32R EM_MN10300 = 89 # Panasonic/MEI MN10300, AM33 EM_OPENRISC = 92 # OpenRISC 32-bit embedded processor EM_ARCOMPACT = 93 # ARCompact processor EM_XTENSA = 94 # Tensilica Xtensa Architecture EM_BLACKFIN = 106 # ADI Blackfin Processor EM_UNICORE = 110 # UniCore-32 EM_ALTERA_NIOS2 = 113 # Altera Nios II soft-core processor EM_TI_C6000 = 140 # TI C6X DSPs EM_HEXAGON = 164 # QUALCOMM Hexagon EM_NDS32 = 167 # Andes Technology compact code size embedded RISC processor family EM_AARCH64 = 183 # ARM 64 bit EM_TILEPRO = 188 # Tilera TILEPro EM_MICROBLAZE = 189 # Xilinx MicroBlaze EM_TILEGX = 191 # Tilera TILE-Gx EM_ARCV2 = 195 # ARCv2 Cores EM_RISCV = 243 # RISC-V EM_BPF = 247 # Linux BPF - in-kernel virtual machine EM_CSKY = 252 # C-SKY EM_FRV = 0x5441 # Fujitsu FR-V # This is an interim value that we will use until the committee comes # up with a final number. EM_ALPHA = 0x9026 # Bogus old m32r magic number, used by old tools. EM_CYGNUS_M32R = 0x9041 # This is the old interim value for S/390 architecture EM_S390_OLD = 0xA390 # Also Panasonic/MEI MN10300, AM33 EM_CYGNUS_MN10300 = 0xbeef architecture_name_to_elf_machine_and_is64bits_and_isbigendian : Dict[ArchitectureName, Tuple[int, bool, bool]] = { ArchitectureName.mipsle: (EM_MIPS, False, False), ArchitectureName.mipsbe: (EM_MIPS, False, True), ArchitectureName.mips64le: (EM_MIPS, True, False), ArchitectureName.mips64be: (EM_MIPS, True, True), ArchitectureName.x86: (EM_386, False, False), ArchitectureName.x86_64: (EM_X86_64, True, False), ArchitectureName.powerpcbe: (EM_PPC, False, True), ArchitectureName.powerpcle: (EM_PPC, False, False), ArchitectureName.armbe: (EM_ARM, False, True), ArchitectureName.armle: (EM_ARM, False, False), ArchitectureName.mips16e: (EM_MIPS, False, True), ArchitectureName.superhle: (EM_SH, False, False), ArchitectureName.superhbe: (EM_SH, False, True), ArchitectureName.aarch64: (EM_AARCH64, True, False), ArchitectureName.sparc: (EM_SPARC, False, True), ArchitectureName.arcompact: (EM_ARCOMPACT, False, False), } """ Guess the architecture based on special knowledge, like custom signatures or binary format """ def guess_architecture_special(binary : bytes) -> ArchitectureName: if binary[:2] == b'MZ': # Maybe UEFI boot stub ? if binary[0x38:0x3C] == b'ARMd': return ArchitectureName.aarch64 return None """ Guess the architecture based on common patterns """ def guess_architecture_common(binary : bytes) -> ArchitectureName: architecture_to_number_of_prologues : Dict[ArchitectureName, int] = Counter() for architecture, prologue in architecture_to_prologue_regex.items(): architecture_to_number_of_prologues[architecture] = len(findall(prologue, binary, flags = DOTALL)) best_architecture_guess, number_of_prologues = architecture_to_number_of_prologues.most_common(1)[0] return None if number_of_prologues < 100 else best_architecture_guess """ Main architecture guess function """ def guess_architecture(binary : bytes) -> ArchitectureName: begin_time = time() architecture_guess = guess_architecture_special(binary) if not architecture_guess: architecture_guess = guess_architecture_common(binary) if not architecture_guess: raise ArchitectureGuessError('The architecture could not be guessed successfully') logging.info('[+] Guessed architecture: %s successfully in %.2f seconds' % (architecture_guess.name, time() - begin_time)) return architecture_guess