Buffer-Overflows_2
Off-by-one buffer overflow는 단일 바이트 값이 오버플로우된 경우로 보통 Null 종료 버그
나 libc
함수 사용 시 계산 실수로 인해 발생된다. Adjacent memory overflows는 Null 종료 버그
를 이용하여 두 문자열을 연결하는 경우로 메모리 상에서 두 버퍼가 나란히 붙어있어 이러한 명칭을 가진다. 예를 들어 Null 종료 버그
로 인해 종료가 제대로 되지 않으면 strlen()함수와 같이 Null 종료에 의존적인 함수들이 인접한 메모리를 계속 읽게 된다.
Off-by-one buffer overflow
다음은 Off-by-one buffer overflow의 예시로 메모리 색인 시 Off-by-one buffer overflow가 발생한다.
static int
b64_decode( const char* str, unsigned char* space, int size )
{
const char* cp;
int space_idx, phase;
int d, prev_d = 0;
unsigned char c;
space_idx = 0;
phase = 0;
for ( cp = str; *cp != '\0'; ++cp )
{
d = b64_decode_table[(int) *cp];
if ( d != -1 )
{
switch ( phase )
{
case 0:
++phase;
break;
case 1:
c = ( ( prev_d << 2 ) | ( ( d & 0x30 ) >> 4 ) );
if ( space_idx < size )
space[space_idx++] = c;
++phase;
break;
case 2:
c = ( (( prev_d & 0xf )<< 4 ) | ( ( d & 0x3c ) >> 2 ));
if ( space_idx < size )
space[space_idx++] = c;
++phase;
break;
case 3:
c = ( ( ( prev_d & 0x03 ) << 6 ) | d );
if ( space_idx < size )
space[space_idx++] = c;
phase = 0;
break;
}
prev_d = d;
}
}
return space_idx;
}
이 코드를 보고 Off-by-one buffer overflow
취약점을 알아차리기는 어렵다. 테이블 내에서 문자를 디코딩할 수 있는지 확인하는 코드로 가능한 경우 switch-case문을 수행한다. switch-case문 내부의 if문은 space_idx 변수를 size 인자 값과 비교하여 space_idx 변수가 size 변수보다 작으면 space_idx 변수 값을 1씩 증가시킨다.
이때, space_idx는 255이고 size는 256이라면 space_idx는 size보다 작기 때문에 space [space_idx ++] = c;
명령을 수행하게 되는 데, 이로 인해 Off-by-one buffer overflow 취약점이 발생하게 된다. 배열은 0부터 시작하여 인덱싱한다. 색인 생성이 0에서 시작하기 때문에 이와 같이 선언 된 배열 (예 : ‘char array [256];’)은 마지막 유효 색인이 255가 된다. 따라서 데이터가 저장되는 버퍼의 총 크기가 하나씩 버퍼 오버플로우가 발생한다.
Adjacent memory overflow
다음은 로깅 데몬 syslogd
에서 발생한 Adjacent memory overflow
이다.
/*
* Validate that the remote peer has permission to log to us.
*/
int
validate(sin, hname)
struct sockaddr_in *sin;
const char *hname;
{
int i;
size_t l1, l2;
char *cp, name[MAXHOSTNAMELEN];
struct allowedpeer *ap;
if (NumAllowed == 0)
/* traditional behaviour, allow everything */
return 1;
strncpy(name, hname, sizeof(name));
if (strchr(name, '.') == NULL) {
strncat(name, ".", sizeof(name) - strlen(name) - 1);
strncat(name, LocalDomain, sizeof name -strlen(name)-1);
}
...
}
strncat()
길이를 지정하여 두 개의 문자열 합침
참고 : falinux - strncat()strlen 과 sizeof 차이
문자열을 취급 할 때에는 strlen을 사용하고, 버퍼의 크기나 변수의 크기를 다룰때에는 sizeof를 사용한다.
참고 : falinux - strlen 과 sizeof 의 차이
이 예에서 hname변수는 MAXHOSTNAMELEN 바이트 이상이고 마침표를 포함하지 않는다고 가정한다.
strncat함수는 name 변수의 크기에서 name 변수에 저장된 바이트 수와 1을 뺀 값이 적용되며 name 변수의 바이트 수가 name 변수에 저장된 바이트 크기와 같은 경우 음수 값을 갖습니다.
('sizeof(name) – strlen(name) – 1')
만약 음수 값을 갖게 된다면 부호가 없는 정수가 나타낼 수 있는 최대 값 4GB의 데이터를 복사하려고 하게 된다. 이로 인해 버퍼 오버플로가 발생한다.
버퍼 오버플로우 취약점을 일으키는 원인을 이해하는 것이 중요하다. 그리고 버퍼 오버 플로우 가 어떻게 악용될 수 있는지 이해하는 것 또한 중요하다. Smashing The Stack For Fun And Profit논문은 이를 학습하기 좋은 문서 중 하나이다.