Oracle TNS 协议分析:方法论及基础知识入门
前言
oracle 客户端与服务端采用tns作为其数据交换协议。tns全称transparent network substrate,是与oracle数据库服务器通讯的专有协议,该协议为oracle内部协议,不向外界公开,在此之前,已经有一些反向工程的实践对各个版本的tns进行解析,比如wireshark就有专门的tns分析工具,中文的协议解析可参见《oracle协议分析》本系列基础介绍中关于tns包基础格式,及连接包等均沿用wireshark提供的格式。本系列重点分析tns 314下的客户端与服务端之间的通讯,通过抓包分析,查看在不同客户端,不同服务端情况下传输方式的不同,尝试还原其协议细节,实现对协议中一些关键内容的解析,如登录用户名,协议版本,oracle版本,sql命令,同时给出示例lua代码。为了分析不同客户端架构,本系列使用了两类客户端32位与64位客户端进行测试,同时重点使用了多个厂商的不同客户端(navicat、plsql、sqlplus)同时也兼顾分析了ojdbc thin client的情况。服务端采用11g和12c两个版本。本文主要分析连接建立,身份验证、命令传输和返回、以及错误信息返回的过程。
方法及工具
主要采用wireshark对客户端与oracle间的通讯进行抓包分析。
客户端: |
服务端 |
navicat premium 15 64bit |
oracle 11g 64bit linux |
navicat premius 12 32bit |
oracle 12c 64bit windows |
plsql 11.2 64bit |
|
sqlplus 11.2 64bit |
|
ojdbc8(thin client) |
|
分析过程中关于包类型定义等参考wireshark的tns 解析器代码。
代码示例说明
代码示例用lua写成,可以在openresty15 64bit window或linux版本下运行
其中从socket流中解码用到了string.unpack 和pack 方法是纯lua开源实现,是对c语言 lua 扩展lpack 的纯lua模拟
系列目录
协议介绍
transparent network substrate顾名思义是对传输层协议无关,根据oracle的介绍:tns底层支持tcp,ssl tp,sdp,named pipeline等协议。在osi七层协议体系中,tns属于会话层协议
详细的介绍可参考
https://docs.oracle.com/cd/b28359_01/network.111/b28316/architecture.htm#netag004
oracle版本与tns协议版本对应关系
oracle 版本 |
tns版本 |
11gr2 12c |
tns314 |
10g |
313 |
9i |
312 |
x |
311 |
8i |
310 |
不同driver的差异
oracle client调用服务端,最终实现模式主要是 oci和thindriver。
- oci是c 语言的lib,和平台相关,有linux和windows 版本;
- thindriver是纯java的包,与平台无关;
很多人反映反编译java代码看到的情况和用plsql等工具调用实现不同,大概率是此原因,但无论navicat,sqlplus,plsql都走的oci。相应jdbc有两种
- jdbc oci driver 走oci调用,
- jdbc thin driver走 纯java实现
thindriver的实现与oci的实现从抓包上看,在连接建立和认证过程差距不大,但data包差距很大,无论endian模式,还是具体数据类型封装格式都有较大差异,在thindriver的实现中,很少未见出现piggy command的情况,所有非0或大于一位的byte或者int变量都会严格前序长度字段。
不同客户端实现的差异
相同位数(都是64位或者都是32位)的不同客户端的实现在抓包分析时也不太相同,大概表现有两个方面,一个是流程上的不同,一个是数据上的不同,我们以执行select语句为例,plsql和navicat在流程上稍有差别,下图是navicat premium15 执行的流程
1 |
------- |
data piggyback(11) cursor close all(69) 注意此处也有可能是 03 5e |
-----> |
具体语句 |
2 |
<----- |
data describeinfo(10) 17 |
------- |
返回列 |
3 |
------- |
data uocifun(03) executearow(04) |
-----> |
|
4 |
<----- |
data returnstatus(04) |
------- |
|
5 |
------- |
data uocifun(03) fetcharow(05) |
-----> |
获取其他值 |
6 |
<----- |
data rowtransferheader(06) 01 |
------- |
返回值 |
而plsql中没有中间3和4的部分。
数据上的不同有的时候是一些数据长度,有的时候是一些设置位上的差异,比如对于select 的piggycommand(pagekage type 0x6 dataid=0x11 callid=0x69)
这个包内容在sqlplus,plsql,navicat上不仅内容,长度也就有差异
语句 |
plsql |
sqlplus |
navicat |
select |
fe ff ff ff ff ff ff ff 01 00 00 00 04 00 00 00 |
fe ff ff ff ff ff ff ff 01 00 00 00 00 00 00 00 05 00 00 00 当没有输入;05会变03 |
fe ff ff ff ff ff ff ff 01 00 00 00 05 00 00 00 |
不同客户端位数间差异
32位客户端和64位客户端也有不同,比如32位客户端中所有0xfe ff ff ff ff ff ff ff 全部以其补码01代替。但64bit 的thinclient也有此情况,所以很难说这些现象是否完全由位数造成,这会导致许多协议中许多部分的长度根据位数不同有一定区别。文中凡协议均会标注出不同客户端位数下的长度,若未单独标注,则说明二者无不同,例如buddle execute command命令格式:
|
32bit |
64bit |
|
序列号 |
1 |
1 |
|
piggy command |
9 or 13 |
16 or 20 |
|
buddle execute command 035e |
变长 |
变长 |
|