당신의 게임은 안전한가요?

시큐어 코딩과 관련해서 경험한 이야기가 있습니다.
잡지에 있는 하나의 글처럼 가볍게 읽어주시면 감사하겠습니다.

취미로 아주 작은 게임 서버를 운영하는 친구가 있습니다.
친구는 자신이 만든 게임을 플레이해보라고 제안했습니다.
그런데 저는 친구의 의도대로 순순히 게임을 즐길 생각이 없었습니다.

사건의 시작

단기간에 강해지길 원했습니다.
노가다없이 게임 내 재화를 많이 얻고 싶었고, 가장 초보적인 방법인 메모리 변조를 해보기로 했습니다.
이 공격을 위해 만들어진 도구인 치트엔진을 사용해서 첫 공격을 시도했습니다.

첫 번째 스캔

골드를 늘리기 위해 현재 가진 골드(71497)를 입력하고 스캔했습니다.
그런데 동일한 값을 가진 항목이 여러 개 검색되어 어떤 게 골드인지 특정할 수 없었습니다.
골드를 의미하는 주소를 정확히 찾기 위해 골드를 2만큼 더 얻어서 71499로 만든 후 다시 스캔해봤습니다.

두 번째 스캔

이것을 반복하다가 결국 하나가 남게 되면 정확한 값을 찾는 데 성공한 것입니다.
찾은 값은 마음대로 변경할 수 있었고, 변경된 값이 게임 내에도 잘 반영되었습니다.

이 방법으로 스탯, 경험치, 아이템, 골드 등 모든 것을 복사하거나 얻었고, 이것을 알게 된 친구의 표정은 어두워졌습니다.

어떻게 이런 게 가능했을까요?
친구가 만든 게임 서버는 클라이언트를 100% 신뢰하고 있었고, 대부분의 로직이 클라이언트에 있었습니다.
그리고 클라이언트가 보낸 데이터를 아무런 의심 없이 게임 서버 DB에 저장해버렸죠.

게임에 필요한 모든 것을 얻었지만 여기서 멈추지 않았습니다.
값 하나를 바꾸기 위해 매번 이 작업을 하기에는 너무나 번거로웠습니다.
어떻게 하면 더 쉽게 재화를 얻을 수 있을까 고민하다가 서버와 클라이언트 사이에 오가는 값을 조작해보기로 했습니다.

본격적인 조작

값을 조작하기에 앞서 어떤 패킷들이 오가는지 확인하고 싶었습니다.
패킷 분석 도구로 와이어샤크라는 유명한 도구가 있습니다.
이 도구로 모니터링을 시작한 채 게임에 접속하자마자 패킷이 쏟아지기 시작했습니다.
사냥터에 들어가 보니 이런 내용을 가진 패킷도 보입니다.

1
2
<mon_move>177,6,4,6,11</mon_move>
<!-- 대략 몬스터가 움직인다는 뜻 -->

패킷은 전혀 암호화되어있지 않았고, 그때 저는 이런 생각이 들었습니다.

서버로 패킷을 바꿔 보내면 데이터 조작이 가능하겠구나!

바로 실행에 옮겼습니다. 게임에 로그인된 상태이니 바로 데이터를 전송해보기로 했습니다.
1
2
3
4
5
ip: 192.168.0.1
port: 12501
content: <str>9000</str>
<!-- IP가 192.168.0.1이고, 포트 번호가 12501인 곳에 힘 스텟 9000을 보냄 -->
<!-- 실제 패킷은 완전히 다르게 생겼습니다. -->

와이어샤크에서 패킷을 추출한 후 일반적인 에디터로 위와 같이 변경했고, bittwist를 사용해서 패킷을 전송해봤습니다.

1
2
3
4
5
6
7
$ bittwist -v -i eth0 test.pcap
sending packets through eth0
trace file: test.pcap

2 packets (2218 bytes) sent
Elapsed time = 0.021880 seconds
# 서버로 패킷을 보내는 데에 성공

패킷이 성공적으로 전송되었습니다.

패킷 전송 별거 아니군!

많은 기대를 하면서 게임에 접속해봤습니다.

???? 실패!!

하지만…기대와는 다르게 전혀 바뀐 게 없었습니다.

실패의 연속

왜 그랬을까? 한참 생각해보다가 이건 HTTP가 아니라 TCP 소켓 연결이라는 것을 알았습니다.
게임에 처음 접속하면 연결을 맺고, 게임을 하는 동안에는 그 연결을 유지한 채로 데이터를 주고받았습니다.
이미 클라이언트와 서버가 서로 연결되어있을 때, 그 중간에 패킷을 넣어 보내는 것은 어렵습니다.

