Debug Technique

Similar documents
Deok9_Exploit Technique

Microsoft Word - UFuz3 분석.doc

hlogin2

Microsoft Word - building the win32 shellcode 01.doc

API 매뉴얼

11장 포인터

목 차 1. 개요 취약점분석추진배경 취약점요약 취약점정보 취약점대상시스템목록 분석 공격기법및기본개념 시나리오 공격코드

Microsoft PowerPoint - a10.ppt [호환 모드]

No Slide Title

제목

Deok9_PE Structure

BMP 파일 처리

이번장에서학습할내용 동적메모리란? malloc() 와 calloc() 연결리스트 파일을이용하면보다많은데이터를유용하고지속적으로사용및관리할수있습니다. 2

Chapter #01 Subject

임베디드시스템설계강의자료 6 system call 2/2 (2014 년도 1 학기 ) 김영진 아주대학교전자공학과

<322EBCF8C8AF28BFACBDC0B9AEC1A6292E687770>

UI TASK & KEY EVENT

In this tutorial i'll try to cover all of the known methods(or at least, those that I know =p) of injecting dll's into a proce

<4D F736F F F696E74202D204B FC7C1B7CEB1D7B7A55F F6E48616E646C6572B8A6C5EBC7D1BFA1B7AFB0CBC3E2B9D7BCF6C1A

Microsoft PowerPoint - chap13-입출력라이브러리.pptx

IDA 5.x Manual hwp

Microsoft PowerPoint - ch09 - 연결형리스트, Stack, Queue와 응용 pm0100

슬라이드 1

금오공대 컴퓨터공학전공 강의자료

Microsoft PowerPoint - chap02-C프로그램시작하기.pptx

Chapter 4. LISTS

Poison null byte Excuse the ads! We need some help to keep our site up. List 1 Conditions 2 Exploit plan 2.1 chunksize(p)!= prev_size (next_chunk(p) 3

Content 1. DLL? 그게뭐야?

<443A5C4C C4B48555C B3E25C32C7D0B1E25CBCB3B0E8C7C1B7CEC1A7C6AE425CBED0C3E0C7C1B7CEB1D7B7A55C D616E2E637070>

Microsoft Word - [Windows Hook] 6.HideProcess.doc

PowerPoint 프레젠테이션

02.Create a shellcode that executes "/bin/sh" Excuse the ads! We need some help to keep our site up. List Create a shellcode that executes "/bin/sh" C

11장 포인터

INTRO Basic architecture of modern computers Basic and most used assembly instructions on x86 Installing an assembly compiler and RE tools Practice co

Microsoft PowerPoint - ch07 - 포인터 pm0415

UI TASK & KEY EVENT

Microsoft Word - FunctionCall

Microsoft PowerPoint - chap06-2pointer.ppt

PowerPoint 프레젠테이션

chap7.key

Microsoft PowerPoint - Lecture_Note_7.ppt [Compatibility Mode]

슬라이드 1

목차 1. 소개... 3 가. BOF란?... 3 나. 윈도우 BOF 개발환경및사용툴 Shellcode 작성하기... 4 가. cmd 쉘 ) 소스코드작성 ) 디스어셈블리 ) 어셈블리코드편집 간단

