[소고] 끝나지 않은 전쟁: 논클라이언트 봇

@codemaru · July 31, 2011 · 49 min read

요약

영겁의 세월을 두고 진화하는 우주처럼 보안에서의 창과 방패의 싸움도 영원히 이어질 수 밖에 없는 구조로 되어 있다. 여기서는 그런 이슈 중에 하나인 논클라이언트 봇과의 전쟁에 대해서 살펴본다. 논클라이언트 봇의 정의 및 구조적 특징과 함께 그간 논클라이언트 봇에 대응하기 위해서 논의되었던 전략들에 대해서 알아보도록 하자.

필자 메모

“보안 프로그래밍”이라는 주제로 글을 부탁 받고는 어떤 내용을 담을지를 한참 고민했다. 정수 연산의 위험성 이라든지, 포맷 스트링 버그 라든지, 버퍼 오버플로우와 같은 보안 프로그래밍을 생각했을 때 누구나 떠올리는 다소 식상한 내용은 담고 싶지 않았다. 달리 생각하면 PC 클라이언트 보안을 하는 업체에서 10년 가까운 세월을 일했지만 감히 “보안”이라는 주제로 글을 쓰기가 두려웠는지도 모르겠다. “보안”이라는 것은 너무도 넓고 너무도 방대한 내용들을 포함하고 있기 때문이다.

식상한 주제는 피하고 싶고, 모르는 것에 대해서 글을 쓰는 것만큼 멍청한 일도 없으니 결국은 내가 그간 했던 고민 중에서 한 꼭지를 끄집어 내 보기로 결정했다. 여기서 설명할 논클라이언트 봇은 과거에도 심각한 문제였고, 현재에도 계속 문제가 되고 있다. 그럼에도 글을 쓰고 있는 지금 이 시점에도 완벽한 대응 전략은 부재중인 상태다. 이 글을 읽고 여러분이 다소 자극을 받아서 혁신적인 새로운 대응 방법을 생각해 낸다면 그 또한 엄청나게 의미 있는 일이 아닐까라는 생각을 하면서 글을 시작해 보려고 한다.

혹시 앞서 언급했던 일반적인 “보안 프로그래밍”이라는 주제에 대해서 공부하고 싶은 독자라면 이 세 권의 책을 꼭 일독하기를 권한다. Writing Secure Code 2/e, 윈도우 비스타 보안 프로그래밍, Secure Coding in C and C++이 그 세 권의 책이다. 그 어떤 책보다도 여러분에게 일반적인 보안 프로그래밍의 관점에 대해서는 가장 많은 영감을 줄 수 있는 책이라고 감히 자부한다.

개요

이 문제는 여러분이 잘 알고 있는 튜링 테스트와 유사하다. 여러분의 여자친구 내지는 남자친구를 생각해 보자. 없다면 부모님을 생각해도 되겠다. 벽이 하나 있고, 반대편에 여러분의 사랑스런 그 사람이 앉아 있는 것이다. 여러분은 상대에게 질문을 할 수 있고, 상대는 여러분에게 대답을 할 수 있다. 이 질문과 답변 과정은 대리인을 통해서 이루어지며 그 사람은 벽 반대편 사람이 여러분이 알고 있는 사람인 것처럼 보이게 하기 위해서 질문과 답변 내용을 조작할 수 있다. 과연 이런 상황에서 여러분은 벽 반대편에 있는 상대가 여러분이 알고 있는 그 사람인지 아닌지를 구분할 수 있을까라는 것이 문제다. 물론 질문은 여러분이 확신이 들 때까지 계속 할 수 있다.

이러한 이야기를 하면 대다수 사람들은 제일 먼저 하고 싶은 질문으로 상대와 나만 알고 있는 은밀한 내용을 꼽는다. 처음 데이트 했던 장소 라던지, 상대의 신체적 특징, 기호 등을 묻는 것이다. 하지만 이는 벽 반대편에 어떤 엉뚱한 존재가 우리의 사랑스런 사람을 인질로 잡고 있다면 전혀 의미가 없다. 협박을 해서 답을 얻어낼 수도 있기 때문이다. 이런 극단적인 상황까지 고려한다면 우리는 이 문제를 풀기 위해서 상대에게 어떤 질문을 던져야 할까? 또 과연 그러한 질문을 통해서 벽 반대편에 있는 보이지 않는 대상의 정체를 판별할 수 있을까?

가볍게 설명한 이 문제가 오랫동안 보안 업계에서 이슈가 되고 있는 원격 클라이언트의 인증 문제다. 인증이라는 말만 듣고 반사적으로 아이디와 비밀번호를 생각했다면 번지수를 잘못 짚었다. 여기서는 원격 사용자에 대한 인증이 아니라 원격에서 실행되고 있는 클라이언트 프로그램 그 자체에 대한 인증이기 때문이다. 그렇다면 원격 사용자도 아닌 원격지 클라이언트의 인증이 왜 그토록 중요할까?

