shramos / polymorph

Polymorph is a real-time network packet manipulation framework with support for almost all existing protocols
GNU General Public License v2.0
445 stars 61 forks source link

Intercept an outgoing Packet #1

Closed Ibonok closed 6 years ago

Ibonok commented 6 years ago

Hi,

Your project is great. I have an question about the intercept function. I would like to interrupt traffic to a server. But if i use the following command no traffic is reach the internet.

intercept -ipt "iptables -I OUTPUT -j NFQUEUE --queue-num 1"

If i use:

intercept -ipt "iptables -I INPUT -j NFQUEUE --queue-num 1"

the intercept function shows me that the paket is change, but wireshark does not shows me the change.

What i do wrong?

Best regards,

Ibonok

shramos commented 6 years ago

Hi Ibonok, Thanks for using the project. With the first iptables rule that you show in your message, Polymorph should intercept the network packets that come out of the machine where Polymorph is running. Instead, with the second rule, you should intercept only the packets that enter the machine where Polymorph is running. This can be tested very easily with ICMP packets (using the ping utility) and the next simple precondition.

def precondition(packet):
    print(packet['IP']['src'])
    return packet

When you use the first rule, it will only show on the screen the address of the machine where Polymorph is installed, if you use the second rule, it will only show the address of the packets that enter the machine.

In order to know what exactly happens in your specific case, would you mind showing me the preconditions, executions and post-conditions that you are using?

Ibonok commented 6 years ago

Hi shramos,

yes that's right i think there is another problem.

intercept -ipt "iptables -I OUTPUT -j NFQUEUE --queue-num 1"

This is my preconditions script:

def new_prec(packet):
    import socket, struct
    try:
        if packet['IP']['proto'] == 1:
            print ('Pre Script')
            hex_ip = packet['IP']['src']
            addr_long = int(hex_ip,16)
            hex(addr_long)
            hex_ip = socket.inet_ntoa(struct.pack(">L", addr_long))
            print (hex_ip)
            return packet
    except:
        return None

this my execution script:

def new_EXEC(packet):
    import binascii
    print ('Exec Script')
    text = 'Hi there'
    bytes_text = bytes(text, 'utf-8')
    packet['RAW']['load'] = binascii.hexlify(bytes_text).decode('utf-8')
    return packet

and this my postcondition script:

def new_post(packet):
    from scapy.all import IP
    pkt = IP(packet.raw)
    if pkt.haslayer('IP') and pkt.haslayer('ICMP'):
        print ('POST Script')
        del pkt['IP'].chksum
        del pkt['IP'].len
        del pkt['ICMP'].chksum
        pkt.show2()
        print (packet['RAW'])
        packet.raw = bytes(pkt)
        return packet

if i use:

intercept -ipt "iptables -I OUTPUT -j NFQUEUE --queue-num 1"

i get this output but the response packet is not reach the ping application: tcpdump shows me the right thing.

10:18:28.156540 IP 192.168.124.42 > 85.214.xxx.xxx: ICMP echo request, id 11292, seq 1, length 18
    0x0000:  4500 0026 31e6 4000 4001 edc4 c0a8 7c2a  E..&1.@.@.....|*
    0x0010:  55d6 8883 0800 2bfb 2c1c 0001 4869 204d  U.....+.,...Hi.t
    0x0020:  6963 6861 656c                           here
10:18:28.190436 IP 85.214.xxx.xxx > 192.168.124.42: ICMP echo reply, id 11292, seq 1, length 18
    0x0000:  4500 0026 14fa 0000 3701 53b1 55d6 8883  E..&....7.S.U...
    0x0010:  c0a8 7c2a 0000 33fb 2c1c 0001 4869 204d  ..|*..3.,...Hi.t
    0x0020:  6963 6861 656c 0000 0000                 there....

but the ping output shows the following:

ping 85.214.xxx.xxx -c 1
PING 85.214.xxx.xxx 56(84) bytes of data.
^C
--- 85.214.xxx.xxx ping statistics ---
1 packets transmitted, 0 received, 100% packet loss, time 0ms
shramos commented 6 years ago

Okay, I'll tell you what I've tried and the results, although it seems that it's not something specific to Polymorph. First, I tested your functions by pinging between two virtual machines in the same local network, and everything seems to work correctly, the ping utility sends and receives the packets without problems. Then I tried to do it with an external service such as www.google.es, and, although the modified packet was sent, no response was received from the server. Apparently, this has some relation to the packet length, since if you add a small padding to your message, the external server responds and ping records the response. I put here the small modification that I made to your execution to receive a response from the external server.

