Reversing.kr WindowKernel 상세분석2

2018. 5. 5. 04:370x02 Reverse Engineer/0x02. Reversing.kr

728x90

잡담 : 첫 번째 바이너리에서 플래그 획득에 대해서는 별다른 소득이 없었었다. 

그렇다면, sys파일이 중요하다는 것이라는 것을 알게 되었다. ( 이까지 알게 된건 몇개월 전이지만, 코드 분석을 완벽하게 하지 않아서 이때까지 풀지 못하였었다.) 


이전글에서 적었던걸로 기억나지만, 한번 더 기술해본다. SYS파일의 엔트리는 DriverEntry이다.

그리고 __security_init_cookie는 윈도우즈 환경에서의 BOF 방지에 사용되는 카나리이며, 

이 카나리는 4바이트 이상의 배열에서만 동작하게 되며, 함수프롤로그에서 ebp와 xor를 취하여 백업을 시켜둔뒤, 함수 에필로그에서 검증을 하는 것으로만 알고 있었는데, 직접 들어가보니 무조건 xor ebp는 아닌 것을 알게 되었다. 




int __security_init_cookie()

{

  unsigned int v0; // eax

  int result; // eax

                                                // BufCheckParameter는 .data섹션

  v0 = BugCheckParameter2;

  if ( !BugCheckParameter2 || BugCheckParameter2 == 0xBB40E64E )

  {

    v0 = (unsigned int)&BugCheckParameter2 ^ KeTickCount.LowPart;// KeTickCount는 .idata섹션

    BugCheckParameter2 = (unsigned int)&BugCheckParameter2 ^ KeTickCount.LowPart;

    if ( &BugCheckParameter2 == (ULONG_PTR *)KeTickCount.LowPart )

    {

      v0 = 0xBB40E64E;

      BugCheckParameter2 = 0xBB40E64E;

    }

  }

  result = ~v0;

  BugCheckParameter3 = result;

  return result;

}


다음으로 본 함수는 이거다.

이 함수는 조사해보니까 Dpc 오브젝트를 초기화 해주는 함수라고 한다. 


DPC Object는 또 무엇인가?

ezbeat님의 문서를 참조하였다.


ref: http://ezbeat.tistory.com/296


DPC 루틴은 디바이스 드라이버를 만들 때 자주 사용된다고 한다.

아직 디바이스 드라이버 코딩을 접하지 않아서 몰랐던 것 같다 ^^ 

자세하게 공부하실 분은 ezbeat님의 블로그를 링크해두었으니 보시면 될 것 같다. 


GetCurrentProcessNumber까지는 무슨 기능인지 딱 봐도 알 것 같았는데 Ke라는 접두어는 본적이 없고,

HalGetInterruptVector역시 수행 되는 기능은 게씽이 가능하였지만, Hal 접두어를 본적이 없었다. 



접두어를 공부해보았다.

Ke를 공부하는 김에 다른 접두어도 한번 살펴보아서 적어둔다.

짱해커가 된다면, 언젠가 다 눈으로 볼 수 있는 날이 오겠지.. 

Ke : Kernel core

Cm: Configuration manager

Ex : Executive 

Hal : Hardware Abstraction Layer

IO : I/O Manager

Mm : Memory manager 

Ob : Object manager

Po : Power manager

Tm : Transaction manager

Zt & Zw : Native system services



void __stdcall DeferredRoutine(struct _KDPC *Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)

{

  int v4; // esi

  unsigned __int8 v5; // al

  int v6; // eax

  int v7; // ecx

  char v8[6]; // [esp+4h] [ebp-10h]

  KAFFINITY Affinity; // [esp+Ch] [ebp-8h]

  KIRQL Irql; // [esp+13h] [ebp-1h]

// Ke : Kernel core

// Hal : Hardware abstraction Layer

  v4 = KeGetCurrentProcessorNumber();

  v5 = HalGetInterruptVector(Isa, 0, 1u, 1u, &Irql, &Affinity);

  __sidt(v8);

  v6 = *(unsigned __int16 *)(*(_DWORD *)&v8[2] + 8 * v5) | (*(unsigned __int16 *)(*(_DWORD *)&v8[2] + 8 * v5 + 6) << 16);

  v7 = v6 - 88;

  if ( MajorVersion < 6 ) // 느낌상 윈도우 커널 버전이지 않을까 한다. 위의 수식이 도무지 해석이 되지 않는다.

    v7 = v6 - 60;

  if ( *((_DWORD *)P + v4) )

  {

    _disable();

    *(_DWORD *)(v7 + 12) = *((_DWORD *)P + v4);

    _enable();

  }

}


