C程序和子进程
我编写了这个简单的 c 程序来解释具有相同特征的更困难的问题。
#include <stdio.h>int main(int argc, char *argv[]){ int n; while (1){ scanf("%d", &n); printf("%d", n); } return 0;}</stdio.h>
它按预期工作。
我还编写了一个子进程脚本来与该程序交互:
from subprocess import popen, pipe, stdoutprocess = popen("./a.out", stdin=pipe, stdout=pipe, stderr=stdout)# sending a byteprocess.stdin.write(b'3')process.stdin.flush()# reading the echo of the numberprint(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</module>
如果我更改我的 python 脚本:
from subprocess import popen, pipe, stdoutprocess = 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 numberprint(process.stdout.readline())# sending another num:pipe.write(b"4")pipe.flush()process.stdin.close()
我得到了这个输出:
» python script.pyb'3'Traceback (most recent call last): File "/tmp/script.py", line 13, in <module> pipe.write(b"4")ValueError: write to closed file</module>
这意味着第一个输入已正确发送,并且读取已完成。
我真的找不到解释这种行为的东西;有人可以帮助我理解吗?提前致谢
[编辑]:由于有很多要点需要澄清,所以我添加了此编辑。我正在使用 rop 技术进行缓冲区溢出漏洞利用的培训,并且我正在编写一个 python 脚本来实现这一目标。为了利用这个漏洞,由于aslr,我需要发现libc地址并使程序重新启动而不终止。由于脚本将在目标机器上执行,我不知道哪些库可用,那么我将使用 subprocess,因为它是内置于 python 中的。不详细说明,攻击在第一个 scanf 上发送一系列字节,目的是泄漏 libc 基地址并重新启动程序;然后发送第二个有效负载以获得一个 shell,我将通过它以交互模式进行通信。
这就是为什么:
- 我只能使用内置库
- 我必须发送字节并且无法附加结尾 :我的有效负载将无法对齐或可能导致失败
- 我需要保持 stdin 打开
- 我无法更改 c 代码
正确答案
更改这些:
这对我有用:
#include <stdio.h>int main(int argc, char *argv[]){ int n; while (1){ scanf("%d", &n); printf("%d", n); fflush(stdout); /* i've added this line only. */ } return 0;}</stdio.h>
import subprocessp = 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'); p.stdin.flush() print('d'); print(repr(p.stdout.readline()));finally: print('e'); print(p.kill())
原始 c 程序在终端窗口中交互运行时能够正常工作的原因是,在 c 中,当将换行符 () 写入终端时,输出会自动刷新。因此 printf("%d", n); 最后会隐式执行 fflush(stdout); 。
当使用 subprocess 从 python 运行时,原始 c 程序无法工作的原因是它将输出写入管道(而不是终端),并且没有自动刷新到管道。发生的情况是,python 程序正在等待字节,而 c 程序不会将这些字节写入管道,但它正在等待更多字节(在下一个 scanf 中),因此两个程序都在无限期地等待对方。 (但是,在输出几 kib(通常为 8192 字节)后,将会出现部分自动刷新。但是单个十进制数太短,无法触发该操作。)
如果无法更改 c 程序,那么您应该使用终端设备而不是管道来在 c 和 python 程序之间进行通信。 pty python 模块可以创建终端设备,这对我来说适用于你的原始 c 程序:
import os, pty, subprocessmaster_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'); master.flush() print('b'); print(repr(master.readline())); print('c'); master.write(b'43'); master.flush() print('d'); print(repr(master.readline()));finally: print('e'); print(p.kill())
如果你不想从python发送换行符,这里有一个没有换行符的解决方案,它对我有用:
import os, pty, subprocess, termiosmaster_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 '); master.flush() print('D'); print(repr(master.readline()));finally: print('E'); print(p.kill())