前言
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 |
变长 |
变长 |
|