흐르는 시간의 블로그...

*** 스크랩 글이므로 추가적인 스크랩은 제한합니다 ***

** 본문출처 : http://www.codeforum.net/blog/pitoosung/entry/Stack-Overflow%EC%99%80%EC%9D%98-%ED%95%9C%ED%8C%90-%EC%8B%B8%EC%9B%80**

Stack Overflow와의 한판 싸움

최근 시스템과의 인터페이스 프로토콜을 Ethernet Protocol만을 사용하기로 Spec이 결정되어 Windows 플래폼에서 이를 하기 위해 winpcap 라이브러리를 근간으로 솔루션을 개발하였다.

winpcap을 사용하는 목적이 모니터링을 위한 캡쳐보다는 커뮤니케이션에 있었기 때문에 커뮤니케이션을 처리할 수 있는 Wrapper Class Framework을 작성하였다.

Stack Overflow에 의한 재밌는 현상(순전히 --; 나의 무식한 실수에 의한 것이었다)을 발견한 것은 winpcap에서 Broadcast와 내가 내보내는 패킷의 캡처를 막기위해 사용한 필터 시스템 때문이었다.

winpcap에서 제공하는 필터 엔진을 사용해 해당 기능을 수행했는데, 실수로 6개로 구성된 맥 주소를 표현하면서 CString Format함수에 파라미터를 7개 주면서 발생했다. 결과적으로 마지막 7번째에 쓰레기 값을 전달한 것이다. 기능이 많아지기 전까지 다행히 아무 이상이 없던 솔루션이 기능이 많아지고 UI와 연동되면서 폭발하고 말았다.

정말 다행스럽게도 현상은 모든것이 정상 동작하나 다만 CPU 점유율을 60~70% 계속 유지한다는 것이었다 --;

원인 파악을 위해 신뢰할 만한 프로그램인 winpcap 기반의 ethereal을 사용해 똑같은 조건에서 수행하고, Wrapper Class Framework의 데모 프로그램을 사용해 동작 시켰으나 어떤 Overhead도 없었다. 가만히 Wrapper Class Framework 소스를 들여다 보다 "앗"하고 허탈한 웃음을 지었다.

Sending 패킷을 막기 위해 Filter를 설정하는 과정에서 Format에서는 6개의 맥주소를 받도록 정의해 두고 정작 파라미터는 7개를 전달하고 있었다.

역시 추측해 볼 수 있는 것은 "Stack Overflow"였다. CString Format 함수의 안정성은 알 수 없으나 Format에서 정의한 파라미터 개수가 틀릴 때 다른 메모리 영역을 침범하거나, 다행히 다른 영역 침범은 없고 Filter String에 스레기 값이 들어가 Filter Engine이 오동작 하든지 했을 것이다.

지금 생각해 보면 Stack Overflow 보다는 후자라는 생각이 들기도 하지만, 문제 코드를 사용한 데모 프로그램에서는 문제가 보이지 않았던 것으로 보아 Stack Overflow를 의심해 볼 수 도 있다.

Stack Overflow의 가장 심각한 문제는 언제 터질지 모르는 폭탄과 같으며, 전혀 예상하지 못한 곳에서 불규칙하게 폭탄이 터진다는 것이다.

이런 Stack Overflow에 대해 컴파일 타임, 또는 런타임에 인지할 수 있는 방법이 없는지 지금부터 알아보려 한다.

예전 부터 OS차원에서 Stack Overflow가 일어나더라도 이로인해 불법적 코드나 의도된 곳의 코드가 실행되게 하는 것을 막는 방법이 시도되고 있었던 듯 합니다. - 여하튼 Stack Overflow 자체를 막거나 감지할 수는 없는 듯 합니다. - OpenBSD, buffer overflow 문제 해결?

위 글에 달린 덧글이 더 유용하군요 - gcc 에 이런 확장 코드가 있다는 군요 - "일본 IBM의 에토 히로아키씨의 stack protector 코드가 OpenBSD gcc에 포함되었습니다. 그걸 이야기하는것 같군요."


앗 주제가 점점 보안쪽으로 흘러가네요 --; 전 다만 개발자가 경륜의 고하와 관계없이 코딩하다가 하기 쉬운 실수를 통해 유발되는 Stack Overflow가 나중에 종잡을 수 없는 폭탄이 되는것을 좀더 쉽게 인지할 수 없겠는가를 알고 싶었습니다.

