본문 바로가기

드림핵

[드림핵]카나리 동적 분석

간단한 C코드를 작성한 뒤 카나리 기법이 적용된 방식으로 컴파일했다.

컴파일된 실행파일을 동적 분석하면서 카나리가 어떤 방식으로 Stack Buffer Overflow 공격을 탐지하는지 확인했다.

 

활용한 C코드(canary.c)는 아래와 같다.

#include <unistd.h>

int main() {
	char buf[8];
    read(0, buf, 32);
    return 0;
}

크기가 8 바이트인 buf 변수에 32바이트를 읽어오므로 Stack Buffer Overflow 취약점이 있는 코드이다.

 

이 코드를 아래와 같이 컴파일하면 카나리가 적용된 실행파일로 컴파일된다.

$ gcc -o canary canary.c -fstack-protector

Ubuntu 22.04에서는 -fstack-protector 옵션이 없어도 기본으로 카나리가 적용되는데 나는 칼리에서 컴파일했기 때문에 위와 같이 옵션을 추가해야 했다.

 

컴파일한 실행파일을 gdb로 열었다.

$ gdb canary

 

main 함수를 disassemble 했다.

pwndbg> disass main

네모 박스 친 두 부분이 카나리와 관련된 부분이다.

첫 네모를 함수의 프롤로그, 뒷 네모를 함수의 에필로그라고 하자.

 

main+8 부분에서 rax에 어떤 값이 저장되는지 보기 위해 해당 지점에 중단점을 설정한 뒤 실행시킨다.

pwndbg> break *main+8
pwndbg> run

 

한 줄 실행 명령어를 입력해서 main+8 의 명령어를 실행한다.

pwndbg> ni

 

이 지점에서 rax값을 출력해보면 아래와 같이 첫 바이트가 널 바이트인 8바이트 데이터가 저장되어있는 것을 확인할 수 있다.

이것이 카나리 값이다.

 

이 값은 main+17에서 rbp-0x8에 저장된다.

 

이제 이 카나리 값으로 어떻게 return address 변조를 탐지하는지 확인해보자.

먼저 에필로그인 main+50에 중단점을 설정한뒤 바이너리를 계속 실행시킨다.

pwndbg> break *main+50
pwndbg> continue

입력값으로는 임의의 문자('L') 16개를 입력해서 return address까지 overwrite 되도록 했다.

 

main+50에서는 rbp-0x8에 저장되었던 카나리 값을 rcx로 옮긴다.

바로 다음 줄인 main+54에서는 그 rcx를 fs:0x28에 저장된 원본 카나리 값과 xor한다.

두 값이 동일하면 연산 결과가 0이 되어 이후 je 명령어를 통해 main+70으로 건너뛰지만

두 값이 다르면 main+65로 명령 흐름이 진행되어 __stack_chk_fail이 호출되면서 프로그램이 강제 종료된다.

 

main+50에서 멈춘 시점에서 ni를 한번 입력해서 rdx에 rbp-0x8의 값이 이동하도록 한 뒤 해당 값을 출력해본다.

'L'문자로 덮어쓰기 되어 0x4c4c4c4c4c4c4c4c 가 들어있는 것을 확인할 수 있다.

 

이후 ni를 두번 더 입력하면 rip가 main+65로 이동하는 것을 볼 수 있다.

 

ni로 해당 call 명령어를 실행하면 아래와 같이 메시지가 출력되며 프로세스가 강제 종료된다.

 

 

여기까지 카나리의 작동원리를 동적 분석을 통해 알아보았다.