정보보안 개론과 실습:네트워크halra.knuw.ac.kr/systemsecurity/ch08.pdf3/41 포맷...
TRANSCRIPT
-
시스템 해킹과 보안
포맷 스트링
-
2/41
Contents
01 포맷 스트링 공격
02 포맷 스트링 공격 대응책
학습목표
포맷 스트링의 취약점을 이해한다.
포맷 스트링 문자로 스택 값을 읽을 수 있다.
포맷 스트링 문자로 임의의 주소 값을 변경할 수 있다.
포맷 스트링 공격을 수행할 수 있다.
포맷 스트링 공격를 방어할 수 있다.
-
3/41
포맷 스트링 공격
포맷 스트링 공격
1990년대 말 알려지기 시작
formatstring.c와 같이 buffer에 저장된 문자열을 printf 함수 이용해 출력
이것이 정상적인 코드 작성법, 사용된 %s와 같은 문자열을 가리켜 포맷 스트링이라 함.
-
4/41
포맷 스트링 공격
표 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바이트 단위
-
5/41
test1.c 컴파일과 실행하기
그림 8-1 test1.c 컴파일 및 실행 결과
실습 8-1 포맷 스트링 공격 원리 이해하기
1
-
6/41
test2.c 컴파일과 실행하기
wishfree 문자열 외에 8048440 출력
이 숫자는 wishfree 문자열이 저장된 다음 메모리에 존재하는 값 0x8048440을 의미
그림 8-2 test2.c 컴파일 및 실행 결과
실습 8-1 포맷 스트링 공격 원리 이해하기
2
-
7/41
gdb에서 0x8048440 값을 확인
• printf(buffer); 행에 브레이크 포인트 설정
그림 8-3 test2 브레이크 포인트 설정
실습 8-1 포맷 스트링 공격 원리 이해하기
-
8/41
• buffer의 포인터(*buffer), 값(buffer), 주소 값(&buffer)을 확인해 보자.
그림 8-4 test2 의 buffer를 기준으로 값 확인
실습 8-1 포맷 스트링 공격 원리 이해하기
-
9/41
• &buffer 값과 buffer 값의 관계
그림 8-5 &buffer 주소에 있는 값이
가리키는 값 확인
그림 8-6 &buffer와 buffer의 관계
실습 8-1 포맷 스트링 공격 원리 이해하기
-
10/41
test3.c 컴파일과 실행하기
test2.c의char *buffer 값에 %x\n%x\n을 추가한 test3.c를 컴파일
gdb로 test2.c처럼 스택 값 확인해 보자.
그림 8-7 test3.c 컴파일 및
브레이크 포인트 설정
실습 8-1 포맷 스트링 공격 원리 이해하기
3
-
11/41
buffer 값을 기준으로 스택 내용을 확인해 보자.
그림 8-8 test3 실행 후 스택 값과 출력 값 확인
실습 8-1 포맷 스트링 공격 원리 이해하기
-
12/41
&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-1 포맷 스트링 공격 원리 이해하기
-
13/41
test4.c 컴파일과 실행하기
포맷 스트링을 이용하면 메모리 내용을 볼 수 있을뿐 아니라, 변조도 가능
실습 8-1 포맷 스트링 공격 원리 이해하기
4
-
14/41
I값이 1에서 4로 바뀜. %n은 printf 문에서 인자에 쓰기를 하는 포맷 스트링으로,
%n자리에 int 포인터를 넣어 주면 바로 전까지 프린트한 문자 개수를 출력한다.
‘printf“( %d%n\n”, d, &i);’문에서 d값을 출력할 때 1234 문자 네개를 출력했으므로
&I 값에 4를 넣어준다.
그림 8-9 test4.c 실행 결과
실습 8-1 포맷 스트링 공격 원리 이해하기
-
15/41
gdb 명령을 이용하여 실제 메모리상에서 어떤 동작이 일어나는지 알아보자.
먼저 ‘printf“( %d%n\n”, d, &i);’의 실행 전과 후를 보기 위해
다음과 같이 6번째 줄과 9번째 줄에 브레이크 포인트를 걸고 실행해 보자.
그림 8-10 test4 브레이크 포인트
설정 및 실행
실습 8-1 포맷 스트링 공격 원리 이해하기
-
16/41
포맷 스트링 공격이 있기 전의 i와 j과 관련한 스택 값을 알아보자.
그림 8-11 test4 포맷 스트링 동작 전 현황
실습 8-1 포맷 스트링 공격 원리 이해하기
-
17/41
i의 주소 0xbffffb80에 저장된 값은 0(0x00000000)이고,
d의 주소 0xbffffb84에 저장된 값은 1234(0x000004d2)임을 확인할 수 있다.
그림 8-12 test4 포맷
스트링 동작 후 현황
실습 8-1 포맷 스트링 공격 원리 이해하기
-
18/41
test5.c 컴파일과 실행하기
test5.c는 이런 문제를 넘어 i에 임의의 숫자를 넣는다. Test4.c 파일에서
‘printf(“%d%n\n”, d, &i);’ 부분이 ‘printf(“%12345d%n\n”, d, &i);로만 바뀌었다.
실습 8-1 포맷 스트링 공격 원리 이해하기
5
-
19/41
그림 8-13 test5.c 실행 결과
실습 8-1 포맷 스트링 공격 원리 이해하기
-
20/41
test6.c 컴파일 및 실행하기
아래와 같이 입력하고 실행tset6.c 파일에서는 프로그램을 실행할 때 메모리 내용을 조
회할 수 있는 dumpcode.h 파일을 함께 사용한다.
실습 8-1 포맷 스트링 공격 원리 이해하기
6
-
21/41
-
22/41
포맷 스트링 공격은 ret 주소를 찾아내는 것이 어려울 뿐이지 운영체제가 바뀌어도 똑
같이 적용된다. 리눅스 6.2를 쓰는 이유는 스택 구조가 가장 명확하기 때문이다.
먼저 test6.c 파일을 컴파일하고 실행해 보자.
그림 8-14 test6.c 실행결과
실습 8-1 포맷 스트링 공격 원리 이해하기
-
23/41
포맷 스트링 문자를 이용한 메모리 값 변조하기
그림 8-15 printf 함수를 사용한 문자열 출력
printf 함수는 ASCII 코드값을 HEX로 입력하여 해당 문자열을 출력할 수 있다.
이런 printf 함수로 test6.c파일에 실행 인수를 전달할 때,
포맷 스트링에서는 다음과 같이 cat문과 파이프( | )를 이용한다.
그림 8-16 0xbffffb98 주소에
0x00000009 값 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
7
-
24/41
이제 %%hn으로 입력해 보자. % 대신 %%로 쓰는 것은 cat을 지나면서 % 하나는 사라
지기 때문이다. 이제 0xbffffb98~0xbffffb9a 2바이트만이 (09 00)으로 바뀌었다.
Printf “\x41\x41\x41\x41\x98\xfb\xff\xbf%%c%%hn” 입력이 앞서 test5.c 파
일의 Printf(“%12345d%n\n”. d, %i);와 비슷한 결과를 가져온 것이다.
그림 8-17 0xbffffb98 주소에 0x0009 값 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
-
25/41
포맷 스트링 문자를 이용한 메모리 값을 특정 값으로 변조하기
메모리 주소가 0xbfff4443이라면, 0xbfff4443 개수만큼 문자열을 넣어 포맷 스트링
공격을 수행하는 것은 무리라고 보기 때문이다.
%%c 대신 %%64d를 입력하면 어떻게 될까? 48 00으로 바뀌었다. 0x48은 10진수로
72(=64+8)이다. 이 72 ‘\x41\x41\x41\x41\x98\xfb\xff\xbf’ 문자열의 길이(8바
이트)와 %%64d의 64를 합한 숫자이다.
그림 8-18 test6으로 0xbffffd98 주소에 0x0048 값 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
8
-
26/41
포맷 스트링 문자를 이용한 메모리 값을 특정 주소 값으로 변조하기
그렇다면 eggshell을 띄워 ret 주소로 0xbfabfabc를 획득한 상태이고
0xbffffb98~0xbffffb9c가 이 함수의 ret 주소 위치일 때,
이 메모리 주소 값을 0xbfabfabc로 바꿀 수 있다면 어떻게 될까?
버퍼 오버플로처럼 함수의 ret 주소를 바꿀 수있고, 셸도 얻을 수 있을 것이다.
0xbffffb98~0xbffffb9c값을 bc fa ab bf(0xbfabfabc)로 바꾸어 보자.
그림 8-19 test6으로 0xbffffd98 주소에 입력가능 범위 이상의 수 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
9
-
27/41
0xbffffb98에서 0xbffffb9a까지 이미 eggshell에서 얻었다고 생각하는 0xbfabfabc
뒤의 2바이트 0xfabc를 입력해 보자. 0xfabc는 12진수로 64188이다. 여기에서도 앞에
서처럼 8을 빼야 한다. 결국 %%64180d가 된다. 이를 입력해보자.
그림 8-20 test6으로 0xbffffd98 주소에 0xfabc 값 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
-
28/41
2바이트를 바꾸는 것은 성공했다. 그럼 나머지 2바이트(= 0xbffffb9a - 0xbffffb9c)는 어
떻게 바꾸어야 할까? 스택에서 bf로 시작하는 주소 값은 - 를 의미한다.
따라서 스택에 입력하고자 하는 0xbfabfabc는‘-1079248196(= 0xbfabfabc -
0x100000000)’이 된다. 그럼 양수인 0xbfabfabc와는 어떻게 구별할까?
보이지는 않지만 사실은 맨 앞에 1이라는 숫자가 있다.
사실상 0xbfabfabc는 0x1bfabfabc이다. 따라서 스택에 1bfab(114603)을 넣고자 한다면
, 앞에 이미 입력했던 64180 을 뺀 50423 을 입력해야 한다. 2 바이트씩 넣어 보자.
그림 8-21 test5로 0xbffffb98 주소에 0xbfbcfac5 값 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
-
29/41
결과를 살펴보자. 먼저 64180d 앞에는
\x41\x41\x41\x41\x98\xfb\xff\xbf\x41\x41\x41\x41\x9a\xfb\xff\xbf 로 문자가 총 16 개
있으므로, 원래의 0xfabc(64188)을 표현하려면 64172(=64188-16)로 바꾸어야 할 것이
다. 또 다음에는 합이 0x1bfab(114603)이 되어야 하므로 나머지 값은 114603-
(16+64172)가 되어 50415이다. 결국 최종 입력 코드는 다음과 같다.
그림 8-22 test6으로 0xbffffb98 주소에 0xbfabfabc 값 입력
실습 8-1 포맷 스트링 공격 원리 이해하기
-
30/41
format_bugfile.c, eggshell.c 컴파일과 권한 부여 및 실행하기
프로그램인 format_bugfile.c과 공격 셸을 띄우기 위한 eggshell 컴파일
그림 8-23 format_bugfile
동작 확인
실습 8-2 포맷 스트링 공격 수행하기
1
-
31/41
ret 주소 확인
AAAAAA와 함께 적당한 길이의 %x 입력
그림 8-24 format_bugfile에 포맷 스트링 문자를 입력할 때 동작 확인
실습 8-2 포맷 스트링 공격 수행하기
2
-
32/41
➊ 입력 값으로 넣은 AAAAAA 문자열의 정상적인 출력 값이다.
➋ 스택에 입력된 앞 부분AAAA 값의 아스키 코드 값(41)이다.
➌ 상위 스택에 저장된 AA 값에 대한 아스키 코드 값이다.
➍ int i=0으로 필자가 확인 지점으로 입력한 0이 저장되어 있다.
➎ 이 함수의 저장된 ebp 값이다.
➏ main 함수의 ret 값이다.
실습 8-2 포맷 스트링 공격 수행하기
AAAAAA 41414141 25204141 78252078 20782520 25207825 78252078 ➊ ➋ ➌ 20782520 25207825 78252078 20782520 25207825 78252078 20782520 25207825 78252078 a782520 0 bffffd68 400309cb ➍ ➎ ➏
-
33/41
실습 8-2 포맷 스트링 공격 수행하기
그림 8-25 format_bugfile을 실행할 때 스택 구조
-
34/41
gdb에서 주소 확인하기
해당 메모리의 주소도 확인해야 한다.
그림 8-26 format_bugfile의 main 함수의 어셈블리어 코드 확인
실습 8-2 포맷 스트링 공격 수행하기
3
-
35/41
sfp와 ret 확인 위해 main+3에 브레이크 포인트 설정 실행
그림 8-27 sfp와 ret 값을 확인하기 위한 break 지점 설정 후 실행
➏값인 0x4000309cb가 우리가 생각한 ret와 일치
그림 8-28 sfp와 ret 값의 주소 값 확인
format_bugfile이 실행되어 0xbfffb78에 sfp(0xbfffb98)가 있고,
0xbfffb7c에 ret 주소가 저장되어 있음을 확인할 수 있다.
실습 8-2 포맷 스트링 공격 수행하기
-
36/41
eggshell 실행하기
eggshell을 실행, 목표로 해야할 주소는 0xbffffb38이다.
그림 8-29 eggshell의 실행
실습 8-2 포맷 스트링 공격 수행하기
4
-
37/41
실습 8-2 포맷 스트링 공격 수행하기
ret 주소 확인하기
eggshell을 메모리에 올린 후 처음 했던 것처럼 format_bugfile을 다시 실행하여 sfp
주소를 확인해 보자. Sfp는 0xbffff168이다. 앞서 sfb와 ret 주소 값의 차이가 0x1c이
므로, ret주소는 0xbffff14c(=0xbffff168-0xc)가 될 것이다.
그림 8-30 eggshell의 실행 후 변화된 format_bugfile의 sfp 확인
실습 8-2 포맷 스트링 공격 수행하기
5
-
38/41
포맷 스트링 공격 수행하기
format_bugfile의 ret 주소(0xbffff14c∼0xbffff14f)에 eggshell에서 확보한
0xbffffb38 입력
0xbffffd38에 1을 붙여 1bfff와 fb38로 나누고 각각 10진수를 구한다.
0xbffff15c에는 50375(=114687-64312) 값 입력
0xbffff15e에는 주소지에 대한 값인 16개의 문자가 들어가므로 64296(=64312-16)입
력
(printf““;cat) | ./bugfile 형식에 위의 코드를 입력해 실제 공격
그림 8-31 포맷 스트링 공격 실시
실습 8-2 포맷 스트링 공격 수행하기
6
-
39/41
관리자 계정 획득하는 데 성공했으므로 UID가 0으로 출력되고 /etc/shadow 파일을
읽는데도 전혀 문제가 없을 것이다.
그림 8-32 관리자 권한 확인
실습 8-2 포맷 스트링 공격 수행하기
-
40/41
포맷 스트링 공격 대응책
printf 함수를 정상적으로 사용하여 공격에 대응
포맷 스트링 공격 취약점을 가진 함수
fprintf(fp, 서식 문자열, 인자 1, …, 인자N) 함수
sprintf(char *str, const char *fmt, …) 함수
snprintf(char *str, size_t count, const char *fmt, …) 함수
-
시스템 해킹과 보안