이 문제가 주요 이슈로 부각된 분야는 다름아닌 게임이다. 게임의 경우에는 게임 플레이를 사람이 할 수도 있지만 기계가 할 수도 있기 때문이다. 생각해 보자. 여러분이 온라인으로 철수와 바둑을 두고 있는데 사실은 알고 봤더니 철수가 둔 것이 아니라 철수 컴퓨터가 뒀다면 어떨까? 물론 여러분은 졌다고 가정했을 때 말이다. 별 느낌이 들지 않는다면 다른 사례를 생각해보자. 요즘 유행하는 MMORPG 게임은 해당 가상 세계 속에서의 행위를 통해서 캐릭터를 육성하는 방식으로 진행되는 게임이다. 이러한 캐릭터 육성 과정에는 필연적으로 시간과 노력이 들어간다. 그런데 여러분은 그런 것들을 실제 여러분의 시간과 노력을 투입해서 얻은 반면에 철수는 철수 컴퓨터가 대신 게임을 해서 그런 것들을 자동으로 얻었다고 한다면 어떤 느낌이 들까?

물론 두 가지 사례 모두 여러분이 게임을 하지 않는다면 단지 기분 나쁜 사건 정도로 생각할 수 있는 문제다. 자본주의 세상답게 진짜 문제는 머니(money)가 개입했을 때 발생한다. MMORPG같은 온라인 게임의 경우에는 해당 가상 세계에서 통용되는 화폐가 실제 화폐와 교환 가지를 가진다. 즉, 가상 세계의 1골드가 실제 세계에서 100원에 팔릴 수 있다는 말이다. 실제로 소위 유명한 게임들의 경우에는 이를 환율과 같은 시세로 표기하는 사례도 있다. 이러한 가상 세계에서 골드를 획득하는 방법은 보통의 경우 사냥을 통해서인데, 이는 상당히 단순한 반복 작업을 필요로 한다. 이러한 반복 작업을 기계가 대신하고 그것을 운영한 사람이 부가적인 수익을 얻는다면 그것은 과연 공정한 일일까? 더욱이 이런 기계 때문에 가상 세계 속에서 사람이 획득할 수 있는 리소스가 줄어든다면 말이다.

여기에는 두 가지 중요한 이슈가 내포되어 있는데 하나는 이러한 부정 게임 방식이 게임 운영에 나쁜 영향을 미친다는 점이다. 생각해 보자. 누구나 부정 행위를 통해서 게임을 한다면 과연 그 게임을 누가 멍청하게 자신의 힘으로 하려고 하겠는가? 아마 아무도 하지 않고 전부 기계들만 게임을 하다가 결국 그 게임은 아무도 하지 않는 게임이 되고 말 것이다. 다른 한 가지는 이런 프로그램들이 아이템 거래 시장을 왜곡 시킨다는 점이다. 아이템 거래 시장이란 앞서 말했던 게임 머니를 실제 머니와 교환하는 시장을 말한다. 일반적인 경우라면 그 시장을 통해서 통용될 수 있는 게임 리소스는 제한적일 수 밖에 없다. 하지만 봇 프로그램의 등장으로 그 속에서 획득할 수 있는 게임 리소스가 폭발적으로 증가했고, 그것들은 별 노력 없이 획득한 것이기 때문에 아이템 거래 시장에서 낮은 가격에 팔릴 수 있다. 이는 결과적으로 정직하게 노력해서 해당 리소스를 획득한 사용자들에게는 자신이 획득한 리소스에 대한 가치를 불법적으로 하락 시키는 행위가 되는 셈이고 결국 시장은 공정하지 않은 상태가 되고 만다.

앞서 언급한 여러 가지 이유로 부정 클라이언트 프로그램이 자동으로 게임을 하는 행위는 좁게는 게임 업체에게 그리고 넓게는 게임 사용자들에게 다시 피해가 가기도 하는 것이다. 따라서 이런 부정 클라이언트 프로그램을 막는 것은 게임 업체에게는 굉장히 중요한 당면 과제라고 할 수 있다.

논클라이언트 봇

게임 업계에서는 앞서 설명한 것과 같이 벽 반대 편에 앉아서 마치 우리가 알고 있는 사람인척 하는 나쁜 프로그램을 두고 논클라이언트 봇(Non-client Bot)이라고 부른다. 좀더 엄밀히 말하자면 논클라이언트 봇이란 게임 프로토콜을 모방해 게임 클라이언트가 아닌 별도의 클라이언트가 게임 서버에 접속해서 플레이어와 같은 행위를 하는 것을 말한다. 즉, 논클라이언트라는 말은 클라이언트가 없다는 것을 봇이라는 말은 자동으로 게임 플레이를 한다는 것을 의미한다.

