邪恶八进制信息安全团队技术讨论组's Archiver

zhuwg 2008-1-13 13:13

[原创]DriverUpx的设计与探索(一)

文章作者:zhuwg
信息来源:邪恶八进制信息安全团队([url]www.eviloctal.com[/url])

unpack论坛服务器终于进入稳定状态了
恭喜1下





能够支持Driver加壳的软件还是很少很少的
貌似除了vmp cv之外就没有了
vmp cv都是基于代码加密理念的 因此移植到驱动下面不是什么困难的事情
驱动加密壳实现了代码加密已经足够,毕竟 公开的驱动dump高端内存dump到文件的东东偶还没看见过

偶想实现的东东是DriverUpx,众所周知
upx是把pe文件完整压缩了保存起来,从而可以实现完美自身脱壳回来
偶也想实现这个功能 申请1块内存 将驱动文件完整释放 自己处理重定位 输入表
最后建立线程执行driverentry

每个加壳软件都有他关键的几个api
GetModuleHandle
GetProcAddress
LoadLibrary
我们将一一实现他们

先别急着实现stub,我们先的准备好packer
packer的目的是压缩sys文件 并且组装到stub指定地方
驱动有个东西必须处理
驱动程序被加壳后必须重新进行校验和的计算,否则加壳后的驱动不能加载
这个功能我们在r3下面完成[code]/*++

Routine Description:

  Calculates a new checksum for the PE image by calling imagehlp.dll

Arguments:

  szPeFile - PE file name

Return Value:

  void

--*/
void
CalcChecksum(
  char *szPeFile
  )
{
  DWORD       dwHeaderSum = 0;
  DWORD       dwCheckSum = 0;
  HANDLE       hFile;
  DWORD       cb;
  IMAGE_DOS_HEADER  dosHdr;
  IMAGE_NT_HEADERS  ntHdr;

  //
  // Open the file and calculate the CheckSum
  //
  if( MapFileAndCheckSum(szPeFile, &dwHeaderSum, &dwCheckSum) != CHECKSUM_SUCCESS )
  {
   printf("Failed to open specified PE file!\n");
   return;
  }
  hFile = CreateFile( szPeFile,
         GENERIC_READ | GENERIC_WRITE,
         FILE_SHARE_READ | FILE_SHARE_WRITE,
         NULL,
         OPEN_EXISTING,
         0,
         NULL
         );
  if( hFile == INVALID_HANDLE_VALUE )
  {
   printf("Failed to open specified PE file!\n");
   return;
  }

  //
  // Seek to the beginning of the file
  //
  SetFilePointer( hFile, 0, 0, FILE_BEGIN );

  //
  // Read in the DOS header
  //
  if( (ReadFile(hFile, &dosHdr, sizeof(dosHdr), &cb, 0) == FALSE)
   || (cb != sizeof(dosHdr)) )
  {
   printf("Failed to read DOS header!\n");
   CloseHandle(hFile);
   return;
  }

  //
  // Seek the PE header
  //
  if( (dosHdr.e_magic != IMAGE_DOS_SIGNATURE) ||
   (SetFilePointer(hFile, dosHdr.e_lfanew, 0, FILE_BEGIN) == -1L) )
  {
   printf("Failed to read NT header!\n");
   CloseHandle(hFile);
   return;
  }

  //
  // Read in the NT header
  //
  if( (!ReadFile(hFile, &ntHdr, sizeof(ntHdr), &cb, 0))
   || (cb != sizeof(ntHdr)) )
  {
   printf("Failed to read NT header!\n");
   CloseHandle(hFile);
   return;
  }

  //
  // Search the PE sisnature
  //
  if(ntHdr.Signature != IMAGE_NT_SIGNATURE)
  {
   printf("The file is not a valid PE file!\n");
   CloseHandle(hFile);
   return;
  }

  //
  // Check if the PE file's checksum need adjusted
  //
  if(ntHdr.OptionalHeader.CheckSum == dwCheckSum)
  {
   printf("The PE file CheckSum needn't to be adjusted\n");
   CloseHandle(hFile);
   return;
  }

  //
  // Seek the PE header
  //
  if( SetFilePointer(hFile, dosHdr.e_lfanew, 0, FILE_BEGIN) == -1L )
  {
   printf("Failed to locate PE header!\n");
   CloseHandle(hFile);
   return;
  }

  printf("Old Checksum = 0x%08X\n", ntHdr.OptionalHeader.CheckSum);
  printf("New Checksum = 0x%08X\n", dwCheckSum);

  //
  // Modify the CheckSum
  //
  ntHdr.OptionalHeader.CheckSum = dwCheckSum;
  if( !WriteFile(hFile, &ntHdr, sizeof(ntHdr), &cb, NULL) )
  {
   printf("Failed to Adjust Checksum!\n");
  }
  else
  {
   printf("Adjust Checksum successfully!\n");
  }

  CloseHandle(hFile);
  return;
}[/code]2.接下来才是我们的关键部分 stub的制作
参考驱动壳编写总结
[quote]在给程序加壳的时候一般都要添加新节,用于存放壳的代码,应用层程序
的节表的最后一项和第一个节之间一般是有一个很大的空间可以用来添加新的
节表项的,但一般情况下驱动程序节表的最后一项后面紧接着就是第一个节,
根本没有足够的0x28大小的空间存放新的节表项。解决的方法有两种,第一种
将所有的节向后移动,而第二种方法则是将PE头整体向前移动覆盖掉部分无用
的dos头,留出足够的空间存放新的节表项[/quote]

