[정보 보안 개론] 코드 보안(셸-2, 프로세스 권한과 SetUID, 버퍼 오버플로우 공격-1)

Jin_Hahha·2024년 11월 14일
0

정보 보안 개론

목록 보기
17/17


  • 버퍼 오버플로우, 포맷 스트링 공격에서는 /bin/sh를 기계어 코드를 바꿔 메모리에 올림
  • 아래는 /bin/sh를 기계어로 바꾼 것

  • 셸을 기계어로 바꾸는 이유
    • 메모리에 원하는 주소 공간을 올리기 위해서

  • 위는 기계어 코드가 실제 셸로 실행되는지 확인하기 위한 c 코드

  • /bin/sh를 실행한 결과와 shell을 컴파일한 실행 코드를 실행한 결과가 매우 유사
  • 버퍼 오버플로우와 포캣 스트링 공격에서도 위와 같은 과정으로 관리자 권한 셸 획득

프로세스 권한과 SetUID

SetUID

  • SetUID는 유닉스 시스템을 해킹하는 데 매우 중요한 요소

  • 유닉스 파일에 rwsr-xr-x로 권한이 설정되어 있는 경우

  • 소유자 권한에서 x가 있을 자리에 s(rws의 마지막 문자)가 적혀있음

  • SetUID 파일은 누가 실행하든 상관없이 해당 파일이 실행될 때 파일 소유자의 권한을 갖는다는 것이 특징

  • 해당 파일의 소유자가 root이면 그 파일을 실행하는 사람이 누구든 파일이 실행되는 동안 파일 소유자인 root 권한으로 실행됨

    • test라는 파일이 root 소유이고 SetUID 비트가 설정되어 있으면 아래와 같이 실행됨

    • SetUID 비트가 설정되어 있지 않으면 아래와 같이 실행됨

SetUID를 통한 해킹

  • 위와 같이 앞서 만든 shell 파일에 SetUID를 부여

  • shell 파일에 SetUID를 부여하고 나서, 일반 사용자 권한에서 shell 파일 실행

  • id 명령으로 자신이 uid 500의 wishfree라는 일반 계정임을 확인

    • 근거로 프롬프트가 $로 표시됨
  • SetUID 권한이 부여된 shell을 실행하면 euid(Effective Uid)가 0번, root로 바뀌고 프롬프트도 관리자 권한을 의미하는 #로 바뀜

  • 위와 같은 간단한 형태의 해킹은 레드햇 6.2 이하 등의 초기 버전 운영체제에서만 가능

  • 오래된 버전의 운영체제에서 예시를 드는 이유

    • 초기 버전 운영체제의 경우, 여러 가지 보안 장치가 적용되지 않아 해킹 공격의 원리를 가장 명확하게 보여줄 수 있기 때문

버퍼 오버플로우 공격

  • 1973년에 C 언어의 데이터 무결성 문제로 개념이 알려짐
  • 단순 프로그램 문제로 알았으나, 모리스 웜(Morris Worm)이 fingerd 버퍼 오버플로우를 이용했다는 것이 알려지면서 문제의 심각성이 커짐

버퍼 오버플로우 공격의 개념

  • 가장 기본적 버퍼 오버플로우 공격
    • 데이터의 형태와 길이에 대한 불명확한 정의로 인한 문제점 중 '길이에 대한 불명확한 정의'를 악용한 덮어쓰기로 발생
  • 정상적인 경우에는 사용되지 않아야 할 주소 공간, 즉 원래는 경계선 관리가 적절하게 수행되어 덮어쓸 수 없는 부분에 임의의 코드를 덮어쓰는 것을 의미
  • 모든 코드에 데이터의 불명확한 정의가 존재하지는 않음
  • 버퍼 오버플로우에 취약한 함수와 그렇지 않은 함수가 있음
  • 프로그래머가 취약한 특정 함수를 사용해야 공격 가능

