지난 주에 프로그램 기능을 확장하면서 이상한 현상을 겪게 되었다. DLL을 확장한 별도의 모듈 타입을 만들어 쓰는데 그 녀석이 CRT만 연결하면 언로딩할 때 문제가 생기는 것이었다. 정확하게는 DLL을 FreeLibrary 할 때에는 문제가 없는데, 프로그램이 종료될 때 문제가 생겼다.
디버거를 돌려보니 프로그램이 종료되는데 이미 언로딩된 그 DLL의 메모리를 참조하는 것이 문제였다. 정확하게는 Fiber Callback을 호출하면서 문제가 생겼다. 왜 이럴까, 라는 생각을 하다가 여친님께서 납시셨다는 소식과 함께 디버깅 세션을 종료했다.
오늘 출근해서 디버깅을 하고 소스를 보고 하다가 엄청난 사실을 알게 되었다. 문제는 DllMain에 있는 lpReserved에 있었다. 우리가 만든 확장 DLL은 이 lpReserved를 사용해서 뭔가를 하도록 되어 있는데 VC++ CRT 소스에는 아래와 같은 엄청난 라인이 숨어 있었던 것이다. #ifdef _DEBUG가 아니라 #ifndef _DEBUG란 사실에 주의해야 한다. 즉, lpreserved를 비교하는 루틴은 릴리즈 버전에만 포함된다는 사실이다. 결국은 우리가 이 lpresreved로 NULL이 아닌 포인터를 공급했고, 그래서 CRT 정리 루틴이 타지 않았고, 프로세스 종료 시에 CRT가 싸놓은 Fiber Callback을 타면서 크래시가 난 것 이었다.
#ifndef _DEBUG
if ( lpreserved == NULL )
{
#endif /* _DEBUG */
/*
* The process is NOT terminating so we must clean up...
*/
/* Shut down lowio */
_ioterm();
_mtterm();
/* This should be the last thing the C run-time does */
_heap_term(); /* heap is now invalid! */
#ifndef _DEBUG
}
#endif /* _DEBUG */
나의 순진한 믿음, lpReserved는 말 그대로 예약된 것일 거라는 사실이 화근이 되었다. 크게는 남의 것을 함부로 건드린 죄라고 할 수 있겠다. 어쨌든 이제는 알게 되었지만 lpReserved는 아무 곳에도 사용되지 않는 값은 아니다. 디버깅 정보에 의하면 lpreserved는 보통 0이고, 특별하게 프로세스가 종료될 때 같이 DLL이 정리되는 상황에서는 1이 넘어 왔다.
어릴 적 누나가 둘이다 보니 보통 남자 아이들이 잘 해보지 못할 법한 일들을 해보곤 했다. 그 중에 하나가 교과서에 껍데기를 씌우는 일이었다. 누나들은 새 학년이 시작되고, 교과서를 받아오면 늘 껍데기를 씌웠다. 처음엔 포장지로 하다가 학년이 좀 올라가고 나서는 영자 신문과 비닐로 포장을 했었다. 난 사실 포장에 소질이 없었고 늘 누나들이 해줬지만 도대체 왜 그런 짓들을 하는지 이해할 순 없었다. 물론 중학교 때부터는 자연스레 누나들과 멀어지면서 그런 것들을 하지 않게 되었던 것 같다.
세월이 많이 지났다. 책 껍데기에 대한 나의 생각도 조금 바뀌었다. 아예 쓸모 없지는 않다는 생각이다. 바로 어떤 책인지를 감추는 용도로 껍데기를 사용할 수 있기 때문이다. 예전에 누군가 허브 코헨의 “협상의 법칙”이란 책을 사면 이틀에 걸쳐 다 읽고는 그 책에 껍데기를 씌워서 책꽂이에 꽃아 둔다는 이야기를 들은 적이 있는 것이다. 책 내용이 너무 좋아서 남들에게 알려 주기 싫음에서 그렇게 한다는 게 요지였다. 물론 “협상의 법칙”이란 책에 대한 무한한 경외심에서 그런 글을 올렸으리라 생각된다.
최근에 정확하게는 지난 주 였다. 한 권의 책을 읽을 시간이 있었다. “The Rootkit Arsenal”이란 책으로 작년에 발간된 루트킷 관련 서적이다. 책을 읽으면서 눈물을 흘리지 않을 수 없었다. 그리고는 속으로 다짐했다. 절대로 서평을 올리지 않으리라. 반드시 껍데기를 씌워서 책꽂이에 꽃아 두리라. 그 정도로 굉장한 책이었다. 맞다. 여기저기서 웅성거리는 소리가 들린다. 구글링 3분만 하면 다 나오는 그런 것들을 모아둔 책에 왜 그렇게 호들갑이냐는 이야기다. 그런 분들은 조용히 이 페이지를 닫고, 계속 쭉 구글링을 하시면 되겠다. 물론 책에 있는 거의 대부분의 내용은 구글링을 하면 단박은 아니라도 충분히 찾을 수 있는 내용이다. 하지만 우리가 감사해야 하는 사실은 저자가 우리를 대신해서 그런 지식을 검증 과정을 거쳐서 모아서 엑기스로 정리한 다음, 우리의 머릿속으로 쏙쏙 집어넣어 준다는 사실이다. 우리는 감사하게 앉아서 받아 먹으면 되는 것이다. 어쨌든 관련 분야에 있거나 해당 기술 분야에 관심이 있는 분들이라면 담뱃값, 술값, 밥값, 심지어는 교통비까지 아껴서라도 반드시 구매하기를 권하고 싶은 책이다.
책 속으로 잠시 들어가서 이 책이 아웃스탠딩한 이유를 몇 가지 열거해야 할 것 같다. 일단 윈도우 아키텍처를 설명하는 부분에서 비스타 이후 변경된 점을 다룬다는 사실에서 그 첫 번째 이유를 찾을 수 있다. 다른 루트킷 서적이 아직도 XP적 지식에서 허우적대로 있는 것을 감안한다면 굉장한 사실이 아닐 수 없다. 물론 그 다루는 내용이 덜 구체적이라도 우리에게 조사할 수 있는 단초를 제공한다는 점에서 충분히 값어치가 있는 작업이라 할 수 있을 것 같다. 그리고 같은 챕터에서 다루는 윈도우 부팅 과정에 대한 설명 또한 일품이다. 무엇이 어떻게 진행되는지를 체계적으로 다루고 있다. Windows Internals 같은 책도 충실하게 내용을 다루지만, 루트킷 제작자의 입장에서 궁금했던 점들에 대해서는 이 책이 좀 더 상세히 그리고 체계적으로 기술하고 있다. 끝으로 다른 루트킷 책과는 다른 점이 부트 루트킷에 대한 부분에 상당한 양의 지면을 할애한다는 점이다. 기존의 다른 루트킷 서적이 자세히 다루지 않는 부분이라 이 점도 장점이 될 것 같다. 물론 이러한 점 외에도 썰을 풀어가는 과정이 논리적이고, 그림만 봐도 뭐가 어떻게 진행되는지를 알 수 있도록 작성한 저자의 능력에 더 많은 점수를 주고 싶다.
6만원 상당의 책 값이 전혀 아깝지 않은 책이다. 루트킷 개발자라면 6만원으로 누릴 수 있는 최고의 행복이 아닐까, 라는 생각을 감히 해 본다.
때로는 전혀 엉뚱한 곳에서 엄청나게 황당한 일들을 마주하곤 한다. 첫 눈에 반한 것처럼 접근한 여성이, ‘도를 아십니까’라는 사실을 알게 되었을 때와 같은 느낌이랄까? 어쨌든 오늘이 바로 그런 날이었다. 회사 프로그램에 기능 추가를 하고 있는데, 뭔가 의도하지 않은 대로 동작하는 삘이 느껴졌다. 예감대로 무한 루프를 돌고 있었다. 디버깅을 하다가 결국 원인이 되는 라인을 찾았는데 단일 연결 리스트 코드가 문제였다. 바로 다음 라인이다.
for(prev=LstBegin(l); prev->next!=item; prev=prev->next)
break;
C언어를 어제 배운 학생도 이 줄은 완전 뻘짓 라인이라는 것을 알 것이다.
원래 내가 의도했던 라인은 다음과 같은 것이다.
for(prev=LstBegin(l); prev->next != item; prev=prev->next)
;
당연하게도 코드를 고치가 문제는 해결되었다. 하지만 찜찜한 기분은 어쩔 수 없었다. 속으로 아마 코드를 새로운 소스 트리로 옮기는 과정에서 오류가 발생했을 것이라 합리화를 시작한다. 주석이 담긴 줄을 추가 하려다 저런 거겠지, 라고 생각하고 싶었다. 하지만 break는 저 상황에 어울릴만한 문장도 아니다.
svn 트리를 뒤졌다. 결과는 3초도 되지 않아 밝혀졌다. 어처구니 없게도 2007년 4월 처음 코드를 쓰던 당시부터 그렇게 작성한 것으로 나왔다. 흐미~ 이건 머지? ㅋ~ 그 동안 문제가 없었던 것은 그 리스트 코드를 쓰는 곳이 단 한 곳이며, 그 단 한 곳 조차도 기능이 필요 없어서 사용하지 않았기 때문이었다. 3년 만에 처음 설계 당시 포함시켰던 바로 그 기능을 사용하게 된 것이다. 그러면서 케케묵은 버그가 같이 발견된 것이다.
TDD가 어깨 뒤에서 나를 비웃는 소리가 들린다. ^^;;