반응형

2022.06.22 - [Pwnable/이론] - Stack Buffer Overflow

 

여기서 3번째 예제로 보았던 실행흐름조작을 활용하여 셸을 획득해보자.

 

1. 취약점 분석

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie
#include <stdio.h>
#include <unistd.h>

void init()
{
    setvbuf(stdin, 0, 2, 0);
    setvbuf(stdout, 0, 2, 0);
}
void get_shell()
{
    char *cmd = "/bin/sh";
    char *args[] = {cmd, NULL};
    execve(cmd, args, NULL);
}
int main()
{
    char buf[0x28];
    init();
    printf("Input: ");
    scanf("%s", buf); //입력값의 최대 길이를 정해주지 않았기 때문에 BOF발생
    return 0;
}

위 예제에서 발생할 수 있는 취약점은 scanf의 "%s"로 인한 BOF이다.

 "%s"는 문자열을 입력받을 때 사용하는데, 입력의 길이를 제한이 없고, 띄어쓰기, 탭, 개행 문자 등이 들어올 때까지 계속 입력을 받는다.

위 코드에서는 "%s"를 통해 문자열을 입력받아 길이가 0x28인 배열에 입력 받은 값을 넣고 있다. 

프로그래머의 설계대로라면 사용자는 0x28개의 문자열만 입력해야 하지만,여기서 "%s"만 사용하여 입력을 받기 때문에  실수로 또는 악의적으로 버퍼의 크기보다 큰 데이터를 입력하면 오버플로우가 발생하게 된다.

 

이것을 방지하기 위해서는 정확히 n개의 문자만 입력받는 "%[n]s"의 형태로 사용해야 한다.

 

이외에도, C/C++의 표준 함수 중 strcpy, strcat,sprintf 등이 있는데,

이 함수들의 특징은 버퍼를 다루면서 길이를 입력하지 않는 함수라는 것이다.

코드를 작성할 때는 버퍼의 크기를 같이 입력하는 strncpy, strncat, snprintf, fgets, memcpy 등을 사용하는 것이 좋고,

프로그램의 취약점을 찾을 때는 취약한 함수들을 유의해서 살펴보는 것이 좋다.

 

C계열의 프로그래밍 언어에서는 문자열의 끝을 널바이트로 구분하는데 위의 빨간색으로 되어 있는 함수들은 전부 널바이트가 있다는 가정을 하고 구현하였기 때문에 함수는 널바이트가 있을때까지 연산을 하게 되어 배열의 크기를 넘어서도 계속해서 인덱스를 증가시킨다.

인덱스 값이 배열의 크기보다 커지는 현상을 Index Out-Of-Bound라고 하며,

해당 버그를 발생시키는 취약점을 Out-Of-Bound(OOB)취약점이라고 한다.

 

 

2. 취약점 확인(트리거)

선언된 버퍼보다 큰 값을 넣었을 때 생기는 실행 오류

 

이와 같이 segmentaion오류가 발생하는 이유는 프로그램이 입력값을 선언한 버퍼보다 많은 값을 입력받게 되어 return address가 잘못된 메모리 주소로 변경되어 변경된 접근했기 때문에 오류가 발생한 것이다.

정상적인 main함수 리턴 주소

 정상적으로는 main함수의 return 주소는 0x7fffff5e77fd로 되어 있지만,

 

버퍼오버플로우를 통해 return 주소를 덮어쓰게 되면,

비정상적인 main함수의 리턴 주소

위처럼 main함수가 돌아갈 곳이 입력했던 값인 0x4141414141414141로 변경된 모습이다.

 

 

3. 익스플로잇

이제 실제로 취약점이 작동하는지 확인 하였으니, 이것을 통하여 원하는 주소를 넣어보자.

우선은 해당 버퍼가 스택 프레임의 어디에 위치하는지를 알아야 한다.

 

디버거로 보면,

scanf 함수를 호출 할때, 인자가 2개 사용되는데, 하나는 "%s" 그리고 또 하나는 자기가 입력한 값이다.

자기가 입력한 값은 main함수의 rbp로부터 0x30 떨어진 위치부터 시작하여,

내가 만약 "abcd"를 입력했다면 0x30 = a, 0x29 = b 이렇게 1바이트씩 저장 되는 것이다.

 

스택 프레임의 구조를 떠올려 보면, rbp에 스택 프레임 포인터(SFP)가 저장되고, rbp+0x8에는 반환 주소가 저장되기 때문에 여기서 입력으로 스택 버퍼 오버 플로우를 발생 시킨다면 메인 함수의 rbp인 부분을 덮어 쓸 수 있게 된다.

그림으로 보니까 더 쉽네

 버퍼의 크기 rbp-0x30과 main함수의 리턴주소값이 시작되는 위치인 rbp+0x8를 합치면 main함수와 버퍼 사이에는 0x38만큼의 거리가 있다는 것이므로, 0x38개의 쓰레기 데이터 뒤에 실행하기를 원하는 함수의 주소 등을 입력해주면, 함수의 흐름을 원하는 곳으로 바꿀 수 있게 된다.

 

이 예제에서는 프로그램 내에 get_shel() l이라는 셸을 실행시키는 함수가 있으니 main함수의 리턴 주소를 여기로 변경해보자.

gdb로 확인해본 결과 get_shell() 함수는 0x4006aa 주소에 있는 것을 알 수 있었고 이것은 리틀 엔디언이기 때문에 잘 맞춰서 \xaa\x06\x40\x00\x00\x00\x00\x00 이것으로 페이로드를 작성하여 전달하면된다.

 

아래처럼

파이썬 출력값을 인자로 전달 해줄 수도 있고, 

 

 pwntools를 사용하여 리틀엔디언 변환 필요 없이 p64함수를 사용하여 변환 하여

사용 할 수도 있다.

반응형

'Pwnable > 이론' 카테고리의 다른 글

Stack Buffer Overflow  (0) 2022.06.22
Shell Code  (0) 2022.06.20
Calling convention  (0) 2022.06.20