大家好,我是逐日,今天依然是不知道自己阳没阳的一天,不知道是感染了,还是昨晚睡觉的原因,嗓子有一点不舒服,希望还阴着吧。
昨天写了一篇文章,讲最近排查的一个问题,我个人感觉,通过公号写出来呢,还是有用处的,一个是梳理,一个是备忘,再一个呢,还能增进了解。昨天我在文末,就讲到了一个细节,没有展开讲,为啥呢,因为昨晚查了下,暂时没查清楚。
今天起来后,看着外面那只有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的逻辑。
这块逻辑我就简单一些了,不然全是图。
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++的类,再用类名过滤,挨个看看里面的方法。
经过一番查找(伪代码界面,可以右键: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==<char,std::char_traits<char>,std::allocator<char>>(&strMsg, "-1011") )
{
nErrNo = -1011;
std::string::operator=(&strErrMsg, &unk_466958);---注意这个地址
}
上面的466958这个地址,对应的错误信息,就是:
image-20221218182307951
到这里,基本就完结了,整个链路都通了,撒花撒花!
没啥可总结的,java层面的问题,感觉还是没那么难的,只要信息给够,可以复现的问题,都能大概找到原因;不能复现的问题,找不到原因的话,那基本也是信息不够(日志不够、监控没有),那就没办法,悬案。
又学废一个ida新技能,挺开心,目前这个行业,动态库还是挺多的,又多了一种定位方法。