정보보안 개론과 실습:네트워크halra.knuw.ac.kr/systemsecurity/ch08.pdf3/41 포맷...

41
시스템 해킹과 보안 포맷 스트링

Transcript of 정보보안 개론과 실습:네트워크halra.knuw.ac.kr/systemsecurity/ch08.pdf3/41 포맷...

  • 시스템 해킹과 보안

    포맷 스트링

  • 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, …) 함수

  • 시스템 해킹과 보안