티스토리 뷰

Break,Break BreakPoint!!

1)Software Breakpoint
softice에서 bpx로 쓰인다.
Opcode로는 0xCC의 1바이트로 이루어져 있다.
재밌는 점은 이게 함수로도 존재한다는 점인데,
무엇을 위한 것인지는 모르겠지만 존재한다.
과연 DebugBreak()라는 함수는 대체 무슨 코드를 가지고
있는가? 라는 호기심에 Disassemble해보니 다음과 같이 나온다.
7C931230 > CC INT3
7C931231 C3 RETN

정말 간단하다 -_-; 그냥 INT3 호출 해주고 나오는 것이었다..

MSDN에는 다음과 같이 나와 있다.

void DebugBreak(void);
Causes a breakpoint exception to occur in the current process. This allows the calling thread to signal the debugger to handle the exception.

To cause a breakpoint exception in another process, use the DebugBreakProcess function

오호 :)
재미난 걸 볼 수 있다.
DebugBreak()는 해당 프로세스 내에서 브레이크 포인트를 발생
시키기 위한 것인데, DebugBreakProcess()라는 API는
다른 프로세스에서 브레이크 포인트를 발생시켜 준다고 한다.

INT3 이 실행되면 IDT에 지정되어 있는 Interrupt3 Handler가
호출 된다.

본 시스템에는 다음과 같이 지정되어 있었다.
[본 화면은 여리님의 kc를 이용하여 얻었다. :)]

--------------------------------------------------------
vector number__________ : 3 (0x03)
- type________________ : INTERRUPT GATE
- description_________ : Breakpoint Exception
- DPL_________________ : 3
- segment_____________ : 0x0008
- offset______________ : 0x804E189D
+ module information__
- name_____________ : ntoskrnl.exe
- base address_____ : 0x804D9000
- path_____________ : \WINDOWS\system32\ntoskrnl.exe
----------------------------------------------------

DPL이 3으로 지정되어 있음으로 Usermode Process
에서도 호출이 가능한 인터럽트 이며,
처리는 0x804E189D에서 하고 있음을 볼 수 있다.
Disassemblme 해보면,
-----------------------------------------------------
804E189D | 6A 00 | PUSH 0
804E189F | 66:C74424 02 0000 | MOV WORD PTR [ESP+2], 0
804E18A6 | 55 | PUSH EBP
804E18A7 | 53 | PUSH EBX
804E18A8 | 56 | PUSH ESI
804E18A9 | 57 | PUSH EDI
804E18AA | 0FA0 | PUSH FS
804E18AC | BB 30000000 | MOV EBX, 30
804E18B1 | 8EE3 | MOV FS, BX
...................
------------------------------------------------------
저 코드의 내용은 MSDN에서 찾았던 DebugBreak()의
Remarks가 설명해 주고 있다.
If the process is not being debugged, the function uses the search logic of a standard exception handler. In most cases, this causes the calling process to terminate because of an unhandled breakpoint exception.

2)Hardware Breakpoint
softice에서 bpm이라는 명령어로 알려져 있다.
Hardware Breakpoint는 CPU에 있는 Debug Register를 이용하여
브레이크 포인트를 거는 방법이다.
Software Breakpoint는 실행을 잡아낼 수 밖에 없지만,
Hardware Breakpoint는 실행/읽기/쓰기를 잡아내는 것이 가능하다.
하지만 Hardware Breakpoint를 사용할떄에, Breakpoint걸 주소를
지정할 수 있는 공간은 DR0,DR1,DR2,DR3 이렇게 4개 밖에 없다.
Hardware Breakpoint의 제한은 4개 인것이다.

Hardware Breakpoint를 지정하는 순서는 다음과 같다.
(DR0을 사용한다고 가정했다.)
1.
DR0에 다음과 같은 방법으로 주소를 넣는다.
__asm
{
mov eax,Break_Address
mov DR0,eax
}


2.
DR7에 Breakpoint를 처리할 방법을 지정해주어야 한다.
DR7을 C언어 구조체로 표기해보면 다음과 같다.

