欢迎来到 嗅灵易学

零基础也能上手的脚本技术课,一对一答疑带你入门

[原创]crackme第5题

[原创]crackme第5题

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();

  }



注意:上传附件及图片大小不得大于30M。

⚠️ 版权声明:
本博客所有内容(含教程、源码、工具)仅供个人技术学习与研究交流使用,严禁商用、倒卖、二次分发及非法用途
未经作者书面授权,任何组织或个人不得转载、复制或用于其他平台,违者将追究相关责任。

0 0 0 举报
复制成功