보통 소스코드를 보호하기 위한 기법(?)으로 크게 세 가지가 있습니다.
ㄴ “소스코드 난독화”, “바이너리 암호화”, “패킹”
소스코드 난독화
소스코드 난독화는 말 그대로 소스코드를 뒤죽박죽 만들어서 리버싱 엔지니어링을 힘들게 만드는 보호 작업을 의미입니다.
난독(명사) -읽기 어려움
소스코드 난독화에는 (1) 컴파일 전 원본 소스코드(*.java) 자체를 난독화하는 경우, (2) 중간 언어(IL)로 컴파일된 코드(*.jar)를 난독화하는 경우가 존재합니다.
(1)에 경우는 자바스크립트(js) 난독화가 대표적입니다.
단순히 소스코드 자체를 어떠한 컴파일 단계도 거치지 않고 난독화하는 경우입니다.
(2) 자바는 Java Bytecode(*.classes)로 안드로이드는 Dalvik Bytecode(dex), 닷넷의 경우는 Common IL(dll)로 컴파일하여 난독화하는 경우를 말합니다.
이 때문에 바이너리 난독화라는 표현도 혼용됩니다.
(1)에 내용처럼 평문으로 된 자바 소스코드 자체를 난독화하는 행위보다,
자바 소스코드(*.java)를 Java Bytecode(*.classes)로 컴파일한 후 난독화하는게 훨씬 쉽습니다.
컴파일된 기계어를 다루는 것이라면 어려운 것이 당연하겠지만, Java Bytecode는 말 그대로 Native Code가 아닌 특정한 형식을 뛴 중간 Code일 뿐입니다. (오히려 Plain Code보다 단순한 구조를 뜁니다)
https://en.wikipedia.org/wiki/Common_Intermediate_Language CIL은 이렇게 생겼습니다.
바이너리 암호화
바이너리 암호화는 소스코드를 알아보기 힘들게 난독화하는게 아니라, 애초에 디컴파일이 불가능하게 바이너리 자체를 암호화하는 것을 말합니다. (이 때문에 위변조 방지라고도 합니다)
암호화된 파일은 복호화 없이는 실행될 수 없는 더미 파일이나 마찬가지입니다. 이를 실행시키기 위해선 내부적으로 임의에 복호화 작업을 거쳐 메모리에 올라가야만 합니다.
이를 가장 많이 쓰고 있는 것은 안드로이드 보안 솔루션입니다.
*.dex 파일을 암호화한 후 assets에 보관합니다.
APK 파일이 실행되면 assets에 보관되어 있는 DEX 파일을 복호화하여 올립니다.
바이너리 암호화에는 한 가지 필수조건이 있습니다.
바로 복호화를 하기 위한 별도의 실행 프로그램이 존재해야 합니다.
대개 이러한 역할을 하는 프로그램을 로더(Loader)라고 합니다.
안드로이드의 경우 classes.dex 파일은 필수이기 때문에 이를 Loader로 제작하고 원본 코드를 넣은 classes2.dex 파일을 암호화하여 사용하는 방식이 일반적입니다.
안드로이드의 경우 대게 Proguard로 소스코드 난독화를 한 후 보안 솔류션으로 바이너리 암호화를 진행하는 모습이 많이 있습니다.
패킹
패커에도 물론 사용 용도에 따라 여러 표현이 있겠지만, 이 글에서는 “Packer for Executable”한 대상에 대해서 말합니다. (그냥 Packer는 표현이 좀..)
리버싱 엔지니어링을 가장 먼저 배우면 실행 바이너리(exe)에 대한 패커를 많이 볼 수 있습니다. 가장 고전적인게 UPX 패커입니다.
실행에 필요한 Header 정보는 건들지 않거나, 동작에 필요한 최소한의 요소만 수정을 가하고, 최초 실행 시 인메모리 상에서 암호화된 코드를 원본 기계어 코드로 복호화하도록 동작됩니다.
소스코드 난독화 보다는 바이너리 암호화에 가까운 측면이며,
제3의 로더를 통해서 복호화하는게 아닌 자기 자신을 복호화하는 측면과 Code Section, Data Section 자체를 암호화한다는 차별화된 내용이 있습니다.
주로 윈도우즈(exe) 파일에 대한 PE 구조가 잘 알려지면서 윈도우 실행파일에 대한 패커가 많이 발전한 상태입니다.