[原创]crackme第5题

谖草 2017-6-10 16:26 634

1. 因为题目中释放了驱动文件,所以首先,把驱动文件拿到,在od中对DeleteFile设置断点,在目录下取到驱动文件:  vmxdrv.sys

2. 应用程序与驱动通信,在ida中查看应用程序的主要调用逻辑如下(对应地址为sub_401760):

   ...
   ...
  if ( *(v8 - 8) != 6 || IsDebuggerPresent() )
  {
    CString::operator=((v1 + 108), byte_431398);
    CString::operator=((v1 + 104), byte_431398);
    sub_41A4F7(0);
  }
  else
  {
   if ( *(v1 + 0x19) )
    {
      v6 = *(v8 - 8);
      v3 = sub_418263(&v8, 0);
      CallSysMd5_401D50(v1, v3, v6);
    }
    else
    {
      PostMessageA(*(v1 + 7), 0x12u, 0, 0);
    }
    v6 = &v9;
    v5 = &v9;
    v11 = &v5;
    sub_417D43(&v5, v1 + 0x17);
    MD5_401920(v5, v6);
    sub_415A78(&v9, &v10, 2, 0xAu);
    v14 = 2;
    sub_4182FA(&v10);
    CString::operator=((v1 + 108), byte_431398);
    CString::operator=((v1 + 104), byte_431398);
    sub_41A4F7(0);
    if ( _mbsicmp(v9, a888aeda4ab) )
    {
      CString::operator=((v1 + 108), byte_431398);
      CString::operator=((v1 + 104), byte_431398);
      sub_41A4F7(0);
    }
    ...
    ...

上面的md5计算过程由peid检测后和调试分析得出,同时可见输入长度需要为6。

3. 调试发现程序被反调试检测到,然后退出,接着分析驱动代码,驱动代码放到ida静态分析即可,看到对进程信息的处理过程:

PEPROCESS anti_10486()
{
  PEPROCESS result; // eax@1
  struct _EPROCESS *v1; // edx@1
  result = IoGetCurrentProcess();
  v1 = result;
  while ( result != (PEPROCESS)dword_114E0 )
  {
    result = (PEPROCESS)(*((_DWORD *)result + 0x22) - 0x88);
    if ( result == v1 )
      return result;
  }
  *((_DWORD *)result + 0x2F) = 0;
  return result;
}

进一步调试需要对该处代码,屏蔽,但修改是需要注意驱动中存储了状态:

int __stdcall sub_1071A(int a1, PIRP Irp)
{
  switch ( *(_DWORD *)(Irp->Tail.Overlay.PacketType + 12) )
  {
    case 0x222004:
      checked = 1;
      dword_114E0 = (int)IoGetCurrentProcess();
      anti_10486();
      break;
    case 0x222008:
      DbgPrint("%s\n", Irp->AssociatedIrp.IrpCount);
      break;
    case 0x22200C:
      DbgPrint("bye!\n");
      break;
    default:
      DbgPrint("unkonw code!\n");
      break;
  }
  Irp->IoStatus.Status = 0;
  Irp->IoStatus.Information = 0;
  IofCompleteRequest(Irp, 0);
  return 0;
}

上面对0x222004的处理过程一定要在r3中调用过,但后面的反调试可以直接去掉,因此拖到16进制编辑器中修改,对anti_10486函数nop掉,然后存储(同时重新生成文件hash,否则驱动会加载失败):

此时,驱动已经拿到,并且去除了反调试,因此,可以进一步调试。

4. 在r3程序启动前,手动加载驱动文件,保证与原始文件名称相同:vmxdrv.sys ,目的是替换应用程序中的驱动的加载;此后配合od的插件可以调试分析程序;

5. 分析驱动调用流程:原理为应用层调用writefile,驱动根据输入计算md5,然后readfile取得md5值,应用层后续转换为文本,然后继续计算hash,该hash被截取(1,5)部分结果与目标值比较,对应的驱动代码:

    memcpy(str, Irpa, v3);
    if ( checked )
    {
      sub_104B6(str, (int)hash);
      hashed = 1;
    }
    ExFreePoolWithTag(str, 0);
    v2->IoStatus.Status = 0;

其中checked由0x222004设置,因此不能从r3屏蔽对应检测线程;

6. 穷举,此时可以对结果进行穷举,中间程序写错了。。。最后正确的是(外层循环省略):

  unsigned long *a4 = 0;
  char *a1 = 0;
  char text[100] = { 0 };
  unsigned char res[16] = { 0 };
  unsigned char res2[16] = { 0 };
  char text2[100] = { 0 };
  sprintf(sn, "%c%c%c%c%c%c",
   table[i1], table[i2], table[i3], table[i4], table[i5], table[i6]);
  func(sn);
  MD5_CTX ctx = { 0 };
  MD5Init(&ctx);
  MD5Update(&ctx, (unsigned char*)sn, strlen(sn));
  MD5Final(&ctx, res);
  int len = 0;
  for (int i = 0; i < 16; i++)
  {
   len += sprintf(text + len, "%02x", res[i]);
  }
  memset(&ctx, 0, sizeof(ctx));
  MD5Init(&ctx);
  MD5Update(&ctx, (unsigned char*)text, 32);
  MD5Final(&ctx, res2);
  len = 0;
  for (int i = 1; i < 6; i++)
  {
   len += sprintf(text2 + len, "%02x", res2[i]);
  }
  if (strcmp(text2, "888aeda4ab") == 0)
  {
   printf("result: %s.\n", sn);
   getchar();
  }



最新回复 (0)
返回