typedef struct tagDebugReg7
{
unsigned L0 :1; //
unsigned G0 :1; //
unsigned L1 :1; //
unsigned G1 :1; //
unsigned L2 :1; //
unsigned G2 :1; //
unsigned L3 :1; //
unsigned G3 :1; //
unsigned GL :1; //
unsigned GE :1; //
unsigned undefined1 :3; // 001
unsigned GD :1; //
unsigned undefined2 :2; // 00
unsigned RW0 :2;
unsigned LEN0 :2;
unsigned RW1 :2;
unsigned LEN1 :2;
unsigned RW2 :2;
unsigned LEN2 :2;
unsigned RW3 :2;
unsigned LEN3 :2;
} DebugReg7;


L0 : 특정 Task만을 목적으로 Breakpoint를 지정할떄 set한다.
Task전환이 일어나면 모든 L비트는 0으로 reset된다.
G0 : 모든 Task를 대상으로 Breakpoint를 지정할떄 set한다.

GD : 이 비트가 set되어 있으면 DebugRegister에 접근이
발생하였을떄, 인터럽트 1번이 발생한다.
인터럽트1번 발생 후 GD비트는 0으로 reset된다.

RW0 : 브레이크 포인트 레지스터의 발생 조건을 지정한다.
00 : 명령이 실행(excute) 되었을 떄 인터럽트1 발생.
01 : 데이터가 기록(write)될 떄 인터럽트 1 발생.
10 : Do not use
11 : 읽거나(read) 쓸 때(write) 인터럽트 1 발생.

LEN0 : 브레이크 포인트의 인식 범위를 지정한다.
00 : 1바이트 = BYTE
01 : 2바이트 = WORD
10 : Do not use
11 : 4바이트 = DWORD

DR0에 있는 주소에 RW0과 LEN0에 만족하는 접근이 발생한다면,
인터럽트 1번이 호출되고, 해당 발생 요인이 DR6에 저장되어 있다.
DR6를 C언어 구조체로 표기해보면 다음과 같다.

typedef struct DebugReg6
{
unsigned B0 :1;
unsigned B1 :1;
unsigned B2 :1;
unsigned B3 :1;
unsigned undefined1 :9; // 011111111
unsigned BD :1;
unsigned BS :1;
unsigned BT :1;
unsigned undefined2 :16; // 1111111111111111
} DebugReg6;


B0 : Breakpoint,R/W,LEN이 충족되어서 발생한 인터럽트 1의
경우 이 비트가 1로 set된다.
BD : GD 비트가 1로 설정되어 있는 상태에서 Debug Register에
접근이 발생하면 이 비트가 1로 set된다.
BS : Flag Register의 TF비트가 1로 설정되어 있을 떄
이 비트가 1로 set된다.
BT : Task Switching이 발생했을 떄 새로운 TSS의 T비트가
1로 set되어 있다면 BT비트가 1로 set된다.
(새로운 태스크의 명령을 실행하기 전에 인터럽트1이 발생한다.)

어떻게 하면 우리가 직접 Hardware Breakpoint를 다루어
줄수 있는가?
Interrupt1을 Hooking함으로써 가능하다.

1. Hooking하여 연결할 함수를 만든다.


_declspec( naked ) void interrupt1( void )
{
__asm{

cmp [DebuggedProcessID],0 //디버깅 중인가?
jz Original //아니라면 기본 핸들러 호출

PUSHAD //32
push ds //4
push es //4
push gs //4
push fs //4

mov ax,0x23
mov ds,ax
mov es,ax
mov gs,ax
mov ax,0x30
mov fs,ax

mov eax,esp
add eax,48
push eax //Process의 스택주소를 넣는다.
CALL PesudoINT1Handler //
cmp eax,1 //리턴 벨류가 1이면 기본 핸들러를 호출하지 않는다.
je Exit
pop fs
pop gs
pop es
pop ds
POPAD
Original:
JMP [Int1Address]

Exit:
pop fs
pop gs
pop es
pop ds
POPAD
IRETD
};

}


PesudoINTHandler의 코드는 다음과 같다.

