온라인게임 프로토콜 보안
보안은 양날의 칼이다. 시스템의 성능과 개발,유지보수의 편리함이 강조될수록 보안성은 떨어지고 반대로 보안성이 올라갈수록 성능은 떨어지고 개발,유지보수하기 힘들다.
실시간 반응을 필요로 하는 온라인게임들은 보안이 생명이기는 하면서도 어느 정도 이상으로는 보안 수준을 높힐 수가 없는 난감한 상황에 있다.
이 글은, 필요이상의 보안강화로 인한 시스템자원 낭비를 최소화하고 또 일반 해커들이 쉽게 넘 볼수 없는 수준의 보안성을 얻어내기 위한 ‘온라인게임 프로토콜 보안 4원칙’에 대해 쓰고자 한다. 개발자들은 클라이언트와의 통신에서 이 최소 4가지 보안책을 구현해야만 한다.
1. Sequence, 시퀀스
시퀀스넘버는 어떤 일련번호이다. 통신할때마다 패킷에 시퀀스넘버를 붙여서 보내도록 하는 것. 가장 고전적 프로토콜 해킹 수법인 ‘Packet Record & Replay’ 를 막아낼 수 있다.
MMORPG에서 한 플레이어가 몬스터를 한대 때렸을때에 클라이언트에서는 서버로 어떤 패킷을 보낼 것이다. 아무리 암호화를 복잡하게 하고 방어코드를 넣었다고 하더라도, 이 패킷을 복사해서 잔뜩 보내는 행위는 패킷시퀀스가 없는한 막아낼 수 없다.
시퀀스넘버를 도입해도, 이 숫자 자체도 변조가 가능하기 때문에 체크섬의 도움이 필요하다.
2. Checksum, 체크섬
체크섬은 패킷의 앞 혹은 뒤에 따라붙는 체크코드로서, 패킷 각 바이트들을 연산해내서 얻어지는 값이다. MD5나 CRC32등이 대표적인 체크섬들인데 널리 공개되어있는 일반적인 알고리즘보다는 공개된 알고리즘을 변형해서 쓰는 것이 현명하다. 해커들도 얼마든지 구할 수 있기 때문이다. 직접 제작하는 체크섬은 보안성이 떨어진다.
체크섬 보안을 도입함으로써 패킷내 데이터의 변조를 차단할 수 있다.
해커들은 다양한 방식으로 패킷을들 변조하고 있는데, 클라이언트에서는 의도적으로 차단되어 있는 행위들을 패킷수준에서 조작할 수 있다. 예를 들자면 상점에 가지고있는 물건 5개를 판다고 해보자. 패킷은 대충 이런 구조이다.
[NPC_ID, ITEM_CODE, ITEM_COUNT] 여기서 아이템수량은 현재 가지고있는 수량인 5개로 클라이언트에서 제한된다 (즉, 유저는 물건을 5개밖에 가지고 있지 않으면서, npc에게 10개를 팔아치울 수 없다). 그렇지만 패킷변조를 이용하면 이 숫자를 10 이건 100 이건 바꿔칠 수 있다.
물론, 이런 경우를 대비해서 안전을 위해 서버에서는 검사를 다시 수행해줘야 한다.
패킷에 체크섬을 도입한 경우에는, 그나마 통신과정에서 변조되었을 위험 하나를 줄일 수 있다.
(통신 보안영역이 아닌 클라이언트프로그램해킹/리버스엔지니어링/메모리해킹등을 이용해서도 이러한 변조가 가능하다)
보안성 향상을 위해, 1에서의 시퀀스넘버도 데이터로 간주하여 체크섬을 만들어 내면, 시퀀스넘버의 변조를 막아내는 효과도 있다.
3. Encryption, 암호화
암호화는 말그대로 패킷훔쳐보기 프로그램으로 쉽게볼수 없도록 다른 기호들로 암호화하는 것이다. 암호화 알고리즘도 공개된 훌륭한 것들이 많지만, 앞서와 마찬가지로 공개된 알고리즘을 변형해서 쓰는 것이 바람직하다.
어떤 알고리즘을 쓰느냐에 따라 초당 패킷암호화/복호화율이 수백배 차이가 나기도 하므로, 온라인게임에서는 보안성보다는 성능 관점에서 고를 필요가 있다.
4. Verification, 검증
검증의 핵심 철학은, ‘클라이언트를 믿지 않는다’라는 개념이다. 절대로 클라이언트를 믿지 않는다.
한 예로, 서버를 다운시켜서 아이템복사를 하는 수법으로 많이 이용되었던 ‘패킷사이즈 변조’ 수법을 살펴보자.
통상적인 패킷들의 구조가 [헤더,데이터]의 형식으로 되어 있고 보통 헤더는 [헤더마크, 패킷길이]로 구성된다.
(여기에 메세지번호나 시퀀스, 체크섬이 포함될수도 있다)
여기서 패킷길이를 살펴보자. 한바이트일경우에 패킷길이는 256 바이트로 제한되므로 보통은 2바이트 Integer를 이용할 것이다. 16bits Int의 표현범위는 0~65535 인데, 대부분의 패킷들은 커봤자 4K 정도일테다. 그래서 최대치를 8K정도로 잡기로 프로그래머들끼리 약속한다.
여기서 서버의 약점이 하나 발견된다.
서버프로그래머가, 클라이언트가 서버로 보내는 패킷들의 크기는 이미 약속된 8K를 넘어갈 일이 절대로 절대로 없다고 착각하는 것이다. 그래서 이런 코드들을 작성하는 오류를 범한다.
#define MAX_PACKET_SIZE 8192 // 8k
typedef struct _st_packet {
char header_mark;
unsigned short int size;
char data[MAX_PACKET_SIZE];
} CPacket;
char header_mark;
unsigned short int size;
char data[MAX_PACKET_SIZE];
} CPacket;
…
CPacket *a_packet = &socket.recieve_buffer;
…
memcpy(&a_message, &Packet.data, Packet.size); // 사이즈만큼 복사한다.
memcpy(&a_message, &Packet.data, Packet.size); // 사이즈만큼 복사한다.
해커는, 클라이언트가 서버로 보내는 패킷하나의 size를 터무니없는 값으로 바꾸어 서버가 죽게 만들 수 있다. 이 방법은 변조방지 체크섬으로 일부 막아낼 수 있지만, 보다 확실한 안전을 보장하는 방법은, 이미 약속한 것보다 사이즈가 큰 패킷은 서버에서 거부하는 것이다.
또 다른 예로, 사냥터에서 마을의 상점열기 수법을 한번 살펴보자.
이 수법은, 일단 마을에서 상점을 한번 연다음에 (보통은 상점 NPC를 클릭한다) 이때 발생하는 클라이언트 패킷을 ‘레코딩’한다. 그리고나서 던젼이나 사냥터에서 실컷 놀다가 아까 기록해둔 패킷을 서버에 발송하는 것이다. 클라이언트 화면에야 NPC가 없으니 NPC를 클릭하여 상점을 열 방법이 절대로 절대로 없지만, 패킷 레코딩&리플레이로는 가능하다. 게다가 이런 수법은 굳이 고생해서 암호화를 풀지 않고도 그냥 가능하다는 장점이 있다.
이런 것을 막아내려면, 시퀀스넘버를 도입하거나 서버의 검증 (플레이어가 해당 NPC근처에 있는지 다시한번 검사)이 필요하다.
두 가지의 예를 들었는데, 이것말고도 수법들은 무궁무진하다. 서버는 통신보안만 믿지말고, 모든 패킷들에 대해서 철저히 그 유효성을 검사해야 한다. 왜냐면 클라이언트는 적의 손에 있으니까!
5. 그러나 여전히 취약하다.
앞서의 4원칙을 준수하면, 통신과정에서 발생하는 탐지(건드리지는 않고 그냥 보기만 하는것), 변조(데이터 내용을 바꾸는 것), 복제(한번 전송된 패킷을 임의로 다시 보내는 것)를 막아낼 수 있다. 서버검증을 통해 클라이언트 버그 혹은 클라이언트 해킹을 일부 막아낼 수는 있다.
그렇지만, 온라인게임 보안성은 여전히 취약하다. 그 가장 근본적인 이유는 ‘클라이언트는 적의 손에 있기’ 때문이다. 유저들은 얼마든지 클라이언트 프로그램들을 역어셈블 할 수도 있고 DLL을 바꿔치기 할수도 있고 메모리를 훔쳐보거나 조작할 수 있고 (해커가 자기돈 주고 산 자기메모리이다) 운영체제도 이상하게 바꿔놨을 수도 있기 때문이다.
이런것들을 방지하기위해 안철수연구소의 ‘MFGS’나 N-Protect등의 상업솔루션이 나와있기는 하지만 안정적이지 못하고, 해커들의 순발력을 따라가기가 힘들다.
온라인게임 최대 강국이라는 대한민국의 명성이 외국해커들에 의해 깨어지고 있다.
'Server > Security' 카테고리의 다른 글
첨부파일로 올라온 디렉토리에서 php cgi등 실행 금지 하기 (0) | 2008.12.18 |
---|---|
스핵... 어떻게 막을 것인가?! (0) | 2007.05.18 |
ETRI 보안 세미나 (0) | 2006.12.10 |