def new_EXEC(packet):
    import binascii
    print ('Exec Script')
    text = 'Hi there00000000000000000000000000'
    bytes_text = bytes(text, 'utf-8')
    packet['RAW']['load'] = binascii.hexlify(bytes_text).decode('utf-8')
    return packet
Ibonok commented 6 years ago

i think this must be an other problem. Ich will test it with a TCP or UDP packet. On my Arch Linux the padding brings no result.

Ibonok commented 6 years ago

I make a simple test with FTP. In the test case i change the USER from test to anonymous. The result is that wireshark shows a successful login but the ftp application hangs. I notice that polymorph loop the execute condition.

Precondition script:

def ftp_pre(packet):
    try:
        if packet['TCP']['dport'] == 21:
            print ('Pre Script')
            #print (packet.raw)
            return packet
        else:
            return None
    except:
        return None

Execute script:

def exec_ftp(packet):
    # your code here
    import re
    from scapy.all import IP
    pkt = IP(packet.raw)
    if re.match('^USER',packet['RAW']['load']): 
        del pkt['IP'].len
        del pkt['IP'].chksum
        del pkt['TCP'].chksum
        load = str(pkt.load, 'utf-8')
        print ('Original load:', load)
        pkt.load = load.replace('test','anonymous')
        print ('Modified load:', pkt.load)
        #pkt.show2()
        packet.raw = bytes(pkt)
        warschon = False
        return packet
    else:
        return None

Postcondition script:

def post_ftp(packet):
    from scapy.all import IP
    pkt = IP(packet.raw)
    if pkt.haslayer('IP') and pkt.haslayer('TCP'):
        print ('POST Script')
        #del pkt['IP'].chksum
        #del pkt['IP'].len
        #pkt.show2()
        #print (packet['RAW'])
        #packet.raw = bytes(pkt)
        return packet

Wireshark:

wire

Polymorph log output:

Pre Script
Pre Script
Pre Script
Pre Script
Original load: USER test

Modified load: b'USER anonymous\r\n'
POST Script
Pre Script
Original load: USER test

Modified load: b'USER anonymous\r\n'
POST Script
Pre Script
Original load: USER test

Modified load: b'USER anonymous\r\n'
POST Script
Pre Script

I think that the exec condition (regex) is correct. What do you mean what the problem is?

shramos commented 6 years ago

That seems to have to do with the length of the network packet in relation to the sequence number and acknowledgment of the TCP/IP session, as you are increasing the packet length, the target machine accepts the packet, but when it responds to that packet, the originating machine realizes that it does not match the expected sequence numbers and assumes that a packet loss has occurred, as a result it constantly forwards the previous packet, therefore it is not Polymorph that is forwarding packets, but rather the machine is forwarding packets and Polymorph is modifying them. I leave here a slice of a presentation that I made some time ago that represents this problem.

captura de pantalla 2018-05-25 a las 13 03 59

I also put here some preconditions, postconditions and executions that show the problem, assuming that a user can login to the remote ftp server with user: anonymous and pass: anonymous.

def newprec(packet):
    try:
        if packet['TCP']['dport'] == 21 and packet['RAW.FTP']['request_command'] == "USER":
            return packet
    except:
        return None
def newexec(packet):
    packet['RAW']['load'] = 'USER anonymous\r\n'
    return packet
def new_post(packet):
    from scapy.all import IP
    pkt = IP(packet.raw)
    if pkt.haslayer('IP') and pkt.haslayer('TCP'):
        del pkt['IP'].len
        del pkt['IP'].chksum
        del pkt['TCP'].chksum
        pkt.show2()
        packet.raw = bytes(pkt)
        return packet

With the following conditional functions we are going to modify in real time the USER field of the ftp packets and we are going to introduce the anonymous value. If when we login we use a user with size less than anonymous, for example testuser, which has a size of 8 characters while anonymous has 9 characters, we will increase the size of the packet and therefore we will leave the sequence numbers of the TCP/IP session inconsistent. The result is that the destination machine accepts the login but when sending the confirmation packet the source machine detects a failure in the sequence number and starts forwarding the previous packet. The ftp application is frozen. If, on the contrary, we introduce a user with the same size as anonymous, such as testuser1, the resulting packet has the same size, even if it has been modified, and therefore, the sequence numbers of the TCP/IP session remain consistent, and the source ftp application accepts the login. If you want to increase the size of the original packet, you must perform a recalculation of the TCP/IP session sequence numbers using the following algorithm and in the following way:

captura de pantalla 2018-05-25 a las 13 10 31

Probably this will be one of the conditional functions that in the next release of Polymorph, will come by default with the framework so that the user does not have to deal with it.

