打密码无论是做题还是研究,使用sagemath都很频繁,虽然单独开一个窗口运行sage的shell不算太麻烦,但是总想整点活,就试着IPython Console跟sage的shell联动一波。
首先从启动方式开始看,研究一下双击电脑上的sagemath都运行了些什么东西才打开的窗口:
实际上,其运行了一段指令:
"C:\Users\10310\AppData\Local\SageMath 9.3\runtime\bin\mintty.exe" -t 'SageMath 9.3 Console' -i sagemath.ico /bin/bash --login -c '/opt/sagemath-9.3/sage'
主要是运行mintty.exe程序来调用bash程序,并在bash里面启用sage。-t是title参数,-i是icon参数。
理论上,pwntools(在windows上是winpwn)可以实现这种交互操作,我们可以直接在IPython Console中开启一个Process,使用interactive函数进行交互即可,但实际情况是,我使用的IDE为Spyder,其IPython Console不支持这样一个函数:sys.stdout.fileno()
,这个函数直接影响了winpwn模块中process.interactive()函数与shell的交互。
也就是说,spyder里面的console对内核io支持的不是很好。起初我认为这是我的认知有限,其有特殊的方法我不知道,于是我去GitHub联系了Spyder的开发者,得到的回复如下:
哈哈 开发者没办法 无语,那我自己看下
里面使用sys.stdin.read()函数进行读入,根据impact27作者的回复,使用input是一样的。我就大胆的改成了input函数,这个问题解决后,又出现了下面的问题:
shell交互数据传输应使用Lattin1编码
发现这个问题,是因为我成功运行了sage,但是得到的输出全都是乱码,我把输出的字节码复制下来,进行了一番比对以及阅读源码,在winpwn/misc.py下找到了这样的代码:
(那里的函数encoding参数是我加上去的,原来的代码中并没有)
IPython Console使用的是utf-8编码,而正常的cmd使用的是gbk编码,都不是Lattin1编码,因此还需要一次编码转化。于是我在winpwn.py中进行了如下调整:
tube类中的interactive()函数,修改如下
def interactive(self):
# it exited, contrl+C, timeout
#showbanner('Interacting',is_noout=False)
go = threading.Event()
go.clear()
def recv_thread():
try:
while not go.is_set():
buf = self.read(0x10000,0.125,interactive=True)
if buf:
out = bytes(buf,"Latin1").decode(self.encoding).rstrip('sage: ')
print(out, end = '')
#showbuf(buf, self.encoding, is_noout=False)
#showbanner('Interacting',is_noout=False)
go.wait(0.2)
except KeyboardInterrupt:
go.set()
print(color('[-]: Exited','red'))
t = threading.Thread(target = recv_thread)
t.daemon = True
t.start()
try:
while not go.is_set():
go.wait(0.2)
try:
if self.is_exit():
time.sleep(0.2) # wait for time to read output
#buf = sys.stdin.readline()
buf = input("sage: ") + '\n'
if buf == 'exit\n' or buf == 'exit()\n':
self.write(buf)
raise KeyboardInterrupt
#buf = input()
if buf:
# sys.stdout.write(buf)
self.write(buf) # remote.write() may cause exception
except Exception as e:
go.set()
print(color('[-]: Exited','red'))
break
except KeyboardInterrupt: # control+C
go.set()
print(color('[-]: Exited','red'))
while t.is_alive():
t.join(timeout = 0.1)
下面的process类加一个encoding参数:
class process(tube):
def __init__(self,argv,cwd=None,flags=None, encoding='gbk'):
tube.__init__(self)
# en/disable PIE, need: pip install pefile
if context.pie is not None:
fpath=""
if not isinstance(argv,list):
fpath=argv
else:
fpath=argv[0]
if context.pie:
PIE(fpath)
else:
NOPIE(fpath)
self.Process=winProcess(argv,cwd,flags)
self.pid=self.Process.pid
self.encoding = encoding
然后外部再写一个封装函数:
from winpwn import process
def sageshell(encoding='utf-8'):
sh = process(["C:/Users/10310/AppData/Local/SageMath 9.3/runtime/bin/bash.exe", "--login", "-c",
"/opt/sagemath-9.3/sage"], encoding=encoding)
sh.interactive()
显然,受winpwn的影响,语法高亮、代码补全还不会做,不过现在已经阶段性进步了。
趁热打铁,我又写了一个runfile函数,只要提供了sage代码的完整路径,就能调用runfile函数在sage中运行该代码,就像spyder中按F5可以直接在console中跑现在写的代码,并且运行后自动会添加__file__
和__dir__
变量,提供当前文件的路径:
本质就是调用sageshell后通过process.send()
发送指令,定义__file__
变量和__dir__
变量,然后调用load函数跑代码。
def run_in_console(filepath, encoding='utf-8'):
real_path = filepath[15:]
real_path = '/home/sage/' + real_path
sh = process(["C:/Users/10310/AppData/Local/SageMath 9.3/runtime/bin/bash.exe", "--login", "-c",
"/opt/sagemath-9.3/sage"], encoding=encoding)
sh.sendline('__file__ = "%s"' % real_path)
sh.sendline('__dir__ = "%s"' % ('/'.join(real_path.split('/')[:-1])))
sh.sendline('load("%s")' % real_path)
sh.interactive()
整挺好。