반응형

* 64 bit 구조에서 32 bit 프로그램으로 컴파일 하는 법

gcc -m32 -o test test.c

-m32 옵션 컴파일시 bits/libc-header-start.h: No such file or directory 오류

=> sudo apt-get install gcc-multilib g++-multilib

 

* 형식 지정자

%d => 부호가 있는 정수, decimal signed int

%u => 부호가 없는 정수, unsigned int

%i => any integer (decimal, octal, hexadecimal)

 

%hd => half decimal, decimal의 반틈이므로 2byte, short 이다.

%hu => unsigned short(unsigned int의 half)

 

%hhd => char (half의 half이므로 8bit=1byte), 8비트 단위의 부호가 있는 정수 출력

%hhu => unsigned char, 8비트 크기의 부호가 없는 정수 출력

 

%ld => decimal signed long

%lu => unsigned long

 

%o => octal integer, 8진수

%x => hexadecimal integer, 16진수

 

 

* printf (“%d, %u, %hd, %hu, %c\n”, -1, -1, -1, -1, -1);

changmin@ubuntu:~/Documents$ ./test
-1, 4294967295, -1, 65535, � 

 

%d => 부호가 있는 정수이므로 -1이 출력

%u => 부호가 없는 정수이므로 언더플로우 발생하여 큰수가 됨

%hd => 2byte, 부호가 있는 short이므로 -1 출력

%hu => 2byte, 부호가 없는 short이므로 언더플로우 발생하여 unsigned short가 표현할 수 있는 최대 수가 됨

 

 

* 오버플로우 or wraparound

어떤 정수 값이 지정된 16bit 크기에 저장될 수 없게 커지게 되면 overflow가 발생한다.

(매우작은수가 되거나 음수가 되버린다. 0이 되버리기도 한다 (표현할 수 없는 상위 비트는 날려버리기 때문))

 

오버 프로우가 발생하면 자원관리와 실행흐름에 문제가 생긴다.

결과적으로 약점이 생기고 C.I.A(기밀성, 무결성, 가용성)이 깨진다.

 

가용성 : 오버플로우로 인해서 예측하지 못하는 행동으로 연결될 수 있다(crash 유발가능). loop에서 인덱스로 사용될 경우 무한루프가 될 수도 있다. => DoS유발가능

무결성 : 데이터에서 중요한 값이 손상될 수 있다. 또한 buffer overflow를 일으킬 수 있다.(배열의 인덱스가 넘친다거나), 메모리 손상 유발 가능

기밀성, 접근제어 : 약점이 buffer overflow를 유발하고 그로인해 임의의 코드를 실행할 수 있게 된다. 따라서 보안 정책의 범위를 벗어나게 된다.

 

integer 오버플로우는 직접적으로 실행흐름을 바꾸거나 악용될 가능성은 적다.

하지만, 어떤 경우는 heap overflow 즉, 버퍼 오버플로우를 유발 할 수 있다. 

따라서 임의의 코드를 실행할 수 있게 되는 더 나쁜 상황이 된다.

 

오버플로우 예시)

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

