25. 코드 난독화 & 가상화
본문 바로가기
Reversing & Cheat Engine

25. 코드 난독화 & 가상화

by boosting 2024. 3. 10.
728x90

왜 프로그램을 보호해야 하는가

이전에 썼던 글에서처럼 싱글 게임일 경우 직접적인 피해는 없습니다.

문제는 온라인 게임 등 사람들 간의 경쟁 게임이거나 수많은 돈이 오고 가고 하는 프로그램은

이러한 부정 방법을 쓸 수 없게 해야 하겠죠. 치트가 허용 되면 공정성이 없으니까요.

물론 치트를 사용하는 유저를 하나하나 다 잡을 수 있으면 제일 좋겠지만 사실 이 방법은

다들 아시다시피 힘든 편입니다. 만성적인 인력 부족의 이유가 제일 크겠지만요.

 

이러한 과정에서 리버싱을 하기 어렵게끔, 혹은 거의 불가능하게끔 하는 기술들이

매우 중요해졌다고 볼 수 있습니다. 내용물을 볼 수 없다면 치트도 못 쓸 테니까요.

 

보호하는 입장에서도 리버싱은 필수다

리버싱이라는 걸 치터들이나 악성코드 개발자들만이 다룬다고 아는 분들도 있습니다.

하지만 보안 개발자들도 리버싱은 필수로 해야 하는 게 요즘은 악성코드나 핵 개발자들도

자기 프로그램의 보안을 위해 많은 노력을 거치고 있습니다. 보안 개발자들도 시중에 나온

악성코드나 핵 프로그램 등이 어떻게 돌아가는지를 알아야 막을 수 있기 때문에 말이죠.

결국 창과 방패의 싸움이라는 말이 딱 맞는 셈입니다. 서로 공격, 방어를 주고받으니까요.

 

 

코드를 보호하는 기법

잘 알려진 방식엔 크게 3가지가 있습니다.

  • Packer(패커) // 이전 글에서도 잠깐 등장
  • Obfuscator(난독화)
  • Protector (Virtualization, 가상화)

1. 패커

 

패커는 지난 글에서도 잠깐 다뤘지만 정적 분석을 하는 방식을 막기 위해 쓰는 도구입니다.

정적 분석의 대표적인 프로그램으로는 Ollydbg가 있습니다. 파일이 실행되기 전에 내용을

볼 수 없도록 막는 방법이죠.

 

지난 글에서 Cheat Engine을 사용할 경우, 정적이 아닌 동적인 분석이 이뤄지기 때문에

패킹이 안 풀린 모습을 볼 일은 잘 없다고 했는데 안 풀린 모습을 보면 요렇게 생겼습니다.

 

 

이런 식으로 API나 이런 곳들은 다 정상적으로 메모리가 올라와 있습니다.

 

 

정작 클라이언트 첫 명령어가 박혀있어야 할 곳은 뭔 알 수 없는 메모리로 박혀 있습니다.

프로그램이 정상적으로 실행이 안 됐기에 패킹이 안 풀린 것입니다. 이러면 내용을 아예 못 보죠.

하지만 패킹의 경우 그냥 프로그램을 실행해서 메모리가 올라간 후 덤프를 떠서 본다는 등의

파훼법이 있기 때문에 패킹만 하는 경우는 없습니다.

 

2. 난독화

 

난독화는 코드의 변수나 클래스, 문자열 등을 해석하기 어렵게 변경해놓거나 실행을 안 하는

일명 쓰레기 코드(Garbage Code) 등을 배치하여 가독성을 떨어뜨려서 리버서 입장에서

분석하는 시간을 지연시키는 방법입니다.

 

이전 글들 중에서 이미 한 번 나왔었습니다.

 

9. 크랙미 첫 번째

시작은 크랙미부터 글을 써보기 위해서 이래저래 크랙미 버전들을 찾아봤는데 적당한 난이도의 크랙미들도 많더군요. 기존에 썼던 내용들을 최대한 써가면서 풀어보는 게 실력 쌓는 데에 도움

poppintip.co.kr

 

위의 글을 보시면 문자열을 프로그래머가 만들어 낸 규칙에 따라 암호화된 것을 풀고 있는 걸 볼 수 있습니다.