버퍼 오버플로우 공격의 원리

  • 아래는 버퍼 오버플로우에 취약한 함수가 포함된 기본 예제 코드

  • 이는 bugfile abcd와 같이 입력하면 결과 값으로 abcd를 출력하는 아주 간단한 프로그램
  1. int main(int argc, char *argv[])

    • argc는 취약한 코드인 bugfile.c가 컴파일되어 실행되는 프로그램의 인수 개수
    • *agrv[]는 포인터 배열, 인자로 입력되는 값에 대한 번지수를 차례대로 저장 / 내용은 다음과 같음
  2. char buffer[10]

    • 10바이트 크기의 버퍼 할당
  3. strcpy(buffer, argv[1])

    • 버퍼에 첫 번째 인자(argv[1]) 복사
    • abcd 값을 버퍼에 저장
  4. printf("%s\n", &buffer)

    • 버퍼에 저장된 내용 출력
  • 실제 버퍼 오버플로우 공격은 strcpy 부분에서 발생
  • GDB를 통해 main 함수와 strcpy가 호출되는 과정 탐색

  1. 0x80483f8 [main]

    • push %ebp
  2. 0x804839f [main+1]

    • mov %esp, %ebp
    • 앞서 살펴본 간단한 프로그램 실행 구조와 동일
    • 스택에 EBP 값을 밀어넣고 현재의 ESP 값을 EBP 레지스터에 저장
  3. 0x80483fb [main+3]

    • sub $0xc, %esp

    • main 함수의 char buffer[10]을 실행하는 과정
    • char 명령으로 메모리에 10바이트 할당했으나, 메모리에서는 모두 4바이트 단위로 할당하기 때문에 실질적 메모리는 12바이트
  4. 0x80483fe [main+6]

    • mov 0xc(%ebp),%eax

    • EBP에서 상위 12바이트(0xC) 내용을 EAX 레지스터에 저장하면 EAX 레지스터는 RET보다 상위 주소 값을 읽음
    • 위와 같이 char *argv[]를 가리키고 EAX에 argv[]에 대한 포인터 값이 저장됨
  5. 0x8048401 [main+9]

    • add $0x4,%eax
    • EAX 값 4바이트 증가
    • argv[]에 대한 포인터
    • argv[1]을 가리킴
  6. 0x8048404 [main+12]

    • mov (%eax),%edx
    • EAX 레지스터가 가리키는 주소의 값을 EDX 레지스터에 저장
    • 프로그램을 실행할 때 인수 부분을 가리킴
  7. 0x8048406 [main+14]

    • push %edx

    • 프로그램을 실행할 때 인수에 대한 포인터를 스택에 저장
    • 인수를 넣지 않고 프로그램 실행하면 0x0의 값이 스택에 저장
  8. 0x8048407 [main+15]

    • lea 0xffffff4(%ebp),%eax
    • EAX 레지스터에 12(%ebp)의 주소 값을 저장
  9. 0x804840a [main+18]

    • 스택에 EAX 레지스터 값 저장
  • lea(load effective address)
    • 왼쪽 피연산자의 주소(메모리)를 오른쪽 피연산자(레지스터)로 전송
    • 보통 C 언어에서 포인터 변수 설정에 사용
  1. 0x804840b [main+19]
  • call 0x8048340 [strcpy]
  • 4~9에서 strcpy(buffer, argv[1])을 실행하기 위해 buffer, argv[1]과 관련된 사항을 스택에 모두 상주
  • 마지막으로 strcpy 명령 호출
  1. 0x8048340 [strcpy]
  • jmp *0x80494c0
  • 버퍼 오버플로우 공격은 해당 지점에서 발생

  • strcpy 함수는 입력된 인수의 경계를 체크하지 않음

    • 인수는 buffer[10]으로, 10바이트 길이를 넘지 않아야만 그보다 큰 인수를 받아도 스택에 쓰임

    • 13개의 A를 인수로 사용한다면?

      • 아래와 같이 A가 쌓임

      • 열여섯 번째 문자에서 세그먼테이션 오류(segmentation fault) 발생
  • bugfile.c의 char buffer[10]이 할당되는 주소 공간이 12바이트, EBP가 저장되는 공간이 4바이트

    • A가 16개 입력되면, 16바이트(주소공간 12바이트 EBP 저장 공간 4바이트)를 덮어씌움
    • 결과적으로 스택의 RET 값을 침범하여 일종의 오류 발생
  • 일반적인 버퍼 오버플로우 공격에는 egg shell이라는 것을 사용

  • egg shell은 기계어로 만든 코드를 메모리에 로드해주고, 그 시작 주소가 어디인지 알려주는 일종의 툴

    • 아래의 명령어로 컴파일 가능

      gcc -o egg eggshell.c

  • egg shell 실행

  • 메모리의 0xbfffb58에 셸 적재됨

  • 위의 과정을 거친 후, 일반 사용자 권한으로 돌아가서 펄(Perl)을 이용하여 A 문자열과 셸의 메모리 주소를 bugfile에 직접 실행

    • -e는 펄을 이용하여 하나의 프로그램을 실행할 때 쓰는 옵션
    • 공격할 때 셸의 주소를 1바이트씩 거꾸로 입력
      • 이는 스택 때문에 일어난 것, 스택에 들어간 것은 거꾸로 나온다는 것 명심

    • 버퍼 오버플로우 공격 성공

1개의 댓글

comment-user-thumbnail
2025년 1월 22일

새로운 글 연재 ㄷㄷㄷㄷ

답글 달기