틀린 부분이 있다면 언제든지 댓글 남겨주세요!
TEB / PEB를 통해 디버깅 탐지
1. PEB의 BeingDebugged Flag 확인
(IsDebuggerPresent API로 플래그 값 확인 가능 or PEB+0x02값 확인)
디버거가 동작중인 경우 1의 값을 가짐
- 우회: 플래그 값 변경, IsDebuggerPresent API 후킹 및 리턴값 0으로 변경
2. Heap메모리 영역의 특성 이용
(Heap메모리 영역에 접근해 디버깅시의 heap 특성 이용)
Heap메모리 영역는 사용되지 않는 부분을 0xFEEEFEEE 또는 0xABABABAB 값으로 채우는 특징을 가짐
따라서 heap메모리 영역을 도는 if을 생성하여 0xFEEEFEEE 또는 0xABABABAB값을 가지면 디버깅 중임을 알 수 있음
* 실행중인 프로세스에 디버거를 attach 시키면 heap메모리의 특성이 나타나지 않음
3. Heap 구조체의 값 이용
processheap+0x0C=Flags멤버, +0x10=Force Flags멤버는 디버깅 실행중 특정값으로 세팅됨
(정상 실행시 Flags는 0x02, Force Flags는 0x0)
디버깅 중일때는 각각 0x02, 0x0가 아닌 값
4. PEB의 NtGlobalFlags 확인 (PEB+0x68값 확인)
디버깅 중이면 ntdll의 heap 조작 루틴을 제어하는 플래그가 설정됨
디버거가 동작 중이면 0x70, 정상이면 0x00
- 우회: 플래그 값 변경
결과
#include <stdio.h>
#include <windows.h>
bool AntiDebugging_PEB(void) {
_TEB* TEB = NtCurrentTeb(); // 현재 스레드의 TEB 구조체 가져옴
UINT PEB = *(UINT*)((UINT)TEB + 0x30); // TEB 구조체를 이용해 PEB 구조체 가져옴
//printf("%d", IsDebuggerPresent());
return *(UINT*)(PEB + 0x02) & 1; // PEB beingdedugging의 값으로 디버깅 탐지
// PEB+0x02 = beingdebugging
// 디버깅 중=0x01, 정상 실행시=0x00
}
bool AntiDebugging_LDR(void) { // XP에서만
_TEB* TEB = NtCurrentTeb();
UINT PEB = *(UINT*)((UINT)TEB + 0x30);
UINT LDR = *(UINT*)(PEB + 0xC); // DLL 로딩 베이스 주소 =LDR
LDR = *(UINT*)(LDR + 0x10); // ?
for (int i = 0; i < 0x200; i++)
if (*(UINT*)(LDR + i) == 0xFEEEFEEE || *(UINT*)(LDR + i) == 0xABABABAB)
return true;
// Heap메모리 영역는 사용되지 않는 부분을 0xFEEEFEEE 또는 0xABABABAB 값으로 채움
// 실행중인 프로세스에 디버거를 attach 시키면 heap메모리의 특성이 나타나지 않음
// 따라서 해당 if문에 걸리면 디버깅 중임을 알 수 있음
return false; // 정상
}
bool AntiDebugging_ProHeap(void){ // XP에서만
_TEB* TEB = NtCurrentTeb();
UINT PEB = *(UINT*)((UINT)TEB + 0x30);
UINT Heap = *(UINT*)(PEB + 0x18); // processHeap = heap 구조체
// +0x0C=Flags멤버, +0x10=Force Flags멤버는 디버깅 실행중 특정값으로 세팅됨
// 정상 실행시 Flags는 0x02, Force Flags는 0x0
// 디버깅 중일때는 각각 0x02, 0x0가 아닌 값
UINT Flags[2] = { *(UINT*)(Heap + 0xC), *(UINT*)(Heap + 0x40) };
UINT Force_Flags[2] = { *(UINT*)(Heap + 0x10), *(UINT*)(Heap + 0x44) };
if (Flags[0] != 2 && Flags[1] != 2) return true;
if (Force_Flags[0] && Force_Flags[1]) return true;
return false; // 정상
}
bool AntiDebugging_NtGlobalFlag(void){
_TEB* TEB = NtCurrentTeb();
UINT PEB = *(UINT*)((UINT)TEB + 0x30);
UINT NtGlobalFlag = *(UINT*)(PEB + 0x68); // 디버깅 중 0x70으로 세팅
if (NtGlobalFlag == 0x70) return true;
return false;
// 이 값은 디버거에 프로그램을 attach시킨 경우에는 설정되지 않고
// 디버거로 프로그램을 여는 경우에만 설정됨
// +) 우회방법: 0x70 값을 0x00으로 바꿈
}
int main(void) {
printf("PEB check > ");
bool Debugged = AntiDebugging_PEB(); // 1. PEB beingdedugging의 값으로 디버깅 탐지
//printf("%d", Debugged);
if (Debugged == 1)
printf("Debugged Mode...\n");
else
printf("normal mode..\n");
printf("LDR check > ");
Debugged = AntiDebugging_LDR(); // 2. Heap 메모리 영역 특징으로 디버깅 탐지
if (Debugged == 1) printf("Debugged Mode...\n");
else printf("normal mode..\n");
printf("ProHeap check > ");
Debugged = AntiDebugging_ProHeap(); // 3. Heap 구조체 내의 Force Flags, Flags 값으로 디버깅 탐지
if (Debugged == 1) printf("Debugged Mode...\n");
else printf("normal mode..\n");
printf("NtGlobalFlag check > ");
Debugged = AntiDebugging_NtGlobalFlag(); // 4. NtGlobalFlag의 값으로 디버깅 탐지
if (Debugged == 1) printf("Debugged Mode...\n");
else printf("normal mode..\n");
}
4. EPROCESS 구조체의 DebugPort 확인
(NtQueryInformationProcess > ProcessInformation > ProcessDebugPort (0x07) 확인)
EPROCESS구조체에서 ProcessDebugPort가 7로 설정된 ProcessInformationClass와 함께 호출될 때 ntdll!NtQueryInformationProcess함수를 이용해 프로세스 정보 검색 가능
디버거가 동작 중이면 -1, 정상이면 0의 값을 가짐
- 우회: NtQueryInformationProcess 함수의 리턴값 0으로 변경
* 그 외 ProcessDebugObjectHandle(0x1E / 동작 중이면 특정 값, 정상이면 0을 가짐) ProcessDebugFlags(0x1F / 동작 중이면 0, 정상이면 1을 가짐)도 이용 가능
5. OutputDebugStringA 함수로 디버깅 정보 확인
Kernel32!OutputDebugStringA함수는 디버거에 출력할 문자열을 전달하는 함수
디버거가 동작 중이면 매개변수로 전달된 문자열을 주소 값을, 정상이면 1을 반환
- 우회: 해당 구간을 우회하거나 OutputDebugStringA함수의 리턴값 수정
6. 수행시간 검사
정상적인 프로세스 수행시간보다 디버거 동작 시의 수행시간이 길어진다는 특징이용
RDTSC(Real Time Stamp Counter)명령을 이용해 두 구간간 타임스탬프 값 비교
- 우회: 해당 구간을 우회하거나 GetTickCount 함수의 값 수정
중단점 탐지를 통해 디버깅 확인
1. 하드웨어 중단점
: 하드웨어 중단점은 DR0~DR3 디버그 레지스터 이용, 해당 메모리에 접근/수정/실행할 때 멈춤
- 윈도우의 경우 SEH핸들러를 이용해 하드웨어 디버그 레지스터 변경 가능
SEH핸들러에 하드웨어 중단점 탐지 루틴을 등록해, 예외상황 발생시 해당 SEH핸들러가 실행되어 contextRecord를 이용해 디버그 레지스터 값 변경 > 중단점 무력화
(이 루틴을 찾기 위해서는 F7/8/9보다 shift+F7/8/9 사용)
- 우회 방법: 하드웨어 중단점에서 예외를 발생시키는 코드를 NOP처리, 중단점 탐지 및 제거 코드 다음에 중단점 설정
#include <windows.h>
#include <stdio.h>
BOOL anti_debug() {
BOOL result = FALSE;
CONTEXT ct;
ct.ContextFlags = CONTEXT_DEBUG_REGISTERS;
GetThreadContext(GetCurrentThread(), &ct);
if(ct.Dr0 || ct.Dr1 || ct.Dr2 || ct.Dr3)
// DR0~DR3 레지스터값 중 1이 있으면 중단점 설정되어 있음
result = TRUE;
return result;
}
int main(int argc, char **argv) {
if(anti_debug())
printf("Debugger Detected\n");
else
printf("No Debugger...\n");
return 0;
}
2. 소프트웨어 중단점
: 소프트웨어 중단점은 명령어의 첫번째 바이트를 0xCC(INT3)로 변경하여 설정
- 보호하려는 코드의 처음 몇 바이트를 검사하여 0xCC 코드 확인 or 보호하려는 코드 전체의 체크섬 값을 계산하여 비교
- INT3 또는 INT1명령어를 지날 때 디버거에서는 보통 예외처리를 하지 않기 때문에 핸들러에 특정 값을 설정하는 루틴을 만들고 INT명령 이후에 특정 값이 설정되었는지 확인하는 방식으로 중단점 탐지
(값이 설정되지 않았으면 디버깅 실행중임을 추측할 수 있음)
#include <windows.h>
#include <excpt.h>
#include <stdio.h>
BOOL anti_debug_flag = TRUE;
void anti_debug(){
__try {
__asm {
//int 3
__emit 0xCC
}
}
__except(EXCEPTION_EXECUTE_HANDLER) // 예외 발생시 특정값 설정
anti_debug_flag = FALSE;
}
int main(int argc, char **argv) {
anti_debug();
if(anti_debug_flag)
printf("Debugger Detected\n");
else
printf("No Debugger...\n");
return 0;
}
'Security > Reversing' 카테고리의 다른 글
UAF 공격 (0) | 2021.11.06 |
---|---|
[CodeEngn] Challenges Basic RCE L20 (0) | 2021.10.18 |
[CodeEngn] Challenges Basic RCE L17, L18, L19 (0) | 2021.10.14 |
KeyLogger 분석 (0) | 2021.09.27 |
Debugger software BP의 원리 (0) | 2021.09.08 |
댓글