如何在没有资料的情况下调用Linux链接库

引言

Moto E2是一部具有相当性价比的Linux智能手机,但是由于某些商业上的原因并没有开放其本地的图形库SDK,导致其竞争力与扩展性大幅度的降低。本文以我在编写其应用中获得的经验,介绍如何在没有相关资料的情况下导出对应的头文件并且编写相应的程序代码。

注意,本文并非面对一般的手机用户,如果想顺利的阅读这篇文章,请首先学习一些相关的C/C++知识以及编译知识。当然,由于本人所学限制,可能有错误,甚至是缪误,也欢迎众多高手斧正讨论。另外,这篇文章所描述的方法并不只限于Moto E2的EZX图形库的使用方法,理论上能够应用于任何类似的环境。

一. 原理

为了破解这些没有资料的动态连接库,首先必须理解在linux下面的程序编译。下面首先假定需要破解的动态连接库是某个系统的基础(例如,Moto E2手机的图形库即EZX,这是一个基于QT2.3.8的扩展图形库,也就是我们需要破解的部分),否则作为非通用性(仅对当前系统而言)没有任何的实际意义。

Linux的程序在链接库的使用方式上有三种。

1.编译时链接.a库文件并将起代码拷贝到生成的二进制文件中,运行时脱离.a库文件运行。

2.编译时链接.so库文件并将其作为引用生成二进制文件,运行时需要依赖相应.so文件。

3.在程序用以动态链接库的方式调用相关的.so库文件,在编译器不依赖这些.so文件,但是在运行期需要依赖这些.so文件。

作为现状来说,第一种方式首先被排除了,因为作为发行版不可能有.a静态链接库。第三种方式也并不适合,因为在编译器不依赖库文件,导致我们不能正确的判断相应函数的类型,反而造成破解效率的低下,当然还有更重要的原因会在后面提到。因此选择第二中方式是比较合适的。

那么,使用第二种方式我们需要那些东西才能正确的编译一个程序呢?

首先,源代码是必须的,就是真正我们写的程序;其次,是so链接库,这是我们程序运行的基础;最后,是连接程序和so链接库的头文件。这个头文件是用于描述so链接库的基础。(当然,一个相应的gcc也必不可少)。

源文件是我们自己写的东西,so文件是从目标系统中导出的,现在我们缺乏的仅仅是相应的头文件。只要我们得到这个头文件,即可自由的编译基于目标系统的程序。

另外补充一点,用这种方式生成的二进制可执行文件和so链接库的联系在调用时依赖的是函数名称的文本方式,这也是我们破解原理的基础。

二. 导出函数列表

由于能够找到相应的gcc以及导出so链接库,大部分人都能够通过简单的nm命令导出so链接库的函数列表,只需要对相应的so链接库(例中链接库名称为a.so)

arm-linux-nm -gsDC –format=bsd –defined-only a.so

当然在运行这段代码之前要设定正确的环境变量,本文重在讲述原理,这些具体实施细节上就不那么严谨了。当然你也可以使用其他的方式来调用nm指令,只是上面给出的参数能够获得至少是我比较满意的效果。

这样就可以导出这个库文件的导出函数库。例如某so文件的导出函数库为:

4184cde8 T colorToString(QColor)

418ab0c4 T setLanguageId(int)

4184cb70 T stringToColor(QString)

….

41a21244 T KbAltItems::initMetaObject()

41a21358 T KbAltItems::staticMetaObject()

41a21424 T KbAltItems::signalChooserResults(int, int, QString const&, int)

41a212b8 T KbAltItems::tr(char const*)

……

在这里我们可以得到相应的函数声明,唯一没有的仅仅是返回值,幸好作为基础库函数一般来说有良好的命名规范,我们大体上能够从函数名称上猜出其返回值。当然这或许需要一定的经验积累以及良好的运气作为基础。

比如

4184cde8 T colorToString(QColor)

4184cb70 T stringToColor(QString)

这两个函数明显是一对配对的函数进行颜色和字符串的相互转换,那么其正确的形态我们能够很简单的得出:

QString colorToString(QColor);

QColor stringToColor(QString) ;

这样在我们的程序中直接声明这样的函数,并且在编译时指明链接相应的so文件就能成功的编译通过并且使用这两个函数为我们的程序服务。

