'과거의 컴퓨터 공부/attack-1.BOF'에 해당하는 글 2건

반응형

소스를 살펴보면 이전 포스팅과 비교했을때 flag 와 password_b 의 변수위치만 바뀌엇다 

소스코드를 살펴보자.


이전 포스팅과 비교해서 그저 변수만 바뀌었을 뿐인데 메모리에서 어떤 일이 생기는지 확인해보자.

break부분은 전번 포스팅과 같은 부분이다.

확인해 보면 flag가 password_b 앞에 있음을 알 수 있다. 이것은 password_b 오버플로우로 인해 flag가 절대로 덮어써질수 없다는 것을 의미한다. 밑의 그림을 확인해보자.

~> 그러면 우리는 전번 소스와 같은 방법으로 오버플로우를 일으킬 수 없다는 것을 의미한다. 하지만 이번 포스팅에서 이제 다른 방법을 제시할 것이다.

위에서 말한대로 flag가 password_b 앞에 있으므로 오버플로우로 auth_flag 변수가 훼손되지 않았다.


하지만 C코드에서는 볼 수 없는 다른 실행 제어 포인트가 존재한다. 실행 제어 포인트는 바로 모든 스택 변수들 다음에 위치하고 있다. 그래서 쉽게 덮어 씌어질 수 있는데 이 메모리는 모든 프로그램의 실행에 반드시 필요하고,  그래서 모든 프로그램에 존재한다. ~>이 부분이 덮어씌워지면 프로그램 충돌이 발생한다(segmentation fault)


다른 포스팅에서 살펴봣는데, 스택은 프로그램에서 사용되는 다섯 개의 메모리 세그먼트 중 하나이다.

~> 이전 포스팅에서 살펴봣듯이, 함수가 호출되면 스택프레임이라는 구조가 스택에 푸시되고, EIP 레지스터가 함수의 첫 번째 명령으로 점프한다. 각 스택 프레임은 그 함수의 지역 변수와 리턴 주소를 갖고 있다. 그래서 나중에 EIP가 복구될 수 있다(이전 포스팅에서 생각해 보라던 부분이다). 함수가 끝나고 나면 스택프레임은 스택에서 팝되고 리턴주소를 이용해 EIP를 복구한다. 이런 모든과정은 보통 컴파일러가 처리한다.

check_authentication() 가 호출되면 새 스택 프레임이 스택의 main() 스택프레임 아래로 푸시된다. 이 스택 프레임 안에 지역 변수, 리턴 주소, 함수 인자가 있다.

<윗주소>

main() 함수의 스택프레임 

*password(함수 인자) 

리턴 주소(ret) 

저장된 프레임 포인터(SFP) 

password_b 변수 

return_value 변수  

<아랫주소>


요소들을 디버거로 살펴보자


왜 브레이크포인트 순서를 이렇게 정햇는지는 소스의 진행을 보면 이해가 갈것이다

이제 진행하면서 메모리들을 확인해보자

첫번째 break는 main()에서 check_authentication()을 호출하기 바로 전이다. 이 시점에서 esp는 0xbffffa10이고 스택의 맨위를 가리킨다.

다음 break까지 계속해 check_authentication()안으로 들어가면, ESP가 이제는 스택에 푸시된 check_authentication()의 스택프레임을 위한 메모리 공간만큼 작아진다. flag와 password_b를 살펴보면 그 변수들이 스택 프레임 안에 위치하는것을 확인 할 수 있다.

마지막 break까지  계속하면 함수가 호출될 때 스택프레임이 스택에 푸시된다. 스택은 낮은 메모리 주소방향으로 커지니까 이제 스택포인터는 0xbffff9d0으로 64바이트 만큼 작아진다. 

~>c.f) 스택의  몇개의 값들은 단지 컴파일러가 붙인것이고,일부는 '저장된 프레임 포인터'라 불린다. 또한, 컴파일 할때 최적화를 위해 -fmoit-frame-pointer 플래그를 사용했다면, 프레임 포인터가 스택프레임에서 사용되지 않는다.

