2016-08-24 6 views
0

한 가지만하는 프록시 DLL 라이브러리를 사용하여 장치에 직접 작성하는 소스 코드가없는 응용 프로그램이 있는데 내 보낸 함수 "GetDataPointer" . 이 라이브러리를 대체하고 각 수신 메모리에 대한 쓰기 간 변경 사항을보고 싶습니다. 메모리 크기는 페이지 크기 (4096)의 배수입니다. 내가이 방법을 시도했습니다보호 된 페이지의 사용을 모니터링하기 위해 구조화 된 예외 처리 (SEH) 사용하기

: DllMain에서

  1. 은 데이터를 비교, 페이지 가드를 제거 VirtualAlloc를 통해 GetDataPointer의 ALLOC 메모리에 SetUnhandledExceptionFilter
  2. 를 통해 예외 핸들러를 설정 예외 처리기에서 가드 페이지
  3. 설정 이전 복사본으로 페이지를 보호하십시오.

이 응용 프로그램은이 메모리 포인터에 쓰는 스레드를 사용합니다. 응용 프로그램이이 메모리 포인터에 주 스레드에서 세 번 씁니다.이 포인터에 세 번 쓰는 다른 스레드를 생성합니다. 스레드가 없어도 스레드가 있으면 페이지 보호 기능이 누락되어 예외를 발생시키지 않고 메모리 포인터에 직접 씁니다. 따라서 예외 처리기는 메모리에 6 번 쓰기를 캡처해야하지만 페이지 보호 기능이 없기 때문에 그 수가 더 적습니다. 안타깝게도 필자는이 방법으로해야하며 100 %의 정확도가 필요합니다. 왜냐하면 현대 시스템에서이 애플리케이션을 사용하기 위해 더 모호한 장치 (그래픽 카드)를위한 모호한 API를위한 래퍼를 만들 계획이기 때문입니다.

전역 임계 영역을 사용하고 연결된 각 스레드에서 SEH에 대한 예외 처리기를 설정하려고했지만 여전히 문제가 남아 있습니다.

이 문제를 해결할 가능성이 있습니까? 아니면 이것을 구현하는 더 좋은 방법이 있습니까? 어떤 사람이 어떻게 작동하는지 설명하는 코드가 필요하면 작업 환경 (응용 프로그램 및 라이브러리)을 시뮬레이트하는 코드를 준비 할 수 있습니다.

미리 감사드립니다. 잠금 부분없이

업데이트

소스 코드 (의사)은 다음과 같습니다 (이 게시물에 대한 서둘러 작성, 완료 할 수 있습니다) :

응용 프로그램 :

extern "C" __declspec(dllimport) void* GetDataPointer(int size); 
extern "C" __declspec(dllimport) int GetCallCount(); 

#define THREAD_COUNT 32 

DWORD WINAPI ThreadEntryPoint(void* param) { 
unsigned int* data = (unsigned int*)param; 

data[0] = 0xEEFF0011; 

return 0; 
} 

int main(int argc, char* argv[]) { 
unsigned int* data = (unsigned int*)GetDataPointer(); 

std::vector<HANDLE> threads; 

data[0] = 0xAABBCCDD; 

for(int i = 0; i<THREAD_COUNT; i++) { 
    DWORD threadID; 
    HANDLE hThread = CreateThread(NULL,0,ThreadEntryPoint,(void*)data,0,&threadID); 
    threads.push_back(hThread); 
} 

for(int i = 0; i<threads.size(); i++) 
    WaitForSingleObject(threads.at(i),INFINITE); 

printf("Should be %i calls, was %i.\n",(THREAD_COUNT+1),GetCallCount()); 
return 0; 
} 

도서관 :

#define DATA_SIZE 4096 

void* data; 
int callCount; 

