티스토리 뷰

Application/Debug

[해킹] Making Your own Packet Trainer on NT,XP

알 수 없는 사용자 2006. 6. 30. 01:57
이 글들을 읽을땐 C나 Asm에 대한 약간의 지식이나 API에 대한 사전 지식은 도움이 됩니다.

Making Your own Packet Trainer on NT,XP
NT,XP 기반의 운영체제에서의 패킷 트레이너 만들기
Written by A #Dual_Roo†




서론

이 글에서는 NT,XP에서 작동하는 패킷 트레이너를
만드는 방법에 대하여 다룰 것입니다.






필요한 도구들

M$ Visual C++ 6.0
Source Code[소스코드]






대상 프로그램 링크

저작권상의 이유로 링크 할수 없습니다.






본문


Making Your own Packet Trainer on NT,XP

에.. 안녕하세요? :p
이 글을 통해서 저를 처음 만난 분도 게실거구, 전에도 몇번 본적이 있는
분들도 게실거라고 생각합니다. 어쨰든 시작하여 보죠~ :p

이글을 읽고 있는 그쪽은 WPE[Winsock Packet Editor]라는 프로그램에 대하여
알고 게십니까? :p 만약 모른다고 가정하고 간단히 설명해 드리자면,
원하는 프로그램의 Packet을 Hooking(이라는 말보단 Sniffing이 어울릴려나? :p)
하는 프로그램 입니다. 9X,NT,XP용 버젼 다 존재 하지만~ :p
exe화 해주는 기능은 9X버젼에서만 지원되고, NT,XP용에선 지원되지 않습니다.
이점이 우리의 마음을 아프게 하죠 T.T (그렇지 않나요? :p)
저는 그래서 NT,XP에서도 여러분이 Packet Trainer을 직접 만들수 있도록,
이 글을 통해서 도와 드리고자 합니다~! :p
물런 C언어에 대하여 어느정도 알고 게셔야 설명이 가능합니다. T.T
뭐~! 알고들 게시다는 가정하에 Start~ :p

1. How to WPE Hook and modify the Packet?
어떻게 WPE는 패킷을 낚아보고 수정할수 있는 걸까요? :p
바로 API Hooking 이라는 기술을 통하여 가능합니다. :p
Windows의 거의 모든 프로그램들은 WSOCK32라는 DLL에 의해
Export되어 지는 함수들을 사용하여 외부 통신을 합니다. :p
그렇단 애기는? :p WSOCK32에서 적정한 함수들을 Hooking(낚아) 주면
우리가 원하는 결과를 얻을수 있을거란 소리 입니다. :p
그럼 이제 부터 API Hooking을 하는 Code를 통하여 설명 하도록 하죠~ :p