위 프로젝트 페이지를 보면 StackGuard 원리를 기본으로 하고 있다고 나와 있습니다. 그래서 도대체 StackGuard가 무엇인지 알아보았습니다. 의외로 간단합니다. 보안 이슈에서 Stack Overflow 공격은 Stack Overflow를 통해 코드상의 함수 return address를 변경하여 자신이 넣은 코드로 가게 하거나, 이미 존재하는 자신이 원하는 코드로 가게 하는 방법을 쓰는 것으로 알고 있습니다. 이를 막기위해 함수의 return address 변경을 감지하거나 방지하는 방법을 쓰고 있답니다. return address 바로 다음에 특정코드(수행시 랜덤하게 생성된답니다)를 넣어 그게 변경되었으면 Stack Overflow가 일어난 것으로 추정한다는 것이죠!

관련 글의 링크입니다. StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks

좋은 글로 생각되어 글 쓰신분의 동의 없이 무단 전제합니다.

/**************************************************************************************************
제목 : StackGuard: Automatic Adaptive Detection and Prevention of Buffer-Overflow Attacks
원문 : http://www.cse.ogi.edu/DISC/projects/immunix/StackGuard/usenixsc98_html/paper.html
저자 : Crispin Cowan, Calton Pu, Dave Maier, Heather Hintongif, Jonathan Walpole,
Peat Bakke, Steve Beattie, Aaron Grier, Perry Wagle and Qian Zhang
Department of Computer Science and Engineering
Oregon Graduate Institute of Science & Technology
번역 : 매맞는아이 (sn1995ar at hotmail.com - 잘못된 곳 있으면 댓글 바람)
*****************************************************************************************************/


# StackGuard: Making the Stack Safe for Network Access

* Detecting Return Address Change Before Return
o Randomizing the Canary
* Preventing Return Address Changes With MemGuard
* Adaptive Defense Strategies


# StackGuard: Making the Stack Safe for Network Access

StackGuard는 실행 가능한 코드를 만들어 buffer-overflow 공격을 탐지하고 방어하는 컴파일러 확장이다.
프로그램의 일반 함수에서 잘 작동한다..
프로그램을 알아 챌 수 있는 단 하나의 방법은..
StackGuard가 정의되지 않은 행동으로 C 구문을 실행하기 때문이다.
StackGuard 프로그램은 실행중인 함수의 return address를 사용하는 행동을 정의한다.

buffer-overflow 공격 방식은 스택을 이용한다.
버퍼를 덮어쓰는 방법은 스택에 코드를 올리고 return address를 올린 코드의 주소로 바꾸는 것이다.
StackGuard는 return address를 바꾸는 스타일의 공격을 방해한다.
만약 return address를 바꿀 수가 없다면.. 공격자는 공격코드를 실행시킬 수가 없다..
그리고 공격은 실패하게 된다.

StackGuard는 함수가 return 되기 전에 return address의 변화를 감지거나 덮어쓰는 것을 방지한다.
return address의 변화를 감지하는 것은 효과가 있다.. return address의 변화를 방지하는 것은 더욱 효과적이다..
StackGuard는 이 두가지 방법을 다 제공한다.



*Detecting Return Address Change Before Return

사용자 삽입 이미지





1. Canary Word Next to Return Address

return address 변화의 탐지는 함수가 return되기 전에 시행되어야 한다.
StackGuard는 스택상의 return address 다음에 "canary" word를 위치시킴으로서 작동한다.
함수가 return 될때.. canary word가 변하지 않고 그대로 있는지를 체크한다..

이런 방식은 canary word가 바뀌었다면 return address 또한 바뀌었다고 가정하면서 시작한다..
buffer-overflow로 공격할 return address는 bounds checking을 하지 않고.. buffer에서 가까이 위치한다.
공격자는 연속된 메모리를 거슬러 올라가면서 공격코드를 올린다.
이런 제한된 상황때문에.. canary word를 건드리지 않고 RET를 바꾸는건 쉽지 않다..

move canary-index-constant into register[5]
push canary-vector[register[5]]


2. Function Prologue Code: Laying Down a Canary

move canary-index-constant into register[4]
move canary-vector[register[4]] into register[4]
exclusive-or register[4] with top-of-stack
jump-if-not-zero to constant address .canary-death-handler
add 4 to stack-pointer
< normal return instructions here>
.canary-death-handler:
...


o Randomizing the Canary
canary 방어는 canary를 감지하지 못하는 대부분의 buffer-overflow 공격을 막기에 충분하다.
사실, 컴파일러의 calling conventions는 대부분의 buffer-overflow 공격을 막기에 충분하다.
대부분의 현재 buffer-overflow 공격은 꽤 다루기 힘들다.
하지만 buffer-overflow 공격을 사용하는 것이 매우 어려운 것은 아니다.

