项目里的so是个黑盒,如何了解内在逻辑

您所在的位置:网站首页 epoll是干嘛的 项目里的so是个黑盒,如何了解内在逻辑

项目里的so是个黑盒,如何了解内在逻辑

2023-03-11 15:15| 来源: 网络整理| 查看: 265

前言

大家好,我是逐日,今天依然是不知道自己阳没阳的一天,不知道是感染了,还是昨晚睡觉的原因,嗓子有一点不舒服,希望还阴着吧。

昨天写了一篇文章,讲最近排查的一个问题,我个人感觉,通过公号写出来呢,还是有用处的,一个是梳理,一个是备忘,再一个呢,还能增进了解。昨天我在文末,就讲到了一个细节,没有展开讲,为啥呢,因为昨晚查了下,暂时没查清楚。

今天起来后,看着外面那只有2,3度的气温,也不想出门了,于是又查了一会资料,这把差不多了解了,又学废了一个新技能,这给大家讲讲,大概会涉及so反编译的问题。

问题背景

我们公司的老项目,少说10多年了吧,那时候java开发应该还是用servlet的年代,我估计那时候struts框架可能都还不怎么流行吧,公司可能是没有这方面的技术研发能力,所以买了深圳某公司的成熟框架来做服务。

这个容器,我先说说,是怎么个用法,如下图,有个二进制文件,假设叫TTTServer,这个就是容器本身了,然后有个TTTServer.xml用来做一些容器的配置。

容器怎么启动呢?执行./TTTServer就能启动起来了。

这个容器,是c++写的,这个TTTServer就是打包后的可执行的二进制文件。它呢,不是servlet容器,它更像是我们用netty写的那种容器,而且协议也不是http的,而是自定义的。

它的原理呢,经过我的探索,已经知道,类似于netty的reactor模式,使用了epoll,也就是一个线程负责accept客户端连接,有另外的线程向epoll注册,表示对这个连接的io事件感兴趣。有请求进来时(相当于是io事件发生),线程这边开始解析该请求,解析完成后,放到一个队列中。

有另外的线程从队列中获取该请求,根据请求参数中的接口id,来反射调用对应的业务代码,拿到结果后,再把响应写回去。

我觉得,很像现在的rpc,比如dubbo,这么看起来,技术其实一直变化也没多大,只是各种新名词,不断地新瓶装旧酒。

image-20221218162808275

然后,补充一点,我们业务人员,就只需要配置如下的一个配置,来指明某个接口对应的class类名即可。

我们的class就是我们的业务类,就算是一个接口写好了,然后给它一个id,这里是一个数字,我们叫功能号。然后,客户端请求时,就会携带功能号和对应的参数。

对于这样一个容器,其实手册里更简单,只说你要怎么怎么配置,然后就可以了;而TTTServer本身,就是一个大黑盒,不出问题还好,出了问题,保证你抓瞎。

昨天那个文章,就说的是,因为我们的业务class中,用到了jackson的jar,但是,我们在classpath中又没有,直接导致框架这个黑盒在底层报错了,我本来是直接找厂商协助的,结果呢,厂商那边,说是我手里这个项目,是没有签合同的,没有维护的义务了,后边就是我自己查,查了好几天才找到是这个原因。

所以呢,在我的负责的业务范围内,是不希望有这么大一个黑盒存在的。

接下来,就说说我是怎么来了解这个黑盒的吧。

线索

昨天的文章提到,是在日志里发现了如下报错:

image-20221217203846368

看我标红处,有个Dispatcher.java,看起来是入口,但是我在各种jar包里挖地三尺,没找到这个类;通过arthas连上,找到了这个类,也找到了其类加载器,但就是不知道类是哪里来的(如某个jar包),看起来也不像是那种动态生成的,真的很让人困惑。

昨晚,我就想着,怕不是这个TTTServer里面搞了啥事情吧,能不能反编译出来看看啊?于是搜了下,发现了IDA这个神器,但是吧,神器是神器,下载却是个问题,到处都找不到资源,当时找了个网站,一下载就要你提交公司信息,提交申请啥的,当时还以为那个是官网,现在看来,应该是找错了。

今天早上想着再试试,发现官网应该是这个:https://hex-rays.com/ida-free/,顺利下载下来了。

然后安装,打开我们的TTTServer这个文件,开始分析。

分析

我之前也没用过,好在互联网提供了各种教程。打开文件后,左边就是各种函数,我们查找,找到入口的main函数,双击打开后,可能发现看不懂,这时候可以按F5,查看伪代码。