ULONG __stdcall PesudoINT1Handler(IN PULONG Stacklocation )
{
ULONG result=2; //2일떈 기본 핸들러가 호출되지 않는다.

if ((ULONG)PsGetCurrentProcessId()==DebuggedProcessID)
{ //지금 이 프로세스가 디버깅 당하는 대상 프로세스인가?
ULONG DR_0,DR_1,DR_2,DR_3;
DebugReg6 DR_6;
DebugReg7 DR_7;

DbgPrint("Welcome to my INT1 Handler");
__asm{
mov eax,dr0
mov DR_0,eax
mov eax,dr1
mov DR_1,eax
mov eax,dr2
mov DR_2,eax
mov eax,dr3
mov DR_3,eax
mov eax,dr6
mov DR_6,eax
mov eax,dr7
mov DR_7,eax
};

//레지스터들의 내용 출력

DbgPrint("Hello from int1\n");
DbgPrint("eax=%x\n",Stacklocation[-1]);
DbgPrint("ebx=%x\n",Stacklocation[-4]);
DbgPrint("ecx=%x\n",Stacklocation[-2]);
DbgPrint("edx=%x\n",Stacklocation[-3]);
DbgPrint("esi=%x\n",Stacklocation[-7]);
DbgPrint("edi=%x\n",Stacklocation[-8]);
DbgPrint("ebp=%x\n",Stacklocation[-6]);
DbgPrint("esp=%x\n",Stacklocation[3]);
DbgPrint("eip=%x\n",Stacklocation[0]);

DbgPrint("DR0=%x\n",DR_0);
DbgPrint("DR1=%x\n",DR_1);
DbgPrint("DR2=%x\n",DR_2);
DbgPrint("DR3=%x\n",DR_3);
DbgPrint("DR6=%x\n",DR_6);
DbgPrint("DR7=%x\n",DR_7);


DbgPrint("DR_7.L3=%d\nDR_6.B3=%d\nDR_3=%x\n",DR_7.L3,DR_6.B3,DR_3);

DbgPrint("-3=%x\n",Stacklocation[-1]);
DbgPrint("-2=%x\n",Stacklocation[-1]);
DbgPrint("-1=%x\n",Stacklocation[-1]);
DbgPrint("0=%x\n",Stacklocation[0]);
DbgPrint("1=%x\n",Stacklocation[1]);
DbgPrint("2=%x\n",Stacklocation[2]);
DbgPrint("3=%x\n",Stacklocation[3]);
DbgPrint("4=%x\n",Stacklocation[4]);
DbgPrint("5=%x\n",Stacklocation[5]);
DbgPrint("6=%x\n",Stacklocation[6]);
DbgPrint("7=%x\n",Stacklocation[7]);
DbgPrint("8=%x\n",Stacklocation[8]);
DbgPrint("9=%x\n",Stacklocation[9]);
DbgPrint("10=%x\n",Stacklocation[10]);
DbgPrint("11=%x\n",Stacklocation[11]);
DbgPrint("12=%x\n",Stacklocation[12]);

if (
((DR_7.L0) && (DR_6.B0) && (DR_0>=DebuggedAddress) && (DR_0<=DebuggedAddress+DebuggedAddressLength)) ||
((DR_7.L1) && (DR_6.B1) && (DR_1>=DebuggedAddress) && (DR_1<=DebuggedAddress+DebuggedAddressLength)) ||
((DR_7.L2) && (DR_6.B2) && (DR_2>=DebuggedAddress) && (DR_2<=DebuggedAddress+DebuggedAddressLength)) ||
((DR_7.L3) && (DR_6.B3) && (DR_3>=DebuggedAddress) && (DR_3<=DebuggedAddress+DebuggedAddressLength))
)

{



//DbgPrint("Handled by my self\n");
result=1;

DR_6.B0=0;
DR_6.B1=0;
DR_6.B2=0;
DR_6.B3=0;

__asm
{
mov eax,DR_6
mov dr6,eax
}

if (BufferSize<50)
{
int spot;
spot=BufferSize;
BufferSize++;
DebugEvents[spot].EAX=Stacklocation[-1];
DebugEvents[spot].EBX=Stacklocation[-4];
DebugEvents[spot].ECX=Stacklocation[-2];
DebugEvents[spot].EDX=Stacklocation[-3];
DebugEvents[spot].ESI=Stacklocation[-7];
DebugEvents[spot].EDI=Stacklocation[-8];
DebugEvents[spot].EBP=Stacklocation[-6];
DebugEvents[spot].ESP=Stacklocation[3];
DebugEvents[spot].EIP=Stacklocation[0];
}



}
else
{
DbgPrint("Unexpected!!!\n");
}

if (DR_6.BS)
{
single step
}

if (DR_6.BD)
{
//the debugregs got accesses
//save the current debugregs
//set the ownprocessdebugregs back to the debugregs
//do a single step (set the step flag in eflags


//set the debugregs back to what the program put them to
DbgPrint("The debugregs got accessed\n");
}


}
}
return result;

}



