시스템 해킹 실습 -3. Integer Overflow 정수 오버플로우

2024. 7. 17. 22:45Information Security 정보보안/Vulnerability Analysis 취약점 분석

728x90

배울내용: 

시스템 해킹

단위공격 기술

Integer Overflow 발생 원리

Integer Overflow 실습 

Integer Overflow  방지 방법

메모리 취약점

정수 오버플로우란? 

Unsigned 자료형

 

 

사진 출처: https://www.sdsolutionsllc.com/forcedentry-and-integer-overflows/

 

 

 

 

논리 취약점중 하나인 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 를 뛰어넘게 만들수가있었다 

 

 

 

728x90