아! 그럼 둘 다 나랑 연결하고, 데이터를 조작해서 전송하면 되겠구나!

새로운 도전을 하기로 했습니다. 이 방법은 중간자 공격이라고도 불립니다.
클라이언트와 서버 사이에 프록시 서버를 두고, 클라이언트와 서버가 통신할 때 이를 조작해서 전송할 수 있습니다.

이 작업을 쉽게 할 수 있는 도구로 Burp Suite가 있으며 이 도구는 HTTP 통신 시 값을 가로채서 조작하는 것에 자주 사용됩니다.
이것만으로는 TCP 소켓 통신을 가로챌 수 없어서 NoPE Proxy확장 프로그램도 함께 설치했습니다.
여러 글을 참고해서 이것저것 설정하고 나서 패킷이 잘 들어오나 확인해보았지만, 전혀 반응이 없었습니다.

패킷이 하나도 없어..ㅠㅠ

관련된 글을 더 찾아보니 NoPE Proxy는 DNS를 이용하는 방식이라서 서버에 도메인이 있어야 합니다.
친구의 서버는 IP만 있고, 도메인이 없어서 이 도구를 사용하는 데에 어려움이 있었습니다.
여기에 더 시간을 쏟는 것보다 다른 방법을 찾는 게 빠를 것 같아서 한참을 만지작대다가 포기했습니다.

새로운 마음가짐

적절한 도구가 더 없을까 찾아보다가 결국 직접 만들기로 했습니다. (저는 개발자니까요!)

게임 클라이언트인 것처럼 사기를 치고 연결해보자!

학창 시절에 TCP 소켓으로 채팅 프로그램을 만들었던 기억을 떠올려서 차근차근 만들었고, 직접 서버에 연결해봤습니다.

1
2
$ ./client 192.168.0.1 12501 # 예시 IP, port 입니다.
Connection Success

서버와 연결하는 데에 성공했습니다!
이제 로그인을 하고 나서 스탯 조작을 위한 명령어를 서버에 보내면 됩니다.

1
2
3
client: <str>9000</str>
server: <str>success</str>
<!-- 대략 힘 스탯을 9000으로 만들었다는 뜻 -->

이전에 와이어샤크로 수집한 패킷을 똑같이 보내보니 서버에서 정상적으로 응답이 왔습니다.
다른 스탯도 모두 9000으로 만들어서 보내봤습니다.

성공적

성공적입니다!! 이제 게임의 모든 걸 조작할 수 있습니다.
보스를 한 방에 없앨 수도 있고, 운영자 전용 아이템을 얻을 수도 있습니다.

친구는 이 상황을 실시간으로 보고 있었습니다.
입에 거품을 물고 있습니다.

여기서 멈출 수 없지

호기심은 여기서 끝나지 않았습니다. 다른 유저의 데이터도 변경해보고 싶었습니다.
앞에서는 데이터 조작을 간단하게 소개했지만, 사실 아래와 같은 순서로 이루어집니다.

서버 연결 > 로그인 > 데이터 로드 > 조작

일반적으로 A 유저로 로그인을 하면 A 유저의 데이터만 가져올 수 있는데,
A 유저로 로그인하고 나서, B 유저의 데이터를 가져오는 게 가능할지 궁금했습니다.

1
2
3
4
5
6
client: <login>userA</login>
server: <login>success</login>
<!-- A 유저로 로그인하고 나서 B 유저의 데이터 로딩 시도 -->
client: <dataload>userB</dataload>
server: <dataload>success</dataload>
<!-- B 유저 로딩 성공 -->

이게 되네!?

이제 저는 다른 유저의 데이터도 조작할 수 있습니다.
마음에 드는 유저에게 아이템을 주거나 저를 방해하는 유저의 모든 스탯을 1로 만들어버릴 수도 있습니다.

요약

  1. 친구가 아주 작은 게임 서버를 운영한다.
  2. 필자는 강해지고 싶었다.
  3. 필자는 클라이언트 메모리 변조로 모든 것을 얻었다.
  4. 친구의 표정은 어두워졌다.
  5. 클라이언트와 서버 사이의 패킷을 훔쳐보고 위조했다.
  6. 친구는 입에 거품을 물었다.
  7. 다른 유저 데이터까지 변경해버렸다.
  8. 친구는 쓰러졌다.

이를 통해 얻은 것

  1. 서버는 클라이언트가 보낸 정보를 무조건 신뢰하면 안 된다.
  2. 중요한 정보는 암호화하여 위변조가 어렵게 해야 한다.