(메모리 보호 기법) SSP +Canary (prologue) / DEP
SSP : Stack Smashing Protector (link )
- 로컬 변수 위치 재배치
- 로컬 변수 전에 포인터 배치
- stack canary 삽입
disable
컴파일 옵션에 다음을 지정한다.
1
-fno-stack-protector
stack boundary (정렬) 설정 컴파일 옵션
1
-mpreferred-stack-boundary=x
x
의 기본 값은 4이고, 일반적으로 2를 준다. stack boundary는 2^x
로 설정된다. e.g., 기본 값은 4이므로 기본 stack boundary는 2^4=16
Byte. x == 2
이면 stack boundary는 2^2=4
Byte.
Canary
Prologue 직후 fs
등 segment register를 기점으로 어떤 데이터를 가져와 stack에 저장해놓게 되는데, 이 데이터가 canary가 된다. Canary는 8byte 이상의 char
배열이 존재하거나 string처리 함수를 호출하는 함수에만 적용되는 것이 기본 설정( -fstack-protector
)이고, 설정을 변경하면 모든 함수에 적용할 수 있다. Epilogue 직전 fs
를 통해 접근한 원본 데이터와 stack에 저장해놓은 데이터를 xor
연산을 통해 비교하게 되는데, 결과가 0이 아니면 call \_\_stack\_chk_fail
하게 되어 SIGABRT
가 발생하게 된다.
Hex-Ray를 통해 확인해보면, segment register에 접근하는 부분이 \*MK\_FP(seg_reg, offset)
으로 나타난다. * call \_\_stack\_chk_fail
하는 부분은 나타내 주지 않으므로 참고. * \*MK_FP()
는 make far pointer. segment register를 지정해서 접근할 때 사용한다.
exploit
fork()
하는 경우 canary도 parent의 것을 그대로 복사해 가져오기 때문에 항상 동일한 값을 가진다.- 프로세스 내 모든 함수에서 canary로 사용하는 값은 같다. 따라서 어느 함수에서든 canary를 알아내기만 하면 된다.
- canary의 하위 1 byte는
0x00
으로 고정이다. 보통 이어지는 변수 출력으로 leak하는 것이 편하고, 불가능한 경우 brute force로 충분히 깰 수 있다. 하위 1 byte 부터 각 byte를 순서대로 알아내는 것이 효율적이다.
참고 : main(argv)를 쓸 때와 쓰지 않을 때의 prologue
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
int main(int argc, char\* argv[]) {
char buf[1024];
return 0;
}
gcc -o re1 re1.c -fstack-protector
0x000000000000066a <+0>:
push rbp
0x000000000000066b <+1>:
mov rbp,rsp
0x000000000000066e <+4>:
sub rsp,0x420
// main(argv)를 적어주면 다음 두 라인이 추가된다. 안적으면 없음.
0x0000000000000675 <+11>:
mov DWORD PTR [rbp-0x414],edi
0x000000000000067b <+17>:
mov QWORD PTR [rbp-0x420],rsi
0x0000000000000682 <+24>:
mov rax,QWORD PTR fs:0x28 // canary
0x000000000000068b <+33>:
mov QWORD PTR [rbp-0x8],rax
0x000000000000068f <+37>:
xor eax,eax
0x0000000000000691 <+39>:
mov eax,0x0
0x0000000000000696 <+44>:
mov rdx,QWORD PTR [rbp-0x8] // canary
0x000000000000069a <+48>:
xor rdx,QWORD PTR fs:0x28 // canary
0x00000000000006a3 <+57>:
je 0x6aa <main+64> // canary
0x00000000000006a5 <+59>:
call 0x540 <\_\_stack\_chk\_fail@plt> // canary
0x00000000000006aa <+64>:
leave
0x00000000000006ab <+65>:
ret
DEP : Data Execution Prevention
stack과 heap에 x
permission을 빼서 stack과 heap에 삽입된 code가 실행되는 것을 막는 방법. * NX권한(Not Executable)을 설정한다고 말하기도 한다. Note ) NX check는 어떤 checksec을 써도 제대로 동작한다고 보장하기 어렵다. 그냥 nxtest 사용하는게 속편함.
disable
1
-z execstack // compile option
If an application attempts to run code from a protected page, the application receives an exception with the status code STATUS\_ACCESS_VIOLATION
. * 설정 된 Protection Constant에 따라 다른 Exception이 발생한다.
Bypass
windows
1
2
3
4
5
6
7
8
9
10
11
12
LPVOID WINAPI VirtualAlloc(
\_In\_opt\_ LPVOID lpAddress,
\_In\_ SIZE\_T dwSize,
\_In\_ DWORD flAllocationType,
\_In\_ DWORD flProtect
);
BOOL WINAPI VirtualProtect(
\_In\_ LPVOID lpAddress,
\_In\_ SIZE\_T dwSize,
\_In\_ DWORD flNewProtect,
\_Out\_ PDWORD lpflOldProtect
);
페이지에 PAGE\_EXECUTE_READWRITE( 0x40 )
을 준다.
Linux