64비트 Windows에서 기능 후킹을 보여주는 점점 더 복잡해지는 일련의 프로그램입니다.
나는 함수 후킹이 어떻게 작동하는지 스스로 배우면서 이 프로그램을 작성했습니다. 같은 일을 하려는 다른 사람들에게 도움이 될 수 있습니다(또는 이 모든 것이 다시 어떻게 작동하는지 잊어버린 후에 미래의 나에게). 순서대로 살펴보도록 되어있습니다. 함수가 프로그램에서 처음 사용되면 해당 특정 예제 프로그램의 .cpp 파일에 이름 앞에 밑줄이 붙은 상태로 포함됩니다. 동일한 함수를 사용하는 모든 후속 예제에서는 코드 중복을 최소화하고 이후 예제 프로그램을 여전히 쉽게 읽을 수 있을 만큼 작게 유지하기 위해 Hooking_common.h에 포함된 해당 함수의 복사본을 사용합니다.
나는 그것들을 정리하기 위해 약간의 작업을 수행했지만 이후의 예는 여전히 약간 지저분합니다. 이 저장소의 최종 결과는 모든 기능을 갖춘 후킹 라이브러리를 작성하기에 충분하지 않지만 해당 경로를 시작하기에는 충분하다는 점에 유의하는 것이 중요합니다.
런타임 시 일부 프로젝트는 빌드 중인 다른 프로젝트에 의존하거나(dll을 주입하려면 dll이 빌드되어야 함) 동시에 실행될 수 있습니다(대상 프로그램을 연결하려면 이미 실행 중이어야 함).
모든 예제는 Windows SDK 10.0.17763.0과 함께 Visual Studio 2019(v142)를 사용하여 빌드되었습니다. 여기에는 VS 또는 SDK 버전에 따라 달라지는 것이 없다고 생각하지만 만일을 대비해 여기에 나열합니다. MSVC와 관련된 몇 가지 사항이 거의 확실합니다.
마지막으로 마지막 트램폴린 예제에서는 mspaint에 후크를 설치합니다. 나는 미래의 어느 시점에서 mspaint 업데이트로 인해 이 예제가 중단될 것이라고 가정합니다. 이 글을 쓰는 당시 mspaint의 현재 버전은 1909(OS 빌드 18363.1016)였습니다.
예제는 트램폴린을 사용하는 것과 사용하지 않는 두 가지 범주로 나뉩니다. 트램펄린이 아닌 예제는 다양한 상황에서 프로그램 흐름을 한 기능에서 다른 기능으로 리디렉션하는 것을 보여주기 위해서만 존재합니다. 트램펄린을 만드는 것은 복잡하며, 함수 후킹이 어떻게 작동하는지 알아내려고 할 때 트램폴린이 아닌 예제를 먼저 만드는 것부터 시작하는 것이 매우 도움이 되었습니다. 또한, 다른(이미 실행 중인) 프로세스에 후크를 설치하는 방법을 보여주려는 예제에서 사용되는 4개의 "대상 프로그램"이 있습니다.
이러한 예제의 대부분은 후크와 관련된 메모리 누수입니다. 나는 별로 상관하지 않는다. 왜냐하면 이 예제들은 단지 후킹 개념을 보여주기 위한 것이고, 이러한 "유출된" 할당은 어쨌든 프로그램이 종료될 때까지 존재해야 하기 때문이다.
함수 후킹 기술에 대한 표준 용어는 많지 않지만 이 저장소의 코드(및 추가 정보)에서는 다음 용어를 사용합니다.
이 예제는 후크를 설치할 때 트램폴린을 생성하지 않기 때문에 후크를 설치한 후에는 원래 기능을 완전히 사용할 수 없다는 점에서 이러한 기능을 "파괴적인" 후크를 보여주는 것으로 생각합니다.
프로그램 흐름을 동일한 프로그램 내의 다른 기능으로 리디렉션하는 점프 명령으로 기능의 시작 바이트를 덮어쓰는 작은 예입니다. 생성되는 트램펄린이 없기 때문에 이 작업은 파괴적이며 원래 함수를 더 이상 호출할 수 없습니다. 이는 저장소의 유일한 32비트 예입니다.
이전 예의 64비트 버전입니다. 64비트 애플리케이션에서 기능은 32비트 상대 점프 명령어를 통해 접근할 수 없을 만큼 메모리에서 멀리 떨어져 있을 수 있습니다. 64비트 상대 점프 명령어가 없기 때문에 이 프로그램은 먼저 메모리의 어느 곳에나 도달할 수 있는 절대 jmp 명령어에 대한 바이트를 포함하는 "릴레이" 함수를 생성합니다(그리고 페이로드 func로 점프합니다). 대상 기능에 설치되는 32비트 점프는 즉시 페이로드로 점프하는 대신 이 릴레이 기능으로 점프합니다.
자유 함수가 아닌 멤버 함수를 연결하기 위해 이전 프로젝트의 기술을 사용하는 예를 제공합니다.
이전 예제와 약간 다르게 이 프로그램은 개체의 vtable을 통해 해당 함수의 주소를 가져와 가상 멤버 함수에 후크를 설치하는 방법을 보여줍니다. 가상 함수를 다루는 다른 예제는 없지만 여기에 포함시킬 만큼 흥미롭다고 생각했습니다.
실행 중인 다른 프로세스에 후크를 설치하는 가장 간단한 예입니다. 이 예에서는 DbgHelp 라이브러리를 사용하여 문자열 이름으로 대상 프로세스(A - 무료 함수가 있는 대상)에서 함수를 찾습니다. 이는 대상 프로그램이 디버그 기호가 활성화된 상태로 구축되었기 때문에 가능합니다. 간단하지만 이 예제는 (원격 프로세스를 찾고 조작하기 위한) 새로운 기능이 많이 도입되었기 때문에 이전 프로그램보다 약간 깁니다.
이 예에서는 다른 프로세스가 dll에서 가져온 함수를 연결하는 방법을 보여줍니다. 여기에서 설명하는 ASLR 작동 방식으로 인해 원격 프로세스에서 dll 함수의 주소를 가져오는 방법에 약간의 차이가 있습니다. 그 외에는 이 예제는 이전 예제와 거의 동일합니다.
이 예에서는 dll에서 가져오지 않고 기호 테이블에도 없는(원격 프로세스에 디버그 기호가 없기 때문에) 함수에 후크를 설치하는 방법을 보여줍니다. 이는 문자열 이름으로 대상 함수를 찾는 (쉬운) 방법이 없음을 의미합니다. 대신, 이 예제에서는 후크하려는 기능의 상대 가상 주소(RVA)를 얻기 위해 x64dbg와 같은 디스어셈블러를 사용했다고 가정합니다. 이 프로그램은 해당 RVA를 사용하여 후크를 설치합니다.
위와 유사하지만 이 예제에서는 원시 기계 코드 바이트를 작성하는 대신 dll 주입을 사용하여 페이로드 기능을 설치합니다. 페이로드를 C++로 다시 작성할 수 있으므로 작업하기가 훨씬 쉽습니다. 이 예제의 페이로드는 프로젝트 08B-DLL-Payload에 포함되어 있습니다.
다음 예제에서는 후크할 때 트램펄린을 설치합니다. 즉, 후크가 설치된 후에도 프로그램이 대상 함수의 논리를 계속 실행할 수 있음을 의미합니다. 후크를 설치하면 대상 기능의 처음 5바이트 이상을 덮어쓰므로 이 5바이트에 포함된 명령은 트램폴린 기능으로 이동됩니다. 따라서 트램폴린 함수를 호출하면 대상 함수의 원래 논리가 효과적으로 실행됩니다.
예제 #2와 동일한 트램폴린 설치. 이 예제는 분해 엔진을 사용할 필요 없이 트램펄린을 만드는 방법을 보여주고 싶었기 때문에 약간 이상합니다. 이 경우 대상 함수는 처음에 알려진 5바이트 명령어를 갖도록 생성되었으므로 해당 함수의 처음 5바이트만 트램펄린 함수에 복사하면 됩니다. 이는 트램폴린을 만드는 것이 정말 쉽다는 것을 의미합니다. 왜냐하면 우리는 트램폴린의 정확한 크기를 알고 수정해야 할 상대 주소 지정을 사용하지 않는다는 것을 알고 있기 때문입니다. 정말 구체적인 사용 사례를 위한 트램폴린을 작성했다면 아마도 이것에 대한 변형만 수행해도 괜찮을 것입니다.
이 예제는 이전 예제와 비슷한 시나리오를 보여줍니다. 단, 이번에는 대상 함수에서 훔치는 데 필요한 바이트를 얻기 위해 디스어셈블러(캡스톤)를 사용합니다. 이를 통해 후킹 코드를 우리가 알고 있는 쉬운 경우뿐만 아니라 모든 기능에서 사용할 수 있습니다. 이 예제에서는 실제로 많은 일이 진행되고 있습니다. 왜냐하면 이전 예제와 같은 대상 후크에서 일반 후킹 기능을 구축하는 것으로 이동하기 때문입니다. 트램펄린은 상대 호출/점프를 절대 주소를 사용하는 명령어로 변환해야 하는데, 이는 상황을 더욱 복잡하게 만듭니다. 이는 일반 후킹의 100% 세련된 예도 아니며 루프 명령으로 인해 실패하고 5바이트 미만의 명령으로 함수를 후크하려고 하면 실패합니다.
기본적으로 위와 동일합니다. 단, 이 예제에는 후크를 설치하는 동안 모든 실행 스레드를 일시 중지하는 코드가 포함되어 있습니다. 모든 경우에 스레드 안전이 보장되는 것은 아니지만 아무것도 하지 않는 것보다 확실히 훨씬 더 안전합니다.
이는 이전 두 예제에서 사용된 후크/트램펄린 코드를 확장하여 여러 함수가 동일한 페이로드로 리디렉션되도록 지원하고 페이로드 함수가 후크가 설치된 다른 함수를 호출할 수 있도록 허용합니다.
이것은 다른 프로세스(이 경우 대상 앱 B - DLL의 무료 기능이 있는 대상)에 후크를 설치하는 첫 번째 트램폴린 예제입니다. 모든 후킹 로직은 dll 페이로드 13B - Trampoline Imported Func DLL 페이로드에 포함되어 있습니다. 여기에는 새로운 것이 별로 없습니다. 이 예는 이미 수행된 트램폴린 후킹 작업과 이전에 dll에서 가져온 함수를 후킹하기 위해 표시된 기술을 결합한 것뿐입니다.
레포의 왕관 보석. 이 예에서는 실행 중인 mspaint 인스턴스에 dll 페이로드(14B - Trampoline Hook MSPaint 페이로드)를 삽입합니다(이를 실행하기 전에 mspaint를 직접 시작해야 합니다). 설치된 후크로 인해 MSPaint에서 실제로 선택한 색상에 관계없이 브러시가 빨간색으로 그려집니다. 이전 예제에 표시되지 않은 것은 솔직히 아무것도 없습니다. 이것이 인위적이지 않은 프로그램에서 작동하는 것을 보는 것은 정말 멋진 일입니다.
루프에서 무료 함수를 호출하는 간단한 대상 애플리케이션입니다. 디버그 정보가 포함되어 컴파일됩니다.
루프의 dll(B2 - GetNum-DLL)에서 가져온 무료 함수를 호출하는 대상 애플리케이션입니다.
루프에서 가상이 아닌 멤버 함수를 호출하는 대상 애플리케이션입니다.
루프에서 가상 멤버 함수를 호출하는 대상 애플리케이션입니다.