2014.12.30 23:44

분석 돌려놨는데 심심해서 또 씀.

자가보호기능 위주로 씀.

 

기본적으로 치트엔진 등을 탐지하는 루틴엔 별다른 보호기능이 없다. 수정하기도쉽고 VM이 걸려있지도 않음.

리턴값만 슥- 해주면 신경쓸게 없는 정도.

 

헌데 자기 자신을 리버싱하려는 시도엔 매우 민감하게 대응해놨음.

 

1. CRC

스스로 중요한 함수 부분이나 모듈 전체에 대한 체크섬을 뜨고 여기저기에서 실행하고 여기저기에다 저장하고, 일부는 서버로 보낸다.

이걸 다 우회하려고 한다면 아마 20개 가까운 CRC를 속여야 할 거다.

일부는 VM안에서 실행되거나 더미다 VM 핸들러를 보호하는 CRC인 경우도 있고, 시간차 공격 CRC, 서버사이드 CRC도 있다.

솔직한 감상으로, 이 부분은 어느정도의 정신병을 각오하고 분석하길 바람.

2. ehsvc.dll의 리버싱 시도가 탐지 됐을때의 대처

모올래 메모리 어딘가에 감지됐다는 플래그를 셋팅(모든 코드는 vm안에서 실행됨)

그 후 vm 내부에서 프로세스 강제종료 시도나 서버에서 요청이 오면 "나 해킹당했어요"하고 대답하는 상태가 된다.

CRC만 아니면 이부분만 조져도 된다. 헌데 쉽진 않을거임. 다 vm이거든 ㅋ

3. 디버그레지스터, 디버거, vm 체크 등

기본적이면서 강력한 방식 위주로 구성돼있음

디버그레지스터 부분은, 조금만 더 신경썼으면 뚫기 상당히 까다로웠을 텐데, 라는 생각이 든다.

그거이외엔 본인 실력에 달렸다.

몇가지는 VM 안에서도 call된다. 그것만 주의하면 끝.

안티덤프나 안티디버깅 등의 기능은 더미다에 크게 의존하는 경향이 있다. 더미다는 런타임에서는 알기만 하면 의외로 쉽게 무력화가 되기 때문에, 경험에 달렸다.

최신버전은 좀 짜증난다.

4. 모니터링 서버로 정보 전달

해킹 시도가 감지되면 입력된 아이디를 모니터링 서버로 전송하는 것 같은데, 매우 무섭다. 이러지 말자.

 

 

다음 글은 슈도 kernel32.dll을 이용한 api 후킹 방어 기능에 대해서 중점적으로 설명해볼 생각

매우 씽크빅하면서도 강력하기에, 포스트 하나를 꽉 채울 정도의 가치를 하는 기능임!

Posted by 라이에
2014.12.20 22:58

최근, 1주일+a 동안 즐겁게 핵쉴드를 분석해서 내리는데 성공했습니다. 그래서 정리도 할 겸 끄적여봐야지~ 싶은 포스트.

*개발자나 리버싱 좋아하시는 분들 가볍게 보고 가시라고, 또 이걸 보고 스크립트키디가 핵쉴드를 내려서 큰 파장이 되지 않도록, 코드나 기술적인 부분의 상세사항은 적지 않습니다.

분석해본 결과 핵쉴드는 크게 치트엔진이나 디버거 등을 이용한 메모리 해킹+매크로 등의 자동플레이 툴, 논클라이언트 봇을 방지 기능을 가지고 있었습니다.

 

생각나는대로 적어야지..

 

인터페이스

게임 프로그램은 EhSvc.dll을 로드하고, 핵쉴드에서 제공하는 SDK에 따라 클라이언트 프로그램을 개발합니다. EhSvc.dll이 핵쉴드의 본체이며, 거의 모든 보안기능은 얘가 합니다.

대강 이렇게 작동합니다.

1. Ehsvc.dll 로드

2. Ehsvc.dll 안에 있는 intialize 함수 호출

이 과정에서 어떤 어떤 보안 기능을 사용할 것인지, 라이센스 정보 등을 전달합니다.