2. IDT에서 1번의 주소를 이전에 만든 함수 주소로 바꾼다.
int HookInterrupts()
{
IDTINFO idt_info;
IDTENTRY* idt_entries;
IDTENTRY* int1_entry;
__asm{
sidt idt_info;
}

idt_entries = (IDTENTRY*) MAKELONG(idt_info.LowIDTbase,idt_info.HiIDTbase);

Int1Address = MAKELONG(idt_entries[1].LowOffset,idt_entries[1].HiOffset);


int1_entry = &(idt_entries[1]);

__asm{
cli;
lea eax,interrupt1;
mov ebx, int1_entry;
mov [ebx],ax;
shr eax,16
mov [ebx+6],ax;

lidt idt_info;
sti;
}
return 0;
}

위에서 사용된 구조체들을 C언어 구조체로 표기해보면 다음과 같다.
typedef struct
{
WORD LowOffset;
WORD selector;
BYTE unused_lo;
unsigned char unused_hi:5; /* stored TYPE ? */
unsigned char DPL:2;
unsigned char P:1; /* present */
WORD HiOffset;
} IDTENTRY;

/* sidt returns idt in this format */
typedef struct
{
WORD IDTLimit;
WORD LowIDTbase;
WORD HiIDTbase;
} IDTINFO;

/* from undoc nt */
typedef struct
{
unsigned short limit_0_15;
unsigned short base_0_15;
unsigned char base_16_23;

unsigned char accessed : 1;
unsigned char readable : 1;
unsigned char conforming : 1;
unsigned char code_data : 1;
unsigned char app_system : 1;
unsigned char dpl : 2;
unsigned char present : 1;

unsigned char limit_16_19 : 4;
unsigned char unused : 1;
unsigned char always_0 : 1;
unsigned char seg_16_32 : 1;
unsigned char granularity : 1;

unsigned char base_24_31;
} CODE_SEG_DESCRIPTOR;

/* from undoc nt */
typedef struct
{
unsigned short offset_0_15;
unsigned short selector;

unsigned char param_count : 4;
unsigned char some_bits : 4;

unsigned char type : 4;
unsigned char app_system : 1;
unsigned char dpl : 2;
unsigned char present : 1;

unsigned short offset_16_31;
} CALLGATE_DESCRIPTOR;



핵심 내용은 아니지만 어떻게 하면 자신의 Thread에
HardwareBreakpoint가 설치되어 있는지 아닌지를
알아내는 간단한 코드를 작성해 보았다.

// GetHBP.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include "windows.h"

typedef struct tagDebugReg7
{
unsigned L0 :1; //
unsigned G0 :1; //
unsigned L1 :1; //
unsigned G1 :1; //
unsigned L2 :1; //
unsigned G2 :1; //
unsigned L3 :1; //
unsigned G3 :1; //
unsigned GL :1; //
unsigned GE :1; //
unsigned undefined1 :3; // 001
unsigned GD :1; //
unsigned undefined2 :2; // 00
unsigned RW0 :2;
unsigned LEN0 :2;
unsigned RW1 :2;
unsigned LEN1 :2;
unsigned RW2 :2;
unsigned LEN2 :2;
unsigned RW3 :2;
unsigned LEN3 :2;
} DebugReg7;

DebugReg7 *DR_7;

