새소식

인기 검색어

TL

21/03/23 TL. BOF를 이용한 공격(1)

  • -

overflow를 이용한 공격을 해보자.

pwnable.kr/play.php

 

http://pwnable.kr/play.php

 

pwnable.kr

해당 링크에 들어가보자. 이 링크에선 해킹 워게임을 진행해볼 수 있다.

이번에 해볼 공격은 overflow를 이용한 것인데, 기본적으로 메모리, 스택 등 OS적인 요소에 대한 선험적인 지식을 필요로 한다.

 

간단히 설명해 보자면,

스택 : 지역변수, 함수가 작동하기 위해 사용되는 메모리. 임시로 데이터를 저장해서 연산처리 하는 영역

힙 : 동적으로 할당되는 영역 ex)malloc

bss : 초기화되지 않은 전역변수

text : 함수

data : 초기화된 전역변수

이다.

 

너구리 녀석을 클릭해서 bof 문제를 보자.

각 카드들 중 원하는 문제를 누르면, 지시사항을 준다. 여기서는 이 2개의 파일을 wget으로 받은 후, 아래를 실행시켜 원하는 모양새로 만들면 통과이다.

 

간단하게 분석해 보자면, run하면서 func을 실행한다. 이 때 key에는 deadbeef가 들어간다.

여기서 gets 는 scanf와 동일하다고 보면 되는데, bof에 취약하다.

만약 key값이 cafebabe면 shell을 실행한다.

 

현재, overflowme는 32byte를 할당받은 상태이다. 만약 여기에 A라는 40byte를 집어 넣는다면??

그 위 8byte 배열에 A가 덮어씌어질 것이다. 즉, 다른 메모리 값을 변조시킬 수 있다.

설명하기 위한 조교님의 그림

이 공격으로 지역변수나 return값을 악의적으로 덮어씌워 공격할 수 있다.

현재 int key는 지역변수이다. 따라서 gets라는 취약 함수를 통해 overflow를 이용해 값을 변조해서 key값을 수정하는 걸 목표로 하자.

 

근데 이 코드를 직접 컴파일해선 안 된다.

BOF를 막기 위한 3가지 방어 기법이 있다.

1. Nx(non excutable) -> 스택에서 코드를 exe할 수 없게 하는 방어 기법이다. bof로 return을 악의적으로 수정할 수 있기 때문이다.

2. 카나리 -> 랜덤한 값을 buffer에 넣어서 해당 값이 변조됐으면 bof가 일어났다고 판단하고 프로그램을 종료한다.

3. ASLR -> 메모리(스택, 힙)의 주소를 random하게 부여해서 주소를 알기 힘들게 만든다.

이러한 방어 기법들이 있는데, gcc -o bof.c bof 이런 식으로 컴파일하면 자동으로 이런 방어기법을 적용해서 컴파일한다.

 

이제 bof.c로 다시 넘어오자. 우선, 우리는 int key의 위치가 어디에 있는지 알 수 없다.

다시 등장한 조교님의 그림

해당 그림 처럼, stack pointer 4byte, return addr 4byte, overflowme 32byte를 넘어가면 key 영역이므로, 40 + 0xcafebabe를 입력하게 하면 해결할 수 있는 거 아냐? 라고 할 수도 있다. 그러나, 컴파일 과정에서 최적화 등 이유로 메모리 값을 바꾸거나 그럴 수도 있기 때문에, 코드만 보고 이론적으로 접근하는 것은 매우 힘들다.

 

따라서 바이너리 값을 보고 바꾸는 과정이 필요하다. 따라서 gdb라는 disassemble 파일을 보고 까보자.

우선, intel form으로 보는 것이 편하니까 intel form으로 설정해주자.
disas main이라는 것은, main을 disassemble 하라는 의미이다.
func의 내용

컴파일은 고급 언어를 한 번에 보고 실행파일(exe, elf)로 바꾸어 준다. 그런데, 고급언어 -> 어셈블러 -> (opcode)기계어 이런 과정으로 진행된다. 실행파일(binary file)을 다시 원래대로 바꿔주는 것을 disassemble이라고 하고 대표적인 툴이 gdb이다. 이 파일을 잘 보고 공격을 해보자.

 

여기서 몇가지 참고해야할 점들이 있다.

1. return addr는 disassemble할 때 생략된다.

2. 스택에선 함수마다 구역을 차지한다.

3. 그 구역은 ebp로 시작해서 ret로 끝난다.

4. 함수를 call로 실행하기 전에, return 주소와 스택 포인터를 저장해 놓는다.

4. return되고 나면 해당 메모리 영역을 재사용하기 위해 해당 메모리를 지워버린다.

 

다시 main으로 돌아가서, 위에서 설명한 대로 +3부터 +16은 func를 실행하기 위해 준비하는 과정이다.

그 바로 밑 mov eax, 0x0은, eax에 0을 넣는 과정인데, return 값에 0이 들어가서 함수가 종료된다고 생각하면 된다.

 

마찬가지로, func도 +3에서 +87이 함수의 내용이라고 볼 수 있다.

여기서 보면, +24, +35, +56, +70, +87에서 call하고 있다. 소스코드에선 4번인데, call을 5번하는 이유는 +75 아래 부분과 +3, +6 부분에선 카나리를 위한 부분이다.

