스택 버퍼 오버플로우 취약점이 있을 때에는 주로 스택의 리턴 주소를 덮는 공격을 한다. 리턴 주소는 함수가 끝나고 돌아갈 이전 함수의 주소로, 스택에 저장된 리턴 주소를 다른 값으로 바꾸면 실행 흐름을 조작할 수 있다.
다음은 스택 버퍼 오버플로우를 실습할 example.c이다.
example1.c에서는 프로그램의 argv[1]을 vuln 함수의 인자로 전달한다.
vuln 함수에서는 src 버퍼를 buf 버퍼에 strcpy 함수를 이용해 복사한다.
strcpy 함수는 피복사 버퍼에 대한 길이 검증이 없기 때문에, 프로그램의 첫 번째 인자에 buf 배열의 크기보다 긴 문자열을 넣으면 스택 버퍼 오버플로우가 발생한다.
※ 그럼 지금부터 vuln 함수의 인자를 전달할 때 buf 버퍼보다 크기가 큰 배열을 전달하므로써 buf 버퍼에서 버퍼 오버플로우가 발생해 return address를 overwrite 하는 실습을 진행해보겠다.
먼저 gdb를 통해 example를 디버깅한다.
취약점이 존재하는 vuln 함수에 브레이크포인트를 설정한 후 오프셋을 확인하기위해 첫 번째 인자와 함께 example1 바이너리를 실행해 보겠다.
브레이크 포인트에서 멈춘 것을 알 수 있다.
브레이크 포인트에서의 스택 메모리를 보면, 첫 4바이트는 vuln 함수의 리턴 주소이고 다음 4바이트는 vuln 함수의 인자인 argv[1]의 주소임을 알 수 있다.
이제 strcpy 함수가 실행되기 직전에 브레이크포인트를 설정해 인자들을 살펴보겠다.
이제 ni 명령을 통해 strcpy 함수를 실행시켜보겠다.
$esp가 가리키고 있는 포인터에 잘 복사된 것을 확인할 수 있다.
SFP랑 RET도 확인해보자.
SFP는 이전함수의 EBP를 저장하고 있다. 크기는 4byte이다.
RET는 다음에 실행해야하는 명령이 위치한 메모리 주소이다.
빨간색 박스가 입력한 문자열이 buf 변수, 즉 메모리 주소 0xffffd03c에 저장된 것이고, 초록색 박스가 SFP, 노란색 박스가 RET이다.
-> RET가 "jjjj"에 해당하는 0x6a6a6a로 덮혔으므로, offset이 36임을 알 수 있다.
-> strcpy함수는 길이 검증을 하지 않기때문에 4바이트(SFP)를 포함해서 8바이트를 더 적어준다면 리턴 어드레스를 덮어쓸 수 있을 것이다.
Linux Exploitation은 로컬 환경의 타겟을 대상으로 하기 때문에 익스플로잇 최종 목표는 프로그램의 실행 흐름을 조작하여 /bin/sh 혹은 셸 바이너리를 실행하는 것이다.
취약점이 존재하는 바이너리를 익스플로잇하여 셸 프로그램을 실행하면 해당 바이너리 권한의 셸을 획득하여 서버에 임의의 명령어를 실행할 수 있게 된다.
그럼 이제부터는 statck buffer overflow를 통해 /bin/sh를 실행하는 코드의 주소로 return address의 값을 조작해보겠다.
1. 그럼 먼저 /bin/sh를 실행시키는 쉘 코드를 작성해야한다.
리눅스에서는 바이너리를 실행시키기 위해서 execve 시스템 콜을 사용한다.
execve 시스템 콜의 인자는 실행시킬 바이너리의 경로, argv는 프로그램의 인자 포인터 배열, envp에는 프로그램의 환경변수 포인터 배열이 요구된다.
단순히 /bin/sh 바이너리를 실행시키는 것이 목적이기 때문에 최종적으로는 다음 인자 형태의 execve 시스템 콜을 호출하면 된다.
sys_execve("/bin/sh" 주소, NULL, NULL)
그럼 sys_execve("/bin/sh" 주소, NULL, NULL)를 호출하는 기계어 코드를 만들어 보자.
먼저, sys_execve("/bin/sh" 주소, NULL, NULL)를 실행하는 어셈블리 코드를 만든다.
다음으로, 만들어진 어셈블리 코드를 기계어 코드로 바꾼다.
어셈블리 코드 - shellcode.asm
$ nasm -f elf shellcode.asm
$ objdump -d shellcode.o
기계어 코드 - shellcode.bin
$ objcopy --dump-section .text=shellcode.bin shellcode.o
$ xxd shellcode.bin
00000000: 31c0 5068 2f2f 7368 682f 6269 6e89 e331 1.Ph//shh/bin..1
00000010: c931 d2b0 0bcd 80 .1.....
$
2. 만들어진 쉘코드와 더미값 그리고 쉘코드의 시작주소를 example1 프로그램의 인자로 함께 전달한다.
아까 보았듯 vuln 함수의 메모리 구조는 이러하고, example 프로그램의 경우 전달받은 인자값을 buf에 복사한다. 따라서 return address 이전까지 36바이트에 쉘코드를 넣어준 다음, return address를 저장된 쉘코드 주소로 바꾸는 payload를 만들어보겠다.
아까 만든 쉘코드의 경우 23byte기 때문에 RET를 건드리기 전까지 더미값을 넣어주고 최종적으로 RET에 buf의 시작주소를 넣어주면 우리가 준비한 쉘코드가 실행되어 쉘을 획득할 수 있을 것이다.
셸코드(buf)의 주소를 확인하기 위해 argv[1]에 40 바이트 길이의 문자열을 넣어 example1 바이너리를 실행하고, strcpy 함수를 호출하는 주소에 브레이크포인트를 설정해 디버깅해보겠다.
strcpy 함수의 첫 번째 인자는 buf(0xffffd05c)이므로 strcpy 함수가 실행된 이후 셸코드는 0xffffd05c에 저장된다.
따라서 다음 payload를 인자값으로 전달해 프로그램을 실행시켜보겠다.
shellcode + "A"*13 + p32(0xffffd05c)
드디어 쉘을 획득하였다!!!!!
다음에는 gdb가 아닌 쉘 환경에서 실행해보도록 하겠다!
참고
dreamhack - Linux Exploitation & Mitigation Part 1
https://jiravvit.tistory.com/entry/Buffer-Overflow-Return-Address-Overwrite-%EC%8B%A4%EC%8A%B5-2
'Hacking > Pwnable' 카테고리의 다른 글
[Pwnable] RTL 실습 (0) | 2021.03.07 |
---|---|
[Pwnable] NX bit (0) | 2021.03.07 |
[Pwnable] ELF 동적 분석 (0) | 2021.03.01 |
[Pwnable] Dreamhack - Memory Corruption - C (I) (0) | 2021.02.28 |
[Pwnable] pwndbg 주요 사용법 (1) | 2021.02.27 |