본문 바로가기

SYSTEM HACKING

[SWING] Pwnable 06 - Type Error

자료형

C언어 자료형

 

자료형 크기 범위 용도
(signed) char 1 바이트   정수, 문자
unsigned char      
(signed) short (int) 2 바이트   정수
unsigned short (int)      
(signed) int 4 바이트   정수
unsigned int      
size_t 32bit: 4 바이트
64bit: 8 바이트
생략 부호 없는 정수
(signed) long 32bit: 4 바이트
64bit: 8 바이트
정수  
unsigned long      
(signed) long long 32bit: 8 바이트
64bit: 8 바이트
정수  
unsigned long long      
float 4 바이트 실수  
double 8 바이트 실수  
Type * 32bit: 4 바이트
64bit: 8 바이트
주소  

 

같은 자료형이더라도 운영체제에 따라 크기가 달라질 수 있다.

 

ex) long

32 bit => 4 byte

64 bit => 8 byte

 

Type Error는 크기, 용도, 부호 여부 등을 고려하지 않고 부적절한 자료형을 사용하면 발생한다.

 

#include <stdio.h>

T factorial(unsigned int n) {
  T res = 1;

  for (int i = 1; i <= n; i++) {
    res *= i;
  }

  return res;
}

int main() {
  unsigned int n;

  printf("Input integer n: ");
  scanf("%d", &n);

  if (n >= 50) {
    fprintf(stderr, "Input is too large");
    return -1;
  }

  printf("Factorial of N: %llu\n", factorial(n));
}

 

Q. 위의 C 코드에서 자료형 T로 가장 적절한 것은?

A. unsigned long long

 

50! 까지 담을 수 있는 unsigned

 

Out of Range: 데이터 유실

#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 값을 반환한다.

 

 

17보다 18을 입력했을 때 더 작은 값이 나오는 것을 볼 수 있다.

양수를 곱했는데 왜일까?

res가 저장할 수 있는 최대 범위를 넘은 값을 저장하려 했기 때문이다.

 

18! = 0x16beecca730000

res는 4 byte이기에 상위 4바이트는 버려지고 하위 4바이트인 0xca730000 만 들어가서 출력된다.

 

이처럼 변수에 저장될 수 있는 범위를 벗어난 값을 대입하면 범위 외의 값은 유실된다.

 

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

#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의 자료형: unsigned int -> int

즉, 음수 입력 -> if (n >= 50) 검사 우회 가능

 

 

-1을 입력하면 시간이 한참 지나도 결과가 출력되지 않는다.

 

int n에 -1을 저장 -> 2의 보수로 인해 n의 메모리 공간에 0xffffffff 저장

factorial 함수는 int n을 인자로 받음 -> 부호 없는 정수인 4294967295로 전달 -> 4294967295번 반복문 실행 -> 시간 오래 걸리고 연산도 어려움

 

양수로만 쓰일 값엔 반드시 unsigned를 붙이도록 하자.

 

Out of Range와 버퍼 오버플로우

 

#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가 int형 -> 음수를 전달 -> size가 32보다 작은지 검사하는 부분 우회 -> 버퍼 오버플로우

 

read 함수의 세번째 인자는 부호가 없는 size_t 형 -> 음수를 전달 -> 매우 큰 수로 해석

 

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

 

 

 

 

-1을 입력하고 32바이트보다 큰 데이터 입력 -> 스택 버퍼오버플로우

 

64비트로 컴파일하면 안 되는 이유

64 비트에서 size_t 는 8바이트 크기 부호 없는 정수
-1 = 0xffffffffffffffff = 18446744073709551615
read 함수는 count 값으로 큰 값이 들어오면 동작하지 않고 에러 값을 반환

 

 

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

변수 값이 연산 중에 자료형의 범위를 벗어나면, 갑자기 크기가 작아지거나 커지는 현상

 

정수 자료형에서 발생하면 Integer Overflow/Underflow

 

 

#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;
}

 

 

오버플로우 -> 최솟값

언더플로우 -> 최댓값

 

Integer Overflow와 버퍼 오버플로우

#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;
}

 

 

Integer Overflow -> Heap Buffer Overflow

 

사용자로부터 size 값을 입력받고, size+1 크기의 버퍼를 할당해, size만큼 입력 값을 받는다.

 

size에 unsigned int 최댓값 4294967295 입력 -> Integer Overflow -> size+1 = 0

-> malloc 에 전달되면 최소 할당 크기 32 바이트 청크 할당, 그러나 read 함수는 size 값 그대로 사용

-> 32 바이트 청크에 4294967295만큼 값을 쓸 수 있음 -> Heap Buffer Overflow