Post

(메모리 보호 기법) SSP +Canary (prologue) / DEP

  1. 로컬 변수 위치 재배치
  2. 로컬 변수 전에 포인터 배치
  3. stack canary 삽입
disable

컴파일 옵션에 다음을 지정한다.

1
2
-fno-stack-protector

stack boundary (정렬) 설정 컴파일 옵션
1
2
-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
  1. fork()하는 경우 canary도 parent의 것을 그대로 복사해 가져오기 때문에 항상 동일한 값을 가진다.
  2. 프로세스 내 모든 함수에서 canary로 사용하는 값은 같다. 따라서 어느 함수에서든 canary를 알아내기만 하면 된다.
  3. 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
41
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
2
-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
13
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

2017/05/10 - [System/function] - mprotect

This post is licensed under CC BY 4.0 by the author.