이렇게 복잡하게 프로토콜을 모방해서 논클라이언트 봇을 제작하는 이유는 게임 클라이언트에서 로딩하는 복잡한 그래픽 리소스 등이 필요하지 않아서 한 PC에서도 여러 개의 논클라이언트 봇 프로그램을 띄워서 동시 작업을 원활하게 할 수 있기 때문이다. 동시 실행이 원활하다는 말은 달리 표현하면 단위 시간당 더 많은 게임 리소스를 획득할 수 있음을 의미한다. 이러한 이유로 논클라이언트 봇은 주로 작업장이라고 불리는 곳에서 은밀하게 제작되기 때문에 실제로 구동이 되는 프로그램을 구하기는 쉽지 않다.

<그림 1>에는 정상적인 게임 클라이언트와 서버의 통신 구조가 나와 있다. C는 게임 클라이언트를 S는 게임 서버를 의미한다. 이 그림이 나타내는 의미는 서버가 Q라는 패킷을 전달하면 클라이언트가 R이라는 응답을 하는 구조로 통신이 이루어진다는 것이다. <그림 2>에는 이 구조를 도용한 논클라이언트 프로그램의 구조가 나와있다. 클라이언트는 변조되거나 새롭게 만든 NC이지만 통신 구조를 그대로 모방했기 때문에 게임 서버 입장에서는 이 클라이언트가 정상적인 게임 클라이언트 프로그램인지 아닌지를 판별할 방법이 없다. 논클라이언트 봇을 구현하는 핵심 기술은 이러한 게임 서버와 클라이언트의 통신 구조를 도용하는 것이다.

그림 1 정상 클라이언트 통신 구조

그림 1 정상 클라이언트 통신 구조

그림 2 논클라이언트 통신 구조

그림 2 논클라이언트 통신 구조

프로토콜 변형

이러한 논클라이언트 봇의 등장을 맞이한 게임 개발자들이 취한 그 첫 번째 대응은 프로토콜 변형이었다. <그림 3>에는 이렇게 통신 방법이 변경된 그림이 나와 있다. 기존의 Q와 R이 Qa, Ra라는 새로운 값들로 변경되었다. 따라서 기존 논클라이언트 봇은 이 서버와는 통신을 할 수가 없다.

그림 3 프로토콜 변조 통신 구조

그림 3 프로토콜 변조 통신 구조

이 방식은 일견 기존의 논클라이언트 봇을 일망타진하면서 동시에 논클라이언트 봇 제작자들에게 새롭게 프로토콜을 분석해야 한다는 짐까지 던져주는 셈이기 때문에 굉장히 효과적인 대응으로 생각되기 쉽다. 하지만 이는 큰 착각이다. 그 사실이 잘못된 가장 큰 이유는 기존의 서비스되고 있는 서버, 클라이언트의 프로토콜 구조 전체를 새롭게 설계하는 작업은 전혀 소프트하지 않기 때문이다. 사실상 이러한 작업을 한다는 것은 거의 미친 짓이나 다름 없다. 보통의 경우에 서비스되고 있는 프로그램에서 선택할 수 있는 방법의 최대치는 새로운 패킷 추가나 기존 패킷의 상수 값을 변경하는 정도가 전부다. 하지만 이러한 것들은 이미 프로토콜 전체가 분석된 논클라이언트 봇 제작자 입장에서는 전혀 어렵지 않은 과제다. 단순히 변경된 부분만 추적하면 되기 때문이다. 결과론적으로 이 방법은 막는 입장에서는 굉장한 노력이 필요하지만 공격하는 입장에서는 그렇게 많은 노력이 필요하지 않다고 할 수 있다.

여기까지 이야기를 듣고 보통 나오는 첫 번째 반응은 그러면 패킷을 암호화하면 되지 않냐는 질문이다. 그런데 이 말은 지금까지의 논의를 전혀 이해하지 못해서 나오는 반응이다. 논클라이언트 봇 제작자가 날패킷(raw packet)을 살펴보지 않는 것은 아니지만 어차피 그들은 게임의 거의 모든 코드를 꿰고 있으므로 암호화는 그들에게 큰 장애물이 되지 못한다. 필연적으로 해독하는 코드가 클라이언트에 같이 존재할 수 밖에 없기 때문이다. 즉, 아주 뛰어난 해커를 가정했을 때 패킷 암호화라는 것은 그 효과가 0이라고 할 수 있다. 물론 현실세계에 존재하는 수많은 어중이떠중이 해커를 고려한다면 하지 않는 것 보다는 하는 것이 효과가 있지만 실제로 논클라이언트 봇을 제작할 수 있는 수준의 실력자들에게는 큰 효과를 보기는 힘들다.