[ 마이크로프로세서 1] 2 주차 3 차시. 포인터와구조체 2 주차 3 차시포인터와구조체 학습목표 1. C 언어에서가장어려운포인터와구조체를설명할수있다. 2. Call By Value 와 Call By Reference 를구분할수있다. 학습내용 1 : 함수 (Functi

chap 5: Trees

Microsoft PowerPoint - chap10-함수의활용.pptx

KNK_C_05_Pointers_Arrays_structures_summary_v02

<4D F736F F F696E74202D20BBB7BBB7C7D15F FBEDFB0A3B1B3C0B05FC1A638C0CFC2F72E BC8A3C8AF20B8F0B5E55D>

K&R2 Reference Manual 번역본

취약점분석보고서 [Photodex ProShow Producer v ] RedAlert Team 안상환

A Hierarchical Approach to Interactive Motion Editing for Human-like Figures

Microsoft Word - MSOffice_WPS_analysis.doc

03장.스택.key

hlogin7

PowerPoint Template

슬라이드 1

The Pocket Guide to TCP/IP Sockets: C Version

Lab 3. 실습문제 (Single linked list)_해답.hwp

목차 포인터의개요 배열과포인터 포인터의구조 실무응용예제 C 2

how_2_write_Exploit_4_the_MSF_v3.x.hwp

초보자를 위한 C# 21일 완성

CKKeyPro 적용가이드

Microsoft Word - Heap_Spray.doc

UI TASK & KEY EVENT

<4D F736F F F696E74202D B3E22032C7D0B1E220C0A9B5B5BFECB0D4C0D3C7C1B7CEB1D7B7A1B9D620C1A638B0AD202D20C7C1B7B9C0D320BCD3B5B5C0C720C1B6C0FD>

PowerPoint 프레젠테이션

A Dynamic Grid Services Deployment Mechanism for On-Demand Resource Provisioning

<4D F736F F F696E74202D FB8DEB8F0B8AE20B8C5C7CE205BC8A3C8AF20B8F0B5E55D>

금오공대 컴퓨터공학전공 강의자료

<4D F736F F F696E74202D20B8AEB4AABDBA20BFC0B7F920C3B3B8AEC7CFB1E22E BC8A3C8AF20B8F0B5E55D>

Frama-C/JESSIS 사용법 소개

익스플로잇실습 / 튜토리얼 Easy RM to MP3 Converter ROP [ Direct RET VirtualProtect() 함수사용 ] By WraithOfGhost

< E20C6DFBFFEBEEE20C0DBBCBAC0BB20C0A7C7D12043BEF0BEEE20492E707074>

API 매뉴얼

Microsoft PowerPoint - 04-UDP Programming.ppt

1. Execution sequence 첫번째로 GameGuard 의실행순서는다음과같습니다 오전 10:10:03 Type : Create 오전 10:10:03 Parent ID : 0xA 오전 10:10:03 Pro

Microsoft PowerPoint - o8.pptx

vi 사용법

제 14 장포인터활용 유준범 (JUNBEOM YOO) Ver 본강의자료는생능출판사의 PPT 강의자료 를기반으로제작되었습니다.


Chapter ...

Reusing Dynamic Linker For Exploitation Author : Date : 2012 / 05 / 13 Contact : Facebook : fb.me/kwonpwn

À©µµ³×Æ®¿÷ÇÁ·Î±×·¡¹Ö4Àå_ÃÖÁ¾

슬라이드 1

AhnLab_template

5.스택(강의자료).key

(Asynchronous Mode) ( 1, 5~8, 1~2) & (Parity) 1 ; * S erial Port (BIOS INT 14H) - 1 -

DLL Injection

본문서는 Syngress 의 Writing Security Tools and Exploits Chap11 을요약정리한 것입니다. 참고로 Chap 10 ~ 12 까지가 Metasploit 에대한설명입니다. Metasploit Framework 활용법 1. Metasplo

Microsoft PowerPoint - a8a.ppt [호환 모드]

<B1E2BCFAB9AEBCAD28C0CCB5BFBCF6295F F6F6B696E672E687770>

C++ Programming

Microsoft PowerPoint - [2009] 02.pptx

<4D F736F F F696E74202D20C1A63132B0AD20B5BFC0FB20B8DEB8F0B8AEC7D2B4E7>

PowerPoint 프레젠테이션

DCL Debugging Support

PowerPoint 프레젠테이션

2009년 상반기 사업계획

Lab 4. 실습문제 (Circular singly linked list)_해답.hwp

Microsoft PowerPoint - chap12-고급기능.pptx

Transcription:

Windows Hook Jerald Lee Contact Me : lucid7@paran.com 본문서는저자가 Windows Hook을공부하면서알게된것들을정리할목적으로작성되었습니다. 본인이 Windows System에대해아는것의거의없기때문에기존에존재하는문서들을짜집기한형태의문서로밖에만들수가없었습니다. 문서를만들면서참고한책, 관련문서등이너무많아일일이다기술하지못한점에대해원문저자들에게매우죄송스럽게생각합니다. 본문서의대상은운영체제와 Win32 API를어느정도알고있다는가정하에쓰여졌습니다. 본문서에기술된일부기법에대한자세한설명은책을참조하시길바랍니다. 제시된코드들은 Windows XP Service Pack2, Visual Studio.net 2003에서테스트되었습니다. 문서의내용중틀린곳이나수정해야할부분이있으면연락해주시기바랍니다. - 1 / 27 -

목 차 1. 사용되는기법들... 3 1.1. 디버그모드로해당프로세스의주소공간으로침투하기... 3 1.2. Debug API를이용한 DLL Injection... 8 1.3. Debug API를이용한 API Hooking... 17 1.4. 참고문서... 27-2 / 27 -

1. 사용되는기법들 이번에는 Debuging Technique을이용해 Hook을하는방법을다루어본다. 이기술은타겟프로세스를디버그모드로접근한뒤 Dll Injection으로도 Api Hooking으로도사용할수있으나난이도에비해결과가썩훌륭하지는않다. 무엇보다도디버깅을중지하면타겟프로세스까지중지되므로실제공격용으로는사용하기가벅차고 ( 멀티스레딩을통한방법이있다고는하는데.. 글쎄본인의실력으로는구현불가능이다. - -) 그냥원리를이해한다는측면에서정리해보았다. 1.1. 디버그모드로해당프로세스의주소공간으로침투하기 Windows는두가지종류의디버거를제공하는데사용자모드디버거와커널모드디버거가바로그것이다. 사용자모드디버거의경우응용프로그램을디버깅하기위해, 커널모드디버거의경우는운영체제의커널을디버깅하기위해사용되며일반적으로디바이스드라이버개발자들이대부분사용한다. 사용자모드디버거의특징은 Win32 Debugging API를사용한다는점이며우리는이 API를이용해서타겟프로세스의주소공간으로침투할것이다. 사용자모드디버거는타겟프로세스를디버깅하기위해 CreateProcess API를이용하는데, 함수의전달인자중 dwcreationflags 변수에 DEBUG_ONLY_THIS_PROCESS 값을사용하여디버깅을시작한다. 그리고발생하는다양한이벤트들을받기위해 WaitForDebugEvent API를호출하고특정한이벤트를처리한후에는 ContinueDebugEvent 함수를호출한다. 이방법의문제점은디버거가실행되는동안타겟프로세스도멈춰있다는것인데이문제는멀티스레드를사용해서해결할수있다. WaitForDebugEvent API가호출되었을때전달되는이벤트정보를저장하는 DEBUG_EVENT 구조체는다음과같다. typedef struct _DEBUG_EVENT { DWORD dwdebugeventcode; DWORD dwprocessid; DWORD dwthreadid; union { EXCEPTION_DEBUG_INFO Exception; CREATE_THREAD_DEBUG_INFO CreateThread; CREATE_PROCESS_DEBUG_INFO CreateProcessInfo; EXIT_THREAD_DEBUG_INFO ExitThread; - 3 / 27 -

EXIT_PROCESS_DEBUG_INFO ExitProcess; LOAD_DLL_DEBUG_INFO LoadDll; UNLOAD_DLL_DEBUG_INFO UnloadDll; OUTPUT_DEBUG_STRING_INFO DebugString; RIP_INFO RipInfo; u; DEBUG_EVENT; 디버그가위구조체의유니온안에나열된이벤트를처리하는동안타겟프로세스는멈춰있게되며 ContinueDebugEvent 함수가호출되면타겟프로세스는다시실행이된다. 일단여기까지의내용을의사코드를통해살펴보자. void _tmain(int argc, TCHAR *argv[]) { CreateProcess( NULL, // No module name (use command line). strprogrampath, // Command line. NULL, // Process handle not inheritable. NULL, // Thread handle not inheritable. FALSE, // Set handle inheritance to FALSE. CREATE_NEW_CONSOLE DEBUG_ONLY_THIS_PROCESS, // No creation flags. NULL, // Use parent's environment block. NULL, // Use parent's starting directory. &si, // Pointer to STARTUPINFO structure. &pi ) ; // Pointer to PROCESS_INFORMATION structure. while(waitfordebugevent(lpdebugevent, 1000) == 1) { if(exit_process) break; else { Do..something ; ContinueDebugEvent(dwProcessId, dwthreadid, dwcontinuestatus); - 4 / 27 -

먼저 strprogrampath 인자로넘어온프로그램을 CreateProcess 함수를이용하여디버그모드로디버깅을시작한다음 WaitForDebugEvent 함수로특정이벤트를기다린다. 특정이벤트에대한처리를해준뒤 ContinueDebugEvent 함수로진행을계속한다. 발생하는디버깅이벤트는아래의표와같다. 디버깅이벤트 CREATE_PROCESS_DEBUG_EVENT CREATE_THREAD_DEBUG_EVENT EXCEPTION_DEBUG_EVENT EXIT_PROCESS_DEBUG_EVENT EXIT_THREAD_DEBUG_EVENT LOAD_DLL_DEBUG_EVENT OUTPUT_DEBUG_STRING_EVENT UNLOAD_DLL_DEBUG_EVENT 설명새로운프로세스가디버깅되는프로세스내에서생성될때나디버거가이미활성화되어있는프로세스를디버깅할때마다생성된다. 디버깅되고있는프로세스에서새로운스레드가생성되거나디버거가이미활성화되어있는프로세스에대한디버깅을시작할때마다발생하며새로운스레드가사용자모드에서실행되기전에생성된다. 디버깅되고있는프로세스에서예외가발생할때마다생성된다. 액세스가불가능한메모리에대한액세스시도, 브레이크포인트명령실행, 0으로나눔명령실행또는 MSDN의 Structured Exception Handling 에서설명하고있는다른예외들을포함한다. 프로세스에있는마지막스레드가디버거를나갈때나 ExitProcess 함수를호출할때마다생성된다. 이이벤트는커널이프로세스의 DLL을언로드하고프로세스의종료코드를업데이트한후에곧바로발생한다. 디버깅되고있는프로세스의일부인스레드가종료할때마다생성된다. 커널은스레드의종료코드를업데이트하자마자이이벤트를생성한다. 디버깅되는프로세스가 DLL을로드할때마다생성된다. 이이벤트는시스템로더가 DLL을연결하거나디버깅되는프로세스가 LoadLibrary 함수를사용할때발생하며 DLL이해당주소공간에로드될때마다호출된다. 디버깅되는프로세스가 OutputDebugString 함수를호출할때생성된다. 디버깅되는프로세스가 FreeLibrary 함수를사용하여 DLL을언로드할때마다생성되며 DLL이프로세스의주소공간으로부터마지막으로언로드될때만발생한다. WaitForDebugEvent API 에서반환되는디버그이벤트를처리하고있는동안에타겟프로세스 - 5 / 27 -

의주소공간으로부터읽거나쓰기작업을할수있다. 디버깅당하는타겟프로세스의메모리로부터데이터를읽기위해서 ReadProcessMemory API를호출한다. ReadProcessMemory API 의원형은아래와같다. BOOL ReadProcessMemory( HANDLE hprocess, LPCVOID lpbaseaddress, LPVOID lpbuffer, SIZE_T nsize, SIZE_T* lpnumberofbytesread ); hprocess : [in], 메모리를읽어들일타겟프로세스의핸들, PROCESS_VM_READ 권한이있어야한다. lpbaseaddress : [in], 타겟프로세스의 Base Address lpbuffer : [out], 읽어들인메모리를저장할버퍼의포인터 nsize : [in], 메모리로부터읽어들일바이트의갯수 lpnumberofbytesread : [out], 버퍼에저장된바이트의개수 메모리에직접쓰는 API 는 WriteProcessMemory 이며 API 의원형은아래와같다. BOOL WriteProcessMemory( HANDLE hprocess, LPVOID lpbaseaddress, LPCVOID lpbuffer, SIZE_T nsize, SIZE_T* lpnumberofbyteswritten ); hprocess : [in], 수정할메모리프로세스의핸들, PROCESS_VM_WRITE 권한과 PROCESS_VM_OPERATION 권한이있어야한다. lpbaseaddress : [in], 타겟프로세스의 Base Address lpbuffer : [in], 타겟프로세스의메모리에쓸데이터를가지고있는버퍼의포인터 nsize : [in], 메모리에쓰여질바이트의개수 lpnumberofbyteswritten : [out], 메모리에쓰여진바이트의개수 - 6 / 27 -

일반적으로 WriteProcessMemony API를이용하여메모리에직접브레이크포인트를삽입한뒤발생하는 EXCEPTION_DEBUG_EVENT를잡아채서이후의작업을하게된다. 브레이크포인트를설정하기위해서는브레이크포인트를설정하고자하는메모리주소를갖고해당위치의 opcode를저장한후해당주소에브레이크포인트명령을쓰면되며 Intel Pentium 계열에서브레이크포인트를뜻하는명령은 INT 3 또는 opcode 값 0xCC이다. 브레이크포인트를설정해서원하는작업을수행한후에는원래의 opcode를다시원상태로복원시켜주어야한다. 브레이크포인트설정후특정 DLL을삽입하거나또는특정 DLL로부터발생하는특정 API를 Hook 할수있다. 전자는 DLL Injection이될것이고후자는 API Hook이될것이다. - 7 / 27 -

1.2. Debug API 를이용한 DLL Injection 먼저 Dll Injection 을알아보자. 1 대상프로그램을디버그모드로실행한다. 2 EXCEPTION_DEBUG_EVENT의 EXCEPTION_BREAKPOINT가발생하면 ( 디버그모드로시작하자마자발생하는첫번째예외 ) 타겟프로세스의컨텍스트와코드섹션의첫페이지를저장한다. 3 저장한영역에 LoadLibrary로특정 dll을로드하는코드와 0xcc를삽입한다. 4 EIP에실행시킬코드의주소를삽입한다. 5 ContinueDebugEvent를실행한다. 6 삽입한 LoadLibrary 실행후브레이크포인트가발생하면저장했던컨텍스트와코드섹션의첫페이지, 그리고 EIP를원상태로복귀한다. 7 ContinueDebugEvent를실행한다. 주의할것은디버그모드로들어가자마자첫번째 EXCEPTION_BREAKPOINT가발생한다는것이며이때타겟프로세스는실행되기전이다. 코드를삽입하는시기가바로이때이며두번째예외발생시에는원래코드를복원시켜주어야한다. 2 번에서첫번째코드페이지를가져오는코드는아래와같다. ( 전역변수 ) CProcessInfo = DebugEvent.u.CreateProcessInfo; pprocessbase = CProcessInfo.lpBaseOfImage; LPVOID CKeyboardHookDlg::GetFirstCodePage(HANDLE hprocess, PVOID pprocessbase) { DWORD baseofcode; DWORD pehdroffset; DWORD dwread; BOOL bret; // IMAGE_NT_HEADER의시작오프셋값을읽어옴 bret = ReadProcessMemory(hProcess, (PBYTE)pProcessBase + offsetof(image_dos_header, e_lfanew),&pehdroffset, sizeof(pehdroffset), &dwread); if (!bret sizeof(pehdroffset)!= dwread) { - 8 / 27 -

OutputDebugString("ERROR"); // Read in the IMAGE_NT_HEADERS.OptionalHeader.BaseOfCode field bret = ReadProcessMemory(hProcess, (PBYTE)pProcessBase + pehdroffset + 4 + IMAGE_SIZEOF_FILE_HEADER + offsetof(image_optional_header, BaseOfCode), &baseofcode, sizeof(baseofcode), &dwread); if(!bret sizeof(baseofcode)!= dwread) { OutputDebugString("ERROR"); return (LPVOID)((DWORD)pProcessBase + baseofcode); // 첫번째섹션코드의 RVA 코드를자세히살펴보자. bret = ReadProcessMemory(hProcess, (PBYTE)pProcessBase + offsetof(image_dos_header, e_lfanew),&pehdroffset, sizeof(pehdroffset), &dwread); 파일의선두로부터 IMAGE_DOS_HEADER 구조체의 e_lfanew 값을읽어들여이값을 pehdroffset 변수에저장시킨다. e_lfanew에는 IMAGE_NT_HEADER 구조체의시작오프셋값이저장되어있다. bret = ReadProcessMemory(hProcess, (PBYTE)pProcessBase + pehdroffset + 4 + IMAGE_SIZEOF_FILE_HEADER + offsetof(image_optional_header, BaseOfCode), &baseofcode, sizeof(baseofcode), &dwread); 파일의선두로부터 IMAGE_NT_HEADER 구조체로접근한다음 (pehdroffset) 4바이트를더하고 (PE x0 x0), IMAGE_SIZEOF_FILE_HEADER의값을더한뒤 (IMAGE_FILE_HEADER 구조체다음에 IMAGE_OPTINAL_HEADER 구조체가위치하고있다.) IMAGE_OPTINAL_HEADER 구조체의 BaseOfCode 값을읽어들여이값을 baseofcode 변수에저장한다. BaseOfCode에는코드영역의시작주소가저장되어있다.( 물론첫번째코드섹션이시작되는 RVA를의미한다.) 무슨말인지이해가가지않는사람을위해다시한번 PE 파일포맷을살펴보자 - 9 / 27 -

return (LPVOID)((DWORD)pProcessBase + baseofcode); 첫번째코드섹션이시작되는주소를반환한다. 다음으로 3 번에서 dll 을로드하고브레이크포인트를삽입한뒤실행하는코드는아래와같다. BOOL CKeyboardHookDlg::InjectionDll(HANDLE hprocess, HANDLE hthread, VOID * HModuleBase, CString szpathnamedll) { FARPROC LoadLibProc; DWORD dwread=0; BOOL bret; *poriginalcodepage=0; - 10 / 27 -

pfirstcodepage=null; *poriginalcodepage=null; // LoadLibraryA 의주소얻어옴 LoadLibProc = GetProcAddress(GetModuleHandle("KERNEL32.dll"), "LoadLibraryA"); if (!LoadLibProc) { OutputDebugString("ERROR"); // 타겟프로세스의첫번째코드페이지의주소를알아옴 pfirstcodepage = GetFirstCodePage(hProcess, HModuleBase); if(!pfirstcodepage) { OutputDebugString("ERROR"); OriginalContext.ContextFlags = CONTEXT_CONTROL; if(!getthreadcontext(hthread, &OriginalContext)) { OutputDebugString("ERROR"); // 실행프로세스의첫번째페이지백업 bret = ReadProcessMemory(hProcess, pfirstcodepage, poriginalcodepage, sizeof(poriginalcodepage), &dwread); if(!bret sizeof(poriginalcodepage)!= dwread) { OutputDebugString("ERROR"); // 구조체초기화 pmyloadlibrarya pnewcode = (pmyloadlibrarya)pfakecodepage; // sup esp, 1000h pnewcode->instr_sub = 0xEC81; pnewcode->operand_sub_value = PAGE_SIZE; // 페이지크기 (4096) - 11 / 27 -

// push < 매개변수 > pnewcode->instr_push = 0x68; pnewcode->operand_push_value = (DWORD)pFirstCodePage + offsetof(myloadlibrarya, DllName); // call < 함수주소 > ; LoadLibraryA() 호출 pnewcode->instr_call = 0xE8; pnewcode->operand_call_offset = (DWORD)LoadLibProc - (DWORD)pFirstCodePage offsetof(myloadlibrarya, instr_call) - 5; // 마지막에브레이크포인트삽입 pnewcode->instr_int_3 = 0xCC; strcpy(pnewcode->dllname, szpathnamedll.getbuffer(szpathnamedll.getlength())); // 우리의루틴을실행프로세스에 Write!! bret = WriteProcessMemory(hProcess, pfirstcodepage, &pfakecodepage, sizeof(pfakecodepage), &dwread); if(!bret sizeof(pfakecodepage)!= dwread) { OutputDebugString("ERROR"); // 실행포인터 (EIP) 를첫번째페이지로설정 FakeContext = OriginalContext; FakeContext.Eip = (DWORD)pFirstCodePage; // 실행스레드컨텍스트설정 if(!setthreadcontext(hthread, &FakeContext)) { OutputDebugString("ERROR"); return TRUE; 코드를자세히살펴보자 LoadLibProc = GetProcAddress(GetModuleHandle("KERNEL32.dll"), "LoadLibraryA"); pfirstcodepage = GetFirstCodePage(hProcess, HModuleBase); - 12 / 27 -

먼저 KERNEL32.dll 로부터 LoadLibraryA 의주소를얻어온다. 이후에 LoadLibProc 를이용해 임의의특정 dll 을로드한다. 주소를얻어온뒤첫번째코드섹션의주소를가져온다. OriginalContext.ContextFlags = CONTEXT_CONTROL; bret = ReadProcessMemory(hProcess, pfirstcodepage, poriginalcodepage, sizeof(poriginalcodepage), &dwread); 프로세스의컨텍스트를컨트롤하기위해 ContextFlags에 CONTEXT_CONTROL 값을할당한다. ContextFlags는여러값들이들어갈수있는데아래와같다. CONTEXT_CONTROL // SS:SP, CS:IP, FLAGS, BP CONTEXT_INTEGER // AX, BX, CX, DX, SI, DI CONTEXT_SEGMENTS // DS, ES, FS, GS CONTEXT_FLOATING_POINT // 387 state CONTEXT_DEBUG_REGISTERS // DB 0-3,6,7 CONTEXT_FULL=(CONTEXT_CONTROL CONTEXT_INTEGER > CONTEXT_SEGMENTS) 프로세스는시스템의현재상태의총합으로생각할수있는데프로세스는실행될때마다프로세스의레지스터와스택등을사용하며이것을프로세스컨텍스트라고한다. 일반적으로 GetThreadContext API를이용하여특정스레드의컨텍스트값을가져오기위해서는먼저적절한값들로초기화시켜야하는데사용된 CONTEXT_CONTROL을설정할경우주석과같이 SS:SP, CS:IP, FLAGS, BP를저장할수있게된다. 그리고첫번째코드섹션을읽어와서 poriginalcodepage에저장한다. pmyloadlibrarya pnewcode = (pmyloadlibrarya)pfakecodepage; // sup esp, 1000h pnewcode->instr_sub = 0xEC81; pnewcode->operand_sub_value = PAGE_SIZE; // 페이지크기 (4096) // push < 매개변수 > pnewcode->instr_push = 0x68; // push pnewcode->operand_push_value = (DWORD)pFirstCodePage + offsetof(myloadlibrarya, DllName); // call < 함수주소 > ; LoadLibraryA() 호출 pnewcode->instr_call = 0xE8; // call pnewcode->operand_call_offset = (DWORD)LoadLibProc - (DWORD)pFirstCodePage - 13 / 27 -

offsetof(myloadlibrarya, instr_call) - 5; // 마지막에브레이크포인트삽입 pnewcode->instr_int_3 = 0xCC; strcpy(pnewcode->dllname, szpathnamedll.getbuffer(szpathnamedll.getlength())); 이제첫번째코드페이지에삽입할구조체를설정하며해당구조체는 LoadLibraryA를호출하게된다. 삽입하게될구조체의모양은다음과같다. typedef struct _MyLoadLibraryA { WORD instr_sub; DWORD operand_sub_value; BYTE instr_push; DWORD operand_push_value; BYTE instr_call; DWORD operand_call_offset; BYTE instr_int_3; char DllName[1]; MyLoadLibraryA, *pmyloadlibrarya; LoadLibrary( load.dll ); 을호출하는코드를기계어로작성하기위해만든구조체이다. LoadLibrary를호출하는코드를어셈블리어로표현하면아래와같다. push ebp mov ebp,esp sub esp,40h push [dll의주소 ] call [LoadLibraryA의주소 ] LoadLibraryA의주소는각윈도우의버전, 서비스팩의버전에따라달라지는데 Dependency Walker를이용해서 Kernel32.dll의 Base Address에서 LoadLibraryA API의 Offset을더한값을사용하는것이가장쉽지만각윈도우버전, 서비스팩의버전만큼구현해야한다는것이다소번거롭다. 성상훈님의강좌에서는다음과같이해당주소를구해서사용하였다. pnewcode->operand_call_offset = (DWORD)LoadLibProc - (DWORD)pFirstCodePage offsetof(myloadlibrarya, instr_call) - 5; - 14 / 27 -

call이나 jmp 명령등의실행제어를변경하는명령어들은 (OP Code) 32비트환경에서보통 5바이트의크기를가지는데 OP Code 1바이트 + 이동할주소 4바이트가결합된크기이다. 이때이동할주소는 RVA이므로현재 OP Code를수행하고돌아올리턴주소를실제이동할주소에서뺀값으로기계어코드를생성하게된다. 위의코드에서 LoadLibraryA의주소 : LoadLibProc 현재실행중인코드의주소 : pfirstcodepage offsetof(myloadlibrarya, instr_call) 현재명령어 (OP Code) 의크기 : 5 가되는것이다. pnewcode->instr_int_3 = 0xCC; strcpy(pnewcode->dllname, szpathnamedll.getbuffer(szpathnamedll.getlength())); 이제마지막에 Break Point를삽입하고삽입할 Dll 이름을복사한다. 여기까지오면이제삽입할구조체가다완성된것이다. 자료를다채워넣은구조체를 WriteProcessMemory API를이용해서실행되고있는타겟프로세스에쓴다. bret = WriteProcessMemory(hProcess, pfirstcodepage, &pfakecodepage, sizeof(pfakecodepage), &dwread); 타겟프로세스 (hprocess) 의첫번째코드페이지에 (pfirstcodepage) 임의의 Dll을로드하는코드가들어있는구조체를 (pfakecodepage) 삽입한다. 자이제 4 번, 즉 EIP 를변경시키기만하면된다. FakeContext = OriginalContext; FakeContext.Eip = (DWORD)pFirstCodePage; // 실행스레드컨텍스트설정 if(!setthreadcontext(hthread, &FakeContext)) { OutputDebugString("ERROR"); 변조할컨텍스트에현재프로세스의컨텍스트를저장한후 EIP를첫번째코드페이지를가리키도록수정한다. 이후 SetThreadContext API를이용해현재프로세스의스레드에변조한컨텍스트를삽입한다. 다음은첨부한코드의실행화면이다. - 15 / 27 -

실행중이아닌프로세스를선택하여임의의 Dll 을주입할수있다. 소스파일은아래의주소에서받을수있다.( 실행파일포함 : KeyboardHook) 소스파일 : http://consult.skinfosec.co.kr/~lucid7/document/debugapi.zip - 16 / 27 -

1.3. Debug API를이용한 API Hooking 이번에는 API Hook을알아보자. 전자에서는실행중이아닌프로그램을 CreateProcess API에 DEBUG_ONLY_THIS_PROCESS 플래그를주어서디버그모드로진입했었다. 이번에는실행중인프로그램을 Attach하여디버그모드에서 API를 Hooking하는방법을알아본다. 현재실행중인 Process를 Debug 모드로 Attach하기위해서 DebugActiveProcess API를사용한다. BOOL DebugActiveProcess( DWORD dwprocessid ); DebugActiveProcess API에넘겨주어야할정보는단지 Process ID뿐이다. 타겟프로세스의 ID만넘겨주면나머지는알아서하게된다. DebugActiveProcess를사용하여타겟프로세스를 Debug Mode로 Attach한이후진행되는과정은 CreateProcess와같다. 본문서에서는 Dual님의 Win32 유저계층에서의 API 문서에서소개된소스를바탕으로진행을한다. 다만소스에서약간틀린곳이있어수정하였음을미리밝힌다.(Dual님이누락한것같아보이는데정확히는알수가없다 -_-;) 방법은거의똑같다. 앞절에서는첫번째코드페이지에 Break Point를삽입했지만이번에는 Hooking하기원하는타겟 API의첫번째바이트에 Break Point를삽입하는것뿐이다. 다만이번에는실행중인프로세스에접근하는것이기때문에 VirtualQueryEx API와 VirtualProtectEx API를사용하여보호모드속성을조정한후 WriteProcessMemory를써야한다. 왜냐하면 CreateProcess API를사용하여프로세스를실행시켰을경우에는자연스럽게해당프로그램에대한제어권이 CreateProcess API를사용한프로그램으로넘어오지만다른사용자가실행시킨, 실행중인프로세스의메모리는다른프로세스에게는읽기전용으로되어있을것이기때문이다. 여기서앞절에서빼먹어버린 Copy-On-Write 라는, 매우중요한개념을설명하기로한다. Windows는가능한많은메모리페이지를다른프로세스와공유하려한다. 만약해당페이지를사용하는프로세스중어느하나가디버거에서실행중이어서이들페이지중하나가브레이크포인트를갖고있다면, 브레이크포인트는모든프로세스가공유하는해당페이지에있어서는안된다. 만약그렇다면디버거외부에서실행중인프로세스가해당코드를실행하자마자, 해당프로세스는충돌하게될것이다. 이러한상황을피하기위해서운영체제는특정한프로세 - 17 / 27 -

스를위해서변경된페이지가있는지살펴보고브레이크포인트가쓰인페이지를특정한프로세스만접근할수있도록복사본을만든다. 따라서프로세스가페이지에쓰자마자, 운영체제는해당페이지를복사한다.(Debugging Applications for Microsoft.net and Microsoft Windows 에서발췌함 ) 따라서 VirtualQueryEx API로보호모드속성을얻은후 VirtualProtectEx API로보호모드속성을조정하는데 (PAGE_EXECUTE_READWRITE) 이때 Windows는 Copy-On-Write를준비한다. VritualQueryEx API 의함수원형은아래와같다. SIZE_T VirtualQueryEx( HANDLE hprocess, LPCVOID lpaddress, PMEMORY_BASIC_INFORMATION lpbuffer, SIZE_T dwlength ); hprocess : [in], 메모리정보가필요한타겟프로세스의핸들 lpaddress : [in], 메모리지역정보 lpbuffer : [out], 반환된메모리의정보가담긴 MEMORY_BASIC_INFORMATION 구조체를가리키는포인터 dwlength : [in], lpbuffer의사이즈, Byte 이 API를이용하면해당프로세스의메모리영역에대한정보를얻을수있다. VirtualProtectEx API 의함수원형은아래와같다. BOOL VirtualProtectEx( HANDLE hprocess, LPVOID lpaddress, SIZE_T dwsize, DWORD flnewprotect, PDWORD lpfloldprotect ); hprocess : [in], 보호모드속성을조정할메모리의프로세스핸들 lpaddress : [in], 보호모드속성을조정할메모리의지역정보 dwsize : [in], 접근하는보호모드속성메모리의크기 flnewprotect : [in], 변경할보호모드의속성 - 18 / 27 -

lpfloldprotect : [out], 원래보호모드의속성을가리키는포인터 기본적인설명은대충했으니이제코드를살펴보도록한다. 기본적인것은앞절과대부분동일하므로핵심적인부분만보도록한다. // 프로세스를처음 Attach 했을때발생하는메시지 (case 3) case CREATE_PROCESS_DEBUG_EVENT: { // 실행된프로세스의정보를얻어옴 CProcessInfo = DebugEvent.u.CreateProcessInfo; // Send API의주소를가진라이브러리의핸들을가져옴 if(!(wsock_handle = GetModuleHandle("WS2_32.DLL")) ) { if(!(wsock_handle = LoadLibrary("WS2_32.DLL")) ) // 없을경우 Load한다. break; // 로드에실패하면 Break // Send API의주소를구한다. if(!(send_adr = GetProcAddress(Wsock_Handle,"send")) ) break; // 주소값을가져오지못하면 Break // send API의메모리정보를가져온다. VirtualQueryEx(CProcessInfo.hProcess, Send_Adr, &mbi, sizeof(mbi)); // send API의메모리프로텍트를가져와수정한다. NewProtect = mbi.protect; NewProtect &= ~(PAGE_READONLY PAGE_EXECUTE_READ); // 제외시키고 NewProtect = (PAGE_READWRITE); // ReadWrite 추가 // 보호모드조정 VirtualProtectEx(CProcessInfo.hProcess, Send_Adr, sizeof(char), NewProtect, &OldProtect); // 첫바이트를읽어온다. ReadProcessMemory(CProcessInfo.hProcess, Send_Adr, &FirstByte, sizeof(firstbyte), &cbbyte); - 19 / 27 -

// 읽어온첫바이트에 Break Point 설정. EXCEPTION_EVENT(0xCC) 기록 WriteProcessMemory(CProcessInfo.hProcess, Send_Adr, &BreakByte, sizeof(breakbyte), &cbbyte); // 보호모드다시원래대로조정 VirtualProtectEx(CProcessInfo.hProcess, Send_Adr, sizeof(char), OldProtect, &NewProtect); CREATE_PROCESS_DEBUG_EVENT 발생시작동하는코드이다. 코드에주석을상세히달아놓았으니별어려움을없으리라고본다. SEND API의주소를가지고있는 WS2_32.DLL을로드한다음 GetProcAddress API를이용해 SEND API의주소를구한다. VirtualQueryEx API로 SEND API의메모리정보를가져온다음메모리보호모드설정을가져와 Read-Write 권한을추가한다. 수정한보호모드를 VirtualProtectEx API를이용해서적용시킨다음 SEND API의첫바이트를읽어와 Break Point를삽입하고다시보호모드를원래대로복원시킨다. case EXCEPTION_BREAKPOINT: // 브레이크포인트이벤트이면 { if(firsthit == FALSE) // 첫번째브포변수체크 FirstHit = TRUE; else { // 첫바이트를원래대로돌림 WriteProcessMemory(CProcessInfo.hProcess, Send_Adr, &FirstByte,sizeof(FirstByte), &cbbyte); // Context Mode Org_Context.ContextFlags = CONTEXT_FULL; GetThreadContext(CProcessInfo.hThread, &Org_Context); // Context를구해온다. ESP = Org_Context.Esp; // API가끝나고리턴 ( 돌아갈 ) 주소 ESP4 = ESP + 4; //S ESP8 = ESP + 8; //buf Adr ESPC = ESP + 0xC; //len - 20 / 27 -

ESP10 = ESP + 0x10; //flag // len읽어옴 ReadProcessMemory(CProcessInfo.hProcess, (void *)ESPC, &Len, sizeof(dword), &cbbyte); buffer = malloc(len); // 길이만큼메모리할당 // Buffer 주소읽어옴 ReadProcessMemory(CProcessInfo.hProcess, (void *)ESP8, &BufAdr, sizeof(dword), &cbbyte); /* // Buffer 읽어옴 ReadProcessMemory(CProcessInfo.hProcess, (void *)BufAdr, buffer, Len, &cbbyte); */ memset(buffer, 0x44, Len); // 내용조작 WriteProcessMemory(CProcessInfo.hProcess, (void *)BufAdr, buffer, Len, &cbbyte); New_Context = Org_Context; // 복사본을만든다. New_Context.Eip = (unsigned long)debugevent.u.exception.exceptionrecord.exceptionaddress; SetThreadContext(CProcessInfo.hThread,&New_Context); // Context 를적용한다. ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, dwcontinuestatus); // 대상프로그램에결과반영 /* // 첫바이트를읽어온다. ReadProcessMemory(CProcessInfo.hProcess, Send_Adr, &FirstByte, sizeof(firstbyte), &cbbyte); */ // 0xCC 를다시기록 WriteProcessMemory(CProcessInfo.hProcess, Send_Adr, &BreakByte, sizeof(breakbyte), - 21 / 27 -

&cbbyte); free(buffer); // 동적할당한메모리를놓아준다. AfxMessageBox("send 함수가발생하였습니다."); // 시각적효과 dwcontinuestatus = DBG_CONTINUE; dwcontinuestatus = DBG_CONTINUE; EXCEPTION_BREAKPOINT, 즉 Break Point 발생시작동하는함수이다. Break Point 이벤트가발생하면먼저변경했던 SEND API의첫번째바이트를원래대로돌려놓는다. 이제 SEND API의 Parameter를조작하기위해 GetThreadContext API를이용해 CONTEXT를정보를가져온다. Stack Overflow를이해한사람이라면무슨말인지알겠지만혹시나기억이가물가물한사람들을위해간단히살펴본다. #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { int i=10; char c='a'; printf("%d %c", i, c); return 0; 위의소스코드는간단하게콘솔에 i와 c를출력하는프로그램이다. #include "stdafx.h" int _tmain(int argc, _TCHAR* argv[]) { 00411A30 push ebp 00411A31 mov ebp,esp 00411A33 sub esp,0d8h 00411A39 push ebx 00411A3A push esi - 22 / 27 -

00411A3B push 00411A3C lea 00411A42 mov 00411A47 mov 00411A4C rep stos int i=10; 00411A4E mov char c='a'; 00411A55 mov edi edi,[ebp-0d8h] ecx,36h eax,0cccccccch dword ptr [edi] dword ptr [i],0ah byte ptr [c],41h printf("%d %c", i, c); 00411A59 movsx eax,byte ptr [c] 00411A5D push eax 00411A5E mov ecx,dword ptr [i] 00411A61 push ecx 00411A62 push offset string "%d %c" (4240C8h) 00411A67 call @ILT+1170(_printf) (411497h) 00411A6C add esp,0ch return 0; 00411A6F xor eax,eax 위는원래의소스코드를 Disassembly 한화면이다. 파란색글씨를보면 Parameter인 i 값과 c 값을먼저 Stack에 push 한다음 printf를 CALL 하고있다. 즉, SEND API가 CALL 되기직전에 Break Point에의해멈추어진상태라면 SEND API 에들어가는 Parameter 값은이미 Stack에 push된상태임을짐작할수있다. ( 기억이잘나지는않는데이게아마파스칼방식이던가? 뭐그랬던것같다. 그리고 Intel은파스칼방식을사용한다.) 자다시앞으로돌아가서얻어온 CONTEXT 정보중에서 ESP 값을기준으로 Return Address 를수정한다.( 왜 ESP 값을기준으로하는지모른다면 Statck Overflow 강좌를한번읽어볼것을권한다. int send(socket s, const char* buf, int len, int flags); 주석에도설명을해놓았지만위의 SEND API의원형에서볼수있듯이 (MFC에서는 CSocket::Send를사용하며 Parameter가틀리지만내부적으로는 send() 함수를이용하기때문에이상없이작동한다.) ESP4는 Parameter s를, ESP8은 Parameter buf의주소를, ESPC는 - 23 / 27 -

Parameter len 을, ESP10 은 Parameter flags 를가리킨다. 위의변수중우리가주목해야할것은바로 ESP8과 ESPC이다. ESP8에는 Send API를사용해전송되는문자열이, ESPC는전송된문자열의길이를가리키게된다. // len읽어옴 ReadProcessMemory(CProcessInfo.hProcess, (void *)ESPC, &Len, sizeof(dword), &cbbyte); buffer = malloc(len); // 길이만큼메모리할당 // Buffer 주소읽어옴 ReadProcessMemory(CProcessInfo.hProcess, (void *)ESP8, &BufAdr, sizeof(dword), &cbbyte); // Buffer 읽어옴 ReadProcessMemory(CProcessInfo.hProcess, (void *)BufAdr, buffer, Len, &cbbyte); 위의코드가실제 Send API를통해전송되는문자열을읽어오는핵심적인부분이다. 보내진문자열의길이를읽어와서그길이만큼메모리를할당한다. 문자열이저장된곳의주소를읽어온다음그주소에위치한문자열을읽어와 buffer 변수에저장한다. (buffer에저장된값을문자열로변환하는방법을몇일고민했지만본인의내공이딸리는관계로그냥넘어갔다 -_-;) 이후의소스는 buffer 변수에임의의문자를덮어써서전송되는문자열을조작하는것을나타내고있다. memset(buffer, 0x44, Len); // 내용조작 WriteProcessMemory(CProcessInfo.hProcess, (void *)BufAdr, buffer, Len, &cbbyte); New_Context = Org_Context; // 복사본을만든다. New_Context.Eip = (unsigned long)debugevent.u.exception.exceptionrecord.exceptionaddress; SetThreadContext(CProcessInfo.hThread,&New_Context); // Context를적용한다. ContinueDebugEvent(DebugEvent.dwProcessId, DebugEvent.dwThreadId, dwcontinuestatus); - 24 / 27 -

// 0xCC를다시기록 WriteProcessMemory(CProcessInfo.hProcess, Send_Adr, &BreakByte, sizeof(breakbyte), &cbbyte); free(buffer); // 동적할당한메모리를놓아준다. Buffer 변수에 0x44로값으로채운후 WriteProcessMemory API를이용해메모리에덮어쓴다. 그리고변경한 CONTEXT 값을다시설정하는데설정하기전 EIP 값을변경시킨다. DebugEvent.u.Exception.ExceptionRecord.ExceptionAddress 값은 Exception이발생한곳의주소를가리킨다. 즉 EIP 값을 Debug Event가발생한곳으로돌려줌으로써마치 Debug Event 가발생하지않았던것처럼위장할수있다. 지금까지설명했던동작들을계속반복하기위해 Break Point를다시설정한후 buffer의메모리를해제한다. 다음은해당프로그램을이용해실제로전송되는내용을조작하는방법과화면이다. 간단하게제작한 C/S 프로그램과 API Hooking 프로그램을사용해전송되는문자열을 DDD로조작할수있다. ( 해당프로그램은버그가꽤많이존재한다. 역시나내공이부족해버그를다잡지못했으니알아서고쳐쓰기를바랄뿐이다. - -) 1. SimpleServer.exe 를실행한후 START 버튼을누른다. 2. SimpleClient.exe 를실행한후 Connect 버튼을누른다. - 25 / 27 -

3. API Hook Program 을실행하여 Find 버튼을눌러 SimpleClient 를선택한뒤 Injection 버튼 을누른다. 4. SimpleClient 의에디트창에임의의문자를쓴다음 Send 버튼을누른다. 5. SimpleServer 화면에 DDDDDDDD 이전송되었음을알수있다. APIHook 의소스파일및 SimpleC/S 의실행파일은아래의주소에서받을수있다. 소스파일 (APIHook) : http://consult.skinfosec.co.kr/~lucid7/document/debugapi.zip 소스파일 (SimpleC/S) : http://consult.skinfosec.co.kr/~lucid7/document/simplecs.zip - 26 / 27 -

1.4. 참고문서 WebSite 1. Microsoft PE File Pormat http://www.microsoft.com/whdc/system/platform/firmware/pecoff.mspx http://msdn.microsoft.com/msdnmag/issues/02/02/pe/default.aspx http://msdn.microsoft.com/msdnmag/issues/02/03/pe2/default.aspx 2. API Hooking Revealed (http://www.codeproject.com/system/hooksys.asp) 3. 동우의홈페이지 http://dasomnetwork.com/~leedw/mywiki/moin.cgi/ 4. #44u6115f:p http://dualpage.muz.ro/ 5. 지니야닷넷 http://www.jiniya.net/tt/ 6. WIN32 - Inside Debug API http://www.woodmann.com/fravia/iceman1.htm Book 1. Debugging Applications for.net and windows, Microsoft Press 2. Visual C++.net programming bible 2 nd Edition, 김용성저 Special Thanks to Code Test and Modify : 이재익 (fishacker, duckgu9@nate.com) 박경호 (gafim2@nate.com) - 27 / 27 -