Study

[Dreamhack_Study] Bufferover Flow

Love_Chickengalbi_Girl 2024. 10. 17. 20:24

스택 오버 플로우 vs 스택 버퍼 오버 플로우

스택 오버플로우: 스택 영역이 너무 많이 확장돼서 발생하는 버그

스택 버퍼 오버플로우: 스택에 위치한 버퍼에 버퍼의 크기 보다 많은 데이터가 입력되어 발생하는 버그

버퍼 오버 플로우( buffer over flow)

<버퍼>

데이터가 목적지로 이동되기 전에 보관되는 임시 저장소

ex) 만약 키보드의 속도가 처리하는 속도보다 더 빠른 경우, 데이터가 유실되서 abcdefg를 치면 adb만 전달 될 수도 있음. —> 이러한 점을 막기 위해 버퍼 필요

송신 → 버퍼로 데이터 전송 ←수신 측이 꺼내감.

 

<버퍼 오버 플로우>

int: 4바이트, char: 10바이트의 크기를 가짐. 만약 10바이트 크기의 버퍼에 20바이트 크기의 데이터가 들어가려고 하면 오버플로우 발생

<영향>

중요 데이터 변조:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int check_auth(char *password) {
    int auth = 0;
    char temp[16];
    
    strncpy(temp, password, strlen(password)); //temp의 버퍼를 password에 복사를 하는데,
password길이만큼 복사함.
    
    if(!strcmp(temp, "SECRET_PASSWORD"))
        auth = 1; //strcmp는 같으면 0인데 여기선 !이니까 같으면 1 반환
    
    return auth;
}
int main(int argc, char *argv[]) {
    if (argc != 2) {
        printf("Usage: ./sbof_auth ADMIN_PASSWORD\\n");
        exit(-1);
// 만약 인자가 2가 아니면 저런 문구가 나옴
    }
    
    if (check_auth(argv[1]))
        printf("Hello Admin!\\n");
    else
        printf("Access Denied!\\n");
}

취약점: temp크기는 16인데 인자로 전잘된 password의 크기만큼 복사하기때문에, 16이 넘는 인자를 전달하면 스택오버플로우 발생함.

⭐ auth는 temp 버퍼의 뒤에 존재하기 때문에 temp 버퍼에 오버플로우 발생시키면 auth값을 0이 아닌 임의의 값으로 바꿀 수 있음!

데이터 유출:

#include <stdio.h>
#include <string.h>
#include <unistd.h>
int main(void) {
  char secret[16] = "secret message";
  char barrier[4] = {};
  char name[8] = {};
  memset(barrier, 0, 4); //barrier값을 0으로 4만큼 초기화
  printf("Your name: ");
  read(0, name, 12);
  printf("Your name is %s.", name);
}

취약점: memset함수를 사용하면 메모리 값을 원하는 크기만큼 특정값으로 초기화

8바이트 크기의 name에 12바이트의 입력을 받음.

secret 버퍼와의 사이에 barrier라는 4바이트의 널 배열이 존재하니까, 오버플로우를 이용해 널바이트를 모두 다른 값으로 변경하면 secret 읽을 수 있음.

실행 흐름 조작:

스택 버퍼 오버플로우로 반환 주소를 조작할 수 있음. → 함수의 반환 주소를 조작해 프로세스의 실행 흐름을 바꿀 수 있음.

#include <stdio.h>
#include <stdlib.h>
int main(void) {
    char buf[8];
    printf("Overwrite return address with 0x4141414141414141: ");
    gets(buf);
    return 0;
}
 

Stack Canary

스택카나리

함수의 프롤로그에서 스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법. 카나리 값의 변조가 확인되면 프로세스 강제 종료.

스택 버퍼 오버플로우로 반환 주소를 덮으려면 반드시 카나리를 먼저 덮어야 함.

카나리 값을 모르는 공격자는 반환 주소를 덮을 때 카나리 값 변조 → 공격자는 실행 흐름 획득 불가

카나리 작동 원리

<카나리 정적 분석>

// Name: canary.c
#include <unistd.h>
int main() {
  char buf[8];
  read(0, buf, 32);
  return 0;
}

현재 우분투 gcc는 기본적으로 스택 카나리 적용되어있어서

“-fno-stack-protector 옵션 추가 해야함.

카나리를 활성화 해서 컴파일 하면, segmentation fault가 아니라 stack samshing detected aborted라는 에러가 발생

fs: 세그먼트 레지스터의 일종. 리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤값을 저장함.

리눅스는 fs를 Tread Local Storage를 가리키는 포인터로 사용함. TLS에서는 카나리를 비롯해 프로세스 실행에 필요한 여러 데이터가 저장됨.

빨간부분은 카나리때문에 빠진부분, 초록색은 추가된 부분
카나리 적용 시 추가된 코드들

rax에 fs:0x28저장 (리눅스에서는 랜덤값) —>rax에 랜덤값 저장

rbp-0x8에 fs 저장함. —> 생성한 랜덤값 rbp-0x8에 저장

 

그리고 <main+50>에서 그 랜덤값이 저장돼 있는 rbp-0x8걸 rcx로 옮기고, 그걸 fs:0x28이랑 xor 함. 만약 같으면 다 0이니까 je의 조건 만족하고, 그래서 main함수가 정상적으로 반환됨. 

만약에 동일하지 않으면 __stack_chk_fail이 호출되고, 강제로 종료함.