void main(void){
	unsigend short us = 65535; //unsigned short max값
    short ss = 32767; // short max값
    
    unsigned char uc = 255; //max
    signed char sc = 127; //max
    
   printf("unsigned_short = %d (%u), signed_short = %d (%u)\n", us, us, ss, ss);
   //unsigned short= 65535 (65535), signed short = 32767 (32767)
   
   //%d, %u 는 signed int, unsigned int범위임으로 short 정상출력됨
   
   printf("unsigned_char = %d (%u), signed_char = %d (%u)\n\n", uc, uc, sc, sc);
   //unsigned char = 255 (255), signed char = 127 (127)
   
   //%d, %u 는 signed int, unsigned int범위임으로 char 정상출력됨
   
   printf("unsigned_short = %hd, %hu, %hhd, %hhu\n", us, us, us, us);
   //unsigned short= -1, 65535, -1, 255
   
   //%hd는 signed int의 절반이므로 2byte, short범위이다. ->
   //->65535가 1111 1111(2)이므로 2의 보수표현으로 생각하여 -1로 출력됨
   
   //%hu는 unsigned int의 절반이므로 정상 출력된다.
   
   //%hhd는 signed int의 절반의 절반이므로 1byte범위이다. ->
   //->16bit를 8bit로 출력하므로 앞으 8bit는 잘리고 1111 1111 1111 1111(2)->
   //->1111 1111(2), 위와 동일하게 2의 보수 표현으로 생각하여 -1이 출력됨
   
   //%hhu는 unsigned int의 절반의 절반이므로 정상 출력된다.
   
   printf("unsigned_short+1 = %hd, %hu, %hhd, %hhu\n", us+1, us+1, us+1, us+1);
   //unsigned short+1 = 0, 0, 0, 0
   
   //unsigned short의 최대값에 1을 더하면 short의 범위를 벗어나게 된다.
   //16bit를 넘어서서 할당된 16bit로 표현이 불가하여 0으로 출력된다.
   
   printf("signed_short = %hd, %hu, %hhd, %hhu\n\n", ss, ss, ss, ss);
   //signed short = 32767, 32767, -1, 255
   
   //%hhd는 signed int의 절반의 절반이므로 1byte(8bit), ->
   //->short(16bit)인데 8bit로 출력하므로 위와 동일하게 앞 8비트가 잘리고 2의 보수표현으로 -1
   
   printf("unsigned_char = %hd, %hu, %hhd, %hhu\n", uc, uc, uc, uc);
   //unsigned char = 255, 255, -1, 255
   
   //%hhd는 부호가 있는 8bit이고, 부호가 없는 8bit를 출력할때 2의 보수 표현으로 -1이 출력
   
   printf("unsigned_char+1 = %hd, %hu, %hhd, %hhu\n", uc+1, uc+1, uc+1, uc+1);
   //unsigned char+1 = 256, 256, 0, 0
   
   //%hhd, %hhu 둘다 8bit인데 unsigned_char+1이 되면 1 0000 0000(2)가 되므로
   // 0000 0000(2)로 0이된다.

}

 

* usigned와 signed 차이

2의 보수를 취하여 최상위 비트가 1이면 음수로 취급한다.

ex) 2+ (-3)

3 => 0011

1의 보수 => 1100

2의 보수 => 1101

따라서 0010 + 1101 = 1111

1111은 -1이된다.

 

8bit의 경우

0111 1111(2) ~ 1000 0000(2)가 범위가 된다 (1111 1111(2) 는 -1 => 위 원형 그림 참고)

127 ~ (-128)

 

- 2의 보수 덧셈

(-4) + (-1)

1100 + 1111 => 1 1011 => 1011 = -5

(-4) + (+4)

1100 + 0100 => 1 0000 = 0

 

(+5) + (+4)

0101 + 0100 => 1001 (-7) =오버플로우발생

(-7) + (-6)

1001 + 1010 => 1 0011 => 0011 (3)= 오버플로우 발생

 

 

* Overflow / Underflow

overflow : INT_MAX = 2147483647 (0X 7FFF FFFF), 값이 INT_MAX보다 크면 segmentation fault가 유발된다.

underflow : INT_MIN = 0x 8000 000 (0x8 = 1000(2)), 값이 INT_MIN보다 작으면 segmentation fault

 

INT_MAX = 2174483647(0x7FFF FFFF)

INT_MIN = -2147483648(0x8000 0000)

UINT_MAX = 4294967295(0xFFFF FFFF)

 

오버플로우 예시) 

#include <stdio.h>
int main(void){
	unsigned int num = 0xffffffff;
    
	printf("num = %u (0x%x)\n", num, num);
	printf("num + 1 = 0x%x\n", num + 1);
    
	return 0;
}


/* EOF */
The output of this program looks
like this:
num = 4294967295 (0xffffffff)
num + 1 = 0x0

num은 INT_MAX이다. 

%u = unsigned int이므로 num = 4294967295 (0xffffffff)

num+1은 INT_MAX 범위를 벗어나므로 0x 1 0000 0000 => 0x 0000 0000 => 0x0이 된다.

 

