2016-09-28 4 views
0

나는 을 가지고 있는데,이 API는 대상에서 처리하는 명명 된 파이프 트래픽을보기 위해 Win32 대상 응용 프로그램 (ASIO를 사용함)으로 후킹합니다. ReadFile 또는 WriteFile 호출로 트랜잭션의 시작을 설정하고 호출이 동 기적으로 이루어지면 해당 호출의 결과를 가져옵니다. 호출이 비동기 인 경우, 호출의 끝을 GetQueuedCompletionStatus에 연결하여 트랩합니다. 검색된 데이터는 Wireshark에 명명 된 파이프 연결을 서비스하는 내 ASIO 스레드 풀로 보내집니다. 그것은 실제로 꽤 달콤합니다.차단 된 Win32 API를 안전하게 언 후크 해제 할 수 있습니까?

프로그램을 원래 상태로 되돌려 야 할 때를 제외하고 모든 것이 잘 작동합니다. 함수의 언 Hooking 잘 작동, 사실은 기존 응용 프로그램이 Named Pipe를 통해 많은 요청을 처리하도록 대상 응용 프로그램을 강제로 않는 한 GetQueuedCompletionStatus에서 무기한 차단 될 수 있습니다. 이러한 스레드가 차단 해제되기를 기다리지 않으면 GetQueuedCompletionStatus가 결국 차단 해제되고 더 이상 존재하지 않는 코드 동굴로 돌아갈 때 AV가 발생합니다.

내가 시도한 또 다른 것은 GetQueuedCompletionStatus에 대한 각 호출을 추적하고 GetQueuedCompletionStatus의 LpOverlapped 매개 변수가 PostQueuedCompletionStatus에 해당하는 호출과 일치 할 때마다 후킹 기능에 신호를 알리는 것이 었습니다. 이것은 모든 것을 확실히 해독 합니다만, AV를 발생시키는 GetQueuedCompletionStatus 호출을 만든 코드를 심각하게 망칠 것입니다.

누구든지 이것을 처리 할 수있는 좋은 방법을 알고 있습니까? 나는 다음 중 하나를 할 수있는 경우에, 이것은 작동합니다 :

  • 는 ASIO는
  • 트랩 PostQueuedCompletionStatus 만든 더미 호출을 무시하고
  • 만들기 스택에 리턴 값을 덮어 쓸 것 PostQueuedCompletionStatus로 전화 걸기 후킹 호출에 대한 함수 포인터 접근자를 전달하는 후킹 차단 호출을위한 트램펄린을 포함하는 반영구 코드 동굴. trampoline 함수는이 값이 null이 아닌 경우 대신 호출자에게 반환 실행 대신 해당 함수 포인터를 호출한다는 것을 알 수 있습니다. 디스패치를 ​​해제 할 때 hooking 코드는 그 함수 포인터를 GetQueuedCompletionStatus의 주소로 설정할 수 있습니다. 그러면 호출자에게 적절한 호출 결과를 반환 할 수 있습니다.

다른 옵션과 함께 사용하기 위해이 응용 프로그램을 일반화 할 수 있다는 점을 제외하면 첫 번째 옵션은 쉽습니다. 두 번째 방법은 코드를 작성하여 가능한 한 안전하도록하려는 경우를 제외하고는 쉽습니다. 마지막 코드가 가능할 수도 있습니다. 코드 동굴로 외부 메모리 공간을 오염시키고 싶지 않습니다.

+0

후크 기능 자체가 푸시 될 수 있습니까? 그렇다면, 입력 횟수를 추적 할 수 있고, 글로벌 "unhook"플래그 또는 신호에 대한 응답으로 입구 카운트가 0이 될 때 자체를 풀 수 있습니다. 완전히 스레드 안전성을 확보하기가 어려울 것입니다 그래도. –

+1

OP : 먼저 제목과 두 번째 단락이 잘못되어 오해의 소지가 있습니다. 발걸음을 잡지 않는 데 문제가 없습니다. 귀하의 질문은 "DLL을 함수로 사용하여 DLL을 언로드하는 것이 안전한지를 어떻게 결정할 수 있습니까?"또는 이와 비슷한 형식 일 수 있습니다. 편집하십시오. 현재 막혀있는 함수를 언 후크하려면 그냥 후크를 푸십시오. 이 문제에 대한 가장 간단하고 안전한 솔루션은 DLL을로드 된 상태로 유지하는 것일 수 있습니다. – conio

+0

@conio : 그건 공정한 일입니다. –

답변

1

단순히로드 된 DLL을 유지하는 것 외에는 일반적인 안전 솔루션이 존재하지 않습니다. 아니면 처음부터 모든 후크 자제는 ... (주요 스포일러에 대한 대답의 끝으로 건너!)


인수 내가 qeustion에 코멘트에 쓴의 라인을 따라입니다 .

DLL을 비우려면 현재 아무도 DLL에서 코드를 실행하고 있지 않은지, 아무도 DLL에서 코드를 실행하지 않을 것임을 확인해야합니다 (우리가 논의중인 슬리 이드 스레드 외에).

