'IDA'에 해당되는 글 1건

  1. 2009/01/19 IDA에 HexRay를 사용할 때의 주의점 - lain32
HexRay는 IDA플러그인으로써 지금까지 디스어셈블리 된 코드로만 리버싱을 했어야 했는데 HexRay에 의해서 C언어로 코드를 볼 수 있게 되었습니다.

거기다가 디컴파일 된 코드를 그대로 옮긴 후 컴파일을 하면 실제로 돌아가는 경우도 많이 있어서 상당히 대단하긴 한데 만약 특정 타켓 프로그램을 완전히 리버싱해야 할 경우 ( 어떠한 프로그램에 기능을 내 프로그램에서 쓰고 싶을 경우 ) 디컴파일에 많은 부분에 의존하여 해당 코드를 자신의 프로그램으로 옮기게 되는데 이 때 다음과 같은 부분에 주의해야 합니다.

다음은 SCSI Miniport 드라이버 코드를 디컴파일 한 것입니다. ( 시디스페이스 아님..-_-)

void __stdcall sub_10546(PVOID HwDeviceExtension, int a2)
{
  int v2; // ebx@1
  unsigned __int8 v3; // al@7
  PVOID v4; // edi@7
  char v5; // zf@13
  signed int v6; // ST04_4@15
  void *v7; // ST08_4@15
  char v8; // ST0C_1@15
  ULONG v9; // ecx@16
  char v10; // al@25
  int v11; // edx@26
  int v12; // ecx@26
  int v13; // ecx@11
  int v14; // ecx@12
  int v15; // ecx@21
  int v16; // ecx@22
  int v17; // edx@26
  unsigned __int8 v18; // [sp+5h] [bp-67h]@17
  int v19; // [sp+Ch] [bp-60h]@17
  char v20; // [sp+4h] [bp-68h]@17
  int v21; // [sp+10h] [bp-5Ch]@17
  int v22; // [sp+14h] [bp-58h]@17
  int v23; // [sp+18h] [bp-54h]@17
  int v24; // [sp+1Ch] [bp-50h]@17
  int v25; // [sp+20h] [bp-4Ch]@17
  signed int v26; // [sp+24h] [bp-48h]@17
  signed int v27; // [sp+64h] [bp-8h]@18
  signed int v28; // [sp+68h] [bp-4h]@18

  v2 = a2;
  if ( !*(_BYTE *)(a2 + 5) )
  {
    if ( *(_BYTE *)(a2 + 6) )
    {
      LOBYTE(a2) = 33;
      goto LABEL_32;
    }
    if ( *(_BYTE *)(a2 + 7) )
    {
      LOBYTE(a2) = 32;
      goto LABEL_32;
    }
    v4 = ScsiPortGetLogicalUnit(HwDeviceExtension, 0, 0, 0);
    v3 = *(_BYTE *)(v2 + 48);
    if ( (signed int)v3 > 37 )
    {
      if ( v3 != 40 )
      {
        if ( v3 != 42 )
        {
          v15 = v3 - 47;
          if ( v15 )
          {
            v16 = v3 - 53;
            if ( !v16 )
              goto LABEL_28;
            v5 = v3 == 59;
LABEL_24:
            if ( !v5 )
              goto LABEL_25;
LABEL_28:
            LOBYTE(a2) = 1;
            goto LABEL_32;
          }
        }
      }
      BYTE3(v17) = 0;
      *(_WORD *)((char *)&v17 + 1) = *(_BYTE *)(v2 + 50);
      LOBYTE(v17) = *(_BYTE *)(v2 + 51);
      v11 = *(_BYTE *)(v2 + 53) | ((*(_BYTE *)(v2 + 52) | (v17 << 8)) << 8);
      BYTE3(v12) = 0;
      *(_WORD *)((char *)&v12 + 1) = *(_BYTE *)(v2 + 55);
      LOBYTE(v12) = *(_BYTE *)(v2 + 56);
      if ( (unsigned int)(v12 + v11) > 0x4000 )
        goto LABEL_25;
      if ( v3 == 47 )
        goto LABEL_28;
      v9 = v12 << 9;
      v8 = v3 == 40;
      v7 = (char *)v4 + 512 * v11;
    }
    else
    {
      if ( v3 == 37 )
      {
        v8 = 1;
        v7 = &v27;
        v27 = -12648448;
        v28 = 131072;
        v6 = 8;
      }
      else
      {
        if ( !v3 )
          goto LABEL_28;
        if ( v3 == 18 )
        {
          memset(&v18, 0, 0x5Fu);




첫 번째로 바로 위에 부분 memset이 있는데 v18이라는 변수는
unsigned __int8 v18; // [sp+5h] [bp-67h]@17
이와 같이 선언되어 있습니다.

당연히 memset을 할 경우 1바이트 짜리 변수이므로 에러가 발생하게 됩니다.
사 실 보시다시피 [sp+5h]로 되어 있는데 v18은 1byte 짜리 변수가 아니라 스택의 0x5F 크기의 배열로 선언되어 있는 변수일 가능성이 높습니다. 또한 v18밑에 선언 된 변수들은 각각의 변수들이 아니라 v18에 포함되는 변수입니다.

다시 한 번 본다면..

  unsigned __int8 v18; // [sp+5h] [bp-67h]@17
  int v19; // [sp+Ch] [bp-60h]@17
  char v20; // [sp+4h] [bp-68h]@17
  int v21; // [sp+10h] [bp-5Ch]@17
  int v22; // [sp+14h] [bp-58h]@17
  int v23; // [sp+18h] [bp-54h]@17
  int v24; // [sp+1Ch] [bp-50h]@17
  int v25; // [sp+20h] [bp-4Ch]@17
  signed int v26; // [sp+24h] [bp-48h]@17
  signed int v27; // [sp+64h] [bp-8h]@18
  signed int v28; // [sp+68h] [bp-4h]@18

변수의 선언은 틀리지만 뒤에 주석이 처리 된 [sp+5h], [sp+Ch].......는 맞을 가능성이 높습니다.
하지만 실제로 저렇게 변수를 선언한다고 해서 WDK 컴파일러에서 저런 스택을 만들지도 확실하지 않으므로 만약 v18에 경우에는
배열로 선언되어야 할 것입니다.
위에 상황과 프로그래머들이 작성하는 코드 스타일을 본다면

BYTE pBuffer[0x5F];
......
memset(pBuffer, 0, sizeof(pBuffer)); 가 원본 코드일 가능성이 높습니다. ( 물론 배열이 아니라 구조체일가능성도 있겠죠.. 그러한 부분은 버퍼를 어떻게 사용하느냐에 따라 다른것이고.. )



두 번째로... Signed와 Unsigned 와 관련해서 문제가 있을 수 있습니다.

위에 코드에서..

      BYTE3(v17) = 0;
      *(_WORD *)((char *)&v17 + 1) = *(_BYTE *)(v2 + 50);
      LOBYTE(v17) = *(_BYTE *)(v2 + 51);
      v11 = *(_BYTE *)(v2 + 53) | ((*(_BYTE *)(v2 + 52) | (v17 << 8)) << 8);
      BYTE3(v12) = 0;
      *(_WORD *)((char *)&v12 + 1) = *(_BYTE *)(v2 + 55);
      LOBYTE(v12) = *(_BYTE *)(v2 + 56);
      if ( (unsigned int)(v12 + v11) > 0x4000


이 부분은 밑에 디스어셈블리 코드와 같습니다.

.text:00010678                 xor     edx, edx
.text:0001067A                 mov     dh, [ebx+32h]
.text:0001067D                 mov     dl, [ebx+33h]
.text:00010680                 shl     edx, 8
.text:00010683                 or      edx, ecx
.text:00010685                 movzx   ecx, byte ptr [ebx+35h]
.text:00010689                 shl     edx, 8
.text:0001068C                 or      edx, ecx
.text:0001068E                 xor     ecx, ecx
.text:00010690                 mov     ch, [ebx+37h]
.text:00010693                 mov     cl, [ebx+38h]
.text:00010696                 mov     esi, ecx
.text:00010698                 lea     ecx, [esi+edx]
.text:0001069B                 cmp     ecx, 4000h

그리고 디컴파일 된 코드에서는 형변환을 다음과 같이 하고 있습니다.
*(_BYTE *)

_BYTE는 IDA에 HexRay에서 정의한 것인데 plugins\defs.h 파일에 정의되어 있으며

#define _BYTE  char

위와 같이 선언되어 있습니다.

즉, *(_BYTE *) = *(char *) 입니다.
이것은 Sigined입니다.
그러나 디스어셈블리 된 코드에서는 일부 데이터를 이동 시 movzx 명령어를 사용하고 있습니다.
movzx명령어는 부호가 없는 Unsigined를 처리하므로 이러한 경우 문제가 발생합니다.

실제로 저 코드를 WDK로 컴파일 후 WinDbg로 디스어셈블링 해보면
movsx로 변환됩니다. 따라서 잘 못 된 결과를 초래할 수 있습니다.

따라서 저런 경우에는 강제적으로 *(unsigned char *)로 바꿔서 컴파일해야 합니다.





세번째로 간혹 디컴파일을 하게 되면 콜링 컨벤션이 제대로 안 나오는 경우가 있는데 다음과 같은 경우입니다.

char __userpurge sub_10486<al>(int a1<esi>, char a2)

이 러한 경우에는 stdcall 도 아니고 fastcall도 아니고 사용자 지정 콜링 컨벤션이므로 레지스터를 이용해 넘기는건지 어떻게 파라메터를 전달해야 하는지 봐야합니다. 일단 대충 봐서는 esi레지스터를 사용해서 넘긴다는 것인데 웃긴건 함수호출하는 부분에서도 esi레지스터에 어떤값이 들어갈지도 신경써야 한다는 겁니다. 즉, 디컴파일을 하면서 디스어셈블리 된 코드도 자세하게 봐야 된다는거죠....


하여튼 저같은 경우에 다른 드라이버를 리버싱할 경우 이런적이 있었습니다.
적어도 두번째에 경우인 movsx, movzx에 따라서 형변환 관련 디컴파일 부분은 버그로 간주해야 될꺼 같습니다.

HexRay 플러그인에 너무 기대는 부분도 있지만 그만큼 가치가 있는 플러그인입니다.
이올린에 북마크하기(0) 이올린에 추천하기(0)
2009/01/19 19:46 2009/01/19 19:46