통신 시스템 간 인터페이스에 문제가 생기면 Wireshark 라는 청진기를 꽂아 보는 것이 가장 효율적이고 정확한 문제 진단 방법이다. 그러나 Wireshark는 자체적으로 개발된 응용 계층 프로토콜의 구조를 알지 못함으로 바이트 배열 형태로 출력해 줄 뿐이다.
우리가 개발한 응용 계층 프로토콜 데이터도 잘 알려진 프로토콜(Ethernet, TCP) 메시지 헤더 같이 구조적으로 분해하여 해석된 형태로 출력해 줄 수 없을까?
맞다! Lua Script 를 활용하면 모든 것이 가능하다!
Lua Script로 메시지 분석기를 개발하여 Wireshark에 적용하면 아래와 같이 유용한 일들을 해준다.
우리 프로토콜 메시지를 마치 잘 알려진 TCP 프로토콜 헤더 같이 세부 필드들의 구조를 분석하여 출력해 준다. 세부 필드들의 값을 단지 출력해 줄 뿐 아니라 해석된 값을 출력해 주거나, 특정 정보까지 추가하여 출력해 줄 수도 있다.
더 나아가 우리 프로토콜 메시지의 특정 필드들을 직접 필터링하거나 관심있는 필드들을 리스트에 컬럼으로 보여지도록 할 수 있어 프로토콜 분석에 매우 유용하다.
Wireshark에서 Lua Script가 실행되도록 하기 위해 아무것도 설치할 필요가 없다. Lua Script 인터프리터가 Wireshark 내부에 내장되어 있다. 단지 Visual Studio Code에서 코드를 작성하기만 하고 해당 파일을 Wireshark Plugin 폴더에 복사해 주기만 하면된다.(자세한 내용은 뒤에) 다만 편리함을 위해 Lua Script 관련 플러그인을 VS Code에 설치해 주길 권한다.
이제 Lua Script 코드를 작성해보자. 먼저 아래 프로토콜 문서 샘플은 인공위성 지상국 안테나를 제어하는 TCP 기반 프로토콜 일부 메시지이다. 해당 메시지를 분석하기 위한 Lua Script 코드를 GitHub에 올려 두었으니 간단히 따라해보면 시작하기에 도움이 될 것 같다.
local jejuLpAcsProto = Proto("rot2", "rot2")
local tvbs = {}
-- common
startFlag = ProtoField.uint8("rot2.start", "Start Flag", base.HEX )
endFlag = ProtoField.uint8("rot2.end", "End Flag", base.HEX )
pulseVertical = ProtoField.uint8("rot2.pv", "Degree/Pulse(Vertical)", base.HEX, {[0x01]=1,[0x2]=0.5,[0x4]=0.24,[0x0A]=0.1})
pulseHorisontal = ProtoField.uint8("rot2.ph", "Degree/Pulse(Horisontal)", base.HEX, {[0x01]=1,[0x2]=0.5,[0x4]=0.24,[0x0A]=0.1})
-- command
cmdAz = ProtoField.float("rot2.cmdAz", "Commanded Azimuth", base.DEC )
cmdEl = ProtoField.float("rot2.cmdEl", "Commanded Elevation", base.DEC )
cmdType = ProtoField.uint8("rot2.cmdType", "Command Type", base.HEX, {[0x0F]="STOP",[0x1F]="STATUS",[0x2F]="SET"})
-- status
actAz = ProtoField.float("rot2.actAz", "Actual Azimuth", base.DEC )
actEl = ProtoField.float("rot2.actEl", "Actual Elevation", base.DEC )
local respTreeViewItems =
{
startFlag,
actAz,
pulseVertical,
actEl,
pulseHorisontal,
endFlag,
}
local cmdTreeViewItems =
{
startFlag,
cmdAz,
pulseVertical,
cmdEl,
pulseHorisontal,
cmdType,
endFlag,
}
local treeViewItemsMap =
{
[12] = respTreeViewItems,
[13] = cmdTreeViewItems,
}
local rot2Items =
{
startFlag= startFlag,
endFlag = endFlag,
pulseVertical = pulseVertical,
pulseHorisontal = pulseHorisontal,
cmdAz = cmdAz,
cmdEl = cmdEl,
actAz = actAz,
actEl = actEl,
cmdType = cmdType,
}
jejuLpAcsProto.fields = rot2Items
function jejuLpAcsProto.init()
tvbs = {}
end
function jejuLpAcsProto.dissector(buffer, packetInfo, detailTreeView)
tvbs = {}
local pktlen = buffer:len()
local fields = { }
local result = verifyData(buffer)
if result == false then
return pktlen
end
splitFields(buffer(0,pktlen), fields)
treeViewItems = treeViewItemsMap[pktlen];
displayFields(buffer, detailTreeView, fields, treeViewItems )
return pktlen
end
function verifyData(buffer)
local pktlen = buffer:len()
if pktlen == 12 or pktlen == 13 then
return true
else
return false
end
end
function splitFields(data, fields)
if data:len() == 12 then -- rx status
fields[1] = { value = data(0,1):uint(), start = 0, len = 1 }
fields[2] = { value = ( (data(1,1):uint()*100) + (data(2,1):uint()*10) + data(3,1):uint() + (data(4,1):uint()/10) ) - 360, start = 1, len = 4 }
fields[3] = { value = data(5,1):uint(), start = 5, len = 1 }
fields[4] = { value = ( (data(6,1):uint()*100) + (data(7,1):uint()*10) + data(8,1):uint() + (data(9,1):uint()/10) ) - 360, start = 6, len = 4 }
fields[5] = { value = data(10,1):uint(), start = 10, len = 1 }
fields[6] = { value = data(11,1):uint(), start = 11, len = 1 }
elseif data:len() == 13 then -- tx cmd
fields[1] = { value = data(0,1):uint(), start = 0, len = 1 }
fields[2] = { value = ( ( ((data(1,1):uint()-0x30)*1000) + ((data(2,1):uint()-0x30)*100) + ((data(3,1):uint()-0x30)*10) + (data(4,1):uint()-0x30) ) / data(5,1):uint() ) - 360, start = 1, len = 4 }
fields[3] = { value = data(5,1):uint(), start = 5, len = 1 }
fields[4] = { value = ( ( ((data(6,1):uint()-0x30)*1000) + ((data(7,1):uint()-0x30)*100) + ((data(8,1):uint()-0x30)*10) + (data(9,1):uint()-0x30) ) / data(10,1):uint() ) - 360, start = 6, len = 4 }
fields[5] = { value = data(10,1):uint(), start = 10, len = 1 }
fields[6] = { value = data(11,1):uint(), start = 11, len = 1 }
fields[7] = { value = data(12,1):uint(), start = 12, len = 1 }
else
-- not defined
end
end
function displayFields(buffer, detailTreeView, fields, viewItems)
local topTreeView = detailTreeView:add(jejuLpAcsProto, buffer(0,pktlen))
for i, field in ipairs(fields) do
topTreeView:add(viewItems[i], buffer:range(field.start,field.len), field.value)
end
end
local function enableDissector()
DissectorTable.get("tcp.port"):add(23, jejuLpAcsProto)
end
enableDissector()
local function disableDissector()
DissectorTable.get("tcp.port"):remove(23, jejuLpAcsProto)
end
그리고 아래 사이트에 가시면 Lua Script 문법이나 추가적인 Wireshark API에 대해 깊이 공부를 할 수 있다.
개발한 Lua Script를 Wireshark에 적용해 보자.
Wireshark가 설치된 경로의 plugin 경로에 스크립트 파일을 복사한다. 또는 관리자 권한의 CMD 창에서 아래의 명령어를 실행해 주는 배치파일을 만들어 두면 편리하다. [LUA script file name] 에는 작성한 lua 스크립트 이름으로 대체하면 된다.
copy /Y ".[LUA script file name].lua" "C:\Program Files\Wireshark\plugins[LUA script file name].lua"
Wireshark를 실행시키면 자동으로 반영이된다.