Misc Notes - tnballo/notebook GitHub Wiki

References:

1 . "Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?” by Raymond Chen

ARM's Default IVT Entries


  • ARM Interrupt Vector Table (IVT) entries are interrupt handler addresses. The table is indexed by interrupt number.

  • RTOSs use a default value of 0xEAFFFFFE for table entries. This encodes b #0 - an infinite spin loop (unconditional branch to current PC). This default allows the following error recovery sequence:

    • Unsupported interrupt is fired, indexing into IVT hits the default entry. CPU enters infinite spin loop.

    • Code to mask/block interrupts has not run, as no handler has yet been executed.

    • Watchdog timer (typically private on-chip peripheral, not external off-chip component) fires while CPU is spinning, sending RESET interrupt.

    • System is reset to a (theoretically) good state in a finite and fixed amount of time. Ideally, the unsupported interrupt won't be raised again.

  • An IVT's entries can be populated during boot, replacing most or all defaults with handler addresses.

    • See function early_trap_init() in the Linux kernel.

Hot-patching in Windows Binaries


  • The /hotpatch compiler flag ensures that every function starts with a 2 byte NOP (ex. mov edi, edi) that is preceded by an additional 5 bytes of NOP slack space (7 total bytes of NOP).

  • During normal operation a function call jumps into those 2 NOP bytes and then slides into the actual code, executing all 7 bytes of NOP on every entry would be wasteful (by comparison the 2 byte mov edi, edi is only 1 clock cycle, 1 pipe).

  • Should we need to hook (hijack and redirect) a function call, that 2 byte entry can be replaced with a jump. However only small relative jumps can be encoded in 2 bytes. Arbitrary jumps can be encoded with 5 bytes, hence the preceding padding.

  • Control flow is re-directed to a hook by patching in a small offset jump backwards (replacing the 2 byte NOP), the target of which is a patched-in jump to the hooked function address (replacement of the 5 byte NOP). This can all be done at runtime, hence "hot-patching".

  • 2 byte writes are atomic given the register size of most architectures, which is why this works cleanly at runtime. A more generic approach would be overwriting the first 5 bytes in any function with a jump target, and making sure the target destination starts with the same 5 bytes that were overwritten (preserve the function prologue/functionality). But this introduces race conditions, if 5 bytes can't be written atomically than a thread may start executing instructions as they are being overwritten.

Re-pack a CPIO archive


Extract the firmware image to find the CPIO archive:

binwalk -eM <firmware.img>

Extract the archive to current directory, then make changes:

cat 0.cpio | sudo cpio -i

Re-pack current directory as ASCII cpio archive (SVR4 with no CRC) (binwalk description):

find . | cpio -H newc -o > 0.cpio.modded

Python REPL Unit Conversion


ASCII/hex/decimal:

>>> hex(70)
'0x46'
>>> 0x46
70
>>> chr(0x46)
'F'
>>> ord('F')
70

Float to little-endian hex string:

>>> import struct
>>> float_val = 13.37
>>> struct.pack("<f", float_val).encode('hex')
'85eb5541'

Little-endian hex string to float:

>>> hex_const_str = '85eb5541'
>>> struct.unpack('<f', hex_const_str.decode('hex'))[0]
13.369999885559082
  • struct library docs here.
  • ASCII/hex/decimal table in man page (man ascii).
⚠️ **GitHub.com Fallback** ⚠️