【文章标题】: 猪头三Winapiex DISK接口分析
【文章作者】: nevergone
【作者邮箱】: wangyongxina2004@163.com
【下载地址】: 自己搜索下载
【编写语言】: VC8
【使用工具】: IDA VC8
【作者声明】: 只是感兴趣,没有其他目的。失误之处敬请诸位大侠赐教!
--------------------------------------------------------------------------------
【详细过程】
  猪头三大哥的winapiex项目中,整合了一系列的接口,包含磁盘操作类 文件操作类 进程操作类等
  我对磁盘操作类比较感兴趣,因此下载了DLL文件,并逆向了磁盘操作类的全部接口
  E_Disk_IsDynamicDisk        [作者: 猪头三 性质: 闭源 修改时间: 2007-06-18 功能: 判断物理磁盘是否是动态磁盘]
  E_Disk_ReadData            [作者: 猪头三 性质: 授权 修改时间: 2007-06-18 功能: 读取物理磁盘数据]    

    
  E_Disk_WriteData          [作者: 猪头三 性质: 授权 修改时间: 2007-06-18 功能: 写入物理磁盘数据]      

  
  E_Disk_GetDiskGeometry        [作者: 猪头三 性质: 授权 修改时间: 2007-06-18 功能: 获取物理磁盘基本信息参数]    

        
  E_Disk_GetPartitionType        [作者: 猪头三 性质: 授权 修改时间: 2007-06-18 功能: 获取指定分区类型]
  E_Disk_GetDiskNumberByFilePath    [作者: 猪头三 性质: 授权 修改时间: 2007-06-19 功能: 获取指定分区所在的磁盘]
  E_Disk_GetPartitionInfoByFilePath  [作者: 猪头三 性质: 授权 修改时间: 2007-06-19 功能: 获取指定分区基本信息(分区类型 分区大小 分区所在磁

盘起始位置)]
  E_Disk_GetPartitionSpace      [作者: 猪头三 性质: 授权 修改时间: 2007-06-19 功能: 获取指定分区实际空闲容量和实际总容量]
  E_Disk_GetDiskNumber        [作者: 猪头三 性质: 授权 修改时间: 2007-06-21 功能: 获取物理磁盘数量]
  E_Disk_FormatPartition        [作者: 猪头三 性质: 授权 修改时间: 2007-06-22 功能: 格式化指定分区]
  下面我将介绍其中的几个接口
  我逆向的第一个接口是:E_Disk_FormatPartition
  按猪头三提供的文档,此函数用于格式化指定分区.
  函数内部调用SHFormatDrive来执行格式化
  值得注意的是这个函数存在一个BUG, 看IDA
  ; int __cdecl E_Disk_FormatPartition(const char *pDriverName)
  .text:100013E0                 public E_Disk_FormatPartition
  .text:100013E0 E_Disk_FormatPartition proc near
  .text:100013E0
  .text:100013E0 pDriverName     = dword ptr  4
  .text:100013E0
  .text:100013E0                 mov     eax, [esp+pDriverName]
  .text:100013E4                 mov     dl, [eax]
  .text:100013E6                 xor     ecx, ecx
  .text:100013E8                 mov     eax, A          ; 'A'
  .text:100013ED                 movsx   edx, dl         ; Get pDriverName[0]
  .text:100013ED
  .text:100013F0
  .text:100013F0 begin_loop:                             ; CODE XREF: E_Disk_FormatPartition+1Dj
  .text:100013F0                 cmp     edx, eax
  .text:100013F2                 jz      short Do_Format ; 循环,用于取得SHFormatDrive的第二个参数
  .text:100013F2
  .text:100013F4                 add     eax, 1
  .text:100013F7                 add     ecx, 1
  .text:100013FA                 cmp     eax, Z          ; 'Z'
  .text:100013FD                 jbe     short begin_loop;比'Z'小时,跳转,当比'Z'大于循环不满足,执行Do_Format.
  .text:100013FD
  .text:100013FF
  .text:100013FF Do_Format:                              ; CODE XREF: E_Disk_FormatPartition+12j
  .text:100013FF                 push    SHFMT_OPT_FULL  ; options
  .text:10001401                 push    SHFMT_ID_DEFAULT ; fmtID
  .text:10001406                 push    ecx             ; drive
  .text:10001407                 call    ds:GetActiveWindow
  .text:10001407
  .text:1000140D                 push    eax             ; hwnd
  .text:1000140E                 call    ds:SHFormatDrive 
  .text:1000140E
  .text:10001414                 retn
  当用户构造一个特殊的字符串pDriverName[0] > 'Z'时,会把用户的Z盘直接格式化,应该在函数入口检测用户输入参数
  是否合法.
  disk接口中比较有用的两个接口是E_Disk_ReadData 和 E_Disk_WriteData.
  这两个接口按猪头三提供的文档:读取物理磁盘数据和写入物理磁盘数据.
  下面的代码是逆向后的
  bool __cdecl MyReadData(IN unsigned long ulong_param_DiskNum,
              IN const char *pchar_param_DriverName,
              IN void *pvoid_param_DataBuffer,
              IN unsigned long ulong_param_DataLength,
              IN LONGLONG llg_param_ByteOffset)
  {
    char szBuffer[0x20] = { 0 };
    ULONG ulDiskNumber = 0;
  
    if (lstrlen(pchar_param_DriverName) > 1)
      ulDiskNumber = MyGetDiskNumberByFilePath(pchar_param_DriverName);
    else
      ulDiskNumber = ulong_param_DiskNum;
  
    wsprintf(szBuffer, "\\\\?\\PhysicalDrive%ld", ulDiskNumber);
    
    HANDLE hFile = CreateFile(szBuffer,
                  GENERIC_READ,
                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL,
                  OPEN_EXISTING,
                  FILE_FLAG_WRITE_THROUGH | FILE_FLAG_NO_BUFFERING,
                  NULL);
  
    if (hFile == INVALID_HANDLE_VALUE)
    {
      MessageBox(NULL, "E_ReadPhysicalDriveData()->OpenPhysicalDrive is failed !", "", MB_OK);
      return false;
    }
  
    BOOL fOk = ReadFileInternal(hFile,
                  pvoid_param_DataBuffer,
                  ulong_param_DataLength,
                  (LONG)llg_param_ByteOffset,
                  *(PLONG)((PBYTE)(&llg_param_ByteOffset) + 4));
  
    if (!fOk)
    {
      MessageBox(NULL, "E_ReadPhysicalDriveData()->ReadPhysicalDriveData is failed !", "", MB_OK);
    }
  
    CloseHandle(hFile);
    return (bool)fOk;
  }
  首先通过参数ulong_param_DiskNum 或 pchar_param_DriverName取得欲写入的硬盘编号
  在文档中:pchar_param_DriverName注明是必须是0,但从IDA分析中,如果pchar_param_DriverName不为0,则
  通过一个内部函数来取得硬盘编号,在我的CPP文件中,我用MyGetDiskNumberByFilePath(pchar_param_DriverName);
  取得了硬盘编号后,构造一个\\?\PhysicalDrive%ld的字符串,调用CreateFile 打开这个对象,最后再调用内部未导出的
  函数来执行读取操作,这个函数在我的CPP文件中我命名为ReadFileInternal.
  int __fastcall ReadFileInternal(HANDLE hFile,
                  PVOID pvBuffer,
                  DWORD long_param_nNumberOfBytesToRead,
                  LONG long_param_lDistanceToMove,
                  LONG long_param_lDistanceToMoveHigh)
  {
    DWORD dwNumberBytesRead = 0;
    LONG lDistanceToMove;
    LONG lDistanceToMoveHigh;
    char szBuffer[MAX_PATH];
  
    ZeroMemory(szBuffer, MAX_PATH * sizeof(char));
    lDistanceToMoveHigh = long_param_lDistanceToMoveHigh;
    lDistanceToMove = long_param_lDistanceToMove;
    DWORD dwPtrLow = SetFilePointer(hFile,
                    lDistanceToMove,
                    &lDistanceToMoveHigh,
                    FILE_BEGIN);
  
    if (dwPtrLow == INVALID_SET_FILE_POINTER)
    {
      ReportError();
      return 0;
    }
  
    BOOL fOk = ReadFile(hFile,
              pvBuffer,
              long_param_nNumberOfBytesToRead,
              &dwNumberBytesRead,
              NULL);
  
    if (!fOk)
    {
      wsprintf(szBuffer, "Read PhysicalDriveData Error %ld !", GetLastError());
      MessageBox(NULL, szBuffer, "", MB_OK);
    }
    return fOk;
  }
  先调用SetFilePointer使得文件指针指向用户要求的区域,然后再调用ReadFile来执行操作.
  原来挺简单的
  
  E_Disk_WriteData的流程和E_Disk_ReadDisk差不多,请参看源码.
  这两个函数可以用来读取硬盘的MBR.
  PVOID  pvBuffer1 = HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, 512);
  E_Disk_ReadData(0, NULL, pvBuffer1, 512, 0);
  这样就取得了MBR
  写入MBR的代码有点危险....嘿嘿...
  
  接下来是用来判断物理磁盘是否是动态磁盘的接口
  
  unsigned int MyIsDynamicDisk(IN const char *pchar_param_FilePath)
  {
    DWORD dwBytesReturned = 0;
    char szBuffer[0x20] = { 0 };
    UINT uiResult = 1;
  
    ULONG ulDiskNumber = MyGetDiskNumberByFilePath(pchar_param_FilePath);
  
    if (ulDiskNumber == 0xFF)
    {
      MessageBox(NULL, "IsDynamicDisk()->Get disk number is failed !", "", MB_OK);
      return 0;
    }
  
    wsprintf(szBuffer, "\\\\?\\PhysicalDrive%ld", ulDiskNumber);
  
    HANDLE hFile = CreateFile(szBuffer,
                  GENERIC_READ,
                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL,
                  OPEN_EXISTING,
                  FILE_FLAG_WRITE_THROUGH |  FILE_FLAG_NO_BUFFERING,
                  NULL);
  
    if (hFile == INVALID_HANDLE_VALUE)
    {
      MessageBox(NULL, "IsDynamicDisk()->OpenPhysicalDrive() is failed !", "", 0);
      return 0;
    }
  
    DeviceIoControl(hFile,
            FSCTL_LOCK_VOLUME,
            NULL,
            0,
            NULL,
            0,
            &dwBytesReturned,
            NULL);
  
    LPVOID pvBuffer = malloc(0x200);
    ZeroMemory(pvBuffer, 0x200);
  
    BOOL fOk = ReadFileInternal(hFile, pvBuffer, 0x200, 0, 0);
    
    if (!fOk)
    {
      MessageBox(NULL, "IsDynamicDisk()->ReadPhysicalDriveData() is failed !", "", MB_OK);
      uiResult = 0;
      goto __Exit;
    }
  
    DWORD dwUnknown = *(LPDWORD)((LPBYTE)pvBuffer + 0x1c2);
  
  #if _DEBUG         // just for test
    BYTE  aByte;
    TCHAR  Buffer[20];
    aByte = *static_cast<LPBYTE>((LPBYTE)pvBuffer + 0x1c2);
    wsprintf(Buffer, "%x", aByte);
    OutputDebugStringA(Buffer);
  #endif
    
    if (dwUnknown == 0x42)
    {
      MessageBox(NULL, "This disk is Dynamic Disk !\n", "", MB_OK);
      uiResult = 3;
      goto __Exit;
    }
  
  __Exit:
    DeviceIoControl(hFile,
            FSCTL_UNLOCK_VOLUME,
            NULL,
            0,
            NULL,  
            0,
            &dwBytesReturned,
            NULL);
  
    CloseHandle(hFile);
    free(pvBuffer);
    return uiResult;
  }
  
  这个函数是整个DISK接口中,代码最长的接口
  先根据pchar_param_FilePath取得硬盘编号.DLL通过一个未导出函数来实现,在我的CPP文件中为
  MyGetDiskNumberByFilePath

  /*  #define IOCTL_DISK_UNKNOWN 0x560000*/
  
    DWORD  dwBytesReturned = 0;
    /*char  szOutBuffer[0x20] = { 0 };*/
    VOLUME_DISK_EXTENTS disk_extents;
  
    char  szDest[0x20] = { 0 };
  
    wsprintfA(szDest, "\\\\.\\%c:", pchar_param_FilePath[0]);
    HANDLE hFile = CreateFile(szDest,
                  GENERIC_READ,
                  FILE_SHARE_READ | FILE_SHARE_WRITE,
                  NULL,
                  OPEN_EXISTING,
                  0,
                  NULL);
    if (hFile == INVALID_HANDLE_VALUE)
    {
      ReportError();
      return 0xFF;
    }
    
    BOOL fOk = DeviceIoControl(hFile, IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS, NULL, 0, &disk_extents, sizeof(disk_extents), &dwBytesReturned, 

NULL);
  
    CloseHandle(hFile);
  
    return fOk ? disk_extents.Extents[0].DiskNumber : 0xFF;
  
  一开始我不知道DeviceIoControl函数参数dwIoControlCode==560000h时,是哪个IOCTL
  只能硬编码.后来通过查找MSDN,找到是IOCTL_VOLUME_GET_VOLUME_DISK_EXTENTS这个IOCTL,一下解决这个问题.
  取得了硬盘编号后,调用CreateFile来创建对象.然后调用DeviceIoControl以FSCTL_LOCK_VOLUME锁住.
  申请内存调用ReadFileInternal来读取头200h字节的内容,取偏移为0x1c2处的双字,如果此值为0x42,则此磁盘是动态
  磁盘.我猜想200h可能是一个结构,0x1c2是结构中的一个偏移.
  其他接口相对简单,不再多说,请参看源码.
  我的工程中包含了测试代码.
  
  
--------------------------------------------------------------------------------
【经验总结】
  引用一句牛人xIkug的话:代码逆向即是在没有源代码的情况下,对目标程序的行为、数据流、及编译器生成的代码进行分析,通
  过分析我们可以了解、发现程序的功能、流程、规则、及技术实现细节等,通过分析我们能对其进行优化、功能增强、漏洞
  填补、甚至还原成源代码等。这个分析过程我们可以称作逆向分析或逆向工程,简称逆向。
  偶觉得很中肯.
  
--------------------------------------------------------------------------------
【版权声明】: 本文原创于看雪技术论坛, 转载请注明作者并保持文章的完整, 谢谢!

                                                       2008年07月21日 20:53:59

上传的附件 TestWinApiEx.rar [附件请到论坛下载:http://bbs.pediy.com/showthread.php?t=69003 ]