off-by-one 이란?
· 문자열의 범위 1 byte 차이로 인해 발생하는 취약점
· 1차이로 인해 SFP(Stack Frame Pointer)의 1바이트가 NULL(\x00)으로 변조되게 되고 이로 인해 기존의 SFP의 주소
가 아닌 전혀 다른 주소로 리턴하게 된다.
문자열의 마지막에는 반드시 NULL 값이 들어간다. 그렇기 때문에 선언된 배열에 문자열을 저장할 때에는 반드시
NULL 값까지 고려해야 한다. 하지만 정해진 공간을 모두 문자열로 채우면, NULL 값은 영역을 넘어 SFP를 변조하게 된다.
그렇다면 이런 변화가 어떻게 문제가 될 것인가?
이를 이해하기 위해서는 함수 에필로그에 대해 알 필요가 있다.
함수를 디버깅해보면 함수의 마지막에 leave,ret 명령이 있으며 이는 main 함수를 포함한 모든 함수에 적용된다.
leave 와 ret 은 이런 명령을 담고 있다.
* leave
mov esp,ebp
pop ebp
* ret
pop eip
jmp eip
leave 에서는 스택 포인터를 서브 함수를 호출하기 이전의 주소로 되돌리는 작업을 수행하며, ret 에서는 함수를
수행하고 main 함수로 돌아가는 작업을 한다.
여기까지가 함수 에필로그의 간단한 설명이다. 이제는 명령 수행 과정을 차례대로 살펴보겠다.
서브 함수를 수행 중인 상태에서 leave 를 만나면 먼저 mov esp,ebp 를 수행하여 esp 가 ebp 가 가리키는 곳으로
이동하게 된다.
그 다음에는 pop ebp 를 수행하게 되는데, 이제 여기서부터 일반적인 흐름과는 다른 방향으로 흐르게 된다.
원래대로라면 esp가 가리키는 곳에는 SFP 값이 위치하고 있으며, pop ebp 를 수행하면 ebp 에 SFP 의 값이
저장되고, ebp 가 이동하게 된다. 하지만 지금 저장된 값은 변조된 SFP 이다. 따라서 ebp 도 아예 다른 곳을
가리키게 된다는 것이다.
여기까지가 서브 함수의 leave 명령에서 일어나는 이상이다. esp 에 이상이 생긴것은 아니기 때문에,
ret 과정은 문제없이 수행될 수 있다.
서브함수의 ret 과정을 수행하여 main 함수로 돌아오고, 다른 명령들을 수행하고 나면 함수를 끝내기 위해
main 함수의 leave,ret 명령을 만나게 된다.
다음 그림은 main 함수의 leave 에서 mov esp,ebp 를 수행하는 과정이다.
서브 함수 에필로그를 수행하고 이상한 곳을 가리키던 ebp 를 mov esp,ebp 로 인해 esp도 가리키게 되었다.
다음은 pop ebp 를 수행한다.
esp 가 가리키던 부분의 값을 ebp 에 pop 하고, esp 를 이동했다. 애초에 esp 가 가리키던 곳에는 설정된 값이 없었기
때문에 ebp 는 아예 다른 곳으로 이동해버렸고, esp 는 ebp 주소에서 4바이트 더한 으로 이동하게 되었다.
이제는 ret 명령이 수행된다. pop eip 명령으로 현재 esp 가 가리키던 곳의 값이 eip 로 pop 될 것이고, jmp eip 명령으로
eip 가 가지고 있는 주소로 점프하게 될 것이다.
지금 상태에서는 esp 가 가리키는 곳에는 아무런 값도 존재하지 않았으므로 이상한 곳으로 점프하게 되겠지만
만약 esp 가 가리키는 곳에 쉘코드가 있었다면 쉘을 실행하고 권한을 획득할 수 있었을 것이다.
이것이 바로 off-by-one 취약점이다.