Просмотр исходного кода

Merge pull request #331 from SharonBrizinov/master

Implemented the infrastructure needed to incorporate AFL fuzzer easily
Martin Melik-Merkumians 5 лет назад
Родитель
Сommit
4539fed87b

+ 51 - 0
README.md

@@ -127,6 +127,57 @@ use the GIT version you will need the program Doxygen for generating the HTML
 documentation. You can generate the documentation by invoking doxygen from the 
 command line in the opener main directory.
 
+
+Fuzzing
+--------------
+### Intro
+Fuzzing is an automated testing method that directs varying input data to a program in 
+order to monitor output. It is a way to test for overall reliability as well as identify 
+potential security bugs.
+
+The fuzzer we are using is AFL, a fuzzer that uses runtime guided techniques to create input for the tested program. From a high-level prespective AFL works as follows:
+- Forks the fuzzed process
+- Genereates a new test case based on a predefined input
+- Feeds the fuzzed process with the test case through STDIN
+- Monitors the execution and registers which paths are reachable
+
+![Alt text](fuzz/imgs/fuzz.png "AFL Fuzzing")
+
+### Compile
+To start fuzzing this project with AFL you'll need to compile it with AFL.
+First make sure you have AFL installed:
+```
+sudo apt install build-essential
+wget http://lcamtuf.coredump.cx/afl/releases/afl-latest.tgz
+tar xzf afl-latest.tgz
+cd afl*
+make && sudo make install
+echo "AFL is ready at: $(which afl-fuzz)"
+
+```
+
+Then, compile OpENer with AFL:
+1. Change to the ``OpENer/bin/posix`` directory
+2. Compile OpENer with AFL ``./setup_posix_fuzz_afl.sh`` 
+3. Run ``make``
+
+### Fuzz
+Finally, generate some test cases and start AFL:
+```
+# Generate inputs
+mkdir inputs
+echo 630000000000000000000000000000000000000000000000 | xxd -r -p > ./inputs/enip_req_list_identity
+# You can also use the inputs we prepared from OpENer/fuzz/inputs
+# Finally, let's fuzz!
+afl-fuzz -i inputs -o findings ./src/ports/POSIX/OpENer <interface_name>
+```
+
+### Reproduce a crash
+Usually to reproduce a crash it's enough to retransmit the testcase using ``cat testcase | nc IP_ADDR 44818``
+However, since CIP runs over the EtherNet/IP layer, it must first register a valid session. Therefore, we need to use a dedicated script:
+`python fuzz/scripts/send_testcase.py IP testcase_path`
+
+
 Porting OpENer:
 ---------------
 For porting OpENer to new platforms please see the porting section in the 

+ 1 - 1
bin/posix/setup_posix.sh

@@ -1,2 +1,2 @@
-cmake -DOpENer_PLATFORM:STRING="POSIX" -DCMAKE_BUILD_TYPE:STRING="" -DBUILD_SHARED_LIBS:BOOL=OFF ../../source
+cmake -DCMAKE_C_COMPILER=gcc -DOpENer_PLATFORM:STRING="POSIX" -DCMAKE_BUILD_TYPE:STRING="" -DBUILD_SHARED_LIBS:BOOL=OFF ../../source
 

+ 2 - 0
bin/posix/setup_posix_fuzz_afl.sh

@@ -0,0 +1,2 @@
+cmake -DCMAKE_C_COMPILER=afl-clang-fast -DUSE_FUZZ_AFL=ON -DOpENer_PLATFORM:STRING="POSIX" -DCMAKE_BUILD_TYPE:STRING="" -DBUILD_SHARED_LIBS:BOOL=OFF ../../source
+

BIN
fuzz/imgs/fuzz.png


BIN
fuzz/inputs/cip_req_forward_open


BIN
fuzz/inputs/cip_req_list_identity_cip


BIN
fuzz/inputs/enip_req_list_identity


BIN
fuzz/inputs/enip_req_register_session


+ 38 - 0
fuzz/scripts/send_testcase.py

