@ -784,13 +784,17 @@ static void ModbusParseWriteResponse(ModbusTransaction *tx,
goto error ;
if ( type & MODBUS_TYP_SINGLE ) {
/* Outputs/Registers value (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & word , input , input_len , offset ) )
goto end ;
/* Check if Outputs/Registers value has been stored */
if ( tx - > data ! = NULL )
{
/* Outputs/Registers value (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & word , input , input_len , offset ) )
goto end ;
/* Check with Outputs/Registers from request */
if ( word ! = tx - > data [ 0 ] )
goto error ;
/* Check with Outputs/Registers from request */
if ( word ! = tx - > data [ 0 ] )
goto error ;
}
} else if ( type & MODBUS_TYP_MULTIPLE ) {
/* Quantity (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & quantity , input , input_len , offset ) )
@ -811,20 +815,25 @@ static void ModbusParseWriteResponse(ModbusTransaction *tx,
if ( quantity ! = tx - > write . quantity )
goto error ;
} else {
/* And_Mask value (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & word , input , input_len , offset ) )
goto end ;
/* Check if And_Mask and Or_Mask values have been stored */
if ( tx - > data ! = NULL )
{
/* And_Mask value (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & word , input , input_len , offset ) )
goto end ;
/* Check And_Mask value according to the request */
if ( word ! = tx - > data [ 0 ] )
goto error ;
/* Check And_Mask value according to the request */
if ( word ! = tx - > data [ 0 ] )
goto error ;
/* And_Or_Mask value (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & word , input , input_len , offset ) )
/* And_Or_Mask value (2 bytes) */
if ( ModbusExtractUint16 ( modbus , & word , input , input_len , offset ) )
goto end ;
/* Check Or_Mask value according to the request */
if ( word ! = tx - > data [ 1 ] )
goto error ;
/* Check Or_Mask value according to the request */
if ( word ! = tx - > data [ 1 ] )
goto error ;
}
/* The length of Mask Write Register (code 22) function response is 8 */
/* Modbus Application Protocol Specification V1.1b3 6.16 */
@ -1529,6 +1538,31 @@ static uint8_t readCoilsErrorRsp[] = {/* Transaction ID */ 0x00, 0x00,
/* Function code */ 0x81 ,
/* Exception code */ 0x05 } ;
/* Modbus Application Protocol Specification V1.1b3 6.6: Write Single register */
/* Example of a request to write register 2 to 00 03 hex */
static uint8_t writeSingleRegisterReq [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
/* Protocol ID */ 0x00 , 0x00 ,
/* Length */ 0x00 , 0x06 ,
/* Unit ID */ 0x00 ,
/* Function code */ 0x06 ,
/* Register Address */ 0x00 , 0x01 ,
/* Register Value */ 0x00 , 0x03 } ;
static uint8_t invalidWriteSingleRegisterReq [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
/* Protocol ID */ 0x00 , 0x00 ,
/* Length */ 0x00 , 0x04 ,
/* Unit ID */ 0x00 ,
/* Function code */ 0x06 ,
/* Register Address */ 0x00 , 0x01 } ;
static uint8_t writeSingleRegisterRsp [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
/* Protocol ID */ 0x00 , 0x00 ,
/* Length */ 0x00 , 0x06 ,
/* Unit ID */ 0x00 ,
/* Function code */ 0x06 ,
/* Register Address */ 0x00 , 0x01 ,
/* Register Value */ 0x00 , 0x03 } ;
/* Modbus Application Protocol Specification V1.1b3 6.12: Write Multiple registers */
/* Example of a request to write two registers starting at 2 to 00 0A and 01 02 hex */
static uint8_t writeMultipleRegistersReq [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
@ -1550,6 +1584,34 @@ static uint8_t writeMultipleRegistersRsp[] = {/* Transaction ID */ 0x00
/* Starting Address */ 0x00 , 0x01 ,
/* Quantity of Registers */ 0x00 , 0x02 } ;
/* Modbus Application Protocol Specification V1.1b3 6.16: Mask Write Register */
/* Example of a request to mask write to register 5 */
static uint8_t maskWriteRegisterReq [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
/* Protocol ID */ 0x00 , 0x00 ,
/* Length */ 0x00 , 0x08 ,
/* Unit ID */ 0x00 ,
/* Function code */ 0x16 ,
/* Reference Address */ 0x00 , 0x04 ,
/* And_Mask */ 0x00 , 0xF2 ,
/* Or_Mask */ 0x00 , 0x25 } ;
static uint8_t invalidMaskWriteRegisterReq [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
/* Protocol ID */ 0x00 , 0x00 ,
/* Length */ 0x00 , 0x06 ,
/* Unit ID */ 0x00 ,
/* Function code */ 0x16 ,
/* Reference Address */ 0x00 , 0x04 ,
/* And_Mask */ 0x00 , 0xF2 } ;
static uint8_t maskWriteRegisterRsp [ ] = { /* Transaction ID */ 0x00 , 0x0A ,
/* Protocol ID */ 0x00 , 0x00 ,
/* Length */ 0x00 , 0x08 ,
/* Unit ID */ 0x00 ,
/* Function code */ 0x16 ,
/* Reference Address */ 0x00 , 0x04 ,
/* And_Mask */ 0x00 , 0xF2 ,
/* Or_Mask */ 0x00 , 0x25 } ;
/* Modbus Application Protocol Specification V1.1b3 6.17: Read/Write Multiple registers */
/* Example of a request to read six registers starting at register 4, */
/* and to write three registers starting at register 15 */
@ -2650,6 +2712,354 @@ end:
UTHFreePackets ( & p , 1 ) ;
return result ;
}
/** \test Send Modbus Mask Write register request/response. */
static int ModbusParserTest13 ( void ) {
AppLayerParserThreadCtx * alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
Flow f ;
TcpSession ssn ;
int result = 0 ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
StreamTcpInitConfig ( TRUE ) ;
SCMutexLock ( & f . m ) ;
int r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOSERVER ,
maskWriteRegisterReq , sizeof ( maskWriteRegisterReq ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
ModbusState * modbus_state = f . alstate ;
if ( modbus_state = = NULL ) {
printf ( " no modbus state: " ) ;
goto end ;
}
ModbusTransaction * tx = ModbusGetTx ( modbus_state , 0 ) ;
if ( ( tx - > function ! = 22 ) | | ( tx - > data [ 0 ] ! = 0x00F2 ) | | ( tx - > data [ 1 ] ! = 0x0025 ) ) {
printf ( " expected function % " PRIu8 " , got % " PRIu8 " : " , 16 , tx - > function ) ;
printf ( " expected And_Mask % " PRIu8 " , got % " PRIu8 " : " , 0x00F2 , tx - > data [ 0 ] ) ;
printf ( " expected Or_Mask % " PRIu8 " , got % " PRIu8 " : " , 0x0025 , tx - > data [ 1 ] ) ;
goto end ;
}
SCMutexLock ( & f . m ) ;
r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOCLIENT ,
maskWriteRegisterRsp , sizeof ( maskWriteRegisterRsp ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
if ( modbus_state - > transaction_max ! = 1 ) {
printf ( " expected transaction_max % " PRIu8 " , got % " PRIu64 " : " , 1 , modbus_state - > transaction_max ) ;
goto end ;
}
result = 1 ;
end :
if ( alp_tctx ! = NULL )
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( TRUE ) ;
FLOW_DESTROY ( & f ) ;
return result ;
}
/** \test Send Modbus Write single register request/response. */
static int ModbusParserTest14 ( void ) {
AppLayerParserThreadCtx * alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
Flow f ;
TcpSession ssn ;
int result = 0 ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
StreamTcpInitConfig ( TRUE ) ;
SCMutexLock ( & f . m ) ;
int r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOSERVER ,
writeSingleRegisterReq , sizeof ( writeSingleRegisterReq ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
ModbusState * modbus_state = f . alstate ;
if ( modbus_state = = NULL ) {
printf ( " no modbus state: " ) ;
goto end ;
}
ModbusTransaction * tx = ModbusGetTx ( modbus_state , 0 ) ;
if ( ( tx - > function ! = 6 ) | | ( tx - > write . address ! = 0x0001 ) | | ( tx - > data [ 0 ] ! = 0x0003 ) ) {
printf ( " expected function % " PRIu8 " , got % " PRIu8 " : " , 16 , tx - > function ) ;
printf ( " expected write address % " PRIu8 " , got % " PRIu8 " : " , 0x01 , tx - > write . address ) ;
printf ( " expected data % " PRIu8 " , got % " PRIu8 " : " , 0x03 , tx - > data [ 0 ] ) ;
goto end ;
}
SCMutexLock ( & f . m ) ;
r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOCLIENT ,
writeSingleRegisterRsp , sizeof ( writeSingleRegisterRsp ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
if ( modbus_state - > transaction_max ! = 1 ) {
printf ( " expected transaction_max % " PRIu8 " , got % " PRIu64 " : " , 1 , modbus_state - > transaction_max ) ;
goto end ;
}
result = 1 ;
end :
if ( alp_tctx ! = NULL )
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( TRUE ) ;
FLOW_DESTROY ( & f ) ;
return result ;
}
/** \test Send invalid Modbus Mask Write register request. */
static int ModbusParserTest15 ( void ) {
AppLayerParserThreadCtx * alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
DetectEngineThreadCtx * det_ctx = NULL ;
Flow f ;
Packet * p = NULL ;
Signature * s = NULL ;
TcpSession ssn ;
ThreadVars tv ;
int result = 0 ;
memset ( & tv , 0 , sizeof ( ThreadVars ) ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
p = UTHBuildPacket ( NULL , 0 , IPPROTO_TCP ) ;
FLOW_INITIALIZE ( & f ) ;
f . alproto = ALPROTO_MODBUS ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . flags | = FLOW_IPV4 ;
p - > flow = & f ;
p - > flags | = PKT_HAS_FLOW | PKT_STREAM_EST ;
p - > flowflags | = FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED ;
StreamTcpInitConfig ( TRUE ) ;
DetectEngineCtx * de_ctx = DetectEngineCtxInit ( ) ;
if ( de_ctx = = NULL )
goto end ;
de_ctx - > flags | = DE_QUIET ;
s = DetectEngineAppendSig ( de_ctx , " alert modbus any any -> any any "
" (msg: \" Modbus invalid Length \" ; "
" app-layer-event: "
" modbus.invalid_length; "
" sid:1;) " ) ;
if ( s = = NULL )
goto end ;
SigGroupBuild ( de_ctx ) ;
DetectEngineThreadCtxInit ( & tv , ( void * ) de_ctx , ( void * ) & det_ctx ) ;
SCMutexLock ( & f . m ) ;
int r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOSERVER ,
invalidMaskWriteRegisterReq ,
sizeof ( invalidMaskWriteRegisterReq ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
ModbusState * modbus_state = f . alstate ;
if ( modbus_state = = NULL ) {
printf ( " no modbus state: " ) ;
goto end ;
}
ModbusTransaction * tx = ModbusGetTx ( modbus_state , 0 ) ;
if ( tx - > function ! = 22 ) {
printf ( " expected function % " PRIu8 " , got % " PRIu8 " : " , 16 , tx - > function ) ;
goto end ;
}
/* do detect */
SigMatchSignatures ( & tv , de_ctx , det_ctx , p ) ;
if ( ! PacketAlertCheck ( p , 1 ) ) {
printf ( " sid 1 didn't match. Should have matched: " ) ;
goto end ;
}
SCMutexLock ( & f . m ) ;
r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOCLIENT ,
maskWriteRegisterRsp , sizeof ( maskWriteRegisterRsp ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
if ( modbus_state - > transaction_max ! = 1 ) {
printf ( " expected transaction_max % " PRIu8 " , got % " PRIu64 " : " , 1 , modbus_state - > transaction_max ) ;
goto end ;
}
result = 1 ;
end :
SigGroupCleanup ( de_ctx ) ;
SigCleanSignatures ( de_ctx ) ;
DetectEngineThreadCtxDeinit ( & tv , ( void * ) det_ctx ) ;
DetectEngineCtxFree ( de_ctx ) ;
if ( alp_tctx ! = NULL )
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( TRUE ) ;
FLOW_DESTROY ( & f ) ;
UTHFreePackets ( & p , 1 ) ;
return result ;
}
/** \test Send invalid Modbus Mask Write register request. */
static int ModbusParserTest16 ( void ) {
AppLayerParserThreadCtx * alp_tctx = AppLayerParserThreadCtxAlloc ( ) ;
DetectEngineThreadCtx * det_ctx = NULL ;
Flow f ;
Packet * p = NULL ;
Signature * s = NULL ;
TcpSession ssn ;
ThreadVars tv ;
int result = 0 ;
memset ( & tv , 0 , sizeof ( ThreadVars ) ) ;
memset ( & f , 0 , sizeof ( f ) ) ;
memset ( & ssn , 0 , sizeof ( ssn ) ) ;
p = UTHBuildPacket ( NULL , 0 , IPPROTO_TCP ) ;
FLOW_INITIALIZE ( & f ) ;
f . alproto = ALPROTO_MODBUS ;
f . protoctx = ( void * ) & ssn ;
f . proto = IPPROTO_TCP ;
f . flags | = FLOW_IPV4 ;
p - > flow = & f ;
p - > flags | = PKT_HAS_FLOW | PKT_STREAM_EST ;
p - > flowflags | = FLOW_PKT_TOSERVER | FLOW_PKT_ESTABLISHED ;
StreamTcpInitConfig ( TRUE ) ;
DetectEngineCtx * de_ctx = DetectEngineCtxInit ( ) ;
if ( de_ctx = = NULL )
goto end ;
de_ctx - > flags | = DE_QUIET ;
s = DetectEngineAppendSig ( de_ctx , " alert modbus any any -> any any "
" (msg: \" Modbus invalid Length \" ; "
" app-layer-event: "
" modbus.invalid_length; "
" sid:1;) " ) ;
if ( s = = NULL )
goto end ;
SigGroupBuild ( de_ctx ) ;
DetectEngineThreadCtxInit ( & tv , ( void * ) de_ctx , ( void * ) & det_ctx ) ;
SCMutexLock ( & f . m ) ;
int r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOSERVER ,
invalidWriteSingleRegisterReq , sizeof ( invalidWriteSingleRegisterReq ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
ModbusState * modbus_state = f . alstate ;
if ( modbus_state = = NULL ) {
printf ( " no modbus state: " ) ;
goto end ;
}
ModbusTransaction * tx = ModbusGetTx ( modbus_state , 0 ) ;
if ( ( tx - > function ! = 6 ) | | ( tx - > write . address ! = 0x0001 ) ) {
printf ( " expected function % " PRIu8 " , got % " PRIu8 " : " , 16 , tx - > function ) ;
printf ( " expected write address % " PRIu8 " , got % " PRIu8 " : " , 0x01 , tx - > write . address ) ;
goto end ;
}
/* do detect */
SigMatchSignatures ( & tv , de_ctx , det_ctx , p ) ;
if ( ! PacketAlertCheck ( p , 1 ) ) {
printf ( " sid 1 didn't match. Should have matched: " ) ;
goto end ;
}
SCMutexLock ( & f . m ) ;
r = AppLayerParserParse ( alp_tctx , & f , ALPROTO_MODBUS , STREAM_TOCLIENT ,
writeSingleRegisterRsp , sizeof ( writeSingleRegisterRsp ) ) ;
if ( r ! = 0 ) {
printf ( " toserver chunk 1 returned % " PRId32 " , expected 0: " , r ) ;
SCMutexUnlock ( & f . m ) ;
goto end ;
}
SCMutexUnlock ( & f . m ) ;
if ( modbus_state - > transaction_max ! = 1 ) {
printf ( " expected transaction_max % " PRIu8 " , got % " PRIu64 " : " , 1 , modbus_state - > transaction_max ) ;
goto end ;
}
result = 1 ;
end :
SigGroupCleanup ( de_ctx ) ;
SigCleanSignatures ( de_ctx ) ;
DetectEngineThreadCtxDeinit ( & tv , ( void * ) det_ctx ) ;
DetectEngineCtxFree ( de_ctx ) ;
if ( alp_tctx ! = NULL )
AppLayerParserThreadCtxFree ( alp_tctx ) ;
StreamTcpFreeConfig ( TRUE ) ;
FLOW_DESTROY ( & f ) ;
UTHFreePackets ( & p , 1 ) ;
return result ;
}
# endif /* UNITTESTS */
void ModbusParserRegisterTests ( void ) {
@ -2678,5 +3088,13 @@ void ModbusParserRegisterTests(void) {
ModbusParserTest11 ) ;
UtRegisterTest ( " ModbusParserTest12 - Modbus invalid PDU Length " ,
ModbusParserTest12 ) ;
UtRegisterTest ( " ModbusParserTest13 - Modbus Mask Write register request " ,
ModbusParserTest13 ) ;
UtRegisterTest ( " ModbusParserTest14 - Modbus Write single register request " ,
ModbusParserTest14 ) ;
UtRegisterTest ( " ModbusParserTest15 - Modbus invalid Mask Write register request " ,
ModbusParserTest15 ) ;
UtRegisterTest ( " ModbusParserTest16 - Modbus invalid Write single register request " ,
ModbusParserTest16 ) ;
# endif /* UNITTESTS */
}