[Dreamhack] Logical Bug: Type Error

Sisyphus·2022년 9월 1일
0

Dreamhack - System Hacking

목록 보기
35/49

타입 에러

자료형

변수의 자료형을 선언할 때는 변수를 활용하는 동안 담게 될 값의 크기, 용도, 부호, 여부를 고려해야 합니다. Type error는 이러한 고려 없이 부적절한 자료형을 사용했을 때 발생합니다.


Out of Range: 데이터 유실

// Name: out_of_range.c
// Compile: gcc -o out_of_range out_of_range.c

#include <stdio.h>

unsigned long long factorial(unsigned int n) {
  unsigned long long res = 1;
  
  for (int i = 1; i <= n; i++) {
    res *= i;
  }
  
  return res;
}

int main() {
  unsigned int n;
  unsigned int res;
  
  printf("Input integer n: ");
  scanf("%d", &n);
  
  if (n >= 50) {
    fprintf(stderr, "Input is too large");
    return -1;
  }
  
  res = factorial(n);
  printf("Factorial of N: %u\n", res);
}
  • int res에 unsigned long long factorial의 값을 반환합니다.

실행 결과

 kali@kali  ~/dreamhack/Logical_Bug  ./out_of_range  
Input integer n: 17
Factorial of N: 4006445056
 kali@kali  ~/dreamhack/Logical_Bug  ./out_of_range
Input integer n: 18
Factorial of N: 3396534272

코드를 실행하고, 값을 입력하다 보면 18에서 값이 갑자기 작아지는 것을 확인할 수 있습니다.

18!=0x16beecca730000인데 이를 4바이트 크기의 res에 대입하려고 해서 상위 4바이트는 버려지고, 하위 4바이트인 0xca730000만 저장되서 그렇습니다.


Out of Range: 부호 반전과 값의 왜곡

// Name: oor_signflip.c
// Compile: gcc -o oor_signflip oor_signflip.c

#include <stdio.h>

unsigned long long factorial(unsigned int n) {
  unsigned long long res = 1;
  
  for (int i = 1; i <= n; i++) {
    res *= i;
  }
  
  return res;
}

int main() {
  int n;
  unsigned int res;
  
  printf("Input integer n: ");
  scanf("%d", &n);
  
  if (n >= 50) {
    fprintf(stderr, "Input is too large");
    return -1;
  }
  
  res = factorial(n);
  printf("Factorial of N: %u\n", res);
}
  • main 함수의 변수 n의 자료형이 int로 바뀌었습니다.
  • 입력 값으로 음수를 입력하면 23번 줄의 검사를 우회할 수 있습니다.

 kali@kali  ~/dreamhack/Logical_Bug  ./oor_signflip 
Input integer n: -1

입력으로 -1을 주면 시간이 지나도 결과가 나오지 않습니다.

그 이유는 -1을 입력하면 2의 보수에 의해 0xffffffff가 저장됩니다. 이 값은 factorial 함수의 인자로 들어갈때 부호가 없는 정수인 4294967295로 전달되고, 결국 반복문이 4294967295번 실행되게 되서 시간이 지나도 결과가 나오지 않게 됩니다.


Out of Range와 버퍼 오버플로우

// Name: oor_bof.c
// Compile: gcc -o oor_bof oor_bof.c -m32

#include <stdio.h>

#define BUF_SIZE 32

int main() {
  char buf[BUF_SIZE];
  int size;
  
  printf("Input length: ");
  scanf("%d", &size);
  
  if (size > BUF_SIZE) {
    fprintf(stderr, "Buffer Overflow Detected");
    return -1;
  }
  
  read(0, buf, size);
  return 0;
}
  • 버퍼 오버플로우를 막기 위해 size가 32보다 작은지를 검사합니다.
  • 그러나 size가 int형이므로 음수를 전달하여 검사를 우회할 수 있습니다.

ssize_t read(int fd, void *buf, size_t count);

read 함수의 세 번째 인자는 부호가 없는 size_t 형이므로, 음수를 전달하면 매우 큰 수로 해석됩니다.


 kali@kali  ~/dreamhack/Logical_Bug  ./oor_bof                         
Input length: -1
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAa
[2]    31135 segmentation fault (core dumped)  ./oor_bof

size에 -1을 입력하고 32 바이트보다 큰 데이터를 입력하면 스택 버퍼 오버플로우가 발생합니다.

*64 비트로 컴파일 했을 때는 안되는 이유*
64비트 환경에서 -1은 0xffffffffffffffff입니다. 이를 부호가 없는 size_t로 환산하면 18446744073709551615가 됩니다. read 함수는 count 값에 이렇게 큰 값이 들어오면 아무런 동작도 하지 않고 에러값을 반환합니다.


타입 오버플로우와 언더플로우

변수 값이 연산 중에 자료형의 범위를 벗어나면, 갑자기 크기가 작아지거나 커지는 현상이 발생하는데, 이런 현상을 Type Overflow/Underflow라고 합니다. 정수 자료형을 대상으로 발생하면 Integer Overflow/Underflow라고 합니다.


// Name: integer_example.c
// Compile: gcc -o integer_example integer_example.c

#include <limits.h>
#include <stdio.h>

int main() {
  unsigned int a = UINT_MAX + 1;
  int b = INT_MAX + 1;
  
  unsigned int c = 0 - 1;
  int d = INT_MIN - 1;
  
  printf("%u\n", a);
  printf("%d\n", b);
  
  printf("%u\n", c);
  printf("%d\n", d);
  return 0;
}
  • 오버플로우가 발생하면 최솟값이 되고, 언더플로우가 발생하면 최대값이 됩니다.
 kali@kali  ~/dreamhack/Logical_Bug  ./integer_example 
0
-2147483648
4294967295
2147483647

Integer Overflow와 버퍼 오버플로우

// Name: integer_overflow.c
// Compile: gcc -o integer_overflow integer_overflow.c -m32

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>

int main() {
  unsigned int size;
  scanf("%u", &size);
  
  char *buf = (char *)malloc(size + 1);
  unsigned int read_size = read(0, buf, size);
  
  buf[read_size] = 0;
  return 0;
}
  • size 값을 입력받고, size+1 크기의 버퍼를 할당합니다.
  • 버퍼에 size 만큼 입력을 받습니다.

size에 unsigned int의 최댓값인 4294967295을 입력하면, integer overflow로 인해 size + 1은 0이 됩니다. malloc에 0이 들어가게 되면 malloc은 최소 할당 크기인 32바이트만큼 청크를 할당해줍니다. 반면 read 함수는 size 값인 4294967295를 그대로 사용합니다. 따라서 힙 버퍼 오버플로우가 발생합니다.

0개의 댓글