├── .gitignore ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── artwork ├── logo.ai ├── logo.png ├── logo.svg ├── wordmark.ai ├── wordmark.png └── wordmark.svg └── src ├── ethernet ├── mod.rs └── testproto.rs ├── ip ├── mod.rs └── udp.rs ├── lib.rs └── main.rs /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "rshark" 3 | version = "0.0.1" 4 | license = "Apache-2.0 OR MIT" 5 | authors = ["Jonathan Anderson "] 6 | description = "Rusty Shark is a tool for deep inspection of malicious packets." 7 | documentation = "http://musec.github.io/rusty-shark/rshark" 8 | repository = "https://github.com/musec/rusty-shark" 9 | readme = "README.md" 10 | 11 | [dependencies] 12 | byteorder = "0.5.2" 13 | docopt = "0.6.80" 14 | num = "0.1.32" 15 | pcap = "0.5.7" 16 | promising-future = "0.2.2" 17 | rustc-serialize="0.3.19" 18 | 19 | [[bin]] 20 | doc = false 21 | name = "rshark" 22 | -------------------------------------------------------------------------------- /LICENSE-APACHE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015 Jonathan Anderson 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![Rusty Shark logo](artwork/logo.png) 2 | 3 | `rshark`, the Rusty Shark library, is a library for deep inspection 4 | of malicious packets. 5 | 6 | [Wireshark](https://www.wireshark.org) is a very useful tool for network 7 | debugging, but it's had its 8 | [fair share of security vulnerabilities](https://www.wireshark.org/security). 9 | `rshark` uses the type safety of Rust to enable the dissection of 10 | malicious packets without worry of buffer overflows or other common memory errors. 11 | That is, Rusty Shark is compartmentalized to minimize the damage that 12 | can be done by a successful adversary. The submarine metaphors write themselves. 13 | 14 | Further details are available 15 | [in the Rustdoc](http://musec.github.io/rusty-shark/rshark/). 16 | 17 | ## License 18 | 19 | Licensed under either of 20 | 21 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 22 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 23 | 24 | at your option. 25 | 26 | ### Contribution 27 | 28 | Unless you explicitly state otherwise, any contribution intentionally submitted 29 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 30 | additional terms or conditions. 31 | -------------------------------------------------------------------------------- /artwork/logo.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musec/rusty-shark/175ed57799de80ad1da90e29a6f6871b216491db/artwork/logo.ai -------------------------------------------------------------------------------- /artwork/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musec/rusty-shark/175ed57799de80ad1da90e29a6f6871b216491db/artwork/logo.png -------------------------------------------------------------------------------- /artwork/logo.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | 11 | 12 | 14 | 15 | 17 | 18 | 22 | 23 | 27 | 28 | 31 | 32 | 36 | 37 | 38 | 39 | 40 | 42 | 43 | 44 | 45 | 46 | 49 | 50 | 53 | 54 | 57 | 58 | 61 | 62 | 63 | 64 | 65 | Rusty 66 | shark 67 | 68 | 69 | -------------------------------------------------------------------------------- /artwork/wordmark.ai: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musec/rusty-shark/175ed57799de80ad1da90e29a6f6871b216491db/artwork/wordmark.ai -------------------------------------------------------------------------------- /artwork/wordmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/musec/rusty-shark/175ed57799de80ad1da90e29a6f6871b216491db/artwork/wordmark.png -------------------------------------------------------------------------------- /artwork/wordmark.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 7 | 8 | Rusty 9 | shark 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/ethernet/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Jonathan Anderson 3 | * 4 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 7 | * copied, modified, or distributed except according to those terms. 8 | */ 9 | 10 | //! Dissection of Ethernet (IEEE 802.3) frames. 11 | 12 | use { 13 | Error, 14 | NamedValue, 15 | Protocol, 16 | RawBytes, 17 | Result, 18 | Val, 19 | ip, 20 | unsigned, 21 | }; 22 | 23 | use byteorder::*; 24 | 25 | 26 | /// The IEEE 802.3 Ethernet protocol. 27 | pub struct Ethernet; 28 | 29 | 30 | /// Parse a six-byte MAC address, which can be encoded as, e.g., ff:ff:ff:ff:ff:ff. 31 | pub fn mac_address(raw: &[u8]) -> Result { 32 | if raw.len() != 6 { 33 | return Error::inval(format!["MAC address should have 6 B, not {}", raw.len()]); 34 | } 35 | 36 | let encoded = raw 37 | .iter() 38 | .map(|b| format!["{:02x}", b]) 39 | .collect::>() 40 | .join(":") 41 | ; 42 | 43 | 44 | Ok(Val::Address { bytes: raw.to_vec(), encoded: encoded }) 45 | } 46 | 47 | 48 | impl Protocol for Ethernet { 49 | fn short_name(&self) -> &'static str { "Ethernet" } 50 | fn full_name(&self) -> &'static str { "IEEE 802.3 Ethernet" } 51 | fn dissect(&self, data: &[u8]) -> Result { 52 | if data.len() < 14 { 53 | return Error::underflow(14, data.len(), "Ethernet frame") 54 | } 55 | 56 | let mut values:Vec = vec![]; 57 | values.push(("Destination", mac_address(&data[0..6]))); 58 | values.push(("Source", mac_address(&data[6..12]))); 59 | 60 | // The type/length field might be either a type or a length. 61 | let tlen = unsigned::(&data[12..14]); 62 | let remainder = &data[14..]; 63 | 64 | match tlen { 65 | Ok(i) if i <= 1500 => { 66 | values.push(("Length", Val::base10(i))); 67 | 68 | let index = i as usize; 69 | let packet_data = remainder[..index].to_vec(); 70 | let padding = remainder[index..].to_vec(); 71 | values.push(("Data", Ok(Val::Bytes(packet_data)))); 72 | values.push(("Padding", Ok(Val::Bytes(padding)))); 73 | }, 74 | 75 | Ok(i) => { 76 | let protocol: Box = match i { 77 | // TODO: use the simple 'box' syntax once it hits stable 78 | 0x800 => Box::new(ip::IPv4), 79 | 0x806 => RawBytes::boxed("ARP", "Address Resolution Protocol"), 80 | 0x8138 => RawBytes::boxed("IPX", "Internetwork Packet Exchange"), 81 | 0x86dd => RawBytes::boxed("IPv6", "Internet Protocol version 6"), 82 | 83 | 0x9000 => Box::new(testproto::TestProtocol), 84 | 85 | _ => RawBytes::unknown_protocol("Unknown Ethertype"), 86 | }; 87 | 88 | values.push(("Type", Ok(Val::Enum(i as u64, protocol.short_name())))); 89 | values.push((protocol.full_name(), protocol.dissect(remainder))); 90 | }, 91 | Err(e) => { 92 | values.push(("Type/length", Err(e))); 93 | }, 94 | }; 95 | 96 | Ok(Val::Subpacket(values)) 97 | } 98 | } 99 | 100 | mod testproto; 101 | -------------------------------------------------------------------------------- /src/ethernet/testproto.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015-2016 Jonathan Anderson 3 | * 4 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 7 | * copied, modified, or distributed except according to those terms. 8 | */ 9 | 10 | //! Dissection of the test protocol from the Xerox Blue Book (but not IEEE 802.3). 11 | 12 | use { 13 | Error, 14 | NamedValue, 15 | Protocol, 16 | Result, 17 | Val, 18 | ethernet, 19 | unsigned, 20 | }; 21 | 22 | use byteorder::*; 23 | 24 | 25 | /// Testing protocol from the Xerox Blue Book. 26 | /// 27 | /// A test packet can contain data to forward to another Ethernet address and/or 28 | /// a reply to a previous test packet. See http://www.mit.edu/~jhawk/ctp.pdf for details. 29 | pub struct TestProtocol; 30 | 31 | enum TestMessage<'a> { 32 | Reply { receipt_number: Result, data: &'a [u8] }, 33 | ForwardData { dest: Result, message: Result>> }, 34 | } 35 | 36 | impl Protocol for TestProtocol { 37 | fn short_name(&self) -> &'static str { "Loopback" } 38 | fn full_name(&self) -> &'static str { "Ethernet Configuration Testing Protocol" } 39 | fn dissect(&self, data: &[u8]) -> Result { 40 | let mut values:Vec = vec![]; 41 | 42 | let skip_count = unsigned::(&data[0..2]); 43 | values.push(("Skip count", skip_count.and_then(Val::base10))); 44 | 45 | let top_message = 46 | TestMessage::parse(&data[2..]) 47 | .map(TestMessage::as_val) 48 | .unwrap_or_else(|e| ("Error", Err(e))) 49 | ; 50 | 51 | values.push(top_message); 52 | 53 | Ok(Val::Subpacket(values)) 54 | } 55 | } 56 | 57 | impl <'a> TestMessage <'a> { 58 | fn name(&self) -> &'static str { 59 | match self { 60 | &TestMessage::Reply{ .. } => "Reply Message", 61 | &TestMessage::ForwardData{ .. } => "Forward Data Message", 62 | } 63 | } 64 | 65 | fn as_val(self) -> NamedValue { 66 | let name = self.name(); 67 | let val = match self { 68 | TestMessage::Reply{ receipt_number, data } => 69 | vec![ 70 | ("Function Code", Ok(Val::Enum(1, "Reply Message"))), 71 | ("Receipt Number", receipt_number.and_then(Val::base10)), 72 | ("Data", Ok(Val::Bytes(data.to_vec()))), 73 | ], 74 | 75 | TestMessage::ForwardData{ dest, message } => { 76 | let data = match message.map(|b| (*b).as_val()) { 77 | Ok((k,v)) => Ok(Val::Subpacket(vec![(k,v)])), 78 | Err(e) => Err(e), 79 | }; 80 | 81 | vec![ 82 | ("Function Code", Ok(Val::Enum(2, "Forward Data Message"))), 83 | ("Forward Address", dest), 84 | ("Data", data), 85 | ] 86 | }, 87 | }; 88 | 89 | (name, Ok(Val::Subpacket(val))) 90 | } 91 | 92 | fn parse(data: &[u8]) -> Result { 93 | let function_code = unsigned::(&data[0..2]); 94 | match function_code { 95 | Ok(1) => Ok({ 96 | let receipt_number = unsigned::(&data[2..4]); 97 | 98 | TestMessage::Reply{ 99 | receipt_number: receipt_number, 100 | data: &data[4..], 101 | } 102 | }), 103 | 104 | Ok(2) => Ok({ 105 | TestMessage::ForwardData{ 106 | dest: ethernet::mac_address(&data[2..8]), 107 | message: TestMessage::parse(&data[8..]).map(Box::new), 108 | } 109 | }), 110 | 111 | Ok(x) => Error::inval(format!["invalid function code: {}", x]), 112 | Err(e) => Err(e), 113 | } 114 | } 115 | } 116 | -------------------------------------------------------------------------------- /src/ip/mod.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jonathan Anderson 3 | * 4 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 7 | * copied, modified, or distributed except according to those terms. 8 | */ 9 | 10 | //! Dissection of Internet Protocol (IP) packets. 11 | //! 12 | //! This module will eventually contain dissectors for protocols in the IP suite, 13 | //! e.g., `rshark::ip::icmp` and `rshark::ip::tcp`. 14 | //! For now, it only handles IP headers. 15 | //! 16 | //! See [RFC 791](https://tools.ietf.org/html/rfc791). 17 | 18 | use { 19 | Error, 20 | NamedValue, 21 | Protocol, 22 | RawBytes, 23 | Result, 24 | Val, 25 | unsigned, 26 | }; 27 | 28 | use byteorder::*; 29 | 30 | 31 | pub struct IPv4; 32 | 33 | impl Protocol for IPv4 { 34 | fn short_name(&self) -> &'static str { "IP" } 35 | fn full_name(&self) -> &'static str { "Internet Protocol version 4" } 36 | 37 | fn dissect(&self, data : &[u8]) -> Result { 38 | if data.len() < 20 { 39 | return Error::underflow(20, data.len(), "IP packet") 40 | } 41 | 42 | let mut values:Vec = vec![]; 43 | 44 | // IP version (should be "4") 45 | let version = data[0] >> 4; 46 | values.push(("Version", Val::base10(version))); 47 | 48 | // Internet Header Length (IHL): number of 32b words in header 49 | let words = data[0] & 0x0f; 50 | values.push(("IHL", Val::base10(words))); 51 | 52 | // Differentiated Services Code Point (DSCP): RFC 2474 53 | let dscp = data[1] >> 2; 54 | values.push(("DSCP", Val::base16(dscp))); 55 | 56 | // Explicit Congestion Notification (ECN): RFC 3168 57 | let ecn = data[1] & 0x03; 58 | values.push(("ECN", Val::base2(ecn))); 59 | 60 | // Total length (including header) 61 | let length = unsigned::(&data[2..4]); 62 | values.push(("Length", length.and_then(Val::base10))); 63 | 64 | // Identification (of datagraph fragments): RFC 6864 65 | values.push(("Identification", Val::base10(data[8]))); 66 | 67 | // Protocol number (assigned by IANA) 68 | let proto_id = data[9] as u64; 69 | let protocol:Box = match proto_id { 70 | // TODO: TCP, etc. 71 | 17 => Box::new(udp::UDP), 72 | _ => RawBytes::unknown_protocol("Unknown IP protocol"), 73 | }; 74 | values.push(("Protocol", Ok(Val::Enum(proto_id, protocol.short_name())))); 75 | 76 | // Header checksum 77 | values.push(("Checksum", Ok(Val::Bytes(data[10..12].to_vec())))); 78 | 79 | // Source and destination addresses 80 | let source = &data[12..16]; 81 | values.push(("Source", Ok(Val::Address { 82 | bytes: source.to_vec(), 83 | encoded: source.iter().map(|b| b.to_string()).collect::>().join("."), 84 | }))); 85 | 86 | let dest = &data[16..20]; 87 | values.push(("Destination", Ok(Val::Address { 88 | bytes: dest.to_vec(), 89 | encoded: dest.iter().map(|b| b.to_string()).collect::>().join("."), 90 | }))); 91 | 92 | // Parse the remainder according to the specified protocol. 93 | let remainder = &data[20..]; 94 | values.push(("Protocol Data", protocol.dissect(remainder))); 95 | 96 | Ok(Val::Subpacket(values)) 97 | } 98 | } 99 | 100 | pub mod udp; 101 | -------------------------------------------------------------------------------- /src/ip/udp.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2016 Jonathan Anderson 3 | * 4 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 7 | * copied, modified, or distributed except according to those terms. 8 | */ 9 | 10 | //! Dissection of User Datagram Protocol (UDP) packets. 11 | //! 12 | //! See [RFC 768](https://tools.ietf.org/html/rfc768). 13 | 14 | use { 15 | Protocol, 16 | RawBytes, 17 | Result, 18 | Val, 19 | unsigned, 20 | }; 21 | 22 | use byteorder::NetworkEndian; 23 | 24 | 25 | /// Parser for the User Datagram Protocol (UDP). 26 | pub struct UDP; 27 | 28 | impl Protocol for UDP { 29 | fn short_name(&self) -> &'static str { "UDP" } 30 | fn full_name(&self) -> &'static str { "User Datagram Protocol" } 31 | 32 | fn dissect(&self, data : &[u8]) -> Result { 33 | let source = unsigned::(&data[0..2]); 34 | let dest = unsigned::(&data[2..4]); 35 | let length = unsigned::(&data[4..6]); 36 | let checksum = unsigned::(&data[6..8]); 37 | 38 | let protocol:Box = match (&source,&dest) { 39 | (&Ok(s), &Ok(d)) => protocol(s, d), 40 | _ => RawBytes::unknown_protocol("UDP error"), 41 | }; 42 | 43 | let values = vec![ 44 | ("Source port", source.and_then(Val::base10)), 45 | ("Destination port", dest.and_then(Val::base10)), 46 | ("Length", length.and_then(Val::base10)), 47 | ("Checksum", checksum.and_then(Val::base16)), 48 | ("Data", protocol.dissect(&data[8..])), 49 | ] 50 | ; 51 | 52 | Ok(Val::Subpacket(values)) 53 | } 54 | } 55 | 56 | 57 | /// Find the UDP protocol that uses given source and destination ports. 58 | pub fn protocol(source_port: u16, dest_port: u16) -> Box { 59 | match (source_port, dest_port) { 60 | (_, _) => RawBytes::boxed("UNKNOWN", "unknown UDP protocol"), 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/lib.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jonathan Anderson 3 | * 4 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 7 | * copied, modified, or distributed except according to those terms. 8 | */ 9 | 10 | //! 11 | //! `rshark`, the Rusty Shark library, is a library for deep inspection 12 | //! of malicious packets. 13 | //! 14 | //! # Background 15 | //! 16 | //! [Wireshark](https://www.wireshark.org) is a very useful tool for network 17 | //! debugging, but it's had its 18 | //! [fair share of security vulnerabilities](https://www.wireshark.org/security). 19 | //! It's generally accepted that, to succeed at Capture the Flag, one should fuzz 20 | //! Wireshark for awhile before the competition to find a few new vulnerabilities 21 | //! (don't worry, they're there, you'll find some) and use those offensively to 22 | //! blind one's opponents. 23 | //! This speaks to both the indispensability of packet capture/dissection tools 24 | //! and the fundamental difficulty of ``just making Wireshark secure''. 25 | //! Wireshark has a *lot* of dissectors, which are written using a 26 | //! [complex C API](https://www.wireshark.org/docs/wsdg_html_chunked/ChDissectAdd.html) 27 | //! (although some are now written in Lua). 28 | //! 29 | //! `rshark` uses the type safety of Rust to enable the dissection of 30 | //! malicious packets without worry of buffer overflows or other common memory errors. 31 | //! Rusty Shark dissectors can make mistakes, but those logical errors should only 32 | //! affect the interpretation of the *current* data, rather than *all* data. 33 | //! That is to say, Rusty Shark is compartmentalized to minimize the damage that 34 | //! can be done by a successful adversary. The submarine metaphors write themselves. 35 | //! 36 | //! # Usage 37 | //! 38 | //! *note: for help on the `rshark` command-line client, 39 | //! run `man rshark` or `rshark --help`.* 40 | //! 41 | //! The `rshark` library provides packet dissection functions such as 42 | //! `rshark::ethernet::dissect()`. Every such dissection function, which should 43 | //! conform to the `rshark::Dissector` function type, takes as input a slice of bytes 44 | //! and returns an `rshark::Result` (which defaults to 45 | //! `Result`). 46 | //! Usage is pretty simple: 47 | //! 48 | //! ``` 49 | //! let data = vec![]; 50 | //! 51 | //! match rshark::ethernet::dissect(&data) { 52 | //! Err(e) => println!["Error: {}", e], 53 | //! Ok(val) => print!["{}", val.pretty_print(0)], 54 | //! } 55 | //! ``` 56 | //! 57 | //! A `Val` can represent an arbitrary tree of structured data 58 | //! (useful in graphical displays) and can be pretty-printed with indentation for 59 | //! sub-objects. 60 | 61 | #![doc(html_logo_url = "https://raw.githubusercontent.com/musec/rusty-shark/master/artwork/wordmark.png")] 62 | 63 | extern crate byteorder; 64 | extern crate num; 65 | extern crate promising_future; 66 | 67 | use byteorder::ByteOrder; 68 | pub use promising_future::Future; 69 | use std::fmt; 70 | 71 | 72 | /// A description of a protocol, including code that can parse it. 73 | pub trait Protocol { 74 | /// A short name that can fit in a user display, e.g., "IPv6". 75 | fn short_name(&self) -> &'static str; 76 | 77 | /// A complete, unambigous protocol name, e.g., "Internet Protocol version 6" 78 | fn full_name(&self) -> &'static str; 79 | 80 | /// A function to dissect some bytes according to the protocol. 81 | fn dissect(&self, &[u8]) -> Result; 82 | } 83 | 84 | 85 | /// A value parsed from a packet. 86 | /// 87 | /// # TODO 88 | /// This value type isn't as expressive as would be required for a real 89 | /// Wireshark replacement just yet. Additional needs include: 90 | /// 91 | /// * tracking original bytes (by reference or by index?) 92 | /// 93 | #[derive(Debug)] 94 | pub enum Val { 95 | /// A signed integer, in machine-native representation. 96 | Signed(i64), 97 | 98 | /// An unsigned integer, in machine-native representation, and a radix (base) for 99 | /// when we display the value to the user (e.g., 0x100 vs 256). 100 | Unsigned { value: u64, radix: u8 }, 101 | 102 | /// An integer value that represents a symbolic value. 103 | Enum(u64, &'static str), 104 | 105 | /// A UTF-8–encoded string. 106 | String(String), 107 | 108 | /// A network address, which can have its own special encoding. 109 | Address { bytes: Vec, encoded: String }, 110 | 111 | /// A set of named values from an encapsulated packet (e.g., TCP within IP). 112 | Subpacket(Vec), 113 | 114 | /// Raw bytes, e.g., a checksum or just unparsed data. 115 | Bytes(Vec), 116 | 117 | /// An error that stopped further parsing. 118 | Error(Error), 119 | 120 | /// A problem with a packet that did not stop further parsing (e.g., bad checksum). 121 | Warning(Error), 122 | } 123 | 124 | impl Val { 125 | pub fn base2(x: T) -> Result 126 | where T: num::ToPrimitive + std::fmt::Display 127 | { 128 | Val::unsigned(x, 2) 129 | } 130 | 131 | pub fn base10(x: T) -> Result 132 | where T: num::ToPrimitive + std::fmt::Display 133 | { 134 | Val::unsigned(x, 10) 135 | } 136 | 137 | pub fn base16(x: T) -> Result 138 | where T: num::ToPrimitive + std::fmt::Display 139 | { 140 | Val::unsigned(x, 16) 141 | } 142 | 143 | pub fn unsigned(x: T, radix:u8) -> Result 144 | where T: num::ToPrimitive + std::fmt::Display 145 | { 146 | x.to_u64() 147 | .map(|value| Val::Unsigned { value: value, radix: radix }) 148 | .ok_or(Error::InvalidData(format!["Cannot convert {} to u64", x])) 149 | } 150 | 151 | pub fn str(s: Str) -> Val 152 | where Str: Into 153 | { 154 | Val::String(s.into()) 155 | } 156 | 157 | pub fn pretty_print(self, indent_level:usize) -> String { 158 | match self { 159 | Val::Subpacket(values) => { 160 | let indent:String = std::iter::repeat(" ").take(2 * indent_level).collect(); 161 | 162 | "\n".to_string() + &values.into_iter() 163 | .map(|(k,v)| { 164 | format!["{}{}: ", indent, k] 165 | + &match v { 166 | Ok(val) => val.pretty_print(indent_level + 1), 167 | Err(e) => format!["<< Error: {} >>", e], 168 | } 169 | }) 170 | .collect::>() 171 | .join("\n") 172 | } 173 | 174 | Val::Signed(i) => format!["{}", i], 175 | Val::Unsigned { value, radix } => match radix { 176 | 2 => format!["{:b}", value], 177 | 8 => format!["{:o}", value], 178 | 10 => format!["{}", value], 179 | 16 => format!["{:x}", value], 180 | _ => format!["{:?} (base {})", value, radix], 181 | }, 182 | Val::Enum(i, s) => format!["{} ({})", i, s], 183 | Val::String(ref s) => format!["{}", s], 184 | Val::Address { ref encoded, .. } => format!["{}", encoded], 185 | Val::Bytes(ref bytes) => { 186 | let mut s = format!["{} B [", bytes.len()]; 187 | 188 | let to_print:&[u8] = 189 | if bytes.len() < 16 { bytes } 190 | else { &bytes[..16] } 191 | ; 192 | 193 | for b in to_print { 194 | s = s + &format![" {:02x}", b]; 195 | } 196 | 197 | if bytes.len() > 16 { 198 | s = s + " ..."; 199 | } 200 | 201 | s + " ]" 202 | }, 203 | Val::Warning(w) => format!["Warning: {}", w], 204 | Val::Error(e) => format!["Error: {}", e], 205 | } 206 | } 207 | } 208 | 209 | 210 | /// An error related to packet dissection (underflow, bad value, etc.). 211 | #[derive(Clone, Debug)] 212 | pub enum Error { 213 | Underflow { expected: usize, have: usize, subject: String, }, 214 | InvalidData(String), 215 | } 216 | 217 | impl Error 218 | { 219 | fn underflow(expected: usize, have: usize, subject: S) -> Result 220 | where S: Into 221 | { 222 | Err(Error::Underflow { expected: expected, have: have, subject: subject.into() }) 223 | } 224 | 225 | fn inval(message: S) -> Result 226 | where S: Into 227 | { 228 | Err(Error::InvalidData(message.into())) 229 | } 230 | } 231 | 232 | impl fmt::Display for Error { 233 | fn fmt(&self, f : &mut fmt::Formatter) -> fmt::Result { 234 | match self { 235 | &Error::Underflow { expected: expect, have, ref subject } => 236 | write![f, "underflow: {} expected {} B, have {}", subject, expect, have], 237 | 238 | &Error::InvalidData(ref msg) => write![f, "invalid data: {}", msg], 239 | } 240 | } 241 | } 242 | 243 | /// The result of a dissection function. 244 | pub type Result = ::std::result::Result; 245 | 246 | 247 | /// A named value-or-error. 248 | pub type NamedValue = (&'static str,Result); 249 | 250 | 251 | /// Parse a signed integer of a given endianness from a byte buffer. 252 | /// 253 | /// The size of the buffer will be used to determine the size of the integer 254 | /// that should be parsed (i8, i16, i32 or i64). 255 | pub fn signed(buffer: &[u8]) -> Result 256 | where T: num::FromPrimitive, E: ByteOrder 257 | { 258 | let len = buffer.len(); 259 | let conversion_error = "Failed to convert integer"; 260 | 261 | match len { 262 | 1 => T::from_u8(buffer[0]).ok_or(conversion_error), 263 | 2 => T::from_i16(E::read_i16(buffer)).ok_or(conversion_error), 264 | 4 => T::from_i32(E::read_i32(buffer)).ok_or(conversion_error), 265 | 8 => T::from_i64(E::read_i64(buffer)).ok_or(conversion_error), 266 | _ => Err("Invalid integer size"), 267 | } 268 | .map_err(|s| format!["{} ({} B)", s, len]) 269 | .map_err(Error::InvalidData) 270 | } 271 | 272 | /// Parse an unsigned integer of a given endianness from a byte buffer. 273 | /// 274 | /// The size of the buffer will be used to determine the size of the integer 275 | /// that should be parsed (u8, u16, u32 or u64). 276 | pub fn unsigned(buffer: &[u8]) -> Result 277 | where T: num::FromPrimitive, E: ByteOrder 278 | { 279 | let len = buffer.len(); 280 | let conversion_error = "Failed to convert {} B integer"; 281 | 282 | match len { 283 | 1 => T::from_u8(buffer[0]).ok_or(conversion_error), 284 | 2 => T::from_u16(E::read_u16(buffer)).ok_or(conversion_error), 285 | 4 => T::from_u32(E::read_u32(buffer)).ok_or(conversion_error), 286 | 8 => T::from_u64(E::read_u64(buffer)).ok_or(conversion_error), 287 | _ => Err("Invalid integer size: {}"), 288 | } 289 | .map_err(|s| format!["{} ({} B)", s, len]) 290 | .map_err(Error::InvalidData) 291 | } 292 | 293 | 294 | /// Dissector of last resort: store raw bytes without interpretation. 295 | pub struct RawBytes { 296 | short_name: &'static str, 297 | full_name: &'static str, 298 | } 299 | 300 | impl RawBytes { 301 | /// Convenience function to wrap `String::from` and `Box::new`. 302 | fn boxed(short_name: &'static str, full_name: &'static str) -> Box 303 | { 304 | Box::new(RawBytes { 305 | short_name: short_name, 306 | full_name: full_name, 307 | }) 308 | } 309 | 310 | fn unknown_protocol(description: &'static str) -> Box { 311 | Box::new(RawBytes { 312 | short_name: "UNKNOWN", 313 | full_name: description, 314 | }) 315 | } 316 | } 317 | 318 | impl Protocol for RawBytes { 319 | fn short_name(&self) -> &'static str { self.short_name } 320 | fn full_name(&self) -> &'static str { self.full_name } 321 | 322 | fn dissect(&self, data: &[u8]) -> Result { 323 | Ok(Val::Subpacket( 324 | vec![("raw data", Ok(Val::Bytes(data.to_vec())))] 325 | )) 326 | } 327 | } 328 | 329 | 330 | pub mod ethernet; 331 | pub mod ip; 332 | -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2015 Jonathan Anderson 3 | * 4 | * Licensed under the Apache License, Version 2.0, or the MIT license , at your option. This file may not be 7 | * copied, modified, or distributed except according to those terms. 8 | */ 9 | 10 | extern crate docopt; 11 | extern crate rshark; 12 | extern crate rustc_serialize; 13 | extern crate pcap; 14 | 15 | use docopt::Docopt; 16 | use rshark::Protocol; 17 | 18 | 19 | // TODO: use docopt_macros once rust-lang/rust#28089 is resolved 20 | const USAGE: &'static str = " 21 | Usage: rshark [options] 22 | rshark (--help | --version) 23 | 24 | Options: 25 | -f, --filter BFP filter (see http://biot.com/capstats/bpf.html) 26 | -h, --help Show this message 27 | -p, --promiscuous Listen to all packets 28 | -s, --snaplen= Bytes to capture from each packet [default: 5000] 29 | -t, --timeout= Packet read timeout, in ms [default: 10] 30 | -v, --version Show the version of rshark 31 | "; 32 | 33 | const VERSION: Option<&'static str> = option_env!("CARGO_PKG_VERSION"); 34 | 35 | 36 | #[derive(RustcDecodable)] 37 | struct Args { 38 | arg_source: String, 39 | flag_filter: String, 40 | flag_snaplen: i32, 41 | flag_timeout: i32, 42 | flag_promiscuous: bool, 43 | flag_version: bool, 44 | } 45 | 46 | type PcapResult = Result, pcap::Error>; 47 | 48 | 49 | fn main() { 50 | let args: Args = Docopt::new(USAGE) 51 | .and_then(|d| d.argv(std::env::args()).decode()) 52 | .unwrap_or_else(|e| e.exit()) 53 | ; 54 | 55 | println!["rshark v{}", VERSION.unwrap_or("")]; 56 | 57 | if args.flag_version { 58 | return; 59 | } 60 | 61 | let protocol = rshark::ethernet::Ethernet; 62 | 63 | let result = open_capture(&args) 64 | .map(|mut c| { 65 | let mut count = 0; 66 | 67 | while let Ok(packet) = c.next() { 68 | println!("\n\n{}-B packet:", packet.data.len()); 69 | 70 | match protocol.dissect(packet.data) { 71 | Ok(dissected) => print!["{}", dissected.pretty_print(1)], 72 | Err(e) => println!["Error: {}", e], 73 | } 74 | 75 | count += 1; 76 | } 77 | 78 | count 79 | }) 80 | ; 81 | 82 | 83 | match result { 84 | Ok(packet_count) => println!["Processed {} packets", packet_count], 85 | Err(e) => { 86 | println!["{}", e]; 87 | std::process::exit(1); 88 | }, 89 | } 90 | } 91 | 92 | 93 | fn open_capture(args: &Args) -> PcapResult { 94 | let device = try![open_device(args)]; 95 | 96 | let capture = match device { 97 | Some(d) => Ok(d), 98 | None => open_file(&args.arg_source), 99 | }; 100 | 101 | capture.and_then(|mut c| { 102 | try![c.filter(&args.flag_filter)]; 103 | println!["Opened packet source: {}", args.arg_source]; 104 | Ok(c) 105 | }) 106 | } 107 | 108 | fn open_device(args: &Args) 109 | -> Result>, pcap::Error> { 110 | 111 | match pcap::Device::list() { 112 | Ok(devices) => { 113 | for d in devices { 114 | if d.name == args.arg_source { 115 | return pcap::Capture::from_device(d) 116 | .map(|d| d.promisc(args.flag_promiscuous) 117 | .rfmon(args.flag_promiscuous) 118 | .snaplen(args.flag_snaplen) 119 | .timeout(args.flag_timeout)) 120 | .and_then(|d| d.open()) 121 | .map(|c| c.into()) 122 | .map(Some) 123 | ; 124 | } 125 | }; 126 | 127 | Ok(None) 128 | }, 129 | Err(e) => Err(e), 130 | } 131 | } 132 | 133 | fn open_file(filename: &str) -> PcapResult { 134 | std::fs::metadata(filename) 135 | .map_err(|e| pcap::Error::PcapError(format!["{}", e])) 136 | .and_then(|f| 137 | if f.is_file() { 138 | pcap::Capture::from_file(filename) 139 | .map(|c| c.into()) 140 | } else { 141 | Err(pcap::Error::PcapError( 142 | format!["{} is not a file or interface", filename])) 143 | } 144 | ) 145 | } 146 | --------------------------------------------------------------------------------