Oracle TNS 314 协议分析——3、连接认证流程与包分析
本系列重点分析TNS 314下的客户端与服务端之间的通讯,通过抓包分析,查看在不同客户端,不同服务端情况下传输方式的不同,尝试还原其协议细节,实现对协议中一些关键内容的解析,如登录用户名,协议版本,oracle版本,sql命令,同时给出示例LUA代码。为了分析不同客户端架构,本系列使用了两类客户端32位与64位客户端进行测试,同时重点使用了多个厂商的不同客户端(Navicat、PLSQL、SQLPlus)同时也兼顾分析了OJDBC Thin Client的情况。服务端采用11g和12c两个版本。本文主要分析连接建立,身份验证、命令传输和返回、以及错误信息返回的过程。
Connect 流程Client |
|
|
| Server |
1 | ------- | Connect(01) | -----> | 获取连接字符串 |
2 | <----- | Resend | ------- |
|
3 | ------- | Connect(01) | -----> |
|
4 | <----- | Accept | ------- | 获取协议Version |
5 | ------- | Data NetworkService(deadbeef) | -----> | 网络参数交换 |
6 | <----- | Data NetworkService(deadbeef) | ------- |
|
7 | ------- | Data SetProtocal(01) | -----> |
|
8 | <----- | Data SetProtocal(01) | ------- |
|
9 | ------- | Data SetDataTypes(02) | -----> |
|
10 | <----- | Data SetDataTypes(02) | ------- |
|
11 | ------- | Data UOCIFun(03) GetSessionKey(76) | -----> |
|
12 | <----- | Data OPIParam(08) with 3 params Sessionkey,verifydata,,dbid | ------- |
|
13 | ------- | Data UOCIFun(03) Generic Auth call(73) | -----> | 获取验证参数:用户名,密码在此传输 Username sessionkey pass |
14 | <----- | Data OPIParam(08) with 40 params | ------- | 认证结果包含 AuthDBName;dbid:AuthUserID;SessionID |
15 | ------- | Data Piggyback(11) session switch(6b) | -----> |
|
16 | <----- | Data OPIParam(08) | ------- | Oracle版本号 |
认证错误时从14包往后,会返回一个marker,然后客户端会发送一个请求marker,接着服务端返回错误信息,此过程详细参见错误信息返回这个章节
特殊数据定义在分析32位和64位客户端时,可以注意到不同版本客户端再解析上出现64位feffffffffffffff和00000000000000 在32位情况下分别都被代换为01和00的情况,所以我们定义
feMagic,在32位时为0x01 64位下为0xfe ff ff ff ff ff ff ff
00Magic,在32位时为0x00 64位下为0x00 00 00 00 00 00 00 00
获取协议版本暨协议头解析Connect 的Accept包是获取TNS版本号的最佳地点,Connect过程会协商版本号,Connect过程中,client会传输自己支持的版本号,服务端会结合自己的情况,最终在Accept中选定一个版本号。Accept包的Package Type为2
Accept包格式
| 32bit | 64bit |
|
Version | 2 | 2 | 版本号 |
Service Option | 2 | 2 | Bit标志选项 |
Session Data Unit Size | 2 | 2 | 一个DataUnit最多多大,在传输超长包时,Data 包会被拆解成如此大小的包 |
Max Transmition Unit Size | 2 | 2 | 最大Data长度 |
Value Of 1 | 2 | 2 | 指定了服务端的Endian类型 |
Accept Data Length | 2 | 2 |
|
Accept Data Offset | 2 | 2 | 指向Accept data的指针,一般直接指向结尾(包含TNS头) |
Connect Flag0 | 1 | 1 | 标志位 |
Connect Flag1 | 1 | 1 | 标志位 |
Unknown | 8 or 17 | 8 or 17 | 未知,一般前8字节为0 |
Service Option:
..0. .... .... .... = Broken Connect Notify
...0 .... .... .... = Packet Checksum
.... 0... .... .... = Header Checksum
.... .0.. .... .... = Full Duplex
.... ..0. .... .... = Half Duplex
.... ...0 .... .... = Don't Care
.... .... 0... .... = Don't Care
.... .... ...0 .... = Direct IO to Transpor
.... .... .... 0... = Attention Processing
.... .... .... .0.. = Can Receive Attention
.... .... .... ..0. = Can Send Attention
Connect Flag0 and flag1
...0 .... = NA services required
.... 0... = NA services linked in
.... .0.. = NA services enabled
.... ..0. = Interchange is involved
.... ...0 = NA services wanted
Accept包示例
代码示例
从此包中解析TNS version
--02 get tns version if(data:byte(3)==2) then tnsVersion=string.unpack(">I2",data:sub(7)) print("tnsVersion:"..tnsVersion) end获取连接字符串及客户端信息
通过解析包Connect包可以获得连接字符串,进而获取客户端的详细信息,包含客户端程序,当前用户,windows版本等。
Connect包格式
| 32bit | 64bit |
|
Version | 2 | 2 | 版本号 |
Compatible Version | 2 | 2 | 兼容最低版本 |
Service Options | 2 | 2 | Bit标志选项 |
Session Data Unit Size | 2 | 2 | 一个DataUnit最多多大,在传输超长包时,Data 包会被拆解成如此大小的包 |
Max Transmition Unit Size | 2 | 2 | 最大Data长度 |
NT Protocol Characteristics | 2 | 2 | 网络参数 |
Line Turn Around Value | 2 | 2 |
|
Value 1 | 2 | 2 | 指定了本地的Endian类型 |
Length of Connect Data | 2 | 2 | 连接字符串长度 |
Offset of Connect Data | 2 | 2 | 连接字符串从TNS头算的偏移量 |
Max Receivable Connect Data | 4 | 4 |
|
Connection Flag0 | 1 | 1 | |
Connection Flag1 | 1 | 1 | |
Trace Across Facility item1 | 4 | 4 | |
Trace Across Facility item2 | 4 | 4 | |
Trace Unique Connection ID | 8 | 8 | |
unknown | 8 or 20 | 8 or 20 |
Service Options,Connection Flag 同Accept
NT Protocol Characteristics:
0... .... .... .... = Hangon to listener connect
.0.. .... .... .... = Confirmed release
..0. .... .... .... = TDU based IO
...0 .... .... .... = Spawner running
.... 0... .... .... = Data test
.... .0.. .... .... = Callback IO supported
.... ..0. .... .... = ASync IO Supported
.... ...0 .... .... = Packet oriented IO
.... .... 0... .... = Can grant connection to another
.... .... .0.. .... = Can handoff connection to another
.... .... ..0. .... = Generate SIGIO signal
.... .... ...0 .... = Generate SIGPIPE signal
.... .... .... 0... = Generate SIGURG signal
.... .... .... .0.. = Urgent IO supported
.... .... .... ..0. = Full duplex IO supported
.... .... .... ...0 = Test operation
包示例
示例代码
获取TNS版本及连接字符串
if(data:byte(3)==1) then tnsVersion=string.unpack(">I2",data,7) print("requestTnsVersion:"..tnsVersion) local connectDataLength=string.unpack(">I2",data,23) local connectDataOffset=string.unpack(">I2",data,25) print("connect string:"..string.unpack("c"..connectDataLength,data,connectDataOffset-2)) end网络参数交换(deadbeef)Secure Network Service
通过解析包 Data Network Service 包可以获得网络相关参数比如servie version,其意义暂不明确,注意此包是data包,下面示例数据没有带data包头
包解析示例
通过解析包dataid 03 callid 73可以获得用户名,密码hash等很多信息
包格式
| 32bit | 64bit |
|
序列号 | 1 | 1 |
|
可变字节 | 16 or 20 | 44 or 48 |
|
用户名长度 | 1 | 1 |
|
用户名 | 上字节决定 | 上字节决定 |
|
Keyvalue pairs | 变长 | 变长 | sessionkey及密码等数据 |
可变头
注意到使用不同客户端连接不同数据库,数据包到用户名这里的偏移量不同(可能原因,oracle版本,不同的客户端)
Sqlplus11 to oracle12c | Navicat to oracle11
| 32位Navicat |
fe ff ff ff ff ff ff ff 18 00 00 00 01 01 00 00 fe ff ff ff ff ff ff ff 12 00 00 00 00 00 00 00 fe ff ff ff ff ff ff ff fe ff ff ff ff ff ff ff | fe ff ff ff ff ff ff ff 0f 00 00 00 01 01 00 00 fe ff ff ff ff ff ff ff 12 00 00 00 fe ff ff ff ff ff ff ff fe ff ff ff ff ff ff ff
| 01 0f 00 00 00 01 01 00 00 01 13 00 00 00 01 01 |
一个在序号后有44个字节,一个48个字节,具体处理可以先跳过44个字节看是否ff,如果是跳到48个字节
32位情况类似,只是将feMagic变为01,所以也有两种情况16或20个字节
Keyvalue对
对灰色头部以下内容除直接跟的用户名外,全部以keyvalue形式存在。
Key和value间存在固定4字节未知字段,keyvalue对之间存在8字节未知字段。
Key和value均以长度开头,长度fe表示变长,fe后续一个字节的长度byte并以00结尾如sessionid的值。
下面以上面的包为例进行解析:
用户名:scott | 0863232373636f7474 |
4字节未知字段 | 24000000 |
AUTH_SESSKEY | 0c415554485f534553534b4559 |
4字节未知字段 | 20010000 |
value | fe403346334137413241324636443935363537434643383241304439314141383033354334334532413932313746424334384437313935343137323638374442414120423145303544373245443630413239333636454331334131444232423941303500 |
8字节未知字段 | 100000027000000 |
AUTH_PASSWORD | 0d415554485f50415353574f5244 |
4字节未知字段 | c0000000 |
value | 4042353343343732314336334342323537334244423535383936364541364630363844353834423034364134313945373146463430444444363537464343343742 |
... | ... |
... | ... |
... | ... |
8字节未知字段 | 0000000030000000 |
AUTH_FAILOVER_ID | 10415554485f4641494c4f5645525f4944 |
8字节未知字段 | 0000000000000000 |
包示例
代码示例
获取用户名
--060307 get username if(data:byte(3)==6 and data:byte(9)==3 and data:byte(10)==0x73) then local userNamePos --test client 32bit or 64bit if(data:byte(12)~=0xfe)then is64Bit=false end if(is64Bit) then print("64bit:true") else print("64bit:false") end if(is64Bit) then if(data:byte(9+2+1+43)==0xff)then userNamePos=9+2+1+44 end if(data:byte(9+2+1+47)==0xff)then userNamePos=9+2+1+48 end else if(data:byte(9+2+1+15)==0xff)then userNamePos=9+2+1+16 end if(data:byte(9+2+1+21)==0xff)then userNamePos=9+2+1+20 end end if(userNamePos) then local username=string.unpack("s1",data,userNamePos) print("username:"..username) ngx.ctx.username=username end end获取Oracle版本号
在连接完成之后,客户端会发起data 116b包,内部后续一个data DB Version(033b)请求,ThinClient 下会直接发起data 033b,请求oracle版本,版本号以data 08包形式返回,解析其返回包可以获得Oracle版本号。注意返回的data 08 包不止在这里使用,很多命令的返回都使用此包,此种包有这样几种形式,
08后直接后续字段:如版本号包
08后后续返回字段数,再接续字段:比如认证结果返回
获取版本包格式如下
| 32bit | 64bit |
|
unused | 2 | 2 | ThinDriver 此处为1 |
Banner Length | 1 | 1 | 版本字符串长度 |
Banner | 上字节决定 | 上字节决定 | Oracle版本字符串 |
版本号 | 4 | 4 | 版本int表示,little endian,minor版本号和build号分别用第二个字节的高低4bit表示 |
变长结尾 | 变长 | 变长 | 可能是1702包也可能是0901包,内容不详 |
包示例
代码示例
--06033b oracle version requestif(data:byte(3)==6 and data:byte(9)==3 and data:byte(10)==0x3b) then cHeaderPos=9 end --start to process db version requestif(cHeaderPos>1 and data:byte(cHeaderPos)==3 and data:byte(cHeaderPos+1)==0x3b) then requestOracleVersion=true end --06089a get oracle versionif(data:byte(3)==6 and requestOracleVersion) then local versionString,p=string.unpack("s1",data,12) print("oracleVersion:"..versionString) local minor oracleVersion.fix,oracleVersion.subbuild,minor,oracleVersion.major=string.unpack("BBBB",data,p) oracleVersion.minor=minor/16 oracleVersion.build=minor%16 print(oracleVersion.major..'.'..oracleVersion.minor..'.'..oracleVersion.build..'.'..oracleVersion.subbuild..'.'..oracleVersion.fix) requestOracleVersion=false end
f1fd0340229b 1 年前
d4923e9f05d8 1 年前