我这边,会看到如下伪代码,比如,检查是32位还是64位,检查license(商业公司嘛):

比如我这边双击这个函数,就会进入到对应的函数里面,比如下图这里,就开始检查我们的license文件内容了:

如果检查没通过,就会执行:

if ( !IsLicenseOK() ) { OUT(&byte_463060); exit(1); }

这里看着就是要输出日志,而日志一般是一个字符串,字符串在这个软件里面,就是用一个地址来代表,这里的

463060就是地址的值,我们可以双击跳过去。

image-20221218171438585

跳过来后,发现还是看不懂呢,经过我一个小时的摸索,这时候,你可以点击上面菜单栏:View--》Open subView --》 HexDump,就会打开一个16进制视图,如下:

image-20221218172138813

我这边有中文,当然了,你得配置对应的编码格式才能正确展示,否则还是乱码。

配置的方法就是:Options->General--》Strings,Default-8Bit,改成gbk,没有gbk的话,就先加一下这个字符集。

https://blog.csdn.net/qq_36535153/article/details/111252053

分析手段就这么点,其他的可以自己学习,接下来,简单说下我们这个so的逻辑。

so的启动过程

这块逻辑我就简单一些了,不然全是图。

1、配置读取

image-20221218173228562

2、日志目录创建、日志文件创建

mkdir("./log", 0x1FDu);

image-20221218173453721

3、创建java虚拟机实例

g_pJavaVM = CreateJavaVM();

这块我理解,就是去启动java虚拟机。启动前,估计要先找到全部的classpath那些,以及各种虚拟机参数,

image-20221218174414831

4、加载自定义的类

看下图,基本就明了了,会读取一个叫做libdata.so的文件,然后解密,解密后,其实就是Dispatcher这个类的class文件了,加载到jvm即可,至此,我之前的疑惑就算是解答了,原来这个类他么真是在这个so里搞的。

image-20221218174711552

之前我就是看到下面这几个so,头都大了,不知道是干嘛的,是不是不能乱动,动了可能就出问题,原来里面都是存放的加密后的class文件,我也尝试过自己解密,但是,没成功。

BlowFish是一个对称加密算法,了解仅限于此。

image-20221218174946518

5、启动各个线程

由于c++里面都是各种函数指针,这块看着眼睛花,我就主要搜索函数名包含init的函数,找到各个c++的类,再用类名过滤,挨个看看里面的方法。

so中的运行逻辑

经过一番查找(伪代码界面,可以右键:Jump to Xref,可以查找用到这个函数的地方),找到了如下入口:

这个一看就是nio相关代码,

image-20221218180801251

下面这里,会根据进来的io事件的类型,调用不同的函数,如请求进来,是调用OnReceive方法:

for ( ia = 0; ia < nCount; ++ia ) { if ( (events[ia].events & 1) != 0 ) { CReactor::OnReceive(thisa, events[ia].data.fd); } else if ( (events[ia].events & 4) != 0 ) { CReactor::OnSend(thisa, events[ia].data.fd); } else if ( (events[ia].events & 8) != 0 || (events[ia].events & 0x10) != 0 ) { CReactor::OnError(thisa, events[ia].data.fd); } }

再往后面走,基本就是解包,然后我看到如下代码:

image-20221218181305954

不过上面好像都不是业务消息,我又在另一个函数中,CReactor::ProcessTKPacket(CReactor *const this, CClient *pClient)找到业务消息处理的地方,在这里,会完成全部解码,把消息放到队列里。

CWorkQueue::AddMessage(pWorkQueue, pReqMsg);

另一个线程,会去队列取出来,消费:

具体消费如下,对消息解密,然后调用另一个函数:

然后,就开始调用java了:

image-20221218181830458

主要是调用Dispatcher类:

image-20221218182123839

Dispatcher类,又会去加载我们的class,反射调用,加载出错,就会报我之前的那个错误,如下:

} else if ( std::operator==(&strMsg, "-1011") ) { nErrNo = -1011; std::string::operator=(&strErrMsg, &unk_466958);---注意这个地址 }

上面的466958这个地址,对应的错误信息,就是:

image-20221218182307951

到这里,基本就完结了,整个链路都通了,撒花撒花!

总结

没啥可总结的,java层面的问题,感觉还是没那么难的,只要信息给够,可以复现的问题,都能大概找到原因;不能复现的问题,找不到原因的话,那基本也是信息不够(日志不够、监控没有),那就没办法,悬案。

又学废一个ida新技能,挺开心,目前这个行业,动态库还是挺多的,又多了一种定位方法。



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3