키인증

본격적으로 보안 업체가 관여하면서 나온 첫 번째 생각은 키인증이다. 키인증이란 클라이언트와 서버만이 알고 있는 비밀스런 질문과 답변을 주고 받자는 것이었다. 이 방법은 서버에서 클라이언트로 특정 키를 전송하고 클라이언트에서는 그 키에 대해서 아주 특수한 연산을 한 다음 그 값을 서버로 전송한다. <그림 4>에 이러한 방식의 통신 구조가 나와 있다. 서버와 클라이언트는 사전에 약속된 아주 비밀스러운 연산 E를 각자 가지고 있다. 이 상태에서 서버는 클라이언트에게 Q를 보내고 클라이언트는 그것을 자신이 가진 연산 E의 인자로 전달해서 나온 결과, R을 서버로 전송한다. 서버에서는 다시 동일한 연산을 수행해서 클라이언트가 응답한 R이 자신이 계산한 답과 맞는지를 비교해서 정상 클라이언트인지 아닌지를 판단한다.

그림 4 키인증 통신 구조

그림 4 키인증 통신 구조

은행에서 사용하는 보안카드를 게임 클라이언트에 적용했다고 생각하면 쉽게 이해할 수 있다. 서버가 보내는 Q는 보안 카드의 인덱스를 의미하고 E는 보안 카드에서 인덱스에 대응하는 값을 찾는 과정을 R은 해당 보안 카드에 기록된 보안 코드 4자리 숫자를 의미하는 것이다. 상당히 유사한 만큼 이 방식은 은행 보안카드와 동일한 맹점을 가지고 있다. 보안카드 자체를 분실하면 무용지물이라는 점이다. 즉, 우리가 아주 은밀하다고 앞서 언급했던 연산 E가 도용되면 해킹툴도 얼마든지 정상 클라이언트로 둔갑할 수 있는 것이다. <그림 5>에는 이러한 연산 함수 E 코드를 도용한 논클라이언트 봇의 통신 구조가 나와있다.

그림 5 키인증 함수를 도용한 논클라이언트 통신 구조

그림 5 키인증 함수를 도용한 논클라이언트 통신 구조

연산을 도용한다는 것은 무엇을 의미할까? 컴퓨터에서 말하는 연산은 결과론적으로는 수행되는 코드의 집합. 즉, 함수가 된다. 앞서 <그림 4>에서 살펴본 연산 E가 아주 단순하게는 XOR이라고 생각할 수 있다. Q, R이 4바이트 정수이고 E는 XOR인 것이다. 그 규칙을 모르면 통신 구조가 어려워 보이지만 규칙을 알면 쉽게 도용할 수 있다. 물론 논클라이언트 봇 제작자들이 이렇게 날패킷(raw packet)을 보고 규칙을 추론해 내는 것은 아니다. 그들은 클라이언트 게임 코드를 보고 그 코드를 직접 가져다 사용한다.

<리스트 1>에는 원본 클라이언트에서 사용했다고 생각할 수 있는 SomeFantasticFunction이라는 연산이 나와 있다. 여기서는 간단하게 산술 연산을 수행한 후에 결과 값을 돌려준다. 이 코드를 컴파일해서 실행 코드를 생성하면 해당 코드에 대한 어셈블리어 코드가 어딘가에는 존재한다. 논클라이언트 봇 제작자들은 그 코드를 찾는 것이다.

리스트 1 원본 연산 함수

ULONG SomeFantasticFunction(ULONG Q)
{
    ULONG R;
    R = (Q >> 13) * 34 + 1573;
    return R;
}

<그림 6>에는 IDA와 같은 정적 분석 툴을 사용해서 해당 코드를 찾아본 화면이 나와 있다. 코드를 보면 오른쪽에는 어셈블리어 코드가 나와 있고 왼쪽에는 바이트 코드가 나와있다. 뛰어난 논클라이언트 봇 제작자라면 함수 자체의 기능도 이해하겠지만, 사실은 그럴 필요도 없다. 이 함수가 특정 시점에 수행된다는 사실만 확인하고 나면 그냥 바이트 코드를 그대로 긁어다 사용하면 되기 때문이다. <리스트 2>에는 이렇게 도용한 함수 코드가 나와 있다. 보면 알겠지만 함수는 바이트 코드를 저장해놓고 실행 시점에 가상 메모리에 복사한 다음 해당 함수를 다시 호출하는 구조로 되어 있다. 여기서는 간단하게 컨셉만 보여주기 위해서 이렇게 코드로 만들었지만 실제로는 이렇게 복잡하게 코드로 구성하지 않고도 손쉽게 도용할 수 있는 다양한 방법이 존재한다. 어쨌든 이 이야기의 결론은 특정 코드가 무엇을 하는지, 내지는 어느 시점에 수행되는 지만 판단하고 나면 그 코드를 훔치는 것은 식은 죽 먹기라는 사실이다.

