1#!/usr/bin/env python
2"""
3Retrieve a packet from a wireshark/tshark core file
4and save it in a packet-capture file.
5"""
6
7# Copyright (C) 2013 by Gilbert Ramirez <gram@alumni.rice.edu>
8#
9# SPDX-License-Identifier: GPL-2.0-or-later
10
11import getopt
12import os
13import re
14import sys
15import tempfile
16
17exec_file = None
18core_file = None
19output_file = None
20
21verbose = 0
22debug = 0
23
24class BackTrace:
25    re_frame = re.compile(r"^#(?P<num>\d+) ")
26    re_func1 = re.compile(r"^#\d+\s+(?P<func>\w+) \(")
27    re_func2 = re.compile(r"^#\d+\s+0x[A-Fa-f\d]+ in (?P<func>\w+) \(")
28
29    def __init__(self, lines):
30
31        # In order; each item is the function name.
32        self.frames = []
33        found_non_bt_frame = 0
34        frame_will_be = 0
35
36        for line in lines:
37            m = self.re_frame.search(line)
38            if m:
39                # Skip the first frame that gdb shows,
40                # which is not part of the backtrace.
41                if not found_non_bt_frame:
42                    found_non_bt_frame = 1
43                    continue
44
45                # Get the frame number and make sure it's
46                # what we expect it should be.
47                frame_num = int(m.group("num"))
48                if frame_num != frame_will_be:
49                    sys.exit("Found frame %d instead of %d" %
50                            (frame_num, frame_will_be))
51
52                # Find the function name. XXX - need to handle '???'
53                n = self.re_func1.search(line)
54                if not n:
55                    n = self.re_func2.search(line)
56
57                if n:
58                    func = n.group("func")
59                else:
60                    sys.exit("Function name not found in %s" % (line,))
61
62                # Save the info
63                self.frames.append(func)
64                frame_will_be += 1
65
66    def Frames(self):
67        return self.frames
68
69
70    def HasFunction(self, func):
71        return func in self.frames
72
73    def Frame(self, func):
74        return self.frames.index(func)
75
76
77# Some values from wiretap; wiretap should be a shared
78# libray and a Python module should be created for it so
79# this program could just write a libpcap file directly.
80WTAP_ENCAP_PER_PACKET                 = -1
81WTAP_ENCAP_UNKNOWN                    = 0
82WTAP_ENCAP_ETHERNET                   = 1
83WTAP_ENCAP_TOKEN_RING                 = 2
84WTAP_ENCAP_SLIP                       = 3
85WTAP_ENCAP_PPP                        = 4
86WTAP_ENCAP_FDDI                       = 5
87WTAP_ENCAP_FDDI_BITSWAPPED            = 6
88WTAP_ENCAP_RAW_IP                     = 7
89WTAP_ENCAP_ARCNET                     = 8
90WTAP_ENCAP_ATM_RFC1483                = 9
91WTAP_ENCAP_LINUX_ATM_CLIP             = 10
92WTAP_ENCAP_LAPB                       = 11
93WTAP_ENCAP_ATM_SNIFFER                = 12
94WTAP_ENCAP_NULL                       = 13
95WTAP_ENCAP_ASCEND                     = 14
96WTAP_ENCAP_LAPD                       = 15
97WTAP_ENCAP_V120                       = 16
98WTAP_ENCAP_PPP_WITH_PHDR              = 17
99WTAP_ENCAP_IEEE_802_11                = 18
100WTAP_ENCAP_SLL                        = 19
101WTAP_ENCAP_FRELAY                     = 20
102WTAP_ENCAP_CHDLC                      = 21
103WTAP_ENCAP_CISCO_IOS                  = 22
104WTAP_ENCAP_LOCALTALK                  = 23
105WTAP_ENCAP_PRISM_HEADER               = 24
106WTAP_ENCAP_PFLOG                      = 25
107WTAP_ENCAP_AIROPEEK                   = 26
108WTAP_ENCAP_HHDLC                      = 27
109# last WTAP_ENCAP_ value + 1
110WTAP_NUM_ENCAP_TYPES                  = 28
111
112wtap_to_pcap_map = {
113        WTAP_ENCAP_NULL                 : 0,
114        WTAP_ENCAP_ETHERNET             : 1,
115        WTAP_ENCAP_TOKEN_RING           : 6,
116        WTAP_ENCAP_ARCNET               : 7,
117        WTAP_ENCAP_SLIP                 : 8,
118        WTAP_ENCAP_PPP                  : 9,
119        WTAP_ENCAP_FDDI_BITSWAPPED      : 10,
120        WTAP_ENCAP_FDDI                 : 10,
121        WTAP_ENCAP_ATM_RFC1483          : 11,
122        WTAP_ENCAP_RAW_IP               : 12,
123        WTAP_ENCAP_LINUX_ATM_CLIP       : 16, # or 18, or 19...
124        WTAP_ENCAP_CHDLC                : 104,
125        WTAP_ENCAP_IEEE_802_11          : 105,
126        WTAP_ENCAP_SLL                  : 113,
127        WTAP_ENCAP_LOCALTALK            : 114,
128        WTAP_ENCAP_PFLOG                : 117,
129        WTAP_ENCAP_CISCO_IOS            : 118,
130        WTAP_ENCAP_PRISM_HEADER         : 119,
131        WTAP_ENCAP_HHDLC                : 121,
132}
133
134
135wtap_name = {
136        WTAP_ENCAP_UNKNOWN                    : "Unknown",
137        WTAP_ENCAP_ETHERNET                   : "Ethernet",
138        WTAP_ENCAP_TOKEN_RING                 : "Token-Ring",
139        WTAP_ENCAP_SLIP                       : "SLIP",
140        WTAP_ENCAP_PPP                        : "PPP",
141        WTAP_ENCAP_FDDI                       : "FDDI",
142        WTAP_ENCAP_FDDI_BITSWAPPED            : "FDDI (Bitswapped)",
143        WTAP_ENCAP_RAW_IP                     : "Raw IP",
144        WTAP_ENCAP_ARCNET                     : "ARCNET",
145        WTAP_ENCAP_ATM_RFC1483                : "ATM RFC1483",
146        WTAP_ENCAP_LINUX_ATM_CLIP             : "Linux ATM CLIP",
147        WTAP_ENCAP_LAPB                       : "LAPB",
148        WTAP_ENCAP_ATM_SNIFFER                : "ATM Sniffer",
149        WTAP_ENCAP_NULL                       : "Null",
150        WTAP_ENCAP_ASCEND                     : "Ascend",
151        WTAP_ENCAP_LAPD                       : "LAPD",
152        WTAP_ENCAP_V120                       : "V.120",
153        WTAP_ENCAP_PPP_WITH_PHDR              : "PPP (with PHDR)",
154        WTAP_ENCAP_IEEE_802_11                : "IEEE 802.11",
155        WTAP_ENCAP_SLL                        : "SLL",
156        WTAP_ENCAP_FRELAY                     : "Frame Relay",
157        WTAP_ENCAP_CHDLC                      : "Cisco HDLC",
158        WTAP_ENCAP_CISCO_IOS                  : "Cisco IOS",
159        WTAP_ENCAP_LOCALTALK                  : "LocalTalk",
160        WTAP_ENCAP_PRISM_HEADER               : "Prism Header",
161        WTAP_ENCAP_PFLOG                      : "PFLog",
162        WTAP_ENCAP_AIROPEEK                   : "AiroPeek",
163        WTAP_ENCAP_HHDLC                      : "HHDLC",
164}
165
166def wtap_to_pcap(wtap):
167    if not wtap_to_pcap_map.has_key(wtap):
168        sys.exit("Don't know how to convert wiretap encoding %d to libpcap." % \
169                (wtap))
170
171    return wtap_to_pcap_map[wtap]
172
173
174def run_gdb(*commands):
175    if len(commands) == 0:
176        return []
177
178    # Create a temporary file
179    fname = tempfile.mktemp()
180    try:
181        fh = open(fname, "w")
182    except IOError, err:
183        sys.exit("Cannot open %s for writing: %s" % (fname, err))
184
185    # Put the commands in it
186    for cmd in commands:
187        fh.write(cmd)
188        fh.write("\n")
189
190    fh.write("quit\n")
191    try:
192        fh.close()
193    except IOError, err:
194        try:
195            os.unlink(fname)
196        except Exception:
197            pass
198        sys.exit("Cannot close %s: %s" % (fname, err))
199
200
201    # Run gdb
202    cmd = "gdb --nw --quiet --command=%s %s %s" % (fname, exec_file, core_file)
203    if verbose:
204        print "Invoking %s" % (cmd,)
205    try:
206        pipe = os.popen(cmd)
207    except OSError, err:
208        try:
209            os.unlink(fname)
210        except Exception:
211            pass
212        sys.exit("Cannot run gdb: %s" % (err,))
213
214    # Get gdb's output
215    result = pipe.readlines()
216    error = pipe.close()
217    if error is not None:
218        try:
219            os.unlink(fname)
220        except Exception:
221            pass
222        sys.exit("gdb returned an exit value of %s" % (error,))
223
224
225    # Remove the temp file and return the results
226    try:
227        os.unlink(fname)
228    except Exception:
229        pass
230    return result
231
232def get_value_from_frame(frame_num, variable, fmt=""):
233    cmds = []
234    if frame_num > 0:
235        cmds.append("up %d" % (frame_num,))
236
237    cmds.append("print %s %s" % (fmt, variable))
238    lines = apply(run_gdb, cmds)
239
240    LOOKING_FOR_START = 0
241    READING_VALUE = 1
242    state = LOOKING_FOR_START
243    result = ""
244    for line in lines:
245        if line[-1] == "\n":
246            line = line[0:-1]
247        if line[-1] == "\r":
248            line = line[0:-1]
249
250        if state == LOOKING_FOR_START:
251            if len(line) < 4:
252                continue
253            else:
254                if line[0:4] == "$1 =":
255                    result = line[4:]
256                    state = READING_VALUE
257
258        elif state == READING_VALUE:
259            result += line
260
261    return result
262
263def get_int_from_frame(frame_num, variable):
264    text = get_value_from_frame(frame_num, variable)
265    try:
266        integer = int(text)
267    except ValueError:
268        sys.exit("Could not convert '%s' to integer." % (text,))
269    return integer
270
271
272def get_byte_array_from_frame(frame_num, variable, length):
273    cmds = []
274    if frame_num > 0:
275        cmds.append("up %d" % (frame_num,))
276
277    cmds.append("print %s" % (variable,))
278    cmds.append("x/%dxb %s" % (length, variable))
279    lines = apply(run_gdb, cmds)
280    if debug:
281        print lines
282
283    bytes = []
284
285    LOOKING_FOR_START = 0
286    BYTES = 1
287    state = LOOKING_FOR_START
288
289    for line in lines:
290        if state == LOOKING_FOR_START:
291            if len(line) < 3:
292                continue
293            elif line[0:3] == "$1 ":
294                state = BYTES
295        elif state == BYTES:
296            line.rstrip()
297            fields = line.split('\t')
298            if fields[0][-1] != ":":
299                print "Failed to parse byte array from gdb:"
300                print line
301                sys.exit(1)
302
303            for field in fields[1:]:
304                val = int(field, 16)
305                bytes.append(val)
306        else:
307            assert 0
308
309    return bytes
310
311def make_cap_file(pkt_data, lnk_t):
312
313    pcap_lnk_t = wtap_to_pcap(lnk_t)
314
315    # Create a temporary file
316    fname = tempfile.mktemp()
317    try:
318        fh = open(fname, "w")
319    except IOError, err:
320        sys.exit("Cannot open %s for writing: %s" % (fname, err))
321
322    print "Packet Data:"
323
324    # Put the hex dump in it
325    offset = 0
326    BYTES_IN_ROW = 16
327    for byte in pkt_data:
328        if (offset % BYTES_IN_ROW) == 0:
329            print >> fh, "\n%08X  " % (offset,),
330            print "\n%08X  " % (offset,),
331
332        print >> fh, "%02X " % (byte,),
333        print "%02X " % (byte,),
334        offset += 1
335
336    print >> fh, "\n"
337    print "\n"
338
339    try:
340        fh.close()
341    except IOError, err:
342        try:
343            os.unlink(fname)
344        except Exception:
345            pass
346        sys.exit("Cannot close %s: %s" % (fname, err))
347
348
349    # Run text2pcap
350    cmd = "text2pcap -q -l %s %s %s" % (pcap_lnk_t, fname, output_file)
351#       print "Command is %s" % (cmd,)
352    try:
353        retval = os.system(cmd)
354    except OSError, err:
355        try:
356            os.unlink(fname)
357        except Exception:
358            pass
359        sys.exit("Cannot run text2pcap: %s" % (err,))
360
361    # Remove the temp file
362    try:
363        os.unlink(fname)
364    except Exception:
365        pass
366
367    if retval == 0:
368        print "%s created with %d bytes in packet, and %s encoding." % \
369                (output_file, len(pkt_data), wtap_name[lnk_t])
370    else:
371        sys.exit("text2pcap did not run successfully.")
372
373
374
375
376def try_frame(func_text, cap_len_text, lnk_t_text, data_text):
377
378    # Get the back trace
379    bt_text = run_gdb("bt")
380    bt = BackTrace(bt_text)
381    if not bt.HasFunction(func_text):
382        print "%s() not found in backtrace." % (func_text,)
383        return 0
384    else:
385        print "%s() found in backtrace." % (func_text,)
386
387    # Figure out where the call to epan_dissect_run is.
388    frame_num = bt.Frame(func_text)
389
390    # Get the capture length
391    cap_len = get_int_from_frame(frame_num, cap_len_text)
392
393    # Get the encoding type
394    lnk_t = get_int_from_frame(frame_num, lnk_t_text)
395
396    # Get the packet data
397    pkt_data = get_byte_array_from_frame(frame_num, data_text, cap_len)
398
399    if verbose:
400        print "Length=%d" % (cap_len,)
401        print "Encoding=%d" % (lnk_t,)
402        print "Data (%d bytes) = %s" % (len(pkt_data), pkt_data)
403    make_cap_file(pkt_data, lnk_t)
404    return 1
405
406def run():
407    if try_frame("epan_dissect_run",
408            "fd->cap_len", "fd->lnk_t", "data"):
409        return
410    elif try_frame("add_packet_to_packet_list",
411            "fdata->cap_len", "fdata->lnk_t", "buf"):
412        return
413    else:
414        sys.exit("A packet cannot be pulled from this core.")
415
416
417def usage():
418    print "pkt-from-core.py [-v] -w capture_file executable-file (core-file or process-id)"
419    print ""
420    print "\tGiven an executable file and a core file, this tool"
421    print "\tuses gdb to retrieve the packet that was being dissected"
422    print "\tat the time wireshark/tshark stopped running. The packet"
423    print "\tis saved in the capture_file specified by the -w option."
424    print ""
425    print "\t-v : verbose"
426    sys.exit(1)
427
428def main():
429    global exec_file
430    global core_file
431    global output_file
432    global verbose
433    global debug
434
435    optstring = "dvw:"
436    try:
437        opts, args = getopt.getopt(sys.argv[1:], optstring)
438    except getopt.error:
439        usage()
440
441    for opt, arg in opts:
442        if opt == "-w":
443            output_file = arg
444        elif opt == "-v":
445            verbose = 1
446        elif opt == "-d":
447            debug = 1
448        else:
449            assert 0
450
451    if output_file is None:
452        usage()
453
454    if len(args) != 2:
455        usage()
456
457    exec_file = args[0]
458    core_file = args[1]
459
460    run()
461
462if __name__ == '__main__':
463    main()
464
465#
466# Editor modelines  -  https://www.wireshark.org/tools/modelines.html
467#
468# Local variables:
469# c-basic-offset: 4
470# indent-tabs-mode: nil
471# End:
472#
473# vi: set shiftwidth=4 expandtab:
474# :indentSize=4:noTabs=true:
475#
476