-
Notifications
You must be signed in to change notification settings - Fork 1.7k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Handle context.newline
correctly in tube.interactive()
#2129
Conversation
The `context.newline` or `self.newline` variable isn't obeyed when typing in interactive mode. It is used when sending and receiving lines through `tube.sendline` though, causing a mismatch. The receiving end of the `tube.interactive()` already has handling of newlines, but the sending side does not. Example: ```python from pwn import * io = process('cat') io.newline = b'\r\n' io.sendline(b'auto') io.interactive() ``` ``` $ python testinteractive.py DEBUG [x] Starting local process '/usr/bin/cat' argv=[b'cat'] [+] Starting local process '/usr/bin/cat' argv=[b'cat'] : pid 19060 [DEBUG] Sent 0x6 bytes: b'auto\r\n' [*] Switching to interactive mode [DEBUG] Received 0x6 bytes: b'auto\r\n' auto $ test [DEBUG] Sent 0x5 bytes: b'test\n' [DEBUG] Received 0x5 bytes: b'test\n' test ``` The expected outcome would be to send `b'test\r\n'. The same problem arises in non-term mode (`PWNLIB_NOTERM=1`), where stdin is read in binary mode causing the OS line seperators to come through. Correctly replacing them with the `context.newline` setting allows to use the interactive input on windows hosts as well, without constantly sending `\r\n`.
I am not sure what is actually correct here: I believe we should send every byte as we get it regardless of OS when not in TERM mode (for example when the exploit is run non-interactively as In TERM mode though, I agree we should send a So I guess the solution we should pick is to ignore LFs sent directly after CRs? |
I'm not sure how commonly used those termios settings are. I'd expect I agree with not messing with the input when not dealing with a tty. |
Don't mess with the line endings when piping data through.
I don't understand. Do you want to always send CR if there's a CRLF? |
I meant always send LF on LF and LF on CR, but if there is an LF directly after a CR, then ignore it. But if you have a better idea, then I am open to discuss it. |
The point of this PR was to send |
Sure! I almost forgot the whole point, I never used the feature myself, because I really like to have control over the actual bytes in the protocol. Anyway, the next thing I am afraid of is changing newlines on receiving from a tube vs changing newlines on sending to a tube. Term handling is another thing, as I mentioned before. If an interactive tube is launched in TERM mode, then it should probably simply use sendline() every time it gets a logical line (i.e. confirmed by an Enter keystroke). Again, I am not sure what is the correct thing to do here. I will probably merge your changes when you say you are satisfied with them, but I am just pointing out some things I thought of. |
Maybe we can make this a parameter to e.g. I don't want But yeah, |
I think I have the same issue but in another way. |
Sorry for the bit rot, I wasn't aware this was stuck on me! Reading through the messages again I feel like I don't understand some of the concerns since they seem to relate to other parts of the
I expected the input in
I don't know of a good os-agnostic way to detect the Enter key. You can probably do that in the term module for unix somehow, but that's beyond me. The implementation uses I'm confident this won't break existing behavior and is a good enough first step to cover the basics of sending newlines in terminals. If there are problems with a terminal using some of the termios settings you mentioned earlier, those can be adressed with a test case in hand?
We could add
|
Alright, I see it now. But I think this repeated read(1) might cause the thing to lag a bit, e.g. if you want to send some data without a newline; is it helpful in actual interactive mode? I don't know for sure, but I had several situations when I needed to send something confirming it with a Ctrl+D instead of a newline. |
Also, term should add context.newline on an Enter keystroke, not os.linesep nor \n (and maybe we can make it configurable, |
The read(1) is present in the old code - did you experience lag previously? Lines 897 to 901 in 83e3b5b
The new loop would exit on EOF/Ctrl+D as well, since stdin.read(1) would return an empty bytes object.
True, the io.TextIOWrapper has complex normalization of newlines as well. Maybe we can mimic that behavior? Only leaking the raw line endings when NOTERM is set sounds weird to me. I'd expect the
Yes, appending pwntools/pwnlib/term/readline.py Line 407 in 83e3b5b
That's a bigger change than I can confidently reason about though. Should the print(input("no pwntools: "))
from pwn import *
print(input("yes pwntools: "))
print(term.readline.readline(prompt="readline: "))
So maybe we should think about not returning a newline in |
I do not mean a time lag, but a need for every piece to end with a newline. Suppose you type
Even when stdin is a tty. I think we can leave the OS-default way of inputting control characters, like I also think the readline() thing keeping its API consistent with stdlib would be great, so removing the |
Oh! Now I understand, I didn't use ^D to send input without newline before. It doesn't seem to work in term mode either.
|
What do you think about this? Going by your #2129 (comment) + stopping from pwnlib.args import term_mode
try:
os_linesep = os.linesep.encode()
to_skip = b""
while not go.isSet():
if term.term_mode:
data = term.readline.readline(prompt=prompt, float=True)
if data:
data += self.newline
else:
stdin = getattr(sys.stdin, "buffer", sys.stdin)
data = stdin.read(1)
# Keep OS's line separator if NOTERM is set and
# the user did not specify a custom newline
# even if stdin is a tty.
if sys.stdin.isatty() and (
term_mode
or context.newline != b"\n"
or self._newline is not None
):
if to_skip:
if to_skip[:1] != data:
data = os_linesep[: -len(to_skip)] + data
else:
to_skip = to_skip[1:]
if to_skip:
continue
data = self.newline
elif data and os_linesep.startswith(data):
if len(os_linesep) > 1:
to_skip = os_linesep[1:]
continue
data = self.newline
if data:
try:
self.send(data)
except EOFError:
go.set()
self.info("Got EOF while sending in interactive")
else:
go.set()
except KeyboardInterrupt:
self.info("Interrupted")
go.set() |
Great idea! I think I am happy enough with it, go ahead. |
Only replace newlines in a tty if NOTERM wasn't explicitly set or the `newline` setting was set.
The builtin `input()` doesn't include the newline.
92be974
to
dec86db
Compare
…d#2129) * Handle `context.newline` correctly in `tube.interactive()` The `context.newline` or `self.newline` variable isn't obeyed when typing in interactive mode. It is used when sending and receiving lines through `tube.sendline` though, causing a mismatch. The receiving end of the `tube.interactive()` already has handling of newlines, but the sending side does not. Example: ```python from pwn import * io = process('cat') io.newline = b'\r\n' io.sendline(b'auto') io.interactive() ``` ``` $ python testinteractive.py DEBUG [x] Starting local process '/usr/bin/cat' argv=[b'cat'] [+] Starting local process '/usr/bin/cat' argv=[b'cat'] : pid 19060 [DEBUG] Sent 0x6 bytes: b'auto\r\n' [*] Switching to interactive mode [DEBUG] Received 0x6 bytes: b'auto\r\n' auto $ test [DEBUG] Sent 0x5 bytes: b'test\n' [DEBUG] Received 0x5 bytes: b'test\n' test ``` The expected outcome would be to send `b'test\r\n'. The same problem arises in non-term mode (`PWNLIB_NOTERM=1`), where stdin is read in binary mode causing the OS line seperators to come through. Correctly replacing them with the `context.newline` setting allows to use the interactive input on windows hosts as well, without constantly sending `\r\n`. * Update CHANGELOG.md * Only replace newlines in TTYs Don't mess with the line endings when piping data through. * Always send bytes immediately on receive Only replace newlines in a tty if NOTERM wasn't explicitly set or the `newline` setting was set. * Don't return a newline after term.readline lines The builtin `input()` doesn't include the newline. * Remove bogous newline from CHANGELOG
This is a regression from Gallopsled#2129 which was based on a bogus test which mixed `input` and `readline` calls. Only `input` (and `raw_input`) are documented to strip the newline, but `readline` itself should include it. Fixes Gallopsled#2342
The
context.newline
orself.newline
variable isn't obeyed when typing in interactive mode. It is used when sending and receiving lines throughtube.sendline
though, causing a mismatch.The receiving end of the
tube.interactive()
already has handling of newlines, but the sending side does not.Example:
The expected outcome would be to send
b'test\r\n'
.The same problem arises in non-term mode (
PWNLIB_NOTERM=1
), where stdin is read in binary mode causing the OS line seperators to come through. Correctly replacing them with thecontext.newline
setting allows to use the interactive input on windows hosts as well, without constantly sending\r\n
.