2024. 7. 17. 22:45ㆍInformation Security 정보보안/Vulnerability Analysis 취약점 분석
배울내용:
시스템 해킹
단위공격 기술
Integer Overflow 발생 원리
Integer Overflow 실습
Integer Overflow 방지 방법
메모리 취약점
정수 오버플로우란?
Unsigned 자료형
논리 취약점중 하나인 Integer Overflow 즉 ,정수 오버플로우 라는 취약점이있다.
이번포스팅에는 이 공격에 대해서 알아본다
먼저 위에 코드를 실행하면 어떤 결과 값이 나올까?
정답은 -1 도 아닌 아래의 큰 값이 나오게 된다
메이플 스토리를 예시로 들어보자
한번쯤 10년전쯤 메이플스토리라는 인기RPG게임에는 풀메소라는게 있었다
이떄 최대 소지금액이 2147483647 이였다.
(필자도 한떄 메이플광인에 포스팅까지하는 사람이였다..(콤보디아의 블로그 <-- 검색하면 나온다) )
이러한 이유는 이게임은 32bit운영체제 컴퓨터는 2진수를 쓰기에 최대량은 10000000000000000000000000000000(2) 로 2^32 이기 떄문이다 이걸 자료형으로 나타내면 그게 바로 (Signed) int 이다
그런데 위에서는 이러한 2147483648도 아닌 4,294,967,295 가 나오는걸 볼수있는데 이것은
Unsigned Int 이기 떄문이다
둘의 차이점은 아래와 같다
- Signed Integer (signed int):
- 범위: 음수와 양수를 모두 포함할 수 있다.
- 비트 구성: 가장 왼쪽 비트는 부호 비트로 사용되며, 0이면 양수, 1이면 음수를 나타낸다.
- 예시: 32비트 signed int는 -2,147,483,648부터 2,147,483,647까지의 값을 표현할 수 있다.
- Unsigned Integer (unsigned int):
- 범위: 양수만 포함할 수 있다.
- 비트 구성: 모든 비트가 숫자를 나타내는 데 사용되며, 부호 비트가 없다.
- 예시: 32비트 unsigned int는 0부터 4,294,967,295까지의 값을 표현할 수 있다.
그렇기 때문에 만약에 최고 숫자인 4,294,967,295 를 넘으면 0 으로 그리고 0 에서 -1 하면 4,294,967,295 로 돌아오게 되는 것을 이용한 것이다.
코드작성
integer.c
/*
gcc ./integer.c -o integer
*/
#include "integer.h"
int init(){
if(storage != NULL){
printf("[-] Storage has already.\n");
return -1;
}
storage = (struct data*)malloc(sizeof(struct data));
storage->buf = (uint8_t*)malloc(MAX_STORAGE_SIZE);
memset(storage->buf, 0, MAX_STORAGE_SIZE);
storage->len = 0;
printf("[+] Storage inited. @ %p\n", storage->buf);
return 0;
}
int save(){
struct input * input = getUserInput();
if(input == NULL){
return -1;
}
if(0 > parseData(input)){
printf("[-] Input not saved.\n");
return -1;
}
printf("[+] Input saved.\n");
return 0;
}
void flushbuf(){
int c;
while ((c = getchar()) != '\n' && c != EOF);
}
struct input* getUserInput(){
if(storage == NULL){
printf("[-] Storage is NULL.\n");
return NULL;
}
struct input *input = (struct input*) malloc(sizeof(struct input));
input->key = (uint8_t*)malloc(MAX_KEY_LEN);
memset(input->key, 0, MAX_KEY_LEN);
input->str = (uint8_t*)malloc(MAX_STR_LEN);
memset(input->str, 0, MAX_STR_LEN);
printf("input key >");
fgets(input->key, MAX_KEY_LEN, stdin);
size_t keySz = strlen(input->key) - 1;
input->key[keySz] = '\0';
printf("input str >");
fgets(input->str, MAX_STR_LEN, stdin);
size_t strSz = strlen(input->str) - 1;
input->str[strSz] = '\0';
if(keySz == 0 || strSz == 0){
printf("[-] Invalid input.\n");
return NULL;
}
input->size = keySz + strSz;
return input;
}
int parseData(struct input *input){
size_t curSz = storage->len;
size_t inputSz = input->size;
size_t keySz = strlen(input->key);
size_t strSz = strlen(input->str);
if (inputSz > MAX_STORAGE_SIZE - 2 - curSz){
printf("[-] Input too Large\n");
return -1;
}
storage->buf[curSz++] = ',';
memcpy(storage->buf + curSz, input->key, keySz);
curSz += keySz;
storage->buf[curSz++] = '=';
memcpy(storage->buf + curSz, input->str, strSz);
curSz += strSz;
storage->buf[curSz] = '\0';
storage->len = curSz;
free(input->key);
free(input->str);
free(input);
return 0;
}
int menu(){
int idx = 0;
printf("[MENU]\n");
printf("1. Init\n");
printf("2. Save\n");
printf("3. Exit\n");
scanf("%d",&idx);
flushbuf();
switch(idx){
case 1:
init();
break;
case 2:
save();
break;
case 3:
printf("[-] Good Bye~\n");
exit(0);
default:
break;
}
}
int main(){
printf("This Data Storage System.\n\n");
while(1){
if(storage){
printf("[!] Storage size : %lu\n", storage->len);
}
menu();
}
}
integer.h
#include <stdio.h>
#include <stdint.h>
#include <string.h>
#include <stdlib.h>
#define MAX_STORAGE_SIZE 64
#define MAX_KEY_LEN 16
#define MAX_STR_LEN 32
struct data{
uint8_t * buf;
size_t len;
};
struct data *storage = NULL;
struct input{
uint8_t *key;
uint8_t *str;
size_t size;
};
int parseData(struct input *input);
struct input* getUserInput();
int init();
int save();
int menu();
코드펼치기
위에 integer.c 를 아래처럼 컴파일하여 실행해보자
gcc -c integer.c -o integer
그리고 아래처럼 실행해보면 된다
./integer
그러면 아래의 Menu 3가지가 나오고 각각의 기능은 1번으로 시작하고 2번으로 저장하는데 3번으로는 완전히 나가진다
아래와 같이 2번을 클릭후에 input key 와 str 을 넣으면 storage size 가 11이 되는걸 알수있다
그러나 1234 + 12345 를 해도 사이즈가 11 이 아닌 9가 나와야 정상이다
이는 코드를 보면 알수있다.
- curSz는 저장 버퍼에 현재 저장된 데이터의 크기(길이)
- inputSz는 입력된 데이터의 총 길이
- MAX_STORAGE_SIZE는 저장 버퍼의 최대 크기
그리고 중간에 storage -> buf[curSz++] = ',' ; 하고 storage->buf[curSz++] = '=';
이걸 보면 아까 입력했던 input key 1234 와 12345 가
아래처럼 붙여 지게 된다
,1234=12345
그러면 아까 길이가 9 가 아닌 11 인걸 이해할수가 있다.
뿐만아니라 inputSz > MAX_STORAGE_SIZE - 2 - curSz는 현재 저장 버퍼에 남은 공간이 입력 데이터를 저장하기에 충분한지를 확인하는 조건이라는 것 또한 알수있다. ( ',' 하고 '=' 를 빼준값)
따라서, 이 조건은 다음과 같이 계산된다:
- MAX_STORAGE_SIZE - 2: 최대 저장 버퍼 크기에서 2를 뺀 값. 여기서 2는 쉼표(,)와 등호(=)를 위한 공간.
- MAX_STORAGE_SIZE - 2 - curSz: 현재 저장 버퍼에 이미 사용된 공간을 제외하고 남은 공간.
이 조건을 만족하지 않으면 (inputSz가 MAX_STORAGE_SIZE - 2 - curSz보다 크면), 새로운 데이터를 저장하기에 충분한 공간이 없다는 의미다.
그런데 그러면 안되지않을까 싶지만 한가지 기본을 생각해보자
scanf(“%s” , &name);
printf(“My name is %s”, name);
이게 실제로 코드가 되면
Printf 가 출력할 문자가 %s 를 출력하는게 아니라 name을 최종문자열을 크기를 미리 알수가없다
(컴파일하기 전까지는 알수없음)
→
이럴때 사용하는게 동적 메모리 관리 방식인데 이 동적메모리가 할당되는 부분이 HEAP 이라고 함 , 이걸 동적메모리 관리자(할당자) 한테 말하는데 그게 malloc(40) 이란 함수를 써서 요청하고 HEAP 을 할당함
즉 Malloc 으로할당받고 Free 로 풀어준다
그런데 malloc 으로 할당받고 free 하는 부분과 위에 부분을 잘이용하면 최대크기인 64를 넘길수 있다
gdb 를 열어서 어떻게 작동하는지를 보니 아까 봤던 -2 부분에
key 에다가 1을 넣고 str 에 1 을 똑같이 넣었더니 64를 뛰어 넘고 좀더 시도를 계속해서
Storage 를 64 를 뛰어넘게 만들수가있었다
'Information Security 정보보안 > Vulnerability Analysis 취약점 분석' 카테고리의 다른 글
시스템 해킹 실습 -6 . Format String Attack 포맷스트링 공격 (0) | 2024.07.19 |
---|---|
시스템 해킹 실습 -5 . Type Confusion 데이터 유형 혼동 (1) | 2024.07.19 |
시스템 해킹 실습 -4 . Uninitialized 미초기화 취약점 (0) | 2024.07.18 |
시스템 해킹실습 - 2. Command Injection 명령어 삽입 (0) | 2024.07.17 |
시스템 해킹실습 - 1. Buffer OverFlow (버퍼 오버플로우) (0) | 2024.07.15 |