#include <stdio.h>
int main(void) {
	int n;
	n = 0x7fffffff;
	
    printf(“n = %d (0x%x)\n", n, n);
	printf(“n + 1 = %d (0x%x)\n", n + 1 , n + 1);
	
    return 0;
}

/* EOF */
The output of which is:
n = 2147483647 (0x7fffffff)
n + 1 = -2147483648 (0x80000000)

n은  signed int에서 MAX 양수이다.

%d => 2147483647 (0x7fffffff)

n + 1 => 0x8000 0000가 된다. 이진수로 변환하면 1000 0000 0000 0000 0000 0000 0000 0000(2)이므로 

2의 보수 표현으로 -2147483648이 된다.

 

- 더하기/빼기/곱에서도 발생함

#include <limits.h>

 unsigned int ui1, ui2 , usum ;

/∗ Initialize ui1 and ui2 ∗/

 usum = ui1 + ui2 ;
 
/* ui1 = 0x7FFFFFF2
 ui2 = 0x6FFFAAAA */

0x7FFF FFF2 + 0x6FFF AAAA를 덧셈하면 INT범위를 벗어나 오버플로우 발생

signed int si1, si2, result;

 /* initialize si1 and si2 */

 result = si1 – si2;
 
 /* si1 = 0xFFFFBEEF
 si2 = 0x6ABCCAFE */

0xFFFF BEEF - 0x6ABC CAFE를 하면 음수 - (양수)로 언더플로우가 발생한다.

(0xF = 1111(2) = 15)

signed int si1 , si2 , result;

/∗ Initialize si1 and si2 ∗/

result = si1 * si2 ; 

곱의 경우도 오버플로우가 발생한다

 

#include <limits.h>
	int i;
	unsigned int j;
	
    i = INT_MAX; // 2,147,483,647
	i++;
	
    printf (“i = %d \n”, i);
	
    j = UINT_MAX; // 4,294,967,295
	j++;
	
    printf (“j = %u \n”, j);

출력

i = -2,147,483,648

j = 0

i=INT_MAX의 경우 +1을 하여 오버플로우

j=UINT_MAX의 경우 +1을 하여 오버플로우로 0이됨

 

#include <stdio.h>
	int main(void){
	int l, x;
    
	l = 0x40000000;
	printf("l = %d (0x%x)\n", l, l);
    
	x = l + 0xc0000000;
	printf("l + 0xc0000000 = %d (0x%x)\n", x, x);
    
	x = l * 0x4;
	printf("l * 0x4 = %d (0x%x)\n", x, x);
    
    x = l - 0xffffffff;
	printf("l - 0xffffffff = %d (0x%x)\n", x, x);
    
	return 0;
}

l = 1073741824 (0x4000 0000)

l + 0xc000 0000 = 0 (0x0)

(0xc = 12 = 1100(2) 따라서 최상위 비트가 1이므로 음수임)

 

l * 0x4 = 0 (0x0)

0x4를 곱하면 큰수가 됨

 

l - 0xffffffff = 1073741825 (0x4000 0001)

(0xFFFF FFFF = -1)이므로 +1이 됨

 

 

 

 

언더 플로우 예시)

i = signed int, j = unsigned int

i = INT_MIN;
i--;
printf("i = %d\n", i);

j = 0;
j--;
printf("j = %u\n", j);

INT_MIN에서 1을 빼면 언더플로우로 INT_MAX가 된다.

0x80000000 + 0x7fffffff (-1) = 0x7ffff ffff

( 0x0000 0001 = 1 -> 0xFFFF FFFE (1의 보수)-> 0xFFFF FFFF(2의보수) =-1)

 

CWE-190의 오버플로우 예시)

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

malloc은 오버플로우 발생 가능성이 있다.

sizeof(img_t)*num_imgs => img_t 구조체의 크기는 10kb이고 num_imgs는 부호가 있는 정수이다.

num_imgs가 큰 수일 경우, 이 둘을 곱하면 오버플로우가 발생할 수 있다. 

 

xmalloc => malloc을 개선함(요청된 메모리의 크기를 할당할 수 없으면 에러를 표시하고 프로그램을 종료함)

xmalloc(nresp*sizeof(char*)) 

만약 nresp 가 1,073,741,284이면

1,073,741,284 * 4 = 4,294,967,296로 

UINT_MAX = 4,294,967,295, 최대범위를 벗어나게 된다.

따라서 오버플로우로 response는 0이 되고,

밑의 for문은 4,294,967,296번 루트를 돌고

response[i] = packet_get_string(NULL)은 실제 할당받은 공간은 작은데 계속 할당을 받게 된다.

while loop에서 bytesRec을 체크하는데

byteRec은 byteRec에 getFromInput(buf + bytesRec)을 더한 값이다.

만약 byteRec이 오버플로우가 일어나 작은 값이 된다면 문제가 발생한다.

 

int myfunction(int *array, int len) {
	int *myarray, i;
	
    myarray = malloc(len * sizeof(int)); /* [1] */
	
    if(myarray == NULL){
		return -1;
	}
    
	for(i = 0; i < len; i++) { /* [2] */
		myarray[i] = array[i];
	}
    
	return myarray;
}

[1]경우 len이 큰 경우 오버플로우가 일어나 작은값이 malloc 될 수 있다.

따라서 [2]에서 len은 크지만 myarray가 작으므로 문제가 발생한다. 

 

