Sharon Brizinov 5 лет назад
Родитель
Сommit
c6c4a749f8

+ 14 - 4
README.md

@@ -130,6 +130,7 @@ command line in the opener main directory.
 
 
 Fuzzing
 Fuzzing
 --------------
 --------------
+#### Intro
 Fuzzing is an automated testing method that directs varying input data to a program in 
 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 
 order to monitor output. It is a way to test for overall reliability as well as identify 
 potential security bugs.
 potential security bugs.
@@ -140,6 +141,9 @@ The fuzzer we are using is AFL, a fuzzer that uses runtime guided techniques to
 - Feeds the fuzzed process with the test case through STDIN
 - Feeds the fuzzed process with the test case through STDIN
 - Monitors the execution and registers which paths are reachable
 - 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.
 To start fuzzing this project with AFL you'll need to compile it with AFL.
 First make sure you have AFL installed:
 First make sure you have AFL installed:
 ```
 ```
@@ -157,16 +161,22 @@ Then, compile OpENer with AFL:
 2. Compile OpENer with AFL ``./setup_posix_fuzz_afl.sh`` 
 2. Compile OpENer with AFL ``./setup_posix_fuzz_afl.sh`` 
 3. Run ``make``
 3. Run ``make``
 
 
+#### Fuzz
 Finally, generate some test cases and start AFL:
 Finally, generate some test cases and start AFL:
 ```
 ```
 # Generate inputs
 # Generate inputs
 mkdir inputs
 mkdir inputs
-echo 630000000000000000000000000000000000000000000000 | xxd -r -p > ./inputs/req_list_identity
-# You can also use the inputs we prepared from ``fuzz/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!
 # Finally, let's fuzz!
-afl-fuzz -i inputs -o findings ./src/ports/POSIX/OpENer eth1
+afl-fuzz -i inputs -o findings ./src/ports/POSIX/OpENer <interface_name>
 ```
 ```
-![Alt text](fuzz/fuzz.png "AFL Fuzzing")
+
+#### 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:
+`python2 fuzz/scripts/send_testcase.py IP testcase_path`
+
 
 
 Porting OpENer:
 Porting OpENer:
 ---------------
 ---------------

+ 0 - 0
fuzz/fuzz.png → 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()