Southern Island

[ Pwnable ] 기본 BOF 시작하기 [해킹]

by 월루

《 유의사항! 》

해당 포스팅은 [해킹] 키워드가 적용된 포스팅입니다! 아래의 유의사항을 꼭 숙지해주세요!

1. 해당 포스팅을 통해 학습한 정보는 안전한 사회를 위한 긍정적 의도로만 사용될 수 있습니다.

2. 만약 실습을 원하실 경우 가상환경이나 허가된 범위에서만 실습을 진행해주세요.

3. 해당 포스팅을 열람하는 모든 인원은 위의 2가지 유의사항에 동의하는 것으로 간주됩니다.

 

BOF 시작하기 !

BOF는 BufferOverFlow의 약자이다, 말 그대로 버퍼를 할당된 공간보다 넘치게 사용하여 공격하는 해킹 기법이다.

함수나 메서드에서 사용하는 지역변수는 모두 스택이라는 버퍼를 사용한다, 또한 해당 스택에는 현재 함수가

종료된 뒤 돌아갈 주소 즉 RET이 담겨있다, 이에 대한 자세한 내용은 '스택 프레임'이란 키워드로 구글링 해보길 바란다 아무튼 BOF는 보통 RET를 원하는 주소로 덮어써서 원하는 쉘 코드나 함수를 호출한다.

 

BOF 발생 원인

BOF의 가장 큰 발생원인은 사용자의 입력을 받을 때 입력값의 최댓값을 확인하지 않고 입력받아 할당된 공간보다 데이터 넘쳐서 발생하는 경우가 많다, (물론 다른 경우도 있긴 하다) 이에 취약한 몇 가지 함수를 소개하겠다.

strcpy()
strcat()
gets()
fscanf()
scanf()
sprintf()
sscanf()
vfscanf()
vsprintf()
vscanf()
vsscanf()
streadd()
strecpy()
strtrns()

 

BOF 사전 준비

BOF 공격을 시작하기 전에 조사 및 준비해될 몇 가지 사항이 있다.

 

1. 우선 해당 프로그램이 BOF에 취약한지를 확인해 보아야 한다. 가장 확실한 방법은 의미 없는 값을 여러 번 넣어서 결과를 확인해보는 것이다.

상위 계정의 권한으로 Setuid가 설정되어있고, 입력값을 입력받는다.
'a'값을 약 600번 입력하자 'Segmentation fault'라는 메시지가 출력됬다.

'Segmentation fault'라는 메시지는 프로그램이 허용되지 않은 메모리 영역에 접근을 시도하거나, 허용되지 않은 방법으로 메모리 영역에 접근을 시도할 경우 발생한다, 따라서 해당 프로그램은 BOF에 취약하다고 확인할 수 있다.

 

2. BOF에 취약하다는 것이 확인되었으면 입력받은 데이터가 저장될 버퍼의 시작 주소와 RET까지의 거리를 알아야 한다 정확한 거리를 알고 있어야 RET에 원하는 주소를 덮어쓸 수 있다.

리눅스의 GDB 디버깅 툴을 활용하여 ASM 확인

프로그램의 내부를 확인해보니 'strcpy' 함수를 사용한다는 걸 알 수 있다. 'strcpy' 함수는 첫 번째 인자로 복사될 버퍼의 시작 주소가 오게 된다. 따라서 복사될 내용이 저장될 버퍼의 시작 주소는 [EBP-520]이 될 것이다.

따라서 우리가 입력한 데이터가 저장되는 공간의 버퍼의 시작 주소부터 RET거리가 524Byte라는 걸 알 수 있다.

왜 520이 아니라 524냐고 궁금해 할 수 있는데, 이는 스택 프레임 구조를 정확하게 알고 있다면 알 수 있다.

EBP가 가리키는 4바이트 공간에는 자신을 호출한 함수의 EBP 주소 값이 들어가 있기 때문에,

4바이트 아래에 RET의 주소가 들어가 있다. 어떻게든 우리는 RET까지의 거리가 524라는 사실을 알게 되었다.

 

3. 마지막으로 RET에 넣을 주소 값, 즉 실행될 함수나 쉘 코드가 필요하다. 우린 전 게시글에서 만든 쉘 코드를 사용해 보도록 하겠다. 쉘코드를 인자 값으로 전달하여 주소를 구하고 실행하는 방법도 있다, 하지만 RET까지의 거리에 따라 쉘 코드의 길이도 맞춰야 하며 주소도 정확하게 구해야 하기 때문에 번거롭다. 그래서 우리는 환경변수를 이용해 볼 것이다.

$LANG 변수값을 ko_KR.euckr로 변경하고 있다.

우선 우린 환경변수의 LANG값을 변경해 주어야 한다. 이 값은 기본값으로 'en_US.UTF-8'로 되어있다.

