Reversing.kr WindowKernel 상세분석

2018. 5. 5. 03:070x02 Reverse Engineer/0x02. Reversing.kr

728x90

WindowsKernel 문제를 풀기전 삽질했던 거에 대해서 적어보려한다. 


우선 WindowsKernel 압축을 해제하면, 두개의 파일이 떨어진다.

WindowKernel.exe

Winker.sys


WindowKernel.exe에 Winker.sys가 dll로 쓰이게 된다.

DLL과 sys는 파일구조는 둘다 PE포맷이다.




하지만, 조금 다르다. 


sys파일은 디바이스 드라이버로 동작하며, 엔트리 포인트가 DriverEntry로 시작된다. 

그리고 DeviceAttach, DeviceRemove, Deviceloctl, Read, Write 등의 OS에서 장치를 제어하기 위한 인터페이스 함수를 구현한다. 디바이스 드라이버이기 때문에 커널모드에서 수행되고, 모든 프로그램에 동일한 인스턴스를 보여준다.

응용프로그램은 직접 sys파일에 Call을 할수가 없다. 


DLL파일은 컴퓨터 내에서 실행되고 있는 프로그램에서 필요에 의해 호출될 수 있다. 

DLL파일은 램에 적재되지 않기 때문에 램 공간을 절약할 수 있다.


우선 exe파일 부터 분석을 해보자.

몰랐던 부분을 엄청나게 많이 알게 되는 시간이었다. 훗날, 커널모드에서 동작하는 악성코드를 분석하게 되더라도 도움이 될 듯하다. (예를 들어 루트킷) 


해당 프로그램이 GUI기반이기 때문에 Winmain이 메인함수임을 확인하였다.


int __stdcall WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)

{

  DialogBoxParamW(hInstance, (LPCWSTR)0x65, 0, DialogFunc, 0);

  return 0;

}



메인함수에서 DialogBoxParamW를 호출하는데 여기서 대화상자를 만들게 된다.


다음으로 볼 함수는 DialogFunc이다.


BOOL __stdcall DialogFunc(HWND hWnd, UINT flag, WPARAM isConnectService, LPARAM a4)

{

  if ( flag == 272 )

  {

    SetDlgItemTextW(hWnd, 1001, L"Wait.. ");

    SetTimer(hWnd, 0x464u, 0x3E8u, 0);

    return 1;

  }

  if ( flag != 273 )

  {

    if ( flag == 275 )

    {

      KillTimer(hWnd, 0x464u);

      RelativeSysFile(hWnd);

      return 1;

    }

    return 0;

  }

  if ( (unsigned __int16)isConnectService == 2 )

  {

    SetDlgItemTextW(hWnd, 1001, L"Wait.. ");

    IsSCManager();

    EndDialog(hWnd, 2);

    return 1;

  }

  if ( (unsigned __int16)isConnectService == 1002 )

  {

    if ( isConnectService >> 16 == 1024 )

    {

      Sleep(0x1F4u);

      return 1;

    }

    return 1;

  }

  if ( (unsigned __int16)isConnectService != 1003 )

    return 0;

  IsCorrect(hWnd);

  return 1;

}

Wait... 부분은 해당 바이너리의 윈도우에 직접 텍스트로 입력되는 부분이니 분석을 굳이 할 필요는 없다.


바로 이 부분이다. 굳이 분석할 필요가 없다 ~ 


분석해야 할 부분은 Wait 다음에 수행되는 행위들이다. 


나는 우선 Windows 10 환경에서 바이너리를 실행하였는데, Windows10에서는 드라이버를 설치할 때 단순히 사용자 권한 말고 개발자 서명도 필요하다고 한다. 그래서 실행했을 때 바이너리가 올바르게 실행되지 않았던 것이다.


시간날 때 윈도우즈 10 이하 환경에서 돌려봐야 겠다. 


지금 내 환경에서는 드라이버를 설치하지 못하기 때문에 이러한 화면이 뜨게 된다.




그래도 분석은 멈추지 않는다. 우리는 디버거를 통해 바이너리를 실행하지 않고도 문제를 풀 수 있기 때문이다. 

하지만, 여기서는 구체적인 라이트업은 작성하지 않을것이다.  

이 문서의 목적은 오로지 해당 바이너리의 구조 파악이기 때문이다 .


int __usercall RelativeSysFile@<eax>(HWND a1@<ebx>)

