首页
社区
课程
招聘
[原创]DriverUpx的设计与探索(一)(二)
发表于: 2008-1-13 13:12 13245

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

zhuwg 活跃值
11
2008-1-13 13:12
13245

能够支持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;
}
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;
}

[注意]传递专业知识、拓宽行业人脉——看雪讲师团队等你加入!

收藏
免费 7
支持
分享
最新回复 (18)
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
2
看来 简单的事情 比最麻烦的事情 还麻烦
一些重定位 输入表处理的东西
还有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个半成品(偶只写完了解压部分)就应该差不多了,
其实这个东西娱乐意义远远大于实用意义
2008-1-13 13:14
0
雪    币: 47147
活跃值: (20465)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
3
好文章,关于驱动加壳,论坛上的分析文章不多。
2008-1-13 13:21
0
雪    币: 451
活跃值: (78)
能力值: ( LV12,RANK:470 )
在线值:
发帖
回帖
粉丝
4
看雪大大大大大牛效率之高 速度之快。。
称赞啊称赞啊
2008-1-13 13:25
0
雪    币: 97697
活跃值: (200839)
能力值: (RANK:10 )
在线值:
发帖
回帖
粉丝
5
Thanks again for sharing your efforts zhuwg!
2008-1-13 13:28
0
雪    币: 146
活跃值: (33)
能力值: ( LV6,RANK:90 )
在线值:
发帖
回帖
粉丝
6
thx for share!
2008-1-13 13:30
0
雪    币: 2134
活跃值: (14)
能力值: (RANK:170 )
在线值:
发帖
回帖
粉丝
7
很感谢很感谢,把我的顶--你两个字去掉了,郁闷
2008-1-13 13:34
0
雪    币: 1946
活跃值: (248)
能力值: (RANK:330 )
在线值:
发帖
回帖
粉丝
8
少见的好贴
2008-1-13 14:31
0
雪    币: 716
活跃值: (162)
能力值: ( LV9,RANK:250 )
在线值:
发帖
回帖
粉丝
9
太精彩了,不能不称赞!!!
2008-1-13 18:59
0
雪    币: 7309
活跃值: (3788)
能力值: (RANK:1130 )
在线值:
发帖
回帖
粉丝
10
驱动压缩壳要出来了,壳从3环走向0环
2008-1-13 19:13
0
雪    币: 1746
活跃值: (287)
能力值: (RANK:450 )
在线值:
发帖
回帖
粉丝
11
收藏
2008-1-13 20:01
0
雪    币: 494
活跃值: (629)
能力值: ( LV9,RANK:1210 )
在线值:
发帖
回帖
粉丝
12
你太牛了,希望早日看到加壳样本
2008-1-13 22:17
0
雪    币: 334
活跃值: (22)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
13
经典之作,赞一个
2008-1-14 09:59
0
雪    币: 1919
活跃值: (901)
能力值: ( LV9,RANK:490 )
在线值:
发帖
回帖
粉丝
14
好文章要支持~~~
2008-1-14 11:52
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
15
太精彩了,不能不称赞!!!
2008-1-14 17:19
0
雪    币: 10122
活跃值: (3321)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
16
好文章,保存学习
2008-1-17 23:17
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
17
强贴!顶~~~~~~~~~~~~~~`
2008-2-20 00:35
0
雪    币: 4560
活跃值: (1002)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
18
加壳脱壳大战终于要进入内核层了
2009-12-12 13:18
0
雪    币: 31
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
19
很经典 ,赞一个
2009-12-12 15:12
0
游客
登录 | 注册 方可回帖
返回
//