#!perl -w # this script converts utlog files from USBTrace to several other formats: # - to libpcap .cap file format .. with direction info, use strict; use IO::File; use Getopt::Long; $|=1; my $hexoutput=0; my $txtlimit= 0x4000; my $rawoutput=0; my $txtoutput=0; my $pcapoutput=0; my $rndis2cap=0; my $removepppframing=0; my $onepppframeperpkt=0; my $linktype=9; my $verbose=0; my $fileversion=0; sub usage { return <<__EOF__ parseutlog [options] utlog.log -x hex output -p [TYPE] pcap output, optionally overide DLT_PPP linktype -a ascii output -L MAX - max amount of data to print unsummarized. -c raw output -d remove ppp framing ( implies pcap output ) -1 one ppp frame per pcap packet ( implies pcap output ) __EOF__ } my $skipbytes; GetOptions( "x"=>\$hexoutput, "o=s"=>\$skipbytes, "c"=>\$rawoutput, "r"=>\$rndis2cap, "p:s"=> sub { $pcapoutput=1; $linktype=eval($_[1]) if ($_[1] && length($_[1])); printf("%d:%s\n", scalar @_, join(",",@_)); }, "a"=>\$txtoutput, "L=s"=> sub { $txtlimit= eval($_[1]); }, "d"=>\$removepppframing, "1"=>\$onepppframeperpkt, "v+"=>\$verbose, ) or die usage(); $pcapoutput=1 if ($removepppframing || $onepppframeperpkt || $rndis2cap); die "specify one of -c(raw), -x(hex), -p(pcap), -r(rndis) or -a(asc) or -v(verbose)\n".usage() unless ($rawoutput || $hexoutput || $txtoutput || $pcapoutput || $verbose); printf("1=%d p=%d l=%d\n", $onepppframeperpkt, $removepppframing, $linktype); my $fn= shift or die "need fn\n"; my $fh= IO::File->new($fn, "r") or die "$fn: $!\n"; binmode $fh; my $magic; $fh->read($magic, 10) or die "read $fn: $!\n"; if ($magic =~ /UTLOG5.\x00\x00\x00/) { $fileversion= 5; } elsif ($magic =~ /UTLOG3.\x00\x00\x00/) { $fileversion= 3; } elsif ($magic =~ /UTLOG2.\x00\x00\x00/) { $fileversion= 2; } elsif ($magic =~ /^UTLOG\D/) { $fileversion= 1; $fh->seek(5, SEEK_SET); } else { die sprintf("invalid utlog magic: %s\n", unpack("H*", $magic)); } printf("File verson %d\n", $fileversion); if ($skipbytes) { $fh->seek($skipbytes, SEEK_SET); } elsif ($fileversion>1) { my $n= unpack("V", substr($magic, 6, 4)); $fh->seek(10, SEEK_SET); for (my $i=0 ; $i<$n ; $i++) { my $data20; $fh->read($data20, 20); my $datalen; $fh->read($datalen, 4); my $datablk; $fh->read($datablk, unpack("V", $datalen)); printf("[%d] %s %04x %s\n", $i, unpack("H*", $data20), unpack("V", $datalen), unpack("H*", $datablk)); # 03 00 00 00 # # 30 f0 77 89 a8 9c 21 8a ff ff ff 00 40 24 fc 00 50 bd 2e 01 # 20 00 00 00:09 02 20 00 01 01 00 c0 fa 09 04 00 00 02 ff ff ff 00 07 05 81 02 40 00 00 07 05 02 02 40 00 00 # # a8 9c 21 8a 00 00 00 00 ff ff ff 00 68 24 fc 00 98 b7 2e 01 # 20 00 00 00:09 02 20 00 01 01 00 c0 fa 09 04 00 00 02 ff ff ff 00 07 05 81 02 40 00 00 07 05 02 02 40 00 00 # } } my $req= 0; my %x; my $tprev; while (!$fh->eof) { my $ofs= $fh->tell(); my $size= readdword($fh) or die "readsize $fn: $!\n"; my $frame; $fh->read($frame, $size-4) or die "readframe $fn: $!\n"; if (length($frame)!=$size-4) { printf("req %d @ %08lx l=%08lx\n", $req++, $ofs, $size); die sprintf("invalid frame at 0x%x, size=0x%x, expected=0x%x\n", $ofs, length($frame)+4, $size); } my $usb= decodeframe($frame); my $haveurbdata= ($usb->{urb} && $usb->{urb}{setupdata} && length($usb->{urb}{setupdata})); my $havedata= ($usb->{data} && length($usb->{data})); my $tstamp= $fileversion>=3 ? usecstamp(map { $usb->{hdr}{$_} } qw(sec usec)) : msecstamp(map { $usb->{hdr}{$_} } qw(hour minute second msec)); printf("%08x(+%8d) %08x %s %d [%-16s] %s\n", $tstamp, $tprev ? $tstamp-$tprev : 0, $usb->{hdr}{deviceobject}, $usb->{endpoint}{y} ? sprintf("ep%02x", $usb->{endpoint}{y}) : " ", $usb->{hdr}{dir}, $haveurbdata ? unpack("H*", $usb->{urb}{setupdata}):"", $havedata ? unpack("H*", $usb->{data}) : "") if $haveurbdata || $havedata; $tprev= $tstamp; if ($verbose>1) { printf("hdr : %s\n", unpack 'H*',$usb->{hdrdata}); printf(" %s\n", join ", ", map { sprintf("%s=0x%x", $_, $usb->{hdr}{$_}) } keys %{$usb->{hdr}}); printf("irp : %s\n", unpack 'H*',$usb->{irpdata}); printf(" %s\n", join ", ", map { sprintf("%s=0x%x", $_, $usb->{irp}{$_}) } keys %{$usb->{irp}}); printf("stack : %s\n", unpack 'H*',$usb->{stackdata}); printf(" %s\n", join ", ", map { sprintf("%s=0x%x", $_, $usb->{stack}{$_}) } keys %{$usb->{stack}}); printf("endpoint : %s\n", unpack 'H*',$usb->{endpointdata}); printf(" %s\n", join ", ", map { sprintf("%s=0x%x", $_, $usb->{endpoint}{$_}) } keys %{$usb->{endpoint}}); printf("urb : %s\n", unpack 'H*',$usb->{urbdata}); printf(" %s\n", join ", ", map { sprintf("%s=0x%x", $_, $usb->{urb}{$_}) } keys %{$usb->{urb}}); } # descriptor types: ( list: see usb20.pdf 9.4 ) # 01 DEVICE 9.6.1 # 02 CONFIG # 03 STRING # 04 INTERFACE # 05 ENDPOINT # 06 DEVQUAL # 07 OTHERSPEED # 08 IFPOWER # standard dev requests # 00 get status # 01 clear feature # 02 # 03 set feature # 04 # 05 set address # 06 get descriptor # 07 set descriptor # 08 get config # 09 set config # 0a get interface # 0b set interface # 0c sync frame # request: # byte bRequestType # byte bRequest # word wValue # word wIndex # word wLength #printf("%08lx : %s urb %s\n", $usb->{hdr}{deviceobject}, $usb->{hdr}{dir}, ) if ($verbose && $usb->{urb}{setupdata}); # todo decode dev req: # 00 00 # 00 01 # 00 22 .. activesync wakeup # 00 30 ? # 02 00 ? .. rndis # 05 00 ? # 07 00 ? # 23 03 set_feature # a3 00 get_[port]status ? # a0 00 get_[hub]status # 23 01 clear_feature # 36 00 ? # 39 00 # 69 23 # 80 06 get descriptor ? # a0 06 get [hub]descriptor # CDC commands # 21 00 SEND_ENCAPSULATED_COMMAND # 21 04 CLEAR_COMM_FEATURE # 21 12 PULSE_SETUP # 21 13 SEND_PULSE # 21 15 RING_AUX_JACK # 21 02 SET_COMM_FEATURE # 21 10 SET_AUX_LINE_STATE # 21 11 SET_HOOK_STATE # 21 14 SET_PULSE_TIME # 21 20 SET_LINE_CODING # 21 22 SET_CONTROL_LINE_STATE # 21 30 SET_RINGER_PARMS # 21 32 SET_OPERATION_PARMS # 21 34 SET_LINE_PARMS # 21 37 SET_UNIT_PARAMETER # 21 40 SET_ETHERNET_MULTICAST_FILTERS # 21 41 SET_ETHERNET_POWER_MANAGEMENT_PATTERN FILTER # 21 43 SET_ETHERNET_PACKET_FILTER # 21 50 SET_ATM_DATA_FORMAT # 21 52 SET_ATM_DEFAULT_VC # 21 23 SEND_BREAK # 21 36 DIAL_DIGITS # 21 39 CLEAR_UNIT_PARAMETER # A1 01 GET_ENCAPSULATED_RESPONSE # A1 03 GET_COMM_FEA TURE # A1 21 GET_LINE_CODING # A1 31 GET_RINGER_P ARMS # A1 33 GET_OPERATION_PARMS # A1 35 GET_LINE_PARMS # A1 38 GET_UNIT_PARAMETER # A1 3A GET_PROFILE # A1 42 GET_ETHERNET_POWER_MANAGEMENT_PATTERN FILTER # A1 44 GET_ETHERNET_STATISTIC # A1 51 GET_ATM_DEVICE_STATISTICS # A1 53 GET_ATM_VC_STATISTICS # if (length($usb->{data})) { #printf("%08lx : %s dat %s\n", $usb->{hdr}{deviceobject}, $usb->{hdr}{dir}, unpack("H*", $usb->{data})) if ($verbose); if (exists $x{$usb->{hdr}{deviceobject}} && $x{$usb->{hdr}{deviceobject}}[-1]{dir}==$usb->{hdr}{dir}) { # extend previous packet $x{$usb->{hdr}{deviceobject}}[-1]{data} .= $usb->{data}; # printf("+"); } else { my $tstamp= $fileversion>=3 ? usecstamp(map { $usb->{hdr}{$_} } qw(sec usec)) : msecstamp(map { $usb->{hdr}{$_} } qw(hour minute second msec)); push @{$x{$usb->{hdr}{deviceobject}}}, { ep=>$usb->{endpoint}{y}, ofs=>$ofs, dev=>$usb->{hdr}{deviceobject}, timestamp=>$tstamp, dir=>$usb->{hdr}{dir}, data=>$usb->{data}, }; # printf("*"); } } else { #printf(": %s\n", unpack("H*", $frame)) if ($verbose); # printf("@"); } #printf("req %d @ %08lx l=%08lx lu=%08lx\n", $req++, $ofs, $size, length($usb->{data})); } $fh->close(); sub msecstamp { my ($h,$m,$s,$ms)= @_; return $ms+1000*($s+60*($m+60*$h)); } sub usecstamp { my ($s,$us)= @_; return $us+1000000*$s; } exit if !$pcapoutput && !$rawoutput; my $ext= $pcapoutput ? "cap" : $rawoutput ? "raw" : "txt"; for my $d (keys %x) { my $ofh= IO::File->new("$d.$ext", "w") or die "$d.$ext: $!\n"; binmode $ofh; if ($pcapoutput) { my $filehdr= make_file_header(); $ofh->print(pack_file_header($filehdr)); } for (my $i=0 ; $i<@{$x{$d}} ; $i++) { #printf("req %d @ %08lx l=%08lx t=%08lx %s -> %s\n", $i, $x{$d}[$i]{ofs}, length($x{$d}[$i]{data}), $x{$d}[$i]{timestamp}, $x{$d}[$i]{dir}?"IN":"OUT", $ofh); if ($txtoutput) { $ofh->printf("%s : %08lx bytes\n", $x{$d}[$i]{dir}?"IN":"OUT", length($x{$d}[$i]{data})); if (!$txtlimit || length($x{$d}[$i]{data}) < $txtlimit) { $ofh->printf("%s\n", $x{$d}[$i]{data}); } else { $ofh->printf("%s .. %s\n", substr($x{$d}[$i]{data}, 0, 0x80), substr($x{$d}[$i]{data}, -0x80, 0x80)); } } elsif ($rawoutput) { $ofh->print($x{$d}[$i]{data}); } elsif ($hexoutput) { $ofh->printf("%s : %08lx bytes\n", $x{$d}[$i]{dir}?"IN":"OUT", length($x{$d}[$i]{data})); if (!$txtlimit || length($x{$d}[$i]{data}) < $txtlimit) { $ofh->printf("%s\n", unpack "H*", $x{$d}[$i]{data}); } else { $ofh->printf("%s .. %s\n", unpack("H*",substr($x{$d}[$i]{data}, 0, 0x80)), unpack("H*",substr($x{$d}[$i]{data}, -0x80, 0x80))); } } elsif ($onepppframeperpkt) { my $data= $x{$d}[$i]{data}; while ($data =~ /(\x7e(.*?)\x7e)/gs) { my ($hdlc, $body)= ($1, $2); $body =~ s/\x7d(.)/$1^' '/egs if ($removepppframing); my ($pkt, $frame)= make_pkt($x{$d}[$i], $removepppframing?$body:$hdlc); $ofh->print(pack_pkt_header($pkt)); $ofh->print($frame); } } elsif ($rndis2cap) { next unless $x{$d}[$i]{ep}; my $data= $x{$d}[$i]{data}; my $ofs= 0; while ($ofs$oframe+$lframe+8) { printf("leftover: %s\n", unpack("H*", substr($data, $ofs+$oframe+8+$lframe, $lrndis-($oframe+$lframe+8)))); } my ($pkt, $frame)= make_pkt($x{$d}[$i], substr($data, $ofs+$oframe+8, $lframe)); $ofh->print(pack_pkt_header($pkt)); $ofh->print($frame); } $ofs += $lrndis; } } elsif ($pcapoutput) { my ($pkt, $data)= make_pkt($x{$d}[$i]); $ofh->print(pack_pkt_header($pkt)); $ofh->print($data); } else { die "don't know what to do\n"; } } $ofh->close(); } sub readdword { my $fh= shift; my $dat; $fh->read($dat, 4) or die "read dw: $!\n"; return unpack 'V', $dat; } ####### usbtrace log format decoder sub decodeframe { my $frame= shift; my $hdrlen= 0x1d-4+(($fileversion>=3)?4:0); die "short framehdr\n" if length($frame)<$hdrlen; my $hdrdata= substr($frame, 0, $hdrlen ); my $hdr= decodeframehdr($hdrdata); # v3: len=29 die "short irp\n" if length($frame)<$hdrlen+0x75; my $irpdata= substr($frame, $hdrlen, 0x75); my $irp= decodeirp($irpdata); die "short stack\n" if length($frame)<$hdrlen+0x75+0x24; my $stackdata= substr($frame, $hdrlen+0x75, 0x24); my $stack= decodestack($stackdata); die "short endpoint\n" if length($frame)<$hdrlen+0x75+0x24+2; my $eplen= ($fileversion<5)?2 : 7; my $endpointdata= substr($frame, $hdrlen+0x75+0x24, $eplen); my $endpoint= decodeendpoint($endpointdata); my $urblen= unpack 'v', substr($frame, $hdrlen+0x75+0x24+$eplen, 2); # printf("h:%d:%d:%d:%d[%02x:%02x]:%d[%04x]\n", $hdrlen, 0x75, 0x24, 2, values %$endpoint, 2, $urblen); die "short urb\n" if $urblen && length($frame)<$hdrlen+0x75+0x24+$eplen+2+$urblen-2; my $urbdata= $urblen ? substr($frame, $hdrlen+0x75+0x24+$eplen+2, $urblen-2) : ""; my $urb = decodeurb($urbdata) if ($urblen); die "short data\n" if length($frame)<$hdrlen+0x75+0x24+$eplen+$urblen; my $data= substr($frame, $hdrlen+0x75+0x24+$eplen+($urblen||2)); # if ($hdrdata.$irpdata.$stackdata.$endpointdata.pack("v",$urblen).$urbdata.$data ne $frame) { # printf("NOTE: reconcat != frame, hl=%x epl=%x ul=%x\n", $hdrlen, $eplen, $urblen); # printf("reconcat: %s\nframe : %s\n", # unpack("H*", $hdrdata.$irpdata.$stackdata.$endpointdata.pack("v",$urblen).$urbdata.$data), # unpack("H*", $frame)); # } return { hdrdata=>$hdrdata, hdr=>$hdr, irpdata=>$irpdata, irp=>$irp, stackdata=>$stackdata, stack=>$stack, endpointdata=>$endpointdata, endpoint=>$endpoint, urbdata=>$urbdata, urb=>$urb, data=>$data, }; } # size = 0x1d-4 sub decodeframehdr { my %hdr; my $rest; ( $hdr{dir}, # C $hdr{unk}, # V ($fileversion>=3)?($hdr{unk2}) : (), # V ($fileversion>=3)?( $hdr{sec}, # V $hdr{usec}, # V ) : ( $hdr{hour}, # v $hdr{minute}, # v $hdr{second}, # v $hdr{msec}, # v ), $hdr{deviceobject}, # V $hdr{urb}, # V $hdr{irp}, # V $rest, )= unpack($fileversion>=3?"CV7a*":"CVv4V3a*", $_[0]); die sprintf("hdr rest=%s\n", unpack("H*", $rest)) if (length($rest)); return \%hdr; } sub decodeirp { my %irp; my $rest; ( $irp{type}, # v $irp{size}, # v $irp{unk0}, # V $irp{unk1}, # V $irp{unk2}, # V $irp{flink}, # V $irp{blink}, # V $irp{status}, # V $irp{unk3}, # V $irp{requestormode}, # C $irp{pendingreturned}, # C $irp{stackcount}, # C $irp{currentlocation}, # C $irp{cancel}, # C $irp{cancelirql}, # C $irp{apcenvironment}, # C $irp{allocationflags}, # C $irp{unk4}, # a77 $rest, )= unpack("vvV7C8a77a*", $_[0]); die sprintf("irp too short: %d\n") if (length($_[0])<40); die sprintf("irp unk4=%s\n", unpack("H*", $irp{unk4})) if (length($irp{unk4})<77); die sprintf("irp rest=%s\n", unpack("H*", $rest)) if (length($rest)); return \%irp; } sub decodestack { my %p; my $rest; ( $p{location}, $p{unk0}, $p{unk1}, $p{unk2}, $p{unk3}, $p{deviceobject}, $p{fileobject}, $p{iocompletionroutine}, $p{context}, $rest, )= unpack("V9a*", $_[0]); die sprintf("stack rest=%s\n", unpack("H*", $rest)) if (length($rest)); return \%p; } sub decodeendpoint { my %ep; my $rest; ( $ep{x}, $ep{y}, # the endpoint nr ($fileversion>=5)?($ep{unk2}) : (), $rest, )= unpack(($fileversion>=5)?"C2a5a*":"C2a*", $_[0]); die sprintf("ep rest=%s\n", unpack("H*", $rest)) if (length($rest)); return \%ep; } sub decodeurb { my %urb; my $rest; my $hcadata; ( $urb{function}, $urb{status}, $urb{devicehandle}, $urb{flags}, $urb{pipehandle}, $urb{transferflags}, $urb{transferbufferlength}, $urb{transferbuffer}, $urb{transferbuffermdl}, $urb{link}, $hcadata, $urb{setupdata}, )= unpack("vV9a32a*", $_[0]); #die sprintf("urb rest=%s\n", unpack("H*", $rest)) if (length($rest)); $urb{hca}= decodehca($hcadata) if (defined $hcadata); return \%urb; } sub decodehca { my %hca; my $rest; ( $hca{hcdendpoint}, $hca{hcdirp}, $hca{listent_flink}, $hca{listent_blink}, $hca{listent2_flink}, $hca{listent2_blink}, $hca{ioflushptr}, $hca{extension}, $rest, )= unpack("V8a*", $_[0]); die sprintf("hca rest=%s\n", unpack("H*", $rest)) if (defined $rest && length($rest)); return \%hca; } ######## pcap fileformat sub make_pkt{ my ($upkt, $data)= @_; #my $prefix= ($upkt->{dir}) ? "\x00\x03" : "\x01\x03"; $data= $upkt->{data} if (!defined $data); return ( { ts=>pack("VV", $upkt->{timestamp}>>22, $upkt->{timestamp}<<10), len=>length($data), caplen=>length($data), }, $data ); } sub make_file_header { my ($data)= @_; return { magic=>0xa1b2c3d4, version_major=>2, version_minor=>4, thiszone=>0, sigfigs=>0, snaplen=>0x1000, linktype=>$linktype, }; } sub pack_file_header { my ($hdr)= @_; return pack("VvvVVVV", $hdr->{magic}, $hdr->{version_major}, $hdr->{version_minor}, $hdr->{thiszone}, $hdr->{sigfigs}, $hdr->{snaplen}, $hdr->{linktype}, ); } sub pack_pkt_header { my ($hdr)= @_; return pack("a8VV", $hdr->{ts}, $hdr->{caplen}, $hdr->{len}, ); }