OWASP / pysap

pysap is an open source Python library that provides modules for crafting and sending packets using SAP's NI, Diag, Enqueue, Router, MS, SNC, IGS, RFC and HDB protocols.
https://owasp.org/www-project-core-business-application-security/
GNU General Public License v2.0
220 stars 61 forks source link

Time format broken in SAPRouterInfoClient #12

Closed gelim closed 7 years ago

gelim commented 7 years ago

Hi, while playing with router_admin.py -l I noticed timestamp (connected_on, started_on) where broken showing date like June 2121.

I don't really know what format SAP use for that, but "reversed" a bit those fields to get something plausible with following modifications:

--- a/pysap/SAPRouter.py
+++ b/pysap/SAPRouter.py
@@ -206,7 +206,8 @@ class SAPRouterInfoClient(PacketNoPadded):
         BitField("flag_traced", 0, 1),
         BitField("flag_connected", 0, 1),
         BitField("flag_routed", 0, 1),
-        LongField("connected_on", 0),
+        IntField("XXXX", 0),
+        IntField("connected_on", 0),
         StrNullFixedLenField("address", None, length=45),
         StrNullFixedLenField("partner", None, length=45),
         StrNullFixedLenField("service", None, length=27),
@@ -234,7 +235,8 @@ class SAPRouterInfoServer(PacketNoPadded):
     fields_desc = [
         IntField("pid", 0),
         IntField("ppid", 0),
-        LongField("started_on", 0),
+        IntField("XXX", 0),
+        IntField("started_on", 0),
         ShortField("port", 0),
         ShortField("pport", 0),
     ]

and in router_admin.py code, do some offseting:

--- a/examples/router_admin.py
+++ b/examples/router_admin.py
@@ -39,6 +39,7 @@ bind_layers(SAPNI, SAPRouter, )
 # Set the verbosity to 0
 conf.verb = 0

+TIME_CS = 1000000000 # Hack on time format

 # Command line options parser
 def parse_options():
@@ -242,12 +243,11 @@ def main():
                     flag = "(*)" if client.flag_traced else "(+)" if client.flag_routed else ""

                     fields = [str(client.id),
-                              client.address,
-                              "%s%s" % (flag, client.partner) if client.flag_routed else "(no partner)",
-                              client.service if client.flag_routed else "",
-                              datetime.fromtimestamp(client.connected_on).ctime()]
+                              client.address.replace('\x00', '|'),
+                              "%s%s" % (flag, client.partner.replace('\x00', '|')) if client.flag_routed else "(no partner)",
+                              client.service.replace('\x00', '|') if client.flag_routed else "",
+                              datetime.fromtimestamp(client.connected_on + TIME_CS).ctime()]
                     clients.append("\t".join(fields))
-
                 # Decode the second packet as server info
                 raw_response = conn.recv()
                 raw_response.decode_payload_as(SAPRouterInfoServer)
@@ -255,7 +255,7 @@ def main():
                 print("SAP Network Interface Router running on port %d (PID = %d)\n"
                       "Started on: %s\n"
                       "Parent process: PID = %d, port = %d\n" % (raw_response.port, raw_response.pid,
-                                                                 datetime.fromtimestamp(raw_response.started_on).ctime(),
+                                                                 datetime.fromtimestamp(raw_response.started_on + TIME_CS).ctime(),
                                                                  raw_response.ppid, raw_response.pport))

It's quick and dirty so you got the idea. Adding the offset should be done at the field level, but my scapy skills are a bit rusty.

Cheers,

NB: btw I changed the '\x00' by '|', more easy to read in my opinion

-- Mathieu

gelim commented 7 years ago

Another variant not casting the Long to Int in the dissector could be to apply this transformation at client: sapts being what's returned by the dissector, and return value of lambda func is a more common UNIX timestamp ready to be consumed by datetime.fromtimestamp().

sapts_to_epoch=lambda sapts: (sapts & 0xFFFFFFFF) + 1000000000
martingalloar commented 7 years ago

I found the same weirdeness when started playing with those fields. In the end, figured out that the first 4 bytes were used as some way of time zone, and the remaining 4 bytes were the actual timestamp. Date is somewhat used in the server's local time zone, instead of using UTC, which is not the best decision by SAP.

Will check your changes to see if that covers the different scenarios, we need to check how that works across time zones. The best way to solve this might be maybe creating a new Scapy field that abstracts the implementation.

gelim commented 7 years ago

Actually I have several testcases on different timezone (India, Asia, US) where the first Int is always 0x01. datetime.fromtimestamp is properly printing time in my local timezone. I would bet for the timezone of Walldorf, DE (UTC+1) being hardcoded :-))