발자취

시스템 보안 05-2. 버퍼 오버플로우 연습문제 본문

3-2/시스템보안

시스템 보안 05-2. 버퍼 오버플로우 연습문제

해린 2023. 12. 14. 07:13

1. welcome.c 프로그램을 gdb를 사용해서 실행하고 아래의 항목에 답하시오.

#include <stdio.h>

int main()
{
  int i;
  
  puts("welcome\n");
  
  return 0;
}

우선 위와 같이 welcome.c 코드를 작성해줬다.

 

컴파일 후, gdb 모드로 실행해준다.

 

*gcc 명령어

-g 옵션: gdb에게 제공하는 정보를 바이너리에 삽입

-o 옵션: 바이너리 형식의 출력 파일 이름 지정 옵션. 이 옵션이 없으면 a.out이 기본 이름이 된다.

 

*gdb 명령어

-q 옵션: 버전 정보 등의 문구가 뜨지 않도록 설정. (quiet)

 

gdb의 문법을 보기 좋게 intel 문법으로 변경해준다.

 

(1) puts 함수의 메모리상의 주소 작성하시오.

7번 라인에 브레이크 포인트를 걸어준 뒤 실행한다.

브레이크 포인트를 걸어주면 그 라인 전까지만 실행해준다.

우리는 puts 함수의 실행 전을 확인해야하기 때문에 7번 라인에 걸어줬다.

 

main 함수를 디버깅한다.

call 그리고 <puts@plt> 이 사이에 있는 0x80001030 < 이 주소가 puts 함수의 주소이다. (14번째 라인)

 

(2) welcome 문자열의 메모리상의 주소를 작성하시오.

위 사진의 12번째 라인에서 edx 값을 스택에 넣고 있는 것을 보아 welcome 문자열의 주소는 edx의 주소일 것으로 추측해볼 수 있다.

 

eip는 다음에 수행될 명령어의 주소가 들어가는 공간이다.

따라서 x/i $eip를 입력해보면 다음에 수행될 명령어를 확인할 수 있다. (x는 메모리의 값을 확인하게 해주는 명령어이다)

nexti 명령어를 사용하여 다음 명령어를 실행한다.

x/i $eip 명령어를 입력했을 때 push edx가 나오는 것을 확인한 후(3번째), nexti를 입력하면 push edx를 실행할 수 있는 것이다.

마지막 x/i $eip 명령어를 입력하여 push edx가 지난 것을 확인해보았다.

 

edx의 내용을 확인해보면 'welcome'이 나오는 것을 확인할 수 있다.

따라서 'welcome'의 주소는 0x80002008이다.

 

(3) return 0;에 break를 걸고 실행한 후의 esp 값을 작성하시오.

return 0;이 있는 9번 라인에 브레이크 포인트를 걸고 실행해보았다.

esp의 주소는 '0xbffff3f0'임을 확인할 수 있다.

(파란색이 주소, 흰색은 값임. 혼동주의!)

 

 

2. buf.c 프로그램을 gdb를 사용해서 실행하고 아래의 항목에 답하시오.

#include <stdio.h>

void test(int i, int j, int k) 
{
   int a;
   int b;
   char buf[14];

   a = 100;
   b = 50;
   buf[0] = '1';
   buf[3] = '2';
   buf[13] = '3';
 
}

int main() {
   test(5, 6, 7);  
}

buf.c의 내용은 다음과 같다.

 

컴파일 후, gdb로 실행해준 뒤 intel 문법으로 세팅해준다.

 

(1) 변수 a, b의 스택상의 주소를 작성하시오.

(2) 배열 buf의 스택상의 주소를 작성하시오.

8번 라인에 브레이크 포인트를 걸어준 뒤 실행해준다. 8번 라인은 a, b, buf 모두가 선언된 이후의 라인이다.

이렇게 변수나 배열의 주소를 알기 위해서는 최소 선언된 이후에 브레이크 포인트를 걸어줘야 한다.

 

- print &_