그림 6 IDA로 살펴본 SomeFantasticFunction

그림 6 IDA로 살펴본 SomeFantasticFunction

리스트 2 도용한 함수 코드

typedef ULONG (*SFFT)(ULONG Q);
ULONG SomeFantasticFunction(ULONG Q)
{
    UCHAR code[] =  "\x55\x8b\xec\x81\xec\xcc\x00\x00\x00"
                    "\x53\x56\x57\x8d\x8d\x34\xff\xff\xff"
                    "\xb9\x33\x00\x00\x00\xb8\xcc\xcc\xcc\xcc"
                    "\xf3\xab\x8b\x45\x08\xc1\xe8\x0d\x6b\xc0\x22"
                    "\x05\x25\x06\x00\x00\x89\x45\xf8\x8b\x45\xf8"
                    "\x5f\x5e\x5b\x8b\xe5\x5d\xc3";

    PVOID cptr = VirtualAlloc(NULL
                                , sizeof(code)
                                , MEM_COMMIT | MEM_RESERVE
                                , PAGE_EXECUTE_READWRITE);
    if(cptr)
    {
        memcpy(cptr, code, sizeof(code));
        SFFT sfft = (SFFT) cptr;
        ULONG R = sfft(Q);
        VirtualFree(cptr, 0, MEM_FREE);
        return R;
    }

    return 0;
}

무결성 검사

E를 도용 당한 보안 업계의 반격은 무결성 검사로 이어졌다. 이는 말 그대로 클라이언트의 내용물을 검사해서 진짜인지 아닌지, 변조가 있었는지 없었는지를 판별하겠다는 말이다. 앞선 키 인증이 사랑하는 사람과 둘만 알고 있는 기억에 대한 질문이었다면, 이는 사랑하는 사람이 가진 고유한 신체적인 특징을 묻는 것이라고 할 수 있겠다.

<그림 7>에는 이러한 경우의 통신 구조가 나와 있다. 서버는 Q라는 랜덤한 값을 보내고 클라이언트는 그 값과 클라이언트 프로그램 자체를 해시 함수 H에 입력으로 넣는다. 그러면 H 함수는 클라이언트에서 Q로 표기된 부분의 해시 값을 구해서 R에 저장한다. 서버에서는 구해진 해시 함수를 가지고 변조된 클라이언트인지 아닌지를 판단하는 것이다.

그림 7 클라이언트 무결성 검사 통신 구조

그림 7 클라이언트 무결성 검사 통신 구조

이 방식의 가장 큰 특징은 연산 H의 입력으로 클라이언트 프로그램 그 자체를 넣도록 만들었다는 점이다. 따라서 클라이언트에 가해진 사소한 수정 사항도 모두 검사할 수 있다. 하지만 과연 이 방법으로 논클라이언트 봇을 막을 수 있었을까? 안타깝게도 아니다. 앞선 키 인증과 마찬가지로 논클라이언트 봇이 클라이언트 프로그램 자체를 도용한다면 여전히 무용지물이 되기 때문이다. <그림 8>에는 이러한 방식으로 게임 클라이언트 프로그램 C와 해시 함수 H를 도용한 논클라이언트 봇의 통신 구조가 나와 있다.

그림 8 클라이언트 및 해시 함수를 도용한 논클라이언트 통신 구조

그림 8 클라이언트 및 해시 함수를 도용한 논클라이언트 통신 구조

앞서 함수를 도용하는 것에 대해서는 설명했다. 그렇다면 게임 클라이언트를 도용한다는 것은 무슨 의미일까? 그 말에 대한 해답이 <리스트 3>과 <리스트 4>에 나와 있다. <리스트 3>은 정상 게임 클라이언트의 응답 함수가, <리스트 4>에는 논클라이언트 봇의 응답 함수가 나와 있다. 이 둘의 차이를 보면 알겠지만 정상 클라이언트는 H 함수에 현재 실행되고 있는 프로그램을 입력으로, 논클라이언트 봇은 자신이 도용한 클라이언트 프로그램을 입력으로 넣는 차이를 가지고 있다. 즉, 이 경우에는 앞선 함수 도용처럼 Reply 함수를 통째로 도용하는 경우에는 동작을 하지 않겠지만 <리스트 4>에 나와 있는 것처럼 약간의 수정을 가한 후에 사용한다면 서버와 통신이 가능하다.

리스트 3 원본 응답 함수 코드

