本系列重點分析TNS 314下的客戶端與服務端之間的通訊,通過抓包分析,查看在不同客戶端,不同服務端情況下傳輸方式的不同,嘗試還原其協議細節,實現對協議中一些關鍵內容的解析,如登錄用戶名,協議版本,oracle版本,sql命令,同時給出示例LUA代碼。為了分析不同客戶端架構,本系列使用了兩類客戶端3... ...
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 request
if(data:byte(3)==6 and data:byte(9)==3 and data:byte(10)==0x3b) then
cHeaderPos=9
end
--start to process db version request
if(cHeaderPos>1 and data:byte(cHeaderPos)==3 and data:byte(cHeaderPos+1)==0x3b) then
requestOracleVersion=true
end
--06089a get oracle version
if(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