pwntools icon indicating copy to clipboard operation
pwntools copied to clipboard

Listening on UDP needs 2 packets before it returns with recvuntil

Open wotwot563 opened this issue 5 years ago • 4 comments

Thanks for contributing to Pwntools!

I made a DNS packet script that sends dns packets to a DNS server both written with pwntools.

When i called listen with udp it requires 2 packets before it can be captured.

poc code terminal 1:


l = listen(port=53, fam='ipv4',typ='udp')
print(l.recvuntil(b"test")

terminal 2:

r = remote("localhost", 53, typ="udp",fam="ipv4")
r.send("test")
r.send("test")

The first send triggers the got connection and the second returns the recvuntil ran with sudo python3 t1.py DEBUG LOG_FILE=l1.txt sudo python3 t2.py DEBUG LOG_FILE=l2.txt

content of l1.txt

================================================================================
= Started at 2020-10-28T15:31:11                                               =
= sys.argv = [                                                                 =
=   't1.py',                                                                   =
= ]                                                                            =
================================================================================
================================================================================
= Started at 2020-10-28T15:31:11                                               =
= sys.argv = [                                                                 =
=   't1.py',                                                                   =
= ]                                                                            =
================================================================================
2020-10-28T15:31:11:INFO:pwnlib.tubes.listen.listen.140625667422288:Trying to bind to 0.0.0.0 on port 53
2020-10-28T15:31:11:INFO:pwnlib.tubes.listen.listen.140625667422288:Trying to bind to 0.0.0.0 on port 53: Trying 0.0.0.0
2020-10-28T15:31:11:INFO:pwnlib.tubes.listen.listen.140625667422288:Trying to bind to 0.0.0.0 on port 53: Done
================================================================================
= Started at 2020-10-28T15:31:11                                               =
= sys.argv = [                                                                 =
=   't1.py',                                                                   =
= ]                                                                            =
================================================================================
2020-10-28T15:31:11:INFO:pwnlib.tubes.listen.listen.140625667422288:Waiting for connections on 0.0.0.0:53
================================================================================
= Started at 2020-10-28T15:31:11                                               =
= sys.argv = [                                                                 =
=   't1.py',                                                                   =
= ]                                                                            =
================================================================================
2020-10-28T15:31:44:INFO:pwnlib.tubes.listen.listen.140625667422288:Waiting for connections on 0.0.0.0:53: Got connection from 127.0.0.1 on port 55530
2020-10-28T15:31:44:DEBUG:pwnlib.tubes.listen.listen.140625667422288:Received 0x4 bytes:
2020-10-28T15:31:44:DEBUG:pwnlib.tubes.listen.listen.140625667422288:b'test'

Contents of l2.txt

================================================================================
= Started at 2020-10-28T15:31:44                                               =
= sys.argv = [                                                                 =
=   't2.py',                                                                   =
= ]                                                                            =
================================================================================
================================================================================
= Started at 2020-10-28T15:31:44                                               =
= sys.argv = [                                                                 =
=   't2.py',                                                                   =
= ]                                                                            =
================================================================================
2020-10-28T15:31:44:INFO:pwnlib.tubes.remote.remote.139748061849680:Opening connection to localhost on port 53
2020-10-28T15:31:44:INFO:pwnlib.tubes.remote.remote.139748061849680:Opening connection to localhost on port 53: Trying 127.0.0.1
2020-10-28T15:31:44:INFO:pwnlib.tubes.remote.remote.139748061849680:Opening connection to localhost on port 53: Done
2020-10-28T15:31:44:DEBUG:pwnlib.tubes.remote.remote.139748061849680:Sent 0x4 bytes:
2020-10-28T15:31:44:DEBUG:pwnlib.tubes.remote.remote.139748061849680:b'test'
2020-10-28T15:31:44:DEBUG:pwnlib.tubes.remote.remote.139748061849680:Sent 0x4 bytes:
2020-10-28T15:31:44:DEBUG:pwnlib.tubes.remote.remote.139748061849680:b'test'

I have the latest version.

sudo python3 -m pip install --upgrade pwntools
Requirement already up-to-date: pwntools in /usr/local/lib/python3.8/dist-packages (4.2.2)

And it also happens in the docker container.

wotwot563 avatar Oct 28 '20 14:10 wotwot563

Can you try inserting a sleep between the sends? The OS may be buffering something.

Also a pcap might be useful to see what's happening at the network level.

zachriggle avatar Oct 28 '20 15:10 zachriggle

I changed t2.py to:

from pwn import *
import time
r = remote("localhost", 53, typ="udp",fam="ipv4")
r.send("test")
time.sleep(5)
r.send("test")

This is the pcap with the two requests dns_test.pcapng.zip

Still the same result

wotwot563 avatar Oct 28 '20 15:10 wotwot563

I've reproduced this on my side by commenting out one of the sends. It shows that the connection was completed, but it looks like the first packet doesn't make it into the internal buffer for one reason or another.

I don't have any ideas for this, maybe @Arusekk or @heapcrash do

zachriggle avatar Oct 28 '20 15:10 zachriggle

@wotwot563 can you reproduce this with the sockets Python standard library? We don't do anything particularly fancy for UDP port setup that should cause data to not be sent.

What happens if you replace

r = remote("localhost", 53, typ="udp",fam="ipv4")

With

r = udp("localhost", 53)

This is a convenience function that's not documented, but I'm curious if it changes anything.

If you dig into the code of how the various Tubes work, the remote tube (which is the superclass for remote, listen, tcp, udp) implementation of send_raw uses socket.sendall:

https://github.com/Gallopsled/pwntools/blob/153b35ba688aaf754b731989dc4c5619b5a4c016/pwnlib/tubes/sock.py#L62-L74

Maybe there is some way to lower the amount of OS-buffered data? On macOS, this simple script shows that the data is sent and received correctly:

>>> from pwn import *
>>> l = listen(typ='udp')
[x] Trying to bind to :: on port 0
[x] Trying to bind to :: on port 0: Trying ::
[+] Trying to bind to :: on port 0: Done
[x] Waiting for connections on :::62633
>>> r = udp('localhost', l.lport)
[x] Opening connection to localhost on port 62633
[x] Opening connection to localhost on port 62633: Trying ::1
[+] Opening connection to localhost on port 62633: Done
>>> r.send('test')
[+] Waiting for connections on :::62633: Got connection from ::1 on port 61092
>>> l.recv()
b'test'

zachriggle avatar Jan 12 '21 11:01 zachriggle