드라이버 로드

Ehsvc.dll 내부에 있는 드라이버를 추출해 기억이 잘 안나는데 temp folder같은데에 파일 만들고 즉시 서비스로 로드합니다.

드라이버 통신에는 수시로 바뀌는 AES키로 암호화된 내용으로 이것저것 보냅니다.

옛날에는 이것저것 기능이 매우매우많았으나, ms가 서명 안한 드라이버는 못올리게 된 이후로 아주 많은 부분이 삭제된 정황이 코드에 남아있네요.

그래서 지금 주 기능은 프로세스에 접근하는 유저모드 API를 막는 것, 멀티 instance(통칭 멀티로더)를 체크 하는 기능입니다.

3. 게임 실행

각종 상황에 대해 클라이언트에 콜백을 전달해줍니다. 스피드핵이나 해킹툴이 감지되었다 등등.

가장 빡치는 기능 중 하나는, 해킹 시도에 대한 콜백이 전달된 후 클라이언트가 스스로 종료하지 않으면 핵쉴드가 강제로 프로세스를 종료해버립니다. 메모리나 코드 보고있는데 계속해 꺼지니 여간 번거로운게 아님.

해킹툴 체크, 디버거 체크, 디버그 레지스터 체크 등 무수한 체크.

코드 수정이 가능하다면 우회하는 건 어렵지 않음

무수히 무수히 무수히 많은 CRC체크

정신병 걸릴뻔 했습니다. 다 뚫고나니 시원함

해킹툴 등의 체크를 무력화하는 것이 쉬운 대신 CRC에 목숨건듯한 느낌이 남.

서버에서 보내는 핵쉴드 인증 패킷을 처리합니다.

서버에서 요청하는 인증은 몇개인지 모르겠으나

현재 핵쉴드가 해킹 툴을 감지했는지에 대한 여부

클라이언트 CRC

Ehsvc.dll CRC

클라이언트 CRC(0x1000바이트씩 국소적으로)

기타 무수히 많은 인증

등을 요청에 따라 적절히 암호화해서 보냅니다.

이부분은 민감한 부분이니만큼 VM이 하도 많아서 짜증나네요.

4. 게임 종료

드라이버를 내리려고는 합니다만, 많은 경우에 성공적으로 내리지 못합니다..

 

메모리 해킹 방지 기능

아시다시피 메모리해킹은 기본적으로 완벽하게 막을수가 없습니다. 컴퓨터 전체가 가상화돼있지 않은 한 모든 메모리 쓰기 및 읽기를 감지하는 것은 기본적으로 불가능합니다. CPU에서 지원하질 않아요. 그래서 핵쉴드는 1차적으로는 드라이버를 올려 프로세스의 메모리를 읽으려는 시도를 막고있습니다. 이 방법으로는 유저모드 어플리케이션의 메모리 해킹을 거의 100% 막을 수 있습니다.

문제는 똑같이 드라이버를 사용해 메모리를 읽는 툴은 막을수가 없습니다.

때문에, 이런 툴들에 대해서는 "감지"하는 것이 유효한 전략입니다. "메모리 해킹 툴이 감지됐으니 종료됩니다." 라는 메세지를 띄우고 스스로 종료되는 겁니다.

 

아.. 더 나가면 게임 해킹의 역사를 설명해야 되네... 그건 싫으니 이만 생략합니다.

 

하여튼 그렇기 때문에 해킹툴 감지 기능은 필수입니다.

분석 결과 감지 기능은 크게 3가지 정도로 나뉩니다.

1. 핸들 체크

현재 열린 모든 핸들의 패턴을 체크합니다. 특정 뮤텍스가 열렸는지 체크하기도하고, ... 등등

2. 프로세스 체크

프로세스의 특정 메모리를 해쉬를 떠서, DB에 있는 내용과 일치하는 것이 있는지 체크합니다.

3. V3 엔진

미니멀한 v3 백신 엔진을 가져와서 탐지하던데, 자세한건 모르겠네요. 머리아프게 분석하기도 싫고..

 

 

