본문 바로가기
hacking/pwnable

[Dream hack] Logical Bug: Type Error

by ilp 2025. 3. 2.
반응형

들어가며

서론

자료형은 변수의 크기를 정의하고, 용도를 암시한다.

자 예를들어 int 형으로 변수를 선언하면, 그 변수는 4바이트 크기고 정수 연산에 사용될 변수이다.

 

근데 자료형 담고 있는 정보가 컴파일러에도 전달이 되는데 

변수의 자료형으로 고려해서 변수에 관한 코드르 생성한다.

(int는 4바이트, char은 1바이트 /연산은 그 메모리 공간을 대상으로 )

 

하번 정의된 변수의 자료형은 바꿀수 없다.

변수에 할당된 메모리의 크기확장되거나 줄어들지 않는다.

 

근대 여기서 만약

1바이트 크기의 변수에 1을 더하다가 0xff를 넘어가면 0x100이되냐

 

응 아닙니다.

0X00 이 된다.

이런형태를 데이터가 넘쳐따고 해서 

overflow 라고 한다.

 

이것 처럼 변수의 크기보다 큰 값을 대입할떄도 데이터가 유실 될 수 있따. 

(4바이트 변수에 0x0123456789abcdef를 대입하면 하위 4바이트인0x89abcdef만 들어감)


타입 에러

자료형

c언어에는 많은 자료형이 있따. (int, float, char...)

아까 자료형마다 저장할 수 있는 데이터 크기가 다르다고 했따

또한 운영체제 에 따라서도 크기가 달라질 수 있다. 

  32bit 운영체제 64bit 운영체제
long 4byte 8byte

 

이렇게 변수 자료형을 결정에는 변수에 담게 될 값의 크기, 용도, 부호 를 고려해야 한는데 

type error은 이걸 고려하지 않은 것이다.

 

C언어 자료형크기용도

자료형 크기 범위 용도
(signed) char 1 byte -128 ~ 127 정수, 문자
unsigned char   0 ~ 255 부호 없는 정수, 문자
(signed) short (int) 2 byte -32,768 ~ 32,767 정수
unsigned short (int)   0 ~ 65,535 부호 없는 정
(signed) int 4 byet -2,147,483,648 ~ 2,147,483,647 정수
unsinged int   0 ~ 4,294,967,295 부호 없는 정수
size_t 32bit: 4 byte
64bit: 8 byte
32bit: 0 ~ 4,294,967,295
64bit: 0 ~ 18,446,744,073,709,551,615
메모리 크기나 배열의 크기를 나타낼 때 사용
(signed) long 32bit: 4 byte
64bit: 8 byte
32bit: -2,147,483,648 ~ 2,147,483,647
64bit: -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807
정수
unsigned long   32bit: 0 ~ 4,294,967,295
64bit: 0 ~ 18,446,744,073,709,551,615
부호 없는 정수
(signed) long long 64bit: 8 byte -9,223,372,036,854,775,808 ~ 9,223,372,036,854,775,807 정수
unsigned long long   0 ~ 18,446,744,073,709,551,615 부호 없는 정수
float 4 byte 1.175494e-38~3.402823e+38 실수
double 8 byte 2.225074e-308~1.797693e+308 실수
Type * 32bit: 4 byte
64bit: 8 byte
메모리 주소 값 포인터

 

 

👇예를 들어보자

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

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

이런 코드가 있는데 여기서 자료형 T에는 뭐가 들어가야 할까?

더보기

T: unsigned long long

 

팩토리얼 함수는 n이 커질수록 매우큰 값이 나오게 되는데
n=20만 되도 int와 long을 넘어 선다. 그래서 
64bit 시스템 에서 가장 크게 저장할 수 있는 unsigned long long이 맞다.


Out of Range: 데이터 유실

👇 아래는 앞의 예제에서 int res factorial 의 값을 반환하게 햇다.

 

실행결과

: 양수 곱하기 에서 값이 왜 작어졌을까?