int catvars(char *buf1, char *buf2, unsigned int len1, unsigned int len2) {
	char mybuf[256];

	if ( (len1 + len2) > 256 ) { /* [3] */
		return -1;
	}
    
    memcpy(mybuf, buf1, len1); /* [4] */
    
    memcpy(mybuf + len1, buf2, len2);

	do_some_stuff(mybuf);
	
    return 0;
}

[3]경우 len1 + len2가 값이 커서 오버플로우가 발생하면 if문을 통과하고 밑의 memcpy를 수행한다.

buf1에서 len1크기만큼 mybuf로 복사하므로 버퍼 오버플로우가 발생할 수 있다.

첫번째 memcpy를 통과하더라도 두번째 memcpy에서도 위에서 설명한 것처럼 문제가 발생할 수 있다.

만약 len1이 0x0000 0002이고 len2가 0xffff ffff라면 

첫번째 memcpy는 통과하지만 두번째 memcpy에선 len2가 unsigned int로 바뀌면서 큰값이 되어 버퍼 오버플로우가 발생할 수 있다.

* Integer Overflow를 막는 방법 예시

(자바 코드)

int sum(int a, int b) {
	int c = a + b;

	if (a > 0 && b > 0 && c < 0)
		throw new MyOverfowException(a, b);
	return c;
}


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

c = a+b 연산을 한 후

a>0, b>0이고 c<0이면 오버플로우가 발생한 경우므로 예외 처리한다.

밑의 곱의 경우도 마찬가지이다.

 

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

-덧셈 연산을 한 후 오버플로우가 발생하는지 체크한다.

if (value + 1 > value)

     value ++; 

 

-덧셈 연산전 오버플로우가 발생하는지 확인한다.

a > INT_MAX - b 이면 오버플로우이다.

(max값에서 b를 뺀값보다 a가 크다면 a와 b를 더하면 max값 보다 클것이다.

즉, 현실세계에서 보면  a+b > INT_MAX 이므로, 합이 MAX값을 넘어서 컴퓨터에선 오버플로우가 발생)

(INT_MAX는 limit.h에 선언되어 있다.)

if( a > INT_MAX - b)

     return -1;

 

다른 방법으로 SW개발 전 단계에서 고려하는 것이다.

-요구사항 분석 : 안전한 언어나 안전한 컴파일러를 사용한다.

-설계 : 안전한 라이브러리나 프레임워크를 사용한다(SafeInt or IntegerLib)

-구현 : 사용하는 범수가 범위내에 있는지 확인한다. 최대값과 최소값 범위를 체크한다.

 

#include <stdio.h> /* ex2.c ʹ loss of precision */
int main(void) {
	int m;
	short s;
	char c;
    
	m = 0xcafe76ef;
	s = m;
	c = m;
    
	printf("l = 0x%x (%d bits)\n", m, sizeof(m) * 8);
   	printf("s = 0x%x (%d bits)\n", s, sizeof(s) * 8);
	printf("c = 0x%x (%d bits)\n", c, sizeof(c) * 8);
    
	return 0;
}

changmin@ubuntu:~/Desktop/c$ ./aaa 
l = 0xcafe76ef (32 bits) 
s = 0x76ef (16 bits) 
c = 0xffffffef (8 bits) 

s는 16bit=2byte만을 표현할 수 있고, 0xcafe 76ef가 4byte=32bit이므로  상단 16bit가 날라가고 0x76ef가 남는다.

0x7 => 0111(2)이므로 최상단 비트가 1이 아니여서 양수이다. 따라서 앞자리는 모두 0이된다. (%x시 32비트로 출력이된다. => 이부분은 아직 잘 이해하지 못하엿다....)

따라서 0x000076ef

 

c는 8bit=1byte를 표현할 수 있으므로 0xef만 남게된다.

0xe = 14 = 1110(2)로 최상단 비트가 1이므로 음수이다. 

0xe는 음수임을 나타내므로 결과 값의 나머지 앞 부분도 음수 표시위해 1, 즉 모두 f가 된다.

 

728x90
반응형

'공부 > 보안' 카테고리의 다른 글

보안개론 정리(2) - software 취약점  (0) 2021.04.16
INT OVERFLOW/UNDERFLOW (2) - 부호버그(Signedness Bugs)  (0) 2021.04.15
보안개론 정리(1)  (0) 2021.04.13
Role-Based Access Control (RBAC)  (0) 2020.12.08
Access Control MAC (2)  (0) 2020.12.03
블로그 이미지

아상관없어

,