나머진 나중에~

 

 

Posted by 라이에
2010.09.26 00:58

개요: 해킹의 취약한 유형의 프로토콜이 무엇인지,  안전한 프로토콜을 작성하기 위한 전략엔 어떤 것이 있는지 소개합니다.
필독: X
조건: 패킷의 개념 정도만 이해하시면 됩니다
난이도: 하 

 *틀린 부분은 지적해주시면 바로 수정하겠습니다(코딩오류, 오타 등).
게임 핵을 만든다고 개설된 커뮤니티들에서는 이러한 이름의 익스플로이팅을 자주 볼 수 있습니다:

미스핵
강화핵
데미지핵
etc..


 말인즉 데미지를 받아도 항상 0으로 받고, 강화나 인챈트 등을 하면 무조건 성공하며, 상대에게 주는 데미지를 마음대로 수정할 수 있다는 것입니다.

 이런 종류의 취약점은 전에 작성하였던 해킹 방지 프로그래밍 기법 1 에서 사용하였던 방법대로  변수에 대한 메모리 접근 보호를 클라이언트 측에서 수행하여 방어할 수도 있습니다.

 하지만 이 방법은 100퍼센트 안전한 방법이라고 말할 수 없습니다. 리버싱에 불가능은 없다고 가정하는 것이 좋습니다. 해커가 충분한 노력과 시간을 들인다면 개발자가 제작한 게임이나 기타 어플리케이션의 기계어 코드와 리소스를 가지고 거의 똑같거나 완벽하게 똑같은 프로그램을 직접 작성하는 것도 아예 불가능하지는 않습니다. 또 이런 극단적인 상황에서 아무리 메모리 해킹에 대한 방어를 철저히 한다고 해도 소용은 없을 것입니다.

 가장 확실한 방법은 프로토콜 자체의 취약점을 없애는 것입니다. 개발자가 정말로 신용할 수 있는 모든 정보는 서버에 있습니다. 클라이언트가 어떤 정보를 보내오든, 그것을 바로 신용해서는 절대로 안됩니다.

 위에서 언급한 익스플로이팅 중 '데미지핵'에 대한 간략한 시나리오를 세워보자면 다음과 같습니다.

 개발자는 클라이언트가 몬스터에게 데미지를 입으면 그 데미지를 클라이언트가 계산하여 서버에 보낸다. 그리고 서버는 클라이언트가 보낸 데미지 정보를 토대로 몬스터의 체력을 깎는 등의 연산을 수행하는 구조이다.

 해커는 보호되어있는 '데미지 계산 루틴'을 찾아 입맛에 맞게 수정한다, 혹은 패킷을 보내는 루틴을 후킹하여 데미지 부분의 정보만 수정한다.

 서버는 클라이언트에게서 도착한 데미지 정보를 신용하고, 해커가 공격했던 무☆적의 보스몹은 짧은 인생을 마감하게된다.

 이러한 익스플로이팅이 가능한 이유는 서버가 클라이언트가 보낸 정보를 그대로 신용한다는 점에 있습니다. 하지만 데미지 연산에는 (아마)랜덤한 상수가 포함되어있을 것이고, 그 랜덤한 상수를 포함하였을때 가장 데미지가 큰 경우'를 가지고 서버 측에서 데미지를 검증하는 방법이 이 프로토콜을 사용하였을 때 가장 바람직한 방법입니다만, 그것 역시 문제가 있습니다.

 '해커는 무조건 최대데미지를 서버에 보낸다' 라는 상황이 발생할 수 있습니다.

 결국 개발자는 이러한 프로토콜 자체를 뜯어고쳐야 할 필요가 있습니다. 근본적인 원인은 클라이언트가 데미지의 수치를 계산할 수 있다는 점에 있습니다. 클라이언트가 데미지를 계산할 수 없는 프로토콜을 제작한다면 아무리 뛰어난 리버서라도 서버 내부에서 연산되는 데미지의 수치를 바꿀 수는 없습니다. 

 다음과 같은 프로토콜에는 이러한 익스플로이팅이 불가능합니다.

