본문 바로가기
Computer Security

코드 보안 - 포맷 스트링(1)

by Doromi 2017. 12. 5.
728x90
반응형

● 코드 보안

 - 버퍼 오버플로우 공격

 - 포맷 스트링 공격

 

포맷 스트링 공격에 대한 보안을 공부해 보도록 하겠습니당~

 

printf문에서 %로 시작하고 메모리에 저장된 이진 코드 값을 사람들이 읽을 수 있는 값으로 변경합니다.

포맷 스트링에 사용된 문자에 따라서, 출력형태가 결정이 되는데 일반적으로 %d는 정수형 10진수 상수로 해당하는 문자를 출력하고, %c는 문자 값을 출력하는 등등 매개변수에 따라 변수 형식이 다양합니다.

%d,%f,%c,%s...이런 것들은 출력과 관련된 것들로 메모리에 있는 값들을 화면에 보여주는 용도 입니다.

 

그런데 이걸 가지고 어떻게 메모리 조작이 가능할까요?

놀랍게도 잘 쓰진 않지만 %n이라는 것이 있습니다.

%n은 지금까지 화면에 출력된 글자 수를 count해서 그 글자 수 만큼을 이 %n하고 짝꿍이 되는 변수(모든 포맷스트링은 짝꿍이 되는 변수가 있습니다. %n도 마찬가지로 짝꿍이 있겠죠?)에 저장을 합니다.

출력지정자인데 값을 출력하는 용도가 아니라 메모리 쓰기가 가능한 포맷스트링입니다.

 

그럼 %hn은 무엇일까요? 여기서 h는 half입니다.

보통 %n은 4byte공간을 차지하고 데이터 덮어쓰기를 하는데 %hn은 half이므로 2byte공간에만 데이터 쓰기를 합니다.

 

#include<stdio.h>

main(){

int i = 4;

printf("the hexa code of i is [0x%x]\n",i);

}

 

이 코드를 보았을 때 메모리 스택이 어떻게 변하는 지를 알 수 있어야 합니다.

메인이 시작되자마자 스택에 쌓이는 것은 ret address입니다.

그다음 ebp(base pointer에 현재 ebp에 있던 값이 적재가 됨),그리고 ebp라는 곳에 esp값이 적재가 됩니다.

메인이 젤 먼저 실행이 되고 한 일은 int i =  4; 입니다. i에 해당하는 4byte가 할당이 되는 거죠.(main에서 사용하는 local variable이니까) 그리고 여기서 4라는 값이 적재가 됩니다.

그다음에 printf라는 건 함수 호출입니다. 함수호출이 되려면 제일 먼저 함수가 필요로 하는 인자가 먼저 적재가 되고 인자다음에 함수 call이 일어나게 됩니다. 그럼 여기서 printf함수안에 인자가 2개입니다. 젤 오른쪽에 있는 것부터 적재를 합니다. 그럼 i부터 적재가 되겠죠? 왜냐하면 함수 왼쪽에 있는 것부터 실행이 되어야 하니까 함수 직전에 적재가 되어야 먼저 실행이 될 것입니다. 그럼 printf에서 필요로 하는 인자 i가 적재가 되고, printf가 필요로 하는 문자열이 적재됩니다.(좀 더 정확히 말하면 문자열이기 때문에 다른 segment에 저장이 되어있습니다.따라서 문자열이 담긴 주소가 적재가 되는겁니다.) 그다음 printf함수를 부릅니다. 그럼 printf가 문자열을 가지고 와야 하는데 printf가 호출되기 직전에 있는 4byte정보를 가지고 와 문자열의 주소를 쭉 가지고 와서 the hexa code of i is가 출력이 됩니다. 그러다가 %x를 만나면 포맷스트링이 있는 것이므로 이에 해당하는 짝꿍으로 부터 짝꿍 값을 16진수로 출력합니다. 그럼 %x를 만나는 순간 i에 해당하는 건 바로 뒤에 있는 4byte의 i의 값이 있으니까 처음에 i를 가져오고 그 다음 그 위에 있는 i = 4라는 정보를 가지고 와서 4에 해당하는 hexa code를 출력을 하게 됩니다.  

 

다음 예시입니다.

#include<stdio.h>

main(){

char *buffer = "wishfree\n";

printf(buffer);

}

*buffer는 포인터 변수입니다.

4byte짜리 buffer라는 이름의 포인터가 하나 만들어지는 거죠.

여기에 문자열이 할당되었다는 건 저 문자열은 어딘가에 저장이 되어 있습니다.(상수값이니까) 그럼 *buffer에는 해당하는 문자열의 주소가 저장이 됩니다. 그리고 printf라는 함수를 호출하는데 인자가 buffer 하나입니다. printf함수를 call하면 제일 먼저 buffer의 주소를 받게 되고 그 주소로 가서 문자열의 주소를 됩니다. 8048440이라는 값을 가지고 와서 문자열을 출력 하고 끝이 납니다. 하지만 문자열을 조금 조작을 한 예시를 보겠습니다.

 

#include<stdio.h>

 

main(){

char *buffer = "wishfree\n%x\n";

printf(buffer);

}

%x라는 포맷스트링을 집어넣었습니다. 문자열을 출력하다 보니 %x가 나옵니다. 포맷스트링이기 때문에 짝꿍이 되는 인자가 있어야 합니다. 상식적으로는 buffer위에 %x와 짝꿍이 되는 변수가 있어야 그 값을 가져다가 출력을 할텐데 여기에는 없습니다. buffer위에 있는 *buffer의 값을 가져다가 출력을 하게 됩니다. 16진수로 출력을 합니다. 결론적으로 출력된 값은

wishfree

8048440

입니다. 8048440이 가리키는 값은 실질적으로 wishfree가 저장된 배열의 주소이고 printf 함수 인자 메모리 위의 메모리 값입니다. 이것으로 알 수 있는 것은 printf에서의 포맷스트링은 반드시 해당하는 짝꿍이 있어야 하는데 짝꿍이 없으면 그 옆에 인접한 메모리의 공간을 그대로 읽어서 출력을 한다는 겁니다. 이 말은 인접한 메모리에 바로 접근을 한다는 얘기고 반대로 쓸 수도 있다는 얘기가 되고 메모리에 원래 쓰지 말아야 되는 공간에 포맷스트링을 이용해서 쓰기도 가능하다라는 말처럼 들립니다.

 

다음에는 메모리에 쓰기를 어떻게 할 것인지 부터 공부해 보겠습니다~

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형