{

  SC_HANDLE v1; // edi

  SC_HANDLE v2; // esi

  WCHAR Filename; // [esp+8h] [ebp-404h]

  char Dst; // [esp+Ah] [ebp-402h]


  Filename = 0;

  memset(&Dst, 0, 0x3FEu);

  GetModuleFileNameW(0, &Filename, 0x200u);

  PathRemoveFileSpecW(&Filename);

  lstrcatW(&Filename, L"\\");

  lstrcatW(&Filename, L"WinKer.sys");

  v1 = OpenSCManagerW(0, 0, 0xF003Fu);

  if ( !v1 )

  {

    SetDlgItemTextW(a1, 1001, L"[Error] OpenSCManager");

    return 0;

  }

  v2 = CreateServiceW(v1, L"WinKer", L"WinKer", 0xF01FFu, 1u, 3u, 1u, &Filename, 0, 0, 0, 0, 0);

  if ( !v2 )

  {

    v2 = OpenServiceW(v1, L"WinKer", 0xF01FFu);

    if ( !v2 )

    {

      CloseServiceHandle(v1);

      SetDlgItemTextW(a1, 1001, L"[Error] OpenService");

      return 0;

    }

  }

  if ( StartServiceW(v2, 0, 0) )

  {

    CloseServiceHandle(v1);

    CloseServiceHandle(v2);

    SetDlgItemTextW(a1, 1001, L"Analyze Me,! :> Hint) Keyboard");

    return 0;

  }

  if ( GetLastError() == 1056 )

  {

    SetDlgItemTextW(a1, 1001, L"[Error] StartService - Service Already Running");

  }

  else if ( GetLastError() == 2 )

  {

    SetDlgItemTextW(a1, 1001, L"[Error] StartService - File Not Found");

  }

  else

  {

    SetDlgItemTextW(a1, 1001, L"[Error] StartService");

  }

  CloseServiceHandle(v1);

  CloseServiceHandle(v2);

  return 0;

}


GetModuleFileName : 현재 실행 경로를 얻을 수 있는 함수로써 악성코드에서도 즐겨 쓰이는 녀석이다.




szEXEPath의 주소는 0x007AE894로 출력되었다. 이를 메모리 덤프에서 확인하면 현재 실행된 바이너리의 절대 경로가 보임을 확인하였다.




이 함수의 결과를 보면 반환값이 eax에 들어가게 되는데 EAX에는 절대경로의 길이가 들어감을 볼 수 있다.



그리고 추가적으로 모든 EXE바이너리는 메인함수에 접근하기 전에 절대경로를 확인하게 된다. 

우리가 마우스로 파일을 클릭했을 때 그 파일이 정상적으로 실행되는 이유라고도 생각한다. 

길을 찾지 못하면 목적지로 갈 수 없듯. 윈도우즈가 해당 파일이 들어있는 주소를 모르면 실행시킬 수 없을 것이다. 



 PathRemoveFileSpecW : 파일명이 포함 된 경로에서 파일명을 제외한 path만을 남겨준다.



PathRemoveFileSpec과 같은함수는 shlwapi.h를 선언해야하며, #pragma comment(lib, "shlwapi.lib")를 추가하거나 

프로젝트 -> 링커 -> 입력 -> 추가종속성에 추가해줘야 한다. 


이 바이너리에서는 경로에서 파일명을 제외한 다음 strcat을 사용하여 Filename에 Winker.sys를 추가해준다.

  lstrcatW(&Filename, L"\\");

  lstrcatW(&Filename, L"WinKer.sys");


OpenSCManager는 서비스를 실행하는 함수인데 연결이 되지 않았을 때는 NULL이다. 

첫번째 인자가 0이라면 호스트를 뜻한다. 두번째는 서비스DB, 세번째는 권한이라고 한다. 


서비스를 실행하였다면, 이번엔 Winker라는 서비스를 생성하게 되고, 이 역시 성공하게 되면, Winker를 실행하게 된다. 

 v1 = OpenSCManagerW(0, 0, 0xF003Fu);

  if ( !v1 )

  {

    SetDlgItemTextW(a1, 1001, L"[Error] OpenSCManager");

    return 0;

  }

  v2 = CreateServiceW(v1, L"WinKer", L"WinKer", 0xF01FFu, 1u, 3u, 1u, &Filename, 0, 0, 0, 0, 0);

  if ( !v2 )

  {

    v2 = OpenServiceW(v1, L"WinKer", 0xF01FFu);

    if ( !v2 )

    {

      CloseServiceHandle(v1);

      SetDlgItemTextW(a1, 1001, L"[Error] OpenService");

      return 0;

    }

  }


현재 환경에서는 서비스가 실행되지 않으므로 [Error] OpenSCManager가 떴음을 확인하였다.


역할이 끝나게 되면 Close 하게 된다.

CloseServiceHandle(v1);

CloseServiceHandle(v2);


다음으로 볼 함수는 아까와 거의 비슷한 루틴이라 추가 설명은 하지 않겠다.

SC_HANDLE IsSCManager()