ULONG Reply(pvoid received_data)
{
    R = H(GetModuleHandle(NULL), data);
    send(R);
}

리스트 4 논클라이언트 봇 응답 함수 코드

GameClient = LoadGameToMemory();

ULONG Reply(pvoid received_data)
{
    R = H(GameClient, data);
    send(R);
}

이 방법을 우회하는 논클라이언트 봇의 핵심은 <리스트 4>에 나와 있는 LoadGameToMemory 함수다. 이 함수는 게임 클라이언트를 로딩하는 역할을 한다. Windows 실행 포맷인 PE 파일의 구조는 이미 분석이 많이 되었기 때문에 이러한 별도의 실행 파일을 메모리에 올리는 작업은 무척이나 간단하다. 물론 실행 파일 자체에 자가 수정 코드나 폴리모픽, 메타모픽 등의 기법을 적용해서 단순히 로딩 만으로는 실제 구동되는 단계의 클라이언트와 다르도록 만들 수 있다. 하지만 이 또한 VM 환경을 사용하면 간단하게 해결할 수 있다. VM 환경이란 실제 CPU를 사용해서 구동하는 것이 아닌 가상의 메모리 공간과 가상의 CPU를 사용해서 게임 클라이언트 코드를 에뮬레이팅해서 푸는 작업을 말한다.

타이밍 체크

이 방법은 실상 필드에서는 큰 효과가 없는 방법이나 논문에서는 흔히 등장하는 해법 중에 하나다. <그림 9>에 나온 것처럼 해당 연산을 수행하는데 소요된 시간을 같이 보낸다는 것이 핵심 개념이다. 그림에서는 t가 E라는 연산을 수행하는데 소요된 시간을 나타낸다. 물론 t 값은 R안에 은밀하게 포장될 수도 있다.

그림 9 타이밍 체크를 사용하는 통신 구조

그림 9 타이밍 체크를 사용하는 통신 구조

우선 이 방법의 가장 큰 문제점은 범용 OS가 보통은 RTOS가 아니라는 점이다. 이는 시간 측정이 정확할 수 없다는 이야기다. 모든 스레드나 프로세스는 다른 스레드나 프로세스에 의해서 선점될 수 있고, 이는 곧 정상 환경에서도 실제 동작 시간이 지연될 수 있음을 의미한다. 또한 클라이언트가 구동되는 실행 환경은 천차만별이라는 점도 문제점이다. 같은 게임을 셀러론 CPU에서 실행하는 사용자도 있고, i7 쿼드 코어 컴퓨터에서 실행하는 사용자도 있다. 이 둘의 실행 시간은 아주 당연하게 전혀 같지 않다.

그리고 결정적으로 앞선 무결성 검사에서 살펴보았던 것처럼 연산 E는 도용뿐만 아니라 변조도 당할 수가 있다. 따라서 이 경우에는 정상의 경우 t가 일정한 값으로 나올 것이기 때문에 조작을 해서 실행 시간에 상관 없이 같은 시간을 보내도록 하면 손쉽게 우회할 수 있다.

물론 이렇게 여러모로 실전에서 사용하기에는 다소 부족한 점이 많은 방법이지만 이 방법을 소개한 이유는 이 방법의 경우 기존 방법과 달리 직접적인 값 보다는 간접적인 값을 통해서 논클라이언트 봇을 진단하려고 했다는 점이다. 즉, 기존 방식들이 예, 아니오의 답을 원하는 닫힌 질문이라면 이 방법은 주관식으로 답할 수 밖에 없는 열린 질문이라는 점이다. 즉, 시간이라는 변수 외에도 이러한 열린 질문을 통해서 정상 클라이언트를 판단을 할 수 있는 방법이 존재한다면 이 테크닉은 논클라이언트 봇에 대한 좋은 대응 방법이 될 수도 있다.

CAPTCHA

지금까지 우리는 이 문제를 논클라이언트와 정상 클라이언트를 판단하는 것으로 파악했다. 하지만 이것들 전혀 새로운 관점에서 기계와 인간을 구분하는 관점에서 바라본 해법이 CAPTCHA다. CAPTCHA(Completely Automated Public Turing test to tell Computers and Humans Apart)는 흔히 인터넷에서 자동 가입 방지용으로 사용되는 사람인지 기계인지를 테스트하는 방법을 말한다. <그림 10>와 같이 아주 이상하게 생긴 문자열을 보여주고는 그대로 다시 입력하라고 하는 방법이 대표적이다. 사실 사람도 이해하기가 쉽지 않다.

그림 10 사람도 이해하기 힘든 인증

그림 10 사람도 이해하기 힘든 인증

