GJDuck / e9patch

A powerful static binary rewriting tool
GNU General Public License v3.0
979 stars 65 forks source link

how does machine code correspond to assembly language in plugin mode? #65

Closed komorogrov closed 1 year ago

komorogrov commented 1 year ago

It is a very great Job!

I met a problem.

The Document said that trampoline template is specified using a form of annotated machine code. If I want to use machine code to develop new plug-in features, how does machine code correspond to assembly language?

The follow example from your Programming Guide, but I wonder how the two correspond.

ma-1 ma-2

GJDuck commented 1 year ago

The trampoline JSON encoding is very low-level (machine code bytes) and there is no built-in assembler. However, here is a basic workflow for designing custom trampolines:

Firstly, design the trampoline as an ordinary assembly files, e.g., in a tmp.s file define:

    # Save registers:
    lea -0x4000(%rsp),%rsp
    push %rdi
    push %rsi
    push %rax
    push %rcx
    push %rdx
    push %r11

    # Setup and execute a SYS_write system call:
    leaq .LasmStr(%rip),%rsi
    mov $asmStrlen,%edx
    mov $0x2,%edi           # stderr
    mov $0x1,%eax           # SYS_write
    syscall

    # Restore registers:
    pop %r11
    pop %rdx
    pop %rcx
    pop %rax
    pop %rsi
    pop %rdi
    lea 0x4000(%rsp),%rsp

Remember, if using this mode, it is up to you to correctly save and restore the CPU state. In this example, the trampoline is saving and restoring several registers to the stack (registers used/clobbered by the system call). This trampoline does not use any instructions that clobber the %rflags register, so it does not need to be saved.

Next, compile the assembly into an object file (tmp.o):

    $ gcc -c tmp.s

Next, disassemble the object file to reveal the machine code:

   $ objdump -d tmp.o

   0:   48 8d a4 24 00 c0 ff    lea    -0x4000(%rsp),%rsp
   7:   ff 
   8:   57                      push   %rdi
   9:   56                      push   %rsi
   a:   50                      push   %rax
   b:   51                      push   %rcx
   c:   52                      push   %rdx
   d:   41 53                   push   %r11
   f:   48 8d 35 00 00 00 00    lea    0x0(%rip),%rsi        # 0x16
  16:   ba 00 00 00 00          mov    $0x0,%edx
  1b:   bf 02 00 00 00          mov    $0x2,%edi
  20:   b8 01 00 00 00          mov    $0x1,%eax
  25:   0f 05                   syscall 
  27:   41 5b                   pop    %r11
  29:   5a                      pop    %rdx
  2a:   59                      pop    %rcx
  2b:   58                      pop    %rax
  2c:   5e                      pop    %rsi
  2d:   5f                      pop    %rdi
  2e:   48 8d a4 24 00 40 00    lea    0x4000(%rsp),%rsp
  35:   00 

The machine byte values can be manually translated into the JSON trampoline format. For example, the first instruction has the machine code hex bytes (48 8d a4 24 00 c0 ff ff) which corresponds to the JSON decimal bytes (72, 141, 164, 36, 0, 192, 255, 255).

Note that the final JSON trampoline will need to be patched up:

  1. The GNU assembler translates unknown labels ($.LasmStr) and constants ($asmStrLen) into zeros (0x0). When translating into JSON trampoline format, the zeros will need to be manually replaced with corresponding label/macro name.
  2. The latter part of the trampoline ($instr, $BREAK, ..., etc.) will need to be manually crafted (see the docs) depending on what you want to do.

It is possible to debug trampolines by passing the --trap-all option to E9Tool and then running the rewritten binary using GDB. This will cause GDB to trap/break for each trampoline entry. You can view the disassembly using GDB's la a command. Then you can step through this trampoline using GDB's si or ni commands.

komorogrov commented 1 year ago

OK, thank you very much. I get it.