From 9a6353cb14c74aee29fbacc8f452aa214747aafa Mon Sep 17 00:00:00 2001 From: bol-van Date: Sun, 23 Nov 2025 12:22:16 +0300 Subject: [PATCH] nfqws2: add mtproto detection --- nfq2/crypto/aes-ctr.c | 38 ++++++++++++++++++++++++++++++++++++++ nfq2/crypto/aes-ctr.h | 7 +++++++ nfq2/desync.c | 11 ++++++++++- nfq2/lua.c | 4 ++++ nfq2/protocol.c | 23 +++++++++++++++++++++-- nfq2/protocol.h | 5 +++-- 6 files changed, 83 insertions(+), 5 deletions(-) create mode 100644 nfq2/crypto/aes-ctr.c create mode 100644 nfq2/crypto/aes-ctr.h diff --git a/nfq2/crypto/aes-ctr.c b/nfq2/crypto/aes-ctr.c new file mode 100644 index 0000000..52a9111 --- /dev/null +++ b/nfq2/crypto/aes-ctr.c @@ -0,0 +1,38 @@ +#include "aes-ctr.h" +#include + +#define AES_BLOCKLEN 16 + +void aes_ctr_xcrypt_buffer(aes_context *ctx, const uint8_t *iv, uint8_t *buf, size_t length) +{ + uint8_t bi, buffer[AES_BLOCKLEN], ivc[AES_BLOCKLEN]; + size_t i; + + memcpy(ivc,iv,AES_BLOCKLEN); + + for (i = 0, bi = AES_BLOCKLEN; i < length; ++i, ++bi) + { + if (bi == AES_BLOCKLEN) /* we need to regen xor compliment in buffer */ + { + memcpy(buffer, ivc, AES_BLOCKLEN); + aes_cipher(ctx, buffer, buffer); + + /* Increment ivc and handle overflow */ + for (bi = (AES_BLOCKLEN - 1); bi >= 0; --bi) + { + /* inc will owerflow */ + if (ivc[bi] == 255) + { + ivc[bi] = 0; + continue; + } + ivc[bi] += 1; + break; + } + bi = 0; + } + buf[i] = (buf[i] ^ buffer[bi]); + } +} + + diff --git a/nfq2/crypto/aes-ctr.h b/nfq2/crypto/aes-ctr.h new file mode 100644 index 0000000..db38f39 --- /dev/null +++ b/nfq2/crypto/aes-ctr.h @@ -0,0 +1,7 @@ +#pragma once + +#include +#include "aes.h" + +// this function rewrites buf +void aes_ctr_xcrypt_buffer(aes_context *ctx, const uint8_t *iv, uint8_t *buf, size_t length); diff --git a/nfq2/desync.c b/nfq2/desync.c index 02d2950..ed017d1 100644 --- a/nfq2/desync.c +++ b/nfq2/desync.c @@ -1162,7 +1162,6 @@ static uint8_t dpi_desync_tcp_packet_play(unsigned int replay_piece, unsigned in } process_retrans_fail(ctrack, IPPROTO_TCP, (struct sockaddr*)&src); - if (IsHttp(rdata_payload, rlen_payload)) { DLOG("packet contains HTTP request\n"); @@ -1253,6 +1252,16 @@ static uint8_t dpi_desync_tcp_packet_play(unsigned int replay_piece, unsigned in } } } + else if (ctrack && (ctrack->seq_last - ctrack->seq0)==1 && IsMTProto(dis->data_payload, dis->len_payload)) + { + // mtproto detection requires aes. react only on the first tcp data packet. do not detect if ctrack unavailable. + l7payload = L7P_MTPROTO_INITIAL; + if (l7proto == L7_UNKNOWN) + { + l7proto = L7_MTPROTO; + if (ctrack->l7proto == L7_UNKNOWN) ctrack->l7proto = l7proto; + } + } else { t_protocol_probe testers[] = { diff --git a/nfq2/lua.c b/nfq2/lua.c index 18885ad..6490118 100644 --- a/nfq2/lua.c +++ b/nfq2/lua.c @@ -3,6 +3,10 @@ #include "lua.h" #include "params.h" #include "helpers.h" +#include "crypto/sha.h" +#include "crypto/aes-gcm.h" +#include "crypto/aes-ctr.h" + static void lua_check_argc(lua_State *L, const char *where, int argc) { diff --git a/nfq2/protocol.c b/nfq2/protocol.c index 7f3d49b..6bfb9df 100644 --- a/nfq2/protocol.c +++ b/nfq2/protocol.c @@ -3,6 +3,9 @@ #include "protocol.h" #include "helpers.h" #include "params.h" +#include "crypto/sha.h" +#include "crypto/aes-gcm.h" +#include "crypto/aes-ctr.h" #include #include @@ -26,7 +29,7 @@ static bool FindNLD(const uint8_t *dom, size_t dlen, int level, const uint8_t ** return true; } -static const char *l7proto_name[] = {"all","unknown","http","tls","quic","wireguard","dht","discord","stun","xmpp","dns"}; +static const char *l7proto_name[] = {"all","unknown","http","tls","quic","wireguard","dht","discord","stun","xmpp","dns","mtproto"}; const char *l7proto_str(t_l7proto l7) { if (l7>=L7_LAST) return NULL; @@ -46,7 +49,9 @@ static const char *l7payload_name[] = { "all","unknown","empty","http_req","http_reply","tls_client_hello","tls_server_hello","quic_initial", "wireguard_initiation","wireguard_response","wireguard_cookie","wireguard_keepalive","wireguard_data", "dht","discord_ip_discovery","stun_binding_req", - "xmpp_stream", "xmpp_starttls", "xmpp_proceed", "xmpp_features", "dns_query", "dns_response"}; + "xmpp_stream", "xmpp_starttls", "xmpp_proceed", "xmpp_features", + "dns_query", "dns_response", + "mtproto_initial"}; t_l7payload l7payload_from_name(const char *name) { int idx = str_index(l7payload_name,sizeof(l7payload_name)/sizeof(*l7payload_name),name); @@ -1385,3 +1390,17 @@ bool IsStunBindingRequest(const uint8_t *data, size_t len) ntohl(*(uint32_t*)(&data[4]))==0x2112A442 && // magic cookie ntohs(*(uint16_t*)(&data[2]))==len-20; } +bool IsMTProto(const uint8_t *data, size_t len) +{ + if (len>=64) + { + aes_context ctx; + uint8_t dcopy[64]; + + aes_init_keygen_tables(); + memcpy(dcopy,data,sizeof(dcopy)); + aes_setkey(&ctx, true, data+8, 32); + aes_ctr_xcrypt_buffer(&ctx, data+40, dcopy, sizeof(dcopy)); + return !memcmp(dcopy+56,"\xEF\xEF\xEF\xEF",4); + } +} diff --git a/nfq2/protocol.h b/nfq2/protocol.h index bc8a18c..02aa46d 100644 --- a/nfq2/protocol.h +++ b/nfq2/protocol.h @@ -3,8 +3,6 @@ #include #include #include -#include "crypto/sha.h" -#include "crypto/aes-gcm.h" #include "helpers.h" typedef enum { @@ -19,6 +17,7 @@ typedef enum { L7_STUN, L7_XMPP, L7_DNS, + L7_MTPROTO, L7_LAST, L7_INVALID=L7_LAST } t_l7proto; const char *l7proto_str(t_l7proto l7); @@ -48,6 +47,7 @@ typedef enum { L7P_XMPP_FEATURES, L7P_DNS_QUERY, L7P_DNS_RESPONSE, + L7P_MTPROTO_INITIAL, L7P_LAST, L7P_INVALID=L7P_LAST } t_l7payload; t_l7payload l7payload_from_name(const char *name); @@ -151,6 +151,7 @@ bool IsWireguardData(const uint8_t *data, size_t len); bool IsDht(const uint8_t *data, size_t len); bool IsDiscordIpDiscoveryRequest(const uint8_t *data, size_t len); bool IsStunBindingRequest(const uint8_t *data, size_t len); +bool IsMTProto(const uint8_t *data, size_t len); #define QUIC_MAX_CID_LENGTH 20 typedef struct quic_cid {