标签:
前面讲过,解压出apk文件的内容是进行apk分析的第一步,而.apk文件其实就是.zip文件。也就是说首先要实现zip文件的解压缩。本文将分别介绍在Windows和Ubuntu下如何使用zlib这一开源库对zip文件进行解压。
zlib is designed to be a free, general-purpose, legally unencumbered – that is, not covered by any patents – lossless data-compression library for use on virtually any computer hardware and operating system. The zlib data format is itself portable across platforms.
zlib是一个C语言编写的、开源的、几乎在所有的计算机和操作系统上都使用的数据压缩库,更多的介绍请查看zlib官方网站;
简而言之,zlib其实是一种数据压缩算法,zip文件压缩只是这种算法的应用之一。
关于zlib压缩算法参考这篇文章:http://blog.jobbole.com/76676/
在zlib的源码中,有一个\contrib\minizip文件夹,这个文件夹中封装了zlib算法,使得其可以用来解压和压缩zip格式的文件。
zlib目前的版本是1.2.8,要下载的内容包括源码和编译好的dll文件(也可以自己使用源码编译)。
zlib source code:http://zlib.net/zlib-1.2.8.tar.gz;
zlib compiled DLL:http://zlib.net/zlib128-dll.zip;
下载完成后将这两个文件解压。
网上有很多关于zlib的文章描述的都是如何使用zlib进行压缩和解压字节流,也就是使用
compress (Bytef *dest, uLongf *destLen, const Bytef *source, uLong sourceLen)
来对字节流进行压缩,使用
uncompress (Bytef *dest, uLongf *destLen,const Bytef *source, uLong sourceLen)
来对字节流进行解压缩。
这两个函数的操作对象就是读入内存的字节流。下面给出一个使用这两个函数的例子(暂时不用关心如何正确配置zlib库使得程序顺利编译):
#include <iostream>
#include <zlib.h>
#include <cstring>
using namespace std;
int main()
{
char str[11] = "HelloWorld";
Byte *Source ;
Byte *Destination;
unsigned long DestLen ;
unsigned long SourLen = strlen(str)+1;
DestLen = compressBound(SourLen);
Source = new Byte[SourLen];
Destination = new Byte[DestLen];
Source = (Byte *)str;
int err = compress(Destination, &DestLen, Source, SourLen);
if (err == 0)
{
cout <<"压缩完成"<< endl;
cout << "原字符串:" << Source << endl;
cout << "压缩后字符串:" << Destination << endl;
cout << "压缩后字符串长度:" << DestLen << endl;
}
else
{
cout << "压缩失败" << endl;
}
Byte *uncompressSource;
Byte *uncompressDest;
unsigned long uncompressSourceLen = DestLen;
unsigned long uncompressDestLen = SourLen;
uncompressSource = Destination;
uncompressDest = new Byte[uncompressDestLen];
err = uncompress(uncompressDest, &uncompressDestLen, uncompressSource, uncompressSourceLen);
if (err == 0)
{
cout << "解压完成" << endl;
cout << "解压前字符串:" << uncompressSource << endl;
cout << "解压后字符串:" << uncompressDest << endl;
cout << "解压后后字符串长度:" << uncompressDestLen << endl;
}
else
{
cout << "解压失败" << endl;
}
system("pause");
return 0;
}
运行结果:
。
至于为什么压缩后的字节长度比压缩前还长,那是因为我们压缩的字节不够长(11个Byte),如果有足够长的字节,那么压缩效果就可以看出来了。
当然,你也可以把“HelloWorld”这个字符串替换成从外部文件中读取的一段字节流。
但是这个结果并不是我们想要的,我们要做的事解压zip文件。
前面提到,zip文件压缩和解压只是对zlib算法的一种应用。事实上,在前面的对字节流的压缩完成后,如果直接将压缩后的数据写入到一个.zip文件中,使用一般的zip解压缩软件是不能打开的。因为zip文件有一定的文件结构。关于zip文件的结构网上有很多文章,就不再赘述了。
要使用zlib库对zip文件进行压缩和解压,就需要我们在zlib库的基础上再做额外的工作,使得其符合zip文件结构。感谢zlib的开发人员,这个工作他们已经做好了。
在zlib的源码中,找到\contrib\minizip文件夹,这个文件夹中有两个头文件zip.h和unzip.h,这就是对zlib库的封装。还有两个例子minizip.c和miniunz.c,描述了压缩和解压zip文件的基本操作。
#include <iostream>
#include <zlib.h>
#include "unzip.h"
#include <cstring>
#include <fstream>
#include <direct.h>
using namespace std;
direct.h头文件用来创建目录
C++创建目录的操作在Windows和Unix下是不同的。
在Windows下,要
#include<direct.h>
使用
int _mkdir(_In_z_ const char * _Path)
函数。
在Unix下,要
#include<sys/stat.h>
#include<sys/types.h>
使用
int mkdir(const char *pathname, mode_t mode)
函数。
参考这两篇文章:
涉及到的minizip库中的解压缩函数有:
unzFile unzOpen64(const char *path);
int unzClose(unzFile file);
int unzGetGlobalInfo64(unzFile file, unz_global_info *pglobal_info);
int unzGoToNextFile(unzFile file);
int unzGetCurrentFileInfo64(unzFile file, unz_file_info *pfile_info, char *szFileName, uLong fileNameBufferSize, void *extraField, uLong extraFieldBufferSize, char *szComment, uLong commentBufferSize);
int unzOpenCurrentFile(unzFile file);
int unzCloseCurrentFile(unzFile file);
int unzReadCurrentFile(unzFile file, voidp buf, unsigned len);
一个解压缩过程包括:
1. 使用unzOpen64函数打开一个zip文件;
2. 使用unzGetGlobalInfo64函数读取zip文件的全局信息,保存在一个unz_global_info64类型的结构体中;
3. 使用unz_global_info64结构体中的number_entry变量循环读取zip文件中的文件,并进行读取、解压、写入、保存等操作
4. 在unzFile结构中,有一个指针指向当前文件,默认指在第一个文件。操作完一个文件后,使用unzCloseCurrentFile函数关闭这个文件,再使用unzGoToNextFile函数使得指针指在下一个文件上,再对该文件进行操作;
5. 解压完整个zip文件后,使用unzClose函数释放资源。
int main()
{
unzFile zfile;//定义一个unzFile类型的结构体zfile
//定义zip文件的路径,可以使用相对路径
char filepath[]="extract/test.zip";
//调用unzOpen64()函数打开zip文件
zfile = unzOpen64(filepath);
if (zfile == NULL)
{
cout << filepath << "[INFO] 打开压缩文件失败" << endl;
return -1;
}
else
{
cout << "[INFO] 成功打开压缩文件" << endl;
}
unz_global_info64 zGlobalInfo;
//unz_global_info64是一个结构体
//其中最重要的是number_entry成员
//这个变量表示了压缩文件中的所有文件数目(包括文件夹、
//文件、以及子文件夹和子文件夹中的文件)
//我们用这个变量来循环读取zip文件中的所有文件
if (UNZ_OK != unzGetGlobalInfo64(zfile, &zGlobalInfo))
//使用unzGetGlobalInfo64函数获取zip文件全局信息
{
cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
return -1;
}
//循环读取zip包中的文件,
//在extract_currentfile函数中
//进行文件解压、创建、写入、保存等操作
for (int i = 0; i < zGlobalInfo.number_entry; i++)
{
//extract_currentfile函数的第二个参数指将文件解压到哪里
//这里使用extract/,表示将其解压到运行目录下的extract文件夹中
int err = extract_currentfile(zfile, "extract/");
//关闭当前文件
unzCloseCurrentFile(zfile);
//使指针指向下一个文件
unzGoToNextFile(zfile);
}
//关闭压缩文件
unzClose(zfile);
system("pause");
return 0;
}
输入参数:
1. unzFile zfile——要解压的unzFile类型的变量
2. char * extractdirectory——解压的目标文件夹路径即解压到哪里
Return:
int
这个函数的流程如下:
1. 使用
unzGetCurrentFileInfo64(zfile, &zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0)
函数获取当前zfile文件中指针指向的文件的文件信息,保存在unz_file_info64类型的结构体zFileInfo变量中,获取当前文件相对于zip文件的相对路径和文件名,保存在字符串fileName_WithPath中;
2. 修改fileName_WithPath变量,使得其包含解压的目标文件夹
3. 判断当前文件是目录还是文件,如果是目录则创建目录,如果是文件则解压文件
4. 如果是文件,则使用 unzOpenCurrentFile函数打开当前文件
5. 使用fstream库以二进制流的形式创建当前文件
6. 使用unzReadCurrentFile读取当前文件内容到一个字符串中
7. 将字符串写入创建好的文件
int extract_currentfile(unzFile zfile,char * extractdirectory)
{
unsigned int fileName_BufSize = 512;
char *fileName_WithPath=new char[fileName_BufSize];
char *p,*fileName_WithoutPath;
unz_file_info64 zFileInfo;
p = fileName_WithoutPath = fileName_WithPath;
if (UNZ_OK != unzGetCurrentFileInfo64(zfile,
&zFileInfo, fileName_WithPath,
fileName_BufSize, NULL, 0, NULL, 0))
{
cout << "[ERROR] 获取当前文件信息失败" << endl;
return -1;
}
char *temp = new char[fileName_BufSize];
//修改fileName_WithPath,使得extractdirectory加在其前面
strcpy_s(temp, 512, extractdirectory);
strcat_s(temp, 512, fileName_WithPath);
fileName_WithPath = temp;
//判断当前文件是目录还是文件
while ((*p) != ‘\0‘)
{
if (((*p) == ‘/‘) || ((*p) == ‘\\‘))
fileName_WithoutPath = p + 1;
p++;
}
if (*fileName_WithoutPath == ‘\0‘)
{
cout << "[INFO] " << "成功读取当前目录:" << fileName_WithPath << endl;
cout << "[INFO] " << "开始创建目录:" << fileName_WithPath << endl;
//创建目录
int err = _mkdir(fileName_WithPath);
if (err != 0)
cout << "[ERROR] " << "创建目录"
<< fileName_WithPath << "失败" << endl;
else
cout << "[INFO] " << "成功创建目录"
<< fileName_WithPath << endl;
}
else
{
cout << "[INFO] " << "成功读取当前文件:"
<< fileName_WithoutPath << endl;
cout << "[INFO] " << "开始解压当前文件:"
<< fileName_WithoutPath << endl;
//打开当前文件
if (UNZ_OK != unzOpenCurrentFile(zfile))
{
//错误处理信息
cout <<"[ERROR] "<< "打开当前文件" <<
fileName_WithoutPath << "失败!" << endl;
}
//定义一个fstream对象,用来写入文件
fstream file;
file.open(fileName_WithPath, ios_base::out | ios_base::binary);
ZPOS64_T fileLength = zFileInfo.uncompressed_size;
//定义一个字符串变量fileData,读取到的文件内容将保存在该变量中
char *fileData = new char[fileLength];
//解压缩文件
ZPOS64_T err = unzReadCurrentFile(zfile, (voidp)fileData, fileLength);
if (err<0)
cout << "[ERROR] " << "解压当前文件"
<< fileName_WithoutPath << "失败!" << endl;
else
cout << "[INFO] " << "解压当前文件"
<< fileName_WithoutPath << "成功!" << endl;
file.write(fileData, fileLength);
file.close();
free(fileData);
}
return 0;
}
需要在运行目录下创建一个extract文件夹,将要解压的文件复制到该文件夹下,并对代码中的压缩文件夹路径进行修改
在/extract文件夹中查看解压好的文件:
./configure
make
sudo make install
代码和windows下唯一的区别在于前面提到的创建文件夹要包含的头文件和使用的函数不同。
创建一个zlibtest.cpp文件,输入下列代码
#include <iostream>
#include <zlib.h>
#include "unzip.h"
#include <cstring>
#include <fstream>
#include <sys/stat.h>
#include <sys/types.h>
using namespace std;
int extract_currentfile(unzFile zfile,char *extractdirectory)
{
unsigned int fileName_BufSize=512;
char *fileName_WithPath=new char[fileName_BufSize];
char *p,*fileName_WithoutPath;
unz_file_info64 zFileInfo;
p = fileName_WithoutPath = fileName_WithPath;
if (UNZ_OK != unzGetCurrentFileInfo64(zfile,
&zFileInfo, fileName_WithPath, fileName_BufSize, NULL, 0, NULL, 0))
{
cout << "[ERROR] 获取当前文件信息失败" << endl;
return -1;
}
char *temp = new char[fileName_BufSize];
strcpy(temp,extractdirectory);
strcat(temp,fileName_WithPath);
fileName_WithPath = temp;
while ((*p) != ‘\0‘)
{
if (((*p) == ‘/‘) || ((*p) == ‘\\‘))
fileName_WithoutPath = p + 1;
p++;
}
if (*fileName_WithoutPath == ‘\0‘)
{
cout << "[INFO] " << "成功读取当前目录:" << fileName_WithPath << endl;
cout << "[INFO] " << "开始创建目录:" << fileName_WithPath << endl;
int err = mkdir(fileName_WithPath,S_IRWXU|S_IRWXG|S_IROTH);
if (err != 0)
cout << "[ERROR] " << "创建目录" << fileName_WithPath << "失败" << endl;
else
cout << "[INFO] " << "成功创建目录" << fileName_WithPath << endl;
}
else
{
cout << "[INFO] " << "成功读取当前文件:" << fileName_WithoutPath << endl;
cout << "[INFO] " << "开始解压当前文件:" << fileName_WithoutPath << endl;
if (UNZ_OK != unzOpenCurrentFile(zfile))
{
//错误处理信息
cout <<"[ERROR] "<< "打开当前文件" << fileName_WithoutPath << "失败!" << endl;
}
fstream file;
file.open(fileName_WithPath, ios_base::out | ios_base::binary);
ZPOS64_T fileLength = zFileInfo.uncompressed_size;
char *fileData = new char[fileLength];
//解压缩文件
ZPOS64_T err = unzReadCurrentFile(zfile, (voidp)fileData, fileLength);
if (err<0)
cout << "[ERROR] " << "解压当前文件" << fileName_WithoutPath << "失败!" << endl;
else
cout << "[INFO] " << "解压当前文件" << fileName_WithoutPath << "成功!" << endl;
file.write(fileData, fileLength);
file.close();
free(fileData);
}
return 0;
}
int main()
{
unzFile zfile;
char filepath[]="./extract/test.zip";
zfile=unzOpen64(filepath);
if(zfile==NULL)
{
cout<<"[ERROR] 文件不存在"<<endl;
return -1;
}
else
{
cout << "[INFO] 成功打开压缩文件" << endl;
}
unz_global_info64 zGlobalInfo;
if (UNZ_OK != unzGetGlobalInfo64(zfile, &zGlobalInfo))
{
cout << "[ERROR] 获取压缩文件全局信息失败" << endl;
return -1;
}
for (int i = 0; i < zGlobalInfo.number_entry; i++)
{
int err = extract_currentfile(zfile, "extract/");
unzCloseCurrentFile(zfile);
unzGoToNextFile(zfile);
}
return 0;
}
gcc ./ioapi.c -c
得到ioapi.o文件gcc ./unzip.c -c
得到unzip,o文件g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so
?????得到zlibtest可执行文件
?????7. 在zlibtest.cpp所在文件夹中建立一个extract文件夹,将要解压的test.zip复制到里面
?????8. 运行./zlibtest
,zip文件就会解压到extract文件中
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so
-L参数后面链接了一个静态库libz.a,一个动态库libz.so
我是怎么知道要链接这两个库的呢(不链接会报错)?我是天才
查看刚才安装zlib的过程就可以看到,在sudo make install这一步,
将一些库文件复制到了/usr/local/lib文件夹中,其中就包含了两个文件:libz.a和libz.so.1.2.8,但是在编译命令中我使用了libz.so而并没有使用libz.so.1.2.8.,为什么编译还是通过了呢?
那么我改用libz.so.1.2.8来编译,输入
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -L /usr/local/lib/libz.a /usr/local/lib/libz.so.1.2.8
来编译,还是通过了。这就很奇怪了,libz.so是从哪里来的?
首先找到libz.so所在的文件夹也就是/usr/local/lib下:
这里竟然有三个文件:libz.so,libz.so.1,libz.so.1.2.8!打开它们的属性,这三个文件竟然是同一时间创建的!而且具有相同的文件大小!
这就表示,在创建libz.so.1.2.8的时候,其它两个文件就一起创建了。但是在sudo make install这一步并没有出现创建其它两个文件的命令,只有将libz.so.1.2.8复制到/usr/local/lib下的命令。
那么很可能就在前一步也就是运行make命令的时候创建的。
果然在make这一条命令运行的终端输出中发现了这个:
这两条命令:
ln -s libz.so.1.2.8 libz.so
ln -s libz.so.1.2.8 libz.so.1
就是创建libz.so和libz.so.1的命令。ln的含义就是创建一个链接,可以理解为快捷方式。
但是为什么要创建这两个链接呢?
在gcc的命令参数中,-l和-L是用来链接库文件的,关于这两个参数的用法,参见文章:
http://www.cnblogs.com/benio/archive/2010/10/25/1860394.html
那么,我就可以使用-lz来替代-L参数了,如下:
g++ ./ioapi.o ./unzip.o ./zlibtest.cpp -o zlibtest -lz
编译成功。
知道了-l的用法,创建libz.so.1.2.8的链接libz.so和libz.so.1的原因就很清楚了,就是为了-lz命令的使用。
既然能够用gcc编译成功,那么在linux下使用QtCreator编写解压的程序就很简单了,因为QtCreator用的也是gcc嘛。
至于如何压缩文件,基本操作也可以在网上找到,重点就在如何配置编译环境。
使用zlib解压.apk/.zip文件(Windows&Ubuntu)
标签:
原文地址:http://blog.csdn.net/leehdsniper/article/details/51321501