Definition | 정의
스택 버퍼와 반환 주소 사이에 임의의 값을 삽입하여 함수의 에필로그에서 해당 값의 변조를 확인하는 보호기법
만약, 스택 카나리의 값이 변조되었다면 프로세스는 강제 종료됨
스택 버퍼오버플로우로 반환 주소를 덮으면서, 스택 카나리의 값을 덮는 것을 피할 수 없음
→ 에필로그에서 변조가 확인되며 해커가 실행 흐름을 가져올 수 없음
카나리를 잘못 덮어쓰면
stack smashing detected
라는 경고와 함께 종료된다
카나리 정적 분석
스택 카나리를 만드는 과정이 프롤로그에, 변조를 확인하는 과정이 에필로그에 추가됨
프롤로그
mov rax,QWORD PTR fs:0x28
mov QWORD PTR [rbp-0x8],rax
xor eax,eax
lea rax,[rbp-0x10]
fs 세그먼트 레지스터가 가리키는 값의 오프셋 0x28 값(카나리 값)을 rax로 이동
rax에 저장된 값을 rbp(base pointer)의 0x8 떨어진 곳에 저장
eax를 초기화하고, rax에 rbp-0x10의 값을 넣는다
[ mov vs lea ]
mov : op2를 op1로 이동
lea : op2의 주소를 op1로 이동 - 주소 자체를 가져옴
에필로그
mov rcx,QWORD PTR [rbp-0x8]
xor rcx,QWORD PTR fs:0x28
je 0x6f0 <main+70>
call __stack_chk_fail@plt
rbp-0x8의 값, 즉 아까 저장한 카나리의 값을 rcx에 넣는다
rcx값, 즉 스택에서 얻어온 값과 원래 카나리의 값을 비교하여 같으면 다음 줄을 실행하고 아니라면 프로세스를 종료한다
카나리 동적 분석
프롤로그
► 0x5555555546b2 <main+8> mov rax, qword ptr fs:[0x28] <0x5555555546aa>
0x5555555546bb <main+17> mov qword ptr [rbp - 8], rax
0x5555555546bf <main+21> xor eax, eax
fs:0x28의 값을 읽어서 rax에 저장함
pwndbg> ni
0x5555555546b2 <main+8> mov rax, qword ptr fs:[0x28] <0x5555555546aa>
► 0x5555555546bb <main+17> mov qword ptr [rbp - 8], rax
0x5555555546bf <main+21> xor eax, eax
pwndbg> print /a $rax
$1 = 0xf80f605895da3c00
이를 실행한 후, rax를 확인해보면 시작이 null (0x00)인 바이트가 저장되어 있음 (little endian)
이를 rbp-0x8에 저장한다
pwndbg> ni
0x5555555546b2 <main+8> mov rax, qword ptr fs:[0x28] <0x5555555546aa>
0x5555555546bb <main+17> mov qword ptr [rbp - 8], rax
► 0x5555555546bf <main+21> xor eax, eax
pwndbg> x/gx $rbp-0x8
0x7fffffffe238: 0xf80f605895da3c00
rbp-0x8의 값이 아까 rax값과 같다는 것을 확인할 수 있음
이 값이 카나리이다
에필로그
현재 오버플로우가 일어나서 카나리에 다른 값이 덮이도록 해놓은 상태라고 가정한다
► 0x5555555546dc <main+50> mov rcx, qword ptr [rbp - 8] <0x7ffff7af4191>
0x5555555546e0 <main+54> xor rcx, qword ptr fs:[0x28]
0x5555555546e9 <main+63> je main+70 <main+70>
↓
0x5555555546f0 <main+70> leave
0x5555555546f1 <main+71> ret
rcx에 스택에 있는 카나리 값을 저장한다
0x5555555546dc <main+50> mov rcx, qword ptr [rbp - 8] <0x7ffff7af4191>
► 0x5555555546e0 <main+54> xor rcx, qword ptr fs:[0x28]
0x5555555546e9 <main+63> je main+70 <main+70>
pwndbg> print /a $rcx
$2 = 0x4848484848484848
rcx를 확인해보면, 원래 카나리 값이 저장되어 있어야하는 스택 위치에 오버플로우 된 값이 저장되어있음을 알 수 있음
► 0x5555555546eb <main+65> call __stack_chk_fail@plt <__stack_chk_fail@plt
카나리가 잘못 덮인 것을 확인하면 프로세스가 종료된다
카나리 생성 과정
fs 레지스터 (segment register의 일종) → TLS
리눅스에서 fs의 값은 쉽게 알아낼 수 없음 ex) info register fs, print $fs 등으로 알아낼 수 없음
fs의 값을 설정할 때 호출되는 시스템 콜
arch_prctl(int code, unsiged long addr)
해당 시스템콜로 설정한다. code 부분에 FS를 넣어주면 addr로 fs가 설정된다
arch_prctl(ARCH_SET_FS, addr)
fs의 값은 addr이 됨
동적 분석으로 확인
catch syscall arch_prctl
catch 명령어로 확인할 수 있음. arch_prctl를 실행하기 전에 멈춤
해당 시스템 콜은 init_tls() - tls를 초기화해주는 함수 안에서 실행됨
인자1 (rdi) : ARCH_SET_FS는 상수값으로 0x1002
인자2 (rsi) : TLS의 저장 위치, fs가 가리킬 공간
카나리 : fs + 0x28의 값이므로 rsi+0x28의 값을 확인 가능
pwndbg> x/gx 0x7ffff7d7f740 + 0x28
0x7ffff7d7f768: 0x0000000000000000
하지만, 아직 카나리값이 저장되지 않았기 때문에 해당 부분에 아무것도 설정되지 않음을 확인 가능
watch *(0x7ffff7d7f740+0x28)
watch 명령어로 확인할 수 있음
watch : 특정 주소에 저장된 값이 변경되면 프로세스를 중간시킴
Hardware watchpoint 4: *(0x7ffff7d7f740+0x28)
Old value = 0
New value = 2005351680
security_init () at rtld.c:870
870 in rtld.c
즉 이때, TLS+0x28의 값을 확인할 수 있음
pwndbg> x/gx 0x7ffff7d7f740+0x28
0x7ffff7d7f768: 0x8ab7f53277873d00
이 값이 카나리의 값과 같다는 것을 확인할 수 있음
카나리 우회
Brute Force 무차별 대입
카나리는 맨 처음이 null byte 이므로 x64는 \(256*7\), x86은 \(256^3\)이라고 가정할 수 있음
TLS 접근
카나리는 TLS에 전역변수로 저장됨
TLS의 주소는 매 실행 시마다 바뀌기 때문에 TLS 주소를 알 수 있다면 가능
스택 카나리 릭 (인포릭)
인포릭과 똑같은 방식으로 진행됨
오버플로우를 내면, canary의 값이 출력되는 경우가 발생함
카나리의 맨 앞부분은 null이기 때문에 한바이트 오버플로우로 나머지를 알아낼 수 있음
'Hacking > Pwnable' 카테고리의 다른 글
[Mitigation] ASLR (0) | 2024.08.25 |
---|---|
[Mitigation] NX (0) | 2024.08.25 |
gdb를 pwntools와 디버깅하기 (0) | 2024.07.26 |
[Memory Corruption] Stack Buffer Overflow (1) | 2024.01.29 |
[Exploit] ShellCode(2) - execve shell code (1) | 2024.01.27 |