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

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

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

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

代码:
/*++

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;
}
2.接下来才是我们的关键部分 stub的制作
参考驱动壳编写总结
引用:
在给程序加壳的时候一般都要添加新节,用于存放壳的代码,应用层程序
的节表的最后一项和第一个节之间一般是有一个很大的空间可以用来添加新的
节表项的,但一般情况下驱动程序节表的最后一项后面紧接着就是第一个节,
根本没有足够的0x28大小的空间存放新的节表项。解决的方法有两种,第一种
将所有的节向后移动,而第二种方法则是将PE头整体向前移动覆盖掉部分无用
的dos头,留出足够的空间存放新的节表项
不过 偶不想这么做 原因 驱动文件毕竟比较“珍贵” 偶不想乱动 怕蓝啊
偶还是把驱动完整的压缩起来 释放到1片新申请的内存 
这样偶不用处理复杂的区段 VA转换 Reloc修改了

关键API编写
其实老V的reloadandcall已经给我们很多必须代码了
另外 老V代码的GetModuleHandle(NULL)
其实应该返回驱动本身基址,其实加载基址可以在驱动driverenter的参数中获得
驱动对象可以从堆栈当中找到,将驱动对象通过DRIVEROBJECT的DriverSection成员遍历这个链表,是能够获得的
代码:
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;
}
LoadLibrary貌似不常用,原因之一是常用模块基本上都是已经加载过了,GetModuleHandle肯定能获得其基址,貌似不需要LoadLibrary,单独调用其他系统默认不加载的模块的sys偶真没见过

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

实现重定位表处理 输入表处理
代码:
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;
}
其实多数代码都是从老V的reload里面复制过来的
其实一个简单的壳也就是自己实现PE文件加载运行的过程

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

  • 标 题:DriverUpx的设计与探索(二)
  • 作 者:zhuwg
  • 时 间:2008-01-13 13:14

看来 简单的事情 比最麻烦的事情 还麻烦
一些重定位 输入表处理的东西 
还有getmodulehandle函数模拟反而比较简单
倒是1个代码解压到内存运行的过程总是出问题?why?

以下代码参考linuxer的虚拟机

代码:
BOOL PELoader(char *lpStaticPEBuff, long lStaticPELen)
{
  long lSectionNum;
  IMAGE_SECTION_HEADER *pISH;
  long lFileAlignMask;
  long lSectionAlignMask;
  int nIndex;
      PIMAGE_FILE_HEADER    pfh;
    PIMAGE_OPTIONAL_HEADER    poh;
    PIMAGE_SECTION_HEADER    psh;
    
  long lPESignOffset = *(long *)(lpStaticPEBuff + 0x3c);
  IMAGE_NT_HEADERS *pINH = (IMAGE_NT_HEADERS *)(lpStaticPEBuff + lPESignOffset);

  //取加载到内存中大小
  long lImageSize = pINH->OptionalHeader.SizeOfImage;
  char *lpDynPEBuff = ExAllocatePool(NonPagedPool,lImageSize);
  dprintf("[dvupx] peoep is %8X\n",lpDynPEBuff);

//  pepool=ExAllocatePool(NonPagedPool, poh->SizeOfImage);

  memset(lpDynPEBuff, 0, lImageSize);

  //取PE文件的节数量
  lSectionNum = pINH->FileHeader.NumberOfSections;

  pISH = (IMAGE_SECTION_HEADER *)((char *)pINH + sizeof(IMAGE_NT_HEADERS));
  
  //加载PE文件第一个节前的信息
  memcpy(lpDynPEBuff, lpStaticPEBuff, pISH->VirtualAddress);

  //加载各个节
   lFileAlignMask = pINH->OptionalHeader.FileAlignment - 1;        //各节在磁盘中的对齐掩码
   lSectionAlignMask = pINH->OptionalHeader.SectionAlignment - 1;  //各节在load后内存中的对齐掩码
  
  for( nIndex = 0; nIndex < lSectionNum; nIndex++, pISH++)
  {
    /***************************************************
    在对FSG1.33加壳程序进行loader的时候,其节对齐属性
    比较怪异,对PE文件接触时间不长,不过地方不知道为什么,
    请各位大虾指点迷津,故注掉节属性验证代码
    ***************************************************/
    //判定各节的对齐属性,合法不
    /*if((pISH->VirtualAddress & lSectionAlignMask) || (pISH->SizeOfRawData & lFileAlignMask))
    {
      //出现非法节
      delete lpDynPEBuff;
      return false;
    }*/

    //加载改节
    memcpy(lpDynPEBuff + pISH->VirtualAddress, lpStaticPEBuff + pISH->PointerToRawData, pISH->SizeOfRawData);
  }
  ProcessImports((ULONG)lpDynPEBuff);
  ProcessRelocs((ULONG)lpDynPEBuff,(ULONG)lpDynPEBuff);
  GetHeaders(lpDynPEBuff,&pfh,&poh,&psh);
  dprintf("[dvupx] peoep is %8X\n",poh->AddressOfEntryPoint+lpDynPEBuff);
  peoep=(poh->AddressOfEntryPoint+(DWORD)lpDynPEBuff);
  dprintf("[dvupx] peoep is %8X\n",peoep);


  return 1;
}
加载结果

