다양한 기록

Integer Overflow, Underflow / Signedness bugs / Widthness bugs 본문

보안개론

Integer Overflow, Underflow / Signedness bugs / Widthness bugs

라구넹 2024. 6. 4. 16:08

아리안 5

적재된 위성을 궤도에 전달하기 위한 용도의 로켓
64비트 숫자를 16비트에 채우면서 유발

 

화성 기후 궤도 탐사선

록히드 마틴에서는 점화 데이터를 야드 단위로 만들었고,

나사는 미터법으로 생각했음 -> 지나치게 낮은 궤도 - 폭발

 

비트 크기를 제대로 맞추지 않으면 문제가 생길 수 있음


 

char: -128~127

unsigned char: 0~255

short: -32768~32767

unsigned short: 0~65535

 

Format Specifier

%d - 10진수 정수 (4바이트)

%u - 부호없는 정수

%i - 아무 정수 (10진수, 8진수, 16진수)

 

%hd - short (2바이트)

%hu - unsigned short

 

%hhd - char (1바이트)

%hhu - unsigned char

 

%ld - long (32비트 시스템이면 4바이트, 64비트 시스템이면 8바이트)

%lu - unsigned long

 

%o - 8진수 정수, 4바이트

%x - 16진수 정수, 4바이트

 

%p - 주소 출력, 32비트 시스템이면 4바이트, 64비트 시스템이면 8바이트 .. 인데 보통 48비트만 사용


Integer overflow / Wraparound

CWE 190

 

정수가 범위를 넘어선 너무 큰 값으로 증가하면 0이나 음수가 됨 (오버플로우)

 

CIA를 깨트릴 수 있음

가용성

- 정의되지 않을 작동으로 연결되어 크래시 발생 가능,  반복문의 인덱스에서 인티저 오버 플로우 발생 시 무한 루프에 빠져 정상 작동하지 않을 수 있음

 

무결성

- 문제가 되는 값들이 중요한 데이터이면 단순한 데이터 오염 발생

- 랩 어라운드가 발생하여 버퍼 오버플로우로 연결 시 메모리 오염 발생 가능

 

비밀성, 가용성, 접근 제어 등 ..

- 임의 코드 실행으로 연결될 수 있음


Signed Numbers

Binary Decimal Hexadecimal
0111 1111 127 0x7F
0111 1110 126 0x7E
1111 1111 -1 0xFF
1111 1110 -2 0xFE
1000 0001 -127 0x81
1000 0000 -128 0x80

 

양수 127

2진수 - 0111 1111

1의 보수 -> 1000 0000

2의 보수 -> 1000 0001

 

2 +(-3) = ?

3 : 이진수로 0011

1의 보수 -> 1100

2의 보수 -> 1101

0010 + 1101 = 1111 -> -1

 

4비트로 제한되어 있다고 치자

+5 + 4

0101 + 0100

1001 -> 오버플로우

 

-7 -6

1001 + 1010

1 / 0011 -> 오버플로우

 

32비트의 경우

int 최고 -> 0x7FFFFFFF (2147483647)

여기다 1 더하면 -> 0x80000000

=> 언더플로우

 

img_t table_ptr;
int num_imgs;
num_imgs = get_num_imgs();
table_ptr = (img_t*)malloc(sizeof(img_t) * num_imgs);

malloc 내부 인자 값이 엄청 커져서 오버플로우가 발생할 수도 있음

대신 xmalloc 권장

 

같은 이유로 memcpy도 문제가 있음

 

int catvars(char *buf1, char *buf2, unsigned int len1, unsigned int len2) {
    char mubuf[256];
    
    if( (len1 + len2) > 256 ) {
        return -1;
    }
    
    memcpy(mybuf, buf1, len1);
    memcpy(mybuf + len1, buf2, len2);
    
    do_some_stuff(mybuf);
    
    return 0;
}

len1과 len2를 더해서 오버플로우 발생 시 256보다 작을 수 있음

 

계산 결과이후 저장된 결과와 올바른 결과가 다를 수 있음

중요한 변수에 잘못된 값을 포함하게 하는 등, 문제가 될 수 있음

 

어떻게 예방할 수 있는가?

에러 핸들링, 익셉션 핸들링

int sum(int a, int b) {
    int c = a + b;
    if( a > 0 && b > 0 && c < 0 ) {
        throw new MyOverflowException(a, b);
    }
    return c;
}

int pord(int a, int b) {
    int c = a * b;
    if( a > 0 && b > 0 && c < 0 ) {
        throw new MyOverflowException(a, b);
    }
    return c;
}

양수를 더했는데, 혹은 곱했는데 음수가 나오면 예외처리

 

static void update_value(char op) {
    if( op == '+' ) {
        if( value + 1 > value ) value++;
        else printf("too big!\n");
    }
    else {
        if( value -1 < value ) value--;
        else printf("too small!\n");
    }
}

