SLAE 0x5: Part I - Analyzing MSFvenom ‘linux/x86/exec’ shellcode

Aditya Chaudhary
6 min readFeb 8, 2019

--

In this post we will analyse linux/x86/exec shellcode.

Shellcode is basically a list of carefully crafted instructions that can be executed once the code is injected into a running application. Stack and heap-based buffer overflows are the most popular way of doing so.

I would recommend that you should do through the basics of shellcoding and MSFvenom before diving head first in this post. For the basics of shellcoding, I would recommend you go through the below mentioned blogs to catch up quickly.

TL;DR

Since we live in the era of widespread attention and time deficiency, here’s the summarised version of this entire story:

  • Generating the shellcode using MSFvenom
  • Disassemble Shellcode — Ndisasm
  • Shellcode Emulation — Libemu
  • Tracing System Calls and Signals — strace

Generating Shellcode

Let’s generate the shellcode using msfvenom.

msfvenom -p linux/x86/exec CMD=”ls -la” R -o linux_x86_exec

Dump Shellcode — Ndisasm

Let’s use ndisasm to generate assembly shellcode from the above generated binary file.

ndisasm -u linux_x86_exec

00000000  6A0B              push byte +0xb
00000002 58 pop eax
00000003 99 cdq
00000004 52 push edx
00000005 66682D63 push word 0x632d
00000009 89E7 mov edi,esp
0000000B 682F736800 push dword 0x68732f
00000010 682F62696E push dword 0x6e69622f
00000015 89E3 mov ebx,esp
00000017 52 push edx
00000018 E807000000 call dword 0x24
0000001D 6C insb
0000001E 7320 jnc 0x40
00000020 2D6C610057 sub eax,0x5700616c
00000025 53 push ebx
00000026 89E1 mov ecx,esp
00000028 CD80 int 0x80

From the above instruction set, it looks like there is system call with a some values set in registers. String 0x632d is reverse of -c, 0x68732f is reverse of /sh and 0x6e69622f is reverse of /bin. This did not provide us enough information, we can now either trace the entire instruction set to figure out the working of this shellcode or we can use libemu to emulate and study the behaviour of this shellcode.

Shellcode Emulation — Libemu

With the below mentioned command, we will generate a dump file which will contain the entire debugged output with pseudo C code for the shellcode in the end. The command will also generate a dot format callgraph.

./libemu/tools/sctest/sctest -vvv -Ss 100000 -G linux_x86_exec.dot < linux_x86_exec > linux_x86_exec_dump

We can convert the dot file to png using:

dot linux_x86_exec.dot -Tpng -o linux_x86_exec.png

linux_x86_exec_dump file: https://gist.github.com/AdityaChaudhary/b9bf4b7f2317c01ea557da04ce6e754e

C pseudo code generated by sctool:

int execve (
const char * dateiname = 0x00416fc0 =>
= "/bin/sh";
const char * argv[] = [
= 0x00416fb0 =>
= 0x00416fc0 =>
= "/bin/sh";
= 0x00416fb4 =>
= 0x00416fc8 =>
= "-c";
= 0x00416fb8 =>
= 0x0041701d =>
= "ls -la";
= 0x00000000 =>
none;
];
const char * envp[] = 0x00000000 =>
none;
) = 0;
Shellcode callgraph

Callgraph gives us very clear understanding of all the system and procedure calls in this shellcode. In this case there is only one systemcall i.e. execve.

Inspecting the C pseudo code, we get a clear idea that the shellcode calls exevce with arguments “/bin/sh”, “-c”, ”ls -la” and none(Null).

Tracing System Calls and Signals — strace

Let’s use strace to observe the system calls as the program runs. For this we can generate a c format shellcode using MSFvenom.

msfvenom -p linux/x86/exec CMD=’ls -la’ -f c -b ‘\x00’

Be cautious, this method will run the shellcode on your machine with no virtualization or emulation.

"\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\x07\x00\x00\x00\x6c"
"\x73\x20\x2d\x6c\x61\x00\x57\x53\x89\xe1\xcd\x80"

Let’s replace this shellcode in our C shellcode wrapper https://gist.github.com/AdityaChaudhary/d37361b4847de1640ad2923d277b936f

#include<stdio.h>
#include<string.h>
unsigned char code[] = \
"\xbf\x4f\x92\xfb\x62\xda\xdb\xd9\x74\x24\xf4\x58\x2b\xc9\xb1"
"\x0b\x31\x78\x14\x83\xc0\x04\x03\x78\x10\xad\x67\x91\x69\x69"
"\x11\x34\x08\xe1\x0c\xda\x5d\x16\x26\x33\x2d\xb0\xb7\x23\xfe"
"\x22\xd1\xdd\x89\x41\x73\xca\x8d\x85\x74\x0a\xfe\xf6\x54\x27"
"\x92\x99\x94\x60\x39\xd0\x74\x43\x3d";
main()
{
printf("Shellcode Length: %d\n", strlen(code)); int (*ret)() = (int(*)())code; ret();}

Compile the shellcode.

gcc -g -fno-stack-protector -z execstack -o shellcode shellcode.c

Now, let’s use strace to trace system calls and signals.

strace ./shellcode

Observe the system calls from the below output. The line mentioned in bold is the systemcall to exevce from our shellcode.

