Gallopsled / pwntools

CTF framework and exploit development library
http://pwntools.com
Other
11.88k stars 1.69k forks source link

get_terminal_size(0) Error #1724

Closed esp0xdeadbeef closed 3 years ago

esp0xdeadbeef commented 3 years ago

I'm trying to get pwncat with pwntools as wrapper running, while doing that i'm getting an error because pwncat is asking for a screensize.

get_terminal_size(0) doesn't work:

process(['python3', '-c', 'import os; columns, rows = os.get_terminal_size(0)'], shell=False).interactive()# throws error:<snip>
File "<string>", line 1, in <module>
OSError: [Errno 25] Inappropriate ioctl for device

expected output:

process(['python3', '-c', 'import os; columns, rows = os.get_terminal_size(0); print(columns, rows)'], shell=False).recv(timeout=0.1)
132 38 (defined by the terminal size)

this works:

process(['python3', '-c', 'import os; print(os.popen("echo \`tput lines\` \`tput cols\`").read())'], shell=False).recv(timeout=0.1)
132 38 (defined by the terminal size)
Arusekk commented 3 years ago

A process' stdout is connected to a PIPE by default, you can overwrite it with process([...], stdout=PTY) if you want. If this helps, please close the issue. Note that pwntools is currently unable to implicitly handle all the terminal mangling happening inside most curses apps. See #1680 for some example.

esp0xdeadbeef commented 3 years ago

I'm sorry but i cannot figure it out. If i try it with the PTY this happends, so the same (like you said) behaviour as before:

process(['python3', "-c", 'import os; columns, rows = os.get_terminal_size(0); print(columns, rows)'], shell=False, stdout=PTY).interactive()
[x] Starting local process '/usr/bin/python3'
[+] Starting local process '/usr/bin/python3': pid 746238
[*] Switching to interactive mode
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 25] Inappropriate ioctl for device
[*] Got EOF while reading in interactive

If i try it with the PIPE, the program is not displaying any integers in the output plus the error code is 1 instead of 0:

process(['python3', "-c", 'import os; columns, rows = os.get_terminal_size(0); print(columns, rows)'], shell=False, stdout=PIPE).interactive()
[x] Starting local process '/usr/bin/python3'
[+] Starting local process '/usr/bin/python3': pid 783248
[*] Switching to interactive mode
[*] Got EOF while reading in interactive
<pressing enter here>
[*] Process '/usr/bin/python3' stopped with exit code 1 (pid 783248)
[*] Got EOF while sending in interactive

The strange behaviour is that the program displays values while in a subprocess:

>>> process(['python3', '-c', 'import os; print(os.popen("echo $(tput lines) $(tput cols)").read())'], shell=False, stdout=PTY).recv(timeout=0.1)
[x] Starting local process '/usr/bin/python3'
[+] Starting local process '/usr/bin/python3': pid 784289
b'24 80\n\n'
>>> process(['python3', '-c', 'import os; print(os.popen("echo $(tput lines) $(tput cols)").read())'], shell=False, stdout=PIPE).recv(timeout=0.1)
[x] Starting local process '/usr/bin/python3'
[+] Starting local process '/usr/bin/python3': pid 784317
b'19 269\n\n
#ipython3, with the same window size:
!echo $(tput lines) $(tput cols)
19 269

Edit: I've tried to export the COL and LIN env vars but that also doesn't work:

process(['python3', '-c', 'import os;os.environ["COLUMNS"] = "269"; os.environ["LINES"]="900"; print(os.get_terminal_size(0))'], aslr=False, shell=False).interactive()
[x] Starting local process '/opt/pwncat/venv/bin/python3'
[+] Starting local process '/opt/pwncat/venv/bin/python3': pid 788858
[*] Switching to interactive mode
Traceback (most recent call last):
  File "<string>", line 1, in <module>
OSError: [Errno 25] Inappropriate ioctl for device
[*] Got EOF while reading in interactive

while this works:

process(['python3', '-c', 'import os; os.environ["COLUMNS"] = "999"; os.environ["LINES"]="999";print(os.popen("echo `tput lines` `tput cols`").read())'], shell=False).recv(timeout=0.1)
[x] Starting local process '/opt/pwncat/venv/bin/python3'
[+] Starting local process '/opt/pwncat/venv/bin/python3': pid 789081
Out[16]: b'999 999\n\n
Arusekk commented 3 years ago

Or it was stdin? I am sorry, but I was sure this worked somehow.

esp0xdeadbeef commented 3 years ago

stdin also does not work:

process(['python3', '-c', 'import os; os.get_terminal_size(0)'], shell=False, stdin=PIPE).recv(timeout=0.1)
[x] Starting local process '/opt/pwncat/venv/bin/python3'
[+] Starting local process '/opt/pwncat/venv/bin/python3': pid 1251706
b'Traceback (most recent call last):\n  File "<string>", line 1, in <module>\n'
Arusekk commented 3 years ago

Well, works fine on my box:

>>> process(['python3', '-c', 'import os; print(os.get_terminal_size(0))'], shell=False, stdin=PTY).recvall()
[x] Starting local process '/usr/bin/python3'
[+] Starting local process '/usr/bin/python3': pid 16537
[x] Receiving all data
[x] Receiving all data: 0B
[x] Receiving all data: 37B
[+] Receiving all data: Done (37B)
[*] Process '/usr/bin/python3' stopped with exit code 0 (pid 16537)
b'os.terminal_size(columns=0, lines=0)\n'

If you accept this as the correct solution, please close the issue. If not, please open a PR enhancing the documentation.

esp0xdeadbeef commented 3 years ago

Hello Arusekk, Thanks for answering all my questions, but my terminal size is not columns=0, lines=0. It's not spitting out any errors indeed but this isn't the correct terminal size and i think it's not yours as well.

>>> process(['python3', '-c', 'import os;os.environ["COLUMNS"] = "999"; os.environ["LINES"]="999";print(os.get_terminal_size(0))'], shell=False, stdin=PTY).recvall()
[x] Starting local process '/opt/pwncat/venv/bin/python3'
[+] Starting local process '/opt/pwncat/venv/bin/python3': pid 1339833
[x] Receiving all data
[x] Receiving all data: 0B
[x] Receiving all data: 37B
[+] Receiving all data: Done (37B)
[*] Process '/opt/pwncat/venv/bin/python3' stopped with exit code 0 (pid 1339833)
b'os.terminal_size(columns=0, lines=0)\n'

shutil for futher notice: https://github.com/python/cpython/blob/e59b2deffde61e5641cabd65034fa11b4db898ba/Lib/shutil.py#L1316 I've tried enhancing the documentation.

Arusekk commented 3 years ago

Well, then you can set environment variables (as you suggested, but it is also possible through process(..., env={'COLUMNS':...})).

Or you can always actually do that TIOCSWINSZ ioctl just as here (note that you will still probably need to use the PTY trick, but note that os.get_terminal_size accepts a file descriptor as its argument): https://github.com/Gallopsled/pwntools/blob/d466fa90179ab9cafc05af2e24cc62cfb6b60af8/pwnlib/ui.py#L44

esp0xdeadbeef commented 3 years ago

I'm sorry for the delayed reply but i didn't manage to do it how you said it should work.

The file descriptor is indeed readable but i didn't wrote the 3rd party script where it is used.

This is what I've tried and didn't succeeded:

To attatch the debugger i've tried multiple string_cmd_to_exec commands which doesn't work.

These commands are used in an ipython3 interactive shell:

env_ = {}
env_["TERM"] = "xterm-256color"
env_['COLUMNS'] = '90'
env_['LINES'] = '90'
output_path = "/tmp/test_pdb.py"
# string_cmd_to_exec = "python3 -c 'import pdb; pdb.set_trace();import os;print(os.get_terminal_size(0))'"
# string_cmd_to_exec = "python3 -c 'import os;print(os.get_terminal_size(0))'"
string_cmd_to_exec = "python3 -m pdb '" + output_path + "'"
test_file_content = """#!python3
import pdb
pdb.set_trace()
"""
!echo "$test_file_content" > $output_path
!chmod 777  $output_path
!cat $output_path
args_python_script = string_cmd_to_exec.split("'")[1]
array_cmd_to_exec_inc_none = string_cmd_to_exec.split("'")[0].split(' ')
array_cmd_to_exec_inc_none.append("'" + args_python_script + "'")
array_cmd_to_exec = list(filter(None, array_cmd_to_exec_inc_none))
import fcntl
import termios
print(str(env_) + "\n" + string_cmd_to_exec + "\n"+str(array_cmd_to_exec))
p = process(array_cmd_to_exec, env=env_, stdin=PTY, stdout=PTY)
void = fcntl.ioctl(p.stdout.fileno(), termios.TIOCSWINSZ, struct.pack("hh", 80, 80))
p.recvall()
p = process(array_cmd_to_exec, env=env_, stdin=PIPE, stdout=PIPE)
void = fcntl.ioctl(p.stdout.fileno(), termios.TIOCSWINSZ, struct.pack("hh", 80, 80))
p.recvall()
!$string_cmd_to_exec