Yeah~! 즐거운 Coding을 시작하여 봅시다. :p 먼저 기본적인 Skeleton Code는 각자 작성하셔도 되구요~! 재가 작성한걸 다운 받으셔도 별 상관은 없겠죠? :p 본인의 코드는 Visaul C++ 6.0에서 작성되었습니다~! :p //================================================================================ BOOL CALLBACK MainDlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam); /* hDlg : 다이얼로그의 핸들 iMessage : 메시지 wParam : 아이템(리소스) 번호가 온다. lParam : 세부 사항이 온다. */ int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { DialogBox(hInstance, MAKEINTRESOURCE(IDD_DLG1), HWND_DESKTOP, MainDlgProc); /* hInstance : 프로그램의 ImageBase를 가르킨다. C에선 GetModuleHandle 함수를 쓸필요없이 자동 제공 되어 진다. MAKEINTRESOURCE(IDD_DLG1) : 리소스에 적용한 아이템 이름을 상수값으로 바꾸어 준다. HWND_DESKTOP : 부모의 핸들이다, 데스크톱의 값을 제공하고 있다. MainDlgProc : 메시지 프로시져의 주소를 대입하여 준다. */ return 0; } BOOL CALLBACK MainDlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) { switch(iMessage) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: return TRUE; default: return FALSE; } case WM_CLOSE: EndDialog(hDlg,0); return TRUE; default: return FALSE; } return FALSE; } //================================================================================ 정말 기본적인 코드죠? :p (코드 길이를 생각해서 DialogBox사용 했습니다.) 이제 코드를 조금씩 추가 시키도록 하죠. :p 가장 먼저 API Hooking을 하기 위해 필요한건 무엇일까요? 대상 프로그램을 지정하는 일 일것입니다. (그렇게 생각안할수도 :p) 만약 저와 생각이 같다면 대상 프로그램을 지정하는 방법을 생각해 보아야 겠죠? 저는 주로 두가지 방법을 사용하는데, 첫번쨰 방법은 모듈의 이름을 가지고 검색하는 방법입니다. (실행 파일이름이 별로 바뀔리는 없다는 점에선 좋죠) 두번쨰 방법은 Window Name을 가지고 검색하는 방법입니다. (창이름을 가지고 할경우 Code가 짧아지고 좋죠) 어쨰든,전 이번 글에선 대도록이면 Code를 짧게 하고자 하기 떄문에, 두번쨰 방법인 Window Name을 가지고 검색하는 방법을 사용하기로 하죠. Window Name을 검색하는 API는? :p 바로 FindWindow() 함수이죠 :p (너무 쉬운 질문이었나? :p) HWND FindWindow( LPCTSTR lpClassName, LPCTSTR lpWindowName ); 음? 첫번쨰 인자는 ClassName이고 두번쨰 인자는 WindowName이군요 :p ClassName으로 할경우 장점은 역시 변화성이 적다는 점이고, (단 단점은 MFC or VB or DELPHI 프로그램 들은 ClassName이.. =_=;) 두번쨰 인자인 WindowName으로 할경우 장점은 WindowName이 무엇인가는 알기가 쉽고, 이름이 겹칠 가능성이 ClassName보다 적습니다. (단점은 간단히 예를 들어 버젼을 제목 표시줄에 표시하는 프로그램의 경우 버젼업이 될때마다 WindowName이 달라 질수 있어서 =_=;) 둘중의 어떤걸 쓰던 FindWindow() 함수를 대상 프로그램의 핸드을 얻어올수 있습니다. 추가적으로 Process ID도 얻어 두겠습니다. DWORD GetWindowThreadProcessId( HWND hWnd, LPDWORD lpdwProcessId ); 첫번쨰 인자는 FindWindow() 함수를 통해서 얻은 창 핸들 이고, 두번쨰 인자는 Process ID를 저장할 변수의 주소 입니다. 어쨰든, 위 두 함수들을 Code에 추가 시키겠습니다. :p //================================================================================ BOOL CALLBACK MainDlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) { HWND W_hWnd; DWORD Pid; switch(iMessage) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: W_hWnd = FindWindow(NULL,"Dual is My Hero"); //WindowName으로 대상 핸들을 얻는 함수. if(W_hWnd == 0) return FALSE; GetWindowThreadProcessId(W_hWnd,&Pid); //ProcessID를 얻는 함수. return TRUE; default: return FALSE; } case WM_CLOSE: EndDialog(hDlg,0); return TRUE; default: return FALSE; } return FALSE; } //=========================================================================== 에~~ 위의 코드로 대상 프로그램을 알아 내는 작업은 완료 된겁니다. :p 정말 Simple 하죠? (6줄의 위력?! :p) 대상 프로그램을 Attach 시키는 API가 무엇인지 여러분은 알고 게십니까? :p 바로 DebugActiveProcess() 라는 API 입니다. :p BOOL DebugActiveProcess( DWORD dwProcessId ); 에~! 인자를 하나만 원하는 함수인데~ Process ID를 원하고 있군요? :p (우린 아까 Process ID를 얻었었죠 아마? :p) 그럼 바로 Code에 추가 시키도록 하죠! //================================================================================ BOOL CALLBACK MainDlgProc(HWND hDlg, UINT iMessage, WPARAM wParam, LPARAM lParam) { HWND W_hWnd; DWORD Pid; BOOL Attach; switch(iMessage) { case WM_INITDIALOG: return TRUE; case WM_COMMAND: switch(LOWORD(wParam)) { case IDOK: W_hWnd = FindWindow(NULL,"Dual is My Hero"); //WindowName으로 대상 핸들을 얻는 함수. if(W_hWnd == 0) return FALSE; GetWindowThreadProcessId(W_hWnd,&Pid); //ProcessID를 얻는 함수. Attach = DebugActiveProcess(Pid); //대상 프로그램을 Attach 하는 함수. if(Attach == 0) return FALSE; return TRUE; default: return FALSE; } case WM_CLOSE: EndDialog(hDlg,0); return TRUE; default: return FALSE; } return FALSE; } //=========================================================================== 에에~~! :p Attach 하는 방법까지 알게되었죠? :p 이제 Attach된 대상 프로그램의 Debug Msg는 우리 프로그램으로 보내지게 됩니다. :p (반은 온거 아니겠어요? :p) 물런 Debug Msg를 받기 위해선 Debug MsgLoop를 만들어 주어야 합니다. 그럼 지금부터 DebugMsg Routine을 작성하여 보도록 하죠~ :p 저는 DebugMsg Loop는 별도의 함수로 두는게 좋다고 생각해서 별도의 함수로 정의 하였습니다. 밑은 해당 Code 입니다. :p //=========================================================================== void DebugMsgLoop() { DEBUG_EVENT DebugEV; CREATE_PROCESS_DEBUG_INFO CPDI; DWORD dwContinueStatus = DBG_CONTINUE; while(TRUE) { WaitForDebugEvent(&DebugEV,INFINITE); dwContinueStatus = DBG_CONTINUE; switch(DebugEV.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: CPDI = DebugEV.u.CreateProcessInfo; break; case EXCEPTION_DEBUG_EVENT: if(DebugEV.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) break; else dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED; break; case EXIT_PROCESS_DEBUG_EVENT: return; } ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); } } //=========================================================================== 위의 코드에 대하여 알아 볼까요? :p 무한 루프를 돌면서 WaitForDebugEvent()함수로 메시지를 가져옵니다. dwDebugEventCode를 통해서 어떤 Event인지 구별 하여 냅니다. CREATE_PROCESS..메시지가 올떈 CPDI란 구조체에 정보를 저장합니다. (정말 중요한 정보죠 :p) EXIT_PROCESS_..메시지가 올떈 루프에서 return 함으로써 프로그램을 종료 합니다. 그리고 가장 중요한 EXCEPTION_DEBUG.. 메시지.. 즉, 예외 처리 메시지가 올떄는 하위 구조체를 조사하여 EXCEPTION_BP인지 다시 한번 체크 한후 그럴경우 어떤 처리를 할지 준비를 해둔 코드가 되겠습니다. ContinueDebugEvent()함수는 Debug가 계속 이어질수 있도록 해주는 함수죠. WaitForDebugEvent()함수와 ContinueDebugEvent()함수가 계속 핑글 핑글 돌아 가는 겁니다. :p What is the Exception Event? 예외처리란 무엇이고 어떤떄 일어나는 것일까요? :p 예외처리란 말 그대로 프로그램에서 일어나는 예상한 그외의 일들을 처리 하는 것들을 말합니다. Exception Event들은 그런 일들이 벌어졌을떄 오는 Message이죠. :p 뭐 여러종류가 있지만 우리가 관심있는것은 EXCEPTION_BREAKPOINT 란 녀석이죠. :p EXCEPTION_BREAKPOINT에서도 몇가지 Code들로 나뉘어 집니다~ EXCEPTION_DEBUG_EVENT ;예외 처리 메시지 CREATE_THREAD_DEBUG_EVENT ;프로그램이 시작 or Attach 될떄의 메시지 EXIT_THREAD_DEBUG_EVENT ;Thread가 종료 될떄의 메시지 EXIT_PROCESS_DEBUG_EVENT ;Process가 종료 될떄의 메시지 LOAD_DLL_DEBUG_EVENT ;DLL이 로드 될떄의 메시지 UNLOAD_DEBUG_EVENT ;DLL이 언로드 될떄의 메시지 ... ' ~ ' 바로 위의 것들이죠~~ 후훗 대충 위의 것들이 있다는걸 알아 두도록 하죠. 이글에서 설명할 Hooking에 쓰이는 Event는 EXCEPTION_DEBUG_EVENT입니다. (예외 디버그 이벤트 라고 해석 하면 되려나? :p) How to can be operating for needed time? 어떻게 하면 통신에 쓰이는 함수가 쓰이는 떄에 그것을 가로챌수 있도록 작동할수 있을까요? :p 이것이 Hooking의 가장 핵심점이라고 저는 생각합니다. 저는 3시간 동안의 낮잠을 통해 답을 얻을수 있었습니다. 재가 생각한 방법은 바로 이렇습니다. ================================================== 1. API호출 이란것은 Dll안에 있는 함수를 호출 하는 것이다. 2. Dll의 내용은 어느정도 조작을 통해서 바꿀수 있을것이다. 3. 원하는 API의 시작부분에 EXCEPTION_DEBUG_EVENT(0xCC)를 심어 두면 API가 호출 되는 순간에 EXCEPTION_DEBUG_EVENT 이벤트가 발생되어 디버그 부모인 우리 프로그램에게 제어권이 넘어 올 것이다. 4. 제어권이 넘어온 순간에 디버그의 대상 프로그램은 일시 중지 상태일것임으로 메모리의 내용도 바뀌지 않은 상태일것이다. 우리는 이동안 원하는 내용을 읽어올수 있을 것이다. ================================================== 어때요? 아주 간단한 생각이면서도 바로 쓸수 있을꺼 같은 기분이 들지 않나요? :p 킼킼킼.... 그런 기분이 들지 않아야 정상입니다. :p 왜냐면 위에껀 그냥 이론일 뿐이죠. 어디 시점에서 API의 시작 부분에 EXCEPTION_DEBUG_EVENT를 심어 둘지도 아마 지금은 모를 것이고. 이보다 먼저 API의 시작부를 구하는 방법 조차 여러분은 아직은 모르고 있을 것입니다. :p (그래서 이 강좌를 읽고 있는것 아니겠어요? 설마 이것이 당신을 격멸하는 말 인가요? Good bye... :p) 자~! 이제 위에서 말한 이론을 실제 Code로 보여 드리겠습니다~! //============================================================ void DebugMsgLoop() { DEBUG_EVENT DebugEV; CREATE_PROCESS_DEBUG_INFO CPDI; DWORD dwContinueStatus = DBG_CONTINUE; HMODULE Wsock_Handle; //후킹할 API가 존재하는 모듈의 핸들 저장 변수 LPVOID Send_Adr; //후킹을 원하는 대상 API주소 저장 변수. MEMORY_BASIC_INFORMATION mbi; unsigned long OldProtect,NewProtect; char FirstByte,BreakByte = (char)0xCC; unsigned long cbByte; CONTEXT Org_Context,New_Context; //Context BOOL FirstHit = FALSE; //Attach시에 생기는 Exception Event 체크 변수 while(TRUE) { WaitForDebugEvent(&DebugEV,INFINITE); //디버그 이벤트를 기다린다. dwContinueStatus = DBG_CONTINUE; switch(DebugEV.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: //프로세스를 처음 Attach 했을떄~ CPDI = DebugEV.u.CreateProcessInfo; Wsock_Handle = GetModuleHandle("WS2_32.DLL"); if(Wsock_Handle == 0) { Wsock_Handle = LoadLibrary("WS2_32.DLL"); if(Wsock_Handle == 0) return; } Send_Adr = GetProcAddress(Wsock_Handle,"send"); if(Send_Adr == 0) return; VirtualQueryEx(CPDI.hProcess,Send_Adr,&mbi,sizeof(mbi)); NewProtect = mbi.Protect; NewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); NewProtect |= (PAGE_READWRITE); VirtualProtectEx(CPDI.hProcess,Send_Adr, sizeof(char),NewProtect, &OldProtect); ReadProcessMemory(CPDI.hProcess,Send_Adr, &FirstByte,sizeof(FirstByte), &cbByte); WriteProcessMemory(CPDI.hProcess,Send_Adr, &BreakByte,sizeof(BreakByte), &cbByte); break; case EXCEPTION_DEBUG_EVENT: if(DebugEV.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) if(FirstHit == FALSE) FirstHit = TRUE; else { WriteProcessMemory(CPDI.hProcess,Send_Adr, &FirstByte,sizeof(FirstByte), &cbByte); Org_Context.ContextFlags = CONTEXT_FULL; GetThreadContext(CPDI.hThread,&Org_Context); New_Context = Org_Context; New_Context.Eip = (unsigned long)DebugEV.u.Exception.ExceptionRecord.ExceptionAddress; //New_Context.Eip--; SetThreadContext(CPDI.hThread,&New_Context); } else dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED; break; case EXIT_PROCESS_DEBUG_EVENT: return; } ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); } } //======================================================= 이제 위에 코드를 살펴 보면~~~ :p GetModuleHandle()함수를 통해 WS2_32.DLL의 HMODULE를 구해 오고,그 HMODULE을 가지고 GetProcAddress()라는 함수의 시작주소를 가져오는 API를 이용하여 WS2_32.DLL안에 있는 send라는 API의 주소를 구해옵니다. 그 다음 VirtualQueryEx() 라는 함수를 이용하여 Send_Adr(send함수의 주소)의 메모리 영역에 관한 정보를 구해옵니다. (우리가 필요로 했던것은 Protect였답니다. :p) 그후 VirtualProtectEx()라는 함수를 이용하여 그부분의 Protect를 PAGE_READWRITE로 바꾸어 버립니다. (이래야 어느정도 자유롭게 읽기 쓰기가 되죠~) 지금 부터 중요한 작업인데... ReadProcessMemory()라는 API를 이용하여 send함수의 첫바이트를 읽어옵니다. 그다음 WriteProcessMemory()라는 함수를 이용하여 첫바이트에 EXCEPTION_DEBUG_EVENT(0xCC)를 기록합니다. (이로써 Hooking 준비는 완료 된것이죠~~ :p) VirtualQueryEx() 바꿧던 Protect를 원래대로 되돌려 놓습니다. 이제 우리가 할일은 대상 프로그램에서 send함수가 발생하길 기다리는일 뿐입니다~~! :p WoW~!! 정말 Hooking이라는것도 그리 어려운것만은 아닌거 같다는 생각들지 않나요? :p (실제로도 그리 어려운 것이 아닙니다. 본인이 설명을 잘 못해서 그렇지~~;) ) 그런데 말이죠~~ 위 코드에서 뭔가 이상하다는거 발견한분~? 별써 있을지도 모릅니다. :p 네 그렇습니다. 위 코드는 한번밖에 작동을 안합니다. 그렇다면 어떻게 해야 계속 작동할까요? :p 대답은 간단합니다. 위 코드중 살표보지 않은 부분을 계속 보며 설명 하도록 하죠~ DebugLoop를 돌면서 대상 프로그램을 처음 Attach했을떄 EXCEPTION_DEBUG_EVENT가 먼저 한번 발생 됩니다. 그떄를 체크하기 위해 저는 FirstHit라는 변수를 두고 처음 걸렸을떄를 체크 하게 하였습니다. :p 그 다음 EXCEPTION_DEBUG_EVENT가 발생했다는것은 send함수가 대상 프로그램에서 사용되었다는 말입니다. :p 재가 위에서 작성해둔 Code는 WriteProcessMemory()를 이용하여 첫번쨰 바이트를 원래되로 돌려 놓고~ (보통 첫바이트는 0x55입니다.) GetThreadContext()라는 함수로 대상 프로그램의 Context를 읽어온후 Eip(다음 실행번지 기억 레지스터)를 이전 EXCEPTION_DEBUG_EVENT가 발생한 위치로 돌려놓습니다. 그후 SetThreadContext()라는 함수로 대상 프로그램에 Context상태를 적용 시킵니다. 이로써 얻는 효과는 다시 대상 프로그램의 Code가 정상적으로 실행 될수 있도록 하는 것입니다. :p 여기까지 진행되면 가장 첫번쨰 Send함수가 발생되었을떄 이 Routine이 실행되고 그다음 부턴 실행되지 않습니다. 우리는 send함수의 첫번쨰 바이트를 원래되로 되돌려 놓았기 떄문에 EXCEPTION_DEBUG_EVENT가 더이상 발생하지 않기 떄문입니다. 다시 발생시키기 위해선 이 뒷부분에 다시 첫번쨰 바이트를 0xCC로 바꾸는 Code를 추가 시킬 필요성이 있습니다. :p 그래서 본인은 밑에와 같은 Code를 Routine에 추가 시키었습니다. :p //============================================================= ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); /* ReadProcessMemory(CPDI.hProcess,Send_Adr, &FirstByte,sizeof(FirstByte), &cbByte); */ WriteProcessMemory(CPDI.hProcess,Send_Adr, &BreakByte,sizeof(BreakByte), &cbByte); MessageBox(NULL,"send함수가 발생하였습니다.","==알림==",MB_OK); //============================================================== 이로써 send함수가 발생 할떄마다 우리는 메시지 박스를 통해 알수 있게 되었습니다. (실제로 되는지 테스트하기에 시각적인 메시지 박스 만큼 좋은것도 없죠 :p) 그럼 위의 Code도 추가시킨 지금까지 우리가 작성한 Routine을 보도록 하죠. //============================================================== void DebugMsgLoop() { DEBUG_EVENT DebugEV; CREATE_PROCESS_DEBUG_INFO CPDI; DWORD dwContinueStatus = DBG_CONTINUE; HMODULE Wsock_Handle; //후킹할 API가 존재하는 모듈의 핸들 저장 변수 LPVOID Send_Adr; //후킹을 원하는 대상 API주소 저장 변수. MEMORY_BASIC_INFORMATION mbi; unsigned long OldProtect,NewProtect; char FirstByte,BreakByte = (char)0xCC; unsigned long cbByte; CONTEXT Org_Context,New_Context; //Context BOOL FirstHit = FALSE; //Attach시에 생기는 Exception Event 체크 변수 while(TRUE) { WaitForDebugEvent(&DebugEV,INFINITE); //디버그 이벤트를 기다린다. dwContinueStatus = DBG_CONTINUE; switch(DebugEV.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: //프로세스를 처음 Attach 했을떄~ CPDI = DebugEV.u.CreateProcessInfo; //정보를 저장하여 둔다. Wsock_Handle = GetModuleHandle("WS2_32.DLL"); //모듈 핸들을 구한다. if(Wsock_Handle == 0) { Wsock_Handle = LoadLibrary("WS2_32.DLL"); //없을 경우 Load한다. if(Wsock_Handle == 0) break; } Send_Adr = GetProcAddress(Wsock_Handle,"send"); //Send함수의 주소를 구한다. if(Send_Adr == 0) break; VirtualQueryEx(CPDI.hProcess,Send_Adr,&mbi,sizeof(mbi)); //메모리 프로텍트를 구해온다. NewProtect = mbi.Protect; NewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); //재외 시키고~ NewProtect |= (PAGE_READWRITE); //추가 시킨다. VirtualProtectEx(CPDI.hProcess,Send_Adr, //보호 모드 조정 sizeof(char),NewProtect, &OldProtect); ReadProcessMemory(CPDI.hProcess,Send_Adr, //첫바이트를 읽어온다. &FirstByte,sizeof(FirstByte), &cbByte); WriteProcessMemory(CPDI.hProcess,Send_Adr, //EXCEPTION_EVENT(0xCC) 기록 &BreakByte,sizeof(BreakByte), &cbByte); break; case EXCEPTION_DEBUG_EVENT: //예외 디버그 이벤트 발생시 if(DebugEV.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) //EXCEPTION_BREAKPOINT인가? { if(FirstHit == FALSE) FirstHit = TRUE; //첫번쨰 브포 변수 체크 else { WriteProcessMemory(CPDI.hProcess,Send_Adr, //첫바이트를 원래 대로 돌림 &FirstByte,sizeof(FirstByte), &cbByte); Org_Context.ContextFlags = CONTEXT_FULL; //Context Mode GetThreadContext(CPDI.hThread,&Org_Context); //Context를 구해온다. New_Context = Org_Context; //복사본을 만든다. New_Context.Eip = (unsigned long)DebugEV.u.Exception.ExceptionRecord.ExceptionAddress; //New_Context.Eip--; //Eip값을 -1 시킨다. SetThreadContext(CPDI.hThread,&New_Context); //Context를 적용한다. ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); //대상 프로그램에 결과 반영 /*ReadProcessMemory(CPDI.hProcess,Send_Adr, //첫 바이트를 읽어온다. &FirstByte,sizeof(FirstByte), &cbByte); */ WriteProcessMemory(CPDI.hProcess,Send_Adr, //0xCC를 기록 &BreakByte,sizeof(BreakByte), &cbByte); MessageBox(NULL,"send함수가 발생하였습니다.","==알림==",64); //시각적 효과 } } else dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED; break; case EXIT_PROCESS_DEBUG_EVENT: //프로세스 종료 디버그 이벤트 일떈 루프 끝 return; } ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); } } //================================================================================ 음 지금까지 우리가 작성한 Routine으로 할수 있는 일은 대상 프로그램에서 원하는 API가 발생되었을떄 우리 프로그램이 그것을 알아 챌수 있는가 없는가 입니다. 이것만으론 우리가 원하는 기능이 아니죠. :p 우리가 원하는 가장 중요한것은 바로 '조작' 일것입니다. :p 지금부터 그러면 조작에 대해서 생각하여 보죠. 어떻게 하면 대상 프로그램에서 API를 호출할떄의 Parameter를 조작할수 있을까요? :p 우리는 바로 위의 Routine에서 GetThreadContext()라는 API를 이용하여 대상 프로그램의 Context상태를 구해올수 있었습니다. 이떄 구해온 Context(상태)가 어떤떄의 Context인지 생각하면 우리는 이미 답을 얻었다는 것을 알수 있습니다. :p 지금까지 했던 과정을 다시 집으며 어떤 상태인지 알아 보도록 하죠. 먼저 첫번쨰로 우린 대상 프로그램의 핸들을 구하고 대상 프로그램을 Attach하였고, 대상 프로그램의 원하는 API부분에 0xCC를 기록하여 두었고 이 GetThreadContext()가 호출 되는 바로 이 시점은 우리가 원하는 API에 심어둔 0xCC가 EXCEPTION_DEBUG_EVENT가 발생되었을떄 입니다. 다시 요약해 말하자면 대상 프로그램에서 우리가 Hooking 하고자 하는 함수(send)를 호출하였는데 우리가 설치해둔 0xCC에 의해서 지금 해당 API의 첫부분에서 멈추어 있는 상태 인것입니다. 그렇기 떄문에 스택에는 API를 호출하는데 필요한 Parameter들이 고스란히 들어있겠죠? :p 그렇다면 대상 프로그램의 스택을 구해오는 방법은??? :p 그것의 답역시 GetThreadContext() 함수에 있습니다. 컴퓨터의 레지스터중 ESP라는 레지스터가 있습니다. (스택의 꼭대기를 가르킵니다. :P) 우리는 이 ESP레지스터의 값을 알고 있습니다. :p 어떻게냐구요? GetThreadContext() 함수가 구해오는 값중에 ESP의 값도 있기 떄문이죠. :p send API의 생김새를 봅시다. :p ==================================== int send( SOCKET s, const char* buf, int len, int flags ); ==================================== 첫번쨰 인자 s는 소켓 구조체를 나타내며, 두번쨰 인자 buf는 전송할 값이 있는 buf위치를 포인트 하며,(중요 포인트1) 세번쨰 인자 len은 buf의 값에 길이를 나타냅니다.(중요 포인트2) 네번쨰 인자 flag는 flag를 나타내죠~ (설명이 뭐 이렇담 :p) send함수를 통해 보낼값은 두번쨰인자 buf에 의해 포인트 되어 있고 len은 그 크기를 나타 내고 있다는 사실은 아주 중요 합니다~! ReadProcessMemory()함수를 통하여 buf와 len값만 읽어온다면 내용 훔쳐 보기는 물런 내용 조작하기도 누워서 떡 먹기 라는 사실입니다. :p (역시나 말 보다는 Code로 보는게 명확할듯 :p) //========================================================== void DebugMsgLoop() { DEBUG_EVENT DebugEV; CREATE_PROCESS_DEBUG_INFO CPDI; DWORD dwContinueStatus = DBG_CONTINUE; HMODULE Wsock_Handle; //후킹할 API가 존재하는 모듈의 핸들 저장 변수 LPVOID Send_Adr; //후킹을 원하는 대상 API주소 저장 변수. MEMORY_BASIC_INFORMATION mbi; unsigned long OldProtect,NewProtect; char FirstByte,BreakByte = (char)0xCC; unsigned long cbByte; CONTEXT Org_Context,New_Context; //Context DWORD ESP,ESP4 = 0,ESP8 = 0,ESPC = 0,ESP10 = 0; DWORD BufAdr,Len; LPVOID buffer; BOOL FirstHit = FALSE; //Attach시에 생기는 Exception Event 체크 변수 while(TRUE) { WaitForDebugEvent(&DebugEV,INFINITE); //디버그 이벤트를 기다린다. dwContinueStatus = DBG_CONTINUE; switch(DebugEV.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: //프로세스를 처음 Attach 했을떄~ CPDI = DebugEV.u.CreateProcessInfo; //정보를 저장하여 둔다. Wsock_Handle = GetModuleHandle("WS2_32.DLL"); //모듈 핸들을 구한다. if(Wsock_Handle == 0) { Wsock_Handle = LoadLibrary("WS2_32.DLL"); //없을 경우 Load한다. if(Wsock_Handle == 0) break; } Send_Adr = GetProcAddress(Wsock_Handle,"send"); //Send함수의 주소를 구한다. if(Send_Adr == 0) break; VirtualQueryEx(CPDI.hProcess,Send_Adr,&mbi,sizeof(mbi)); //메모리 프로텍트를 구해온다. NewProtect = mbi.Protect; NewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); //재외 시키고~ NewProtect |= (PAGE_READWRITE); //추가 시킨다. VirtualProtectEx(CPDI.hProcess,Send_Adr, //보호 모드 조정 sizeof(char),NewProtect, &OldProtect); ReadProcessMemory(CPDI.hProcess,Send_Adr, //첫바이트를 읽어온다. &FirstByte,sizeof(FirstByte), &cbByte); WriteProcessMemory(CPDI.hProcess,Send_Adr, //EXCEPTION_EVENT(0xCC) 기록 &BreakByte,sizeof(BreakByte), &cbByte); break; case EXCEPTION_DEBUG_EVENT: //예외 디버그 이벤트 발생시 if(DebugEV.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) //EXCEPTION_BREAKPOINT인가? { if(FirstHit == FALSE) FirstHit = TRUE; //첫번쨰 브포 변수 체크 else { WriteProcessMemory(CPDI.hProcess,Send_Adr, //첫바이트를 원래 대로 돌림 &FirstByte,sizeof(FirstByte), &cbByte); Org_Context.ContextFlags = CONTEXT_FULL; //Context Mode GetThreadContext(CPDI.hThread,&Org_Context); //Context를 구해온다. ESP = Org_Context.Esp; //API가 끝나고 리턴(돌아갈) 주소 ESP4 = ESP + 4; //S ESP8 = ESP + 8; //buf Adr ESPC = ESP + 0xC; //len ESP10 = ESP + 0x10; //flag ReadProcessMemory(CPDI.hProcess,(void *)ESPC, //len읽어옴 &Len,sizeof(DWORD), &cbByte); buffer = malloc(Len); //길이 만큼 메모리 할당 ReadProcessMemory(CPDI.hProcess,(void *)ESP8, //Buffer 주소 읽어옴 &BufAdr,sizeof(DWORD), &cbByte); ReadProcessMemory(CPDI.hProcess,(void *)BufAdr, //Buffer 읽어옴 buffer,Len, &cbByte); New_Context = Org_Context; //복사본을 만든다. New_Context.Eip = (unsigned long)DebugEV.u.Exception.ExceptionRecord.ExceptionAddress; //New_Context.Eip--; //Eip값을 -1 시킨다. SetThreadContext(CPDI.hThread,&New_Context); //Context를 적용한다. ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); //대상 프로그램에 결과 반영 /* ReadProcessMemory(CPDI.hProcess,Send_Adr, //첫 바이트를 읽어온다. &FirstByte,sizeof(FirstByte), &cbByte); */ WriteProcessMemory(CPDI.hProcess,Send_Adr, //0xCC를 기록 &BreakByte,sizeof(BreakByte), &cbByte); free(buffer); //동적 할당한 메모리를 놓아준다. MessageBox(NULL,"send함수가 발생하였습니다.","==알림==",64); //시각적 효과 } } else dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED; break; case EXIT_PROCESS_DEBUG_EVENT: //프로세스 종료 디버그 이벤트 일떈 루프 끝 return; } ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); } } //================================================= 에에~ 위에 새롭게 추가 시킨 Code들을 살펴 보면~ :p Context 스트럭쳐의 Esp변수의 값을 ESP라는 변수에 저장시키고~ 나머지 상대 위치들도 세팅 시킨후 :p ReadProcessMemory()함수를 이용하여 len(길이)를 읽어온후 malloc()를 이용하여 Len만큼 크기의 메모리를 할당하고, ReadProcessMemory()함수로 Buffer의 주소를 읽어온후 ReadProcessMemory()함수의 인자로 Buffer의 주소에서 Len(길이)만큼 읽어와서 malloc()으로 할당한 메모리에 저장 시키는 구조가 되겠습니다. :p 여기까지가 Parameter를 몰래 읽어오는 Sniffing입니다. :p 생각보다 Parameter를 몰래 읽어옴으로써 통신의 내용을 감청하는 것도 쉽다고 느껴지지 않나요? :p (지금 buffe에 저장 시킨값을 문자열로 변환 시킨후 txt 파일에 저장시키는 구조를 택한다면 통신한 내용을 몰래 저장 시키는 Logger 종류의 프로그램이 되겠군요? :p) 다음 내용인 내용을 조작하기전에 몇가지 집고 넘어 가도록 하겠습니다~ :p 왜 저는 일반적으로 쓰이는 방법인 임포트 테이블의 점프 위치를 바꾸거나 엑스포트 테이블을 조작하거나 Code overwriting(코드 덮어쓰기)를 통한 방법을 쓰지 않았을 까요? :p 그건 바로 위의 방법 모두 Dll Injection이 필요하기 떄문입니다. (반드시 Dll Injection이 필요한것은 아니지만 WriteProcessMemory()함수로 직접 Routine을 삽입하려 할 경우 어셈블리어에 대한 지식이 필요로 됩니다. :p 본인 같은 경우는 C언어를 접하기 전에 어셈블리어를 먼저 접했기 때문에 상관 없지만 거의 대부분의 분들이 C언어를 먼저 접하고 어셈블리어에 대해 무뇌하기 때문에 골란하다고 할수 있습니다. 그러나 엑스포트 테이블(Export Table)을 조작하는 방법은 본인이 가장 추천하는 방법입니다. 또한 가장 좋아하는 방법이기도 하구요 :p) 임포트 테이블을 조작하는 경우는 문제가 있습니다~! 그것은? 대상 프로그램이 Packing 이나 Encrypting 되어 있을 경우 임포트 테이블을 지우고 LoadLibrary(), GetProcAddress()를 이용한 동적 루틴을 구현해서 사용하기 떄문에 임포트 테이블을 조작하는 방법으론 헛땅만 치게 됩니다. :p (본인의 exe암호화기 1.1b 코드를 참고 하시어도 됩니다~ 거기에 동적 루틴을 담아놨죠 :p) 엑스포트 테이블을 조작하는 방법은 통신함수 후킹에 있어선 정말 좋은 방법이라고 생각합니다. 왜냐하면 WS2_32.DLL 과 WSOCK32.DLL 간에 Export를 해주고 있기 떄문에 둘중의 한 DLL의 엑스포트 테이블을 조작하여 두면 =_= b 좋은(?) 효과를 줄수 있습니다. 그러나 뭐 본인의 현재 글은 Simple을 목적으로 하기에 엑스포트 테이블 조작은 적당하지 않다고 생각하여 쓰지 않았습니다. :p 두번쨰 집고 넘어갈 점은 본인의 글에서 현재까지 소개된 코드의 경우, 대상 프로그램을 후킹하고 있는 동안 내 프로그램 자신은 정지된 상태로 보이게 됩니다. (응답없음 상태라고 하죠 흔히~ :p) 뭐, DebugLoop를 돌고 있기 떄문에 당연한 현상이라고도 볼수 있습니다. 이를 해결하는 아주 간단한 방법을 알려 드리겠습니다. :p 이 방법을 생각하게 된건 1년전 어느 겨울날 이었던듯 싶군요 :p 방법이란 무엇인가 하면...? CreateThread() 라는 API를 이용하는 것입니다. :p (CreateRemoteThread라는 대상 프로그램에 Thread를 생성 시키는 API도 있습니다. :p 이 API역시 API Hooking에 많이 쓰이는 함수인데~ :p 본인의 글에선 다루어지지 않는군요~ T.T 다른 분들의 많은 글에서 얼마든지 볼수 있을테니 슬퍼하지는 마세요~ :p) ============================================ HANDLE CreateThread( LPSECURITY_ATTRIBUTES lpThreadAttributes, //Security SIZE_T dwStackSize, //스택 크기 LPTHREAD_START_ROUTINE lpStartAddress, //함수 시작 주소 LPVOID lpParameter, //페러미터(인자) DWORD dwCreationFlags, //생성 플레그 LPDWORD lpThreadId //값 ); ============================================= 이 API를 이용하면 다중 쓰레드 프로그래밍을 할수 있는데, DebugMsgLoop()를 함수시작 주소로 주면 대상 프로그램을 후킹하는 동안에도 본인의 프로그램도 응답없음 상태가 아닌 =_=b 정상적 작동 상태를 유지 할수 있음을 보실수 있을겁니다. (이에 해당하는 내용은 구지 본인의 코드에 넣고 싶지도 않고, 여러분들도 얼마든지 작성할수 있을 겁니다. :p 왜냐구요? 일반적인 Windows Programming책에서 많이 소개되는 내용이기 때문이죠. :p 사실 이건 변명이고 내부적인 실제요인은 코드를 수정하기가 귀찮아서 입니다. :p) 이제 우리는 Parameter를 읽어오는것,즉 패킷의 내용을 읽어오는것 까지 할수 있게 되었습니다. 이제 지금부터 이것을 조작하는 방법을 알아 보도록 하겠습니다. 내용을 조작하는것은 내용을 읽어오는것 만큼이나 쉽습니다. :p 말로 하는것보단 Code를 먼저 보는게 좋을겁니다. (Code를 봐야 설명가능 :p) //========================================================== void DebugMsgLoop() { DEBUG_EVENT DebugEV; CREATE_PROCESS_DEBUG_INFO CPDI; DWORD dwContinueStatus = DBG_CONTINUE; HMODULE Wsock_Handle; //후킹할 API가 존재하는 모듈의 핸들 저장 변수 LPVOID Send_Adr; //후킹을 원하는 대상 API주소 저장 변수. MEMORY_BASIC_INFORMATION mbi; unsigned long OldProtect,NewProtect; char FirstByte,BreakByte = (char)0xCC; unsigned long cbByte; CONTEXT Org_Context,New_Context; //Context DWORD ESP,ESP4 = 0,ESP8 = 0,ESPC = 0,ESP10 = 0; DWORD BufAdr,Len; LPVOID buffer; BOOL FirstHit = FALSE; //Attach시에 생기는 Exception Event 체크 변수 while(TRUE) { WaitForDebugEvent(&DebugEV,INFINITE); //디버그 이벤트를 기다린다. dwContinueStatus = DBG_CONTINUE; switch(DebugEV.dwDebugEventCode) { case CREATE_PROCESS_DEBUG_EVENT: //프로세스를 처음 Attach 했을떄~ CPDI = DebugEV.u.CreateProcessInfo; //정보를 저장하여 둔다. Wsock_Handle = GetModuleHandle("WS2_32.DLL"); //모듈 핸들을 구한다. if(Wsock_Handle == 0) { Wsock_Handle = LoadLibrary("WS2_32.DLL"); //없을 경우 Load한다. if(Wsock_Handle == 0) break; } Send_Adr = GetProcAddress(Wsock_Handle,"send"); //Send함수의 주소를 구한다. if(Send_Adr == 0) break; VirtualQueryEx(CPDI.hProcess,Send_Adr,&mbi,sizeof(mbi)); //메모리 프로텍트를 구해온다. NewProtect = mbi.Protect; NewProtect &= ~(PAGE_READONLY | PAGE_EXECUTE_READ); //재외 시키고~ NewProtect |= (PAGE_READWRITE); //추가 시킨다. VirtualProtectEx(CPDI.hProcess,Send_Adr, //보호 모드 조정 sizeof(char),NewProtect, &OldProtect); ReadProcessMemory(CPDI.hProcess,Send_Adr, //첫바이트를 읽어온다. &FirstByte,sizeof(FirstByte), &cbByte); WriteProcessMemory(CPDI.hProcess,Send_Adr, //EXCEPTION_EVENT(0xCC) 기록 &BreakByte,sizeof(BreakByte), &cbByte); break; case EXCEPTION_DEBUG_EVENT: //예외 디버그 이벤트 발생시 if(DebugEV.u.Exception.ExceptionRecord.ExceptionCode == EXCEPTION_BREAKPOINT) //EXCEPTION_BREAKPOINT인가? { if(FirstHit == FALSE) FirstHit = TRUE; //첫번쨰 브포 변수 체크 else { WriteProcessMemory(CPDI.hProcess,Send_Adr, //첫바이트를 원래 대로 돌림 &FirstByte,sizeof(FirstByte), &cbByte); Org_Context.ContextFlags = CONTEXT_FULL; //Context Mode GetThreadContext(CPDI.hThread,&Org_Context); //Context를 구해온다. ESP = Org_Context.Esp; //API가 끝나고 리턴(돌아갈) 주소 ESP4 = ESP + 4; //S ESP8 = ESP + 8; //buf Adr ESPC = ESP + 0xC; //len ESP10 = ESP + 0x10; //flag ReadProcessMemory(CPDI.hProcess,(void *)ESPC, //len읽어옴 &Len,sizeof(DWORD), &cbByte); buffer = malloc(Len); //길이 만큼 메모리 할당 ReadProcessMemory(CPDI.hProcess,(void *)ESP8, //Buffer 주소 읽어옴 &BufAdr,sizeof(DWORD), &cbByte) /*ReadProcessMemory(CPDI.hProcess,(void *)BufAdr, //Buffer 읽어옴 buffer,Len, &cbByte); */ memset(buffer,0x90,Len); WriteProcessMemory(CPDI.hProcess,(void *)BufAdr, buffer,Len, &cbByte); New_Context = Org_Context; //복사본을 만든다. New_Context.Eip = (unsigned long)DebugEV.u.Exception.ExceptionRecord.ExceptionAddress; //New_Context.Eip--; //Eip값을 -1 시킨다. SetThreadContext(CPDI.hThread,&New_Context); //Context를 적용한다. ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); //대상 프로그램에 결과 반영 /* ReadProcessMemory(CPDI.hProcess,Send_Adr, //첫 바이트를 읽어온다. &FirstByte,sizeof(FirstByte), &cbByte); */ WriteProcessMemory(CPDI.hProcess,Send_Adr, //0xCC를 기록 &BreakByte,sizeof(BreakByte), &cbByte); free(buffer); //동적 할당한 메모리를 놓아준다. MessageBox(NULL,"send함수가 발생하였습니다.","==알림==",64); //시각적 효과 } } else dwContinueStatus = DBG_EXCEPTION_NOT_HANDLED; break; case EXIT_PROCESS_DEBUG_EVENT: //프로세스 종료 디버그 이벤트 일떈 루프 끝 return; } ContinueDebugEvent(DebugEV.dwProcessId,DebugEV.dwThreadId,dwContinueStatus); } } //================================================= 위의 Code에서 변경된것이라곤 ReadProcessMemory()함수로 buffer이름의 버퍼(Len 만큼의 길이)에 Parameter(패킷의 내용)을 읽어오게 했던것을 주석처리 하고, memset()함수를 이용하여 buffer변수를 0x90로 채우고, WriteProcessMemory()함수로 대상 프로그램에 이값을 써넣었습니다. 실제로 패킷의 내용이 변경되는지 확인하기 위해서 다른 패킷 스니핑 프로그램을 이용하여서 확인한 결과 밑과 같은 결과를 얻을수 있었습니다. //===================================================== 0000 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ................ 0010 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ................ 0020 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 90 ................ 0030 90 90 90 90 90 90 90 90 ........ //===================================================== 즉 성공적으로 Parameter의 내용(패킷의 내용)을 변경시킬수 있다는 것을 알수 있었습니다. 이것은 어떤 패킷이든 무조건 조작하는 것이지만, memcmp함수로 패킷의 고정적인 부분을 비교하여, 원하는 패킷이면 처리하게 변경할수도 있겠습니다. :p 하지만 위에서 재가 쓴 방법대로 라면, 분명 뭔가를 Set(Write)하는 함수의 내용을 조작하였을 경우 성과를 발휘할수 있지만, 뭔가 Get(Read)하는 함수의 내용을 조작하고자 한다면 위의 내용과는 약간 방법이 달라질 필요가 있습니다. 위의 방법에선 Hooking하고자 하는 함수의 작업이 시작되기 전에 내용을 바꾸는 방법이었지만, 뭔가를 Get(Read)하는 함수의 Parameter를 읽어오고자 함은 Hooking의 대상 함수가 실행되고 나서 해야 원하는 값을 얻어올수 있을것입니다. :p 이것의 해결법은 생각외로 아주 간단합니다. :p 위에서 저는 ESP에 Hooking 하고자 하는 함수가 실행한다음 돌아갈 Return Address가 들어있다는 말을 한적이 있습니다. (무슨 뜻인지 머리 좋은 분들은 벌써 알아차리셨겠죠?) Return Address에 0xCC를 적어놓고 기다리면 Hooking하고자 하는 함수의 작업이 모두 끝난후 올바른 내용을 읽어올수 있을것입니다. 이것이 바로 recv함수의 Parameter Hooking의 방법입니다. 재가 적고자 했던 내용은 모두 끝이 났습니다. 글의 끝부분이 흐지부지 해서 죄송합니다. 개인적인 사정으로 자세히 쓸수 없게되어서~ T.T 어쨰든 전체적인 내용은 전달되었으리라 봅니다. 그래도 끝부분인 만큼 지금까지 한 내용을 정리할 필요성이 있다고 생각해서 마지막으로 정리하여 보면, //======================================= ①Hooking 하고자 하는 대상 프로그램(Remote_Server) 지정 ②디버그 메시지 루프를 돌림 ③프로세스 생성 디버그 이벤트가 발생하였을떄 구조체를 채우고 Hooking 하고자 하는 함수의 첫바이트를 대피 시켜 두고 그자리에 0xCC를 적어둠. ④Hooking의 대상(Remote_Server)이 Hooking할 대상 함수를 실행한순간 제어권이 우리 프로세스(Remote_Host)로 제어권이 넘어온다. ⑤원하는 처리를 하여 준다. (내용 읽어오기,내용 조작하기) ⑥Hooking 대상 함수의 첫바이트를 원래대로 되돌려 놓는다. ⑦대상(Remote_Host)에게 함수를 정상적으로 실행하게 한다. ⑧다시 Hooking의 대상 함수의 첫바이트에 0xCC를 심어둔다. //========================================= 정리하여 보니 어떤 방법인지 대충 아시겠나요? :p 이 글을 읽고 있는 분은 분명 자신만의 방법을 만들어 낼것입니다. 재가 이글에서 썻던 방법보다 훨씬 좋은 방법을 말이죠~! :p 내용은 미흡했지만 시간을 조금씩 비워서 쓴글인만큼 흐지부지 하게 끝내는 본인의 마음이 아프군요 T.T 그러나 뭐 보다 좋은 내용을 위해서 이번 글은 여기서 마치도록 하겠습니다. 다음에 써볼 글은 PE파일의 Resource를 조작하는 방법에 대해서 써보고자 합니다. 언제쯤 글을 다시 쓰게 될지는 불확실 하지만 말이죠 :P 다음 글도 읽는 분에게 최대한 이해하기 쉽도록 써보도록 하겠습니다. :p 이 글에서 잘못된 내용이 있는 경우 Dual5651@hotmail.com 으로 알려 주시면 고맙겠다는 말을 남기며 이만 글을 마치겠습니다. 그럼 Good Byte ~ :p P.S: BPM(Break Point at Memory)라는 것을 알고 게십니까? 간단히 정의하여 보면 메모리의 내용이 변경되었을떄 어디에서 변경하였는지 알수 있는 기술이라고 하겠습니다. 예를 들면, main(){ int a; a = 11; //---(1) } 라는 Code가 있을떄 a의 값을 변경시키는 명령의 위치는 (1)이 됩니다. 이것을 외부 프로그램에서 알아내는 방법이 있습니다. 그것이 바로 BPM이죠. :p 쓰이는 API는.. VirtualProtectEx() 함수로 a의 메모리 속성을 READ_ONLY로 바꾸어 놓습니다. 그렇다면 (1)에서 a에 값을 써넣으려 했을떄 EXCEPTION_DEBUG_EVENT가 발생할것입니다. 이떄의, DebugEV.u.Exception.ExceptionRecord.ExceptionAddress는 (1)이 되는 것입니다. 무슨 말인지 아시겠나요? :p 반대로 a의 값을 읽어가는 경우도 메모리 속성을 WRITE_ONLY따위로 바꾸어 둔다면 처리가 가능할것입니다. (물런 원하는 결과를 얻은후엔 메모리 속성을 원래되로 돌려놓는 센스가 필요합니다. :p) 출처: http://dualpage.muz.ro/

'Application > Debug' 카테고리의 다른 글

[해킹] What a W.P.E?  (0) 2006.06.30
[해킹] Inside Of Tsearch  (0) 2006.06.30
[해킹] Icon Grabber 2.1v Crack With DeDe  (0) 2006.06.30
[해킹] My Diary Key Crack with IDA  (2) 2006.06.30
[해킹] 010Editor Crack With StringRef  (0) 2006.06.30