발자취
시스템 보안 05-2. 버퍼 오버플로우 연습문제 본문
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이기만 하면 아무 숫자를 넣어줘도 된다!
'3-2 > 시스템보안' 카테고리의 다른 글
| 시스템 보안 07-2. 코드 재사용 공격 실습 1 - RTL (0) | 2023.12.15 |
|---|---|
| 시스템 보안 07-1. 코드 재사용 공격 (0) | 2023.12.15 |
| 시스템 보안 06. 쉘코드 기본 개념 및 연습문제 (0) | 2023.11.21 |
| 시스템 보안 05-1. 버퍼 오버플로우 공격 (0) | 2023.11.03 |
| 시스템 보안 04. gdb 디버거 (0) | 2023.10.31 |