따라서, 흰색 부분이 코드 내용이라 볼 수 있다. 여기서 call은 printf, gets, printf, system이라고 할 수 있다. cmp는 compare인데, cafebabe와 같은지 검사하는 부분이다.

 

이제, 우리가 예상한 대로 overflowme가 32byte를 그대로 할당할 것이냐?? 를 보자.

그 전에 또 알아야 할 게 있다.

ebp에서 bp는 base pointer을 의미하는데, 현재 스택 프레임에 대한 주소를 표시한다.

esp에서 sp는 stack pointer를 의미하는데, 현재 스택 주소를 표시한다.

e는 현재가 32bit 환경이라는 것을 의미한다.

따라서 +1에 mov ebp, esp는 스택 프레임 주소에 현재 스택 주소를 넣고 시작한다는 의미이다.

그 다음 usb esp, 0x48은 esp에 0x48만큼 빼겠다는 의미이다. 이는 십진수로 표현하면 72이다. esp가 0x48만큼 메모리를 할당하겠다는 의미이다. 즉, 32byte가 아니라는 의미이다. 더 클 수도 있다. 스택은 아래로 자라니까 빼는 것이 할당하는 것이라고 생각하면 된다.

 

36은 72 - 32 - 4에서 나온 것이다. 4는 call

즉, 이제 76byte + 0xcafebabe로 값을 변조시킬 수 있다.

 

이제 해결법을 알았으니 수행해보자.

b *func로 breakpoint를 걸고 r로 실행해보자.

이젠 offset이 아니라 주소로 나온다.

n으로 한 줄씩 넘어가면서 메모리 상태를 확인할 수 있다.

넘어가다 보면, eax에 값을 세팅한 다음에 call을 한다. 여기서 세팅하는 값은 인자(deadbeef)이다.

만약 인자가 2개다? eax, ebx를 쓴다. 3개다? eax, ebx, ecx를 쓴다. 이런 식으로. 4개다? edx까지. 5개 이상이다? stack에 저장해 놓고 함수를 호출한다. -> 함수 호출할 때, 일시적으로 저장할 수 있는 공간을 필요로 하기 때문.

 

그리고, ebp - 0x2c가 배열의 시작 주소라는 것도 알 수 있다. 여기서 다시 조교님의 그림을 보자.

12는 컴퓨터가 알아서 배정해 준 메모리이다.

만약에 stp(= ebp)가 0x100이라면, 이 밑으로 0x2c만큼 내려간다. 이 때, 크기는 44byte인데, 여기서 12는 컴퓨터가 알아서 배정해 준 메모리이다. 여기다가 stp, ret (4, 4) byte씩을 더하면 52byte가 나온다. 이제, 52 + 0xcafebabe를 입력하면 이제 exploit code를 짤 수 있을 것이다!!

 

이제 마지막이다.

"cafebabe"는 문자당 1byte이므로 총 8byte이다.

그런데, 0xcafebabe는 2개당 1byte라고 칠 수 있어서 4byte라고 볼 수 있다. 여기서 32bit는 4byte이므로,

컴퓨터가 한 번에 연산할 수 있는 수는(32bit이므로) 4byte라고 할 수 있다.

또, key는 int이므로 4byte이다.

 

따라서, 그냥 cafebabe라고 입력하면 문자열로 인식하므로, 인코딩을 통해 0xcafebabe로 만들어줘야 한다.

ASCII 코드표를 참고하자.

추가로, 리틀 엔디안으로 넣어야 하므로, 0xbebafeca로 입력해야 한다.

인코딩할 땐, python을 이용해서 넣어주자. python -c 'print"\xbe\xba\xfe\ca"'로 0xcafebabe를 넣어줄 수 있다!

 

이제 거의 다 왔다. 52만큼 의미없는 값을 넣어준 후, \xbe\xba\xfe\ca도 함께 넣어 실행해주자. 그런데, 이렇게 shell을 실행해 줄 때, shell은 입력값이 없으면 자동으로 종료되므로 shell을 열어놓기 위해 입력스트림을 열어놓아야 한다.

cat을 아무 인자도 안 넣고 실행해 주면, 입력스트림을 열고 입력값을 기다리므로 cat을 넣어주자.

 

정리해 보자면,

(python -c 'print"A"*52 + "\xbe\xba\xfe\xca"'; cat) | nc pwnable.kr 9000

를 입력해서 문제를 해결할 수 있다.

이제 flag를 볼 수 있다!

이제 flag에 있던 값을 집어넣어 보자.

로그인이 안 돼있으면, 로그인 하라고 뜨는데, 로그인 하고 다시 넣으면 5포인트나 준다.

 

추가로, bof 취약점을 이용해 공격하면, 카나리도 뚫을 수 있다. cat으로 입력 스트림을 열어 쉘을 실행시켰는데, 카나리가 체크하기 전에 쉘을 실행시켜놓은 상태이기 때문이다.

 

그럼 카나리가 쓸모가 없냐? 그건 또 아니다.

이 프로그램은 매우 취약한 코드로 구성돼있기 때문이다. 일반적으로, return addr가 카나리 다음으로 나오기 때문에 일반적인 buffer overflow 공격은 대처 가능하다. 

Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.