본문 바로가기

Let's Study/Basic System Hacking Technique

2. Stack Corruption & gdb 명령어


system_hacking2.pdf





Shayete입니다. 오늘은 기본적인 Stack Corruption 및 gdb명령어 사용법에 대해 배워보도록 하겠습니다.



 Stack Corruption은 Buffer Overflow의 현대적 용어로 의미는 같습니다. Buffer Overflow가 스택에 할당된 버퍼의 메모리를 흘러넘치게 해서 메모리를 오염시키는 것처럼 Stack Corruption의 의미는 메모리 오염입니다. 오늘 공부할 Stack Corruption을 이해하려면 기본적인 메모리, 레지스터, 스택 지식이 필요한데, 가령






메모리 구조는 이렇게 생겼습니다. 유저가 침입할 수 없는 Kernel 공간, 유저영역의 스택, 라이브러리, 힙, 초기화된 & 되지않은 전역변수 데이터, 코드영역 등  메모리는 크게 커널과 그 외로 나뉩니다. 메모리는 필요에 따라 프로그램들이 적재되었다가 해제되었다가를 반복하는데 커널은 CPU에 어떤 프로그램을 적재할 것인가, 메모리에는 어떤 프로그램을 적재할 것인가를 스케쥴링합니다. 이 메모리는 흔히 말하는 RAM과 ROM을 의미합니다.

시스템해킹을 공부할 때는 그냥 메모리는 이렇게 생겼다 정도로만 알아두시면 됩니다.


근데 그림을 보시면 스택은 위에서 아래로 자라고 힙은 아래에서 위로 자랍니다. 그 사이에 라이브러리 영역이 존재하고요. 


왜그럴까요.




스택의 방향은 커널의 반대방향으로 자랍니다. 커널은 절대 침범해서는 안될 영역이고 커널은 소프트웨어와 하드웨어를 관리하기 때문에 커널을 건드리면 안되는 구조로 되어있습니다. 그리고 스택과 힙 사이에 라이브러리 영역을 둠으로써 더 메모리를 효율적으로 관리할 수 있다고 합니다.





스택은 LIFO 구조로 되어있습니다. 가장 먼저 들어간 자료가 가장 나중에 나오는데 PUSH로 스택에 값을 넣고 (esp - 4) POP으로 스택의 값을 뺍니다. (esp + 4)

스택은 위에서 아래로 자라기때문에 스택에 값을 넣으면 위에서 아래로 값이 들어가야하므로 esp가 4바이트씩 빠집니다.



메모리, 스택까지 나왔으니 레지스터도 마저 설명드릴게요.


레지스터는 CPU 내에 있는 저장영역인데 이 영역은 파일을 저장하기엔 너무 작고, 대신 프로그램이 실행되는 동안 어떤 정보를 저장하기 위해 사용됩니다. 이 레지스터라는 영역이 CPU 내에 있으므로 메모리에 있는 데이터보다 더 빨리 접근이 가능합니다. 레지스터에는 데이터 레지스터, 포인터 레지스터, 인덱스 레지스터, 세그먼트 레지스터, 플래그 레지스터 등이 있지만 우리는 데이터, 포인터, 인덱스 레지스터 정도만 알아두기로 합니다.


메모리, 스택, 레지스터에 대한 아주 기본적인 설명이 끝났으니 가장 기본적인 Stack Corruption을 보도록 하겠습니다.



1 . Stack Corruption




char buf[20];

scanf("%s", buf);


특정 함수 안에 buf를 20만큼 주고 scanf로 값을 받았을 때의 모습입니다. 스택에는 buf가 쓸 20바이트가 할당되고 그 뒤에는 sfp, ret가 차례로 있습니다. 이 sfp는 save frame pointer로 바로 이전에 호출된 함수의 ebp를 저장해뒀다가 이 함수가 return되기 전에 sfp를 참조하여 ebp를 복구시킵니다.

sfp 뒤의 return address는 이 함수가 끝나고 나서 복귀할 주소를 담고 있죠.

scanf나 strcpy나, 개발자들의 실수로 신용할 수 없는 함수, untrusted input function 들을 사용했을 때 스택에 할당된 버퍼를 흘러넘치게 할 수 있어 sfp, ret 영역까지 덮을 수 있습니다. 볼까요?





할당된 buf는 20이지만 이것보다 8바이트 많은 28바이트값을 입력했을 시, buf 20 바이트 + sfp 4바이트 + ret 4바이트. 복귀할 주소가 "HEHE"로 변경되었습니다. 하지만 이런 주소는 존재하지 않으므로 Segmentation fault가 뜨겠죠.

