일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | |||||
3 | 4 | 5 | 6 | 7 | 8 | 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 |
17 | 18 | 19 | 20 | 21 | 22 | 23 |
24 | 25 | 26 | 27 | 28 | 29 | 30 |
![]() [GGGG]이번에 회사에서 책 구매를 하면서 평소에 보고싶었던 원서를 몇 권 신청했습니다. 그런데 요놈들 대박이네요. 그 중 하나가 백신 엔진과 관련된 책입니다. 백신 엔진이라기 보다는 제목 그대로 바이러스 분석에 관한 책입니다. 제목이 "The art of Computer Virus Research and Defense"입니다. 제목 그대로 아트입니다. 책 읽고 있으면 나도 백신 엔진을 만들수 있겠다는 사기가 충만해지죠. 그 정도로 세부적으로 잘 적혀져 있습니다. 전반부는 바이러스를 분석하고, 후반부는 바이러스 스캐너(백신 엔진)에 관해서 다룹니다. 저는 특히나 스캐너 부분이 흥미로웠습니다. 뭐 일세대 스캐너, 이세대 스캐너 이런 식으로 설명을 해줍니다. 일세대 스캐너가 이랬는데, 어떤 놈 때문에 이세대 스캐너가 나왔고 요론 식입니다. 저자의 경험에서 우러나오는 귀에 쏙쏙 들어오는 설명이죠. ㅋ 스캐너 부분 보면 어떤 엔진에서 이러한 방식을 탑재했고, 이 방식은 누가 최초로 고안했고 하는 내용들이 나옵니다. 우리가 흔히 들었던 F-prot, Kaspersky, 유진 카스퍼스키같은 이름들이 나오더군요. 유진 카스퍼스키란 사람이 이쪽 분야에선 굉장한가 보더군요. ㅎㅎ 스캐너 부분에서 제가 굉장히 충격을 받은 것은 매크로 파서와 CPU 에뮬레이션 이었습니다. 매크로 파서는 매크로를 파싱해서 불필요한 더미를 제거하고 코드의 흐름만 비교한다는 것이 요지입니다. 책에는 뼈대 탐지(스켈레톤 디텍션)이란 이름으로 소개되어있습니다. 요 방식을 고안한 사람이 유진 카스퍼스키죠. 매크로의 경우 변경하기가 굉장히 쉽습니다. 변수 이름만 전체 치환해도 새로운 바이러스가 되는 것이죠. 그런 것들을 한방에 통채로 잡기 위해서 나온 방식입니다. CPU 에뮬레이션은 말 그대로 가상의 CPU에서 바이러스 코드를 추적하는 방식입니다. 언뜻 생각하면 CPU를 소프트웨어적으로 흉내내는게 간단해 보이지만 실제로는 생각해야할 것이 많습니다. 특히나 API같은 것은 굉장히 까다롭죠. 그런데 그런 API까지 에뮬레이션 하는 녀석들이 벌써 업계에서는 구현이 되어서 사용되고 있나 봅니다. 아쉬운 점은 책에는 16비트 CPU에 대한 걔략적인 의사 코드 정도만 나온다는 점입니다. 그것 외에도 스캐너 종반부에는 휴리스틱 적인 방법에 대한 소개도 나옵니다. 그런데 그 휴리스틱이 우리가 생각하는 것보다 굉장히 단순한 형태입니다. PE 파일의 이상적인(anomaly) 구조를 판단하는 것이 주된 관점이더군요. 백신과 바이러스에 관심이 있는 분이라면 일독을 권해드리고 싶네요. 아니 소장하시기를 ㅋㅋ. 강컴보니 원서임에도 별로 비싸지도 않습니다. |
[GGGG]아래 문법에 대한 파싱 테이블을 구조체 배열에 저장한 후, 현재 토큰과 상태에 따라 shift/reduce 과정을 반복하는 파서입니다. 스택은 모두 stl의 벡터를 사용해서 구현되었으며, 구현을 단순화 하기 위해서 파서 과정의 오류 처리는 모두 생략하였습니다.문법 get_tok함수가 입력받은 문자열을 스캐닝하는 함수이며, do_reduce함수가 reduce를 수행하는 함수입니다. 그리고 main 함수의 가장 큰 while문이 전체 파서 알고리즘을 표현하고 있습니다. [CPP]#include <stdio.h> #include <vector> using namespace std; #undef TRUE #undef FALSE #define TRUE 1 #define FALSE 0 #define SHIFT 1 #define REDUCE 2 #define GOTO 4 #define ACCEPT 8 #define ID 1 #define SERR -1 #define TABLE_SIZE (sizeof(TABLE)/sizeof(PTABLE)) #define RTABLE_SIZE (sizeof(RTABLE)/sizeof(RDTABLE)) typedef struct _PTABLE { int state; // 현재 상태 int tok; // 입력 토큰 int atype; // 행동 타입 int nstate; // 다은 상태 } PTABLE, *PPTABLE; typedef struct _RDTABLE { int cnpop; int cnpush; char tok; } RDTABLE, *PRDTABLE; // 교제에 나와있는 문법에 대한 LR 파싱 테이블 PTABLE TABLE[] = { {0, ID, SHIFT, 5}, {0, '(', SHIFT, 4}, {0, 'E', GOTO, 1}, {0, 'T', GOTO, 2}, {0, 'F', GOTO, 3}, {1, '+', SHIFT, 6}, {1, '$', ACCEPT, 0}, {2, '+', REDUCE, 2}, {2, '*', SHIFT, 7}, {2, ')', REDUCE, 2}, {2, '$', REDUCE, 2}, {3, '+', REDUCE, 4}, {3, '*', REDUCE, 4}, {3, ')', REDUCE, 4}, {3, '$', REDUCE, 4}, {4, ID, SHIFT, 5}, {4, '(', SHIFT, 4}, {4, 'E', GOTO, 8}, {4, 'T', GOTO, 2}, {4, 'F', GOTO, 3}, {5, '+', REDUCE, 6}, {5, '*', REDUCE, 6}, {5, ')', REDUCE, 6}, {5, '$', REDUCE, 6}, {6, ID, SHIFT, 5}, {6, '(', SHIFT, 4}, {6, 'T', GOTO, 9}, {6, 'F', GOTO, 3}, {7, ID, SHIFT, 5}, {7, '(', SHIFT, 4}, {7, 'F', GOTO, 10}, {8, '+', SHIFT, 6}, {8, ')', SHIFT, 11}, {9, '+', REDUCE, 1}, {9, '*', SHIFT, 7}, {9, ')', REDUCE, 1}, {9, '$', REDUCE, 1}, {10, '+', REDUCE, 3}, {10, '*', REDUCE, 3}, {10, ')', REDUCE, 3}, {10, '$', REDUCE, 3}, {11, '+', REDUCE, 5}, {11, '*', REDUCE, 5}, {11, ')', REDUCE, 5}, {11, '$', REDUCE, 5}, }; // 리듀스시에 작업해야 할 내용을 담은 테이블 RDTABLE RTABLE[] = { {0,}, {3, 1, 'E'}, {1, 1, 'E'}, {3, 1, 'T'}, {1, 1, 'T'}, {3, 1, 'F'}, {1, 1, 'F'} }; // 토큰 스캐너 // 토큰을 저장하고 있는 문자열과, 해당 토큰을 제거할지 입력 char get_tok(char **str, int remove) { int tok = SERR; int rstep = 0; if(*str == NULL) return SERR; if(**str == '\0') return SERR; if(**str == 'i' && *(*str+1) == 'd') { rstep = 2; tok = ID; } else if(**str == '*' || **str == '+' || **str == '(' || **str == ')' || **str == '$') { rstep = 1; tok = **str; } if(remove) *str += rstep; return tok; } // 상태와 토큰에 해당하는 스택 출력 void print_stack(vector<int> &State, vector<char> &Token) { char buffer[4096], tmp[80]; sprintf(buffer, "%d ", State.front()); for(int i=0; i<Token.size(); ++i) { if(Token[i] == ID) sprintf(tmp, "id %d ", State[i+1]); else sprintf(tmp, "%c %d ", Token[i], State[i+1]); strcat(buffer, tmp); } printf("%-40s", buffer); } // 확장 트리 출력 void print_rtrace(vector<int> &rtrace) { char *msg[] = { NULL, "E -> E + T \n", "E -> T \n", "T -> T * F \n", "T -> F\n", "F -> (E)\n", "F -> id\n" }; printf("\n\n확장 과정\n"); for(int i=rtrace.size()-1; i>=0; --i) printf("%s", msg[rtrace[i]]); } // 리듀스 작업 수행 void do_reduce(vector<int> &State, vector<char> &Token, int order) { for(int i=0; i<RTABLE[order].cnpop; ++i) { State.pop_back(); Token.pop_back(); } Token.push_back(RTABLE[order].tok); int s = State.back(); char tok = Token.back(); for(int i=0; i<TABLE_SIZE; ++i) { if(TABLE[i].state == s && TABLE[i].tok == tok) { State.push_back(TABLE[i].nstate); break; } } } int main() { vector<int> State; vector<char> Token; vector<int> rtrace; int curstate; char buffer[4096]; int end = FALSE; int tok; char *ptr; State.push_back(0); printf("입력 ==> "); gets(buffer); ptr = buffer; while((tok = get_tok(&ptr, FALSE)) != SERR && !end) { curstate = State.back(); print_stack(State, Token); printf("%20s", ptr); for(int i=0; i<TABLE_SIZE; ++i) { // 테이블을 순회하면서, 현재 상태와 토큰에 맞는 행동을 결정한다. if(TABLE[i].state == curstate && TABLE[i].tok == tok) { switch(TABLE[i].atype) { // accept 상태 case ACCEPT: printf(" (Accept)\n"); end = TRUE; break; // shift 상태 case SHIFT: Token.push_back(tok); State.push_back(TABLE[i].nstate); get_tok(&ptr, TRUE); printf(" (Shift %d)\n", TABLE[i].nstate); break; // reduce 상태 case REDUCE: do_reduce(State, Token, TABLE[i].nstate); rtrace.push_back(TABLE[i].nstate); printf(" (Reduce %d)\n", TABLE[i].nstate); break; } break; } } } // 결과 출력 print_rtrace(rtrace); return 0; }[/CPP] 컴파일에 사용된 g++ 버전과 컴파일 과정 ![]() id+id+id$을 파싱하는 과정 ![]() id*(id+id)$을 파싱하는 과정 ![]() |
기탁님이랑 전날 다른 곳에서 크랩같은걸 먹었었는데. 망치 말고 가위도 아닌 것이 이상한 부수는 기계를 주더군요. 근데 생전 그런 툴(tool)을 써본적이 없어서 제대로 먹지도 못했던 기억이 나네요. ㅋ 먹어본 놈이 잘먹는다고 ㅋㅋㅋ [GGG] |
[GGGG]현충일날 곰티비 스타리그에서 아주 재미난 경기를 보았습니다. SKT와 팬택의 프로리그 경기였는데, 2:2에서 에이스 결졍전까지 갔습니다. 에이스로 SKT에서는 저그 박태민 선수를, 팬택에서는 테란 이윤열 선수를 내보냈습니다. 싱겁게 이윤열이 이기겠다고 생각을 했었는데, 박태민 선수의 초반 저글링 러시로 이윤열 선수가 아주 곤혹을 치릅니다. 그런데 그 와중에도 이윤열 선수는 벙커 버그로 쌩고생을 합니다. 아주 느린 화면으로 보아야만 알 수 있습니다. 워낙 순식간에 벌어진 일이라 말이죠. 프로게이머의 컨트롤이 얼마나 빠른지 알 수 있었습니다. 0.2배속으로 봐도 꼼꼼히 보지 않으면 잘 모릅니다. ㅋㅋㅋ 버그의 진상은 다음과 같습니다.
막았지만 막은게 아닌 상황이었습니다. 또한 상대는 운영의 박태민 선수 아니겠습니까? 정말 이 당시 이윤열 선수의 상황은 답없음 이었습니다. 보통 S급 테란 선수들이라면 이 상황에서 울트라 수퍼 하이퍼 우주 방어 테란을 구사합니다. 오는거 막고 또 막고 또 막고... 베슬 모으고, 마린 모으고, 업그레이드 하고, 베슬 모으고, 그리곤 한방 러시를 가죠. 그런데 이것은 그렇게 하기에도 정말 너무 심각한 타격이었습니다. 앞마당 띄운 것도 아니고, 부서진 것도 아닌... 본진 안에서 극초반에 생 난리를 친 것이기 때문이죠. 다 부서진 서플. 하지만 괜히 천재가 아니죠. 이윤열 선수 마린 다 이끌고 생 마린 러시를 갑니다. 박태민 선수 진영에는 성큰 하나 완성, 두 개 변태 중인 상황이었죠. 마린은 도착했고, 메딕은 뒤에서 쫓아 오는 중. 메딕 기다리면 늦는 상황이었습니다. 안기다리고 들어가려니 좀 부담 스러운 상황이죠. 그런데 그 찰나. 정말 찰나 메딕 오기전, 변태 완료전 성큰 하나 부시면 메딕이 도착해서 힐해줄 정도 타이밍에 성큰을 일점사하면서 들어갑니다. 초 대박이었죠. 이후에 온 파벳 두 마리도 컨트롤로 저글링을 엄청 잡았습니다. 김창선 해설의 말대로 잃어버린 시간을 되찾는 순간이었습니다. 결국 그 러시로 이윤열 선수가 경기를 잡았습니다. 진짜 정말 아주 오랜만에 보는 아찔한 경기였습니다. 이런 역전승이 가장 재밌잖아요. ㅎㅎ |