00000000    0.00000000    [dvupx] Loaded    
00000001    0.00029277    [dvupx] peloadaddr is 81644C48    
00000002    0.00158456    [dvupx] peoep is 81644E48

使用windbg查看 代码正确
lkd> dd 81644e48
81644e48  b8ec8b55 c0000182 0008c2c9 00000000
81644e58  00000000 00000000 00000000 00000000
81644e68  00000000 00000008 00000000 00000000
81644e78  00000000 00000000 00000000 00000000
81644e88  0a040049 74726f50 00050005 00000000
81644e98  81644e98 81644e98 7fffffff e182d7e8
81644ea8  0a060004 ee657645 00000001 00000001
81644eb8  817babf8 00000000 80561580 00000000
lkd> u 81644e48
81644e48 55              push    ebp
81644e49 8bec            mov     ebp,esp
81644e4b b8820100c0      mov     eax,0C0000182h
81644e50 c9              leave
81644e51 c20800          ret     8
81644e54 0000            add     byte ptr [eax],al
81644e56 0000            add     byte ptr [eax],al
81644e58 0000            add     byte ptr [eax],al

但是1运行,
pehex为被加壳文件的解开的文件镜像
执行    PELoader(pehex,sizeof(pehex));
然后
push pRegistryString
push pDriverObj
call peoep



继续在这里写好了 
两边的接口问题都解决了
下面应该是考虑压缩算法了
不过这个偶真的没有发言权 还是大牛们来评论吧
aPLib 和LZMA 应该是最常用的2个选择了

引用:
由 dwing 发布
论压缩率大致是这样的:
zip < RAR16 < aPLib < RAR32 < LZMA < PPM
TeLeMan:
aPlib只是属于传统LZ方式,一般不会比LZH的高,所以aPlib在上面几种里压缩率是最低的。
dwing:
aPlib压缩率不是最低的,因为使用的字典比zip大,匹配算法比zip完善(但比较慢).
LZH压缩率与zip差不多.
TeLeMan:
aPlib之所以比LZH和zip压缩率要小,是因为没有用到Huffman编码。这一点不需要有什么质疑的,举个现实的例子就可以了。ASPack就是用LZH算法,要比UPX和只用到aPlib库的Packer压缩率大。
 dwing
你说的也有道理,经过测试发现压缩较小的文件aPLib有优势,较大的文件使用Huffman算法的有优势,前者的优势只是较大的字典和更好的匹配算法.
LZMA则发挥了LZ算法最大的潜能,使用最完善的匹配算法+算术编码(比Huffman更好)

理论偶就不仔细说了 因为偶也不懂

aplib
引用:
压缩函数
size_t aP_pack( const void *source,
                void *destination,
                size_t length,
                void *workmem,
                int (*callback)(size_t, size_t, size_t, void *),
                void *cbparam );

Parameters: 
source - pointer to the data to be compressed.
destination - pointer to where the compressed data should be stored.
length - the length of the uncompressed data in bytes.
workmem - pointer to the work memory which is used during compression.
callback - pointer to the callback function (or NULL).
cbparam - callback argument.

size_t aPsafe_pack( const void *source,
                    void *destination,
                    size_t length,
                    void *workmem,
                    int (*callback)(size_t, size_t, size_t, void *),


                    void *cbparam );