이런 식으로 문구를 암호화시키는 행위도 난독화에 해당합니다. 크랙미에서의 문제들이 대부분 난독화를

이용하여 리버서의 코드 가닥을 떨어뜨리는 행위로 이루어집니다.

또한 여태껏 글들을 보시면 Module+XX 이런 식으로 사람이 쉽게 알게끔 표기된 정보들이 있는데

이를 변경하거나 랜덤으로 바꾸는 식으로 가독성을 떨어뜨리기도 합니다.

 

3. Protector

 

위의 2가지 방법을 섞어 쓰며, 안티 디버깅이나 커널 드라이버 감지 등 분석 환경 감지

그리고 코드 가상화의 기법을 사용합니다. 사실상 끝판왕에 해당하겠죠.

 

안티 디버깅의 경우 제가 거의 모든 글에서 디버거를 사용했었죠.

이 디버거를 사용할 수 없도록 디버깅 중인 상태를 감지한다는 등 하는 방식입니다.

기본적인 디버깅을 사용할 수 없을 때 커널 쪽으로 드라이버를 올려서 커널 디버깅을

사용하는데, 이 관련 드라이버들을 감지하는 등이 분석 환경 감지에 해당합니다.

하지만 커널 드라이버 감지의 경우 OS마다 커널 구조가 다르기 때문에 이를 다 아우르는

감지 체계는 제 경험상으론 못 본 거 같습니다.

 

결국 커널 드라이버까지 사용을 동원한다 하면 디버깅하는 행위 자체를 막는 건

힘들기 때문에 이 가상화라는 기술까지 사용을 하는 것이겠죠.

 

코드 가상화라 하면 원래의 명령어들을 가상화시키고 이 가상화된 코드를 본체 CPU가

처리하는 게 아닌 개발자가 만든 소프트웨어적인 핸들러가 처리하는 되는 기술입니다.

그러니까 정리를 하자면 다음과 같습니다.

 

바이트코드 -> Virtual CPU -> 하드웨어 CPU

 

이 순서로 실행이 되어서 가상화되었음에도 정상적으로 프로그램은 실행이 되는 것입니다.

코드 가상화에도 많은 프로그램들이 있는데 각 프로그램들마다 가상화하는 방식들은 다릅니다.

실제 사용되고 있는 예시를 하나 가져와보면 이렇습니다.

 

 

보시면 시작 부분은 저희가 아는 어셈 명령어인 거 같은데 밑 부분부터 이상합니다.

어딘가로 jmp를 하게 되어 있고 밑에는 다 깨져 있습니다.

 

조금 더 내려봐도 전부 깨져있습니다.

일반적인 명령어들이 아니라 패킹이 안 풀린 것 마냥 어셈이 다 깨져있습니다.

즉 이 안에 있는 명령어들과 호출되는 함수들을 제작자가 의도하고 숨긴 거라 볼 수 있겠습니다.

그러면 결국 분석이든 뭐든 시도를 해보려면 이 jmp를 타고 넘어가야 할 것입니다.

 

 

jmp를 타고 넘어왔더니 push와 pop 명령어를 난사해서 스택부터 다 부숴버리네요.

저희가 알고 있는 일반적인 명령어 체계가 전혀 아닌 겁니다.

이러한 경우 이 가상화 코드를 만드는 로직을 알아내거나 원본이 없으면 이 내용은

알 수가 없습니다. 뭐 이 가상화된 함수도 어딘가에서 호출을 받고 뭐 하긴 할 테니

그런 단편적인 정보 습득이 전부일 것입니다. 그 단편적인 정보로 안의 함수 내용이

어떤 거일지 추리를 해야 되는 그런 영역에 도달을 하죠...

그렇기 때문에 이전 글들에서 한 두 번씩 말했던 대로 리버싱은 막일의 향연입니다.

결국 얼마나 오래 붙들어 매서 쳐다보냐의 싸움인 거예요...

 

예시로 다뤄볼 만한 프로그램들을 찾아보긴 할 텐데 있으면 한 번 자세하게 소개를 해보고

없으면 제가 어쩔 수 없이 온라인 게임들로 예시를 들어야 할텐데 그러진 않았으면 좋겠네요..

728x90