이 방식을 논클라이언트 봇 문제에 적용하는 것은 간단하다. 위와 같은 이미지 코드가 Q가 되고 사용자가 입력한 문자열이 R이 되는 것이다. 물론 입력 받는 함수가 연산 E가 되겠다. 이 경우에는 CAPTCHA라는 방식 자체가 기계가 자동으로 답을 판단할 수 없도록 고안되었기 때문에 이론적으로는 사람이 개입할 수 밖에 없다. 즉, 자동으로 입력하는 것이 불가능해진다는 의미이고, 그렇다면 논클라이언트 봇은 자연스럽게 의미가 없어진다.

물론 이 방법도 논클라이언트 봇이 작정하고 자동화를 하겠다고 다짐을 한다면 할 수 있는 방법이 전혀 없는 것은 아니다. 서버가 가지고 있는 모든 이미지 코드에 대한 결과 값을 수집한 다음 해당 값들에 대한 매핑 함수를 제작하는 것이다. <그림 11>에 이러한 방법이 나와 있다. 모든 가능한 Q들에 대한 R을 구한 다음 그것 사이를 매핑하는 M 함수를 제작해서 자동으로 대답하는 방법이다. 하지만 이 경우에는 앞선 프로토콜 변경과 마찬가지로 논클라이언트 봇 제작자 입장에서는 무척 힘든 작업이지만 서버 쪽에서 요청의 내용을 교체하는 것은 쉽다는 특징이 있다. 즉, 서버 쪽에서는 데이터만 교체하면 순식간에 모든 논클라이언트 봇이 무력화되고, 논클라이언트 봇 제작자는 모든 요청과 결과에 대한 내용을 다시 수집해야 한다는 말이다. 더욱이 이런 교체 작업은 자동화 시킬 수 있기 때문에 논클라이언트 봇 제작자 입장에서는 이러한 M을 만들어서 대응하는 것은 사실상 의미가 없다.

그림 11 매핑 함수를 사용한 논클라이언트 봇

그림 11 매핑 함수를 사용한 논클라이언트 봇

CAPTCHA 방식이 가지는 문제점은 이러한 논클라이언트 봇에 대한 대응력이 아닌 다른 곳에 있다. 바로 저 문자열을 입력하라는 귀찮은 과정을 게임 플레이 중간 중간에 하게 한다면 과연 어떤 사용자가 그 게임을 하고 있겠냐는 점이다. 사실 인터넷 사이트도 CAPTCHA가 적용된 곳은 진짜 필요한 곳이 아니면 가입이 꺼려지는 것이 사실이다. 더 중요한 사실은 이런 입력의 경우 기계뿐만 아니라 사람도 이해하기 힘들며 종종 틀린다는 점이다. 따라서 실제로 이를 게임이나 특정 클라이언트 프로그램에 적용하는 것은 실용적인 관점에서의 한계가 존재한다.

그렇다면 사람이 귀찮아 하지 않는 CAPTCHA 방식을 사용하면 괜찮을까? 그런 것들 중에 하나가 게임 상에서 GM(Game Master)들이 하는 말걸기가 있다. GM들이 필드에서 이상 행위를 반복하는 사람들을 보면 말을 걸어보고 제대로 된 대답을 하지 않는다면 자동 플레이로 간주하고 해당 사용자를 퇴장 시키는 방법이다. 그럴듯해 보이지만 요즘 나오는 논클라이언트 봇은 GM의 질문에 대한 몇 가지 대답들을 미리 준비해 놓거나 주변에 GM이 오면 자동으로 다른 곳으로 움직이는 기능을 추가하고 있는 경우도 있다. 따라서 이러한 비강제성 CAPTCHA 방식은 효과를 가지기가 쉽지 않다.

그럼에도 이 방식이 의미가 있는 것은 새로운 CAPTCHA 방식이 강제성을 가지지만 사람은 전혀 귀찮음을 느끼지 않고, 오히려 흥미를 느끼며, 사람이라면 거의 99.9%의 확률로 틀리지 않는 방식이라면 논클라이언트 봇을 손쉽게 무력화 시킬 수 있는 가장 강력한 방법이라는 특징 때문이다.

데이터 마이닝

끝으로 소개할 방법은 데이터 마이닝이다. 이 방법은 논클라이언트 봇 문제를 정상 플레이어와 비정상 플레이어의 문제로 바라본 해결책이다. 간단하게 소개하자면 서버 쪽에서 개별 플레이어의 행위를 모두 파악한 다음 그것을 토대로 데이터 마이닝을 해서 해당 플레이어가 정상의 범주에 속하는지 비정상의 범주에 속하는지를 판별한다는 것이다.