두 가지 이유로 DLL에서 코드가 실행 중일 수 있습니다. 우리는 DLL을 호출하려고하거나 DLL로 돌아갈 코드로 돌아갑니다.). 첫 번째 문제는 푸시 해제로 해결할 수 있습니다. 우리는 여전히 두 번째 원인이 남아 있습니다. 어쩌면 우리는 이미 내부에있어.

  1. JMP [PreHookWriteFile]

  2. PreHookWriteFile: 
    LOCK INC [ref_count] 
    POP R15 
    CALL HookWriteFile 
    PostHookWriteFile: 
    LOCK DEC [ref_count] 
    JMP R15 
    
  3. 훅의 WriteFile 같은 릴리스를 수행 않는 "코드 동굴"을 할당 :

    는이 같은 일을 생각할 수 WriteFile을 unhook하고 recount가 0이 될 때까지 기다리는 전용 스레드에서. 그런 다음 코드 동굴을 할당 해제합니다.

그러나 두 가지 문제점이 있습니다. 첫째, 원래 반환 주소를 저장할 장소가 없습니다. HookWriteFile은 그곳을 볼 것으로 예상하지 않기 때문에 스택에 놓을 수는 없으며 점프하기 전에 PostHookWriteFile에 복원해야하기 때문에 비 휘발성 레지스터에 저장할 수는 없지만 문제는 우리가 물건을 저장할 장소가 없다는 것입니다.

둘째로 릴리스 스레드는 점프가 발생하기 전에 감소를 알아 차리고 코드 동굴을 조기에 릴리스 할 수 있습니다.

우리는 (__declspec(naked) 함수를 사용하거나 프로토 타입을 수정하여) 후크 함수가 스택의 원래 반환 주소를 기대하는 것이 얼마나 영리합니까? 근본적인 문제가 남아 있습니다 - 귀하가 돌아 오는 전에 참조 번호를 으로 줄입니다. 반환하기 전에 코드를 삭제하는 것은 안전하지 않지만, 반환 한 후에는 더 이상 제어 할 수 없으므로 신고 할 수 없습니다. 그게 바로 FreeLibraryAndExitThread이 만들어진 이유입니다.

DLL에있는 모든 스레드를 일시 중지하고 DLL에서 코드를 확인하기 위해 스택을 걷는 것 같은 미친 생각이들 수도 있습니다.하지만 이는 다른 웜 깡통을 여는 것입니다.


하지만 좋은 소식도 있습니다.

명명 된 파이프 통신을 모니터링하고 Windows 8 이상 버전을 사용하려는 경우 훨씬 강력하고 문서화되며 지원되는 솔루션이 있으며 코드 줄이 훨씬 적습니다. 방해하지 마라. Koby Kahanefilesystem mini-filter that does exactly that이라고 썼습니다.

Microsoft Message Analyzer 또는 매니페스트 기반 공급자의 ETW 이벤트를 사용하는 다른 도구 중 하나에서 볼 수있는 ETW 이벤트가 출력됩니다. Wireshark에서 실제로 이것을 볼 필요가 있다면 이벤트를 소비하는 작은 ETW 소비자를 작성하여 파이프를 통해 다시 Wireshark로 보낼 수 있습니다. 그 모든 갈고리보다 여전히 쉬우 며, 확실히 안전하고 훨씬 덜 혼란 스럽습니다.

+0

이 캡처 DLL이하는 일 중 하나는 명명 된 파이프 연결을 통해 Wireshark로 데이터를 더미 TCP 대화로 보냅니다. 이것은 내가 프로토콜을 해부하기 위해 Wireshark 플러그인을 가지고 있기 때문에 내가 필요한 것이다. Microsoft Message Analyzer를 원시 데이터로 변환 할 수 있다면 변환기를 pcap 형식으로 작성할 수 있다고 생각합니다. 제안 해 주셔서 감사합니다. –

+0

@HarryJohnston : ** 단지 ** InterlockedDecrement'로 바로 이동할 수 없습니다. OP는 Win32 API 함수를 후킹하고 있으며 그 함수는 stdcall입니다. 스택의 반환 주소를 변경하는 것은 좋지만 반환하는 동안 스택을 정리하는 것은 어떨까요? 예를 들어'InterlockedDecrement'는'RETN 4'로 끝나고'CreateFileW'는'RETN 1C'로 끝납니다. (또한 ** 여분의 ** 호출 프레임이 없습니다. ** 코드는 ** 리턴 주소를 대체하고 후크로 점프합니다. 그러나 'MOV' 명령어의 일부로 간접 액세스를 위해 ESP를 사용할 수 없기 때문에, 'PUSH'와'JMP'가'CALL'에 압축 된'JMP'와'POP','PUSH','JMP'를하고 있습니다.) – conio

+0

두번째 생각에서는, 나가는 쓰레드에 대해 뭔가를 할 수도 있지만 , 스레드 중 하나가 이미 코드 동굴로 이동했지만 아직 카운터를 증가시키지 않았을 가능성을 다루는 합리적인 방법은 없다고 생각합니다. 제로 그래, 그것이 계속되는 동안 재미있는 생각 실험이었다! –