不过 偶不想这么做 原因 驱动文件毕竟比较“珍贵” 偶不想乱动 怕蓝啊
偶还是把驱动完整的压缩起来 释放到1片新申请的内存
这样偶不用处理复杂的区段 VA转换 Reloc修改了

关键API编写
其实老V的reloadandcall已经给我们很多必须代码了
另外 老V代码的GetModuleHandle(NULL)
其实应该返回驱动本身基址,其实加载基址可以在驱动driverenter的参数中获得
驱动对象可以从堆栈当中找到,将驱动对象通过DRIVEROBJECT的DriverSection成员遍历这个链表,是能够获得的[code]PVOID GetModuleHandle(char *pModuleName)
{
  PVOID pModuleBase = NULL;
  ULONG i;

  PSYSTEM_MODULE_INFORMATION ModulesInfo = (PSYSTEM_MODULE_INFORMATION)GetSysInf(SystemModuleInformation);
  if (ModulesInfo == NULL)
    return NULL;

  if (!strcmp(pModuleName, "ntoskrnl.exe"))
  {
    pModuleBase = (PVOID)ModulesInfo->aSM[0].Base;
  } else {
    for (i = 0; i < ModulesInfo->uCount; i++)
    {
      if (strstr(ModulesInfo->aSM.ImageName, pModuleName))
        pModuleBase = (PVOID)ModulesInfo->aSM.Base;
    }
  }

  ExFreePool(ModulesInfo);

  return pModuleBase;
}
PVOID GetProcAddress(PVOID ModuleBase, char *pFunctionName)
{
  PVOID pFunctionAddress = NULL;
  PIMAGE_EXPORT_DIRECTORY exports;
  ULONG i, addr, ord, size = 0;
  PULONG names, functions;
  PSHORT ordinals;

  if (ModuleBase == NULL)
    return NULL;

  __try
  {
    exports = (PIMAGE_EXPORT_DIRECTORY)RtlImageDirectoryEntryToData(ModuleBase,
      TRUE, IMAGE_DIRECTORY_ENTRY_EXPORT, &size);

    addr = (ULONG)exports - (ULONG)ModuleBase;

    functions = (PULONG)((ULONG)ModuleBase + exports->AddressOfFunctions);
    ordinals = (PSHORT)((ULONG)ModuleBase + exports->AddressOfNameOrdinals);
    names  = (PULONG)((ULONG)ModuleBase + exports->AddressOfNames);

    for (i = 0; i < exports->NumberOfNames; i++)
    {
      ord = ordinals;

      if (i >= exports->NumberOfNames || ord >= exports->NumberOfFunctions)
        return NULL;

      if (functions[ord] < addr || functions[ord] >= addr + size)
      {
        if (strcmp((char *)((ULONG)ModuleBase + names), pFunctionName) == 0)
        {
          pFunctionAddress =(PVOID)((ULONG)ModuleBase + functions[ord]);
          break;
        }
      }
    }
  }
  __except(EXCEPTION_EXECUTE_HANDLER)
  {
    DbgMsg("KernelGetProcAddress() EXEPTION\n");
    pFunctionAddress = NULL;
  }

  return pFunctionAddress;
}
PVOID GetSysInf(SYSTEMINFOCLASS pdData)
{
  NTSTATUS ns;
  ULONG dSize = 4096;
  ULONG dData = 0;
  PVOID shi;

  do {
    shi = ExAllocatePool(NonPagedPool, dSize);

    if (shi == NULL) return 0;

    ns = ZwQuerySystemInformation(pdData, shi, dSize, &dData);

    if (ns == STATUS_INFO_LENGTH_MISMATCH)
    {
      ExFreePool(shi);
      dSize *= 2;
    }
      
  } while (ns != 0);
  
  return shi;
}[/code]LoadLibrary貌似不常用,原因之一是常用模块基本上都是已经加载过了,GetModuleHandle肯定能获得其基址,貌似不需要LoadLibrary,单独调用其他系统默认不加载的模块的sys偶真没见过

3.申请一片大小为原始文件SizeOfImage的内存,用来存放我们的释放体
这个不知道是不是需要必须是nonpagedpool?