@@ -0,0 +1,38 @@
+import sys
+import socket
+import struct
+
+if len(sys.argv) != 3:
+	print("python {} IP TESTCASE_PATH".format(sys.argv[0]))
+	sys.exit(1)
+
+HOST_IP = sys.argv[1]
+HOST_PORT = 44818
+TESTCASE_PATH = sys.argv[2]
+
+ENIP_SESSION_CONTEXT = b"\x92\x83J\x0b=\x9e\x0cW"
+ENIP_INIT_SESSION_PACKET = b"e\x00\x04\x00\x00\x00\x00\x00\x00\x00\x00\x00" + ENIP_SESSION_CONTEXT + b"\x00\x00\x00\x00\x01\x00\x00\x00"
+
+
+print("[-] Connecting to {}:{}".format(HOST_IP, HOST_PORT))
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.connect((HOST_IP, HOST_PORT))
+
+print("[-] Init ENIP session")
+s.sendall(ENIP_INIT_SESSION_PACKET)
+enip_session = s.recv(1024)
+session_handle = enip_session[4:8]
+print("[-] Got ENIP Session Handle: {}".format(struct.unpack("<I", session_handle)[0]))
+print("[-] Reading testcase from: '{}'".format(TESTCASE_PATH))
+with open(TESTCASE_PATH, "rb") as f:
+	testcase_data = f.read()
+
+print("[-] Patching sender context and session handle")
+testcase = testcase_data[:4]		# command, len
+testcase += session_handle 		# session handle
+testcase += testcase_data[8:12] 	# status
+testcase += ENIP_SESSION_CONTEXT 	# session context
+testcase += testcase_data[20:]		# options and payload
+print("[-] Sending testcase of {} bytes".format(len(testcase)))
+s.send(testcase)
+s.close()

+ 9 - 0
source/src/enet_encap/encap.c

@@ -749,6 +749,15 @@ EipInt16 CreateEncapsulationStructure(const EipUint8 *receive_buffer,
  */
 SessionStatus CheckRegisteredSessions(
   const EncapsulationData *const receive_data) {
+
+/* Skip the check when fuzzing
+    in order to increase our code coverage 
+    we are simply bypassing all the session checks
+*/
+#ifdef FUZZING_AFL
+  return kSessionStatusValid;
+#endif
+
   if( (0 < receive_data->session_handle) && (receive_data->session_handle <=
                                              OPENER_NUMBER_OF_SUPPORTED_SESSIONS) )
   {

+ 7 - 0
source/src/ports/POSIX/CMakeLists.txt

@@ -12,6 +12,13 @@ if(OpENer_RT)
   add_definitions(-DOPENER_RT_THREAD_SIZE=${OpENer_RT_Additional_Stacksize})
 endif(OpENer_RT)
 
+#######################################
+# AFL Fuzzing                         #
+#######################################
+if(USE_FUZZ_AFL)
+  add_definitions( -DFUZZING_AFL )
+endif(USE_FUZZ_AFL)
+
 #######################################
 # Add common includes                 #
 #######################################

+ 39 - 0
source/src/ports/POSIX/main.c

@@ -6,6 +6,7 @@
 #include <stdio.h>
 #include <stdlib.h>
 #include <signal.h>
+#include <unistd.h>
 #include <sys/capability.h>
 
 #ifdef OPENER_RT
@@ -50,6 +51,12 @@ static void LeaveStack(int signal);
  */
 static void *executeEventLoop(void *pthread_arg);
 
+/******************************************************************************/
+/** @brief Fuzz TCP packets handling flow with AFL.
+ *
+ */
+static void fuzzHandlePacketFlow(void);
+
 /*****************************************************************************/
 /** @brief Flag indicating if the stack should end its execution
  */
@@ -127,6 +134,12 @@ int main(int argc,
    */
   GetHostName(&g_tcpip.hostname);
 
+  /* Fuzzing UDP/TCP handle packet flow */
+#ifdef FUZZING_AFL
+  fuzzHandlePacketFlow();
+  return EXIT_SUCCESS;
+#endif 
+
   /* The CIP objects are now created and initialized with their default values.
    *  After that any NV data values are loaded to change the attribute contents
    *  to the stored configuration.
@@ -277,3 +290,29 @@ static void *executeEventLoop(void *pthread_arg) {
 
   return &pthread_dummy_ret;
 }
+
+static void fuzzHandlePacketFlow(void) {
+#ifdef FUZZING_AFL
+  int socket_fd = 0;   // Fake socket fd
+  uint8_t buff[512];   // Input buffer
+  struct sockaddr_in from_address = { 0 }; // Fake socket address
+  int remaining_bytes = 0; // Fake reamining bytes
+  ENIPMessage outgoing_message;
+
+  /* AFL persistent mode */
+  while(__AFL_LOOP(100000)) {
+    /* Read input from STDIN and enter the handle receive flow */
+    memset(buff, 0, 512);
+    ssize_t received_size = read(STDIN_FILENO, buff, 512);
+    EipUint8 *receive_buffer = &buff[0];
+
+    InitializeENIPMessage(&outgoing_message);
+
+    // Fuzz UDP
+    //EipStatus need_to_send = HandleReceivedExplictUdpData(socket_fd, &from_address, receive_buffer, received_size, &remaining_bytes, true, &outgoing_message);
+
+    // Fuzz TCP
+    EipStatus need_to_send = HandleReceivedExplictTcpData(socket_fd, receive_buffer, received_size, &remaining_bytes, &from_address, &outgoing_message);
+  }
+#endif
+}