저수준 API를 후킹할 때에는 해당 훅 대상이 되는 함수에 대해서 면밀하게 살펴야합니다. 그렇지 않을 경우에는 엉뚱한 부분 때문에 디버깅에 상당히 많은 시간을 잡아먹을 수 있습니다.
흔히 저지르는 실수는 훅 함수 내에서 후킹 대상 함수보다 높은 수준의 API를 호출하는 것입니다. 뭔소린가 싶은데 그냥 쉽게 설명하면 RtlAllocateHeap 따위의 함수를 후킹해놓고 해당 후킹 함수 내에서 동작을 살피기 위해서 OutputDebugStringW 따위를 호출하는 행위를 조심해야 한다는 것을 의미합니다. OutputDebugStringW 함수는 내부적으로 유니코드 문자열을 멀티바이트 문자열로 변경하기 위해서 힙을 할당하는 함수를 사용하는데 그러면 다시 후킹 함수가 호출되고, 그러면 다시 OutputDebugStringW가 호출되고, 다시 후킹 함수가 호출되고, 다시 OutputDebugStringW가 호출되고 하는 무한 재귀 루프에 빠져버립니다. 결국 스택을 다 소진할 때까지 반복하다 크래시가 발생하는 것이죠.
위와 같은 단순한 경우 외에도 좀 복잡한 경우도 있습니다. 후킹을 하는 대상 함수가 특정한 시스템 락을 획득한 상태에서 호출되는 함수이고, 후킹 함수 내에서 호출하는 API가 그 락을 다시 요청하는 경우에 문제가 발생할 수 있습니다. 락이 재귀적으로 획득할 수 있도록 구성된 경우에는 문제가 없지만 재귀적으로 획득할 수 없도록 구성된 경우에는 문제가 발생할 수 있습니다.
그래서 저수준 API를 후킹하는 함수를 작성할 때에는 해당 후킹 함수 내부에서 호출하는 함수 중에 혹시나 다시 후킹 함수를 호출하게 될 가능성이 있는건 없는지를 꼼꼼히 살펴야 합니다. 물론 함수를 본다고 해당 함수가 어떤 내부 함수를 호출하는지를 다 알수는 없는 법이죠. 결론은 그냥 후킹 함수는 최대한 간단하게 작성하는 것이 좋습니다. 없는듯이 작성하고, 복잡한 연산은 훅 함수가 아닌 다른 곳에서 처리하도록 보류 시키는 것이 좋은 전략입니다. 당연한 소리지만 그것보다 더 좋은 전략은 후킹을 사용하지 않는 겁니다.