Chapter 46 TEB
TEB는 프로세스에서 실행되는 스레드에 대한 정보를 담고 있는 구조체이다. 스레드별로 TEB 구조체가 하나씩 할당된다. 또한 TEB 구조체는 OS 종류별로 그 모양이 조금씩 달라지며 세부적인 내용에 대해서는 문서화 되어있다.
다음은 TEB 구조체의 정의이다.
위는 각각 Windows XP SP3, Windows 7의 TEB 구조체이다. 구성을 보면 Windows 7 이 XP보다 구조체가 더 커진 것을 확인 할 수 있다.
위의 많은 멤버중 유저 모드 디버깅에 중요한 멤버들은 다음과 같다.
먼저 TEB 구조체의 첫번째 멤버는 _NT_TIB 구조체이다. _NT_TIB 구조체의 정의는 다음과 같다. TIB는 Thread Information Block의 줄임말이다.
가장 첫번째 멤버인 ExceptionList 멤버는 _EXCEPTION_REGISTRATION_RECORD 구조체의 연결리스트를 가리키고 있다. 이것은 SEH(Structed Exception Handler)라고 하는 Windows OS의 예외 처리 메커니즘에 사용된다.(48장 참고) Self 멤버는 _NT_TIB 구조체의 셀프포인터이다. _NT_TIB 구조체는 TEB 구조체의 첫번째 멤버이기 때문에 이는 TEB 구조체의 포인터이기도 하다.
이제 TEB 구조체에 유저모드에서 접근하는 방법에 대해서 알아볼 것이다.
Ntdll.NtCurrentTeb() API는 현재 스레드의 TEB 구조체 주소를 리턴하는 함수이다. 이 함수가 어떻게 구현 되어 있는지 OllyDbg를 통해서 확인해보면, 해당 API의 코드는 다음과 같다.
MOV EAX,DWORD PTR FS:[18]
RETN
매우 간단한 코드로, 단순이 FS:[18] 주소의 값을 리턴하는 것이다. 그렇다면 이제 FS 세그먼트 레지스터에 대해서 알아볼것이다.
FS 세그먼트 레지스터는 현재 스레드의 TEB를 지시하는데 사용된다. IA32 시스템에서 프로세스의 가상 메모리 크기는 4GB이므로 32비트 크기의 포인터를 이용해야 전체 메모리 공간이 접근이 가능하다. 그러나 FS 레지스터의 크기는 16바이트이다. FS 세그먼트 레지스터는 TEB를 지시하는데 사용되지만, 직접 TEB 주소를 가리키는 것이 아니라 실제 TEB 주소를 갖고 있는 Segment Descriptor Table의 Index 값을 갖고 있는 것이다.
GDTR 레지스터에 Segment Descriptor Table의 시작 주소가 저장 되어있고 FS 레지스터에 그 인덱스가 저장 되어있다.
따라서 FS:[0x18]은 Segment Descriptor Table에서 0x18 인덱스에 있는 주소 값에 있는 값을 의미한다. 이는 TEB에서 _NT_TIB 구조체 내의 Self 멤버를 의미하며 이는 _NT_TIB 구조체의 주소이자 TEB 구조체의 주소를 의미한다. 허나 0x18 인덱스의 Self 멤버의 값에 접근하지 않고 바로 FS:0 으로 접근하여 TEB의 주소를 구할 수도 있다.
하여 마지막으로 FS 레지스터 인덱스의 중요한 멤버들은 다음과 같다.
FS:[0x30] = TEB.ProcessEnvironmentBlock = PEB 시작 주소
FS:[0x30]은 TEB 구조체 내의 ProcessEnvironmentBlock 멤버의 값을 의미하는데 이는 PEB의 주소를 의미한다. PEB에 대해서는 다음 챕터에서 다룰 예정이다.
FS:[0] = TEB.NtTib.ExceptionList = SEH 시작 주소
위에서 언급하였듯이 FS:[0]은 _NT_TIB 구조체의 첫번째 멤버인 ExceptionList를 의미한다. 이는 SEH의 시작 주소를 의미한다. SEH는 48 챕터에서 다룰 예정이다.
'리버싱핵심원리' 카테고리의 다른 글
| Reverse Engineering Chap 48 (0) | 2016.02.03 |
|---|---|
| Reverse Engineering Chap 47 (0) | 2016.02.03 |
| Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
| Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
| Reverse Engineering Chap 45 (0) | 2016.01.30 |
50. 안티 디버깅
디버깅 -> 프로그램의 코드 흐름과 데이터 구조를 쉽게 파악 가능
안티 디버깅을 통해 이를 방지
안티 안티 디버깅 : 리버서들이 안티 디버깅을 상대하는 방법을 일컫는다.
static
디버깅을 시작할 때 한번만 해체해주면 되는 기법
이 기법이 적용된 파일은 RUN조차 되지 않음.
static 기법을 해제해줘야 실행 가능
dynamic
만날 때마다 해결해줘야함.
RUN은 되지만 TRACING은 방해하여 원본 프로그램의 코드와 데이터를 확인할 수 없게 만든다.
run : 디버기 프로세스를 실행시키는 것
trace : 디버기의 내부 Instruction 을 하나씩 실행하면서 레지스터, 메모리(스택) 등을 실시간으로 확인하는 것
51. Static Anti Debugging
51.1. 목적
디버깅중이라고 판단되면 일반 실행과는 다른 코드를 실행
구현 방법 : 디버거 탐지 / 디버깅 환경 탐지 / 디버거 강제 분리
회피 방법 : 탐지 코드에서 얻어오는 정보를 파악하여 그 정보 자체를 변경
51.2. PEB
Process Environment Block : 프로세스 환경 정보가 담겨져 있다.
신용 가능하고, 사용또한 쉽기 대문에 가장 널리 사용되는 안티 디버깅 기법
Windows XP SP3의 PEB 구조체에서 안티 디버깅을 위해 사용되는 멤버는 아래와 같다
+0x002 BeingDebugged : UChar
+0x00c Ldr : Ptr32_PEB_LDR_DATA
+0x018 ProcessHeap : Ptr32_Void
+0x068 NtGlobalFlag : Uint4B
PEB 구하는법 : 올리디버거로 해당 디버기 불러왔을 때, EBX에 저장된 값이 PEB의 시작주소
51.2.1. BeingDebugged (+0x2)
디버깅 중이면 1, 일반 실행이면 0
IsDebuggerPresent()를 통해 이 값을 가져오고, 이로써 디버깅 여부를 판별한다.
회피방법 : 0으로 변경
51.2.2. Ldr (+0xC)
디버깅 프로세스는 힙 메모리 영역에 자신이 디버깅 당하는 프로세스라는 표시를 한다. 그 중 가장 큰 특징은 사용되지 않는 역역은 0xFEEEFEEE 로 채운다는 것이다. 이를 통해 디버깅 여부 판별 가능하다.
Ldr 주소로 가면 0xFEEEFEEE를 찾을 수 있다.
회피방법 : NULL로 덮어씌우면 된다.
XP 이하에서만 동작하고 Vista 이후부터는 동작하지 않는다.
51.2.3. Process Heap (+0x18)
heap 구조체를 가리키는 포인터이다.
heap 구조체 중 Flags(+0xC) 와 Force Flags(+0x10) 멤버는 디버깅 중 특정한 값으로 세팅된다.
일반 실행시 Flags는 0x2 이고, ForceFlags는 0x0 이다.
디버깅 중에는 이 값들이 변경된다.
회피방법 : 정상 값으로 변경.
XP에서만 효과가 있다.
51.2.4. NtGlobaIFlag (+0x68)
디버깅 중일때는 이 값이 0x70으로 세팅된다.
회피방법 : 0x0으로 세팅
51.3. NtQueryInformationProcess()
ntdll!NtQueryInformationProcess() API를 사용하여 프로세스의 정보를 가져온 후, 이 중 디버깅 관련 정보를 통해 안티 디버깅을 수행한다.
3번째 파라미터인 PVOID ProcessInformation에 해당 정보가 세팅되는데 이 정보 중, ProcessDebugPort(0x7), Process DebugObjectHandle(0x1E), ProcessDebugFlags(0x1F) 가 사용된다.
51.3.1. ProcessDebugPort(0x7)
프로세스가 디버깅 중일 때 Debug Port가 할당된다.
디버깅 중이 아니라면 0이 세팅되지만
디버깅중이라면 0xFFFFFFFF 가 세팅된다.
CheckRemoteDebuggerPresent()를 통해 이 값을 가져오고, 이로써 디버깅 여부를 판별한다.
51.3.2. ProcessDebugObjectHandle(0x1E)
프로세스가 디버깅될 때 Debug Object가 생성된다.
디버깅 중이라면 Handle 값이 존재하고, 디버깅중이 아니라면 Handle은 NULL이다.
51.3.3. ProcessDebugFlags(0x1F)
0이면 디버깅 상태이고, 1이면 디버깅이 아니다.
51.3.4. 회피방법
API 후킹을 통해 특정 파라미터일 때 무조건 정상 값을 반환하도록 조작함.
51.4. NtQuerySystemInformation()
디버깅 환경을 체크하는 안티 디버깅 기법
OS가 Debug Mode로 부팅되었는지를 판단하는 안티 디버깅 기법
ntdll!NtQuerySystemInformation() API 를 통해 현재 동작 중인 OS 시스템에 대한 다양한 정보를 구할 수 있다.
첫번째 파라미터인 SYSTEM_INFORMATION_CLASS SystemInformationClass 에 SystemKernelDebuggerlnformation(0x23) 을 입력하면 현재 OS 시스템이 디버그 모드로 부팅되었는지 알 수 있다.
디버그 모드인 경우 SYSTEM_KERNEL_DEBUGGER_INFORMATION.DebuggerEnabled는 1에 세팅된다.
[회피]
xp의 경우, boot.ini를 편집하고 ‘/debugport=com1 /bandrate=115200 /Debug’ 값을 제거한다.
7의 경우, cmd 창에서 ‘bcdedit /debug off’ 명령을 내리고 재부팅하면 일반모드로 부팅된다.
51.5. NtQueryObject()
프로세스가 디버깅될 때 Debug Object가 생성되는데 이 존재를 확인하는 방법이다.
ntdll!NtQueryObject()는 시스템의 다양한 종류의 커널 객체 정보를 구해오는 함수이다.
두번째 파라미터에 원하는 값을 입력하고 API를 호출하면 세번째 파라미터에 관련 정보의 구조체 포인터를 리턴한다.
ObjectAllTypesInformaion 항목을 이용하여 시스템의 모든 객체 정보를 구한 다음 그 중에 DebugObject가 있는지 확인한다.
회피방법 : 이 함수가 호출될 때 breakpoint를 걸어 두번째 파라미터에 3(ObjectAllTypesInformaion)이 아닌 0(ObjecBasicTypesInformaion)이 들어가게 한다.
51.6. ZwSetInformationThread()
디버기에서 강제로 디버거를 떼어내는 기법이다.
이 함수는 스레드에게 정보를 세팅하는 System Native API이다.
첫번째 파라미터에 현재 스레드의 핸들을 넘겨주고
두번째 파라미터에 ThreadHideFromDebugger(Ox11) 값을 입력하면 디버거 프로세스가 분리된다.
회피방법 : breakpoint 를 걸어 두번째 파라미터에 0x11이 들어가 있다면 0으로 변경한다.
51.7. TLS
45장 참고
51.8. ETC
- 디버거 창 검색
- 디버거 프로세스 검색
- 컴퓨터 이름 확인 (TEST, ANALYSIS)
- 프로그램 실행 경로 검사 (TEST, SAMPLE)
- 가상 머신 실행 중인지 확인
회피방법 : 반환되는 문자열을 NULL로 세팅
'리버싱핵심원리' 카테고리의 다른 글
| Reverse Engineering Chap 47 (0) | 2016.02.03 |
|---|---|
| Reverse Engineering Chap 46 (0) | 2016.02.03 |
| Reverse Engineering Chap 21-22 (0) | 2016.02.03 |
| Reverse Engineering Chap 45 (0) | 2016.01.30 |
| Reverse Engineering Chap 34 (0) | 2016.01.27 |
21. Windows Message Hooking
21.1. Hook
Hook(갈고리)는 원하는 것을 낚아채고 싶을 때 사용하는 도구이다. 컴퓨터 분야에서는 정보를 엿보거나 가로채는 경우에 사용되는 단어로 확장되어 사용된다. Hook을 하는 행위를 ‘후킹한다(Hooking)’라고 표현하는데 이는 중간에서 오고가는 정보를 엿보거나 가로채기 위한 일을 하거나 정보를 조작하는 행위를 의미한다. 실제로 ‘OS-Application-User’ 사이의 정보를 모두 조작 가능하다. 가장 기본적인 방식의 후킹은 Message Hook이다.
21.2. Message Hook
Windows 운영체제는 GUI를 제공하고, 이는 Event Driven1 방식으로 동작한다. 이벤트가 발생할 때 OS는 미리 정의된 메시지를 해당 응용프로그램으로 보낸다.
일반적인 경우의 Windows 메시지 흐름
Hook Chain의 키보드 메시지 훅들은 해당 메시지 열람 및 변조가 가능하다. Hook Chain은 동시에 여러개 설치가 가능하며, 설치 순서대로 호출된다. 이러한 특성때문에 Hook Chain이라고 불린다.
21.3. SetWindowsHookEx()
메시지 훅은 SetWindowsHookEx() 함수를 통해 간단히 구현할 수 있다.
//SetWindowsHookEx() API
HHOOK SetWindowsHookEx(
int idHook, // hook type
HOOKPROC lpfn, // hook procedure : 운영체제가 호출해주는 콜백함수
HINSTANCE hMod , // 위 hook procedure가 속해있는 DLL 핸들
DWORD dwThreadld // Hook을 걸고 싶은 thread의 ID. 0을 주고 호출하면 글로벌 훅
);
21.4. 키보드 메시지 후킹 실습
- HookMain.exe는 KeyHook.dll을 최초로 로딩하여 키보드 훅을 설치하는 프로그램
- KeyHook.dll은 훅 프로시저가 존재하는 DLL 파일
- SetWindowssHookEx()를 이용하여 키보드 훅 설치
- 다른 프로세스에서 키 입력 이벤트가 발생하면 OS 에서 해당 프로세스의 메모리 공간에 KeyHook.dll을 강제로 로딩
- KeyboardProc() 함수가 호출됨
21.4.1. 실습 예제
21.4.2. 소스코드 분석
HookMain.cpp
#include "stdio.h"
#include "conio.h"
#include "windows.h"
#define DEF_DLL_NAME "KeyHook.dll"
#define DEF_HOOKSTART "HookStart"
#define DEF_HOOKSTOP "HookStop"
typedef void(*PFN_HOOKSTART)();
typedef void(*PFN_HOOKSTOP)();
void main()
{
HMODULE hDll - NULL;
PFN_HOOKSTART HookStart = NULL;
PFN_HOOKSTOP HookStop = NULL;
char ch = 0;
// KeyHook.dll 로딩
hDll = LoadLibraryA(DEF_DLL_NAME);
//export 함수 주소 얻기
HookStart =(PFN_HOOKSTART)GetProcAddress(hDll, DEF_HOOKSTART);
HookStop =(PFN_HOOKSTOP)GetProcAddress(hDll, DEF_HOOKSTOP);
// 후킹 시작
HookStart();
// 사용자가 'q'를 입력할 때까지 대기
printf("press 'q' to quit\n");
while( _getch() != 'q' );
// 후킹 종료
HookStop();
// KeyHook.dll 언로딩
FreeLibrary(hDll);
}
KeyHook.cpp
#include "stdio.h"
#include "windows.h"
#define DEF_PROCESS_NAME "notepad.exe"
HINSTANCE g_hInstance = NULL;
HHOOK g_hHook = NULL;
HWND g_hWnd = NULL;
BOOL WINAPI DllMain(HINSTANCE hinstDLL, DWORD dwReason, LPVOID lpvReserved)
{
switch(dwReason)
{
case DLL_PROCESS_ATTACH:
g_hInstance = hinstDLL;
break;
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
LRESULT CALLBACK KeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
{
char szPath[MAX_PATH] = {0,};
char *p = NULL;
if(nCode = 0)
{
// bit 31: 0 = key press, 1 = key release
if( !(lParam & 0x80000000) ) // 키보드가 눌렀다 떨어질 때
{
GetModuleFileNameA(NULL, szPath, MAX_PATH);
p = strrchr(szPath, '\\');
// 현재 프로세스 이름을 비교해서 만약 notepad.exe. 라면,
// 메시지는 응용 프로그램(혹은 다음 훅)으로 전달되지 않음
if( !_stricmp(p+1, DEP_PROCESS_NAME)
return 1;
}
}
// 일반적인 경우에는 CallNextHookEx()를 호출하여
// 응용 프로그램(혹은 다음 훅)으로 메시지를 전달함
return CallNextHookEx(g_hHook, nCode, wParam, lParam);
}
#ifdef __cplusplus
extern "C" {
#endif
__declspec(dllexport) void HookStart()
{
g_hHook = SetWindowsHookEx(WH_KEYBOARD, KeyboardProc, g_hInstance, 0);
}
__declspec(dllexport) void HookStop()
{
if(g_hHook)
{
UnhookWindowsHookEx(g_hHook);
g_hHook = NULL;
}
}
#ifdef __cplusplus
}
#endif
21.5. Debugging
21.5.1. ‘HookMain.exe’ 디버깅
- “press ‘q’ to quit!” 문자열을 참조한 코드를 찾음 으로써 main 함수 찾는다.
- main 함수에 breakpoint를 걸어 HookStart()가 실행될 때 까지 디버깅
- HookStart() 해당 주소의 CALL EAX 를 따라가서 KeyHook.dll의 함수임을 확인
- HookStart에서 사용되는 SetWindowsHookExW() 함수 및 파라미터 확인
21.5.2. Notepad.exe 프로세스 내의 KeyHook.dll 디버깅
- OllyDbg의 옵션(‘Break on new module(DLL)’을 Check 상태로 변경함으로써 새로운 DLL이 로딩될 때마다 자동으로 디버깅을 멈출 수 있다.
- 이 상태에서 HookMain.exe를 실행하고 notepad.exe 에서 키보드를 입력하면 OllyDbg가 멈추면서 ‘Executable modules’ 창이 뜬다.
- 여기서 KeyHook.dll이 로딩된 것을 확인할 수 있다.
22. 악의적인 목적으로 사용되는 키로거
관리 차원에서 (사용자 동의하에) 사용되는 키로거는 나쁘다고 할 수 없을 것이다. 하지만 악의적인 목적으로 사용자 몰래 실행되는 키로거는 문제가 된다.
22.1. 악성 키로거의 목표 및 사례
- 목표 : 돈
- 사례
- 온라인 게임
- 인터넷 뱅킹
- 기업 정보 유출
22.2. 키로거의 종류와 향후 발전 방향
키보드 끝에 연결되어 키보드의 입력이 PC로 전달되는 과정에서 가로챈다. 내부에 flash memory를 내장하고 있어 키보드로부터 들어오는 전기 신호를 직접 입력받아 저장하는 장치이다.
22.3. 키로거에 대처하는 우리의 자세
- 공공장소에서는 개인정보 입력 X
- 보안 프로그램 업데이트
- 개인정보 복붙으로 입력
- 개인 방화벽 사용
22.4. 개인정보
비밀번호 어렵게 하자
- 이벤트에 반응하여 동작을 변경하는 방식 ↩
'리버싱핵심원리' 카테고리의 다른 글
| Reverse Engineering Chap 46 (0) | 2016.02.03 |
|---|---|
| Reverse Engineering Chap 50-51 (0) | 2016.02.03 |
| Reverse Engineering Chap 45 (0) | 2016.01.30 |
| Reverse Engineering Chap 34 (0) | 2016.01.27 |
| Reverse Engineering Chap 3-6 (0) | 2016.01.27 |