execve("./shellcode", ["./shellcode"], [/* 23 vars */]) = 0
brk(0) = 0x90c6000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7708000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=71789, ...}) = 0
mmap2(NULL, 71789, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb76f6000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1734120, ...}) = 0
mmap2(NULL, 1747676, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb754b000
mprotect(0xb76ef000, 4096, PROT_NONE) = 0
mmap2(0xb76f0000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a4) = 0xb76f0000
mmap2(0xb76f3000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb76f3000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb754a000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb754a900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb76f0000, 8192, PROT_READ) = 0
mprotect(0x8049000, 4096, PROT_READ) = 0
mprotect(0xb772b000, 4096, PROT_READ) = 0
munmap(0xb76f6000, 71789) = 0
fstat64(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 5), ...}) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7707000
write(1, "Shellcode Length: 69\n", 22Shellcode Length: 69
) = 22
execve("/bin/sh", ["/bin/sh", "-c", "ls -la"], [/* 0 vars */]) = 0
brk(0) = 0x97d7000
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
mmap2(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb77be000
access("/etc/ld.so.preload", R_OK) = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat64(3, {st_mode=S_IFREG|0644, st_size=71789, ...}) = 0
mmap2(NULL, 71789, PROT_READ, MAP_PRIVATE, 3, 0) = 0xb77ac000
close(3) = 0
access("/etc/ld.so.nohwcap", F_OK) = -1 ENOENT (No such file or directory)
open("/lib/i386-linux-gnu/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\1\1\1\0\0\0\0\0\0\0\0\0\3\0\3\0\1\0\0\0000\226\1\0004\0\0\0"..., 512) = 512
fstat64(3, {st_mode=S_IFREG|0755, st_size=1734120, ...}) = 0
mmap2(NULL, 1747676, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0xb7601000
mprotect(0xb77a5000, 4096, PROT_NONE) = 0
mmap2(0xb77a6000, 12288, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1a4) = 0xb77a6000
mmap2(0xb77a9000, 10972, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0xb77a9000
close(3) = 0
mmap2(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0xb7600000
set_thread_area({entry_number:-1 -> 6, base_addr:0xb7600900, limit:1048575, seg_32bit:1, contents:0, read_exec_only:0, limit_in_pages:1, seg_not_present:0, useable:1}) = 0
mprotect(0xb77a6000, 8192, PROT_READ) = 0
mprotect(0x805f000, 4096, PROT_READ) = 0
mprotect(0xb77e1000, 4096, PROT_READ) = 0
munmap(0xb77ac000, 71789) = 0
getpid() = 18456
rt_sigaction(SIGCHLD, {0x8055f60, ~[RTMIN RT_1], 0}, NULL, 8) = 0
geteuid32() = 1000
getppid() = 18455
brk(0) = 0x97d7000
brk(0x97f8000) = 0x97f8000
getcwd("/media/sf_rev_engg/shell_coding/analysis", 4096) = 41
rt_sigaction(SIGINT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGINT, {0x8055f60, ~[RTMIN RT_1], 0}, NULL, 8) = 0
rt_sigaction(SIGQUIT, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGQUIT, {SIG_DFL, ~[RTMIN RT_1], 0}, NULL, 8) = 0
rt_sigaction(SIGTERM, NULL, {SIG_DFL, [], 0}, 8) = 0
rt_sigaction(SIGTERM, {SIG_DFL, ~[RTMIN RT_1], 0}, NULL, 8) = 0
stat64("/usr/local/sbin/ls", 0xbfc33380) = -1 ENOENT (No such file or directory)
stat64("/usr/local/bin/ls", 0xbfc33380) = -1 ENOENT (No such file or directory)
stat64("/usr/sbin/ls", 0xbfc33380) = -1 ENOENT (No such file or directory)
stat64("/usr/bin/ls", 0xbfc33380) = -1 ENOENT (No such file or directory)
stat64("/sbin/ls", 0xbfc33380) = -1 ENOENT (No such file or directory)
stat64("/bin/ls", {st_mode=S_IFREG|0755, st_size=108612, ...}) = 0
clone(child_stack=0, flags=CLONE_CHILD_CLEARTID|CLONE_CHILD_SETTID|SIGCHLD, child_tidptr=0xb7600968) = 18457
wait4(-1, total 80
drwxrwx--- 1 root vboxsf 384 Feb 7 11:36 .
drwxrwx--- 1 root vboxsf 1408 Feb 6 12:33 ..
drwxrwx--- 1 root vboxsf 64 Feb 6 15:33 .libs
drwxrwx--- 1 root vboxsf 1152 Feb 7 09:52 libemu
-rwxrwx--- 1 root vboxsf 42 Feb 7 10:43 linux_x86_exec
-rwxrwx--- 1 root vboxsf 315 Feb 7 11:32 linux_x86_exec.c
-rwxrwx--- 1 root vboxsf 66 Feb 7 11:20 linux_x86_exec.dot
-rwxrwx--- 1 root vboxsf 39040 Feb 7 11:06 linux_x86_exec.png
-rwxrwx--- 1 root vboxsf 248 Feb 7 11:26 linux_x86_exec.sh
-rwxrwx--- 1 root vboxsf 8039 Feb 7 11:05 linux_x86_exec_dump
-rwxrwx--- 1 root vboxsf 8614 Feb 7 11:36 shellcode
-rwxrwx--- 1 root vboxsf 461 Feb 7 11:36 shellcode.c
[{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 18457
--- SIGCHLD (Child exited) @ 0 (0) ---
sigreturn() = ? (mask now [])
exit_group(0) = ?

Observe the execve call with arguments below. This shellcode is just executing the command /bin/bash -c ls -la.

execve("/bin/sh", ["/bin/sh", "-c", "ls -la"], [/* 0 vars */])

This summaries this blog. Continue reading the Part II here, where we’ll walk through the linux/x86/meterpreter/reverse_tcp shellcode.

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: PA-8416

--

--