해당 글은 매우 카나리 보호기법에 익숙치 않은 사람들이 공부할 수 있는 방법을 기술했습니다.
Buffer Overflow Attack을 통해 실행흐름을 조작하거나 데이터 변조, 유출이 가능하고 이를 막기 위한 방법 중 하나를 알아보겠습니다.
카나리가 적용되면 Buffer Overflow가 날 경우 Canary 값을 비교합니다.
Canary도 종류가 여러개 있는데
Null Canary | 0x00000000 | 0x00 |
Terminator Canary | 0x00000aff | 00x00, 0x0a, 0xff |
Random Canary | 4Byte (32-Bit), 8Byte (64-Bit) Random | 0x00으로 시작 |
Random XOR Canary | Random Canary와 동일 취약점을 가지지만 Canary 값을 읽어오는 과정이 더 복잡함 |
그러면 Buffer 값을 넘기면 각각 어떻게 동작하는지 알아보겠습니다.
1. Stack Canary 적용 프로그램 실행 비교
첫 번째 실행은 Stack Canary가 적용된 상태, 두 번째 실행은 Stack Canary가 적용되지 않은 상태에서 각각 Buf값을 넘겨보았습니다. (직관적으로 볼 수 있게 직접 a를 타이핑해봤습니다)
Canary가 적용되면 *** stack smashing detected *** : terminated 로 프로그램이 종료되는데 Buffer 다음 Canary 검사에서 Fail했기 때문입니다.
Canary가 적용되지 않은 상태라면 Buffer 이후 값은 이미 다른 값으로 덮여진 상태입니다.
이 상태에서는 Segmentation fault로 프로그램이 종료되었습니다.
다음은 아주 간단한게 짠 코드와 보호기법에 따른 어셈블리입니다.
X64 시스템
확인해보면 해당 스택을 할당하는 과정에서 Canary가 적용되면서 0x10만큼 더 할당되었습니다.
<+12> 카나리가 저장되는 fs:0x28에서 카나리 값을 불러옵니다.
<+21> rbp-0x8 에 카나리 값을 저장합니다.
함수내 모든 명령어가 끝난 후
<+86> rbp-0x8 카나리 값이 변조되었는지 확인하기 위해
<+90> fs:0x28 (원래 카나리 가져온 곳)과 XOR하여 (Assembly상 sub)
<+99> 0이 만족하는지 검사한다. 만족한다면 leave
<+101> 불만족한다면(rbp-0x8이 위조되었으면) __stack_chk_fail 호출 -> Stack Smashing Detected
즉 정리하자면
1. 스택에 공간이 주어지자마자 rbp-0x8에 카나리를 fs:0x28로부터 읽어옴
2. 스택 공간 내에서 모든 연산이 끝난 뒤 rbp-0x8가 변조되었는지 확인하기 위해 rbp-0x8값과 fs:0x28을 XOR(또는 SUB)
3. 0이 나오면 변조가 되지 않았다고 판단하여 정상 실행, 0이 아니면 변조되었다 판단하여 프로그램 강제 중단
X86 시스템
x86에서는 gs:0x14 에서 값을 가져오는 것을 확인할 수 있다. [ref. GS Buffer Security Check]
그외는 x64에서 설명한 방법과 동일하다.
2. Stack Canary 생성 과정
fs 레지스터는 gdb에서 레지스터 값을 출력하듯 뽑을 수 없습니다.
그래서 gdb에서 catch syscall arch_prctl을 통해 시스템 콜을 잡아봤습니다.
fs의 값을 설정할 때 호출되는 arch_prctl(int code, unsigned long addr)로 캐치포인트를 잡고
이때 rdi, rsi를 확인해보는 것입니다.
잡힌 이후 rdi 값은 0x1002, addr(fs base)은 0x7ffff7d8d740으로 잡힙니다.
그러면 카나리가 생성되는 fs:0x28은 0x7ffff7d8d740+0x28입니다.
하지만 해당 값은 0으로 채워져있어 watch *0x7ffff7d8d768 명령어로 해당 값이 바뀔 때 프로그램이 멈추도록 했습니다.
그 결과 값이 변했고 직접 까보니 0xe2fe5d5048d2a600이 카나리 값으로 설정된 것을 볼 수 있습니다.
3. Stack Canary 우회 방법
3-1. Leak Canary 방법
Stack Canary를 Leak 할 수 있는 POC 코드를 만들어봤습니다.
do_me()에 도달하기 위해 중간에 Canary 위변조 없이 RET(rbp+8)를 do_me() 함수의 주소로 설정해야 합니다.
Canary를 Leak하기 위해 <cnry.c + 18>에 그대로 buf를 출력하게 만들어보겠습니다.
사진에 dummy와 Canary 사이의 \x00을 임의의 값으로 채워주면 <cnry.c + 18>에서 NULL문자가 없으므로 뒤에 Stack Canary가 Leak될 겁니다.
사진의 어셈블리를 보면 [rbp-50]부터 [rbp-8]까지 buffer + dummy 이고 Canary의 '\x00'은 [rbp-7]입니다.
즉, 우리는 처음 read 함수 <cnry.c + 17>에서 buffer를 삽입할 때 임의의 문자 0x49 만큼 넣어주면 카나리 값이 출력될겁니다.
실제로 A 바이트를 0x49만큼 채워주면 뒤에 Canary가 출력된걸 볼 수 있습니다.
우리는 Leak된 Canary를 받아주고 다음 buf 입력때 0x48 + Canary(8) + SFP(8) + do_me(8)를 넣어주면 카나리 검사를 통과하고 실행흐름을 조작할 수 있습니다.
왼쪽은 Exploit 코드이고 우측 상단은 Exploit 결과입니다.
do_me 함수가 정상적으로 작동된 것을 확인할 수 있습니다.
3-2. Master Canary
이번엔 Canary를 Leak 하는 것이 아닌 Canary가 저장된 부분(fs:0x28)을 덮어서 검사를 우회하는 방법입니다.
일반적인 Canary Leak와 상황이 다른데, Thread를 사용하는 환경에서만 Master Canary를 덮어서 우회가 가능합니다.
Thread에서 할당한 변수는 Master Canary가 저장된 주소보다 낮은 주소에 있기에 카나리를 덮어쓸 수 있습니다.
Thread에서 할당해준 변수가 아닐 경우 주소차이가 덮기엔 너무 크게 차이납니다.
Exploit Tech는 매우 간단하니 아래 강좌를 참고해주세요.
https://learn.dreamhack.io/267#1
시간이 좀 될 때 예제 작성 후 포스팅하겠습니다.
'Computer Science > Operating System' 카테고리의 다른 글
inode pointer structure (1) | 2024.09.25 |
---|---|
[운영체제] Mechanism - Limited Direct Execution 간단정리 (0) | 2024.07.10 |
Process API, 시스템콜을 알아보자 (0) | 2024.06.05 |
프로세스의 추상화 과정 (The Abstraction: A Process) (0) | 2024.06.05 |