I hope this has solved your doubts.

Ibonok commented 6 years ago

Thank you. Now I understand the problem. I try to fix it but its a little bit complicated :-)

I think it is to fix with:

len(old_payload) - len(modified_payload) = diff_payload
modified_seq = old_seq + len(diff_payload)

I am looking forward to the next version and how you could solve the problem.

shramos commented 6 years ago

Here is the hack for this FTP example :)

Iptables rule:

intercept -ipt "iptables -I OUTPUT -j NFQUEUE --queue-num 1;iptables -I INPUT -j NFQ
UEUE --queue-num 1"

Conditional functions:

def track_tcpip(packet):
    from scapy.all import IP
    # Global vars for tracking the seq numbers
    packet.global_var("sseq", 0)
    packet.global_var("sack", 0)
    packet.global_var("snextseq", 0)
    packet.global_var("dseq", 0)
    packet.global_var("dack", 0)
    packet.global_var("dnextseq", 0)
    packet.global_var("inserted", False)
    try:
        scapy_pkt = IP(packet.get_payload())
        # Saving the seq numbers state for each packet
        if scapy_pkt['TCP'].sport == 21:
            packet.sseq = scapy_pkt['TCP'].seq
            packet.sack = scapy_pkt['TCP'].ack
            packet.snextseq = scapy_pkt['TCP'].seq + scapy_pkt['IP'].len - 52
        elif scapy_pkt['TCP'].dport == 21:
            packet.dseq = scapy_pkt['TCP'].seq
            packet.dack = scapy_pkt['TCP'].ack
            packet.dnextseq = scapy_pkt['TCP'].seq + scapy_pkt['IP'].len - 52
        # TCP/IP session simulation is required
        if packet.inserted:
            if scapy_pkt['TCP'].sport == 21:
                scapy_pkt['TCP'].seq = packet.dack
                scapy_pkt['TCP'].ack = packet.dnextseq
            elif scapy_pkt['TCP'].dport == 21:
                scapy_pkt['TCP'].seq = packet.sack
                scapy_pkt['TCP'].ack = packet.snextseq
            # Recalculating the control fields of the pkt
            del scapy_pkt['IP'].chksum
            del scapy_pkt['TCP'].chksum
            scapy_pkt.show2()
        # Forward the packet
        packet.set_payload(bytes(scapy_pkt))
        return packet
    except:
        return None
def newprec(packet):
    try:
        if packet['TCP']['dport'] == 21 and packet['RAW.FTP']['request_command'] == "USER":
            return packet
    except:
        return None
def newexec(packet):
    if len(packet['RAW.FTP']['request_arg']) < len("anonymous"):
        print("*************************************")
        print("STARTING TCP/IP SESSION RECALCULATION")
        print("*************************************")        
        packet.inserted = True
    from scapy.all import IP
    pkt = IP(packet.get_payload())
    pkt['Raw'].load = 'USER anonymous\r\n'
    del pkt['IP'].len
    del pkt['IP'].chksum
    del pkt['TCP'].chksum
    pkt.show2()
    packet.set_payload(bytes(pkt))
    return packet
Ibonok commented 6 years ago

Wow this was fast. I will try it at monday. Thanks shramos :-)

llqll commented 5 years ago

hello,according to polymorph's whitepaper_english,modify MQTT publish package ,I have also an question about the intercept function. when i use " intercept -ipt "iptables -I INPUT -j NFQUEUE --queue-num 1"" nothing is intercepted,and when stop,I could not connect Internet and moqsuitto_pub could not publish message. I try many times,get same result. ,I do not know why? can you help me?

shramos commented 5 years ago

Hello, Thank you for using the project. First, check that there are not some iptables rules that prevent communication, you can do it through iptables -S. If there are any rules set, delete it with iptables -F. After this, to make the modification of the MQTT protocol with the clients and the broker in the same machine, you can follow the following video where all the commands that must be executed are shown.

https://www.youtube.com/watch?v=o9EWMBzURos

If there is something from the video that you do not understand, I recommend you take a look at the wiki.

If the problem persists, tell me and we try to solve it!

llqll commented 5 years ago

Thank you very much,this problem is solved,but I have the other problem ,when I use "mosquitto_pub -t test -m hello -h 127.0.0.1" publish a message,after intercept,it shows hello twice,I think it might be because the packet between mosquitto_pub and mosquitto broker and the packet between broker and mosquitto_sub were intercepted.But,the result in the video that you give me is not like this. Sorry ,I am a beginner in this field,I have many problem,hope you don't dislike me.