System Hacking

Integer issue (정수의 범위, 묵시적 형변환, integer overflow)

jir4vvit 2021. 1. 27. 18:04

ref : dreamhack.io

 

얼마전에 integer overflow를 마주했었다. 너무 대충 아는 것 같아서 드림핵 강의를 보면서 정리해보려고 한다. 


정수의 범위

  • (signed) char : -2^7 ~ 2^7-1
  • unsigned char : 0 ~ 2^8-1 (only 양수)
  • (signed) short : -2^15 ~ 2^15-1
  • unsigned short : 0 ~ 2^16-1 (only 양수)
  • (signed) int : -2^31 ~ 2^31-1
  • unsigned int : 0 ~ 2^32-1 (only 양수)
  • (signed) long long : -2^63 ~ 2^63-1
  • unsigned long long : 0 ~ 2^64-1 (only 양수)

size_t와 long 자료형은 아키텍쳐에 따라 표현할 수 있는 수의 범위가 달라진다.

 

long 자료형

32bit -> int와 동일 (-2^31 ~ 2^31-1)

64bit -> long long과 동일 (-2^63 ~ 2^63-1)

 

size_t 자료형

32bit -> unsigned int와 동일 0 ~ 2^32-1 (only 양수)

64bit -> u

 

 

size_t란??

부호 없는 정수형(unsigned integer)으로 sizeof, alignof, offsetof의 반환값일반적으로 배열 인덱싱 및 루프 계산, 문자열이나 메모리의 사이즈를 나타낼 때 사용)size_t는 32bit OS에서는 "unsigned 32bit integer"이고, 64bit OS에서는 "unsigned 64bit integer"이다. 그러나 "unsigned int" 또는 "int"는 64bit OS라고 해서 꼭 64bit정수는 아니다.. 여전히 32bit일 수도 있다. 이것이 size_t와 unsigned int의 차이다.

 

 

묵시적 형 변환

연산을 진행할 때 피연산자로 오는 데이터들의 자료형이 서로 다를 경우, 다양한 종류의 형 변환이 일어나게 된다. 이 때, 프로그래머가 자료형을 직접 명시해주지 않는다면 묵시적으로 형 변환이 발생한다. 

  • 대입 연산 : 대입 연산의 좌변과 우변이 다를 경우 묵시적 형 변환이 발생, 작은 정수 자료형에 큰 정수를 저장하는 경우, 작은 정수의 크기에 맞춰서 상위 바이트가 소멸됨
  • 정수 승격 : char이나 short 자료형이 연산될 때. 컴퓨터가 int형을 기반으로 연산하기 때문에 발생
  • 피연산자가 불일치할 경우 : int < long < long long < float < double < long double 순으로 변환, 작은 바이트에서 큰 바이트로, 정수에서 실수로 형변환. 
    eex) int + double => int가 double 형으로 형변환된 후 연산이 진행 

 

 

Integer Issues

test 1) int-1

//int-1.c

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

int main(void) {
	char *buf;
	int len;

	printf("Length: ");
	scanf("%d", &len);

	buf = (char *)malloc(len + 1);

	if(!buf) {
		printf("Error!");
		return -1;
	}

	read(0, buf, len);
}

 

len 값을 사용자에게 입력받은 이후 len+1 만큼 메모리를 할당받고 그 포인터를 buf에 저장한다. 그리고 read 함수를 통해 buf에 데이터를 len만큼 입력받는다.

 

공격자가 len 값으로 -1을 넣으면 어떻게 될까?

 

len=-1이므로 buf = malloc(0)이 호출된다.

여기서부터 정상적으로 실행이 안될 것 같지만.. 리눅스에서는 malloc의 인자가 0이라면 정상적인 힙 메모리가 반환이 된다. 이후 read(0, buf, -1)이 호출된다.

 

인자로 전달된 값은 int형 값 -1이고, read함수의 세 번째 인자는 size_t형이다. 따라서 묵시적 형변환이 일어나게 된다.

따라서 아래와 같이 len에는 0xffffffff 즉, 2^32-1 값이 들어가게 된다. 

 

read함수 인자

64bit OS이니 read함수의 인자는 RDI, RSI, RDX 에 들어갈 것이다. 이 중에서 RDX에 들어가는 것이 len이다. 

 

 

지정된 크기의 버퍼를 넘는 데이터를 넣을 수 있어 힙 오버플로우가 발생하게 된다..

 

 

test 2) int-2

// int-2.c
char *create_tbl(unsigned int width, unsigned int height, char *row) {
	unsigned int n;
	int i;
	char *buf;
	n = width * height;
	buf = (char *)malloc(n);
	
	if(!buf)
		return NULL;
	for(i = 0; i < height; i++)
		memcpy(&buf[i * width], row, width);
	return buf;
}

 

width, height 값과 초기화 데이터인 row 포인터를 인자로 받고 테이블을 초기화 한다. 

width * height 크기의 테이블을 할당한 후 각 행에 init_row 데이터를 복사한다. 

 

memcpy 함수

#include <string.h>  // C++ 에서는 <cstring>

void* memcpy(void* destination, const void* source, size_t num);

source가 가리키는 곳부터 num 바이트 만큼을 destination이 가리키는 곳에 복사한다. 

 

 

위 test code에서 width, height, n은 전부 unsigned int형의 변수이다. 따라서 width * height가 2^32를 넘어가면 의도치 않은 값이 들어간다.

  • unsigned int : 0 ~ 2^32-1 (only 양수)

 

width가 65536이고 height가 65537이라고 가정해보자. 

width * height = 65536 * 65537 = 2^32 + 65536 => 65536

 

실제로 malloc은 65536 * 65537 크기의 힙이 할당되는 것이 아니라 65536 크기만큼만 할당되게 된다. 

하지만 for문의 memcpy 함수에서 루프를 돌면서 메모리를 실제로 할당된 65536 크기를 넘어 65536 * 65537 만큼 계속 복사하기 때문에 버퍼오버플로우가 발생하게 된다.

 

 

3) Quiz

몇 번째 line이 취약할까?

quiz

length는 입력할 수 있는 값이라고 주석에 적혀있다.

0x8000 크기로 힙에 버퍼를 할당한다.

그리고 if(length < 0 || length + 1 >= MAX_SIZ) 에서 살짝... overflow를 막아주는 척한다. 하지만 실제로 그렇지 않다. 

아무튼 read 함수에서 할당한 buf에 length만큼 입력이 가능하다.

 

 

length에 int형의 최대값인 0x7FFFFFFF를 넣어보면 어떻게 될까?

length = 0x7FFFFFFF 양수이다. 

length + 1 = 0x80000000 int 범위에 넘어가서 음수로 취급된다. 

그래서 if(length < 0 || length + 1 >= MAX_SIZ) 이 조건에 해당하지 않아 line13으로 건너뛰어 read함수를 호출하게 된다.

 

결론은 read(fd, buf, 0x7FFFFFFF) 가 호출되어 힙 오버플로우가 발생하게 된다.

 

'System Hacking' 카테고리의 다른 글

[HackingCamp 22] 퍼징의 이해 - 장대희님  (0) 2021.03.02
힙풍수 (Heap Feng Shui)  (2) 2021.03.02
Use-After-Free (UAF)  (0) 2021.01.06
Master canary (마스터 카나리)  (1) 2020.12.21
one-shot gadget (원샷 가젯)  (0) 2020.11.13