Training 2020 #3 ( Direct EIP )
이번 포스팅에는 전에 사용했던 취약한 소스코드를 가지고 HelloSCP! 라는 메세지 박스를 띄우는 시간을 가져볼 것이다.
시나리오 확인
전에 사용했던 이 소스코드를 가지고 실험을 할건데 이 코드가 어떻게 취약한지 다시 복습을 해보자.
메모리 구조를 보면 대략 위와 같은 과정을 거칠텐데 이 과정에서 readbuf에 입력하는 문자열의 길이가 500을 넘게 된다면 다음과 같은 상황이 발생한다.
다음과 같은 메모리 코럽션(?)이 일어난다. 여기까지 전 포스팅에서 다뤘었는데 이 취약한 부분을 가지고 다음과 같은 시나리오를 작성 할 수 있다.
다음과 같이 실행하고 싶은 SHELL CODE 를 끼워넣어 내가 원하는 실행 결과를 가져갈 수 있다. 우리는 여기서 사용할 쉘 코드로 HelloSCP라는 메세지 박스를 사용할 것이다.
일단 이러한 시나리오를 가지고 있고 A를 가득 채웠을 때 메모리에서는 어떠한 일이 일어나는지 직접 눈으로 확인하기 위해 immunity debugger를 사용하도록 하자. 또한 지금 사용할 것은 아니지만 후에 imm에서 mona라는 모듈을 사용하기 위해 모듈을 추가해주도록 하자.
mona module 추가해주기
폴더안에 mona.py라는 파일이 있을텐데 이 파일을 복사해주도록 하자.
위 사진을 따라서 Windows 키를 누르고 imm 을 검색 후 Immunity Debugger를 우클릭 해준뒤 Open file location을 눌러 파일 경로를 열어주도록 하자.
그럼 이런 창을 볼 수 있는데 immunity debugger는 플러그인들을 PyCommands 안에 관리한다. 폴더안으로 들어가자.
그리고 다음과 같은 경로에 왔으면 mona.py 를 붙여넣기 해주면 된다.
immunity debugger 사용하기
처음 실행했을 때 위와 같은 화면을 볼 수 있을텐데 이곳은 프로그램에 엔트리 포인트이다. 우리가 봐야 할 부분은 위 소스코드인 reader.exe의 main 부분이므로 그 부분을 찾아가자.
저 부분을 누르면 참조하는 모듈을 확인할 수 있다. 눌러보자.
그럼 이런 화면이 나오는데 우리가 볼 프로그램은 reader 이니 reader를 더블클릭하면 그 프로그램의 위치로 이동한다.
이제 이런 화면을 볼 수 있으면 성공적으로 따라온 것이다.
일단 프로그램을 실행 시켰을 때 이 부분에서 멈추도록 BreakPoint ( F2 ) 를 걸어주도록 하자. 제대로 걸렸는지 확인하려면 제일 왼쪽 칸이 푸른빛으로 색이 바뀌었으면 BP( Break Point ) 가 제대로 걸린것이다.
그 다음 우리가 확인해야할 부분은 입력을 받는 fgets 함수 부분이다. 이 부분을 찾아 BP를 걸어주도록 하자.
또한 이 부분이 중요한데 저 부분은 strcpy를 이용하여 readbuf에 있는 값을 printbuf에 옮겨가는 부분이다. ( 더 자세히 보면 위에 MOV CL,BYTE PTR SS:[EBP+EAX-9C4] 부터 JMZ SHORT reader.0040100 부분을 보면 인자를 하나씩 옮겨 문자열의 길이만큼 루프를 돌아 인자를 하나씩 넘기는 것을 알 수 있다. 이해가 안가면 디버깅 해보자. ) 마찬가지로 BP를 걸어주자.
그 후 실행하면 아무일도 안일어날텐데 지금은 넘겨준 인자가 없어서 그렇다.
그럼 이제 인자를 넘겨주고 immunity debugger를 이용하여 확인해보고 결과창을 보도록 하자. 저는 세번의 실습을 걸칠건데 정상적인 값을 입력했을 때, 비정상적 ( 길이가 긴 문자열 ) 인 값을 입력했을 때, 비정상적 ( 쉘 코드로 작성된 메세지 박스 실행 명령어 ) 인 값2를 입력했을 때 로 나누도록 하겠다.
리버싱에 관심 없으신 분들은 실습2 부터 보아도 무관할거 같다고 생각한다.
실습1 (정상적인 값 입력 )
인자로 넘겨줄 문자열을 test.txt로 만들어 reader.exe와 같은 위치에 위치시켜 두자. 나는 간단하게 abcd를 인자로 넘겨주기로 했다.
그리고 imm을 실행시킨 뒤 인자로 test.txt를 전해주도록 한다.
그리고 위에 과정과 똑같이 BP를 걸어주면 되는데 가상 머신을 종료하거나 로컬 컴퓨터를 재시작하지 않은 이상 자동으로 똑같은 위치에 BP가 걸려있을 것이니 생략하도록 하겠다.
그럼 F9를 눌러 실행시켜 따라가도록 하자.
우선 신경 쓸 곳은 위 3가지 부분인 것 같다. 하나씩 짚고 넘어가 보자.
함수의 프롤로그 ( 여기서는 reader.exe 의 main 함수 프롤로그 )
text reader 라는 문자열 출력 ( printf 함수 호출 )
인자의 유무
- 인자가 없는지 확인
- Usage : reader.exe filename 을 화면에 표시해줌
- 프로그램 종료
- 위 과정에 대해 자세히 설명하고 싶은데 지식이 부족하여 이렇게 밖에 설명 못 함 ㅎ;;; 일단 이 정도로만 알고 있자.
- fopen 함수 사용 ( 인자 불러옴 )
- fgets 함수 사용 ( 입력 시작 )
대략 이 정도로 알고 있자. 그럼 다시 F9를 눌러 실행시켜 보자.
그럼 다음과 같이 fget 함수 부분에서 멈출텐대 ( 당연히 여기다 BP를 걸어놨으니 여기서 멈추겠죠? ) fgets 함수는 EBP -9C4 부분에서부터 입력을 받는것을 알 수 있다. 확인해보자.
여태까지 좌측 상단 화면을 봐왔다면 이제 우측 하단 화면을 봐주자. 우측 하단에는 메모리 상태를 보여주는데 여기서 우클릭 한 뒤 Go to EBP 를 눌러주면 현재 EBP 위치로 이동할 수 있다. 눌러주자.
성공적으로 EBP 위치로 이동했다. ( 우측 상단을 보면 Register 값을 알려주는데 여기에 나오는 EBP 값과 동일 {물론 같은거니까 ^^; })
화살표가 가리키는 부분 ( 메모리 주소가 써져있는 부분 0012FF44 )를 더블 클릭하면 위와 같이 주소를 상대적으로 볼 수 있다. 우리가 찾아야 할 부분은 fget가 입력을 받기 시작하는 부분 EBP-9C4 이다. 스크롤을 왔다갔다 하며 찾아보자.
EBP-9C4 부분을 찾았다. 우리가 예상한 대로라면 0012F580 부분에서 입력을 받기 시작할 것이다. 그럼 다시 F9를 눌러주면?
예상한대로 0012F580 부터 abcd가 들어갔다. 그리고 이를 통해 strcpy함수를 이용해 인자를 복사하여 입력받은 printbuf이 시작 위치도 알 수 있다.
또한 EBP-1F4 부분은 printbuf의 시작위치이므로 값이 옮겨가 저 부분에도 abcd가 들어가 있을것이다.
)
예상대로 이 곳이 바로 printbuf의 시작위치라는 것을 알 수 있다.
그 후 한번 더 실행시켜주면 reader.exe에도 abcd가 성공적으로 출력된 것을 확인할 수 있다.
실습2 ( 길이가 긴 문자열 넘겨주기 )
우리가 예상하는 시나리오는 위에 사진과 같다. ( 같은 사진 여러번 우려먹네 ㅎㅎ;; )
readbuf 에서 A를 500자 이상 넘게 인자를 넘겨주면 printbuf에서는 크기가 500임에도 불구하고 넘어서 입력을 받아버린다. 따라서 원래 정상적으로 있어야할 SFP( Stack Frame Pointer )는 물론 RET 마저 덮어씌워진다.
그렇게 되면 함수가 끝난 뒤 돌아갈 주소를 찾지 못하여 프로그램이 비정상 종료될 것이다. ( 전 포스팅에 한 것을 그대로 할 것 )
저번에 짯던 이 익스코드를 실행시켜 A가 550개 입력되어있는 test.txt 텍스트 파일을 만들어준다. 그리고 imm에서 똑같이 test.txt를 인자로 넣고 실행시켜주자.
BP또한 전과 같이 걸어주자.
그 뒤 fgets 함수 부분까지 실행시켜주자. 여기서 입력을 받을 때 마찬가지로 EBP-9C4부분에서 부터 입력을 받을텐데 500자가 넘는 문자열을 받는다면 printbuf [EBP -1F4] 로 인자를 복사해가는 과정에서 인자가 SFP와 RET를 모두 덮어버릴것이라는 가설을 세우고 있다. 그럼 우리는 여기서 EBP의 값과 RET의 값을 알아놔야 한다. 알아보자.
전과 같이 Go to EBP 를 눌러 EBP 위치로 가보자.
그럼 이렇게 EBP위치로 올텐데 그 밑에 RETRUN to .... 라고 써져있는 부분이 바로 함수가 끝난 뒤 돌아갈 RET 값이 된다. 우리는 이 값이 덮어씌워지는지 확인하면 된다. ( EBP = 0012FF44 , RET = 0012FF48 )
성공적으로 덮어씌워진 것을 확인할 수 있다. 그럼 저 0012FF44까지 NOP + SHELL CODE + DUMMY BYTE 로 채워넣고 RET값의 printbuf의 위치값을 넣으면 함수가 끝난 뒤 원래 함수로 돌아가지 않고 우리가 설정해둔 메세지 박스가 뜨게 될 것이라 예측할 수 있다.
실습3 ( 메세지 박스 띄우기 )
위에 사진은 우리가 할 내용을 간략히 사진으로 표현해본 것이다. RET 변조 값에는 EBP-1F4 인 0012FD50이 들어갈 것이다.
reader_exploit2.py 라는 쉘코드를 포함한 익스코드가 이미 짜여져 있으니 열어서 BUF값 즉 printbuf의 위치만 0012FD50으로 바꿔주자. ( 초기값 = 00000000 으로 되어있을 것이다. )
그 후 똑같이 imm 을 실행시키고 test.txt를 인자로 주자.
그럼 다음과 같은 결과를 얻는다.
- 쭉 프로그램이 실행된다.
- RET 값에 0012FD50을 삽입하여 printbuf의 시작위치로 이동한다.
- NOP을 따라 쭉 미끄러져 내려온다.
- SHELL CODE를 만난다.
- SEHLL CODE를 실행한다.
이런 동작 과정을 거치게 된다.
여기까지 프로그램의 취약점을 이용하여 메세지 박스 띄우기를 실습해봤다.
이로 인해 알 수 있는 점은 프로그램의 본연의 의도 ( 여기서는 우리가 입력한 문자열을 그대로 출력해주는 프로그램 ) 를 벗어나 내가 원하는 실행 결과 ( 메세지 박스와 같은 결과 ) 를 가져올 수 있다는 점에서 위험하다는 것을 알 수 있다.
다음 포스팅에 봅시당.