pwntools icon indicating copy to clipboard operation
pwntools copied to clipboard

UDP recv works in Python console, does not work when executed as python3 exploit.py

Open Z6543 opened this issue 4 years ago • 5 comments

The following issue was reproducible on the latest Docker image, on macOS and in Kali.

from pwn import *
context.log_level = 'debug'
l = listen(typ='udp')
r = udp('localhost', l.lport)
r.send(b'test')
l.recv(timeout=4)

If this script is started inside python shell, all is good, l.recv receives hello.

Python 3.9.2 (default, Feb 28 2021, 17:03:44) 
[GCC 10.2.1 20210110] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> from pwn import *
>>> context.log_level = 'debug'
>>> 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 :::34099
>>> r = udp('localhost', l.lport)
[x] Opening connection to localhost on port 34099
[x] Opening connection to localhost on port 34099: Trying ::1
[+] Opening connection to localhost on port 34099: Done
>>> r.send(b'test')
[DEBUG] Sent 0x4 bytes:
    b'test'
>>> l.recv(timeout=4)[+] Waiting for connections on :::34099: Got connection from ::1 on port 40984

b'test'
>>> 

But once the code is saved as a python file and run with python3 exploit.py, l.recv is not receiving anything.

[+] Trying to bind to :: on port 0: Done
[+] Waiting for connections on :::55214: Got connection from ::1 on port 35994
[+] Opening connection to localhost on port 55214: Done
[DEBUG] Sent 0x4 bytes:
    b'test'
[*] Closed connection to localhost port 55214
[*] Closed connection to ::1 port 35994

strace of the incorrect run

write(1, "[\33[1m\33[31mDEBUG\33[m] Sent 0x4 byt"..., 39[DEBUG] Sent 0x4 bytes:
) = 39
getpWaiting for connections on :::36419: Got connection from ::1 on port 48863
[+] e(1, "    b'test'\33[K\n", 15    b'test'
)      = 15
poll([{fd=5, events=POLLOUT}], 1, 1048576000) = 1 ([{fd=5, revents=POLLOUT}])
)        = 4
ioctl(1, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
ioctl(1, TCGETS, {B38400 opost isig -icanon -echo ...}) = 0
ioctl(1, SNDCTL_TMR_STOP or TCSETSW, {B38400 opost isig icanon echo ...}) = 0
ioctl(1, TCGETS, {B38400 opost isig icanon echo ...}) = 0
close(5)                                = 0
getpid()                                = 65307
write(1, "\33[?12l\33[?25h\33[?1l\33> \10[\33[1m\33[34m*"..., 82[*] Closed connection to localhost port 36419
) = 82
close(4)                                = 0
getpid()                                = 65307
write(1, "[\33[1m\33[34m*\33[m] Closed connectio"..., 55[*] Closed connection to ::1 port 48863
) = 55
lseek(3, 0, SEEK_CUR)                   = 0
rt_sigaction(SIGINT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, {sa_handler=0x6402c0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, 8) = 0
rt_sigaction(SIGCONT, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, {sa_handler=0x6402c0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, 8) = 0
rt_sigaction(SIGTSTP, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, {sa_handler=0x6402c0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, 8) = 0
rt_sigaction(SIGWINCH, {sa_handler=SIG_DFL, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, {sa_handler=0x6402c0, sa_mask=[], sa_flags=SA_RESTORER, sa_restorer=0x7f26f1931140}, 8) = 0
lseek(3, 0, SEEK_CUR)                   = 0
lseek(3, 0, SEEK_CUR)                   = 0
close(3)                                = 0

Z6543 avatar Oct 05 '21 10:10 Z6543

probably related #1713

Arusekk avatar Oct 07 '21 07:10 Arusekk

Solution: call l.wait_for_connection before l.recv.

The issue is caused by the threaded accepter of listen. To "accept" UDP connection, the accepter calls recvfrom to receive the first message, and unrecv it into the buffer. In pyshell mode, the accepter has time to finish its stuff before l.recv is called, and l.recv picks the message from buffer. Everything works fine. However, when executed as a file, l.recv is called before connection accepted, and tube._recv will find the buffer empty, thus blocks at _fillbuffer->recv_raw->self.sock.recv. Note that accessing self.sock will join the accepter thread (and put the first message to buffer), so self.sock.recv will block until the second message received.

This also explains #1713

One more thing: the first message is not printed when log_level=debug.

tkmikan avatar Feb 15 '22 08:02 tkmikan

Thank you for your answer, this indeed resolved my issue. I think updating the doc with this UDP example https://docs.pwntools.com/en/stable/tubes/sockets.html#pwnlib.tubes.listen.listen will help others in the future Thank you!

cj-zoltan-balazs avatar Feb 15 '22 09:02 cj-zoltan-balazs

@tkmikan would you mind submitting a PR to fix this?

Arusekk avatar Feb 15 '22 09:02 Arusekk

I'm thinking of overriding _recv to ensure the connection before checking the buffer, and the accepter need to log the first msg.

tkmikan avatar Feb 15 '22 16:02 tkmikan