├── debian ├── compat ├── dirs ├── rules ├── control ├── changelog └── copyright ├── AUTHORS ├── NEWS ├── ChangeLog ├── .gitignore ├── README ├── INSTALL ├── moduleinfo.h ├── mingw32-windows.toolchain ├── cmake ├── FindGcrypt.cmake ├── FindWireshark.cmake ├── UseMakeDissectorReg.cmake ├── COPYING-CMAKE-SCRIPTS ├── CMakeForceCompiler.cmake └── FindGLIB2.cmake ├── COPYING ├── whatsapp-proto.h ├── README.md ├── CMakeLists.txt ├── protocol-spec.txt ├── tools ├── make-dissector-reg └── make-dissector-reg.py ├── packet-whatsapp.c ├── tinfl.c └── whatsapp-proto.cc /debian/compat: -------------------------------------------------------------------------------- 1 | 7 2 | -------------------------------------------------------------------------------- /debian/dirs: -------------------------------------------------------------------------------- 1 | usr/lib/wireshark/plugins 2 | -------------------------------------------------------------------------------- /AUTHORS: -------------------------------------------------------------------------------- 1 | Author: 2 | David Guillen Fandos 3 | -------------------------------------------------------------------------------- /NEWS: -------------------------------------------------------------------------------- 1 | December 1, 2012 2 | Start 3 | February 14, 2013 4 | Relase public version 5 | 6 | -------------------------------------------------------------------------------- /ChangeLog: -------------------------------------------------------------------------------- 1 | 2012-12-01 2 | First version of the WhatsApp protocol dissector for wireshark 3 | 2013-02-14 4 | Quite stable version, released the sources at github 5 | -------------------------------------------------------------------------------- /debian/rules: -------------------------------------------------------------------------------- 1 | 2 | build: 3 | rm -rf build/ 4 | mkdir build 5 | cd build 6 | cmake .. 7 | cd .. 8 | 9 | make -C build 10 | 11 | %: 12 | dh $@ 13 | 14 | 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled Object files 2 | *.slo 3 | *.lo 4 | *.o 5 | 6 | # Compiled Dynamic libraries 7 | *.so 8 | *.dylib 9 | 10 | # Compiled Static libraries 11 | *.lai 12 | *.la 13 | *.a 14 | 15 | # Backup files 16 | *~ 17 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is a plugin to help with WhatsApp protocol dissection. The protocol is 2 | quite tricky and uses encryption, so at the moment the dissector is very basic. 3 | Checked it using real traces taken from the qemu android emulator. Used to develop 4 | a pidgin plugin for whatsapp successfully. 5 | 6 | Hope you like it ;) 7 | -------------------------------------------------------------------------------- /INSTALL: -------------------------------------------------------------------------------- 1 | This wireshark plugin is built with cmake (in a separate build/ directory): 2 | mkdir build 3 | cd build 4 | cmake .. 5 | make 6 | make install 7 | 8 | This will build the .so plugin for wireshark and install it into the user's 9 | ~/.wireshark/plugins/ directory, where wireshark will load plugins from. 10 | -------------------------------------------------------------------------------- /moduleinfo.h: -------------------------------------------------------------------------------- 1 | /* Included *after* config.h, in order to re-define these macros */ 2 | 3 | #ifdef PACKAGE 4 | #undef PACKAGE 5 | #endif 6 | 7 | /* Name of package */ 8 | #define PACKAGE "whatsapp" 9 | 10 | 11 | #ifdef VERSION 12 | #undef VERSION 13 | #endif 14 | 15 | /* Version number of package */ 16 | #define VERSION "0.0.1" 17 | 18 | -------------------------------------------------------------------------------- /debian/control: -------------------------------------------------------------------------------- 1 | Source: wireshark-whatsapp 2 | Section: net 3 | Priority: optional 4 | Maintainer: David Guillen Fandos 5 | DM-Upload-Allowed: yes 6 | Build-Depends: debhelper (>= 7.0.50), libglib2.0-dev (>= 2.4.0), libwireshark-dev, libssl-dev, cmake (>= 2.6.0), libwiretap-dev 7 | Standards-Version: 3.8.4 8 | Homepage: https://github.com/davidgfnet/wireshark-whatsapp 9 | 10 | Package: wireshark-whatsapp 11 | Architecture: any 12 | Depends: ${shlibs:Depends}, ${misc:Depends}, libwireshark1, libssl1.0.0 13 | Description: WhatsApp plugin for Wireshark 14 | This is a WhatsApp plugin for Wireshark and libwireshark. It allows to sniff 15 | whatsapp network traffic and decode it (under certain conditions) 16 | 17 | 18 | -------------------------------------------------------------------------------- /mingw32-windows.toolchain: -------------------------------------------------------------------------------- 1 | # the name of the target operating system 2 | SET(CMAKE_SYSTEM_NAME Windows) 3 | 4 | # which compilers to use for C and C++ 5 | SET(CMAKE_C_COMPILER i686-w64-mingw32-gcc) 6 | SET(CMAKE_CXX_COMPILER i686-w64-mingw32-g++) 7 | SET(CMAKE_RC_COMPILER i686-w64-mingw32-windres) 8 | 9 | # here is the target environment located 10 | SET(CMAKE_FIND_ROOT_PATH /home/david/mingw/mingw-w64-i686/ ) 11 | 12 | # adjust the default behaviour of the FIND_XXX() commands: 13 | # search headers and libraries in the target environment, search 14 | # programs in the host environment 15 | set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) 16 | set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) 17 | set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) 18 | -------------------------------------------------------------------------------- /debian/changelog: -------------------------------------------------------------------------------- 1 | wireshark-whatsapp (0.14) quantal; urgency=low 2 | 3 | * Adding support for WhatsApp protocol v1.4 4 | 5 | -- David Guillen Fandos Sun, 13 Apr 2014 18:40:00 +0200 6 | 7 | wireshark-whatsapp (0.1-3) quantal; urgency=low 8 | 9 | * Adding libwiretap as dependency for building package 10 | 11 | -- David Guillen Fandos Mon, 26 Aug 2013 16:50:00 +0200 12 | 13 | wireshark-whatsapp (0.1-2) quantal; urgency=low 14 | 15 | * Small packagin fix for Ubuntu 16 | 17 | -- David Guillen Fandos Mon, 26 Aug 2013 15:59:00 +0200 18 | 19 | wireshark-whatsapp (0.1-1) quantal; urgency=low 20 | 21 | * Initial release for Ubuntu 22 | 23 | -- David Guillen Fandos Mon, 26 Aug 2013 14:10:00 +0200 24 | 25 | -------------------------------------------------------------------------------- /debian/copyright: -------------------------------------------------------------------------------- 1 | Copyright: 2 | 3 | Copyright 2013, David Guillen Fandos 4 | 5 | License: 6 | 7 | This program is free software: you can redistribute it and/or modify 8 | it under the terms of the GNU General Public License as published by 9 | the Free Software Foundation, either version 2 of the License, or 10 | (at your option) any later version. 11 | 12 | This program is distributed in the hope that it will be useful, 13 | but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | GNU General Public License for more details. 16 | 17 | You should have received a copy of the GNU General Public License 18 | along with this program. If not, see . 19 | 20 | On Debian GNU/Linux systems, the complete text of the GNU General 21 | Public License can be found in the /usr/share/common-licenses/GPL file. 22 | 23 | 24 | -------------------------------------------------------------------------------- /cmake/FindGcrypt.cmake: -------------------------------------------------------------------------------- 1 | # Copyright (C) 2011 Felix Geyer 2 | # 3 | # This program is free software: you can redistribute it and/or modify 4 | # it under the terms of the GNU General Public License as published by 5 | # the Free Software Foundation, either version 2 or (at your option) 6 | # version 3 of the License. 7 | # 8 | # This program is distributed in the hope that it will be useful, 9 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | # GNU General Public License for more details. 12 | # 13 | # You should have received a copy of the GNU General Public License 14 | # along with this program. If not, see . 15 | 16 | find_path(GCRYPT_INCLUDE_DIR gcrypt.h) 17 | 18 | find_library(GCRYPT_LIBRARIES gcrypt) 19 | 20 | include(FindPackageHandleStandardArgs) 21 | find_package_handle_standard_args(Gcrypt DEFAULT_MSG GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) 22 | 23 | mark_as_advanced(GCRYPT_LIBRARIES GCRYPT_INCLUDE_DIR) 24 | -------------------------------------------------------------------------------- /cmake/FindWireshark.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # Try to find the wireshark library and its includes 3 | # 4 | # This snippet sets the following variables: 5 | # WIRESHARK_FOUND True if wireshark library got found 6 | # WIRESHARK_INCLUDE_DIRS Location of the wireshark headers 7 | # WIRESHARK_LIBRARIES List of libraries to use wireshark 8 | # 9 | # Copyright (c) 2011 Reinhold Kainhofer 10 | # 11 | # Redistribution and use is allowed according to the terms of the New 12 | # BSD license. 13 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 14 | # 15 | 16 | # wireshark does not install its library with pkg-config information, 17 | # so we need to manually find the libraries and headers 18 | 19 | FIND_PATH( WIRESHARK_INCLUDE_DIRS epan/column-info.h PATH_SUFFIXES wireshark ) 20 | FIND_LIBRARY( WIRESHARK_LIBRARIES wireshark ) 21 | 22 | # Report results 23 | IF ( WIRESHARK_LIBRARIES AND WIRESHARK_INCLUDE_DIRS ) 24 | SET( WIRESHARK_FOUND 1 ) 25 | ELSE ( WIRESHARK_LIBRARIES AND WIRESHARK_INCLUDE_DIRS ) 26 | MESSAGE( SEND_ERROR "Could NOT find the wireshark library and headers" ) 27 | ENDIF ( WIRESHARK_LIBRARIES AND WIRESHARK_INCLUDE_DIRS ) 28 | 29 | IF (WIRESHARK_FOUND) 30 | MESSAGE (STATUS "Found wireshark libs at ${WIRESHARK_INCLUDE_DIRS}") 31 | ELSE() 32 | MESSAGE (STATUS "Wireshark libs not found!") 33 | ENDIF() 34 | 35 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | Redistribution and use in source and binary forms, with or without 2 | modification, are permitted provided that the following conditions 3 | are met: 4 | 5 | 1. Redistributions of source code must retain the copyright 6 | notice, this list of conditions and the following disclaimer. 7 | 2. Redistributions in binary form must reproduce the copyright 8 | notice, this list of conditions and the following disclaimer in the 9 | documentation and/or other materials provided with the distribution. 10 | 3. The name of the author may not be used to endorse or promote products 11 | derived from this software without specific prior written permission. 12 | 13 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 14 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 15 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 16 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 17 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 18 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 19 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 20 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 21 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 22 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 23 | -------------------------------------------------------------------------------- /cmake/UseMakeDissectorReg.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # $Id: UseMakeDissectorReg.cmake 33616 2010-07-22 12:18:36Z stig $ 3 | # 4 | MACRO(REGISTER_DISSECTOR_FILES _outputfile _registertype ) 5 | # FIXME: Only the Python stuff has been implemented 6 | # Make this into a MACRO, to avoid duplication with plugins/.../ 7 | #register.c: $(plugin_src) $(ALL_DISSECTORS_SRC) $(top_srcdir)/tools/make-dissector-reg \ 8 | # $(top_srcdir)/tools/make-dissector-reg.py 9 | # @if test -n "$(PYTHON)"; then \ 10 | # echo Making register.c with python ; \ 11 | # $(PYTHON) $(top_srcdir)/tools/make-dissector-reg.py $(srcdir) \ 12 | # dissectors $(ALL_DISSECTORS_SRC) ; \ 13 | # else \ 14 | # echo Making register.c with shell script ; \ 15 | # $(top_srcdir)/tools/make-dissector-reg $(srcdir) \ 16 | # dissectors $(plugin_src) $(ALL_DISSECTORS_SRC) ; \ 17 | # fi 18 | set( _sources ${ARGN} ) 19 | ADD_CUSTOM_COMMAND( 20 | OUTPUT 21 | ${_outputfile} 22 | COMMAND ${PYTHON_EXECUTABLE} 23 | ${CMAKE_SOURCE_DIR}/tools/make-dissector-reg.py 24 | ${CMAKE_CURRENT_SOURCE_DIR} 25 | ${_registertype} 26 | ${_sources} 27 | DEPENDS 28 | ${_sources} 29 | ${CMAKE_SOURCE_DIR}/tools/make-dissector-reg 30 | ${CMAKE_SOURCE_DIR}/tools/make-dissector-reg.py 31 | ) 32 | ENDMACRO(REGISTER_DISSECTOR_FILES) 33 | 34 | -------------------------------------------------------------------------------- /cmake/COPYING-CMAKE-SCRIPTS: -------------------------------------------------------------------------------- 1 | Copyright notice for the files copied from 2 | http://www.opensync.org/browser/branches/3rd-party-cmake-modules/modules 3 | 4 | $Id: COPYING-CMAKE-SCRIPTS 34248 2010-09-25 15:38:12Z jmayer $ 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions 8 | are met: 9 | 10 | 1. Redistributions of source code must retain the copyright 11 | notice, this list of conditions and the following disclaimer. 12 | 2. Redistributions in binary form must reproduce the copyright 13 | notice, this list of conditions and the following disclaimer in the 14 | documentation and/or other materials provided with the distribution. 15 | 3. The name of the author may not be used to endorse or promote products 16 | derived from this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 19 | IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 20 | OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 21 | IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 23 | NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 27 | THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /cmake/CMakeForceCompiler.cmake: -------------------------------------------------------------------------------- 1 | # This module defines macros intended for use by cross-compiling 2 | # toolchain files when CMake is not able to automatically detect the 3 | # compiler identification. 4 | # 5 | # Macro CMAKE_FORCE_C_COMPILER has the following signature: 6 | # CMAKE_FORCE_C_COMPILER( ) 7 | # It sets CMAKE_C_COMPILER to the given compiler and the cmake 8 | # internal variable CMAKE_C_COMPILER_ID to the given compiler-id. 9 | # It also bypasses the check for working compiler and basic compiler 10 | # information tests. 11 | # 12 | # Macro CMAKE_FORCE_CXX_COMPILER has the following signature: 13 | # CMAKE_FORCE_CXX_COMPILER( ) 14 | # It sets CMAKE_CXX_COMPILER to the given compiler and the cmake 15 | # internal variable CMAKE_CXX_COMPILER_ID to the given compiler-id. 16 | # It also bypasses the check for working compiler and basic compiler 17 | # information tests. 18 | # 19 | # So a simple toolchain file could look like this: 20 | # INCLUDE (CMakeForceCompiler) 21 | # SET(CMAKE_SYSTEM_NAME Generic) 22 | # CMAKE_FORCE_C_COMPILER (chc12 MetrowerksHicross) 23 | # CMAKE_FORCE_CXX_COMPILER (chc12 MetrowerksHicross) 24 | 25 | MACRO(CMAKE_FORCE_C_COMPILER compiler id) 26 | SET(CMAKE_C_COMPILER "${compiler}") 27 | SET(CMAKE_C_COMPILER_ID_RUN TRUE) 28 | SET(CMAKE_C_COMPILER_ID ${id}) 29 | SET(CMAKE_C_COMPILER_WORKS TRUE) 30 | SET(CMAKE_C_COMPILER_FORCED TRUE) 31 | 32 | # Set old compiler id variables. 33 | IF("${CMAKE_C_COMPILER_ID}" MATCHES "GNU") 34 | SET(CMAKE_COMPILER_IS_GNUCC 1) 35 | ENDIF("${CMAKE_C_COMPILER_ID}" MATCHES "GNU") 36 | ENDMACRO(CMAKE_FORCE_C_COMPILER) 37 | 38 | MACRO(CMAKE_FORCE_CXX_COMPILER compiler id) 39 | SET(CMAKE_CXX_COMPILER "${compiler}") 40 | SET(CMAKE_CXX_COMPILER_ID_RUN TRUE) 41 | SET(CMAKE_CXX_COMPILER_ID ${id}) 42 | SET(CMAKE_CXX_COMPILER_WORKS TRUE) 43 | SET(CMAKE_CXX_COMPILER_FORCED TRUE) 44 | 45 | # Set old compiler id variables. 46 | IF("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") 47 | SET(CMAKE_COMPILER_IS_GNUCXX 1) 48 | ENDIF("${CMAKE_CXX_COMPILER_ID}" MATCHES "GNU") 49 | ENDMACRO(CMAKE_FORCE_CXX_COMPILER) 50 | 51 | -------------------------------------------------------------------------------- /whatsapp-proto.h: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * WhatsApp protocol dissector 4 | * Written by David Guillen Fandos 5 | * Based on WhatsAPI sources 6 | * 7 | */ 8 | 9 | 10 | extern const char * global_imei_whatsapp_1; 11 | extern gboolean global_enable_decoding; 12 | 13 | typedef struct _wa_userpass_t { 14 | char* username; 15 | char* password; 16 | } wa_userpass_t; 17 | extern wa_userpass_t * wa_userpass_uats; 18 | extern guint wa_userpass_uats_num; 19 | 20 | 21 | int whatsapp_data_length(const char * data, int len); 22 | int whatsapp_data_dissect_tree(const char * data, int len, proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo); 23 | 24 | extern int hf_whatsapp_node; 25 | extern int hf_whatsapp_nodesize8; 26 | extern int hf_whatsapp_nodesize16; 27 | extern int hf_whatsapp_attr_key; 28 | extern int hf_whatsapp_attr_val; 29 | extern int hf_whatsapp_attr_crypted; 30 | extern int hf_whatsapp_attr_compressed; 31 | extern int hf_whatsapp_attr_flags; 32 | extern int hf_whatsapp_message; 33 | extern int message_whatsapp; 34 | extern int tree_whatsapp; 35 | extern int userserver_string; 36 | extern int tree_msg_flags; 37 | 38 | extern int hf_whatsapp_userserver; 39 | extern int hf_whatsapp_attribute; 40 | extern int hf_whatsapp_attr_key_enc; 41 | extern int hf_whatsapp_attr_val_enc; 42 | extern int hf_whatsapp_attr_key_enc_ext; 43 | extern int hf_whatsapp_attr_val_enc_ext; 44 | extern int hf_whatsapp_attr_key_plain; 45 | extern int hf_whatsapp_attr_val_plain; 46 | extern int hf_whatsapp_tag_enc; 47 | extern int hf_whatsapp_tag_enc_ext; 48 | extern int hf_whatsapp_tag_plain; 49 | extern int hf_whatsapp_nvalue_enc; 50 | extern int hf_whatsapp_nvalue_enc_ext; 51 | extern int hf_whatsapp_nibble_enc; 52 | extern int hf_whatsapp_nvalue_plain; 53 | extern int hf_whatsapp_crypted_hmac_hash; 54 | extern int whatsapp_msg_crypted_message; 55 | extern int whatsapp_msg_crypted_payload; 56 | extern int whatsapp_msg_crypted_payload_mismatch; 57 | extern int whatsapp_msg_compressed_message; 58 | extern int whatsapp_msg; 59 | extern int proto_whatsapp; 60 | 61 | extern const value_string strings_list[]; 62 | extern const value_string strings_list_ext[]; 63 | 64 | 65 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | wireshark-whatsapp 2 | ================== 3 | 4 | Whatsapp dissector plugin for wireshark 5 | 6 | ***Important*** I'm no longer working on this repo. This plugin works for protocol version 1.6 or older (check out the tags), with whatsapp protocol 2.0 it is probably necessary to perform MITM in order for a machine to capture the protocol data (since it uses an ephemeral shared secret calculated in a DH way) so this plugin becomes essentially useless. Still it is perfect as a base to implement 2.0 protocol. 7 | 8 | Get a copy 9 | ---------- 10 | 11 | Ubuntu users may install the plugin using launchpad repo: https://launchpad.net/~wireshark-whatsapp/+archive/ppa 12 | 13 | Windows users may find releases at: https://www.gosell.it/product/whatsapp-dissector-for-wireshark-26 14 | 15 | Build and install 16 | ----------------- 17 | 18 | 1. Create a build directory and move to it, for example "mkdir build; cd build" 19 | 2. Generate Makefile "cmake .." 20 | 3. Now build the plugin "make" 21 | 4. And the plugin should be built as "whatsapp.so", just copy it to the plugins folder "cp whatsapp.so ~/.wireshark/plugins/" 22 | 23 | You need the wireshark headers, the glib-2.0 headers, the libcrypto headers (install openssl headers) and of course the gcc C/C++ compiler. 24 | 25 | For Windows build: 26 | 27 | 1. Create a build directory and move to it, for example "mkdir build; cd build" 28 | 2. Tweak mingw32-windows.toolchain according to your needs. 29 | 3. Generate Makefile "cmake -DCMAKE_TOOLCHAIN_FILE=../mingw32-windows.toolchain .." (set WIRESHARK_INCLUDE_DIRS, GCRYPT_INCLUDE_DIR variables if needed) 30 | 4. Now build the plugin "make" 31 | 5. And the plugin should be built as "whatsapp.dll", just copy it to the plugins folder "C:\Program Files\Wireshark\Plugins\'version'\whatsapp.dll" 32 | 33 | You will probably need libglib-2.0, libwireshark and libgcrypt to properly link the DLL. 34 | 35 | Windows builds are currently under test, report any bugs you find please. 36 | 37 | 38 | Usage 39 | ----- 40 | 41 | Using the plugin it's easy. You can use it to filter whatsapp packets (although it does not work as well as I'd like) and to dissect the data of the packet. 42 | For decryption support goto to protocol preferences, enable the data decoding and fill some decryption keys (the passwords for the accounts you are sniffing). 43 | 44 | 45 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt 2 | # 3 | # $Id: CMakeLists.txt 31995 2010-02-24 22:32:10Z jmayer $ 4 | # 5 | # Wireshark - Network traffic analyzer 6 | # By Gerald Combs 7 | # Copyright 1998 Gerald Combs 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the GNU General Public License 11 | # as published by the Free Software Foundation; either version 2 12 | # of the License, or (at your option) any later version. 13 | # 14 | # This program is distributed in the hope that it will be useful, 15 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 16 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17 | # GNU General Public License for more details. 18 | # 19 | # You should have received a copy of the GNU General Public License 20 | # along with this program; if not, write to the Free Software 21 | # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. 22 | # 23 | 24 | project(whatsapp C CXX) 25 | 26 | cmake_minimum_required(VERSION 2.6) 27 | set(CMAKE_BACKWARDS_COMPATIBILITY 2.6) 28 | set(CMAKE_MODULE_PATH ${CMAKE_SOURCE_DIR}/cmake) 29 | IF(EXISTS /usr/lib/wireshark/libwireshark1/plugins/) 30 | set(CMAKE_INSTALL_LIBDIR /usr/lib/wireshark/libwireshark1/plugins/) 31 | ELSE() 32 | set(CMAKE_INSTALL_LIBDIR ~/.wireshark) 33 | ENDIF() 34 | 35 | INCLUDE(UseMakeDissectorReg) 36 | include(CMakeForceCompiler) 37 | 38 | set(GLIB2_MIN_VERSION 2.4.0) 39 | 40 | IF(NOT DEFINED ENV{GLIB2_INCLUDE_DIRS}) 41 | find_package(GLIB2) 42 | ELSE() 43 | SET(GLIB2_INCLUDE_DIRS $ENV{GLIB2_INCLUDE_DIRS}) 44 | ENDIF() 45 | include_directories (${GLIB2_INCLUDE_DIRS}) 46 | 47 | IF(NOT DEFINED WIRESHARK_INCLUDE_DIRS) 48 | find_package(Wireshark) 49 | ELSE() 50 | SET(GLIB2_INCLUDE_DIRS $ENV{WIRESHARK_INCLUDE_DIRS}) 51 | ENDIF() 52 | include_directories (${WIRESHARK_INCLUDE_DIRS}) 53 | 54 | IF(DEFINED WIRESHARK_LIB_DIR) 55 | link_directories (${WIRESHARK_LIB_DIR}) 56 | ENDIF() 57 | 58 | IF(NOT DEFINED GCRYPT_INCLUDE_DIR) 59 | find_package(Gcrypt) 60 | ELSE() 61 | SET(GLIB2_INCLUDE_DIRS $ENV{GCRYPT_INCLUDE_DIR}) 62 | ENDIF() 63 | include_directories (${GCRYPT_INCLUDE_DIR}) 64 | 65 | set(LINK_MODE_LIB SHARED) 66 | set(LINK_MODE_MODULE MODULE) 67 | 68 | set(DISSECTOR_SRC 69 | packet-whatsapp.c 70 | whatsapp-proto.cc 71 | tinfl.c 72 | ) 73 | 74 | set(PLUGIN_FILES 75 | plugin.c 76 | ${DISSECTOR_SRC} 77 | ) 78 | 79 | set(CLEAN_FILES 80 | ${PLUGIN_FILES} 81 | ) 82 | 83 | if (WERROR) 84 | set_source_files_properties( 85 | ${CLEAN_FILES} 86 | PROPERTIES 87 | COMPILE_FLAGS -Werror 88 | ) 89 | endif() 90 | 91 | include_directories(${CMAKE_CURRENT_SOURCE_DIR}) 92 | 93 | register_dissector_files(plugin.c 94 | plugin 95 | ${DISSECTOR_SRC} 96 | ) 97 | 98 | add_library(whatsapp ${LINK_MODE_MODULE} 99 | ${PLUGIN_FILES} 100 | ) 101 | set_target_properties(whatsapp PROPERTIES PREFIX "") 102 | set_target_properties(whatsapp PROPERTIES LINK_FLAGS "${WS_LINK_FLAGS}") 103 | 104 | IF(DEFINED MANUAL_LINK) 105 | target_link_libraries(whatsapp) 106 | ELSE() 107 | target_link_libraries(whatsapp wireshark gcrypt glib-2.0) 108 | ENDIF() 109 | 110 | install(TARGETS whatsapp 111 | LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/plugins NAMELINK_SKIP 112 | ) 113 | 114 | IF(DEFINED GNU_HOST) 115 | CMAKE_FORCE_C_COMPILER( ${GNU_HOST}-gcc GNU) 116 | CMAKE_FORCE_CXX_COMPILER(${GNU_HOST}-g++ GNU) 117 | set(CMAKE_STRIP "${GNU_HOST}-strip") 118 | ENDIF() 119 | 120 | IF(DEFINED STATIC_GCC_BUILD) 121 | set(CMAKE_MODULE_LINKER_FLAGS "${CMAKE_MODULE_LINKER_FLAGS} -static-libgcc -static-libstdc++") 122 | ENDIF() 123 | 124 | if (WIN32) 125 | if(MINGW) 126 | message(STATUS "Compiling with MINGW!") 127 | # Static link 128 | set(CMAKE_MODULE_LINKER_FLAGS "-static-libgcc -static-libstdc++ -lpthread") 129 | endif(MINGW) 130 | endif (WIN32) 131 | 132 | if (WIN32) 133 | add_custom_command(TARGET whatsapp 134 | POST_BUILD 135 | COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded whatsapp.dll 136 | ) 137 | ELSE() 138 | add_custom_command(TARGET whatsapp 139 | POST_BUILD 140 | COMMAND ${CMAKE_STRIP} --strip-debug --strip-unneeded whatsapp.so 141 | ) 142 | ENDIF() 143 | 144 | 145 | -------------------------------------------------------------------------------- /protocol-spec.txt: -------------------------------------------------------------------------------- 1 | ============================== 2 | ============================== 3 | WhatsApp protocol 4 | ============================== 5 | ============================== 6 | 7 | Basic stream format 8 | 9 | WhatsApp protocol uses a TCP stream to send and 10 | receive all the information. Over this TCP stream 11 | it sends packets which contain the application 12 | data. Those packets have a 3 byte header before 13 | the actual packet data. 14 | 15 | Packet format 16 | 17 | ************************************* 18 | * Flags * Packet size * Packet data * 19 | ************************************* 20 | * 1b * 2b * 0 to 65536b * 21 | ************************************* 22 | 23 | Field description: 24 | 25 | Flags: Seems to be a bitfield. The first bit (the 26 | MSB) is set when the server sends a crypted packet. 27 | The fourth bit is set when the client sends a 28 | packet to the server which is crypted. 29 | 30 | Packet size: A network ordered 16 bit unsigned 31 | integer which indicates the amount of information 32 | following the header (thus not couting the header 33 | size). 34 | 35 | Packet data: The actual app data. 36 | 37 | If the packet is encrypted the first or last four 38 | bytes of the packet are used for HMAC-SHA1 checksum. 39 | If the packet is a client to server packet the four 40 | bytes are placed at the end of the data. If it's a 41 | server to client packet the four bytes are placed 42 | in the four first positions. Therefore the minimum 43 | size for an encrypted packet should be four. 44 | 45 | App data format 46 | 47 | The data sent and received by the protocol is 48 | something similar to a XML. A tree of information 49 | with nodes, attributes, subchildrens and data. 50 | 51 | The basic format for an element is: 52 | 53 | ************************************************************ 54 | * Element Size * Type * Tag * Attributes * Children / Data * 55 | ************************************************************ 56 | * 2-3b * 1b * * * * 57 | ************************************************************ 58 | 59 | Element size: Describes the length of the element 60 | in terms of the number of attributes and wether it 61 | has data or children elements. The field is 62 | calculated as the number of attributes mutiplied by 63 | two and plus one. In case the element has children or 64 | data another one is added. 65 | 66 | Type: Indicates the of element. If set to one 67 | indicates that is a starting element (usually 68 | used at the start of communication). If set to 69 | two indicates an empty element (not sure). Other 70 | values indicate a regular element. 71 | 72 | Tag: A string (or sequence of bytes) that indicate the 73 | "name" of the element. 74 | 75 | Attributes: A sequence of (key,array) tuples consisting 76 | in pairs of strings. 77 | 78 | Children: A list of elements which are considered to be 79 | children of the current one. Those elements can have 80 | children too creating a hierachical structure. 81 | 82 | Data: A string or sequence of bytes containing some data 83 | for the element. 84 | 85 | Encrytion 86 | 87 | The data sent by the client and/or the server may be 88 | encrypted. This is indicated by the flags bits. 89 | When negotiating the connection the to endpoints 90 | establish a session key: a 20 byte key which is used 91 | both for encryption and authentication. 92 | 93 | The authentication is based on a challenge auth. The 94 | server sends an authentication request to the client 95 | which contains some bytes of random data. The client 96 | takes those bytes and combines them with the password 97 | of the account using PBKDF2. The resulting data is used 98 | for session key. As the server knows the password it can 99 | derive the session key too and they can start talking 100 | using encrypted packets. The success of the auth will 101 | is determined when the client sends a packet which 102 | contains the username (and some other data) encrypted 103 | with the session key. If the server is able to recover 104 | the plaintext it means that both are using the same key. 105 | 106 | The encryption is done using a RC4 stream cipher. 107 | 108 | 109 | 110 | -------------------------------------------------------------------------------- /tools/make-dissector-reg: -------------------------------------------------------------------------------- 1 | #! /bin/sh 2 | 3 | # 4 | # $Id: make-dissector-reg 21716 2007-05-07 17:55:42Z gal $ 5 | # 6 | 7 | # 8 | # The first argument is the directory in which the source files live. 9 | # 10 | srcdir="$1" 11 | shift 12 | 13 | # 14 | # The second argument is either "plugin" or "dissectors"; if it's 15 | # "plugin", we build a plugin.c for a plugin, and if it's 16 | # "dissectors", we build a register.c for libwireshark. 17 | # 18 | registertype="$1" 19 | shift 20 | if [ "$registertype" = plugin ] 21 | then 22 | outfile="plugin.c" 23 | elif [ "$registertype" = dissectors ] 24 | then 25 | outfile="register.c" 26 | else 27 | echo "Unknown output type '$registertype'" 1>&2 28 | exit 1 29 | fi 30 | 31 | # 32 | # All subsequent arguments are the files to scan. 33 | # 34 | rm -f ${outfile}-tmp 35 | echo '/* Do not modify this file. */' >${outfile}-tmp 36 | echo '/* It is created automatically by the Makefile. */'>>${outfile}-tmp 37 | if [ "$registertype" = plugin ] 38 | then 39 | cat <<"EOF" >>${outfile}-tmp 40 | #ifdef HAVE_CONFIG_H 41 | # include "config.h" 42 | #endif 43 | 44 | #include 45 | 46 | #include "moduleinfo.h" 47 | 48 | #ifndef ENABLE_STATIC 49 | G_MODULE_EXPORT const gchar version[] = VERSION; 50 | 51 | /* Start the functions we need for the plugin stuff */ 52 | 53 | G_MODULE_EXPORT void 54 | plugin_register (void) 55 | { 56 | EOF 57 | # 58 | # Build code to call all the protocol registration routines. 59 | # 60 | for f in "$@" 61 | do 62 | if [ -f $f ] 63 | then 64 | srcfile=$f 65 | else 66 | srcfile=$srcdir/$f 67 | fi 68 | grep '^proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 69 | done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp 70 | for f in "$@" 71 | do 72 | if [ -f $f ] 73 | then 74 | srcfile=$f 75 | else 76 | srcfile=$srcdir/$f 77 | fi 78 | grep '^void proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 79 | done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp 80 | else 81 | cat <<"EOF" >>${outfile}-tmp 82 | #include "register.h" 83 | void 84 | register_all_protocols(register_cb cb, gpointer client_data) 85 | { 86 | EOF 87 | # 88 | # Build code to call all the protocol registration routines. 89 | # 90 | for f in "$@" 91 | do 92 | if [ -f $f ] 93 | then 94 | srcfile=$f 95 | else 96 | srcfile=$srcdir/$f 97 | fi 98 | grep '^proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 99 | done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_REGISTER, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp 100 | for f in "$@" 101 | do 102 | if [ -f $f ] 103 | then 104 | srcfile=$f 105 | else 106 | srcfile=$srcdir/$f 107 | fi 108 | grep '^void proto_register_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 109 | done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_REGISTER, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp 110 | 111 | fi 112 | echo '}' >>${outfile}-tmp 113 | 114 | 115 | # 116 | # Build code to call all the protocol handoff registration routines. 117 | # 118 | if [ "$registertype" = plugin ] 119 | then 120 | cat <<"EOF" >>${outfile}-tmp 121 | G_MODULE_EXPORT void 122 | plugin_reg_handoff(void) 123 | { 124 | EOF 125 | for f in "$@" 126 | do 127 | if [ -f $f ] 128 | then 129 | srcfile=$f 130 | else 131 | srcfile=$srcdir/$f 132 | fi 133 | grep '^proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 134 | done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp 135 | for f in "$@" 136 | do 137 | if [ -f $f ] 138 | then 139 | srcfile=$f 140 | else 141 | srcfile=$srcdir/$f 142 | fi 143 | grep '^void proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 144 | done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); \1 ();}/' >>${outfile}-tmp 145 | else 146 | cat <<"EOF" >>${outfile}-tmp 147 | void 148 | register_all_protocol_handoffs(register_cb cb, gpointer client_data) 149 | { 150 | EOF 151 | for f in "$@" 152 | do 153 | if [ -f $f ] 154 | then 155 | srcfile=$f 156 | else 157 | srcfile=$srcdir/$f 158 | fi 159 | grep '^proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 160 | done | sed -e 's/^.*://' -e 's/^\([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_HANDOFF, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp 161 | for f in "$@" 162 | do 163 | if [ -f $f ] 164 | then 165 | srcfile=$f 166 | else 167 | srcfile=$srcdir/$f 168 | fi 169 | grep '^void proto_reg_handoff_[a-z_0-9A-Z]* *(' $srcfile 2>/dev/null | grep -v ';' 170 | done | sed -e 's/^.*://' -e 's/^void \([a-z_0-9A-Z]*\).*/ {extern void \1 (void); if(cb) (*cb)(RA_HANDOFF, \"\1\", client_data); \1 ();}/' >>${outfile}-tmp 171 | fi 172 | echo '}' >>${outfile}-tmp 173 | if [ "$registertype" = plugin ] 174 | then 175 | echo '#endif' >>${outfile}-tmp 176 | else 177 | cat <<"EOF" >>${outfile}-tmp 178 | gulong register_count(void) 179 | { 180 | EOF 181 | proto_regs=`grep RA_REGISTER ${outfile}-tmp | wc -l` 182 | handoff_regs=`grep RA_HANDOFF ${outfile}-tmp | wc -l` 183 | echo " return $proto_regs + $handoff_regs;" >>${outfile}-tmp 184 | echo '}' >>${outfile}-tmp 185 | fi 186 | mv ${outfile}-tmp ${outfile} 187 | -------------------------------------------------------------------------------- /cmake/FindGLIB2.cmake: -------------------------------------------------------------------------------- 1 | # 2 | # $Id: FindGLIB2.cmake 34248 2010-09-25 15:38:12Z jmayer $ 3 | # 4 | # - Try to find GLib2 5 | # Once done this will define 6 | # 7 | # GLIB2_FOUND - system has GLib2 8 | # GLIB2_INCLUDE_DIRS - the GLib2 include directory 9 | # GLIB2_LIBRARIES - Link these to use GLib2 10 | # 11 | # HAVE_GLIB_GREGEX_H glib has gregex.h header and 12 | # supports g_regex_match_simple 13 | # 14 | # Copyright (c) 2006 Andreas Schneider 15 | # Copyright (c) 2006 Philippe Bernery 16 | # Copyright (c) 2007 Daniel Gollub 17 | # Copyright (c) 2007 Alban Browaeys 18 | # Copyright (c) 2008 Michael Bell 19 | # Copyright (c) 2008-2009 Bjoern Ricks 20 | # 21 | # Redistribution and use is allowed according to the terms of the New 22 | # BSD license. 23 | # For details see the accompanying COPYING-CMAKE-SCRIPTS file. 24 | # 25 | 26 | 27 | IF (GLIB2_LIBRARIES AND GLIB2_INCLUDE_DIRS ) 28 | # in cache already 29 | SET(GLIB2_FOUND TRUE) 30 | ELSE (GLIB2_LIBRARIES AND GLIB2_INCLUDE_DIRS ) 31 | 32 | INCLUDE(FindPkgConfig) 33 | 34 | ## Glib 35 | IF ( GLIB2_FIND_REQUIRED ) 36 | SET( _pkgconfig_REQUIRED "REQUIRED" ) 37 | ELSE ( GLIB2_FIND_REQUIRED ) 38 | SET( _pkgconfig_REQUIRED "" ) 39 | ENDIF ( GLIB2_FIND_REQUIRED ) 40 | 41 | IF ( GLIB2_MIN_VERSION ) 42 | PKG_SEARCH_MODULE( GLIB2 ${_pkgconfig_REQUIRED} glib-2.0>=${GLIB2_MIN_VERSION} ) 43 | ELSE ( GLIB2_MIN_VERSION ) 44 | PKG_SEARCH_MODULE( GLIB2 ${_pkgconfig_REQUIRED} glib-2.0 ) 45 | ENDIF ( GLIB2_MIN_VERSION ) 46 | IF ( PKG_CONFIG_FOUND ) 47 | IF ( GLIB2_FOUND ) 48 | SET ( GLIB2_CORE_FOUND TRUE ) 49 | ELSE ( GLIB2_FOUND ) 50 | SET ( GLIB2_CORE_FOUND FALSE ) 51 | ENDIF ( GLIB2_FOUND ) 52 | ENDIF ( PKG_CONFIG_FOUND ) 53 | 54 | # Look for glib2 include dir and libraries w/o pkgconfig 55 | IF ( NOT GLIB2_FOUND AND NOT PKG_CONFIG_FOUND ) 56 | FIND_PATH( 57 | _glibconfig_include_DIR 58 | NAMES 59 | glibconfig.h 60 | PATHS 61 | /opt/gnome/lib64 62 | /opt/gnome/lib 63 | /opt/lib/ 64 | /opt/local/lib 65 | /sw/lib/ 66 | /usr/lib64 67 | /usr/lib 68 | /usr/local/include 69 | ${CMAKE_LIBRARY_PATH} 70 | PATH_SUFFIXES 71 | glib-2.0/include 72 | ) 73 | 74 | FIND_PATH( 75 | _glib2_include_DIR 76 | NAMES 77 | glib.h 78 | PATHS 79 | /opt/gnome/include 80 | /opt/local/include 81 | /sw/include 82 | /usr/include 83 | /usr/local/include 84 | PATH_SUFFIXES 85 | glib-2.0 86 | ) 87 | 88 | #MESSAGE(STATUS "Glib headers: ${_glib2_include_DIR}") 89 | 90 | FIND_LIBRARY( 91 | _glib2_link_DIR 92 | NAMES 93 | glib-2.0 94 | glib 95 | PATHS 96 | /opt/gnome/lib 97 | /opt/local/lib 98 | /sw/lib 99 | /usr/lib 100 | /usr/local/lib 101 | ) 102 | IF ( _glib2_include_DIR AND _glib2_link_DIR ) 103 | SET ( _glib2_FOUND TRUE ) 104 | ENDIF ( _glib2_include_DIR AND _glib2_link_DIR ) 105 | 106 | 107 | IF ( _glib2_FOUND ) 108 | SET ( GLIB2_INCLUDE_DIRS ${_glib2_include_DIR} ${_glibconfig_include_DIR} ) 109 | SET ( GLIB2_LIBRARIES ${_glib2_link_DIR} ) 110 | SET ( GLIB2_CORE_FOUND TRUE ) 111 | ELSE ( _glib2_FOUND ) 112 | SET ( GLIB2_CORE_FOUND FALSE ) 113 | ENDIF ( _glib2_FOUND ) 114 | 115 | # Handle dependencies 116 | # libintl 117 | IF ( NOT LIBINTL_FOUND ) 118 | FIND_PATH(LIBINTL_INCLUDE_DIR 119 | NAMES 120 | libintl.h 121 | PATHS 122 | /opt/gnome/include 123 | /opt/local/include 124 | /sw/include 125 | /usr/include 126 | /usr/local/include 127 | ) 128 | 129 | FIND_LIBRARY(LIBINTL_LIBRARY 130 | NAMES 131 | intl 132 | PATHS 133 | /opt/gnome/lib 134 | /opt/local/lib 135 | /sw/lib 136 | /usr/local/lib 137 | /usr/lib 138 | ) 139 | 140 | IF (LIBINTL_LIBRARY AND LIBINTL_INCLUDE_DIR) 141 | SET (LIBINTL_FOUND TRUE) 142 | ENDIF (LIBINTL_LIBRARY AND LIBINTL_INCLUDE_DIR) 143 | ENDIF ( NOT LIBINTL_FOUND ) 144 | 145 | # libiconv 146 | IF ( NOT LIBICONV_FOUND ) 147 | FIND_PATH(LIBICONV_INCLUDE_DIR 148 | NAMES 149 | iconv.h 150 | PATHS 151 | /opt/gnome/include 152 | /opt/local/include 153 | /opt/local/include 154 | /sw/include 155 | /sw/include 156 | /usr/local/include 157 | /usr/include 158 | PATH_SUFFIXES 159 | glib-2.0 160 | ) 161 | 162 | FIND_LIBRARY(LIBICONV_LIBRARY 163 | NAMES 164 | iconv 165 | PATHS 166 | /opt/gnome/lib 167 | /opt/local/lib 168 | /sw/lib 169 | /usr/lib 170 | /usr/local/lib 171 | ) 172 | 173 | IF (LIBICONV_LIBRARY AND LIBICONV_INCLUDE_DIR) 174 | SET (LIBICONV_FOUND TRUE) 175 | ENDIF (LIBICONV_LIBRARY AND LIBICONV_INCLUDE_DIR) 176 | ENDIF ( NOT LIBICONV_FOUND ) 177 | 178 | IF (LIBINTL_FOUND) 179 | SET (GLIB2_LIBRARIES ${GLIB2_LIBRARIES} ${LIBINTL_LIBRARY}) 180 | SET (GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS} ${LIBINTL_INCLUDE_DIR}) 181 | ENDIF (LIBINTL_FOUND) 182 | 183 | IF (LIBICONV_FOUND) 184 | SET (GLIB2_LIBRARIES ${GLIB2_LIBRARIES} ${LIBICONV_LIBRARY}) 185 | SET (GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS} ${LIBICONV_INCLUDE_DIR}) 186 | ENDIF (LIBICONV_FOUND) 187 | 188 | ENDIF ( NOT GLIB2_FOUND AND NOT PKG_CONFIG_FOUND ) 189 | ## 190 | 191 | IF (GLIB2_CORE_FOUND AND GLIB2_INCLUDE_DIRS AND GLIB2_LIBRARIES) 192 | SET (GLIB2_FOUND TRUE) 193 | ENDIF (GLIB2_CORE_FOUND AND GLIB2_INCLUDE_DIRS AND GLIB2_LIBRARIES) 194 | 195 | IF (GLIB2_FOUND) 196 | IF (NOT GLIB2_FIND_QUIETLY) 197 | MESSAGE (STATUS "Found GLib2: ${GLIB2_LIBRARIES} ${GLIB2_INCLUDE_DIRS}") 198 | ENDIF (NOT GLIB2_FIND_QUIETLY) 199 | ELSE (GLIB2_FOUND) 200 | IF (GLIB2_FIND_REQUIRED) 201 | MESSAGE (SEND_ERROR "Could not find GLib2") 202 | ENDIF (GLIB2_FIND_REQUIRED) 203 | ENDIF (GLIB2_FOUND) 204 | 205 | # show the GLIB2_INCLUDE_DIRS and GLIB2_LIBRARIES variables only in the advanced view 206 | MARK_AS_ADVANCED(GLIB2_INCLUDE_DIRS GLIB2_LIBRARIES) 207 | MARK_AS_ADVANCED(LIBICONV_INCLUDE_DIR LIBICONV_LIBRARY) 208 | MARK_AS_ADVANCED(LIBINTL_INCLUDE_DIR LIBINTL_LIBRARY) 209 | 210 | ENDIF (GLIB2_LIBRARIES AND GLIB2_INCLUDE_DIRS) 211 | 212 | IF ( WIN32 ) 213 | # include libiconv for win32 214 | IF ( NOT LIBICONV_FOUND ) 215 | FIND_PATH(LIBICONV_INCLUDE_DIR iconv.h PATH_SUFFIXES glib-2.0) 216 | 217 | FIND_LIBRARY(LIBICONV_LIBRARY NAMES iconv) 218 | 219 | IF (LIBICONV_LIBRARY AND LIBICONV_INCLUDE_DIR) 220 | SET (LIBICONV_FOUND TRUE) 221 | ENDIF (LIBICONV_LIBRARY AND LIBICONV_INCLUDE_DIR) 222 | ENDIF ( NOT LIBICONV_FOUND ) 223 | IF (LIBICONV_FOUND) 224 | SET (GLIB2_LIBRARIES ${GLIB2_LIBRARIES} ${LIBICONV_LIBRARY}) 225 | SET (GLIB2_INCLUDE_DIRS ${GLIB2_INCLUDE_DIRS} ${LIBICONV_INCLUDE_DIR}) 226 | ENDIF (LIBICONV_FOUND) 227 | ENDIF ( WIN32 ) 228 | 229 | IF ( GLIB2_FOUND ) 230 | # Check if system has a newer version of glib 231 | # which supports g_regex_match_simple 232 | INCLUDE( CheckIncludeFiles ) 233 | SET( CMAKE_REQUIRED_INCLUDES ${GLIB2_INCLUDE_DIRS} ) 234 | CHECK_INCLUDE_FILES ( glib/gregex.h HAVE_GLIB_GREGEX_H ) 235 | CHECK_INCLUDE_FILES ( glib/gchecksum.h HAVE_GLIB_GCHECKSUM_H ) 236 | # Reset CMAKE_REQUIRED_INCLUDES 237 | SET( CMAKE_REQUIRED_INCLUDES "" ) 238 | ENDIF( GLIB2_FOUND ) 239 | -------------------------------------------------------------------------------- /tools/make-dissector-reg.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # 3 | # Looks for registration routines in the protocol dissectors, 4 | # and assembles C code to call all the routines. 5 | # 6 | # This is a Python version of the make-reg-dotc shell script. 7 | # Running the shell script on Win32 is very very slow because of 8 | # all the process-launching that goes on --- multiple greps and 9 | # seds for each input file. I wrote this python version so that 10 | # less processes would have to be started. 11 | # 12 | # $Id: make-dissector-reg.py 30447 2009-10-09 20:47:18Z krj $ 13 | 14 | import os 15 | import sys 16 | import re 17 | import pickle 18 | from stat import * 19 | 20 | VERSION_KEY = '_VERSION' 21 | CUR_VERSION = '$Id: make-dissector-reg.py 30447 2009-10-09 20:47:18Z krj $' 22 | 23 | # 24 | # The first argument is the directory in which the source files live. 25 | # 26 | srcdir = sys.argv[1] 27 | 28 | # 29 | # The second argument is either "plugin" or "dissectors"; if it's 30 | # "plugin", we build a plugin.c for a plugin, and if it's 31 | # "dissectors", we build a register.c for libwireshark. 32 | # 33 | registertype = sys.argv[2] 34 | if registertype == "plugin" or registertype == "plugin_wtap": 35 | tmp_filename = "plugin.c-tmp" 36 | final_filename = "plugin.c" 37 | cache_filename = None 38 | preamble = """\ 39 | /* 40 | * Do not modify this file. 41 | * 42 | * It is created automatically by Makefile or Makefile.nmake. 43 | */ 44 | """ 45 | elif registertype == "dissectors": 46 | tmp_filename = "register.c-tmp" 47 | final_filename = "register.c" 48 | cache_filename = "register-cache.pkl" 49 | preamble = """\ 50 | /* 51 | * Do not modify this file. 52 | * 53 | * It is created automatically by the "register.c" target in 54 | * epan/dissectors/Makefile or Makefile.nmake using information in 55 | * epan/dissectors/register-cache.pkl. 56 | * 57 | * You can force this file to be regenerated completely by deleting 58 | * it along with epan/dissectors/register-cache.pkl. 59 | */ 60 | """ 61 | else: 62 | print "Unknown output type '%s'" % registertype 63 | sys.exit(1) 64 | 65 | 66 | # 67 | # All subsequent arguments are the files to scan. 68 | # 69 | files = sys.argv[3:] 70 | 71 | # Create the proper list of filenames 72 | filenames = [] 73 | for file in files: 74 | if os.path.isfile(file): 75 | filenames.append(file) 76 | else: 77 | filenames.append(os.path.join(srcdir, file)) 78 | 79 | if len(filenames) < 1: 80 | print "No files found" 81 | sys.exit(1) 82 | 83 | 84 | # Look through all files, applying the regex to each line. 85 | # If the pattern matches, save the "symbol" section to the 86 | # appropriate array. 87 | regs = { 88 | 'proto_reg': [], 89 | 'handoff_reg': [], 90 | 'wtap_register': [], 91 | } 92 | 93 | # For those that don't know Python, r"" indicates a raw string, 94 | # devoid of Python escapes. 95 | proto_regex0 = r"^(?Pproto_register_[_A-Za-z0-9]+)\s*\([^;]+$" 96 | proto_regex1 = r"void\s+(?Pproto_register_[_A-Za-z0-9]+)\s*\([^;]+$" 97 | 98 | handoff_regex0 = r"^(?Pproto_reg_handoff_[_A-Za-z0-9]+)\s*\([^;]+$" 99 | handoff_regex1 = r"void\s+(?Pproto_reg_handoff_[_A-Za-z0-9]+)\s*\([^;]+$" 100 | 101 | wtap_reg_regex0 = r"^(?Pwtap_register_[_A-Za-z0-9]+)\s*\([^;]+$" 102 | wtap_reg_regex1 = r"void\s+(?Pwtap_register_[_A-Za-z0-9]+)\s*\([^;]+$" 103 | 104 | # This table drives the pattern-matching and symbol-harvesting 105 | patterns = [ 106 | ( 'proto_reg', re.compile(proto_regex0) ), 107 | ( 'proto_reg', re.compile(proto_regex1) ), 108 | ( 'handoff_reg', re.compile(handoff_regex0) ), 109 | ( 'handoff_reg', re.compile(handoff_regex1) ), 110 | ( 'wtap_register', re.compile(wtap_reg_regex0) ), 111 | ( 'wtap_register', re.compile(wtap_reg_regex1) ), 112 | ] 113 | 114 | # Open our registration symbol cache 115 | cache = None 116 | if cache_filename: 117 | try: 118 | cache_file = open(cache_filename, 'rb') 119 | cache = pickle.load(cache_file) 120 | cache_file.close() 121 | if not cache.has_key(VERSION_KEY) or cache[VERSION_KEY] != CUR_VERSION: 122 | cache = {VERSION_KEY: CUR_VERSION} 123 | except: 124 | cache = {VERSION_KEY: CUR_VERSION} 125 | 126 | # Grep 127 | for filename in filenames: 128 | file = open(filename) 129 | cur_mtime = os.fstat(file.fileno())[ST_MTIME] 130 | if cache and cache.has_key(filename): 131 | cdict = cache[filename] 132 | if cur_mtime == cdict['mtime']: 133 | # print "Pulling %s from cache" % (filename) 134 | regs['proto_reg'].extend(cdict['proto_reg']) 135 | regs['handoff_reg'].extend(cdict['handoff_reg']) 136 | regs['wtap_register'].extend(cdict['wtap_register']) 137 | file.close() 138 | continue 139 | # We don't have a cache entry 140 | if cache is not None: 141 | cache[filename] = { 142 | 'mtime': cur_mtime, 143 | 'proto_reg': [], 144 | 'handoff_reg': [], 145 | 'wtap_register': [], 146 | } 147 | # print "Searching %s" % (filename) 148 | for line in file.readlines(): 149 | for action in patterns: 150 | regex = action[1] 151 | match = regex.search(line) 152 | if match: 153 | symbol = match.group("symbol") 154 | sym_type = action[0] 155 | regs[sym_type].append(symbol) 156 | if cache is not None: 157 | # print "Caching %s for %s: %s" % (sym_type, filename, symbol) 158 | cache[filename][sym_type].append(symbol) 159 | file.close() 160 | 161 | if cache is not None and cache_filename is not None: 162 | cache_file = open(cache_filename, 'wb') 163 | pickle.dump(cache, cache_file) 164 | cache_file.close() 165 | 166 | # Make sure we actually processed something 167 | if len(regs['proto_reg']) < 1: 168 | print "No protocol registrations found" 169 | sys.exit(1) 170 | 171 | # Sort the lists to make them pretty 172 | regs['proto_reg'].sort() 173 | regs['handoff_reg'].sort() 174 | regs['wtap_register'].sort() 175 | 176 | reg_code = open(tmp_filename, "w") 177 | 178 | reg_code.write(preamble) 179 | 180 | # Make the routine to register all protocols 181 | if registertype == "plugin" or registertype == "plugin_wtap": 182 | reg_code.write(""" 183 | #ifdef HAVE_CONFIG_H 184 | # include "config.h" 185 | #endif 186 | 187 | #include 188 | 189 | #include "moduleinfo.h" 190 | 191 | #ifndef ENABLE_STATIC 192 | G_MODULE_EXPORT const gchar version[] = VERSION; 193 | 194 | /* Start the functions we need for the plugin stuff */ 195 | 196 | G_MODULE_EXPORT void 197 | plugin_register (void) 198 | { 199 | """); 200 | else: 201 | reg_code.write(""" 202 | #include "register.h" 203 | void 204 | register_all_protocols(register_cb cb, gpointer client_data) 205 | { 206 | """); 207 | 208 | for symbol in regs['proto_reg']: 209 | if registertype == "plugin" or registertype == "plugin_wtap": 210 | line = " {extern void %s (void); %s ();}\n" % (symbol, symbol) 211 | else: 212 | line = " {extern void %s (void); if(cb) (*cb)(RA_REGISTER, \"%s\", client_data); %s ();}\n" % (symbol, symbol, symbol) 213 | reg_code.write(line) 214 | 215 | reg_code.write("}\n") 216 | 217 | 218 | # Make the routine to register all protocol handoffs 219 | if registertype == "plugin" or registertype == "plugin_wtap": 220 | reg_code.write(""" 221 | G_MODULE_EXPORT void 222 | plugin_reg_handoff(void) 223 | { 224 | """); 225 | else: 226 | reg_code.write(""" 227 | void 228 | register_all_protocol_handoffs(register_cb cb, gpointer client_data) 229 | { 230 | """); 231 | 232 | for symbol in regs['handoff_reg']: 233 | if registertype == "plugin" or registertype == "plugin_wtap": 234 | line = " {extern void %s (void); %s ();}\n" % (symbol, symbol) 235 | else: 236 | line = " {extern void %s (void); if(cb) (*cb)(RA_HANDOFF, \"%s\", client_data); %s ();}\n" % (symbol, symbol, symbol) 237 | reg_code.write(line) 238 | 239 | reg_code.write("}\n") 240 | 241 | if registertype == "plugin": 242 | reg_code.write("#endif\n"); 243 | elif registertype == "plugin_wtap": 244 | reg_code.write(""" 245 | G_MODULE_EXPORT void 246 | register_wtap_module(void) 247 | { 248 | """); 249 | 250 | for symbol in regs['wtap_register']: 251 | line = " {extern void %s (void); %s ();}\n" % (symbol, symbol) 252 | reg_code.write(line) 253 | 254 | reg_code.write("}\n"); 255 | reg_code.write("#endif\n"); 256 | else: 257 | reg_code.write(""" 258 | static gulong proto_reg_count(void) 259 | { 260 | """); 261 | 262 | line = " return %d;\n" % len(regs['proto_reg']) 263 | reg_code.write(line) 264 | 265 | reg_code.write(""" 266 | } 267 | """); 268 | reg_code.write(""" 269 | static gulong handoff_reg_count(void) 270 | { 271 | """); 272 | 273 | line = " return %d;\n" % len(regs['handoff_reg']) 274 | reg_code.write(line) 275 | 276 | reg_code.write(""" 277 | } 278 | """); 279 | reg_code.write(""" 280 | gulong register_count(void) 281 | { 282 | """); 283 | 284 | line = " return proto_reg_count() + handoff_reg_count();" 285 | reg_code.write(line) 286 | 287 | reg_code.write(""" 288 | }\n 289 | """); 290 | 291 | 292 | # Close the file 293 | reg_code.close() 294 | 295 | # Remove the old final_file if it exists. 296 | try: 297 | os.stat(final_filename) 298 | os.remove(final_filename) 299 | except OSError: 300 | pass 301 | 302 | # Move from tmp file to final file 303 | os.rename(tmp_filename, final_filename) 304 | 305 | 306 | -------------------------------------------------------------------------------- /packet-whatsapp.c: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * WhatsApp protocol dissector 4 | * Written by David Guillen Fandos 5 | * Based on WhatsAPI sources 6 | * 7 | */ 8 | 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #define WHATSAPP_PORT 5222 16 | #define WHATSAPP_PORT_NEW 443 17 | 18 | int whatsapp_data_length(const char * data, int len); 19 | int whatsapp_data_dissect_tree(const char * data, int len, proto_tree *tree, tvbuff_t *tvb, packet_info *pinfo); 20 | 21 | const char * global_imei_whatsapp_1 = 0; 22 | gboolean global_enable_decoding; 23 | int proto_whatsapp = -1; 24 | int message_whatsapp = -1; 25 | int userserver_string = -1; 26 | int tree_whatsapp = -1; 27 | int tree_msg_flags = -1; 28 | static gint ett_whatsapp = -1; 29 | int whatsapp_msg_crypted_payload = -1; 30 | int whatsapp_msg_crypted_payload_mismatch = -1; 31 | int whatsapp_msg_crypted_message = -1; 32 | int whatsapp_msg_compressed_message = -1; 33 | 34 | /* Variables for whatsapp packets */ 35 | int hf_whatsapp_message = -1; 36 | int hf_whatsapp_node = -1; 37 | int hf_whatsapp_nodesize16 = -1; 38 | int hf_whatsapp_nodesize8 = -1; 39 | int hf_whatsapp_attr_key_enc = -1; 40 | int hf_whatsapp_attr_val_enc = -1; 41 | int hf_whatsapp_attr_key_enc_ext = -1; 42 | int hf_whatsapp_attr_val_enc_ext = -1; 43 | int hf_whatsapp_attr_key_plain = -1; 44 | int hf_whatsapp_attr_val_plain = -1; 45 | int hf_whatsapp_attr_crypted = -1; 46 | int hf_whatsapp_attr_compressed = -1; 47 | int hf_whatsapp_attr_flags = -1; 48 | int hf_whatsapp_attribute = -1; 49 | int hf_whatsapp_tag_enc = -1; 50 | int hf_whatsapp_tag_enc_ext = -1; 51 | int hf_whatsapp_tag_plain = -1; 52 | int hf_whatsapp_nvalue_enc = -1; 53 | int hf_whatsapp_nvalue_enc_ext = -1; 54 | int hf_whatsapp_nibble_enc = -1; 55 | int hf_whatsapp_nvalue_plain = -1; 56 | int hf_whatsapp_crypted_hmac_hash = -1; 57 | int hf_whatsapp_userserver = -1; 58 | 59 | const value_string strings_list[]; 60 | const value_string strings_list_ext[]; 61 | 62 | int whatsapp_msg = -1; 63 | 64 | #define MIN_PAKCET_SIZE 4 65 | 66 | typedef struct _wa_userpass_t { 67 | char* username; 68 | char* password; 69 | } wa_userpass_t; 70 | 71 | wa_userpass_t * wa_userpass_uats = NULL; 72 | guint wa_userpass_uats_num = 0; 73 | 74 | UAT_CSTRING_CB_DEF(wa_userpass_uats,username,wa_userpass_t) 75 | UAT_CSTRING_CB_DEF(wa_userpass_uats,password,wa_userpass_t) 76 | 77 | static uat_t * wa_userpass_uat = NULL; 78 | 79 | void waup_free_cb(void* r) { 80 | wa_userpass_t* h = (wa_userpass_t*)r; 81 | 82 | g_free(h->username); 83 | g_free(h->password); 84 | } 85 | void * waup_copy_cb(void* dest, const void* orig, size_t len _U_) { 86 | const wa_userpass_t* o = (const wa_userpass_t*)orig; 87 | wa_userpass_t* d = (wa_userpass_t*)dest; 88 | 89 | d->username = g_strdup(o->username); 90 | d->password = g_strdup(o->password); 91 | return d; 92 | } 93 | 94 | gboolean fld_chk_cb(void* r _U_, const char* p, guint len _U_, const void* u1 _U_, const void* u2 _U_, const char ** err) { 95 | return TRUE; 96 | } 97 | 98 | static guint get_whatsapp_message_len(packet_info *pinfo, tvbuff_t *tvb, int offset) 99 | { 100 | int length = tvb_length(tvb)-offset; 101 | guint8* buffer = tvb_memdup(NULL, tvb, offset, length); 102 | int wa_len = whatsapp_data_length(buffer, length); 103 | g_free(buffer); 104 | return wa_len; 105 | } 106 | 107 | static int dissect_whatsapp_message(tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree) 108 | { 109 | col_set_str (pinfo-> cinfo, COL_PROTOCOL, "WhatsApp XMPP protocol packet"); 110 | col_clear (pinfo->cinfo, COL_INFO); 111 | int r = 0; 112 | 113 | if (tree) { 114 | proto_item *ti = proto_tree_add_item (tree, proto_whatsapp, tvb, 0,-1, ENC_NA); 115 | proto_tree * subtree = proto_item_add_subtree (ti, message_whatsapp); 116 | 117 | int length = tvb_length(tvb); 118 | guint8* buffer = tvb_memdup(NULL, tvb, 0, length); 119 | r = whatsapp_data_dissect_tree(buffer, length, subtree, tvb, pinfo); 120 | g_free(buffer); 121 | } 122 | return r; 123 | } 124 | 125 | static int dissect_whatsapp (tvbuff_t *tvb, packet_info *pinfo, proto_tree *tree, void * u) { 126 | tcp_dissect_pdus(tvb, pinfo, tree, TRUE, MIN_PAKCET_SIZE, 127 | get_whatsapp_message_len, dissect_whatsapp_message); 128 | return get_whatsapp_message_len(pinfo, tvb, 0); 129 | } 130 | void proto_reg_handoff_whatsapp(void); 131 | 132 | void proto_register_whatsapp(void) { 133 | static hf_register_info hf_whatsapp[] = { 134 | { &whatsapp_msg, 135 | { "Message", "whatsapp.message", 136 | FT_NONE, BASE_NONE, 137 | NULL, 0x0, 138 | NULL, HFILL }, 139 | }, 140 | { &whatsapp_msg_crypted_payload, 141 | { "Crypted payload", "whatsapp.payload", 142 | FT_NONE, BASE_NONE, 143 | NULL, 0x0, 144 | NULL, HFILL }, 145 | }, 146 | { &whatsapp_msg_crypted_payload_mismatch, 147 | { "Crypted payload (version mismatch!)", "whatsapp.payload", 148 | FT_NONE, BASE_NONE, 149 | NULL, 0x0, 150 | NULL, HFILL }, 151 | }, 152 | { &whatsapp_msg_crypted_message, 153 | { "Crypted message", "whatsapp.crypted_message", 154 | FT_NONE, BASE_NONE, 155 | NULL, 0x0, 156 | NULL, HFILL }, 157 | }, 158 | { &whatsapp_msg_compressed_message, 159 | { "Crypted message", "whatsapp.compressed_message", 160 | FT_NONE, BASE_NONE, 161 | NULL, 0x0, 162 | NULL, HFILL }, 163 | }, 164 | 165 | { &hf_whatsapp_message, 166 | { "Message size", "whatsapp.message", 167 | FT_UINT24, BASE_DEC, 168 | NULL, 0x0, 169 | NULL, HFILL }, 170 | }, 171 | { &hf_whatsapp_node, 172 | { "Node", "whatsapp.node", 173 | FT_NONE, BASE_NONE, 174 | NULL, 0x0, 175 | NULL, HFILL }, 176 | }, 177 | { &hf_whatsapp_nodesize16, 178 | { "Size", "whatsapp.node", 179 | FT_UINT16, BASE_DEC, 180 | NULL, 0x0, 181 | NULL, HFILL }, 182 | }, 183 | { &hf_whatsapp_attribute, 184 | { "Attribute", "whatsapp.attr", 185 | FT_NONE, BASE_NONE, 186 | NULL, 0x0, 187 | NULL, HFILL }, 188 | }, 189 | { &hf_whatsapp_userserver, 190 | { "User@Server", "whatsapp.userserver", 191 | FT_NONE, BASE_NONE, 192 | NULL, 0x0, 193 | NULL, HFILL }, 194 | }, 195 | { &hf_whatsapp_nodesize8, 196 | { "Size", "whatsapp.node", 197 | FT_UINT8, BASE_DEC, 198 | NULL, 0x0, 199 | NULL, HFILL }, 200 | }, 201 | // Version 1.6 key+val+tag+nval (+exts) 202 | { &hf_whatsapp_attr_key_enc, 203 | { "Key", "whatsapp.keyenc", 204 | FT_UINT8, BASE_DEC, 205 | VALS(strings_list), 0x0, 206 | NULL, HFILL }, 207 | }, 208 | { &hf_whatsapp_attr_val_enc, 209 | { "Value", "whatsapp.valueenc", 210 | FT_UINT8, BASE_DEC, 211 | VALS(strings_list), 0x0, 212 | NULL, HFILL }, 213 | }, 214 | { &hf_whatsapp_attr_key_enc_ext, 215 | { "Key", "whatsapp.keyencext", 216 | FT_UINT16, BASE_DEC, 217 | VALS(strings_list_ext), 0x0, 218 | NULL, HFILL }, 219 | }, 220 | { &hf_whatsapp_attr_val_enc_ext, 221 | { "Value", "whatsapp.valueencext", 222 | FT_UINT16, BASE_DEC, 223 | VALS(strings_list_ext), 0x0, 224 | NULL, HFILL }, 225 | }, 226 | { &hf_whatsapp_tag_enc, 227 | { "Tag", "whatsapp.tagenc", 228 | FT_UINT8, BASE_DEC, 229 | VALS(strings_list), 0x0, 230 | NULL, HFILL }, 231 | }, 232 | { &hf_whatsapp_tag_enc_ext, 233 | { "Tag", "whatsapp.tagencext", 234 | FT_UINT16, BASE_DEC, 235 | VALS(strings_list_ext), 0x0, 236 | NULL, HFILL }, 237 | }, 238 | { &hf_whatsapp_nvalue_enc, 239 | { "Value", "whatsapp.nodevalueenc", 240 | FT_UINT8, BASE_DEC, 241 | VALS(strings_list), 0x0, 242 | NULL, HFILL }, 243 | }, 244 | { &hf_whatsapp_nvalue_enc_ext, 245 | { "Value", "whatsapp.nodevalueencext", 246 | FT_UINT16, BASE_DEC, 247 | VALS(strings_list_ext), 0x0, 248 | NULL, HFILL }, 249 | }, 250 | { &hf_whatsapp_nibble_enc, 251 | { "Nibble encoded number", "whatsapp.nibbleencoded", 252 | FT_NONE, BASE_NONE, 253 | NULL, 0x0, 254 | NULL, HFILL }, 255 | }, 256 | // No encryption 257 | { &hf_whatsapp_attr_key_plain, 258 | { "Key", "whatsapp.keyplain", 259 | FT_STRING, BASE_NONE, 260 | NULL, 0x0, 261 | NULL, HFILL }, 262 | }, 263 | { &hf_whatsapp_attr_val_plain, 264 | { "Value", "whatsapp.valueplain", 265 | FT_STRING, BASE_NONE, 266 | NULL, 0x0, 267 | NULL, HFILL }, 268 | }, 269 | { &hf_whatsapp_tag_plain, 270 | { "Tag", "whatsapp.tagplain", 271 | FT_STRING, BASE_NONE, 272 | NULL, 0x0, 273 | NULL, HFILL }, 274 | }, 275 | { &hf_whatsapp_nvalue_plain, 276 | { "Value", "whatsapp.nodevalueplain", 277 | FT_STRING, BASE_NONE, 278 | NULL, 0x0, 279 | NULL, HFILL }, 280 | }, 281 | 282 | { &hf_whatsapp_attr_flags, 283 | { "Flags", "whatsapp.flags", 284 | FT_UINT8, BASE_HEX, 285 | NULL, 0xF0, 286 | NULL, HFILL }, 287 | }, 288 | { &hf_whatsapp_attr_crypted, 289 | { "Crypted", "whatsapp.crypted", 290 | FT_BOOLEAN, BASE_NONE, 291 | TFS(&tfs_set_notset), 0x0, 292 | NULL, HFILL }, 293 | }, 294 | { &hf_whatsapp_attr_compressed, 295 | { "Compressed", "whatsapp.compressed", 296 | FT_BOOLEAN, BASE_NONE, 297 | TFS(&tfs_set_notset), 0x0, 298 | NULL, HFILL }, 299 | }, 300 | { &hf_whatsapp_crypted_hmac_hash, 301 | { "HMAC-SHA1", "whatsapp.crypt_hash", 302 | FT_UINT32, BASE_HEX, 303 | NULL, 0x0, 304 | NULL, HFILL }, 305 | } 306 | }; 307 | static gint *ett_whatsapp_arr[] = { /* protocol subtree array */ 308 | &message_whatsapp, 309 | &tree_whatsapp, 310 | &userserver_string, 311 | &tree_msg_flags 312 | }; 313 | proto_whatsapp = proto_register_protocol( 314 | "WhatsApp XMPP protocol", "WhatsApp", "whatsapp"); 315 | proto_register_field_array (proto_whatsapp, hf_whatsapp, array_length (hf_whatsapp)); 316 | proto_register_subtree_array (ett_whatsapp_arr, array_length (ett_whatsapp_arr)); 317 | 318 | struct pref_module * whatsapp_module = 319 | (struct pref_module *)prefs_register_protocol(proto_whatsapp, proto_reg_handoff_whatsapp); 320 | prefs_register_string_preference(whatsapp_module, "imei1", 321 | "Phone IMEI (1)", 322 | "Telephone IMEI to use as key", 323 | &global_imei_whatsapp_1); 324 | 325 | static uat_field_t wa_auth_uats_flds[] = { 326 | UAT_FLD_CSTRING_OTHER(wa_userpass_uats, username, "Phone", fld_chk_cb, "Whatapp phone number username"), 327 | UAT_FLD_CSTRING_OTHER(wa_userpass_uats, password, "Password", fld_chk_cb, "Whatapp account password (base64 encoded)"), 328 | UAT_END_FIELDS 329 | }; 330 | 331 | wa_userpass_uat = uat_new("WhatsApp accounts", 332 | sizeof(wa_userpass_t), 333 | "wauserpasstable", /* filename */ 334 | TRUE, /* from_profile */ 335 | &wa_userpass_uats, /* data_ptr */ 336 | &wa_userpass_uats_num, /* numitems_ptr */ 337 | UAT_AFFECTS_DISSECTION, /* affects dissection of packets, but not set of named fields */ 338 | "WAUSERPASS_DOC", 339 | waup_copy_cb, 340 | NULL, 341 | waup_free_cb, 342 | NULL, 343 | NULL, 344 | wa_auth_uats_flds); 345 | 346 | prefs_register_uat_preference(whatsapp_module, "cfg", 347 | "Whatsapp user/pass list", 348 | "A table for phones and passwords to decrypt conversations", 349 | wa_userpass_uat); 350 | 351 | prefs_register_bool_preference(whatsapp_module, "enable_decoding", 352 | "Enable packet decoding", 353 | "Decodes network traffic if possible", 354 | &global_enable_decoding); 355 | } 356 | 357 | void proto_reg_handoff_whatsapp(void) { 358 | static dissector_handle_t whatsapp_handle; 359 | whatsapp_handle = new_create_dissector_handle (dissect_whatsapp, proto_whatsapp); 360 | dissector_add_uint ("tcp.port", WHATSAPP_PORT, whatsapp_handle); 361 | dissector_add_uint ("tcp.port", WHATSAPP_PORT_NEW, whatsapp_handle); 362 | } 363 | 364 | 365 | 366 | const value_string strings_list[] = { 367 | { 0, "" }, 368 | { 1, "" }, 369 | { 2, "" }, 370 | { 3, "account" }, 371 | { 4, "ack" }, 372 | { 5, "action" }, 373 | { 6, "active" }, 374 | { 7, "add" }, 375 | { 8, "after" }, 376 | { 9, "all" }, 377 | { 10, "allow" }, 378 | { 11, "apple" }, 379 | { 12, "audio" }, 380 | { 13, "auth" }, 381 | { 14, "author" }, 382 | { 15, "available" }, 383 | { 16, "bad-protocol" }, 384 | { 17, "bad-request" }, 385 | { 18, "before" }, 386 | { 19, "bits" }, 387 | { 20, "body" }, 388 | { 21, "broadcast" }, 389 | { 22, "cancel" }, 390 | { 23, "category" }, 391 | { 24, "challenge" }, 392 | { 25, "chat" }, 393 | { 26, "clean" }, 394 | { 27, "code" }, 395 | { 28, "composing" }, 396 | { 29, "config" }, 397 | { 30, "contacts" }, 398 | { 31, "count" }, 399 | { 32, "create" }, 400 | { 33, "creation" }, 401 | { 34, "debug" }, 402 | { 35, "default" }, 403 | { 36, "delete" }, 404 | { 37, "delivery" }, 405 | { 38, "delta" }, 406 | { 39, "deny" }, 407 | { 40, "digest" }, 408 | { 41, "dirty" }, 409 | { 42, "duplicate" }, 410 | { 43, "elapsed" }, 411 | { 44, "enable" }, 412 | { 45, "encoding" }, 413 | { 46, "encrypt" }, 414 | { 47, "error" }, 415 | { 48, "event" }, 416 | { 49, "expiration" }, 417 | { 50, "expired" }, 418 | { 51, "fail" }, 419 | { 52, "failure" }, 420 | { 53, "false" }, 421 | { 54, "favorites" }, 422 | { 55, "feature" }, 423 | { 56, "features" }, 424 | { 57, "feature-not-implemented" }, 425 | { 58, "field" }, 426 | { 59, "file" }, 427 | { 60, "filehash" }, 428 | { 61, "first" }, 429 | { 62, "free" }, 430 | { 63, "from" }, 431 | { 64, "g.us" }, 432 | { 65, "gcm" }, 433 | { 66, "get" }, 434 | { 67, "google" }, 435 | { 68, "group" }, 436 | { 69, "groups" }, 437 | { 70, "groups_v2" }, 438 | { 71, "http://etherx.jabber.org/streams" }, 439 | { 72, "http://jabber.org/protocol/chatstates" }, 440 | { 73, "ib" }, 441 | { 74, "id" }, 442 | { 75, "image" }, 443 | { 76, "img" }, 444 | { 77, "index" }, 445 | { 78, "internal-server-error" }, 446 | { 79, "ip" }, 447 | { 80, "iq" }, 448 | { 81, "item-not-found" }, 449 | { 82, "item" }, 450 | { 83, "jabber:iq:last" }, 451 | { 84, "jabber:iq:privacy" }, 452 | { 85, "jabber:x:event" }, 453 | { 86, "jid" }, 454 | { 87, "kind" }, 455 | { 88, "last" }, 456 | { 89, "leave" }, 457 | { 90, "list" }, 458 | { 91, "max" }, 459 | { 92, "mechanism" }, 460 | { 93, "media" }, 461 | { 94, "message_acks" }, 462 | { 95, "message" }, 463 | { 96, "method" }, 464 | { 97, "microsoft" }, 465 | { 98, "mimetype" }, 466 | { 99, "missing" }, 467 | { 100, "modify" }, 468 | { 101, "msg" }, 469 | { 102, "mute" }, 470 | { 103, "name" }, 471 | { 104, "nokia" }, 472 | { 105, "none" }, 473 | { 106, "not-acceptable" }, 474 | { 107, "not-allowed" }, 475 | { 108, "not-authorized" }, 476 | { 109, "notification" }, 477 | { 110, "notify" }, 478 | { 111, "off" }, 479 | { 112, "offline" }, 480 | { 113, "order" }, 481 | { 114, "owner" }, 482 | { 115, "owning" }, 483 | { 116, "p_o" }, 484 | { 117, "p_t" }, 485 | { 118, "paid" }, 486 | { 119, "participant" }, 487 | { 120, "participants" }, 488 | { 121, "participating" }, 489 | { 122, "paused" }, 490 | { 123, "picture" }, 491 | { 124, "pin" }, 492 | { 125, "ping" }, 493 | { 126, "pkmsg" }, 494 | { 127, "platform" }, 495 | { 128, "port" }, 496 | { 129, "presence" }, 497 | { 130, "preview" }, 498 | { 131, "probe" }, 499 | { 132, "prop" }, 500 | { 133, "props" }, 501 | { 134, "qcount" }, 502 | { 135, "query" }, 503 | { 136, "raw" }, 504 | { 137, "read" }, 505 | { 138, "readreceipts" }, 506 | { 139, "reason" }, 507 | { 140, "receipt" }, 508 | { 141, "relay" }, 509 | { 142, "remote-server-timeout" }, 510 | { 143, "remove" }, 511 | { 144, "request" }, 512 | { 145, "required" }, 513 | { 146, "resource-constraint" }, 514 | { 147, "resource" }, 515 | { 148, "response" }, 516 | { 149, "result" }, 517 | { 150, "retry" }, 518 | { 151, "rim" }, 519 | { 152, "s_o" }, 520 | { 153, "s_t" }, 521 | { 154, "s.us" }, 522 | { 155, "s.whatsapp.net" }, 523 | { 156, "seconds" }, 524 | { 157, "server-error" }, 525 | { 158, "server" }, 526 | { 159, "service-unavailable" }, 527 | { 160, "set" }, 528 | { 161, "show" }, 529 | { 162, "silent" }, 530 | { 163, "size" }, 531 | { 164, "skmsg" }, 532 | { 165, "stat" }, 533 | { 166, "state" }, 534 | { 167, "status" }, 535 | { 168, "stream:error" }, 536 | { 169, "stream:features" }, 537 | { 170, "subject" }, 538 | { 171, "subscribe" }, 539 | { 172, "success" }, 540 | { 173, "sync" }, 541 | { 174, "t" }, 542 | { 175, "text" }, 543 | { 176, "timeout" }, 544 | { 177, "timestamp" }, 545 | { 178, "tizen" }, 546 | { 179, "to" }, 547 | { 180, "true" }, 548 | { 181, "type" }, 549 | { 182, "unavailable" }, 550 | { 183, "unsubscribe" }, 551 | { 184, "upgrade" }, 552 | { 185, "uri" }, 553 | { 186, "url" }, 554 | { 187, "urn:ietf:params:xml:ns:xmpp-sasl" }, 555 | { 188, "urn:ietf:params:xml:ns:xmpp-stanzas" }, 556 | { 189, "urn:ietf:params:xml:ns:xmpp-streams" }, 557 | { 190, "urn:xmpp:ping" }, 558 | { 191, "urn:xmpp:whatsapp:account" }, 559 | { 192, "urn:xmpp:whatsapp:dirty" }, 560 | { 193, "urn:xmpp:whatsapp:mms" }, 561 | { 194, "urn:xmpp:whatsapp:push" }, 562 | { 195, "urn:xmpp:whatsapp" }, 563 | { 196, "user" }, 564 | { 197, "user-not-found" }, 565 | { 198, "v" }, 566 | { 199, "value" }, 567 | { 200, "version" }, 568 | { 201, "voip" }, 569 | { 202, "w:g" }, 570 | { 203, "w:p:r" }, 571 | { 204, "w:p" }, 572 | { 205, "w:profile:picture" }, 573 | { 206, "w" }, 574 | { 207, "wait" }, 575 | { 208, "WAUTH-2" }, 576 | { 209, "xmlns:stream" }, 577 | { 210, "xmlns" }, 578 | { 211, "1" }, 579 | { 212, "chatstate" }, 580 | { 213, "crypto" }, 581 | { 214, "phash" }, 582 | { 215, "enc" }, 583 | { 216, "class" }, 584 | { 217, "off_cnt" }, 585 | { 218, "w:g2" }, 586 | { 219, "promote" }, 587 | { 220, "demote" }, 588 | { 221, "creator" }, 589 | { 222, "background" }, 590 | { 223, "backoff" }, 591 | { 224, "chunked" }, 592 | { 225, "context" }, 593 | { 226, "full" }, 594 | { 227, "in" }, 595 | { 228, "interactive" }, 596 | { 229, "out" }, 597 | { 230, "registration" }, 598 | { 231, "sid" }, 599 | { 232, "urn:xmpp:whatsapp:sync" }, 600 | { 233, "flt" }, 601 | { 234, "s16" }, 602 | { 235, "u8" }, 603 | { 236, "Extended-Dict-1"}, 604 | { 237, "" }, 605 | { 238, "" }, 606 | { 239, "" }, 607 | { 240, "" }, 608 | { 241, "" }, 609 | { 242, "" }, 610 | { 243, "" }, 611 | { 244, "" }, 612 | { 245, "" }, 613 | { 246, "" }, 614 | { 247, "" }, 615 | { 248, "" }, 616 | { 249, "" }, 617 | { 250, "" }, 618 | { 251, "" }, 619 | { 252, "" }, 620 | { 253, "" }, 621 | { 254, "" }, 622 | { 255, "" }, 623 | {0,NULL} 624 | }; 625 | 626 | const value_string strings_list_ext[] = { 627 | { 0, "adpcm" }, 628 | { 1, "amrnb" }, 629 | { 2, "amrwb" }, 630 | { 3, "mp3" }, 631 | { 4, "pcm" }, 632 | { 5, "qcelp" }, 633 | { 6, "wma" }, 634 | { 7, "h263" }, 635 | { 8, "h264" }, 636 | { 9, "jpeg" }, 637 | { 10, "mpeg4" }, 638 | { 11, "wmv" }, 639 | { 12, "audio/3gpp" }, 640 | { 13, "audio/aac" }, 641 | { 14, "audio/amr" }, 642 | { 15, "audio/mp4" }, 643 | { 16, "audio/mpeg" }, 644 | { 17, "audio/ogg" }, 645 | { 18, "audio/qcelp" }, 646 | { 19, "audio/wav" }, 647 | { 20, "audio/webm" }, 648 | { 21, "audio/x-caf" }, 649 | { 22, "audio/x-ms-wma" }, 650 | { 23, "image/gif" }, 651 | { 24, "image/jpeg" }, 652 | { 25, "image/png" }, 653 | { 26, "video/3gpp" }, 654 | { 27, "video/avi" }, 655 | { 28, "video/mp4" }, 656 | { 29, "video/mpeg" }, 657 | { 30, "video/quicktime" }, 658 | { 31, "video/x-flv" }, 659 | { 32, "video/x-ms-asf" }, 660 | { 33, "302" }, 661 | { 34, "400" }, 662 | { 35, "401" }, 663 | { 36, "402" }, 664 | { 37, "403" }, 665 | { 38, "404" }, 666 | { 39, "405" }, 667 | { 40, "406" }, 668 | { 41, "407" }, 669 | { 42, "409" }, 670 | { 43, "410" }, 671 | { 44, "500" }, 672 | { 45, "501" }, 673 | { 46, "503" }, 674 | { 47, "504" }, 675 | { 48, "abitrate" }, 676 | { 49, "acodec" }, 677 | { 50, "app_uptime" }, 678 | { 51, "asampfmt" }, 679 | { 52, "asampfreq" }, 680 | { 53, "clear" }, 681 | { 54, "conflict" }, 682 | { 55, "conn_no_nna" }, 683 | { 56, "cost" }, 684 | { 57, "currency" }, 685 | { 58, "duration" }, 686 | { 59, "extend" }, 687 | { 60, "fps" }, 688 | { 61, "g_notify" }, 689 | { 62, "g_sound" }, 690 | { 63, "gone" }, 691 | { 64, "google_play" }, 692 | { 65, "hash" }, 693 | { 66, "height" }, 694 | { 67, "invalid" }, 695 | { 68, "jid-malformed" }, 696 | { 69, "latitude" }, 697 | { 70, "lc" }, 698 | { 71, "lg" }, 699 | { 72, "live" }, 700 | { 73, "location" }, 701 | { 74, "log" }, 702 | { 75, "longitude" }, 703 | { 76, "max_groups" }, 704 | { 77, "max_participants" }, 705 | { 78, "max_subject" }, 706 | { 79, "mode" }, 707 | { 80, "napi_version" }, 708 | { 81, "normalize" }, 709 | { 82, "orighash" }, 710 | { 83, "origin" }, 711 | { 84, "passive" }, 712 | { 85, "password" }, 713 | { 86, "played" }, 714 | { 87, "policy-violation" }, 715 | { 88, "pop_mean_time" }, 716 | { 89, "pop_plus_minus" }, 717 | { 90, "price" }, 718 | { 91, "pricing" }, 719 | { 92, "redeem" }, 720 | { 93, "Replaced by new connection" }, 721 | { 94, "resume" }, 722 | { 95, "signature" }, 723 | { 96, "sound" }, 724 | { 97, "source" }, 725 | { 98, "system-shutdown" }, 726 | { 99, "username" }, 727 | { 100, "vbitrate" }, 728 | { 101, "vcard" }, 729 | { 102, "vcodec" }, 730 | { 103, "video" }, 731 | { 104, "width" }, 732 | { 105, "xml-not-well-formed" }, 733 | { 106, "checkmarks" }, 734 | { 107, "image_max_edge" }, 735 | { 108, "image_max_kbytes" }, 736 | { 109, "image_quality" }, 737 | { 110, "ka" }, 738 | { 111, "ka_grow" }, 739 | { 112, "ka_shrink" }, 740 | { 113, "newmedia" }, 741 | { 114, "library" }, 742 | { 115, "caption" }, 743 | { 116, "forward" }, 744 | { 117, "c0" }, 745 | { 118, "c1" }, 746 | { 119, "c2" }, 747 | { 120, "c3" }, 748 | { 121, "clock_skew" }, 749 | { 122, "cts" }, 750 | { 123, "k0" }, 751 | { 124, "k1" }, 752 | { 125, "login_rtt" }, 753 | { 126, "m_id" }, 754 | { 127, "nna_msg_rtt" }, 755 | { 128, "nna_no_off_count" }, 756 | { 129, "nna_offline_ratio" }, 757 | { 130, "nna_push_rtt" }, 758 | { 131, "no_nna_con_count" }, 759 | { 132, "off_msg_rtt" }, 760 | { 133, "on_msg_rtt" }, 761 | { 134, "stat_name" }, 762 | { 135, "sts" }, 763 | { 136, "suspect_conn" }, 764 | { 137, "lists" }, 765 | { 138, "self" }, 766 | { 139, "qr" }, 767 | { 140, "web" }, 768 | { 141, "w:b" }, 769 | { 142, "recipient" }, 770 | { 143, "w:stats" }, 771 | { 144, "forbidden" }, 772 | { 145, "max_list_recipients" }, 773 | { 146, "en-AU" }, 774 | { 147, "en-GB" }, 775 | { 148, "es-MX" }, 776 | { 149, "pt-PT" }, 777 | { 150, "zh-Hans" }, 778 | { 151, "zh-Hant" }, 779 | { 152, "relayelection" }, 780 | { 153, "relaylatency" }, 781 | { 154, "interruption" }, 782 | { 155, "Bell.caf" }, 783 | { 156, "Boing.caf" }, 784 | { 157, "Glass.caf" }, 785 | { 158, "Harp.caf" }, 786 | { 159, "TimePassing.caf" }, 787 | { 160, "Tri-tone.caf" }, 788 | { 161, "Xylophone.caf" }, 789 | { 162, "aurora.m4r" }, 790 | { 163, "bamboo.m4r" }, 791 | { 164, "chord.m4r" }, 792 | { 165, "circles.m4r" }, 793 | { 166, "complete.m4r" }, 794 | { 167, "hello.m4r" }, 795 | { 168, "input.m4r" }, 796 | { 169, "keys.m4r" }, 797 | { 170, "note.m4r" }, 798 | { 171, "popcorn.m4r" }, 799 | { 172, "pulse.m4r" }, 800 | { 173, "synth.m4r" }, 801 | { 174, "Apex.m4r" }, 802 | { 175, "Beacon.m4r" }, 803 | { 176, "Bulletin.m4r" }, 804 | { 177, "By The Seaside.m4r" }, 805 | { 178, "Chimes.m4r" }, 806 | { 179, "Circuit.m4r" }, 807 | { 180, "Constellation.m4r" }, 808 | { 181, "Cosmic.m4r" }, 809 | { 182, "Crystals.m4r" }, 810 | { 183, "Hillside.m4r" }, 811 | { 184, "Illuminate.m4r" }, 812 | { 185, "Night Owl.m4r" }, 813 | { 186, "Opening.m4r" }, 814 | { 187, "Playtime.m4r" }, 815 | { 188, "Presto.m4r" }, 816 | { 189, "Radar.m4r" }, 817 | { 190, "Radiate.m4r" }, 818 | { 191, "Ripples.m4r" }, 819 | { 192, "Sencha.m4r" }, 820 | { 193, "Signal.m4r" }, 821 | { 194, "Silk.m4r" }, 822 | { 195, "Slow Rise.m4r" }, 823 | { 196, "Stargaze.m4r" }, 824 | { 197, "Summit.m4r" }, 825 | { 198, "Twinkle.m4r" }, 826 | { 199, "Uplift.m4r" }, 827 | { 200, "Waves.m4r" }, 828 | { 201, "eligible" }, 829 | { 202, "planned" }, 830 | { 203, "current" }, 831 | { 204, "future" }, 832 | { 205, "disable" }, 833 | { 206, "expire" }, 834 | { 207, "start" }, 835 | { 208, "stop" }, 836 | { 209, "accuracy" }, 837 | { 210, "speed" }, 838 | { 211, "bearing" }, 839 | { 212, "recording" }, 840 | { 213, "key" }, 841 | { 214, "identity" }, 842 | { 215, "w:gp2" }, 843 | { 216, "admin" }, 844 | { 217, "locked" }, 845 | { 218, "unlocked" }, 846 | { 219, "new" }, 847 | { 220, "battery" }, 848 | { 221, "archive" }, 849 | { 222, "adm" }, 850 | { 223, "plaintext_size" }, 851 | { 224, "plaintext_disabled" }, 852 | { 225, "plaintext_reenable_threshold" }, 853 | { 226, "compressed_size" }, 854 | { 227, "delivered" }, 855 | { 228, "everyone" }, 856 | { 229, "transport" }, 857 | { 230, "mspes" }, 858 | { 231, "e2e_groups" }, 859 | { 232, "e2e_images" }, 860 | { 233, "encr_media" }, 861 | { 234, "encrypt_v2" }, 862 | { 235, "encrypt_image" }, 863 | { 236, "encrypt_sends_push" }, 864 | { 237, "force_long_connect" }, 865 | { 238, "audio_opus" }, 866 | { 239, "video_max_edge" }, 867 | { 240, "call-id" }, 868 | { 241, "call" }, 869 | { 242, "preaccept" }, 870 | { 243, "accept" }, 871 | { 244, "offer" }, 872 | { 245, "reject" }, 873 | { 246, "busy" }, 874 | { 247, "te" }, 875 | { 248, "terminate" }, 876 | { 249, "begin" }, 877 | { 250, "end" }, 878 | { 251, "opus" }, 879 | { 252, "rtt" }, 880 | { 253, "token" }, 881 | { 254, "priority" }, 882 | { 255, "p2p" }, 883 | { 256, "rate" }, 884 | { 257, "amr" }, 885 | { 258, "ptt" }, 886 | { 259, "srtp" }, 887 | { 260, "os" }, 888 | { 261, "browser" }, 889 | { 262, "encrypt_group_gen2" }, 890 | {0,NULL} 891 | }; 892 | 893 | 894 | 895 | -------------------------------------------------------------------------------- /tinfl.c: -------------------------------------------------------------------------------- 1 | /* tinfl.c v1.11 - public domain inflate with zlib header parsing/adler32 checking (inflate-only subset of miniz.c) 2 | See "unlicense" statement at the end of this file. 3 | Rich Geldreich , last updated May 20, 2011 4 | Implements RFC 1950: http://www.ietf.org/rfc/rfc1950.txt and RFC 1951: http://www.ietf.org/rfc/rfc1951.txt 5 | 6 | The entire decompressor coroutine is implemented in tinfl_decompress(). The other functions are optional high-level helpers. 7 | */ 8 | #ifndef TINFL_HEADER_INCLUDED 9 | #define TINFL_HEADER_INCLUDED 10 | 11 | #include 12 | #include 13 | 14 | typedef unsigned char mz_uint8; 15 | typedef signed short mz_int16; 16 | typedef unsigned short mz_uint16; 17 | typedef unsigned int mz_uint32; 18 | typedef unsigned int mz_uint; 19 | typedef unsigned long long mz_uint64; 20 | 21 | #if defined(_M_IX86) || defined(_M_X64) 22 | // Set MINIZ_USE_UNALIGNED_LOADS_AND_STORES to 1 if integer loads and stores to unaligned addresses are acceptable on the target platform (slightly faster). 23 | #define MINIZ_USE_UNALIGNED_LOADS_AND_STORES 1 24 | // Set MINIZ_LITTLE_ENDIAN to 1 if the processor is little endian. 25 | #define MINIZ_LITTLE_ENDIAN 1 26 | #endif 27 | 28 | #if defined(_WIN64) || defined(__MINGW64__) || defined(_LP64) || defined(__LP64__) 29 | // Set MINIZ_HAS_64BIT_REGISTERS to 1 if the processor has 64-bit general purpose registers (enables 64-bit bitbuffer in inflator) 30 | #define MINIZ_HAS_64BIT_REGISTERS 1 31 | #endif 32 | 33 | // Works around MSVC's spammy "warning C4127: conditional expression is constant" message. 34 | #ifdef _MSC_VER 35 | #define MZ_MACRO_END while (0, 0) 36 | #else 37 | #define MZ_MACRO_END while (0) 38 | #endif 39 | 40 | // Decompression flags used by tinfl_decompress(). 41 | // TINFL_FLAG_PARSE_ZLIB_HEADER: If set, the input has a valid zlib header and ends with an adler32 checksum (it's a valid zlib stream). Otherwise, the input is a raw deflate stream. 42 | // TINFL_FLAG_HAS_MORE_INPUT: If set, there are more input bytes available beyond the end of the supplied input buffer. If clear, the input buffer contains all remaining input. 43 | // TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF: If set, the output buffer is large enough to hold the entire decompressed stream. If clear, the output buffer is at least the size of the dictionary (typically 32KB). 44 | // TINFL_FLAG_COMPUTE_ADLER32: Force adler-32 checksum computation of the decompressed bytes. 45 | enum 46 | { 47 | TINFL_FLAG_PARSE_ZLIB_HEADER = 1, 48 | TINFL_FLAG_HAS_MORE_INPUT = 2, 49 | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF = 4, 50 | TINFL_FLAG_COMPUTE_ADLER32 = 8 51 | }; 52 | 53 | // High level decompression functions: 54 | // tinfl_decompress_mem_to_heap() decompresses a block in memory to a heap block allocated via malloc(). 55 | // On entry: 56 | // pSrc_buf, src_buf_len: Pointer and size of the Deflate or zlib source data to decompress. 57 | // On return: 58 | // Function returns a pointer to the decompressed data, or NULL on failure. 59 | // *pOut_len will be set to the decompressed data's size, which could be larger than src_buf_len on uncompressible data. 60 | // The caller must free() the returned block when it's no longer needed. 61 | void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags); 62 | 63 | // tinfl_decompress_mem_to_mem() decompresses a block in memory to another block in memory. 64 | // Returns TINFL_DECOMPRESS_MEM_TO_MEM_FAILED on failure, or the number of bytes written on success. 65 | #define TINFL_DECOMPRESS_MEM_TO_MEM_FAILED ((size_t)(-1)) 66 | size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); 67 | 68 | // tinfl_decompress_mem_to_callback() decompresses a block in memory to an internal 32KB buffer, and a user provided callback function will be called to flush the buffer. 69 | // Returns 1 on success or 0 on failure. 70 | typedef int (*tinfl_put_buf_func_ptr)(const void* pBuf, int len, void *pUser); 71 | int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags); 72 | 73 | struct tinfl_decompressor_tag; typedef struct tinfl_decompressor_tag tinfl_decompressor; 74 | 75 | // Max size of LZ dictionary. 76 | #define TINFL_LZ_DICT_SIZE 32768 77 | 78 | // Return status. 79 | typedef enum 80 | { 81 | TINFL_STATUS_BAD_PARAM = -3, 82 | TINFL_STATUS_ADLER32_MISMATCH = -2, 83 | TINFL_STATUS_FAILED = -1, 84 | TINFL_STATUS_DONE = 0, 85 | TINFL_STATUS_NEEDS_MORE_INPUT = 1, 86 | TINFL_STATUS_HAS_MORE_OUTPUT = 2 87 | } tinfl_status; 88 | 89 | // Initializes the decompressor to its initial state. 90 | #define tinfl_init(r) do { (r)->m_state = 0; } MZ_MACRO_END 91 | #define tinfl_get_adler32(r) (r)->m_check_adler32 92 | 93 | // Main low-level decompressor coroutine function. This is the only function actually needed for decompression. All the other functions are just high-level helpers for improved usability. 94 | // This is a universal API, i.e. it can be used as a building block to build any desired higher level decompression API. In the limit case, it can be called once per every byte input or output. 95 | tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags); 96 | 97 | // Internal/private bits follow. 98 | enum 99 | { 100 | TINFL_MAX_HUFF_TABLES = 3, TINFL_MAX_HUFF_SYMBOLS_0 = 288, TINFL_MAX_HUFF_SYMBOLS_1 = 32, TINFL_MAX_HUFF_SYMBOLS_2 = 19, 101 | TINFL_FAST_LOOKUP_BITS = 10, TINFL_FAST_LOOKUP_SIZE = 1 << TINFL_FAST_LOOKUP_BITS 102 | }; 103 | 104 | typedef struct 105 | { 106 | mz_uint8 m_code_size[TINFL_MAX_HUFF_SYMBOLS_0]; 107 | mz_int16 m_look_up[TINFL_FAST_LOOKUP_SIZE], m_tree[TINFL_MAX_HUFF_SYMBOLS_0 * 2]; 108 | } tinfl_huff_table; 109 | 110 | #if MINIZ_HAS_64BIT_REGISTERS 111 | #define TINFL_USE_64BIT_BITBUF 1 112 | #endif 113 | 114 | #if TINFL_USE_64BIT_BITBUF 115 | typedef mz_uint64 tinfl_bit_buf_t; 116 | #define TINFL_BITBUF_SIZE (64) 117 | #else 118 | typedef mz_uint32 tinfl_bit_buf_t; 119 | #define TINFL_BITBUF_SIZE (32) 120 | #endif 121 | 122 | struct tinfl_decompressor_tag 123 | { 124 | mz_uint32 m_state, m_num_bits, m_zhdr0, m_zhdr1, m_z_adler32, m_final, m_type, m_check_adler32, m_dist, m_counter, m_num_extra, m_table_sizes[TINFL_MAX_HUFF_TABLES]; 125 | tinfl_bit_buf_t m_bit_buf; 126 | size_t m_dist_from_out_buf_start; 127 | tinfl_huff_table m_tables[TINFL_MAX_HUFF_TABLES]; 128 | mz_uint8 m_raw_header[4], m_len_codes[TINFL_MAX_HUFF_SYMBOLS_0 + TINFL_MAX_HUFF_SYMBOLS_1 + 137]; 129 | }; 130 | 131 | #endif // #ifdef TINFL_HEADER_INCLUDED 132 | 133 | // ------------------- End of Header: Implementation follows. (If you only want the header, define MINIZ_HEADER_FILE_ONLY.) 134 | 135 | #ifndef TINFL_HEADER_FILE_ONLY 136 | 137 | #include 138 | 139 | // MZ_MALLOC, etc. are only used by the optional high-level helper functions. 140 | #ifdef MINIZ_NO_MALLOC 141 | #define MZ_MALLOC(x) NULL 142 | #define MZ_FREE(x) x, ((void)0) 143 | #define MZ_REALLOC(p, x) NULL 144 | #else 145 | #define MZ_MALLOC(x) malloc(x) 146 | #define MZ_FREE(x) free(x) 147 | #define MZ_REALLOC(p, x) realloc(p, x) 148 | #endif 149 | 150 | #define MZ_MAX(a,b) (((a)>(b))?(a):(b)) 151 | #define MZ_MIN(a,b) (((a)<(b))?(a):(b)) 152 | #define MZ_CLEAR_OBJ(obj) memset(&(obj), 0, sizeof(obj)) 153 | 154 | #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES && MINIZ_LITTLE_ENDIAN 155 | #define MZ_READ_LE16(p) *((const mz_uint16 *)(p)) 156 | #define MZ_READ_LE32(p) *((const mz_uint32 *)(p)) 157 | #else 158 | #define MZ_READ_LE16(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U)) 159 | #define MZ_READ_LE32(p) ((mz_uint32)(((const mz_uint8 *)(p))[0]) | ((mz_uint32)(((const mz_uint8 *)(p))[1]) << 8U) | ((mz_uint32)(((const mz_uint8 *)(p))[2]) << 16U) | ((mz_uint32)(((const mz_uint8 *)(p))[3]) << 24U)) 160 | #endif 161 | 162 | #define TINFL_MEMCPY(d, s, l) memcpy(d, s, l) 163 | #define TINFL_MEMSET(p, c, l) memset(p, c, l) 164 | 165 | #define TINFL_CR_BEGIN switch(r->m_state) { case 0: 166 | #define TINFL_CR_RETURN(state_index, result) do { status = result; r->m_state = state_index; goto common_exit; case state_index:; } MZ_MACRO_END 167 | #define TINFL_CR_RETURN_FOREVER(state_index, result) do { for ( ; ; ) { TINFL_CR_RETURN(state_index, result); } } MZ_MACRO_END 168 | #define TINFL_CR_FINISH } 169 | 170 | // TODO: If the caller has indicated that there's no more input, and we attempt to read beyond the input buf, then something is wrong with the input because the inflator never 171 | // reads ahead more than it needs to. Currently TINFL_GET_BYTE() pads the end of the stream with 0's in this scenario. 172 | #define TINFL_GET_BYTE(state_index, c) do { \ 173 | if (pIn_buf_cur >= pIn_buf_end) { \ 174 | for ( ; ; ) { \ 175 | if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) { \ 176 | TINFL_CR_RETURN(state_index, TINFL_STATUS_NEEDS_MORE_INPUT); \ 177 | if (pIn_buf_cur < pIn_buf_end) { \ 178 | c = *pIn_buf_cur++; \ 179 | break; \ 180 | } \ 181 | } else { \ 182 | c = 0; \ 183 | break; \ 184 | } \ 185 | } \ 186 | } else c = *pIn_buf_cur++; } MZ_MACRO_END 187 | 188 | #define TINFL_NEED_BITS(state_index, n) do { mz_uint c; TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; } while (num_bits < (mz_uint)(n)) 189 | #define TINFL_SKIP_BITS(state_index, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END 190 | #define TINFL_GET_BITS(state_index, b, n) do { if (num_bits < (mz_uint)(n)) { TINFL_NEED_BITS(state_index, n); } b = bit_buf & ((1 << (n)) - 1); bit_buf >>= (n); num_bits -= (n); } MZ_MACRO_END 191 | 192 | // TINFL_HUFF_BITBUF_FILL() is only used rarely, when the number of bytes remaining in the input buffer falls below 2. 193 | // It reads just enough bytes from the input stream that are needed to decode the next Huffman code (and absolutely no more). It works by trying to fully decode a 194 | // Huffman code by using whatever bits are currently present in the bit buffer. If this fails, it reads another byte, and tries again until it succeeds or until the 195 | // bit buffer contains >=15 bits (deflate's max. Huffman code size). 196 | #define TINFL_HUFF_BITBUF_FILL(state_index, pHuff) \ 197 | do { \ 198 | temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]; \ 199 | if (temp >= 0) { \ 200 | code_len = temp >> 9; \ 201 | if ((code_len) && (num_bits >= code_len)) \ 202 | break; \ 203 | } else if (num_bits > TINFL_FAST_LOOKUP_BITS) { \ 204 | code_len = TINFL_FAST_LOOKUP_BITS; \ 205 | do { \ 206 | temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; \ 207 | } while ((temp < 0) && (num_bits >= (code_len + 1))); if (temp >= 0) break; \ 208 | } TINFL_GET_BYTE(state_index, c); bit_buf |= (((tinfl_bit_buf_t)c) << num_bits); num_bits += 8; \ 209 | } while (num_bits < 15); 210 | 211 | // TINFL_HUFF_DECODE() decodes the next Huffman coded symbol. It's more complex than you would initially expect because the zlib API expects the decompressor to never read 212 | // beyond the final byte of the deflate stream. (In other words, when this macro wants to read another byte from the input, it REALLY needs another byte in order to fully 213 | // decode the next Huffman code.) Handling this properly is particularly important on raw deflate (non-zlib) streams, which aren't followed by a byte aligned adler-32. 214 | // The slow path is only executed at the very end of the input buffer. 215 | #define TINFL_HUFF_DECODE(state_index, sym, pHuff) do { \ 216 | int temp; mz_uint code_len, c; \ 217 | if (num_bits < 15) { \ 218 | if ((pIn_buf_end - pIn_buf_cur) < 2) { \ 219 | TINFL_HUFF_BITBUF_FILL(state_index, pHuff); \ 220 | } else { \ 221 | bit_buf |= (((tinfl_bit_buf_t)pIn_buf_cur[0]) << num_bits) | (((tinfl_bit_buf_t)pIn_buf_cur[1]) << (num_bits + 8)); pIn_buf_cur += 2; num_bits += 16; \ 222 | } \ 223 | } \ 224 | if ((temp = (pHuff)->m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) \ 225 | code_len = temp >> 9, temp &= 511; \ 226 | else { \ 227 | code_len = TINFL_FAST_LOOKUP_BITS; do { temp = (pHuff)->m_tree[~temp + ((bit_buf >> code_len++) & 1)]; } while (temp < 0); \ 228 | } sym = temp; bit_buf >>= code_len; num_bits -= code_len; } MZ_MACRO_END 229 | 230 | tinfl_status tinfl_decompress(tinfl_decompressor *r, const mz_uint8 *pIn_buf_next, size_t *pIn_buf_size, mz_uint8 *pOut_buf_start, mz_uint8 *pOut_buf_next, size_t *pOut_buf_size, const mz_uint32 decomp_flags) 231 | { 232 | static const int s_length_base[31] = { 3,4,5,6,7,8,9,10,11,13, 15,17,19,23,27,31,35,43,51,59, 67,83,99,115,131,163,195,227,258,0,0 }; 233 | static const int s_length_extra[31]= { 0,0,0,0,0,0,0,0,1,1,1,1,2,2,2,2,3,3,3,3,4,4,4,4,5,5,5,5,0,0,0 }; 234 | static const int s_dist_base[32] = { 1,2,3,4,5,7,9,13,17,25,33,49,65,97,129,193, 257,385,513,769,1025,1537,2049,3073,4097,6145,8193,12289,16385,24577,0,0}; 235 | static const int s_dist_extra[32] = { 0,0,0,0,1,1,2,2,3,3,4,4,5,5,6,6,7,7,8,8,9,9,10,10,11,11,12,12,13,13}; 236 | static const mz_uint8 s_length_dezigzag[19] = { 16,17,18,0,8,7,9,6,10,5,11,4,12,3,13,2,14,1,15 }; 237 | static const int s_min_table_sizes[3] = { 257, 1, 4 }; 238 | 239 | tinfl_status status = TINFL_STATUS_FAILED; mz_uint32 num_bits, dist, counter, num_extra; tinfl_bit_buf_t bit_buf; 240 | const mz_uint8 *pIn_buf_cur = pIn_buf_next, *const pIn_buf_end = pIn_buf_next + *pIn_buf_size; 241 | mz_uint8 *pOut_buf_cur = pOut_buf_next, *const pOut_buf_end = pOut_buf_next + *pOut_buf_size; 242 | size_t out_buf_size_mask = (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF) ? (size_t)-1 : ((pOut_buf_next - pOut_buf_start) + *pOut_buf_size) - 1, dist_from_out_buf_start; 243 | 244 | // Ensure the output buffer's size is a power of 2, unless the output buffer is large enough to hold the entire output file (in which case it doesn't matter). 245 | if (((out_buf_size_mask + 1) & out_buf_size_mask) || (pOut_buf_next < pOut_buf_start)) { *pIn_buf_size = *pOut_buf_size = 0; return TINFL_STATUS_BAD_PARAM; } 246 | 247 | num_bits = r->m_num_bits; bit_buf = r->m_bit_buf; dist = r->m_dist; counter = r->m_counter; num_extra = r->m_num_extra; dist_from_out_buf_start = r->m_dist_from_out_buf_start; 248 | TINFL_CR_BEGIN 249 | 250 | bit_buf = num_bits = dist = counter = num_extra = r->m_zhdr0 = r->m_zhdr1 = 0; r->m_z_adler32 = r->m_check_adler32 = 1; 251 | if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) 252 | { 253 | TINFL_GET_BYTE(1, r->m_zhdr0); TINFL_GET_BYTE(2, r->m_zhdr1); 254 | counter = (((r->m_zhdr0 * 256 + r->m_zhdr1) % 31 != 0) || (r->m_zhdr1 & 32) || ((r->m_zhdr0 & 15) != 8)); 255 | if (!(decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) counter |= (((1U << (8U + (r->m_zhdr0 >> 4))) > 32768U) || ((out_buf_size_mask + 1) < (size_t)(1U << (8U + (r->m_zhdr0 >> 4))))); 256 | if (counter) { TINFL_CR_RETURN_FOREVER(36, TINFL_STATUS_FAILED); } 257 | } 258 | 259 | do 260 | { 261 | TINFL_GET_BITS(3, r->m_final, 3); r->m_type = r->m_final >> 1; 262 | if (r->m_type == 0) 263 | { 264 | TINFL_SKIP_BITS(5, num_bits & 7); 265 | for (counter = 0; counter < 4; ++counter) { if (num_bits) TINFL_GET_BITS(6, r->m_raw_header[counter], 8); else TINFL_GET_BYTE(7, r->m_raw_header[counter]); } 266 | if ((counter = (r->m_raw_header[0] | (r->m_raw_header[1] << 8))) != (mz_uint)(0xFFFF ^ (r->m_raw_header[2] | (r->m_raw_header[3] << 8)))) { TINFL_CR_RETURN_FOREVER(39, TINFL_STATUS_FAILED); } 267 | while ((counter) && (num_bits)) 268 | { 269 | TINFL_GET_BITS(51, dist, 8); 270 | while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(52, TINFL_STATUS_HAS_MORE_OUTPUT); } 271 | *pOut_buf_cur++ = (mz_uint8)dist; 272 | counter--; 273 | } 274 | while (counter) 275 | { 276 | size_t n; while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(9, TINFL_STATUS_HAS_MORE_OUTPUT); } 277 | while (pIn_buf_cur >= pIn_buf_end) 278 | { 279 | if (decomp_flags & TINFL_FLAG_HAS_MORE_INPUT) 280 | { 281 | TINFL_CR_RETURN(38, TINFL_STATUS_NEEDS_MORE_INPUT); 282 | } 283 | else 284 | { 285 | TINFL_CR_RETURN_FOREVER(40, TINFL_STATUS_FAILED); 286 | } 287 | } 288 | n = MZ_MIN(MZ_MIN((size_t)(pOut_buf_end - pOut_buf_cur), (size_t)(pIn_buf_end - pIn_buf_cur)), counter); 289 | TINFL_MEMCPY(pOut_buf_cur, pIn_buf_cur, n); pIn_buf_cur += n; pOut_buf_cur += n; counter -= (mz_uint)n; 290 | } 291 | } 292 | else if (r->m_type == 3) 293 | { 294 | TINFL_CR_RETURN_FOREVER(10, TINFL_STATUS_FAILED); 295 | } 296 | else 297 | { 298 | if (r->m_type == 1) 299 | { 300 | mz_uint8 *p = r->m_tables[0].m_code_size; mz_uint i; 301 | r->m_table_sizes[0] = 288; r->m_table_sizes[1] = 32; TINFL_MEMSET(r->m_tables[1].m_code_size, 5, 32); 302 | for ( i = 0; i <= 143; ++i) *p++ = 8; for ( ; i <= 255; ++i) *p++ = 9; for ( ; i <= 279; ++i) *p++ = 7; for ( ; i <= 287; ++i) *p++ = 8; 303 | } 304 | else 305 | { 306 | for (counter = 0; counter < 3; counter++) { TINFL_GET_BITS(11, r->m_table_sizes[counter], "\05\05\04"[counter]); r->m_table_sizes[counter] += s_min_table_sizes[counter]; } 307 | MZ_CLEAR_OBJ(r->m_tables[2].m_code_size); for (counter = 0; counter < r->m_table_sizes[2]; counter++) { mz_uint s; TINFL_GET_BITS(14, s, 3); r->m_tables[2].m_code_size[s_length_dezigzag[counter]] = (mz_uint8)s; } 308 | r->m_table_sizes[2] = 19; 309 | } 310 | for ( ; (int)r->m_type >= 0; r->m_type--) 311 | { 312 | int tree_next, tree_cur; tinfl_huff_table *pTable; 313 | mz_uint i, j, used_syms, total, sym_index, next_code[17], total_syms[16]; pTable = &r->m_tables[r->m_type]; MZ_CLEAR_OBJ(total_syms); MZ_CLEAR_OBJ(pTable->m_look_up); MZ_CLEAR_OBJ(pTable->m_tree); 314 | for (i = 0; i < r->m_table_sizes[r->m_type]; ++i) total_syms[pTable->m_code_size[i]]++; 315 | used_syms = 0, total = 0; next_code[0] = next_code[1] = 0; 316 | for (i = 1; i <= 15; ++i) { used_syms += total_syms[i]; next_code[i + 1] = (total = ((total + total_syms[i]) << 1)); } 317 | if ((65536 != total) && (used_syms > 1)) 318 | { 319 | TINFL_CR_RETURN_FOREVER(35, TINFL_STATUS_FAILED); 320 | } 321 | for (tree_next = -1, sym_index = 0; sym_index < r->m_table_sizes[r->m_type]; ++sym_index) 322 | { 323 | mz_uint rev_code = 0, l, cur_code, code_size = pTable->m_code_size[sym_index]; if (!code_size) continue; 324 | cur_code = next_code[code_size]++; for (l = code_size; l > 0; l--, cur_code >>= 1) rev_code = (rev_code << 1) | (cur_code & 1); 325 | if (code_size <= TINFL_FAST_LOOKUP_BITS) { mz_int16 k = (mz_int16)((code_size << 9) | sym_index); while (rev_code < TINFL_FAST_LOOKUP_SIZE) { pTable->m_look_up[rev_code] = k; rev_code += (1 << code_size); } continue; } 326 | if (0 == (tree_cur = pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)])) { pTable->m_look_up[rev_code & (TINFL_FAST_LOOKUP_SIZE - 1)] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } 327 | rev_code >>= (TINFL_FAST_LOOKUP_BITS - 1); 328 | for (j = code_size; j > (TINFL_FAST_LOOKUP_BITS + 1); j--) 329 | { 330 | tree_cur -= ((rev_code >>= 1) & 1); 331 | if (!pTable->m_tree[-tree_cur - 1]) { pTable->m_tree[-tree_cur - 1] = (mz_int16)tree_next; tree_cur = tree_next; tree_next -= 2; } else tree_cur = pTable->m_tree[-tree_cur - 1]; 332 | } 333 | tree_cur -= ((rev_code >>= 1) & 1); pTable->m_tree[-tree_cur - 1] = (mz_int16)sym_index; 334 | } 335 | if (r->m_type == 2) 336 | { 337 | for (counter = 0; counter < (r->m_table_sizes[0] + r->m_table_sizes[1]); ) 338 | { 339 | mz_uint s; TINFL_HUFF_DECODE(16, dist, &r->m_tables[2]); if (dist < 16) { r->m_len_codes[counter++] = (mz_uint8)dist; continue; } 340 | if ((dist == 16) && (!counter)) 341 | { 342 | TINFL_CR_RETURN_FOREVER(17, TINFL_STATUS_FAILED); 343 | } 344 | num_extra = "\02\03\07"[dist - 16]; TINFL_GET_BITS(18, s, num_extra); s += "\03\03\013"[dist - 16]; 345 | TINFL_MEMSET(r->m_len_codes + counter, (dist == 16) ? r->m_len_codes[counter - 1] : 0, s); counter += s; 346 | } 347 | if ((r->m_table_sizes[0] + r->m_table_sizes[1]) != counter) 348 | { 349 | TINFL_CR_RETURN_FOREVER(21, TINFL_STATUS_FAILED); 350 | } 351 | TINFL_MEMCPY(r->m_tables[0].m_code_size, r->m_len_codes, r->m_table_sizes[0]); TINFL_MEMCPY(r->m_tables[1].m_code_size, r->m_len_codes + r->m_table_sizes[0], r->m_table_sizes[1]); 352 | } 353 | } 354 | for ( ; ; ) 355 | { 356 | mz_uint8 *pSrc; 357 | for ( ; ; ) 358 | { 359 | if (((pIn_buf_end - pIn_buf_cur) < 4) || ((pOut_buf_end - pOut_buf_cur) < 2)) 360 | { 361 | TINFL_HUFF_DECODE(23, counter, &r->m_tables[0]); 362 | if (counter >= 256) 363 | break; 364 | while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(24, TINFL_STATUS_HAS_MORE_OUTPUT); } 365 | *pOut_buf_cur++ = (mz_uint8)counter; 366 | } 367 | else 368 | { 369 | int sym2; mz_uint code_len; 370 | #if TINFL_USE_64BIT_BITBUF 371 | if (num_bits < 30) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE32(pIn_buf_cur)) << num_bits); pIn_buf_cur += 4; num_bits += 32; } 372 | #else 373 | if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } 374 | #endif 375 | if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) 376 | code_len = sym2 >> 9; 377 | else 378 | { 379 | code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); 380 | } 381 | counter = sym2; bit_buf >>= code_len; num_bits -= code_len; 382 | if (counter & 256) 383 | break; 384 | 385 | #if !TINFL_USE_64BIT_BITBUF 386 | if (num_bits < 15) { bit_buf |= (((tinfl_bit_buf_t)MZ_READ_LE16(pIn_buf_cur)) << num_bits); pIn_buf_cur += 2; num_bits += 16; } 387 | #endif 388 | if ((sym2 = r->m_tables[0].m_look_up[bit_buf & (TINFL_FAST_LOOKUP_SIZE - 1)]) >= 0) 389 | code_len = sym2 >> 9; 390 | else 391 | { 392 | code_len = TINFL_FAST_LOOKUP_BITS; do { sym2 = r->m_tables[0].m_tree[~sym2 + ((bit_buf >> code_len++) & 1)]; } while (sym2 < 0); 393 | } 394 | bit_buf >>= code_len; num_bits -= code_len; 395 | 396 | pOut_buf_cur[0] = (mz_uint8)counter; 397 | if (sym2 & 256) 398 | { 399 | pOut_buf_cur++; 400 | counter = sym2; 401 | break; 402 | } 403 | pOut_buf_cur[1] = (mz_uint8)sym2; 404 | pOut_buf_cur += 2; 405 | } 406 | } 407 | if ((counter &= 511) == 256) break; 408 | 409 | num_extra = s_length_extra[counter - 257]; counter = s_length_base[counter - 257]; 410 | if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(25, extra_bits, num_extra); counter += extra_bits; } 411 | 412 | TINFL_HUFF_DECODE(26, dist, &r->m_tables[1]); 413 | num_extra = s_dist_extra[dist]; dist = s_dist_base[dist]; 414 | if (num_extra) { mz_uint extra_bits; TINFL_GET_BITS(27, extra_bits, num_extra); dist += extra_bits; } 415 | 416 | dist_from_out_buf_start = pOut_buf_cur - pOut_buf_start; 417 | if ((dist > dist_from_out_buf_start) && (decomp_flags & TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF)) 418 | { 419 | TINFL_CR_RETURN_FOREVER(37, TINFL_STATUS_FAILED); 420 | } 421 | 422 | pSrc = pOut_buf_start + ((dist_from_out_buf_start - dist) & out_buf_size_mask); 423 | 424 | if ((MZ_MAX(pOut_buf_cur, pSrc) + counter) > pOut_buf_end) 425 | { 426 | while (counter--) 427 | { 428 | while (pOut_buf_cur >= pOut_buf_end) { TINFL_CR_RETURN(53, TINFL_STATUS_HAS_MORE_OUTPUT); } 429 | *pOut_buf_cur++ = pOut_buf_start[(dist_from_out_buf_start++ - dist) & out_buf_size_mask]; 430 | } 431 | continue; 432 | } 433 | #if MINIZ_USE_UNALIGNED_LOADS_AND_STORES 434 | else if ((counter >= 9) && (counter <= dist)) 435 | { 436 | const mz_uint8 *pSrc_end = pSrc + (counter & ~7); 437 | do 438 | { 439 | ((mz_uint32 *)pOut_buf_cur)[0] = ((const mz_uint32 *)pSrc)[0]; 440 | ((mz_uint32 *)pOut_buf_cur)[1] = ((const mz_uint32 *)pSrc)[1]; 441 | pOut_buf_cur += 8; 442 | } while ((pSrc += 8) < pSrc_end); 443 | if ((counter &= 7) < 3) 444 | { 445 | if (counter) 446 | { 447 | pOut_buf_cur[0] = pSrc[0]; 448 | if (counter > 1) 449 | pOut_buf_cur[1] = pSrc[1]; 450 | pOut_buf_cur += counter; 451 | } 452 | continue; 453 | } 454 | } 455 | #endif 456 | do 457 | { 458 | pOut_buf_cur[0] = pSrc[0]; 459 | pOut_buf_cur[1] = pSrc[1]; 460 | pOut_buf_cur[2] = pSrc[2]; 461 | pOut_buf_cur += 3; pSrc += 3; 462 | } while ((int)(counter -= 3) > 2); 463 | if ((int)counter > 0) 464 | { 465 | pOut_buf_cur[0] = pSrc[0]; 466 | if ((int)counter > 1) 467 | pOut_buf_cur[1] = pSrc[1]; 468 | pOut_buf_cur += counter; 469 | } 470 | } 471 | } 472 | } while (!(r->m_final & 1)); 473 | if (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) 474 | { 475 | TINFL_SKIP_BITS(32, num_bits & 7); for (counter = 0; counter < 4; ++counter) { mz_uint s; if (num_bits) TINFL_GET_BITS(41, s, 8); else TINFL_GET_BYTE(42, s); r->m_z_adler32 = (r->m_z_adler32 << 8) | s; } 476 | } 477 | TINFL_CR_RETURN_FOREVER(34, TINFL_STATUS_DONE); 478 | TINFL_CR_FINISH 479 | 480 | common_exit: 481 | r->m_num_bits = num_bits; r->m_bit_buf = bit_buf; r->m_dist = dist; r->m_counter = counter; r->m_num_extra = num_extra; r->m_dist_from_out_buf_start = dist_from_out_buf_start; 482 | *pIn_buf_size = pIn_buf_cur - pIn_buf_next; *pOut_buf_size = pOut_buf_cur - pOut_buf_next; 483 | if ((decomp_flags & (TINFL_FLAG_PARSE_ZLIB_HEADER | TINFL_FLAG_COMPUTE_ADLER32)) && (status >= 0)) 484 | { 485 | const mz_uint8 *ptr = pOut_buf_next; size_t buf_len = *pOut_buf_size; 486 | mz_uint32 i, s1 = r->m_check_adler32 & 0xffff, s2 = r->m_check_adler32 >> 16; size_t block_len = buf_len % 5552; 487 | while (buf_len) 488 | { 489 | for (i = 0; i + 7 < block_len; i += 8, ptr += 8) 490 | { 491 | s1 += ptr[0], s2 += s1; s1 += ptr[1], s2 += s1; s1 += ptr[2], s2 += s1; s1 += ptr[3], s2 += s1; 492 | s1 += ptr[4], s2 += s1; s1 += ptr[5], s2 += s1; s1 += ptr[6], s2 += s1; s1 += ptr[7], s2 += s1; 493 | } 494 | for ( ; i < block_len; ++i) s1 += *ptr++, s2 += s1; 495 | s1 %= 65521U, s2 %= 65521U; buf_len -= block_len; block_len = 5552; 496 | } 497 | r->m_check_adler32 = (s2 << 16) + s1; if ((status == TINFL_STATUS_DONE) && (decomp_flags & TINFL_FLAG_PARSE_ZLIB_HEADER) && (r->m_check_adler32 != r->m_z_adler32)) status = TINFL_STATUS_ADLER32_MISMATCH; 498 | } 499 | return status; 500 | } 501 | 502 | // Higher level helper functions. 503 | void *tinfl_decompress_mem_to_heap(const void *pSrc_buf, size_t src_buf_len, size_t *pOut_len, int flags) 504 | { 505 | tinfl_decompressor decomp; void *pBuf = NULL, *pNew_buf; size_t src_buf_ofs = 0, out_buf_capacity = 0; 506 | *pOut_len = 0; 507 | tinfl_init(&decomp); 508 | for ( ; ; ) 509 | { 510 | size_t src_buf_size = src_buf_len - src_buf_ofs, dst_buf_size = out_buf_capacity - *pOut_len, new_out_buf_capacity; 511 | tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf + src_buf_ofs, &src_buf_size, (mz_uint8*)pBuf, pBuf ? (mz_uint8*)pBuf + *pOut_len : NULL, &dst_buf_size, 512 | (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); 513 | if ((status < 0) || (status == TINFL_STATUS_NEEDS_MORE_INPUT)) 514 | { 515 | MZ_FREE(pBuf); *pOut_len = 0; return NULL; 516 | } 517 | src_buf_ofs += src_buf_size; 518 | *pOut_len += dst_buf_size; 519 | if (status == TINFL_STATUS_DONE) break; 520 | new_out_buf_capacity = out_buf_capacity * 2; if (new_out_buf_capacity < 128) new_out_buf_capacity = 128; 521 | pNew_buf = MZ_REALLOC(pBuf, new_out_buf_capacity); 522 | if (!pNew_buf) 523 | { 524 | MZ_FREE(pBuf); *pOut_len = 0; return NULL; 525 | } 526 | pBuf = pNew_buf; out_buf_capacity = new_out_buf_capacity; 527 | } 528 | return pBuf; 529 | } 530 | 531 | size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags) 532 | { 533 | tinfl_decompressor decomp; tinfl_status status; tinfl_init(&decomp); 534 | status = tinfl_decompress(&decomp, (const mz_uint8*)pSrc_buf, &src_buf_len, (mz_uint8*)pOut_buf, (mz_uint8*)pOut_buf, &out_buf_len, (flags & ~TINFL_FLAG_HAS_MORE_INPUT) | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF); 535 | return (status != TINFL_STATUS_DONE) ? TINFL_DECOMPRESS_MEM_TO_MEM_FAILED : out_buf_len; 536 | } 537 | 538 | int tinfl_decompress_mem_to_callback(const void *pIn_buf, size_t *pIn_buf_size, tinfl_put_buf_func_ptr pPut_buf_func, void *pPut_buf_user, int flags) 539 | { 540 | int result = 0; 541 | tinfl_decompressor decomp; 542 | mz_uint8 *pDict = (mz_uint8*)MZ_MALLOC(TINFL_LZ_DICT_SIZE); size_t in_buf_ofs = 0, dict_ofs = 0; 543 | if (!pDict) 544 | return TINFL_STATUS_FAILED; 545 | tinfl_init(&decomp); 546 | for ( ; ; ) 547 | { 548 | size_t in_buf_size = *pIn_buf_size - in_buf_ofs, dst_buf_size = TINFL_LZ_DICT_SIZE - dict_ofs; 549 | tinfl_status status = tinfl_decompress(&decomp, (const mz_uint8*)pIn_buf + in_buf_ofs, &in_buf_size, pDict, pDict + dict_ofs, &dst_buf_size, 550 | (flags & ~(TINFL_FLAG_HAS_MORE_INPUT | TINFL_FLAG_USING_NON_WRAPPING_OUTPUT_BUF))); 551 | in_buf_ofs += in_buf_size; 552 | if ((dst_buf_size) && (!(*pPut_buf_func)(pDict + dict_ofs, (int)dst_buf_size, pPut_buf_user))) 553 | break; 554 | if (status != TINFL_STATUS_HAS_MORE_OUTPUT) 555 | { 556 | result = (status == TINFL_STATUS_DONE); 557 | break; 558 | } 559 | dict_ofs = (dict_ofs + dst_buf_size) & (TINFL_LZ_DICT_SIZE - 1); 560 | } 561 | MZ_FREE(pDict); 562 | *pIn_buf_size = in_buf_ofs; 563 | return result; 564 | } 565 | 566 | #endif // #ifndef TINFL_HEADER_FILE_ONLY 567 | 568 | /* 569 | This is free and unencumbered software released into the public domain. 570 | 571 | Anyone is free to copy, modify, publish, use, compile, sell, or 572 | distribute this software, either in source code form or as a compiled 573 | binary, for any purpose, commercial or non-commercial, and by any 574 | means. 575 | 576 | In jurisdictions that recognize copyright laws, the author or authors 577 | of this software dedicate any and all copyright interest in the 578 | software to the public domain. We make this dedication for the benefit 579 | of the public at large and to the detriment of our heirs and 580 | successors. We intend this dedication to be an overt act of 581 | relinquishment in perpetuity of all present and future rights to this 582 | software under copyright law. 583 | 584 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 585 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 586 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 587 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 588 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 589 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 590 | OTHER DEALINGS IN THE SOFTWARE. 591 | 592 | For more information, please refer to 593 | */ 594 | 595 | 596 | -------------------------------------------------------------------------------- /whatsapp-proto.cc: -------------------------------------------------------------------------------- 1 | 2 | /* 3 | * WhatsApp protocol dissector 4 | * Written by David Guillen Fandos 5 | * Based on WhatsAPI sources 6 | * 7 | */ 8 | 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | extern "C" { 19 | #include 20 | #include 21 | #include 22 | #include "whatsapp-proto.h" 23 | } 24 | 25 | const char * key_desc[4] = { 26 | "Out key", "Out HMAC", "In key", "In HMAC" 27 | }; 28 | 29 | extern "C" { 30 | size_t tinfl_decompress_mem_to_mem(void *pOut_buf, size_t out_buf_len, const void *pSrc_buf, size_t src_buf_len, int flags); 31 | } 32 | 33 | std::string base64_decode(std::string const& encoded_string); 34 | 35 | class Tree; class DataBuffer; class KeyGenerator; class RC4Decoder; 36 | 37 | class DissectSession { 38 | private: 39 | // Current dissection classes 40 | address server_addr, client_addr, client_mac; // Identify server/client role 41 | RC4Decoder * in, * out; 42 | unsigned char session_key[20*4]; // V16 session keys (4) 43 | std::string challenge_data, challenge_response; 44 | std::map < unsigned int, DataBuffer* > * blist; 45 | std::map < unsigned int, DataBuffer* > * dlist; 46 | std::string userphone; 47 | bool found_auth; 48 | unsigned int wa_version; 49 | 50 | bool check_key(); 51 | bool tryKeys(); 52 | 53 | public: 54 | DissectSession (const char * data, int len, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo); 55 | int dissect(const char * data, int len, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo); 56 | Tree * next_tree(DataBuffer * data,proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo); 57 | Tree * read_tree(DataBuffer * data, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo); 58 | }; 59 | 60 | enum EncodingManner { EncType, EncTypeExt, EncTypePlain }; 61 | enum EncodingKind { TokenTypeKey = 0, TokenTypeVal, TokenTypeTag, TokenTypeNValue }; 62 | 63 | int * encoding_table[4][3] = { 64 | {&hf_whatsapp_attr_key_enc, &hf_whatsapp_attr_key_enc_ext, &hf_whatsapp_attr_key_plain}, 65 | {&hf_whatsapp_attr_val_enc, &hf_whatsapp_attr_val_enc_ext, &hf_whatsapp_attr_val_plain}, 66 | {&hf_whatsapp_attr_key_enc, &hf_whatsapp_attr_key_enc_ext, &hf_whatsapp_tag_plain}, 67 | {&hf_whatsapp_attr_key_enc, &hf_whatsapp_attr_key_enc_ext, &hf_whatsapp_nvalue_plain} 68 | }; 69 | 70 | 71 | #define COPY_ADDRESS_CC(to, from) { \ 72 | guint8 *COPY_ADDRESS_data; \ 73 | (to)->type = (from)->type; \ 74 | (to)->len = (from)->len; \ 75 | COPY_ADDRESS_data = (guint8*)g_malloc((from)->len); \ 76 | memcpy(COPY_ADDRESS_data, (from)->data, (from)->len); \ 77 | (to)->data = COPY_ADDRESS_data; \ 78 | } 79 | 80 | extern const value_string strings_list[]; 81 | 82 | void SHA1(const unsigned char * in, int len, char unsigned * out) { 83 | gcry_md_hash_buffer(GCRY_MD_SHA1, out, in, len); 84 | } 85 | 86 | void HMAC_SHA1(const unsigned char *text, int text_len, const unsigned char *key, int key_len, unsigned char *digest) { 87 | const int SHA1_BLOCK_SIZE = 64; 88 | const int SHA1_DIGEST_LENGTH = 20; 89 | 90 | unsigned char SHA1_Key[4096], AppendBuf2[4096], szReport[4096]; 91 | unsigned char * AppendBuf1 = new unsigned char[text_len+64]; 92 | unsigned char m_ipad[64], m_opad[64]; 93 | 94 | memset(SHA1_Key, 0, SHA1_BLOCK_SIZE); 95 | 96 | /* repeated 64 times for values in ipad and opad */ 97 | memset(m_ipad, 0x36, sizeof(m_ipad)); 98 | memset(m_opad, 0x5c, sizeof(m_opad)); 99 | 100 | /* STEP 1 */ 101 | if (key_len > SHA1_BLOCK_SIZE) 102 | SHA1(key,key_len,SHA1_Key); 103 | else 104 | memcpy(SHA1_Key, key, key_len); 105 | 106 | /* STEP 2 */ 107 | for (int i=0; i>4)&0xF]; 156 | hashhex[2*i+1] = hexmap[hash[i]&0xF]; 157 | } 158 | 159 | PKCS5_PBKDF2_HMAC_SHA1 (hashhex,32,(unsigned char*)salt,saltlen,16,20,(unsigned char*)out); 160 | } 161 | static void generateKeyV2(const std::string pw, const char * salt, int saltlen, char * out) { 162 | std::string dpw = base64_decode(pw); 163 | 164 | PKCS5_PBKDF2_HMAC_SHA1 (dpw.c_str(),20,(unsigned char*)salt,saltlen,16,20,(unsigned char*)out); 165 | } 166 | static void generateKeyV14(const std::string pw, const char * salt, int saltlen, char * out) { 167 | std::string dec = base64_decode(pw); 168 | char salt_[saltlen+1]; memcpy(salt_,salt,saltlen); 169 | 170 | for (int i = 0; i < 4; i++) { 171 | salt_[saltlen] = (i+1); 172 | PKCS5_PBKDF2_HMAC_SHA1(dec.c_str(), 20, (unsigned char *)salt_, saltlen+1, 2, 20, (unsigned char *)&out[20*i]); 173 | } 174 | } 175 | 176 | static void generateKeyMAC(const address * macaddr, const char * salt, int saltlen, char * out) { 177 | unsigned char * ad = (unsigned char*)macaddr->data; 178 | if (ad == NULL) return; 179 | char fmt_addr[6*3*2]; 180 | for (int i = 0; i < 6; i++) { 181 | fmt_addr[i*3+0] = hexmap2[ad[i]>>4]; 182 | fmt_addr[i*3+1] = hexmap2[ad[i]&0xF]; 183 | fmt_addr[i*3+2] = ':'; 184 | 185 | fmt_addr[i*3+0+17] = hexmap2[ad[i]>>4]; 186 | fmt_addr[i*3+1+17] = hexmap2[ad[i]&0xF]; 187 | fmt_addr[i*3+2+17] = ':'; 188 | } 189 | 190 | char hash[16]; 191 | MD5(fmt_addr,34,hash); 192 | 193 | // Convert to hex 194 | char hashhex[32]; 195 | for (int i = 0; i < 16; i++) { 196 | hashhex[2*i] = hexmap[(hash[i]>>4)&0xF]; 197 | hashhex[2*i+1] = hexmap[hash[i]&0xF]; 198 | } 199 | 200 | PKCS5_PBKDF2_HMAC_SHA1 (hashhex,32,(unsigned char*)salt,saltlen,16,20,(unsigned char*)out); 201 | } 202 | static void calc_hmac(const unsigned char *data, int l, const unsigned char *key, bool hash_at_end, 203 | unsigned char * hmac, unsigned int seq) { 204 | 205 | unsigned char temp[20]; 206 | unsigned char data_temp[l]; 207 | if (hash_at_end) memcpy(data_temp, data, l-4); 208 | else memcpy(data_temp,&data[4],l-4); 209 | data_temp[l-4] = (seq >> 24); 210 | data_temp[l-3] = (seq >> 16); 211 | data_temp[l-2] = (seq >> 8); 212 | data_temp[l-1] = (seq ); 213 | 214 | HMAC_SHA1 (data_temp,l,key,20,temp); 215 | 216 | memcpy(hmac,temp,4); 217 | } 218 | }; 219 | 220 | 221 | class RC4Decoder { 222 | private: 223 | unsigned char s[256]; 224 | unsigned char i,j; 225 | inline void swap (unsigned char i, unsigned char j) { 226 | unsigned char t = s[i]; 227 | s[i] = s[j]; 228 | s[j] = t; 229 | } 230 | public: 231 | RC4Decoder(const unsigned char * key, int keylen, int drop, bool zerodrop) { 232 | for (unsigned int k = 0; k < 256; k++) s[k] = k; 233 | i = j = 0; 234 | do { 235 | unsigned char k = key[i % keylen]; 236 | j = (j + k + s[i]) & 0xFF; 237 | swap(i,j); 238 | } while (++i != 0); 239 | i = j = 0; 240 | 241 | unsigned char temp[drop]; 242 | for (int k = 0; k < drop; k++) temp[k] = zerodrop ? 0 : k; 243 | cipher(temp,drop); 244 | } 245 | 246 | void cipher (unsigned char * data, int len) { 247 | while (len--) { 248 | i++; 249 | j += s[i]; 250 | swap(i,j); 251 | unsigned char idx = s[i]+s[j]; 252 | *data++ ^= s[idx]; 253 | } 254 | } 255 | }; 256 | 257 | class DataBuffer { 258 | private: 259 | unsigned char * buffer; 260 | int blen, skip; 261 | unsigned char hmac[4]; 262 | public: 263 | int version; 264 | 265 | DataBuffer (const void * ptr, int size, int v) { 266 | if (ptr != NULL and size > 0) { 267 | buffer = (unsigned char*)malloc(size); 268 | memcpy(buffer,ptr,size); 269 | blen = size; 270 | }else{ 271 | blen = 0; 272 | buffer = (unsigned char*)malloc(1024); 273 | } 274 | skip = 0; 275 | version = v; 276 | memset(hmac,0,4); 277 | } 278 | ~DataBuffer() { 279 | free(buffer); 280 | } 281 | DataBuffer (const DataBuffer * d) { 282 | skip = d->skip; 283 | blen = d->blen; 284 | version = d->version; 285 | buffer = (unsigned char*)malloc(blen+1024); 286 | memcpy(buffer,d->buffer,blen); 287 | memcpy(hmac,d->hmac,4); 288 | } 289 | 290 | DataBuffer * decodedBuffer(RC4Decoder * decoder, int clength, bool dout) { 291 | DataBuffer * deco = new DataBuffer(this->buffer,clength,version); 292 | if (dout) decoder->cipher(&deco->buffer[0],clength-4); 293 | else decoder->cipher(&deco->buffer[4],clength-4); 294 | return deco; 295 | } 296 | 297 | unsigned int getPacketHMAC(bool dout, int size) { 298 | unsigned int r; 299 | if (dout) memcpy(&r,&buffer[size-4],4); 300 | else memcpy(&r,&buffer[0],4); 301 | return r; 302 | } 303 | 304 | void getHMAC(unsigned char * p) { 305 | memcpy(p,hmac,4); 306 | } 307 | void setHMAC(const unsigned char * p) { 308 | memcpy(hmac,p,4); 309 | } 310 | void * getPtr() { return buffer; } 311 | void addData(void * ptr, int size) { 312 | if (ptr != NULL and size > 0) { 313 | buffer = (unsigned char*)realloc(buffer,blen+size); 314 | memcpy(&buffer[blen],ptr,size); 315 | blen += size; 316 | } 317 | } 318 | void popData(int size) { 319 | if (size > blen) { 320 | throw 0; 321 | }else{ 322 | memmove(&buffer[0],&buffer[size],blen-size); 323 | blen -= size; 324 | buffer = (unsigned char*)realloc(buffer,blen+1); 325 | } 326 | skip += size; 327 | } 328 | void crunchData(int size) { 329 | if (size > blen) { 330 | throw 0; 331 | }else{ 332 | blen -= size; 333 | } 334 | } 335 | int curr() { return skip; } 336 | int getInt(int nbytes, int offset = 0) { 337 | if (nbytes > blen) 338 | throw 0; 339 | int ret = 0; 340 | for (int i = 0; i < nbytes; i++) { 341 | ret <<= 8; 342 | ret |= buffer[i+offset]; 343 | } 344 | return ret; 345 | } 346 | int readInt(int nbytes) { 347 | if (nbytes > blen) 348 | throw 0; 349 | int ret = getInt(nbytes); 350 | popData(nbytes); 351 | return ret; 352 | } 353 | 354 | int readListSize(proto_tree *tree, tvbuff_t *tvb) { 355 | if (blen == 0) 356 | throw 0; 357 | int ret; 358 | if (buffer[0] == 0xf8 or buffer[0] == 0xf3) { 359 | if (tree) 360 | proto_tree_add_item (tree, hf_whatsapp_nodesize8, tvb, curr()+1, 1, ENC_BIG_ENDIAN); 361 | ret = buffer[1]; 362 | popData(2); 363 | } 364 | else if (buffer[0] == 0xf9) { 365 | if (tree) 366 | proto_tree_add_item (tree, hf_whatsapp_nodesize16, tvb, curr()+1, 2, ENC_BIG_ENDIAN); 367 | ret = getInt(2,1); 368 | popData(3); 369 | } 370 | else { 371 | // FIXME throw 0 error 372 | printf("Parse error! %d\n", (int)buffer[0]); 373 | return 0; 374 | } 375 | return ret; 376 | } 377 | std::string readRawString(int size) { 378 | if (size < 0 or size > blen) 379 | throw 0; 380 | std::string st(size,' '); 381 | memcpy(&st[0],buffer,size); 382 | popData(size); 383 | return st; 384 | } 385 | 386 | std::string readString(proto_tree *tree, tvbuff_t *tvb, EncodingKind encoding, int wa_version) { 387 | if (blen == 0) 388 | throw 0; 389 | int type = readInt(1); 390 | // No more version specific, only 1.6 supported 391 | if (type > 2 and type < 236) { 392 | proto_tree_add_item (tree, *encoding_table[encoding][EncType], tvb, curr()-1, 1, ENC_NA); 393 | return std::string(strings_list[type].strptr); 394 | } 395 | else if (type == 0) { 396 | return ""; 397 | } 398 | else if (type == 236) { 399 | // Extended Token 400 | proto_tree_add_item (tree, *encoding_table[encoding][EncType], tvb, curr()-1, 1, ENC_NA); 401 | proto_tree_add_item (tree, *encoding_table[encoding][EncTypeExt], tvb, curr()-1, 2, ENC_NA); 402 | type = readInt(1); 403 | 404 | return std::string(strings_list_ext[type].strptr); 405 | } 406 | else if (type == 0xfc) { 407 | int slen = readInt(1); 408 | proto_tree_add_item (tree, *encoding_table[encoding][EncTypePlain], tvb, curr(), slen, ENC_NA); 409 | return readRawString(slen); 410 | } 411 | else if (type == 0xfd) { 412 | int slen = readInt(3); 413 | proto_tree_add_item (tree, *encoding_table[encoding][EncTypePlain], tvb, curr(), slen, ENC_NA); 414 | return readRawString(slen); 415 | } 416 | else if (type == 0xfe) { 417 | int slen = readInt(4); 418 | proto_tree_add_item (tree, *encoding_table[encoding][EncTypePlain], tvb, curr(), slen, ENC_NA); 419 | return readRawString(slen); 420 | } 421 | else if (type == 0xfa) { 422 | proto_item * ti = 0; proto_tree * msg = 0; int ns = curr(); 423 | if (tree != 0) { 424 | ti = proto_tree_add_item (tree, hf_whatsapp_userserver, tvb, curr(), 0, ENC_NA); 425 | msg = proto_item_add_subtree (ti, userserver_string); 426 | } 427 | 428 | std::string u = readString(msg,tvb,encoding, wa_version); 429 | std::string s = readString(msg,tvb,encoding, wa_version); 430 | 431 | if (ti) 432 | proto_item_set_len(ti,curr()-ns); 433 | 434 | if (u.size() > 0 and s.size() > 0) 435 | return u + "@" + s; 436 | else if (s.size() > 0) 437 | return s; 438 | return ""; 439 | } 440 | else if (type == 0xff || type == 0xfb) { 441 | // Some sort of number encoding (using 4 bit) 442 | char bchar = (type == 0xff) ? '-' : 'A'; 443 | int nbyte = readInt(1); 444 | int size = nbyte & 0x7f; 445 | int numnibbles = size*2 - ((nbyte&0x80) ? 1 : 0); 446 | 447 | proto_item * hh = proto_tree_add_item (tree, hf_whatsapp_nibble_enc, tvb, curr()-2, size+2, ENC_NA); 448 | 449 | std::string rawd = readRawString(size); 450 | std::string s; 451 | for (int i = 0; i < numnibbles; i++) { 452 | char c = (rawd[i/2] >> (4-((i&1)<<2))) & 0xF; 453 | if (c < 10) s += (c+'0'); 454 | else s += (c-10+bchar); 455 | } 456 | 457 | proto_item_append_text(hh, " (%s)", s.c_str()); 458 | 459 | return s; 460 | } 461 | return ""; 462 | } 463 | bool isList() { 464 | if (blen == 0) 465 | throw 0; 466 | return (buffer[0] == 248 or buffer[0] == 0 or buffer[0] == 249); 467 | } 468 | std::vector readList(proto_tree * tree , tvbuff_t *tvb,packet_info *pinfo, DissectSession * session) { 469 | std::vector l; 470 | int size = readListSize(0,0); 471 | while (size--) { 472 | l.push_back(session->read_tree(this,tree,tvb,pinfo)); 473 | } 474 | return l; 475 | } 476 | int size() { return blen; } 477 | }; 478 | 479 | class Tree { 480 | private: 481 | std::map < std::string, std::string > attributes; 482 | std::vector < Tree* > children; 483 | std::string tag, data; 484 | public: 485 | Tree() {} 486 | ~Tree() { 487 | for (int i = 0; i < children.size(); i++) 488 | delete children[i]; 489 | } 490 | 491 | void setTag(std::string tag) { 492 | this->tag = tag; 493 | } 494 | void readAttributes(DataBuffer * data, int size, proto_tree *tree, tvbuff_t *tvb, int wa_version) { 495 | int count = (size - 2 + (size % 2)) / 2; 496 | while (count--) { 497 | proto_item * ti; proto_tree * msg = 0; int ns = data->curr(); 498 | if (tree) { 499 | ti = proto_tree_add_item (tree, hf_whatsapp_attribute, tvb, data->curr(), 0, ENC_NA); 500 | msg = proto_item_add_subtree (ti, tree_whatsapp); 501 | } 502 | 503 | std::string key = data->readString(msg,tvb,TokenTypeKey,wa_version); 504 | std::string value = data->readString(msg,tvb,TokenTypeVal,wa_version); 505 | 506 | if (ti) 507 | proto_item_set_len(ti,data->curr()-ns); 508 | 509 | attributes[key] = value; 510 | } 511 | } 512 | void setData(std::string d) { 513 | data = d; 514 | } 515 | std::string getData() { 516 | return data; 517 | } 518 | void setChildren(std::vector < Tree* > c) { 519 | children = c; 520 | } 521 | std::string getAttr(const std::string & key) const { 522 | if (attributes.find(key) != attributes.end()) { 523 | return attributes.at("user"); 524 | } 525 | return ""; 526 | } 527 | }; 528 | 529 | DissectSession::DissectSession (const char * data, int len, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo) { 530 | found_auth = false; 531 | in = 0; out = 0; 532 | this->blist = new std::map < unsigned int, DataBuffer* >(); 533 | this->dlist = new std::map < unsigned int, DataBuffer* >(); 534 | } 535 | 536 | int DissectSession::dissect(const char * data, int len, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo) { 537 | int rlen = 0; 538 | try { 539 | DataBuffer * d = new DataBuffer(data,len,wa_version); 540 | 541 | // Skip initial header 542 | if (d->getInt(1,0) == (int)'W' and d->getInt(1,1) == (int)'A' and 543 | d->getInt(1,2) >= 0 and d->getInt(1,2) <= 9 and d->getInt(1,3) >= 0 and 544 | d->getInt(1,3) <= 9 ) { 545 | 546 | // Important to properly handle token parsing and ciphering 547 | wa_version = d->getInt(1,2) * 10 + d->getInt(1,3); 548 | 549 | d->popData(4); 550 | } 551 | if (d->size() <= 3) return 0; 552 | 553 | // Consume as many trees as possible 554 | Tree * t = NULL; 555 | int n = 0; 556 | do { 557 | t = next_tree(d,tree,tvb,pinfo); 558 | if (t != NULL) delete t; 559 | } while (t != NULL and d->size() >= 3); 560 | 561 | rlen = len - d->size(); 562 | delete d; 563 | }catch (int n) { 564 | return 0; 565 | } 566 | return rlen; 567 | } 568 | 569 | Tree * DissectSession::next_tree(DataBuffer * data,proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo) { 570 | int flag = data->getInt(1); 571 | int bflag = (flag & 0xF0)>>4; 572 | int bsize = data->getInt(2,1); 573 | bsize |= ((flag & 0xF) << 16); 574 | if (bsize > data->size()-3) { 575 | return NULL; // Next message incomplete, return consumed data 576 | } 577 | 578 | proto_tree * msg = 0; proto_item *ti; 579 | if (tree) { 580 | ti = proto_tree_add_item (tree, whatsapp_msg, tvb, data->curr(), bsize+3, ENC_NA); 581 | msg = proto_item_add_subtree (ti, message_whatsapp); 582 | proto_tree_add_uint (msg, hf_whatsapp_message, tvb, data->curr(), 3, bsize & 0xFFFFF); 583 | ti = proto_tree_add_uint (msg, hf_whatsapp_attr_flags, tvb, data->curr(), 1, flag & 0xF0); 584 | 585 | proto_tree * msgf = proto_item_add_subtree(ti, tree_msg_flags); 586 | proto_tree_add_bits_item (msgf, hf_whatsapp_attr_crypted, tvb, data->curr()*8, 1, ENC_LITTLE_ENDIAN); 587 | proto_tree_add_bits_item (msgf, hf_whatsapp_attr_compressed, tvb, data->curr()*8+1, 1, ENC_LITTLE_ENDIAN); 588 | } 589 | 590 | data->popData(3); 591 | 592 | if (bflag & 8 || bflag & 1) { 593 | // Decode data, buffer conversion 594 | if (global_enable_decoding and found_auth) { 595 | DataBuffer * decoded_data; 596 | RC4Decoder * decoder = this->in; 597 | bool dataout = addresses_equal(&server_addr,&pinfo->dst); 598 | if (dataout) 599 | decoder = out; 600 | 601 | unsigned int packet_hmac = data->getPacketHMAC(dataout,bsize); 602 | unsigned char hmac[4]; 603 | if (blist->find(packet_hmac) != blist->end()) { 604 | decoded_data = new DataBuffer((*blist)[packet_hmac]); 605 | decoded_data->getHMAC(hmac); 606 | }else{ 607 | unsigned char * skey = dataout ? &session_key[20*1] : &session_key[20*3]; 608 | 609 | KeyGenerator::calc_hmac((unsigned char*)data->getPtr(),bsize,skey,dataout,hmac,0); 610 | decoded_data = data->decodedBuffer(decoder, bsize, true); 611 | decoded_data->setHMAC(hmac); 612 | (*blist)[packet_hmac] = new DataBuffer(decoded_data); 613 | } 614 | 615 | guint8* decrypted_buffer = (guint8*)g_malloc(bsize); 616 | memcpy(decrypted_buffer,decoded_data->getPtr(),bsize); 617 | tvbuff_t * decoded_tvb = tvb_new_child_real_data(tvb, decrypted_buffer, bsize, bsize); 618 | tvb_set_free_cb(decoded_tvb, g_free); 619 | 620 | add_new_data_source(pinfo, decoded_tvb, "Decrypted data"); 621 | 622 | if (tree) { 623 | if (decoded_data->version == 16) 624 | ti = proto_tree_add_item (msg, whatsapp_msg_crypted_payload, tvb, data->curr(), -1, ENC_NA); 625 | else 626 | ti = proto_tree_add_item (msg, whatsapp_msg_crypted_payload_mismatch, tvb, data->curr(), -1, ENC_NA); 627 | 628 | msg = proto_item_add_subtree (ti, message_whatsapp); 629 | 630 | proto_item * hh; 631 | { 632 | hh = proto_tree_add_item (msg,hf_whatsapp_crypted_hmac_hash, 633 | decoded_tvb, bsize-4, 4, ENC_BIG_ENDIAN); 634 | ti = proto_tree_add_item (msg, whatsapp_msg_crypted_message, 635 | decoded_tvb, 0, bsize-4, ENC_NA); 636 | decoded_data->crunchData(4); // Remove hash 637 | } 638 | proto_item_append_text(hh, " (calculated: 0x%02x%02x%02x%02x)", 639 | hmac[0],hmac[1],hmac[2],hmac[3]); 640 | 641 | msg = proto_item_add_subtree (ti, message_whatsapp); 642 | } 643 | 644 | if (bflag & 4) { 645 | DataBuffer * decomp_data = NULL; 646 | if (dlist->find(packet_hmac) != dlist->end()) { 647 | decomp_data = new DataBuffer((*dlist)[packet_hmac]); 648 | }else{ 649 | // Deflate data, completely arbitrary max size 650 | const int osize = 512*1024; 651 | char tmpbuf[osize]; 652 | size_t r = tinfl_decompress_mem_to_mem(tmpbuf, osize, decoded_data->getPtr(), decoded_data->size(), 1); 653 | 654 | if (r < osize) { 655 | decomp_data = new DataBuffer(tmpbuf, r, decoded_data->version); 656 | (*dlist)[packet_hmac] = new DataBuffer(decomp_data); 657 | } 658 | } 659 | 660 | if (decomp_data) { 661 | guint8* decompressed_buffer = (guint8*)g_malloc(decomp_data->size()); 662 | memcpy(decompressed_buffer,decomp_data->getPtr(),decomp_data->size()); 663 | tvbuff_t * decomp_tvb = tvb_new_child_real_data(decoded_tvb, decompressed_buffer, decomp_data->size(), decomp_data->size()); 664 | tvb_set_free_cb(decomp_tvb, g_free); 665 | add_new_data_source(pinfo, decomp_tvb, "Decompressed data"); 666 | 667 | ti = proto_tree_add_item (msg, whatsapp_msg_compressed_message, 668 | decomp_tvb, 0, decomp_data->size(), ENC_NA); 669 | msg = proto_item_add_subtree (ti, message_whatsapp); 670 | 671 | // Call recursive 672 | data->popData(bsize); // Pop data for next parsing! 673 | return read_tree(decomp_data,msg,decomp_tvb,pinfo); 674 | } 675 | } 676 | 677 | // Call recursive 678 | data->popData(bsize); // Pop data for next parsing! 679 | if (decoded_data->version == 16) 680 | return read_tree(decoded_data,msg,decoded_tvb,pinfo); 681 | 682 | return NULL; 683 | 684 | }else{ 685 | if (tree) { 686 | proto_tree_add_item (msg, whatsapp_msg_crypted_payload, tvb, data->curr(), -1, ENC_NA); 687 | } 688 | data->popData(bsize); 689 | return NULL; 690 | } 691 | } 692 | return read_tree(data,msg,tvb,pinfo); 693 | } 694 | 695 | Tree * DissectSession::read_tree(DataBuffer * data, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo) { 696 | proto_item * ti = proto_tree_add_item (tree, hf_whatsapp_node, tvb, data->curr(), 0, ENC_NA); 697 | proto_tree * msg = proto_item_add_subtree (ti, tree_whatsapp); 698 | int nstart = data->curr(); 699 | 700 | int lsize = data->readListSize(msg,tvb); 701 | int type = data->getInt(1); 702 | if (type == 1) { 703 | data->popData(1); 704 | Tree * t = new Tree(); 705 | t->readAttributes(data,lsize,msg,tvb,wa_version); 706 | t->setTag("start"); 707 | proto_item_set_len(ti,data->curr()-nstart); 708 | return t; 709 | }else if (type == 2) { 710 | data->popData(1); 711 | proto_item_set_len(ti,data->curr()-nstart); 712 | return NULL; // No data in this tree... 713 | } 714 | std::string tag = data->readString(msg,tvb,TokenTypeTag, wa_version); 715 | Tree * t = new Tree(); 716 | t->readAttributes(data,lsize,msg,tvb,wa_version); 717 | t->setTag(tag); 718 | 719 | // Look for the phone number 720 | if (tag == "auth") 721 | userphone = t->getAttr("user"); 722 | 723 | if ((lsize & 1) == 1) { 724 | proto_item_set_len(ti,data->curr()-nstart); 725 | return t; 726 | } 727 | if (data->isList()) { 728 | std::vector l = data->readList(msg,tvb,pinfo,this); 729 | t->setChildren(l); 730 | proto_item_set_len(ti,data->curr()-nstart); 731 | return t; 732 | } 733 | int dstart = data->curr(); 734 | t->setData(data->readString(msg,tvb,TokenTypeNValue, wa_version)); 735 | proto_item_set_len(ti,data->curr()-nstart); 736 | 737 | // Check for challenge send 738 | if (tag == "challenge" and not found_auth) { 739 | challenge_data = t->getData(); 740 | COPY_ADDRESS_CC(&server_addr,&pinfo->src); 741 | COPY_ADDRESS_CC(&client_addr,&pinfo->dst); 742 | } 743 | else if (tag == "response") { 744 | if (not found_auth) { 745 | COPY_ADDRESS_CC(&client_mac,&pinfo->dl_src); 746 | challenge_response = t->getData(); 747 | 748 | found_auth = this->tryKeys(); // Try keys and find a good one 749 | unsigned char * in_key = &session_key[20*2]; 750 | unsigned char * out_key = &session_key[20*0]; 751 | in = new RC4Decoder(in_key, 20, 768, true); 752 | out = new RC4Decoder(out_key, 20, 768, true); 753 | } 754 | 755 | // Decode the response data, to train the decoder 756 | DataBuffer * resp; 757 | 758 | unsigned char hmac[4]; 759 | if (blist->find(0) != blist->end()) { 760 | resp = (*blist)[0]; 761 | resp->getHMAC(hmac); 762 | }else{ 763 | bool dataout = addresses_equal(&server_addr,&pinfo->dst); 764 | 765 | unsigned char * skey = dataout ? &session_key[20*1] : &session_key[20*3]; 766 | 767 | DataBuffer * orig = new DataBuffer(t->getData().c_str(),t->getData().size(),wa_version); 768 | KeyGenerator::calc_hmac((unsigned char*)orig->getPtr(),orig->size(),skey,false,hmac,0); 769 | resp = orig->decodedBuffer(out,t->getData().size(),false); 770 | resp->setHMAC(hmac); 771 | 772 | (*blist)[0] = resp; 773 | } 774 | 775 | guint8* decrypted_buffer = (guint8*)malloc(resp->size()); 776 | memcpy(decrypted_buffer,resp->getPtr(),resp->size()); 777 | 778 | tvbuff_t * decoded_tvb = tvb_new_child_real_data(tvb, decrypted_buffer, t->getData().size(), t->getData().size()); 779 | tvb_set_free_cb(decoded_tvb, g_free); 780 | add_new_data_source(pinfo, decoded_tvb, "Decrypted data"); 781 | if (msg != 0) { 782 | ti = proto_tree_add_item(msg,whatsapp_msg_crypted_payload,tvb,dstart,t->getData().size(), ENC_NA); 783 | 784 | msg = proto_item_add_subtree (ti, message_whatsapp); 785 | proto_item *hh=proto_tree_add_item (msg, hf_whatsapp_crypted_hmac_hash,decoded_tvb, 0, 4, ENC_BIG_ENDIAN); 786 | proto_tree_add_item (msg, whatsapp_msg_crypted_message,decoded_tvb, 4, -1, ENC_NA); 787 | 788 | proto_item_append_text(hh, " (calculated: 0x%02x%02x%02x%02x)", 789 | hmac[0],hmac[1],hmac[2],hmac[3]); 790 | 791 | { 792 | proto_item_append_text(hh, " (session key: "); 793 | for (int i = 0; i < 20; i++) { 794 | if (i % 5 == 0) 795 | proto_item_append_text(hh, "%s: ", key_desc[i/5]); 796 | proto_item_append_text(hh, "%02x%02x%02x%02x ", 797 | session_key[i*4+0], session_key[i*4+1], session_key[i*4+2], session_key[i*4+3] 798 | ); 799 | } 800 | } 801 | } 802 | } 803 | 804 | return t; 805 | } 806 | 807 | // Decrypt the challenge response with the session key and search for the challenge data 808 | // If we find a match it's likely that we have the good key 809 | bool DissectSession::check_key() { 810 | if (challenge_response.size() < challenge_data.size()) 811 | return false; 812 | 813 | // Decode the data 814 | std::string decoded = challenge_response.substr(4); 815 | RC4Decoder dec(session_key, 20, 768, true); 816 | dec.cipher((unsigned char*)decoded.c_str(),decoded.size()); 817 | 818 | for (int i = 0; i < decoded.size()-challenge_data.size()+1; i++) { 819 | if (memcmp(challenge_data.c_str(),&decoded[i],challenge_data.size()) == 0) { 820 | return true; 821 | } 822 | } 823 | 824 | return false; 825 | } 826 | 827 | // Try to guess the session key 828 | // We use the 3 IMEIs and the SRC MAC ADDR (iPhone) 829 | bool DissectSession::tryKeys() { 830 | char * pass = NULL; 831 | int i; 832 | for (i = 0; i < wa_userpass_uats_num; i++) { 833 | if (strcmp(wa_userpass_uats[i].username, userphone.c_str()) == 0) { 834 | pass = wa_userpass_uats[i].password; 835 | break; 836 | } 837 | } 838 | 839 | if (wa_version >= 16) { 840 | if (pass) { 841 | KeyGenerator::generateKeyV14(pass, challenge_data.c_str(), challenge_data.size(), (char*)session_key); 842 | return true; 843 | } 844 | } 845 | return false; 846 | } 847 | 848 | int whatsapp_data_dissect_tree(const char * data, int len, proto_tree *tree, tvbuff_t *tvb,packet_info *pinfo) { 849 | conversation_t * conversation = find_or_create_conversation(pinfo); 850 | DissectSession * session = (DissectSession*)conversation_get_proto_data(conversation, proto_whatsapp); 851 | 852 | if (session == NULL) { 853 | session = new DissectSession(data,len,tree,tvb,pinfo); 854 | conversation_add_proto_data(conversation, proto_whatsapp, session); 855 | } 856 | 857 | return session->dissect(data,len,tree,tvb,pinfo); 858 | } 859 | 860 | 861 | // Packet length parser 862 | int whatsapp_data_length(const char * data, int len) { 863 | try { 864 | int aclen = 0; 865 | DataBuffer * d = new DataBuffer(data,len, 14); //FIXME 866 | 867 | // Skip initial header 868 | if ( d->getInt(1,0) == (int)'W' and 869 | d->getInt(1,1) == (int)'A' and 870 | d->getInt(1,2) >= 0 and 871 | d->getInt(1,2) <= 9 and 872 | d->getInt(1,3) >= 0 and 873 | d->getInt(1,3) <= 9 ) { 874 | 875 | d->popData(4); 876 | aclen += 4; 877 | } 878 | 879 | // Consume as many trees as possible 880 | while (d->size() >= 3) { 881 | int flag = d->getInt(1); 882 | int bsize = d->getInt(2,1); 883 | bsize |= ((flag & 0xF) << 16); 884 | aclen += bsize + 3; 885 | if (d->size() < bsize + 3) break; // Not enough data for the next packet 886 | d->popData(bsize+3); 887 | } 888 | 889 | delete d; 890 | return aclen; 891 | }catch (int n) { 892 | return 0; 893 | } 894 | } 895 | 896 | 897 | static const std::string base64_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; 898 | static inline bool is_base64(unsigned char c) { 899 | return (isalnum(c) || (c == '+') || (c == '/')); 900 | } 901 | std::string base64_decode(std::string const& encoded_string) { 902 | int in_len = encoded_string.size(); 903 | int i = 0; 904 | int j = 0; 905 | int in_ = 0; 906 | unsigned char char_array_4[4], char_array_3[3]; 907 | std::string ret; 908 | 909 | while (in_len-- && ( encoded_string[in_] != '=') && is_base64(encoded_string[in_])) { 910 | char_array_4[i++] = encoded_string[in_]; in_++; 911 | if (i ==4) { 912 | for (i = 0; i <4; i++) 913 | char_array_4[i] = base64_chars.find(char_array_4[i]); 914 | 915 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 916 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 917 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 918 | 919 | for (i = 0; (i < 3); i++) 920 | ret += char_array_3[i]; 921 | i = 0; 922 | } 923 | } 924 | 925 | if (i) { 926 | for (j = i; j <4; j++) 927 | char_array_4[j] = 0; 928 | 929 | for (j = 0; j <4; j++) 930 | char_array_4[j] = base64_chars.find(char_array_4[j]); 931 | 932 | char_array_3[0] = (char_array_4[0] << 2) + ((char_array_4[1] & 0x30) >> 4); 933 | char_array_3[1] = ((char_array_4[1] & 0xf) << 4) + ((char_array_4[2] & 0x3c) >> 2); 934 | char_array_3[2] = ((char_array_4[2] & 0x3) << 6) + char_array_4[3]; 935 | 936 | for (j = 0; (j < i - 1); j++) ret += char_array_3[j]; 937 | } 938 | 939 | return ret; 940 | } 941 | 942 | 943 | --------------------------------------------------------------------------------