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.