{

  SC_HANDLE result; // eax

  void *v1; // edi

  SC_HANDLE v2; // esi

  void (__stdcall *v3)(SC_HANDLE); // edi

  BOOL v4; // eax

  void *v5; // [esp-8h] [ebp-2Ch]

  struct _SERVICE_STATUS ServiceStatus; // [esp+4h] [ebp-20h]


  result = OpenSCManagerW(0, 0, 0xF003Fu);

  v1 = result;

  if ( result )

  {

    v2 = OpenServiceW(result, L"WinKer", 0xF01FFu);

    if ( !v2 )

    {

      CloseServiceHandle(v1);

      return 0;

    }

    if ( !ControlService(v2, 1u, &ServiceStatus) )

    {

      v5 = v1;

      v3 = (void (__stdcall *)(SC_HANDLE))CloseServiceHandle;

LABEL_6:

      v3(v5);

      v3(v2);

      return 0;

    }

    v4 = DeleteService(v2);

    v5 = v1;

    v3 = (void (__stdcall *)(SC_HANDLE))CloseServiceHandle;

    if ( !v4 )

      goto LABEL_6;

    CloseServiceHandle(v5);

    CloseServiceHandle(v2);

    result = (SC_HANDLE)1;

  }

  return result;

}


다음으로 정답인지 아닌지 분별하는 함수이다.

HWND __thiscall IsCorrect(HWND hDlg)

{

  HWND v1; // edi

  HWND result; // eax

  HWND v3; // eax

  HWND v4; // eax

  HWND v5; // eax

  WCHAR String; // [esp+8h] [ebp-204h]


  v1 = hDlg;

  GetDlgItemTextW(hDlg, 1003, &String, 512);

  if ( lstrcmpW(&String, L"Enable") )

  {

    result = (HWND)lstrcmpW(&String, L"Check");

    if ( !result )

    {

      if ( IsDeviceConnect(v1, 0x2000u) == 1 )

        MessageBoxW(v1, L"Correct!", L"Reversing.Kr", 0x40u);

      else

        MessageBoxW(v1, L"Wrong", L"Reversing.Kr", 0x10u);

      SetDlgItemTextW(v1, 1002, &word_4021F0);

      v5 = GetDlgItem(v1, 1002);

      EnableWindow(v5, 0);

      result = (HWND)SetDlgItemTextW(v1, 1003, L"Enable");

    }

  }

  else if ( IsDeviceConnect(v1, 0x1000u) )

  {

    v3 = GetDlgItem(v1, 1002);

    EnableWindow(v3, 1);

    SetDlgItemTextW(v1, 1003, L"Check");

    SetDlgItemTextW(v1, 1002, &word_4021F0);

    v4 = GetDlgItem(v1, 1002);

    result = SetFocus(v4);

  }

  else

  {

    result = (HWND)MessageBoxW(v1, L"Device Error", L"Reversing.Kr", 0x10u);

  }

  return result;

}

  GetDlgItemTextW 함수는 Win32API 키젠 바이너리에서 필수적으로 분석을 해야하는 녀석 중 하나이다. 

나는 대체로 WIN32API 문제가 나오게 되면 GetDlgItemText부터 breakpoint걸고 하는 편이다. 


IsDeviceConnect 함수 RevKr 파일을 생성하려한다. 

그리고 DeviceIoControl에도 접근해본다. 

int __usercall IsDeviceConnect@<eax>(HWND a1@<edi>, DWORD dwIoControlCode)

{

  HANDLE v2; // esi

  int result; // eax

  DWORD BytesReturned; // [esp+4h] [ebp-8h]

  int OutBuffer; // [esp+8h] [ebp-4h]


  v2 = CreateFileW(L"\\\\.\\RevKr", 0xC0000000, 0, 0, 3u, 0, 0);

  if ( v2 == (HANDLE)-1 )

  {

    MessageBoxW(a1, L"[Error] CreateFile", L"Reversing.Kr", 0x10u);

    result = 0;

  }

  else if ( DeviceIoControl(v2, dwIoControlCode, 0, 0, &OutBuffer, 4u, &BytesReturned, 0) )

  {

    CloseHandle(v2);

    result = OutBuffer;

  }

  else

  {

    MessageBoxW(a1, L"[Error] DeviceIoControl", L"Reversing.Kr", 0x10u);

    result = 0;

  }

  return result;

}


지금까지 분석을 한 것은 EXE파일이다. 여기까지 분석을 했을 때 딱히 문제 풀이에 도움이 되는 부분은 있지 않았었다. 

하지만, 몇개의 함수는 복습을 하였고, 몇개의 함수는 새로 익히는 시간이 되었다. 


'0x02 Reverse Engineer > 0x02. Reversing.kr' 카테고리의 다른 글

HateIntel  (0) 2018.05.27
Reversing.kr WindowKernel 상세분석2  (0) 2018.05.05
WindowsKernel  (0) 2018.05.05
Winkernel 문제 풀기 전 공부한 사항  (0) 2018.05.04
ImagePrc  (0) 2018.05.04