*이걸 리눅스에서 실습할 때는 컴파일할 시 gcc -fno-stack-protector -mpreferred-stack-boundary=2 옵션을 걸어주세요. 우리가 실습하는 환경에는 dep, aslr, stack canary 등등 메모리 보호기법이 있는데 -fno-stack-protector == 스택카나리 해제, -mpreferred-stack-boundary=2 == 컴파일 최적화 옵션을 풀어 해킹 실습하기 좋은 스택 상태로 만들어줍니다.



근데 복귀할 주소가 아마 0x45484548로 되었을 겁니다. "H"는 아스키코드로 0x48, "E는 아스키코드로 0x45. 즉 "EHEH"로 보이는데 이건 현재 운영체제가 리틀엔디안 방식이기 때문입니다. 빅엔디안은 0x12345678를 넣었을 때 그대로 0x12345678로 보이고 리틀엔디안은 0x12345678을 넣었을 때 0x78563412 로 1바이트씩 뒤집어서 들어간다고 보시면 됩니다. 보통 빅엔디안은 볼 기회가 거의 없습니다.





어셈보면서 함수 프롤로그나 에필로그는 알아야 할 것 같아서 추가했습니다. 보통, 함수는 프롤로그와 에필로그가 존재합니다. 프롤로그에는 이전 함수의 Stack Frame 시작점을 백업해서 이 함수가 끝나고 이전 함수로 넘어갈 때 base pointer를 복원시켜줍니다. 이 PUSH EBP 값이 흔히 말하는 sfp입니다. 그리고 mov EBP, ESP를 함으로써 이 함수가 쓸 새로운 Stack Frame을 생성합니다.




함수가 끝나는 부분에는 에필로그가 존재합니다. mov ESP, EBP를 해서 sfp가 저장되어 있는 곳으로 스택포인터를 맞추고 그걸 POP EBP, sfp를 ebp에 빼내어 이전 함수의 ebp를 다시 복원시켜줍니다. 


그리고. RET. sfp 바로 밑에 있는 이전함수의 리턴주소를 EIP에 POP해서 복귀합니다.


이 함수 프롤로그와 에필로그는 마치, 어느 탐험가가 던전에 들어갔는데 길을 잃을까봐 미리 던전 입구에 gps 추적장치를 심어놨다가 나중에 던전을 탈출할 때 심어뒀던 gps 추적장치를 핸드폰으로 확인하면서 빠져나가는 모양이 되겠습니다. 이 던전이 함수가 되겠고, 던전 밖은 이전 함수가 되겠습니다.





함수들에게는 호출규약이 있는데 (Calling Convention) 우리가 실습하는 리눅스환경은 cdecl로 부모 쪽에서 스택복구를 합니다.(라고는 하지만 이건 윈도우에서 통용되는 말이고 리눅스는 자식쪽 함수 내에서 sub esp, 0x20으로 스택정리를 미리 합니다.) 윈도우는 stdcall인데 RETN 8 (esp + 8)처럼 자식 쪽에서 스택을 복구합니다.

인터넷에서 검색했을 때 보이는 cdecl , add esp, 8은 윈도우에서 작동되는 호출규약 방식으로 부모 쪽에 스택 복구합니다.



이제 레지스터 종류를 보겠습니다.


2. Registers




포인터 레지스터입니다. 반드시 알아야 할 중요한 레지스터들이죠. 흔히 EIP컨트롤을 한다는게 EIP에는 다음에 실행될 주소가 담겨있기 때문에 우리가 원하는 주소를 EIP에 넣을 수만 있다면 거기로 건너뛰어 원하는 것을 할 수가 있습니다. EBP는 함수가 나타내는 파라미터나 지역변수의 기준이 되어, 지역변수를 부를 때 ebp - 0xc 이런식으로 스택에 저장되어 있는 값을 부를 수 있습니다. ESP는 현재 가르키고 있는 스택의 끝부분을 가리키고 있고요.




데이터 레지스터 및 인덱스 레지스터들입니다. 뭐.. 레지스터들마다 이런 기능들을 수행한다고 되어있지만 시스템해킹 초보 입장에서는 이것들 다 알 필요 없습니다.




저 레지스터들은 그냥 변수라고 생각하세요.







가령 mov eax, ebx를 했을 때 eax = ebx다 처럼요. mov a, b == a = b 라고 보시면 됩니다. 우리가 rop까지 배울 동안은 데이터레지스터와 인덱스레지스터는 그냥 변수라고 보셔도 무방합니다. 바이너리를 이것저것 뜯어보면서 분석하다보면 자연스레 저 설명이 이해되면서 "음 그래 저건 저런 역할을 하는거야" 라고 공감을 하게 됩니다.




3. gdb 명령어


바이너리를 잘 뜯어보려면 gdb 디버거를 잘 쓸 줄 알아야 합니다. 기본적인 설명이 끝났으니 gdb 명령어에 대해 알아보도록 합니다. 제가 자주 사용하는 명령어들이고 반드시 알아야 하는 명령어들이기도 합니다.