011 Anti Debugging
I. Anti-Debugging
: 디버깅을 방해하는 기술, 디버거와 OS에 따라 기법이 달라짐(높은 의존성)
i. Static vs. Dynamic
> Static Anti-Debugging
: 디버깅 시작할 때 한 번만 해체하면 해결되는 기법
: 디버거 탐지, 디버깅 환경 탐지 → 디버거에서 실행 x, 종료 코드 등 실행, 디버거 강제 분리
: String in Process Virtual Memory, Kernel Mode Driver, System Environment Targeting, OutputDebugString(), Memory Break Point, Filename Format String, ESI Value, (Guard Page - Memory Break Point) 사용
→ 회피 방법 : 탐지 코드가 얻는 정보 변경
> Dynamic Anti-Debugging
: 트레이싱하며 만날 때마다 해결하는 기법
: 디버거에서 트레이싱하여 원본 프로그램의 코드와 데이터 확인
: API Redirection, Guard Page, Virtual machine(자체 구현) 사용
II. Static Anti-Debugging
i. PEB
> IsDebuggerPresent() - PEB.BeingDebugged 멤버의 값이 1(True)인 경우
> PEB.Ldr에서 힙 메모리의 사용되지 않는 영역이 0xFEEEFEEE로 채워짐
> HAEP 구조체의 Flags 멤버와 Force Flags 멤버가 특정한 값으로 세팅
→ PEB.ProcessHeap 멤버
→ GetProcessHeap()
> PEB.NtGlobalFlag가 0x70으로 세팅
ii. NtQueryInformationProcess()
: PROCESSINFOCLASS ProcessInformationClass에 원하는 정보 형식을 입력한 후 호출하면 PVOID ProcessInformation에 해당 정보 세팅
> ProcessDebugPort : 파라미터에 값을 입력하면 Debug Port 얻을 수 있음, dwDebugPort에 디버깅 중이라면 0xFFFFFFFF, 아니라면 0이 세팅
→ CheckRemoteDebuggerPresent( ) : 현재 프로세스와 다른 프로세스의 디버깅 여부 판단
> ProcessDebugObjectHandle : 프로젝트가 디버깅될 때 Debug Object가 생성되기 때문에, 입력했을 때 프로세스가 디버깅 중이라면 Debug Object Handle이 존재하고, 아니라면 NULL이다.
> ProcessDebugFlags : 입력하여 디버깅 중이면 Debug Flags 값이 0(False), 아니면 1(True)
iii. NtQuerySystemInformation()
: SystemInformationClass 파라미터에 원하는 시스템 정보를 입력하고 SystemInformation 파라미터에 관련 구조체 주소를 넘겨주면 API가 리턴하며 그 구조체에 관련 정보를 채워줌
> SystemKerneIDebuggerlnformation
: SystemInformationClass에 SystemKernelDebuggerInformation 값을 입력하고 Systemlnformtion에는 SYSTEM_KERNEL_DEBUGGER_INFORMAION 구조체 주소 입력하면, API가 리턴되면서 디버깅 중일 경우 SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled에 1(True), 아닐 경우 0(False) 세팅 (SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerNotPresent는 항상 1(True)로 세팅)
iv. NtQueryObject()
: 시스템에서 디버거가 다른 프로세스를 디버깅 중일 때 DebugObject 타입의 커널 객체가 생성되는데, 이 존재를 확인
→ ObjectInformationClass에 원하는 값을 입력한 후 API 호출하면 Object Information에 관련 정보의 구조체 포인터 리턴
v. ZwSetInformationThread()
: 강제 디버거 detach하는 API
→ ThreadHandle에 현재 스레드의 핸들, ThreadInformationClass에 ThreadHideFromDebugger 값을 입력하면 디버거 프로세스 분리(자신 프로세스도 같이 종료)
vi. TLS Callback Function
: Entry Point 코드보다 먼저 실행되어 IsDebuggerPresent() 등의 함수로 디버깅 여부를 판단하여 프로그램을 계속 실행할 지 말 지 결정
III. Dynamic Anti-Debugging
i. Exception
: 정상 실행된 프로세스에서의 발생 경우 SEH 매커니즘에 의해 OS에서 예외를 받아 프로세스에 등록된 SEH를 호출하지만, 디버기에서 예외 발생 경우 디버거에서 예외 처리를 하는 차이를 이용
> SEH
→ EXCEPTION_BREAKPOINT : BP를 이용하여 예외를 발생시켰을 때 등록된 SEH 실행이 아닌 디버깅 실행의 경우 실행이 멈추고 제어권이 넘어오므로 예외 처리기 따라 들어가야 함
> SetUnhandledExceptionFilter : 프로세스 예외 발생 시에 SEH에서 처리되지 않거나 등록된 SEH가 없다면 시스템의 kemel32!UnhandledExceptionFilter()가 호출되고 이 함수 내부에서 Top Level Exception Filter(or Last Exception Filter)라는 시스템의 마지막 예외 처리기 실행
ii. Timing Check
: 디버거를 이용했을 때 증가하는 실행 시간 측정
iii. Trap Flag
: RFLAGS[8] 비트
: 1(True)로 설정할 경우 CPU가 Single Step 모드로 변경하면 하나의 명령어를 실행시킨 후 EXCEPTION_SINGLE_STEP 예외 발생(이 경우 TF는 자동으로 0으로 초기화)
: INT 2D 명령어는 커널 모드에서 작동하는 BP 예외를 발생시키지만 유저 모드에서도 예외를 발생시키는데, 디버깅 실행의 경우 무시하고 넘어감
iv. 0xCC Detection
: BP의 x86 Instruction이 0xCC이므로 이 값을 탐지하거나 Checksum 값 변화 비교
IV. 고급 안티 디버깅
i. Garbage Code
: 의미 없는 코드를 대량으로 추가
ii. Breaking Code Alignment
: 디스어셈블러를 혼란시키기 위해 의도적으로 추가시킨 코드
→ StepInto(F7) 명령으로 이동하면 정상 코드 보임
iii. Encryption/Decryption
: 복호화해야 정상 코드 나타남, 코드 재생성
iv. Stolen Bytes(Remove OEP)
: 원본 코드의 일부(주로 OEP 코드)를 패커/프로텍터가 생성한 메모리 영역으로 옮김
v. API Redirection
: 주요 API들의 코드 전체 혹은 일부를 다른 메모리 영역으로 복사한 후 해당 API를 호출하는 코드를 패치시켜 원본 API 주소에 BP를 설치하여도 복사해둔 API가 실행되어 소용이 없게 만듦
vi. Debug Blocker(Self Debugging)
: 스스로 디버깅 모드로 실행하여 다른 디버거가 Attach될 수 없게 하고 디버기 프로세스를 제어