int main(int argc, char* argv[])
{
int i = 0;
CONTEXT z;
z.ContextFlags = CONTEXT_DEBUG_REGISTERS;
//Coded by Dual(http://dualpage.muz.ro)
while(1)
{
GetThreadContext(GetCurrentThread(),&z);
if(!z.Dr0 && !z.Dr1 && !z.Dr2 && !z.Dr3)
{
printf("There is no Debugger\n");
}
else
{
printf("There is Debugger!!\n");
DR_7 = (DebugReg7 *)&z.Dr7;
printf("GD : %X\n",DR_7->GD);
if(z.Dr0)
{
printf("HardwareBreakpoint at : 0x%X",z.Dr0);
if(DR_7->LEN0 == 0)
printf(" Len : 1byte");
else if(DR_7->LEN0 == 1)
printf(" Len : 2byte");
else if(DR_7->LEN0 == 2)
printf(" Len : Not Active");
else if(DR_7->LEN0 == 3)
printf(" Len : 4byte");
if(DR_7->RW0 == 0)
printf(" condition : excute\n");
else if(DR_7->RW0 == 1)
printf(" condition : write\n");
else if(DR_7->RW0 == 2)
printf(" condtion : Not Active\n");
else if(DR_7->RW0 == 3)
printf(" condition : read/write\n");
}
if(z.Dr1)
{
printf("HardwareBreakpoint at : 0x%X",z.Dr1);
if(DR_7->LEN1 == 0)
printf(" Len : 1byte");
else if(DR_7->LEN1 == 1)
printf(" Len : 2byte");
else if(DR_7->LEN1 == 2)
printf(" Len : Not Active");
else if(DR_7->LEN1 == 3)
printf(" Len : 4byte");
if(DR_7->RW1 == 0)
printf(" condition : excute\n");
else if(DR_7->RW1 == 1)
printf(" condition : write\n");
else if(DR_7->RW1 == 2)
printf(" condtion : Not Active\n");
else if(DR_7->RW1 == 3)
printf(" condition : read/write\n");
}
if(z.Dr2)
{
printf("HardwareBreakpoint at : 0x%X",z.Dr2);
if(DR_7->LEN2 == 0)
printf(" Len : 1byte");
else if(DR_7->LEN2 == 1)
printf(" Len : 2byte");
else if(DR_7->LEN2 == 2)
printf(" Len : Not Active");
else if(DR_7->LEN2 == 3)
printf(" Len : 4byte");
if(DR_7->RW2 == 0)
printf(" condition : excute\n");
else if(DR_7->RW2 == 1)
printf(" condition : write\n");
else if(DR_7->RW2 == 2)
printf(" condtion : Not Active\n");
else if(DR_7->RW2 == 3)
printf(" condition : read/write\n");
}
if(z.Dr3)
{
printf("HardwareBreakpoint at : 0x%X",z.Dr3);
if(DR_7->LEN3 == 0)
printf(" Len : 1byte");
else if(DR_7->LEN3 == 1)
printf(" Len : 2byte");
else if(DR_7->LEN3 == 2)
printf(" Len : Not Active");
else if(DR_7->LEN3 == 3)
printf(" Len : 4byte");
if(DR_7->RW3 == 0)
printf(" condition : excute\n");
else if(DR_7->RW3 == 1)
printf(" condition : write\n");
else if(DR_7->RW3 == 2)
printf(" condtion : Not Active\n");
else if(DR_7->RW3 == 3)
printf(" condition : read/write\n");
}
}

Sleep(100);
}
return 0;
}


실행 화면은 이렇다 :

1. 디버거에 의해 HardwareBreakPoint가 지정되어 있지않을 떄:



2. R/W를 걸어 두었을 때 :



3. excute를 걸어 두었을 때;



4. write를 걸어 두었을 때 :



-------------------------------------------------------
그렇다면 이러한 Detection Code가 존재하는 프로그램에
Hardware Breakpoint를 거는 것은 불가능한가?
물런 아니다. GetThreadContext()로 감지해냄으로,
이 함수를 Global Hooking하면 된다.
WinAPI의 GetThreadContext()는 NTAPI의
ZwGetContextThread() 로 연결되는데,
이를 SSDT Hooking이나, Inline Hooking하여,
아래와 같이 처리하여 주면 될것이다.

NTSTATUS NewZwGetContextThread(HANDLE hThread,PCONTEXT pContext)
{
NTSTATUS rc;

rc = OldZwGetContextThread(hThread,pContext);
if(PsGetCurrentProcess() != MyProcess)
{
pContext->DR0 = 0;
pContext->DR1 = 0;
pContext->DR2 = 0;
pContext->DR3 = 0;
pContext->DR6 = 0;
pContext->DR7 = 0;
}
return rc;
};


글 내용에 잘못된 내용이 있으면
메일 주세요 :)
Dual5651@hotmail.com

트랙백 주소 :: http://dual5651.hacktizen.com/tt/trackback/205

'보안' 카테고리의 다른 글

Assembly 명령어 [펌 Newms Blog]  (1) 2008.01.14
Instruction Set  (0) 2008.01.13
Break,Break BreakPoint!! [펌 dual5651.hacktizen.com]  (0) 2008.01.13
Windows NT Kernel Programming  (0) 2008.01.05
vmware + windbg 연동 커널 디버그 setting  (4) 2008.01.05
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
글 보관함