하지만 우린 BOF 공격을 진행할 때 주소 값이나 쉘 코드를 넣어야 한다. 이는 기계어로 아스키코드표에 명시되어 있지 않다. 따라서 환경변수의 언어 설정을 꼭 변경해주어야 우리가 원하는 기계어 코드가 정상적으로 삽입된다.

영어로 설정되어 있다면 아스키코드에 명시되지 않은 데이터 값은 모조리 무시되거나 오류가 발생한다.

어쨌든 언어 설정이 완료되었다면 이젠 쉘 코드를 환경변수에 설정해주겠다.

code라는 이름으로 쉘코드 환경변수 선언

이때 사용된 쉘 코드는 https://southern-island.tistory.com/12에서 확인이 가능하다. 이때 쉘코드 앞의 '\x90"이라는 값이 무려 1000개나 들어가는 걸 볼 수 있다. '\x90'은 기계어로 NOP이라는 명령어다. NOP은 아무런 기능도 수행하지 않고 다음 명령어를 실행한다. 따라서 NOP 1000개는 아무런 의미가 없어 보인다. 저기서 사용한 NOP은 NOP 슬라이딩이란 기법이다. 우리는 쉘 코드가 저장된 환경변수의 주소를 구해야한다. 하지만 환경변수의 주소는 실행되는 프로그램이름길이나 환경에 따라 조금씩 차이가난다. 따라서 환경변수 주소를 구했던 프로그램과 BOF 공격을 진행할 프로그램의 이름의 길이가 동일하다면 문제가 없지만 차이가 발생한다면 원하는 쉘코드를 실행하지 못 할지도 모른다, 따라서 NOP 명령어를 1000개나 넣음으로써 주소가 살짝 밀리더라도 문제없이 쉘코드가 동작할 수 있도록 만든 것이다. 이제 쉘 코드를 환경변수에 선언했다면 해당 환경변수의 주소 값을 알아야 한다.

// env.c

#include <stdlib.h>

int main(int argc, char* argv[0]) {
		// 인자값이 하나인지 확인
        if(argc != 2) {
                printf("Usage : %s <ENV NAME>\n", argv[0]);
                return 0;
        }

        char* addr;
        // 해당 이름의 환경변수를 얻어옴
        addr = getenv(argv[1]);
        // 얻은 데이터의 포인터를 16진수로 출력
        printf("located is %p\n", addr);

        return 0;
}

해당 프로그램을 작성하고 공격 프로그램의 'bof' 이름과 길이를 맞춰주기 위해 이름을 'env'로 컴파일한다.

쉘코드의 주소는 0xbffffb95이다.

그렇게 쉘 코드의 주소 또한 구하였다. 이제 모든 준비가 끝났다, 공격을 시작해보자!

 

BOF 공격 시작!

프로그램이 종료되지 않고 sh 쉘이 실행되었다, id를 확인해보니 root 권한인걸 알 수 있다.

해당 프로그램은 root의 권한으로 Setuid가 설정되어있다. 따라서 공격에 성공한다면 root 권한을 얻을 수 있다.

공격을 진행할 때 사용한 계정은 아무런 권한이 없는 tester 계정이다. 우선 './bof'의 인자로 전달한 내용을 자세히

알아보자, 'a'(1바이트)를 우리가 사전 준비에서 구한 524 거리 즉 524개만큼 작성하였다. 따라서 다음에 올 문자열은 RET에 덮어써질 예정이다. 그 후 RET에 덮어써질 내용은 우리의 쉘 코드 주소이다, 아까 구한 쉘 코드의 주소를 입력해준다. 참고로 내가 진행한 환경의 컴퓨터는 리틀 엔디안 방식으로 데이터를 저장한다. 따라서 데이터 또한 리틀 엔디안에 맞도록 뒤에서부터 주소를 적어준다. 엔디안에 대한 자세한 내용은 구글링을 해보길 바란다.

이렇게 기본 BOF에 성공하게 되었다. 실제로는 여러 메모리 보호 기법(NX, ASLR, CANARY 등)에 의해 해당 기본 BOF는 성공하지 못할 확률이 높다, 따라서 다음 게시글은 RTL, ROP 등 여러 메모리 기법을 우회할 수 있는 BOF 공격에 대해 알아보도록 하겠다 !

 

마치며...

제가 글을 쓰는 가장 큰 이유는 배운 내용을 정리하고 나중에 다시 공부하기 위해서입니다, 따라서 잘못된 정보가 포함되어 있거나 중요한 내용이 빠져 있을 수 있습니다, 잘못된 내용이나 빠진 내용이 있는 경우 댓글로 말씀해주시면 정말 감사드리겠습니다!

블로그의 정보

남쪽의 외딴섬

월루

활동하기