LONG WINAPI ExceptionHandler(LPEXCEPTION_POINTERS ExceptionInfo) { 
LONG ret = EXCEPTION_CONTINUE_SEARCH; 

switch(ExceptionInfo->ExceptionRecord->ExceptionCode) { 
    case STATUS_GUARD_PAGE_VIOLATION: { 
    ExceptionInfo->ContextRecord->EFlags |= 0x100; 
    ret = EXCEPTION_CONTINUE_EXECUTION; 
    break; 
    } 

    case EXCEPTION_SINGLE_STEP: { 
    DWORD old; 

    callCount++; 

    VirtualProtect(data,DATA_SIZE,PAGE_READWRITE,&old); 
    // Find what was changed in data... 
    VirtualProtect(data,DATA_SIZE,PAGE_GUARD | PAGE_READWRITE,&old); 

    ret = EXCEPTION_CONTINUE_EXECUTION; 
    break; 
    } 
} 

return ret; 
} 

extern "C" void* __declspec(dllexport) GetDataPointer() { 
data = (void*)VirtualAlloc((PVOID)data,DATA_SIZE,MEM_RESERVE | MEM_COMMIT,PAGE_READWRITE); 
memset(data,0,DATA_SIZE); 

DWORD old; 
VirtualProtect(data,DATA_SIZE,PAGE_GUARD | PAGE_READWRITE,&old); 

return data; 
} 

extern "C" int __declspec(dllexport) GetCallCount() { 
return callCount; 
} 

extern "C" BOOL APIENTRY DllMain(HINSTANCE hinstDLL, DWORD fdwReason, LPVOID lpvReserved) { 
switch(fdwReason) { 
    case DLL_PROCESS_ATTACH: { 
    data = NULL; 
    callCount = 0; 

    SetUnhandledExceptionFilter(ExceptionHandler); 

    break; 
    } 

    case DLL_PROCESS_DETACH: { 
    if(data) 
    VirtualFree(data,data_size,MEM_RELEASE | MEM_DECOMMIT); 

    break; 
    } 

    case DLL_THREAD_ATTACH: { 
    SetUnhandledExceptionFilter(ExceptionHandler); 

    break; 
    } 

    case DLL_THREAD_DETACH: { 
    break; 
    } 
} 

return TRUE; 
} 

이것이 더 명확 해 지길 바랍니다.

+1

올바르게 작동하려면 모든 단일 쓰기에 예외가 필요합니까 ?? 다른 것을하기 전에이 코드를 프로파일하십시오. –

+0

@ hans-passant 예, 올바르게 작동하려면 모든 단일 메모리 쓰기를 알아야합니다. 메모리 덩어리는 구조체로 해석 될 수 있으며 변경된 요소를 알아야합니다. 예를 들어, 첫 번째 쓰기는 4 개의 정적 오프셋 중 하나에서 작성된 명령이 될 것이고 다음에 잡힌 쓰기는 정적 오프셋이 아닌 params가 될 것이므로 변경된 내용과 그 값이 무엇인지 알아야합니다. 더구나 명령에 대한 매개 변수는 한 번에 두 번 같은 오프셋으로 겹쳐 쓸 수 있습니다. –

+0

SetUnhandledExceptionFilter에 대한 문서를 읽는 방법은 잡히지 않은 예외 만 트래핑하는 것입니다. 따라서 응용 프로그램의 스레드가 먼저 예외를 포착하면이를 볼 수 없습니다. – Brad

답변

-1

은 기본 응용 프로그램을 디버그하는 조언 미니 디버거를 제공 할 수 있습니다. 디버거가 스레드에서 예외를 처리하는 동안 프로세스의 다른 모든 스레드가 일시 중단됩니다. 이것이 핵심 포인트입니다. STATUS_GUARD_PAGE_VIOLATION을 처리 할 때 현재 스레드 디버거에서 모든 스레드의 목록을 유지 관리하는 데 문제가없는 경우를 제외하고 모든 스레드에 대해 SuspendThread를 호출해야합니다. 또는 현재 스레드에 대한 대체 호출 ZwSuspendProcess 및 ZwResumeThread. 현재 스레드에 TF 플래그 (0x100)를 설정하고 DBG_CONTINUE를 설정하십시오. 그런 다음 STATUS_SINGLE_STEP 예외 (또는 STATUS_WX86_SINGLE_STEP)가 발생하면 현재를 제외한 모든 스레드에 대해 PAGE_GUARD 및 ResumeThread를 복원합니다. (또는 단순히 ZwResumeProcess를 호출하십시오).이것은 물론 단순한 작업이 될 수는 없지만 추적 메모리 참조를 놓치지 않도록 100 % 보장합니다.