linux_0x03 Basic BOF
0x03 - Basic BOF & 메모리 보호 기법
안녕하세요! Sulla임돠….
이번에는 저번 시간에 이어서 Basic BOF 또는 Direct EIP overwrite 라고 불리는 기본적인 BOF와 메모리 보호 기법에 대해서 알아 보겠습니다.
Basic BOF 준비
지난 시간에 RET구역 즉, EIP 값이 저장되는 구역에 “HACK “ 저장됨을 확인했습니다. 이 말은 공격자가 RET 구역에 원래 있어야 할 값을 임의의 값으로 변경이 가능 하다는 말이죠. 초기에 레지스터의 역할을 간략히 설명 드릴때 EIP 레지스터의 역할은 “다음 명령의 주소를 가리킨다”라고 했었죠. 이것을 노려서 공격자는 EIP에 원하는 주소값을 저장해 공격을 이어갑니다. 명령어가 종료 된 후 EIP 주소를 참조해 공격자가 미리 지정한 주소로 이동하여 명령을 이어가죠. 이 때 공격자는 shell을 따 내는것이 최종 목표입니다. 물론 굳이 shell 아니더라도 원하는 행위만 미리 구현해 둔다면 실행이 될 것 입니다.
말이 주절주절 길어졌는데 간략한 그림으로 표현 해보죠.
지난 포스팅에서 다룬 내용과 비슷합니다. 함수 프롤로그 과정이 지난 후에 버퍼 크기만큼 공간을 확보 후 함수가 진행되고 함수 종료후 ret 구역에 저장되있는 주소를 참조하여 이동 합니다. 위 그림과 같이 Shellcode가 저장되있는 임의의 주소가 될 것이며 해당 주소 이동 후 Shellcode가 실행 되어 공격자가 원하는 행위를 하게 되는 것이죠!!
그런데, Shellcode, Shellcode, 쉘코드 하는데 이게 뭐하는 놈일까요? 명칭의 기원은 모르나 역할만은 분명합니다. 이름 그대로 shell을 따내는 code이죠. 위에서도 계속 말했듯이 공격자가 원하는 행위를 수행하도록 하는게 목표라고 했죠. 파일을 생성하든, 삭제하든 passwd, shadow 및 각종 config 파일 과 같은 중요 파일을 대상으로 뭔가를 행위를 하도록 하면 됩니다. 단지 이 모든걸 한방에 할 수 있는건 shell을 따냄으로서 해결됩니다. 물론 권한 문제가 필요하기에 root권한이 필요할겁니다. 쉽게 표현 하자면 감기약, 두통약, 치통약, 변비약 등등 이것저것 챙겨 먹을 필요없이 만병통치약 하나 먹으면 된다는 것이죠. shellcode는 이미 인터넷에 다양하게 생성되있습니다. 해당 코드를 따와서 사용해도 되고, 직접 만들어서 사용해도 됩니다. shellcode 작성 방법에 대해서는 나중에 따로 다뤄보도록 하곘습니다.
그럼 Shellcode가 뭔지는 알았으니 이놈을 써서 직접 BOF를 해봐야 하는데 문제가 하나 있습니다. 지금까지 설명한 내용처럼 사용자가 메모리의 주소를 알고 있는 상태 즉, 메모리의 주소값이 고정값이라면 공격이 쉬워 지기에 이를 방어하기 위해서 메모리의 주소값을 랜덤으로 부여 합니다.(ASLR) 다음 사진을 보시죠.
붉은 박스에서 보여지듯 ESP의 메모리 주소는 계속 바뀝니다.(사실 잘 보시면 완전 랜덤화는 아닙니다….끝에만 바뀌고 있죠…) 즉, 프로그램이 시작 될 때마다 할당받는 메모리의 주소값이 바뀌는 것이죠. 그럼 어찌 해야 할까요?? 가장 간단한 방법은 메모리의 주소가 안바뀐다면?? 메모리의 주소가 항상 고정된 주소를 사용하는 뭔가가 있지 않을까??라는 생각을 하게됩니다. 그런게 뭐가 있을까요?? 고정된 메모리 주소값을 가지며 쉽게 다룰 수 있는???
황경변수는 항상 고정된 메모리 주소값을 가집니다.
환경변수
환경변수의 역할은 OS가 필요한 정보들을 미리 메모리에 등록해서 필요할떄 바로바로 해당 내용들을 참조합니다. python, java 또는 서버 구축 후에 환경변수를 등록해서 쉽게 구동시키는 작업을 생각한다면 이해가 되실겁니다.(실행경로를 환경 변수 등록하는 과정….JAVA_HOME이 대표적이죠.)
미리 등록해서 번거롭게 설치된 위치로 이동해서 실행한는 것이 아니라 간편하게 바로바로 실행할 수 있도록 경로를 환경변수로 등록 하는것이죠. 자연스럽게 아셨겠지만 이 환경변수는 사용자가 직접 등록해서 사용할 수 있다는것도 아실겁니다. 미리 운영체제에 필요한 내용들이 등록되있고 사용자의 필요로 인한 내용을 등록해서 사용도 가능하죠.
그렇다면 환경변수의 주소를 어떻게 알 수 있을까요? 다음과 같은 코드를 작성해서 우리가 원하는 환경변수의 주소값을 출력 가능합니다.
코드는 간단합니다. argv[]로 입력받은 값을 addr에 저장 합니다. 이떄 addr은 char형트로 포인터가 붙어있어서 주소값 형태로 저장 됩니다. 위 코드를 작성 하고 해당 프로그램을 실행하면 다음과 입력한 환경변수의 주소값을 출력해줍니다. 또한 환경변수의 주소값이 변하지 않는다는것 또한 확인 가능합니다.
거의 다 되갑니다…. 이제 공격의 환경을 만들어 볼까 합니다. 우리는 shell을 따내는것이 목표이지만 더 정확히 말하자면 root 권한을 얻는것이 목표입니다. shell을 따도 root 권한이 없을경우 제한적인 권한을 가지게 되죠. 그런데 setuid가 설정 되어있는 경우 이런 문제가 해결 됩니다. 이유는 setuid가 설정된 파일을 실행하는 동안은 해당 파일의 소유권자와 동일한 권한을 부여 받습니다. root 계정이 a파일을 setuid 권한으로 설정헀다면 일반 계정이 해당 파일을 실행하는 동안은 root 권한을 부여 받는것이죠. 실제로 root권한을 부여 받는지 확인을 위해 일반 계정으로 변경 해줍니다.
추가로 언어팩도 아래와 같이 변경해 줍니다. 이떄 계정은 일반 계정으로 변경하고 진행 해줍니다. 기본 셋팅은 UTF-8로 설정되어 있는데 영어의 경우 한 글자 당 1바이트를 사용합니다. 추후에 쉘코드 입력시 값이 1바이트를 넘는다면 의도된 값으로 입력되지 않기 때문에 2바이트를 사용하는 한글로 변경 해야하죠. (ASCII (7bit 인식)와 UNICODE(8bit 인식)의 인식 차이입니다.)
쉽게 말해 공격수행에 문제가 생기기 때문에 바꿔주는 것입니다. 환경변수를 바꾸는 방법은 export 명령어를 사용하며 아래와 같이 진행됩니다.
지금부터는 일반계정(sulla)으로 변경하여 진행 합니다. 환경은 준비가 끝났고 이제 본격적으로 일반계정으로 BOF를 시도하여 root 권한의 shell을 따보도록 합시다.
Basic BOF 진짜 시작
시작 전에 정리하자면 아래와 같습니다.
- 고정된 메모리 주소를 사용하는 환경변수를 이용해 shellcode를 환경변수에 저장하고 해당 황경변수의 주소를 알아냅니다.
- 쉘코드가 저장된 환경변수의 메모리 주소를 취약한 bof1 파일에 삽입합니다.
즉, 공격 페이로드는 이런 모습으로 구성될 것 입니다 [44byte 잉여값] + [4byte 환경변수 주소]
그럼 1번 과정 부터 먼저 해보도록 합시다.
환경변수를 설정해야 하니 export 명령어를 사용해서 환경변수 명과 내용을 채워보도록 하죠. 공격용 쉘코드는 다음과 같습니다.
\x31\xc0\x89\xc3\xb0\x17\xcd\x80\xeb\x0f\x5e\x31\xc0\x50\x89\xe2\x56\x89\xe1\x89\xf3\xb0\x0b\xcd\x80\xe8\xec\xff\xff\xff\x2f\x62\x69\x6e\x2f\x73\x68
이 길다란 코드를 아래와 같이 환경변수로 저장하고 주소까지 확인해줍니다.
bof라는 이름으로 환경변수를 만들었습니다. 환경변수 내용은 /bin/sh 를 실행하는 내용이며 해당 환경변수의 주소를 확인하니 0xbffffee9의 주소값을 가지고 있습니다. 이로써 1번 과정을 끝났습니다. 이제 2번 과정을 진행해 봅시다. 그림으로 표현하자면 아래와 같이 보여지겠네요.
공격 페이로드는 저번 시간에서 작성했던 방식과 동일합니다.
단, 지난 시간에서는 버퍼 영역에 “A” 40byte와 sfp 영역에 “B” 4byte 마지막으로 ret 영역에 “HACK“를 저장 헀습니다. 즉, 잉여 데이터 44byte를 입력했고 ret에 원하는 4byte를 입력하는 과정이었습니다.
지금부터는 “HACK“이 아닌 위에서 알아낸 환경변수의 주소값을 입력 할것입니다. 물론 sfp 영역의 “B” 또한 잉여 데이터기에 “A”로 통일해서 입력 합니다.(그동안은 각 영역을 구분하기 위해 A B HACK 로 나눠서 입력했습니다…..) 주의점은 환경변수의 주소값은 리틀엔디안 형식으로 입력해 줍니다. 그럼 아래와 같이 공격 페이로드가 완성 됩니다.
./bof1 `python -c 'print "A"*11 + "\xe9\xfe\xff\xbf"'`
작성된 페이로드를 입력 하게 되면 아래와 같이 shell이 떨어지며 가장 중요한 점은 shell의 uid권한이 root권한임을 확인할 수 있습니다!!!!
이렇게해서 드디어 root권한의 shell을 따내는데 성공 헀습니다. 앞에서 설정헀던 setuid를 해체한 상태로 진행하면 uid가 root가 아닌 일반 계정(sulla)으로 설정 됨을 확인하실 수 있습니다.
메모리 보호 기법
위에서 말헀던 메모리의 주소를 랜덤화 하는 메모리 보호기법을 ASLR(Address Space Layout Randomization) 이라고 부릅니다. ASLR 외의 다양한 보호 기법이 있는데 알아보도록 하죠.
- ASLR : 프로세스가 실행될 떄마다 메모리의 주소를 랜덤화 합니다.
- DEP/NX bit : Stack/Heap등 메모리 영역에 실행 권한을 없애 코드 실행을 막습니다.( 쉽게 말해 Stack/heap 영역에 저장된 Shellcode의 실행을 막는다는 겁니다.)
- CANARY : 메모리에 무결성 확인을 위해 버퍼 영역과 SFP 영역 사이에 특정한 값을 설정 합니다. (네트워크의 패킷 무결성을 위한 패리티 비트와 비슷한 역할이라 생각하면 편합니다.)
- ASCII-Armor : 공유 라이브러리 영역 상위에 NULL값을 삽입하여 호출을 막습니다.
이외에도 더 있습니다. 이러한 메모리 보호 기법이 하나, 둘 적용 되면서 우회기법이 생기고 묻히고 생기고 묻히길 반복합니다. 다들 아시겠지만 많이 보이던 패턴이죠. (취약점 > 대응 > 우회 > 대응 >우회 > 대응 > 우회 > 대응 > 우회 > 대응) * 무한 반복
앞으로는 Basic BOF를 중심으로 어떤 보호기법이 생겼고 그 우회 방법은 어떤것이 있는지 또 그 우회 방법의 어떤 대응법이 있고 또 다시 대응법의 새로운 우회ㅂ…..후……
천천히 오늘 했던 방식처럼 하나 하나씩 알아보도록 하겠습니다. 고생하셨습니다. 다음 포스팅까지 차근차근 준비해서 다시 뵙겠습니다.
감사합니다! 뿅!