Wrapper function for aP_pack, which adds a header to the compressed data containing the length of the original data, and CRC32 checksums of the original and compressed data.

Parameters: 
source - pointer to the data to be compressed.
destination - pointer to where the compressed data should be stored.
length - the length of the uncompressed data in bytes.
workmem - pointer to the work memory which is used during compression.
callback - pointer to the callback function (or NULL).
cbparam - callback argument

 与之对应的解压缩函数
size_t aP_depack( const void *source,
                  void *destination );
Decompresses the compressed data from source[] into destination[]. 

The destination[] buffer must be large enough to hold the decompressed data.

Parameters: 
source - pointer to the compressed data.
destination - pointer to where the decompressed data should be stored.

Returns: 
the length of the decompressed data, or APLIB_ERROR on error. 


size_t aPsafe_depack( const void *source,
                      size_t srclen,
                      void *destination,
                      size_t dstlen );
Wrapper function for aP_depack_asm_safe, which checks the CRC32 of the compressed data, decompresses, and checks the CRC32 of the decompressed data.

Parameters: 
source - pointer to the compressed data.
srclen - the size of the source buffer in bytes.
destination - pointer to where the decompressed data should be stored.
dstlen - the size of the destination buffer in bytes.

Returns: 
the length of the decompressed data, or APLIB_ERROR on error.
其实这个过程一点也不复杂
如下代码data为原始数据
compressed 为压缩后的数据
   /* allocate workmem and destination memory */
   char *workmem    = malloc(aP_workmem_size(length));
   char *compressed = malloc(aP_max_packed_size(length));

   /* compress data[] to compressed[] */
   size_t outlength = aPsafe_pack(data, compressed, length, workmem, NULL, NULL);

   /* if APLIB_ERROR is returned, and error occured */
   if (outlength == APLIB_ERROR)
   {
      printf("An error occured!\n");
   } else {
      printf("Compressed %u bytes to %u bytes\n", length, outlength);
   }
//---------------------------
   /* get original size */
   size_t orig_size = aPsafe_get_orig_size(compressed);

   /* allocate memory for decompressed data */
   char *data = malloc(orig_size);

   /* decompress compressed[] to data[] */
   size_t outlength = aPsafe_depack(compressed, compressed_size, data, orig_size);

   /* check decompressed length */
   if (outlength != orig_size)
   {
      printf("An error occured!\n");
   } else {
      printf("Decompressed %u bytes\n", outlength);
   }

//----------------------------------------------------------------------------

压缩过程没有什么好说的 选择1个算法处理处理就ok了
另外 前面有几个小问题 的专门处理1下才行

驱动退出的时候 如果注册了DriverUnload函数 那么就会去调用
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
我们需要将这个函数设置成壳的处理函数,先执行完被加壳程序的DriverUnload
然后再ExFreePool

这里驱动有2种  1种的正常驱动 注册了DriverUnload函数的 我们照上面处理即可
另外1种是流氓驱动 没有注册DriverUnload函数 我们此时不能注册壳的DriverUnload处理函数,不然 也许一些代码还在运行,你就ExFreePool了,那么你只能得到蓝屏

我们需要1个函数 MmIsAddressValid来验证DriverUnload处理函数是否被注册了
在DriverEntry里面加入
    if (MmIsAddressValid(pDriverObj->DriverUnload))
    {
        OldDriverUnload=pDriverObj->DriverUnload;
        pDriverObj->DriverUnload = DriverUnload;
    }

相应的
我们先判断OldDriverUnload是否有效,如果是正常驱动,那么去执行他
完成以后ExFreePool掉空间
如果OldDriverUnload函数无效,那么我们只好什么也不做了
VOID DriverUnload(PDRIVER_OBJECT pDriverObj)
{    
    if (MmIsAddressValid(OldDriverUnload))
    {
        _asm
        {
            push pDriverObj
            call OldDriverUnload
        }
        ExFreePool(lpDynPEBuff);
    }
    dprintf("[dvupx] Unloaded\n");
}

基本上到这里 1个半成品(偶只写完了解压部分)就应该差不多了,
其实这个东西娱乐意义远远大于实用意义