当然在很多时候这些函数并非那么轻易的得出返回值的说明,有时候对于某些函数放弃返回值,直接声明void或许是一个比较好的折衷选择。

如果你的目标是纯C的链接库,恭喜你,现在你已经成功的破解并且能够使用了,当然我相信既然你点开了这篇文档,那么你的目标系统必然不是纯C写的系统。那么,对于C++的链接库应该怎么处理呢?

三. 处理C++的导出函数

将C++的导出函数构造成为一个类,并且能够正常的使用,这是一个漫长的过程,期间拼的不仅仅是良好的技术和丰富的经验,优秀的运气和富有亲和力的人品才是是否能够搞定一个系统的关键(笑,我就是因为这几个方面都比较欠缺所以导出的SDK到现在都是一个半吊子)。

首先,我们需要明确的一点是,so链接库中,仅仅是函数列表的集合,任何一个类的成员函数,在这里只是名称比C函数古怪的普通函数而已。这可能和大家想象的有些大相径庭,不过正是以为如此我们才使用前面所说的三种方法中的第二种的最根本原因。

因此,一切抱有so中含有类的正确格式信息的想法都是不现实以及不实际的,因为so链接库中根本就没有所谓的C++概念,一切都是编译器的杰作而已。

还是不提这些问题,我们先把一个类函数表组成一个类的形式:(如上面得到的结果片段)

class KbAltItems

{

public:

void initMetaObject() ;

void staticMetaObject() ;

void signalChooserResults(int, int, QString const&, int) ;

void tr(char const*) ;

};

(由于系统的特殊性,某些函数的特殊处理不再本文的讨论之列)

当然既然是导出函数,必然函数是可见的,属性都是public。这时候这样构成一个class已经能够正确的编写程序并且通过编译了,不过很遗憾的是,只要一使用这个类,无论是如何使用,都会立即崩溃,segment fault。

原因事实上很简单:这个类定义里面没有定义正确的成员变量。

无论是直接声明一个类的对象或者使用new方式,一个对象的大小是在编译期决定的,其大小是根据头文件中类的大小决定的,我们导出的class中没有含有相应的成员变量,大小为4(当然为什么一个什么都没有的类的对象也有4个字节的大小就是另外一段故事了,这里暂时不讨论)。一旦这些成员函数想要调用并没有分配但是被它“认为”是存在的成员变量时,就会访问到不恰当的内存单元激发系统的内存异常,segment fault也是很平常的事情了。

由于so中不存在相应的类格式,那么正确的使用一个类是不可能的事情了么?那也未必。事实上有一种非常粗旷的方法来规避这个问题,当然这个也是这篇文章唯一值得炫耀的一点了:

在类的定义中加入 char tmp[X];作为成员变量。

X的大小我们能够通过简单的测试的出来,通过这样再试试,是不是以前不能运行的类能够正确运行了?

其原理也非常简单,既然对象没有足够的空间来正确的执行,那么我们就给它分配一个足够大的空间让它正确的执行。

这样,源代码、库、头文件我们都有了,那么,应用程序还会远么?

写在最后

事实上,成功的构造一个类远远不止这些;首先要揣摩类的正确使用方法;其次,类之间的继承关系我们只有通过类函数的相关性和类名称的联系进行“有限的猜想”。当然,由于使用了上述的那种粗旷的解决方法,只要保证使用了足够的空间将起所有父类都包括进来,也并非必须;另外,还要对整个系统的架构进行彻底的分析,否则不能将其连接起来;最后,枚举能够通过强制转换int值的方式进行转换,而纯粹的结构体就必须直接放弃了,因为除了名字什么也没有,我们只有通过更彻底的阅读反编译的代码进行解读,破解系统时太消耗时间了。

最后是一个小技巧:对于类中难以使用的类(比如依赖其他的类作为参数的),直接注释掉就好了,对于链接仅仅链接使用了的函数,没有使用的函数是不处理的。

预祝对某些系统想入非非的程序员能够获得一个让自己满意的结果^o^。

以前我是个爱仰望天空的人,苍蓝的天空总是给我求生的勇气,

如何在没有资料的情况下调用Linux链接库

相关文章:

你感兴趣的文章:

标签云: