Closed Kyle-Kyle closed 7 years ago
You can, but you're doing that RCTF challenge wrong.
See tube.shutdown
Excuse me. I guess you misunderstand what I mean. It's supposed to be a PTY problem not a shutdown or stdin.close problem. Actually I have work the challenge out, but not in the way I have mentioned. But I just wonder why it does not work. If you compile the code and run it manually, after it receives EOF, it goes on and wait to receive your next input. I wonder whether I can accomplish it with pwntools. Because if you simply stdin.close, it will not receive any inputs anymore.
I might have misunderstood, there are a large number of IRC requests for a given challenge. In any case, you can send whatever PTY commands you want. You'll need to make stdin a PTY, and configure it. I recommend looking at "man stty" for more information. You want to send EOT, not EOF. In either case, it's not something the binary actually receives, but something handled in the kernel by the PTY emulation layer.
What you're asking for is surprisingly complex for a lot of reasons. To follow up a bit, you need to look at the following resources:
man 1 stty
stty eof=41
)man 4 tty_ioctl
TCGETA
/ TCSETA
which are used to get and set these attributesman 3 termios
c_iflag
, c_oflag
, ... c_cc
)VEOF
and CEOF
termios
tcgetattr
and tcsetattr
wrappers corresponding to the above IOCTLs.process
class
stdin=
and raw=
arguments for creationUltimately, there is an easy way to get what you want, but it has consequences. In particular, all of the standard input space is mapped to TTY control codes. This means that when you send the byte \x04
, the PTY will interpret this as EOF
. This is the same as hitting Ctrl+D with a normal TTY (note that D is the 4th letter in the alphabet).
Let's take a look at a few variants. By default, when using pwntools, stdin is a pipe. All input bytes are sent directly and not touched by any PTY driver. stdout is a raw PTY, which is done because glibc will not buffer output if stdout is a PTY. The raw PTY is done to prevent any post-processing of the data (i.e. we get all bytes that are written, without newline transformations, etc.)
Note that we cannot get the terminal attributes, as stdin isn't a PTY.
>>> p = process('cat')
>>> termios.tcgetattr(p.stdin.fileno())
error: (25, 'Inappropriate ioctl for device')
We can make stdin be a PTY. This doesn't have much effect, as it's still a raw PTY. However, it will make isatty(stdin)
return True, which means that most binaries will perform in interactive mode. As an example, try process('python')
vs process('python', stdin=PTY)
.
If we examine the terminal control flags, note that not many flags are set, as the PTY that pwntools uses is raw by default. This prevents interpretation of input bytes, which is important for 99.999% of exploits.
>>> p = process('cat', stdin=PTY)
>>> pprint(termios.tcgetattr(p.stdin.fileno()))
[0, # iflag == 0
4, # oflag == ONLCR
191, # cflag ...
2608, # lflag == ECHOE | ECHOK | ECHOCTL | ECHOKE
We can make it be a non-RAW pty. Note that ICANON
is set in the lflags, so the terminal behaves canonically (e.g. Ctrl+D would end input, Ctrl+C kills the process, etc.). This is only desired in 0.00001% of exploits that would use pwntools, as it becomes impossible to send many bytes.
>>> p = process('cat', stdin=PTY, raw=False)
>>> pprint(termios.tcgetattr(p.stdin.fileno()))
[1280, # iflag == INLCR | IXON
5, # oflag == OPOST | ONLCR
191, # cflag ...
35387, # lflag == ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHOCTL | ECHOKE | IEXTEN
15,
15,
['\x03', # VINTR == 0 / CINTR == 3 / Ctrl+C
'\x1c',
'\x7f',
'\x15',
'\x04', # VEOF == 4 / CEOF == 4 / Ctrl+D
...
In thie scenario, we can send most standard control codes. For example, sending '\x03'
will terminate the process with SIGINT
. Sending '\x04'
will send EOF
. Sending '\x08'
is a backspace. However, it's now actually impossible to send these bytes to the target process -- they are intercepted by the PTY driver!
So, how do you get to be able to really send EOF? You can use stdin=PTY, raw=False
, as long as you don't actually need '\x08'
or '\x03'
etc. in your exploit. Then you send the appropriate character, which is canonically '\x04'
. You can re-map all of these, but I'm not going to dive into that.
As an added bonus, because it's behaving as a canonical TTY, all input is echoed.
Fine.
int main() {
char buffer[32];
while(1) {
int n = read(0, buffer, sizeof(buffer));
dprintf(1, "XXX"); // <-- To demonstrate echo
dprintf(1, "Got %#x bytes\n", n); // <-- To show return value
if(n > 0)
write(1, buffer, n); // <-- To echo contents
}
}
Here's a basic example. We'll just send "Hello" and then a non-printing character '\x7f'
(chosen by fair dice roll, no bamboozles).
from pwn import *
context.log_level = 'debug'
p = process('./a.out', stdin=PTY, raw=False)
p.sendline("Hello\x7f")
p.clean()
What do you think will be printed? Common sense says something like:
XXXGot 0x7 bytes
Hello
However, what we actually get is different for several reasons. The debug output looks like:
[+] Starting local process './a.out': pid 22089
[DEBUG] Sent 0x7 bytes:
00000000 48 65 6c 6c 6f 7f 0a │Hell│o··│
00000007
[DEBUG] Received 0x22 bytes:
00000000 48 65 6c 6c 6f 08 20 08 0d 0a 58 58 58 47 6f 74 │Hell│o· ·│··XX│XGot│
00000010 20 30 78 35 20 62 79 74 65 73 0d 0a 48 65 6c 6c │ 0x5│ byt│es··│Hell│
00000020 0d 0a │··│
00000022
[*] Stopped process './a.out' (pid 22089)
What the fuck? Why did we get "Hello" twice? Any why are there carriage returns? Why did the first '\x7f'
get turned into '\x08\x20\x08
? Why is the second "Hello" truncated? Why did the process only receive 5 bytes!?!? Welcome to the world of terminals! Here there be dragons. . Read the resources that I linked to at the top, and you'll learn more about terminal emulation than you ever cared about.
The example that you care about is basically this:
import tty
from pwn import *
context.log_level='debug'
p = process('./a.out', stdin=PTY, raw=False)
p.send(chr(tty.CEOF))
p.clean()
p.sendline("Wow!")
p.clean()
Which yields the long-sought-after result (or at least something close)...
[+] Starting local process './a.out': pid 29142
[DEBUG] Sent 0x1 bytes:
'\x04' * 0x1
[DEBUG] Received 0x10 bytes:
'XXXGot 0 bytes\r\n'
[DEBUG] Sent 0x5 bytes:
'Wow!\n'
[DEBUG] Received 0x1e bytes:
'Wow!\r\n'
'XXXGot 0x5 bytes\r\n'
'Wow!\r\n'
[*] Stopped process './a.out' (pid 29142)
Hi zachrihhle, thanks you for your excellent answer. Is there any way to send a real 'EOF' to a remote program ?
And then get shell and interactive with the remote program?
I have the same question as @05lover.
It seems that pwntools can not help sometimes when dealing with a shell binary (need EOF to move on to next level) For example:
To get out of the loop, EOF is needed. And this program behaves as expected when run directly. However, as far as I know what I can do with pwntools is
I tried to use PTY, but it seems not work for me.