C#은 왜 디컴파일에 취약할까?

#C-Family

C#의 컴파일 과정과 실행 과정 그리고 그 취약점에 대해서


C#의 활용력은 정말로 넓다. 윈도우 앱, 웹 개발, 클라우드, 모바일, IOT 그리고 게임까지 정말 만능인 거 같다. 또한 이식성이 매우 뛰어나다. 윈도우 환경에서 개발한 C# 소프트웨어를 .NET이 구축된 리눅스와 맥에서 실행할 수 있다(물론 제약 사항은 존재한다). 이 이식성을 위해서 설계한 부분이 C#의 취약점을 낳았다.

컴파일 과정

우선 C#의 컴파일과 실행 과정을 살펴 보아야 한다.

c_sharp_compile_process

우리가 작성한 C# 코드는 C# 컴파일러에 의해 중간 언어(Intermediate Language, IL)가 만들어진다.

우리가 C# 프로그램을 실행하면 CLR(Common Language Runtime)이 IL 언어를 JIT(Just-in-Time) 컴파일러로 컴파일하여 실행 가능한 네이티브 코드를 만들어서 실행한다.

이렇게 컴파일을 두 번이나 하는 이유는 이식성 때문이다. IL 파일만 가지고 있으면 CLR이 구축된 어느 환경에서도 실행이 가능하다.

마치 자바의 JVM(자바 가상 머신)과 느낌이 비슷하다. 이 과정에서 자바와 C#은 피할 수 없는 취약점을 안고 다닐 수 밖에 없다.

디컴파일에 취약

바로 디컴파일이 매우 쉽다는 것이다. C#의 실행파일(exe, dll 등)은 IL(중간 언어)를 가지고 있다. 이 중간 언어에는 아주 풍부한 메타데이터가 들어있고 이것을 해석하면 원본 소스 코드와 거의 유사하게 코드가 복원이 된다. 흔히 dotpeek이라는 소프트웨어를 이용해서 디컴파일한다. 역시 자바도 마찬가지이다. 자바도 jd-gui를 이용하면 쉽게 디컴파일이 된다.

취약점 극복

취약점 극복의 방법으로 다음과 같은 방법이 있다.

  1. 코드 난독화

Dotfuscator같은 난독화 소프트웨어를 이용해서 코드를 난독화 시키는 것이다. 설령 디컴파일이 된다고 해도 코드 해석에 어려움을 주는 것이다.

  1. C++ DLL 활용

내부 중요한 로직은 C++ DLL로 만들어서 C#에서는 호출만 한다면 디컴파일을 어렵게 할 수 있다. 물론 C++도 리버싱의 위험에서 자유롭지 못하지만 C언어나 C#에 비하면 비교적 리버싱이 어려운 편이다.