- RET의 변화를 살펴보기 위해서 공격자는 새로운 값을 몇번씩 입력한다..
- 프로그램에 대한 공격 코드의 offset을 맞추기 위해서, 공격자는 많은 NOPs를 사용해서 NOPs 부분으로 점프한다.
- alignment를 정확히 맞추기 위해서 공격자는 단지 4번의 시도만 필요로 한다..

StackGuard를 극복하기 위해서 특별한 공격을 하는 것도 하능하다.
buffer-overflow를 탐지하는 canary를 극복하기 위한 2가지의 방법이 있다..

1. Canary word가 있는 부분을 건너뛰는 것이다.
bounds checking을 하지 않고 필요한 것들을 채울 만큼 크지 않은 공간이라도 canary word가 위치한 곳을 남겨두고 복사하는 것은 가능할지 모른다.
하지만 이런 종류의 취약점은 드물고.. 공격하기도 어렵다.

2. Canary word를 흉내내는 것이다. 만약 공격자가 canary 값을 쉽게 추측할 수 있다면 공격 코드에
넣음으로써 공격 할 수 있다. canary word가 정해져있다면 추측하기가 쉬울 것이다.
이 공격법은 좀 미심쩍다..


Canary 값을 추측하기 어렵게 하기 위해서 random하게 값을 준다.
우리가 현재 사용하는 방법은 프로그램이 시작할때 ramdom canary words를 사용하기 위해서 crt0 library를 강화한다.
이 ramdom words는 random canary words로 사용된다.
Canary 값을 추측하는게 불가능하진 않지만 쉽지도 않다..
공격자는 ramdom하게 선택된 word를 얻기 위해서 실행중인 프로세스의 메모리 이미지를 살펴봐야 한다.



*Preventing Return Address Changes With MemGuard

Synthetix project는 'quasi-invariants'라 불리는 개념을 소개했다.
'quasi-invariants'는 잠시동안 잠겨 있는 상태이다.
하지만 통보없이 변할지도 모른다.
'quasi-invariants'는 활용 분야를 설명하는데 사용됐다 : 코드 활용은 quasi-invariants가 잠겨 있는 동안에만 효과적이었다.
우리는 실행되는 동안의 quasi-invariant와 같은 기능을 return address에 적용시켰다.
이 기능이 실행되고 있는 동안 return address는 변하지 않는 read-only 모드이다..
그러므로 buffer-overflow 공격을 막을 수 있다.


MemGuard는 quasi-invariant의 값을 변화하기 위해 존재하는 코드를 막기 위해 개발된 툴이다.
MemGuard는 잘 짜여진 메모리 보호를 제공한다:
메모리의 일개 단어들은(quasi-invariant terms)..
MemGuard API로 확실하게 만들어진 것들을 제외하곤 read-only 모드로 될 수 있다.
우리는 좀 더 안전하게 하기 위해서 MemGuard를 사용한다.



push a
push b
move 164 into a
move arg[0] into b
trap 0x80
pop b
pop a




o Function Prologue Code: Protecting the Return Address With MemGuard

MemGuard는 return address를 보호함으로써 buffer-overflow 공격을 막는데 사용된다.

MemGuard는 read-only 모드로 quasi-invariant terms를 포함하는 virtual memory pages를 사용한다.
그리고 보호된 pages를 만들기 위해 trap handler를 사용한다.
또한 보호되지 않은 words를 보호된 pages에 쓴다..
보호되지 않은 words를 보호된 pages에 쓰는 것은 보통 쓰는 것에 대한 대가의 약 1800배에 달한다.
quasi-invariant terms가 커널 주소 공간의 한적한 부분에 있고 MemGuard가 주로 디버깅으로 사용 될 때..
이것은 맘에 드는 방식이다.

보호된 words가 stack의 top부분에 위치해 있을때는 이것은 받아들이기 힘들다.
MemGuard는 커널안에서 다양한 것들을 보호하도록 설계되었다.
stack을 보호하기 위해 MemGuard는 몇가지 방법을 사용한다.

- 사용자 pages를 보호하기 위해 VM model을 사용한다.
- return address 근처에서 비번히 쓰여지는 words에서 오는 'false sharing' 때문에 peroformance는 나누어져야 한다.
- MemGuard를 위해 light-weight system-call interface을 제공한다.
virtual meomry hardware를 사용하는 것은 특권이다..
그렇기 때문에 어플리케이션 프로세스는 word를 보호하기 위해 커널 모드로 사용되어야 한다.