개발자는 클라이언트가 몬스터에게 데미지를 주었다는 사실만 서버에 알린다.

서버는 그 정보를 받아 들고 데미지를 알☆맞★게 계산하여 몬스터의 데미지를 깎아 내리고 데미지가 얼마인지 클라이언트에게 통보해줍니다.

 물론 모든 데미지의 수치를 서버가 계산해야 된다는 점에서 서버의 부하가 늘어나는 것은 어쩔 수 없습니다. 하지만 그러한 부하도 서버의 설계만 잘 해놓았다면 쉽게 줄일 수 있습니다.

 일반적으로 데미지를 계산할 때 필요한 상수는 쉽게 바뀌지 않습니다. 일단 클라이언트가 착용한 장비의 영향을 받을 것이고, 간혹 소지품의 영향을 받는 경우도 있을 것이고, 마법같은 시스템이 있다면 그것의 영향 역시 받을 수 있습니다.

 부하를 줄일 전략을 세워보자면 이렇습니다.

 데미지에 필요한 상수를 그 상수에 영향을 끼치는 요소(장비, 마법 등의 효과로 인하여)가 바뀔때마다 미리 계산해두고 랜덤한 상수의 범위를 계산해둡니다. 장비나 마법등에 의해 상태가 바뀌는 경우는 그렇게 많지 않습니다.

 하지만 이 두가지 정보만 계산해 두면 서버는 데미지를 아래와 같은 아주 단순한 방법으로 구할 수 있습니다.

Dmg  = 계산된고정상수 + rand() % 랜덤상수의최대값
 이정도 연산에서 생기는 부하가 아깝지는 않을 것입니다. 이제 개발자의 서버에서는 절대로 '데미지핵'과 같은 익스플로이팅이 불가능합니다.




 2편에서는 아마도 시간 계산에 관련된 익스플로이팅(공격속도, 스킬 쿨타임이나 '스피드핵' 유틸리티 등)에 관하여 포스팅하게 될 것 같습니다.
 졸리네요 바이바이 ><
Posted by 라이에
2010.09.25 15:41

개요: 메모리 해킹이 일반적으로 어떻게 이루어지는지, 또 그에대한 대처책을 소개합니다. 난이도는 '하'이므로 간단한 방법 위주로 설명합니다.

필독: 치트엔진을 사용한 핀볼 해킹 예시 1

조건: C언어 사용 가능 혹은 다른 언어로의 프로그래밍에 능숙

난이도: 하

 

 *틀린 부분은 지적해주시면 바로 수정하겠습니다(코딩오류, 오타 등).

 

 

 '치트엔진을 사용한 핀볼 해킹 예시 1 문서'를 읽었다는 가정하에 진행합니다.

 어플리케이션 (특히 게임) 개발자로서 위와 같은 간단한 방법으로 내 프로그램 내의 변수를 수정할 수 있다는 것은 상당히 위협적입니다.

 실제로 온라인 게임을 작성하는 개발자들도 위와같은 방식의 해킹 기법을 간과하고 프로그램을 작성하는 경우가 많아 안타깝습니다. 약간의 지식만 있어도 게임가드, 핵실드 등의 비싼 보안 솔루션을 사용하지 않아도 될텐데 말입니다.

 실제로 이런 방법을 사용해 게임 내의 체력과 딜레이 등의 정보를 수정하여 쉽게 해킹이 가능했던 온라인 게임들이 수 개월만에 서비스를 종료하게 된 사례도 있습니다.

 

 개발자 입장에서 구현 단계에서부터 설명하겠습니다.

 당신은 바람의나라와 같은 방식으로 진행되는(4방향으로 1칸씩 움직이는 시스템에 주목) 온라인 RPG 게임을 제작하는 개발자라고 가정합니다.

 지금은 개발 초기단계입니다. 당신은 클라이언트가 서버에 접속해 로그인 후에 맵 상에 돌아다니는 잉여 몬스터를 때릴 수 있도록 서버와 클라이언트 간의 프로토콜을 구현하였습니다. 구현된 프로토콜은 다음과 같습니다.

