여태까지와 마찬가지로 힌트를 읽으면서 시작.
아주아주 전형적인 bof문제의 형식이고 아주아주 쉬운 수준이다.
----------------------------------------------------------------------------------------------------------------
음... 이후 부분을 어떻게 넘어갈지 참 고민을 많이 했다.
혹시 간단한 디버깅도 해본적 없는 처음 공부하는 사람이 ftz를 공부하며 이글을 접하지도 않을까 하는 고민이다.
내 블로그는 구글 검색해도 안나오는 허접한 블로그이긴 하지만...
혹시나 미래에는 내 지인중 누구라도 초심자가 이 글을 보면서 공부할 수도 있지 않을까라는 생각을 했었다.
그러나 한편으로는 아무도 안보는 블로그의 있을지 없을지 모르는 초심자를 위해 처음부터 다 설명하는게 오바라는 생각도 들었다.
사실 나는 뒷부분도 이미 공부가 되어있는 상태라 대강 프로그램이 어떻게 돌아가는지 알고있는 상황이고
이 블로그는 누구에게 설명하기 위해서 작성한다기 보다는 내 공부한 내용을 정리하기 위해 작성하는 것이기 때문이다.
고민한 결과 운영체제에서 프로그램의 작동 원리 전반을 다 다루기에는 하나의 포스팅에 부담이 너무 크므로...
프로그램이 스텍을 어떻게 활용하는지 + 디버깅을 위한 32비트 레지스터들의 전반적인 역할을 위주로 어느정도 모르는 사람도 이해할 수 있게끔 써보자는 결론에 도달했다.
그래서 그런 쓰잘데기없는 기우의 결과로 쓰잘데기없는 설명을 붙이자면
프로그램을 실행해서 메모리에 올라가 있을때는 프로세스라고 부른다. 이 프로세스들은
이 유명한 그림과 같은 구조를 가진다.
text 영역에는 프로그램의 코드, 즉 기계어로 된 명령어들이 들어간다.
data영역에는 전역변수가 들어간다.
그렇다면 bof파일에서 buf나 buf2와 같은 변수들은 어디로 들어가냐하면 바로 stack의 영역으로 들어가게 된다.
이 문제에서는 필요하지 않지만 추가로 설명하자면 char* buf와 같이 포인터를 선언하고 malloc으로 buf에 동적인 영역을 할당하게 되면 힙영역에 문자열이 들어갈 공간을 할당하게 되고 그것을 가르키는 4바이트 주소가 buf라는 변수에 들어가게 되는 것이다.
아무튼 스텍으로 돌아와서 이 스텍이라는 놈은 그림처럼 높은주소에서 낮은주소로 자라난다. 거꾸로 자란다고 표현한다 보통.
보통 디버깅을 할때 하나의 함수를 어셈블리어로 쭉 풀어보면.. 함수의 프롤로그라고 해서 push ebp/ mov ebp, esp를 한다음 꼭 sub esp, 0x~~ 이런식으로 esp에서 어느정도를 빼주는데 이게 바로 스텍의 공간을 확보하는 행위이다.
설명을 위해 아주 간단한 이런 프로그램을 작성하고 디버거를 이용해 디스어셈블 해보았다.
맨 위의 두줄이 함수의 프롤로그 부분이고 그다음 esp에서 0x8만큼을 빼는것이 보인다.
main+16과 main+23을 보니 ebp-4 부분에는 10인 0xa가, ebp-5에는 c의 아스키코드인 0x63이 들어간 것이 보인다.
즉 변수 a, b가 각각 ebp-4, ebp-5에 들어갔다는 것이다. 이부분까지 함수의 스텍 변화를 한번 그려봤는데..
이렇게 되는것이다. 여기서 프롤로그 이후 움직이지 않는 저 ebp를 베이스포인터라고 하고 스텍이 pop, push되면서 따라다니는 저 esp레지스터를 스텍 포인터라고 한다. 더 자세한 다른 레지스터들이나 프로그램 원리에 대한 공부는 읽는사람 자신에게 맡긴다. 구글은 위대하다.
아무튼 이 길고 장황하고 재미없는 설명에서 중요한 점은 스텍에서 변수들의 위치는 ebp를 기준으로 얼마만큼을 뺀 지점이고, 실제로 사용할때도 ebp-5, ebp-4 이런식으로 주로 사용한다는 것이다.
한가지 더 사소한 디버깅 팁은 call 명령어를 기준으로 보는 것이다. 하나의 call 명령어로부터 다음 call 명령어까지는 주로 push, mov등의 명령어로 스텍에 뭔가를 집어넣는 작업이 들어가는데 이렇게 스텍에 들어가는 것들이 바로 다음에 call할 함수에서 사용할 인자라는 것이다.
진짜 정말정말 귀찮으므로 여기까지만 하고 직접 이 bof라는 파일을 디버깅하는 것을 보면 어느정도 이해에 도움이 될 것이다.
----------------------------------------------------------------------------------------------------------------
소스를 그대로 긁어서 /tmp 밑에 새로운 bof파일을 컴파일 한 후 gdb로 디버깅을 해보자.
위와같이 먼저 bof.c를 vi로 만들어준후 gcc로 컴파일해주고 gdb에서 bof를 연다.
그다음 내편한대로 인텔 문법으로 설정해준 뒤 main을 디스어셈블 해보았다.
세번째 줄에 보면 sub esp, 0x28을 해주는 것이 보이는데 스텍을 사용할만큼 할당해주는 것이다 0x28 즉 40바이트정도의 스텍이 할당되었다.
대충 call 명령어를 위주로 살펴보면 fgets 함수를 call하기 전에 eax로 ebp-40의 주소가 들어가고 eax를 push 해주는 것을 알 수 있다.
fgets에 인자를 넘겨주는 일련의 과정인데 위 소스에서 fgets에 인자로 buf가 들어가는 것을 확인했으니 ebp-40부분이 buf변수에게 할당된 부분인 것을 유추할 수 있다. (그전에 push된 0x28과 ds:0x8049698은 각각 숫자 40과 stdin의 주소다. fgets(buf, 40, stdin);이었으므로 뒤의 인자가 먼저 push된다는 것을 알수있다.)
마찬가지로 비슷한 과정을 거쳐서 strncmp로 넘어가는 ebp-24라는 부분이 buf2에 할당된 부분이라는 것 역시 유추할 수 있겠다.
그러면 buf는 ebp-40에 할당되었고 buf2는 ebp-24에 할당되었으므로 두 변수 사이에 16만큼 차이가 있는것을 알 수 있다.
스텍구조를 그림으로 그려보면
대충 이렇게 될것이다.
위와같은 스텍 구조에서 바운더리가 10byte짜리 buf 공간에 fgets가 40바이트나 입력을 받아버리니 오버플로우가 가능해진다.
buf2에 go를 넣는것이 목적이므로 16바이트를 아무 글자나 채워준뒤 뒤에 두개의 바이트를 go로 넣어주면 된다.
사실 aaaaaaaaaaaaaaaago하면 되지만 간지나게 파이썬 파이프를 사용해주자.
파이프를 이용하면 꼭 쉘이 좀 이상하게 뜨지만 my-pass가 정상적으로 작동하는 것을 확인했다.
끝-