이런 방법들은 소프트웨어 개발에 있어 단순한 것들이다.. 하지만 수행하는데 있어 문제가 있을지도 모른다.
다행히도 Pentium processor는 4가지 'debug' registers가 있다.
이 registers는 각 register에 올라간 virtual address를 read, write, 그리고 execute 접근을 지켜보고..
문제가 발생하면 예외를 일으키도록 구성되었다.

우리는 이 registers를 가장 최근에 보호된 return addresses의 cache로 사용한다.
목표는 read-only된 스택의 top-most page의 필요성을 없애는 것이고..
스택 꼭대기에 변수들을 사용함으로써 발생하는 page faults를 제거하는 것이다.
스택 꼭대기의 쓰기 특권은 조절되어야 한다.

가장 최근의 4가지 return addresses를 보호한다는 것이 모든 것을 보호한다는 것은 단지 가능성에 근거를 두고 있다.
하지만, 만약 컴파일러가 가장작은 page의 1/4의 stack frames를 잡는다면..
4가지 resisters가 top page를 덮을 것이라는 것은 항상 사실이다.

이런 방법을 이용하면..
stack frames의 가장 작은 size를 줄임으로써 공간의 소모량을 줄이며..
또한 stack의 top page는 항상 Memguard의 보호를 필요로 한다는 가능성을 증가시킨다.



*Adaptive Defense Strategies

StackGuard는 보안 위협에 대처 할 수 있도록 만들어졌다.
우리는 위협에 대처 할 수 있도록 Canary versions과 MemGuard versions 사이를 switching 할 수 있도록 제공한다.

기본 모델의 StackGuard는 buffer-overflow가 발생하면 Canary나 MemGuard에 의해 프로세스가 종료하게 된다.
프로세스가 종료해야된다..
왜나햐면 공격이 탐지된 시간에는 상태가 어떤지 믿을 수 없기 때문이다.
그래서 프로세스를 안전하게 되찾는 것이 불가능하다..
그러므로 프로세스가 종료하면.. 공격자로부터 어떠한 변조도 피할 수 있다.

죽은 프로세스를 되돌리는 것은 context-dependent이다.
많은 예에서..
접속을 요구하는 서비스에서는 inetd를 재시작하는 것만으로도 충분하다.
하지만 만약 inetd가 관리하는 데몬이 아니라면 그 데몬을 재시작 할 필요가 있다..

StackGuard의 Canary versions와 MemGuard versions는 security와 performance 사이의 교류에 있어 다른 점을 제공한다.
Canary 버젼은 좀 더 performance하고 MemGuard는 좀 더 Security하다.
특별한 사실은..
Canary versions의 중요한 보안 결함은 Canary value를 추측 할 수 있다는 것이다.
Canary versions는 value 값을 추측하는 것을 종료에 의해서 자기 자신을 방어한다.
그리고 공격당한 Canary-guarded daemon을 MemGuard-guarded daemon으로 대체한다.

이런 반응은 항상 high-performance 상태를 유지하며 시스템이 동작할 수 있게 해준다.
그리고 low-performance로 바뀔 수 있다. 공격 당할 때는 high-security 상태를 유지한다.
공격자는 주기적으로 lower-performance MemGuard mode가 되게끔 데몬을 공격함으로써 Degradation-Of-Service를 일으킬지도 모른다.
하지만 서비스는 전적으로 다운되지 않는다.
왜나하면 데몬은 function을 계속 실행하고.. 공격자는 더 이상 buffer-overflow를 통해 불법적인 권한을 얻을 수 없을 것이다.




**********
현재 StackGuard나 StackShield를 회피하는 공격법들이 존재하며..
그 공격을 막기 위해 StackGuard나 StackShield도 조금씩 변화해 왔다.
**********




역시나 보안쪽 주제들이 많을 것으로 예상됨으로 방향을 선회하고자 한다.

역시 예방만한 방법은 없으며 위를 위해 가장 일반적인 방법이 소스 코드의 논리적 결함을 발견해 주는 정적 코드 결함 분석 툴들이다. 내가 알고 있는 툴은 DevPartner 제품군이다.

먼저 코드 결함을 알려주는 툴들에는 어떤 것들이 있는지 Google에서 "코드 결함"이란 키워드로 검색해 보았다.

먼저 눈에 띄는 것이 모질라 재단에서 소스 코드 문제점 분석을 위해 Coverity를 이용한다는 발표가 있었군요.

