Packer는 기본적으로 IAT 를 파괴한다. 정확히는 압축을 위해서 원래의 IAT 를 희생하면서까지 크기를 줄이고 실행 중에 Packer 에서 자체적으로 IAT 를 만든다고 할 수 있겠다. Protector를 Packer + 각종 난독화 기능이 추가된 Packer로 생각하면 어떻게 접근하겠는가?
아래 글에서 API 를 쓰지 않은 사용자 함수 (XOR 로 이루어진 디코딩 함수) 를 난독화한다고 하자. Protector 에서는 이걸 어떻게 난독화 시키겠는가? 시스템 API 는 user32.dll kernel32.dll 등 코어 DLL 에 포함되어있다.
가장 쉬운 방법으로는
1. Packing 하기 전 파일의 IAT 를 싸그리 조사해서 어떤 함수를 사용하는지 확인한다.
2. Packing 과정에서 원본 IAT 에 Unpacking 을 위한 최소한의 API 만 남기고 전부 지운다.
3. 1번에서 확인한 함수이름을 GetProcAddress 함수를 이용해서 주소를 뽑아온다.
여기서 의문이 드는게 어차피 원본 파일의 IAT 가 필요하고 이를 위한 공간이 필요한데 뭐하러 별도의 IAT 에다가 이걸 압축했다 푸는 짓거리를 하냐는 거다. 오히려 IAT 가 하나 추가되면서 용량이 커지지 않느냐는 의문이 들 수 있다.
Import 하는 DLL 이름과 함수를 미리 저장한다. 주소가 아니라 이름이다. IAT 생성 시에 LoadLibrary 함수를 이용해서 미리 저장된 DLL 이름에 해당되는 DLL 을 로드하고 마찬가로 GetProcAddress 함수를 이용해서 미리 저장된 함수이름을 인자로 뽑아서 실제 주소를 구해서 기록한다. 그래서 Unpacking 작업에서 IAT 복구에는 LoadLibrary 함수와 GetProcAddress 함수는 필수적이다. 차이점은 Packing 하지 않은 파일은 IAT 가 미리 만들어져 있고 Packing 된 파일은 Unpacking 을 위한 최소한의 API 들만 있고 실행 중에 IAT 가 생성된다. 즉 원래의 IAT 보다 작은 IAT 를 가지고 있으니 압축의 효과가 있다.
사용자가 만든 함수에도 마찬가지로 DLL 로 배포할 것인가 아니면 파일 자체에 포함시킬 것인가로 나눌 수 있다. DLL 로 배포될 경우 마찬가지로 IAT 를 망가뜨리고 Import 하는 모든 DLL 과 함수들을 미리 뽑아서 난독화 시키는 방법이 있겠다. 즉, 일반적인 API Redirect 나 API Wrapping 과 동일한 방법으로 난독화가 된다는 사실은 쉽게 알 수 있다.
내가 의문이 드는 것은 특정 DLL 에서 뽑아쓰는 함수 없이 파일 하나에 만들어 놓은 함수이다. 인코딩의 경우 적당한 XOR 과 사칙연산의 조합만으로 강력한 암호화가 가능하다고 알고있다. 내가 악성코드 배포자라면 이러한 핵심 함수를 목숨걸고 난독화 할 것인데... Packer 나 Protector 들이 API Redirect 하거나 API Wrapping 하는 방식이 원본 파일의 IAT 만 조사해서 하는 것인지 Call 명령어만 보이면 무조건 opcode 만 바꾸는건지 모르겠다.