다양한 기록

Byte Ordering, Simple Buffer Overflow 본문

보안개론

Byte Ordering, Simple Buffer Overflow

라구넹 2024. 6. 4. 21:43

Little endian

- 높은 바이트가 높은 주소에 저장

- 인텔 x86

 

Big endian

- 낮은 바이트가 높은 주소에 저장

- PowerPC, SPARC

 

Data: 0x01020304 0x100 0x101 0x102 0x103
Little Endian 0x04 0x03 0x02 0x01
Big Endian 0x01 0x02 0x03 0x04

공격자들은 주소를 조작하기 위해 바이트 오더링 방식을 알아야 함


CWE-121: Stack-based Buffer Overflow

버퍼는 배열로 이루어진 임시 기억 공간

제일 많이 할당되는 부분은 스택(지역변수)

* 전역변수면 데이터 영역, malloc으로 할당 받았으면 힙 영역에 존재

보통 스택에서 오버플로우가 많이 일어남

 

Stack-based 버퍼 오버플로우

* 그냥 스택 오버플로우에는 재귀함수로 스택을 소진시키는 것도 해당됨

 

스택에는 지역변수, 리턴 어드레스, saved ebp

스택 보호 기법이 있으면 스택 카나리까지 들어감

지역 변수 중 버퍼가 있고, 넘치면 문제가 됨 (리턴 어드레스 조작 시 임의 코드 실행 가능)

 

#include <stdio.h>

void main() {
    char dum[4];
    int cookie;
    char buf[20];
    
    printf("buf: %p, &cookie: %p\n", buf, &cookie);
    
    gets(buf);
    
    if (cookie == 0x41424344)
        printf("you win!\n");
}

쿠키가 0x41424344 면 통과인데 왜 DCBA 순으로 들어가는가

-> 실행환경이 인텔 cpu라 리틀 엔디안 ..

스택 구조 상 DCBA로 넣어야 D가 더 낮은 주소에 들어가고 쿠키에 오버플로우되면 ABCD 순서로 들어가 있을 것

 

#include <string.h>
#include <stdio.h>
#define goodPass “GOODPASS”
int main() {
    char passIsGood = 0;
    char buf[28];
    
    printf("buf: %p, &passIsGood: %p\n", buf, &passIsGood);
    printf(“Enter password: \n”);
    
    gets(buf);
    
    if (strcmp (buf, goodPass) == 0) passIsGood = 1;
    if (passIsGood == 1)
        printf("you win!\n");
}

버퍼 28바이트 위에 passIsGood이 있을 것

29번째에 \x01을 넣으면 1이 들어감

 

#include <string.h>
#include <stdio.h>
#define goodPass “GOODPASS”
int main() {
    char passIsGood = 0;
    short canary = 20;
    char buf[28];
    
    printf("buf: %p, &passIsGood: %p, &canary: %p\n", buf, &passIsGood, &canary);
    printf(“Enter password: \n”);
    
    gets(buf);
    
    printf("%d (0x%x\n)", canary, canary);
    
    if (strcmp (buf, goodPass) == 0) passIsGood = 1;
    
    if (passIsGood == 1)
        printf("you win!\n");
        
    return 0;
}

실제 카나리는 값이 바뀌는데, 예제로 short크기의 20 값을 가지는 카나리를 넣음

이 카나리의 값이 오염되면 공격이 있다고 감지

=> 공격자들은 카나리의 값을 유지시키는 형태로 공격을 하고자 시도

 

예측한다면 스택 보호기법도 껐으니 캐릭터 28바이트 + 카나리 2바이트 + 캐릭터 바이트 -> 31바이트 위치를 바꿔줘야 함

하지만 실제론 32바이트 위치를 바꿔야 했음

컴파일러가 모종의 이유로 이렇게 했을 것이고, 해커들은 이를 알아내려고 함

 

어찌되었든, 카나리는 우회 가능함

스택 카나리는 예측 가능하면 안됨

 

위 예제에서는 파이썬을 사용해서 값을 주었음

strcpy를 쓰는 경우엔 보통 펄로 값을 주고, gets를 쓸 때는 보통 파이썬으로 값을 줌

버퍼 크기가 엄청 크더라도 프로그래밍 언어를 쓰면 공격하기가 쉬워짐

 

 

- strcpy

destination 사이즈를 고려하지 않고 그냥 복사

- strncpy

destination 사이즈 만큼 맞춰서 복사, 널 문자가 없어서 스트링 끝을 모름

- strlcpy

destination 사이즈 -1 만큼 복사하고 마지막에 널문자

정보 손실은 있으나 다른 곳에 피해를 주지 않음

 

영향

가용성 - 도스(크래시, 종료, 리스타트), 도스(리소스 소모 - CPU), 도스(리소스 소모 - 메모리)

무결성, 비밀성, 가용성, 접근제어 등 - 메모리 변조, 임의 코드 및 커맨드 실행, 프로텍션 패싱

 

 

Mitigation

완벽한 보안은 없음 -> 완화

해커가 공격을 통해 얻는 이익보다 투자하는 시간, 비용이 더 들게 하면 됨

그 방안들 ..

 

페이즈: 컴파일

- GS플래그(비주얼 스튜디오), FORTIFY_SOURCE GCC flag 등, 컴파일 단계에서 관련 플래그 사용

- 스택 가드(카나리) 사용

 

페이즈: 오퍼레이션

- ASLR(Address Space Layout Randomization), PIE(Position-Independent Excutables) 사용

 

페이즈: 설계

- 안전한 라이브러리 사용

 

페이즈: 구현

- 인풋 범위 체크하기

- gets같은 위험한 함수 쓰지 않기

 

int main() {
    int arr[10];
    int buffer[10] = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
    buffer[15] = 100;
    buffer[20] = 50;
    buffer[30] = 75;
}

스택 보호 기법이 없다고 가정하면 buffer[15]는 arr을 건드림 => 실행은 됨

buffer[20], buffer[30]? -> 실행은 될지 꺼질지 알 수 없음 (단순 유저 데이터만 오염될 시 실행은 됨)

 

Q.

1. buffer == &buffer[0] => O

2. *(buffer + 1) => 2

3. buffer = 500, (buffer + 1) = ? ... 504

4. *(buffer + 2) == buffer[2] => O

5. &buffer[0] = 500,

    &buffer[1] = ? .. 504

    arr = ? .. 540

    &arr[10] = ? .. 580

6. buffer[10] = 11 -> arr[0] = 11 => O


CWE-123: Write-What-Where Condition

임의의 값을 임의의 위치에 쓸 수 있는 조건

버퍼 오퍼플로우와 관련

 

#define BUFSIZE 256

int main(int argc, char **argv) {
    char *buf1 = (char *)malloc(BUFSIZE);
    char *buf2 = (char *)malloc(BUFSIZE);
    strcpy(buf1, argv[1]);
    free(buf2);
}

buf1의 영역을 벗어나 buf2의 영역을 침범하거나 메타데이터를 망가뜨릴 수 있는 코드

특히 이전 청크나 다음 청크에 대한 정보를 망가뜨릴 수 있음

이를 악용하여 임의 코드 실행이 가능

 

이를 예방하기 위해 메모리 관리를 잘 해주는 언어를 쓰거나, 운영체제 레벨의 방어 기법 사용