用Qt写软件系列一:QCacheViewer(浏览器缓存查看器)
介绍
Cache技术广泛应用于计算机行业的软硬件领域。该技术既是人们对新技术探讨的结果,也是对当前软硬件计算能力的一种妥协。在浏览器中使用cache技术,可以大幅度提高web页面的响应速度,降低数据传输延迟,提高web用户的体验。因此,客户端在浏览网页的过程中,会在本地缓存许多文件。随着使用时间增长,本地缓存的文件日渐增多。对于用户来说,查看本地主机当前的缓存文件数目和种类成为一种迫切的需要。
作为主项目的一部分功能,我们需要完成这样一个浏览器缓存查看器。在网上偶然看到了一款这样的软件:IECacheViewer。这款软件功能恰到好处,正是我们所需要的。奈何该网站上并未公布其实现方式,因此只好以该软件界面作为模板,自动动手一一实现其功能。寻寻觅觅良久之后,终于发现了两种实现方式:(1)调用windows系统提供的API。这些API使用简单,只需要循环调用即可获取Cache信息。但缺点是,该方法只能扫描当前系统中存在的cache文件信息。(2)解析index.dat文件。index.dat文件采用增量记录方法,所有在系统中曾经存在过的cache文件,在index.dat文件中都有记录。关于index.dat文件是什么,在参考资料中可以得到详尽的答案。我们将在方法二中详细剖析index.dat的结构。
方法一、调用系统API
1. 相关的API:
1 HANDLE FindFirstUrlCacheEntry(
2 __in LPCTSTR lpszUrlSearchPattern,
3 __out LPINTERNET_CACHE_ENTRY_INFO lpFirstCacheEntryInfo,
4 __in_out LPDWORD lpcbCacheEntryInfo
5 );
6
7 BOOLAPI FindNextUrlCacheEntry(
8 __in HANDLE hEnumHandle,
9 __out LPINTERNET_CACHE_ENTRY_INFO lpNextCacheEntryInfo,
10 __in_out LPDWORD lpcbCacheEntryInfo
11 );
12
13 BOOLAPI FindCloseUrlCache(
14 __in HANDLE hEnumHandle
15 );
16
17 typedef struct _INTERNET_CACHE_ENTRY_INFO {
18 DWORD dwStructSize;
19 LPTSTR lpszSourceUrlName;
20 LPTSTR lpszLocalFileName;
21 DWORD CacheEntryType;
22 DWORD dwUseCount;
23 DWORD dwHitRate;
24 DWORD dwSizeLow;
25 DWORD dwSizeHigh;
26 FILETIME LastModifiedTime;
27 FILETIME ExpireTime;
28 FILETIME LastAccessTime;
29 FILETIME LastSyncTime;
30 LPBYTE lpHeaderInfo;
31 DWORD dwHeaderInfoSize;
32 LPTSTR lpszFileExtension;
33 union { DWORD dwReserved; DWORD dwExemptDelta; };
34 } INTERNET_CACHE_ENTRY_INFO, *LPINTERNET_CACHE_ENTRY_INFO;
FindFirstUrlCacheEntry()函数开始枚举Cache信息。其返回一个句柄,该句柄用于所有后续的FindNextUrlCacheEntry()调用。FindCloseUrlCache()函数用户关闭句柄,结束枚举过程。利用上述的三个函数,循环调用并将Cache信息保存在INTERNET_CACHE_ENTRY_INFO结构体中。INTERNET_CACHE_ENTRY_INFO结构体包含了当前Cache文件的详细信息,如文件大小、命中次数、访问时间、修改时间、同步时间等。这样,就可以完成IE Cache信息的提取了。
方法二、 解析index.dat文件
1. 文件结构
如果解析PE文件一样,在解析index.dat文件之前,我们需要知道index.dat文件的组织结构。网上并没有找到index.dat文件的结构说明,只能依着搜到的几个结构体定义来查看index.dat的结构了。大致示意图如下:
一个index.dat文件以small header开始,该small header占0x250个字节,其结构定义如下:
其中最重要的字段是dwHashTableOffset,该字段是DWORD型,在32位机器上占4个字节。dwHashTableOffset保存了index.dat文件中的第一个hash section的地址。nDirCount和DirArray字段分别表示子目录个数和子目录名称数组。通常对于Cache来说,所有的缓存文件都放在一个目录中,这两个字段作用不大。而对于Cookie来说,Cookies文件可能分布于多个子目录中。跟在Small header后面的是full header。其具体作用不详,定义如下:
再来看Hash Section部分。每个hash section都有一个头部,占16个字节。其定义如下:
hash头部的dwSig字段占4字节,是由“HASH”这个四个字母的ASCII码填充的。nBlocks字段表明本哈希节占用多少个块,块单位为0x80字节。dwNext字段指出下一个hash 头部的开始地址,以index.dat文件的起始地址为基准。nOrder则是当前哈希节的编号。紧随头部的便是hash itmes了。一个hash item占8字节,前4字节是哈希值,后4字节是Cache记录在index.dat文件中的偏移,也是以index.dat文件的起始地址为基准。
2. 分析实例
下面以我的机器上的index.dat文件为例进行实例分析:
根据第一个哈希表的偏移地址(0x4000),跳到0x4000处,如下:
可以看到,hash头部第一个字段为:48, 41, 53, 48.为"HASH"四个字母。紧接着的四个字节值为0x20,单位为块,每块大小为128字节。值得注意的是,由于我使用的是小端机(little endian:大端高位在前,小端低位在前),因此需要转换一下。第三个四字节值为0x11000,是下一个hash section的头部地址。第四个四字节值为0,表明当前hash section的编号为0。我们再接着看0x4010位置的值。根据上述的结构定义可知,第一个四字节是哈希值,不用管它。接下来的0x1BA00才是最重要,它指明了hash条目在文件中的偏移位置。注意相对的偏移基准。我们再跳到0x1BA00位置:
这是一个URL类型。在index.dat文件中,hash条目有多种类型,在参考资料中有说明,这里不再赘述。不过,我们应当重点看看hash条目的定义结构:
按着字段大小一一提取即可。到这里,完成了一次cache信息的提取。我们接着要做的,是查看下一个hash section。因此,再跳到0x11000处:
当前编号为1,下一个hash section 在0x23000处。再跳到0x23000看看:
果然,此时下一个hash section 的地址为0,表明这是最后一个section了。当前编号为2.由此可知,这个index.dat文件中只有3个section。所有的hash条目都可以依此提取出来。值得注意的是hash section中存在着空洞。如遇到两个字段都为3或者1,表明这是一个空洞。如下图所示,继续查看,仍然有hash条目存在。当遇到两个字节都是0xDEADBEEF,说明后面不再有hash条目了。
预览效果
参考资料
- Windows 中 Cookie、Internet Temp Files、History、Temp Directory 具体路径(2000、Xp、Vista、Win7)
- 很好的文章:index.dat的分析(也详细介绍了cookie)
- A few words about the cache / history on Internet Explorer 10
- Index.Dat Files and Primary I.E. Folders
- Understanding Microsoft Internet Explorer Cache
- Reading the Internet Explorer Cache
- Exploring the URL Cache
- Internet Explorer History File Format
代码
View it on github.
- SpringBoot2.x开发案例之整合Quartz任务管理系统
- 给出一组非负整数,重新排序组成最大的数
- [机智的机器在学习] TensorFlow实现Kmeans聚类
- [机智的机器在学习] 利用TensorFlow实现多元线性回归分类器
- [数据结构和算法]《算法导论》动态规划笔记(1)
- [数据结构和算法]《算法导论》动态规划笔记(2)
- [算法与数据结构] 《算法导论》堆排序笔记
- [数据结构与算法] 链表的其他类型
- [数据结构与算法] 链接表总结
- [数据结构与算法] 线性表总结
- [数据结构与算法] Python实现二分查找
- [机智的机器在学习] 机器学习中的归一化和正则化问题
- [情人节] jieba分词介绍
- 左手用R右手Python系列——异常捕获与容错处理
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- 放弃dagger?Anrdoi依赖注入框架koin
- WPF开发之C#中关闭进程的方式
- R语言实现:混合正态分布EM最大期望估计法
- MongoDB 指令
- 硬件设计之 Distributed Arithmetic 一例
- JavaScript 框架学习(JQuery)
- MongoDB 部署
- 结构体对齐原则在自定义协议解析时的妙用之法
- Spring JDBC 框架一个最简单的Hello World级别的例子
- Celery 分布式框架 学习
- .NET Core + K8S + Loki 玩转日志聚合
- varint是啥你真的知道么?
- 一篇文章带你入门移动安全
- Could not load JDBC driver class [com.mysql.jdbc.Driver]
- [Bazel]自定义规则实现将多个静态库合并为一个动态库或静态库