이론적으로는 굉장히 깔끔해 보이지만 실상 이 방법은 많은 단점을 가지고 있다. 우선 플레이어의 모든 행위를 기록내지는 실시간으로 판별해야 한다는 점이다. 이를 위해서는 추가적인 많은 리소스가 투입될 수 밖에는 없다. 그리고 그것을 자동적으로 판별하는 함수를 제작하는 것도 쉬운 일은 아니다. 논클라이언트 봇이 문제가 되는 MMORPG 류의 게임에서 정상의 플레이어 임에도 논클라이언트 봇처럼 플레이를 하는 사용자들도 많기 때문이다.

하지만 그런 모든 단점을 극복했다고 하더라도 정말 이 방법이 효과가 없을 수 밖에 없는 마지막 약점이 있다. 논클라이언트 봇이 정상의 범주와 같이 플레이를 한다면 이 방법으로는 절대로 논클라이언트 봇을 잡을 수 없다는 점 때문이다. 물론 그것이 문제가 되지 않을 수 있다고 생각할 수 있겠지만 기계가 사람을 대신하여 게임을 자동으로 플레이한다는 관점에서는 여전히 공평하지 않다.

결론적으로 다시 설명하자면 이렇다. 이 방법에서 생각하는 정상과 비정상을 구분하는 방법을 담고 있는 함수를 P라고 가정하자. 이 P라는 함수가 정상의 범주를 좁히면 좁힐수록 정상 플레이어도 차단되는 경우가 생기는 문제가 발생한다. 반면에 이 P라는 함수가 정상의 범주를 넓히면 넓힐수록 비정상 플레이어가 차단되지 않는 경우도 생긴다. 결정적으로 논클라이언트 봇 제작자는 이 P의 범주를 파악해서 딱 그 범주의 최대치만큼만 부정적인 방법으로 플레이를 한다면 이 방법은 무용지물이 된다. 즉, 이 P의 판단 범주를 어떤 범위에 두더라도 논클라이언트 봇은 항상 그것을 통과할 수 있다는 점에서 이 방법은 좋은 대안이 되기는 힘들다.

끝은 없다

여기까지가 그간 논클라이언트 봇에 대응하는 게임 업계, 보안 업계, 그리고 학계의 생각들이다. 하지만 개별 항목에서 설명한 것처럼 하나같이 단점 없는 방법이 없으며, 완전하게 논클라이언트 봇을 탐지할 수 있는 방법은 더더욱 없다. 그래서 이 문제가 더욱 매력적인지도 모르겠다.

다시 제일 앞 소개 글에서 했던 질문을 해보자. 과연 여러분은 질문을 통해서 벽 반대편에 있는 사람의 정체를 판단할 수 있을까? 결론은 불가능하다. 왜냐하면 질문과 답변이라는 메커니즘 자체가 이미 결정돼 있는 것들이기 때문이다. 여러분이 질문을 던지는 순간 그 해답은 이미 나와 있는 것이라는 말이다. 이는 결국 그 질문들을 미리 다 알고 있는 상대라면 얼마든지 여러분의 판단을 흐리게 만들 수 있다는 것을 의미한다.

하지만 이러한 이론적인 결론 때문에 기죽을 필요는 없다. 실제 현실 세계에 존재하는 논클라이언트 봇 제작자가 우리가 가정한 신적인 존재는 아니라는 점 때문이다. 그들도 결국 사람이기 때문에 모든 코드들을 한번에 다 파악할 수는 없다. 문제는 얼마나 적은 리소스를 사용해서 효과적으로 그들을 괴롭히는가에 달려있다.

이런 이야기가 끝날 즈음엔 사람들은 항상 왜 보안 업체가 완벽하게 막을 수 없는지를 궁금해 한다. 그러면서 잘 생각해 내면 뭔가 마법 같은 방법으로 이 모든 것을 해결해 낼 은탄환(silver bullet)이 있지 않을까라는 생각도 덧붙인다. 이 싸움이 원숭이나 개나 고양이와의 싸움이었다면 그런 은탄환이 존재했을지도 모른다. 하지만 이 싸움은 적어도 우리와 동등한 내지는 우리보다 더 나은 지능을 가진 사람들과의 싸움이다. 우리가 완벽하게 막을 방법을 생각해 낸다는 건 그들이 완벽하게 우회할 방법을 생각해 낼 수 있다는 말과 같은 의미다. 결국 이 싸움은 뫼비우스의 띠처럼 끝없이 반복될 수 밖에 없는 구조로 되어 있는 것이다.

@codemaru
돌아보니 좋은 날도 있었고, 나쁜 날도 있었다. 그런 나의 모든 소소한 일상과 배움을 기록한다. 여기에 기록된 모든 내용은 한 개인의 관점이고 의견이다. 내가 속한 조직과는 1도 상관이 없다.
(C) 2001 YoungJin Shin, 0일째 운영 중