여기서 0x080484bb는 ret 주소이고 0xbffff9b7은 30개의 A를 포함한 문자열을 가리킨다(왜 해당값이 이런의미가 있는지 생각해보자. 이번 포스팅에서의 과제이다. 마찬가지로 계속 생각해봐도 이해가 되지 않는다면 비밀댓글이나 비밀방명록으로 물어봐 주길 바란다.! 힌트를 주자면 스택 프레임의 리턴 주소 위치는  스택 프레임이 어떻게 생성됬는지 이해하면 알 수 있다. 이 과정은 함수 호출 전의 main()함수에서 시작된다.

check_authentication() 함수로 넘어와서 계속 진행된다. 첫번째 명령은 스택 프레임을 위한 메모리 저장을 끝마친다. 이 과정은 다른 포스팅에서 봣듯이 '함수 프롤로그' 과정이다. 

다음 두번째 명령은 저장된 프레임 포인터를 위한것이고, 그다음 명령은 ESP에서 0x38을 뺴는 명령이다. 그래서 함수의 지역변수를 위해 56바이트가 저장된다. 또한, 리턴 주소와 저장된 프레임 포인터는 이미 스택에 푸시되어 있고, 64바이트 스택프레임의 추가 8바이트가 바로 SFP 와 RET 이다.

함수가 끝나면 leave와 ret 명령은 스택 프레임을 지우고 EIP를 스택 프레임에 저장된 리턴 주소로 설정한다. 이 과정은 함수 호 출 때마다 발생한다.

여기서 리턴주소의 몇바이트가 A의 값(\x41)로 변조되었다. 하지만  저장된 리턴주소의 몇바이트가  덮어써져도 프로그램은 여전히 그 값을 이용해 EIP를 복수 하려한다. 실행이 임의의 장소로 점프해버리면 충돌이 발생한다.

하지만 덮어씌워진 리턴주소가 임의의 값일 필요는 없고 덮어쓰기를 제어할 수 있다면 실행을 특정장소로 점프시킬 수 있을 것이다. 이 부분을 이용하면 되는 것이다!


다음 포스팅에서는 점프될 장소를 정하는 방법에대해서 알아볼 것이고, 이어서는 버퍼오버플로우로 결과적으로 쉘코드를 실행시키는 방법에 대해 알아볼 것이다.


반응형

'과거의 컴퓨터 공부 > attack-1.BOF' 카테고리의 다른 글

BOF-(1)간단한 소스로 BOF 맛보기  (0) 2014.03.17
,
반응형

쉘코드를 만드는 것에 대한 내용은 BOF 챕터의 포스팅에서는 일절 언급하지 않습니다. 쉘코드에 대한 지식이 필요하다 싶은 경우, 쉘코드 챕터로 넘어가셔서 쉘코드에 대한 내용을 숙지하고 오시기 바랍니다.

다른 포스팅에서도 말햇듯, 이하 존칭은 생략합니다.

버퍼 오버플로우(=버퍼 오버런)

버퍼 오버플로우 취약점은 컴퓨터 초기부터 지금까지 쭉 발생되고 있으며, 대부분의 인터넷 웜은 전파를 위해 버퍼 오버플로우 취약점을 이용한다.

C는  하이레벨 프로그래밍 언어지만 자체적으로 데이터 무결성을 검사하는 기능이 없고, 프로그래머 직접 데이터 무결성을 검사해야 한다. C 컴파일러에서 직접 데이터를 무결성을 검사했다면 어떻게 됫을까? 당연히 속도가 엄청나게 느려졌을 것이다. 또한 프로그래머가 프로그램을 세세하게 제어하게 할 수 없게 됬을것이고 언어도 복잡해졌을 것이다.

정리해보면, C는 단순한 언어여서 프로그래머가 프로그램을 원하는 대로 제어하고 효율적으로 만들 수 있다는 장점이 있다. 그러나 한편으로는 프로그래머가 신경 쓰지 않을 경우 버퍼 오버플로우와 메모리 누수 현상이 일어날 수 있다는 단점이 있다. 즉, 어떤 변수가 메모리에 할당되어 있을 경우 그변수의 내용이 메모리 경계를 벗어나지 않는지 검사하는 내부 안전장치가 존재하지 않는다. 예를 들면 프로그래머가 8바이트 크기의 버퍼에 10바이트를 넣을 수도 있다. 그렇게하면 프로그램이 잘못된 연산으로 종료되는데 우리는 이것을 '버퍼 오버플로우(=버퍼 오버런)'이라 부른다.

위와같은 예로 첫번쨰 포스팅에서는 여러분들이 오버플로우를 공부하는데 의지를 꺾지 않기 위해 기초적인 예시를 가지고 살펴보려 한다



이 프로그램은 패스워드를 인자로 받아서 check authentication() 함수를 호출한다.

check_authentication()함수는 다음과 같이 두개의 패스워드를 허용하고, 둘중 하나가 사용된다면 접근 허용을 의미하는 1을 리턴한다.(소스코드를 한번 쭉 훑어보면 어떤 코드인지 이해가 갈것이다.)

소스를 보고 이해한대로 소스는 아주 잘 작동하고 있다.

하지만 우리가 원하는건 이렇게 잘 작동하는 프로그램을 보고 기분좋아 하는것이 아니다.

우리가 해볼 것은 이 프로그램을 오버플로우 시켜 예상치 못한,올바르지 않은 패스워드를 허용하게 하는 것이다.

우선 결과를 한번 보자 A를 엄청나게 집어넣었더니 다음과 같이 access complete 이 떳다.

와 신기하다! 라는 말보다는 왜 이렇게 access complete가 나올까라는 의문을 가져야한다.

gdb로 분석을 해보도록 하자.

여기서 우리는 9번과 16번에 break를 걸것이다. 왜  이 부분에 break를 걸까? 

여러분들에게 주는 2번째 과제다.. 마찬가지로 생각해보고 이해가 되지 않는다면 방명록이나 댓글을 남겨주면 답을 주도록 하겟다.

이제 gdb로  break를 건 부분을 하나하나 진행해 가면서 메모리를 조사해보도록 하겟다.

<1번 break>

첫 번째 break는 strcpy()바로 전에 있다. password_b 포인터를 조사해보니 0xbffff9e0에 존재하고, 초기화 되지 않은 임의의 값들이 들어있다. 또한 flag변수의 주소를 조사해보니 0xbffff9fc에 존재하고 값은 0이다. print 명령으로 계산해보면 flag는 password_b의 28 byte 뒤에있음을 알수있다.  따라서 password_b의 메모리 블럭안에서 flag를 찾을수 있다 (0x00000000)


<2번 break>

strcpy()이후 디버깅을 계속해 다음 break에 도착했다. 여기서도 메모리를 주소하면 password_b가 오버플로우 되어 flag의 첫 두바이트를 0x41로 바꿔 버렸다. 궁극적으로 프로그램은 이 값을 16705라는 정수로 처리하게 된다.

오버플로우 후에는 check_authentication함수가 0대신 16705를 리턴한다. if 구문에서 0이 아닌 값은 모두 인증되는 것으로 처리했으므로 프로그램은 접근 허용 쪽 구문을 실행한다.

위의 예제에서는 덮어씌어진 flag변수가 바로 실행 제어 포인트여서 위와 같이 아주 쉽게 오버플로우 되어있지만 실제로 오버플로우 기법은 이렇게 쉬운 기법만은 아니다. 다음 포스팅에서는 password_b와 flag변수를 앞뒤만 한번 바꾸어 볼것인데 어떤 일이 일어날지에 대해 포스팅을 할 것이다. 어떻게 변할지 한번 생각해보자!


반응형
,