* 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가 된다.
'공부 > 보안' 카테고리의 다른 글
보안개론 정리(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 |