-> 클라이언트는 현재 자신의 장비와 몬스터의 방어력 등을 계산해 결과 총 데미지를 계산하고, 자신이 타격한 몬스터의 오브젝트 번호를 전송합니다.

 

<- 서버는 클라이언트가 보내준 정보를 그대로 받아 해당 오브젝트 번호의 몬스터에 해당 데미지를 주고, 이벤트를 발생시킵니다.

 현명한 개발자라면 이 프로토콜에 상당한 문제점이 있다는 것을 쉽게 눈치챕니다. 하지만 현업 게임 개발자는 실제로 프로토콜을 이런식으로 설계하는 경우가 허다합니다.

 이 프로토콜을 그대로 사용한다면 해커는 쉽게 다음과 같이 게임을 해킹할 수 있습니다.

 

1. 데미지에 영향을 주는 변수(장비 등)를 찾아 값을 수정해 서버에 전송되는 데미지를 증가시킬 수 있다.

 

2. 리버싱을 통해 몬스터가 아무리 멀리 떨어져 있어도 공격에 맞도록 한다. 서버는 오브젝트 번호만을 받아 유효성 검사 없이 무조건 처리하기 때문에 해커는 사정거리 제한 없이 몬스터에게 데미지를 줄 수 있다.

 *이 글은 프로토콜에 대하여 이야기 하는 것이 아니므로, 심화된 내용은 '게임 등의 어플리케이션 개발 시 바람직한 프로토콜 설계 1 문서'를 참고하여 주시기 바랍니다.

 

 '데미지에 영향을 주는 변수'에 포커스를 맞추어 봅시다.

 개발자는 데미지 계산 루틴을 다음과 같이 작성했습니다.

 int CalcDmg(~){

  int Calc = (User->무기->데미지 + DEFAULT_DMG) - Monster->방어력;

  return  Calc >= 0 ? Calc : 0 ;

 }

 데미지 변수에 대한 아무런 검사가 이루어지지 않는 상태입니다. 이상태에서 해커가 유저의 무기 데미지혹은 몬스터의 방어력을 수정하면 쉽게 데미지가 올라갈 것입니다. 가장 중요한 일은 스캔 혹은 메모리 수정을 어렵게 하거나 불가능하게 만드는 것입니다.

 이를 방지하기 위한 방법은 여러가지가 있습니다.

 

 1번째로 변수의 내용을 암호화해 스캔이 어렵도록 하는 방법이 있습니다. 해커는 반드시 수정할 변수를 찾아야 합니다. 찾는 과정을 어렵게 만들고, 찾은 후에도 값의 수정을 어렵도록 만드는 원리입니다.

 아래는 간단한 암호화를 적용한 뒤의 코드입니다.

 void InitWeapons(){

  int i;

  for(i=0; i < WEAPONNUM ; i++){

    무기[i].데미지 = 10 ^ 0x12345678; // xor을 이용한 간단한 암호화

  }

 }

 

 int CalcDmg(~){

  int Calc = ((User->무기->데미지 ^ 0x12345678) + DEFAULT_DMG) - Monster->방어력;

  return  Calc >= 0 ? Calc : 0 ;

 }

 

 이 방법을 사용하면 무기의 데미지가 암호화되었으므로 스캐닝 및 변수를 다루는 과정이 어려워지게 됩니다. 뉴비 해커들은 이 단계에서 포기합니다. 하지만 암호화 과정을 거쳤어도 스캐닝 자체는 가능하므로 암호화는 완벽한 방법이라고 볼 수는 없습니다.

 

 2번째 방법은 메모리 수정 자체를 감지하는 방법입니다. 해커는 반드시 변수에 값을 써 넣야아 하는 입장이기 때문에 이를 적절히 이용할 수 있습니다. 변수에 값을 써 넣는 시점에서 '체크섬'을 따로 저장해놓고, 읽는 시점에서 이와 메모리 내용을 비교하여 같지 않다면 해킹에 대한 시도로 보고 클라이언트를 종료한다면 도중에 프로그램 내의 루틴을 거치지 않고 수정된 메모리가 있는지 검사가 가능합니다.

 

 void InitWeapons(){

  int i;

  for(i=0; i < WEAPONNUM ; i++){

    무기[i].인덱스 = i;

    무기[i].데미지 = 10;

    무기체크섬데미지[i] = &무기[i].데미지 ^ 10 ^ 0x12345678; // 간단한 체크섬 값을 만들어놓는다.

  }

 }

 

 int CalcDmg(~){

  if ( (&User->무기->데미지 ^ User->무기->데미지 ^ 0x12345678)

       != 무기체크섬데미지[User->무기->인덱스]; ){

         MessageBoxA(0,"해킹하지마 바보녀석아!",~~);

         ExitProcess();

      }

  int Calc = ((User->무기->데미지) + DEFAULT_DMG) - Monster->방어력;

  return  Calc >= 0 ? Calc : 0 ;

 }

 

 이런 방법으로 메모리 수정 시도를 감지하는 경우에는 해커 입장에서 리버싱을 할 줄 모른다면 해킹을 포기해야합니다. 99%의 해킹 시도를 차단할 수 있습니다.

 하지만 스캔 과정에서 체크섬의 값이 '변수와 함께' 스캔되는 경우가 있습니다. 그러므로 이 방법도 완벽하다고 할 수는 없습니다.

 

 3번째로, 아예 스캔 자체가 되지 않도록 변수를 보호하는 루틴을 작성하는 방법이 있습니다. 변수에 대한 스캔은 찾으려는 변수가 해커의 입장에서 마음대로 바꿀 수 있거나, 변수가 바뀐 시점과 바뀐 내용을 알 수 있다는 가정 하에 가능해집니다.

 이번 방법은 변수가 바뀌지 않아도 계속 수정되게 함으로써 스캔이 불가능하도록 만듭니다. 별도의 스레드를 생성하여 시간마다 달라지는 키를 사용해 변수 내용을 계속하여 바꿔주기 때문에 변수 자체가 제대로 검색되지 않습니다.

 long LoopThread(void* tmp){

  while(1){

    EnterCriticalSection(~);

    int i;   무기데미지키 = GetTickCount();

    for(i=0; i < WEAPONNUM ; i++){

      무기[i].데미지 = 무기[i].데미지 ^ 무기데미지키;

    }

    LeaveCriticalSection(~);

    Sleep(1);

  }

 }

 

 

 void InitWeapons(){

  int i;

  무기데미지키 = GetTickCount(); // 시간마다 키를 다르게한다.

  for(i=0; i < WEAPONNUM ; i++){

    무기[i].인덱스 = i;

    무기[i].데미지 = 10 ^ 무기데미지키;

  }

  CreateThread(~,LoopThread,~);

 }

 

 

 int CalcDmg(~){

  EnterCriticalSection(~);

  int Calc = ((User->무기->데미지 ^ 무기데미지키) + DEFAULT_DMG) - Monster->방어력;

  LeaveCriticalSection(~);

  return  Calc >= 0 ? Calc : 0 ;

 }

 

 

 제가 소개해드린 방법 중 가장 완벽한 방법입니다. 변수 자체가 검색되지 않으니 리버서 입장에서 리버싱할 루틴을 찾기 난해하고, 검색이 안되니 변수 수정도 불가능합니다. 만약 변수를 찾았다고 해도 1ms를 주기로 암호화 key가 바뀌기 때문에 변수를 제대로 수정할 수 없고,  '변수 수정 감지'와 병행하여 사용한다면 더욱 강력한 메모리 해킹 방지 기법이 될것입니다.

 정말 뛰어난 리버서가 ThreadLoop() 스레드를 죽여버리지 않는다면 이 방법은 해커로부터 거의 안전합니다.

 

이번 글은 이쯤에서 마칠까 합니다.

 

 

 

 

 

 

 

 

 

 

 

 

어쩌다보니 블로그에 글을 쓰고있습니다..
-선린고에 재학중인 어느 한 블로거-

Posted by 라이에

티스토리 툴바