/src/wireshark/wiretap/mplog.c
Line | Count | Source |
1 | | /* mplog.c |
2 | | * |
3 | | * File format support for Micropross mplog files |
4 | | * Copyright (c) 2016 by Martin Kaiser <martin@kaiser.cx> |
5 | | * |
6 | | * Wireshark - Network traffic analyzer |
7 | | * By Gerald Combs <gerald@wireshark.org> |
8 | | * Copyright 1998 Gerald Combs |
9 | | * |
10 | | * SPDX-License-Identifier: GPL-2.0-or-later |
11 | | */ |
12 | | |
13 | | |
14 | | /* |
15 | | The mplog file format logs the communication between a contactless |
16 | | smartcard and a card reader. Such files contain information about the |
17 | | physical layer as well as the bytes exchanged between devices. |
18 | | Some commercial logging and testing tools by the French company Micropross |
19 | | use this format. |
20 | | |
21 | | The information used for implementing this wiretap module were |
22 | | obtained from reverse-engineering. There is no publicly available |
23 | | documentation of the mplog file format. |
24 | | |
25 | | Mplog files start with the string "MPCSII". This string is part of |
26 | | the header which is in total 0x80 bytes long. |
27 | | |
28 | | Following the header, the file is a sequence of 8 byte-blocks. |
29 | | data (one byte) |
30 | | block type (one byte) |
31 | | timestamp (six bytes) |
32 | | |
33 | | The timestamp is a counter in little-endian format. The counter is in |
34 | | units of 10ns. |
35 | | */ |
36 | | |
37 | | #include "config.h" |
38 | | #include "mplog.h" |
39 | | |
40 | | #include <string.h> |
41 | | |
42 | | #include <wsutil/pint.h> |
43 | | |
44 | | #include <wtap_module.h> |
45 | | #include <file_wrappers.h> |
46 | | |
47 | | /* the block types */ |
48 | 0 | #define TYPE_PCD_PICC_A 0x70 |
49 | 0 | #define TYPE_PICC_PCD_A 0x71 |
50 | 0 | #define TYPE_PCD_PICC_B 0x72 |
51 | 0 | #define TYPE_PICC_PCD_B 0x73 |
52 | 0 | #define TYPE_UNKNOWN 0xFF |
53 | | |
54 | 0 | #define KNOWN_TYPE(x) \ |
55 | 0 | ( \ |
56 | 0 | ((x) == TYPE_PCD_PICC_A) || \ |
57 | 0 | ((x) == TYPE_PICC_PCD_A) || \ |
58 | 0 | ((x) == TYPE_PCD_PICC_B) || \ |
59 | 0 | ((x) == TYPE_PICC_PCD_B) \ |
60 | 0 | ) |
61 | | |
62 | 0 | #define MPLOG_BLOCK_SIZE 8 |
63 | | |
64 | | /* ISO14443 pseudo-header, see https://www.kaiser.cx/pcap-iso14443.html */ |
65 | 0 | #define ISO14443_PSEUDO_HDR_VER 0 |
66 | 0 | #define ISO14443_PSEUDO_HDR_LEN 4 |
67 | | /* the two transfer events are the types that include a trailing CRC |
68 | | the CRC is always present in mplog files */ |
69 | 0 | #define ISO14443_PSEUDO_HDR_PICC_TO_PCD 0xFF |
70 | 0 | #define ISO14443_PSEUDO_HDR_PCD_TO_PICC 0xFE |
71 | | |
72 | | |
73 | 0 | #define ISO14443_MAX_PKT_LEN 4096 |
74 | | |
75 | 0 | #define PKT_BUF_LEN (ISO14443_PSEUDO_HDR_LEN + ISO14443_MAX_PKT_LEN) |
76 | | |
77 | | |
78 | | static int mplog_file_type_subtype = -1; |
79 | | |
80 | | void register_mplog(void); |
81 | | |
82 | | /* read the next packet, starting at the current position of fh |
83 | | as we know very little about the file format, our approach is rather simple: |
84 | | - we read block-by-block until a known block-type is found |
85 | | - this block's type is the type of the next packet |
86 | | - this block's timestamp will become the packet's timestamp |
87 | | - the data byte will be our packet's first byte |
88 | | - we carry on reading blocks and add the data bytes |
89 | | of all blocks of "our" type |
90 | | - if a different well-known block type is found, this is the end of |
91 | | our packet, we go back one block so that this block can be picked |
92 | | up as the start of the next packet |
93 | | - if two blocks of our packet's block type are more than 200us apart, |
94 | | we treat this as a packet boundary as described above |
95 | | */ |
96 | | static bool mplog_read_packet(wtap *wth, FILE_T fh, wtap_rec *rec, |
97 | | int *err, char **err_info) |
98 | 0 | { |
99 | 0 | uint8_t *p, *start_p; |
100 | | /* --- the last block of a known type --- */ |
101 | 0 | uint64_t last_ctr = 0; |
102 | | /* --- the current block --- */ |
103 | 0 | uint8_t block[MPLOG_BLOCK_SIZE]; /* the entire block */ |
104 | 0 | uint8_t data, type; /* its data and block type bytes */ |
105 | 0 | uint64_t ctr; /* its timestamp counter */ |
106 | | /* --- the packet we're assembling --- */ |
107 | 0 | int pkt_bytes = 0; |
108 | 0 | uint8_t pkt_type = TYPE_UNKNOWN; |
109 | | /* the timestamp of the packet's first block, |
110 | | this will become the packet's timestamp */ |
111 | 0 | uint64_t pkt_ctr = 0; |
112 | | |
113 | |
|
114 | 0 | ws_buffer_assure_space(&rec->data, PKT_BUF_LEN); |
115 | 0 | p = ws_buffer_start_ptr(&rec->data); |
116 | 0 | start_p = p; |
117 | | |
118 | | /* leave space for the iso14443 pseudo header |
119 | | we can't create it until we've seen the entire packet */ |
120 | 0 | p += ISO14443_PSEUDO_HDR_LEN; |
121 | 0 | ws_buffer_increase_length(&rec->data, ISO14443_PSEUDO_HDR_LEN); |
122 | |
|
123 | 0 | do { |
124 | 0 | if (!wtap_read_bytes_or_eof(fh, block, sizeof(block), err, err_info)) { |
125 | | /* If we've already read some data, if this failed with an EOF, |
126 | | so that *err is 0, it's a short read. */ |
127 | 0 | if (pkt_bytes != 0) { |
128 | 0 | if (*err == 0) |
129 | 0 | *err = WTAP_ERR_SHORT_READ; |
130 | 0 | } |
131 | 0 | break; |
132 | 0 | } |
133 | 0 | data = block[0]; |
134 | 0 | type = block[1]; |
135 | 0 | ctr = pletohu48(&block[2]); |
136 | |
|
137 | 0 | if (pkt_type == TYPE_UNKNOWN) { |
138 | 0 | if (KNOWN_TYPE(type)) { |
139 | 0 | pkt_type = type; |
140 | 0 | pkt_ctr = ctr; |
141 | 0 | } |
142 | 0 | } |
143 | |
|
144 | 0 | if (type == pkt_type) { |
145 | 0 | if (last_ctr != 0) { |
146 | | /* if the distance to the last byte of the |
147 | | same type is larger than 200us, this is very likely the |
148 | | first byte of a new packet -> go back one block and exit |
149 | | ctr and last_ctr are in units of 10ns |
150 | | at 106kbit/s, it takes approx 75us to send one byte */ |
151 | 0 | if (ctr - last_ctr > 200*100) { |
152 | 0 | if (file_seek(fh, -MPLOG_BLOCK_SIZE, SEEK_CUR, err) == -1) { |
153 | 0 | return false; |
154 | 0 | } |
155 | 0 | break; |
156 | 0 | } |
157 | 0 | } |
158 | | |
159 | 0 | *p++ = data; |
160 | 0 | pkt_bytes++; |
161 | 0 | last_ctr = ctr; |
162 | 0 | } |
163 | 0 | else if (KNOWN_TYPE(type)) { |
164 | 0 | if (file_seek(fh, -MPLOG_BLOCK_SIZE, SEEK_CUR, err) == -1) { |
165 | 0 | return false; |
166 | 0 | } |
167 | 0 | break; |
168 | 0 | } |
169 | 0 | } while (pkt_bytes < ISO14443_MAX_PKT_LEN); |
170 | | |
171 | 0 | if (pkt_type == TYPE_UNKNOWN) |
172 | 0 | return false; |
173 | | |
174 | 0 | ws_buffer_increase_length(&rec->data, pkt_bytes); |
175 | 0 | start_p[0] = ISO14443_PSEUDO_HDR_VER; |
176 | |
|
177 | 0 | if (pkt_type==TYPE_PCD_PICC_A || pkt_type==TYPE_PCD_PICC_B) |
178 | 0 | start_p[1] = ISO14443_PSEUDO_HDR_PCD_TO_PICC; |
179 | 0 | else |
180 | 0 | start_p[1] = ISO14443_PSEUDO_HDR_PICC_TO_PCD; |
181 | |
|
182 | 0 | phtonu16(&start_p[2], pkt_bytes); |
183 | |
|
184 | 0 | wtap_setup_packet_rec(rec, wth->file_encap); |
185 | 0 | rec->block = wtap_block_create(WTAP_BLOCK_PACKET); |
186 | 0 | rec->presence_flags = WTAP_HAS_TS | WTAP_HAS_CAP_LEN; |
187 | 0 | rec->ts.secs = (time_t)((pkt_ctr*10)/(1000*1000*1000)); |
188 | 0 | rec->ts.nsecs = (int)((pkt_ctr*10)%(1000*1000*1000)); |
189 | 0 | rec->rec_header.packet_header.caplen = ISO14443_PSEUDO_HDR_LEN + pkt_bytes; |
190 | 0 | rec->rec_header.packet_header.len = rec->rec_header.packet_header.caplen; |
191 | |
|
192 | 0 | return true; |
193 | 0 | } |
194 | | |
195 | | |
196 | | static bool |
197 | | mplog_read(wtap *wth, wtap_rec *rec, int *err, char **err_info, |
198 | | int64_t *data_offset) |
199 | 0 | { |
200 | 0 | *data_offset = file_tell(wth->fh); |
201 | |
|
202 | 0 | return mplog_read_packet(wth, wth->fh, rec, err, err_info); |
203 | 0 | } |
204 | | |
205 | | |
206 | | static bool |
207 | | mplog_seek_read(wtap *wth, int64_t seek_off, wtap_rec *rec, |
208 | | int *err, char **err_info) |
209 | 0 | { |
210 | 0 | if (-1 == file_seek(wth->random_fh, seek_off, SEEK_SET, err)) |
211 | 0 | return false; |
212 | | |
213 | 0 | if (!mplog_read_packet(wth, wth->random_fh, rec, err, err_info)) { |
214 | | /* Even if we got an immediate EOF, that's an error. */ |
215 | 0 | if (*err == 0) |
216 | 0 | *err = WTAP_ERR_SHORT_READ; |
217 | 0 | return false; |
218 | 0 | } |
219 | 0 | return true; |
220 | 0 | } |
221 | | |
222 | | |
223 | | wtap_open_return_val mplog_open(wtap *wth, int *err, char **err_info) |
224 | 0 | { |
225 | 0 | bool ok; |
226 | 0 | uint8_t magic[6]; |
227 | |
|
228 | 0 | ok = wtap_read_bytes(wth->fh, magic, 6, err, err_info); |
229 | 0 | if (!ok) { |
230 | 0 | if (*err != WTAP_ERR_SHORT_READ) |
231 | 0 | return WTAP_OPEN_ERROR; |
232 | 0 | return WTAP_OPEN_NOT_MINE; |
233 | 0 | } |
234 | 0 | if (memcmp(magic, "MPCSII", 6) != 0) |
235 | 0 | return WTAP_OPEN_NOT_MINE; |
236 | | |
237 | 0 | wth->file_encap = WTAP_ENCAP_ISO14443; |
238 | 0 | wth->snapshot_length = 0; |
239 | 0 | wth->file_tsprec = WTAP_TSPREC_NSEC; |
240 | |
|
241 | 0 | wth->priv = NULL; |
242 | |
|
243 | 0 | wth->subtype_read = mplog_read; |
244 | 0 | wth->subtype_seek_read = mplog_seek_read; |
245 | 0 | wth->file_type_subtype = mplog_file_type_subtype; |
246 | | |
247 | | /* skip the file header */ |
248 | 0 | if (-1 == file_seek(wth->fh, 0x80, SEEK_SET, err)) |
249 | 0 | return WTAP_OPEN_ERROR; |
250 | | |
251 | 0 | *err = 0; |
252 | | |
253 | | /* |
254 | | * Add an IDB; we don't know how many interfaces were |
255 | | * involved, so we just say one interface, about which |
256 | | * we only know the link-layer type, snapshot length, |
257 | | * and time stamp resolution. |
258 | | */ |
259 | 0 | wtap_add_generated_idb(wth); |
260 | |
|
261 | 0 | return WTAP_OPEN_MINE; |
262 | 0 | } |
263 | | |
264 | | static const struct supported_block_type mplog_blocks_supported[] = { |
265 | | /* |
266 | | * We support packet blocks, with no comments or other options. |
267 | | */ |
268 | | { WTAP_BLOCK_PACKET, MULTIPLE_BLOCKS_SUPPORTED, NO_OPTIONS_SUPPORTED } |
269 | | }; |
270 | | |
271 | | static const struct file_type_subtype_info mplog_info = { |
272 | | "Micropross mplog", "mplog", "mplog", NULL, |
273 | | false, BLOCKS_SUPPORTED(mplog_blocks_supported), |
274 | | NULL, NULL, NULL |
275 | | }; |
276 | | |
277 | | void register_mplog(void) |
278 | 15 | { |
279 | 15 | mplog_file_type_subtype = wtap_register_file_type_subtype(&mplog_info); |
280 | | |
281 | | /* |
282 | | * Register name for backwards compatibility with the |
283 | | * wtap_filetypes table in Lua. |
284 | | */ |
285 | 15 | wtap_register_backwards_compatibility_lua_name("MPLOG", |
286 | 15 | mplog_file_type_subtype); |
287 | 15 | } |
288 | | |
289 | | /* |
290 | | * Editor modelines - https://www.wireshark.org/tools/modelines.html |
291 | | * |
292 | | * Local variables: |
293 | | * c-basic-offset: 4 |
294 | | * tab-width: 8 |
295 | | * indent-tabs-mode: nil |
296 | | * End: |
297 | | * |
298 | | * vi: set shiftwidth=4 tabstop=8 expandtab: |
299 | | * :indentSize=4:tabSize=8:noTabs=true: |
300 | | */ |