- x/x &_

위 두가지 방식으로 주소를 알아낼 수 있다.

 

a의 주소는 0xbffff3f0

b의 주소는 0xbffff3ec

buf의 주소는 0xbffff3de 임을 알아낼 수 있다.

 

메모리 구조는 다음과 같다.

 

(3) 배열 buf의 7번째와 11번째 원소의 값을 각각 작성하시오.

앞 x는 위에서 봤듯 메모리를 확인할 수 있게 해주는 명령어, 뒤에 오는 x는 16진수로 보여주는 옵션이다.

x/16x buf는 즉, 메모리를 16진수로 보여주는데, 이때 buf부터 시작하여 16바이트까지 보여주는 명령이다.

 

리틀 엔디안 법칙을 따르기 때문에 뒤에서 부터 세서,

7번째 원소는 b4, 11번째 원소는 bc임을 확인할 수 있다.

 

3. buf_of.c 프로그램을 gdb를 사용해서 실행하고 아래의 항에 답하시오.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>


int doCopy(char *str) {
       
        int tmp=0; 
        char token=0x00;
        int flag=0;
	char buf1[4];
	char buf2[4];
	
	strcpy(buf2, str);

        return 1;

}

int main(int argc, char *argv[]) {
       if(doCopy(argv[1]))
          puts("1111\n");
       else
          puts("0000\n");
}

buf_of.c의 내용은 위와 같다.

 

(1) 변수 token, flag, tmp의 스택상의 주소를 작성하시오.

(2) 배열 buf1, buf2의 스택상의 주소를 작성하시오.

2-(1), 2-(2) 문제와 같은 방법으로 변수들과 배열들의 주소를 알아낼 수 있다.

변수 token의 주소는 0xbffff3cb

변수 flag의 주소는 0xbffff3c4

변수 tmp의 주소는 0xbffff3cc

배열 buf1의 주소는 0xbffff3c0

배열 buf2의 주소는 0xbffff3bc 임을 확인할 수 있다.

 

메모리 구조는 다음과 같다.

 

(3) 버퍼 오버플로우 공격을 통해서 변수 token의 값을 'A'로 변경하시오.

buf_of.c의 14번째 라인에서는 사용자 입력값으로 받아온 str을 buf2에 복사한다.

buf2의 size보다 큰 값을 사용자 입력값으로 넣어주면 오버플로우가 발생하여 buf2의 아래에 있는 값에도 영향을 끼친다.

즉, 오버플로우가 발생할 수 있는 영역은 buf1, flag, token, tmp이다.

 

사용자 입력값을 buf2에 복사한 이후인 15번째 라인에 브레이크 포인트를 걸어준다.

buf2는 총 4바이트, buf1은 총 4바이트, flag는 총 7바이트이고 그 뒤에 token이 나오기 때문에 적어도 16바이트의 값을 입력해줘야 오버플로우가 발생하여 token에 값을 덮어씌울 수 있다.

 

token의 16진수 값을 확인해보면 0x00000041('A'의 ASCII 코드 값)이고, char값도 'A'로 나오는 것을 확인할 수 있다.

 

(4) 버퍼 오버플로우 공격을 통해서 변수 flag의 값을 100로 변경하시오.

소문자 'd'의 아스키코드 값이 0x64(16) == 100이다. 따라서 소문자 'd'를 입력해주면 flag는 int 타입이므로 100으로 인식할 것이다.

flag 값을 바꿔주기 위해서는 적어도 9바이트 만큼을 입력해줘야 한다.

 

3-(3)과 같은 방식을 사용하면 flag에 100을 넣을 수 있다.

 

(5) 버퍼 오버플로우 공격을 통해서 배열 buf1의 내용을 "5678"로 변경하시오.

3-(3)과 같은 방식으로 buf1에 "5678" 값을 넣어줄 수 있다.

사용자 입력값으로 56785678을 넣어줬지만, 뒷 4자리만 5678이기만 하면 아무 숫자를 넣어줘도 된다!