实现重定位表处理 输入表处理[code]BOOLEAN ProcessImports(ULONG ImageBase)
{
  IMAGE_THUNK_DATA32 *pThunk;
  PVOID LibAddr;
  char *LibName, *FuncName;
  PIMAGE_IMPORT_BY_NAME pImageImportByName;
  ULONG FuncAddr;

  __try
  {
    PIMAGE_NT_HEADERS pImageNtHeaders = (PIMAGE_NT_HEADERS)
      (ImageBase + ((PIMAGE_DOS_HEADER)ImageBase)->e_lfanew);

    PIMAGE_IMPORT_DESCRIPTOR pImageImportDescriptor = (PIMAGE_IMPORT_DESCRIPTOR)(RVATOVA(ImageBase,
      pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress));

    while (pImageImportDescriptor->Name != 0)
    {  
      LibName = (char *)RVATOVA(ImageBase, pImageImportDescriptor->Name);
      LibAddr = KernelGetModuleBase(LibName);
      if (LibAddr == NULL)
        return FALSE;

      DbgMsg("0x%.8x:%s\n", LibAddr, LibName);

      pThunk = (IMAGE_THUNK_DATA32 *)RVATOVA(ImageBase, pImageImportDescriptor->FirstThunk);

      while (pThunk->u1.Ordinal != 0)
      {
        pImageImportByName = (PIMAGE_IMPORT_BY_NAME)RVATOVA(ImageBase, pThunk->u1.AddressOfData);
        FuncName = (char *)(&pImageImportByName->Name);
        FuncAddr = (ULONG)KernelGetProcAddress(LibAddr, FuncName);

        DbgMsg(" 0x%.8x:%s\n", FuncAddr, FuncName);

        if (FuncAddr == 0)
          return FALSE;
        
        *(PULONG)pThunk = FuncAddr;
        pThunk++;
      }

      pImageImportDescriptor++;
    }
  }
  __except(EXCEPTION_EXECUTE_HANDLER)
  {
    DbgMsg("ProcessImports() EXEPTION\n");
    return FALSE;
  }

  return TRUE;
}

BOOLEAN ProcessRelocs(ULONG ImageBase)
{
  __try
  {
    PIMAGE_NT_HEADERS pImageNtHeaders = (PIMAGE_NT_HEADERS)
      (ImageBase + ((PIMAGE_DOS_HEADER)ImageBase)->e_lfanew);

    ULONG RellocsSize = pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].Size;

    PIMAGE_BASE_RELOCATION pImageBaseRelocation = (PIMAGE_BASE_RELOCATION)(RVATOVA(ImageBase,
      pImageNtHeaders->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_BASERELOC].VirtualAddress));

    PIMAGE_BASE_RELOCATION pRelocation = pImageBaseRelocation;

    ULONG ImageBaseDelta = ImageBase - pImageNtHeaders->OptionalHeader.ImageBase, Number, i, Size = 0;
    PUSHORT Rel;

    DbgMsg("pImageBaseRelocation: 0x%.8x; Size: %d\n", pImageBaseRelocation, RellocsSize);
  
    while (RellocsSize > Size)
    {
      Size += pRelocation->SizeOfBlock;

      Number = (pRelocation->SizeOfBlock - 8) / 2;
      Rel = (PUSHORT)((ULONG)pRelocation + 8);

      DbgMsg("VirtualAddress: 0x%.8x; Number of Relocs: %d; Size: %d\n", pRelocation->VirtualAddress,
        Number, pRelocation->SizeOfBlock);

      for (i = 0; i < Number - 1; i++)
      {
        DbgMsg(" %d:0x%.8x\n", i, Rel & 0x0FFF);
        *(PULONG)(RVATOVA(ImageBase, pRelocation->VirtualAddress + (Rel & 0x0FFF))) += ImageBaseDelta;
      }

      pRelocation = (PIMAGE_BASE_RELOCATION)((ULONG)pImageBaseRelocation + Size);
    }
  }
  __except(EXCEPTION_EXECUTE_HANDLER)
  {
    DbgMsg("ProcessRelocs() EXEPTION\n");
    return FALSE;
  }

  return TRUE;
}[/code]其实多数代码都是从老V的reload里面复制过来的
其实一个简单的壳也就是自己实现PE文件加载运行的过程

下面这一步有点麻烦
我们已经把原始驱动文件放在了不分页内存里面
并且处理好了重定位 输入表
下面是启动这个驱动了
这个驱动对象DriverOblect偶不是很明白
不知道可否自己分配1个 还是只能继承系统分配下来的这一个
偶目前做的是直接PsCreateSystemThread(DriverEntry)
使用系统传过来的
IN PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath
发送下去
不知道这个RegistryPath可否自己“乱改”呢?DriverObject可否自己分配?
先写到这里
休息去

vxk 2008-1-14 08:27

咔嚓发到这里来了~~~
我只发了DebugMan啊DebugMan啊~

fonge 2008-1-18 09:50

*** 作者被禁止或删除 内容自动屏蔽 ***

页: [1]
© 1999-2008 EvilOctal Security Team