이 함수를 보게 되면, Custom DPC를 DPC 큐에 넣는 작업을 한다는 것을 알 수 있다고 한다.

이 역시 ezbeat님의 블로그를 참조하여 알게 되었다.

int __usercall sub_1108C@<eax>(int a1@<edx>, int a2@<ecx>, int a3@<ebp>)

{

  int v3; // ST14_4

  int v4; // ST18_4

  unsigned int v5; // et0


  v4 = a2;

  v3 = a1;

  v5 = __readeflags();

  KeInsertQueueDpc((PRKDPC)(*(_DWORD *)SystemArgument2 + 116), 0, SystemArgument2);

  *(_DWORD *)(a3 - 4) = KeGetCurrentProcessorNumber();

  dword_13014 = (int (__fastcall *)(_DWORD, _DWORD))*((_DWORD *)P + *(_DWORD *)(a3 - 4));

  __writeeflags(v5);

  return dword_13014(v4, v3);

}


이 부분부터 문제를 푸는 사람들은 유심히 보아야 하는 구간이다.


함수를 그대로 해석해보면 PORT를 통해 UCHAR 데이터형을 읽어오고 그 반환 값을 TARGET에 대입한다는 것이다. 

포트 0X60은 출력버퍼이다.

void __stdcall sub_11266(struct _KDPC *Dpc, PVOID DeferredContext, PVOID SystemArgument1, PVOID SystemArgument2)

{

  char target; // al


  target = READ_PORT_UCHAR((PUCHAR)0x60);

  sub_111DC(target);

}


그 이상의 함수들은 문제에 직접적으로 연관이 있기 때문에 작성하지는 않으려고 한다. 


이 문제에서 가장 크게 얻을 수 있는 정보는 스캔코드(scancode)이다. 


나 같은 경우는 운영체제 공부를 잠깐 할 때 스캔코드에 대해 공부를 한적이 있었는데 생각이 나지 않아 다시 책을 보고 공부해야겠다.  비록 이 문제만 인증하려면 정확한 원리는 몰라도 된다. 하지만, 원리를 알고 넘어가는게 더 재밌을거라 생각한다. 원리는 따로 적지 않겠다. 나도 복습을 해야하기 때문이다. 


출처 : 64비트 멀티코어 os원리와 구조

스캔코드(Scan Code)

키보드는 키가 눌리거나(KEYDOWN) 떨어질 때 마다 (KEYUP) 키 별로 할당된 특수한 값을 키보드 컨트롤러로 전달하며,

이 값을 스캔코드라고 한다. 


별다른 명령을 키보드 컨트롤러로 보내지 않으면, 키보드 컨트롤러의 출력 버퍼에는 키보드 또는 마우스에서 수신된 데이터가 저장되게 된다.

따라서 상태 레지스터를 읽어서 출력 버퍼에 데이터가 있는지 확인한 후, 데이터가 있다면 출력버퍼를 읽어서 저장하면 된다. 


예시 : "키보드 컨트롤에서 키 값을 읽는 코드"

Reversing.kr WindowKernel에서도 이와 비슷한 행위를 하는 코드를 볼 수 있을 것이다. 

BYTE kGetKeyboardScanCode( void )

{

    // 출력 버퍼(Port 0x60)에 데이터가 있을 때까지 대기 

    while ( kIsOutputBufferFull() == FALSE) // 출력 버퍼의 상태를 확인하여 데이터가 차 있을 경우 true. 비어있다면 false 

    {

       .......

    }


    return kInPortByte( 0x60 ) ;   


이상으로 플래그와는 연관이 없지만, 짧게 나마 구조에 대해 공부하는 시간을 끝내본다. 

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

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