int addOvf(int *result, int a, int b) {
    if( a > INT_MAX - b ) return -1;
    else {
        *result = a + b;
        return 0;
    }
}

더했는데 원해 값보다 작아지거나,

원래 값이 너무 커서 남은 자리가 충분하지 않은지 검사하기

 

완화시키기(SDLC 단계에서 신경써야 함)

요구사항 단계

- 언어를 잘 선택해라: 파이썬같은 건 부족하면 알아서 늘림

 

설계 단계

- 라이브러리나 프레임워크를 잘 선택해라(SafeInt, IntegerLib)

 

구현 단계

- 입력 검증 하기

- 크기를 나타내는 건 unsigned 쓰기


Signedness bugs

- 부호가 없는 변수가 부호가 있는 것으로 해석되는 경우

- 부호가 있는 변수가 부호가 없는 경우로 해석되는 경우

 

// int.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int main(int argc, char *argv[]) {
    char buf[20];
    
    int i = atoi(argv[1]);
    memcpy(buf, argv[2], i * sizeof(int));
}
// void *memcpy(void *dest, const void *src, size_t num);

1. 버퍼 오버플로우

./ 111 AAAA

i * sizeof(int) => 444

444만큼 argv[2]의 내용을 카피? -- buf는 20바이트 크기 -> 넘침

스택의 구조가 망가지고, 리턴 어드레스나 saved ebp가 망가지고, 스택 카나리가 망가짐 (스택 스매싱 detected)

 

2. 부호화 버그로 인한 버퍼 오버플로우

memcpy의 size_t는 unsigned integer임

그런데 ./int -1 AAAA

이런식으로 사이즈에 음수값을 주면 -1은 16진수로 0xffffffff

0xffffffff를 memcpy에서 unsigned int로 받아들이면 굉장히 큰 숫자가 되어 버퍼 오버플로우 발생

 

맥에서는 테스트 결과 애초에 버퍼 크기를 넘기면 안된다

우분투에선 이렇게 된다

 

 

프로세스 구조 중 스택은 위와 같다.

여기서 버퍼 오버플로우가 발생하여 argc의 값까지 망가진 것을 볼 수 있다.

무결성이 깨지는 문제

 

CWE-195: Signed to Unsigned Conversion Error

unsigned int readdata() {
    int amount = 0;
    ....
    if( result == ERROR ) amount = -1;
    ....
    return amount;
}

unsigned int로 -1을 리턴하면 엄청 큰 수가 리턴 될 것

 

int copy_something(char *buf, int len) {
    char kbuf[800];
    
    if( len > sizeof(kbuf) ) {
        return -1;
    }
    
    return memcpy(kbuf, buf, len);
}

len이 음수이면 if문은 통과하는데, memcpy에서 unsigned int로 컨버전되면서 엄청 큰 수로 바뀜


#include <stdio.h>

int main(void) {
    int i;
    short s;
    char c;
    
    i = 0xdeadbeef;	// 0xdeadbeef
    s = i;			// 0xbeef
    c = i;			// 0xef
    
    printf("i = 0x%x, (%d bits)\n", i, sizeof(i) * 8);	// 0xdeadbeef (32bits)
    printf("s = 0x%x, (%d bits)\n", d, sizeof(s) * 8);	// 0xffffbeef (16bits)
    printf("c = 0x%x, (%d bits)\n", c, sizeof(c) * 8);	// 0xffffffef (8bits)
    
    return 0;
}

d는 앞쪽 비트가 1 -> 사이즈 안맞추면 f로 차게 됨

%hx, %hhx를 사용하기

 

// width.c
#include <stdio.h>
#include <string.h>
#incldue <stdlib.h>

int main(int argc, char *argv[]) {
    unsigned short s;
    int i;
    char buf[80];
    if( argc < 3 ) return -1;
    
    i = atoi(argv[1]);
    s = i;
    
    if( s >= 80 ) {
    	printf("you don't");
        return -1;
    }
    
    printf("s=%d\n", s);
    memcpy(buf, argv[2], i);
    buf[i] = '\0';
    printf("%s\n", buf);
    
    return 0;
}
// ./width1 5 hello
// s = 5
// hello

// ./width1 80 hello
// you don't

// ./width1 65536 hello
// s = 0
// Segmentation fault (core dumped)

int에 65536 받아놓고 unsigned short로 검사 .. > 너무 커서 버퍼가 넘침

 

'보안개론' 카테고리의 다른 글

Byte Ordering, Simple Buffer Overflow  (0) 2024.06.04
Array Operations  (0) 2024.06.04
Use After Free  (0) 2024.06.03
bin, chunk, double free  (0) 2024.06.03
ptr[-1], ptr[-2]가 가지는 의미  (0) 2024.05.19