我编写了这个简单的 c 程序来解释具有相同特征的更困难的问题。
#include <stdio.h> int main(int argc, char *argv[]) { int n; while (1){ scanf("%d", &n); printf("%d\n", n); } return 0; }
它按预期工作。
我还编写了一个子进程脚本来与该程序交互:
from subprocess import popen, pipe, stdout process = popen("./a.out", stdin=pipe, stdout=pipe, stderr=stdout) # sending a byte process.stdin.write(b'3') process.stdin.flush() # reading the echo of the number print(process.stdout.readline()) process.stdin.close()
问题是,如果我运行 python 脚本,执行会在 readline()
上冻结。事实上,如果我中断脚本,我会得到:
/tmp » python script.py ^ctraceback (most recent call last): file "/tmp/script.py", line 10, in <module> print(process.stdout.readline()) ^^^^^^^^^^^^^^^^^^^^^^^^^ keyboardinterrupt
如果我更改我的 python 脚本:
from subprocess import popen, pipe, stdout process = popen("./a.out", stdin=pipe, stdout=pipe, stderr=stdout) with process.stdin as pipe: pipe.write(b"3") pipe.flush() # reading the echo of the number print(process.stdout.readline()) # sending another num: pipe.write(b"4") pipe.flush() process.stdin.close()
我得到了这个输出:
» python script.py b'3\n' Traceback (most recent call last): File "/tmp/script.py", line 13, in <module> pipe.write(b"4") ValueError: write to closed file
这意味着第一个输入已正确发送,并且读取已完成。
我真的找不到解释这种行为的东西;有人可以帮助我理解吗? 提前致谢
[编辑]:由于有很多要点需要澄清,所以我添加了此编辑。我正在使用 rop
技术进行缓冲区溢出漏洞利用的培训,并且我正在编写一个 python 脚本来实现这一目标。为了利用这个漏洞,由于aslr,我需要发现libc
地址并使程序重新启动而不终止。由于脚本将在目标机器上执行,我不知道哪些库可用,那么我将使用 subprocess,因为它是内置于 python 中的。不详细说明,攻击在第一个 scanf
上发送一系列字节,目的是泄漏 libc
基地址并重新启动程序;然后发送第二个有效负载以获得一个 shell,我将通过它以交互模式进行通信。
这就是为什么:
\n
:我的有效负载将无法对齐或可能导致失败stdin
打开更改这些:
在 c 程序读取的数字之间发送分隔符。 scanf(3) 接受任何非数字字节作为分隔符。为了最简单的缓冲,请从 python 发送换行符(例如 .write(b'42\n')
)。如果没有分隔符,scanf(3) 将无限期地等待更多数字。
每次写入(c 和 python 中)后,刷新输出。
这对我有用:
#include <stdio.h> int main(int argc, char *argv[]) { int n; while (1){ scanf("%d", &n); printf("%d\n", n); fflush(stdout); /* i've added this line only. */ } return 0; }
import subprocess p = subprocess.popen( ('./a.out',), stdin=subprocess.pipe, stdout=subprocess.pipe) try: print('a'); p.stdin.write(b'42 '); p.stdin.flush() print('b'); print(repr(p.stdout.readline())); print('c'); p.stdin.write(b'43\n'); p.stdin.flush() print('d'); print(repr(p.stdout.readline())); finally: print('e'); print(p.kill())
原始 c 程序在终端窗口中交互运行时能够正常工作的原因是,在 c 中,当将换行符 (\n
) 写入终端时,输出会自动刷新。因此 printf("%d\n", n);
最后会隐式执行 fflush(stdout);
。
当使用 subprocess
从 python 运行时,原始 c 程序无法工作的原因是它将输出写入管道(而不是终端),并且没有自动刷新到管道。发生的情况是,python 程序正在等待字节,而 c 程序不会将这些字节写入管道,但它正在等待更多字节(在下一个 scanf
中),因此两个程序都在无限期地等待对方。 (但是,在输出几 kib(通常为 8192 字节)后,将会出现部分自动刷新。但是单个十进制数太短,无法触发该操作。)
如果无法更改 c 程序,那么您应该使用终端设备而不是管道来在 c 和 python 程序之间进行通信。 pty
python 模块可以创建终端设备,这对我来说适用于你的原始 c 程序:
import os, pty, subprocess master_fd, slave_fd = pty.openpty() p = subprocess.popen( ('./a.out',), stdin=slave_fd, stdout=slave_fd, preexec_fn=lambda: os.close(master_fd)) try: os.close(slave_fd) master = os.fdopen(master_fd, 'rb+', buffering=0) print('a'); master.write(b'42\n'); master.flush() print('b'); print(repr(master.readline())); print('c'); master.write(b'43\n'); master.flush() print('d'); print(repr(master.readline())); finally: print('e'); print(p.kill())
如果你不想从python发送换行符,这里有一个没有换行符的解决方案,它对我有用:
import os, pty, subprocess, termios master_fd, slave_fd = pty.openpty() ts = termios.tcgetattr(master_fd) ts[3] &= ~(termios.ICANON | termios.ECHO) termios.tcsetattr(master_fd, termios.TCSANOW, ts) p = subprocess.Popen( ('./a.out',), stdin=slave_fd, stdout=slave_fd, preexec_fn=lambda: os.close(master_fd)) try: os.close(slave_fd) master = os.fdopen(master_fd, 'rb+', buffering=0) print('A'); master.write(b'42 '); master.flush() print('B'); print(repr(master.readline())); print('C'); master.write(b'43\t'); master.flush() print('D'); print(repr(master.readline())); finally: print('E'); print(p.kill())
以上がC プログラムとサブプロセスの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。