This results in:

In [13]: env_ = {} 

In [14]: env_["TERM"] = "xterm-256color" 

In [15]: env_['COLUMNS'] = '90' 

In [16]: env_['LINES'] = '90' 

In [17]: output_path = "/tmp/test_pdb.py" 

In [18]: # string_cmd_to_exec = "python3 -c 'import pdb; pdb.set_trace();import os;print(os.get_terminal_size(0))'" 

In [19]: string_cmd_to_exec = "python3 -m pdb '" + output_path + "'" 

In [20]: test_file_content = """#!python3 
 ...: import pdb 
 ...: pdb.set_trace() 
 ...: """ 

In [21]: !echo "$test_file_content" > $output_path 

In [22]: !chmod 777 $output_path 

In [23]: !cat $output_path 
#!python3 
import pdb 
pdb.set_trace() 

In [24]: args_python_script = string_cmd_to_exec.split("'")[1] 

In [25]: array_cmd_to_exec_inc_none = string_cmd_to_exec.split("'")[0].split(' ') 

In [26]: array_cmd_to_exec_inc_none.append("'" + args_python_script + "'") 

In [27]: array_cmd_to_exec = list(filter(None, array_cmd_to_exec_inc_none)) 

In [28]: print(str(env_) + "\n" + string_cmd_to_exec + "\n"+str(array_cmd_to_exec)) 
{'TERM': 'xterm-256color', 'COLUMNS': '90', 'LINES': '90'}
python3 -m pdb '/tmp/test_pdb.py'
['python3', '-m', 'pdb', "'/tmp/test_pdb.py'"]

In [29]: p = process(array_cmd_to_exec, env=env_, stdin=PTY, stdout=PTY) 
[x] Starting local process '/opt/pwncat/venv/bin/python3'
[+] Starting local process '/opt/pwncat/venv/bin/python3': pid 1732669

In [30]: void = fcntl.ioctl(p.stdout.fileno(), termios.TIOCSWINSZ, struct.pack("hh", 80, 80)) 

In [31]: p.recvall() 
[x] Receiving all data
[x] Receiving all data: 0B
[x] Receiving all data: 41B
[+] Receiving all data: Done (41B)
[*] Process '/opt/pwncat/venv/bin/python3' stopped with exit code 1 (pid 1732669)
Out[31]: b"Error: '/tmp/test_pdb.py' does not exist\n"

In [32]: p = process(array_cmd_to_exec, env=env_, stdin=PIPE, stdout=PIPE) 
[x] Starting local process '/opt/pwncat/venv/bin/python3'
[+] Starting local process '/opt/pwncat/venv/bin/python3': pid 1732670

In [33]: void = fcntl.ioctl(p.stdout.fileno(), termios.TIOCSWINSZ, struct.pack("hh", 80, 80)) 
---------------------------------------------------------------------------
OSError Traceback (most recent call last)
<ipython-input-33-236b82a9775f> in <module>
----> 1 void = fcntl.ioctl(p.stdout.fileno(), termios.TIOCSWINSZ, struct.pack("hh", 80, 80))

OSError: [Errno 25] Inappropriate ioctl for device

In [34]: p.recvall() 
[x] Receiving all data
[x] Receiving all data: 0B
[*] Process '/opt/pwncat/venv/bin/python3' stopped with exit code 1 (pid 1732670)
[x] Receiving all data: 41B
[+] Receiving all data: Done (41B)
Out[34]: b"Error: '/tmp/test_pdb.py' does not exist\n"

In [35]: !$string_cmd_to_exec 
> /tmp/test_pdb.py(2)<module>()
-> import pdb
(Pdb) --KeyboardInterrupt--
zachriggle commented 3 years ago

Try running your script with PWNLIB_NOTERM=1 in your environment, or set it at runtime:

$ python exploit.py NOTERM=1

Or directly from you Python script

import os
os.environ['PWNLIB_NOTERM'] = '1'
from pwn import *

If any of these work for you, I would recommend creating a pull request that does this automatically in pwnlib.term. We currently have this workaround happen automatically for IPython, bypthon, and dreampie.

https://github.com/Gallopsled/pwntools/blob/153b35ba688aaf754b731989dc4c5619b5a4c016/pwnlib/term/__init__.py#L36-L57

zachriggle commented 3 years ago

My mistake, I didn't mean to click "Close with comment". Re-opened and edited my previous message.

esp0xdeadbeef commented 3 years ago

sorry i'm doing my oscp now, i didn't check the web ui in a while, i'll close it for now i'm sorry that i didn't react earlier.