[Wireshark] Lua Script 로 메시지 분석기(Message Dissector) 개발하기

주싱·2021년 5월 9일
1

1. 왜 필요한가?

통신 시스템 간 인터페이스에 문제가 생기면 Wireshark 라는 청진기를 꽂아 보는 것이 가장 효율적이고 정확한 문제 진단 방법이다. 그러나 Wireshark는 자체적으로 개발된 응용 계층 프로토콜의 구조를 알지 못함으로 바이트 배열 형태로 출력해 줄 뿐이다.

우리가 개발한 응용 계층 프로토콜 데이터도 잘 알려진 프로토콜(Ethernet, TCP) 메시지 헤더 같이 구조적으로 분해하여 해석된 형태로 출력해 줄 수 없을까?

맞다! Lua Script 를 활용하면 모든 것이 가능하다!

2. 개발결과

Lua Script로 메시지 분석기를 개발하여 Wireshark에 적용하면 아래와 같이 유용한 일들을 해준다.

2.1 Details View

우리 프로토콜 메시지를 마치 잘 알려진 TCP 프로토콜 헤더 같이 세부 필드들의 구조를 분석하여 출력해 준다. 세부 필드들의 값을 단지 출력해 줄 뿐 아니라 해석된 값을 출력해 주거나, 특정 정보까지 추가하여 출력해 줄 수도 있다.

2.2 Customized Filter & Column

더 나아가 우리 프로토콜 메시지의 특정 필드들을 직접 필터링하거나 관심있는 필드들을 리스트에 컬럼으로 보여지도록 할 수 있어 프로토콜 분석에 매우 유용하다.

3. 개발하기

3.1 환경설정

Wireshark에서 Lua Script가 실행되도록 하기 위해 아무것도 설치할 필요가 없다. Lua Script 인터프리터가 Wireshark 내부에 내장되어 있다. 단지 Visual Studio Code에서 코드를 작성하기만 하고 해당 파일을 Wireshark Plugin 폴더에 복사해 주기만 하면된다.(자세한 내용은 뒤에) 다만 편리함을 위해 Lua Script 관련 플러그인을 VS Code에 설치해 주길 권한다.

3.2 코드작성

이제 Lua Script 코드를 작성해보자. 먼저 아래 프로토콜 문서 샘플은 인공위성 지상국 안테나를 제어하는 TCP 기반 프로토콜 일부 메시지이다. 해당 메시지를 분석하기 위한 Lua Script 코드를 GitHub에 올려 두었으니 간단히 따라해보면 시작하기에 도움이 될 것 같다.

GitHub : Lua Script 참고 소스코드 ⭐

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에 대해 깊이 공부를 할 수 있다.

3.3 Wireshark 적용

개발한 Lua Script를 Wireshark에 적용해 보자.

  1. 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"

  2. Wireshark를 실행시키면 자동으로 반영이된다.

  1. 만약 Wireshark가 실행중에 복사했다면 Wireshark에서 Ctrl+Shift+L 단축키를 입력하면 자동 반영된다.
profile
소프트웨어 엔지니어, 일상

0개의 댓글