다음으로 Klocwork K7 라는 도구에 관련된 세미나 알림 글이 있군요. 기타 시장이 극히 제한적일 거라 생각되지만 많은 제품들이 있는것으로 보입니다.

또한 방금 자료를 찾다가 Visual Studio 2005 Team System에서는 단위 테스트 및 코드 점검을 툴 차원에서 지원하는 것으로 보였습니다. 구체적으로 찾아봅니다.

먼저 개발 환경의 새로운 변화「비주얼 스튜디오 2005 맛보기

다음의 소개 내용이 있다.

분석 도구의 필요성
여러 다양한 분석 도구들 중에서 소프트웨어 분석 요건을 충족하는 동시에 예산 범위를 벗어나지 않는 도구를 선택하는 것은 항상 쉬운 일만은 아니다. 게다가 이러한 도구는 비주얼 스튜디오 IDE에 통합되지 않을 수도 있고, 능숙하게 사용할 수 있으려면 일반적으로 별도의 시간을 투자해 사용 방법을 습득해야 한다.

별도의 사용자 지정 분석 도구를 구현하는 작업도 쉽지 않은 작업으로 이를 위해서는 자원, 기술 및 경험이 요구된다. 하지만 현실적으로 대부분의 개발팀이 이 모든 것을 갖추기란 어려운 일이며, 이러한 방법으로 분석 도구를 선택할 수 있는 회사는 거의 없을 것으로 생각된다.

촉박한 마감 시간과 한정된 예산을 핑계로 분석을 수행하지 않고 코드를 제공하는 것 또한 현실적으로 타당한 방법처럼 보일 수 있으나, 응용 프로그램이 배포된 후에 오류가 발생하면 이를 수정하기 위해 필요한 비용은 배포 전 소요되는 비용보다 더 많은 비용이 들어갈 수 있다. 견고하고 안정적인 응용 프로그램 개발을 위해 팀 디벨로퍼는 다음 두 가지 범주의 분석 도구를 제공한다.

◆ 코드 분석 도구
◆ 성능 도구


코드 분석 도구
코드 분석 도구의 목표는 개발자가 응용 프로그램 개발 중 정적 분석을 통해 코드 결함을 발견하고 수정할 수 있도록 하는 것으로, 이를 위해 PREfast와 FxCop이라는 두 가지 도구가 제공된다.

Prefast는 C/C++ 소스코드에서 있을 수 있는 결함에 대한 정보를 개발자에게 제공해 주는 정적 분석 도구이다. Prefast가 보고하는 일반적인 코딩 결함에는 버퍼 오버런, 초기화되지 않은 메모리, null 포인터 역참조, 메모리 및 리소스 누수 등이 있으며, 개발자의 생산성 증대 측면에서 보다 편리하고 자연스럽게 분석 도구를 사용할 수 있도록 Prefast는 IDE 내에 통합된다.

<화면 8>에서 보는 바와 같이 개발자는 프로젝트의 ‘Property Pages’에서 ‘Yes(/prefast)’를 선택하여 Prefast를 간단하게 활성화할 수 있다.

FxCop는 관리되는 코드 어셈블리를 분석하고 어셈블리에 대한 정보(예 : 닷넷 프레임워크 디자인 지침 명시된 프로그래밍 및 디자인 규칙의 위반)를 보고하는 정적 분석 도구이다. FxCop는 분석 도중에 수행하는 검사를 규칙으로 나타낸다. 규칙은 어셈블리를 분석하고 발견된 사항에 대한 메시지를 반환하도록 관리되는 코드이다. 규칙 메시지는 관련된 모든 프로그래밍 및 디자인 문제를 식별하고, 가능한 경우 문제를 수정하는 방법에 대한 정보를 제공한다.

<화면 8> PREfast 활성화



<화면 9> FxCop활성화



다음으로 바로 Microsoft Visual Studio Team System 2005 둘러보기 를 보자.

소프트웨어 개발자용 Team System

을 보면 FxCop 및 PreFast등의 분석 도구가 언급되어 있다. 앞으로 이 도구들에 대해 탐구하여야 할 것 같다.

위 기사를 보고 현재 사용중인 VS 2003 도 있는지 찾아보았으나 불행이도 없는듯 하다.

MSDN의 코드 분석 도구 사용 지침 도 함 보자.


기회가 된다면 다음에 PreFast 사용에 대한 글을 올려보겠다.


크리에이티브 커먼즈 라이센스
Creative Commons License