Canary

‍김상빈·2023년 10월 7일

learning_pwnable

목록 보기
2/6

오늘은 RET을 보호해주는 기법인 Canary에 대해서 알아보려고 한다.

Canary

stack canary는 함수 프롤로그 때 버퍼와 RET 사이에 임의의 값을 삽입한 후 에필로그 때 이 값이 변조되었는지 확인 후 변조되지 않았으면 RET으로가서 return을 하고 변조가 되었으면 바로 exit하는 기법을 말한다.

gdb를 확인 해 보면 rcx와 fs:0x28을 xor하여 값이 일치하면 main+70으로 return하고 그렇지 않으면 __stack_chk_fil을 실행해서 강제 종료시키는 것을 확인 할 수가 있다.

fs는 목적이 정해져있지 않은 레지스터로 운영체제가 임의로 가져다가 사용이 가능한 세그먼트 레지스터이다. 리눅스에서는 TLS를 가리키는 포인터로 사용하고 있다.

Canary 생성 과정

프로세스가 시작함과 동시에 fs:0x28에는 랜덤한 값이 저장이 된다. 그 값을 rax에 저장시킨 후 그 값을 rbp - 0x8위치에 넣는다. 이렇게 해서 Canary를 sfp 전에 저장시키는 것을 확인 할 수가 있다.

여기서 누가 fs:0x28에 랜덤한 값을 저장시키는지 의문이 들 수가 있다. 프로세스가 시작이 되면 security_init 함수가 fs:0x28에 값을 랜덤으로 값을 넣는것을 watch로 확인 할 수가 있다.

TLS는 기본적으로, 멀티스레드 프로그램에서 모든 스레드는 데이터 영역을 공유한다. 이는 여러 스레드가 동시에 동일한 데이터에 액세스할 때 문제가 발생할 수 있기 때문에, 때로는 스레드별로 데이터를 격리시키는 것이 필요할 수 있다. 이때 TLS를 사용하면 각 스레드에 대해 개별적인 데이터를 할당할 수 있어, 스레드 간의 데이터 충돌 없이 안전하게 데이터를 관리할 수 있다.

실제로 canary 값을 보면 첫 바이트가 널인 8바이트 데이터인 것을 확인 할 수가 있다.

x64 아키텍처에서는 8바이트의 카나리가 생성되며, x86 아키텍처에서는 4바이트의 카나리가 생성된다.

참고 부분

카나리의 첫 바이트가 널 바이트 (0x00)인 이유는 문자열 연산 중에 종료 조건을 만나면 연산이 중단되는 C와 같은 언어의 특성을 이용하기 위함입니다. C언어에서 문자열은 널 바이트로 종료되므로, 공격자가 문자열을 사용하여 버퍼를 오버플로우 시킬 경우, 널 바이트에 도달하면 문자열 연산이 중단됩니다. 따라서, 카나리의 첫 바이트를 널 바이트로 설정함으로써 일부 문자열 기반의 버퍼 오버플로우 공격을 방어하는 추가적인 방법을 제공합니다.

그러나 모든 카나리 구현이 첫 바이트를 널 바이트로 가지는 것은 아닙니다. 다양한 카나리 전략과 값이 있으며, 각각은 고유한 장점과 단점을 가집니다. 널 바이트를 포함하는 카나리는 그 중 하나의 전략일 뿐입니다.

우회 방법

그렇다면 어떻게 우회를 해야할까라는 고민이 들기 시작할 것이다.

드림핵에서는 TLS 주소를 런타임 중에 알수 있다면 TLS에 저장되어있는 canary를 읽어 오는 방법과 canary leak 두가지를 이야기하고 있다. 물론 무차별 공격도 존재는 하지만 현실에서 사용하기에는 힘들기에 제외 시키도록 했다.

Canary Leak

leak의 뜻은 새다로 말 그대로 canary 정보가 새어나와서 그걸 토대로 해킹을 시도하겠다 이런 방법이다. 그렇다면 어떻게 leak이 되는지에 대해서 알아보겠다.

예를 들어 read 함수를 통해서 읽어올수 있는 바이트 수가 50이고 버퍼의 크기가 42이라고 가정한다. 그리고 버퍼를 다 채우게 된다면 뒤에 저장되어있는 canary까지 읽어올 수 있기 때문에 leak이 발생하는 것이다.

그래서 실제로 출력해보면 입력한 값 뒤에 이상한 값들이 붙어있는것을 확인 할 수가 있다. 그리고 마지막에는 널바이트인 a가 있다. 그래서 만약 wargame을 풀 때는 7바이트만 recv한 후 널바이트를 채우는 방식으로 원래 canary 값으로 우회한다.

profile
nickname: pwn_newbe

0개의 댓글