Ark's Blog

Happy coding

下载微软符号文件的方法

        我们都知道IDA和Windbg都能在微软的服务器上下载指定文件的符号。但是我遇到了个很头疼的问题,就是如果需要符号的文件在内网,无法连接到外网。外

        网的机器虽然可以连接微软的符号服务器,但是又缺少需要符号的文件。所以在这样的情况下IDA和Windbg都没有办法下载指定文件的符号。于是我就想到自己写一个工具来绕过这个限制,从而在外网下载到需要的符号,然后传到内网。


        在分析了几个下载符号的小工具之后,大概明白了这一过程,同时确定我的方案是可行的。简单说来就是调用dbghelp.dll中的SymFindFileInPath函数。这个函数非常的好用,他的主要功能就是从指定目录寻找指定的符号文件,如果指定的目录中含

        有微软的符号服务器地址,那么他会在没有找到匹配符号的时候,访问微软的服务器,自动的下载所需要的符号。
函数原型如下:

BOOL SymFindFileInPath(
  HANDLE hprocess,
  LPSTR  SearchPath,
  LPSTR  FileName,
  PVOID  id,
  DWORD  two,
  DWORD  three,
  DWORD  flags,
  LPSTR  FilePath,
  PFINDFILEINPATHCALLBACK callback,
  PVOID context
);


        看参数确实够吓人了,two,three...但是,在经过一定的调试之后,并且结合msdn的解释,大概明白这个函数的用法了。hprocess是调用此函数的进程句柄,msdn的说法是这个进程必须调用过SymInitialize函数。但是从对某个软件调试情况来看,似乎这个值为0时,函数也可以正常工作。SearchPath用来指定函数搜索的目录,这里可以指定多个目录,只要用分号隔开就行了。值得注意的如果想从微软的服务器上下载符号,这里就必须填上服务器的地址,例如srv*c:\\DownstreamStore*http://msdl.microsoft.com/download/symbols,这样符号就会下载到c:\\DownstreamStore目录了。callback和context可以见到的赋值为NULL,具体的用法可以参考msdn,这里不需要他们。参数three没有用,赋为0即可。
        重点来了,就是FileName,id,two,flags这四个参数。首先介绍flags,如果要查找的文件的pdb,那么这里就应该指定为SSRVOPT_GUIDPTR,因为这个标志会影响到参数id和参数two的意义。FileName是需要下载的符号的pdb的文件名,比如kernel32.dll所需要的pdb就是kernel32.pdb。当然如果直接用该文件名的方法获取符号文件名显然是不可靠的。后面我会介绍这么获取他。id这个参数,在flags的标志为SSRVOPT_GUIDPTR的时候,它是pdb的signature。这时候的two参数是pdb的age。

        OK,现在的问题就是,如果从文件中获得这些参数。

        这就涉及到一些PE结构的知识了。这并不是一片讲解PE结构的文章,所以具体PE的内容可以到网上搜索。这里我主要关注的就是调试节,不用猜关于调试的所有信息都放在这个地方。
 

IMAGE_NT_HEADERS32
 |
 +------------- IMAGE_OPTIONAL_HEADER32
    |
    +---------------------IMAGE_DATA_DIRECTORY


        这里会看到VirtualAddress和Size。如图,这是kernel32.dll的数据。

        

 

        

 

        这些数据实际上表达了一个调试的信息的结构
 

typedef struct _IMAGE_DEBUG_DIRECTORY {
    DWORD   Characteristics;
    DWORD   TimeDateStamp;
    WORD    MajorVersion;
    WORD    MinorVersion;
    DWORD   Type;
    DWORD   SizeOfData;
    DWORD   AddressOfRawData;
    DWORD   PointerToRawData;
} IMAGE_DEBUG_DIRECTORY, *PIMAGE_DEBUG_DIRECTORY;


        看看下图,其中原始数据指针就是我们想要的(PointerToRawData)。从这个地址,我们就可以得到SymFindFileInPath真正需要的信息了。

        

        

        这段数据是一个CV_INFO_PDB70结构,前四个字节是一个结构的标示,后面连续的16个字节是SymFindFileInPath所需要的id,它是一个GUID结构,其后面的四个字节是我们所需要的two,它是一个DWORD,最后就是pdb的文件名了。

        最后,需要提醒的一点,SymFindFileInPath是dbghelp.dll里的函数,但是他也用到了symsrv.dll,所以我们必须把写好的程序和这两个dll放在一起,否则函数SymFindFileInPath会返回错误。

        OK,有了这些,我也实现了我预先期望的效果。在内网中分析PE文件,dump出PE的调试信息,保存在txt上。然后在外网机器上,创建一个txt,把内网的txt内容“”到外网的txt中,然后用下载程序分析txt的内容下载正确的pdb。整个过程还是挺纠结的,但是有符号总比没有好。