res에 저장될 수 있는 범위보다 큰 값을 저장하려고 했기 때문이다.

아까 unsigned int 는 4바이트 라고 했다 그런데 factorial 함수가 반환하는 값은 8바이트 이다.

이 상황에서 usinged int에 값을 넣을 때 상위 4바이트는 버려지고 하위 4바이트만 남는다.

 

이렇게 변수에 값을 넣었는데 변수가 저장할 수 있는 범위를 넘어서면,

저장할 수 있는 최대치 까지 저장하고 나머지는 버린다.


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

👇앞의 예제랑 비슷한데 main 함수의 n의 자료형이 int로 바뀌었다.

(먼저 -1을 입력하면 23번쨰 출의 n>=50 무시할 수 있따.)

 

실행결과

코드를 실행하고 -1을 입력하면 아무것도 안나온다.

왜냐하면

int n에 -1을 저장하면, n의 메모리 공간에 저장되는 값은  0xffffffff이다.

(이렇게 저장되는 이유가 궁금하다면  2의 보수에 대해 알아야 한다. 따로 정리할 거다.)

 

쨌든 factorial 함수는 unsigned int n을 인자로 받기때문에 -1이 아니라 4294967295로 전달되고 

그러면 값이 너무 크고 시간도 오래걸려서 작동 되지 않는다.

(예방을 위해 양수로만 쓰이면 unsigned를 붙여야 한다.)


Out of Range 와 버퍼 오버플로우

👆 위에 코드는 잘못된 자료형으로 버퍼오버플로우로 이어진다.

하지만 사실 코드에선 32보다 작은지 검사해서 버퍼오버플로우를 막으려고 하고있지만

size의 자료형이 int이기 때문에 음수를 입력해서 검사를 우회할 수 잇따.

 

read함수의 마지막 인자의 자료형은 size_t 이다. 이건 아까 표에서 볼수 있듯이 부호가 없기떄문에
-1을 전해주게 된다면 매우 큰 수로 해석되어 버퍼오버플로우를 발생할 수 있다.

 

실제 32비트로 컴파일 해주면 버퍼 오버플로우가 발생한다.

💡64비트로 컴파일 하면 안됨?

64비트에선 -1은  0xffffffffffffffff 이다. size_t는 64비트 환경에서 8바이트 크기의 부호 없는 정수를 나타내고,
-1을 size_t로 환산하면 아주 큰 값이 된다. read함수는 이렇게 큰 값이 들어오게 되면 아무 동작도 하지 않고 에러를 뱉어넨다.


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

연산중에 자료형의 범위를 넘어가면 변수값이 갑자기 작어나 커지는 상황이 있다. 

이걸 Type Oveflow/Underflow라고 부른다.

 

정수 자료형을 대상으로 발생하면 Type에 Integer을 넣어 Integer Overflow/Underflow 라고 한다.

 

👇정수 오버플로우 / 언더플로우 예제 코드

그리고 이 예제를 실행하면,

오버플로우 일때는 자료형이 표현하는 최소값

언더플로우 일대는 최댓값 되는 것을 알 수 있다.

실행결과


Interger Overflow버퍼 오버프로우

👇아래 코드는 interger overflow힙 버퍼 오버플로우로 이어지는 예제 코드이다.

사용자에게 size를 입력 받고 size+1 크기의 문자 포인터를 만든다.

그리고 그 포인터에 size 만큼 입력을 받는다.

 

이제 취약점을 찾아보면

size4294967295(unsigned int의 최대값)을 입력하면 오버플로우가 나서 size+1은 0이 된다.

그리고 그게 malloc에 전달하면, malloc은 최소 할당 크기를 할당한다.

(드림핵에는 32바이트 라고 하는데 16바이트 일수도 있고 잘 모르겠다)

그러나 read 함수는 size 값을 그대로 사용해서 힙 버퍼 오버플로우가 발생한다.


마지막


반응형